- Add on_the_air boolean to beacons (default true); surfaced as a checkbox on the form, a badge on the index, and in the detail list. - Render a Leaflet map on the beacon show page with a marker at the beacon's lat/lon tooltipped with callsign + frequency. Marker is green when on air, gray when off. - Compute and draw a reception-range estimate as concentric signal- strength rings. New Microwaveprop.Beacons.RangeEstimate solves a link budget (FSPL + O2/H2O absorption from BandConfig) at five RX thresholds (-100 to -145 dBm), then scales by 0.5 + score/100 from the latest Propagation.point_detail at the beacon's grid square, so current HRRR conditions shift the rings in or out. - Re-enable the hourly PropagationGridWorker cron and freshness monitor in dev.exs so dev actually has HRRR-backed scores to feed the new estimator.
96 lines
2.8 KiB
Elixir
96 lines
2.8 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.0,
|
|
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, score, and a list of rings" do
|
|
result = RangeEstimate.estimate(beacon())
|
|
|
|
assert result.band_mhz == 10_000
|
|
assert is_integer(result.score) or is_float(result.score)
|
|
assert is_float(result.eirp_dbm)
|
|
assert is_list(result.rings)
|
|
assert length(result.rings) > 0
|
|
end
|
|
|
|
test "rings are ordered strongest-to-weakest with increasing radius" do
|
|
result = RangeEstimate.estimate(beacon())
|
|
radii = Enum.map(result.rings, & &1.radius_km)
|
|
assert radii == Enum.sort(radii)
|
|
end
|
|
|
|
test "each ring carries a label, color, threshold, and radius_km" do
|
|
result = RangeEstimate.estimate(beacon())
|
|
|
|
for ring <- result.rings do
|
|
assert is_binary(ring.label)
|
|
assert is_binary(ring.color)
|
|
assert is_number(ring.rx_dbm)
|
|
assert is_float(ring.radius_km)
|
|
assert ring.radius_km >= 0.0
|
|
end
|
|
end
|
|
|
|
test "a more powerful beacon produces larger rings" do
|
|
weak = RangeEstimate.estimate(beacon(power_mw: 10.0))
|
|
strong = RangeEstimate.estimate(beacon(power_mw: 10_000.0))
|
|
|
|
weakest_weak = List.last(weak.rings).radius_km
|
|
weakest_strong = List.last(strong.rings).radius_km
|
|
assert weakest_strong > weakest_weak
|
|
end
|
|
|
|
test "uses a default score of 50 when no propagation data exists" do
|
|
result = RangeEstimate.estimate(beacon())
|
|
# No scores in test DB → score defaults
|
|
assert result.score == 50
|
|
assert result.valid_time == nil
|
|
end
|
|
end
|
|
end
|