fix: bug correction in awards where a reference regex was not checked
This commit is contained in:
@@ -6821,12 +6821,14 @@ func (a *App) runDownloadConfirmations(svc extsvc.Service, cfg extsvc.ExternalSe
|
|||||||
} else {
|
} else {
|
||||||
emit(fmt.Sprintf("Downloading all LoTW confirmations for %s…", callLabel))
|
emit(fmt.Sprintf("Downloading all LoTW confirmations for %s…", callLabel))
|
||||||
}
|
}
|
||||||
|
emit(fmt.Sprintf("Window: since=%q → resolved=%q (scope owncall=%q)", since, sinceDate, ownCall))
|
||||||
adifText, err := extsvc.DownloadLoTWConfirmations(ctx, nil, cfg.LoTW, sinceDate, ownCall)
|
adifText, err := extsvc.DownloadLoTWConfirmations(ctx, nil, cfg.LoTW, sinceDate, ownCall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
emit("Download failed: " + err.Error())
|
emit("Download failed: " + err.Error())
|
||||||
done(matched, total)
|
done(matched, total)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
emit(fmt.Sprintf("LoTW returned %d KB of ADIF", len(adifText)/1024))
|
||||||
keyIDs, kerr := a.qso.DedupeKeyIDs(ctx)
|
keyIDs, kerr := a.qso.DedupeKeyIDs(ctx)
|
||||||
if kerr != nil {
|
if kerr != nil {
|
||||||
emit("Error reading local log: " + kerr.Error())
|
emit("Error reading local log: " + kerr.Error())
|
||||||
|
|||||||
+28
-4
@@ -13,6 +13,7 @@
|
|||||||
package award
|
package award
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -257,6 +258,21 @@ type RefMeta struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRefList builds the engine's reference view from (code, meta) pairs.
|
// NewRefList builds the engine's reference view from (code, meta) pairs.
|
||||||
|
// compileAwardRE compiles an award / reference regex. Award patterns are run
|
||||||
|
// over free-text log fields (QTH, NOTES) whose capitalization is arbitrary — a
|
||||||
|
// log may hold "Tokyo", "TOKYO" or "TOKIO" — so we match case-insensitively by
|
||||||
|
// default. An author who set their own flag group (e.g. "(?s)") is respected.
|
||||||
|
func compileAwardRE(p string) (*regexp.Regexp, error) {
|
||||||
|
p = strings.TrimSpace(p)
|
||||||
|
if p == "" {
|
||||||
|
return nil, errors.New("empty pattern")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(p, "(?") {
|
||||||
|
p = "(?i)" + p
|
||||||
|
}
|
||||||
|
return regexp.Compile(p)
|
||||||
|
}
|
||||||
|
|
||||||
func NewRefList(metas []RefMeta) refList {
|
func NewRefList(metas []RefMeta) refList {
|
||||||
rl := refList{byCode: make(map[string]RefMeta, len(metas))}
|
rl := refList{byCode: make(map[string]RefMeta, len(metas))}
|
||||||
for _, m := range metas {
|
for _, m := range metas {
|
||||||
@@ -265,7 +281,7 @@ func NewRefList(metas []RefMeta) refList {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if p := strings.TrimSpace(m.Pattern); p != "" {
|
if p := strings.TrimSpace(m.Pattern); p != "" {
|
||||||
if re, err := regexp.Compile(p); err == nil {
|
if re, err := compileAwardRE(p); err == nil {
|
||||||
m.re = re
|
m.re = re
|
||||||
rl.withPattern = append(rl.withPattern, code)
|
rl.withPattern = append(rl.withPattern, code)
|
||||||
}
|
}
|
||||||
@@ -297,7 +313,7 @@ func Compute(defs []Def, qsos []qso.QSO, refMetas map[string][]RefMeta, nameOf N
|
|||||||
perr := make([]string, len(defs))
|
perr := make([]string, len(defs))
|
||||||
for i := range defs {
|
for i := range defs {
|
||||||
if p := strings.TrimSpace(defs[i].Pattern); p != "" {
|
if p := strings.TrimSpace(defs[i].Pattern); p != "" {
|
||||||
re, err := regexp.Compile(p)
|
re, err := compileAwardRE(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
perr[i] = "bad pattern: " + err.Error()
|
perr[i] = "bad pattern: " + err.Error()
|
||||||
} else {
|
} else {
|
||||||
@@ -432,7 +448,7 @@ func MatchQSO(d Def, metas []RefMeta, q *qso.QSO) []string {
|
|||||||
}
|
}
|
||||||
var re *regexp.Regexp
|
var re *regexp.Regexp
|
||||||
if p := strings.TrimSpace(d.Pattern); p != "" {
|
if p := strings.TrimSpace(d.Pattern); p != "" {
|
||||||
if c, err := regexp.Compile(p); err == nil {
|
if c, err := compileAwardRE(p); err == nil {
|
||||||
re = c
|
re = c
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
@@ -565,6 +581,14 @@ func searchOne(field, matchBy string, re *regexp.Regexp, exact bool, leading, tr
|
|||||||
found = append(found, nc.code)
|
found = append(found, nc.code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A reference may also declare its own regex to broaden matching beyond
|
||||||
|
// its plain name (e.g. Tokyo's `\bTok[iy]o\b` also catches "TOKIO"). Test
|
||||||
|
// those too — the name match and the pattern are OR'd.
|
||||||
|
for _, code := range rl.withPattern {
|
||||||
|
if m := rl.byCode[code]; m.re != nil && m.re.MatchString(raw) {
|
||||||
|
found = append(found, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
case predefined && !exact:
|
case predefined && !exact:
|
||||||
// "Search reference inside the field": look up each token of the field in
|
// "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
|
// the list — O(tokens), not O(all references) — plus test the few
|
||||||
@@ -602,7 +626,7 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
|||||||
r := &d.OrRules[i]
|
r := &d.OrRules[i]
|
||||||
var rre *regexp.Regexp
|
var rre *regexp.Regexp
|
||||||
if p := strings.TrimSpace(r.Pattern); p != "" {
|
if p := strings.TrimSpace(r.Pattern); p != "" {
|
||||||
c, err := regexp.Compile(p)
|
c, err := compileAwardRE(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // skip a rule with a bad regex rather than failing the award
|
continue // skip a rule with a bad regex rather than failing the award
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,6 +128,28 @@ func TestComputeMatchByDescription(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// via its `\bTok[iy]o\b` pattern, which the plain name "Tokyo" can't catch.
|
||||||
|
func TestComputeDescriptionRefPattern(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: "JA1LZB", Band: "10m", DXCC: ip(339), QTH: "ITABASHIKU, TOKIO"}, // pattern + case
|
||||||
|
{Callsign: "JA3DEF", Band: "40m", DXCC: ip(339), QTH: "osaka pref"}, // lowercase name
|
||||||
|
}
|
||||||
|
refMetas := map[string][]RefMeta{"WAJA": {
|
||||||
|
{Code: "13", Name: "Tokyo", Pattern: `\bTok[iy]o\b`, Valid: true},
|
||||||
|
{Code: "27", Name: "Osaka", Valid: true},
|
||||||
|
{Code: "01", Name: "Hokkaido", Valid: true},
|
||||||
|
}}
|
||||||
|
r := Compute([]Def{def}, qsos, refMetas, nil)[0]
|
||||||
|
if r.Worked != 2 {
|
||||||
|
t.Errorf("WAJA worked = %d, want 2 (Tokyo via pattern + Osaka via lowercase name); got %v", r.Worked, refCodes(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VUCC: a grid4 award counts distinct 4-char grid squares, and a QSO on a grid
|
// VUCC: a grid4 award counts distinct 4-char grid squares, and a QSO on a grid
|
||||||
// line (VUCC_GRIDS) contributes several. grid4 derives from VUCC_GRIDS else the
|
// line (VUCC_GRIDS) contributes several. grid4 derives from VUCC_GRIDS else the
|
||||||
// 4-char prefix of GRIDSQUARE.
|
// 4-char prefix of GRIDSQUARE.
|
||||||
|
|||||||
Reference in New Issue
Block a user