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
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
<div class="text-sm text-base-content/50 italic">
<%= if @contact.terrain_status == :queued do %>
<span class="flex items-center gap-2">
<span class="loading loading-spinner loading-sm"></span>
Computing terrain profile...
<span class="loading loading-spinner loading-sm"></span> Computing terrain profile...
</span>
<% else %>
No terrain profile available.
@ -722,7 +764,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
<% :queued -> %>
<span class="flex items-center gap-2">
<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>
<% :unavailable -> %>
HRRR data unavailable for this contact time.
@ -825,8 +870,10 @@ defmodule MicrowavepropWeb.ContactLive.Show do
<div>{length(@surface_observations)} stations</div>
<%= if @surface_observations != [] do %>
<div>
{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
</div>
<% end %>
</div>