252 lines
9.8 KiB
Python
252 lines
9.8 KiB
Python
import os
|
|
import sqlite3
|
|
import pandas as pd
|
|
from datetime import datetime
|
|
from config import DATA_DIR
|
|
|
|
DB_PATH = os.path.join(DATA_DIR, "scud_orion_ai.db")
|
|
|
|
|
|
def get_connection():
|
|
"""Создает подключение к базе данных SQLite с оптимизированными настройками."""
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.execute("PRAGMA foreign_keys = ON;")
|
|
conn.execute("PRAGMA journal_mode = WAL;") # Режим быстрого чтения/записи
|
|
return conn
|
|
|
|
|
|
def init_db():
|
|
"""Создает структуру таблиц и индексов в базе данных."""
|
|
with get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
|
|
# 1. Таблица СКУД
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS scud_logs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
log_date TEXT NOT NULL,
|
|
fio TEXT NOT NULL,
|
|
fio_clean TEXT NOT NULL,
|
|
department TEXT,
|
|
position TEXT,
|
|
time_in TEXT,
|
|
time_out TEXT,
|
|
time_in_building TEXT,
|
|
is_present INTEGER NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
""")
|
|
|
|
# 2. Таблица Штатного расписания 1С
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS zup_staff (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
snapshot_date TEXT NOT NULL,
|
|
fio TEXT NOT NULL,
|
|
fio_clean TEXT NOT NULL,
|
|
department TEXT,
|
|
position TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
""")
|
|
|
|
# 3. Таблица Отсутствий из 1С
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS zup_absences (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
absence_date TEXT NOT NULL,
|
|
fio TEXT NOT NULL,
|
|
fio_clean TEXT NOT NULL,
|
|
absence_type TEXT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
""")
|
|
|
|
# 4. Реестр аномалий
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS anomalies_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
anomaly_date TEXT NOT NULL,
|
|
fio TEXT NOT NULL,
|
|
anomaly_type TEXT NOT NULL,
|
|
details TEXT NOT NULL,
|
|
human_status TEXT DEFAULT 'Pending',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
""")
|
|
|
|
# 5. База знаний ИИ
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS ai_knowledge_base (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
rule_text TEXT UNIQUE NOT NULL,
|
|
added_by TEXT DEFAULT 'Human',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
""")
|
|
|
|
# ⚡ ИНДЕКСЫ ДЛЯ УСКОРЕНИЯ ЗАПРОСОВ
|
|
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);")
|
|
|
|
conn.commit()
|
|
|
|
# Автоматически загружаем базовые правила компании
|
|
sync_knowledge_base_to_db()
|
|
|
|
|
|
def sync_knowledge_base_to_db():
|
|
"""
|
|
Автоматически мигрирует все правила из knowledge_base.json в таблицу ai_knowledge_base SQLite.
|
|
"""
|
|
kb_path = os.path.join(os.path.dirname(DATA_DIR), "knowledge_base.json")
|
|
rules_to_insert = [
|
|
"ГЛОБАЛЬНОЕ ПРАВИЛО 1: Любое физическое присутствие сотрудника по СКУД при наличии в 1С документа отсутствия (отпуск, больничный, уход за ребенком и др.), КРОМЕ командировок и служебных поездок — ЯВЛЯЕТСЯ СТРОГОЙ ИСТИННОЙ АНОМАЛИЕЙ.",
|
|
"ГЛОБАЛЬНОЕ ПРАВИЛО 2: Документы отсутствий из 1С:ЗУП имеют 100% высший приоритет над статическим реестром удалёнщиков.",
|
|
"ГЛОБАЛЬНОЕ ПРАВИЛО 3: Удалённые сотрудники имеют право беспрепятственно работать в офисе по СКУД в любое время (это не является аномалией)."
|
|
]
|
|
|
|
if os.path.exists(kb_path):
|
|
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}")
|
|
|
|
with get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
for rule in rules_to_insert:
|
|
cursor.execute(
|
|
"INSERT OR IGNORE INTO ai_knowledge_base (rule_text) VALUES (?)",
|
|
(rule.strip(),)
|
|
)
|
|
conn.commit()
|
|
|
|
def save_scud_to_db(df_scud, date_str):
|
|
"""Оптимизированное сохранение логов СКУД за дату."""
|
|
if df_scud.empty:
|
|
return
|
|
|
|
data_to_insert = []
|
|
for _, r in df_scud.iterrows():
|
|
data_to_insert.append((
|
|
date_str,
|
|
r.get('Сотрудник', r.get('fio_raw', '')),
|
|
r.get('fio_clean', ''),
|
|
r.get('Подразделение', ''),
|
|
r.get('Должность', ''),
|
|
r.get('Начало_дня', 'Нет входа'),
|
|
r.get('Конец_дня', 'Нет выхода'),
|
|
r.get('Находился_в_здании', '00:00'),
|
|
1 if r.get('Пришел', False) else 0
|
|
))
|
|
|
|
with get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("DELETE FROM scud_logs WHERE log_date = ?", (date_str,))
|
|
cursor.executemany("""
|
|
INSERT INTO scud_logs (log_date, fio, fio_clean, department, position, time_in, time_out, time_in_building, is_present)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""", data_to_insert)
|
|
conn.commit()
|
|
|
|
|
|
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('Должность', '')
|
|
))
|
|
|
|
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)
|
|
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('Вид_отсутствия', '')
|
|
))
|
|
|
|
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)
|
|
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)
|
|
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]
|
|
|
|
|
|
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)
|
|
)
|
|
conn.commit()
|
|
except Exception as e:
|
|
print(f"[⚠️] Ошибка записи правила в БД: {e}") |