New go/internal/domain/matching package porting three helpers from scripts/match_payments.py: - BuildNameVariants: normalized ASCII variants from a member name (nickname in parens, last/first split, len<3 filtered); variants[0] is always the full base name — MatchMembers relies on this invariant. - MatchMembers: auto/review confidence matching with an exact-name short-circuit pass that prevents nickname substrings (tov) from firing inside longer surnames (ottova); common-surname filter for review tier. - FormatDate: nil/empty/""/serial int/float64 (since 1899-12-30, fractional days supported)/YYYY-MM-DD passthrough/garbage → never errors. - InferTransactionDetails: composes BuildNameVariants+MatchMembers+ ParseMonthReferences; falls back to sender-only member match and date-derived month when text carries no signal. 21 table-driven tests; all expected values verified against live Python on 2026-05-06. go-build, go-test, go-lint all clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
63 lines
1.5 KiB
Go
63 lines
1.5 KiB
Go
package matching
|
|
|
|
// Expected values verified against scripts/match_payments.py on 2026-05-06:
|
|
//
|
|
// PYTHONPATH=scripts:. python3 -c '
|
|
// from match_payments import _build_name_variants
|
|
// for n in ["František Vrbík (Štrúdl)", "Tov (St)", "Jana", " Petr Novák ( Jenda ) "]:
|
|
// print(repr(n), "->", _build_name_variants(n))
|
|
// '
|
|
//
|
|
// Output:
|
|
//
|
|
// 'František Vrbík (Štrúdl)' -> ['frantisek vrbik', 'strudl', 'vrbik', 'frantisek']
|
|
// 'Tov (St)' -> ['tov']
|
|
// 'Jana' -> ['jana']
|
|
// ' Petr Novák ( Jenda ) ' -> ['petr novak', ' jenda ', 'novak', 'petr']
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildNameVariants(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cases := []struct {
|
|
name string
|
|
input string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "full name with nickname",
|
|
input: "František Vrbík (Štrúdl)",
|
|
want: []string{"frantisek vrbik", "strudl", "vrbik", "frantisek"},
|
|
},
|
|
{
|
|
name: "nickname too short filtered out",
|
|
input: "Tov (St)",
|
|
want: []string{"tov"},
|
|
},
|
|
{
|
|
name: "single-part name no nickname",
|
|
input: "Jana",
|
|
want: []string{"jana"},
|
|
},
|
|
{
|
|
name: "extra whitespace inside parens preserved by normalize",
|
|
input: " Petr Novák ( Jenda ) ",
|
|
want: []string{"petr novak", " jenda ", "novak", "petr"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got := BuildNameVariants(tc.input)
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Errorf("BuildNameVariants(%q)\n got %q\n want %q", tc.input, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|