Live-update contact detail page when enrichment completes

- HRRR and terrain workers broadcast via PubSub on completion
- Contact show page subscribes and recomputes elevation profile,
  propagation analysis, and HRRR data on receipt
- Page updates in-place without reload when background jobs finish
This commit is contained in:
Graham McIntire 2026-04-02 08:17:43 -05:00
parent 0503fb3799
commit d207863b15
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
4 changed files with 66 additions and 0 deletions

View file

@ -34,6 +34,7 @@ defmodule Microwaveprop.Workers.HrrrFetchWorker do
{:ok, grid_data} ->
Enum.each(grid_data, fn {{lat, lon}, data} ->
store_profile(lat, lon, valid_time, data)
broadcast_enrichment(lat, lon, valid_time)
end)
Logger.info("HRRR batch: saved #{map_size(grid_data)} profiles for #{valid_time_str}")
@ -59,6 +60,7 @@ defmodule Microwaveprop.Workers.HrrrFetchWorker do
case HrrrClient.fetch_profile(lat, lon, valid_time) do
{:ok, data} ->
store_profile(lat, lon, valid_time, data)
broadcast_enrichment(lat, lon, valid_time)
:ok
{:error, reason} ->
@ -123,4 +125,12 @@ defmodule Microwaveprop.Workers.HrrrFetchWorker do
duct_characteristics: params.duct_characteristics
})
end
defp broadcast_enrichment(lat, lon, valid_time) do
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"contact_enrichment:hrrr",
{:hrrr_ready, %{lat: lat, lon: lon, valid_time: valid_time}}
)
end
end

View file

@ -56,6 +56,12 @@ defmodule Microwaveprop.Workers.TerrainProfileWorker do
verdict: analysis.verdict
})
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"contact_enrichment:#{qso_id}",
{:terrain_ready, qso_id}
)
:ok
{:error, reason} ->

View file

@ -21,6 +21,11 @@ defmodule MicrowavepropWeb.ContactLive.Show do
def mount(%{"id" => id}, _session, socket) do
contact = Radio.get_contact!(id)
if connected?(socket) do
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:#{contact.id}")
Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "contact_enrichment:hrrr")
end
weather = load_weather(contact)
solar = load_solar(contact)
hrrr_path = Weather.hrrr_profiles_for_path(contact)
@ -106,6 +111,47 @@ defmodule MicrowavepropWeb.ContactLive.Show do
)}
end
@impl true
def handle_info({:terrain_ready, _qso_id}, socket) do
contact = socket.assigns.contact
terrain = Terrain.get_terrain_profile(contact.id)
soundings = socket.assigns.soundings
hrrr_path = Weather.hrrr_profiles_for_path(contact)
hrrr = List.first(hrrr_path)
elevation_profile = compute_elevation_profile(contact, hrrr_path, soundings)
propagation_analysis = build_propagation_analysis(contact, hrrr, terrain, elevation_profile, soundings)
{:noreply,
assign(socket,
terrain: terrain,
elevation_profile: elevation_profile,
propagation_analysis: propagation_analysis
)}
end
def handle_info({:hrrr_ready, _info}, socket) do
contact = socket.assigns.contact
hrrr_path = Weather.hrrr_profiles_for_path(contact)
hrrr = List.first(hrrr_path)
if hrrr do
soundings = socket.assigns.soundings
terrain = socket.assigns.terrain
elevation_profile = compute_elevation_profile(contact, hrrr_path, soundings)
propagation_analysis = build_propagation_analysis(contact, hrrr, terrain, elevation_profile, soundings)
{:noreply,
assign(socket,
hrrr: hrrr,
hrrr_status: :loaded,
elevation_profile: elevation_profile,
propagation_analysis: propagation_analysis
)}
else
{:noreply, socket}
end
end
defp toggle_sort(current_field, current_order, new_field) do
if current_field == new_field && current_order == "asc",
do: {new_field, "desc"},

View file

@ -11,6 +11,10 @@ defmodule MicrowavepropWeb.ContactLiveTest do
Req.Test.json(conn, [])
end)
Req.Test.stub(Microwaveprop.Weather.HrrrClient, fn conn ->
Plug.Conn.send_resp(conn, 404, "not found")
end)
:ok
end