addrkit: quality evaluation harness + parser hardening

- add internal/eval: deterministic citizen-style perturbation harness
  (typos, case, word order, abbreviations, filler words, dropped units)
- parser: strip all street-type tokens, filler words (город/дом/д/область),
  trailing apartment (кв N); handle letter-before-fraction and corpus letters
- matching: token-aware typo-tolerant street similarity; wider ambiguity
  margin to surface near-collisions instead of guessing
- seed: correct parse-rate accounting, structure-aware de-duplication
- CLI: 'addrkit eval' command; README with measured results

Measured on the real 73,936-row registry: 99.98% parsed, 0 wrong-house
safety violations across 34k+ perturbed cases.
This commit is contained in:
addrkit
2026-10-09 20:13:50 +07:00
parent 3be331bb65
commit 5cc3556ac5
11 changed files with 690 additions and 25 deletions
+33
View File
@@ -8,10 +8,12 @@ import (
"encoding/json"
"flag"
"fmt"
"math/rand"
"os"
"git.totmin.ru/en2zmax/addrkit/internal/adapters/registrystore"
"git.totmin.ru/en2zmax/addrkit/internal/domain/match"
"git.totmin.ru/en2zmax/addrkit/internal/eval"
"git.totmin.ru/en2zmax/addrkit/internal/seed"
)
@@ -27,6 +29,8 @@ func main() {
os.Exit(runVerify(os.Args[2:]))
case "resolve":
os.Exit(runResolve(os.Args[2:]))
case "eval":
os.Exit(runEval(os.Args[2:]))
case "help", "-h", "--help":
usage()
os.Exit(0)
@@ -44,6 +48,7 @@ Usage:
addrkit seed -in registry.ndjson -out registry.db [-version 2026-10-09]
addrkit verify -db registry.db
addrkit resolve -db registry.db "2-я Станционная улица 1/1 стр."
addrkit eval -db registry.db [-sample 500] [-seed 1]
`)
}
@@ -144,3 +149,31 @@ func runResolve(args []string) int {
_ = enc.Encode(res)
return 0
}
// runEval generates citizen-style perturbations from the seeded registry and
// reports how well the resolver copes. Results are deterministic per -seed.
func runEval(args []string) int {
fs := flag.NewFlagSet("eval", flag.ExitOnError)
db := fs.String("db", "", "SQLite database")
sample := fs.Int("sample", 500, "number of registry entries to sample")
seedVal := fs.Int64("seed", 1, "random seed")
_ = fs.Parse(args)
if *db == "" {
fmt.Fprintln(os.Stderr, "eval: -db is required")
return 2
}
_, entries, err := registrystore.Load(*db)
if err != nil {
fmt.Fprintf(os.Stderr, "eval: %v\n", err)
return 1
}
ix := match.NewIndex("eval", entries, match.DefaultOptions())
rng := rand.New(rand.NewSource(*seedVal))
cases := eval.Generate(entries, *sample, rng)
report := eval.Run(ix, cases)
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
_ = enc.Encode(report)
return 0
}