""" =============================================================================== FILE: services/scud_etl/svodka_generator.py ROLE: Генератор Ежедневной Сводки (оперативный контроль, текущий срез). =============================================================================== """ import os import logging from typing import Dict, Any, Optional import pandas as pd from config import DATE_TODAY from core.database import load_scud_from_db_by_snapshot from services.scud_etl.pipeline import load_best_snapshot_for_date, load_1c_files_for_date from services.scud_etl.merger import merge_scud_and_1c, calculate_summary_metrics from services.scud_etl.anomaly_detector import detect_registry_anomalies from services.snapshots.finder import find_or_create_snapshot_for_time from services.excel_exporter import generate_summary_excel, get_dated_reports_dir, format_date_ru logger = logging.getLogger("SVODKA_GENERATOR") def generate_svodka_service( target_date: Optional[str] = None, target_time: Optional[str] = None, snapshot_id: Optional[str] = None ) -> Dict[str, Any]: """ Формирует оперативную сводку: - По умолчанию берет ПОСЛЕДНИЙ готовый снапшот из базы SQLite (без долгого опроса MS SQL). """ date_clean = str(target_date or DATE_TODAY).replace('_', '.') applied_note = "" # 1. Загрузка среза СКУД строго из готовых в базе if snapshot_id: df_scud = load_scud_from_db_by_snapshot(date_clean, snapshot_param=snapshot_id) elif target_time: found_id, note = find_or_create_snapshot_for_time(date_clean, target_time, allow_ondemand_export=False) applied_note = note df_scud = load_scud_from_db_by_snapshot(date_clean, snapshot_param=found_id) else: # По умолчанию: берем самый свежий существующий срез за дату df_scud = load_best_snapshot_for_date(date_clean, prefer_final_y=False) if df_scud is None or df_scud.empty: return { "status": "error", "message": f"Срез СКУД за {date_clean} не найден в базе данных." } # Извлекаем время фактического среза для имени файла actual_snap_time = "" if 'snapshot_time' in df_scud.columns and not df_scud.empty: raw_st = str(df_scud['snapshot_time'].iloc[0]).strip() if " " in raw_st: actual_snap_time = raw_st.split()[1][:5].replace(':', '-') # 2. Загружаем актуальные кадровые данные 1С (штат + отсутствия + реестры) df_staff, df_abs = load_1c_files_for_date(date_clean) # 3. Слияние и расчет df_merged = merge_scud_and_1c(df_scud, df_staff, df_abs) metrics = calculate_summary_metrics(df_merged) anomalies = detect_registry_anomalies(df_merged, df_raw_scud=df_scud) # 4. Формирование книги Excel time_suffix = f" на {actual_snap_time}" if actual_snap_time else "" filename = f"{format_date_ru(date_clean)} сводка{time_suffix}.xlsx" generate_summary_excel(df_merged, date_str=date_clean, filename=filename) target_dir = get_dated_reports_dir(date_clean) full_filepath = os.path.join(target_dir, filename) return { "status": "success", "report_type": "SVODKA", "date": date_clean, "target_time": actual_snap_time, "snapshot_id": snapshot_id or "LATEST_READY", "filename": filename, "filepath": full_filepath, "download_url": f"/api/v1/files/download/reports/{os.path.basename(full_filepath)}", "metrics": metrics, "anomalies_count": len(anomalies), "note": applied_note, "message": f"Ежедневная сводка на {date_clean} успешно сформирована." }