infer_payments was building member_names from get_members_with_fees() (adults sheet only). Junior-only members were invisible to the matcher, so a payment message containing an exact junior name would produce a fuzzy review match against a different adult sharing the same first name. Fix: union the adult and junior rosters (deduped via canonical_member_key) so all members are candidates. The existing exact-name short-circuit in match_members then handles precedence correctly. Two regression tests added for the Jáchym Kubík / Jáchym Hrušák case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
73 lines
3.0 KiB
Python
73 lines
3.0 KiB
Python
import unittest
|
|
from scripts.match_payments import match_members
|
|
|
|
|
|
MEMBERS = [
|
|
"Henrietta Ottová",
|
|
"Tomáš Němeček (Tov)",
|
|
"František Vrbík (Štrúdl)",
|
|
"Jana Nováková",
|
|
]
|
|
|
|
|
|
class TestMatchMembersExact(unittest.TestCase):
|
|
def test_full_name_in_message_returns_only_that_member(self):
|
|
# "tov" is a substring of "ottova" — the old code returned both members
|
|
result = match_members("Henrietta Ottová (Heny): 04/2026", MEMBERS)
|
|
names = [r[0] for r in result]
|
|
self.assertEqual(names, ["Henrietta Ottová"])
|
|
self.assertTrue(all(conf == "auto" for _, conf in result))
|
|
|
|
def test_nickname_tov_not_matched_inside_ottova(self):
|
|
# Bare nickname message should NOT match Tomáš via "tov" inside "ottova"
|
|
result = match_members("platba ottova 04/2026", MEMBERS)
|
|
names = [r[0] for r in result]
|
|
self.assertNotIn("Tomáš Němeček (Tov)", names)
|
|
|
|
def test_combined_payment_two_full_names(self):
|
|
result = match_members("Henrietta Ottová a Tomáš Němeček 04/2026", MEMBERS)
|
|
names = [r[0] for r in result]
|
|
self.assertIn("Henrietta Ottová", names)
|
|
self.assertIn("Tomáš Němeček (Tov)", names)
|
|
self.assertTrue(all(conf == "auto" for _, conf in result))
|
|
|
|
def test_nickname_alone_still_matches_correctly(self):
|
|
# "Tov" alone should still match Tomáš (as long as "ottova" is not in the text)
|
|
result = match_members("Tov platba 04/2026", MEMBERS)
|
|
names = [r[0] for r in result]
|
|
self.assertIn("Tomáš Němeček (Tov)", names)
|
|
|
|
def test_full_name_no_diacritics_still_matches(self):
|
|
result = match_members("Henrietta Ottova 04/2026", MEMBERS)
|
|
names = [r[0] for r in result]
|
|
self.assertIn("Henrietta Ottová", names)
|
|
self.assertNotIn("Tomáš Němeček (Tov)", names)
|
|
|
|
def test_first_last_name_present_any_order(self):
|
|
result = match_members("Platba od Nemeček Tomas 04/2026", MEMBERS)
|
|
names = [r[0] for r in result]
|
|
self.assertIn("Tomáš Němeček (Tov)", names)
|
|
|
|
def test_shared_first_name_junior_in_roster_wins_exact(self):
|
|
# Regression: two members share first name "Jáchym"; message has full name
|
|
# of the junior-only member → exact match must win, no [?] on the adult.
|
|
roster = ["Jáchym Hrušák (G)", "Jáchym Kubík"]
|
|
result = match_members(
|
|
"JIŘÍ KUBÍK Jáchym Kubík: 01/2026+03/2026+04/2026", roster
|
|
)
|
|
self.assertEqual(result, [("Jáchym Kubík", "auto")])
|
|
|
|
def test_shared_first_name_without_junior_in_roster_falls_back(self):
|
|
# Without Kubík in the roster (old behaviour), Hrušák wins via first-name
|
|
# partial match — confirms the roster-expansion fix is the real solution.
|
|
roster = ["Jáchym Hrušák (G)"]
|
|
result = match_members(
|
|
"JIŘÍ KUBÍK Jáchym Kubík: 01/2026+03/2026+04/2026", roster
|
|
)
|
|
names = [r[0] for r in result]
|
|
self.assertIn("Jáchym Hrušák (G)", names)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|