проект теперь работает с использованием локальной базы данных SQLite. Все данные аккумулируются и хранятся в ней. Впереди - разработка веб интерфейса.
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
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}")
|
||||
Reference in New Issue
Block a user