fix: Improved awards matches

This commit is contained in:
2026-07-10 15:28:01 +02:00
parent 6f2f9236b0
commit 96390110f8
7 changed files with 316 additions and 50 deletions
+75
View File
@@ -128,6 +128,81 @@ func TestComputeMatchByDescription(t *testing.T) {
}
}
// OR rules are ordered fallbacks: the primary match wins and short-circuits the
// rules; when the primary finds nothing, the first OR rule that hits wins and
// later rules are skipped. DDFM-style: a French department from the NOTE first,
// else captured from a postal code in the address (Prefix "D" → "D74").
func TestComputeOrRuleFallback(t *testing.T) {
def := Def{Code: "DDFM", Type: TypeQSOFields, Field: "notes", Pattern: `\b(D\d{2})\b`,
DXCCFilter: []int{227}, Valid: true,
OrRules: []OrRule{
{Field: "address", MatchBy: "pattern", Pattern: `\b(\d{2})\d{3}\b`, Prefix: "D"},
},
}
refMetas := map[string][]RefMeta{"DDFM": {
{Code: "D74", Name: "Haute-Savoie", Valid: true},
{Code: "D75", Name: "Paris", Valid: true},
}}
worked := func(q qso.QSO) []string {
r := Compute([]Def{def}, []qso.QSO{q}, refMetas, nil)[0]
var out []string
for _, rf := range r.Refs {
if rf.Worked {
out = append(out, rf.Ref)
}
}
return out
}
// Primary (note "D74") matches → the postal OR rule is skipped, so the
// address's 75001 does NOT also add D75. Exactly the user's ask: first
// condition wins, no fall-through.
if got := worked(qso.QSO{Callsign: "F4ABC", DXCC: ip(227), Notes: "worked D74", Address: "75001 Paris"}); len(got) != 1 || got[0] != "D74" {
t.Errorf("primary wins: got %v, want [D74] (no postal fall-through to D75)", got)
}
// No note dept → fall through to the OR rule → postal 74140 → D74.
if got := worked(qso.QSO{Callsign: "F4ABC", DXCC: ip(227), Notes: "", Address: "74140 Annecy"}); len(got) != 1 || got[0] != "D74" {
t.Errorf("OR fallback: got %v, want [D74]", got)
}
}
// A manually-assigned override (ManualRefsKey) must count in Compute — not just
// MatchQSO — so a custom award like WAPC (matches ADDRESS by description, writes
// no QSO field) sticks after the operator picks the province by hand. The engine
// only auto-detects "Jiangsu" when the address spells it out; here it doesn't, so
// the ref exists solely as the override.
func TestComputeManualOverride(t *testing.T) {
def := Def{Code: "WAPC", Type: TypeQSOFields, Field: "address", MatchBy: "description",
DXCCFilter: []int{318}, Confirm: []string{"lotw", "qsl"}, Valid: true}
qsos := []qso.QSO{
// Address doesn't name the province → only the override tags it.
{Callsign: "BA1ABC", Band: "20m", DXCC: ip(318), Address: "Beijing Rd 5",
Extras: map[string]string{ManualRefsKey: "WAPC@JS"}},
}
refMetas := map[string][]RefMeta{"WAPC": {
{Code: "JS", Name: "Jiangsu", Valid: true},
{Code: "GD", Name: "Guangdong", Valid: true},
}}
r := Compute([]Def{def}, qsos, refMetas, nil)[0]
if r.Worked != 1 {
t.Fatalf("WAPC worked = %d, want 1 (Jiangsu via manual override); got %v", r.Worked, refCodes(r))
}
var worked []string
for _, rf := range r.Refs {
if rf.Worked {
worked = append(worked, rf.Ref)
}
}
if len(worked) != 1 || worked[0] != "JS" {
t.Errorf("worked refs = %v, want [JS]", worked)
}
// An override for a ref NOT in the predefined list is rejected (no typo refs).
qsos[0].Extras[ManualRefsKey] = "WAPC@ZZ"
if w := Compute([]Def{def}, qsos, refMetas, nil)[0].Worked; w != 0 {
t.Errorf("bogus override ZZ: worked = %d, want 0", w)
}
}
// A description-match award must also honour a REFERENCE's own regex (used to
// broaden matching past the plain name) AND match case-insensitively — a log
// QTH "ITABASHIKU, TOKIO" (uppercase, "Tokio" spelling) must count for Tokyo