prop/test/microwaveprop/beacons/range_estimate_test.exs
Graham McIntire 2c1c221398 Change beacon height_ft from float to integer
Height in feet doesn't need decimal precision. Migrates the DB
column, updates schema type, and strips trailing .0 from form
input so the integer cast succeeds.
2026-04-11 09:44:42 -05:00

116 lines
3.5 KiB
Elixir

defmodule Microwaveprop.Beacons.RangeEstimateTest do
use Microwaveprop.DataCase
alias Microwaveprop.Beacons.Beacon
alias Microwaveprop.Beacons.RangeEstimate
defp beacon(overrides \\ %{}) do
base = %Beacon{
id: "00000000-0000-0000-0000-000000000000",
frequency_mhz: 10_368.1,
callsign: "W5HN",
grid: "EM12",
lat: 32.897,
lon: -97.038,
power_mw: 10_000.0,
height_ft: 100,
on_the_air: true
}
struct!(base, overrides)
end
describe "mw_to_dbm/1" do
test "1 mW is 0 dBm" do
assert_in_delta RangeEstimate.mw_to_dbm(1.0), 0.0, 0.001
end
test "1 W (1000 mW) is 30 dBm" do
assert_in_delta RangeEstimate.mw_to_dbm(1000.0), 30.0, 0.001
end
test "10 W (10_000 mW) is 40 dBm" do
assert_in_delta RangeEstimate.mw_to_dbm(10_000.0), 40.0, 0.001
end
end
describe "nearest_band_mhz/1" do
test "10368.1 MHz maps to 10000 (10 GHz band)" do
assert RangeEstimate.nearest_band_mhz(10_368.1) == 10_000
end
test "24192.1 MHz maps to 24000" do
assert RangeEstimate.nearest_band_mhz(24_192.1) == 24_000
end
test "47088 MHz maps to 47000" do
assert RangeEstimate.nearest_band_mhz(47_088.0) == 47_000
end
end
describe "estimate/1" do
test "returns a map with band info, center score, and per-grid cells" do
result = RangeEstimate.estimate(beacon())
assert result.band_mhz == 10_000
assert is_integer(result.center_score) or is_float(result.center_score)
assert is_float(result.eirp_dbm)
assert is_list(result.cells)
assert length(result.cells) > 0
assert result.grid_step == 0.125
assert is_list(result.tiers)
end
test "each cell carries lat/lon, distance, rx_dbm, tier label/color" do
result = RangeEstimate.estimate(beacon())
for cell <- result.cells do
assert is_float(cell.lat)
assert is_float(cell.lon)
assert is_float(cell.distance_km)
assert is_integer(cell.rx_dbm)
assert is_binary(cell.label)
assert is_binary(cell.color)
# Detection floor enforced
assert cell.rx_dbm >= -145
end
end
test "cells closer to the beacon have stronger rx_dbm" do
result = RangeEstimate.estimate(beacon())
[closest | _] = Enum.sort_by(result.cells, & &1.distance_km)
[farthest | _] = Enum.sort_by(result.cells, &(-&1.distance_km))
assert closest.rx_dbm > farthest.rx_dbm
end
test "a more powerful beacon produces more cells" do
weak = RangeEstimate.estimate(beacon(power_mw: 10.0))
strong = RangeEstimate.estimate(beacon(power_mw: 10_000.0))
assert length(strong.cells) > length(weak.cells)
end
test "uses a default score of 50 when no propagation data exists" do
result = RangeEstimate.estimate(beacon())
# No scores in test DB → center defaults, cells all use 50.
assert result.center_score == 50
assert result.valid_time == nil
# All cells should have score 50 when there's no HRRR data.
assert Enum.all?(result.cells, fn c -> c.score == 50 end)
end
test "cells align with the 0.125° HRRR grid" do
result = RangeEstimate.estimate(beacon())
for cell <- result.cells do
# lat and lon should both be multiples of 0.125
lat_mod = abs(cell.lat / 0.125 - round(cell.lat / 0.125))
lon_mod = abs(cell.lon / 0.125 - round(cell.lon / 0.125))
assert lat_mod < 0.01
assert lon_mod < 0.01
end
end
end
end