Add 902-5760 MHz bands, centralize band options

New BandConfig entries for 902, 1296, 2304, 3456, 5760 MHz:
- All beneficial humidity effect (like 10 GHz)
- Near-zero gaseous absorption and rain attenuation
- Ranges: 902 MHz typical 400 km, 5760 MHz typical 220 km
- Same seasonal curves as 10 GHz (ducting-driven)

BandConfig.band_options/0 generates dropdown options from configs.
All pages (path, rover, submit) use centralized band_options instead
of hardcoded lists. Map page already used BandConfig.all_bands().
10 GHz remains the default on all pages.
This commit is contained in:
Graham McIntire 2026-04-07 16:39:24 -05:00
parent 691ee9fbd4
commit cfaef287e4
6 changed files with 166 additions and 41 deletions

View file

@ -46,6 +46,146 @@ defmodule Microwaveprop.Propagation.BandConfig do
@refractivity_default {42, 42}
@band_configs %{
902 => %{
freq_mhz: 902,
label: "902 MHz",
o2_db_km: 0.006,
h2o_coeff: 0.0,
humidity_effect: :beneficial,
humidity_penalty: 0.0,
rain_k: 0.000,
rain_alpha: 1.0,
seasonal_base: %{
1 => 38,
2 => 40,
3 => 22,
4 => 55,
5 => 68,
6 => 90,
7 => 95,
8 => 75,
9 => 78,
10 => 88,
11 => 78,
12 => 25
},
seasonal_adj: %{},
typical_range_km: 400,
extended_range_km: 800,
exceptional_range_km: 1500
},
1_296 => %{
freq_mhz: 1_296,
label: "1296 MHz",
o2_db_km: 0.006,
h2o_coeff: 0.0,
humidity_effect: :beneficial,
humidity_penalty: 0.0,
rain_k: 0.000,
rain_alpha: 1.0,
seasonal_base: %{
1 => 38,
2 => 40,
3 => 22,
4 => 55,
5 => 68,
6 => 90,
7 => 95,
8 => 75,
9 => 78,
10 => 88,
11 => 78,
12 => 25
},
seasonal_adj: %{},
typical_range_km: 350,
extended_range_km: 700,
exceptional_range_km: 1200
},
2_304 => %{
freq_mhz: 2_304,
label: "2304 MHz",
o2_db_km: 0.006,
h2o_coeff: 0.0,
humidity_effect: :beneficial,
humidity_penalty: 0.0,
rain_k: 0.001,
rain_alpha: 1.15,
seasonal_base: %{
1 => 38,
2 => 40,
3 => 22,
4 => 55,
5 => 68,
6 => 90,
7 => 95,
8 => 75,
9 => 78,
10 => 88,
11 => 78,
12 => 25
},
seasonal_adj: %{},
typical_range_km: 300,
extended_range_km: 600,
exceptional_range_km: 1000
},
3_456 => %{
freq_mhz: 3_456,
label: "3456 MHz",
o2_db_km: 0.006,
h2o_coeff: 0.0,
humidity_effect: :beneficial,
humidity_penalty: 0.0,
rain_k: 0.002,
rain_alpha: 1.20,
seasonal_base: %{
1 => 38,
2 => 40,
3 => 22,
4 => 55,
5 => 68,
6 => 90,
7 => 95,
8 => 75,
9 => 78,
10 => 88,
11 => 78,
12 => 25
},
seasonal_adj: %{},
typical_range_km: 250,
extended_range_km: 550,
exceptional_range_km: 900
},
5_760 => %{
freq_mhz: 5_760,
label: "5760 MHz",
o2_db_km: 0.007,
h2o_coeff: 0.0,
humidity_effect: :beneficial,
humidity_penalty: 0.0,
rain_k: 0.005,
rain_alpha: 1.25,
seasonal_base: %{
1 => 38,
2 => 40,
3 => 22,
4 => 55,
5 => 68,
6 => 90,
7 => 95,
8 => 75,
9 => 78,
10 => 88,
11 => 78,
12 => 25
},
seasonal_adj: %{},
typical_range_km: 220,
extended_range_km: 500,
exceptional_range_km: 1000
},
10_000 => %{
freq_mhz: 10_000,
label: "10 GHz",
@ -292,6 +432,12 @@ defmodule Microwaveprop.Propagation.BandConfig do
|> Enum.sort()
end
@doc "Returns band options as {label, value_string} tuples for form dropdowns."
@spec band_options() :: [{String.t(), String.t()}]
def band_options do
Enum.map(all_bands(), fn band -> {band.label, to_string(band.freq_mhz)} end)
end
@doc "Returns the scoring weights map. All 10 values sum to 1.0."
@spec weights() :: map()
def weights, do: @weights

View file

@ -11,16 +11,7 @@ defmodule MicrowavepropWeb.PathLive do
alias Microwaveprop.Terrain.TerrainAnalysis
alias Microwaveprop.Weather
@band_options [
{"10 GHz", "10000"},
{"24 GHz", "24000"},
{"47 GHz", "47000"},
{"68 GHz", "68000"},
{"76 GHz", "75000"},
{"122 GHz", "122000"},
{"134 GHz", "134000"},
{"241 GHz", "241000"}
]
@band_options BandConfig.band_options()
@url_params ~w(source destination band src_height_ft dst_height_ft tx_power_dbm src_gain_dbi dst_gain_dbi)
@defaults %{

View file

@ -6,18 +6,12 @@ defmodule MicrowavepropWeb.RoverLive do
"""
use MicrowavepropWeb, :live_view
alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Radio.CallsignClient
alias Microwaveprop.Radio.Maidenhead
alias Microwaveprop.Rover.Coverage
@band_options [
{"10 GHz", "10000"},
{"24 GHz", "24000"},
{"47 GHz", "47000"},
{"76 GHz", "75000"},
{"122 GHz", "122000"},
{"241 GHz", "241000"}
]
@band_options BandConfig.band_options()
@impl true
def mount(_params, _session, socket) do

View file

@ -3,25 +3,13 @@ defmodule MicrowavepropWeb.SubmitLive do
use MicrowavepropWeb, :live_view
use LiveStash
alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Contact
alias Microwaveprop.Radio.CsvImport
alias Microwaveprop.Workers.ContactWeatherEnqueueWorker
@band_options [
{"1296 MHz", "1296"},
{"2304 MHz", "2304"},
{"3456 MHz", "3456"},
{"5760 MHz", "5760"},
{"10 GHz", "10000"},
{"24 GHz", "24000"},
{"47 GHz", "47000"},
{"68 GHz", "68000"},
{"76 GHz", "75000"},
{"122 GHz", "122000"},
{"134 GHz", "134000"},
{"241 GHz", "241000"}
]
@band_options BandConfig.band_options()
@mode_options ~w(CW SSB FM FT8 FT4)

View file

@ -3,7 +3,7 @@ defmodule Microwaveprop.Propagation.BandConfigTest do
alias Microwaveprop.Propagation.BandConfig
@all_freqs [10_000, 24_000, 47_000, 68_000, 75_000, 122_000, 134_000, 241_000]
@all_freqs [902, 1_296, 2_304, 3_456, 5_760, 10_000, 24_000, 47_000, 68_000, 75_000, 122_000, 134_000, 241_000]
describe "get/1" do
test "returns config for 10 GHz" do
@ -98,9 +98,9 @@ defmodule Microwaveprop.Propagation.BandConfigTest do
end
describe "all_bands/0" do
test "returns 8 bands" do
test "returns 13 bands" do
bands = BandConfig.all_bands()
assert length(bands) == 8
assert length(bands) == 13
end
test "bands are sorted by frequency" do
@ -275,10 +275,16 @@ defmodule Microwaveprop.Propagation.BandConfigTest do
assert BandConfig.get(10_000).humidity_effect == :beneficial
end
test "all other bands have harmful humidity effect" do
for freq <- @all_freqs -- [10_000] do
config = BandConfig.get(freq)
assert config.humidity_effect == :harmful, "#{freq} should be :harmful"
test "sub-10 GHz bands are beneficial, 24+ GHz are harmful" do
beneficial = [902, 1_296, 2_304, 3_456, 5_760, 10_000]
harmful = @all_freqs -- beneficial
for freq <- beneficial do
assert BandConfig.get(freq).humidity_effect == :beneficial, "#{freq} should be :beneficial"
end
for freq <- harmful do
assert BandConfig.get(freq).humidity_effect == :harmful, "#{freq} should be :harmful"
end
end
end

View file

@ -5,7 +5,7 @@ defmodule Microwaveprop.PropagationTest do
alias Microwaveprop.Propagation.GridScore
describe "score_grid_point/4" do
test "scores a single point for all 8 bands" do
test "scores a single point for all bands" do
hrrr_profile = %{
surface_temp_c: 25.0,
surface_dewpoint_c: 18.0,
@ -25,7 +25,7 @@ defmodule Microwaveprop.PropagationTest do
valid_time = ~U[2026-07-15 13:00:00Z]
results = Propagation.score_grid_point(hrrr_profile, valid_time, 33.0, -97.0)
assert length(results) == 8
assert length(results) == 13
Enum.each(results, fn result ->
assert result.score >= 0 and result.score <= 100