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.
82 lines
3.4 KiB
Go
82 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"hamlog/internal/autocall"
|
|
)
|
|
|
|
// The entity's verdict is not the station's.
|
|
//
|
|
// From the air: on 10 m, where most countries are already in the log, the
|
|
// engine refused twenty decodes out of twenty-one as "worked" — a watched
|
|
// DXpedition calling CQ among them — because the ENTITY's status was read as
|
|
// the station's.
|
|
// allChased is the default: every orthogonal marker ticked.
|
|
var allChased = chaseExtras{pota: true, grid: true, pfx: true, county: true, state: true}
|
|
|
|
func TestCandidateWorkedIsTheCallsignNotTheEntity(t *testing.T) {
|
|
d := autocall.Decode{Call: "J38DX", Band: "10m", Mode: "FT8", IsNew: true}
|
|
|
|
// Grenada worked on 10 m FT8, this callsign never worked: nothing is needed
|
|
// from it, and calling it is NOT a duplicate.
|
|
c := candidateOf(d, SpotStatus{Status: "worked", WorkedSlot: false}, allChased)
|
|
if c.Worked {
|
|
t.Error("a station never worked was refused because its entity was")
|
|
}
|
|
if c.Need != autocall.NeedNone {
|
|
t.Errorf("need = %v on a worked entity, want none", c.Need)
|
|
}
|
|
|
|
// The same callsign already in the log on this band and mode IS a duplicate.
|
|
if c := candidateOf(d, SpotStatus{Status: "worked", WorkedSlot: true}, allChased); !c.Worked {
|
|
t.Error("a callsign already worked on this band and mode was not flagged")
|
|
}
|
|
|
|
// And a real need still carries through, with the unconfirmed distinction.
|
|
c = candidateOf(d, SpotStatus{Status: "new-band", UnconfStatus: true}, allChased)
|
|
if c.Need != autocall.NeedBand || !c.Unconfirmed {
|
|
t.Errorf("new-band unconfirmed came through as %v (unconf=%v)", c.Need, c.Unconfirmed)
|
|
}
|
|
}
|
|
|
|
// A station whose ENTITY has nothing left to give can still be the reason the
|
|
// operator is on the band: a WPX prefix, a county, a square, a park that has
|
|
// never been worked. Reported from the air — auto-call sat through a
|
|
// never-worked prefix calling CQ and did nothing.
|
|
func TestOrthogonalMarkersAreWorthACall(t *testing.T) {
|
|
d := autocall.Decode{Call: "BH2SWB", Band: "10m", Mode: "FT8", IsNew: true}
|
|
worked := SpotStatus{Status: "worked"}
|
|
|
|
for _, tc := range []struct {
|
|
what string
|
|
st SpotStatus
|
|
want string
|
|
}{
|
|
{"prefix", SpotStatus{Status: "worked", NewPfx: true}, "prefix"},
|
|
{"county", SpotStatus{Status: "worked", NewCounty: true}, "county"},
|
|
{"state", SpotStatus{Status: "worked", NewState: true}, "state"},
|
|
{"square", SpotStatus{Status: "worked", NewGrid: true}, "square"},
|
|
{"park", SpotStatus{Status: "worked", NewPOTA: true}, "park"},
|
|
} {
|
|
c := candidateOf(d, tc.st, allChased)
|
|
if c.Need != autocall.NeedExtra || c.Extra != tc.want {
|
|
t.Errorf("%s: need=%v extra=%q, want extra %q", tc.what, c.Need, c.Extra, tc.want)
|
|
}
|
|
// And not when the operator does not chase that kind of thing: the
|
|
// switch that withdraws the badge withdraws the call with it.
|
|
if c := candidateOf(d, tc.st, chaseExtras{}); c.Need != autocall.NeedNone {
|
|
t.Errorf("%s: called for a marker the operator does not chase", tc.what)
|
|
}
|
|
}
|
|
|
|
// It is the LOWEST rung: a real need on the entity still says what it is.
|
|
if c := candidateOf(d, SpotStatus{Status: "new-band", NewPfx: true}, allChased); c.Need != autocall.NeedBand {
|
|
t.Errorf("need = %v — a new band that is also a new prefix is a new band", c.Need)
|
|
}
|
|
// Nothing anywhere is still nothing.
|
|
if c := candidateOf(d, worked, allChased); c.Need != autocall.NeedNone {
|
|
t.Errorf("need = %v on a station with nothing to gain", c.Need)
|
|
}
|
|
}
|