diff --git a/lib/microwaveprop/beacons/range_estimate.ex b/lib/microwaveprop/beacons/range_estimate.ex index a5182811..50969b8a 100644 --- a/lib/microwaveprop/beacons/range_estimate.ex +++ b/lib/microwaveprop/beacons/range_estimate.ex @@ -35,6 +35,15 @@ defmodule Microwaveprop.Beacons.RangeEstimate do # HRRR grid step (degrees). @grid_step 0.125 + # Below this frequency free-space loss dominates over atmospheric + # loss, so `rx_dbm` exceeds the detection floor across tens of + # thousands of km². On a 1 W / 432 MHz beacon the estimate produced + # ~126k cells — Jason-encoding that into the map's data attribute + # locked up browsers in prod. Return an empty estimate below the + # threshold so the coverage toggle (also gated at 5760 MHz in the + # LiveView) never has a payload to render. + @min_supported_mhz 5760 + @doc """ Convert a power in milliwatts to dBm. Returns `-999.9` for non-positive input. """ @@ -60,6 +69,10 @@ defmodule Microwaveprop.Beacons.RangeEstimate do detection floor. """ @spec estimate(struct()) :: map() + def estimate(%{frequency_mhz: freq_mhz} = beacon) when freq_mhz < @min_supported_mhz do + empty_estimate(beacon) + end + def estimate(beacon) do band_mhz = nearest_band_mhz(beacon.frequency_mhz) band_config = BandConfig.get(band_mhz) @@ -123,6 +136,25 @@ defmodule Microwaveprop.Beacons.RangeEstimate do } end + defp empty_estimate(beacon) do + band_mhz = nearest_band_mhz(beacon.frequency_mhz) + band_config = BandConfig.get(band_mhz) + + %{ + beacon_id: beacon.id, + band_mhz: band_mhz, + band_label: band_config && band_config.label, + center_score: 50, + valid_time: nil, + eirp_dbm: Float.round(mw_to_dbm(beacon.power_mw || 0.0), 1), + atm_per_km: 0.0, + grid_step: @grid_step, + max_range_km: 0.0, + cells: [], + tiers: @tiers + } + end + # --- path loss ------------------------------------------------------------ defp received_dbm(_eirp, _f, _atm, +0.0, _score), do: 999.0 diff --git a/test/microwaveprop/beacons/range_estimate_test.exs b/test/microwaveprop/beacons/range_estimate_test.exs index e1a47551..d558e7b9 100644 --- a/test/microwaveprop/beacons/range_estimate_test.exs +++ b/test/microwaveprop/beacons/range_estimate_test.exs @@ -112,5 +112,23 @@ defmodule Microwaveprop.Beacons.RangeEstimateTest do assert lon_mod < 0.01 end end + + test "short-circuits with empty cells for beacons below 5.76 GHz" do + # 432 MHz with 1 W at unrestricted range would generate >100k cells + # because atmospheric loss is negligible — encoding that payload + # into the map's data attribute was locking up browsers in prod. + result = RangeEstimate.estimate(beacon(frequency_mhz: 432.38, power_mw: 1000.0)) + assert result.cells == [] + assert result.max_range_km == 0.0 + # Still reports the nearest configured band for display purposes. + assert is_integer(result.band_mhz) + end + + test "short-circuits at exactly 5760 MHz threshold" do + above = RangeEstimate.estimate(beacon(frequency_mhz: 5760.0, power_mw: 1000.0)) + below = RangeEstimate.estimate(beacon(frequency_mhz: 5759.9, power_mw: 1000.0)) + assert above.cells != [] + assert below.cells == [] + end end end