All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
13 KiB
13 KiB
Changelog
2026-05-07 14:13 CEST — feat(go): --print-fio-table + Fio debug logging + date parser fix
- Added
--print-fio-tableflag tofuj sync --dry-run: prints an aligned table of every Fio transaction in the window withSTATUS=NEW/DUP, usingtext/tabwriter. Key files:go/internal/services/banksync/fio_table.go,sync.go,cmd/fuj/main.go. - Added
LOG_LEVEL=DEBUGdebug logging on the Fio fetch path: client variant selected, full GET URL (token redacted on API path), HTTP status, body bytes, and per-parse drop-reason counters (raw_rows,kept,dropped_bad_date,dropped_nonpositive_amount). Key files:go/internal/io/fio/{client,api,transparent}.go. - Fixed
parseCzechDateto acceptDD.MM.YY(2-digit year) in addition to the 4-digit variant — Fio's transparent page now serves this format. Key file:go/internal/io/fio/transparent.go. - Added
make go-sync-debug [DAYS=N]Makefile target (default 30 days).
2026-05-07 10:32 CEST — feat(go): --dry-run for fuj sync
SyncOpts.DryRun booladded; when true,SyncToSheetsprints planned writes (would write header row,would append date=… amount=… sender=…,would sort by date) and returns without callingWriteHeader,AppendValues, orSortByDateColumn.fuj sync --dry-runflag wired incmd/fuj/main.go; mirrors existingfuj infer --dry-runbehaviour.TestSyncToSheets_DryRunadded to banksync test suite.
2026-05-07 01:03 CEST — feat(go/M4): IO layer behind interfaces
go/internal/io/attendance: CSV-over-public-URL client +Fakefor both adult and junior tabs.go/internal/io/drive: thin Drive v3 wrapper formodifiedTimereads +Fake.go/internal/io/sheets: Sheets v4 client (GetValues,AppendValues,BatchUpdateValues,WriteHeader,SortByDateColumn) +Fakewith call-capture for assertions.go/internal/io/cache: Drive-modifiedTime-gatedFileCachewith two TTL knobs, atomic writes, and genericGet[T]; Python-compatible JSON format;Flush()support.go/internal/io/fio:Clientinterface backed by Fio REST API (apiClient) and HTML-scraper (transparentClient);Fakefor tests. Fixtures intestdata/.go/internal/services/membership/sources.go:NewSourceswires attendance CSV + Sheets + cache intoLoadAdults,LoadJuniors,LoadTransactions,LoadExceptions. Includes Czech month/merged-month parsing logic.go/internal/services/banksync:SyncToSheets(dedup via SHA-256 Sync ID, optional sort) andInferPayments(name-match +[?]review prefix, dry-run) — fully tested with fakes.go/cmd/fuj/main.go:syncandinfersubcommands wired to real clients;feesandreconcilenow use realNewSources.- All packages lint-clean (golangci-lint v1.64.8, gofumpt extra-rules).
2026-05-06 23:25 CEST — feat(go/M3): fixture capture + parity test framework
scripts/capture_fixtures.py: dispatcher CLI that calls each ported function with seeded inputs and emits captured output as JSON fixtures.scripts/scrub_fixtures.py: deterministic PII scrubber (SHA-256 pseudonyms, digit-preserving account/VS hashes, name-sweep in free text).scripts/_fixture_seeds.py: handcrafted seed registry for all 10 pure functions + 10 reconcile branch-coverage cases.- 98 fixture files committed under
go/tests/fixtures/pure/<func>/andgo/tests/fixtures/reconcile/; all PII-free. go/tests/parity/parityio.go: shared loader with genericLoadDir/RunAllhelpers and typedIn/Outstructs for all 10 functions.- 11 parity test packages under
//go:build parity: 10 pure-function tests + bespoke reconcile test with per-cell float tolerance. - Makefile:
go-parity,go-test-all,capture-fixturestargets. go/tests/fixtures/README.md: refresh workflow, PII audit guide, adding-a-fixture steps.
2026-05-06 17:49 CEST — feat(go/M2.11-12): wire fuj fees + fuj reconcile subcommands
- New
go/internal/services/membershippackage:AttendanceLoader,TransactionLoader,ExceptionLoaderinterfaces, a stub (NewStubSources) that returnsErrIOPending, andFeesReport/ReconcileReportorchestration functions backed by realdomain/fees+domain/reconcilelogic. - Text formatters
printFeesTable/printReconcileReportport the output ofcalculate_fees.pyandmatch_payments.py print_reportverbatim. cmd/fuj/main.go:fuj feesandfuj reconcilesubcommands now dispatch properly;fuj sync/fuj inferretain the [M4] placeholder.- Both subcommands exit 1 with a clean
"io layer not yet wired up; lands in milestone M4"message until real Sheets loaders are injected in M4. - 13 unit tests covering stubs, all formatter branches (OK/partial/UNPAID/dash cells, credits, debts, unmatched, review annotation), and orchestration wiring via fake loaders.
2026-05-06 16:38 CEST — fix: include juniors in payment-inference roster
scripts/infer_payments.py: union adults + junior rosters so junior-only members are visible to the matcher.- Root cause:
get_members_with_fees()reads only the adults sheet; junior-only kids like Jáchym Kubík were absent frommember_names, causing the exact-match short-circuit to never fire and a different adult sharing the first name to win via fuzzy review. - Two regression tests added to
tests/test_match_members.py.
2026-05-06 16:05 CEST — feat(go/M2.10): port domain/reconcile.Reconcile
- New
go/internal/domain/reconcilepackage porting the three-phase payment allocation fromscripts/match_payments.py reconcile(). - 12 unit tests covering all Python test cases plus Go-only extras (diacritics tolerance,
[?]stripping,other:purpose, out-of-window credit, inference fallback, unmatched, no-transaction guard).
2026-05-06 13:18 CEST — feat(go/M2.7-2.9): port domain/matching package
- New
go/internal/domain/matchingpackage porting three helpers fromscripts/match_payments.py. BuildNameVariants— extracts normalized ASCII search variants from a member name, including nickname (from parens) and separate first/last; filters variants shorter than 3 chars;variants[0]is always the full normalized base name.MatchMembers— finds members in free text with"auto"or"review"confidence; exact-name short-circuit prevents nickname substrings (e.g.tov) from matching inside surnames (e.g.ottova).FormatDate— normalizes Google Sheets date values: handles nil, empty, int/float64 serial-days since 1899-12-30 (supports fractional serials), pre-formattedYYYY-MM-DDstrings, and garbage input — never errors.InferTransactionDetails— composes name + month matching over sender/message/user_id; falls back to sender-only member match and date-derived month when text gives no signal.- 21 table-driven tests; all expected values verified against live Python on 2026-05-06.
2026-05-06 12:43 CEST — feat(go/M2.6): port domain/synch.GenerateSyncID
- New
go/internal/domain/synchpackage withGenerateSyncID(Transaction) stringported fromscripts/sync_fio_to_sheets.pygenerate_sync_id. - Byte-stable SHA-256 hash over
date|amount|currency|sender|vs|message|bank_id(lowercased, UTF-8);Currency: ""defaults to"CZK"matching the Python missing-key fallback. - Key subtlety: Python's
str(float)emits"500.0"for whole-valued floats and switches to scientific notation at|f| >= 1e16or|f| < 1e-4— replicated informatAmountusing'f'/'e'format selection. - 6 table-driven hash tests + 9
formatAmounttests; all expected values verified against live Python on 2026-05-06.
2026-05-06 09:38 CEST — feat(go/M2.5): port domain/money.ParseCZK
- New
go/internal/domain/moneypackage withParseCZK(string) (float64, error)ported fromscripts/infer_payments.pyparse_czk_amount. - Preserves the Czech-locale heuristic: comma → decimal sep; 2+ dots → thousand seps; single dot → decimal (so
"1.500"→1.5). - Returns
(0, ErrInvalidAmount)on parse failure; callers wanting Python's silent-zero contract usev, _ := ParseCZK(s). - 15 table-driven tests plus a silent-zero contract test; all expected values verified against live Python on 2026-05-06.
2026-05-06 09:24 CEST — feat(go/M2.3+M2.4): port domain/fees.CalculateFee and CalculateJuniorFee
- New
go/internal/domain/feespackage with adult and junior fee calculators ported fromscripts/attendance.py. CalculateFee(count, monthKey) int—0→0,1→200,2+→AdultFeeMonthlyRate[month](fallback 700 CZK).CalculateJuniorFee(count, monthKey) Expected—0→{0},1→{Unknown:true}(the"?"sentinel, now strictly typed),2+→JuniorFeeMonthlyRate[month](fallback 500 CZK).- 20 table-driven tests, all verified against live Python;
-raceclean;golangci-lintclean.
2026-05-06 00:07 CEST — feat(go/M2.2): port czech.ParseMonthReferences
internal/domain/czech.ParseMonthReferences: three-pass regex (numeric slash, dot, Czech month names) with range wrap-around andm≥10 → previousYearheuristic, byte-equivalent to Python.- 35 table-driven tests; all expected outputs verified against live Python before locking (addresses risk #4 from the rewrite plan).
2026-05-05 23:33 CEST — feat(go/M2.1): port czech.Normalize
- First M2 pure-domain task:
internal/domain/czech.Normalize(NFKD + Mn-strip + lowercase), byte-equivalent to Pythonczech_utils.normalize. - Adds
golang.org/x/text v0.36.0as first external Go dependency. - 13-case table-driven test, all spot-checked against Python before locking.
2026-05-04 23:08 CEST — fix: payment inference exact-match short-circuit
match_members()now short-circuits on whole-word full-name hits; nickname/partial checks only run when no full name is present.- Replaced bare
insubstring checks with_word_in()word-boundary regex throughout, closing the class of bugs where a short nickname (e.g.tov) matches inside another member's surname (ottova). - Added
tests/test_match_members.py(6 cases). Affectsscripts/match_payments.py.
2026-05-04 23:08 CEST — feat: lower adult monthly fee to 700 CZK from April 2026
ADULT_FEE_DEFAULTreduced from 750 → 700 CZK.ADULT_FEE_MONTHLY_RATEnow pins Sep 2025 – Feb 2026 at 750 to preserve historical billing; Mar 2026 stays 350; Apr–May 2026 at 700. Affectsscripts/attendance.py.
2026-05-04 12:02 CEST — Go rewrite M1: skeleton + tooling
- Created
go/tree with modulefuj-management/go(Go 1.26). cmd/fuj: stdlib-flag subcommand dispatcher;serverandversionimplemented, stubs for M2/M4 commands.internal/config: env loader mirroringscripts/config.py(same env var names and defaults).internal/logging: slog setup accepting log level from config.internal/web:net/httpServeMux on:8080;middleware/timer.gologs method/path/status/ms.go/build/Dockerfile: multi-stage (golang:1.26→alpine:3) producing a static binary image.- Makefile:
web→web-pyalias; addedweb-go,go-build,go-test,go-run,go-lint. .gitea/workflows/build.yaml: parallelbuild-gojob pushing<tag>-goimage.- Gate:
make go-build,make go-lint,make go-test,curl :8080all pass.
2026-05-03 20:37 CEST — Fix Balance column to correctly reflect past-month debt
- Balance (and Pay-All) are now computed as
sum(paid − expected)over past months only, iterating directly over the ledger entries fromreconcile(). - Previously the balance used
total_balance(which includes current/future-month activity and out-of-window credits) plus a one-sided current-month debt adjustment. Current-month surplus leaked through, making the balance appear less negative than the actual past-month debt. - Pay-All is now
max(0, −balance)so the two values are derived from a single source and can never disagree. - Affected:
adults_view()andjuniors_view()inapp.py.
2026-05-03 19:26 CEST — Fee-aware allocation for multi-month payments
reconcile()no longer splits a multi-month payment evenly. Allocation is now per-member with two phases: greedy (if amount ≥ total expected, each month gets exactly its expected fee and overflow → credit) and proportional (otherwise distribute by each month's expected). Fixes the case where e.g. 1250 CZK covering 3 months with mixed fees (750/350/150) marked two months red.- Out-of-window months keep the previous even-split-to-credit behavior. Fallback to even split when all matched months have
expected = 0(prepayment before attendance is recorded). - Display layer only — no changes to how payments are stored in Google Sheets;
Inferred Amountstill holds the full bank amount. - Files: scripts/match_payments.py, tests/test_reconcile_exceptions.py (6 new test cases).