- Scrub three parity fixtures that still held raw bank messages
- Use fictional names in the junior regression test, its changelog
entry and its plan file
- State the rule in CLAUDE.md: a real name as a bare test input is
fine; a real name next to payment details, or any junior or parent
name, is not
- Tick T4 in the cleanup task list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 2026-08 added to the junior rate table at 0 CZK, same as adults
- A single practice in a 0-rate month is 0 instead of "?", since a
free month needs no manual review
- Mirrored in Go and legacy Python; new calculate_junior_fee parity
fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the bank message starts with "other:" (case-insensitive),
inference does not look for a month, neither in the text nor from the
payment date. The trimmed message is written to Purpose, so reconcile
books the payment as an "other" transaction for the person instead of
a monthly fee. Messages containing "other:" elsewhere are unchanged.
- matching.IsOtherPurpose / is_other_purpose shared by inference and
reconcile
- reconcile applies the rule to rows not yet processed by inference
- no person found: Purpose is still written, row shows as unmatched
- implemented in Go and legacy Python; four new parity fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Go server now runs sync -> infer -> flush-cache by itself at the
times in SYNC_SCHEDULE (comma-separated HH:MM, default 04:00, "off"
disables), read in SYNC_TZ (default Europe/Prague). Nothing runs at
startup; an invalid value stops the server with an error.
- internal/scheduler: time-of-day parsing, Next, and the run loop
- banksync.Runner: one lock shared by /sync-bank and the scheduler so
runs never overlap, plus the status of the last run
- footer shows the last sync since start and the next scheduled run
- binary embeds time/tzdata because the Alpine image has no zone data
Deliberately not implemented in the legacy Python app, which is being
phased out; recorded in CLAUDE.md under "Deliberately Go-only features".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/sync-bank now uses one shared default window in both implementations:
1 November of the previous year to 31 December of the current year.
Payments dated around New Year are picked up in January; duplicates
are still filtered by Sync ID.
Python used a hardcoded 2026 window and would have stopped syncing on
2027-01-01; Go used the current calendar year only.
Adds make sync-year [YEAR=N] with the same window. The CLI default
without flags (last 30 days) is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Default adult monthly rate 700 -> 600 CZK
- 2026-07 and 2026-08 set to 0; 2026-09 through 2026-12 added at 600
- Single-session fee is capped at the month's rate, so a 0-rate month
is free for one session as well
- Mirrored in Go and legacy Python; new calculate_fee parity fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Go returns months_to_show in /api/adults and /api/juniors; the Python
API did not, so make parity failed on both routes. The Python HTML
pages already had the feature.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Go stringified sheet cells with fmt.Sprint, so a numeric VS read with
UNFORMATTED_VALUE was rendered as 2.609767878e+09. Text columns of the
payments sheet now go through sheets.CellString, which never uses
exponent form; bank inference uses the same helper.
Both sync implementations now append VS as a text cell so Sheets keeps
it a string and leading zeros survive. Existing rows are not rewritten.
Python already read VS as a digit string; a regression test pins that.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>