feat(bandopen): announce sporadic-E openings on 6, 4 and 2 m

Observation, not prediction, and it needs no new data source: the cluster event
worker already enriches every spot with the great-circle distance and bearing
from the operator's grid, which is exactly what a single-hop Es detection rests
on.

The signature is four or more DISTINCT stations at 500-2400 km inside a 90
degree bearing sector within twelve minutes. Each constraint earns its place:
distinct callsigns because one station spotted by six skimmers is six spots and
one station; the lower bound because a 6 m contact under 500 km is ordinary
tropo and says nothing about the ionosphere; the upper bound because past one
hop the bearing test stops meaning anything; and the sector because a real Es
cloud illuminates a direction, which is what separates an opening from a merely
busy evening.

Fed AFTER the Historical guard in the worker. A SH/DX reply replays a hundred
past spots in a second - precisely the shape of a burst - and would announce an
opening that ended hours ago.

Season LABELS, it never gates. Both hemispheres get a summer peak and a lesser
winter one, and an opening outside those is announced with "unusual for the
season" attached: the rare one is the one an operator must not hear about last.

One announcement per band per opening (45 minute quiet period). An opening runs
for hours and produces hundreds of spots; one alert is information, forty is
noise.
This commit is contained in:
2026-08-10 09:49:22 +02:00
parent 86a644863a
commit f6f5235a8b
10 changed files with 607 additions and 5 deletions
+6 -1
View File
@@ -695,7 +695,8 @@ type App struct {
awardSnap []qso.QSO // light-scanned + enriched logbook snapshot reused across award computations
awardSnapRev string // logbook revision the snapshot was built at ("" = none)
awardSnapUsed time.Time // last read — the snapshot is dropped once it goes cold (see awardSnapshotJanitor)
webpub webPublisher // log-to-website publishing: debounce timer, periodic ticker, last result
webpub webPublisher // log-to-website publishing: debounce timer, periodic ticker, last result
bandOpen bandOpenState // sporadic-E / band-opening detector over the spot stream
dataDir string // <exeDir>/data — holds config.json, logs, cty.dat
// shuttingDown gates beforeClose re-entry: the first user attempt to
@@ -7824,6 +7825,10 @@ func (a *App) clusterEventWorker() {
if s.Historical {
continue
}
// Band-opening detector. Deliberately AFTER the Historical guard above: a
// SH/DX reply replays a hundred past spots in a second, which is exactly
// the shape of a burst and would announce an opening that ended hours ago.
a.detectBandOpening(s)
// Fire any matching alert rules (sound / visual / e-mail).
a.evaluateAlerts(s)
// Mirror the spot onto the FlexRadio panadapter when enabled. Infer the