Short-circuit RangeEstimate below 5.76 GHz to stop browser lockup

For a 1 W / 432 MHz beacon, RangeEstimate.estimate/1 was returning
~126 k cells because free-space loss dominates over atmospheric
loss at low UHF, letting the rx_dbm filter pass almost the whole
bbox. Jason-encoding that payload into the map's data-cells
attribute was locking up browsers on the beacon detail page for
low-band beacons even though the coverage toggle is already
disabled for them. Return an empty estimate below the same 5760
MHz threshold the UI already enforces.
This commit is contained in:
Graham McIntire 2026-04-14 16:56:23 -05:00
parent dc7bb5e110
commit d5480ba676
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 50 additions and 0 deletions

View file

@ -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

View file

@ -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