#!/usr/bin/env python3 """ Step 1 — Ispezione automatica PDF Analizza il PDF pagina per pagina e produce un report con score (0–100) e lista dei problemi per pagina. Serve per capire la qualità del documento e mappare i problemi prima della revisione manuale (step 4). Uso: python step1/inspect.py Output: step1/_step1_report.txt """ import re import sys import statistics from collections import Counter from datetime import datetime from pathlib import Path # ── Penalità per il calcolo dello score ─────────────────────────────────── SYLLABIF_PENALTY = 0.3 # per occorrenza di sillabazione COLUMN_PENALTY = 3.0 # per pagina con layout a colonne UNICODE_PENALTY = 1.5 # per pagina con caratteri anomali EMPTY_PENALTY = 1.0 # per pagina vuota HEADER_FOOTER_PEN = 5.0 # fisso se intestazioni/piè ripetitivi rilevati def inspect_pdf(pdf_path: str, save: bool = True) -> None: try: import pdfplumber except ImportError: print("Errore: pdfplumber non è installato.") print(" pip install pdfplumber") sys.exit(1) path = Path(pdf_path) if not path.exists(): print(f"Errore: file non trovato — {pdf_path}") sys.exit(1) lines = [] def out(text=""): lines.append(text) print(text) out("Step 1 — Ispezione automatica PDF") out(f"File: {path.name}") out(f"Data: {datetime.now().strftime('%Y-%m-%d %H:%M')}") out("=" * 50) # ── Lettura pagine ───────────────────────────────────────────────────── with pdfplumber.open(path) as pdf: n_pages = len(pdf.pages) pages_text = [page.extract_text() or "" for page in pdf.pages] # ── Analisi per pagina ───────────────────────────────────────────────── issues = [] # (page_num, descrizione) — page_num=0 → problema globale deductions = 0.0 first_lines = [] # prima riga significativa di ogni pagina (per header) last_lines = [] # ultima riga significativa di ogni pagina (per footer) for i, text in enumerate(pages_text): page_num = i + 1 stripped = text.strip() # 1. Pagina vuota if len(stripped) < 50: issues.append((page_num, "pagina vuota")) deductions += EMPTY_PENALTY continue page_lines = text.splitlines() nonempty = [l.strip() for l in page_lines if l.strip()] # Raccogli prima/ultima riga per il controllo header/footer if nonempty: first_lines.append(nonempty[0]) last_lines.append(nonempty[-1]) # 2. Sillabazione a fine riga (es. "estra-" + a capo) syllabif = sum( 1 for line in page_lines if re.search(r'\b\w{2,}-$', line.rstrip()) ) if syllabif: label = "occorrenza" if syllabif == 1 else "occorrenze" issues.append((page_num, f"sillabazione rilevata ({syllabif} {label})")) deductions += syllabif * SYLLABIF_PENALTY # 3. Layout a colonne (righe molto corte e numerose) if len(nonempty) >= 10: median_len = statistics.median(len(l) for l in nonempty) short_ratio = sum(1 for l in nonempty if len(l) < median_len * 0.4) / len(nonempty) if short_ratio > 0.35: issues.append((page_num, f"possibile layout a colonne ({short_ratio:.0%} righe corte)")) deductions += COLUMN_PENALTY # 4. Caratteri Unicode anomali # (control chars esclusi \n \t \r, replacement char, PUA block) anomalies = re.findall( r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f\ufffd\ue000-\uf8ff]', text ) if anomalies: issues.append((page_num, f"caratteri Unicode anomali ({len(anomalies)} trovati)")) deductions += UNICODE_PENALTY # ── Intestazioni e piè di pagina ripetitivi ──────────────────────────── def _check_repetition(line_list: list, label: str) -> None: nonlocal deductions if not line_list: return threshold = max(3, len(line_list) * 0.25) repeated = [ (txt, cnt) for txt, cnt in Counter(line_list).items() if cnt >= threshold and len(txt) > 3 ] if repeated: deductions += HEADER_FOOTER_PEN for txt, cnt in repeated[:3]: issues.append((0, f"{label} ripetitivo: \"{txt[:45]}\" ({cnt} volte)")) _check_repetition(first_lines, "intestazione") _check_repetition(last_lines, "piè di pagina") # ── Score ────────────────────────────────────────────────────────────── score = max(0, round(100 - deductions)) # ── Riepilogo ────────────────────────────────────────────────────────── pages_with_issues = len({p for p, _ in issues if p > 0}) out() out(f"Score: {score}/100") out(f"Pagine totali: {n_pages}") out(f"Pagine con problemi: {pages_with_issues}") out() if issues: global_issues = [(p, d) for p, d in issues if p == 0] page_issues = sorted([(p, d) for p, d in issues if p > 0]) for _, desc in global_issues: out(f" ⚠️ {desc}") for page_num, desc in page_issues: out(f" Pagina {page_num:>4}: {desc}") else: out(" Nessun problema rilevato.") out() # ── Prossimi passi ───────────────────────────────────────────────────── out("PROSSIMI PASSI:") if score >= 70: out(" → conversione con marker funzionerà bene") elif score >= 40: out(" → conversione possibile, attendi più errori nella revisione") else: out(" → qualità bassa — valuta una fonte PDF migliore") attention_pages = sorted({p for p, _ in issues if p > 0}) if attention_pages: sample = ", ".join(str(p) for p in attention_pages[:10]) if len(attention_pages) > 10: sample += f" … e altre {len(attention_pages) - 10}" out(f" → attenzione alle pagine {sample} nella revisione manuale") out() _maybe_save(lines, path, save) def _maybe_save(lines: list, pdf_path: Path, save: bool) -> None: if not save: return script_dir = Path(__file__).parent out_file = script_dir / f"{pdf_path.stem}_step1_report.txt" out_file.write_text("\n".join(lines), encoding="utf-8") print(f"Report salvato in: {out_file}") if __name__ == "__main__": project_root = Path(__file__).parent.parent sources_dir = project_root / "sources" if not sources_dir.exists(): print(f"Errore: cartella sources/ non trovata in {project_root}") sys.exit(1) pdfs = sorted(sources_dir.glob("*.pdf")) if not pdfs: print(f"Errore: nessun PDF trovato in {sources_dir}") sys.exit(1) for pdf in pdfs: inspect_pdf(str(pdf), save=True) if len(pdfs) > 1: print("-" * 50)