Files

596 lines
29 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
import argparse
import sqlite3
import pandas as pd
from datetime import datetime
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from modules.web_api.llm.db.db_chat import db_clear_all_chat_context
from core.database import dump_database_to_excel
from core.repositories.scud_repo import get_building_presence
from config import DATA_DIR, DATE_TODAY, OUTPUT_DIR, EXCEPTIONS_PATH, normalize_fio
from core.database import (
get_connection,
get_available_snapshots,
get_all_rules_from_db,
load_scud_from_db_by_snapshot,
get_latest_snapshot_time
)
from services.exceptions_repo import (
get_all_exceptions_from_db,
add_exception_to_db,
remove_exception_from_db,
sync_json_to_db
)
DB_PATH = os.path.join(DATA_DIR, "scud_orion_ai.db")
def cmd_mapping(args_list):
"""Управление подтвержденными сопоставлениями ФИО (СКУД <-> 1С:ЗУП)."""
with get_connection() as conn:
cursor = conn.cursor()
if not args_list or args_list[0] in ["list", "show"]:
cursor.execute("SELECT id, scud_fio, zup_fio, match_source, status FROM person_identity_mapping ORDER BY id DESC")
rows = cursor.fetchall()
print("\n🔗 СОХРАНЕННЫЕ СОПОСТАВЛЕНИЯ ФИО (person_identity_mapping):")
print("=" * 80)
if rows:
for r in rows:
print(f" #{r[0]} [{r[4]}] СКУД: '{r[1]}' ⟷ 1С: '{r[2]}' ({r[3]})")
else:
print(" — сопоставлений пока нет")
print("=" * 80 + "\n")
return
subcmd = args_list[0]
if subcmd == "add":
if len(args_list) < 3:
print("Использование: python scripts/db_cli.py mapping add 'ФИО в СКУД' 'ФИО в 1С'")
return
scud_f, zup_f = args_list[1], args_list[2]
cursor.execute("""
INSERT OR REPLACE INTO person_identity_mapping (scud_fio, zup_fio, match_source, status)
VALUES (?, ?, 'MANUAL', 'ACTIVE')
""", (scud_f.strip(), zup_f.strip()))
conn.commit()
print(f"✅ Успешно добавлена связка: '{scud_f}' ⟷ '{zup_f}'")
elif subcmd in ["del", "delete", "remove"]:
if len(args_list) < 2:
print("Использование: python scripts/db_cli.py mapping del 'ФИО в СКУД'")
return
cursor.execute("DELETE FROM person_identity_mapping WHERE scud_fio = ?", (args_list[1].strip(),))
conn.commit()
print(f"✅ Связка для '{args_list[1]}' удалена.")
def print_tool_actions():
"""Выводит реестр декларативных действий инструментов и шаблоны кнопок."""
print("\n" + "=" * 110)
print("🧰 ДЕКЛАРАТИВНЫЙ РЕЕСТР ДЕЙСТВИЙ ИНСТРУМЕНТОВ (tool_action_registry):")
print("=" * 110)
with get_connection() as conn:
cursor = conn.cursor()
try:
cursor.execute("""
SELECT id, tool_name, category, bypass_llm, success_template, follow_up_question, buttons_json
FROM tool_action_registry
WHERE is_active = 1
ORDER BY id ASC
""")
rows = cursor.fetchall()
if not rows:
print("Таблица tool_action_registry пуста.")
else:
for r in rows:
print(f"ID: {r[0]} | Tool: [{r[1]}] | Категория: {r[2]} | Bypass LLM: {'ДА (0.05с)' if r[3] else 'НЕТ'}")
print(f" • Сообщение: {r[4]}")
if r[5]:
print(f" • Вопрос: {r[5]}")
print(f" • Кнопки: {r[6]}")
print("-" * 110)
except Exception as e:
print(f"Таблица tool_action_registry недоступна: {e}")
print("=" * 110 + "\n")
def print_stats():
"""Выводит общую статистику по записям в таблицах БД."""
print("\n" + "=" * 60)
print("📊 СТАТИСТИКА БАЗЫ ДАННЫХ SQLITE (scud_orion_ai.db):")
print("=" * 60)
with get_connection() as conn:
cursor = conn.cursor()
tables = [
'scud_logs', 'scud_events_raw', 'zup_staff', 'zup_absences', 'anomalies_history',
'ai_knowledge_base', 'chat_messages', 'session_states',
'system_prompt_nodes', 'tasks', 'exceptions_registry'
]
for t in tables:
try:
cursor.execute(f"SELECT COUNT(*) FROM {t}")
cnt = cursor.fetchone()[0]
print(f" • Таблица [{t:<22}]: {cnt:>6} записей")
except Exception:
pass
print("=" * 60 + "\n")
def print_snapshots_list(date_str=None):
"""Выводит реестр снапшотов с отображением даты и точного времени среза."""
rows = get_available_snapshots(date_str)
print("\n" + "=" * 105)
print(f"📸 РЕЕСТР СОХРАНЕННЫХ СНАПШОТОВ (СВЕРХУ СВЕЖИЕ) {'ЗА ЛОГИ ' + date_str if date_str else ''}:")
print("=" * 105)
header = f"{'ID снапшота':<16} | {'Дата снапшота (создания)':<24} | {'Дата и время среза':<20} | {'Записей':<8}"
print(header)
print("-" * 105)
if not rows:
print("Снапшотов пока нет.")
print("=" * 105 + "\n")
return
def snapshot_sort_key(row):
snap_id = row[0] or ""
snap_time = row[2] or ""
seq_num = 0
if "-" in snap_id:
parts = snap_id.replace("Y", "").split("-")
if len(parts) > 1 and parts[1].isdigit():
seq_num = int(parts[1])
return (snap_time, seq_num)
sorted_rows = sorted(rows, key=snapshot_sort_key, reverse=True)
for r in sorted_rows:
snap_id = r[0] if r[0] else '----------'
log_date = r[1] if r[1] else '—'
snap_time = r[2] if r[2] else '—'
count = r[3]
time_part = "—"
if snap_time and " " in snap_time:
time_part = snap_time.split(" ")[1]
slice_datetime_str = f"{log_date} {time_part}" if time_part != "—" else log_date
formatted_snap_id = f" {snap_id}" if not snap_id.startswith("Y") else snap_id
print(f"{formatted_snap_id:<16} | {snap_time:<24} | {slice_datetime_str:<20} | {count:<8}")
print("=" * 105 + "\n")
def delete_snapshot_by_id(snapshot_id: str):
"""Удаляет конкретный снапшот из таблицы scud_logs по его ID."""
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM scud_logs WHERE snapshot_id = ?", (snapshot_id,))
deleted_count = cursor.rowcount
conn.commit()
print(f"\n[✓] Успешно удален снапшот [{snapshot_id}]. Удалено строк: {deleted_count}\n")
return deleted_count
def delete_snapshots_by_date(date_str: str):
"""Удаляет все снапшоты за указанную дату (например, '04.08.2026')."""
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM scud_logs WHERE log_date = ? OR snapshot_id LIKE ?", (date_str, f"%{date_str.replace('.', '')}%"))
deleted_count = cursor.rowcount
conn.commit()
print(f"\n[✓] Успешно удалены все снапшоты за дату [{date_str}]. Удалено строк: {deleted_count}\n")
return deleted_count
def inspect_scud(snapshot_id=None, date_str=None, export_xlsx=None):
"""Инспектирует логи СКУД за выбранный снапшот или дату и опционально сохраняет XLSX."""
target_date = date_str if date_str else DATE_TODAY
print("\n" + "=" * 115)
if snapshot_id:
print(f"🔍 ИНСПЕКЦИЯ СКУД ПО СНАПШОТУ [{snapshot_id}] (Дата среза: {target_date}):")
else:
print(f"🔍 ИНСПЕКЦИЯ СКУД ЗА ТЕКУЩУЮ ДАТУ [{target_date}] (ПОСЛЕДНИЙ СРЕЗ):")
print("=" * 115)
df = load_scud_from_db_by_snapshot(target_date, snapshot_param=snapshot_id)
if df is None or df.empty:
print("Записи СКУД не найдены.")
print("=" * 115 + "\n")
return
fio_col = next((c for c in ['Сотрудник', 'fio', 'fio_clean'] if c in df.columns), None)
dept_col = next((c for c in ['Подразделение', 'department_scud', 'department'] if c in df.columns), None)
pos_col = next((c for c in ['Должность', 'position'] if c in df.columns), None)
in_col = next((c for c in ['Начало_дня', 'time_in'] if c in df.columns), None)
first_act_col = next((c for c in ['Первая_активность', 'first_activity'] if c in df.columns), None)
out_col = next((c for c in ['Конец_дня', 'time_out'] if c in df.columns), None)
dur_col = next((c for c in ['Находился_в_здании', 'time_in_building', 'duration'] if c in df.columns), None)
present_col = next((c for c in ['Пришел', 'is_present'] if c in df.columns), None)
anom_col = 'anomaly_flag' if 'anomaly_flag' in df.columns else None
snap_col = 'snapshot_id' if 'snapshot_id' in df.columns else None
total = len(df)
if present_col:
present_cnt = len(df[df[present_col].astype(str).str.lower().isin(['true', '1'])])
else:
present_cnt = 0
absent_cnt = total - present_cnt
print(f"Всего записей: {total} | Присутствовали: {present_cnt} | Отсутствовали: {absent_cnt}")
print("-" * 115)
display_cols = [c for c in [fio_col, dept_col, in_col, first_act_col, out_col, dur_col, present_col, anom_col, snap_col] if c]
print(df[display_cols].head(30).to_string(index=False))
if len(df) > 30:
print(f"\n... и ещё {len(df) - 30} строк.")
if export_xlsx:
out_path = export_xlsx if export_xlsx.endswith('.xlsx') else f"{export_xlsx}.xlsx"
if not os.path.isabs(out_path):
out_path = os.path.join(OUTPUT_DIR, out_path)
df.to_excel(out_path, index=False)
print("\n" + "*" * 115)
print(f"[✓] УСПЕШНЫЙ ЭКСПОРТ ДЕБАГ-ФАЙЛА В EXCEL: {out_path}")
print("*" * 115)
print("=" * 115 + "\n")
def print_absences(date_str=None):
"""Выводит список официально отсутствующих сотрудников из 1С:ЗУП за выбранный день."""
target_date = date_str if date_str else DATE_TODAY
print("\n" + "=" * 90)
print(f"📋 ОФИЦИАЛЬНЫЕ ОТСУТСТВИЯ ИЗ 1С:ЗУП ЗА ДАТУ [{target_date}]:")
print("=" * 90)
with get_connection() as conn:
df = pd.read_sql_query(
"SELECT fio as 'ФИО', absence_type as 'Причина отсутствия 1С' FROM zup_absences WHERE absence_date = ? ORDER BY absence_type, fio",
conn,
params=(target_date,)
)
if df.empty:
print(f"Записи об отсутствиях 1С за {target_date} в базе не найдены.")
else:
print(f"Всего зафиксировано документов 1С: {len(df)}")
print("-" * 90)
print(df.to_string(index=False))
print("=" * 90 + "\n")
def print_anomalies():
"""Выводит список аномалий СКУД из БД."""
print("\n" + "=" * 80)
print("🚨 ИСТОРИЯ НАЙДЕННЫХ АНОМАЛИЙ СКУД ⟷ 1С:")
print("=" * 80)
with get_connection() as conn:
df = pd.read_sql_query("SELECT anomaly_date, fio, anomaly_type, details FROM anomalies_history ORDER BY id DESC LIMIT 50", conn)
if df.empty:
print("Аномалии не найдены.")
else:
print(df.to_string(index=False))
print("=" * 80 + "\n")
def print_rules():
"""Выводит правила базы знаний ИИ."""
rules = get_all_rules_from_db()
print("\n" + "=" * 80)
print("🧠 ПРАВИЛА БАЗЫ ЗНАНИЙ ИИ:")
print("=" * 80)
if not rules:
print("База знаний пуста.")
else:
for idx, r in enumerate(rules, 1):
print(f" {idx}. {r}")
print("=" * 80 + "\n")
def dump_all_to_excel(out_filename="db_dump_full.xlsx"):
"""Дампит всю базу SQLite во многостраничный Excel."""
print(f"\n[🔄] Создание полного дампа БД в файл: {out_filename} ...")
out_path = dump_database_to_excel(out_filename)
print(f"[✓] Дамп успешно сохранен: {out_path}\n")
def print_system_prompts():
"""Выводит все системные промпты из базы данных."""
print("\n" + "=" * 80)
print("📝 СИСТЕМНЫЕ ПРОМПТЫ (system_prompt_nodes):")
print("=" * 80)
with get_connection() as conn:
cursor = conn.cursor()
try:
cursor.execute("SELECT section_id, item_id, content, is_active, updated_at FROM system_prompt_nodes ORDER BY section_id ASC, item_id ASC")
rows = cursor.fetchall()
except Exception:
rows = []
if not rows:
print("Таблица system_prompt_nodes пуста.")
else:
for r in rows:
print(f"Раздел {r[0]}.{r[1]} | Active: {r[3]} | Updated: {r[4]}")
print(f" {r[2]}")
print("-" * 80)
print("=" * 80 + "\n")
def print_session_states():
"""Выводит текущие активные сессии и превью (session_states)."""
print("\n" + "=" * 80)
print("🔄 АКТИВНЫЕ СЕССИИ И ПРЕВЬЮ (session_states):")
print("=" * 80)
with get_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT session_id, state_type, updated_at, pending_data FROM session_states")
rows = cursor.fetchall()
if not rows:
print("Таблица session_states пуста (нет активных превью).")
else:
for r in rows:
print(f"Session: {r[0]} | Type: {r[1]} | Updated: {r[2]}")
print("-" * 80)
print(f"Pending Data:\n{r[3]}\n")
print("=" * 80 + "\n")
def print_chat_messages(session_id=None, limit=50):
"""Выводит таблицу истории сообщений чата (контекст)."""
print("\n" + "=" * 105)
print(f"💬 ИСТОРИЯ СООБЩЕНИЙ ЧАТА (chat_messages) {f'для сессии: {session_id}' if session_id else 'все сессии'}:")
print("=" * 105)
with get_connection() as conn:
cursor = conn.cursor()
if session_id:
cursor.execute("""
SELECT id, session_id, role, content, is_ephemeral, created_at
FROM chat_messages
WHERE session_id = ?
ORDER BY id ASC
LIMIT ?
""", (session_id, limit))
else:
cursor.execute("""
SELECT id, session_id, role, content, is_ephemeral, created_at
FROM chat_messages
ORDER BY id DESC
LIMIT ?
""", (limit,))
rows = cursor.fetchall()
if not rows:
print("Таблица chat_messages пуста.")
else:
if not session_id:
rows = list(reversed(rows))
for r in rows:
msg_id, sess, role, content, ephemeral, created = r
eph_marker = " [ЭФЕМЕРНОЕ]" if ephemeral else ""
print(f"[{msg_id}] {created} | Сессия: {sess} | Роль: {role.upper()}{eph_marker}")
print("-" * 105)
content_preview = content if content else ""
print(f"{content_preview}")
print("=" * 105)
print("\n")
def purge_chat_context(session_id=None, purge_all=False):
"""Очистка контекста сообщений через репозиторий чата."""
deleted_msgs = db_clear_all_chat_context(session_id=session_id, purge_all=purge_all)
mode = "Полная" if purge_all else "Умная"
print(f"\n[✓] {mode} зачистка контекста выполнена! Удалено сообщений: {deleted_msgs}\n")
def print_exceptions():
"""Выводит реестр исключений и белый список сотрудников из базы SQLite."""
exc = get_all_exceptions_from_db()
print("\n" + "=" * 80)
print("📋 РЕЕСТР ИСКЛЮЧЕНИЙ И БЕЛЫЙ СПИСОК (exceptions_registry):")
print("=" * 80)
for cat, items in exc.items():
print(f"[{cat.upper()}] ({len(items)} шт.):")
if items:
for it in items:
print(f" • {it}")
else:
print(" — пусто")
print("-" * 80)
print("=" * 80 + "\n")
def print_building_presence(date_str: str, all_statuses: bool = False):
"""Выводит оперативный список сотрудников, находящихся в здании."""
records = get_building_presence(date_str, only_inside=not all_statuses)
title = f"КТО СЕЙЧАС В ЗДАНИИ [{date_str}]" if not all_statuses else f"ОПЕРАТИВНЫЙ СТАТУС СОТРУДНИКОВ [{date_str}]"
print("\n" + "=" * 80)
print(f"🏢 {title} (Всего: {len(records)})")
print("=" * 80)
if not records:
print(" Нет данных о проходах за указанную дату.")
else:
print(f"{'ФИО':<35} | {'Подразделение':<15} | {'Время':<10} | {'Статус':<8}")
print("-" * 80)
for r in records:
time_short = r['last_event_time'].split()[-1][:8] if ' ' in r['last_event_time'] else r['last_event_time'][:8]
print(f"{r['fio']:<35} | {r['department'][:15]:<15} | {time_short:<10} | {r['status']:<8}")
print("=" * 80 + "\n")
HELP_TEXT = """
CLI-утилита инспекции и управления SQLite базой данных СКУД (scud_orion_ai.db)
ДОСТУПНЫЕ КОМАНДЫ:
stats -- Общая статистика строк по всем таблицам БД
snapshots [ДД.ММ.ГГГГ] -- Посмотреть реестр снапшотов (опционально за конкретную дату)
scud [ДД.ММ.ГГГГ] [--snapshot ID] [--export-xlsx NAME] -- Инспекция логов СКУД по дате/снапшоту и экспорт в Excel
in_building [ДД.ММ.ГГГГ] [--all] -- Оперативный статус: кто сейчас в здании (или все статусы с флагом --all)
absences [ДД.ММ.ГГГГ] -- Посмотреть список официально отсутствующих из 1С:ЗУП
anomalies -- Посмотреть историю найденных аномалий СКУД ⟷ 1С
rules -- Посмотреть правила Базы Знаний ИИ из SQLite
prompts -- Посмотреть узлы системного промпта (system_prompt_nodes)
tools -- Посмотреть реестр действий инструментов и кнопок (tool_action_registry)
sessions -- Посмотреть активные сессии и превью (session_states)
context [session_id] [--limit N] -- Посмотреть таблицу контекста сообщений чата (chat_messages)
context purge [session_id] [--all] -- Очистить контекст (умная зачистка или полная с флагом --all)
dump [output.xlsx] -- Полный дамп всех таблиц БД в многостраничный Excel
snapshot del [ID] или [--day ДД.ММ.ГГГГ] -- Удаление снапшота по ID или всех за выбранный день
exceptions [list] -- Посмотреть реестр исключений и белый список (SQLite)
exceptions add -c CATEGORY -v VALUE [-m COMMENT] -- Добавить исключение (fio, include_fio, departments, positions, position_keywords)
exceptions del -c CATEGORY -v VALUE -- Удалить исключение из БД
exceptions sync -- Синхронизировать exceptions.json -> SQLite
ПРИМЕРЫ ЗАПУСКА:
python scripts/db_cli.py stats
python scripts/db_cli.py snapshots 06.08.2026
python scripts/db_cli.py scud 06.08.2026 --export-xlsx срез_четверг
python scripts/db_cli.py in_building 07.09.2026
python scripts/db_cli.py in_building 07.09.2026 --all
python scripts/db_cli.py absences 07.08.2026
python scripts/db_cli.py prompts
python scripts/db_cli.py tools
python scripts/db_cli.py sessions
python scripts/db_cli.py context web_session_main --limit 20
python scripts/db_cli.py context purge
python scripts/db_cli.py context purge --all
python scripts/db_cli.py context purge web_session_main --all
python scripts/db_cli.py snapshot del Y20260805-007
python scripts/db_cli.py dump my_dump.xlsx
python scripts/db_cli.py exceptions
python scripts/db_cli.py exceptions add -c include_fio -v "Тарасенко Александр Александрович"
python scripts/db_cli.py exceptions add -c fio -v "Михалев Сергей Геннадьевич" -m "Уборщик"
python scripts/db_cli.py exceptions del -c fio -v "Михалев Сергей Геннадьевич"
python scripts/db_cli.py exceptions sync
"""
def main():
if any(arg in sys.argv for arg in ['-h', '--help']):
print(HELP_TEXT)
sys.exit(0)
parser = argparse.ArgumentParser(
description=HELP_TEXT,
formatter_class=argparse.RawDescriptionHelpFormatter,
add_help=False
)
parser.add_argument('command', nargs='?', default=None, choices=[
'stats', 'snapshots', 'scud', 'absences', 'anomalies',
'rules', 'prompts', 'sessions', 'dump', 'snapshot',
'tools', 'context', 'in_building', 'exceptions'
], help="Основная команда")
parser.add_argument('action', nargs='?', default=None, help="Действие ('del', 'purge', 'add', 'sync') или дата/сессия")
parser.add_argument('param', nargs='?', default=None, help="Параметр (дата, ID снапшота, session_id, имя файла)")
parser.add_argument('-c', '--category', type=str, default=None, choices=[
'departments', 'positions', 'fio', 'position_keywords',
'include_fio', 'turnstile_fio', 'turnstile_departments',
'fligel_fio', 'fligel_departments'
], help="Категория исключения")
parser.add_argument('-v', '--value', type=str, default=None, help="Значение исключения (ФИО, отдел, должность)")
parser.add_argument('-m', '--comment', type=str, default="", help="Комментарий к исключению")
parser.add_argument('--snapshot', type=str, default=None, help="ID конкретного снапшота для инспекции")
parser.add_argument('--export-xlsx', type=str, default=None, help="Экспорт среза СКУД в Excel-файл")
parser.add_argument('--day', type=str, default=None, help="Удалить снапшоты за конкретный день (ДД.ММ.ГГГГ)")
parser.add_argument('--limit', type=int, default=50, help="Лимит выводимых сообщений чата (для команды context)")
parser.add_argument('--all', action='store_true', help="Полная очистка всех сообщений (для команды context purge)")
if len(sys.argv) == 1:
print_stats()
return
args = parser.parse_args()
if args.command == 'stats':
print_stats()
elif args.command == 'snapshots':
date_val = args.action or args.param
print_snapshots_list(date_str=date_val)
elif args.command == 'scud':
date_val = args.action or args.param
inspect_scud(snapshot_id=args.snapshot, date_str=date_val, export_xlsx=args.export_xlsx)
elif args.command == 'absences':
date_val = args.action or args.param
print_absences(date_str=date_val)
elif args.command == 'anomalies':
print_anomalies()
elif args.command == 'rules':
print_rules()
elif args.command == 'prompts':
print_system_prompts()
elif args.command == 'sessions':
print_session_states()
elif args.command == 'tools':
print_tool_actions()
elif args.command in ('in_building', 'presence'):
target_date = args.action if args.action else datetime.now().strftime("%d.%m.%Y")
print_building_presence(target_date, all_statuses=args.all)
elif args.command == 'context':
if args.action in ['purge', 'clear']:
is_all = args.all or (args.param == '--all')
sess_id = None if (args.param == '--all' or not args.param) else args.param
purge_chat_context(session_id=sess_id, purge_all=is_all)
else:
sess_id = args.action if args.action else None
print_chat_messages(session_id=sess_id, limit=args.limit)
elif args.command == 'dump':
filename = args.action or args.param or "db_dump_full.xlsx"
dump_all_to_excel(filename)
elif args.command == 'snapshot':
if args.action == 'del':
if args.day:
delete_snapshots_by_date(args.day)
elif args.param:
delete_snapshot_by_id(args.param)
else:
print("\n[❌] Ошибка: Не указан ID снапшота или параметр --day для удаления.")
print("Пример: python scripts/db_cli.py snapshot del Y20260805-007\n")
else:
print(f"\n[❌] Ошибка: Неизвестное действие '{args.action}' для команды snapshot.")
print("Используйте: python scripts/db_cli.py snapshot del [ID или --day 'ДД.ММ.ГГГГ']\n")
elif args.command == 'exceptions':
if args.action == 'add':
if not args.category or not args.value:
print("\n[❌] Ошибка: Для добавления исключения укажите флаги -c/--category и -v/--value")
print("Пример: python scripts/db_cli.py exceptions add -c include_fio -v \"Тарасенко Александр Александрович\"\n")
return
if add_exception_to_db(args.category, args.value, args.comment):
print(f"\n[✓] Успешно добавлено исключение: [{args.category}] {args.value}\n")
else:
print(f"\n[❌] Ошибка добавления исключения [{args.category}] {args.value}\n")
elif args.action in ['del', 'delete', 'remove']:
if not args.category or not args.value:
print("\n[❌] Ошибка: Для удаления исключения укажите флаги -c/--category и -v/--value")
print("Пример: python scripts/db_cli.py exceptions del -c fio -v \"Михалев Сергей Геннадьевич\"\n")
return
if remove_exception_from_db(args.category, args.value):
print(f"\n[✓] Успешно удалено исключение: [{args.category}] {args.value}\n")
else:
print(f"\n[⚠️] Запись не найдена в базе: [{args.category}] {args.value}\n")
elif args.action == 'sync':
sync_json_to_db()
print("\n[✓] Синхронизация exceptions.json -> SQLite успешно завершена.\n")
else:
print_exceptions()
else:
print("\n[❌] Ошибка: Неизвестная команда.")
print(HELP_TEXT)
if __name__ == "__main__":
main()