From 66de041886da3c83a4549ef5a101c64ed2ac6bda Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 2 Apr 2026 14:42:45 -0500 Subject: [PATCH] Show HRRR queue position on contact detail page When HRRR data is loading, display how many jobs are ahead in the queue so users can estimate wait time. --- .../live/contact_live/show.ex | 95 ++++++++++++++----- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 26116642..7665887f 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -2,8 +2,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do @moduledoc false use MicrowavepropWeb, :live_view - require Logger - alias Microwaveprop.Propagation.BandConfig alias Microwaveprop.Propagation.Scorer alias Microwaveprop.Radio @@ -16,6 +14,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do alias Microwaveprop.Workers.HrrrFetchWorker alias Microwaveprop.Workers.TerrainProfileWorker + require Logger + @earth_radius_m 6_371_000.0 @impl true @@ -32,7 +32,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do solar = load_solar(contact) hrrr_path = Weather.hrrr_profiles_for_path(contact) hrrr = List.first(hrrr_path) - {hrrr, contact} = maybe_enqueue_hrrr(hrrr, contact) + {hrrr, contact, hrrr_jobs_ahead} = maybe_enqueue_hrrr(hrrr, contact) terrain = Terrain.get_terrain_profile(contact.id) contact = maybe_enqueue_terrain(terrain, contact) contact = maybe_enqueue_weather(weather, contact) @@ -58,6 +58,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do obs_sort_order: "asc", sounding_sort_by: "station_name", sounding_sort_order: "asc", + hrrr_jobs_ahead: hrrr_jobs_ahead, expanded_soundings: MapSet.new() )} end @@ -150,6 +151,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do assign(socket, contact: contact, hrrr: hrrr, + hrrr_jobs_ahead: 0, elevation_profile: elevation_profile, propagation_analysis: propagation_analysis )} @@ -297,34 +299,75 @@ defmodule MicrowavepropWeb.ContactLive.Show do end end - defp maybe_enqueue_hrrr(hrrr, %{hrrr_status: :complete} = contact), do: {hrrr, contact} + defp maybe_enqueue_hrrr(hrrr, %{hrrr_status: :complete} = contact), do: {hrrr, contact, 0} defp maybe_enqueue_hrrr(hrrr, contact) when not is_nil(hrrr) do Radio.set_enrichment_status!([contact.id], :hrrr_status, :complete) - {hrrr, %{contact | hrrr_status: :complete}} + {hrrr, %{contact | hrrr_status: :complete}, 0} end defp maybe_enqueue_hrrr(nil, contact) do lat = contact.pos1 && contact.pos1["lat"] lon = contact.pos1 && (contact.pos1["lon"] || contact.pos1["lng"]) - if lat && lon && contact.hrrr_status in [:pending, :failed] do - valid_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) - {rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) + cond do + lat && lon && contact.hrrr_status in [:pending, :failed] -> + valid_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) + {rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) - Oban.insert( - HrrrFetchWorker.new(%{ - "lat" => rlat, - "lon" => rlon, - "valid_time" => DateTime.to_iso8601(valid_time) - }) + {:ok, job} = + Oban.insert( + HrrrFetchWorker.new(%{ + "lat" => rlat, + "lon" => rlon, + "valid_time" => DateTime.to_iso8601(valid_time) + }) + ) + + Radio.set_enrichment_status!([contact.id], :hrrr_status, :queued) + jobs_ahead = if job.id, do: count_hrrr_jobs_ahead(job.id), else: 0 + {nil, %{contact | hrrr_status: :queued}, jobs_ahead} + + lat && lon && contact.hrrr_status == :queued -> + valid_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) + {rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) + {nil, contact, count_hrrr_jobs_ahead_by_args(rlat, rlon, valid_time)} + + true -> + {nil, contact, 0} + end + end + + defp count_hrrr_jobs_ahead(job_id) do + import Ecto.Query + + Microwaveprop.Repo.one( + from(j in Oban.Job, + where: j.queue == "hrrr", + where: j.state in ["available", "scheduled", "retryable", "executing"], + where: j.id < ^job_id, + select: count(j.id) + ) + ) + end + + defp count_hrrr_jobs_ahead_by_args(lat, lon, valid_time) do + import Ecto.Query + + args = %{"lat" => lat, "lon" => lon, "valid_time" => DateTime.to_iso8601(valid_time)} + + job_id = + Microwaveprop.Repo.one( + from(j in Oban.Job, + where: j.queue == "hrrr", + where: j.state in ["available", "scheduled", "retryable", "executing"], + where: fragment("? @> ?", j.args, ^args), + select: j.id, + limit: 1 + ) ) - Radio.set_enrichment_status!([contact.id], :hrrr_status, :queued) - {nil, %{contact | hrrr_status: :queued}} - else - {nil, contact} - end + if job_id, do: count_hrrr_jobs_ahead(job_id), else: 0 end defp load_solar(contact) do @@ -548,8 +591,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
<%= if @contact.terrain_status == :queued do %> - - Computing terrain profile... + Computing terrain profile... <% else %> No terrain profile available. @@ -722,7 +764,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do <% :queued -> %> - Fetching HRRR atmospheric data (this will take a while)... + Fetching HRRR atmospheric data + <%= if @hrrr_jobs_ahead > 0 do %> + ({@hrrr_jobs_ahead} {if @hrrr_jobs_ahead == 1, do: "job", else: "jobs"} ahead in queue) + <% end %> <% :unavailable -> %> HRRR data unavailable for this contact time. @@ -825,8 +870,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
{length(@surface_observations)} stations
<%= if @surface_observations != [] do %>
- {Calendar.strftime(hd(@surface_observations).observed_at, "%H:%M")} – - {Calendar.strftime(List.last(@surface_observations).observed_at, "%H:%M")} UTC + {Calendar.strftime(hd(@surface_observations).observed_at, "%H:%M")} – {Calendar.strftime( + List.last(@surface_observations).observed_at, + "%H:%M" + )} UTC
<% end %>