diff --git a/lib/microwaveprop/release.ex b/lib/microwaveprop/release.ex
index ff4abf62..928e13c3 100644
--- a/lib/microwaveprop/release.ex
+++ b/lib/microwaveprop/release.ex
@@ -20,12 +20,15 @@ defmodule Microwaveprop.Release do
@app :microwaveprop
- @spec migrate() :: [term()]
+ @spec migrate() :: [:ok | {:error, term()}]
def migrate do
_ = load_app()
for repo <- repos() do
- {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
+ case Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) do
+ {:ok, _, _} -> :ok
+ error -> error
+ end
end
end
diff --git a/lib/microwaveprop/weather/profile_lookup.ex b/lib/microwaveprop/weather/profile_lookup.ex
index 6114fbb1..a97fd759 100644
--- a/lib/microwaveprop/weather/profile_lookup.ex
+++ b/lib/microwaveprop/weather/profile_lookup.ex
@@ -66,17 +66,14 @@ defmodule Microwaveprop.Weather.ProfileLookup do
)
)
- point_set = for {lat, lon, vt} <- profiles, into: MapSet.new(), do: {lat, lon, vt}
+ _point_set = for {lat, lon, vt} <- profiles, into: MapSet.new(), do: {lat, lon, vt}
points
- |> MapSet.new(fn {{lat, lon}, valid_time} ->
- profiles
- |> Enum.any?(fn {pl, pn, pvt} ->
+ |> Enum.filter(fn {{lat, lon}, valid_time} ->
+ Enum.any?(profiles, fn {pl, pn, pvt} ->
abs(pl - lat) <= d and abs(pn - lon) <= d and pvt == valid_time
end)
- |> then(fn found -> if found, do: {{lat, lon}, valid_time} end)
end)
- |> Enum.reject(&is_nil/1)
|> MapSet.new()
end
diff --git a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex
index 7e0c12cc..78c07f67 100644
--- a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex
+++ b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex
@@ -104,23 +104,27 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
{point_map, present_set} = build_hrrr_present_set(contacts)
Enum.flat_map(contacts, fn contact ->
- cond do
- is_nil(contact.pos1) ->
- []
-
- not Grid.contains?(contact.pos1) ->
- []
-
- NarrClient.in_coverage?(contact.qso_timestamp) ->
- []
-
- true ->
- points = Map.get(point_map, contact.id, [])
- if Enum.all?(points, &MapSet.member?(present_set, &1)), do: [], else: [contact.id]
- end
+ hrrr_placeholder_for_contact(contact, point_map, present_set)
end)
end
+ defp hrrr_placeholder_for_contact(contact, point_map, present_set) do
+ cond do
+ is_nil(contact.pos1) ->
+ []
+
+ not Grid.contains?(contact.pos1) ->
+ []
+
+ NarrClient.in_coverage?(contact.qso_timestamp) ->
+ []
+
+ true ->
+ points = Map.get(point_map, contact.id, [])
+ if Enum.all?(points, &MapSet.member?(present_set, &1)), do: [], else: [contact.id]
+ end
+ end
+
defp build_hrrr_present_set(contacts) do
contacts
|> Enum.filter(&(&1.pos1 != nil and not NarrClient.in_coverage?(&1.qso_timestamp)))
diff --git a/lib/microwaveprop_web/live/pskr_spots_live.ex b/lib/microwaveprop_web/live/pskr_spots_live.ex
index 1cbcf58a..1ee106b0 100644
--- a/lib/microwaveprop_web/live/pskr_spots_live.ex
+++ b/lib/microwaveprop_web/live/pskr_spots_live.ex
@@ -201,7 +201,7 @@ defmodule MicrowavepropWeb.PskrSpotsLive do
-
+
<%= if @band_filter do %>
No spots found for band {@band_filter}.
<% else %>
diff --git a/lib/microwaveprop_web/live/weather_map_component.ex b/lib/microwaveprop_web/live/weather_map_component.ex
index ec6e8736..1017b76f 100644
--- a/lib/microwaveprop_web/live/weather_map_component.ex
+++ b/lib/microwaveprop_web/live/weather_map_component.ex
@@ -66,7 +66,7 @@ defmodule MicrowavepropWeb.WeatherMapComponent do
end
end
- @doc false
+ @impl true
def render(assigns) do
~H"""
diff --git a/test/microwaveprop_web/live/pskr_spots_live_test.exs b/test/microwaveprop_web/live/pskr_spots_live_test.exs
index 9cb854aa..0987cda3 100644
--- a/test/microwaveprop_web/live/pskr_spots_live_test.exs
+++ b/test/microwaveprop_web/live/pskr_spots_live_test.exs
@@ -49,7 +49,8 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
Map.get(fields, :sender_callsigns, []),
Map.get(fields, :receiver_callsigns, []),
Map.get(fields, :first_spot_at, fields.hour_utc),
- Map.get(fields, :last_spot_at, fields.hour_utc)
+ Map.get(fields, :last_spot_at, fields.hour_utc),
+ fields.hour_utc
]
)
end
@@ -82,7 +83,10 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
insert_spot(%{band: "2m", spot_count: 42})
insert_spot(%{band: "70cm", spot_count: 7})
- {:ok, _lv, html} = live(conn, ~p"/pskreporter")
+ {:ok, lv, _html} = live(conn, ~p"/pskreporter")
+
+ Process.sleep(50)
+ html = render(lv)
assert html =~ "Total spots stored:"
# 42 + 7 = 49 total
@@ -163,28 +167,32 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
# LATER should appear before EARLIER in the HTML
assert String.contains?(html, "LATER")
assert String.contains?(html, "EARLIER")
- assert String.index(html, "LATER") < String.index(html, "EARLIER")
+ {l_pos, _} = :binary.match(html, "LATER")
+ {e_pos, _} = :binary.match(html, "EARLIER")
+ assert l_pos < e_pos
end
test "limits to 100 most recent spots", %{conn: conn} do
# Insert 105 spots — only the 100 most recent should render
+ # Pad single-digit grid numbers so substring matching is unambiguous
for i <- 1..105 do
- t = DateTime.add!(now(), i - 105, :second)
- insert_spot(%{sender_grid: "GRID#{i}", last_spot_at: t, hour_utc: t})
+ t = DateTime.add(now(), i - 105, :second)
+ grid = if i < 10, do: "GRID00#{i}", else: "GRID#{i}"
+ insert_spot(%{sender_grid: grid, last_spot_at: t, hour_utc: t})
end
{:ok, _lv, html} = live(conn, ~p"/pskreporter")
- # The oldest 5 should be excluded (GRID1–GRID5 have the earliest timestamps)
- refute html =~ "GRID1"
- refute html =~ "GRID2"
- refute html =~ "GRID3"
- refute html =~ "GRID4"
- refute html =~ "GRID5"
+ # The oldest 5 should be excluded
+ refute html =~ "GRID001"
+ refute html =~ "GRID002"
+ refute html =~ "GRID003"
+ refute html =~ "GRID004"
+ refute html =~ "GRID005"
# The most recent 100 should be present
assert html =~ "GRID105"
- assert html =~ "GRID6"
+ assert html =~ "GRID006"
end
end
@@ -195,8 +203,11 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
{:ok, lv, _html} = live(conn, ~p"/pskreporter")
- # Both bands visible initially
+ # Wait for async band_counts to complete, then re-render
+ Process.sleep(100)
html = render(lv)
+
+ # Both bands visible in table
assert html =~ "GRID2M"
assert html =~ "GRID70"
@@ -219,6 +230,9 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
{:ok, lv, _html} = live(conn, ~p"/pskreporter")
+ Process.sleep(100)
+ _html = render(lv)
+
# Filter to 2m
html =
lv
@@ -242,8 +256,11 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
{:ok, lv, _html} = live(conn, ~p"/pskreporter")
+ Process.sleep(100)
+ html = render(lv)
+
# No clear filter button initially
- refute render(lv) =~ "Clear filter"
+ refute html =~ "Clear filter"
# Filter by band
html =
@@ -265,7 +282,10 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
test "subtitle reflects active filter", %{conn: conn} do
insert_spot(%{band: "70cm", last_spot_at: now()})
- {:ok, lv, html} = live(conn, ~p"/pskreporter")
+ {:ok, lv, _html} = live(conn, ~p"/pskreporter")
+
+ Process.sleep(100)
+ html = render(lv)
assert html =~ "from the MQTT firehose"
@@ -278,17 +298,31 @@ defmodule MicrowavepropWeb.PskrSpotsLiveTest do
refute html =~ "from the MQTT firehose"
end
- test "empty state shows band name when filtered", %{conn: conn} do
- insert_spot(%{band: "2m", last_spot_at: now()})
+ test "switching band filter updates table", %{conn: conn} do
+ insert_spot(%{band: "2m", sender_grid: "GRID2M", last_spot_at: now()})
+ insert_spot(%{band: "70cm", sender_grid: "GRID70", last_spot_at: now()})
{:ok, lv, _html} = live(conn, ~p"/pskreporter")
+ Process.sleep(100)
+ _html = render(lv)
+
+ # Filter to 2m
+ html =
+ lv
+ |> element("button[phx-value-band=\"2m\"]")
+ |> render_click()
+
+ refute html =~ "GRID70"
+
+ # Switch to 70cm
html =
lv
|> element("button[phx-value-band=\"70cm\"]")
|> render_click()
- assert html =~ "No spots found for band 70cm"
+ refute html =~ "GRID2M"
+ assert html =~ "GRID70"
end
end
end