fix(lookup): /QRP is about power, not about place
QRZ has no record under M0BFS/QRP, so the lookup falls back to the home call — that part worked. What followed did not: the home record's location is then discarded, on the reasoning that a portable operator is not at their registered address. True for /P and /M, and simply wrong for /QRP, which says something about the transmitter and nothing about where it is. So the grid came back empty for M0BFS/QRP while the same call without the suffix answered perfectly, which is exactly how it was reported. The distinction is now explicit rather than lumped in with the other operational suffixes. /P and /M keep clearing the location, because mobile and portable both mean somewhere other than home — that behaviour was correct and is untouched. A power suffix stacked on a portable one (F4BPO/P/QRP) still clears: one of them moved the operator.
This commit is contained in:
+4
-2
@@ -7,14 +7,16 @@
|
|||||||
"Web publishing: the page now widens to fit the table. It was capped at a comfortable reading width, so with more than about eight columns the rest sat behind a scrollbar on a screen wide enough to show them all — and Windows hides that scrollbar until something moves, which made a table that scrolls look like a table missing columns. Columns also take the width their contents need instead of being squeezed to fit first.",
|
"Web publishing: the page now widens to fit the table. It was capped at a comfortable reading width, so with more than about eight columns the rest sat behind a scrollbar on a screen wide enough to show them all — and Windows hides that scrollbar until something moves, which made a table that scrolls look like a table missing columns. Columns also take the width their contents need instead of being squeezed to fit first.",
|
||||||
"Every QSO now carries its distance. Nothing ever recorded one, so the field went out empty in every ADIF export and left the Distance column blank on a published page. It is computed from the two locators when a contact is logged and when an ADIF is imported, and a one-time pass fills in the QSOs already in your log the first time this version runs — in the background, without asking. A distance the imported file supplied is always kept.",
|
"Every QSO now carries its distance. Nothing ever recorded one, so the field went out empty in every ADIF export and left the Distance column blank on a published page. It is computed from the two locators when a contact is logged and when an ADIF is imported, and a one-time pass fills in the QSOs already in your log the first time this version runs — in the background, without asking. A distance the imported file supplied is always kept.",
|
||||||
"RDA: 1015 districts were filed under the wrong DXCC entity. Every reference sat on European Russia; 991 belong to Asiatic Russia and the 24 KA- districts to Kaliningrad, which is a separate entity altogether. Corrected against the reference list, and Kaliningrad added to the award filter so those 24 can be claimed at all.",
|
"RDA: 1015 districts were filed under the wrong DXCC entity. Every reference sat on European Russia; 991 belong to Asiatic Russia and the 24 KA- districts to Kaliningrad, which is a separate entity altogether. Corrected against the reference list, and Kaliningrad added to the award filter so those 24 can be claimed at all.",
|
||||||
"QSO filter: asking a field to equal nothing now finds the empty ones. SQL answers that question with nothing at all — a missing value never equals an empty one — so the filter looked broken rather than wrong. Empty on a numeric field also covers zero as well as missing, which SQLite and MySQL disagreed about."
|
"QSO filter: asking a field to equal nothing now finds the empty ones. SQL answers that question with nothing at all — a missing value never equals an empty one — so the filter looked broken rather than wrong. Empty on a numeric field also covers zero as well as missing, which SQLite and MySQL disagreed about.",
|
||||||
|
"Callsign lookup: a /QRP call now returns its locator. The lookup falls back to the home callsign when the slashed form is not registered, and then cleared the location — right for /P and /M, where the operator is somewhere other than their registered address, and wrong for /QRP, which says something about power and nothing about place. The grid came back empty while the same call without the suffix answered perfectly."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"L édition groupée sait enfin régler le mode, le sous-mode et le RST. Ils étaient exclus comme champs propres à chaque QSO, ce qui manquait l essentiel : l édition groupée sert à RÉPARER un lot — un import qui a tout mis en SSB, un ADIF sans aucun mode — et refuser obligeait à corriger cent lignes une par une. Régler le mode efface le sous-mode, celui de l ancien mode contredisant le nouveau. La bande reste avec la fréquence, qui pose déjà les deux ensemble.",
|
"L édition groupée sait enfin régler le mode, le sous-mode et le RST. Ils étaient exclus comme champs propres à chaque QSO, ce qui manquait l essentiel : l édition groupée sert à RÉPARER un lot — un import qui a tout mis en SSB, un ADIF sans aucun mode — et refuser obligeait à corriger cent lignes une par une. Régler le mode efface le sous-mode, celui de l ancien mode contredisant le nouveau. La bande reste avec la fréquence, qui pose déjà les deux ensemble.",
|
||||||
"Publication web : la page s élargit désormais à la taille du tableau. Elle était bridée à une largeur de lecture confortable, donc au-delà de huit colonnes environ le reste passait derrière une barre de défilement sur un écran assez large pour tout montrer — et Windows masque cette barre tant que rien ne bouge, si bien qu un tableau qui défile ressemblait à un tableau amputé. Les colonnes prennent aussi la largeur qu il leur faut au lieu d être comprimées d abord.",
|
"Publication web : la page s élargit désormais à la taille du tableau. Elle était bridée à une largeur de lecture confortable, donc au-delà de huit colonnes environ le reste passait derrière une barre de défilement sur un écran assez large pour tout montrer — et Windows masque cette barre tant que rien ne bouge, si bien qu un tableau qui défile ressemblait à un tableau amputé. Les colonnes prennent aussi la largeur qu il leur faut au lieu d être comprimées d abord.",
|
||||||
"Chaque QSO porte désormais sa distance. Rien ne l enregistrait, elle partait donc vide dans chaque export ADIF et laissait la colonne Distance blanche sur une page publiée. Elle est calculée depuis les deux locators à l enregistrement d un contact et à l import d un ADIF, et une passe unique complète les QSO déjà présents au premier lancement de cette version — en tâche de fond, sans rien demander. Une distance fournie par le fichier importé est toujours conservée.",
|
"Chaque QSO porte désormais sa distance. Rien ne l enregistrait, elle partait donc vide dans chaque export ADIF et laissait la colonne Distance blanche sur une page publiée. Elle est calculée depuis les deux locators à l enregistrement d un contact et à l import d un ADIF, et une passe unique complète les QSO déjà présents au premier lancement de cette version — en tâche de fond, sans rien demander. Une distance fournie par le fichier importé est toujours conservée.",
|
||||||
"RDA : 1015 districts étaient rangés sous la mauvaise entité DXCC. Toutes les références étaient sur la Russie européenne ; 991 relèvent de la Russie asiatique et les 24 districts KA- de Kaliningrad, qui est une entité à part entière. Corrigé d après la liste de référence, et Kaliningrad ajouté au filtre de l award pour que ces 24 puissent être revendiqués.",
|
"RDA : 1015 districts étaient rangés sous la mauvaise entité DXCC. Toutes les références étaient sur la Russie européenne ; 991 relèvent de la Russie asiatique et les 24 districts KA- de Kaliningrad, qui est une entité à part entière. Corrigé d après la liste de référence, et Kaliningrad ajouté au filtre de l award pour que ces 24 puissent être revendiqués.",
|
||||||
"Filtre QSO : demander à un champ d être égal à rien trouve désormais les vides. SQL répond à cette question par rien du tout — une valeur absente n est jamais égale à une valeur vide — et le filtre paraissait cassé plutôt que mal posé. Vide sur un champ numérique couvre aussi le zéro autant que l absence, ce sur quoi SQLite et MySQL n étaient pas d accord."
|
"Filtre QSO : demander à un champ d être égal à rien trouve désormais les vides. SQL répond à cette question par rien du tout — une valeur absente n est jamais égale à une valeur vide — et le filtre paraissait cassé plutôt que mal posé. Vide sur un champ numérique couvre aussi le zéro autant que l absence, ce sur quoi SQLite et MySQL n étaient pas d accord.",
|
||||||
|
"Recherche d indicatif : un indicatif en /QRP rend enfin son locator. La recherche se rabat sur l indicatif de base quand la forme avec barre n est pas enregistrée, puis effaçait la localisation — ce qui est juste pour /P et /M, où l opérateur n est pas à son adresse déclarée, et faux pour /QRP, qui parle de puissance et pas de lieu. Le locator revenait vide alors que le même indicatif sans le suffixe répondait parfaitement."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -176,12 +176,20 @@ func (m *Manager) Lookup(ctx context.Context, callsign string) (Result, error) {
|
|||||||
r.Callsign = call
|
r.Callsign = call
|
||||||
r.Source = p.Name()
|
r.Source = p.Name()
|
||||||
r.FetchedAt = time.Now().UTC()
|
r.FetchedAt = time.Now().UTC()
|
||||||
// The home record's location is the operator's HOME, not where they
|
// The home record's location is the operator's HOME — clear it so
|
||||||
// are portable now — clear it so cty.dat fills the real entity.
|
// cty.dat fills in where they actually are.
|
||||||
|
//
|
||||||
|
// UNLESS the suffix says nothing about location. /QRP is a statement
|
||||||
|
// about power, not about place: M0BFS/QRP is M0BFS, at home, running
|
||||||
|
// five watts. Wiping the grid there threw away the one field the
|
||||||
|
// operator was looking the call up for, and it came back empty while
|
||||||
|
// the same lookup without the suffix answered perfectly.
|
||||||
|
if !saysNothingAboutLocation(call) {
|
||||||
r.Country, r.Continent = "", ""
|
r.Country, r.Continent = "", ""
|
||||||
r.CQZ, r.ITUZ, r.DXCC = 0, 0, 0
|
r.CQZ, r.ITUZ, r.DXCC = 0, 0, 0
|
||||||
r.Lat, r.Lon = 0, 0
|
r.Lat, r.Lon = 0, 0
|
||||||
r.Grid, r.State, r.County = "", "", ""
|
r.Grid, r.State, r.County = "", "", ""
|
||||||
|
}
|
||||||
fillFromDXCC(&r, dxcc) // entity/zones/lat-lon from the FULL (slashed) call
|
fillFromDXCC(&r, dxcc) // entity/zones/lat-lon from the FULL (slashed) call
|
||||||
normalizeNames(&r)
|
normalizeNames(&r)
|
||||||
_ = m.cache.Put(ctx, r)
|
_ = m.cache.Put(ctx, r)
|
||||||
@@ -232,6 +240,33 @@ var LogSink = func(string, ...any) {}
|
|||||||
// right and must be looked up exactly as entered.
|
// right and must be looked up exactly as entered.
|
||||||
var opSuffixes = map[string]bool{"M": true, "MM": true, "AM": true, "P": true, "QRP": true}
|
var opSuffixes = map[string]bool{"M": true, "MM": true, "AM": true, "P": true, "QRP": true}
|
||||||
|
|
||||||
|
// nonLocationSuffixes say nothing about WHERE the operator is.
|
||||||
|
//
|
||||||
|
// /QRP is a statement about power. /M and /P and their kin are not: mobile and
|
||||||
|
// portable both mean "somewhere other than the home station", which is exactly
|
||||||
|
// why the home record's location is discarded for them. Keeping that distinction
|
||||||
|
// is the difference between a grid that is stale and a grid that is absent.
|
||||||
|
var nonLocationSuffixes = map[string]bool{"QRP": true}
|
||||||
|
|
||||||
|
// saysNothingAboutLocation reports a call whose every suffix leaves the operator
|
||||||
|
// at their registered address — so the home record's location can be trusted.
|
||||||
|
func saysNothingAboutLocation(call string) bool {
|
||||||
|
parts := strings.Split(strings.ToUpper(strings.TrimSpace(call)), "/")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
base := strings.TrimSpace(parts[0])
|
||||||
|
if len(base) < 3 || !strings.ContainsAny(base, "0123456789") {
|
||||||
|
return false // "JW/OR1A": the first part is a prefix — a location change
|
||||||
|
}
|
||||||
|
for _, p := range parts[1:] {
|
||||||
|
if !nonLocationSuffixes[strings.TrimSpace(p)] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// stripOpSuffix returns the bare callsign when call carries nothing but
|
// stripOpSuffix returns the bare callsign when call carries nothing but
|
||||||
// operational suffixes ("F4LYI/M" → "F4LYI", true). Reports false for anything
|
// operational suffixes ("F4LYI/M" → "F4LYI", true). Reports false for anything
|
||||||
// that changes entity or area ("JW/OR1A", "F4BPO/8"), and for a call whose base
|
// that changes entity or area ("JW/OR1A", "F4BPO/8"), and for a call whose base
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package lookup
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// A grid came back empty for M0BFS/QRP while the same lookup without the suffix
|
||||||
|
// answered perfectly. The home-call pass wiped the location on the grounds that
|
||||||
|
// a portable operator is not at their registered address — true for /P and /M,
|
||||||
|
// and simply wrong for /QRP, which is a statement about power.
|
||||||
|
func TestSaysNothingAboutLocation(t *testing.T) {
|
||||||
|
keep := []string{"M0BFS/QRP", "f4bpo/qrp", "G0ABC/QRP"}
|
||||||
|
for _, c := range keep {
|
||||||
|
if !saysNothingAboutLocation(c) {
|
||||||
|
t.Errorf("%s: the home location should be kept — /QRP does not move anyone", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// These DO move the operator, or change the entity outright.
|
||||||
|
drop := []string{"F4BPO/P", "F4BPO/M", "F4BPO/MM", "F4BPO/AM", "JW/OR1A", "VP8/F4BPO", "F4BPO/8", "F4BPO"}
|
||||||
|
for _, c := range drop {
|
||||||
|
if saysNothingAboutLocation(c) {
|
||||||
|
t.Errorf("%s: the home location must NOT be trusted", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A power suffix on top of a portable one still moves them.
|
||||||
|
if saysNothingAboutLocation("F4BPO/P/QRP") {
|
||||||
|
t.Error("F4BPO/P/QRP is portable — location must not be kept")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user