diff --git a/lib/microwaveprop/rover_planning.ex b/lib/microwaveprop/rover_planning.ex index 75433546..a9ef5231 100644 --- a/lib/microwaveprop/rover_planning.ex +++ b/lib/microwaveprop/rover_planning.ex @@ -175,17 +175,20 @@ defmodule Microwaveprop.RoverPlanning do end @doc """ - Walks every mission and re-enqueues any (rover_location × station) - pair whose `Path` is missing or not `:complete`. Existing complete - rows are left alone — the worker also short-circuits on `:complete`, - so this is idempotent and safe to run on a cron. + Walks every mission and re-enqueues any (rover_location × station × + band) pair whose `Path` is missing, not `:complete`, OR whose + cached `result` lacks the `"term"` blob (paths persisted before + PathCompute extraction shipped — the row click on /rover-planning + needs that blob to render `/path` from cache instead of recomputing). - Use case: a rolling deploy or a transient elevation-API failure left - some missions with `:pending` / `:failed` entries; the next backfill - tick recovers them without operator action. + Idempotent: the path-profile worker short-circuits on `:complete`, + and we only nudge :complete rows back to :pending when their term + is missing. Safe to run on a cron. """ @spec backfill_paths() :: :ok def backfill_paths do + reset_paths_missing_term() + Mission |> Repo.all() |> Repo.preload(stations: from(s in Station, order_by: s.position)) @@ -194,6 +197,20 @@ defmodule Microwaveprop.RoverPlanning do :ok end + # Flip :complete paths whose result map has no "term" key back to + # :pending so the next reconcile pass picks them up. JSONB + # `key_exists` is `?` in pg, but Ecto's fragment uses `?` as a + # placeholder — use the function form `jsonb_exists/2` instead. + defp reset_paths_missing_term do + Repo.update_all( + from(p in Path, + where: p.status == :complete, + where: not fragment("jsonb_exists(?, 'term')", p.result) + ), + set: [status: :pending] + ) + end + defp enqueue_paths_for(%Mission{} = mission) do mission |> desired_tuples()