fix(outages): parse portal address at the boundary; strip district suffix

The outages portal appends the district in parentheses, e.g.
"Медкадры улица, 1 (Заельцовский район)". That suffix hid the house number
from the parser, so every portal address failed house matching and /v1/outages
always returned empty.

- address: NormalizeText drops a trailing "(...)" group (root fix, benefits
  every source)
- outages: introduce HouseUnit; the ArcGIS adapter parses the (cleaned) address
  at the boundary and stores the structured unit; MatchingHouse matches on the
  unit (with a parse fallback for sources that only provide text)
- config: portal.url is now optional (core runs without the events module)
- httpapi: always return "outages": [] (no more omitempty); report
  "detail" when the events module is not configured
- tests: regression coverage for the portal district format
This commit is contained in:
Maksim Totmin
2026-10-09 22:05:18 +07:00
parent 59a8b2c164
commit 50f180ea10
10 changed files with 156 additions and 29 deletions
+13 -5
View File
@@ -23,6 +23,7 @@ import (
"git.totmin.ru/en2zmax/addrkit/internal/adapters/registrystore"
"git.totmin.ru/en2zmax/addrkit/internal/config"
"git.totmin.ru/en2zmax/addrkit/internal/domain/match"
"git.totmin.ru/en2zmax/addrkit/internal/domain/outages"
)
func main() {
@@ -58,11 +59,18 @@ func main() {
}
resolver := match.NewResolver(index, rewriter)
portal := arcgis.New(
cfg.Portal.URL,
time.Duration(cfg.Portal.TimeoutSeconds)*time.Second,
cfg.Portal.Retries,
)
var portal outages.Source
if cfg.Portal.URL != "" {
portal = arcgis.New(
cfg.Portal.URL,
time.Duration(cfg.Portal.TimeoutSeconds)*time.Second,
cfg.Portal.Retries,
)
logger.Info("outages module enabled", "url", cfg.Portal.URL)
} else {
// portal.url is optional: without it only /v1/resolve is served.
logger.Info("outages module disabled (portal.url not set)")
}
loc := operationalLocation()
srv := httpapi.New(resolver, portal, logger, loc)