prop/priv/repo/migrations/20260503190225_add_multi_band_rover_planning.exs
Graham McIntire 2c88fba1df
feat(rover-planning): multi-band missions + smart reconcile
Mission now carries bands_mhz ({:array, :integer}) — operator picks
one or more bands as multi-checkboxes. enqueue_paths_for builds the
cross product (rover x station x band) and persists each tuple as its
own Path row keyed by (mission_id, rover_location_id, station_id,
band_mhz). The path-profile worker reads band_mhz from the path
itself (legacy single-band jobs without band_mhz in args still resolve
to their unique row).

replace_mission_paths/1 is now a thin alias for reconcile_mission_paths/1
which diffs desired vs actual: stale tuples (old band that the user
unchecked, station they removed, rover-site they deleted) get dropped,
new tuples become :pending and enqueue, surviving :complete rows are
left in place — no more wholesale destruction of already-computed
paths on every edit.

The show table gains a Band column, and band_label() in the mission
summary becomes bands_label() (joins the list with commas).
2026-05-03 14:08:49 -05:00

67 lines
1.8 KiB
Elixir

defmodule Microwaveprop.Repo.Migrations.AddMultiBandRoverPlanning do
use Ecto.Migration
def up do
# Mission carries the SET of bands the operator wants to evaluate.
# `band_mhz` stays as a denormalized "primary band" for any legacy
# readers; new code reads `bands_mhz`.
alter table(:rover_missions) do
add :bands_mhz, {:array, :integer}, default: [], null: false
end
execute(
"UPDATE rover_missions SET bands_mhz = ARRAY[band_mhz] WHERE coalesce(array_length(bands_mhz, 1), 0) = 0"
)
# Each computed path is now scoped to a specific band — the worker
# uses this for its loss budget + propagation-score lookup.
alter table(:rover_mission_paths) do
add :band_mhz, :integer
end
execute("""
UPDATE rover_mission_paths p
SET band_mhz = m.band_mhz
FROM rover_missions m
WHERE p.mission_id = m.id AND p.band_mhz IS NULL
""")
alter table(:rover_mission_paths) do
modify :band_mhz, :integer, null: false
end
drop_if_exists(
unique_index(:rover_mission_paths, [:mission_id, :rover_location_id, :station_id],
name: :rover_mission_paths_unique
)
)
create unique_index(
:rover_mission_paths,
[:mission_id, :rover_location_id, :station_id, :band_mhz],
name: :rover_mission_paths_unique
)
end
def down do
drop_if_exists(
unique_index(
:rover_mission_paths,
[:mission_id, :rover_location_id, :station_id, :band_mhz],
name: :rover_mission_paths_unique
)
)
create unique_index(:rover_mission_paths, [:mission_id, :rover_location_id, :station_id],
name: :rover_mission_paths_unique
)
alter table(:rover_mission_paths) do
remove :band_mhz
end
alter table(:rover_missions) do
remove :bands_mhz
end
end
end