feat(pdf-to-md): sostituisci report.md con report.json + validate.py
pipeline.py produce conversione/<stem>/report.json invece di structure_profile.json + report.md. Il JSON contiene tutto: trasformazioni, struttura, distribuzione lunghezze sezioni, anomalie (bare_headers, short/long sections) e residui con esempi. Fix: bare_headers flagga solo header senza corpo < 30 chars; header numerati con corpo lungo (aforismi) non sono anomalie. Nuovo validate.py legge tutti i report.json e stampa tabella di stato per ogni stem (✅ / ⚠️ / ❌) con soglie configurabili. README aggiornato con sezione validazione batch e struttura report.json.
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
conversione/validate.py — Validazione batch di tutti gli stem convertiti
|
||||
|
||||
Legge i report.json prodotti da pipeline.py e stampa una tabella di stato
|
||||
per ogni documento, evidenziando anomalie e problemi residui.
|
||||
|
||||
Stato per stem:
|
||||
✅ nessuna anomalia critica
|
||||
⚠️ anomalie presenti ma documento processabile
|
||||
❌ struttura non rilevata o problemi gravi
|
||||
|
||||
Uso:
|
||||
python conversione/validate.py # tutti gli stem
|
||||
python conversione/validate.py analisi1 # stem specifico
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# ─── Soglie ──────────────────────────────────────────────────────────────────
|
||||
|
||||
_CRITICO_STRUTTURA = 0 # livello_struttura == 0 → testo piatto, nessun header
|
||||
_CRITICO_BACKTICK = 50 # molti accenti non corretti → testo illeggibile
|
||||
_WARNING_BARE = 1 # anche un solo header senza titolo è sospetto
|
||||
_WARNING_BACKTICK = 1 # qualsiasi backtick residuo va verificato
|
||||
_WARNING_LONG_SECS = 80 # troppe sezioni lunghe indica struttura insufficiente
|
||||
|
||||
|
||||
def _status(r: dict) -> str:
|
||||
structure = r.get("structure", {})
|
||||
anomalie = r.get("anomalie", {})
|
||||
residui = r.get("residui", {})
|
||||
|
||||
livello = structure.get("livello_struttura", -1)
|
||||
backtick = residui.get("backtick", 0)
|
||||
|
||||
if livello <= _CRITICO_STRUTTURA or backtick >= _CRITICO_BACKTICK:
|
||||
return "❌"
|
||||
if (
|
||||
anomalie.get("bare_headers", 0) >= _WARNING_BARE
|
||||
or backtick >= _WARNING_BACKTICK
|
||||
or anomalie.get("long_sections", 0) >= _WARNING_LONG_SECS
|
||||
):
|
||||
return "⚠️ "
|
||||
return "✅"
|
||||
|
||||
|
||||
def _fmt(value, width: int) -> str:
|
||||
return str(value).ljust(width)
|
||||
|
||||
|
||||
def validate(stems: list[str], project_root: Path) -> None:
|
||||
conv_dir = project_root / "conversione"
|
||||
|
||||
if stems:
|
||||
paths = [conv_dir / s / "report.json" for s in stems]
|
||||
else:
|
||||
paths = sorted(conv_dir.glob("*/report.json"))
|
||||
|
||||
if not paths:
|
||||
print("Nessun report.json trovato in conversione/*/")
|
||||
sys.exit(0)
|
||||
|
||||
rows = []
|
||||
for path in paths:
|
||||
if not path.exists():
|
||||
rows.append({"stem": path.parent.name, "_missing": True})
|
||||
continue
|
||||
r = json.loads(path.read_text(encoding="utf-8"))
|
||||
rows.append(r)
|
||||
|
||||
# ── Intestazione ─────────────────────────────────────────────────────
|
||||
col_stem = max(len(r.get("stem", "stem")) for r in rows) + 2
|
||||
header = (
|
||||
f"{'stem':<{col_stem}}"
|
||||
f"{'h2':>4}{'h3':>5} "
|
||||
f"{'strategia':<20}"
|
||||
f"{'bare':>5}{'corte':>6}{'lunghe':>7}"
|
||||
f"{'backtick':>9}{'dotlead':>8}{'url':>4}"
|
||||
f" {'status'}"
|
||||
)
|
||||
sep = "─" * len(header)
|
||||
print()
|
||||
print(header)
|
||||
print(sep)
|
||||
|
||||
# ── Righe ─────────────────────────────────────────────────────────────
|
||||
for r in rows:
|
||||
if r.get("_missing"):
|
||||
print(f"{r['stem']:<{col_stem}} (report.json non trovato)")
|
||||
continue
|
||||
|
||||
stem = r.get("stem", "?")
|
||||
structure = r.get("structure", {})
|
||||
anomalie = r.get("anomalie", {})
|
||||
residui = r.get("residui", {})
|
||||
|
||||
h2 = structure.get("n_h2", 0)
|
||||
h3 = structure.get("n_h3", 0)
|
||||
strat = structure.get("strategia_chunking", "?")
|
||||
bare = anomalie.get("bare_headers", 0)
|
||||
corte = anomalie.get("short_sections", 0)
|
||||
lunghe = anomalie.get("long_sections", 0)
|
||||
backtick = residui.get("backtick", 0)
|
||||
dotlead = residui.get("dotleader", 0)
|
||||
url = residui.get("url", 0)
|
||||
status = _status(r)
|
||||
|
||||
print(
|
||||
f"{stem:<{col_stem}}"
|
||||
f"{h2:>4}{h3:>5} "
|
||||
f"{strat:<20}"
|
||||
f"{bare:>5}{corte:>6}{lunghe:>7}"
|
||||
f"{backtick:>9}{dotlead:>8}{url:>4}"
|
||||
f" {status}"
|
||||
)
|
||||
|
||||
print(sep)
|
||||
totali = len(rows)
|
||||
ok = sum(1 for r in rows if not r.get("_missing") and _status(r) == "✅")
|
||||
warn = sum(1 for r in rows if not r.get("_missing") and _status(r).startswith("⚠"))
|
||||
err = sum(1 for r in rows if not r.get("_missing") and _status(r) == "❌")
|
||||
print(f"Totale: {totali} ✅ {ok} ⚠️ {warn} ❌ {err}")
|
||||
print()
|
||||
print("Legenda colonne: bare=header senza titolo corte=sezioni<150ch "
|
||||
"lunghe=sezioni>1500ch backtick=accenti residui")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
project_root = Path(__file__).parent.parent
|
||||
stems = sys.argv[1:]
|
||||
validate(stems, project_root)
|
||||
Reference in New Issue
Block a user