Experimentation Workflow
A repeatable loop for testing hypotheses, benchmarking, and evaluating accuracy improvements.
Experimentation means making a change to the pipeline and measuring its effect on accuracy or latency before declaring it part of the baseline. This page describes a practical, repeatable loop.
Private data rules
Never commit real receipt images to this repository. Never commit holdout evaluation reports, even if they contain only aggregate metrics. Keep all private corpus files in private storage. Only aggregate numbers (exact match rate, p95 latency, false-strong count) may be published.
The experimentation loop
1. Define the hypothesis
State what you expect to change and why. Example:
"Increasing contrast normalization threshold from X to Y will improve candidate extraction on Zepto dark-mode screenshots."
Write this down before running any code. Bias from looking at results first corrupts the signal.
2. Add synthetic or authorized private cases
If the condition can be reproduced synthetically, add a fixture to tests/fixtures/ and describe
it in tests/fixtures/README.md.
If you have authorized private receipts (collected with explicit consent), keep them in private storage only. Reference them via a manifest for holdout evaluation — never commit them.
3. Run portable tests
Verify your change does not break existing contracts:
pytest -m 'not integration'Fix any failures before continuing.
4. Run integration tests
Verify the full pipeline behaves correctly with Tesseract:
pytest -m integration5. Benchmark on the development runtime
Use tools/benchmark.py to measure latency on your local images. This gives you a relative
signal, not a conformant measurement:
python tools/benchmark.py tests/fixtures/*.png \
--repetitions 5 \
--runtime-policy developmentThe output is JSON with p50, p95, and p99 latency across all runs. The
environment_eligibility.eligible_for_conformance_reporting field will be false on macOS or
any non-baseline runtime.
6. Benchmark on the retained conformance runtime
For measurements that count toward the v1 gate, run on the retained Ubuntu 24.04 / amd64 /
tesseract 5.3.4 instance with --runtime-policy conformant:
python tools/benchmark.py tests/fixtures/*.png \
--repetitions 5 \
--runtime-policy conformanteligible_for_conformance_reporting will be true only when all runs are conformant. Record the
p95 result.
7. Run the private holdout evaluator (conformant runtime only)
The holdout evaluator is only meaningful on the retained conformance runtime with a frozen, authorized private corpus:
python tools/holdout/evaluate.py \
--manifest /private/corpus/manifest.json \
--corpus-root /private/corpus/images/ \
--output /private/reports/run-YYYY-MM-DD.jsonFor dry runs on a development runtime (result is ineligible but useful for debugging):
python tools/holdout/evaluate.py \
--manifest /private/corpus/manifest.json \
--corpus-root /private/corpus/images/ \
--output /private/reports/dry-run.json \
--allow-development-runtimeExit codes: 0 = gate passed, 1 = gate failed, 2 = manifest error.
8. Inspect aggregates
Check the report's aggregates section:
{
"exact_match_rate": 0.961,
"false_strong_count": 0,
"confirmation_rate": 1.0,
"p95_duration_ms": 3210,
"gates": {
"eligible": true,
"exact_amount_currency_rate_min_0_95": true,
"false_strong_count_zero": true,
"confirmation_rate_eq_1_0": true,
"p95_duration_ms_lt_5000": true
},
"gate_passed": true
}An ineligible run (eligible: false) means the evaluation did not run on the conformant runtime
with a fully conformant baseline — it cannot count toward the gate.
9. Document baseline-affecting decisions
If the change affects the model files, pass schedule, preprocessing, or grading logic, record it
in an ADR under docs/adr/. Changes to these areas require a new holdout evaluation run before
the new baseline is declared conformant.
See CONTRIBUTING.md for the full list of baseline-affecting changes.
Benchmark output format
{
"tool": "payable-receipt-ocr benchmark",
"runtime_policy": "development",
"repetitions": 3,
"durations": {
"count": 9,
"p50_ms": 2410,
"p95_ms": 3870,
"p99_ms": 4200
},
"environment_eligibility": {
"eligible_for_conformance_reporting": false,
"runtime_baseline_ids": ["linux-noble-amd64-2026-08"]
},
"runs": [...]
}What not to do
- Do not commit private receipts to this repository under any name or path.
- Do not commit holdout evaluation reports (even aggregate-only ones) to the public repository.
- Do not use macOS benchmark numbers as evidence for the v1 gate.
- Do not run the holdout evaluator on a development-policy runtime and treat the result as
eligible — the evaluator will set
eligible: falseand the gate check will fail.