Deploy to K8s / deploy (push) Successful in 20s
Go (go/) becomes the primary implementation. The original Python version moves to legacy/ and is kept runnable; new features are still built in both (see the Dual implementation rule in CLAUDE.md). - git mv app.py, scripts/, templates/, tests/, pyproject.toml, uv.lock and build/ into legacy/ - Makefile: Python targets point at legacy/, venv in legacy/.venv, CREDENTIALS_PATH exported for the root .secret/ file - CI and make image build the Python image with legacy/ as context; the Makefile is no longer copied into the image - legacy/.dockerignore moved to the context root so it takes effect - CLAUDE.md, docs/README.md, Go comments, fixture README, capture script and VS Code launch config updated to the new layout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
129 lines
4.3 KiB
Markdown
129 lines
4.3 KiB
Markdown
# Parity Fixtures
|
|
|
|
Captured outputs from the live Python implementation used as ground truth for
|
|
the Go parity test suite. All 98 files are committed and PII-free.
|
|
|
|
## Directory layout
|
|
|
|
```
|
|
fixtures/
|
|
pure/
|
|
normalize/ # scripts.czech_utils.normalize
|
|
parse_month_references/ # scripts.czech_utils.parse_month_references
|
|
calculate_fee/ # scripts.attendance.calculate_fee
|
|
calculate_junior_fee/ # scripts.attendance.calculate_junior_fee
|
|
parse_czk_amount/ # scripts.infer_payments.parse_czk_amount
|
|
generate_sync_id/ # scripts.sync_fio_to_sheets.generate_sync_id
|
|
build_name_variants/ # scripts.match_payments._build_name_variants
|
|
match_members/ # scripts.match_payments.match_members
|
|
infer_transaction_details/ # scripts.match_payments.infer_transaction_details
|
|
format_date/ # scripts.match_payments.format_date
|
|
reconcile/ # scripts.match_payments.reconcile (10 branch-coverage cases)
|
|
```
|
|
|
|
## Fixture format
|
|
|
|
One JSON object per file:
|
|
|
|
```json
|
|
{
|
|
"case": "range_wrap_nov_to_jan",
|
|
"func": "scripts.czech_utils.parse_month_references",
|
|
"captured_at": "2026-05-06",
|
|
"input": { "text": "...", "default_year": 2026 },
|
|
"output": { "months": ["2025-11", "2025-12", "2026-01"] }
|
|
}
|
|
```
|
|
|
|
`captured_at` is date-only so same-day re-runs produce byte-identical files.
|
|
|
|
### Amount type envelope
|
|
|
|
Four fields carry a type envelope to distinguish Python `int` / `float` / `None`:
|
|
|
|
```json
|
|
{"type": "int", "value": 750}
|
|
{"type": "float", "value": 750.0}
|
|
{"type": "string", "value": "..."}
|
|
{"type": "none"}
|
|
```
|
|
|
|
Fields that use envelopes: `generate_sync_id.tx.amount`, `parse_czk_amount.val`,
|
|
`format_date.val`, `infer_transaction_details.tx.date`.
|
|
|
|
### Reconcile member format
|
|
|
|
Reconcile input members use a named dict to allow consistent PII scrubbing:
|
|
|
|
```json
|
|
{"name": "Member_d035d9f9", "tier": "A", "fees": {"2026-01": [750, 3]}}
|
|
```
|
|
|
|
## Running the parity tests
|
|
|
|
```bash
|
|
make go-parity # run all parity tests
|
|
make go-test-all # unit tests + parity tests
|
|
```
|
|
|
|
Or directly:
|
|
|
|
```bash
|
|
cd go && go test -tags=parity ./tests/parity/...
|
|
cd go && go test -tags=parity -v -run TestReconcileParity ./tests/parity/reconcile/
|
|
```
|
|
|
|
## Refresh workflow
|
|
|
|
Regenerate the entire corpus from the live Python implementation:
|
|
|
|
```bash
|
|
make capture-fixtures
|
|
git diff go/tests/fixtures/ # review changes before committing
|
|
```
|
|
|
|
To refresh a single function:
|
|
|
|
```bash
|
|
PYTHONPATH=legacy/scripts:legacy python3 legacy/scripts/capture_fixtures.py --func normalize --all \
|
|
| while IFS= read -r line; do
|
|
id=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['case'])")
|
|
echo "$line" | python3 legacy/scripts/scrub_fixtures.py \
|
|
> go/tests/fixtures/pure/normalize/${id}.json
|
|
done
|
|
```
|
|
|
|
## When to refresh
|
|
|
|
- A ported function is intentionally changed to match updated Python behaviour.
|
|
- A new Czech declension or fee tier is added to the Python implementation.
|
|
- A new reconcile code path needs fixture coverage.
|
|
|
|
**Do not refresh to silence a failing parity test** without first confirming that
|
|
the Python behaviour is the correct reference. A parity failure means either the
|
|
Go port diverges or the Python implementation changed — diagnose before regenerating.
|
|
|
|
## PII scrubbing audit
|
|
|
|
No real member names should appear in committed fixtures. Before committing any
|
|
regenerated fixtures, verify with:
|
|
|
|
```bash
|
|
# Replace with names from the real roster to check:
|
|
git ls-files go/tests/fixtures | xargs grep -l "Real Name Here" | head
|
|
```
|
|
|
|
The scrubber applies deterministic SHA-256 pseudonyms (`Member_<8hex>`) to all
|
|
PII fields. `match_members` and `infer_transaction_details` fixtures use a
|
|
synthetic roster of fictional names and are exempt from field-key scrubbing;
|
|
verify that no real roster names appear in their `member_names` arrays.
|
|
|
|
## Adding a new fixture
|
|
|
|
1. Add a seed to `legacy/scripts/_fixture_seeds.py` under `SEEDS[("func_name", "case_id")]`.
|
|
2. Add `In`/`Out` struct fields to `go/tests/parity/parityio.go` if the function
|
|
is new.
|
|
3. Run the single-file capture recipe above and review the diff.
|
|
4. The parity test picks up new fixtures automatically — no test code changes needed
|
|
(unless the function itself is new).
|