Skip to content

fix(pipeline): robustly surface scanned-PDF OcrError on the SSE /process path (#8) - #11

Closed
aircode610 wants to merge 1 commit into
fix/issue-8-scanned-pdf-extractionfrom
fix/issue-8-sse-orchestrator-ocrerror
Closed

fix(pipeline): robustly surface scanned-PDF OcrError on the SSE /process path (#8)#11
aircode610 wants to merge 1 commit into
fix/issue-8-scanned-pdf-extractionfrom
fix/issue-8-sse-orchestrator-ocrerror

Conversation

@aircode610

Copy link
Copy Markdown
Owner

Summary

Follow-up hardening for #8 (500 on /api/letters upload when a PDF has no text layer), stacked on #10.

#10 fixed the leaf functions (extraction.py, ai/react_agent/ocr.py) and added the orchestrator's OcrError handling — but two things were left thin on the SSE /process path, which #10 itself calls "the path the production upload flow actually uses":

  1. The orchestrator detected OcrError with a fragile type(exc).__name__ == "OcrError" string match in its catch-all.
  2. No test exercised the orchestrator wiring at all — only the leaf functions were covered. A refactor of the catch block could silently reintroduce the 500 with all tests still green.

Changes

  • backend/app/pipeline/orchestrator.py
    • Import OcrError in the existing lazy-import block (alongside extract_text_from_image, so no new eager import of the AI stack).
    • Detect it via isinstance(exc, OcrError) instead of the brittle __name__ string match — survives subclassing and can't be silently broken by a rename/refactor.
  • backend/tests/test_scanned_pdf_graceful.py
    • New OrchestratorSsePathTest: drives process_letter_stream with the OCR stage raising OcrError (DB + agent import mocked) and asserts the stream emits exactly one graceful SSE error event carrying PDF_RENDER_FAILED + the user-facing message — and never raises.

Why it matters

This closes the test-coverage gap on the exact production path the issue describes. Verified the test is a real guard: temporarily bypassing the OcrError branch makes it fail (the stream falls back to the generic EXTRACTION_FAILED), and it passes with the fix.

Testing

  • python backend/tests/test_scanned_pdf_graceful.py7/7 passing (was 6/6).
  • ruff check + ruff format --check — clean.

Refs #8. Builds on #10.

Note: targeted at the #10 branch since it depends on that PR's orchestrator OcrError handling. Re-base to main once #10 merges (or squash both together).

The SSE /process orchestrator is the production upload->process flow for
issue #8: a scanned image-only PDF (no text layer) makes the OCR stage
raise OcrError, which must reach the client as a graceful 'error' event
(PDF_RENDER_FAILED), never a crashed stream / raw 500.

- Detect OcrError via isinstance instead of a fragile type(exc).__name__
  string match, so the guard survives subclassing and can't be silently
  broken by a refactor. OcrError is imported in the existing lazy-import
  block alongside extract_text_from_image.
- Add the first regression test that exercises the orchestrator wiring
  (process_letter_stream), asserting an OcrError becomes exactly one
  graceful SSE error event with code PDF_RENDER_FAILED + its user-facing
  message. Previously only the leaf functions (extraction.py / ocr.py)
  were covered, leaving the production SSE path untested.

Full suite: 7/7 passing; ruff check + format clean.
@aircode610

Copy link
Copy Markdown
Owner Author

Closing in favour of #13 which has been merged.

@aircode610 aircode610 closed this Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant