проект теперь работает с использованием локальной базы данных SQLite. Все данные аккумулируются и хранятся в ней. Впереди - разработка веб интерфейса.

This commit is contained in:
2026-08-03 12:59:23 +03:00
parent d39618c891
commit 0efbe4ff99
14 changed files with 976 additions and 300 deletions
+252
View File
@@ -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}")