package match import ( "context" "git.totmin.ru/en2zmax/addrkit/internal/domain/address" ) // Rewriter is an optional, pluggable query rewriter. Its only job is to // propose alternative surface forms of a user address (for example an LLM // normalising "кошурникова одиннадцать дробь один" to "Кошевого 11/1"). The // rewriter never decides the answer: every proposal is parsed and resolved // deterministically, and only entries that exist in the index can be returned. // // It is defined here (consumer side) so the domain stays free of any transport // or vendor dependency. type Rewriter interface { Rewrite(ctx context.Context, raw string) ([]string, error) } // Resolver wraps an Index with an optional Rewriter. // // The deterministic path is always tried first. The rewriter is consulted only // when the deterministic result is not a confident "found", and its // suggestions can only ever turn a miss into a match against real data. type Resolver struct { index *Index rewriter Rewriter } // NewResolver builds a resolver. rewriter may be nil to disable rewriting. func NewResolver(index *Index, rewriter Rewriter) *Resolver { return &Resolver{index: index, rewriter: rewriter} } // Version returns the current data version. func (r *Resolver) Version() string { return r.index.Version() } // Ready reports whether the resolver has a usable index. func (r *Resolver) Ready() bool { return r.index != nil && r.index.Size() > 0 } // Resolve parses raw and resolves it, consulting the rewriter when enabled. func (r *Resolver) Resolve(ctx context.Context, raw string) Result { parsed := address.Parse(raw) res := r.index.Resolve(parsed) if res.Status == address.StatusFound || r.rewriter == nil { return res } if res.Status != address.StatusNotFound && res.Status != address.StatusAmbiguous { return res } alternatives, err := r.rewriter.Rewrite(ctx, raw) if err != nil || len(alternatives) == 0 { return res } best := res for _, alt := range alternatives { candidate := r.index.Resolve(address.Parse(alt)) if better(candidate, best) { best = candidate } } return best } // better ranks results: a stronger status wins, then higher confidence. func better(a, b Result) bool { ra, rb := statusRank(a.Status), statusRank(b.Status) if ra != rb { return ra > rb } return a.Confidence > b.Confidence } func statusRank(s address.Status) int { switch s { case address.StatusFound: return 3 case address.StatusAmbiguous: return 2 case address.StatusPartial: return 1 default: return 0 } }