feat(go): fixture capture + characterization framework (M3) #12
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## 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>/` and `go/tests/fixtures/reconcile/`; all PII-free.
|
||||
- `go/tests/parity/parityio.go`: shared loader with generic `LoadDir`/`RunAll` helpers and typed `In`/`Out` structs 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-fixtures` targets.
|
||||
- `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/membership` package: `AttendanceLoader`, `TransactionLoader`, `ExceptionLoader` interfaces, a stub (`NewStubSources`) that returns `ErrIOPending`, and `FeesReport` / `ReconcileReport` orchestration functions backed by real `domain/fees` + `domain/reconcile` logic.
|
||||
|
||||
28
Makefile
28
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: help fees match web web-py web-debug web-go go-build go-test go-run go-lint image run sync sync-2026 test test-v docs
|
||||
.PHONY: help fees match web web-py web-debug web-go go-build go-test go-test-all go-parity go-run go-lint capture-fixtures image run sync sync-2026 test test-v docs
|
||||
|
||||
export PYTHONPATH := scripts:$(PYTHONPATH)
|
||||
VENV := .venv
|
||||
@@ -23,8 +23,11 @@ help:
|
||||
@echo " make web-go - Build and start Go dashboard on :8080"
|
||||
@echo " make web-debug - Start Python dashboard in debug mode"
|
||||
@echo " make go-build - Build Go binary to bin/fuj"
|
||||
@echo " make go-test - Run Go tests"
|
||||
@echo " make go-test - Run Go unit tests"
|
||||
@echo " make go-parity - Run Go parity tests (requires -tags=parity fixture corpus)"
|
||||
@echo " make go-test-all - Run both unit and parity tests"
|
||||
@echo " make go-lint - Run golangci-lint on Go code"
|
||||
@echo " make capture-fixtures - Regenerate parity fixture corpus from live Python"
|
||||
@echo " make image - Build Python OCI container image"
|
||||
@echo " make run - Run the built Python Docker image locally"
|
||||
@echo " make sync - Sync Fio transactions to Google Sheets"
|
||||
@@ -64,6 +67,27 @@ go-build:
|
||||
go-test:
|
||||
cd $(GO_SRC) && go test -race ./...
|
||||
|
||||
go-parity:
|
||||
cd $(GO_SRC) && go test -tags=parity ./tests/parity/...
|
||||
|
||||
go-test-all: go-test go-parity
|
||||
|
||||
capture-fixtures: $(PYTHON)
|
||||
@echo "Capturing and scrubbing fixtures for all registered functions..."
|
||||
@for func in normalize parse_month_references calculate_fee calculate_junior_fee \
|
||||
parse_czk_amount generate_sync_id build_name_variants match_members \
|
||||
infer_transaction_details format_date reconcile; do \
|
||||
dir="go/tests/fixtures/$$([[ $$func == reconcile ]] && echo reconcile || echo pure/$$func)"; \
|
||||
mkdir -p "$$dir"; \
|
||||
PYTHONPATH=scripts:. $(PYTHON) scripts/capture_fixtures.py --func $$func --all \
|
||||
| while IFS= read -r line; do \
|
||||
case_id=$$(echo "$$line" | $(PYTHON) -c "import sys,json; print(json.load(sys.stdin)['case'])"); \
|
||||
echo "$$line" | $(PYTHON) scripts/scrub_fixtures.py > "$$dir/$${case_id}.json"; \
|
||||
done; \
|
||||
echo " $$func done"; \
|
||||
done
|
||||
@echo "capture-fixtures complete."
|
||||
|
||||
go-run: go-build
|
||||
./$(GO_BIN) $(ARGS)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Companion to [2026-05-03-2349-go-backend-rewrite.md](2026-05-03-2349-go-backend-rewrite.md).
|
||||
|
||||
**Current milestone:** M2 — Pure-domain helpers
|
||||
**Current milestone:** M3 — Fixture capture + characterization framework ✅
|
||||
**Started:** 2026-05-04
|
||||
**Last updated:** 2026-05-06
|
||||
|
||||
@@ -65,14 +65,14 @@ Each task: port the function, write Go unit tests for fresh cases, hook into the
|
||||
|
||||
Goal: deterministic, PII-free fixture corpus that drives parity tests. Runs in parallel with M2 (M3.1/M3.2 unblocks M2.1).
|
||||
|
||||
- [ ] **M3.1** `scripts/capture_fixtures.py` — pure-function output dumper. Reads inputs from stdin / argv, prints `{"input":..., "output":...}` JSON
|
||||
- [ ] **M3.2** `scripts/scrub_fixtures.py` — replaces names with `Member_<8hex>` (deterministic per name); scrambles sender/account/VS/bank_id with stable bijection; preserves dates, amounts, exception keys
|
||||
- [ ] **M3.3** Capture pure-fn fixtures for M2.1–M2.9 (run helper + scrubber, commit to `tests/fixtures/pure/<func>/<case>.json`)
|
||||
- [ ] **M3.4** Capture ~10 reconcile fixtures spanning every code path: greedy, proportional (float remainder), even-split, out-of-window credit, exception override, `other:` purpose, junior `"?"`, multi-person comma-split, multi-month range, unmatched. Commit to `tests/fixtures/reconcile/`
|
||||
- [ ] **M3.5** Hook fixtures into Tier-1 test runner with `-tags=parity` build constraint
|
||||
- [ ] **M3.6** Document fixture-refresh workflow in `tests/fixtures/README.md` (what to do when sheet schema changes)
|
||||
- [x] **M3.1** `scripts/capture_fixtures.py` — pure-function output dumper. Reads inputs from stdin / argv, prints `{"input":..., "output":...}` JSON
|
||||
- [x] **M3.2** `scripts/scrub_fixtures.py` — replaces names with `Member_<8hex>` (deterministic per name); scrambles sender/account/VS/bank_id with stable bijection; preserves dates, amounts, exception keys
|
||||
- [x] **M3.3** Capture pure-fn fixtures for M2.1–M2.9 (run helper + scrubber, commit to `tests/fixtures/pure/<func>/<case>.json`)
|
||||
- [x] **M3.4** Capture ~10 reconcile fixtures spanning every code path: greedy, proportional (float remainder), even-split, out-of-window credit, exception override, `other:` purpose, junior `"?"`, multi-person comma-split, multi-month range, unmatched. Commit to `tests/fixtures/reconcile/`
|
||||
- [x] **M3.5** Hook fixtures into Tier-1 test runner with `-tags=parity` build constraint
|
||||
- [x] **M3.6** Document fixture-refresh workflow in `tests/fixtures/README.md` (what to do when sheet schema changes)
|
||||
|
||||
**Gate:** `tests/fixtures/` populated; M2 parity tests green; raw `tmp/*.json` confirmed gitignored.
|
||||
**Gate:** ✅ `tests/fixtures/` populated (98 files); `make go-parity` green; `make go-lint` (parity tag) clean; raw `tmp/*.json` confirmed gitignored.
|
||||
|
||||
---
|
||||
|
||||
|
||||
261
docs/plans/2026-05-06-2111-go-m3-fixture-capture.md
Normal file
261
docs/plans/2026-05-06-2111-go-m3-fixture-capture.md
Normal file
@@ -0,0 +1,261 @@
|
||||
# M3 — Fixture capture + characterization framework
|
||||
|
||||
> On approval: copy this plan to `docs/plans/2026-05-06-2111-go-m3-fixture-capture.md` per [CLAUDE.md](../../srv/personal/fuj-management/CLAUDE.md) plan-location convention.
|
||||
|
||||
## Context
|
||||
|
||||
The Go rewrite (tracked in [docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md](../../srv/personal/fuj-management/docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md)) finished M2.1–M2.12 — every pure-domain helper is ported and the `fuj fees` / `fuj reconcile` CLIs are wired. M3 closes the loop: it builds the **parity safety net** that proves Go output matches Python output for every ported function. Without it, M2 is "trust me", and the rewrite has no defensible cutover criterion.
|
||||
|
||||
M3 has three deliverables:
|
||||
|
||||
1. **A capture pipeline** (`scripts/capture_fixtures.py` + `scripts/scrub_fixtures.py`) that produces deterministic, PII-free JSON fixtures from the live Python implementations.
|
||||
2. **A fixture corpus** at [go/tests/fixtures/](../../srv/personal/fuj-management/go/tests/fixtures/) covering the 10 pure functions of M2 (M2.1–M2.9) plus 10 reconcile cases spanning every code path of `reconcile()` (M2.10).
|
||||
3. **A parity test runner** in [go/tests/parity/](../../srv/personal/fuj-management/go/tests/parity/) under `//go:build parity` that replays each fixture and asserts byte/value equality against the Go port.
|
||||
|
||||
User-confirmed scope decisions:
|
||||
- **Single MR** for all six sub-tasks (M3.1–M3.6) — they're tightly coupled; no half-state is committable.
|
||||
- **Type envelope only where it matters** — four fields (`generate_sync_id.tx.amount`, `parse_czk_amount.val`, `format_date.val`, `infer_transaction_details.tx.date`) use `{"type":..., "value":...}` to disambiguate int/float/none. Everything else uses raw JSON.
|
||||
- **Real seeds for `parse_month_references` and `match_members` only** — read curated message strings from `tmp/payments_transactions_cache.json`, scrub, ship. Other functions stay on handcrafted seeds.
|
||||
- **Plan committed at `docs/plans/2026-05-06-2111-go-m3-fixture-capture.md`** — same convention as every M-series predecessor.
|
||||
|
||||
## Branch + landing
|
||||
|
||||
- Branch: `feat/m3-fixture-capture`. Single MR via `tea pr create`. Tick M3.1–M3.6 on merge with the SHA.
|
||||
- No edits to existing Python or Go production code. M3 is purely additive: new scripts, new fixtures, new test files, new Makefile targets, README, CHANGELOG entry, plan archive, progress tracker tick.
|
||||
|
||||
## File layout
|
||||
|
||||
**Python (capture pipeline):**
|
||||
- [scripts/capture_fixtures.py](../../srv/personal/fuj-management/scripts/capture_fixtures.py) — dispatcher CLI; one entry per function via `--func`.
|
||||
- [scripts/scrub_fixtures.py](../../srv/personal/fuj-management/scripts/scrub_fixtures.py) — stdin→stdout deterministic bijection scrubber.
|
||||
- [scripts/_fixture_seeds.py](../../srv/personal/fuj-management/scripts/_fixture_seeds.py) — internal: handcrafted seeds keyed by `(func, case_id)`, plus the curated real-message extractor.
|
||||
|
||||
**Fixture corpus** (committed, PII-free):
|
||||
- [go/tests/fixtures/README.md](../../srv/personal/fuj-management/go/tests/fixtures/README.md) — refresh workflow + scrubbing audit guide.
|
||||
- `go/tests/fixtures/pure/<func>/<case>.json` — one directory per function (10 functions: `normalize`, `parse_month_references`, `calculate_fee`, `calculate_junior_fee`, `parse_czk_amount`, `generate_sync_id`, `build_name_variants`, `match_members`, `infer_transaction_details`, `format_date`).
|
||||
- `go/tests/fixtures/reconcile/<NN>_<case>.json` — 10 numbered reconcile cases.
|
||||
|
||||
**Go parity tests** (all under `//go:build parity`):
|
||||
- [go/tests/parity/parityio.go](../../srv/personal/fuj-management/go/tests/parity/parityio.go) — shared loader with generic `Case[I,O]` walker, type envelopes mirrored from §3, float tolerance helper.
|
||||
- [go/tests/parity/pure/<func>/<func>_parity_test.go](../../srv/personal/fuj-management/go/tests/parity/pure/) — one file per function, ~30 lines each.
|
||||
- [go/tests/parity/reconcile/reconcile_parity_test.go](../../srv/personal/fuj-management/go/tests/parity/reconcile/) — bespoke comparator using `math.Abs(got-want) <= 0.01` for `paid` floats, exact equality on int balances.
|
||||
|
||||
**Modified:**
|
||||
- [Makefile](../../srv/personal/fuj-management/Makefile) — append `go-parity`, `go-test-all`, `capture-fixtures` targets.
|
||||
- [CHANGELOG.md](../../srv/personal/fuj-management/CHANGELOG.md) — single entry at top.
|
||||
- [docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md](../../srv/personal/fuj-management/docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md) — tick M3.1–M3.6 with SHA.
|
||||
|
||||
## Capture invocation interface
|
||||
|
||||
Two-stage pipeline (capture | scrub) so each stage is independently debuggable:
|
||||
|
||||
```bash
|
||||
python scripts/capture_fixtures.py --func <name> --case <id> --input-seed <id> \
|
||||
| python scripts/scrub_fixtures.py \
|
||||
> go/tests/fixtures/pure/<func>/<id>.json
|
||||
```
|
||||
|
||||
Capture flags:
|
||||
- `--func` — target function (`normalize`, `reconcile`, etc.).
|
||||
- `--case` — human-authored case ID, becomes the file stem. Never auto-generated (auto-IDs cause git churn).
|
||||
- `--input-seed <id>` — pull from `_fixture_seeds.py` registry (the default mode for handcrafted cases).
|
||||
- `--input-stdin` — read a single JSON `{"args":[...], "kwargs":{...}}` doc from stdin (used by the real-message extractor for `parse_month_references` / `match_members`).
|
||||
- `--all` — iterate every seed for one function, emit newline-delimited JSON to stdout. Used by the `make capture-fixtures` recipe.
|
||||
|
||||
Capture **never writes files**. Output goes to stdout; the caller redirects. The scrubber is always stdin→stdout. Both are pure transforms.
|
||||
|
||||
The `make capture-fixtures` target codifies the full refresh workflow. Humans read the target before they read the README.
|
||||
|
||||
## Fixture JSON shape (normative)
|
||||
|
||||
One JSON object per case:
|
||||
|
||||
```json
|
||||
{
|
||||
"case": "range_wrap_nov_to_jan",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": { ... },
|
||||
"output": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
`captured_at` is date-only — same-day re-runs produce byte-identical files. No git SHA, no hostname, no time component.
|
||||
|
||||
### Per-function input/output schemas
|
||||
|
||||
The schema is the **stable contract** between Python capture and Go consumption. Where Python returns heterogeneous types, the capture step pre-translates to the typed shape Go expects.
|
||||
|
||||
| Function | Input | Output |
|
||||
|---|---|---|
|
||||
| `normalize` | `{"text":"…"}` | `{"text":"…"}` |
|
||||
| `parse_month_references` | `{"text":"…","default_year":2026}` | `{"months":["2026-01",…]}` |
|
||||
| `calculate_fee` | `{"attendance_count":3,"month_key":"2026-02"}` | `{"fee":750}` |
|
||||
| `calculate_junior_fee` | `{"attendance_count":1,"month_key":"2026-02"}` | `{"value":0,"unknown":true}` (mirrors `fees.Expected{Value, Unknown}`) |
|
||||
| `parse_czk_amount` | `{"val":<envelope>}` | `{"amount":1500.0}` |
|
||||
| `generate_sync_id` | `{"tx":{"date":"…","amount":<envelope>,"currency":"CZK","sender":"…","vs":"…","message":"…","bank_id":"…"}}` | `{"sync_id":"<sha256-hex>"}` |
|
||||
| `_build_name_variants` | `{"name":"…"}` | `{"variants":["…"]}` |
|
||||
| `match_members` | `{"text":"…","member_names":["…"]}` | `{"matches":[{"name":"…","confidence":"auto"}]}` |
|
||||
| `infer_transaction_details` | `{"tx":{"sender":"…","message":"…","user_id":"…","date":<envelope>},"member_names":[…],"default_year":2026}` | `{"members":[…],"months":[…],"search_text":"…"}` |
|
||||
| `format_date` | `{"val":<envelope>}` | `{"date":"…"}` |
|
||||
|
||||
**Type envelope** (used in 4 fields above):
|
||||
|
||||
```json
|
||||
{"type":"int","value":750} // distinguishes 750 from 750.0
|
||||
{"type":"float","value":750.0}
|
||||
{"type":"string","value":"…"}
|
||||
{"type":"none"}
|
||||
```
|
||||
|
||||
The envelope is the answer to the `generate_sync_id` parity risk: Python's `str(750.0) == "750.0"` vs `str(750) == "750"` produces different SHA-256 inputs. JSON natively conflates these; the envelope round-trips them. Go's loader switches on `type` and constructs the matching native value before calling the port.
|
||||
|
||||
**`reconcile`** uses raw JSON for everything (its inputs are typed maps/slices already), with one nuance: the `Member.fees[month]` value can be an `int` or a `(fee, count)` tuple per [match_payments.py:339-340](../../srv/personal/fuj-management/scripts/match_payments.py#L339). Capture normalises both to `{"fee":int,"count":int}` so Go side has one shape.
|
||||
|
||||
## Scrubber strategy
|
||||
|
||||
`scrub_fixtures.py`: stdin → stdout, no state, no salt, no random. Deterministic plain SHA-256. Re-runs are idempotent. Trade-off acknowledged: an attacker with the script can mathematically reverse the mapping. That's fine — the scrubber's job is to keep PII out of git diffs and Claude transcripts, not to defend against an adversary with the source tree.
|
||||
|
||||
### Scramble whitelist (only these field keys are scrambled)
|
||||
|
||||
`name`, `member_names[]`, `person`, `sender`, `sender_account`, `account`, `vs`, `bank_id`, `user_id`, `note`. Plus a per-document name-substring sweep over `message` strings — applied **before** the field-key walk, because real names show up embedded in message text.
|
||||
|
||||
Everything else (dates, amounts, currency, `month_key`, `attendance_count`, `purpose`, `confidence`, `expected`, `paid`, `total_balance`, `fee`, all `YYYY-MM` keys, `match`/`matches` structure) is preserved verbatim. **Whitelist-of-scramble** (not blacklist-of-preserve): when a new field appears, it stays raw until someone explicitly adds it to the list. Fails safe.
|
||||
|
||||
### Scrambling functions
|
||||
|
||||
- **Names**: `Member_<8hex>` where `<8hex> = sha256(name).hexdigest()[:8]`. Same name → same pseudonym across the whole document and across all fixtures. Stable diffs.
|
||||
- **Account numbers** (`[0-9]+/[0-9]{4}`): scramble prefix and bank-suffix separately, preserving length and format.
|
||||
- **VS / bank_id / user_id**: digit-string-preserving hash to a same-length numeric token. Non-numeric input → `id_<8hex>`.
|
||||
- **Note**: replaced verbatim with `"<scrubbed>"`. Notes are never load-bearing for any test.
|
||||
- **Message** (free text): name-sweep applied; rest preserved. Corpus author spot-checks before commit. README §5 documents the audit grep.
|
||||
|
||||
## Reconcile fixtures (10 handcrafted cases)
|
||||
|
||||
All seeds live in `_fixture_seeds.py` as triples `(members, sorted_months, transactions, exceptions, default_year)`. Capture runs the live Python `reconcile()` and emits canonical JSON; scrubber is a no-op for handcrafted synthetic names but runs anyway for uniformity.
|
||||
|
||||
| File | Branch exercised |
|
||||
|---|---|
|
||||
| `01_greedy_exact.json` | Greedy: amount == sum(expected); zero credit. |
|
||||
| `02_greedy_overpayment_credit.json` | Greedy with overflow → credit. |
|
||||
| `03_proportional_remainder.json` | Underpayment across 3 months with non-integer split (last month absorbs float remainder per [match_payments.py:421+](../../srv/personal/fuj-management/scripts/match_payments.py#L421)). |
|
||||
| `04_even_split_prepayment.json` | All `expected == 0` → even-split fallback. |
|
||||
| `05_out_of_window_credit.json` | Month outside `sorted_months` → that share goes to credits, in-window proportional for the rest. |
|
||||
| `06_exception_override.json` | Exception entry overrides expected. |
|
||||
| `07_other_purpose_split.json` | `purpose="other:tournament"` with two members. |
|
||||
| `08_junior_question_mark.json` | Junior with attendance count 1 → `Expected{Unknown:true}`; reconcile reads it as 0 expected. |
|
||||
| `09_multiperson_multimonth.json` | `person="Alice, Bob", purpose="2026-01, 2026-02"` → 2x2 fan-out: even-split-by-people then proportional-by-month. |
|
||||
| `10_unmatched.json` | Empty `person`, garbage message → goes to `unmatched`. |
|
||||
|
||||
The seed registry is the **single source of truth** for these inputs. If Python behaviour drifts intentionally, fixtures regenerate cleanly via `make capture-fixtures`.
|
||||
|
||||
## Real-data seeds (for `parse_month_references` and `match_members` only)
|
||||
|
||||
`_fixture_seeds.py` reads `tmp/payments_transactions_cache.json` (already gitignored) and selects:
|
||||
|
||||
- **`parse_month_references`**: ~15 distinct messages exercising the 45 Czech month declensions, range wraps (`"prosinec-leden"`), year inference, and the `m >= 10 → previous year` heuristic. Selection done once interactively, the chosen indices hardcoded into `_fixture_seeds.py` so re-runs are deterministic. Messages flow through capture (which calls `parse_month_references(msg, default_year=2026)`) then scrubber (name-sweep against the live member roster).
|
||||
- **`match_members`**: ~10 distinct `(message, member_names)` pairs exercising auto vs review confidence, common-surname filter, exact-short-circuit. Same pipeline.
|
||||
|
||||
**Out of scope for real seeds**: `normalize`, `_build_name_variants`, `reconcile`. These either don't benefit from real data (synthetic exhaustively covers `normalize`, `_build_name_variants`) or have surgical-input requirements that real data can't reliably hit (`reconcile`'s 10 branches).
|
||||
|
||||
## Go parity-test layout
|
||||
|
||||
One file per function, one Go package per function, mirroring the fixture tree. Each file is short (~30 lines):
|
||||
|
||||
```go
|
||||
//go:build parity
|
||||
|
||||
package normalize_parity_test
|
||||
|
||||
import (
|
||||
"fuj-management/go/internal/domain/czech"
|
||||
"fuj-management/go/tests/parity"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNormalizeParity(t *testing.T) {
|
||||
t.Parallel()
|
||||
parity.RunAll(t, "../../../fixtures/pure/normalize",
|
||||
func(in parity.NormalizeIn) parity.NormalizeOut {
|
||||
return parity.NormalizeOut{Text: czech.Normalize(in.Text)}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
The shared [go/tests/parity/parityio.go](../../srv/personal/fuj-management/go/tests/parity/parityio.go) (also `//go:build parity`) provides:
|
||||
|
||||
- `Case[I, O any]` generic loader: walks a fixture directory, decodes each `.json`, returns `(name, input, want)` triples.
|
||||
- `RunAll[I, O any](t, dir, fn func(I) O)`: invokes `fn`, compares against `want` with `reflect.DeepEqual` (sorted-slice normalisation for the few sets-cast-to-lists Python returns); for floats uses `math.Abs(got-want) <= 0.01`.
|
||||
- One typed `<Func>In` / `<Func>Out` struct pair per function (10 pairs), mirroring §3's JSON shape exactly. Envelope decoder helpers (`AmountEnvelope`, `ValueEnvelope`) live here.
|
||||
|
||||
**Reconcile is bespoke** — `reconcile/reconcile_parity_test.go` doesn't use `RunAll` because it needs cell-by-cell tolerant float compare across nested maps. It walks the fixture dir directly.
|
||||
|
||||
**Why one-file-per-function** (instead of an umbrella runner): each function lives in a different domain package, so tests must `import` a different package; an umbrella would obscure which package is being checked. Split also enables `go test -tags=parity ./tests/parity/pure/normalize/` to iterate on a single port.
|
||||
|
||||
**Why a separate test tree** (instead of co-located parity tests): the M2 unit tests are co-located by convention (e.g. [go/internal/domain/czech/normalize_test.go](../../srv/personal/fuj-management/go/internal/domain/czech/normalize_test.go)). The progress tracker explicitly says fixtures live at `go/tests/fixtures/` and the gate is `go test -tags=parity ./tests/parity/pure/...`. Co-location would scatter fixtures across packages — messy. Separate tree wins.
|
||||
|
||||
## Build tag + Makefile
|
||||
|
||||
Every parity test file starts with `//go:build parity`. Default `make go-test` excludes them; `make go-parity` runs them:
|
||||
|
||||
```makefile
|
||||
go-parity:
|
||||
cd $(GO_SRC) && go test -tags=parity ./tests/parity/...
|
||||
|
||||
go-test-all: go-test go-parity
|
||||
|
||||
capture-fixtures:
|
||||
@bash scripts/capture_all_fixtures.sh # invokes capture | scrub for every seed
|
||||
```
|
||||
|
||||
Parity is **not** folded into default `go-test`: keeps the M2 unit-test loop fast, and a missing-fixture failure shouldn't block routine work. CI runs both targets independently so a parity break is a distinct red signal from a unit-test break.
|
||||
|
||||
## README content (`go/tests/fixtures/README.md`)
|
||||
|
||||
Six sections, ~120 lines:
|
||||
|
||||
1. **What's in this tree** — directory map; one line per fixture function explaining what it validates.
|
||||
2. **Fixture format** — link to schemas in §3; worked example for `parse_month_references` and one for `reconcile`.
|
||||
3. **Refresh workflow** — `make capture-fixtures` regenerates everything; single-file recipe for incremental updates. Always diff before committing.
|
||||
4. **When to refresh** — bullet list (schema change, new Czech declension, new fee tier, new reconcile branch). **Do not refresh to "fix" a parity failure** without first proving the Python behaviour is the intended one.
|
||||
5. **Verifying scrubbing** — `git diff` should show only `Member_<hex>`-shaped names, `<scrubbed>` notes, structurally-preserved account/VS digits. Audit grep: `git ls-files go/tests/fixtures | xargs grep -l '<your real name>'` should return zero before commit.
|
||||
6. **Adding a new fixture** — three steps (add to `_fixture_seeds.py`, run capture, add `In/Out` Go struct fields if needed).
|
||||
|
||||
## Parity concerns
|
||||
|
||||
- **Float arithmetic in reconcile proportional phase**: ordering-sensitive, may diverge between Python and Go due to FMA. Tolerance `0.01` already in [go/internal/domain/reconcile/reconcile_test.go](../../srv/personal/fuj-management/go/internal/domain/reconcile/reconcile_test.go); parity uses the same tolerance.
|
||||
- **Sync-ID float-vs-int stringification**: handled by the envelope (§3). Capture two paired cases per amount value (`amount_750_int.json`, `amount_750_float.json`) so any Go-side conflation surfaces immediately.
|
||||
- **NFKD edge cases**: capture set must include rare characters from real names. The handcrafted `normalize` seeds enumerate every distinct character observed in the live member roster (extracted once from `tmp/attendance_regular_cache.json`, hardcoded into `_fixture_seeds.py` as a single-character-per-case sweep).
|
||||
- **Czech month declensions**: the real-message seeds for `parse_month_references` cover the wild; handcrafted seeds cover the corner cases (`prosinec-leden` wrap, `m >= 10` heuristic).
|
||||
- **Insertion-order determinism in `reconcile`**: Python 3.7+ dict iteration is insertion-ordered; the seed registry preserves order. Go side iterates `sortedMonths` slice explicitly; the parity test verifies this.
|
||||
- **`infer_transaction_details` default_year**: Python signature defaults to 2026; capture passes `default_year` as an explicit input. Go side reads it from the fixture.
|
||||
|
||||
## Out of scope (explicitly DO NOT touch)
|
||||
|
||||
- Real Google Sheets / Drive / Fio loader implementations — M4.1–M4.6.
|
||||
- Web routes / handlers — M5.
|
||||
- `fuj sync` and `fuj infer` subcommands — M4.7/M4.8.
|
||||
- Tier-2 JSON-API parity (`cmd/parity/main.go`) — M5.4.
|
||||
- Any change to existing Python code (capture is read-only against the production scripts).
|
||||
- Any change to existing Go production code under `go/internal/`.
|
||||
|
||||
## Verification
|
||||
|
||||
1. `make go-build` — clean build (parity tests excluded by default tag).
|
||||
2. `make go-test` — all M2 unit tests still green; no parity test runs.
|
||||
3. `make go-parity` — every fixture in `go/tests/fixtures/pure/` and `go/tests/fixtures/reconcile/` deserialises and passes its parity assertion.
|
||||
4. `make go-lint` — clean (parity test files lint-clean under `-tags=parity` since `golangci-lint` honours build tags via `.golangci.yml`).
|
||||
5. **Capture round-trip**: pick one fixture (e.g. `parse_month_references/range_wrap_nov_to_jan.json`), regenerate via `python scripts/capture_fixtures.py --func parse_month_references --case range_wrap_nov_to_jan --input-seed range_wrap_nov_to_jan | python scripts/scrub_fixtures.py`, confirm byte-identical to the committed file.
|
||||
6. **Scrubbing audit**: run the README §5 grep against any name from the live roster — zero hits.
|
||||
7. **Reconcile branch coverage**: read each of the 10 reconcile fixture files, confirm the `output` field shows the expected branch (e.g. `02_greedy_overpayment_credit.json` has a non-zero `credits` entry; `04_even_split_prepayment.json` has equal `paid` across all months).
|
||||
8. Append CHANGELOG entry per [CLAUDE.md](../../srv/personal/fuj-management/CLAUDE.md) (timestamp via `date "+%Y-%m-%d %H:%M %Z"`).
|
||||
9. Tick M3.1–M3.6 in [docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md](../../srv/personal/fuj-management/docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md) with the merge SHA. Update the M3 milestone summary line if M3 is now fully closed.
|
||||
10. Push branch, open MR via `tea pr create --title "feat(go): fixture capture + characterization framework (M3)" --base main --head feat/m3-fixture-capture`, print URL, leave merge to user.
|
||||
|
||||
## Critical files
|
||||
|
||||
- **Read for parity** — [scripts/czech_utils.py:22](../../srv/personal/fuj-management/scripts/czech_utils.py#L22), [scripts/czech_utils.py:28](../../srv/personal/fuj-management/scripts/czech_utils.py#L28), [scripts/attendance.py:91](../../srv/personal/fuj-management/scripts/attendance.py#L91), [scripts/attendance.py:100](../../srv/personal/fuj-management/scripts/attendance.py#L100), [scripts/infer_payments.py:17](../../srv/personal/fuj-management/scripts/infer_payments.py#L17), [scripts/sync_fio_to_sheets.py:62](../../srv/personal/fuj-management/scripts/sync_fio_to_sheets.py#L62), [scripts/match_payments.py:33](../../srv/personal/fuj-management/scripts/match_payments.py#L33), [scripts/match_payments.py:65](../../srv/personal/fuj-management/scripts/match_payments.py#L65), [scripts/match_payments.py:144](../../srv/personal/fuj-management/scripts/match_payments.py#L144), [scripts/match_payments.py:187](../../srv/personal/fuj-management/scripts/match_payments.py#L187), [scripts/match_payments.py:304](../../srv/personal/fuj-management/scripts/match_payments.py#L304).
|
||||
- **Reuse** — `domain/czech.{Normalize, ParseMonthReferences}`, `domain/fees.{CalculateFee, CalculateJuniorFee, Expected}`, `domain/money.ParseCZK`, `domain/synch.GenerateSyncID`, `domain/matching.{BuildNameVariants, MatchMembers, InferTransactionDetails, FormatDate}`, `domain/reconcile.{Member, Transaction, ExceptionKey, Exception, Result, Reconcile}`.
|
||||
- **Mirror conventions** — package layout from [go/internal/domain/matching/](../../srv/personal/fuj-management/go/internal/domain/matching/) (one symbol per file, top-of-test provenance comments, `t.Parallel()`, `// [Go]` markers for Go-only cases).
|
||||
- **New** — `scripts/{capture_fixtures,scrub_fixtures,_fixture_seeds}.py`; `go/tests/fixtures/README.md` + the corpus; `go/tests/parity/parityio.go` + 10 parity test files + 1 reconcile parity test file.
|
||||
- **Modify** — `Makefile` (3 new targets), `CHANGELOG.md` (1 entry), `docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md` (tick M3.1–M3.6).
|
||||
128
go/tests/fixtures/README.md
vendored
Normal file
128
go/tests/fixtures/README.md
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
# Parity Fixtures
|
||||
|
||||
Captured outputs from the live Python implementation used as ground truth for
|
||||
the Go parity test suite. All 98 files are committed and PII-free.
|
||||
|
||||
## Directory layout
|
||||
|
||||
```
|
||||
fixtures/
|
||||
pure/
|
||||
normalize/ # scripts.czech_utils.normalize
|
||||
parse_month_references/ # scripts.czech_utils.parse_month_references
|
||||
calculate_fee/ # scripts.attendance.calculate_fee
|
||||
calculate_junior_fee/ # scripts.attendance.calculate_junior_fee
|
||||
parse_czk_amount/ # scripts.infer_payments.parse_czk_amount
|
||||
generate_sync_id/ # scripts.sync_fio_to_sheets.generate_sync_id
|
||||
build_name_variants/ # scripts.match_payments._build_name_variants
|
||||
match_members/ # scripts.match_payments.match_members
|
||||
infer_transaction_details/ # scripts.match_payments.infer_transaction_details
|
||||
format_date/ # scripts.match_payments.format_date
|
||||
reconcile/ # scripts.match_payments.reconcile (10 branch-coverage cases)
|
||||
```
|
||||
|
||||
## Fixture format
|
||||
|
||||
One JSON object per file:
|
||||
|
||||
```json
|
||||
{
|
||||
"case": "range_wrap_nov_to_jan",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": { "text": "...", "default_year": 2026 },
|
||||
"output": { "months": ["2025-11", "2025-12", "2026-01"] }
|
||||
}
|
||||
```
|
||||
|
||||
`captured_at` is date-only so same-day re-runs produce byte-identical files.
|
||||
|
||||
### Amount type envelope
|
||||
|
||||
Four fields carry a type envelope to distinguish Python `int` / `float` / `None`:
|
||||
|
||||
```json
|
||||
{"type": "int", "value": 750}
|
||||
{"type": "float", "value": 750.0}
|
||||
{"type": "string", "value": "..."}
|
||||
{"type": "none"}
|
||||
```
|
||||
|
||||
Fields that use envelopes: `generate_sync_id.tx.amount`, `parse_czk_amount.val`,
|
||||
`format_date.val`, `infer_transaction_details.tx.date`.
|
||||
|
||||
### Reconcile member format
|
||||
|
||||
Reconcile input members use a named dict to allow consistent PII scrubbing:
|
||||
|
||||
```json
|
||||
{"name": "Member_d035d9f9", "tier": "A", "fees": {"2026-01": [750, 3]}}
|
||||
```
|
||||
|
||||
## Running the parity tests
|
||||
|
||||
```bash
|
||||
make go-parity # run all parity tests
|
||||
make go-test-all # unit tests + parity tests
|
||||
```
|
||||
|
||||
Or directly:
|
||||
|
||||
```bash
|
||||
cd go && go test -tags=parity ./tests/parity/...
|
||||
cd go && go test -tags=parity -v -run TestReconcileParity ./tests/parity/reconcile/
|
||||
```
|
||||
|
||||
## Refresh workflow
|
||||
|
||||
Regenerate the entire corpus from the live Python implementation:
|
||||
|
||||
```bash
|
||||
make capture-fixtures
|
||||
git diff go/tests/fixtures/ # review changes before committing
|
||||
```
|
||||
|
||||
To refresh a single function:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=scripts:. python3 scripts/capture_fixtures.py --func normalize --all \
|
||||
| while IFS= read -r line; do
|
||||
id=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['case'])")
|
||||
echo "$line" | python3 scripts/scrub_fixtures.py \
|
||||
> go/tests/fixtures/pure/normalize/${id}.json
|
||||
done
|
||||
```
|
||||
|
||||
## When to refresh
|
||||
|
||||
- A ported function is intentionally changed to match updated Python behaviour.
|
||||
- A new Czech declension or fee tier is added to the Python implementation.
|
||||
- A new reconcile code path needs fixture coverage.
|
||||
|
||||
**Do not refresh to silence a failing parity test** without first confirming that
|
||||
the Python behaviour is the correct reference. A parity failure means either the
|
||||
Go port diverges or the Python implementation changed — diagnose before regenerating.
|
||||
|
||||
## PII scrubbing audit
|
||||
|
||||
No real member names should appear in committed fixtures. Before committing any
|
||||
regenerated fixtures, verify with:
|
||||
|
||||
```bash
|
||||
# Replace with names from the real roster to check:
|
||||
git ls-files go/tests/fixtures | xargs grep -l "Real Name Here" | head
|
||||
```
|
||||
|
||||
The scrubber applies deterministic SHA-256 pseudonyms (`Member_<8hex>`) to all
|
||||
PII fields. `match_members` and `infer_transaction_details` fixtures use a
|
||||
synthetic roster of fictional names and are exempt from field-key scrubbing;
|
||||
verify that no real roster names appear in their `member_names` arrays.
|
||||
|
||||
## Adding a new fixture
|
||||
|
||||
1. Add a seed to `scripts/_fixture_seeds.py` under `SEEDS[("func_name", "case_id")]`.
|
||||
2. Add `In`/`Out` struct fields to `go/tests/parity/parityio.go` if the function
|
||||
is new.
|
||||
3. Run the single-file capture recipe above and review the diff.
|
||||
4. The parity test picks up new fixtures automatically — no test code changes needed
|
||||
(unless the function itself is new).
|
||||
15
go/tests/fixtures/pure/build_name_variants/common_diacritics.json
vendored
Normal file
15
go/tests/fixtures/pure/build_name_variants/common_diacritics.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "common_diacritics",
|
||||
"func": "scripts.match_payments._build_name_variants",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"full_name": "Alžběta Testovická"
|
||||
},
|
||||
"output": {
|
||||
"variants": [
|
||||
"alzbeta testovicka",
|
||||
"testovicka",
|
||||
"alzbeta"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/build_name_variants/full_name_no_nick.json
vendored
Normal file
15
go/tests/fixtures/pure/build_name_variants/full_name_no_nick.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "full_name_no_nick",
|
||||
"func": "scripts.match_payments._build_name_variants",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"full_name": "Jan Novák"
|
||||
},
|
||||
"output": {
|
||||
"variants": [
|
||||
"jan novak",
|
||||
"novak",
|
||||
"jan"
|
||||
]
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/build_name_variants/short_name_filtered.json
vendored
Normal file
11
go/tests/fixtures/pure/build_name_variants/short_name_filtered.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "short_name_filtered",
|
||||
"func": "scripts.match_payments._build_name_variants",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"full_name": "Jo"
|
||||
},
|
||||
"output": {
|
||||
"variants": []
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/build_name_variants/single_word.json
vendored
Normal file
13
go/tests/fixtures/pure/build_name_variants/single_word.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "single_word",
|
||||
"func": "scripts.match_payments._build_name_variants",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"full_name": "Jáchym"
|
||||
},
|
||||
"output": {
|
||||
"variants": [
|
||||
"jachym"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/build_name_variants/three_word_name.json
vendored
Normal file
16
go/tests/fixtures/pure/build_name_variants/three_word_name.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "three_word_name",
|
||||
"func": "scripts.match_payments._build_name_variants",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"full_name": "Jan Tomášek (Honza)"
|
||||
},
|
||||
"output": {
|
||||
"variants": [
|
||||
"jan tomasek",
|
||||
"honza",
|
||||
"tomasek",
|
||||
"jan"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/build_name_variants/with_nickname.json
vendored
Normal file
16
go/tests/fixtures/pure/build_name_variants/with_nickname.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "with_nickname",
|
||||
"func": "scripts.match_payments._build_name_variants",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"full_name": "František Vrbík (Štrúdl)"
|
||||
},
|
||||
"output": {
|
||||
"variants": [
|
||||
"frantisek vrbik",
|
||||
"strudl",
|
||||
"vrbik",
|
||||
"frantisek"
|
||||
]
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/calculate_fee/one_session.json
vendored
Normal file
12
go/tests/fixtures/pure/calculate_fee/one_session.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "one_session",
|
||||
"func": "scripts.attendance.calculate_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 1,
|
||||
"month_key": "2026-01"
|
||||
},
|
||||
"output": {
|
||||
"fee": 200
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/calculate_fee/three_sessions_known_rate.json
vendored
Normal file
12
go/tests/fixtures/pure/calculate_fee/three_sessions_known_rate.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "three_sessions_known_rate",
|
||||
"func": "scripts.attendance.calculate_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 3,
|
||||
"month_key": "2026-02"
|
||||
},
|
||||
"output": {
|
||||
"fee": 750
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/calculate_fee/two_sessions_default_fallback.json
vendored
Normal file
12
go/tests/fixtures/pure/calculate_fee/two_sessions_default_fallback.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "two_sessions_default_fallback",
|
||||
"func": "scripts.attendance.calculate_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 2,
|
||||
"month_key": "2099-01"
|
||||
},
|
||||
"output": {
|
||||
"fee": 700
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/calculate_fee/two_sessions_known_rate.json
vendored
Normal file
12
go/tests/fixtures/pure/calculate_fee/two_sessions_known_rate.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "two_sessions_known_rate",
|
||||
"func": "scripts.attendance.calculate_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 2,
|
||||
"month_key": "2026-01"
|
||||
},
|
||||
"output": {
|
||||
"fee": 750
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/calculate_fee/two_sessions_reduced_march.json
vendored
Normal file
12
go/tests/fixtures/pure/calculate_fee/two_sessions_reduced_march.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "two_sessions_reduced_march",
|
||||
"func": "scripts.attendance.calculate_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 2,
|
||||
"month_key": "2026-03"
|
||||
},
|
||||
"output": {
|
||||
"fee": 350
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/calculate_fee/zero_sessions.json
vendored
Normal file
12
go/tests/fixtures/pure/calculate_fee/zero_sessions.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "zero_sessions",
|
||||
"func": "scripts.attendance.calculate_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 0,
|
||||
"month_key": "2026-01"
|
||||
},
|
||||
"output": {
|
||||
"fee": 0
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/calculate_junior_fee/one_session_unknown.json
vendored
Normal file
13
go/tests/fixtures/pure/calculate_junior_fee/one_session_unknown.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "one_session_unknown",
|
||||
"func": "scripts.attendance.calculate_junior_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 1,
|
||||
"month_key": "2026-01"
|
||||
},
|
||||
"output": {
|
||||
"value": 0,
|
||||
"unknown": true
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_default.json
vendored
Normal file
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_default.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "two_sessions_default",
|
||||
"func": "scripts.attendance.calculate_junior_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 2,
|
||||
"month_key": "2026-01"
|
||||
},
|
||||
"output": {
|
||||
"value": 500,
|
||||
"unknown": false
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_default_fallback.json
vendored
Normal file
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_default_fallback.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "two_sessions_default_fallback",
|
||||
"func": "scripts.attendance.calculate_junior_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 2,
|
||||
"month_key": "2099-06"
|
||||
},
|
||||
"output": {
|
||||
"value": 500,
|
||||
"unknown": false
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_reduced_march.json
vendored
Normal file
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_reduced_march.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "two_sessions_reduced_march",
|
||||
"func": "scripts.attendance.calculate_junior_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 2,
|
||||
"month_key": "2026-03"
|
||||
},
|
||||
"output": {
|
||||
"value": 250,
|
||||
"unknown": false
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_reduced_sep.json
vendored
Normal file
13
go/tests/fixtures/pure/calculate_junior_fee/two_sessions_reduced_sep.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "two_sessions_reduced_sep",
|
||||
"func": "scripts.attendance.calculate_junior_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 2,
|
||||
"month_key": "2025-09"
|
||||
},
|
||||
"output": {
|
||||
"value": 250,
|
||||
"unknown": false
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/calculate_junior_fee/zero_sessions.json
vendored
Normal file
13
go/tests/fixtures/pure/calculate_junior_fee/zero_sessions.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "zero_sessions",
|
||||
"func": "scripts.attendance.calculate_junior_fee",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"attendance_count": 0,
|
||||
"month_key": "2026-01"
|
||||
},
|
||||
"output": {
|
||||
"value": 0,
|
||||
"unknown": false
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/format_date/empty_string.json
vendored
Normal file
14
go/tests/fixtures/pure/format_date/empty_string.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "empty_string",
|
||||
"func": "scripts.match_payments.format_date",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": ""
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"date": ""
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/format_date/none_value.json
vendored
Normal file
13
go/tests/fixtures/pure/format_date/none_value.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "none_value",
|
||||
"func": "scripts.match_payments.format_date",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"date": ""
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/format_date/serial_float.json
vendored
Normal file
14
go/tests/fixtures/pure/format_date/serial_float.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "serial_float",
|
||||
"func": "scripts.match_payments.format_date",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "float",
|
||||
"value": 46027.5
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"date": "2026-01-05"
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/format_date/serial_float_exact.json
vendored
Normal file
14
go/tests/fixtures/pure/format_date/serial_float_exact.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "serial_float_exact",
|
||||
"func": "scripts.match_payments.format_date",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "float",
|
||||
"value": 45957.0
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"date": "2025-10-27"
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/format_date/serial_int.json
vendored
Normal file
14
go/tests/fixtures/pure/format_date/serial_int.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "serial_int",
|
||||
"func": "scripts.match_payments.format_date",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "int",
|
||||
"value": 46027
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"date": "2026-01-05"
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/format_date/string_iso.json
vendored
Normal file
14
go/tests/fixtures/pure/format_date/string_iso.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "string_iso",
|
||||
"func": "scripts.match_payments.format_date",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "2026-01-15"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"date": "2026-01-15"
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/format_date/string_non_iso.json
vendored
Normal file
14
go/tests/fixtures/pure/format_date/string_non_iso.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "string_non_iso",
|
||||
"func": "scripts.match_payments.format_date",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "garbage"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"date": "garbage"
|
||||
}
|
||||
}
|
||||
22
go/tests/fixtures/pure/generate_sync_id/empty_fields.json
vendored
Normal file
22
go/tests/fixtures/pure/generate_sync_id/empty_fields.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"case": "empty_fields",
|
||||
"func": "scripts.sync_fio_to_sheets.generate_sync_id",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"date": "2026-03-01",
|
||||
"amount": {
|
||||
"type": "float",
|
||||
"value": 0.0
|
||||
},
|
||||
"currency": "CZK",
|
||||
"sender": "",
|
||||
"vs": "",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"sync_id": "80d5f2762dbe807adde8dab64c3f3f00936ceafc75d4ceba232b08c09bb71c60"
|
||||
}
|
||||
}
|
||||
22
go/tests/fixtures/pure/generate_sync_id/integer_amount.json
vendored
Normal file
22
go/tests/fixtures/pure/generate_sync_id/integer_amount.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"case": "integer_amount",
|
||||
"func": "scripts.sync_fio_to_sheets.generate_sync_id",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"date": "2026-01-15",
|
||||
"amount": {
|
||||
"type": "int",
|
||||
"value": 750
|
||||
},
|
||||
"currency": "CZK",
|
||||
"sender": "Member_9b16314c",
|
||||
"vs": "864722",
|
||||
"message": "pausal leden",
|
||||
"bank_id": "983770300"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"sync_id": "155e983a0a3a11210e19728c427395f6681ee5d2a0ef3b60438e6efeaf3775df"
|
||||
}
|
||||
}
|
||||
22
go/tests/fixtures/pure/generate_sync_id/large_amount.json
vendored
Normal file
22
go/tests/fixtures/pure/generate_sync_id/large_amount.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"case": "large_amount",
|
||||
"func": "scripts.sync_fio_to_sheets.generate_sync_id",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"date": "2025-10-05",
|
||||
"amount": {
|
||||
"type": "float",
|
||||
"value": 2100.0
|
||||
},
|
||||
"currency": "CZK",
|
||||
"sender": "Member_bd5eb92a",
|
||||
"vs": "110515",
|
||||
"message": "FUJ treninky",
|
||||
"bank_id": "609470745"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"sync_id": "639d98f8ab8e6954b7e4d31508936cc4366ee0281eebc860338585cdeda43ae3"
|
||||
}
|
||||
}
|
||||
21
go/tests/fixtures/pure/generate_sync_id/missing_currency.json
vendored
Normal file
21
go/tests/fixtures/pure/generate_sync_id/missing_currency.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"case": "missing_currency",
|
||||
"func": "scripts.sync_fio_to_sheets.generate_sync_id",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"date": "2026-02-01",
|
||||
"amount": {
|
||||
"type": "float",
|
||||
"value": 500.0
|
||||
},
|
||||
"sender": "Member_32a79b03",
|
||||
"vs": "720261",
|
||||
"message": "trenink",
|
||||
"bank_id": "072657565"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"sync_id": "8bd2cc2c2e6b376ad2d2501f72ee5d987fdca37662c4be0b9bb5345dcb28553d"
|
||||
}
|
||||
}
|
||||
22
go/tests/fixtures/pure/generate_sync_id/typical_float_amount.json
vendored
Normal file
22
go/tests/fixtures/pure/generate_sync_id/typical_float_amount.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"case": "typical_float_amount",
|
||||
"func": "scripts.sync_fio_to_sheets.generate_sync_id",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"date": "2026-01-15",
|
||||
"amount": {
|
||||
"type": "float",
|
||||
"value": 750.0
|
||||
},
|
||||
"currency": "CZK",
|
||||
"sender": "Member_9b16314c",
|
||||
"vs": "864722",
|
||||
"message": "pausal leden",
|
||||
"bank_id": "983770300"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"sync_id": "155e983a0a3a11210e19728c427395f6681ee5d2a0ef3b60438e6efeaf3775df"
|
||||
}
|
||||
}
|
||||
36
go/tests/fixtures/pure/infer_transaction_details/member_in_message.json
vendored
Normal file
36
go/tests/fixtures/pure/infer_transaction_details/member_in_message.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"case": "member_in_message",
|
||||
"func": "scripts.match_payments.infer_transaction_details",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"sender": "Test Payer",
|
||||
"message": "alzbeta testovicka leden 2026",
|
||||
"user_id": "",
|
||||
"date": {
|
||||
"type": "string",
|
||||
"value": "2026-01-15"
|
||||
}
|
||||
},
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Alžběta Testovická",
|
||||
"confidence": "auto"
|
||||
}
|
||||
],
|
||||
"months": [
|
||||
"2026-01"
|
||||
],
|
||||
"search_text": "Test Payer alzbeta testovicka leden 2026 "
|
||||
}
|
||||
}
|
||||
36
go/tests/fixtures/pure/infer_transaction_details/member_in_sender.json
vendored
Normal file
36
go/tests/fixtures/pure/infer_transaction_details/member_in_sender.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"case": "member_in_sender",
|
||||
"func": "scripts.match_payments.infer_transaction_details",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"sender": "Tomáš Fiktivný",
|
||||
"message": "FUJ trenink",
|
||||
"user_id": "",
|
||||
"date": {
|
||||
"type": "string",
|
||||
"value": "2026-02-01"
|
||||
}
|
||||
},
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Tomáš Fiktivný (Tov)",
|
||||
"confidence": "auto"
|
||||
}
|
||||
],
|
||||
"months": [
|
||||
"2026-02"
|
||||
],
|
||||
"search_text": "Tomáš Fiktivný FUJ trenink "
|
||||
}
|
||||
}
|
||||
36
go/tests/fixtures/pure/infer_transaction_details/month_fallback_from_date.json
vendored
Normal file
36
go/tests/fixtures/pure/infer_transaction_details/month_fallback_from_date.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"case": "month_fallback_from_date",
|
||||
"func": "scripts.match_payments.infer_transaction_details",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"sender": "Alžběta Testovická",
|
||||
"message": "platba",
|
||||
"user_id": "",
|
||||
"date": {
|
||||
"type": "string",
|
||||
"value": "2026-03-15"
|
||||
}
|
||||
},
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Alžběta Testovická",
|
||||
"confidence": "auto"
|
||||
}
|
||||
],
|
||||
"months": [
|
||||
"2026-03"
|
||||
],
|
||||
"search_text": "Alžběta Testovická platba "
|
||||
}
|
||||
}
|
||||
28
go/tests/fixtures/pure/infer_transaction_details/no_member_no_month.json
vendored
Normal file
28
go/tests/fixtures/pure/infer_transaction_details/no_member_no_month.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"case": "no_member_no_month",
|
||||
"func": "scripts.match_payments.infer_transaction_details",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"sender": "Unknown Person",
|
||||
"message": "random text",
|
||||
"user_id": "",
|
||||
"date": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"matches": [],
|
||||
"months": [],
|
||||
"search_text": "Unknown Person random text "
|
||||
}
|
||||
}
|
||||
36
go/tests/fixtures/pure/infer_transaction_details/serial_date.json
vendored
Normal file
36
go/tests/fixtures/pure/infer_transaction_details/serial_date.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"case": "serial_date",
|
||||
"func": "scripts.match_payments.infer_transaction_details",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"tx": {
|
||||
"sender": "Jana Nováková",
|
||||
"message": "leden",
|
||||
"user_id": "",
|
||||
"date": {
|
||||
"type": "float",
|
||||
"value": 46027.0
|
||||
}
|
||||
},
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Jana Nováková",
|
||||
"confidence": "auto"
|
||||
}
|
||||
],
|
||||
"months": [
|
||||
"2026-01"
|
||||
],
|
||||
"search_text": "Jana Nováková leden "
|
||||
}
|
||||
}
|
||||
18
go/tests/fixtures/pure/match_members/common_surname_no_match.json
vendored
Normal file
18
go/tests/fixtures/pure/match_members/common_surname_no_match.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"case": "common_surname_no_match",
|
||||
"func": "scripts.match_payments.match_members",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "novak leden",
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"matches": []
|
||||
}
|
||||
}
|
||||
23
go/tests/fixtures/pure/match_members/exact_full_name.json
vendored
Normal file
23
go/tests/fixtures/pure/match_members/exact_full_name.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"case": "exact_full_name",
|
||||
"func": "scripts.match_payments.match_members",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "platba od alzbeta testovicka leden",
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Alžběta Testovická",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
23
go/tests/fixtures/pure/match_members/first_and_last.json
vendored
Normal file
23
go/tests/fixtures/pure/match_members/first_and_last.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"case": "first_and_last",
|
||||
"func": "scripts.match_payments.match_members",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "jan nový payment tomas fiktivny",
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Tomáš Fiktivný (Tov)",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
23
go/tests/fixtures/pure/match_members/nickname_match.json
vendored
Normal file
23
go/tests/fixtures/pure/match_members/nickname_match.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"case": "nickname_match",
|
||||
"func": "scripts.match_payments.match_members",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "payment from strudl",
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Pavel Smutný (Štrúdl)",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
18
go/tests/fixtures/pure/match_members/no_match.json
vendored
Normal file
18
go/tests/fixtures/pure/match_members/no_match.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"case": "no_match",
|
||||
"func": "scripts.match_payments.match_members",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "xyz platba",
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"matches": []
|
||||
}
|
||||
}
|
||||
23
go/tests/fixtures/pure/match_members/review_lastname_only.json
vendored
Normal file
23
go/tests/fixtures/pure/match_members/review_lastname_only.json
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"case": "review_lastname_only",
|
||||
"func": "scripts.match_payments.match_members",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "testovicka leden",
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Alžběta Testovická",
|
||||
"confidence": "review"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
27
go/tests/fixtures/pure/match_members/two_members_exact.json
vendored
Normal file
27
go/tests/fixtures/pure/match_members/two_members_exact.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"case": "two_members_exact",
|
||||
"func": "scripts.match_payments.match_members",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "pavel smutny a alzbeta testovicka",
|
||||
"member_names": [
|
||||
"Alžběta Testovická",
|
||||
"Tomáš Fiktivný (Tov)",
|
||||
"Pavel Smutný (Štrúdl)",
|
||||
"Jana Nováková",
|
||||
"Adam Novák"
|
||||
]
|
||||
},
|
||||
"output": {
|
||||
"matches": [
|
||||
{
|
||||
"name": "Alžběta Testovická",
|
||||
"confidence": "auto"
|
||||
},
|
||||
{
|
||||
"name": "Pavel Smutný (Štrúdl)",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/normalize/czech_basic.json
vendored
Normal file
11
go/tests/fixtures/pure/normalize/czech_basic.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "czech_basic",
|
||||
"func": "scripts.czech_utils.normalize",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "štefan čakrtový"
|
||||
},
|
||||
"output": {
|
||||
"text": "stefan cakrtovy"
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/normalize/czech_full_set.json
vendored
Normal file
11
go/tests/fixtures/pure/normalize/czech_full_set.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "czech_full_set",
|
||||
"func": "scripts.czech_utils.normalize",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "áčďéěíňóřšťůúýžÁČĎÉĚÍŇÓŘŠŤŮÚÝŽ"
|
||||
},
|
||||
"output": {
|
||||
"text": "acdeeinorstuuyzacdeeinorstuuyz"
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/normalize/digits_symbols.json
vendored
Normal file
11
go/tests/fixtures/pure/normalize/digits_symbols.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "digits_symbols",
|
||||
"func": "scripts.czech_utils.normalize",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "FUJ2026! +3"
|
||||
},
|
||||
"output": {
|
||||
"text": "fuj2026! +3"
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/normalize/empty_string.json
vendored
Normal file
11
go/tests/fixtures/pure/normalize/empty_string.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "empty_string",
|
||||
"func": "scripts.czech_utils.normalize",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": ""
|
||||
},
|
||||
"output": {
|
||||
"text": ""
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/normalize/mixed_case.json
vendored
Normal file
11
go/tests/fixtures/pure/normalize/mixed_case.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "mixed_case",
|
||||
"func": "scripts.czech_utils.normalize",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Henrietta OTTOVÁ"
|
||||
},
|
||||
"output": {
|
||||
"text": "henrietta ottova"
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/normalize/simple_ascii.json
vendored
Normal file
11
go/tests/fixtures/pure/normalize/simple_ascii.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "simple_ascii",
|
||||
"func": "scripts.czech_utils.normalize",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "hello world"
|
||||
},
|
||||
"output": {
|
||||
"text": "hello world"
|
||||
}
|
||||
}
|
||||
11
go/tests/fixtures/pure/normalize/with_parens.json
vendored
Normal file
11
go/tests/fixtures/pure/normalize/with_parens.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"case": "with_parens",
|
||||
"func": "scripts.czech_utils.normalize",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Pavel Smutný (Štrúdl)"
|
||||
},
|
||||
"output": {
|
||||
"text": "pavel smutny (strudl)"
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/czech_comma_decimal.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/czech_comma_decimal.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "czech_comma_decimal",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "1.500,00"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 1500.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/czech_comma_no_thousands.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/czech_comma_no_thousands.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "czech_comma_no_thousands",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "750,00"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 750.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/dot_decimal.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/dot_decimal.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "dot_decimal",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "1500.00"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 1500.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/dot_thousand_separator.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/dot_thousand_separator.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "dot_thousand_separator",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "1.500"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 1.5
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/empty_string.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/empty_string.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "empty_string",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": ""
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 0.0
|
||||
}
|
||||
}
|
||||
13
go/tests/fixtures/pure/parse_czk_amount/none_value.json
vendored
Normal file
13
go/tests/fixtures/pure/parse_czk_amount/none_value.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"case": "none_value",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "none"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 0.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/plain_float.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/plain_float.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "plain_float",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "float",
|
||||
"value": 750.0
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 750.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/plain_int.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/plain_int.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "plain_int",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "int",
|
||||
"value": 750
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 750.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/space_thousands.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/space_thousands.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "space_thousands",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "1 500"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 1500.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/with_czk_suffix.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/with_czk_suffix.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "with_czk_suffix",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "1500CZK"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 1500.0
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_czk_amount/with_kc_suffix.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_czk_amount/with_kc_suffix.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "with_kc_suffix",
|
||||
"func": "scripts.infer_payments.parse_czk_amount",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"val": {
|
||||
"type": "string",
|
||||
"value": "750 Kč"
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"amount": 750.0
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/parse_month_references/empty_string.json
vendored
Normal file
12
go/tests/fixtures/pure/parse_month_references/empty_string.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "empty_string",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": []
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/mixed_czech_numeric.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/mixed_czech_numeric.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "mixed_czech_numeric",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "leden+únor+03/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-01",
|
||||
"2026-02",
|
||||
"2026-03"
|
||||
]
|
||||
}
|
||||
}
|
||||
12
go/tests/fixtures/pure/parse_month_references/no_month_found.json
vendored
Normal file
12
go/tests/fixtures/pure/parse_month_references/no_month_found.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"case": "no_month_found",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "random text without months",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": []
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/numeric_dot_format.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/numeric_dot_format.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "numeric_dot_format",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "12.2025",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/parse_month_references/numeric_plus_multi.json
vendored
Normal file
15
go/tests/fixtures/pure/parse_month_references/numeric_plus_multi.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "numeric_plus_multi",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "11+12/2025",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-11",
|
||||
"2025-12"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/numeric_slash_four_digit_year.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/numeric_slash_four_digit_year.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "numeric_slash_four_digit_year",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "1/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/numeric_slash_leading_zero.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/numeric_slash_leading_zero.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "numeric_slash_leading_zero",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "03/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-03"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/numeric_slash_two_digit_year.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/numeric_slash_two_digit_year.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "numeric_slash_two_digit_year",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "01/26",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/parse_month_references/range_no_wrap_leden_unor.json
vendored
Normal file
15
go/tests/fixtures/pure/parse_month_references/range_no_wrap_leden_unor.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "range_no_wrap_leden_unor",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "leden-únor",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-01",
|
||||
"2026-02"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/range_wrap_listopad_leden.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/range_wrap_listopad_leden.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "range_wrap_listopad_leden",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "listopad-leden",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-11",
|
||||
"2025-12",
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/parse_month_references/range_wrap_prosinec_leden.json
vendored
Normal file
15
go/tests/fixtures/pure/parse_month_references/range_wrap_prosinec_leden.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "range_wrap_prosinec_leden",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "prosinec-leden",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12",
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
19
go/tests/fixtures/pure/parse_month_references/real_alex_numeric_long.json
vendored
Normal file
19
go/tests/fixtures/pure/parse_month_references/real_alex_numeric_long.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"case": "real_alex_numeric_long",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_3f7108b7: 10/2025+11/2025+01/2026+02/2026+03/2026+04/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-10",
|
||||
"2025-11",
|
||||
"2026-01",
|
||||
"2026-02",
|
||||
"2026-03",
|
||||
"2026-04"
|
||||
]
|
||||
}
|
||||
}
|
||||
17
go/tests/fixtures/pure/parse_month_references/real_dominika_numeric_multi.json
vendored
Normal file
17
go/tests/fixtures/pure/parse_month_references/real_dominika_numeric_multi.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"case": "real_dominika_numeric_multi",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_22e1170d paušál 11+12/25, 01/26, 02/26",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-11",
|
||||
"2025-12",
|
||||
"2026-01",
|
||||
"2026-02"
|
||||
]
|
||||
}
|
||||
}
|
||||
19
go/tests/fixtures/pure/parse_month_references/real_emily_numeric_long.json
vendored
Normal file
19
go/tests/fixtures/pure/parse_month_references/real_emily_numeric_long.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"case": "real_emily_numeric_long",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_b09f5558: 10/2025+11/2025+01/2026+02/2026+03/2026+04/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-10",
|
||||
"2025-11",
|
||||
"2026-01",
|
||||
"2026-02",
|
||||
"2026-03",
|
||||
"2026-04"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/real_filip_prosinec_leden_unor.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/real_filip_prosinec_leden_unor.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "real_filip_prosinec_leden_unor",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Filip Halamka - prosinec, leden, unor",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12",
|
||||
"2026-01",
|
||||
"2026-02"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/parse_month_references/real_franc_numeric_space.json
vendored
Normal file
15
go/tests/fixtures/pure/parse_month_references/real_franc_numeric_space.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "real_franc_numeric_space",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_f42b5277:02/2026 03/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-02",
|
||||
"2026-03"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/real_jachym_numeric_multi.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/real_jachym_numeric_multi.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "real_jachym_numeric_multi",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Jáchym Kubík: 01/2026+03/2026+04/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-01",
|
||||
"2026-03",
|
||||
"2026-04"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/real_jana_numeric_multi.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/real_jana_numeric_multi.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "real_jana_numeric_multi",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_ca47f547: 02/2026+03/2026+04/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-02",
|
||||
"2026-03",
|
||||
"2026-04"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/real_list_prosinec_leden_unor.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/real_list_prosinec_leden_unor.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "real_list_prosinec_leden_unor",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Kacerr - pausal prosinec, leden, unor",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12",
|
||||
"2026-01",
|
||||
"2026-02"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/parse_month_references/real_martin_prosinec_leden.json
vendored
Normal file
15
go/tests/fixtures/pure/parse_month_references/real_martin_prosinec_leden.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "real_martin_prosinec_leden",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Martin Bolvansky Pausal Prosinec Leden",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12",
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/real_mixed_czech_numeric.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/real_mixed_czech_numeric.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "real_mixed_czech_numeric",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_7e9cb37a paušál leden+únor a 500 za 11,12/2025",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12",
|
||||
"2026-01",
|
||||
"2026-02"
|
||||
]
|
||||
}
|
||||
}
|
||||
16
go/tests/fixtures/pure/parse_month_references/real_range_listopad_leden.json
vendored
Normal file
16
go/tests/fixtures/pure/parse_month_references/real_range_listopad_leden.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"case": "real_range_listopad_leden",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_3f0f0061 pausal listopad-leden",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-11",
|
||||
"2025-12",
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/parse_month_references/real_range_prosinec_leden.json
vendored
Normal file
15
go/tests/fixtures/pure/parse_month_references/real_range_prosinec_leden.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "real_range_prosinec_leden",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_8fa4ba0e prosinec-leden",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12",
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/real_single_leden.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/real_single_leden.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "real_single_leden",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_89d22e73, paušál za leden 2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
15
go/tests/fixtures/pure/parse_month_references/real_tomik_numeric_plus.json
vendored
Normal file
15
go/tests/fixtures/pure/parse_month_references/real_tomik_numeric_plus.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"case": "real_tomik_numeric_plus",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "Member_e4654d4c: 02/2026+03/2026",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-02",
|
||||
"2026-03"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/single_czech_leden.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/single_czech_leden.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "single_czech_leden",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "leden",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2026-01"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/single_czech_prosinec_high_month.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/single_czech_prosinec_high_month.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "single_czech_prosinec_high_month",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "prosinec",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-12"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
go/tests/fixtures/pure/parse_month_references/single_czech_rijen_high_month.json
vendored
Normal file
14
go/tests/fixtures/pure/parse_month_references/single_czech_rijen_high_month.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"case": "single_czech_rijen_high_month",
|
||||
"func": "scripts.czech_utils.parse_month_references",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"text": "říjen",
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"months": [
|
||||
"2025-10"
|
||||
]
|
||||
}
|
||||
}
|
||||
68
go/tests/fixtures/reconcile/01_greedy_exact.json
vendored
Normal file
68
go/tests/fixtures/reconcile/01_greedy_exact.json
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"case": "01_greedy_exact",
|
||||
"func": "scripts.match_payments.reconcile",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"members": [
|
||||
{
|
||||
"name": "Member_d035d9f9",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-01": [
|
||||
750,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sorted_months": [
|
||||
"2026-01"
|
||||
],
|
||||
"transactions": [
|
||||
{
|
||||
"date": "2026-01-20",
|
||||
"amount": 750,
|
||||
"manual_fix": "",
|
||||
"person": "Member_d035d9f9",
|
||||
"purpose": "2026-01",
|
||||
"inferred_amount": 750,
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
],
|
||||
"exceptions": [],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"members": {
|
||||
"Member_d035d9f9": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-01": {
|
||||
"expected": 750,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 3,
|
||||
"exception": null,
|
||||
"paid": 750.0,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 750.0,
|
||||
"date": "2026-01-20",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"other_transactions": [],
|
||||
"total_balance": 0
|
||||
}
|
||||
},
|
||||
"unmatched": [],
|
||||
"credits": {
|
||||
"Member_d035d9f9": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
68
go/tests/fixtures/reconcile/02_greedy_overpayment.json
vendored
Normal file
68
go/tests/fixtures/reconcile/02_greedy_overpayment.json
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"case": "02_greedy_overpayment",
|
||||
"func": "scripts.match_payments.reconcile",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"members": [
|
||||
{
|
||||
"name": "Member_d035d9f9",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-01": [
|
||||
750,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sorted_months": [
|
||||
"2026-01"
|
||||
],
|
||||
"transactions": [
|
||||
{
|
||||
"date": "2026-01-20",
|
||||
"amount": 900,
|
||||
"manual_fix": "",
|
||||
"person": "Member_d035d9f9",
|
||||
"purpose": "2026-01",
|
||||
"inferred_amount": 900,
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
],
|
||||
"exceptions": [],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"members": {
|
||||
"Member_d035d9f9": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-01": {
|
||||
"expected": 750,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 3,
|
||||
"exception": null,
|
||||
"paid": 750.0,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 750.0,
|
||||
"date": "2026-01-20",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"other_transactions": [],
|
||||
"total_balance": 150
|
||||
}
|
||||
},
|
||||
"unmatched": [],
|
||||
"credits": {
|
||||
"Member_d035d9f9": 150
|
||||
}
|
||||
}
|
||||
}
|
||||
110
go/tests/fixtures/reconcile/03_proportional_remainder.json
vendored
Normal file
110
go/tests/fixtures/reconcile/03_proportional_remainder.json
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"case": "03_proportional_remainder",
|
||||
"func": "scripts.match_payments.reconcile",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"members": [
|
||||
{
|
||||
"name": "Member_d035d9f9",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-01": [
|
||||
750,
|
||||
3
|
||||
],
|
||||
"2026-02": [
|
||||
750,
|
||||
2
|
||||
],
|
||||
"2026-03": [
|
||||
350,
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sorted_months": [
|
||||
"2026-01",
|
||||
"2026-02",
|
||||
"2026-03"
|
||||
],
|
||||
"transactions": [
|
||||
{
|
||||
"date": "2026-03-10",
|
||||
"amount": 800,
|
||||
"manual_fix": "",
|
||||
"person": "Member_d035d9f9",
|
||||
"purpose": "2026-01,2026-02,2026-03",
|
||||
"inferred_amount": 800,
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
],
|
||||
"exceptions": [],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"members": {
|
||||
"Member_d035d9f9": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-01": {
|
||||
"expected": 750,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 3,
|
||||
"exception": null,
|
||||
"paid": 324.3243243243243,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 324.3243243243243,
|
||||
"date": "2026-03-10",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2026-02": {
|
||||
"expected": 750,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 2,
|
||||
"exception": null,
|
||||
"paid": 324.3243243243243,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 324.3243243243243,
|
||||
"date": "2026-03-10",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2026-03": {
|
||||
"expected": 350,
|
||||
"original_expected": 350,
|
||||
"attendance_count": 2,
|
||||
"exception": null,
|
||||
"paid": 151.35135135135135,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 151.35135135135135,
|
||||
"date": "2026-03-10",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"other_transactions": [],
|
||||
"total_balance": -1051
|
||||
}
|
||||
},
|
||||
"unmatched": [],
|
||||
"credits": {
|
||||
"Member_d035d9f9": -1051
|
||||
}
|
||||
}
|
||||
}
|
||||
89
go/tests/fixtures/reconcile/04_even_split_prepayment.json
vendored
Normal file
89
go/tests/fixtures/reconcile/04_even_split_prepayment.json
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"case": "04_even_split_prepayment",
|
||||
"func": "scripts.match_payments.reconcile",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"members": [
|
||||
{
|
||||
"name": "Member_f4a93e46",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-04": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"2026-05": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sorted_months": [
|
||||
"2026-04",
|
||||
"2026-05"
|
||||
],
|
||||
"transactions": [
|
||||
{
|
||||
"date": "2026-03-25",
|
||||
"amount": 700,
|
||||
"manual_fix": "",
|
||||
"person": "Member_f4a93e46",
|
||||
"purpose": "2026-04,2026-05",
|
||||
"inferred_amount": 700,
|
||||
"sender": "Member_f4a93e46",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
],
|
||||
"exceptions": [],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"members": {
|
||||
"Member_f4a93e46": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-04": {
|
||||
"expected": 0,
|
||||
"original_expected": 0,
|
||||
"attendance_count": 0,
|
||||
"exception": null,
|
||||
"paid": 350.0,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 350.0,
|
||||
"date": "2026-03-25",
|
||||
"sender": "Member_f4a93e46",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2026-05": {
|
||||
"expected": 0,
|
||||
"original_expected": 0,
|
||||
"attendance_count": 0,
|
||||
"exception": null,
|
||||
"paid": 350.0,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 350.0,
|
||||
"date": "2026-03-25",
|
||||
"sender": "Member_f4a93e46",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"other_transactions": [],
|
||||
"total_balance": 700
|
||||
}
|
||||
},
|
||||
"unmatched": [],
|
||||
"credits": {
|
||||
"Member_f4a93e46": 700
|
||||
}
|
||||
}
|
||||
}
|
||||
68
go/tests/fixtures/reconcile/05_out_of_window_credit.json
vendored
Normal file
68
go/tests/fixtures/reconcile/05_out_of_window_credit.json
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"case": "05_out_of_window_credit",
|
||||
"func": "scripts.match_payments.reconcile",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"members": [
|
||||
{
|
||||
"name": "Member_d035d9f9",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-01": [
|
||||
750,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sorted_months": [
|
||||
"2026-01"
|
||||
],
|
||||
"transactions": [
|
||||
{
|
||||
"date": "2026-01-20",
|
||||
"amount": 1500,
|
||||
"manual_fix": "",
|
||||
"person": "Member_d035d9f9",
|
||||
"purpose": "2026-01,2025-08",
|
||||
"inferred_amount": 1500,
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
],
|
||||
"exceptions": [],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"members": {
|
||||
"Member_d035d9f9": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-01": {
|
||||
"expected": 750,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 3,
|
||||
"exception": null,
|
||||
"paid": 750.0,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 750.0,
|
||||
"date": "2026-01-20",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"other_transactions": [],
|
||||
"total_balance": 750
|
||||
}
|
||||
},
|
||||
"unmatched": [],
|
||||
"credits": {
|
||||
"Member_d035d9f9": 750
|
||||
}
|
||||
}
|
||||
}
|
||||
78
go/tests/fixtures/reconcile/06_exception_override.json
vendored
Normal file
78
go/tests/fixtures/reconcile/06_exception_override.json
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"case": "06_exception_override",
|
||||
"func": "scripts.match_payments.reconcile",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"members": [
|
||||
{
|
||||
"name": "Member_d035d9f9",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-01": [
|
||||
750,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sorted_months": [
|
||||
"2026-01"
|
||||
],
|
||||
"transactions": [
|
||||
{
|
||||
"date": "2026-01-20",
|
||||
"amount": 300,
|
||||
"manual_fix": "",
|
||||
"person": "Member_d035d9f9",
|
||||
"purpose": "2026-01",
|
||||
"inferred_amount": 300,
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
],
|
||||
"exceptions": [
|
||||
{
|
||||
"name": "Member_d035d9f9",
|
||||
"period": "2026-01",
|
||||
"amount": 300,
|
||||
"note": "<scrubbed>"
|
||||
}
|
||||
],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"members": {
|
||||
"Member_d035d9f9": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-01": {
|
||||
"expected": 300,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 3,
|
||||
"exception": {
|
||||
"amount": 300,
|
||||
"note": "<scrubbed>"
|
||||
},
|
||||
"paid": 300.0,
|
||||
"transactions": [
|
||||
{
|
||||
"amount": 300.0,
|
||||
"date": "2026-01-20",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"confidence": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"other_transactions": [],
|
||||
"total_balance": 0
|
||||
}
|
||||
},
|
||||
"unmatched": [],
|
||||
"credits": {
|
||||
"Member_d035d9f9": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
104
go/tests/fixtures/reconcile/07_other_purpose_split.json
vendored
Normal file
104
go/tests/fixtures/reconcile/07_other_purpose_split.json
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"case": "07_other_purpose_split",
|
||||
"func": "scripts.match_payments.reconcile",
|
||||
"captured_at": "2026-05-06",
|
||||
"input": {
|
||||
"members": [
|
||||
{
|
||||
"name": "Member_d035d9f9",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-01": [
|
||||
750,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Member_f4a93e46",
|
||||
"tier": "A",
|
||||
"fees": {
|
||||
"2026-01": [
|
||||
750,
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sorted_months": [
|
||||
"2026-01"
|
||||
],
|
||||
"transactions": [
|
||||
{
|
||||
"date": "2026-01-10",
|
||||
"amount": 800,
|
||||
"manual_fix": "",
|
||||
"person": "Member_d035d9f9, Member_f4a93e46",
|
||||
"purpose": "other:tournament",
|
||||
"inferred_amount": 800,
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"bank_id": ""
|
||||
}
|
||||
],
|
||||
"exceptions": [],
|
||||
"default_year": 2026
|
||||
},
|
||||
"output": {
|
||||
"members": {
|
||||
"Member_d035d9f9": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-01": {
|
||||
"expected": 750,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 3,
|
||||
"exception": null,
|
||||
"paid": 0.0,
|
||||
"transactions": []
|
||||
}
|
||||
},
|
||||
"other_transactions": [
|
||||
{
|
||||
"amount": 400.0,
|
||||
"date": "2026-01-10",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"purpose": "other:tournament",
|
||||
"confidence": "auto"
|
||||
}
|
||||
],
|
||||
"total_balance": -750
|
||||
},
|
||||
"Member_f4a93e46": {
|
||||
"tier": "A",
|
||||
"months": {
|
||||
"2026-01": {
|
||||
"expected": 750,
|
||||
"original_expected": 750,
|
||||
"attendance_count": 2,
|
||||
"exception": null,
|
||||
"paid": 0.0,
|
||||
"transactions": []
|
||||
}
|
||||
},
|
||||
"other_transactions": [
|
||||
{
|
||||
"amount": 400.0,
|
||||
"date": "2026-01-10",
|
||||
"sender": "Member_d035d9f9",
|
||||
"message": "",
|
||||
"purpose": "other:tournament",
|
||||
"confidence": "auto"
|
||||
}
|
||||
],
|
||||
"total_balance": -750
|
||||
}
|
||||
},
|
||||
"unmatched": [],
|
||||
"credits": {
|
||||
"Member_d035d9f9": -750,
|
||||
"Member_f4a93e46": -750
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user