fix(dxcc): a retired prefix is not a wrong one
Reported from a real import: every ZK2 contact came back New Zealand and Niue vanished from a DXCC that had it confirmed. cty.dat is not wrong, it is CURRENT — Niue moved to E6, so ZK2 reverted to New Zealand there. ZK1 loses the Cook Islands the same way, and the reporter was right to suspect more. ClubLog's prefix table is date-ranged and still knows both, which is the whole reason for enabling its country file. We consulted it only for callsigns that already HAD a per-callsign exception — so a ZK2 with no exception never reached it. It is now asked whenever no exception covers the QSO's date. Two limits keep the blast radius honest. It never overrules an exact '=CALLSIGN' entry in cty.dat — that is somebody having looked at this very callsign, and a prefix rule does not overrule it, which is why Match now says how it matched. And where ClubLog has no answer (E6, TO5A and their like are absent from its prefix table) cty.dat still decides, because silence is not an answer. Measured before changing: on a sample of thirty calls the two files agreed on twenty-eight, and both disagreements were this bug. Opens 0.27.11.
This commit is contained in:
@@ -10249,14 +10249,26 @@ func (a *App) applyClublogException(q *qso.QSO, force bool) bool {
|
||||
}
|
||||
e, ok := a.clublog.Resolve(q.Callsign, date)
|
||||
if !ok {
|
||||
// No exception COVERS this QSO's date. If the call nonetheless HAS a
|
||||
// date-ranged exception (e.g. G1T = Scotland only from 2024-02-21), then
|
||||
// cty.dat's date-blind "=G1T → Scotland" override is WRONG for an older
|
||||
// QSO — resolve it by ClubLog's date-aware PREFIX table instead (G1 →
|
||||
// England for a 2012 contact). Ordinary calls (no exception) are left to
|
||||
// cty.dat.
|
||||
if a.clublog.HasException(q.Callsign) {
|
||||
if pe, pok := a.clublog.ResolvePrefix(q.Callsign, date); pok {
|
||||
// No exception covers this QSO's date, so the question becomes which
|
||||
// country file knows the PREFIX better.
|
||||
//
|
||||
// ClubLog's prefix table is date-ranged and cty.dat's is not, and that
|
||||
// is not a detail: cty.dat describes the world as it is TODAY. Niue
|
||||
// moved to E6, so ZK2 went back to New Zealand there — and every ZK2
|
||||
// contact ever made was silently relabelled New Zealand, taking a
|
||||
// confirmed entity out of an operator's DXCC with it. ZK1 loses the
|
||||
// Cook Islands the same way. ClubLog still knows both.
|
||||
//
|
||||
// It only speaks when it HAS an answer (E6, TO5A and their like are not
|
||||
// in its prefix table at all), and never over an exact "=CALLSIGN" entry
|
||||
// in cty.dat: that is somebody having looked at this very callsign, and
|
||||
// a prefix rule does not overrule it.
|
||||
if pe, pok := a.clublog.ResolvePrefix(q.Callsign, date); pok {
|
||||
ctyExact := false
|
||||
if m, mok := a.dxcc.Lookup(q.Callsign); mok {
|
||||
ctyExact = m.Exact
|
||||
}
|
||||
if !ctyExact {
|
||||
e, ok = pe, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
[
|
||||
{
|
||||
"version": "0.27.11",
|
||||
"date": "",
|
||||
"en": [
|
||||
"Country resolution with the ClubLog file enabled: retired prefixes are recognised again. cty.dat describes the world as it is TODAY — Niue moved to E6, so ZK2 reverted to New Zealand there, and every ZK2 contact ever made was silently relabelled New Zealand, taking a confirmed entity out of the operator’s DXCC with it. ZK1 lost the Cook Islands the same way. ClubLog’s prefix table still knows both and is now consulted whenever no per-callsign exception applies, instead of only for callsigns that happened to have one. It never overrules an exact “=CALLSIGN” entry in cty.dat, and where it has no answer cty.dat still decides. Reprocess an affected import with Update from ClubLog."
|
||||
],
|
||||
"fr": [
|
||||
"Résolution des entités avec le fichier ClubLog activé : les préfixes retirés sont de nouveau reconnus. cty.dat décrit le monde tel qu’il est AUJOURD’HUI — Niue est passée en E6, donc ZK2 y est revenu à la Nouvelle-Zélande, et tous les contacts ZK2 jamais faits étaient silencieusement réétiquetés Nouvelle-Zélande, emportant une entité confirmée hors du DXCC de l’opérateur. ZK1 perdait les Cook du Sud de la même façon. La table de préfixes ClubLog connaît toujours les deux : elle est désormais consultée dès qu’aucune exception par indicatif ne s’applique, et non plus seulement pour les indicatifs qui en avaient une. Elle ne prime jamais sur une entrée exacte « =INDICATIF » de cty.dat, et là où elle n’a pas de réponse c’est cty.dat qui tranche. Repassez un import concerné par « Mettre à jour depuis ClubLog »."
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "0.27.10",
|
||||
"date": "",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hamlog/internal/clublog"
|
||||
"hamlog/internal/dxcc"
|
||||
"hamlog/internal/qso"
|
||||
)
|
||||
|
||||
// Reported from a real import: every ZK2 contact came back as New Zealand and a
|
||||
// confirmed entity — Niue — disappeared from the operator's DXCC.
|
||||
//
|
||||
// cty.dat is not wrong, it is CURRENT: Niue moved to E6, so ZK2 reverted to New
|
||||
// Zealand there. ClubLog still knows ZK2 was Niue, and knowing that is the whole
|
||||
// reason for enabling its country file.
|
||||
func TestClublogPrefixRescuesRetiredPrefixes(t *testing.T) {
|
||||
dir := filepath.Join("build", "bin", "data")
|
||||
dm := dxcc.NewManager(dir)
|
||||
if err := dm.LoadFromDisk(); err != nil {
|
||||
t.Skipf("cty.dat not available here: %v", err)
|
||||
}
|
||||
cm := clublog.NewManager("", dir)
|
||||
if err := cm.EnsureLoaded(); err != nil {
|
||||
t.Skipf("ClubLog country file not available here: %v", err)
|
||||
}
|
||||
a := &App{ctx: context.Background(), dxcc: dm, clublog: cm}
|
||||
|
||||
when := time.Date(2005, 6, 1, 0, 0, 0, 0, time.UTC)
|
||||
cases := []struct {
|
||||
call string
|
||||
want int // ADIF entity
|
||||
why string
|
||||
}{
|
||||
{"ZK2KK", 188, "Niue — the reported case"},
|
||||
{"ZK1XYZ", 234, "South Cook Islands, lost the same way"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
q := qso.QSO{Callsign: c.call, QSODate: when}
|
||||
if !a.applyClublogException(&q, true) {
|
||||
t.Errorf("%s: ClubLog changed nothing (%s)", c.call, c.why)
|
||||
continue
|
||||
}
|
||||
if q.DXCC == nil || *q.DXCC != c.want {
|
||||
got := 0
|
||||
if q.DXCC != nil {
|
||||
got = *q.DXCC
|
||||
}
|
||||
t.Errorf("%s resolved to %d (%s), want %d — %s", c.call, got, q.Country, c.want, c.why)
|
||||
}
|
||||
}
|
||||
|
||||
// A callsign ClubLog's prefix table does not know must be left to cty.dat
|
||||
// rather than blanked: silence is not an answer.
|
||||
q := qso.QSO{Callsign: "E6AG", QSODate: when}
|
||||
if a.applyClublogException(&q, true) && q.DXCC != nil && *q.DXCC != 188 {
|
||||
t.Errorf("E6AG was moved off Niue, to %d", *q.DXCC)
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -40,6 +40,14 @@ type Match struct {
|
||||
Continent string `json:"continent"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lon float64 `json:"lon"`
|
||||
// Exact marks a hit on cty.dat's "=CALLSIGN" list rather than on a prefix.
|
||||
//
|
||||
// The two carry very different authority. A prefix match is a rule of thumb
|
||||
// about a block of callsigns; an exact entry is somebody having looked at
|
||||
// THIS callsign and written down where it was. A second country file may
|
||||
// improve on the first kind and should not be allowed to overrule the
|
||||
// second.
|
||||
Exact bool `json:"exact,omitempty"`
|
||||
}
|
||||
|
||||
type prefixEntry struct {
|
||||
@@ -149,7 +157,9 @@ func (db *DB) Lookup(callsign string) (Match, bool) {
|
||||
return Match{}, false
|
||||
}
|
||||
if e, ok := db.exact[call]; ok {
|
||||
return materialize(e), true
|
||||
m := materialize(e)
|
||||
m.Exact = true
|
||||
return m, true
|
||||
}
|
||||
// KG4 special case: Guantanamo Bay (DXCC 105) is "KG4" followed by EXACTLY
|
||||
// two characters (KG4XX). "KG4", "KG4X", "KG4XYZ"… are continental USA.
|
||||
|
||||
Reference in New Issue
Block a user