prop/priv/repo/migrations/20260504135243_create_pskr_spots_hourly.exs
Graham McIntire cfc5f7583f
feat(pskr): ingest PSK Reporter MQTT firehose for weather correlation
Subscribes to mqtt.pskreporter.info:1883 over plain TCP and folds
every reception report into hourly aggregates per
(band, sender_grid_4, receiver_grid_4). Each spot is the empirical
"propagation actually occurred on this path right now" signal we'll
correlate against HRRR atmospheric state to recalibrate the scoring
weights. Aggregating at the path-hour bucket collapses 50-reporter
QSOs to one row instead of 50.

Topic filter anchors on USA (DXCC 291) on either sender or receiver
side so the broker pre-filters out everything outside our HRRR
domain. Bands subscribed: VHF and up (6m, 2m, 70cm, 23cm, +
microwave) — HF is dominated by ionospheric propagation, which this
project doesn't model.

Pskr.Client cluster-elects a singleton via :global.register_name —
all replicas start the GenServer but only one connects to MQTT;
the rest stay in :standby and watch for the leader's nodedown to
re-run election. Off in dev/test, on by default in prod
(PSKR_MQTT_ENABLED=false as kill-switch).

Pskr.Aggregator buffers in memory keyed by Pskr.path_key/1 and
flushes every 60s via Repo.insert_all/3 with an additive
ON CONFLICT (count summed, SNR envelope by GREATEST/LEAST,
modes unioned via unnest+DISTINCT). Idempotent across overlapping
flushes and across leader handover.

Dockerfile sets BUILD_WITHOUT_QUIC=1 — emqtt's transitive quicer
dep wants CMake + OpenSSL headers we'd otherwise have to add to
the builder image just to never use QUIC over plain MQTT. Base
image is unchanged; the new dep compiles cleanly into the existing
prop-base runtime.
2026-05-04 09:24:20 -05:00

33 lines
1.1 KiB
Elixir

defmodule Microwaveprop.Repo.Migrations.CreatePskrSpotsHourly do
use Ecto.Migration
def change do
create table(:pskr_spots_hourly, primary_key: false) do
add :id, :binary_id, primary_key: true
add :hour_utc, :utc_datetime, null: false
add :band, :string, null: false
add :sender_grid, :string, null: false
add :receiver_grid, :string, null: false
add :sender_country, :integer
add :receiver_country, :integer
add :sender_lat, :float
add :sender_lon, :float
add :receiver_lat, :float
add :receiver_lon, :float
add :midpoint_lat, :float
add :midpoint_lon, :float
add :distance_km, :float
add :spot_count, :integer, null: false, default: 0
add :max_snr_db, :integer
add :min_snr_db, :integer
add :modes, {:array, :string}, null: false, default: []
add :first_spot_at, :utc_datetime
add :last_spot_at, :utc_datetime
timestamps(type: :utc_datetime)
end
create unique_index(:pskr_spots_hourly, [:hour_utc, :band, :sender_grid, :receiver_grid])
create index(:pskr_spots_hourly, [:hour_utc])
end
end