up
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -96,6 +96,38 @@ func TestComputeMultiRef(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// WAJA-style award: MatchBy="description", non-exact, scanning the QTH for a
|
||||
// reference's NAME (the prefecture). Also guards against the nil-slice crash:
|
||||
// an award with nothing worked must return empty (non-nil) Refs/Bands.
|
||||
func TestComputeMatchByDescription(t *testing.T) {
|
||||
def := Def{Code: "WAJA", Type: TypeQSOFields, Field: "qth", MatchBy: "description",
|
||||
DXCCFilter: []int{339}, Confirm: []string{"lotw", "qsl"}, Valid: true}
|
||||
qsos := []qso.QSO{
|
||||
{Callsign: "JA1ABC", Band: "20m", DXCC: ip(339), QTH: "Tokyo city", LOTWRcvd: "Y"},
|
||||
{Callsign: "JA3DEF", Band: "40m", DXCC: ip(339), QTH: "Osaka"},
|
||||
{Callsign: "JA9XYZ", Band: "20m", DXCC: ip(339), QTH: "nowhere special"}, // no prefecture name
|
||||
}
|
||||
refMetas := map[string][]RefMeta{"WAJA": {
|
||||
{Code: "100", Name: "Tokyo", Valid: true},
|
||||
{Code: "270", Name: "Osaka", Valid: true},
|
||||
{Code: "010", Name: "Hokkaido", Valid: true},
|
||||
}}
|
||||
r := Compute([]Def{def}, qsos, refMetas, nil)[0]
|
||||
if r.Worked != 2 { // Tokyo + Osaka found by name inside QTH
|
||||
t.Errorf("WAJA worked = %d, want 2 (%v)", r.Worked, refCodes(r))
|
||||
}
|
||||
if r.Total != 3 { // predefined denominator = list size
|
||||
t.Errorf("WAJA total = %d, want 3", r.Total)
|
||||
}
|
||||
|
||||
// Nil-slice guard: an award with zero worked refs must still return
|
||||
// non-nil (empty) Refs/Bands so the JSON isn't null (UI white-screen).
|
||||
empty := Compute([]Def{{Code: "WWFF", Type: TypeReference, Field: "wwff", Dynamic: true, Valid: true}}, nil, nil, nil)[0]
|
||||
if empty.Refs == nil || empty.Bands == nil {
|
||||
t.Errorf("empty award must have non-nil Refs/Bands, got Refs=%v Bands=%v", empty.Refs, empty.Bands)
|
||||
}
|
||||
}
|
||||
|
||||
func refCodes(r Result) []string {
|
||||
out := make([]string, 0, len(r.Refs))
|
||||
for _, rf := range r.Refs {
|
||||
|
||||
Reference in New Issue
Block a user