2026-04-16 15:35:42 +02:00
|
|
|
# conversione — PDF → Markdown pulito
|
|
|
|
|
|
|
|
|
|
Pipeline automatica che trasforma un PDF grezzo in Markdown strutturato e
|
|
|
|
|
pronto per la suddivisione in chunk. Gestisce l'intero processo: validazione
|
|
|
|
|
del PDF, estrazione del testo, pulizia strutturale e analisi della struttura
|
|
|
|
|
del documento.
|
|
|
|
|
|
|
|
|
|
## Requisiti
|
|
|
|
|
|
|
|
|
|
### Python
|
|
|
|
|
```
|
|
|
|
|
pip install opendataloader-pdf pdfplumber
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Java 11+
|
|
|
|
|
`opendataloader-pdf` richiede Java sul PATH. Se non è installato:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Ubuntu / Debian / WSL
|
|
|
|
|
sudo apt install default-jdk
|
|
|
|
|
|
|
|
|
|
# Verifica
|
|
|
|
|
java -version
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Download alternativo: https://adoptium.net/
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Utilizzo
|
|
|
|
|
|
|
|
|
|
Posiziona il PDF in `sources/<nome>.pdf`, poi:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Singolo documento
|
|
|
|
|
python conversione/pipeline.py --stem <nome>
|
|
|
|
|
|
|
|
|
|
# Tutti i PDF in sources/
|
|
|
|
|
python conversione/pipeline.py
|
|
|
|
|
|
|
|
|
|
# Forza la riesecuzione (sovrascrive output esistente)
|
|
|
|
|
python conversione/pipeline.py --stem <nome> --force
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Il parametro `--stem` è il nome del file PDF senza estensione.
|
|
|
|
|
Esempio: `sources/analisi1.pdf` → `--stem analisi1`
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Output
|
|
|
|
|
|
|
|
|
|
Per ogni stem vengono prodotti tre file in `conversione/<stem>/`:
|
|
|
|
|
|
|
|
|
|
| File | Descrizione |
|
|
|
|
|
|------|-------------|
|
|
|
|
|
| `raw.md` | Markdown grezzo estratto dal PDF — **non modificare** |
|
|
|
|
|
| `clean.md` | Markdown pulito e strutturato — input per il chunker |
|
2026-04-16 15:53:46 +02:00
|
|
|
| `report.json` | Metriche complete di qualità della conversione |
|
2026-04-16 15:35:42 +02:00
|
|
|
|
2026-04-16 15:53:46 +02:00
|
|
|
### report.json
|
|
|
|
|
|
|
|
|
|
Contiene tutto ciò che serve per valutare la conversione: statistiche
|
|
|
|
|
trasformazioni, struttura rilevata, distribuzione lunghezze sezioni,
|
|
|
|
|
anomalie e problemi residui con esempi.
|
2026-04-16 15:35:42 +02:00
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
{
|
2026-04-16 15:53:46 +02:00
|
|
|
"stem": "dirittoprivato",
|
|
|
|
|
"timestamp": "2026-04-16 15:41",
|
|
|
|
|
"transforms": {
|
|
|
|
|
"n_accenti_corretti": 0,
|
|
|
|
|
"n_dotleader_rimossi": 0,
|
|
|
|
|
"toc_rimosso": false,
|
|
|
|
|
"n_sezioni_numerate": 63,
|
|
|
|
|
"riduzione_pct": 1
|
|
|
|
|
},
|
|
|
|
|
"structure": {
|
|
|
|
|
"livello_struttura": 3,
|
|
|
|
|
"n_h1": 0, "n_h2": 6, "n_h3": 163,
|
|
|
|
|
"lingua_rilevata": "it",
|
|
|
|
|
"strategia_chunking": "h3_aware",
|
|
|
|
|
"avvertenze": []
|
|
|
|
|
},
|
|
|
|
|
"distribution": { "min": 12, "p25": 312, "mediana": 681, "p75": 1197, "max": 6120 },
|
|
|
|
|
"anomalie": {
|
|
|
|
|
"bare_headers": 0,
|
|
|
|
|
"short_sections": 1,
|
|
|
|
|
"long_sections": 39,
|
|
|
|
|
"bare_headers_list": [],
|
|
|
|
|
"short_sections_list": [...],
|
|
|
|
|
"long_sections_list": [...]
|
|
|
|
|
},
|
|
|
|
|
"residui": {
|
|
|
|
|
"backtick": 0, "dotleader": 0, "url": 0, "immagini": 0,
|
|
|
|
|
"backtick_esempi": []
|
|
|
|
|
}
|
2026-04-16 15:35:42 +02:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-04-16 15:53:46 +02:00
|
|
|
**`strategia_chunking`** indica come suddividere il documento in chunk:
|
2026-04-16 15:35:42 +02:00
|
|
|
|
|
|
|
|
| Valore | Significato |
|
|
|
|
|
|--------|-------------|
|
|
|
|
|
| `h3_aware` | Documento ricco di sezioni `###` — usa i `###` come boundary |
|
|
|
|
|
| `h2_paragraph_split` | Struttura parziale `##` — suddividi per paragrafo dentro ogni `##` |
|
|
|
|
|
| `paragraph` | Nessuna gerarchia chiara — suddividi per paragrafo |
|
|
|
|
|
| `sliding_window` | Testo piatto — usa finestra scorrevole |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2026-04-16 15:53:46 +02:00
|
|
|
## Validazione batch
|
|
|
|
|
|
|
|
|
|
Dopo aver convertito uno o più documenti, esegui `validate.py` per ottenere
|
|
|
|
|
una tabella di stato su tutti gli stem:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
python conversione/validate.py
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Output di esempio:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
stem h2 h3 strategia bare corte lunghe backtick dotlead url status
|
|
|
|
|
──────────────────────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
analisi1 13 279 h3_aware 0 36 151 10 0 0 ⚠️
|
|
|
|
|
dirittoprivato 6 163 h3_aware 0 1 39 0 0 0 ✅
|
|
|
|
|
nietzsche 4 303 h3_aware 6 104 100 0 0 0 ⚠️
|
|
|
|
|
──────────────────────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
Totale: 3 ✅ 1 ⚠️ 2 ❌ 0
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Legenda colonne:**
|
|
|
|
|
|
|
|
|
|
| Colonna | Significato | Soglia warning |
|
|
|
|
|
|---------|-------------|----------------|
|
|
|
|
|
| `bare` | Header solo-numero senza corpo (`### 1.` vuoto) | ≥ 1 |
|
|
|
|
|
| `corte` | Sezioni con corpo < 150 chars | informativo |
|
|
|
|
|
| `lunghe` | Sezioni con corpo > 1500 chars | ≥ 80 |
|
|
|
|
|
| `backtick` | Backtick `` ` `` residui nel testo | ≥ 1 |
|
|
|
|
|
| `dotlead` | Dot-leader residui (`. . . .`) | ≥ 1 |
|
|
|
|
|
|
|
|
|
|
**Stato:**
|
|
|
|
|
- ✅ nessuna anomalia critica
|
|
|
|
|
- ⚠️ anomalie presenti, documento processabile ma da verificare
|
|
|
|
|
- ❌ struttura non rilevata (`livello_struttura = 0`) o > 50 backtick residui
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
2026-04-16 15:35:42 +02:00
|
|
|
## Cosa fa la pipeline
|
|
|
|
|
|
|
|
|
|
La pipeline esegue quattro fasi in sequenza.
|
|
|
|
|
|
|
|
|
|
### Fase 1 — Validazione
|
|
|
|
|
|
|
|
|
|
Verifica che il PDF esista, non sia vuoto, non sia protetto da password e
|
|
|
|
|
contenga testo digitale estraibile. I PDF scansionati (immagini) non sono
|
|
|
|
|
supportati.
|
|
|
|
|
|
|
|
|
|
### Fase 2 — Estrazione testo
|
|
|
|
|
|
|
|
|
|
Usa `opendataloader-pdf` con l'algoritmo **XY-Cut++** per ricostruire il
|
|
|
|
|
corretto ordine di lettura anche in documenti multi-colonna. Le immagini
|
|
|
|
|
vengono ignorate completamente — il `clean.md` non contiene mai riferimenti
|
|
|
|
|
a immagini.
|
|
|
|
|
|
|
|
|
|
### Fase 3 — Pulizia strutturale
|
|
|
|
|
|
|
|
|
|
Serie di trasformazioni applicate al Markdown grezzo:
|
|
|
|
|
|
|
|
|
|
| Trasformazione | Problema risolto |
|
|
|
|
|
|----------------|-----------------|
|
|
|
|
|
| Rimozione riferimenti immagini | Artefatti `![...]()` lasciati dal convertitore |
|
|
|
|
|
| Fix accenti backtick LaTeX | `` `e``→`è`, ``puo` ``→`può`, ``sar`a``→`sarà` |
|
|
|
|
|
| Rimozione dot-leader TOC | `- 1.1 Titolo . . . . . 42` (voci indice) |
|
|
|
|
|
| Rimozione numerali romani pagina | `i`, `ii`, `iii` su riga isolata (footer LaTeX) |
|
|
|
|
|
| Fix header + body concatenati | `### 11 TitoloCorpo testo...` → header + paragrafo separati |
|
|
|
|
|
| Estrazione header Capitolo inline | `Capitolo 3: IL TITOLO` nel corpo → `## Capitolo 3: ...` |
|
|
|
|
|
| Normalizzazione livelli header | `####`, `#####` → `###` (gerarchia uniforme a 3 livelli) |
|
|
|
|
|
| Rimozione bold negli header | `## **Titolo**` → `## Titolo` |
|
|
|
|
|
| Normalizzazione ALL-CAPS header | `## IL TITOLO` → `## Il titolo` |
|
|
|
|
|
| Rimozione TOC | Blocchi indice/sommario rilevati per keyword |
|
|
|
|
|
| ALL-CAPS standalone → header | Righe in maiuscolo isolate → `## Titolo` |
|
|
|
|
|
| Sezioni numerate → header | `N. Titolo sezione` → `### N.` + corpo |
|
|
|
|
|
| Sezioni con punto → header | `- N. Testo aphorismo...` → `### N.` + corpo |
|
|
|
|
|
| Sezioni lista numerate → header | `- N Titolo Corpo testo...` → `### N. Titolo` + corpo |
|
|
|
|
|
| Unione paragrafi spezzati | Paragrafi tagliati dal salto pagina PDF ricongiunti |
|
|
|
|
|
| Normalizzazione whitespace | Spazi multipli ridotti a singoli |
|
|
|
|
|
| Riduzione righe vuote | Tre o più righe vuote consecutive → due |
|
|
|
|
|
| Rimozione URL watermark | `www.piattaforma.com`, `https://...` su riga isolata |
|
|
|
|
|
| Rimozione header senza corpo | Sezioni vuote e header watermark scartati |
|
|
|
|
|
|
|
|
|
|
> **Rilevamento automatico tipo documento**: se il documento contiene sezioni
|
|
|
|
|
> "Esercizi" (libri di testo accademici), la conversione dei numeri di esercizio
|
|
|
|
|
> in header viene disabilitata automaticamente.
|
|
|
|
|
|
|
|
|
|
### Fase 4 — Analisi struttura
|
|
|
|
|
|
|
|
|
|
Rileva la gerarchia del documento (conteggio `#`/`##`/`###`), la lingua
|
|
|
|
|
(italiano / inglese / sconosciuta), la lunghezza media delle sezioni e
|
|
|
|
|
suggerisce la strategia di chunking ottimale. I risultati sono scritti in
|
|
|
|
|
`structure_profile.json`.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Tipi di documento supportati
|
|
|
|
|
|
|
|
|
|
| Tipo | Esempi | Note |
|
|
|
|
|
|------|--------|------|
|
|
|
|
|
| Testo giuridico / accademico | Manuali, dispense, codici | Header numerati `N.` e `N.N` |
|
|
|
|
|
| Filosofia / saggistica | Aforismi numerati, capitoli | Pattern `- N. testo` |
|
|
|
|
|
| Matematica / LaTeX | Analisi, algebra, fisica | Fix accenti, TOC, numerali romani |
|
|
|
|
|
| Testo generico strutturato | Qualsiasi PDF digitale | Paragrafi e header standard |
|
|
|
|
|
|
|
|
|
|
**Non supportati**: PDF scansionati (solo immagini), PDF protetti da password.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Log di esecuzione
|
|
|
|
|
|
|
|
|
|
Durante l'esecuzione la pipeline stampa le statistiche di ogni trasformazione:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
[3/4] Pulizia strutturale...
|
|
|
|
|
✅ Immagini rimosse: 0
|
|
|
|
|
Accenti corretti: 3701
|
|
|
|
|
Dot-leader rimossi: 53
|
|
|
|
|
Header concat fixati: 0
|
|
|
|
|
TOC rimosso: sì
|
|
|
|
|
ALL-CAPS → ##: 14
|
|
|
|
|
Sezioni → ###: 279
|
|
|
|
|
Paragrafi uniti: 12998
|
|
|
|
|
Riduzione testo: 3%
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Se un documento è già stato convertito, la pipeline lo salta automaticamente.
|
|
|
|
|
Usa `--force` per rieseguire.
|