Files
OpsLog/internal/alerts/bandplan.go
T
rouggy a1a3bad682 fix(cluster): the FT8/FT4 watering holes exist on every band
The inference tables knew FT8 on some bands and nothing on others: a 10.136
spot with a bare comment read as generic DATA — which is what every
skimmerless spot of a DXpedition's 30 m FT8 slot did. The standard FT8/FT4
windows are added on 30 m, 60 m, 80 m (FT4), 17 m (FT4) and 12 m, in BOTH
tables (backend band plan and frontend spot.ts), ahead of the wide segments
they sit inside — order is the mechanism there.
2026-08-30 03:14:21 +02:00

81 lines
3.6 KiB
Go

package alerts
// The band plan behind InferMode, and why it is written out segment by segment.
//
// A cluster line carries no mode, so it has to be inferred — and OpsLog was
// inferring it TWICE, in two places, from two different tables. The frontend
// knew that 10.130-10.150 is the 30 m data segment; this side only knew a CW
// range that stopped at 10.130 and called everything after it SSB. So a spot at
// 10131.5 showed as DATA in the cluster list and raised an alert saying SSB.
// Reported with a screenshot of exactly that, on RI1FJL.
//
// The two tables are now the same table, transcribed from the frontend's
// (frontend/src/lib/spot.ts) with its segment boundaries kept intact. They stay
// two files because they are two languages, and the test next door is what
// keeps them one answer.
// modeSeg is one stretch of a band, in Hz, and what is worked there.
type modeSeg struct {
loHz, hiHz int64
mode string
}
// bandPlan is scanned IN ORDER, so a narrow watering hole listed before the
// wide segment it sits inside wins — FT8 at 14.074 before the 14.070-14.100
// data block. Order is the whole mechanism here; sorting this table by
// frequency would quietly turn every FT8 hole into "DATA".
var bandPlan = []modeSeg{
{1_800_000, 1_838_000, "CW"}, {1_838_000, 1_840_000, "FT8"}, {1_840_000, 2_000_000, "SSB"},
{3_573_000, 3_575_000, "FT8"}, {3_575_000, 3_578_000, "FT4"}, {3_500_000, 3_580_000, "CW"},
{3_580_000, 3_600_000, "DATA"}, {3_600_000, 4_000_000, "SSB"},
{5_357_000, 5_360_000, "FT8"}, {5_300_000, 5_500_000, "SSB"},
{7_074_000, 7_077_000, "FT8"}, {7_047_500, 7_048_500, "FT4"},
{7_000_000, 7_040_000, "CW"}, {7_040_000, 7_100_000, "DATA"}, {7_100_000, 7_300_000, "SSB"},
// 30 m: CW to 10.130, data above it — and nothing else. No SSB on this band.
// The FT8/FT4 watering holes come first, or a 10.136 spot with a bare
// comment reads as generic DATA — which is what every skimmerless spot of a
// DXpedition's 30 m FT8 slot did.
{10_136_000, 10_139_000, "FT8"}, {10_140_000, 10_143_000, "FT4"},
{10_100_000, 10_130_000, "CW"}, {10_130_000, 10_150_000, "DATA"},
{14_074_000, 14_077_000, "FT8"}, {14_080_000, 14_081_500, "FT4"},
{14_000_000, 14_070_000, "CW"}, {14_070_000, 14_100_000, "DATA"}, {14_100_000, 14_350_000, "SSB"},
{18_100_000, 18_103_000, "FT8"}, {18_104_000, 18_107_000, "FT4"},
{18_068_000, 18_095_000, "CW"}, {18_095_000, 18_110_000, "DATA"}, {18_110_000, 18_168_000, "SSB"},
{21_074_000, 21_077_000, "FT8"}, {21_140_000, 21_143_000, "FT4"},
{21_000_000, 21_070_000, "CW"}, {21_070_000, 21_150_000, "DATA"}, {21_150_000, 21_450_000, "SSB"},
{24_915_000, 24_918_000, "FT8"}, {24_919_000, 24_922_000, "FT4"},
{24_890_000, 24_915_000, "CW"}, {24_915_000, 24_940_000, "DATA"}, {24_940_000, 24_990_000, "SSB"},
{28_074_000, 28_077_000, "FT8"}, {28_180_000, 28_183_000, "FT4"},
{28_000_000, 28_070_000, "CW"}, {28_070_000, 28_300_000, "DATA"}, {28_300_000, 29_700_000, "SSB"},
{50_313_000, 50_316_000, "FT8"}, {50_318_000, 50_321_000, "FT4"},
{50_000_000, 50_100_000, "CW"}, {50_100_000, 50_500_000, "SSB"},
{144_174_000, 144_177_000, "FT8"},
{144_000_000, 144_150_000, "CW"}, {144_150_000, 144_500_000, "SSB"},
}
// modeFromFrequency returns what the band plan says is worked at freqHz, or ""
// where it says nothing.
//
// Empty rather than a guess: a spot outside every listed segment is on a band
// this table does not cover, and answering "SSB" for it is how a 30 m data spot
// came to raise an SSB alert.
func modeFromFrequency(freqHz int64) string {
for _, s := range bandPlan {
if freqHz >= s.loHz && freqHz < s.hiHz {
return s.mode
}
}
return ""
}