prop/test/microwaveprop/rover/compute_test.exs
Graham McIntire eac817cd57
feat(rover): prefer broad hilltops near roads as ideal candidates
- Local prominence: each cell's elevation vs. its 8-neighbor ring
  (~1.3 km radius) feeds a small additive bonus so broad hilltops
  rank above isolated SRTM voxels
- Road proximity: one Overpass call per Calculate fetches drivable
  ways inside the bbox; cells beyond ~0.5 km of any road get a
  light dB penalty, gated off in tests
2026-04-25 17:45:48 -05:00

116 lines
3.4 KiB
Elixir

defmodule Microwaveprop.Rover.ComputeTest do
use ExUnit.Case, async: true
alias Microwaveprop.Rover.Compute
defp build_grid(home_lat, home_lon) do
# 3x3 grid of cells around home spaced 0.1 deg apart
for dlat <- [-0.1, 0.0, 0.1], dlon <- [-0.1, 0.0, 0.1] do
%{
lat: Float.round(home_lat + dlat, 4),
lon: Float.round(home_lon + dlon, 4),
score: 80
}
end
end
test "returns top_candidates ordered by score desc with required fields" do
home = %{lat: 33.0, lon: -96.0, elev_m: 200}
stations = [%{callsign: "W5LUA", lat: 33.10, lon: -96.625, selected: true}]
grid = build_grid(home.lat, home.lon)
scores_at = fn _band, _t, _bbox -> grid end
elev_lookup = fn points -> Map.new(points, fn p -> {p, 250} end) end
clearance_lookup = fn _cells, _stations -> %{} end
prominence_lookup = fn cells -> Map.new(cells, fn c -> {{c.lat, c.lon}, 0} end) end
road_lookup = fn _cells, _bbox -> {:error, :stubbed} end
args = %{
home: home,
stations: stations,
band_mhz: 10_000,
valid_time: ~U[2026-04-25 12:00:00Z],
mode: :ssb,
max_distance_km: 65.0,
min_elev_gain: 0
}
result =
Compute.run(args,
scores_at: scores_at,
elev_lookup: elev_lookup,
clearance_lookup: clearance_lookup,
prominence_lookup: prominence_lookup,
road_lookup: road_lookup
)
assert is_map(result)
assert Map.has_key?(result, :cells)
assert Map.has_key?(result, :top_candidates)
assert is_list(result.cells)
assert length(result.top_candidates) <= 5
refute result.top_candidates == []
# Sorted by score desc.
scores = Enum.map(result.top_candidates, & &1.score)
assert scores == Enum.sort(scores, :desc)
# Required candidate fields.
cand = hd(result.top_candidates)
for key <- [
:grid,
:lat,
:lon,
:elev_m,
:drive_min,
:score,
:tier_color,
:distance_km,
:bearing_compass,
:name
] do
assert Map.has_key?(cand, key), "expected candidate to have key #{inspect(key)}"
end
assert is_binary(cand.grid)
assert is_binary(cand.tier_color)
assert is_binary(cand.bearing_compass)
assert is_binary(cand.name)
end
test "filters cells failing min_elev_gain" do
home = %{lat: 33.0, lon: -96.0, elev_m: 500}
stations = [%{callsign: "W5LUA", lat: 33.1, lon: -96.6, selected: true}]
grid = build_grid(home.lat, home.lon)
# All elevations below home — nothing should pass min_elev_gain=200.
scores_at = fn _band, _t, _bbox -> grid end
elev_lookup = fn points -> Map.new(points, fn p -> {p, 100} end) end
clearance_lookup = fn _cells, _stations -> %{} end
prominence_lookup = fn cells -> Map.new(cells, fn c -> {{c.lat, c.lon}, 0} end) end
road_lookup = fn _cells, _bbox -> {:error, :stubbed} end
result =
Compute.run(
%{
home: home,
stations: stations,
band_mhz: 10_000,
valid_time: ~U[2026-04-25 12:00:00Z],
mode: :ssb,
max_distance_km: 65.0,
min_elev_gain: 200
},
scores_at: scores_at,
elev_lookup: elev_lookup,
clearance_lookup: clearance_lookup,
prominence_lookup: prominence_lookup,
road_lookup: road_lookup
)
assert result.cells == []
assert result.top_candidates == []
end
end