{"record":{"id":"650727e5877cc347","repo":"MadsLorentzen/ai-job-search","slug":"text-layer-has-len-normalized-character-s-exp","errorCode":null,"errorMessage":"text layer has {len(normalized)} character(s); expected at least {min_chars} (extractor: {extractor})","messagePattern":"text layer has (.+?) character\\(s\\); expected at least (.+?) \\(extractor: (.+?)\\)","errorType":"exception","errorClass":"VerificationError","httpStatus":null,"severity":"error","filePath":"tools/verify_pdf.py","lineNumber":118,"sourceCode":"        try:\n            dump_path.parent.mkdir(parents=True, exist_ok=True)\n            dump_path.write_text(\n                extracted_text if extracted_text.endswith(\"\\n\") else extracted_text + \"\\n\",\n                encoding=\"utf-8\",\n            )\n        except OSError as exc:\n            raise VerificationError(\n                f\"could not write --dump-text to {dump_path}: {exc}\"\n            ) from exc\n\n    if expected_pages is not None and actual_pages != expected_pages:\n        raise VerificationError(\n            f\"expected {expected_pages} page(s), found {actual_pages} (extractor: {extractor})\"\n        )\n\n    normalized = normalize_text(extracted_text)\n    if len(normalized) < min_chars:\n        raise VerificationError(\n            f\"text layer has {len(normalized)} character(s); expected at least {min_chars} \"\n            f\"(extractor: {extractor})\"\n        )\n\n    for required in required_text:\n        if normalize_text(required) not in normalized:\n            raise VerificationError(\n                f\"text layer is missing required text: {required!r} (extractor: {extractor})\"\n            )\n    return extractor, extracted_text, actual_pages\n\n\ndef build_parser():\n    parser = argparse.ArgumentParser(\n        description=\"Verify a PDF's page count and ATS-readable text layer.\"\n    )\n    parser.add_argument(\"pdf\", type=Path, help=\"PDF file to verify\")\n    parser.add_argument(\"--pages\", type=int, help=\"required exact page count\")","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/MadsLorentzen/ai-job-search/blob/79cd383e58f0af7948c7c6462a3a289e9b67421e/tools/verify_pdf.py#L100-L136","documentation":"Raised when the normalized text extracted from the PDF is shorter than the min_chars threshold (default 1). This catches scanned/image-only PDFs or documents whose text layer is empty, since normalize_text collapses whitespace before measuring length.","triggerScenarios":"Calling verify_pdf(path, min_chars=N) on a PDF with no or tiny extractable text: image-only scans, PDFs where text is rendered as glyphs/outlines, password-protected files whose text extraction returns nothing, or a min_chars set higher than the document's actual character count.","commonSituations":"A pipeline that generates PDFs via a print-to-PDF of images produces no text layer; OCR step missing; extractor fallback not installed so text comes back empty; min_chars tuned for English text applied to a short localized document.","solutions":["Inspect the PDF with `pdftotext file.pdf -` to confirm what text is extractable","If the PDF is scanned images, add an OCR step (ocrmypdf --force-ocr) before verification","Lower min_chars to a realistic threshold for the document","If text should exist, fix the generator (embed real fonts/text instead of outlined or rasterized text)"],"exampleFix":"# before\nverify_pdf(scan.pdf, min_chars=1000)  # image-only scan, no text layer\n# after\nsubprocess.run(['ocrmypdf', '--force-ocr', 'scan.pdf', 'scan_ocr.pdf'], check=True)\nverify_pdf('scan_ocr.pdf', min_chars=1000)","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef has_text_layer(pdf: str, min_chars: int = 1) -> bool:\n    out = subprocess.run(['pdftotext', pdf, '-'], capture_output=True, text=True)\n    return len(' '.join(out.stdout.split())) >= min_chars\n\nif not has_text_layer(pdf, min_chars):\n    subprocess.run(['ocrmypdf', '--force-ocr', pdf, pdf], check=True)","typeGuard":null,"tryCatchPattern":"try:\n    verify_pdf(pdf, min_chars=1000)\nexcept VerificationError as e:\n    if 'character(s)' in str(e):\n        run_ocr(pdf)  # then retry verification","preventionTips":["OCR scanned PDFs (ocrmypdf --force-ocr) before verifying text","Set min_chars based on realistic document length","Check pdftotext output manually when adding new PDF sources"],"tags":["pdf","text-extraction","ocr","min-chars","verification"],"backgroundTag":null,"analyzedSha":"79cd383e58f0af7948c7c6462a3a289e9b67421e","analyzedAt":"2026-08-27T21:51:12.330Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}