Deploy to K8s / deploy (push) Successful in 20s
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>
60 lines
1.7 KiB
Go
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
|
|
}
|