Compare commits

..

4 Commits

Author SHA1 Message Date
07ca1cd9e1 fix(py): coerce VS column to string in payments tx projection
All checks were successful
Deploy to K8s / deploy (push) Successful in 7s
The Sheets API returns VS (variabilní symbol) cells as float64 when
the column is number-formatted, so Python was emitting vs: 0 (a JSON
number) while Go's getVal uses fmt.Sprint and always emits vs: "0"
(a string). Add get_str helper that converts whole-number floats via
int() first (matching Go's %g formatting), applied to the vs field.

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

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 23:50:33 +02:00
3 changed files with 19 additions and 0 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

@@ -236,6 +236,8 @@ def fetch_sheet_data(spreadsheet_id: str, credentials_path: str) -> list[dict]:
idx_sender = get_col_index("Sender") idx_sender = get_col_index("Sender")
idx_message = get_col_index("Message") idx_message = get_col_index("Message")
idx_bank_id = get_col_index("Bank ID") idx_bank_id = get_col_index("Bank ID")
idx_vs = get_col_index("VS")
idx_sync_id = get_col_index("Sync ID")
required = {"Date": idx_date, "Amount": idx_amount, "Person": idx_person, "Purpose": idx_purpose} required = {"Date": idx_date, "Amount": idx_amount, "Person": idx_person, "Purpose": idx_purpose}
missing = [name for name, idx in required.items() if idx == -1] missing = [name for name, idx in required.items() if idx == -1]
@@ -247,6 +249,12 @@ 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)
tx = { tx = {
"date": format_date(get_val(idx_date)), "date": format_date(get_val(idx_date)),
"amount": get_val(idx_amount), "amount": get_val(idx_amount),
@@ -255,8 +263,10 @@ def fetch_sheet_data(spreadsheet_id: str, credentials_path: str) -> list[dict]:
"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_str(idx_vs),
"message": get_val(idx_message), "message": get_val(idx_message),
"bank_id": get_val(idx_bank_id), "bank_id": get_val(idx_bank_id),
"sync_id": get_val(idx_sync_id),
} }
transactions.append(tx) transactions.append(tx)

View File

@@ -129,6 +129,7 @@ class TestWebApp(unittest.TestCase):
'date': '2026-01-01', 'amount': 750, 'person': 'Test Member', 'date': '2026-01-01', 'amount': 750, 'person': 'Test Member',
'purpose': '2026-01', 'message': 'test payment', 'purpose': '2026-01', 'message': 'test payment',
'sender': 'External Bank User', 'inferred_amount': 750, 'sender': 'External Bank User', 'inferred_amount': 750,
'vs': '', 'sync_id': 'abc123',
}] }]
response = self.client.get('/api/adults') response = self.client.get('/api/adults')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@@ -155,6 +156,7 @@ class TestWebApp(unittest.TestCase):
mock_fetch_sheet.return_value = [{ mock_fetch_sheet.return_value = [{
'date': '2026-01-15', 'amount': 500, 'person': 'Junior One', 'date': '2026-01-15', 'amount': 500, 'person': 'Junior One',
'purpose': '2026-01', 'message': '', 'sender': 'Parent', 'inferred_amount': 500, 'purpose': '2026-01', 'message': '', 'sender': 'Parent', 'inferred_amount': 500,
'vs': '', 'sync_id': 'def456',
}] }]
response = self.client.get('/api/juniors') response = self.client.get('/api/juniors')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@@ -172,6 +174,7 @@ class TestWebApp(unittest.TestCase):
mock_fetch_sheet.return_value = [{ mock_fetch_sheet.return_value = [{
'date': '2026-01-01', 'amount': 750, 'person': 'Test Member', 'date': '2026-01-01', 'amount': 750, 'person': 'Test Member',
'purpose': '2026-01', 'message': 'test', 'sender': 'Someone', 'purpose': '2026-01', 'message': 'test', 'sender': 'Someone',
'vs': '', 'sync_id': 'ghi789',
}] }]
response = self.client.get('/api/payments') response = self.client.get('/api/payments')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)