feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets

This commit is contained in:
2026-10-09 15:26:58 +05:00
parent 7f91366201
commit 0b12e4583b
244 changed files with 611461 additions and 1617 deletions
+153
View File
@@ -0,0 +1,153 @@
#!/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()