The watch list was a tab, and an operator working FT8 lives on the decodes one: a station they had asked to be told about turned up on a screen they were not looking at. The same answer is now docked in the widget strip, above the tabs, reduced to what is worth acting on — on the air and still needed, one row per band and mode, with the cluster's own NEW DXCC / NEW BAND / NEW SLOT badge and a click that tunes. Off by default. The "active and needed" answer costs a debounced query per visible slot, so it is written once (lib/watchlistSpots) and the tab uses it too. Auto-call: - It answers a new prefix, county, state, square or park. Those markers are orthogonal to the entity, they ranked as nothing-needed, and the engine sat through a never-worked WPX prefix calling CQ. New rung at the foot of the ladder, gated by the chase switches the badges use — which meant making those switches portable, since the backend cannot read localStorage. - It calls THROUGH a pileup. Giving up the moment the DX answered somebody else is precisely how a queue is not worked; the call and miss counters already bound the effort, and a station in mid-exchange is still never chosen as a new target. The PSK Reporter panel now follows the station auto-call is waiting for: the analysis takes a history query and a period or two to fill, so starting it when the DX comes free is starting it too late. Callbook lookup: a compound callsign with a page of its OWN keeps that page's location. QRZ files HP/WE9G under exactly that form, with the Panama square the station is operating from, and the rule that drops a home address from a portable call was throwing it away. The record's own country tells an operation's page from a home page. Changelog: entries may open with [NEW], drawn as a pill in the What's new dialog — a release is mostly fixes and the two or three genuinely new things should not have to be found by reading all of it.
151 lines
5.4 KiB
Go
151 lines
5.4 KiB
Go
package lookup
|
|
|
|
import "testing"
|
|
|
|
// fakeDXCC resolves a handful of calls, enough to stand in for cty.dat.
|
|
type fakeDXCC map[string]struct {
|
|
num int
|
|
country string
|
|
cont string
|
|
cqz, ituz int
|
|
lat, lon float64
|
|
}
|
|
|
|
func (f fakeDXCC) Resolve(call string) (int, string, string, int, int, float64, float64, bool) {
|
|
e, ok := f[call]
|
|
if !ok {
|
|
return 0, "", "", 0, 0, 0, 0, false
|
|
}
|
|
return e.num, e.country, e.cont, e.cqz, e.ituz, e.lat, e.lon, true
|
|
}
|
|
|
|
func testDXCC() fakeDXCC {
|
|
return fakeDXCC{
|
|
// Costa Rica, as cty.dat reads the OPERATING call.
|
|
"TI8/W2RE": {num: 308, country: "Costa Rica", cont: "NA", cqz: 7, ituz: 11, lat: 9.9, lon: -84.1},
|
|
// The home call: United States.
|
|
"W2RE": {num: 291, country: "United States", cont: "NA", cqz: 5, ituz: 8, lat: 39.8, lon: -98.5},
|
|
// A same-entity portable: still France either way.
|
|
"F4BPO/P": {num: 227, country: "France", cont: "EU", cqz: 14, ituz: 27, lat: 46.2, lon: 2.2},
|
|
"F4BPO": {num: 227, country: "France", cont: "EU", cqz: 14, ituz: 27, lat: 46.2, lon: 2.2},
|
|
}
|
|
}
|
|
|
|
// A callbook record for a portable call routinely carries the operator's HOME
|
|
// address — most QRZ pages for a portable call do — and a subdivision belongs to
|
|
// an entity. Carrying the home county across an entity change is not cosmetic:
|
|
// CNTY is defined as a US county, so TI8/W2RE was coming out as a Costa Rica
|
|
// contact credited to Dutchess County, New York, with the distance and beam
|
|
// heading taken from a square 3,600 km from where the station actually was.
|
|
func TestPortableInAnotherEntityDropsTheHomeSubdivision(t *testing.T) {
|
|
r := Result{
|
|
Callsign: "TI8/W2RE",
|
|
Name: "Raymond", // who they are — kept
|
|
QTH: "Poughquag",
|
|
Address: "499 Pleasant Ridge Road", // cards still go there — kept
|
|
State: "NY",
|
|
County: "Dutchess",
|
|
Grid: "FN31",
|
|
Lat: 41.6, Lon: -73.7,
|
|
DXCC: 291,
|
|
}
|
|
fillFromDXCC(&r, testDXCC())
|
|
|
|
if r.County != "" {
|
|
t.Errorf("county = %q, want empty — a US county on a Costa Rica QSO is a false award credit", r.County)
|
|
}
|
|
if r.State != "" {
|
|
t.Errorf("state = %q, want empty — a subdivision of the entity that is not being worked", r.State)
|
|
}
|
|
if r.Grid != "" {
|
|
t.Errorf("grid = %q, want empty — it is the home square, 3,600 km from the operation", r.Grid)
|
|
}
|
|
// The entity and its centroid take over, so distance and bearing mean
|
|
// something again.
|
|
if r.DXCC != 308 || r.Country != "Costa Rica" {
|
|
t.Errorf("entity = %d %q, want 308 Costa Rica", r.DXCC, r.Country)
|
|
}
|
|
if r.Lat != 9.9 || r.Lon != -84.1 {
|
|
t.Errorf("lat/lon = %v/%v, want the Costa Rica centroid — the home coordinates must not survive", r.Lat, r.Lon)
|
|
}
|
|
// Who they are, and where their cards go, is unchanged.
|
|
if r.Name != "Raymond" || r.Address != "499 Pleasant Ridge Road" {
|
|
t.Errorf("name/address were cleared (%q / %q) — a portable operator's post still reaches home", r.Name, r.Address)
|
|
}
|
|
}
|
|
|
|
// The other half of the rule: a portable WITHIN the same entity is at home as
|
|
// far as the entity is concerned, and its details must survive untouched.
|
|
func TestSameEntityPortableKeepsItsLocation(t *testing.T) {
|
|
r := Result{
|
|
Callsign: "F4BPO/P",
|
|
State: "77", County: "Seine-et-Marne", Grid: "JN18cs",
|
|
Lat: 48.8, Lon: 2.4, DXCC: 227,
|
|
}
|
|
fillFromDXCC(&r, testDXCC())
|
|
|
|
if r.Grid != "JN18cs" || r.County != "Seine-et-Marne" || r.State != "77" {
|
|
t.Errorf("a same-entity portable lost its location: grid=%q county=%q state=%q", r.Grid, r.County, r.State)
|
|
}
|
|
if r.Lat != 48.8 || r.Lon != 2.4 {
|
|
t.Errorf("lat/lon = %v/%v — the precise home position was replaced by the entity centroid", r.Lat, r.Lon)
|
|
}
|
|
}
|
|
|
|
// The other half again, and the one an operator reported: a compound call with
|
|
// a callbook page OF ITS OWN. QRZ files HP/WE9G under that exact form, with the
|
|
// Panama address and square the station is actually operating from — and the
|
|
// QSO was being logged with no grid at all while the page plainly showed one.
|
|
func TestACompoundCallWithItsOwnPageKeepsThatPagesLocation(t *testing.T) {
|
|
dxcc := testDXCC()
|
|
dxcc["HP/WE9G"] = struct {
|
|
num int
|
|
country string
|
|
cont string
|
|
cqz, ituz int
|
|
lat, lon float64
|
|
}{num: 88, country: "Panama", cont: "NA", cqz: 7, ituz: 11, lat: 8.5, lon: -80.0}
|
|
dxcc["WE9G"] = struct {
|
|
num int
|
|
country string
|
|
cont string
|
|
cqz, ituz int
|
|
lat, lon float64
|
|
}{num: 291, country: "United States", cont: "NA", cqz: 5, ituz: 8, lat: 39.8, lon: -98.5}
|
|
|
|
r := Result{
|
|
Callsign: "HP/WE9G",
|
|
Name: "Richard B",
|
|
Country: "Panama", // the RECORD's own country: this page is the operation
|
|
Grid: "EJ98xq",
|
|
Lat: 8.686667, Lon: -80.043333,
|
|
}
|
|
fillFromDXCC(&r, dxcc)
|
|
|
|
if r.Grid != "EJ98xq" {
|
|
t.Errorf("grid = %q, want EJ98xq — the page describes the operation, not a home address", r.Grid)
|
|
}
|
|
if r.Lat != 8.686667 || r.Lon != -80.043333 {
|
|
t.Errorf("lat/lon = %v/%v — the station's own position was replaced by the entity centroid", r.Lat, r.Lon)
|
|
}
|
|
}
|
|
|
|
func TestSameEntityNameAcrossVocabularies(t *testing.T) {
|
|
same := [][2]string{
|
|
{"Germany", "Fed. Rep. of Germany"},
|
|
{"United States", "United States of America"},
|
|
{"Panama", "Panama"},
|
|
{"Kosovo", "Republic of Kosovo"},
|
|
}
|
|
for _, p := range same {
|
|
if !sameEntityName(p[0], p[1]) {
|
|
t.Errorf("%q and %q read as different entities", p[0], p[1])
|
|
}
|
|
}
|
|
for _, p := range [][2]string{{"Costa Rica", "United States"}, {"France", "Belgium"}, {"", "Panama"}} {
|
|
if sameEntityName(p[0], p[1]) {
|
|
t.Errorf("%q and %q read as the same entity", p[0], p[1])
|
|
}
|
|
}
|
|
}
|