feat: implement svodka/otchet generators, Y-23:59:59 and snap-to-grid time finder
This commit is contained in:
+37
-20
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
===============================================================================
|
||||
FILE: main_etl.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
ROLE: Главная точка входа ETL-конвейера СКУД ⟷ 1С:ЗУП.
|
||||
===============================================================================
|
||||
"""
|
||||
@@ -15,11 +16,13 @@ from datetime import datetime, timedelta
|
||||
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.scud_etl.svodka_generator import generate_svodka_service
|
||||
from services.scud_etl.otchet_generator import generate_otchet_service
|
||||
from services.text_reporter import generate_markdown_report
|
||||
|
||||
from services.scud_export import run_export
|
||||
from services.share_copier import copy_1c_files_from_share
|
||||
from services.excel_exporter import generate_summary_excel, generate_detailed_excel, export_raw_scud
|
||||
from services.excel_exporter import export_raw_scud
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="[%(asctime)s] [%(levelname)s] %(message)s")
|
||||
|
||||
@@ -28,7 +31,9 @@ def main():
|
||||
parser = argparse.ArgumentParser(description="Модульный контроллинг СКУД ⟷ 1С")
|
||||
parser.add_argument("-d", "--debug", action="store_true", help="Режим отладки")
|
||||
parser.add_argument("--skip-export", action="store_true", help="Пропустить выгрузку СКУД из MS SQL")
|
||||
parser.add_argument("--snapshot", type=str, default=None, help="ID снапшота для расчета")
|
||||
parser.add_argument("--date", type=str, default=None, help="Дата расчета в формате ДД.ММ.ГГГГ")
|
||||
parser.add_argument("--time", type=str, default=None, help="Время среза для сводки (например, 14:30)")
|
||||
parser.add_argument("--snapshot", type=str, default=None, help="Точный ID снапшота для расчета")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
@@ -36,17 +41,22 @@ def main():
|
||||
print("=" * 60)
|
||||
|
||||
now = datetime.now()
|
||||
today_str = now.strftime("%d.%m.%Y")
|
||||
|
||||
if now.weekday() == 0:
|
||||
yesterday_str = (now - timedelta(days=3)).strftime("%d.%m.%Y")
|
||||
if args.date:
|
||||
today_str = args.date.replace('_', '.')
|
||||
dt_target = datetime.strptime(today_str, "%d.%m.%Y")
|
||||
days_back = 3 if dt_target.weekday() == 0 else 1
|
||||
yesterday_str = (dt_target - timedelta(days=days_back)).strftime("%d.%m.%Y")
|
||||
else:
|
||||
yesterday_str = (now - timedelta(days=1)).strftime("%d.%m.%Y")
|
||||
today_str = now.strftime("%d.%m.%Y")
|
||||
if now.weekday() == 0:
|
||||
yesterday_str = (now - timedelta(days=3)).strftime("%d.%m.%Y")
|
||||
else:
|
||||
yesterday_str = (now - timedelta(days=1)).strftime("%d.%m.%Y")
|
||||
|
||||
# [Этап 0] Выгрузка свежих данных СКУД
|
||||
if not args.skip_export and not args.snapshot:
|
||||
print(f"\n[0/5] Экспорт данных СКУД за {today_str} и {yesterday_str}...")
|
||||
run_export(debug=args.debug, save_xlsx=True)
|
||||
run_export(input_date=args.date, debug=args.debug, save_xlsx=True)
|
||||
else:
|
||||
print("\n[0/5] Пропуск прямого экспорта СКУД из MS SQL (--skip-export)...")
|
||||
|
||||
@@ -65,25 +75,33 @@ def main():
|
||||
df_staff_yesterday, df_abs_yesterday = load_1c_files_for_date(yesterday_str)
|
||||
df_staff_today, df_abs_today = load_1c_files_for_date(today_str)
|
||||
|
||||
# Сохранение диагностического дампа сырого СКУД
|
||||
if df_scud_today is not None and not df_scud_today.empty:
|
||||
export_raw_scud(df_scud_today, filename=f"СКУД_Сырые_данные_{today_str}.xlsx")
|
||||
|
||||
# [Этап 3] Детальный отчет за вчера
|
||||
# [Этап 3] Детальный отчет за вчера через otchet_generator
|
||||
print(f"\n[3/5] Обработка и построение детального отчета за ВЧЕРА ({yesterday_str})...")
|
||||
df_merged_yesterday = merge_scud_and_1c(df_scud_yesterday, df_staff_yesterday, df_abs_yesterday)
|
||||
generate_detailed_excel(df_merged_yesterday, date_str=yesterday_str)
|
||||
res_otchet = generate_otchet_service(target_date=yesterday_str)
|
||||
if res_otchet.get("status") == "success":
|
||||
print(f"[✓] {res_otchet.get('message')}: {res_otchet.get('filepath')}")
|
||||
|
||||
# [Этап 4] Сводка за сегодня
|
||||
print(f"\n[4/5] Обработка и построение Ежедневной сводки за {today_str}...")
|
||||
df_merged_today = merge_scud_and_1c(df_scud_today, df_staff_today, df_abs_today)
|
||||
metrics_today = calculate_summary_metrics(df_merged_today)
|
||||
anomalies_today = detect_registry_anomalies(df_merged_today, df_raw_scud=df_scud_today)
|
||||
generate_summary_excel(df_merged_today, date_str=today_str)
|
||||
# [Этап 4] Сводка за сегодня через svodka_generator
|
||||
print(f"\n[4/5] Обработка и построение Ежедневной сводки за {today_str} {args.time or ''}...")
|
||||
res_svodka = generate_svodka_service(
|
||||
target_date=today_str,
|
||||
target_time=args.time,
|
||||
snapshot_id=args.snapshot
|
||||
)
|
||||
if res_svodka.get("status") == "success":
|
||||
print(f"[✓] {res_svodka.get('message')}: {res_svodka.get('filepath')}")
|
||||
if res_svodka.get("note"):
|
||||
print(f" ℹ️ {res_svodka.get('note')}")
|
||||
|
||||
# [Этап 5] Формирование Markdown-сводки через ИИ-аудитора
|
||||
# [Этап 5] Формирование Markdown-сводки через ИИ-аудитора (Ollama)
|
||||
print(f"\n[5/5] Формирование Markdown-сводки через ИИ-аудитора (Ollama)...")
|
||||
|
||||
df_merged_today = merge_scud_and_1c(df_scud_today, df_staff_today, df_abs_today)
|
||||
anomalies_today = detect_registry_anomalies(df_merged_today, df_raw_scud=df_scud_today)
|
||||
|
||||
absent_explained = df_merged_today[
|
||||
(df_merged_today['Пришел'] == False) &
|
||||
(df_merged_today['Вид_отсутствия'].notna()) &
|
||||
@@ -95,7 +113,6 @@ def main():
|
||||
(df_merged_today.get('is_excluded', False) == False)
|
||||
]
|
||||
|
||||
# ⭐️ Передаем сырые датасеты для выявления опечаток и анализа мульти-записей
|
||||
summary_md = generate_markdown_report(
|
||||
merged_df=df_merged_today[df_merged_today.get('is_excluded', False) == False],
|
||||
absent_explained=absent_explained,
|
||||
|
||||
Reference in New Issue
Block a user