Skip to content

Chapter-aware figures — Phase A implementation

1. Issue reference

  • GitHub issue: #342 (chapter-aware figures epic)
  • Issue title: Chapter-aware figures & cross-chapter linking — Phase A (identity/parsing groundwork)
  • Issue type: feature
  • Milestone: TBD

2. Summary

  • What this issue changed: Added schema-free groundwork for chapter-aware figures — a pure bilingual figure cross-reference extractor + figure-number normalizer, structured JSON output from VisionService (now capturing figure identity and actually using the caption context), wiring of the already-extracted caption_nearby into the vision call, and surfacing figure identity in the admin asset preview.
  • Why the change was needed: figures are referenced across chapters; we need identity + parsing primitives before any schema (Phase B) or persistence (Phase C) can link them.

3. Initial repo state

  • Relevant behavior before implementation: VisionService.describe_image returned bare text and ignored its context param; references.llm_analysis.figures[].caption_nearby was extracted but never reached the vision call; no notion of a figure's own number/label.
  • Known constraints or gaps at start: built on top of the merged #342 watermark filter (#385).

4. Plan doc referenced

  • Plan doc path: docs/95_plans/2026-08-16-chapter-aware-figures-and-cross-chapter-linking.md
  • Plan status at implementation start: authored in this PR (Phase A of a phased rollout A–F).
  • Was the plan updated during implementation?: No (plan authored alongside code).

5. Decisions taken

Decision Reason Alternative rejected
PD-1 = "referenced-by" body cross-references Founder decision; tractable, high-value signal Full semantic figure-to-concept mapping
PD-2 = describe now, show later Keep Phase A schema/Storage-free Uploading image bytes now
PD-3 = enable LLM chapters as a later ops step Not app code Flipping INGEST_USE_LLM_STRUCTURE in this PR
describe_image returns FigureDescription with .description Backward-compatible identity carrier Breaking callers with a raw dict
Reuse TextNormalizer for Arabic letters; add only digit folding No duplication New Arabic normalizer

6. Files changed

File Change summary
app/services/figure_references.py New pure module: normalize_figure_number, extract_figure_references
app/services/vision_service.py Forced-JSON structured output, FigureDescription, use context
app/services/asset_service.py Look up caption_nearby by page; pass as vision context; read .description
app/api/routers/admin.py Preview surfaces figure_number/figure_label_verbatim/caption
tests/services/test_figure_references.py New — 17 tests
tests/services/test_vision_service.py Updated for structured output
tests/services/test_asset_service.py Updated to structured FigureDescription
tests/routers/test_admin_assets.py Updated + assert identity fields
docs/95_plans/..., docs/96_implementation/..., mkdocs.yml Plan + record + nav

7. Migrations / schema changes

  • Migration files: none.
  • Schema changes: none — Phase A is schema-free. Columns + figure_references table are Phase B (migration/).
  • Data backfill or manual steps: none.
  • Rollback notes: revert the PR; nothing persisted.

8. API changes

Surface Change Compatibility impact
POST /admin/references/{id}/assets/preview Adds figure_number, figure_label_verbatim, caption per asset Additive; dry-run only

9. Tests added or updated

Test file or suite Change
tests/services/test_figure_references.py New pure-function suite
tests/services/test_vision_service.py Structured output, JSON forcing, context use
tests/services/test_asset_service.py FigureDescription-based fixtures
tests/routers/test_admin_assets.py Identity fields surfaced

10. Risks / caveats

  • Vision now forces response_format={"type":"json_object"}; malformed JSON degrades to an empty (falsy) FigureDescription, same best-effort contract as the old return "".

11. Follow-up work

  • Phase B: assets columns + figure_references table (migration project).
  • Phase C: persist identity fields + cross-reference edges.
  • Phase D: retrieval context-expansion + system-prompt rule.
  • Phase E: image display (deferred). Phase F: enable INGEST_USE_LLM_STRUCTURE (ops).

12. Final repo state

  • Relevant behavior after implementation: figure identity is extracted and grounded by the nearby caption; the admin preview shows it; cross-reference parsing exists as a tested pure module.
  • Remaining limitations: nothing is persisted yet; retrieval is unchanged.

13. Docs updated

Doc path Update summary
docs/95_plans/2026-08-16-chapter-aware-figures-and-cross-chapter-linking.md New plan
docs/96_implementation/2026-08-16-chapter-aware-figures-phase-a.md This record
mkdocs.yml Nav entries for both