All checks were successful
Build and Test / build-and-test (push) Successful in 37s
Add a global, temporal ai_notes list appended to the classifier prompt (seeded once from no-PII defaults, documented in README), managed inline on a new AI tab with a read-only view of the assembled prompt. Every AI-run upload records the browser-round-tripped suggestion blob + model; misreads are derived (final field != AI guess) and reviewed one by one (image + per-field guess-vs-entered + notes-since), attributing which note fixed each or closing unresolved. Update SPEC (new section 10), DESIGN item 15, README, and changelog (0.0.3). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
2.6 KiB
Go
90 lines
2.6 KiB
Go
package classify
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"maisym.com/hsa/internal/config"
|
|
)
|
|
|
|
func contains(list []string, want string) bool {
|
|
for _, s := range list {
|
|
if s == want {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// The Tremblay family: Jean-Michel and Jude share the initial "J", so neither may
|
|
// get a bare "J. Tremblay" variant; Alex ("A") and Evan ("E") are unique and may.
|
|
func TestNameVariantsAmbiguousInitial(t *testing.T) {
|
|
all := []config.Person{
|
|
{First: "Jean-Michel", Last: "Tremblay"},
|
|
{First: "Jude", Last: "Tremblay"},
|
|
{First: "Alex", Last: "Tremblay"},
|
|
{First: "Evan", Last: "Tremblay"},
|
|
{First: "Lynna", Last: "Nguyen"},
|
|
}
|
|
|
|
jm := NameVariants(all[0], all)
|
|
if contains(jm, "J. Tremblay") || contains(jm, "Tremblay, J.") {
|
|
t.Errorf("Jean-Michel must not get an ambiguous single-initial variant: %v", jm)
|
|
}
|
|
// Compound initials are unique to Jean-Michel and should appear.
|
|
if !contains(jm, "J-M Tremblay") {
|
|
t.Errorf("Jean-Michel should get compound initial variant J-M Tremblay: %v", jm)
|
|
}
|
|
|
|
jude := NameVariants(all[1], all)
|
|
if contains(jude, "J. Tremblay") {
|
|
t.Errorf("Jude must not get an ambiguous single-initial variant: %v", jude)
|
|
}
|
|
|
|
alex := NameVariants(all[2], all)
|
|
if !contains(alex, "A. Tremblay") {
|
|
t.Errorf("Alex (unique initial) should get A. Tremblay: %v", alex)
|
|
}
|
|
|
|
// Lynna's surname is unique, so a single initial is fine.
|
|
lynna := NameVariants(all[4], all)
|
|
if !contains(lynna, "L. Nguyen") {
|
|
t.Errorf("Lynna (unique surname) should get L. Nguyen: %v", lynna)
|
|
}
|
|
|
|
// Order/comma variants are always present and unambiguous.
|
|
for _, want := range []string{"Jean-Michel Tremblay", "Tremblay Jean-Michel", "Tremblay, Jean-Michel"} {
|
|
if !contains(jm, want) {
|
|
t.Errorf("expected variant %q in %v", want, jm)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildSystemPrompt(t *testing.T) {
|
|
persons := []config.Person{
|
|
{First: "Jean-Michel", Last: "Tremblay"},
|
|
{First: "Lynna", Last: "Nguyen"},
|
|
}
|
|
categories := []config.Category{
|
|
{Name: "Pharmacy", Examples: []string{"CVS", "Rx"}},
|
|
{Name: "Other"},
|
|
}
|
|
p := BuildSystemPrompt(persons, categories, "2026-06-17", []string{"Treat handwritten totals as authoritative."})
|
|
|
|
for _, want := range []string{
|
|
"Jean-Michel Tremblay", // canonical label
|
|
"Lynna Nguyen",
|
|
"Pharmacy",
|
|
"CVS", // example injected
|
|
"2026-06-17", // today
|
|
"AMBIGUOUS", // the ambiguity rule
|
|
"IGNORE everyone", // ignore-other-people rule
|
|
"null", // unknown handling
|
|
"raw_name", // raw echo
|
|
"CLOSEST to today", // date tie-breaker
|
|
} {
|
|
if !strings.Contains(p, want) {
|
|
t.Errorf("system prompt missing %q", want)
|
|
}
|
|
}
|
|
}
|