Each fix is covered by a regression test that fails on `main` and passes on this commit. Round 1 (initial review): * propagation: thread `latitude` into the conditions map so `score_season/4` actually picks up regional multipliers * hrrr_client / fetcher.rs: `nearest_hrrr_hour` rounds DOWN, never at a future cycle that NOAA hasn't published yet * radio: spherical-vector great-circle midpoint replaces the arithmetic mean — anti-meridian paths no longer fold to Greenwich * weather: `reconcile_weather_statuses` scales the longitude band by `1 / cos(lat)` so the bbox stays ~150 km wide at every latitude * radio/maidenhead: clamp 90°/180° below the field-bucket overflow so `from_latlon` never emits invalid characters like 'S' * prop_grid_rs/pipeline: merge HRRR + NEXRAD-derived rain rates and read `best_duct_freq_ghz` into `best_duct_band_ghz` so the Native Duct Boost actually fires * propagation/region (Elixir + Rust): inclusive upper bounds so points exactly at lat_max get the regional multiplier * weather/sounding_params (Elixir + Rust): drop the 10 m gradient floor so HRRR's thin near-surface layers stop hiding sharp ducts * weather/sounding_params: when the profile ends inside a duct, finalize it with the highest sample as the top instead of throwing it away (Rust port already correct) Round 2 (post-fix sweep): * radio + commercial: single canonical haversine in Radio (atan2 form); Commercial delegates instead of carrying a second copy that could disagree at threshold distances * prop_grid_rs/profiles_file: `snap_coords` matches Elixir's step-aware snap (`round(coord/0.125) * 0.125`, then 3-dp round) so Rust-keyed and Elixir-keyed profile maps land on the same cell * weather/grib2/wgrib2: `parse_lon_val_segment` uses `Float.parse` uniformly — wgrib2 dropping the trailing `.0` from a longitude no longer crashes the whole chain step
65 lines
2 KiB
Elixir
65 lines
2 KiB
Elixir
defmodule Microwaveprop.Propagation.RegionTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Microwaveprop.Propagation.Region
|
|
|
|
describe "for_point/2" do
|
|
test "Dallas TX is in southern_plains" do
|
|
assert Region.for_point(32.8, -96.8) == :southern_plains
|
|
end
|
|
|
|
test "Houston TX is in gulf_coast" do
|
|
assert Region.for_point(29.8, -95.4) == :gulf_coast
|
|
end
|
|
|
|
test "Des Moines IA is in corn_belt" do
|
|
assert Region.for_point(41.6, -93.6) == :corn_belt
|
|
end
|
|
|
|
test "Tucson AZ is in desert_southwest" do
|
|
assert Region.for_point(32.2, -110.9) == :desert_southwest
|
|
end
|
|
|
|
test "Seattle WA is in pacific_northwest" do
|
|
assert Region.for_point(47.6, -122.3) == :pacific_northwest
|
|
end
|
|
|
|
test "New York NY is in northeast" do
|
|
assert Region.for_point(40.7, -74.0) == :northeast
|
|
end
|
|
|
|
test "Atlanta GA is in southeast" do
|
|
assert Region.for_point(33.7, -84.4) == :southeast
|
|
end
|
|
|
|
test "Denver CO falls into a region" do
|
|
region = Region.for_point(39.7, -104.9)
|
|
assert is_atom(region)
|
|
end
|
|
|
|
test "returns :other for points outside CONUS" do
|
|
assert Region.for_point(19.0, -155.0) == :other
|
|
end
|
|
|
|
test "includes points sitting exactly on the upper latitude boundary" do
|
|
# corn_belt is defined as 38.0..48.0 lat; pacific_northwest as
|
|
# 42.0..50.0. The exclusive upper bound dropped points exactly at
|
|
# 48.0 / 50.0 into `:other` and stripped their seasonal multiplier.
|
|
assert Region.for_point(48.0, -93.0) != :other
|
|
assert Region.for_point(50.0, -122.0) != :other
|
|
end
|
|
end
|
|
|
|
describe "seasonal_adjustment/2" do
|
|
test "returns a multiplier for known regions and months" do
|
|
adj = Region.seasonal_adjustment(:gulf_coast, 8)
|
|
assert is_float(adj)
|
|
# Gulf coast August should be drier → higher seasonal score than default
|
|
assert adj >= 0.8 and adj <= 1.3
|
|
end
|
|
|
|
test "returns 1.0 for unknown regions" do
|
|
assert Region.seasonal_adjustment(:other, 6) == 1.0
|
|
end
|
|
end
|
|
end
|