]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commit
Fix: consolidate born-digital PDF detection between archive decision and OCR (#13409) dev
authorTrenton H <797416+stumpylog@users.noreply.github.com>
Wed, 29 Jul 2026 19:41:09 +0000 (12:41 -0700)
committerGitHub <noreply@github.com>
Wed, 29 Jul 2026 19:41:09 +0000 (12:41 -0700)
commit350684cd6b29f01681b9d0fa849863ba4d470fe6
treef2190bd63d70ef94bc74cd0cf2b5b014064cb695
parent668fa77428b3b2ad3788202342f49ad7ba0d5c88
Fix: consolidate born-digital PDF detection between archive decision and OCR (#13409)

* Fix: unify born-digital PDF detection between archive decision and OCR

should_produce_archive() and RasterisedDocumentParser.parse() each
reimplemented the "does this PDF have real text" check independently,
using different normalization of pdftotext output. Raw pdftotext output
can be non-empty (whitespace/form-feed layout padding) even when there
is no real content, so the two checks could disagree: consumer.py
treated a tagged-but-textless PDF as born-digital and skipped the
archive, while the parser's own (stricter, normalized) check found no
text and ran OCR anyway, leaving the document with no archive despite
real OCR text (GH #13387).

Both call sites now share one predicate, pdf_born_digital_text() in
paperless/parsers/utils.py, so they can no longer drift apart.

* Fix: restore extract_text seam for born-digital detection in parse()

parse() had switched to calling pdf_born_digital_text() directly for
its initial text/born-digital check, bypassing the parser's own
extract_text instance method. That broke test mockability (tests patch
tesseract_parser.extract_text to control the born-digital decision)
and caused CI failures with mismatched OCR call counts and text.

Split pdf_born_digital_text() into is_born_digital_text(text, path,
log) - a pure decision function - and a thin pdf_born_digital_text()
wrapper for callers without text in hand (consumer.should_produce_archive).
parse() now extracts via self.extract_text(None, document_path) and
passes the result to is_born_digital_text(), restoring the seam with
no change to production behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Cleanup: simplify born-digital detection, close #13387 test gap

Simplification pass over the born-digital detection consolidation:
- is_born_digital_text(): drop the has_text temp for an early return.
- consumer.py: standardize the archive-decision log lines on plain
  hyphens (was a mix of em-dash and hyphen) and hoist the duplicated
  text_length computation.
- Parametrize TestPdfBornDigitalText instead of four near-identical
  tests.

Code review follow-up: the existing tests only ever exercised
pdf_born_digital_text() through mocks, so the actual #13387 scenario
(a tagged PDF whose only "text" is layout padding) was never checked
against real pdftotext/pikepdf output - a regression in the
normalize-before-decide logic itself would have gone undetected.
Moved tagged_no_text_pdf_file from parsers/conftest.py up to the
shared paperless/tests/conftest.py (it was previously only visible to
tests under parsers/) and added a non-mocked regression test against
the real sample file.

Also fixed two stale comments in test_consumer.py referencing a
_extract_text_for_archive_check helper that no longer exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
src/documents/consumer.py
src/documents/tests/test_consumer.py
src/documents/tests/test_consumer_archive.py
src/paperless/parsers/tesseract.py
src/paperless/parsers/utils.py
src/paperless/tests/conftest.py
src/paperless/tests/parsers/test_tesseract_parser.py
src/paperless/tests/samples/tesseract/tagged-but-no-text.pdf [new file with mode: 0644]
src/paperless/tests/test_parser_utils.py