Skip to content

Backend → doc-intel migration audit

Date: 2026-05-19 Author: investigation triggered by autofill silent-failure debugging Scope: backend code performing document/PDF/OCR work that should live in kwiloai_doc_intelligence

The autofill bug surfaced a broader pattern: backend code calling phantom or local doc-handling paths despite an explicit migration of doc responsibility to the doc-intelligence microservice ("we had memory issue in converting the pdf to images etc so we moved the complete responsibility to doc intelligence"). This document lists the call sites still on the wrong side.

Tier 1 — Silently broken in production

These call sites depend on a /ocr/extract-document-text endpoint that has never existed on doc-intel. They have been silently returning empty data for ~3 months (since commit b538c09e, 2026-02-24). Every caller masks the 404 with a fallback path so individual features look "fine but degraded".

File Sites Feature impact
services/attachment_context.py L132, L161, L265 AI Tutor uploaded-file context — silently runs without OCR text
services/document_context.py L221 AI content generation from Google Drive / uploads — same
api/v1/homework.py 4 call sites Homework OCR enhancement — same
api/v1/admin_analytics.py 4 call sites Admin analytics on documents — same

Action: add a /ocr/extract-text-from-document (or equivalent) endpoint to doc-intel that takes a signed URL or base64 PDF and returns plain text + page count. Rewrite DocIntelligenceClient.extract_document_text to call it. Without this, the migration is incomplete and 11+ call sites stay degraded.

Tier 2 — Backend renders PDFs locally (direct violation of migration goal)

PyMuPDF (fitz) is doing PDF-to-image conversion on the backend in multiple places. This is the workload the migration explicitly targeted.

File Sites What it does
services/answer_sheet_service.py L425, L437–440, L452–457 Renders student answer-sheet PDFs to PNG pages before OCR
api/v1/physical_exam.py L1353–1354 Renders an uploaded PDF page (2× zoom) inline
services/pdf_crop.py L55, L69–70 Crops a sub-region of a PDF page to PNG using clip=

Action: move rasterization to doc-intel. answer_sheet_service is the biggest win — it's the same memory pattern that drove the migration. pdf_crop is small and tightly scoped (one rect → PNG) but still backend RAM at upload time. Either lift the whole flow into doc-intel or expose a /ocr/crop-pdf-region helper there.

Tier 3 — Duplicate of a doc-intel capability

File Backend purpose doc-intel equivalent
services/ocr_service.py Gemini Vision + Google Cloud Vision OCR for submissions /api/v1/ocr/extract
services/pdf_export.py Assignment PDF generation /api/v1/export/pdf/assignment, /api/v1/export/docx, /api/v1/export/pptx, /api/v1/export/pdf/analytics, /api/v1/export/excel/analytics (all already live)

Action: drop the backend implementations, route all calls through the existing doc-intel endpoints. pdf_export.py in particular is duplicated work — doc-intel already exposes the export surface.

Tier 4 — Worth a closer look but not clearly mis-placed

File Notes
services/document_extractor.py Routing-layer only by its docstring ("routing attachments to the appropriate processing path"). Probably stays — but worth confirming nothing leaked into it.
services/platform_content_extractor.py "Extracting text content from platform resources" — could be OCR-flavored or pure DB lookups. Audit before migrating.
services/insight_extractor.py LLM on doc content for AI-Tutor memory. Not strictly doc handling; the LLM call is about the conversation, not the doc. Keep on backend.
  1. Add the missing extract-text-from-document doc-intel endpoint — unblocks Tier 1's ~11 silently-degraded call sites in one move.
  2. Migrate answer_sheet_service rasterization — highest-value Tier 2 win, directly attacks the memory pressure the migration is meant to solve.
  3. Retire services/ocr_service.py — pure duplication.
  4. Retire services/pdf_export.py — pure duplication.
  5. Migrate pdf_crop + the inline fitz use in physical_exam.py — smaller scope, can be batched.

After (1) and (2), the backend should hold approximately zero fitz imports outside of legitimate use cases (e.g. validating page count for an upload). Tracking that count as a regression metric would surface future drift early.

Notes for whoever picks this up

  • Every fallback path on the backend currently swallows extraction failures silently (_empty_result(), pass, try/except: return None). When you retire each call site, also audit its caller — silent fallbacks hid this problem for 3 months and will hide the next one.
  • The Doc intelligence extraction failed: DocIntelligenceError warning previously logged only the exception class. PR MySetu-AI/kwiloai_webapp#933 fixed that for the metadata path; apply the same %s: %s, type, exc pattern when you touch the other call sites.