feat(extract,cli): typography as a PDF heading source and OCR behind an optional group, both off
A PDF carries no notion of a heading -- a heading in a PDF is a typographic
fact -- so the text stream `pdfplumber` hands the segment proposer has already
thrown away the only evidence there was. The `docx` path never had that problem:
the converter emits ATX headings and `_ATX` cuts on them. Two readers close the
gap, and both are OFF.
`--pdf-headings font` infers a heading from the conjunction this repository
already measured (size above the document's character-weighted body median AND
a bold font name, recall 1.000 / precision 0.846) and emits it as ATX in the
SAME markdown the office path produces, so `_ATX` applies unchanged and no
PDF-only heading grammar exists.
It stays off BY MEASUREMENT, and the measurement is the point of the round:
against the operator's unit worksheet it takes `pdf` from 2 of 8 to 0 of 8,
losing two exact matches. The mechanism of the loss is stated rather than
guessed -- on those documents the outline rule already recovers the document's
own numbered chapters, so a second heading source can only add. Whole-corpus
screen: 25 of 32 `pdf` change, 0 of 5 `docx`, 0 of 2 `xlsx`. The default bundle
is byte-identical before and after this commit (`diff -r`, exit 0).
`--ocr` reads a page as an image when its own text never arrived: empty, or
`(cid:N)` placeholder codes at or above a threshold READ OFF a measured
distribution -- 834 pages over 32 files, 818 at exactly 0.0 and 16 at 0.93 or
above, nothing in between. On the one corpus document with the failure: 95.07 %
cid to 0 %, 44 to 2561 words of four or more letters, 17 to 18 pages with text.
Its engine is an optional dependency group and never a runtime dependency; a
packaging test pins both halves, and without the group every affected file is a
coded rejection (`extractor_ocr_group_missing`) rather than a crash.
Also corrects two stale published facts found while measuring: the README still
said two segmentation rules were on by default after `f6fea13` made it three,
and CLAUDE.md's K2 digest named the round-3 default. The current default is
492 concepts / 944 files, `bdefa679...`.
Report: docs/2026-09-08-k3-runde4-pdf-skrift-og-ocr.md
Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
parent
f6fea13299
commit
53d5c74c96
15 changed files with 1394 additions and 28 deletions
|
|
@ -88,6 +88,36 @@ okf = "llm_ingestion_okf.cli:main"
|
|||
# third entry cannot arrive unexamined.
|
||||
extract = ["pdfplumber>=0.11.10,<0.12", "pypandoc-binary==1.17"]
|
||||
|
||||
# The OCR engine for `--ocr`, and NEVER a runtime dependency. It is a separate
|
||||
# group from `extract` rather than three more entries in it, because it buys
|
||||
# something categorically different: `extract` decides which file types can be
|
||||
# read at all, while this one only changes how a PDF page is read when the
|
||||
# page's own text never arrived. A consumer who installs `[extract]` gets every
|
||||
# file type; a consumer who never meets a scanned document should never carry
|
||||
# an inference runtime.
|
||||
#
|
||||
# WHY rapidocr ON onnxruntime, and why not the obvious alternative. Docling was
|
||||
# measured first and is OUT on a platform fact, not a preference: it needs
|
||||
# torch, and torch stopped publishing macOS x86_64 wheels after 2.2.2, with no
|
||||
# `transformers` version inside Docling's own window that works against that
|
||||
# one (4 tried, 2026-09-08). rapidocr on onnxruntime installs and runs on this
|
||||
# machine, and it carries its ONNX models inside its own wheel, so `--ocr`
|
||||
# needs no network at run time -- which matters here more than usual, since
|
||||
# this library's network gate is an explicit per-run opt-in and an engine that
|
||||
# downloaded a model on first use would walk straight through it.
|
||||
#
|
||||
# `pypdfium2` is named although `[extract]` already reaches it through
|
||||
# pdfplumber: the OCR path RENDERS a page before reading it, and the renderer
|
||||
# is a dependency of that path rather than a happy accident of another one.
|
||||
#
|
||||
# The pins are ranges rather than exact versions, and that is a weaker promise
|
||||
# than `[extract]` makes on purpose: OCR output is a model's reading of an
|
||||
# image, so it is deterministic within one model version and NOT across one,
|
||||
# and no range can make it otherwise. A bundle built with `--ocr` is
|
||||
# reproducible against the versions it was built with, which is stated in the
|
||||
# report rather than implied by a pin.
|
||||
ocr = ["rapidocr>=3.9,<4", "onnxruntime>=1.20,<2", "pypdfium2>=4,<6"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["pytest>=8", "mypy>=1.14", "ruff>=0.9"]
|
||||
|
||||
|
|
@ -129,6 +159,15 @@ ignore_missing_imports = true
|
|||
module = ["pypandoc", "pypandoc.*"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
# `rapidocr` ships no py.typed marker either, and it is behind an OPTIONAL
|
||||
# group -- so on a machine without that group installed the import does not
|
||||
# resolve at all. Only `_ocr_reader` imports it, and the only value that
|
||||
# crosses back is coerced to `str` there, the same discipline as the two
|
||||
# overrides above.
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["rapidocr", "rapidocr.*"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
# Install CHANNEL for the guard, which is not on a package index yet. It is
|
||||
# uv-specific, and it reaches further than a dev-only setting: a consumer
|
||||
# installing this package from git WITH UV picks the guard up from this tag
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue