Files
fuj-management/go/internal/domain/matching/name_variants.go
kacerrandClaude Opus 5.5 022facc957
Deploy to K8s / deploy (push) Successful in 20s
refactor: move Python implementation to legacy/, make Go primary
Go (go/) becomes the primary implementation. The original Python
version moves to legacy/ and is kept runnable; new features are still
built in both (see the Dual implementation rule in CLAUDE.md).

- git mv app.py, scripts/, templates/, tests/, pyproject.toml, uv.lock
  and build/ into legacy/
- Makefile: Python targets point at legacy/, venv in legacy/.venv,
  CREDENTIALS_PATH exported for the root .secret/ file
- CI and make image build the Python image with legacy/ as context;
  the Makefile is no longer copied into the image
- legacy/.dockerignore moved to the context root so it takes effect
- CLAUDE.md, docs/README.md, Go comments, fixture README, capture
  script and VS Code launch config updated to the new layout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:33:57 +02:00

60 lines
1.7 KiB
Go

package matching
import (
"fuj-management/go/internal/domain/czech"
"regexp"
"strings"
)
var (
nicknameRe = regexp.MustCompile(`\(([^)]+)\)`)
nicknameStripRe = regexp.MustCompile(`\s*\([^)]*\)\s*`)
)
// BuildNameVariants returns searchable lowercase ASCII variants of a member name.
//
// Example: "František Vrbík (Štrúdl)" → ["frantisek vrbik", "strudl", "vrbik", "frantisek"]
//
// variants[0] is always the full normalized base name (no nickname). MatchMembers relies on
// this invariant for the exact short-circuit pass. Variants shorter than 3 characters are
// dropped.
//
// Ports legacy/scripts/match_payments.py _build_name_variants.
func BuildNameVariants(name string) []string {
var nickname string
if m := nicknameRe.FindStringSubmatch(name); m != nil {
nickname = m[1]
}
base := strings.TrimSpace(nicknameStripRe.ReplaceAllString(name, " "))
normalizedBase := czech.Normalize(base)
normalizedNick := czech.Normalize(nickname)
variants := []string{normalizedBase}
if normalizedNick != "" {
variants = append(variants, normalizedNick)
}
parts := strings.Fields(normalizedBase)
if len(parts) >= 2 {
variants = append(variants, parts[len(parts)-1]) // last name
variants = append(variants, parts[0]) // first name
}
filtered := variants[:0]
for _, v := range variants {
if len(v) >= 3 {
filtered = append(filtered, v)
}
}
return filtered
}
// wordIn returns true if needle appears as a whole word in haystack.
// Both needle and haystack must already be ASCII-folded (via czech.Normalize).
func wordIn(needle, haystack string) bool {
pattern := `\b` + regexp.QuoteMeta(needle) + `\b`
matched, _ := regexp.MatchString(pattern, haystack)
return matched
}