feat(infer): messages starting with "other:" are not monthly fees

When the bank message starts with "other:" (case-insensitive),
inference does not look for a month, neither in the text nor from the
payment date. The trimmed message is written to Purpose, so reconcile
books the payment as an "other" transaction for the person instead of
a monthly fee. Messages containing "other:" elsewhere are unchanged.

- matching.IsOtherPurpose / is_other_purpose shared by inference and
  reconcile
- reconcile applies the rule to rows not yet processed by inference
- no person found: Purpose is still written, row shows as unmatched
- implemented in Go and legacy Python; four new parity fixtures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 12:37:03 +02:00
co-authored by Claude Opus 5.5
parent 294c40fa42
commit bbad19d822
15 changed files with 567 additions and 10 deletions
@@ -0,0 +1,36 @@
{
"case": "other_not_at_start",
"func": "scripts.match_payments.infer_transaction_details",
"captured_at": "2026-10-08",
"input": {
"tx": {
"sender": "Jana Novakova",
"message": "leden other: dres",
"user_id": "",
"date": {
"type": "string",
"value": "2026-04-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": "Jana Nováková",
"confidence": "auto"
}
],
"months": [
"2026-01"
],
"search_text": "Jana Novakova leden other: dres "
}
}
@@ -0,0 +1,36 @@
{
"case": "other_prefix",
"func": "scripts.match_payments.infer_transaction_details",
"captured_at": "2026-10-08",
"input": {
"tx": {
"sender": "Jana Novakova",
"message": "other: tournament leden 2026",
"user_id": "",
"date": {
"type": "string",
"value": "2026-04-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": "Jana Nováková",
"confidence": "auto"
}
],
"months": [
"other: tournament leden 2026"
],
"search_text": "Jana Novakova other: tournament leden 2026 "
}
}
@@ -0,0 +1,31 @@
{
"case": "other_prefix_uppercase",
"func": "scripts.match_payments.infer_transaction_details",
"captured_at": "2026-10-08",
"input": {
"tx": {
"sender": "Test Payer",
"message": " Other: Dres ",
"user_id": "",
"date": {
"type": "string",
"value": "2026-04-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": [],
"months": [
"Other: Dres"
],
"search_text": "Test Payer Other: Dres "
}
}
@@ -0,0 +1,69 @@
{
"case": "11_other_message_fallback",
"func": "scripts.match_payments.reconcile",
"captured_at": "2026-10-08",
"input": {
"members": [
{
"name": "Member_d035d9f9",
"tier": "A",
"fees": {
"2026-01": [
750,
3
]
}
}
],
"sorted_months": [
"2026-01"
],
"transactions": [
{
"date": "2026-01-10",
"amount": 300,
"manual_fix": "",
"person": "",
"purpose": "",
"inferred_amount": 300,
"sender": "Member_d035d9f9",
"message": "other: tournament",
"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": 300.0,
"date": "2026-01-10",
"sender": "Member_d035d9f9",
"message": "other: tournament",
"purpose": "other: tournament",
"confidence": "auto"
}
],
"total_balance": -750
}
},
"unmatched": [],
"credits": {
"Member_d035d9f9": -750
}
}
}