Score time-of-day per grid point using longitude/15 solar offset instead of hardcoded CST/CDT. Add PWAT as 10th scoring factor. Refine pressure thresholds. Update ML model and training pipeline to use local solar time.
297 lines
8.6 KiB
Elixir
297 lines
8.6 KiB
Elixir
defmodule Microwaveprop.Propagation.BandConfigTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Microwaveprop.Propagation.BandConfig
|
|
|
|
@all_freqs [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
|
|
config = BandConfig.get(10_000)
|
|
assert config.freq_mhz == 10_000
|
|
assert config.label == "10 GHz"
|
|
assert config.humidity_effect == :beneficial
|
|
assert config.humidity_penalty == 0.0
|
|
assert config.rain_k == 0.010
|
|
assert config.rain_alpha == 1.28
|
|
assert config.typical_range_km == 200
|
|
assert config.extended_range_km == 500
|
|
assert config.exceptional_range_km == 1000
|
|
end
|
|
|
|
test "returns config for 24 GHz" do
|
|
config = BandConfig.get(24_000)
|
|
assert config.freq_mhz == 24_000
|
|
assert config.label == "24 GHz"
|
|
assert config.humidity_effect == :harmful
|
|
assert config.humidity_penalty == 1.6
|
|
assert config.rain_k == 0.070
|
|
assert config.rain_alpha == 1.07
|
|
assert config.typical_range_km == 100
|
|
assert config.extended_range_km == 250
|
|
assert config.exceptional_range_km == 500
|
|
end
|
|
|
|
test "returns config for 47 GHz" do
|
|
config = BandConfig.get(47_000)
|
|
assert config.freq_mhz == 47_000
|
|
assert config.humidity_effect == :harmful
|
|
assert config.humidity_penalty == 1.0
|
|
assert config.rain_k == 0.187
|
|
assert config.rain_alpha == 0.93
|
|
end
|
|
|
|
test "returns config for 68 GHz with atmospheric absorption" do
|
|
config = BandConfig.get(68_000)
|
|
assert config.o2_db_km == 0.90
|
|
assert config.h2o_coeff == 0.007
|
|
assert config.humidity_penalty == 1.4
|
|
assert config.rain_k == 0.310
|
|
assert config.rain_alpha == 0.86
|
|
assert config.typical_range_km == 40
|
|
assert config.extended_range_km == 80
|
|
assert config.exceptional_range_km == 150
|
|
end
|
|
|
|
test "returns config for 75 GHz" do
|
|
config = BandConfig.get(75_000)
|
|
assert config.o2_db_km == 0.012
|
|
assert config.h2o_coeff == 0.006
|
|
assert config.humidity_penalty == 1.2
|
|
assert config.rain_k == 0.345
|
|
assert config.rain_alpha == 0.84
|
|
end
|
|
|
|
test "returns config for 122 GHz" do
|
|
config = BandConfig.get(122_000)
|
|
assert config.o2_db_km == 0.80
|
|
assert config.h2o_coeff == 0.010
|
|
assert config.humidity_penalty == 1.0
|
|
assert config.rain_k == 0.498
|
|
assert config.rain_alpha == 0.77
|
|
end
|
|
|
|
test "returns config for 134 GHz" do
|
|
config = BandConfig.get(134_000)
|
|
assert config.o2_db_km == 0.08
|
|
assert config.h2o_coeff == 0.015
|
|
assert config.humidity_penalty == 1.3
|
|
assert config.rain_k == 0.520
|
|
assert config.rain_alpha == 0.75
|
|
end
|
|
|
|
test "returns config for 241 GHz" do
|
|
config = BandConfig.get(241_000)
|
|
assert config.o2_db_km == 0.08
|
|
assert config.h2o_coeff == 0.30
|
|
assert config.humidity_penalty == 3.0
|
|
assert config.rain_k == 0.550
|
|
assert config.rain_alpha == 0.70
|
|
assert config.typical_range_km == 10
|
|
assert config.extended_range_km == 50
|
|
assert config.exceptional_range_km == 115
|
|
end
|
|
|
|
test "returns nil for unknown band" do
|
|
assert BandConfig.get(999) == nil
|
|
end
|
|
end
|
|
|
|
describe "all_bands/0" do
|
|
test "returns 8 bands" do
|
|
bands = BandConfig.all_bands()
|
|
assert length(bands) == 8
|
|
end
|
|
|
|
test "bands are sorted by frequency" do
|
|
freqs = Enum.map(BandConfig.all_bands(), & &1.freq_mhz)
|
|
assert freqs == @all_freqs
|
|
end
|
|
end
|
|
|
|
describe "all_freqs/0" do
|
|
test "returns sorted frequency list" do
|
|
assert BandConfig.all_freqs() == @all_freqs
|
|
end
|
|
end
|
|
|
|
describe "weights/0" do
|
|
test "returns all 10 factor keys" do
|
|
weights = BandConfig.weights()
|
|
|
|
expected_keys =
|
|
MapSet.new([
|
|
:humidity,
|
|
:time_of_day,
|
|
:td_depression,
|
|
:refractivity,
|
|
:sky,
|
|
:season,
|
|
:wind,
|
|
:rain,
|
|
:pwat,
|
|
:pressure
|
|
])
|
|
|
|
assert MapSet.new(Map.keys(weights)) == expected_keys
|
|
end
|
|
|
|
test "weights sum to 1.0" do
|
|
total = BandConfig.weights() |> Map.values() |> Enum.sum()
|
|
assert_in_delta total, 1.0, 0.001
|
|
end
|
|
|
|
test "individual weights have correct values" do
|
|
weights = BandConfig.weights()
|
|
assert weights.humidity == 0.18
|
|
assert weights.time_of_day == 0.10
|
|
assert weights.td_depression == 0.10
|
|
assert weights.refractivity == 0.08
|
|
assert weights.sky == 0.08
|
|
assert weights.season == 0.08
|
|
assert weights.wind == 0.05
|
|
assert weights.rain == 0.08
|
|
assert weights.pwat == 0.10
|
|
assert weights.pressure == 0.15
|
|
end
|
|
end
|
|
|
|
describe "sunrise_table/0" do
|
|
test "returns 12 monthly values" do
|
|
table = BandConfig.sunrise_table()
|
|
assert length(table) == 12
|
|
end
|
|
|
|
test "returns expected sunrise hours" do
|
|
assert BandConfig.sunrise_table() == [
|
|
7.4,
|
|
7.3,
|
|
7.0,
|
|
6.7,
|
|
6.35,
|
|
6.25,
|
|
6.35,
|
|
6.65,
|
|
6.9,
|
|
7.1,
|
|
7.35,
|
|
7.45
|
|
]
|
|
end
|
|
end
|
|
|
|
describe "tiers/0" do
|
|
test "returns 5 tiers" do
|
|
tiers = BandConfig.tiers()
|
|
assert length(tiers) == 5
|
|
end
|
|
|
|
test "tiers are ordered by threshold descending" do
|
|
thresholds = Enum.map(BandConfig.tiers(), & &1.min_score)
|
|
assert thresholds == [80, 65, 50, 33, 0]
|
|
end
|
|
|
|
test "tier labels and colors are correct" do
|
|
tiers = BandConfig.tiers()
|
|
[excellent, good, marginal, poor, negligible] = tiers
|
|
|
|
assert excellent == %{min_score: 80, label: "EXCELLENT", color: "#00ffa3"}
|
|
assert good == %{min_score: 65, label: "GOOD", color: "#7dffd4"}
|
|
assert marginal == %{min_score: 50, label: "MARGINAL", color: "#ffe566"}
|
|
assert poor == %{min_score: 33, label: "POOR", color: "#ff9044"}
|
|
assert negligible == %{min_score: 0, label: "NEGLIGIBLE", color: "#ff4f4f"}
|
|
end
|
|
end
|
|
|
|
describe "seasonal bases" do
|
|
test "every band has 12 months in seasonal_base" do
|
|
for freq <- @all_freqs do
|
|
config = BandConfig.get(freq)
|
|
assert map_size(config.seasonal_base) == 12, "#{freq} missing months in seasonal_base"
|
|
assert Enum.all?(1..12, &Map.has_key?(config.seasonal_base, &1))
|
|
end
|
|
end
|
|
|
|
test "10 GHz seasonal base peaks in summer" do
|
|
config = BandConfig.get(10_000)
|
|
assert config.seasonal_base[7] == 95
|
|
assert config.seasonal_base[3] == 22
|
|
end
|
|
|
|
test "24 GHz seasonal base peaks in winter" do
|
|
config = BandConfig.get(24_000)
|
|
assert config.seasonal_base[11] == 96
|
|
assert config.seasonal_base[7] == 18
|
|
end
|
|
|
|
test "24 GHz has seasonal adjustments" do
|
|
config = BandConfig.get(24_000)
|
|
assert config.seasonal_adj[7] == -10
|
|
assert config.seasonal_adj[5] == -4
|
|
end
|
|
|
|
test "10 GHz has empty seasonal adjustments" do
|
|
config = BandConfig.get(10_000)
|
|
assert config.seasonal_adj == %{}
|
|
end
|
|
end
|
|
|
|
describe "humidity_beneficial_thresholds/0" do
|
|
test "returns threshold tuples sorted by hour" do
|
|
thresholds = BandConfig.humidity_beneficial_thresholds()
|
|
assert thresholds == [{4, 55}, {7, 70}, {10, 82}, {14, 90}, {18, 95}, {22, 88}]
|
|
end
|
|
end
|
|
|
|
describe "humidity_beneficial_default/0" do
|
|
test "returns 75" do
|
|
assert BandConfig.humidity_beneficial_default() == 75
|
|
end
|
|
end
|
|
|
|
describe "refractivity_thresholds/0" do
|
|
test "returns gradient/score tuples" do
|
|
thresholds = BandConfig.refractivity_thresholds()
|
|
|
|
assert thresholds == [
|
|
{-200, 98, 85},
|
|
{-150, 92, 80},
|
|
{-100, 82, 72},
|
|
{-75, 68, 62},
|
|
{-55, 55, 55},
|
|
{-40, 48, 48}
|
|
]
|
|
end
|
|
end
|
|
|
|
describe "refractivity_default/0" do
|
|
test "returns default scores" do
|
|
assert BandConfig.refractivity_default() == {42, 42}
|
|
end
|
|
end
|
|
|
|
describe "shallow_bl_threshold_m/0" do
|
|
test "returns 300" do
|
|
assert BandConfig.shallow_bl_threshold_m() == 300
|
|
end
|
|
end
|
|
|
|
describe "shallow_bl_score/0" do
|
|
test "returns 82" do
|
|
assert BandConfig.shallow_bl_score() == 82
|
|
end
|
|
end
|
|
|
|
describe "only 10 GHz is beneficial" do
|
|
test "10 GHz has beneficial humidity effect" 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"
|
|
end
|
|
end
|
|
end
|
|
end
|