fix: Tolerate diacritic/case/whitespace mismatches in Person column matching
- Add canonical_member_key() in match_payments.py to normalize names via NFKD + lowercase + whitespace-collapse before ledger lookup; resolves payments attributed to e.g. "Maria Maco" to canonical "Mária Maco". Emits logger.info when a non-canonical cell is rescued so sheet typos are visible in logs without losing the payment allocation. - Extend group_payments_by_person() in app.py to accept member_names and re-key raw-payment groups under the canonical attendance-sheet name so the modal's Raw Payments debug section also finds the row correctly. - Add raw payments collapsible section to member detail modal in adults.html and juniors.html for debugging payment attribution issues. - Remove 4 obsolete tests targeting routes /fees, /fees-juniors, /reconcile, /reconcile-juniors that no longer exist; add test_match_payments.py covering canonical key equivalence and reconcile() tolerance end-to-end. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,15 @@ from czech_utils import normalize, parse_month_references
|
||||
from sync_fio_to_sheets import get_sheets_service, DEFAULT_SPREADSHEET_ID
|
||||
|
||||
|
||||
def canonical_member_key(name: str) -> str:
|
||||
"""Diacritic-, case-, and whitespace-insensitive key for member-name matching.
|
||||
|
||||
Used to resolve `Person`-column values from the payments sheet to canonical
|
||||
attendance-sheet names, tolerating cells like "Maria Maco" vs "Mária Maco".
|
||||
"""
|
||||
return re.sub(r"\s+", " ", normalize(name)).strip()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Name matching
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -309,6 +318,12 @@ def reconcile(
|
||||
member_tiers = {name: tier for name, tier, _ in members}
|
||||
member_fees = {name: fees for name, _, fees in members}
|
||||
|
||||
# Map canonical key → first attendance-sheet name with that key, so a
|
||||
# `Person` cell that drifts in diacritics/case/whitespace still resolves.
|
||||
canonical_by_key: dict[str, str] = {}
|
||||
for name in member_names:
|
||||
canonical_by_key.setdefault(canonical_member_key(name), name)
|
||||
|
||||
# Initialize ledger
|
||||
ledger: dict[str, dict[str, dict]] = {}
|
||||
other_ledger: dict[str, list] = {}
|
||||
@@ -386,8 +401,9 @@ def reconcile(
|
||||
if is_other:
|
||||
num_allocations = len(matched_members)
|
||||
per_allocation = amount / num_allocations if num_allocations > 0 else 0
|
||||
for member_name, confidence in matched_members:
|
||||
if member_name in other_ledger:
|
||||
for raw_member_name, confidence in matched_members:
|
||||
member_name = canonical_by_key.get(canonical_member_key(raw_member_name))
|
||||
if member_name is not None:
|
||||
other_ledger[member_name].append({
|
||||
"amount": per_allocation,
|
||||
"date": tx["date"],
|
||||
@@ -400,14 +416,20 @@ def reconcile(
|
||||
|
||||
member_share = amount / len(matched_members) if matched_members else 0
|
||||
|
||||
for member_name, confidence in matched_members:
|
||||
if member_name not in ledger:
|
||||
for raw_member_name, confidence in matched_members:
|
||||
member_name = canonical_by_key.get(canonical_member_key(raw_member_name))
|
||||
if member_name is None:
|
||||
logger.warning(
|
||||
"Payment matched to unknown member %r (tx: %s, %s) — adding to unmatched",
|
||||
member_name, tx.get("date", "?"), tx.get("message", "?"),
|
||||
raw_member_name, tx.get("date", "?"), tx.get("message", "?"),
|
||||
)
|
||||
unmatched.append(tx)
|
||||
continue
|
||||
if member_name != raw_member_name:
|
||||
logger.info(
|
||||
"Person cell %r resolved to canonical member %r — consider fixing the sheet",
|
||||
raw_member_name, member_name,
|
||||
)
|
||||
|
||||
in_window = [(m, ledger[member_name][m]["expected"]) for m in matched_months if m in ledger[member_name]]
|
||||
out_of_window = [m for m in matched_months if m not in ledger[member_name]]
|
||||
|
||||
Reference in New Issue
Block a user