diff --git a/cmd/addrkit/main.go b/cmd/addrkit/main.go index 5de2198..ddcfba7 100644 --- a/cmd/addrkit/main.go +++ b/cmd/addrkit/main.go @@ -16,6 +16,7 @@ import ( "git.totmin.ru/en2zmax/addrkit/internal/adapters/llm" "git.totmin.ru/en2zmax/addrkit/internal/adapters/registrystore" "git.totmin.ru/en2zmax/addrkit/internal/config" + "git.totmin.ru/en2zmax/addrkit/internal/domain/address" "git.totmin.ru/en2zmax/addrkit/internal/domain/match" "git.totmin.ru/en2zmax/addrkit/internal/eval" "git.totmin.ru/en2zmax/addrkit/internal/seed" @@ -35,6 +36,8 @@ func main() { os.Exit(runResolve(os.Args[2:])) case "eval": os.Exit(runEval(os.Args[2:])) + case "selfcheck": + os.Exit(runSelfcheck(os.Args[2:])) case "help", "-h", "--help": usage() os.Exit(0) @@ -53,6 +56,7 @@ Usage: addrkit verify -db registry.db addrkit resolve -db registry.db "2-я Станционная улица 1/1 стр." addrkit eval -db registry.db [-sample 500] [-seed 1] + addrkit selfcheck -db registry.db `) } @@ -265,3 +269,57 @@ func (c *countingRewriter) avg() time.Duration { } return c.total / time.Duration(c.calls) } + +// runSelfcheck resolves every indexed address against the index and reports how +// many resolve back to themselves. This is the strongest answer to "do all +// addresses resolve?": it exercises the whole registry, not a sample. +func runSelfcheck(args []string) int { + fs := flag.NewFlagSet("selfcheck", flag.ExitOnError) + db := fs.String("db", "", "SQLite database") + _ = fs.Parse(args) + if *db == "" { + fmt.Fprintln(os.Stderr, "selfcheck: -db is required") + return 2 + } + _, entries, err := registrystore.Load(*db) + if err != nil { + fmt.Fprintf(os.Stderr, "selfcheck: %v\n", err) + return 1 + } + resolver := match.NewResolver(match.NewIndex("selfcheck", entries, match.DefaultOptions()), nil) + + exact := 0 + byStatus := map[string]int{} + var failures []string + for _, e := range entries { + res := resolver.Resolve(context.Background(), e.Address) + if res.Status == address.StatusFound && sameEntry(res.Address, e) { + exact++ + continue + } + byStatus[string(res.Status)]++ + if len(failures) < 40 { + failures = append(failures, fmt.Sprintf("[%s] %q -> %q", res.Status, e.Address, res.Address)) + } + } + out := map[string]any{ + "entries": len(entries), + "exact": exact, + "exact_rate": float64(exact) / float64(max(len(entries), 1)), + "non_exact": byStatus, + "failures": failures, + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + _ = enc.Encode(out) + return 0 +} + +// sameEntry reports whether a resolved address is the same street and house +// unit as the registry entry it came from. +func sameEntry(resolved string, e address.Entry) bool { + p := address.Parse(resolved) + return p.StreetKey == e.StreetKey && + p.House == e.House && p.HouseFrac == e.HouseFrac && p.HouseLetter == e.HouseLetter && + p.Building == e.Building && p.BuildingFrac == e.BuildingFrac && p.BuildingLetter == e.BuildingLetter +}