From cfaef287e4376f9275ab91f24282e5cfed19a014 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 7 Apr 2026 16:39:24 -0500 Subject: [PATCH] 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. --- lib/microwaveprop/propagation/band_config.ex | 146 ++++++++++++++++++ lib/microwaveprop_web/live/path_live.ex | 11 +- lib/microwaveprop_web/live/rover_live.ex | 10 +- lib/microwaveprop_web/live/submit_live.ex | 16 +- .../propagation/band_config_test.exs | 20 ++- test/microwaveprop/propagation_test.exs | 4 +- 6 files changed, 166 insertions(+), 41 deletions(-) diff --git a/lib/microwaveprop/propagation/band_config.ex b/lib/microwaveprop/propagation/band_config.ex index e0d7c144..850487fd 100644 --- a/lib/microwaveprop/propagation/band_config.ex +++ b/lib/microwaveprop/propagation/band_config.ex @@ -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 diff --git a/lib/microwaveprop_web/live/path_live.ex b/lib/microwaveprop_web/live/path_live.ex index 8160c44e..99234509 100644 --- a/lib/microwaveprop_web/live/path_live.ex +++ b/lib/microwaveprop_web/live/path_live.ex @@ -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 %{ diff --git a/lib/microwaveprop_web/live/rover_live.ex b/lib/microwaveprop_web/live/rover_live.ex index 7bb7cfe9..ba6e7e62 100644 --- a/lib/microwaveprop_web/live/rover_live.ex +++ b/lib/microwaveprop_web/live/rover_live.ex @@ -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 diff --git a/lib/microwaveprop_web/live/submit_live.ex b/lib/microwaveprop_web/live/submit_live.ex index 3e95f309..3cd31ae3 100644 --- a/lib/microwaveprop_web/live/submit_live.ex +++ b/lib/microwaveprop_web/live/submit_live.ex @@ -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) diff --git a/test/microwaveprop/propagation/band_config_test.exs b/test/microwaveprop/propagation/band_config_test.exs index babbcbe3..c9911b40 100644 --- a/test/microwaveprop/propagation/band_config_test.exs +++ b/test/microwaveprop/propagation/band_config_test.exs @@ -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 diff --git a/test/microwaveprop/propagation_test.exs b/test/microwaveprop/propagation_test.exs index 3c489647..4c5903ae 100644 --- a/test/microwaveprop/propagation_test.exs +++ b/test/microwaveprop/propagation_test.exs @@ -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