Files
davide e78c404211 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>
2026-05-20 16:07:40 +02:00

114 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Parametri della pipeline chunks: chunker.py (+ md_optimizer interno) + verify/fix.
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.
"""
# ─── Stage 1 — md_optimizer (pulizia Markdown) ───────────────────────────────
# 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