- remove the events/outages module, ArcGIS adapter, portal config and event statuses - add a configurable city dictionary/parser and make City part of StreetKey - seed: read city/city_name, namespace IDs by city, keep structures distinct - fix reliability: schema v2 with fail-closed load, deterministic ordering, input limits, config validation, atomic DB publish, LLM response caps - add read-only shadow mode comparing the old ES path with addrkit - breaking: registry.db must be re-seeded; config has no portal section
180 lines
6.0 KiB
Go
180 lines
6.0 KiB
Go
package address
|
||
|
||
import "testing"
|
||
|
||
func TestParseHouseBlock(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
raw string
|
||
wantOrdinal string
|
||
wantName string
|
||
wantType string
|
||
wantHouse string
|
||
wantFrac string
|
||
wantLetter string
|
||
wantBuild string
|
||
wantBFrac string
|
||
wantBLetter string
|
||
wantStruct string
|
||
wantHas bool
|
||
}{
|
||
{
|
||
name: "ordinal, fraction and structure",
|
||
raw: "2-я Станционная улица 1/1 стр.",
|
||
wantOrdinal: "2", wantName: "станционная", wantType: "улица",
|
||
wantHouse: "1", wantFrac: "1", wantStruct: "", wantHas: true,
|
||
},
|
||
{
|
||
name: "building with fraction and structure",
|
||
raw: "Станционная улица 30а к9/1 стр.",
|
||
wantName: "станционная", wantType: "улица",
|
||
wantHouse: "30", wantLetter: "а", wantBuild: "9", wantBFrac: "1", wantHas: true,
|
||
},
|
||
{
|
||
name: "letter glued to house",
|
||
raw: "10-й Бронный переулок 33а",
|
||
wantOrdinal: "10", wantName: "бронный", wantType: "переулок",
|
||
wantHouse: "33", wantLetter: "а", wantHas: true,
|
||
},
|
||
{
|
||
name: "microdistrict, two-word name",
|
||
raw: "Зеленый бор микрорайон 13",
|
||
wantName: "бор зеленый", wantType: "микрорайон",
|
||
wantHouse: "13", wantHas: true,
|
||
},
|
||
{
|
||
name: "spelled-out ordinal",
|
||
raw: "вторая Станционная улица 1",
|
||
wantOrdinal: "2", wantName: "станционная", wantType: "улица",
|
||
wantHouse: "1", wantHas: true,
|
||
},
|
||
{
|
||
name: "en dash variant",
|
||
raw: "2–я Станционная улица 1",
|
||
wantOrdinal: "2", wantName: "станционная", wantType: "улица",
|
||
wantHouse: "1", wantHas: true,
|
||
},
|
||
{
|
||
name: "no house",
|
||
raw: "Станционная улица",
|
||
wantName: "станционная", wantType: "улица", wantHas: false,
|
||
},
|
||
{
|
||
name: "structure with number only",
|
||
raw: "Станционная улица 3 стр.",
|
||
wantName: "станционная", wantType: "улица",
|
||
wantHouse: "3", wantStruct: "", wantHas: true,
|
||
},
|
||
{
|
||
name: "letter before fraction",
|
||
raw: "Выборная улица 45а/4",
|
||
wantName: "выборная", wantType: "улица",
|
||
wantHouse: "45", wantLetter: "а", wantFrac: "4", wantHas: true,
|
||
},
|
||
{
|
||
name: "spoken fraction word",
|
||
raw: "Горская улица 18 дробь 1",
|
||
wantName: "горская", wantType: "улица",
|
||
wantHouse: "18", wantFrac: "1", wantHas: true,
|
||
},
|
||
{
|
||
name: "corpus letter without digit",
|
||
raw: "1-я Ельцовка улица 1 кД",
|
||
wantOrdinal: "1", wantName: "ельцовка", wantType: "улица",
|
||
wantHouse: "1", wantBLetter: "д", wantHas: true,
|
||
},
|
||
{
|
||
name: "two type words in a registry name",
|
||
raw: "Гусинобродский Тракт улица 21",
|
||
wantName: "гусинобродский", wantType: "тракт",
|
||
wantHouse: "21", wantHas: true,
|
||
},
|
||
{
|
||
name: "name coincides with its type word",
|
||
raw: "Набережная улица 5",
|
||
wantName: "набережная", wantType: "набережная",
|
||
wantHouse: "5", wantHas: true,
|
||
},
|
||
{
|
||
name: "abbreviations dom and flat",
|
||
raw: "ул. Кошурникова, д. 11/1 кв. 5",
|
||
wantName: "кошурникова", wantType: "улица",
|
||
wantHouse: "11", wantFrac: "1", wantHas: true,
|
||
},
|
||
{
|
||
name: "district suffix in parentheses",
|
||
raw: "Медкадры улица, 1 (Заельцовский район)",
|
||
wantName: "медкадры", wantType: "улица",
|
||
wantHouse: "1", wantHas: true,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := Parse(tt.raw)
|
||
if got.Ordinal != tt.wantOrdinal {
|
||
t.Errorf("Ordinal = %q, want %q", got.Ordinal, tt.wantOrdinal)
|
||
}
|
||
if got.NameKey != tt.wantName {
|
||
t.Errorf("NameKey = %q, want %q", got.NameKey, tt.wantName)
|
||
}
|
||
if got.StreetType != tt.wantType {
|
||
t.Errorf("StreetType = %q, want %q", got.StreetType, tt.wantType)
|
||
}
|
||
if got.House != tt.wantHouse {
|
||
t.Errorf("House = %q, want %q", got.House, tt.wantHouse)
|
||
}
|
||
if got.HouseFrac != tt.wantFrac {
|
||
t.Errorf("HouseFrac = %q, want %q", got.HouseFrac, tt.wantFrac)
|
||
}
|
||
if got.HouseLetter != tt.wantLetter {
|
||
t.Errorf("HouseLetter = %q, want %q", got.HouseLetter, tt.wantLetter)
|
||
}
|
||
if got.Building != tt.wantBuild {
|
||
t.Errorf("Building = %q, want %q", got.Building, tt.wantBuild)
|
||
}
|
||
if got.BuildingFrac != tt.wantBFrac {
|
||
t.Errorf("BuildingFrac = %q, want %q", got.BuildingFrac, tt.wantBFrac)
|
||
}
|
||
if got.BuildingLetter != tt.wantBLetter {
|
||
t.Errorf("BuildingLetter = %q, want %q", got.BuildingLetter, tt.wantBLetter)
|
||
}
|
||
if got.Structure != tt.wantStruct {
|
||
t.Errorf("Structure = %q, want %q", got.Structure, tt.wantStruct)
|
||
}
|
||
if got.HasHouse != tt.wantHas {
|
||
t.Errorf("HasHouse = %v, want %v", got.HasHouse, tt.wantHas)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestParseDeterministic guards the core invariant: parsing the same input
|
||
// always yields the same structured result and the same street key.
|
||
func TestParseDeterministic(t *testing.T) {
|
||
input := "2-я Станционная улица 1/1 стр."
|
||
first := Parse(input)
|
||
for i := 0; i < 100; i++ {
|
||
if got := Parse(input); got.StreetKey != first.StreetKey || got.House != first.House {
|
||
t.Fatalf("non-deterministic parse: %+v vs %+v", got, first)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestNormalizeIdempotent(t *testing.T) {
|
||
inputs := []string{
|
||
" 2-Я Станционная\u00a0улица 1/1 стр. ",
|
||
"ЁЛКИ д. 5, кв. 3",
|
||
"СТАНИЦИОННАЯ — улица, 30а",
|
||
"Медкадры улица, 1 (Заельцовский район)",
|
||
"",
|
||
}
|
||
for _, in := range inputs {
|
||
once := NormalizeText(in)
|
||
twice := NormalizeText(once)
|
||
if once != twice {
|
||
t.Errorf("NormalizeText not idempotent for %q: %q != %q", in, once, twice)
|
||
}
|
||
}
|
||
}
|