From ee732d03b1b04ec3f917e99720af1eea29a7fa13 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Mon, 20 Apr 2026 16:37:13 -0500 Subject: [PATCH] fix(contact-detail): re-enqueue HRRR on every page view when profile missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'Fetching HRRR atmospheric data' spinner stayed up forever for contacts stuck in :queued without a matching hrrr_fetch_tasks row. Ways a contact lands in that state: - HrrrPointEnqueuer.enqueue/1 raises, the rescue swallows the error and returns {:ok, 0}, but the caller still marked the contact :queued before it noticed the failure. - Rust worker marks a (valid_time) task :done with a points array that never included this contact's point, so no profile is ever written for it. - hrrr_fetch_tasks row is cleaned up by retention before the contact page loads. Drop the hrrr_status != :queued short-circuit and always call the enqueuer when the profile is nil. The ON CONFLICT clause in HrrrPointEnqueuer.enqueue/1 unions points into the existing row, resets :done/:failed rows back to :queued with attempt=0, and no-ops when the point is already present — so re-entering on every page view is cheap and self-healing. --- lib/microwaveprop_web/live/contact_live/show.ex | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 3361b004..5b36cbe4 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -748,14 +748,27 @@ defmodule MicrowavepropWeb.ContactLive.Show do lat = contact.pos1 && contact.pos1["lat"] lon = contact.pos1 && contact.pos1["lon"] - if lat && lon && contact.hrrr_status != :queued do + # Always re-enqueue when there's no profile on this page render. + # The `hrrr_status == :queued` guard used to short-circuit here, but + # contacts can end up stuck in :queued without a live task row + # (HrrrPointEnqueuer swallowing an exception, Rust worker marking a + # task :done with a points array that never contained this contact's + # point, or a later retention prune removing the row). Re-entering + # the upsert is idempotent — the ON CONFLICT clause in + # HrrrPointEnqueuer unions points, resets :done/:failed rows back + # to :queued, and no-ops if the point + valid_time already match. + if lat && lon do valid_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) {rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) # Phase 3 Stream C: route retries through hrrr_fetch_tasks so the # Rust hrrr-point-worker picks them up alongside backfill work. {:ok, _} = HrrrPointEnqueuer.enqueue(%{valid_time => [{rlat, rlon}]}) - Radio.set_enrichment_status!([contact.id], :hrrr_status, :queued) + + if contact.hrrr_status != :queued do + Radio.set_enrichment_status!([contact.id], :hrrr_status, :queued) + end + {nil, %{contact | hrrr_status: :queued}} else {nil, contact}