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.
116 lines
3.5 KiB
Elixir
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
|