refactor(chunks): rimuove Stage 1 e riscrive config per MD pulito

Eliminati md_optimizer.py e fix_chunks.py: la pipeline non parte più
da _content_list_v2.json ma da un .md già pulito in sources/.

config.py ridotto da 114 a 64 righe: rimossi tutti i parametri MinerU
(NOISE_TYPES, FRONTMATTER_HEADINGS, MODEL_SKIP_LABELS, ecc.) e aggiunti
i parametri effettivamente utili al chunking: SKIP_HEADINGS,
SKIP_PRE_HEADING, MERGE_SHORT_PARAGRAPHS, ATOMIC_TYPES, CONTEXT_DEPTH.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 14:18:44 +02:00
parent aabe4d168c
commit ce4c3e5c87
3 changed files with 47 additions and 1033 deletions
+47 -96
View File
@@ -1,113 +1,64 @@
#!/usr/bin/env python3
"""
Parametri della pipeline chunks: chunker.py (+ md_optimizer interno) + verify/fix.
Parametri della pipeline di chunking.
La pipeline è unificata: chunker.py esegue prima l'ottimizzazione del Markdown
(Stage 1, equivalente a md_optimizer.py) e poi il chunking (Stage 2).
I parametri sono pensati per essere generici rispetto agli output di MinerU:
i file *_content_list_v2.json e *_model.json hanno sempre la stessa struttura,
indipendentemente dal documento sorgente.
Input atteso: sources/<stem>/<stem>.md — Markdown già pulito e ben strutturato.
"""
# ─── Stage 1 — md_optimizer (pulizia Markdown) ───────────────────────────────
# ─── Dimensione chunk ─────────────────────────────────────────────────────────
# Tipi MinerU da ignorare completamente.
NOISE_TYPES: set[str] = {
"page_header", "page_number", "page_footer", "index", "page_aside_text",
}
# Paragrafi promossi a H3 se testo ≤ H3_MAX_CHARS e matcha H3_DETECTION_RE.
# Regex generica: riga che inizia con numero seguito da punto e spazio.
# Per disabilitare la promozione a H3: imposta H3_DETECTION_RE = r"(?!)"
H3_DETECTION_RE: str = r"^\d+\.\s+\S"
H3_MAX_CHARS: int = 120
# Se True, i blocchi immagine non vengono inclusi nel Markdown.
SKIP_IMAGES: bool = True
# Heading le cui sezioni vengono rimosse completamente (titolo + tutto il contenuto).
# Match case-insensitive: il testo dell'heading deve essere uguale o iniziare
# con uno dei valori seguenti.
# Nota: specifici per documento — impostare set vuoto per documenti non italiani
# o senza sezioni di frontmatter note.
FRONTMATTER_HEADINGS: set[str] = {
"sommario",
"indice",
"autori",
"abbreviazioni",
"atti normativi",
"specifici provvedimenti normativi",
"abbreviazioni generiche",
}
# Numero minimo di heading consecutivi senza testo per riconoscere un TOC.
# Abbassare se il documento ha molti capitoli corti senza sottosezioni.
MIN_TOC_HEADINGS: int = 5
# Caratteri minimi di testo reale sotto un heading perché sia considerato
# "contenuto vero" (non frontespizio/copyright).
# Impostare >= lunghezza massima del testo di copyright/copertina nel documento.
MIN_CONTENT_CHARS: int = 2500
# Pattern per riconoscere prefissi di capitolo in blocchi paragraph.
# MinerU talvolta produce il numero/identificatore di capitolo come paragraph
# anziché come title L1 (comportamento non uniforme). Questi pattern permettono
# di bufferizzare tali paragrafi e fonderli col titolo L1 successivo.
# Impostare lista vuota [] per disabilitare.
CHAPTER_PREFIX_PATTERNS: list[str] = [
r"^(CAPITOLO|PARTE)\s+(\d+|[IVXLCDM]+)\b", # italiano
r"^(CHAPTER|PART|SECTION)\s+(\d+|[IVXLCDM]+)\b", # inglese
r"^(CHAPITRE|PARTIE)\s+(\d+|[IVXLCDM]+)\b", # francese
r"^(KAPITEL|TEIL)\s+(\d+|[IVXLCDM]+)\b", # tedesco
]
# Pattern testuali (regex) per riconoscere paragrafi "sommario interno" da saltare.
# Usati come fallback quando _model.json non assegna label "abstract".
# Generici: un pattern per paragrafo che inizia con indice/sommario di sezione.
# Per disabilitare: impostare lista vuota [].
SOMMARIO_PATTERNS: list[str] = [
r"^SOMMARIO\s*:", # italiano
r"^SUMMARY\s*:", # inglese
r"^RÉSUMÉ\s*:", # francese
r"^ÍNDICE\s*:", # spagnolo/portoghese
r"^INHALT\s*:", # tedesco
]
# ─── _model.json label sets ───────────────────────────────────────────────────
# Label di layout da saltare completamente.
MODEL_SKIP_LABELS: set[str] = {
"header", "number", "footer_image", "ocr_text", "aside_text",
}
# Label che identifica indici/sommari interni (da saltare).
MODEL_ABSTRACT_LABELS: set[str] = {"abstract"}
# ─── Stage 2 — chunker ────────────────────────────────────────────────────────
# Lunghezza massima di un chunk (caratteri, prefisso incluso).
# Paragrafi che superano questo limite vengono spezzati a confine di frase.
# Una singola frase che supera MAX_CHARS viene emessa intera (non si spezza mai).
# Caratteri massimi per chunk (prefisso di contesto incluso).
# Paragrafi più lunghi vengono spezzati a confine di frase.
# Una singola frase che supera MAX_CHARS non viene mai spezzata.
MAX_CHARS: int = 1200
# Lunghezza minima attesa (warning in verify_chunks, non blocker).
# Soglia minima attesa (usata da verify_chunks come warning, non blocca).
MIN_CHARS: int = 80
# Frasi di overlap: l'ultima frase del chunk N viene preposta al chunk N+1.
OVERLAP_SENTENCES: int = 1
# ─── Spezzatura frasi ─────────────────────────────────────────────────────────
# Regex per rilevare il confine di fine frase per lo split.
# Regex per rilevare il confine di fine frase.
# Split solo prima di lettera maiuscola o virgolette — evita split su abbreviazioni.
SENTENCE_SPLIT_RE: str = r"(?<=[.!?»])\s+(?=[A-ZÀÈÉÌÒÙ\"])"
# ─── Blocchi atomici ──────────────────────────────────────────────────────────
# ─── verify_chunks.py / fix_chunks.py ─────────────────────────────────────────
# Blocchi Markdown che non vengono mai spezzati, anche se superano MAX_CHARS.
ATOMIC_TYPES: set[str] = {"table", "code", "list"}
# fix_chunks spezza un chunk too_long solo se supera MAX_CHARS × questo fattore.
SPLIT_THRESHOLD_FACTOR: float = 1.5
# ─── Contesto heading ─────────────────────────────────────────────────────────
MATH_SYMS_MIN: int = 3
PROTECT_TABLES: bool = True
PROTECT_MATH: bool = True
FIX_MAX_ITERATIONS: int = 3
# Profondità massima del percorso heading incluso nel prefisso di ogni chunk.
# 1 = solo H1, 2 = H1 > H2, 3 = H1 > H2 > H3.
CONTEXT_DEPTH: int = 3
# ─── Sezioni da escludere ────────────────────────────────────────────────────
# Heading (case-insensitive) le cui sezioni vengono saltate completamente.
# Il match è su prefisso: "indice" salta anche "Indice delle figure".
# Lasciare vuoto per non escludere nulla.
SKIP_HEADINGS: set[str] = {
"indice",
"sommario",
"bibliografia",
"ringraziamenti",
"abbreviazioni",
}
# Se True, salta il contenuto che precede il primo heading (frontespizio, copertina).
SKIP_PRE_HEADING: bool = True
# ─── Merge paragrafi corti ────────────────────────────────────────────────────
# Paragrafi consecutivi più corti di MIN_CHARS vengono fusi fino a raggiungerlo,
# purché appartengano allo stesso contesto heading.
MERGE_SHORT_PARAGRAPHS: bool = True
# ─── verify_chunks ────────────────────────────────────────────────────────────
# Numero minimo di simboli matematici perché un chunk incompleto sia classificato
# come "matematico" (warning meno grave rispetto a frase spezzata normale).
MATH_SYMS_MIN: int = 3
PROTECT_TABLES: bool = True
PROTECT_MATH: bool = True