fix(counties): make the cluster and the entry panel agree on a US county
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.
This commit is contained in:
+46
-2
@@ -34,6 +34,7 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -114,6 +115,33 @@ func (s *Store) Count() int {
|
||||
return n
|
||||
}
|
||||
|
||||
// RulesVersion is bumped whenever a correction applied DURING import changes
|
||||
// the county a callsign resolves to. The value is stamped into uls_meta, so a
|
||||
// database built by an older OpsLog can be told apart from one built by this
|
||||
// one — the rows themselves carry no clue, and the fix cannot be applied
|
||||
// retroactively because the ZIP each county was derived from is not stored.
|
||||
//
|
||||
// 1 — original.
|
||||
// 2 — Connecticut ZIPs mapped back to their legal county instead of the
|
||||
// 2022 planning region GeoNames now reports (see ctcounty_gen.go).
|
||||
const RulesVersion = 2
|
||||
|
||||
// NeedsRefresh reports that the store was built before a correction that
|
||||
// changes its answers, so the operator should download it again. False for an
|
||||
// empty store: there is nothing stale about data that was never fetched.
|
||||
func (s *Store) NeedsRefresh() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
var n int
|
||||
if s.db.QueryRow(`SELECT COUNT(*) FROM uls_callsign`).Scan(&n); n == 0 {
|
||||
return false
|
||||
}
|
||||
var v string
|
||||
s.db.QueryRow(`SELECT value FROM uls_meta WHERE key='rules_version'`).Scan(&v)
|
||||
got, _ := strconv.Atoi(v)
|
||||
return got < RulesVersion
|
||||
}
|
||||
|
||||
// UpdatedAt returns when the store was last imported (zero if never).
|
||||
func (s *Store) UpdatedAt() time.Time {
|
||||
s.mu.RLock()
|
||||
@@ -285,6 +313,10 @@ func (s *Store) rebuild(ctx context.Context, amatZip string, zipmap map[string]z
|
||||
time.Now().UTC().Format(time.RFC3339)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `INSERT OR REPLACE INTO uls_meta(key,value) VALUES('rules_version',?)`,
|
||||
strconv.Itoa(RulesVersion)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -333,9 +365,21 @@ func parseGeoNames(zipPath string) (map[string]zipRow, error) {
|
||||
}
|
||||
lat := parseFloat(f[9])
|
||||
lon := parseFloat(f[10])
|
||||
state := strings.ToUpper(strings.TrimSpace(f[4]))
|
||||
county := strings.TrimSpace(f[5])
|
||||
// Connecticut: GeoNames reports the 2022 planning regions, which are
|
||||
// county-equivalents for the Census and nothing at all for amateur
|
||||
// radio — no award, no callbook and no log uses them, so every CT
|
||||
// station resolved to a name that matched nothing. Substitute the legal
|
||||
// county for the ZIP. See cmd/ctzipgen.
|
||||
if state == "CT" {
|
||||
if c, ok := ctCounty[zip5]; ok {
|
||||
county = c
|
||||
}
|
||||
}
|
||||
out[zip5] = zipRow{
|
||||
state: strings.ToUpper(strings.TrimSpace(f[4])),
|
||||
county: strings.TrimSpace(f[5]),
|
||||
state: state,
|
||||
county: county,
|
||||
lat: lat,
|
||||
lon: lon,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user