faa8acae84
- converter: parametri adattivi (use_struct_tree per PDF taggati, table_method=cluster, content_safety_off) - transforms: +20 PUA bracket TeX U+F8EB-F8FE (290 simboli corretti su analisi1) - transforms: _t_math_header_demotion — demota header ##/### che sono enunciati esercizi o formule - report: metrica formula_headers_residui con esempi - validator: penalità formula_headers (−3/cad, cap −15), colonna fhdr nel report tabellare Risultato su analisi1: voto 92/A, PUA residui 0, formula-hdr residui 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
111 lines
4.4 KiB
Python
111 lines
4.4 KiB
Python
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from .checker import check_pdf
|
|
from .converter import convert_pdf
|
|
from .transforms import apply_transforms
|
|
from .structure import analyze
|
|
from .report import build_report
|
|
|
|
|
|
_LIVELLO_DESC = {3: "ricca (h3)", 2: "parziale (h2)", 1: "paragrafi", 0: "testo piatto"}
|
|
|
|
|
|
def run(stem: str, project_root: Path, force: bool) -> bool:
|
|
pdf_path = project_root / "sources" / f"{stem}.pdf"
|
|
out_dir = project_root / "conversione" / stem
|
|
raw_out = out_dir / "raw.md"
|
|
clean_out = out_dir / "clean.md"
|
|
|
|
print(f"\n{'─' * 52}")
|
|
print(f" {stem}")
|
|
print(f"{'─' * 52}")
|
|
|
|
if clean_out.exists() and not force:
|
|
print(f" ⚠️ conversione/{stem}/clean.md già presente — skip")
|
|
print(f" (usa --force per rieseguire)")
|
|
return True
|
|
|
|
# [1] Validazione
|
|
print(" [1/4] Validazione PDF...")
|
|
ok, msg = check_pdf(pdf_path)
|
|
if not ok:
|
|
print(f" ✗ {msg}")
|
|
return False
|
|
print(f" ✅ {msg}")
|
|
|
|
# [2] Conversione
|
|
print(" [2/4] Conversione PDF → Markdown (opendataloader-pdf)...")
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
try:
|
|
md_file = convert_pdf(pdf_path, Path(tmp))
|
|
except MemoryError:
|
|
print(" ✗ Memoria esaurita durante la conversione")
|
|
return False
|
|
except Exception as e:
|
|
print(f" ✗ Conversione fallita: {e}")
|
|
return False
|
|
try:
|
|
raw_text = md_file.read_text(encoding="utf-8")
|
|
except UnicodeDecodeError as e:
|
|
print(f" ✗ Errore encoding nel file prodotto: {e}")
|
|
return False
|
|
|
|
size_kb = len(raw_text.encode()) // 1024
|
|
n_lines = raw_text.count("\n")
|
|
print(f" ✅ Markdown grezzo: {size_kb} KB, {n_lines} righe")
|
|
|
|
# [3] Pulizia strutturale
|
|
print(" [3/4] Pulizia strutturale...")
|
|
clean_text, t = apply_transforms(raw_text)
|
|
reduction = 100 * (1 - len(clean_text) / len(raw_text)) if raw_text else 0
|
|
print(f" ✅ Simboli PUA corretti: {t['n_simboli_pua_corretti']}")
|
|
print(f" Immagini rimosse: {t['n_immagini_rimosse']}")
|
|
print(f" Note rimosse: {t['n_note_rimosse']}")
|
|
print(f" Accenti corretti: {t['n_accenti_corretti']}")
|
|
print(f" Dot-leader rimossi: {t['n_dotleader_rimossi']}")
|
|
print(f" Header concat fixati: {t['n_header_concat_fixati']}")
|
|
print(f" Header num. normaliz.: {t['n_header_numerati_normalizzati']}")
|
|
print(f" Articoli → ###: {t['n_articoli_estratti']}")
|
|
print(f" Ambienti matematici: {t['n_ambienti_matematici']}")
|
|
print(f" Titoli header uniti: {t['n_titoli_uniti']}")
|
|
print(f" TOC rimosso: {'sì' if t['toc_rimosso'] else 'no'}")
|
|
print(f" Versi poesia riprist.: {t['n_versi_ripristinati']}")
|
|
print(f" Header verso demotati: {t['n_header_verso_demotati']}")
|
|
print(f" ALL-CAPS → ##: {t['n_header_allcaps']}")
|
|
print(f" Sezioni → ###: {t['n_sezioni_numerate']}")
|
|
print(f" Paragrafi uniti: {t['n_paragrafi_uniti']}")
|
|
print(f" Formula-hdr demotati: {t['n_formula_headers_demotati']}")
|
|
print(f" Riduzione testo: {reduction:.0f}%")
|
|
|
|
# [4] Profilo strutturale
|
|
print(" [4/4] Analisi struttura...")
|
|
try:
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
raw_out.write_text(raw_text, encoding="utf-8")
|
|
clean_out.write_text(clean_text, encoding="utf-8")
|
|
except PermissionError as e:
|
|
print(f" ✗ Permesso negato durante la scrittura: {e}")
|
|
return False
|
|
|
|
profile = analyze(clean_out)
|
|
(out_dir / "structure_profile.json").write_text(
|
|
json.dumps(profile, ensure_ascii=False, indent=2), encoding="utf-8"
|
|
)
|
|
|
|
print(f" ✅ Struttura: livello {profile['livello_struttura']} — "
|
|
f"{_LIVELLO_DESC[profile['livello_struttura']]}")
|
|
print(f" h1={profile['n_h1']} h2={profile['n_h2']} h3={profile['n_h3']} "
|
|
f"paragrafi={profile['n_paragrafi']}")
|
|
print(f" Strategia chunking: {profile['strategia_chunking']}")
|
|
print(f" Lingua rilevata: {profile['lingua_rilevata']}")
|
|
for w in profile["avvertenze"]:
|
|
print(f" ⚠️ {w}")
|
|
|
|
build_report(stem, out_dir, clean_text, t, profile, reduction)
|
|
|
|
print(f"\n Output → conversione/{stem}/")
|
|
print(f" raw.md (immutabile) clean.md report.json")
|
|
return True
|