feat(turnstile): двухконтурный учет СКУД, реестры исключений с автокомплитом 1С и калибровка таймзон
This commit is contained in:
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd /home/puh/projects/scud_ai
|
||||
mkdir -p /home/puh/projects/scud_ai/logs
|
||||
|
||||
echo "==================================================" >> /home/puh/projects/scud_ai/logs/cron_etl.log
|
||||
echo "[CRON START] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_etl.log
|
||||
echo "==================================================" >> /home/puh/projects/scud_ai/logs/cron_etl.log
|
||||
|
||||
/home/puh/scud_orion_ai_v2/venv/bin/python /home/puh/projects/scud_ai/main_etl.py >> /home/puh/projects/scud_ai/logs/cron_etl.log 2>&1
|
||||
|
||||
echo "[CRON FINISH] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_etl.log
|
||||
echo "" >> /home/puh/projects/scud_ai/logs/cron_etl.log
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd /home/puh/projects/scud_ai
|
||||
mkdir -p /home/puh/projects/scud_ai/logs
|
||||
|
||||
echo "[CRON HOURLY START] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_hourly.log
|
||||
/home/puh/scud_orion_ai_v2/venv/bin/python /home/puh/projects/scud_ai/services/scud_export.py >> /home/puh/projects/scud_ai/logs/cron_hourly.log 2>&1
|
||||
echo "[CRON HOURLY FINISH] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_hourly.log
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd /home/puh/projects/scud_ai
|
||||
mkdir -p /home/puh/projects/scud_ai/logs
|
||||
|
||||
echo "[CRON REPORTS START] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_reports.log
|
||||
/home/puh/scud_orion_ai_v2/venv/bin/python /home/puh/projects/scud_ai/main_etl.py --use-existing-snapshot >> /home/puh/projects/scud_ai/logs/cron_reports.log 2>&1
|
||||
echo "[CRON REPORTS FINISH] $(date '+%Y-%m-%d %H:%M:%S')" >> /home/puh/projects/scud_ai/logs/cron_reports.log
|
||||
+30
-2
@@ -7,6 +7,7 @@ from datetime import datetime
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
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,
|
||||
@@ -450,6 +451,26 @@ def print_exceptions():
|
||||
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)
|
||||
@@ -458,6 +479,7 @@ CLI-утилита инспекции и управления SQLite базой
|
||||
stats -- Общая статистика строк по всем таблицам БД
|
||||
snapshots [ДД.ММ.ГГГГ] -- Посмотреть реестр снапшотов (опционально за конкретную дату)
|
||||
scud [ДД.ММ.ГГГГ] [--snapshot ID] [--export-xlsx NAME] -- Инспекция логов СКУД по дате/снапшоту и экспорт в Excel
|
||||
in_building [ДД.ММ.ГГГГ] [--all] -- Оперативный статус: кто сейчас в здании (или все статусы с флагом --all)
|
||||
absences [ДД.ММ.ГГГГ] -- Посмотреть список официально отсутствующих из 1С:ЗУП
|
||||
anomalies -- Посмотреть историю найденных аномалий СКУД ⟷ 1С
|
||||
rules -- Посмотреть правила Базы Знаний ИИ из SQLite
|
||||
@@ -478,6 +500,8 @@ CLI-утилита инспекции и управления 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
|
||||
@@ -509,11 +533,11 @@ def main():
|
||||
parser.add_argument('command', nargs='?', default=None, choices=[
|
||||
'stats', 'snapshots', 'scud', 'absences', 'anomalies',
|
||||
'rules', 'prompts', 'sessions', 'dump', 'snapshot',
|
||||
'tools', 'context', 'exceptions'
|
||||
'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'], help="Категория исключения")
|
||||
parser.add_argument('-c', '--category', type=str, default=None, choices=['departments', 'positions', 'fio', 'position_keywords', 'include_fio', 'turnstile_fio', 'turnstile_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 конкретного снапшота для инспекции")
|
||||
@@ -549,6 +573,10 @@ def main():
|
||||
print_session_states()
|
||||
elif args.command == 'tools':
|
||||
print_tool_actions()
|
||||
elif args.command in ('in_building', 'presence'):
|
||||
# Принимаем дату из позиционного параметра action (или param), либо берем текущую
|
||||
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')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: scripts/diagnostics/make_etl_snapshot.py
|
||||
ROLE: Генерация компактного слепка ETL-конвейера, БД и сервисов СКУД.
|
||||
ROLE: Генерация компактного слепка ETL-конвейера, генераторов отчетов и БД.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
@@ -11,30 +11,42 @@ import os
|
||||
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
OUTPUT_FILE = os.path.join(ROOT_DIR, "etl_code_snapshot.md")
|
||||
|
||||
# Строгий целевой список файлов для ETL-слепка
|
||||
TARGET_FILES = [
|
||||
# Конфигурация и точка входа
|
||||
"config.py",
|
||||
"exceptions.json",
|
||||
"main_etl.py",
|
||||
"run_cron_etl.sh",
|
||||
"scripts/db_cli.py", # ⭐️ Гарантированно включен
|
||||
"scripts/db_cli.py",
|
||||
|
||||
# Крон скрипты
|
||||
"scripts/cron/run_hourly_snapshot.sh",
|
||||
"scripts/cron/run_reports_only.sh",
|
||||
"scripts/cron/run_cron_etl.sh",
|
||||
|
||||
# Ядро БД
|
||||
"core/connection.py",
|
||||
"core/database.py",
|
||||
"core/schema.py",
|
||||
"core/repositories/scud_repo.py",
|
||||
"core/repositories/zup_repo.py",
|
||||
|
||||
# Сервисный слой загрузки и реестров
|
||||
"services/data_loader.py",
|
||||
"services/scud_export.py",
|
||||
"services/share_copier.py",
|
||||
"services/excel_exporter.py",
|
||||
"services/text_reporter.py",
|
||||
"services/exceptions_repo.py",
|
||||
"services/manual_absences_repo.py",
|
||||
"services/zup_extractor.py",
|
||||
"services/ai_verifier.py",
|
||||
"services/knowledge_base.py",
|
||||
"services/knowledge/service.py",
|
||||
|
||||
# Модули сборки Сводки и Отчета
|
||||
"services/scud_etl/pipeline.py",
|
||||
"services/scud_etl/merger.py",
|
||||
"services/scud_etl/svodka_generator.py",
|
||||
"services/scud_etl/otchet_generator.py",
|
||||
"services/scud_etl/anomaly_detector.py",
|
||||
"services/snapshots/service.py",
|
||||
"services/tasks/repository.py",
|
||||
@@ -50,7 +62,12 @@ def create_etl_snapshot():
|
||||
full_path = os.path.join(ROOT_DIR, rel_path)
|
||||
if os.path.exists(full_path):
|
||||
ext = os.path.splitext(rel_path)[1].replace(".", "")
|
||||
lang = "py" if ext == "py" else ("json" if ext == "json" else "bash")
|
||||
lang_map = {
|
||||
"py": "py",
|
||||
"json": "json",
|
||||
"sh": "bash"
|
||||
}
|
||||
lang = lang_map.get(ext, "text")
|
||||
try:
|
||||
with open(full_path, "r", encoding="utf-8") as f:
|
||||
file_text = f.read()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: scripts/diagnostics/make_web_snapshot.py
|
||||
ROLE: Генерация слепка Web API, LLM-движка и клиентских скриптов.
|
||||
ROLE: Генерация актуального слепка Web API, фронтенда (HTML/JS) и LLM-движка.
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
@@ -12,37 +12,56 @@ ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
OUTPUT_FILE = os.path.join(ROOT_DIR, "web_api_code_snapshot.md")
|
||||
|
||||
WEB_TARGET_FILES = [
|
||||
# Главная точка входа и роутеры
|
||||
"modules/web_api/main.py",
|
||||
"modules/web_api/routers/chat.py",
|
||||
"modules/web_api/routers/auth.py",
|
||||
"modules/web_api/routers/tasks.py",
|
||||
"modules/web_api/routers/remote_workers.py",
|
||||
"modules/web_api/routers/manual_absences.py",
|
||||
"modules/web_api/routers/exceptions.py",
|
||||
"modules/web_api/routers/snapshots.py",
|
||||
"modules/web_api/routers/tasks.py",
|
||||
"modules/web_api/routers/auth.py",
|
||||
"modules/web_api/routers/admin.py",
|
||||
"modules/web_api/routers/files.py",
|
||||
"modules/web_api/routers/context.py",
|
||||
|
||||
# LLM ядро
|
||||
"modules/web_api/llm/agent.py",
|
||||
"modules/web_api/llm/db_tools.py",
|
||||
"modules/web_api/llm/schemas.py",
|
||||
"modules/web_api/llm/core/context_manager.py",
|
||||
"modules/web_api/llm/core/tool_injector.py",
|
||||
"modules/web_api/llm/core/ollama_client.py",
|
||||
"modules/web_api/llm/db_tools.py",
|
||||
"modules/web_api/llm/core/fast_path.py",
|
||||
"modules/web_api/static/js/app.js",
|
||||
"modules/web_api/static/js/auth.js",
|
||||
"modules/web_api/llm/core/context_manager.py",
|
||||
"modules/web_api/llm/core/ollama_client.py",
|
||||
"modules/web_api/llm/core/tool_injector.py",
|
||||
|
||||
# Фронтенд (Разметка и клиентские скрипты)
|
||||
"modules/web_api/static/index.html",
|
||||
"modules/web_api/static/js/sidebar.js",
|
||||
"modules/web_api/static/js/tasks.js",
|
||||
"modules/web_api/static/js/manual_absences.js",
|
||||
"modules/web_api/static/js/chat/core.js",
|
||||
"modules/web_api/static/js/chat/task_widget.js"
|
||||
"modules/web_api/static/js/chat/task_widget.js",
|
||||
"modules/web_api/static/js/auth.js",
|
||||
"modules/web_api/static/js/app.js"
|
||||
]
|
||||
|
||||
|
||||
def create_web_snapshot():
|
||||
content = ["# 🌐 WEB API & LLM AGENT CODE SNAPSHOT\n"]
|
||||
content = ["# 🌐 WEB API & FRONTEND CODE SNAPSHOT\n"]
|
||||
included_count = 0
|
||||
|
||||
for rel_path in WEB_TARGET_FILES:
|
||||
full_path = os.path.join(ROOT_DIR, rel_path)
|
||||
if os.path.exists(full_path):
|
||||
ext = os.path.splitext(rel_path)[1].replace(".", "")
|
||||
lang = "js" if ext == "js" else ("py" if ext == "py" else "text")
|
||||
lang_map = {
|
||||
"js": "js",
|
||||
"py": "py",
|
||||
"html": "html",
|
||||
"css": "css",
|
||||
"json": "json"
|
||||
}
|
||||
lang = lang_map.get(ext, "text")
|
||||
try:
|
||||
with open(full_path, "r", encoding="utf-8") as f:
|
||||
file_text = f.read()
|
||||
@@ -50,12 +69,14 @@ def create_web_snapshot():
|
||||
included_count += 1
|
||||
except Exception as e:
|
||||
print(f"[⚠️] Ошибка чтения {rel_path}: {e}")
|
||||
else:
|
||||
print(f"[ℹ️] Пропущен отсутствующий файл: {rel_path}")
|
||||
|
||||
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(content))
|
||||
|
||||
size_kb = os.path.getsize(OUTPUT_FILE) / 1024
|
||||
print(f"\n[✓] Web API слепок создан: {OUTPUT_FILE}")
|
||||
print(f"\n[✓] Web API + Фронтенд слепок создан: {OUTPUT_FILE}")
|
||||
print(f" Включено файлов: {included_count} | Размер: {size_kb:.1f} KB\n")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user