# Fix the two Go/Python parity differences ## Context Running `make parity` against both containers on 2026-10-08 reported `0/3 routes match`. Two causes, neither related to the move to `legacy/`: 1. **VS rendered in scientific notation by Go.** The payments sheet is read with `UNFORMATTED_VALUE`, so a numeric VS cell arrives as `float64`. Go stringifies cells with `fmt.Sprint`, which prints any float ≥ 1e6 in exponent form (`2.609767878e+09`). Python already converts integer floats to plain digits for VS (`get_str` in `fetch_sheet_data`). VS must always be a plain string. 2. **`months_to_show` missing from the Python JSON API.** Go returns it in `/api/adults` and `/api/juniors` (and it is `required` in the committed schemas). Python already has the feature in its HTML pages (`MONTHS_TO_SHOW` is passed to the templates) but never added the key to the API responses. Backport the key so the API contract matches. Outcome: `make parity` reports 3/3 routes matching. ## Branching Both are bug fixes that touch `legacy/` paths, so they depend on the uncommitted `feat/python-to-legacy` work. Commit that branch first (still awaiting confirmation), then branch `fix/api-parity-vs-months-to-show` from it, with one commit per task. Open the MR with `tea` after the legacy move is merged, or target it at that branch. ## Task 1 — VS (and other text cells) always a plain string in Go Root cause is `getVal` in `parseTransactionRows` (`go/internal/services/membership/sources.go`, the `fmt.Sprint(row[i])` closure). The same pattern exists in `get` in `go/internal/services/banksync/infer.go`. 1. Add `CellString(v any) string` to `go/internal/io/sheets` (new small file `cell.go`; both services already import this package): - `nil` → `""` - `float64` → `strconv.FormatFloat(x, 'f', -1, 64)` (never exponent form; `2609767878.0` → `"2609767878"`, `12.5` → `"12.5"`) - `string` → unchanged - anything else → `fmt.Sprint(v)` 2. Use it in `getVal` in `sources.go`. This fixes VS and protects the other text columns (Message, Bank ID, Sender, …) from the same failure. - `getVal` currently returns `""` for nil cells and the inferred-amount code checks for that literal; simplify that check to `iv != ""`. - Leave `getRaw`/`FormatDate` and `parseFloat` for Date and Amount as they are — those must stay numeric. 3. Use it in `get` in `banksync/infer.go` (keeping the `TrimSpace`), so inference sees the same text the dashboard shows. 4. Tests: - `go/internal/io/sheets/cell_test.go`: table test for `CellString` (large integer float, small integer float, fractional, string with leading zeros, nil). - Extend `TestLoadTransactions` in `go/internal/services/membership/sources_test.go` with a row whose VS is `2609767878.0` and whose Bank ID is an 11-digit float; assert plain digit strings. 5. Python: no code change needed — `get_str` already does this for VS. Add one regression test for `fetch_sheet_data` in `legacy/tests/test_match_payments.py` (mock the Sheets service, numeric VS cell → `"2609767878"`) so the two sides are pinned to the same behaviour. Not included (note for later): VS is *written* to the sheet with `USER_ENTERED` by both sync implementations, so Sheets stores it as a number and drops leading zeros. Fixing that means writing VS as text and is a separate change to the sheet's data. ## Task 2 — backport `months_to_show` to the Python JSON API `legacy/app.py` only; `MONTHS_TO_SHOW` is already imported there. 1. In `api_adults()` and `api_juniors()`, add the key to the response: build `out = _unwrap_view_model_for_api(vm)`, set `out["months_to_show"] = MONTHS_TO_SHOW`, return `jsonify(out)`. - Do not add it inside `build_*_view_model` or `_unwrap_view_model_for_api`: the HTML routes already pass `months_to_show=` to `render_template` alongside `**vm`, and a duplicate key would raise `TypeError`. - `/api/payments` is unchanged; Go does not return the key there. 2. Tests in `legacy/tests/test_app.py`: - Add `"months_to_show"` to the expected key list used by `test_api_adults` / `test_api_juniors` (the tuple near the top of the file), and assert the value is an `int`. 3. Go: no change. The key and schemas (`go/tests/fixtures/api-schema/{adults,juniors}.schema.json`) already contain it. ## Wrap-up - `CHANGELOG.md` entry at the top (after confirmation it works), per repo convention. - Copy this plan to `docs/plans/2026-10-08-HHMM-api-parity-vs-months-to-show.md`, per the repo's plan-location rule. - Add both items to `docs/plans/2026-10-08-1026-pre-feature-cleanup-tasks.md` as done tasks so the audit list stays complete. ## Verification 1. `make go-test-all` — unit tests (including the new ones) and parity fixtures pass. 2. `make test` — Python tests pass, including the new API key and VS tests. 3. `cd go && go test ./internal/web/api/...` — schema golden test still matches (no schema change expected). 4. End to end: start `make web-py` and `make web-go`, then `make parity`; expect `3/3 routes match`. Alternatively repeat the two-container run used to find the bugs (`make image`, Go image from `go/build/Dockerfile`, then `go run ./cmd/parity -py … -go …`). 5. Open `/payments` on :8080 and check the affected transaction's VS shows as plain digits.