feat: upload to external services clublog qrz

This commit is contained in:
2026-05-28 22:52:50 +02:00
parent e82e30dd02
commit 5c004f5e2f
26 changed files with 1710 additions and 31 deletions
+25 -1
View File
@@ -146,7 +146,11 @@ var dxccByName = map[string]int{
"liechtenstein": 251,
"austria": 206,
"italy": 248,
"sicily": 225,
// Sicily (IT9) and African Italy (IG9/IH9) are cty.dat WAE splits, not
// DXCC entities — they count as Italy (248). Sardinia (IS0) IS its own
// DXCC entity (225) and keeps its number.
"sicily": 248,
"african italy": 248,
"sardinia": 225,
"spain": 281,
"portugal": 272,
@@ -318,3 +322,23 @@ func EntityDXCC(name string) int {
}
return dxccByName[strings.ToLower(strings.TrimSpace(name))]
}
// ctyEntityAliases maps cty.dat's non-DXCC pseudo-entities — the CQ-zone /
// WAE splits AD1C lists separately — onto the ARRL DXCC entity they belong
// to. cty.dat reports e.g. "Sicily" or "Sardinia" so contesters get the
// right zones, but for DXCC (and the COUNTRY field) they are Italy. Add an
// entry here for any other split that should report its parent entity.
var ctyEntityAliases = map[string]string{
"sicily": "Italy",
"african italy": "Italy",
// NB: Sardinia is intentionally absent — it's a real DXCC entity.
}
// CanonicalEntityName normalises a cty.dat entity name to its ARRL DXCC
// entity. Names that already are DXCC entities pass through unchanged.
func CanonicalEntityName(name string) string {
if c, ok := ctyEntityAliases[strings.ToLower(strings.TrimSpace(name))]; ok {
return c
}
return name
}
+14 -2
View File
@@ -181,10 +181,22 @@ func parseEntityHeader(line string) *Entity {
if len(parts) < 8 {
return nil
}
name := strings.TrimSpace(parts[0])
primary := strings.TrimSpace(parts[7])
// cty.dat marks non-DXCC entities (WAE / contest-only zone splits such
// as Sicily *IT9 and African Italy *IG9) with a leading '*' on the
// primary prefix. Those report under their parent DXCC entity. True
// DXCC entities — including Sardinia (IS0) and Corsica (TK) — have no
// '*' and keep their own name. Per-prefix zones/lat-lon are preserved,
// so e.g. IG9 still resolves to CQ 33 / continent AF under "Italy".
if strings.HasPrefix(primary, "*") {
primary = strings.TrimPrefix(primary, "*")
name = CanonicalEntityName(name)
}
e := &Entity{
Name: strings.TrimSpace(parts[0]),
Name: name,
Continent: strings.TrimSpace(parts[3]),
Primary: strings.TrimSpace(parts[7]),
Primary: primary,
}
e.CQZone, _ = strconv.Atoi(strings.TrimSpace(parts[1]))
e.ITUZone, _ = strconv.Atoi(strings.TrimSpace(parts[2]))
+48
View File
@@ -54,6 +54,54 @@ func TestLookup(t *testing.T) {
}
}
// cty.dat marks non-DXCC entities (Sicily *IT9, African Italy *IG9) with a
// leading '*'; the parser must fold those into their parent DXCC entity
// "Italy" while leaving real DXCC entities — Sardinia (IS0), no '*' — alone.
func TestCanonicalEntityNames(t *testing.T) {
const cty = `Italy: 15: 28: EU: 42.82: -12.58: -1.0: I:
I,IK,IZ;
African Italy: 33: 37: AF: 35.67: -12.67: -1.0: *IG9:
IG9,IH9;
Sardinia: 15: 28: EU: 40.15: -9.27: -1.0: IS0:
IM0,IS,IW0U,IW0V;
Sicily: 15: 28: EU: 37.50: -14.00: -1.0: *IT9:
IT9,IW9;
`
db, err := Load(strings.NewReader(cty))
if err != nil {
t.Fatalf("load: %v", err)
}
cases := map[string]string{
"IW9EZO": "Italy", // Sicily (*IT9) → Italy
"IT9CLY": "Italy",
"IG9A": "Italy", // African Italy (*IG9) → Italy
"IK0ABC": "Italy",
"IS0XYZ": "Sardinia", // real DXCC entity — must stay Sardinia
"IM0ABC": "Sardinia",
}
for call, want := range cases {
m, ok := db.Lookup(call)
if !ok {
t.Errorf("%s: no match", call)
continue
}
if m.Entity.Name != want {
t.Errorf("%s: country = %q, want %q", call, m.Entity.Name, want)
}
}
// African Italy keeps its own zones/continent even though it reports
// Italy as the entity.
if m, _ := db.Lookup("IG9A"); m.CQZone != 33 || m.Continent != "AF" {
t.Errorf("IG9A: got CQ=%d cont=%s, want 33/AF", m.CQZone, m.Continent)
}
if EntityDXCC("Sicily") != 248 {
t.Errorf("EntityDXCC(Sicily) = %d, want 248 (Italy)", EntityDXCC("Sicily"))
}
if EntityDXCC("Sardinia") != 225 {
t.Errorf("EntityDXCC(Sardinia) = %d, want 225", EntityDXCC("Sardinia"))
}
}
func TestNormalize(t *testing.T) {
cases := map[string]string{
"F4BPO": "F4BPO",