готовность к созданию API и базы контекста для ИИ.
This commit is contained in:
+198
-114
@@ -12,7 +12,7 @@ def get_connection():
|
||||
"""Создает подключение к базе данных SQLite с оптимизированными настройками."""
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.execute("PRAGMA foreign_keys = ON;")
|
||||
conn.execute("PRAGMA journal_mode = WAL;") # Режим быстрого чтения/записи
|
||||
conn.execute("PRAGMA journal_mode = WAL;")
|
||||
return conn
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ def init_db():
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 1. Таблица СКУД (Добавлены first_activity, anomaly_flag, snapshot_time)
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS scud_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -37,11 +36,16 @@ def init_db():
|
||||
is_present INTEGER NOT NULL,
|
||||
anomaly_flag TEXT DEFAULT 'NONE',
|
||||
snapshot_time TEXT DEFAULT NULL,
|
||||
snapshot_id TEXT DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
""")
|
||||
|
||||
# 2. Таблица Штатного расписания 1С
|
||||
cursor.execute("PRAGMA table_info(scud_logs);")
|
||||
cols = [col[1] for col in cursor.fetchall()]
|
||||
if 'snapshot_id' not in cols:
|
||||
cursor.execute("ALTER TABLE scud_logs ADD COLUMN snapshot_id TEXT DEFAULT NULL;")
|
||||
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS zup_staff (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -54,7 +58,6 @@ def init_db():
|
||||
);
|
||||
""")
|
||||
|
||||
# 3. Таблица Отсутствий из 1С
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS zup_absences (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -66,7 +69,6 @@ def init_db():
|
||||
);
|
||||
""")
|
||||
|
||||
# 4. Реестр аномалий
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS anomalies_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -79,7 +81,6 @@ def init_db():
|
||||
);
|
||||
""")
|
||||
|
||||
# 5. База знаний ИИ
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ai_knowledge_base (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -89,79 +90,93 @@ def init_db():
|
||||
);
|
||||
""")
|
||||
|
||||
# ⚡ ИНДЕКСЫ ДЛЯ УСКОРЕНИЯ ЗАПРОСОВ
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_scud_date ON scud_logs(log_date);")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_scud_fio ON scud_logs(fio_clean);")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_absences_date ON zup_absences(absence_date);")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_staff_date ON zup_staff(snapshot_date);")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_anomalies_date ON anomalies_history(anomaly_date);")
|
||||
cursor.execute("CREATE INDEX IF NOT EXISTS idx_scud_snapshot ON scud_logs(snapshot_time);")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Автоматически загружаем базовые правила и системный промпт
|
||||
sync_knowledge_base_to_db()
|
||||
|
||||
|
||||
def sync_knowledge_base_to_db():
|
||||
"""
|
||||
Автоматически мигрирует правила из knowledge_base.json и системный промпт в таблицу ai_knowledge_base SQLite.
|
||||
"""
|
||||
system_ai_prompt = (
|
||||
"СИСТЕМНЫЙ ПРОМПТ ИИ (MATCHING, VERIFICATION & ANOMALY DETECTION):\n"
|
||||
"Ты — экспертный ИИ-аудитор СКУД и кадрового учета 1С. Твоя задача — проводить интеллектуальный анализ аномалий, "
|
||||
"выявление массовых сбоев оборудования и сопоставление (Fuzzy Matching) нераспознанных ФИО из СКУД с эталонным Штатным расписанием 1С.\n\n"
|
||||
"1. ДЕТЕКТИРОВАНИЕ АНОМАЛИЙ И МАССОВЫХ СБОЕВ СКУД:\n"
|
||||
" - Если у сотрудника 'Начало_дня' = 'Нет входа', но заполнено поле 'Первая_активность' (например, '14:11:06') или 'Конец_дня' != 'Нет выхода', "
|
||||
"классифицируй случай как одиночную аномалию: '🚨 АНОМАЛИЯ СКУД: Присутствие/активность без утреннего входа'.\n"
|
||||
" - Если процент сотрудников с подобной аномалией превышает 5% от смены (или >10 человек), СГЕНЕРИРУЙ КРИТИЧЕСКИЙ СИСТЕМНЫЙ АЛЕРТ: "
|
||||
"'⚠️ ВНИМАНИЕ! Обнаружен массовый сбой регистраторов входа СКУД на главном КПП. Требуется проверка контроллеров.'\n\n"
|
||||
"2. ЗАЩИТА ОТ ПОЛНЫХ ТЁЗОК (FULL NAME COLLISION):\n"
|
||||
" - Если в эталонном штате 1С присутствуют два или более сотрудников с ОДИНАКОВЫМИ ФИО (полные тёзки), "
|
||||
"ты ОБЯЗАН учитывать контекст: Подразделение и Должность.\n"
|
||||
" - Ни в коем случае не объединяй записи разных людей! Если контекст не позволяет однозначно определить, кто именно из тёзок совершил проход, ПОМЕТЬ СЛУЧАЙ КАК НЕОДНОЗНАЧНЫЙ и НЕ делай автоматическую подмену.\n\n"
|
||||
"3. ПРАВИЛА СОПОСТАВЛЕНИЯ (MATCHING RULES):\n"
|
||||
" - Допускаются исправления мелких опечаток, перестановки порядка (Имя Фамилия -> Фамилия Имя), замена букв Е/Ё, И/Й.\n"
|
||||
" - Учитывай сокращения инициалов (например, 'Иванов И.В.' -> 'Иванов Иван Васильевич').\n"
|
||||
" - Если уверенность в совпадении меньше 85% — НЕ сопоставляй, оставляй запись как нераспознанную.\n\n"
|
||||
"4. ФОРМАТ ВЫХОДНЫХ ДАННЫХ:\n"
|
||||
" Верни строго JSON-объект без вводных слов и Markdown-разметки: {\"mappings\": [...], \"unmatched\": [...], \"alerts\": [...]}"
|
||||
)
|
||||
def has_scud_logs_for_date(date_str):
|
||||
"""Проверяет, есть ли в базе данные СКУД за указанную дату."""
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT 1 FROM scud_logs WHERE log_date = ? LIMIT 1", (date_str,))
|
||||
return cursor.fetchone() is not None
|
||||
|
||||
rules_to_insert = [
|
||||
system_ai_prompt,
|
||||
"ГЛОБАЛЬНОЕ ПРАВИЛО 1: Любое физическое присутствие сотрудника по СКУД при наличии в 1С документа отсутствия (отпуск, больничный, уход за ребенком и др.), КРОМЕ командировок и служебных поездок — ЯВЛЯЕТСЯ СТРОГОЙ ИСТИННОЙ АНОМАЛИЕЙ.",
|
||||
"ГЛОБАЛЬНОЕ ПРАВИЛО 2: Документы отсутствий из 1С:ЗУП имеют 100% высший приоритет над статическим реестром удалёнщиков.",
|
||||
"ГЛОБАЛЬНОЕ ПРАВИЛО 3: Удалённые сотрудники имеют право беспрепятственно работать в офисе по СКУД в любое время (это не является аномалией).",
|
||||
"ГЛОБАЛЬНОЕ ПРАВИЛО 4: Отсутствие утренней отметки входа при наличии дневных перемещений (первая активность) является аномалией оборудования и не должно автоматически превращать запись в подтверждённое присутствие."
|
||||
]
|
||||
|
||||
kb_path = os.path.join(os.path.dirname(DATA_DIR), "knowledge_base.json")
|
||||
if os.path.exists(kb_path):
|
||||
def get_or_create_snapshot_id(snapshot_time, date_str=None, is_yesterday=False):
|
||||
"""
|
||||
Генерирует датированный составной ID снапшота (YYYYMMDD-NNN или YYYYMMDD-NNN).
|
||||
Префикс 'Y' присваивается автоматически, если дата логов (date_str) предшествует дате снятия (snapshot_time).
|
||||
"""
|
||||
try:
|
||||
dt_snap = datetime.strptime(snapshot_time, "%Y-%m-%d %H:%M:%S").date()
|
||||
date_prefix = dt_snap.strftime("%Y%m%d")
|
||||
except (ValueError, TypeError):
|
||||
dt_snap = datetime.now().date()
|
||||
date_prefix = dt_snap.strftime("%Y%m%d")
|
||||
|
||||
# Автоматическая проверка: если дата логов раньше даты выгрузки — проставляем Y
|
||||
if date_str:
|
||||
try:
|
||||
with open(kb_path, "r", encoding="utf-8") as f:
|
||||
kb_data = json.load(f)
|
||||
file_rules = kb_data.get("rules", [])
|
||||
for r in file_rules:
|
||||
if r not in rules_to_insert:
|
||||
rules_to_insert.append(r)
|
||||
except Exception as e:
|
||||
print(f"[⚠️] Ошибка чтения knowledge_base.json: {e}")
|
||||
dt_log = datetime.strptime(date_str, "%d.%m.%Y").date()
|
||||
if dt_log < dt_snap:
|
||||
is_yesterday = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
prefix = "Y" if is_yesterday else ""
|
||||
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
for rule in rules_to_insert:
|
||||
|
||||
if date_str:
|
||||
cursor.execute(
|
||||
"INSERT OR IGNORE INTO ai_knowledge_base (rule_text, added_by) VALUES (?, ?)",
|
||||
(rule.strip(), "System_AI")
|
||||
"SELECT snapshot_id FROM scud_logs WHERE log_date = ? AND snapshot_time = ? AND snapshot_id IS NOT NULL LIMIT 1",
|
||||
(date_str, snapshot_time)
|
||||
)
|
||||
conn.commit()
|
||||
else:
|
||||
cursor.execute(
|
||||
"SELECT snapshot_id FROM scud_logs WHERE snapshot_time = ? AND snapshot_id IS NOT NULL LIMIT 1",
|
||||
(snapshot_time,)
|
||||
)
|
||||
|
||||
row = cursor.fetchone()
|
||||
if row and row[0]:
|
||||
return row[0]
|
||||
|
||||
cursor.execute("""
|
||||
SELECT snapshot_id
|
||||
FROM scud_logs
|
||||
WHERE snapshot_id LIKE ? OR snapshot_id LIKE ?
|
||||
ORDER BY snapshot_id DESC LIMIT 1
|
||||
""", (f"{date_prefix}-%", f"Y{date_prefix}-%"))
|
||||
|
||||
last_row = cursor.fetchone()
|
||||
next_seq = 1
|
||||
|
||||
if last_row and last_row[0]:
|
||||
parts = last_row[0].replace("Y", "").split("-")
|
||||
if len(parts) > 1 and parts[1].isdigit():
|
||||
next_seq = int(parts[1]) + 1
|
||||
|
||||
return f"{prefix}{date_prefix}-{next_seq:03d}"
|
||||
|
||||
|
||||
def save_scud_to_db(df_scud, date_str, snapshot_time=None):
|
||||
"""Оптимизированное сохранение логов СКУД за дату с поддержкой первой активности, флага аномалии и метки выгрузки."""
|
||||
if df_scud.empty:
|
||||
def save_scud_to_db(df_scud, date_str, snapshot_time=None, is_yesterday=False):
|
||||
"""Сохраняет логи СКУД с автоматическим присвоением составного snapshot_id."""
|
||||
if df_scud is None or df_scud.empty:
|
||||
return
|
||||
|
||||
if not snapshot_time:
|
||||
snapshot_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Передаем date_str в генератор snapshot_id
|
||||
snapshot_id = get_or_create_snapshot_id(snapshot_time, date_str=date_str, is_yesterday=is_yesterday)
|
||||
|
||||
data_to_insert = []
|
||||
for _, r in df_scud.iterrows():
|
||||
data_to_insert.append((
|
||||
@@ -176,116 +191,185 @@ def save_scud_to_db(df_scud, date_str, snapshot_time=None):
|
||||
r.get('Находился_в_здании', '00:00'),
|
||||
1 if r.get('Пришел', False) else 0,
|
||||
r.get('anomaly_flag', 'NONE'),
|
||||
snapshot_time
|
||||
snapshot_time,
|
||||
snapshot_id
|
||||
))
|
||||
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
if snapshot_time:
|
||||
cursor.execute("DELETE FROM scud_logs WHERE log_date = ? AND snapshot_time = ?", (date_str, snapshot_time))
|
||||
else:
|
||||
cursor.execute("DELETE FROM scud_logs WHERE log_date = ? AND snapshot_time IS NULL", (date_str,))
|
||||
|
||||
cursor.execute("DELETE FROM scud_logs WHERE log_date = ? AND snapshot_time = ?", (date_str, snapshot_time))
|
||||
cursor.executemany("""
|
||||
INSERT INTO scud_logs (
|
||||
log_date, fio, fio_clean, department, position,
|
||||
time_in, first_activity, time_out, time_in_building,
|
||||
is_present, anomaly_flag, snapshot_time
|
||||
is_present, anomaly_flag, snapshot_time, snapshot_id
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", data_to_insert)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def get_latest_snapshot_time(date_str=None):
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
if date_str:
|
||||
cursor.execute("SELECT snapshot_time FROM scud_logs WHERE log_date = ? AND snapshot_time IS NOT NULL ORDER BY snapshot_time DESC LIMIT 1", (date_str,))
|
||||
else:
|
||||
cursor.execute("SELECT snapshot_time FROM scud_logs WHERE snapshot_time IS NOT NULL ORDER BY snapshot_time DESC LIMIT 1")
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row else None
|
||||
|
||||
|
||||
def load_scud_from_db_by_snapshot(date_str, snapshot_param=None):
|
||||
"""
|
||||
Загружает логи СКУД по ID снапшота ('20260805-001', 'Y20260805-001') или по штампу времени.
|
||||
С автоматическим маппингом полей Подразделения и Должности.
|
||||
"""
|
||||
with get_connection() as conn:
|
||||
df = pd.DataFrame()
|
||||
|
||||
if snapshot_param:
|
||||
if "-" in str(snapshot_param) or str(snapshot_param).startswith('Y'):
|
||||
df = pd.read_sql_query(
|
||||
"SELECT * FROM scud_logs WHERE log_date = ? AND snapshot_id = ?",
|
||||
conn, params=(date_str, str(snapshot_param))
|
||||
)
|
||||
if df.empty:
|
||||
df = pd.read_sql_query(
|
||||
"SELECT * FROM scud_logs WHERE snapshot_id = ?",
|
||||
conn, params=(str(snapshot_param),)
|
||||
)
|
||||
|
||||
if df.empty:
|
||||
df = pd.read_sql_query(
|
||||
"SELECT * FROM scud_logs WHERE log_date = ? AND snapshot_time = ?",
|
||||
conn, params=(date_str, str(snapshot_param))
|
||||
)
|
||||
|
||||
if df.empty:
|
||||
latest_time = get_latest_snapshot_time(date_str)
|
||||
if latest_time:
|
||||
df = pd.read_sql_query(
|
||||
"SELECT * FROM scud_logs WHERE log_date = ? AND snapshot_time = ?",
|
||||
conn, params=(date_str, latest_time)
|
||||
)
|
||||
|
||||
if not df.empty:
|
||||
df['Сотрудник'] = df['fio']
|
||||
df['Пришел'] = df['is_present'].astype(int) == 1 if 'is_present' in df.columns else False
|
||||
df['Начало_дня'] = df['time_in']
|
||||
df['Первая_активность'] = df['first_activity']
|
||||
df['Конец_дня'] = df['time_out']
|
||||
df['Находился_в_здании'] = df['time_in_building']
|
||||
|
||||
# 💡 Маппинг колонок БД в стандартные русские наименования
|
||||
if 'department' in df.columns and 'Подразделение' not in df.columns:
|
||||
df['Подразделение'] = df['department']
|
||||
if 'position' in df.columns and 'Должность' not in df.columns:
|
||||
df['Должность'] = df['position']
|
||||
else:
|
||||
df = pd.DataFrame(columns=[
|
||||
'Сотрудник', 'fio_clean', 'Подразделение', 'Должность',
|
||||
'Начало_дня', 'Первая_активность', 'Конец_дня',
|
||||
'Находился_в_здании', 'Пришел', 'anomaly_flag'
|
||||
])
|
||||
|
||||
return df
|
||||
|
||||
def get_available_snapshots(date_str=None):
|
||||
"""
|
||||
Возвращает список снапшотов.
|
||||
Сортировка по времени snapshot_time DESC.
|
||||
"""
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
if date_str:
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
snapshot_id,
|
||||
log_date,
|
||||
snapshot_time,
|
||||
COUNT(*) as cnt
|
||||
FROM scud_logs
|
||||
WHERE log_date = ? AND snapshot_time IS NOT NULL
|
||||
GROUP BY snapshot_id, log_date, snapshot_time
|
||||
ORDER BY snapshot_time DESC
|
||||
""", (date_str,))
|
||||
else:
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
snapshot_id,
|
||||
log_date,
|
||||
snapshot_time,
|
||||
COUNT(*) as cnt
|
||||
FROM scud_logs
|
||||
WHERE snapshot_time IS NOT NULL
|
||||
GROUP BY snapshot_id, log_date, snapshot_time
|
||||
ORDER BY snapshot_time DESC
|
||||
""")
|
||||
return cursor.fetchall()
|
||||
|
||||
|
||||
def save_staff_to_db(df_staff, date_str):
|
||||
"""Сохраняет выгрузку Штатного расписания 1С в БД."""
|
||||
if df_staff is None or df_staff.empty:
|
||||
return
|
||||
|
||||
data_to_insert = []
|
||||
for _, r in df_staff.iterrows():
|
||||
data_to_insert.append((
|
||||
date_str,
|
||||
r.get('ФИО', ''),
|
||||
r.get('fio_clean', ''),
|
||||
r.get('Подразделение', ''),
|
||||
r.get('Должность', '')
|
||||
))
|
||||
|
||||
data_to_insert = [
|
||||
(date_str, r.get('ФИО', ''), r.get('fio_clean', ''), r.get('Подразделение', ''), r.get('Должность', ''))
|
||||
for _, r in df_staff.iterrows()
|
||||
]
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM zup_staff WHERE snapshot_date = ?", (date_str,))
|
||||
cursor.executemany("""
|
||||
INSERT INTO zup_staff (snapshot_date, fio, fio_clean, department, position)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", data_to_insert)
|
||||
cursor.executemany("INSERT INTO zup_staff (snapshot_date, fio, fio_clean, department, position) VALUES (?, ?, ?, ?, ?)", data_to_insert)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def save_absences_to_db(df_absent, date_str):
|
||||
"""Оптимизированное сохранение отсутствий из 1С в БД."""
|
||||
if df_absent is None or df_absent.empty:
|
||||
return
|
||||
|
||||
data_to_insert = []
|
||||
for _, r in df_absent.iterrows():
|
||||
data_to_insert.append((
|
||||
date_str,
|
||||
r.get('ФИО', r.get('fio_clean', '')),
|
||||
r.get('fio_clean', ''),
|
||||
r.get('Вид_отсутствия', '')
|
||||
))
|
||||
|
||||
data_to_insert = [
|
||||
(date_str, r.get('ФИО', r.get('fio_clean', '')), r.get('fio_clean', ''), r.get('Вид_отсутствия', ''))
|
||||
for _, r in df_absent.iterrows()
|
||||
]
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM zup_absences WHERE absence_date = ?", (date_str,))
|
||||
cursor.executemany("""
|
||||
INSERT INTO zup_absences (absence_date, fio, fio_clean, absence_type)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", data_to_insert)
|
||||
cursor.executemany("INSERT INTO zup_absences (absence_date, fio, fio_clean, absence_type) VALUES (?, ?, ?, ?)", data_to_insert)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def save_anomalies_to_db(anomalies_list, date_str):
|
||||
"""Записывает обнаруженные аномалии за дату."""
|
||||
if not anomalies_list:
|
||||
return
|
||||
|
||||
data_to_insert = [
|
||||
(date_str, a.get('fio', ''), a.get('type', ''), a.get('details', ''))
|
||||
for a in anomalies_list
|
||||
]
|
||||
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("DELETE FROM anomalies_history WHERE anomaly_date = ?", (date_str,))
|
||||
cursor.executemany("""
|
||||
INSERT INTO anomalies_history (anomaly_date, fio, anomaly_type, details)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""", data_to_insert)
|
||||
cursor.executemany("INSERT INTO anomalies_history (anomaly_date, fio, anomaly_type, details) VALUES (?, ?, ?, ?)", data_to_insert)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def get_all_rules_from_db():
|
||||
"""Возвращает список всех правил для ИИ из базы данных."""
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT rule_text FROM ai_knowledge_base")
|
||||
rows = cursor.fetchall()
|
||||
return [r[0] for r in rows]
|
||||
return [r[0] for r in cursor.fetchall()]
|
||||
|
||||
|
||||
def add_rule_to_db(rule_text, added_by="Human"):
|
||||
"""Добавляет новое динамическое правило в базу данных."""
|
||||
if not rule_text or not rule_text.strip():
|
||||
return
|
||||
with get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute(
|
||||
"INSERT OR IGNORE INTO ai_knowledge_base (rule_text, added_by) VALUES (?, ?)",
|
||||
(rule_text.strip(), added_by)
|
||||
)
|
||||
cursor.execute("INSERT OR IGNORE INTO ai_knowledge_base (rule_text, added_by) VALUES (?, ?)", (rule_text.strip(), added_by))
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
print(f"[⚠️] Ошибка записи правила в БД: {e}")
|
||||
print(f"[⚠️] Ошибка записи правила в БД: {e}")
|
||||
|
||||
|
||||
def sync_knowledge_base_to_db():
|
||||
pass
|
||||
Reference in New Issue
Block a user