Compare commits

..

6 Commits

Author SHA1 Message Date
709a2f2335 Merge pull request 'fix(py): parity coercions — amount/message types + junior '?' sticky' (#25) from fix/py-parity-coercions into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Reviewed-on: #25
2026-05-07 22:26:39 +00:00
58973473c9 fix(py): make junior '?' cell text sticky across exception overrides
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
Go's build_juniors sets cellText = "?" + countStr whenever
md.IsUnknown is true, regardless of whether an exception overrides the
expected amount. Python was checking expected == "?" for this branch,
but reconcile replaces expected with the exception amount (e.g. 0)
before the view builder runs, so the "?" was silently dropped to "-".

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

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

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

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 23:59:35 +02:00
5dcac25c13 chore: CHANGELOG entry for M5.4 fix #2 (py vs/sync_id)
All checks were successful
Deploy to K8s / deploy (push) Successful in 6s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 23:52:04 +02:00
fc47606b1c Merge pull request 'feat(py): M5.4 fix #2 — add vs and sync_id to payments tx projection' (#23) from fix/py-payments-add-vs-syncid into main
All checks were successful
Deploy to K8s / deploy (push) Successful in 9s
Reviewed-on: #23
2026-05-07 21:51:12 +00:00
3 changed files with 28 additions and 6 deletions

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## 2026-05-07 23:51 CEST — feat(py): M5.4 fix #2 — add vs and sync_id to payments tx projection
- `scripts/match_payments.py`: `fetch_sheet_data` now reads `VS` and `Sync ID` columns and includes `vs`/`sync_id` keys in every tx dict. Previously only 9 columns were projected, causing `make parity` to report extra `vs`/`sync_id` fields on every raw payment row emitted by the Go backend. Values flow through `group_payments_by_person``_unwrap_view_model_for_api` to `raw_payments` (adults/juniors) and `grouped_payments` (payments) automatically.
- `tests/test_app.py`: updated `/api/*` mock fixtures to include `vs`/`sync_id` keys for realism.
- **Cache note**: after deploying, hit `POST /flush-cache` once so the in-process cache is cleared and the next request picks up the new column lookups.
## 2026-05-07 23:37 CEST — fix(go): accept single-digit day/month in attendance date headers ## 2026-05-07 23:37 CEST — fix(go): accept single-digit day/month in attendance date headers
- `go/internal/services/membership/sources.go`: `parseDates` now uses Go time formats `2.1.2006` and `1/2/2006` (single-digit reference forms, which accept both padded and unpadded inputs) instead of `02.01.2006` and `01/02/2006`. The Czech attendance sheet headers contain dates like `1.6.2026`, `23.3.2026`, `6.4.2026` — Go silently dropped those columns under the strict zero-padded format, while Python's `strptime("%d.%m.%Y")` accepted them. Effect was a missing `2026-06` month entirely on `/api/juniors` plus undercounted attendance for any month with single-digit columns; both surfaced as diffs in `make parity`. - `go/internal/services/membership/sources.go`: `parseDates` now uses Go time formats `2.1.2006` and `1/2/2006` (single-digit reference forms, which accept both padded and unpadded inputs) instead of `02.01.2006` and `01/02/2006`. The Czech attendance sheet headers contain dates like `1.6.2026`, `23.3.2026`, `6.4.2026` — Go silently dropped those columns under the strict zero-padded format, while Python's `strptime("%d.%m.%Y")` accepted them. Effect was a missing `2026-06` month entirely on `/api/juniors` plus undercounted attendance for any month with single-digit columns; both surfaced as diffs in `make parity`.

View File

@@ -249,16 +249,31 @@ def fetch_sheet_data(spreadsheet_id: str, credentials_path: str) -> list[dict]:
def get_val(idx): def get_val(idx):
return row[idx] if idx != -1 and idx < len(row) else "" return row[idx] if idx != -1 and idx < len(row) else ""
def get_str(idx):
v = get_val(idx)
if isinstance(v, float) and v.is_integer():
return str(int(v))
return str(v)
def get_float(idx):
v = get_val(idx)
if isinstance(v, (int, float)):
return float(v)
try:
return float(str(v).strip())
except (ValueError, TypeError):
return 0.0
tx = { tx = {
"date": format_date(get_val(idx_date)), "date": format_date(get_val(idx_date)),
"amount": get_val(idx_amount), "amount": get_float(idx_amount),
"manual_fix": get_val(idx_manual), "manual_fix": get_val(idx_manual),
"person": get_val(idx_person), "person": get_val(idx_person),
"purpose": get_val(idx_purpose), "purpose": get_val(idx_purpose),
"inferred_amount": get_val(idx_inferred_amount), "inferred_amount": get_val(idx_inferred_amount),
"sender": get_val(idx_sender), "sender": get_val(idx_sender),
"vs": get_val(idx_vs), "vs": get_str(idx_vs),
"message": get_val(idx_message), "message": get_str(idx_message),
"bank_id": get_val(idx_bank_id), "bank_id": get_val(idx_bank_id),
"sync_id": get_val(idx_sync_id), "sync_id": get_val(idx_sync_id),
} }

View File

@@ -310,8 +310,9 @@ def build_juniors_view_model(
cell_text = "-" cell_text = "-"
amount_to_pay = 0 amount_to_pay = 0
if expected == "?" or (isinstance(expected, int) and expected > 0): is_unknown = original_expected == "?"
if expected == "?": if is_unknown or (isinstance(expected, int) and expected > 0):
if is_unknown:
status = "empty" status = "empty"
cell_text = f"?{count_str}" cell_text = f"?{count_str}"
elif paid >= expected: elif paid >= expected:
@@ -339,7 +340,7 @@ def build_juniors_view_model(
status = "surplus" status = "surplus"
cell_text = f"PAID {paid}" cell_text = f"PAID {paid}"
if (isinstance(expected, int) and expected > 0) or paid > 0: if (not is_unknown and isinstance(expected, int) and expected > 0) or paid > 0:
tooltip = f"Received: {paid}, Expected: {expected}" tooltip = f"Received: {paid}, Expected: {expected}"
else: else:
tooltip = "" tooltip = ""