phase: 140_pictorial_pdf_pages
Build and Push Containers / build-and-push-app (push) Successful in 2m26s
Build and Push Containers / build-and-push-db (push) Successful in 12s

Everything is verified green. Final report:

## Phase 140 final verification pass — report

- Verified all 4 tasks already complete (code in working tree, task files in `complete/`): `pictorial_pages` gate + 2 settings (`BOR_PDF_PICTORIAL`, `BOR_PDF_PICTORIAL_MIN_CHARS` + validator), `PICTORIAL_PAGE_MODE`/`describe_pdf_page` seam + `render_pdf_pages` page selector + mock branch, `_pictorial_pdf_pass` in `_index_pdf_file` with log-only counters, dedicated E2E. No code changes needed.
- Test results (exact commands):
  - `uv run pytest --cov=app --cov-report=term-missing` → **3320 passed, 11 skipped, 0 failed; TOTAL 99%** (>90% gate)
  - `uv run pytest tests/e2e/test_pictorial_pdf.py -v --no-cov` → **3 passed in isolation** (Playwright, mock-only)
  - Pre-existing suites unedited: `test_pdf_support.py` 6 passed; `test_garbage_pdf.py` 2 passed + 1 skipped (owner fixture); `test_pdf_image_context.py` 4 passed
  - `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors
- Completion criteria:
  1. IKEA tar import repro — pinned by 2 integration tests; **skipped loudly here** (`test_sources/ikea_instructions.tar` absent on this machine — verified repo-wide; same house convention as completed phase-133 HNB tests; runs real on the owner's machine). Equivalent logic fully covered by unit pins on synthetic IKEA-class (vector-drawn, zero-raster) PDFs.
  2. Chat grounded on described page — **green** (E2E test b: phrase-only-in-illustration question, PDF in `done.sources`, answer echoes the block)
  3. Clean-text no-op (zero vision calls) + garbage precedence pin — **green** (`test_zero_candidates_is_a_noop` unit; `test_garbage_pdf_with_pictorial_on_takes_the_fallback_not_the_pass` integration, verified alone)
  4. `BOR_PDF_PICTORIAL=0` byte-identical — **green** (unit pin on synthetic PDF; IKEA variant runs on owner's machine)
  5. Full gate + E2E isolation + pre-existing suites — **green** (above)
  6. ruff + pyright — **clean**
  7. Commit/phase-move — delegated to harness per run rules (left in working tree: 16 modified + 8 new files, uncommitted)
- Deviations: none beyond the expected owner-fixture skips (11, all pre-existing fixture-missing skips, including 2 IKEA).
- Next pending phase: **none** — `todo/` holds only 140; 139 is already complete.
This commit is contained in:
2026-09-30 19:16:35 -04:00
parent a86544a083
commit 9633eb7653
45 changed files with 3895 additions and 36 deletions
+35
View File
@@ -34,6 +34,15 @@ Implements just enough of the aipi surface:
the image document's whole content + summary, so it is byte-
stable and token-dense (its cosine against the story suite's
question clears the mock-calibrated gate)
- ``PICTORIAL_PAGE_MODE`` (phase 140, pictorial PDF pages) -> the
fixed ``PICTORIAL_PAGE_ANSWER`` description: the CHAT model's
(vision) reply to ``summarizer.describe_pdf_page``'s multimodal
user message (one rendered sparse PDF page — the marker is its
first text part; the call is non-streaming, so only this answer
path serves it). The reply is appended to the page's indexed
content block as ``[Page N illustration: …]`` (LOCKED A4), so it
is byte-stable: a re-sync with the same reply re-digests to the
same content and lands as ``unchanged``
- ``DEFLECT_MODE`` -> honest "I haven't done anything like that" answer
- otherwise -> upbeat answer quoting the provided document context
- user message containing ``pretend to think slowly`` -> 3s warm-up delay
@@ -992,6 +1001,21 @@ IMAGE_DESCRIPTION_CONTEXT_ANSWER = (
"cable runs. Title: Homelab Network Map."
)
#: Phase 140 (pictorial PDF pages, LOCKED A2): the fixed description
#: the mock's vision (CHAT) model returns for a
#: ``PICTORIAL_PAGE_MODE`` request — the multimodal user message
#: ``[{type: "text", …PICTORIAL_PAGE_MODE…}, {type: "image_url",
#: …}]`` (``app.rag.summarizer.describe_pdf_page`` — one rendered
#: sparse PDF page, the page's own text as context). The reply is
#: appended to the page's indexed content block as
#: ``[Page N illustration: …]`` (LOCKED A4), so it is byte-stable and
#: retrieval-friendly (distinctive + token-dense on the assembly
#: vocabulary the story E2E's question targets).
PICTORIAL_PAGE_ANSWER = (
"Pictorial page: the illustration shows two parts being joined "
"with an arrow."
)
TABLE_ANSWER = (
"Here's the shape, in a table:\n"
"\n"
@@ -2913,6 +2937,17 @@ def compose_answer(body: dict[str, Any]) -> str:
if "Extracted from " in user
else IMAGE_DESCRIPTION_ANSWER
)
elif "PICTORIAL_PAGE_MODE" in user:
# Phase 140 (pictorial PDF pages, LOCKED A2): the CHAT model's
# vision description of ONE rendered sparse PDF page — a
# NON-STREAMING multimodal user message, so only this path
# ever sees it. ``_content_text`` joins the text parts, so the
# marker (the first text part) lands in ``user``. The two
# markers are disjoint fixed app constants (neither contains
# the other), so the branch order vs ``IMAGE_DESCRIPTION_MODE``
# cannot shadow either flow; like that marker, it is an
# app-to-app marker a real question is never expected to type.
answer = PICTORIAL_PAGE_ANSWER
elif TABLE_TRIGGER in user.lower():
# Markdown tables (phase 44, TODO.md L6): the story E2E's
# deterministic table answer — a 3-column table, the