feat(chunks): pipeline unificata Stage 1+2 con md_optimizer
chunker.py ora esegue in sequenza:
- Stage 1 (md_optimizer.py): _content_list_v2.json + _model.json → _clean.md
con pulizia TOC, frontespizio, sommari interni, merge titoli capitolo
- Stage 2: _clean.md → chunks.json (paragraph-overlap, atomici tabelle/liste)
config.py esteso con CHAPTER_PREFIX_PATTERNS, SOMMARIO_PATTERNS,
MODEL_SKIP_LABELS, MODEL_ABSTRACT_LABELS, MIN_CONTENT_CHARS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+106
-81
@@ -1,88 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Parametri di configurazione della pipeline di chunking.
|
||||
Parametri della pipeline chunks: chunker.py (+ md_optimizer interno) + verify/fix.
|
||||
|
||||
Modifica questo file per cambiare il comportamento di chunker.py,
|
||||
verify_chunks.py e fix_chunks.py senza toccare il codice applicativo.
|
||||
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.
|
||||
"""
|
||||
|
||||
# ─── Grandezza target dei chunk ───────────────────────────────────────────────
|
||||
#
|
||||
# TARGET_CHARS è la dimensione ideale a cui il chunker mira.
|
||||
# CHUNK_TOLERANCE è la tolleranza relativa (es. 0.25 = ±25%).
|
||||
#
|
||||
# range accettabile = [TARGET × (1 − TOL), TARGET × (1 + TOL)]
|
||||
#
|
||||
# Con TARGET=600 e TOL=0.25 → ogni chunk sarà tra 450 e 750 char,
|
||||
# il più vicino possibile a 600, terminando sempre su un confine di frase.
|
||||
#
|
||||
TARGET_CHARS = 300
|
||||
CHUNK_TOLERANCE = 0.25
|
||||
# ─── Stage 1 — md_optimizer (pulizia Markdown) ───────────────────────────────
|
||||
|
||||
# ─── Overlap ──────────────────────────────────────────────────────────────────
|
||||
|
||||
# Numero di frasi ripetute all'inizio del chunk successivo per preservare
|
||||
# il contesto tra chunk adiacenti della stessa sezione.
|
||||
OVERLAP_SENTENCES = 1
|
||||
|
||||
# ─── Soglie di validazione ────────────────────────────────────────────────────
|
||||
|
||||
# fix_chunks.py spezza un chunk "too_long" solo se supera upper × questo fattore.
|
||||
# Es. upper=750, fattore=1.5 → split solo per chunk > 1125 char.
|
||||
# Chunk in [upper, upper×fattore] restano come warning non bloccanti.
|
||||
SPLIT_THRESHOLD_FACTOR = 1.5
|
||||
|
||||
MATH_SYMS_MIN = 3 # min. simboli math per declassare incomplete → incomplete_math
|
||||
|
||||
# ─── Pattern e formato ────────────────────────────────────────────────────────
|
||||
|
||||
SENTENCE_SPLIT_PATTERN = r"(?<=[.!?»])\s+"
|
||||
PREFIX_TEMPLATE = "[{sezione} > {titolo}]"
|
||||
|
||||
# ─── Protezione contenuti speciali ────────────────────────────────────────────
|
||||
|
||||
# Se True, un blocco prevalentemente tabella Markdown (≥50% righe |…|)
|
||||
# viene emesso come chunk atomico senza sentence-splitting.
|
||||
PROTECT_TABLES = True
|
||||
|
||||
# Riservato — blocchi LaTeX non spezzabili (implementazione futura).
|
||||
PROTECT_MATH = True
|
||||
|
||||
# ─── Fix behavior ─────────────────────────────────────────────────────────────
|
||||
|
||||
# Numero massimo di iterazioni del loop fix → verify → fix.
|
||||
# Con 1 si ottiene il comportamento originale (fix singolo senza re-verifica).
|
||||
FIX_MAX_ITERATIONS = 3
|
||||
|
||||
# ─── Override per strategia ───────────────────────────────────────────────────
|
||||
#
|
||||
# Sovrascrivono TARGET_CHARS / CHUNK_TOLERANCE / OVERLAP_SENTENCES
|
||||
# per la specifica strategia indicata in structure_profile.json.
|
||||
# Chiavi riconosciute: "target_chars", "tolerance", "overlap".
|
||||
#
|
||||
STRATEGY_OVERRIDES: dict[str, dict] = {
|
||||
"h3_aware": {
|
||||
# Documenti strutturati H2→H3: chunk medi, overlap moderato.
|
||||
"target_chars": 600,
|
||||
"tolerance": 0.25,
|
||||
"overlap": 2,
|
||||
},
|
||||
"h2_paragraph_split": {
|
||||
# Documenti piatti (solo H2): chunk più ampi, overlap ridotto.
|
||||
"target_chars": 800,
|
||||
"tolerance": 0.25,
|
||||
"overlap": 1,
|
||||
},
|
||||
"paragraph": {
|
||||
# Documenti senza header significativi: chunk più corti.
|
||||
"target_chars": 500,
|
||||
"tolerance": 0.30,
|
||||
"overlap": 1,
|
||||
},
|
||||
"sliding_window": {
|
||||
# Testo lineare/narrativo: finestre ampie, overlap generoso.
|
||||
"target_chars": 800,
|
||||
"tolerance": 0.25,
|
||||
"overlap": 3,
|
||||
},
|
||||
# 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).
|
||||
MAX_CHARS: int = 1200
|
||||
|
||||
# Lunghezza minima attesa (warning in verify_chunks, non blocker).
|
||||
MIN_CHARS: int = 80
|
||||
|
||||
# Frasi di overlap: l'ultima frase del chunk N viene preposta al chunk N+1.
|
||||
OVERLAP_SENTENCES: int = 1
|
||||
|
||||
# Regex per rilevare il confine di fine frase per lo split.
|
||||
# Split solo prima di lettera maiuscola o virgolette — evita split su abbreviazioni.
|
||||
SENTENCE_SPLIT_RE: str = r"(?<=[.!?»])\s+(?=[A-ZÀÈÉÌÒÙ\"])"
|
||||
|
||||
|
||||
# ─── verify_chunks.py / fix_chunks.py ─────────────────────────────────────────
|
||||
|
||||
# fix_chunks spezza un chunk too_long solo se supera MAX_CHARS × questo fattore.
|
||||
SPLIT_THRESHOLD_FACTOR: float = 1.5
|
||||
|
||||
MATH_SYMS_MIN: int = 3
|
||||
PROTECT_TABLES: bool = True
|
||||
PROTECT_MATH: bool = True
|
||||
FIX_MAX_ITERATIONS: int = 3
|
||||
|
||||
Reference in New Issue
Block a user