fix(lookup): a zone is not a property of the country
Reported with real callsigns: every Asiatic Russia contact logged CQ 17 / ITU 30, whatever the operator's real zone. RU0LL and RA0FF are 19/34, UA0SDX is 18/32, and QRZ.com had all three right. Measured before touching anything: cty.dat answers 17/30 for RU0, RA0, UA0 and UA9 alike — one representative pair for a country eight CQ zones wide — while ClubLog's prefix table gives 19, 19, 18 and 17. The reporter's instinct that no UA0 sits in CQ 17 was exactly right. fillFromDXCC overrode the callbook's zones on purpose, and the reason holds only for the country: QRZ returns the political nation where cty.dat returns the DXCC entity. A zone answers a different question — not what the callsign IS but where the station SITS — and there the per-station page beats a country default. Zones now FILL rather than override; the entity is untouched. The cache made it worse by remembering our conclusion as though the page had said it, so the wrong zones would have outlived this fix. A lookup is now cached as the callbook returned it and the country file is applied on read, which also lets a cty.dat update reach old rows.
This commit is contained in:
+6
-2
@@ -3,10 +3,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."
|
||||
"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.",
|
||||
"CQ and ITU zones now come from the callbook when it has them. cty.dat carries ONE representative pair per entity, and OpsLog was stamping it over QRZ.com’s per-station answer — so every Asiatic Russia contact was logged CQ 17 / ITU 30, whatever the operator’s real zone (RU0LL is 19/34, UA0SDX 18/32). That is a WAZ credit for a zone never worked. The entity still comes from cty.dat, which is the authority on what a callsign IS; a zone says where the station SITS, and only the callbook knows that within a country eight CQ zones wide. Where the callbook is silent, cty.dat fills as before.",
|
||||
"Callsign cache: a lookup is now stored as the callbook returned it, and the country file is applied when it is read. A value OpsLog derived can no longer come back later looking like something the page said — which is how the wrong zones outlived their fix — and a country-file update now reaches rows already cached."
|
||||
],
|
||||
"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 »."
|
||||
"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 ».",
|
||||
"Les zones CQ et ITU proviennent désormais du callbook quand il les connaît. cty.dat ne porte QU’UNE paire représentative par entité, et OpsLog l’imposait par-dessus la réponse par station de QRZ.com — tout contact avec la Russie asiatique était donc enregistré en CQ 17 / ITU 30, quelle que soit la zone réelle (RU0LL est en 19/34, UA0SDX en 18/32). C’est un crédit WAZ pour une zone jamais travaillée. L’entité vient toujours de cty.dat, qui fait autorité sur ce qu’un indicatif EST ; une zone dit où la station SE TROUVE, et seul le callbook le sait dans un pays large de huit zones CQ. Là où le callbook se tait, cty.dat comble comme avant.",
|
||||
"Cache des indicatifs : une recherche est désormais stockée telle que le callbook l’a rendue, le fichier pays étant appliqué à la lecture. Une valeur déduite par OpsLog ne peut plus revenir plus tard avec l’apparence de ce qu’a dit la page — c’est ainsi que les mauvaises zones survivaient à leur correctif — et une mise à jour du fichier pays atteint maintenant les fiches déjà en cache."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -167,9 +167,14 @@ func (m *Manager) Lookup(ctx context.Context, callsign string) (Result, error) {
|
||||
r.Callsign = call
|
||||
r.Source = p.Name()
|
||||
r.FetchedAt = time.Now().UTC()
|
||||
fillFromDXCC(&r, dxcc)
|
||||
normalizeNames(&r)
|
||||
// Cached BEFORE the cty.dat pass, so the row is a copy of the
|
||||
// callbook page rather than of our conclusions about it. Every
|
||||
// read runs the pass again (see the cache-hit path above), so a
|
||||
// later cty.dat update reaches old rows — and a value we derived
|
||||
// can never come back looking like something the page said.
|
||||
_ = m.cache.Put(ctx, r)
|
||||
fillFromDXCC(&r, dxcc)
|
||||
return r, nil
|
||||
}
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
@@ -208,9 +213,9 @@ func (m *Manager) Lookup(ctx context.Context, callsign string) (Result, error) {
|
||||
if !saysNothingAboutLocation(call) {
|
||||
clearHomeLocation(&r)
|
||||
}
|
||||
fillFromDXCC(&r, dxcc) // entity/zones/lat-lon from the FULL (slashed) call
|
||||
normalizeNames(&r)
|
||||
_ = m.cache.Put(ctx, r)
|
||||
_ = m.cache.Put(ctx, r) // the page as it was; cty.dat is applied on read
|
||||
fillFromDXCC(&r, dxcc) // entity/zones/lat-lon from the FULL (slashed) call
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
@@ -441,11 +446,23 @@ func fillFromDXCC(r *Result, dxcc DXCCResolver) bool {
|
||||
r.Continent = cont
|
||||
filled = true
|
||||
}
|
||||
if cqz != 0 {
|
||||
// Zones FILL, they do not override.
|
||||
//
|
||||
// The rule above is right for the country and wrong for the zones, because
|
||||
// they answer different questions. An entity is what a callsign IS, and
|
||||
// cty.dat is the authority on that. A zone is where the station SITS, and a
|
||||
// large entity has many: Asiatic Russia spans CQ 16 to 23 and ITU 20 to 34,
|
||||
// and cty.dat carries one representative pair for the whole country. Stamping
|
||||
// it on every UA0 threw away the callbook's per-station answer and recorded a
|
||||
// WAZ credit for a zone the operator had not worked — RU0LL is CQ 19, ITU 34,
|
||||
// and was logged 17/30.
|
||||
//
|
||||
// So the callbook wins where it spoke, and cty.dat fills the silence.
|
||||
if cqz != 0 && r.CQZ == 0 {
|
||||
r.CQZ = cqz
|
||||
filled = true
|
||||
}
|
||||
if ituz != 0 {
|
||||
if ituz != 0 && r.ITUZ == 0 {
|
||||
r.ITUZ = ituz
|
||||
filled = true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package lookup
|
||||
|
||||
import "testing"
|
||||
|
||||
// asiaticRussia stands in for cty.dat: one representative zone pair for a
|
||||
// country eight CQ zones wide.
|
||||
func asiaticRussia(cqz, ituz int) fakeDXCC {
|
||||
return fakeDXCC{"RU0LL": {num: 15, country: "Asiatic Russia", cont: "AS", cqz: cqz, ituz: ituz}}
|
||||
}
|
||||
|
||||
// A zone is where the station SITS; an entity is what the callsign IS. Asiatic
|
||||
// Russia spans CQ 16-23 and ITU 20-34, and cty.dat carries one representative
|
||||
// pair for the whole country — so stamping it over a callbook's per-station
|
||||
// answer records a WAZ credit for a zone the operator never worked.
|
||||
//
|
||||
// Reported with real callsigns: RU0LL is CQ 19 / ITU 34 and was logged 17/30.
|
||||
func TestCallbookZonesSurviveTheEntityDefault(t *testing.T) {
|
||||
// What QRZ said about this very station.
|
||||
r := Result{Callsign: "RU0LL", CQZ: 19, ITUZ: 34}
|
||||
fillFromDXCC(&r, asiaticRussia(17, 30))
|
||||
if r.CQZ != 19 || r.ITUZ != 34 {
|
||||
t.Errorf("callbook zones were overwritten: CQ%d ITU%d, want CQ19 ITU34", r.CQZ, r.ITUZ)
|
||||
}
|
||||
if r.Country != "Asiatic Russia" {
|
||||
t.Errorf("the ENTITY must still come from cty.dat, got %q", r.Country)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntityZonesFillSilence(t *testing.T) {
|
||||
// A callbook that says nothing about zones still gets an answer.
|
||||
r := Result{Callsign: "RU0LL"}
|
||||
fillFromDXCC(&r, asiaticRussia(17, 30))
|
||||
if r.CQZ != 17 || r.ITUZ != 30 {
|
||||
t.Errorf("empty zones were not filled: CQ%d ITU%d", r.CQZ, r.ITUZ)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user