diff --git a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex index 570e18d3..9781b670 100644 --- a/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex +++ b/lib/microwaveprop/workers/contact_weather_enqueue_worker.ex @@ -77,17 +77,36 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do # mark_hrrr_status!/3 flips hrrr_queued on the contact row. Actual # scheduling happens via HrrrPointEnqueuer.enqueue_for_contacts/1 # above. Returns [] for pre-coverage contacts (NARR's territory) so - # the status stays :unavailable. + # the status stays :unavailable, and [] for contacts whose HRRR + # profiles are already fully present in the DB so mark_hrrr_status!/3 + # flips them directly to :complete — otherwise backfill re-flags the + # contact as :queued every cron cycle forever (HrrrPointEnqueuer + # sees 0 missing points, inserts nothing, but the status stays + # :queued and the contact is picked up again next tick). defp hrrr_placeholder_jobs(contacts) do Enum.flat_map(contacts, fn contact -> cond do is_nil(contact.pos1) -> [] NarrClient.in_coverage?(contact.qso_timestamp) -> [] + hrrr_data_fully_present?(contact) -> [] true -> [contact.id] end end) end + defp hrrr_data_fully_present?(contact) do + alias Microwaveprop.Weather.HrrrClient + + rounded = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) + + contact + |> Radio.contact_path_points() + |> Enum.all?(fn {lat, lon} -> + {rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) + Weather.has_hrrr_profile?(rlat, rlon, rounded) + end) + end + defp mark_enrichment_statuses(contact, types, jobs_by_type) do ids = [contact.id] diff --git a/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs b/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs index d74e8a10..d7b124b1 100644 --- a/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs +++ b/test/microwaveprop/workers/contact_weather_enqueue_worker_test.exs @@ -282,6 +282,29 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorkerTest do assert [] = Repo.all(from t in "hrrr_fetch_tasks", select: t.id) end + test "enqueue_for_contact marks hrrr_status :complete when all path points already have profiles" do + # Regression: ~22k contacts stuck at :queued forever because + # HrrrPointEnqueuer saw 0 missing points (data already landed) + # and inserted nothing, but hrrr_placeholder_jobs always returned + # [contact.id] so mark_hrrr_status!/3 re-flagged the row as + # :queued every backfill cron tick. Contacts with full data must + # flip to :complete. + # grid2 = nil prevents Radio.ensure_positions! from deriving pos2, + # so contact_path_points returns only the pos1 single-point form. + contact = + create_contact(%{pos1: %{"lat" => 32.907, "lon" => -97.038}, pos2: nil, grid2: nil}) + + rounded = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) + + # Seed an hrrr_profile at the exact HRRR grid cell for pos1 + {rlat, rlon} = Weather.round_to_hrrr_grid(32.907, -97.038) + {:ok, _} = Weather.upsert_hrrr_profile(%{lat: rlat, lon: rlon, valid_time: rounded, profile: []}) + + :ok = ContactWeatherEnqueueWorker.enqueue_for_contact(contact, [:hrrr]) + + assert %Contact{hrrr_status: :complete} = Repo.get!(Contact, contact.id) + end + test "re-enqueuing the same valid_time UPSERT-unions the points" do import Ecto.Query