feat(rover): show current pipeline step next to Calculate spinner
Compute.run now accepts a `progress` callback and reports human-readable labels before each pipeline step (loading grid, looking up elevation, checking road access, scoring cells, etc.). RoverLive feeds it via send/2 to itself and renders the latest label as small text to the left of the Calculate button while it's running.
This commit is contained in:
parent
88d785be50
commit
9f8c4d3b36
3 changed files with 80 additions and 16 deletions
|
|
@ -90,6 +90,7 @@ defmodule Microwaveprop.Rover.Compute do
|
|||
Keyword.get(deps, :buildings_clutter_lookup, &default_building_clutter/1)
|
||||
|
||||
hilltop_snap = Keyword.get(deps, :hilltop_snap, &Hilltop.snap/1)
|
||||
progress = Keyword.get(deps, :progress, fn _ -> :ok end)
|
||||
|
||||
%{
|
||||
home: home,
|
||||
|
|
@ -105,6 +106,7 @@ defmodule Microwaveprop.Rover.Compute do
|
|||
radius_km = max_distance_km * 1.0
|
||||
bbox = bbox_around(home, radius_km)
|
||||
|
||||
progress.("Loading propagation grid")
|
||||
raw_cells = time_step("scores_at", fn -> scores_at.(band_mhz, valid_time, bbox) end)
|
||||
|
||||
in_radius =
|
||||
|
|
@ -117,17 +119,26 @@ defmodule Microwaveprop.Rover.Compute do
|
|||
)
|
||||
|
||||
points = Enum.map(in_radius, &{&1.lat, &1.lon})
|
||||
progress.("Looking up elevation")
|
||||
elev_map = time_step("elev_lookup", fn -> elev_lookup.(points) end)
|
||||
progress.("Loading building footprints")
|
||||
_ = time_step("buildings_load", fn -> BuildingsLoader.ensure_loaded_for_bbox(bbox) end)
|
||||
progress.("Computing path clearance")
|
||||
clearance_map = time_step("clearance", fn -> clearance_lookup.(in_radius, selected_stations) end)
|
||||
progress.("Measuring terrain prominence")
|
||||
prominence_map = time_step("prominence", fn -> prominence_lookup.(in_radius) end)
|
||||
|
||||
road_map =
|
||||
if Application.get_env(:microwaveprop, :rover_road_proximity_enabled, true),
|
||||
do: time_step("road_proximity", fn -> fetch_road_map(road_lookup, in_radius, bbox) end),
|
||||
else: %{}
|
||||
if Application.get_env(:microwaveprop, :rover_road_proximity_enabled, true) do
|
||||
progress.("Checking road access")
|
||||
time_step("road_proximity", fn -> fetch_road_map(road_lookup, in_radius, bbox) end)
|
||||
else
|
||||
%{}
|
||||
end
|
||||
|
||||
progress.("Scanning building clutter")
|
||||
clutter_map = time_step("building_clutter", fn -> buildings_clutter_lookup.(in_radius) end)
|
||||
progress.("Scoring cells")
|
||||
|
||||
home_elev = home.elev_m || 0
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
station_form_error: nil,
|
||||
home_form_error: nil,
|
||||
scoring_loading: false,
|
||||
scoring_step: nil,
|
||||
url_loaded?: false
|
||||
)}
|
||||
end
|
||||
|
|
@ -324,7 +325,12 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(top_candidates: cands, selected_candidate: nil, scoring_loading: false)
|
||||
|> assign(
|
||||
top_candidates: cands,
|
||||
selected_candidate: nil,
|
||||
scoring_loading: false,
|
||||
scoring_step: nil
|
||||
)
|
||||
|> apply_scoring_warnings(warnings)
|
||||
|> push_event("candidate_paths", %{candidate: nil, paths: []})
|
||||
|> push_event("rover_results", %{
|
||||
|
|
@ -338,10 +344,15 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(top_candidates: [], scoring_loading: false)
|
||||
|> assign(top_candidates: [], scoring_loading: false, scoring_step: nil)
|
||||
|> put_flash(:error, "Calculate failed: #{inspect(reason)}")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:rover_progress, label}, socket) do
|
||||
{:noreply, assign(socket, scoring_step: label)}
|
||||
end
|
||||
|
||||
defp apply_scoring_warnings(socket, []), do: clear_flash(socket)
|
||||
|
||||
defp apply_scoring_warnings(socket, warnings) do
|
||||
|
|
@ -693,10 +704,12 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
|
||||
defp start_scoring(socket) do
|
||||
args = scoring_args(socket)
|
||||
pid = self()
|
||||
progress = fn label -> send(pid, {:rover_progress, label}) end
|
||||
|
||||
socket
|
||||
|> assign(scoring_loading: true)
|
||||
|> start_async(:scoring, fn -> Compute.run(args) end)
|
||||
|> assign(scoring_loading: true, scoring_step: "Starting…")
|
||||
|> start_async(:scoring, fn -> Compute.run(args, progress: progress) end)
|
||||
end
|
||||
|
||||
defp scoring_args(socket) do
|
||||
|
|
@ -908,15 +921,23 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
{band_label(@band)} · Forecast +{@forecast_hour}h
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="calculate"
|
||||
class="btn btn-primary btn-sm"
|
||||
disabled={@scoring_loading}
|
||||
>
|
||||
<span :if={@scoring_loading} class="loading loading-spinner loading-xs"></span>
|
||||
{if @scoring_loading, do: "Calculating…", else: "Calculate"}
|
||||
</button>
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
:if={@scoring_loading and @scoring_step}
|
||||
class="text-xs text-base-content/60 font-mono"
|
||||
>
|
||||
{@scoring_step}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="calculate"
|
||||
class="btn btn-primary btn-sm"
|
||||
disabled={@scoring_loading}
|
||||
>
|
||||
<span :if={@scoring_loading} class="loading loading-spinner loading-xs"></span>
|
||||
{if @scoring_loading, do: "Calculating…", else: "Calculate"}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-1 min-h-0">
|
||||
|
|
|
|||
|
|
@ -129,6 +129,38 @@ defmodule Microwaveprop.Rover.ComputeTest do
|
|||
assert ref_score - tall_score >= 5.5
|
||||
end
|
||||
|
||||
test "calls progress callback with each pipeline step label" 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)
|
||||
|
||||
test_pid = self()
|
||||
progress = fn label -> send(test_pid, {:progress, label}) end
|
||||
|
||||
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: 0
|
||||
},
|
||||
scores_at: fn _, _, _ -> grid end,
|
||||
elev_lookup: fn points -> Map.new(points, fn p -> {p, 250} end) end,
|
||||
clearance_lookup: fn _, _ -> %{} end,
|
||||
prominence_lookup: fn cells -> Map.new(cells, fn c -> {{c.lat, c.lon}, 0} end) end,
|
||||
road_lookup: fn _, _ -> {:error, :stubbed} end,
|
||||
progress: progress
|
||||
)
|
||||
|
||||
assert_received {:progress, "Loading propagation grid"}
|
||||
assert_received {:progress, "Looking up elevation"}
|
||||
assert_received {:progress, "Computing path clearance"}
|
||||
assert_received {:progress, "Scoring cells"}
|
||||
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}]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue