This commit is contained in:
2026-06-06 14:16:30 +02:00
parent f91f9ff3b8
commit 17f7a00bd7
19 changed files with 1278 additions and 91 deletions
+32
View File
@@ -210,8 +210,13 @@ type refList struct {
byCode map[string]RefMeta // uppercased code → metadata
codes []string // codes in input order (for stable unworked listing)
withPattern []string // codes whose reference declares a regex (usually none)
names []nameCode // (uppercased name → code) for MatchBy="description"
}
// nameCode pairs a reference's uppercased description with its code, for
// description-based matching (e.g. WAJA finding a prefecture NAME in the QTH).
type nameCode struct{ name, code string }
// RefMeta is one reference's metadata for the engine: enough to enforce a
// predefined list, per-reference DXCC scoping, a per-reference pattern, and to
// label results.
@@ -243,6 +248,9 @@ func NewRefList(metas []RefMeta) refList {
m.Code = code
if _, dup := rl.byCode[code]; !dup {
rl.codes = append(rl.codes, code)
if nm := strings.ToUpper(strings.TrimSpace(m.Name)); nm != "" {
rl.names = append(rl.names, nameCode{name: nm, code: code})
}
}
rl.byCode[code] = m
}
@@ -376,6 +384,15 @@ func Compute(defs []Def, qsos []qso.QSO, refMetas map[string][]RefMeta, nameOf N
for _, b := range sortedBands(bandWorked) {
r.Bands = append(r.Bands, BandCount{Band: b, Worked: bandWorked[b], Confirmed: bandConfirmed[b]})
}
// Never return nil slices: they marshal to JSON null, and the UI calls
// .filter/.length on them (an award with nothing worked yet — e.g. a
// freshly-created WWFF/WAJA — would otherwise white-screen the panel).
if r.Refs == nil {
r.Refs = []Ref{}
}
if r.Bands == nil {
r.Bands = []BandCount{}
}
out[i] = r
}
return out
@@ -428,12 +445,27 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
return nil
}
predefined := hasList && !d.Dynamic
byDesc := predefined && strings.EqualFold(strings.TrimSpace(d.MatchBy), "description")
var found []string
switch {
case re != nil:
// Award-level regex: capture group 1 (or whole match) for each hit.
found = regexTokens(re, raw)
case byDesc:
// Match references by their DESCRIPTION/name appearing in the field
// (e.g. WAJA finds the prefecture name inside the QTH). ExactMatch means
// the field equals the name; otherwise the name is a substring of it.
up := strings.ToUpper(raw)
for _, nc := range rl.names {
if d.ExactMatch {
if up == nc.name {
found = append(found, nc.code)
}
} else if strings.Contains(up, nc.name) {
found = append(found, nc.code)
}
}
case predefined && !d.ExactMatch:
// "Search reference inside the field": look up each token of the field in
// the list — O(tokens), not O(all references) — plus test the few