Commit Graph

135 Commits

Author SHA1 Message Date
78e5059759 feat(go): M6.1 — template skeleton, embed.FS, HTML routes
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Stand up the Go-native HTML frontend foundation:
- base.tmpl layout + nav/footer partials (three-tier nav, active-link highlighting)
- terminal-green-on-black theme extracted to static/css/app.css (served via embed.FS)
- HTMLHandler with stub pages for all five routes; / redirects to /adults
- NewRenderer parses per-page template sets at startup so parse failures abort boot
- Smoke test: each route returns 200 text/html with exactly one class="active" link

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 00:45:22 +02:00
2f635db2b4 chore: CHANGELOG for M5.4 parity coercions (PR #25)
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 00:27:34 +02:00
709a2f2335 Merge pull request 'fix(py): parity coercions — amount/message types + junior '?' sticky' (#25) from fix/py-parity-coercions into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Reviewed-on: #25
2026-05-07 22:26:39 +00:00
58973473c9 fix(py): make junior '?' cell text sticky across exception overrides
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Go's build_juniors sets cellText = "?" + countStr whenever
md.IsUnknown is true, regardless of whether an exception overrides the
expected amount. Python was checking expected == "?" for this branch,
but reconcile replaces expected with the exception amount (e.g. 0)
before the view builder runs, so the "?" was silently dropped to "-".

Fix: derive is_unknown from original_expected == "?" (set before
exception substitution) instead of expected == "?". Also align the
tooltip guard: Go only shows Received/Expected tooltip for non-unknown
months (or when paid > 0), matching the same is_unknown flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 00:26:04 +02:00
b68d95d217 fix(py): coerce amount to float and message to string in tx projection
Two remaining make parity diffs vs Go:

- amount: non-numeric sheet values like "---" passed through as strings;
  Go's parseFloat silently returns 0.0 for unparseable values. Add
  get_float helper that matches that behaviour.
- message: numeric cell values (e.g. a bank reference in the message
  column) passed through as float64; Go's getVal uses fmt.Sprint and
  always emits a string. Apply get_str to the message field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 00:26:04 +02:00
07ca1cd9e1 fix(py): coerce VS column to string in payments tx projection
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
The Sheets API returns VS (variabilní symbol) cells as float64 when
the column is number-formatted, so Python was emitting vs: 0 (a JSON
number) while Go's getVal uses fmt.Sprint and always emits vs: "0"
(a string). Add get_str helper that converts whole-number floats via
int() first (matching Go's %g formatting), applied to the vs field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 23:59:35 +02:00
5dcac25c13 chore: CHANGELOG entry for M5.4 fix #2 (py vs/sync_id)
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 23:52:04 +02:00
fc47606b1c Merge pull request 'feat(py): M5.4 fix #2 — add vs and sync_id to payments tx projection' (#23) from fix/py-payments-add-vs-syncid into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Reviewed-on: #23
2026-05-07 21:51:12 +00:00
65694ad378 feat(py): M5.4 fix #2 — add vs and sync_id to payments tx projection
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Python's fetch_sheet_data read 9 sheet columns but skipped VS and
Sync ID, causing make parity to report extra fields on every raw
payment row emitted by the Go backend. Both columns are already on
the sheet; add idx_vs / idx_sync_id lookups and the matching keys
to the tx dict so the Python /api/* wire shape matches Go's
RawTransaction.

Update /api/* test fixtures to include vs/sync_id keys for realism.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 23:50:33 +02:00
092dff25a5 Merge pull request 'fix(go): accept single-digit day/month in attendance date headers' (#22) from fix/go-attendance-date-parser into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
Reviewed-on: #22
2026-05-07 21:39:02 +00:00
56c21bcf03 fix(go): accept single-digit day/month in attendance date headers
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
parseDates was using "02.01.2006" / "01/02/2006" which require
zero-padded fields. The Czech attendance sheet headers contain dates
like "1.6.2026", "23.3.2026", "6.4.2026" — Go silently dropped those
columns while Python's strptime accepted them. Effect was a missing
2026-06 month on /api/juniors plus undercounted attendance in any month
with single-digit columns; surfaced via make parity.

Use the unpadded reference forms "2.1.2006" / "1/2/2006" instead — Go's
time.Parse accepts both padded and unpadded inputs against them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:38:06 +02:00
208f762c18 Merge pull request 'feat(go): M5.4 — parity diff binary + make parity' (#19) from feat/go-m5-4-parity-binary into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
Reviewed-on: #19
2026-05-07 21:25:23 +00:00
4d035213b5 Merge pull request 'fix(go): pass raw value to FormatDate so numeric dates format' (#21) from fix/go-date-format into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Reviewed-on: #21
2026-05-07 21:24:42 +00:00
2b15280d03 fix(go): exclude /api/version from parity diff — identity, not contract
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
/api/version returns each binary's own tag/commit/build_date, which
differs by design between independently built backends. Diffing it
always produces a false positive. Drop it from allRoutes; the route
remains reachable via `make parity ARGS="-route /api/version"`.

Also remove the vestigial `build_meta` allowlist entry (Python returns
the build dict as the top-level response body, not nested under
build_meta, so the scrubber never matched anything).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:23:38 +02:00
723152cdad fix(go): pass raw value to FormatDate so numeric serial-day dates format
All checks were successful
Deploy to K8s / deploy (push) Successful in 11s
The transaction-row parser in services/membership/sources.go used a
helper (`getVal`) that did `fmt.Sprint(row[i])` before passing to
`matching.FormatDate`.  The Sheets API returns date-formatted cells
as `float64` (Sheets serial-day numbers); pre-stringifying defeated
`FormatDate`'s `case float64:` dispatch, so values like 46147 leaked
through unchanged as the string "46147" instead of being converted
to "2026-05-05".

Surfaced by `make parity` (M5.4) — every `transactions[].date` on
/api/adults and /api/juniors differed between Python and Go.  Python
side passes the raw value through directly (`isinstance(val, (int,
float))` in scripts/match_payments.py format_date), so it was always
correct.

Added a `getRaw` helper that returns row[i] without stringifying;
only the date column needs it.  Extended TestLoadTransactions with
a numeric-serial-day row to lock in the regression.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:17:45 +02:00
fe0e49a134 feat(go): M5.4 — parity diff binary + make parity
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Adds cmd/parity/main.go: a standalone Go binary that GETs
/api/version, /api/adults, /api/juniors, /api/payments from both
the Python (:5001) and Go (:8080) backends, scrubs an allowlist
(render_time.total, build_meta), and prints cmp.Diff for any
remaining differences.  Exits 0 on full match, 1 on diffs, 2 on
fetch/parse errors — CI-friendly for M7.2.

- go/cmd/parity/main.go: flags (-py, -go, -route, -timeout), fetch
  helper, allowlist scrubber (dotted-path aware), exit-code logic.
- go/cmd/parity/scrub_test.go: 4 unit tests for the scrubber.
- go/go.mod: promote github.com/google/go-cmp to direct dep.
- Makefile: parity target + help entry.
- Progress tracker: M5.4 ticked; milestone updated to M5 complete.
- Plan archived to docs/plans/2026-05-07-2254-m5-4-parity-binary.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:08:42 +02:00
e5a272b682 Merge pull request 'fix(go): default CacheDir to tmp/go to avoid Python collision' (#20) from fix/cache-collision into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 11s
Reviewed-on: #20
2026-05-07 21:07:18 +00:00
8b3064ffab fix(go): default CacheDir to tmp/go to avoid Python collision
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Previously both backends defaulted to `CacheDir=tmp` and used the
same cache keys (`attendance_regular`, `attendance_juniors`,
`payments_transactions`, `exceptions_dict`) but stored different
shapes: Python caches post-processed view-model tuples
(e.g. `(members, sorted_months)`), Go caches raw sheet rows.
Whichever backend wrote last poisoned the cache for the other,
producing `ValueError: too many values to unpack (expected 2,
got 68)` on Python's /adults after the Go side populated the
file with 68 raw CSV rows.

This breaks the M5.4 `make parity` workflow that requires both
backends running side-by-side.

Fix: change Go's default to `tmp/go` so the two cache trees
never overlap.  `CACHE_DIR` env var override still works.
`os.MkdirAll` already handles creating the new subdirectory on
first write.

Recovery for users with poisoned `tmp/`: hit /flush-cache on
the Python side once after pulling, then restart the Go server.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 23:06:34 +02:00
423c3e2a4b Merge pull request 'feat(py): M5.3 — Python /api/* shadow endpoints' (#18) from feat/go-m5-3-python-api-shadow into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Reviewed-on: #18
2026-05-07 20:42:54 +00:00
f4c497681f chore: CHANGELOG and progress tracker for M5.3
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 22:37:52 +02:00
40e4a9e45e feat(py): M5.3 — add Python /api/* shadow endpoints
Four new JSON routes mirror the Go /api/* handlers so the M5.4 parity
tool can diff them: /api/version, /api/adults, /api/juniors,
/api/payments. A small _unwrap_view_model_for_api() helper in app.py
expands the three pre-serialised JSON strings in the view-model dicts
and renames month_labels_json → month_labels and
raw_payments_json → raw_payments to match the Go wire contract.

Tests in test_app.py assert top-level key sets match the Go API schema
and that member_data, month_labels, raw_payments are objects not strings.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 22:37:14 +02:00
68810369bd Merge pull request 'feat(go): M5.2 — HTTP handlers for /api/adults, /api/juniors, /api/payments, /api/version' (#17) from feat/go-m5-2-api-handlers into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Reviewed-on: #17
2026-05-07 19:08:01 +00:00
2b7eff14c4 chore: CHANGELOG and progress tracker for M5.2
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 21:02:54 +02:00
7d48e8f607 feat(go): M5.2 — HTTP handlers for /api/adults, /api/juniors, /api/payments, /api/version
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
- Add web/api/handler.go: Handler struct wiring Sources+Config into ServeAdults,
  ServeJuniors, ServePayments, ServeVersion
- Add web/api/build_common.go: getMonthLabels, groupRawPaymentsByPerson, settledBalance,
  domain-to-wire converters, ensureSlice generic helper
- Add web/api/build_adults.go: buildAdultsResponse + buildAdultMemberRow mirroring
  scripts/views.py:build_adults_view_model
- Add web/api/build_juniors.go: buildJuniorsResponse + buildJuniorMemberRow mirroring
  scripts/views.py:build_juniors_view_model, including "?" sentinel and :NJ,MA breakdown
- Add web/api/build_payments.go: buildPaymentsResponse with Unmatched/Unknown bucket
- Extend reconcile.FeeData/MonthData with IsUnknown, JuniorAttendance, AdultAttendance
- Extend reconcile.Transaction with ManualFix, VS, BankID, SyncID for raw_payments wire field
- Export membership.AdultMergedMonths and JuniorMergedMonths
- Update sources.go to propagate new FeeData fields and parse extra transaction columns
- Wire sources+cfg into web.Run; register /api/* routes via Go 1.22 method+path patterns
- Fix pre-existing gofumpt formatting in fio_test.go and fio_table.go

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 20:13:38 +02:00
be4ecef20f Merge pull request 'feat(go): M5.1 — hand-author /api/* wire types + JSON Schemas' (#16) from feat/go-m5-1-api-structs into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Reviewed-on: #16
2026-05-07 17:50:55 +00:00
da5b82fcdb chore: CHANGELOG and progress tracker for M5.1
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 17:38:00 +02:00
f253e3fcb1 feat(go): M5.1 — hand-author /api/* wire types + JSON Schemas
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Add internal/web/api package with Go structs for every /api/X route:
AdultsResponse, JuniorsResponse, PaymentsResponse, VersionResponse.
All fields carry explicit json: tags matching the Python view-model keys.

Key design choices:
- member_data / month_labels / raw_payments are nested objects (not
  the pre-serialised JSON strings used in Jinja templates)
- Expected{Value int; Unknown bool} with custom MarshalJSON emits int
  or the string "?" for junior single-attendance months
- RawTransaction covers the full 11-column payments sheet row

schemagen_test.go reflects all four response types via
github.com/invopop/jsonschema and golden-compares against committed
schemas in tests/fixtures/api-schema/. The JSONSchema() method on
Expected lives in the test file so the prod binary has no jsonschema
dependency.

Closes M5.1 in docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 17:36:46 +02:00
59223c0da4 chore: CHANGELOG for Python view-model extraction
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:27:17 +02:00
32a16ff50d Merge pull request 'refactor(app): extract view-model builders into scripts/views.py' (#15) from feat/m5-python-views-extraction into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 5s
Reviewed-on: #15
2026-05-07 13:26:42 +00:00
2eec51bb34 fix(app): restore missing import re needed by qr_code route
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Accidentally removed when moving group_payments_by_person to views.py;
re.match in qr_code caused a 500 on every QR request.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:24:40 +02:00
b562ce3201 refactor(app): extract view-model builders into scripts/views.py
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Pull 350+ lines of inline per-row computation out of adults_view,
juniors_view, and payments into three pure builder functions with no
Flask globals or IO dependencies. Route handlers now contain only
cache/IO calls and a single render_template. No behaviour change —
all 27 tests pass.

Also moves get_month_labels, group_payments_by_person, and
adapt_junior_members out of app.py. Prep for /api/* shadow endpoints
(M5 Go parity).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 15:22:12 +02:00
f0de300292 chore: CHANGELOG for --print-fio-table, debug logging, and date parser fix
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:13:56 +02:00
2164e99866 Merge pull request 'feat(go): add --print-fio-table debug flag to fuj sync' (#14) from feat/fuj-sync-print-fio-table into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Reviewed-on: #14
2026-05-07 12:13:19 +00:00
b41b8ef29c fix(go/fio): accept 2-digit year format in transparent date parser
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Fio's transparent account page now serves dates as DD.MM.YY (e.g.
07.05.26) rather than the previously expected 4-digit-year format.
Extends parseCzechDate to try all eight layout variants: padded and
non-padded, dot and slash separators, 4-digit and 2-digit years.

Go maps 2-digit year 00-68 → 2000-2068, so 26 → 2026.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:12:34 +02:00
80db33945d chore: add make go-sync-debug target
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Wraps `LOG_LEVEL=DEBUG ./bin/fuj sync -dry-run -print-fio-table -days N`
behind a single make target. Default DAYS=30, override with
`make go-sync-debug DAYS=90`. Builds the Go binary first via go-build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 14:01:46 +02:00
f87adeff9f feat(go/fio): debug logging via slog at LOG_LEVEL=DEBUG
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Wires slog.SetDefault to honour LOG_LEVEL in all CLI commands and adds
debug logs on the Fio fetch path so a silent "fetched 0 transaction(s)"
can be diagnosed without code changes:

- fio.New: which client variant (api/transparent) was selected
- apiClient: GET URL (token redacted as ****), HTTP status, body bytes,
  parsed transaction count
- transparentClient: GET URL, HTTP status, body bytes, plus parser
  stats (raw rows from second table, kept, dropped_bad_date,
  dropped_nonpositive_amount)

Also suppresses the --print-fio-table block when zero transactions were
fetched, so the bare header no longer prints under that condition.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:59:22 +02:00
a7cf45fc95 feat(go): add --print-fio-table flag to fuj sync --dry-run
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Prints an aligned tabwriter table of every Fio transaction in the
look-back window, with a STATUS column showing NEW (would be appended)
or DUP (already in sheet). Only fires when --dry-run is also set, so
it can't affect real syncs. Refactors Sync ID computation into a single
pre-pass shared by both the table printer and the row builder.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 13:49:42 +02:00
f0a0f79475 Merge pull request 'feat(go): IO layer behind interfaces (M4)' (#13) from feat/m4-io-layer into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
Reviewed-on: #13
2026-05-07 08:48:54 +00:00
fcb83691f5 fix(go/fio): nested-table early exit + non-padded date parsing
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
extractSecondTableRows tracked a boolean inTarget flag and exited on
the first </table> token while inside the target. Any nested <table>
(e.g. pagination markup in the real Fio page) would cause an early
return before reading any data rows, explaining the 0-transaction report.
Fixed by tracking targetDepth instead: depth increments on every <table>
inside the target and we only return when it reaches 0 again.

parseCzechDate also only tried zero-padded layouts ("02.01.2006").
The real Fio transparent page emits non-padded dates ("7.5.2026");
added "2.1.2006" and "2/1/2006" as the preferred layouts.

Also adds a dry-run diagnostic line ("fetched N transaction(s) from Fio")
so the fetch vs dedup split is visible without reading logs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 10:47:54 +02:00
8275db1a63 fix(go/fio): nil http client panic in fio.New
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
When token is empty, New falls back to transparentClient with the
caller-supplied hc. main.go passes nil, so the first Do() call panicked.
Default to http.DefaultClient when hc is nil.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 10:36:20 +02:00
36a28a40d2 feat(go): add --dry-run to fuj sync
All checks were successful
Deploy to K8s / deploy (push) Successful in 18s
Mirror fuj infer's read-only mode: SyncOpts.DryRun skips WriteHeader,
AppendValues, and SortByDateColumn, printing one "Dry run: would …"
line per planned operation instead. ID-dedup still runs so the output
reflects exactly what the next real sync would write.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 10:33:55 +02:00
6465e2a221 feat(go): IO layer behind interfaces (M4)
All checks were successful
Deploy to K8s / deploy (push) Successful in 11s
- io/attendance: CSV-over-public-URL client + Fake for adult/junior tabs
- io/drive: Drive v3 modifiedTime client + Fake
- io/sheets: Sheets v4 client (GetValues/AppendValues/BatchUpdateValues/
  WriteHeader/SortByDateColumn) + Fake with call-capture
- io/cache: Drive-modifiedTime-gated FileCache; two TTL knobs; atomic
  writes; generic Get[T]; Python-compatible JSON format; Flush()
- io/fio: Client interface backed by Fio REST API (apiClient) and HTML
  scraper (transparentClient); Fake; testdata fixtures
- membership/sources: NewSources wires attendance CSV + Sheets + cache
  into LoadAdults/LoadJuniors/LoadTransactions/LoadExceptions; Czech
  month parsing + merged-month maps
- banksync: SyncToSheets (SHA-256 dedup, optional sort) and
  InferPayments ([?] review prefix, dry-run) — tested with fakes
- cmd/fuj: sync and infer subcommands wired; fees and reconcile use
  real NewSources; go.mod gains google.golang.org/api + x/net
- gofumpt extra-rules applied across all packages; lint clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 01:05:59 +02:00
7afd12d9a5 chore: tick M3.1–M3.6 in progress tracker + CHANGELOG entry
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Merges SHA 57518a8 (PR #12).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 23:34:00 +02:00
57518a8a68 Merge pull request 'feat(go): fixture capture + characterization framework (M3)' (#12) from feat/m3-fixture-capture into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Reviewed-on: #12
2026-05-06 21:29:48 +00:00
67d2f11d7c feat(go): fixture capture + characterization framework (M3)
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
Closes M3.1–M3.6.  Parity safety net proving Go output matches Python
for every ported pure-domain function (M2.1–M2.9) and reconcile (M2.10).

Capture pipeline:
- scripts/capture_fixtures.py: calls each Python function with seeded
  inputs, emits JSON fixtures to stdout (never writes files directly).
- scripts/scrub_fixtures.py: deterministic PII scrubber — SHA-256
  pseudonyms for member names, digit-preserving hashes for VS/account/
  bank_id, name-sweep in message text.  Idempotent; no salt.
- scripts/_fixture_seeds.py: handcrafted seeds for all 11 functions;
  synthetic names throughout (no real roster members).
- scripts/capture_all_fixtures.sh: convenience wrapper for full corpus
  regeneration outside of make.

Fixture corpus (98 files, all PII-free):
- go/tests/fixtures/pure/<func>/<case>.json — 10 function directories.
- go/tests/fixtures/reconcile/<NN>_<case>.json — 10 branch-coverage
  cases: greedy, overpayment credit, proportional remainder, even-split,
  out-of-window, exception override, other: purpose, junior ?, multi-
  person+month fan-out, unmatched.

Go parity tests (//go:build parity):
- go/tests/parity/parityio.go: generic LoadDir/RunAll helpers + typed
  In/Out struct pairs for all 10 pure functions; Envelope decoder for
  int/float/none disambiguation.
- 10 pure-function test packages + bespoke reconcile test with per-cell
  float tolerance (math.Abs <= 0.01 for `paid` values).

Makefile: go-parity, go-test-all, capture-fixtures targets.
go/tests/fixtures/README.md: refresh workflow + PII audit guide.

Gate: make go-test green, make go-parity green (11/11 packages),
      make go-lint clean (parity tag), make go-build clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 23:26:24 +02:00
28f0e468f7 Merge pull request 'feat(go): wire fuj fees + fuj reconcile (M2.11-12)' (#11) from feat/m2-11-12-fees-reconcile-cli into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Reviewed-on: #11
2026-05-06 15:54:33 +00:00
8386af8078 chore: tick M2.11 + M2.12 in progress tracker + CHANGELOG entry
All checks were successful
Deploy to K8s / deploy (push) Successful in 12s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 17:50:41 +02:00
56aa2303a8 feat(go/M2.11-12): wire fuj fees + fuj reconcile subcommands
Add internal/services/membership package: AttendanceLoader,
TransactionLoader, ExceptionLoader interfaces + NewStubSources stub
(returns ErrIOPending until M4 lands real Sheets loaders).

FeesReport and ReconcileReport orchestrate domain/fees + domain/reconcile
and write fixed-width text reports matching Python calculate_fees.py and
match_payments.py print_report output. 13 unit tests cover all formatter
branches and orchestration wiring via fake loaders.

cmd/fuj/main.go: fees and reconcile subcommands now dispatch; sync/infer
retain the [M4] placeholder.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 17:50:31 +02:00
ea8622a541 Merge pull request 'feat(go/M2.10): port domain/reconcile.Reconcile' (#10) from feat/m2-10-reconcile-domain into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Reviewed-on: #10
2026-05-06 14:55:17 +00:00
71278e6f7a chore: tick M2.10 in progress tracker + CHANGELOG entry
All checks were successful
Deploy to K8s / deploy (push) Successful in 16s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 16:53:39 +02:00