prop/priv/repo/migrations/20260513191924_create_beacon_measurements.exs
Graham McIntire bd731685de
feat(api): ingest endpoint for propmonitor beacon measurements
POST /api/v1/beacon-monitor/measurements accepts one measurement per
integration window from a propmonitor client, authenticated by the
BeaconMonitor token. Records noise floor, signal peak/avg dBFS, SNR,
signal-active-fraction, gain, and frequency for later correlation
against weather and propagation scores.

Beacon UUID must resolve to an approved + on-the-air beacon (404
otherwise — the client drops 404s without retry). Retries with the
same (monitor, beacon, measured_at) are idempotent: a unique index
makes the second insert a 409. Every accepted upload stamps the
monitor's last_seen_at — measurements *are* the heartbeat.

MonitorAuth is a separate plug from the user API-token Auth plug;
monitors are not users. The rate limiter buckets each monitor by id so
retry storms after a 5xx don't burn the shared anon-IP bucket.
2026-05-13 16:07:04 -05:00

43 lines
1.7 KiB
Elixir

defmodule Microwaveprop.Repo.Migrations.CreateBeaconMeasurements do
use Ecto.Migration
def change do
create table(:beacon_measurements, primary_key: false) do
add :id, :binary_id, primary_key: true
add :beacon_id, references(:beacons, type: :binary_id, on_delete: :delete_all), null: false
add :monitor_id, references(:beacon_monitors, type: :binary_id, on_delete: :delete_all),
null: false
add :measured_at, :utc_datetime, null: false
add :frequency_hz, :bigint, null: false
add :integration_s, :integer, null: false
add :passband_hz, :float, null: false
add :gain_db, :float, null: false
add :noise_floor_dbfs, :float, null: false
add :signal_peak_dbfs, :float, null: false
add :signal_avg_dbfs, :float, null: false
add :snr_peak_db, :float, null: false
add :snr_avg_db, :float, null: false
add :signal_active_fraction, :float, null: false
add :propmonitor_version, :string, null: false
timestamps(type: :utc_datetime)
end
# Time-series query patterns: latest-N for one monitor, latest-N for one
# beacon, and joint monitor+beacon trends.
create index(:beacon_measurements, [:monitor_id, "measured_at DESC"])
create index(:beacon_measurements, [:beacon_id, "measured_at DESC"])
# Idempotency: the client retries on 5xx with the same `measured_at`. A
# given monitor cannot legitimately produce two distinct measurements for
# the same beacon at the same start-of-window timestamp.
create unique_index(:beacon_measurements, [:monitor_id, :beacon_id, :measured_at],
name: :beacon_measurements_monitor_beacon_measured_at_idx
)
end
end