import re import ftfy import nltk from nltk.tokenize import PunktSentenceTokenizer from .constants import RE_LEGAL_PREFIX_STR # Ensure required NLTK tokenizer resources are downloaded for pkg in ['punkt', 'punkt_tab']: try: nltk.data.find(f'tokenizers/{pkg}') except LookupError: nltk.download(pkg, quiet=True) RE_LEGAL_PREFIX = re.compile(RE_LEGAL_PREFIX_STR, re.IGNORECASE) RE_DEHYPHEN = re.compile(r'([a-zA-Z]{2,})-\s*\n\s*([a-zA-Z]{2,})') RE_PAGE_STRIP = re.compile(r'(?im)^\s*(page\s+\d+(\s+of\s+\d+)?|[\-—]\s*\d+\s*[\-—]|\[\s*\d+\s*\])\s*$\n?') def clean_text_pipeline(raw_text: str) -> str: """Deterministic cleaning pipeline for legal documents and PDF copies: 1. Repairs encoding artifacts and Mojibake (via ftfy). 2. Normalizes line endings (CRLF and old CR -> LF) and removes form feeds (\x0c). 3. De-hyphenates words fractured across margin line wraps (e.g. 'arbi-\\n tration' -> 'arbitration'). 4. Strips standalone page numbers and PDF pagination markers. 5. Isolates section headings and date metadata with double newlines. 6. Normalizes soft line wraps into single spaces while preserving paragraph breaks. 7. Normalizes consecutive whitespace and tabs. """ if not raw_text: return "" text = ftfy.fix_text(raw_text) text = text.replace('\r\n', '\n').replace('\r', '\n').replace('\x0c', '\n') # De-hyphenate broken words across line wraps text = RE_DEHYPHEN.sub(r'\1\2', text) # Strip standalone page markers text = RE_PAGE_STRIP.sub('', text) # Ensure section headers and date metadata are separated by double newline text = re.sub(r'\n+((?:last\s+updated|effective\s+date|date\s+of\s+revision)[\s:])', r'\n\n\1', text, flags=re.IGNORECASE) text = re.sub(r'\n+(\d+(\.\d+)*\.?\s+[A-Z])', r'\n\n\1', text) text = re.sub(r'\n+((?:Section|Article|Clause)\s+[A-Z0-9]+)', r'\n\n\1', text, flags=re.IGNORECASE) # Collapse soft line wraps within paragraphs into single spaces text = re.sub(r'(?