defmodule Microwaveprop.Pskr.FeatureBin do @moduledoc """ One feature-bucket row from a `Pskr.RecalibrationRun`. Stores the spot-density statistics for a (band, feature, bin) cell of the corpus so an operator can read whether a feature actually discriminates propagation at the granularity the scorer cares about. Example: at run R, on the 10 GHz band, the `pwat_mm` feature has five rows (one per bin label: `<15`, `15-25`, `25-40`, `40-55`, `>55`). Each row carries: * `sample_count` — how many corpus samples landed in the bin * `spot_count_total` — sum of spot_count across those samples * `spot_count_avg` / `spot_count_p50` / `spot_count_p90` — central tendency + tail for the spot-density distribution The unique key (run_id, band, feature, bin_label) means a rerun of the same recalibration run is idempotent. """ use Ecto.Schema import Ecto.Changeset @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "pskr_feature_bins" do belongs_to :run, Microwaveprop.Pskr.RecalibrationRun, foreign_key: :run_id field :band, :string field :feature, :string field :bin_label, :string field :bin_min, :float field :bin_max, :float field :sample_count, :integer, default: 0 field :spot_count_total, :integer, default: 0 field :spot_count_avg, :float field :spot_count_p50, :float field :spot_count_p90, :float timestamps(type: :utc_datetime) end @type t :: %__MODULE__{} @cast_fields ~w(run_id band feature bin_label bin_min bin_max sample_count spot_count_total spot_count_avg spot_count_p50 spot_count_p90)a @required_fields ~w(run_id band feature bin_label sample_count)a @spec changeset(t(), map()) :: Ecto.Changeset.t() def changeset(record, attrs) do record |> cast(attrs, @cast_fields) |> validate_required(@required_fields) |> unique_constraint([:run_id, :band, :feature, :bin_label]) end end