feat: docked watch-list panel, and auto-call learns the orthogonal markers
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.
This commit is contained in:
@@ -395,6 +395,47 @@ func titleCase(s string) string {
|
||||
// lives, and keeps the ones that say WHO they are — name, address, QSL route.
|
||||
// A portable operator's cards still go to the home address, so that address is
|
||||
// not wrong; their county is.
|
||||
// sameEntityName reports whether two country names denote the same entity.
|
||||
//
|
||||
// The two come from different vocabularies — the callbook writes "Germany"
|
||||
// where cty.dat writes "Fed. Rep. of Germany", "United States" where the ADIF
|
||||
// list says "United States of America" — so they are compared on their
|
||||
// significant words with the boilerplate of officialdom removed, and one
|
||||
// containing the other counts as a match.
|
||||
//
|
||||
// Deliberately generous. Getting it wrong in the strict direction discards a
|
||||
// correct grid, which is the fault this exists to fix; getting it wrong in the
|
||||
// generous direction keeps a location from a neighbouring entity, which is the
|
||||
// state of every callbook record that has no page for the portable call anyway.
|
||||
func sameEntityName(a, b string) bool {
|
||||
na, nb := entityKey(a), entityKey(b)
|
||||
if na == "" || nb == "" {
|
||||
return false
|
||||
}
|
||||
return na == nb || strings.Contains(na, nb) || strings.Contains(nb, na)
|
||||
}
|
||||
|
||||
var entityNoise = map[string]bool{
|
||||
"THE": true, "OF": true, "FED": true, "REP": true, "REPUBLIC": true,
|
||||
"FEDERAL": true, "FEDERATION": true, "DEM": true, "DEMOCRATIC": true,
|
||||
"STATE": true, "KINGDOM": true, "AMERICA": true,
|
||||
}
|
||||
|
||||
// entityKey reduces a country name to its significant letters.
|
||||
func entityKey(s string) string {
|
||||
s = strings.ToUpper(strings.TrimSpace(s))
|
||||
var b strings.Builder
|
||||
for _, tok := range strings.FieldsFunc(s, func(r rune) bool {
|
||||
return r == ' ' || r == '.' || r == ',' || r == '-'
|
||||
}) {
|
||||
if entityNoise[tok] {
|
||||
continue
|
||||
}
|
||||
b.WriteString(tok)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func clearHomeLocation(r *Result) {
|
||||
r.Country, r.Continent = "", ""
|
||||
r.CQZ, r.ITUZ, r.DXCC = 0, 0, 0
|
||||
@@ -430,11 +471,23 @@ func fillFromDXCC(r *Result, dxcc DXCCResolver) bool {
|
||||
//
|
||||
// Same-entity portables (F4BPO/P, W2RE/2) are untouched: the entities match,
|
||||
// and there the home details ARE where the operator is.
|
||||
// UNLESS THE RECORD IS ABOUT THE OPERATION ITSELF.
|
||||
//
|
||||
// Some compound calls have a callbook page of their own, filed under the
|
||||
// slashed form and describing where the station actually is: QRZ's HP/WE9G
|
||||
// carries Altos del Maria, Panama, square EJ98xq. Clearing that threw away
|
||||
// the one field the lookup existed to find, and the QSO was logged with no
|
||||
// grid at all while the page plainly showed one.
|
||||
//
|
||||
// The record's OWN country is what tells the two apart: a page for the
|
||||
// operation names the entity being operated from, a home page names home.
|
||||
if dxccNum != 0 && strings.ContainsRune(r.Callsign, '/') && !saysNothingAboutLocation(r.Callsign) {
|
||||
if home := homeCall(r.Callsign); home != "" && home != r.Callsign {
|
||||
if homeNum, _, _, _, _, _, _, homeOK := dxcc.Resolve(home); homeOK && homeNum != 0 && homeNum != dxccNum {
|
||||
clearHomeLocation(r)
|
||||
filled = true
|
||||
if !sameEntityName(r.Country, country) {
|
||||
clearHomeLocation(r)
|
||||
filled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user