towerops/priv/repo/migrations/20260506181711_create_coverages.exs
Graham McIntire 5a9381f91a feat: add /coverage RF prediction feature scaffold
End-to-end CRUD scaffold for per-site, per-antenna RF coverage
prediction. Schema, context, antenna registry, Oban worker stub, and
three LiveViews are wired up; the actual ITM + LIDAR + buildings
compute pipeline lands in a follow-up.

- Migration + Coverage schema with full validation (RF params, pixel-
  budget cap, antenna_slug existence). organization_id excluded from
  cast (programmatic-only).
- Coverages context: org-scoped CRUD, validate_site_in_organization
  rejects cross-tenant site_id refs at insert/update, queue_compute
  with PubSub broadcast on per-coverage and per-org topics.
- MSI Planet .ant parser + persistent_term registry loaded at boot
  from priv/antennas/. Two synthetic .ant fixtures (omni + 90deg
  sector) bundled.
- CoverageWorker on new :coverage Oban queue (concurrency 2). Job
  args carry organization_id; worker refuses to run on org mismatch.
  Stub flips status to "failed" with "compute not yet implemented"
  so the UI flow is exercisable.
- LiveViews: index (org-wide, live status updates), form (antenna
  picker grouped by manufacturer), show (status banners, params
  panel, map placeholder). Sidebar nav link added.
- Routes /coverage, /coverage/new, /coverage/:id, /coverage/:id/edit
  under :require_authenticated_user_with_default_org.
- 41 new tests covering schema validation, .ant parsing, context
  CRUD with cross-org guards, Oban job enqueue.
2026-05-06 13:44:14 -05:00

48 lines
1.5 KiB
Elixir

defmodule Towerops.Repo.Migrations.CreateCoverages do
use Ecto.Migration
def change do
create table(:coverages, primary_key: false) do
add :id, :binary_id, primary_key: true
add :site_id, references(:sites, type: :binary_id, on_delete: :delete_all), null: false
add :organization_id, references(:organizations, type: :binary_id, on_delete: :delete_all),
null: false
add :name, :string, null: false
add :antenna_slug, :string, null: false
add :height_agl_m, :float, null: false
add :azimuth_deg, :float, null: false
add :downtilt_deg, :float, null: false, default: 0.0
add :frequency_mhz, :integer, null: false
add :eirp_dbm, :float, null: false
add :radius_m, :integer, null: false
add :cell_size_m, :integer, null: false
add :receiver_height_m, :float, null: false, default: 3.0
add :rx_threshold_dbm, :float, null: false, default: -90.0
add :status, :string, null: false, default: "draft"
add :progress_pct, :integer, null: false, default: 0
add :error_message, :text
add :computed_at, :utc_datetime
add :bbox_min_lat, :float
add :bbox_max_lat, :float
add :bbox_min_lon, :float
add :bbox_max_lon, :float
add :raster_path, :string
add :png_path, :string
timestamps(type: :utc_datetime)
end
create unique_index(:coverages, [:site_id, :name], name: :coverages_site_id_name_index)
create index(:coverages, [:organization_id])
create index(:coverages, [:status])
end
end