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.
This commit is contained in:
Graham McIntire 2026-04-02 14:42:45 -05:00
parent 1c2879c466
commit 66de041886
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59

View file

@ -2,8 +2,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do
@moduledoc false @moduledoc false
use MicrowavepropWeb, :live_view use MicrowavepropWeb, :live_view
require Logger
alias Microwaveprop.Propagation.BandConfig alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Propagation.Scorer alias Microwaveprop.Propagation.Scorer
alias Microwaveprop.Radio alias Microwaveprop.Radio
@ -16,6 +14,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do
alias Microwaveprop.Workers.HrrrFetchWorker alias Microwaveprop.Workers.HrrrFetchWorker
alias Microwaveprop.Workers.TerrainProfileWorker alias Microwaveprop.Workers.TerrainProfileWorker
require Logger
@earth_radius_m 6_371_000.0 @earth_radius_m 6_371_000.0
@impl true @impl true
@ -32,7 +32,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
solar = load_solar(contact) solar = load_solar(contact)
hrrr_path = Weather.hrrr_profiles_for_path(contact) hrrr_path = Weather.hrrr_profiles_for_path(contact)
hrrr = List.first(hrrr_path) 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) terrain = Terrain.get_terrain_profile(contact.id)
contact = maybe_enqueue_terrain(terrain, contact) contact = maybe_enqueue_terrain(terrain, contact)
contact = maybe_enqueue_weather(weather, contact) contact = maybe_enqueue_weather(weather, contact)
@ -58,6 +58,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
obs_sort_order: "asc", obs_sort_order: "asc",
sounding_sort_by: "station_name", sounding_sort_by: "station_name",
sounding_sort_order: "asc", sounding_sort_order: "asc",
hrrr_jobs_ahead: hrrr_jobs_ahead,
expanded_soundings: MapSet.new() expanded_soundings: MapSet.new()
)} )}
end end
@ -150,6 +151,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
assign(socket, assign(socket,
contact: contact, contact: contact,
hrrr: hrrr, hrrr: hrrr,
hrrr_jobs_ahead: 0,
elevation_profile: elevation_profile, elevation_profile: elevation_profile,
propagation_analysis: propagation_analysis propagation_analysis: propagation_analysis
)} )}
@ -297,34 +299,75 @@ defmodule MicrowavepropWeb.ContactLive.Show do
end end
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 defp maybe_enqueue_hrrr(hrrr, contact) when not is_nil(hrrr) do
Radio.set_enrichment_status!([contact.id], :hrrr_status, :complete) Radio.set_enrichment_status!([contact.id], :hrrr_status, :complete)
{hrrr, %{contact | hrrr_status: :complete}} {hrrr, %{contact | hrrr_status: :complete}, 0}
end end
defp maybe_enqueue_hrrr(nil, contact) do defp maybe_enqueue_hrrr(nil, contact) do
lat = contact.pos1 && contact.pos1["lat"] lat = contact.pos1 && contact.pos1["lat"]
lon = contact.pos1 && (contact.pos1["lon"] || contact.pos1["lng"]) lon = contact.pos1 && (contact.pos1["lon"] || contact.pos1["lng"])
if lat && lon && contact.hrrr_status in [:pending, :failed] do cond do
valid_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) lat && lon && contact.hrrr_status in [:pending, :failed] ->
{rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) valid_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp)
{rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon)
Oban.insert( {:ok, job} =
HrrrFetchWorker.new(%{ Oban.insert(
"lat" => rlat, HrrrFetchWorker.new(%{
"lon" => rlon, "lat" => rlat,
"valid_time" => DateTime.to_iso8601(valid_time) "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) if job_id, do: count_hrrr_jobs_ahead(job_id), else: 0
{nil, %{contact | hrrr_status: :queued}}
else
{nil, contact}
end
end end
defp load_solar(contact) do defp load_solar(contact) do
@ -548,8 +591,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do
<div class="text-sm text-base-content/50 italic"> <div class="text-sm text-base-content/50 italic">
<%= if @contact.terrain_status == :queued do %> <%= if @contact.terrain_status == :queued do %>
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<span class="loading loading-spinner loading-sm"></span> <span class="loading loading-spinner loading-sm"></span> Computing terrain profile...
Computing terrain profile...
</span> </span>
<% else %> <% else %>
No terrain profile available. No terrain profile available.
@ -722,7 +764,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
<% :queued -> %> <% :queued -> %>
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<span class="loading loading-spinner loading-sm"></span> <span class="loading loading-spinner loading-sm"></span>
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 %>
</span> </span>
<% :unavailable -> %> <% :unavailable -> %>
HRRR data unavailable for this contact time. HRRR data unavailable for this contact time.
@ -825,8 +870,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
<div>{length(@surface_observations)} stations</div> <div>{length(@surface_observations)} stations</div>
<%= if @surface_observations != [] do %> <%= if @surface_observations != [] do %>
<div> <div>
{Calendar.strftime(hd(@surface_observations).observed_at, "%H:%M")} {Calendar.strftime(hd(@surface_observations).observed_at, "%H:%M")} {Calendar.strftime(
{Calendar.strftime(List.last(@surface_observations).observed_at, "%H:%M")} UTC List.last(@surface_observations).observed_at,
"%H:%M"
)} UTC
</div> </div>
<% end %> <% end %>
</div> </div>