package award import "strings" // Name normalisation for USCountyKey. It exists because the county a QSO // carries and the county the USA-CA reference list carries come from different // hands and rarely agree letter for letter: // // - the reference is the FIPS list ("Doña Ana County", "Baltimore city"); // - a callbook writes what the licensee typed; // - and OpsLog's own offline resolver reads GeoNames, which spells the // county-equivalents its own way ("Baltimore (city)", "Nome (CA)", // "City and County of San Francisco"). // // Measured against a full FCC ULS import (1.56 M US callsigns), the shapes // handled here account for ~7 300 stations whose county matched nothing at all // — they showed as a new county for ever and counted for no award. // // Everything here is SPELLING. A county that genuinely no longer exists under // that name is a different problem, handled by renamed below. // foldAccents strips the diacritics that separate a FIPS name from the ASCII // every log and callbook actually holds. Only the letters that occur in US // place names are listed — this is not a general Unicode folder, and it must // not become one: silently folding arbitrary marks would let two distinct // references collapse onto one key. var accents = strings.NewReplacer( "ñ", "n", "Ñ", "N", "á", "a", "Á", "A", "é", "e", "É", "E", "í", "i", "Í", "I", "ó", "o", "Ó", "O", "ú", "u", "Ú", "U", "ü", "u", "Ü", "U", "ç", "c", "Ç", "C", ) func foldAccents(s string) string { // Fast path: US county names are ASCII with a single exception (Doña Ana), // so the common case must not pay for the replacer. for i := 0; i < len(s); i++ { if s[i] >= 0x80 { return accents.Replace(s) } } return s } // expandCountyEquivalent rewrites GeoNames' parenthetical and prefixed spellings // of county-equivalents into the plain FIPS form, which the rest of USCountyKey // then normalises as usual. // // Input is already upper-cased with periods and hyphens removed. // // BALTIMORE (CITY) → BALTIMORE CITY (FIPS "Baltimore city") // NOME (CA) → NOME CENSUS AREA (suffix stripped after) // CITY AND COUNTY OF SAN FRANCISCO → SAN FRANCISCO // // The truncated "(CITY" is not a typo here: GeoNames really does ship // "Colonial Heights (city" with the closing parenthesis missing. func expandCountyEquivalent(co string) string { switch { case strings.HasSuffix(co, " (CITY)"): co = strings.TrimSuffix(co, " (CITY)") + " CITY" case strings.HasSuffix(co, " (CITY"): co = strings.TrimSuffix(co, " (CITY") + " CITY" case strings.HasSuffix(co, " (CA)"): co = strings.TrimSuffix(co, " (CA)") + " CENSUS AREA" } // "City and County of X" and "City and Borough of X" are the same place as // FIPS's bare "X" (San Francisco, Denver, Honolulu, Juneau, Sitka). Note the // order: the longer prefixes must be tried before "CITY OF ". for _, p := range []string{ "CITY AND COUNTY OF ", "CITY AND BOROUGH OF ", "MUNICIPALITY OF ", "BOROUGH OF ", "CITY OF ", } { if strings.HasPrefix(co, p) { return strings.TrimPrefix(co, p) } } return co } // renamed folds a county renamed or re-drawn since the CQ USA-CA list was // published onto the name the award still counts. Keys and values are finished // USCountyKey county parts (upper-case, no spaces), looked up as "ST/COUNTY". // // This deliberately does NOT track the FIPS list. USA-CA is a fixed target of // ~3 100 counties; when a state renames one, the award does not reissue its // list, so a QSO with the new name would otherwise match nothing and count for // nothing. Folding it onto the old name is the only outcome that scores. // // Connecticut is absent on purpose. Its 2022 planning regions are not renamed // counties: a region is drawn from towns belonging to several different // counties, so there is no name that maps to another name. That one is resolved // from the station's ZIP, in internal/uls. var renamed = map[string]string{ // Wade Hampton Census Area, AK → Kusilvak Census Area (2015). "AK/KUSILVAK": "WADEHAMPTON", // Shannon County, SD → Oglala Lakota County (2015). "SD/OGLALALAKOTA": "SHANNON", // Valdez-Cordova Census Area, AK was split in two (2019). Both halves fold // back to the parent the award still lists. "AK/CHUGACH": "VALDEZCORDOVA", "AK/COPPERRIVER": "VALDEZCORDOVA", // Alaska's four "X City and Borough". The reference codes for these look // wrong and are not: the trailing " BOROUGH" is stripped as a county type, // leaving "JUNEAU CITY AND". Nothing else in the chain produces that shape // — a log that simply says "Juneau", and GeoNames' "City and Borough of // Juneau", both arrive here as "JUNEAU" and would match nothing. Rather // than re-key four published references, bend the bare name onto them. "AK/JUNEAU": "JUNEAUCITYAND", "AK/SITKA": "SITKACITYAND", "AK/WRANGELL": "WRANGELLCITYAND", "AK/YAKUTAT": "YAKUTATCITYAND", }