fix(dxcc): /MM and /AM belong to no entity
RI1FJL/MM resolved to Franz Josef Land while the expedition was still sailing there, telling the operator they had worked an entity they had not. The old behaviour was deliberate — the comment read "strict DXCC says no entity, but the log should still show the operator's country" — and that is wrong for exactly this reason. A home country on a maritime mobile is not extra information; it is a false claim about where the contact happened, and it is the kind of false claim that gets a QSO submitted for an award it cannot win. Trailing only. A LEADING "MM" is the Scotland prefix and "AM" is Spain, so MM0ABC and AM5X keep their entities — getting that wrong would be a far larger error than the one being fixed. /P, /M and /B are unaffected: a portable station is still ashore. In the cluster the column says "Maritime mobile — no DXCC" rather than going blank, because blank is what an UNRESOLVED spot looks like and the two mean opposite things — one is "we don't know yet", this is "there is nothing to know". Display only: the QSO's own Country stays empty, since that field holds a DXCC entity name and there is none.
This commit is contained in:
@@ -17452,6 +17452,16 @@ func (a *App) ClusterSpotStatuses(spots []SpotQuery) []SpotStatus {
|
|||||||
if a.dxcc == nil {
|
if a.dxcc == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Say WHY a maritime/aeronautical mobile has no entity, rather than
|
||||||
|
// leaving the column blank — blank is what an unresolved spot looks like,
|
||||||
|
// and the two mean opposite things: one is "we don't know yet", this is
|
||||||
|
// "there is nothing to know, the station is at sea". Display only: the
|
||||||
|
// QSO's own Country stays empty, because it is a DXCC entity name and
|
||||||
|
// there is none.
|
||||||
|
if dxcc.IsMobileNoEntity(q.Call) {
|
||||||
|
out[i].Country = "Maritime mobile — no DXCC"
|
||||||
|
continue
|
||||||
|
}
|
||||||
m, ok := a.dxcc.Lookup(q.Call)
|
m, ok := a.dxcc.Lookup(q.Call)
|
||||||
if !ok || m.Entity == nil {
|
if !ok || m.Entity == nil {
|
||||||
continue
|
continue
|
||||||
|
|||||||
+4
-2
@@ -10,7 +10,8 @@
|
|||||||
"Station Control: the short and long path headings are repeated under the compass, at a readable size and clickable.",
|
"Station Control: the short and long path headings are repeated under the compass, at a readable size and clickable.",
|
||||||
"Compact mode: the window now fits the entry strip instead of leaving a band of empty space below it.",
|
"Compact mode: the window now fits the entry strip instead of leaving a band of empty space below it.",
|
||||||
"HRDLog: an ON AIR option publishes your live frequency, mode and rig on hrdlog.net.",
|
"HRDLog: an ON AIR option publishes your live frequency, mode and rig on hrdlog.net.",
|
||||||
"ADIF: the county is exported as STATE,COUNTY as the standard requires, and a county imported that way now fills the state too."
|
"ADIF: the county is exported as STATE,COUNTY as the standard requires, and a county imported that way now fills the state too.",
|
||||||
|
"A /MM or /AM callsign no longer resolves to a country — RI1FJL/MM read as Franz Josef Land while the expedition was still sailing there."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"Cluster : « Masquer les contactés » ne masque plus un spot qui est un nouveau préfixe, comté, carré ou parc dans une contrée déjà faite.",
|
"Cluster : « Masquer les contactés » ne masque plus un spot qui est un nouveau préfixe, comté, carré ou parc dans une contrée déjà faite.",
|
||||||
@@ -20,7 +21,8 @@
|
|||||||
"Contrôle station : les azimuts court et long chemin sont repris sous la boussole, lisibles et cliquables.",
|
"Contrôle station : les azimuts court et long chemin sont repris sous la boussole, lisibles et cliquables.",
|
||||||
"Mode compact : la fenêtre épouse la bande de saisie au lieu de laisser une bande vide en dessous.",
|
"Mode compact : la fenêtre épouse la bande de saisie au lieu de laisser une bande vide en dessous.",
|
||||||
"HRDLog : une option ON AIR publie ta fréquence, ton mode et ta radio en direct sur hrdlog.net.",
|
"HRDLog : une option ON AIR publie ta fréquence, ton mode et ta radio en direct sur hrdlog.net.",
|
||||||
"ADIF : le comté est exporté sous la forme ÉTAT,COMTÉ comme l exige le standard, et un comté importé ainsi remplit aussi l état."
|
"ADIF : le comté est exporté sous la forme ÉTAT,COMTÉ comme l exige le standard, et un comté importé ainsi remplit aussi l état.",
|
||||||
|
"Un indicatif en /MM ou /AM ne se résout plus en pays — RI1FJL/MM affichait Franz Josef Land alors que l expédition faisait encore route."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -132,6 +132,18 @@ func (db *DB) Entities() []*Entity {
|
|||||||
func (db *DB) Lookup(callsign string) (Match, bool) {
|
func (db *DB) Lookup(callsign string) (Match, bool) {
|
||||||
db.mu.RLock()
|
db.mu.RLock()
|
||||||
defer db.mu.RUnlock()
|
defer db.mu.RUnlock()
|
||||||
|
// Maritime and aeronautical mobile belong to NO entity, and that is not a
|
||||||
|
// technicality — the station is at sea or in the air. RI1FJL/MM resolved to
|
||||||
|
// Franz Josef Land while the expedition was still on its way there, telling
|
||||||
|
// the operator they had worked an entity they had not.
|
||||||
|
//
|
||||||
|
// The previous choice was deliberate ("the log should still show the
|
||||||
|
// operator's country") and it is wrong for exactly that reason: a home
|
||||||
|
// country here is not extra information, it is a false claim about where the
|
||||||
|
// contact happened.
|
||||||
|
if IsMobileNoEntity(callsign) {
|
||||||
|
return Match{}, false
|
||||||
|
}
|
||||||
call := normalizeCallsign(callsign)
|
call := normalizeCallsign(callsign)
|
||||||
if call == "" {
|
if call == "" {
|
||||||
return Match{}, false
|
return Match{}, false
|
||||||
@@ -364,3 +376,22 @@ func replaceAreaDigit(call string, d byte) string {
|
|||||||
}
|
}
|
||||||
return call
|
return call
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsMobileNoEntity reports a callsign DXCC assigns to no entity: a TRAILING
|
||||||
|
// /MM (maritime mobile) or /AM (aeronautical mobile).
|
||||||
|
//
|
||||||
|
// Trailing only. A LEADING "MM" is the Scotland prefix and "AM" is Spain, so
|
||||||
|
// MM0ABC and AM5X are ordinary calls — treating those as entity-less would be a
|
||||||
|
// far larger error than the one this fixes.
|
||||||
|
func IsMobileNoEntity(callsign string) bool {
|
||||||
|
s := strings.ToUpper(strings.TrimSpace(callsign))
|
||||||
|
i := strings.LastIndex(s, "/")
|
||||||
|
if i < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch s[i+1:] {
|
||||||
|
case "MM", "AM":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package dxcc
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// /MM and /AM belong to NO DXCC entity: the station is at sea or in the air.
|
||||||
|
//
|
||||||
|
// This is not a technicality. RI1FJL/MM resolved to Franz Josef Land while the
|
||||||
|
// expedition was still sailing there, which told the operator they had worked
|
||||||
|
// an entity they had not.
|
||||||
|
func TestMobileNoEntity(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
call string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"RI1FJL/MM", true},
|
||||||
|
{"YB1SCY/AM", true},
|
||||||
|
{"ri1fjl/mm", true},
|
||||||
|
{" RI1FJL/MM ", true},
|
||||||
|
// A LEADING MM is Scotland and AM is Spain. Treating those as
|
||||||
|
// entity-less would be a far larger error than the one this fixes.
|
||||||
|
{"MM0ABC", false},
|
||||||
|
{"AM5X", false},
|
||||||
|
{"MM/F4BPO", false},
|
||||||
|
{"AM/DL1ABC", false},
|
||||||
|
// Other suffixes keep their entity — a portable station is still ashore.
|
||||||
|
{"F4BPO/P", false},
|
||||||
|
{"F4BPO/M", false},
|
||||||
|
{"4U1UN/B", false},
|
||||||
|
{"F4BPO", false},
|
||||||
|
// Only the LAST element counts: RI1FJL/MM/P is still maritime, and a
|
||||||
|
// call whose /MM is not final is not.
|
||||||
|
{"RI1FJL/MM/P", false},
|
||||||
|
{"", false},
|
||||||
|
} {
|
||||||
|
if got := IsMobileNoEntity(tc.call); got != tc.want {
|
||||||
|
t.Errorf("IsMobileNoEntity(%q) = %v, want %v", tc.call, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user