An operator asked why K1SEI showed "Middlesex" in Info (F2) and "Lower
Connecticut River Valley" in the cluster. Two sources: the entry panel has
a callbook answer, a spot carries only a callsign so the cluster derives one
from the FCC licence ZIP through GeoNames — and GeoNames has followed the
Census in replacing Connecticut's counties with the 2022 planning regions.
No award, callbook or log uses those, so every CT station matched nothing:
new county for ever, and counting toward nothing.
Measured against a full ULS import (1 556 444 US callsigns), 23 223 resolved
to a name the USA-CA reference does not contain. Three causes, three fixes:
- Spelling. "City and County of San Francisco", "Baltimore (city)",
"Nome (CA)", plus counties renamed since the award list was drawn
(Kusilvak, Oglala Lakota, the Valdez-Cordova split) and Alaska's four
"X City and Borough", whose reference codes read "JUNEAUCITYAND" because
the county-type suffix strip eats the wrong end. Normalised in
award.USCountyKey, which both sides already go through. 7 515 callsigns,
no re-download needed.
- Doña Ana, NM shipped into the reference as "NM/DO̱AANA" — mangled by a
non-UTF-8 CSV line, a code nothing could ever produce, so that county was
unwinnable and silent about it. Row repaired, cntygen now refuses such a
line, and a test makes every one of the 3 102 references reproduce its own
code from its own name.
- Connecticut. A planning region is drawn from towns in several counties, so
no name maps to a name — only the ZIP can resolve it. cmd/ctzipgen builds
the table from the Census 2020 crosswalk, filling PO-box-only ZIPs from the
nearest resolved centroid; all 11 ZIPs GeoNames still labels with a real
county agree with the result. 15 037 callsigns, applied at import, so the
store now carries a rules version and Settings says when a re-download is
needed.
Alignment itself is the last piece: a spot now shows the county the station is
logged with when we have one, and falls back to the ZIP-derived county only for
stations never worked.
111 lines
3.3 KiB
Go
111 lines
3.3 KiB
Go
package main
|
|
|
|
// One-shot generator: reads the FIPS county CSV and emits
|
|
// internal/awardref/uscounties_gen.go. Not part of the build.
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"hamlog/internal/award"
|
|
)
|
|
|
|
// dxccForState maps the two US "states" that are separate DXCC entities.
|
|
func dxccForState(st string) int {
|
|
switch st {
|
|
case "AK":
|
|
return 6
|
|
case "HI":
|
|
return 110
|
|
default:
|
|
return 291
|
|
}
|
|
}
|
|
|
|
// territories we exclude entirely — plus DC, which USA-CA does not count as a
|
|
// county.
|
|
var skipState = map[string]bool{"PR": true, "GU": true, "VI": true, "AS": true, "MP": true, "NA": true, "DC": true}
|
|
|
|
// excludedUSACA reports county-equivalents that the CQ USA-CA award does NOT
|
|
// count as counties: the independent cities of Virginia and Carson City (NV).
|
|
// (Baltimore MD and St. Louis MO ARE counted, so they are kept.) A contact in
|
|
// one of these counts toward a bordering county under the award rules.
|
|
func excludedUSACA(state, name string) bool {
|
|
n := strings.ToLower(strings.TrimSpace(name))
|
|
switch state {
|
|
case "VA":
|
|
return strings.HasSuffix(n, " city")
|
|
case "NV":
|
|
return n == "carson city"
|
|
}
|
|
return false
|
|
}
|
|
|
|
func main() {
|
|
f, err := os.Open(os.Args[1])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
type row struct{ code, name string; dxcc int }
|
|
var rows []row
|
|
seen := map[string]bool{}
|
|
sc := bufio.NewScanner(f)
|
|
sc.Scan() // header
|
|
for sc.Scan() {
|
|
line := sc.Text()
|
|
// The FIPS CSV is not reliably UTF-8, and the one county whose name is
|
|
// not ASCII (Doña Ana, NM) came through mangled once already — it landed
|
|
// in the reference as "NM/DO̱AANA", a code no log could ever match,
|
|
// silently costing that county for every operator. Refuse the row rather
|
|
// than emit a broken one.
|
|
if !utf8.ValidString(line) {
|
|
fmt.Fprintf(os.Stderr, "cntygen: skipping non-UTF-8 line: %q\n", line)
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, ",", 3)
|
|
if len(parts) < 3 {
|
|
continue
|
|
}
|
|
name := strings.TrimSpace(parts[1])
|
|
st := strings.TrimSpace(parts[2])
|
|
if skipState[st] || len(st) != 2 || strings.EqualFold(name, "UNITED STATES") {
|
|
continue
|
|
}
|
|
if excludedUSACA(st, name) {
|
|
continue
|
|
}
|
|
// State header rows have an all-caps state NAME and state code "NA"
|
|
// (already skipped). County rows have a 2-letter state code.
|
|
code := award.USCountyKey(st, name)
|
|
if code == "" || seen[code] {
|
|
continue
|
|
}
|
|
seen[code] = true
|
|
rows = append(rows, row{code: code, name: name + ", " + st, dxcc: dxccForState(st)})
|
|
}
|
|
sort.Slice(rows, func(i, j int) bool { return rows[i].code < rows[j].code })
|
|
|
|
var b strings.Builder
|
|
b.WriteString("// Code generated by cmd/cntygen from FIPS county data. DO NOT EDIT.\n")
|
|
b.WriteString("package awardref\n\n")
|
|
b.WriteString("// usCounties is the US Counties (USA-CA) reference list: one entry per county\n")
|
|
b.WriteString("// in the 50 states, keyed by the canonical \"STATE,COUNTY\" match code that\n")
|
|
b.WriteString("// award.usCountyKey produces from a QSO's state + cnty fields.\n")
|
|
fmt.Fprintf(&b, "func usCounties() []Ref {\n\treturn []Ref{\n")
|
|
for _, r := range rows {
|
|
fmt.Fprintf(&b, "\t\tref(%q, %q, %d),\n", r.code, r.name, r.dxcc)
|
|
}
|
|
b.WriteString("\t}\n}\n")
|
|
|
|
if err := os.WriteFile(os.Args[2], []byte(b.String()), 0o644); err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf("wrote %d counties to %s\n", len(rows), os.Args[2])
|
|
}
|