addrkit: strict ordinal matching, LLM-variant guard, externalized prompt

- match: require candidate ordinal to equal the query ordinal in fuzzy search
  (deterministic typo recall 73% -> 80%, no wrong houses)
- resolver: compatibleVariant guard — LLM may only fix street spelling, keeping
  the same words, ordinal and house unit; damerau distance for transpositions
- llm: prompt moved out of code (go:embed default_prompt.txt) with
  llm.system_prompt / llm.system_prompt_file overrides; guided JSON parsing
  tolerant of thinking blocks
- address: recognize ordinals appearing after the street name
- tests: first coverage for the LLM adapter and config prompt loading; README

Measured (sample 200): deterministic 94.7-95.8% exact / 0 wrong across 4 seeds;
LLM+guard typo 90.7-89.8% / 0 wrong. LLM fallback latency ~1.2-1.9s.
This commit is contained in:
Maksim Totmin
2026-10-09 21:40:46 +07:00
parent ba4705fc4f
commit f1537f1d6b
17 changed files with 590 additions and 48 deletions
+4 -3
View File
@@ -8,6 +8,7 @@
package eval
import (
"context"
"fmt"
"math/rand"
"sort"
@@ -55,15 +56,15 @@ type Report struct {
Categories map[string]CategoryStat `json:"categories"`
}
// Run evaluates all cases against the index.
func Run(ix *match.Index, cases []Case) Report {
// Run evaluates all cases against the resolver.
func Run(resolver *match.Resolver, cases []Case) Report {
rep := Report{Categories: map[string]CategoryStat{}}
for _, c := range cases {
st := rep.Categories[c.Category]
st.Total++
rep.Total++
res := ix.Resolve(address.Parse(c.Query))
res := resolver.Resolve(context.Background(), c.Query)
exp := address.Parse(c.Expect)
exact := false