prop/lib/microwaveprop/rover_planning/path.ex
Graham McIntire d1a5442afb
feat(rover-planning): /rover-planning page with path-profile worker
Adds a new globally-scoped, owner-mutable rover-mission tracker:
- /rover-planning paginated table (LiveTable) with View/Edit/Delete
- /rover-planning/new + /:id/edit form: name, band, antenna heights,
  notes, "only check against known good locations" toggle (default on),
  and a dynamic list of stationary stations entered as callsigns,
  Maidenhead grids, or lat,lon pairs (Station changeset geocodes via
  LocationResolver, lat/lon stays editable after add)
- /rover-planning/:id show page renders the station list, scope, and a
  matrix of computed path profiles (distance, min clearance, diffraction,
  verdict) populated as the worker completes each pairing

After save, RoverPlanning enqueues one RoverPathProfileWorker job per
(rover-location × station) pairing. The worker mirrors PathLive's
synchronous compute (ElevationClient + TerrainAnalysis at the mission's
band + heights) and stores the result on the matching path row.
PubSub broadcast on completion lets the show page live-refresh.

Admins can edit/delete any mission; owners can edit their own.
2026-05-03 11:04:11 -05:00

56 lines
1.5 KiB
Elixir

defmodule Microwaveprop.RoverPlanning.Path do
@moduledoc """
A computed (or pending) path-profile result for one rover-location
→ station pairing inside a mission. `result` holds the same shape
produced by `MicrowavepropWeb.PathLive`'s synchronous compute, so
the show page can render it identically.
"""
use Ecto.Schema
import Ecto.Changeset
alias Microwaveprop.Rover.Location
alias Microwaveprop.RoverPlanning.Mission
alias Microwaveprop.RoverPlanning.Station
@statuses [:pending, :computing, :complete, :failed]
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "rover_mission_paths" do
field :status, Ecto.Enum, values: @statuses, default: :pending
field :result, :map
field :error, :string
field :computed_at, :utc_datetime
belongs_to :mission, Mission
belongs_to :rover_location, Location
belongs_to :station, Station
timestamps(type: :utc_datetime)
end
@type t :: %__MODULE__{}
@spec statuses() :: [atom()]
def statuses, do: @statuses
@spec changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
def changeset(path, attrs) do
path
|> cast(attrs, [
:status,
:result,
:error,
:computed_at,
:mission_id,
:rover_location_id,
:station_id
])
|> validate_required([:mission_id, :rover_location_id, :station_id, :status])
|> validate_inclusion(:status, @statuses)
|> unique_constraint([:mission_id, :rover_location_id, :station_id],
name: :rover_mission_paths_unique
)
end
end