Configured-month cases now read expected values from AdultFeeMonthlyRate /
JuniorFeeMonthlyRate via a mustRate helper that panics if a test month is
removed from the map. Fallback cases use AdultFeeDefault / JuniorFeeDefault.
This way the tests verify dispatch logic (0/1/2+ branching, map vs. fallback)
without breaking when rates are intentionally updated in the map.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 2026-05: 700 → 450 CZK
- 2026-06, 07, 08: 600 CZK (new months)
Changes are mirrored in both Python (scripts/attendance.py) and Go (go/internal/domain/fees/fees.go).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace proportional split with a fill-first loop that allocates
min(remaining, deficit) to each matched month in user-supplied order,
where deficit = expected - already_paid. Prior transactions' contributions
are now properly accounted for, so a second payment on overlapping months
fills only what's still owed instead of splitting proportionally by total
expected. Surplus after all deficits are covered goes to the credit bucket.
Fixes: Matyáš Thér 200+550 showing 566/183 instead of 500/250.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New go/internal/domain/matching package porting three helpers from
scripts/match_payments.py:
- BuildNameVariants: normalized ASCII variants from a member name (nickname
in parens, last/first split, len<3 filtered); variants[0] is always the
full base name — MatchMembers relies on this invariant.
- MatchMembers: auto/review confidence matching with an exact-name
short-circuit pass that prevents nickname substrings (tov) from firing
inside longer surnames (ottova); common-surname filter for review tier.
- FormatDate: nil/empty/""/serial int/float64 (since 1899-12-30, fractional
days supported)/YYYY-MM-DD passthrough/garbage → never errors.
- InferTransactionDetails: composes BuildNameVariants+MatchMembers+
ParseMonthReferences; falls back to sender-only member match and
date-derived month when text carries no signal.
21 table-driven tests; all expected values verified against live Python
on 2026-05-06. go-build, go-test, go-lint all clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SHA-256 dedup hash from sync_fio_to_sheets.py generate_sync_id.
Key subtlety: Python str(float) emits "500.0" for whole-valued floats
and switches to scientific notation at |f|>=1e16 or |f|<1e-4 —
replicated via formatAmount using 'f'/'e' format selection.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Port scripts/infer_payments.py parse_czk_amount to Go as
internal/domain/money.ParseCZK. Preserves the Czech-locale heuristic
(comma = decimal sep; 2+ dots = thousand seps; single dot = decimal)
and returns (float64, error) so callers can opt into Python's
silent-zero contract via v, _ := money.ParseCZK(s).
All expected values verified against live Python on 2026-05-06.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ports calculate_fee and calculate_junior_fee from scripts/attendance.py
into a new go/internal/domain/fees package. Introduces the Expected type
(Value int, Unknown bool) for the junior "?" sentinel, keeping the Go
API strictly typed instead of mirroring Python's str|int return.
All 20 table-driven tests pass with -race; golangci-lint clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three-pass regex parser matching python/czech_utils.py parse_month_references:
1. Numeric slash notation — "11+12/2025", "01/26"; 2-digit year → +2000
2. Dot notation — "12.2025" (4-digit year only)
3. Czech month names — range walk (listopad-leden wrap logic) then
standalone with m≥10 → defaultYear-1 heuristic; longest-match
alternation (sorted desc by name length) handles cervenec vs cerven
35 table-driven tests, all expected outputs verified against live Python
on 2026-05-05 before locking. Plan at
docs/plans/2026-05-05-2337-go-rewrite-m2-2-parse-month-references.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds internal/domain/czech.Normalize, the first pure-domain function in
the Go rewrite (M2 milestone). Matches Python czech_utils.normalize byte-
for-byte: NFKD decompose via golang.org/x/text/unicode/norm, drop Mn-
category combining marks (unicode.Mn, not IsMark, to match Python's
unicodedata.combining() semantics), then strings.ToLower.
Includes 13-case table-driven test; all inputs spot-checked against the
Python implementation before locking. Adds golang.org/x/text v0.36.0 as
first external dependency.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>