Skip to content

Add synthetic typed-scan vision fixtures and integration harness - #49

Merged
gavinbee merged 6 commits into
mainfrom
48-synthetic-vision-fixtures
May 31, 2026
Merged

Add synthetic typed-scan vision fixtures and integration harness#49
gavinbee merged 6 commits into
mainfrom
48-synthetic-vision-fixtures

Conversation

@gavinbee

Copy link
Copy Markdown
Collaborator

Summary

Lands the committable, deterministic vision-path integration fixture and the pytest -m integration harness for the typed digital-export workflow. This is meaningful, self-contained progress on #48 — it ships the fixtures, generator, golden, and harness. It does not close #48: making the harness pass is the remaining quality-iteration work, which stays on the issue.

  • Design doc 0002 (docs/design/0002-synthetic-vision-fixtures.md) — positions the typed-scan as a new tier alongside 0001's three (complements, doesn't replace, the real-scan and pseudonymized tiers).
  • Generator (tests/fixtures/vision_scan/make_synthetic_scan.py) — renders the Faker-seeded form to a flattened, image-only PDF (no widgets, no text layer → routes to the vision path), with a deterministic mix of signed-off / struck-through / blank-initials rows, and emits an exact golden JSON. times_worked_position deliberately differs from lane_number to expose column bleed.
  • Fixture + goldensession_1_evals_scan.pdf (2 pages) + session_1_evals_scan.golden.json.
  • Harness (tests/test_integration_vision.py) — scores vision output on the (official, position, successful) tuple contract (modulo position-name normalization). Self-manages the Ollama daemon via OllamaDaemon (one command, no manual ollama serve), adds --vision-model / --pull-models pytest options, and skips cleanly when Ollama or the model is absent.

Quality gate (expected to fail)

ACCURACY_FLOOR = 1.0 on purpose: this is the easiest possible input (typed, clean, no handwriting) and humans clear even the hardest handwritten forms at ~99%, so anything short of a perfect read is a quality bug. The integration tests are expected to fail until the issues they surface are fixed — that is their job. First run found: struck-through row read as signed-off, blank-initials+mentor row read as signed-off, a position vertical-bleed, a dropped row, and a doubled-token competition name (details in #48). These never run in CI (no GPU), so a red integration test blocks nothing automated.

Test plan

  • pytest -q — default suite green (286 passed, 3 integration skipped).
  • pytest -m integration — runs end-to-end against the real model, daemon auto-managed; currently red by design at the 100% floor.

Refs #48

🤖 Generated with Claude Code

gavinbee added 3 commits May 30, 2026 00:47
Amends 0001's test-data tiers with a committed, deterministic vision-path
fixture for the typed digital-export shape. Records why a clean typed scan
is a real production input rather than the cautioned-against easy fake, and
the (official, position, successful) scoring contract for the integration
harness.

Refs #48
Generator renders the Faker-seeded form to a flattened image-only PDF (no
widgets, no text layer) that routes to the vision path, and emits an exact
golden.json. Fills the columns the form-field fixture leaves blank
(times_worked, mentor, level, successful) with a deterministic mix:
times_worked deliberately differs from lane to expose column bleed, and the
successful field covers true (typed initials), false (struck-through row),
and null (mentor assigned, initials blank).

Refs #48
Add the integration test that runs the real vision model over the
committed flattened typed-scan and scores it against the exact golden on
the (official, position, successful) tuple contract.

The harness self-manages the Ollama daemon via OllamaDaemon (one
`pytest -m integration` command — no manual `ollama serve` step) and adds
--vision-model / --pull-models pytest options; a missing model skips with
a copy-paste pull hint rather than silently downloading multiple GB.

ACCURACY_FLOOR is 1.0: this is the easiest possible input (typed, clean,
no handwriting) and humans clear even the hardest handwritten forms at
~99%, so anything short of a perfect read is a quality bug. The tests are
expected to fail until those issues are fixed — that is their purpose.
Tracked on #48.

Refs #48
Comment thread tests/fixtures/vision_scan/make_synthetic_scan.py Outdated
Comment thread tests/fixtures/vision_scan/make_synthetic_scan.py Outdated
@@ -0,0 +1,390 @@
"""Regenerate the synthetic typed-scan vision fixture + its golden.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine for this initial PR. However, we do need to turn this into a config driven synthetic scan maker. The hardcoding of all the data that ends up in the PDF makes me cry a bit on the inside.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — the hardcoding is bootstrap-only. Filed #50 to make the generator config-driven so this isn't lost. Keeping it out of this PR as you suggested.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our input to make_synthetic_scan.py should be something like these goldens, plus a few more bits about how we want each row to look ... not in this pr though.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Captured this exactly in #50: the generator's input should be golden-shaped (rows + header) plus a few per-row rendering hints, with the golden emitted as the echoed-back source of truth. Not in this PR.

Comment thread tests/fixtures/vision_scan/session_1_evals_scan.golden.json Outdated
…scan

Address PR review on #49:
- Copy the blank Swim Ontario eval_form.pdf into the repo at
  tests/fixtures/templates/swim_ontario_v1/eval_form.pdf (one per
  supported template) so the fixture regenerates without an external
  eval-gen clone, and a material template change shows up as a diff to
  its committed blank. The vision generator now prefers this in-tree
  copy, falling back to a sibling eval-gen checkout.
- Render officiating levels as Arabic numerals (2-5), matching how they
  appear on real forms, instead of Roman numerals. Regenerate the PDF
  and golden accordingly.

Refs #48
@gavinbee

Copy link
Copy Markdown
Collaborator Author

Addressed the review in 10463fd:

  • Blank template committed in-tree at tests/fixtures/templates/swim_ontario_v1/eval_form.pdf (one per supported template); the generator prefers it and no longer needs an external eval-gen clone.
  • Arabic levels (2–5) in both the generator and the regenerated golden/PDF.
  • Config-driven generator (golden-as-input + per-row render hints) filed as Make the synthetic scan generator config-driven (golden-as-input) #50 — kept out of this PR per your note.

Default suite green (286 passed, 3 integration skipped). The level change doesn't touch the scored (official, position, successful) tuple, so no integration re-run was needed for it.

gavinbee added 2 commits May 30, 2026 11:42
The blank template is a first-class reference asset, not a test fixture:
nothing in the test suite opens it — only the fixture generators and
(conceptually) template detection do. Move it out of tests/fixtures to a
repo-root templates/<id>.pdf, flat-named by template id to match
src/templates/<id>.py and docs/templates/. Point both the vision-scan
and form-field generators at the shared in-tree copy (falling back to a
sibling eval-gen checkout when refreshing it), so the canonical blank
isn't used by only one generator.

Refs #48
Follow-on to the template move: update the vision-scan and form-field
generators to resolve the blank template at the repo-root
templates/<id>.pdf, falling back to a sibling eval-gen checkout. Docstrings
and --template help updated to match.

Refs #48
@gavinbee
gavinbee merged commit 8a5afc4 into main May 31, 2026
1 check passed
@gavinbee
gavinbee deleted the 48-synthetic-vision-fixtures branch May 31, 2026 04:23
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.

Synthetic typed-scan vision fixtures + integration harness

1 participant