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:
@@ -15,13 +15,26 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.totmin.ru/en2zmax/addrkit/internal/domain/address"
|
||||
"git.totmin.ru/en2zmax/addrkit/internal/domain/outages"
|
||||
)
|
||||
|
||||
// reDistrictSuffix matches the administrative suffix the portal appends to its
|
||||
// display address, e.g. "Медкадры улица, 1 (Заельцовский район)". It must be
|
||||
// removed before the address is parsed, otherwise the trailing district text
|
||||
// hides the house number. The district is available separately as a field.
|
||||
var reDistrictSuffix = regexp.MustCompile(`\s*\([^)]*\)\s*$`)
|
||||
|
||||
// cleanAddress strips the portal's district suffix and surrounding spaces.
|
||||
func cleanAddress(s string) string {
|
||||
return strings.TrimSpace(reDistrictSuffix.ReplaceAllString(s, ""))
|
||||
}
|
||||
|
||||
// Client is a bounded HTTP client for the portal.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
@@ -127,8 +140,9 @@ func parseResponse(body []byte) ([]outages.Outage, error) {
|
||||
|
||||
out := make([]outages.Outage, 0, len(pr.Features))
|
||||
for _, feat := range pr.Features {
|
||||
addr := cleanAddress(addressOf(feat.Attributes, feat.Value))
|
||||
o := outages.Outage{
|
||||
Address: addressOf(feat.Attributes, feat.Value),
|
||||
Address: addr,
|
||||
Type: lookup(maps, "type_id", feat.Attributes),
|
||||
System: lookup(maps, "system_id", feat.Attributes),
|
||||
Time: lookup(maps, "time_category", feat.Attributes),
|
||||
@@ -136,6 +150,9 @@ func parseResponse(body []byte) ([]outages.Outage, error) {
|
||||
EndDate: stringOf(feat.Attributes["end_date"]),
|
||||
}
|
||||
o.Action = actionFor(o.System)
|
||||
// Parse the address at the boundary so the domain matches on structure
|
||||
// and never depends on this portal's text format.
|
||||
o.Unit = outages.HouseUnitOf(address.Parse(addr))
|
||||
out = append(out, o)
|
||||
}
|
||||
return out, nil
|
||||
|
||||
Reference in New Issue
Block a user