diff --git a/server/app/goauto/aimatching/normalizer.go b/server/app/goauto/aimatching/normalizer.go index 3f31584..1c5daac 100644 --- a/server/app/goauto/aimatching/normalizer.go +++ b/server/app/goauto/aimatching/normalizer.go @@ -15,6 +15,7 @@ var ( traditionalToSimplified *gocc.OpenCC traditionalErr error weightPattern = regexp.MustCompile(`(?i)(\d+(?:\.\d+)?)\s*(?:[-~~至到]\s*(\d+(?:\.\d+)?))?\s*(公斤|千克|kg|斤)`) + sizeTokenPattern = regexp.MustCompile(`(?i)^(?:xxxs|xxl|xxxl|xxxxl|[2-9]xl|xs|s|m|l|xl|\d+xl)\b`) ) // Normalize makes a deterministic comparison key. It intentionally removes @@ -80,7 +81,7 @@ func normalizeWeight(raw string) string { // deliberately treated as no match instead of picking the first label. func DeterministicMatch(request MatchRequest) (MatchResult, bool) { color, colorOK := exactCandidate(request.TargetColor, request.Colors) - size, sizeOK := exactCandidate(request.TargetSize, request.Sizes) + size, sizeOK := exactSizeCandidate(request.TargetSize, request.Sizes) if !colorOK || !sizeOK { return MatchResult{}, false } @@ -88,6 +89,27 @@ func DeterministicMatch(request MatchRequest) (MatchResult, bool) { return result, true } +func exactSizeCandidate(target string, candidates []string) (string, bool) { + key := sizeTokenPattern.FindString(strings.ToLower(Normalize(target))) + if key == "" { + return exactCandidate(target, candidates) + } + matches := make([]string, 0, 1) + seen := map[string]bool{} + for _, candidate := range candidates { + candidate = strings.TrimSpace(candidate) + if candidate == "" || sizeTokenPattern.FindString(strings.ToLower(Normalize(candidate))) != key || seen[candidate] { + continue + } + seen[candidate] = true + matches = append(matches, candidate) + } + if len(matches) == 1 { + return matches[0], true + } + return "", false +} + // ManualMatch records an already-confirmed mapping without reinterpreting it. // The caller owns the human-confirmation check; this helper only creates the // same bounded task snapshot shape used by automatic decisions.