Adds a per-contact enrichment pipeline that determines whether a QSO was
most likely carried by rain scatter, tropospheric ducting, or ordinary
troposcatter — using IEM n0q composite reflectivity sampled inside the
lens-shaped intersection of 400 km-radius disks around each endpoint.
Pieces:
* Microwaveprop.Propagation.CommonVolume — lens geometry (haversine,
in-CV test, bbox, area).
* contact_common_volume_radar table (1:1 per contact) storing
aggregate dBZ stats inside the CV + radar_status column on contacts.
* Microwaveprop.Workers.CommonVolumeRadarWorker — Oban :radar queue,
fetches the n0q frame at QSO time, iterates pixels inside the CV
bbox, aggregates rain/heavy/core-pixel counts, max/mean dBZ, and
coverage percentage.
* Microwaveprop.Propagation.RainScatterClassifier — rule-based mapper
from (band, distance, radar stats, duct flags) to one of
:likely_rainscatter | :rainscatter_possible | :tropo_duct |
:troposcatter | :unknown.
* ContactWeatherEnqueueWorker learns a :radar enrichment type and
enqueues the CV worker on contact submission; pre-2014 contacts
(outside IEM n0q coverage) are pinned to :unavailable.
* `mix radar_backfill` bulk-enqueues historical contacts with
--year / --limit / --dry-run.
* Contact detail page renders a mechanism badge with supporting
stats (common-volume area, max dBZ, heavy-rain pixel count,
coverage %).
47 lines
1.4 KiB
Elixir
47 lines
1.4 KiB
Elixir
defmodule Microwaveprop.Radio.ContactCommonVolumeRadar do
|
|
@moduledoc """
|
|
Aggregated n0q composite reflectivity statistics sampled inside the
|
|
common volume (lens-shaped intersection of two 400 km circles around
|
|
the endpoints) for a single contact, at the QSO time.
|
|
|
|
One row per contact. Feeds `Microwaveprop.Propagation.RainScatterClassifier`.
|
|
"""
|
|
use Ecto.Schema
|
|
|
|
import Ecto.Changeset
|
|
|
|
alias Microwaveprop.Radio.Contact
|
|
|
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
@foreign_key_type :binary_id
|
|
|
|
schema "contact_common_volume_radar" do
|
|
belongs_to :contact, Contact
|
|
|
|
field :observed_at, :utc_datetime
|
|
field :common_volume_km2, :float
|
|
field :pixel_count, :integer, default: 0
|
|
field :rain_pixel_count, :integer, default: 0
|
|
field :heavy_rain_pixel_count, :integer, default: 0
|
|
field :core_pixel_count, :integer, default: 0
|
|
field :max_dbz, :float
|
|
field :mean_dbz, :float
|
|
field :coverage_pct, :float
|
|
|
|
timestamps(type: :utc_datetime)
|
|
end
|
|
|
|
@type t :: %__MODULE__{}
|
|
|
|
@required ~w(contact_id observed_at)a
|
|
@optional ~w(common_volume_km2 pixel_count rain_pixel_count heavy_rain_pixel_count
|
|
core_pixel_count max_dbz mean_dbz coverage_pct)a
|
|
|
|
@spec changeset(t() | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
|
|
def changeset(row, attrs) do
|
|
row
|
|
|> cast(attrs, @required ++ @optional)
|
|
|> validate_required(@required)
|
|
|> unique_constraint(:contact_id)
|
|
end
|
|
end
|