fix(rover-planning): two-line station, drop noise columns, fix verdict badge
Path table now shows callsign on top with grid muted below, and the detail-only Min clearance / Diffraction columns are gone. The verdict badge matched lowercase 'clear/blocked/marginal' but the worker stores uppercase 'CLEAR/BLOCKED/FRESNEL_PARTIAL/FRESNEL_MINOR' from TerrainAnalysis, so every cell silently rendered empty. Updated the guard clauses to the actual upstream strings and added Fresnel variants.
This commit is contained in:
parent
bd77d1a5b0
commit
d3b96725c8
2 changed files with 105 additions and 37 deletions
|
|
@ -150,19 +150,24 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
~H|<span class="badge badge-ghost badge-sm">Pending</span>|
|
~H|<span class="badge badge-ghost badge-sm">Pending</span>|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp verdict_badge("clear") do
|
defp verdict_badge("CLEAR") do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
~H|<span class="badge badge-success badge-xs">Clear</span>|
|
~H|<span class="badge badge-success badge-xs">Clear</span>|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp verdict_badge("blocked") do
|
defp verdict_badge("BLOCKED") do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
~H|<span class="badge badge-error badge-xs">Blocked</span>|
|
~H|<span class="badge badge-error badge-xs">Blocked</span>|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp verdict_badge("marginal") do
|
defp verdict_badge("FRESNEL_PARTIAL") do
|
||||||
assigns = %{}
|
assigns = %{}
|
||||||
~H|<span class="badge badge-warning badge-xs">Marginal</span>|
|
~H|<span class="badge badge-warning badge-xs">Fresnel partial</span>|
|
||||||
|
end
|
||||||
|
|
||||||
|
defp verdict_badge("FRESNEL_MINOR") do
|
||||||
|
assigns = %{}
|
||||||
|
~H|<span class="badge badge-info badge-xs">Fresnel minor</span>|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp verdict_badge(_), do: ""
|
defp verdict_badge(_), do: ""
|
||||||
|
|
@ -209,32 +214,27 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
|
|
||||||
defp format_distance(_), do: "—"
|
defp format_distance(_), do: "—"
|
||||||
|
|
||||||
defp format_db(nil), do: "—"
|
# Two-line station identity for the path table. Returns
|
||||||
defp format_db(value) when is_number(value), do: "#{Float.round(value * 1.0, 1)} dB"
|
# `{primary, secondary}` — primary is the callsign (or grid when no
|
||||||
|
# callsign), secondary is a Maidenhead grid (stored or derived from
|
||||||
|
# lat/lon) when it adds info beyond `primary`.
|
||||||
|
defp station_lines(%{station: %{} = station}), do: station_lines_for(station)
|
||||||
|
defp station_lines(_), do: {"—", nil}
|
||||||
|
|
||||||
defp format_meters(nil), do: "—"
|
defp station_lines_for(%{callsign: c} = s) when is_binary(c) and c != "", do: {c, station_grid(s)}
|
||||||
defp format_meters(value) when is_number(value), do: "#{Float.round(value * 1.0, 1)} m"
|
|
||||||
|
|
||||||
# Grid+callsign-first station label. Stations entered as raw lat/lon
|
defp station_lines_for(%{grid: g}) when is_binary(g) and g != "", do: {g, nil}
|
||||||
# have no callsign or grid stored, so derive a 6-char grid from the
|
|
||||||
# coords — that's what the user wants to see, not bare decimals.
|
|
||||||
defp station_label(%{station: %{} = station}), do: station_label_for(station)
|
|
||||||
defp station_label(_), do: "—"
|
|
||||||
|
|
||||||
defp station_label_for(%{callsign: c, grid: g}) when is_binary(c) and c != "" and is_binary(g) and g != "",
|
defp station_lines_for(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon),
|
||||||
do: "#{c} · #{g}"
|
do: {Maidenhead.from_latlon(lat, lon, 6), nil}
|
||||||
|
|
||||||
defp station_label_for(%{callsign: c, lat: lat, lon: lon})
|
defp station_lines_for(_), do: {"—", nil}
|
||||||
when is_binary(c) and c != "" and is_number(lat) and is_number(lon),
|
|
||||||
do: "#{c} · #{Maidenhead.from_latlon(lat, lon, 6)}"
|
|
||||||
|
|
||||||
defp station_label_for(%{callsign: c}) when is_binary(c) and c != "", do: c
|
defp station_grid(%{grid: g}) when is_binary(g) and g != "", do: g
|
||||||
defp station_label_for(%{grid: g}) when is_binary(g) and g != "", do: g
|
|
||||||
|
|
||||||
defp station_label_for(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon),
|
defp station_grid(%{lat: lat, lon: lon}) when is_number(lat) and is_number(lon), do: Maidenhead.from_latlon(lat, lon, 6)
|
||||||
do: Maidenhead.from_latlon(lat, lon, 6)
|
|
||||||
|
|
||||||
defp station_label_for(_), do: "—"
|
defp station_grid(_), do: nil
|
||||||
|
|
||||||
# Endpoint string used for /path?destination=…. Prefers callsign, then
|
# Endpoint string used for /path?destination=…. Prefers callsign, then
|
||||||
# any stored or derived grid (PathLive's LocationResolver re-resolves
|
# any stored or derived grid (PathLive's LocationResolver re-resolves
|
||||||
|
|
@ -440,8 +440,6 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
<th>Station</th>
|
<th>Station</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Distance</th>
|
<th>Distance</th>
|
||||||
<th>Min clearance</th>
|
|
||||||
<th>Diffraction</th>
|
|
||||||
<th>Verdict</th>
|
<th>Verdict</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -454,17 +452,15 @@ defmodule MicrowavepropWeb.RoverPlanningLive.Show do
|
||||||
]}
|
]}
|
||||||
title={path_url(@mission, path) && "Open in Path Calculator"}
|
title={path_url(@mission, path) && "Open in Path Calculator"}
|
||||||
>
|
>
|
||||||
<td class="font-mono text-xs">{station_label(path)}</td>
|
<td class="font-mono text-xs">
|
||||||
|
<% {primary, secondary} = station_lines(path) %>
|
||||||
|
<div>{primary}</div>
|
||||||
|
<div :if={secondary} class="opacity-60">{secondary}</div>
|
||||||
|
</td>
|
||||||
<td>{status_badge(path.status)}</td>
|
<td>{status_badge(path.status)}</td>
|
||||||
<td class="text-xs">
|
<td class="text-xs">
|
||||||
{if path.result, do: format_distance(path.result["distance_km"]), else: "—"}
|
{if path.result, do: format_distance(path.result["distance_km"]), else: "—"}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-xs">
|
|
||||||
{if path.result, do: format_meters(path.result["min_clearance_m"]), else: "—"}
|
|
||||||
</td>
|
|
||||||
<td class="text-xs">
|
|
||||||
{if path.result, do: format_db(path.result["diffraction_db"]), else: "—"}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
{if path.result, do: verdict_badge(path.result["verdict"]), else: "—"}
|
{if path.result, do: verdict_badge(path.result["verdict"]), else: "—"}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do
|
||||||
refute html =~ ~s(<td class="font-mono text-xs">33.189, -96.452</td>)
|
refute html =~ ~s(<td class="font-mono text-xs">33.189, -96.452</td>)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "station label shows callsign + grid together when both are known", %{conn: conn} do
|
test "station cell renders callsign + grid as separate lines", %{conn: conn} do
|
||||||
user = AccountsFixtures.user_fixture()
|
user = AccountsFixtures.user_fixture()
|
||||||
mission = create_mission(user, "Callsign-grid mission")
|
mission = create_mission(user, "Callsign-grid mission")
|
||||||
|
|
||||||
|
|
@ -125,9 +125,81 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do
|
||||||
|
|
||||||
{:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
{:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
||||||
|
|
||||||
# The path table should display "AA5C · EM13se" so the operator can
|
# Callsign and grid both appear, on separate lines (callsign primary,
|
||||||
# see both at a glance — not just one or the other and not lat/lon.
|
# grid as a muted secondary). The legacy one-liner "AA5C · EM13se"
|
||||||
assert html =~ "AA5C · EM13se"
|
# no longer appears anywhere.
|
||||||
|
assert html =~ "AA5C"
|
||||||
|
assert html =~ "EM13se"
|
||||||
|
refute html =~ "AA5C · EM13se"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "verdict column renders the badge for completed paths", %{conn: conn} do
|
||||||
|
user = AccountsFixtures.user_fixture()
|
||||||
|
mission = create_mission(user, "Verdict mission")
|
||||||
|
|
||||||
|
[path | _] = Repo.all(Path)
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
path
|
||||||
|
|> Ecto.Changeset.change(%{
|
||||||
|
status: :complete,
|
||||||
|
result: %{
|
||||||
|
"distance_km" => 12.0,
|
||||||
|
"verdict" => "CLEAR",
|
||||||
|
"min_clearance_m" => 50.0,
|
||||||
|
"diffraction_db" => 0.0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
{:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
||||||
|
assert html =~ "Clear"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "verdict column maps BLOCKED + FRESNEL_PARTIAL to badges", %{conn: conn} do
|
||||||
|
user = AccountsFixtures.user_fixture()
|
||||||
|
|
||||||
|
mission =
|
||||||
|
create_mission(user, "Blocked mission",
|
||||||
|
locations: [
|
||||||
|
%{lat: 32.0, lon: -97.0, status: :good},
|
||||||
|
%{lat: 33.0, lon: -96.0, status: :good}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
[p1, p2] = Repo.all(Path)
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
p1
|
||||||
|
|> Ecto.Changeset.change(%{
|
||||||
|
status: :complete,
|
||||||
|
result: %{"distance_km" => 8.0, "verdict" => "BLOCKED"}
|
||||||
|
})
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
{:ok, _} =
|
||||||
|
p2
|
||||||
|
|> Ecto.Changeset.change(%{
|
||||||
|
status: :complete,
|
||||||
|
result: %{"distance_km" => 9.0, "verdict" => "FRESNEL_PARTIAL"}
|
||||||
|
})
|
||||||
|
|> Repo.update()
|
||||||
|
|
||||||
|
{:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
||||||
|
assert html =~ "Blocked"
|
||||||
|
assert html =~ "Fresnel"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "path table omits min clearance + diffraction columns", %{conn: conn} do
|
||||||
|
user = AccountsFixtures.user_fixture()
|
||||||
|
mission = create_mission(user, "Trim columns mission")
|
||||||
|
|
||||||
|
{:ok, _lv, html} = live(conn, ~p"/rover-planning/#{mission.id}")
|
||||||
|
# Header columns relevant to a planning summary: Station / Status /
|
||||||
|
# Distance / Verdict. Min clearance + diffraction are detail-level
|
||||||
|
# values better viewed on the Path Calculator itself.
|
||||||
|
refute html =~ "Min clearance"
|
||||||
|
refute html =~ "Diffraction"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "path rows link to /path with mission params prefilled", %{conn: conn} do
|
test "path rows link to /path with mission params prefilled", %{conn: conn} do
|
||||||
|
|
@ -198,7 +270,7 @@ defmodule MicrowavepropWeb.RoverPlanningLiveTest do
|
||||||
"distance_km" => 0,
|
"distance_km" => 0,
|
||||||
"min_clearance_m" => 0,
|
"min_clearance_m" => 0,
|
||||||
"diffraction_db" => 0,
|
"diffraction_db" => 0,
|
||||||
"verdict" => "clear"
|
"verdict" => "CLEAR"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue