up
This commit is contained in:
+20
-2
@@ -449,8 +449,10 @@ func candidates(d *Def, re *regexp.Regexp, q *qso.QSO, rl refList, hasList bool)
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Whole field value is the candidate.
|
||||
found = []string{normalizeRef(raw)}
|
||||
// Whole field value is the candidate, split on comma/semicolon so a
|
||||
// multi-reference field (e.g. an n-fer POTA QSO "US-6544,US-0680")
|
||||
// counts each reference separately.
|
||||
found = splitRefs(raw)
|
||||
}
|
||||
|
||||
if !predefined {
|
||||
@@ -547,6 +549,22 @@ func stripAffix(s, lead, trail string) string {
|
||||
|
||||
func normalizeRef(s string) string { return strings.ToUpper(strings.TrimSpace(s)) }
|
||||
|
||||
// splitRefs splits a field value on comma/semicolon into normalized references,
|
||||
// so a multi-reference field (n-fer POTA "US-6544,US-0680") yields one entry
|
||||
// per reference. A value with no separator yields a single reference.
|
||||
func splitRefs(raw string) []string {
|
||||
if !strings.ContainsAny(raw, ",;") {
|
||||
return []string{normalizeRef(raw)}
|
||||
}
|
||||
var out []string
|
||||
for _, p := range strings.FieldsFunc(raw, func(r rune) bool { return r == ',' || r == ';' }) {
|
||||
if n := normalizeRef(p); n != "" {
|
||||
out = append(out, n)
|
||||
}
|
||||
}
|
||||
return dedupe(out)
|
||||
}
|
||||
|
||||
func isDigit(b byte) bool { return b >= '0' && b <= '9' }
|
||||
|
||||
// natLess is a natural ("human") comparison: digit runs compare as numbers, so
|
||||
|
||||
@@ -83,6 +83,19 @@ func TestNatLess(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A multi-reference field (n-fer POTA) counts each park separately.
|
||||
func TestComputeMultiRef(t *testing.T) {
|
||||
def := Def{Code: "POTA", Type: TypeReference, Field: "pota_ref", Dynamic: true, Confirm: []string{"lotw", "qsl"}, Valid: true}
|
||||
qsos := []qso.QSO{
|
||||
{Callsign: "W2QMI", Band: "20m", POTARef: "US-6544,US-0680", LOTWRcvd: "Y"},
|
||||
{Callsign: "K1ABC", Band: "40m", POTARef: "US-0680"}, // shared park
|
||||
}
|
||||
r := Compute([]Def{def}, qsos, nil, nil)[0]
|
||||
if r.Worked != 2 { // distinct parks: US-6544, US-0680
|
||||
t.Errorf("POTA worked = %d, want 2 (%v)", r.Worked, refCodes(r))
|
||||
}
|
||||
}
|
||||
|
||||
func refCodes(r Result) []string {
|
||||
out := make([]string, 0, len(r.Refs))
|
||||
for _, rf := range r.Refs {
|
||||
|
||||
@@ -3,6 +3,7 @@ package awardref
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -24,6 +25,38 @@ var Importers = map[string]Importer{
|
||||
"POTA": {AwardCode: "POTA", URL: "https://pota.app/all_parks.csv", Fetch: parsePOTA},
|
||||
"SOTA": {AwardCode: "SOTA", URL: "https://www.sotadata.org.uk/summitslist.csv", Fetch: parseSOTA},
|
||||
"WWFF": {AwardCode: "WWFF", URL: "https://wwff.co/wwff-data/wwff_directory.csv", Fetch: parseWWFF},
|
||||
"IOTA": {AwardCode: "IOTA", URL: "https://www.iota-world.org/islands-on-the-air/downloads/download-file.html?path=groups.json", Fetch: parseIOTA},
|
||||
}
|
||||
|
||||
// parseIOTA reads iota-world.org's groups.json (refreshed daily): an array of
|
||||
// {refno, name, dxcc_num, grp_region}. The reference is the IOTA number
|
||||
// (EU-005); the DXCC number lets the per-QSO picker filter by entity.
|
||||
func parseIOTA(_ context.Context, body io.Reader) ([]Ref, error) {
|
||||
var groups []struct {
|
||||
RefNo string `json:"refno"`
|
||||
Name string `json:"name"`
|
||||
DXCC string `json:"dxcc_num"`
|
||||
Region string `json:"grp_region"`
|
||||
}
|
||||
if err := json.NewDecoder(body).Decode(&groups); err != nil {
|
||||
return nil, fmt.Errorf("parse IOTA json: %w", err)
|
||||
}
|
||||
out := make([]Ref, 0, len(groups))
|
||||
for _, g := range groups {
|
||||
ref := strings.ToUpper(strings.TrimSpace(g.RefNo))
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
dxcc, _ := strconv.Atoi(strings.TrimSpace(g.DXCC))
|
||||
grp := strings.TrimSpace(g.Region)
|
||||
if grp == "" { // fall back to the continent prefix (AF/EU/NA/…)
|
||||
if i := strings.IndexByte(ref, '-'); i > 0 {
|
||||
grp = ref[:i]
|
||||
}
|
||||
}
|
||||
out = append(out, Ref{Code: ref, Name: strings.TrimSpace(g.Name), DXCC: dxcc, Group: grp})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CanUpdate reports whether an award has an online reference list.
|
||||
|
||||
Reference in New Issue
Block a user