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:
@@ -32,6 +32,74 @@ import (
|
||||
|
||||
var inferMembers = []string{"Tomáš Němeček (Tov)", "Jana Nováková"}
|
||||
|
||||
func TestIsOtherPurpose(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]bool{
|
||||
"other: tournament": true,
|
||||
"other:shirt": true,
|
||||
" Other: Tournament": true,
|
||||
"OTHER:x": true,
|
||||
"leden other: x": false,
|
||||
"other tournament": false,
|
||||
"another: x": false,
|
||||
"2026-01": false,
|
||||
"": false,
|
||||
}
|
||||
for in, want := range cases {
|
||||
if got := IsOtherPurpose(in); got != want {
|
||||
t.Errorf("IsOtherPurpose(%q) = %v, want %v", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTransactionDetails_OtherMessage(t *testing.T) {
|
||||
t.Parallel()
|
||||
jana := []Match{{Name: "Jana Nováková", Confidence: ConfidenceAuto}}
|
||||
cases := []struct {
|
||||
name string
|
||||
tx Transaction
|
||||
wantMembers []Match
|
||||
wantMonths []string
|
||||
}{
|
||||
{
|
||||
// Month words and the date must both be ignored.
|
||||
name: "other prefix: message becomes the purpose, no month",
|
||||
tx: Transaction{Sender: "Jana Novakova", Message: " other: turnaj leden 2026 ", Date: "2026-04-15"},
|
||||
wantMembers: jana,
|
||||
wantMonths: []string{"other: turnaj leden 2026"},
|
||||
},
|
||||
{
|
||||
name: "other prefix is case-insensitive and keeps the original text",
|
||||
tx: Transaction{Sender: "Jana Novakova", Message: "Other: Dres", Date: "2026-04-15"},
|
||||
wantMembers: jana,
|
||||
wantMonths: []string{"Other: Dres"},
|
||||
},
|
||||
{
|
||||
name: "other prefix with no known member still yields the purpose",
|
||||
tx: Transaction{Sender: "Unknown Payer", Message: "other: dres", Date: "2026-04-15"},
|
||||
wantMembers: nil,
|
||||
wantMonths: []string{"other: dres"},
|
||||
},
|
||||
{
|
||||
name: "other: not at the start is handled the old way",
|
||||
tx: Transaction{Sender: "Jana Novakova", Message: "leden other: dres", Date: "2026-04-15"},
|
||||
wantMembers: jana,
|
||||
wantMonths: []string{"2026-01"},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := InferTransactionDetails(tc.tx, inferMembers, 2026)
|
||||
if len(got.Members) != len(tc.wantMembers) || (len(got.Members) > 0 && !reflect.DeepEqual(got.Members, tc.wantMembers)) {
|
||||
t.Errorf("Members = %v, want %v", got.Members, tc.wantMembers)
|
||||
}
|
||||
if !reflect.DeepEqual(got.Months, tc.wantMonths) {
|
||||
t.Errorf("Months = %v, want %v", got.Months, tc.wantMonths)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferTransactionDetails(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user