#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ staging_inspect.py ================== Инспектор и анализатор ТЕСТОВОЙ базы данных (oracle_passports_staging.json). Позволяет сравнивать тестовую базу с основной, просматривать карточки и делать выгрузки. """ import os import sys import json import argparse from pathlib import Path from typing import Dict, Any PROJECT_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(PROJECT_ROOT)) from oracle_enricher.enricher.passport_schema import ( PROMPTS_78, render_passport_markdown, count_filled_fields, ) MAIN_DB_PATH = PROJECT_ROOT / "assets" / "data" / "oracle_passports.json" STAGING_DB_PATH = PROJECT_ROOT / "assets" / "data" / "oracle_passports_staging.json" OUT_DIR = PROJECT_ROOT / "out" / "staging_cards" def load_db(path: Path) -> Dict[str, Dict[str, Any]]: if not path.exists(): print(f"[ERROR] База не найдена: {path}") sys.exit(1) with open(path, "r", encoding="utf-8") as f: return json.load(f) def print_comparison_stats(main_db: Dict[str, Any], staging_db: Dict[str, Any]) -> None: total_words = len(staging_db) main_filled = sum(e.get("filled_count", 0) for e in main_db.values()) staging_filled = sum(e.get("filled_count", 0) for e in staging_db.values()) delta = staging_filled - main_filled max_total = total_words * 78 main_complete = sum(1 for e in main_db.values() if e.get("filled_count", 0) >= 78) staging_complete = sum(1 for e in staging_db.values() if e.get("filled_count", 0) >= 78) print("\n" + "═" * 70) print("📊 СРАВНИТЕЛЬНЫЙ АНАЛИЗ: ОСНОВНАЯ БАЗА vs ТЕСТОВАЯ БАЗА (STAGING)") print("═" * 70) print(f" Всего знаков в реестре: {total_words}") print(f" Заполнено ячеек в ОСНОВНОЙ: {main_filled} / {max_total} ({main_filled/max_total*100:.2f}%)") print(f" Заполнено ячеек в ТЕСТОВОЙ: {staging_filled} / {max_total} ({staging_filled/max_total*100:.2f}%)") print(f" Прирост данных в тестовой базе: +{delta} ячеек (+{delta/max_total*100:.2f}% всего каталога)") print(f" Полных карточек (78/78): ОСНОВНАЯ: {main_complete} | ТЕСТОВАЯ: {staging_complete}") print("─" * 70) print(f"{'РАЗДЕЛ КАТАЛОГА':<40} {'ОСНОВНАЯ':>12} {'ТЕСТОВАЯ':>12} {'ПРИРОСТ':>10}") print("─" * 70) for sec in ["I", "II", "III", "IV", "V", "VI"]: sec_fields = [p.key for p in PROMPTS_78 if p.section_num == sec] sec_max = len(sec_fields) * total_words cnt_main = sum(1 for e in main_db.values() for k in sec_fields if e.get("fields", {}).get(k)) cnt_staging = sum(1 for e in staging_db.values() for k in sec_fields if e.get("fields", {}).get(k)) sec_delta = cnt_staging - cnt_main sec_name = f"Раздел {sec}" print(f"{sec_name:<40} {cnt_main:>6}/{sec_max} {cnt_staging:>6}/{sec_max} {f'+{sec_delta}':>10}") print("═" * 70 + "\n") def find_entry(db: Dict[str, Any], query: str) -> Optional[Dict[str, Any]]: q = query.strip().lower() if q.isdigit(): idx_str = str(int(q)) if idx_str in db: return db[idx_str] for e in db.values(): if e.get("word", "").lower() == q: return e return None def show_diff(main_db: Dict[str, Any], staging_db: Dict[str, Any], query: str) -> None: main_e = find_entry(main_db, query) stag_e = find_entry(staging_db, query) if not stag_e: print(f"[ERROR] Знак «{query}» не найден в тестовой базе.") return word = stag_e.get("word", "") idx = stag_e.get("id", 0) print(f"\n═ Построчный DIFF для #{idx:03d} «{word.upper()}» (Основная -> Тестовая) ═") main_fields = main_e.get("fields", {}) if main_e else {} stag_fields = stag_e.get("fields", {}) diff_count = 0 for p in PROMPTS_78: mv = main_fields.get(p.key, "") sv = stag_fields.get(p.key, "") if mv != sv: diff_count += 1 print(f"\n[#{p.index:02d} | Раздел {p.section_num}] {p.title}:") if mv: print(f" ОСНОВНАЯ : {mv}") else: print(" ОСНОВНАЯ : (пусто)") print(f" ТЕСТОВАЯ : {sv}") print(f"\nВсего различий: {diff_count} полей из 78.\n") def main(): parser = argparse.ArgumentParser(description="Инспектор тестовой базы данных Оракула 999") parser.add_argument("--stats", action="store_true", help="Сравнить статистику основной и тестовой базы") parser.add_argument("--view", type=str, help="Просмотреть канонический Markdown карточки из тестовой базы (ID или слово)") parser.add_argument("--diff", type=str, help="Показать построчный diff между основной и тестовой базой для знака") parser.add_argument("--export-card", type=str, help="Экспортировать карточку из тестовой базы в файл out/staging_cards/...") args = parser.parse_args() main_db = load_db(MAIN_DB_PATH) staging_db = load_db(STAGING_DB_PATH) if args.stats or (not args.view and not args.diff and not args.export_card): print_comparison_stats(main_db, staging_db) if args.view: e = find_entry(staging_db, args.view) if e: print(render_passport_markdown(e)) else: print(f"[ERROR] Знак «{args.view}» не найден.") if args.diff: show_diff(main_db, staging_db, args.diff) if args.export_card: e = find_entry(staging_db, args.export_card) if e: OUT_DIR.mkdir(parents=True, exist_ok=True) idx_str = f"{e.get('id', 0):03d}" word = e.get("word", "") out_file = OUT_DIR / f"{idx_str}_{word}_staging.md" with open(out_file, "w", encoding="utf-8") as f: f.write(render_passport_markdown(e)) print(f"✓ Карточка сохранена в: {out_file}") else: print(f"[ERROR] Знак «{args.export_card}» не найден.") if __name__ == "__main__": main()