Convert every context/worker upsert that used the catch-all
`:replace_all_except` form to the explicit `{:replace, [:col1, ...]}`
form. Reviewers can now see exactly which columns an upsert overwrites,
and adding a new column to a schema no longer silently opts it into the
update path.
Behaviour is preserved bit-for-bit: each new explicit list contains
every column the old `:replace_all_except` would have overwritten.
Touched:
- Microwaveprop.SpaceWeather (Kp / F10.7 / X-ray shared helper)
- Microwaveprop.Ionosphere (observation upsert)
- NexradWorker, HrrrNativeGridWorker (insert_all grids)
- CommonVolumeRadarWorker, RadarFrameWorker (per-contact stats)
Also pins the conflict behaviour for the ContactCommonVolumeRadar
upsert path in RadarFrameWorkerTest so a future column addition that
isn't reflected in the explicit list fails loudly.
93 lines
2.7 KiB
Elixir
93 lines
2.7 KiB
Elixir
defmodule Microwaveprop.SpaceWeather do
|
|
@moduledoc """
|
|
Context for NOAA SWPC space-weather observations: planetary K-index,
|
|
10.7 cm solar flux, and GOES X-ray flux. Used by the propagation
|
|
scorer for HF MUF prediction (F10.7 → SSN proxy), geomagnetic storm
|
|
effects (Kp), and short-wave fade detection (GOES X-ray flares).
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Microwaveprop.Repo
|
|
alias Microwaveprop.SpaceWeather.GeomagneticObservation
|
|
alias Microwaveprop.SpaceWeather.SolarFluxObservation
|
|
alias Microwaveprop.SpaceWeather.SolarXrayObservation
|
|
|
|
# ---------- Kp ----------
|
|
|
|
@kp_update_fields ~w(kp_index estimated_kp updated_at)a
|
|
|
|
@spec upsert_kp([map()]) :: {:ok, non_neg_integer()}
|
|
def upsert_kp([]), do: {:ok, 0}
|
|
|
|
def upsert_kp(rows) when is_list(rows) do
|
|
stamp_upsert(GeomagneticObservation, rows,
|
|
conflict_target: :valid_time,
|
|
update_fields: @kp_update_fields
|
|
)
|
|
end
|
|
|
|
@spec latest_kp() :: GeomagneticObservation.t() | nil
|
|
def latest_kp do
|
|
Repo.one(from o in GeomagneticObservation, order_by: [desc: o.valid_time], limit: 1)
|
|
end
|
|
|
|
# ---------- F10.7 ----------
|
|
|
|
@solar_flux_update_fields ~w(frequency_mhz flux_sfu reporting_schedule ninety_day_mean updated_at)a
|
|
|
|
@spec upsert_solar_flux([map()]) :: {:ok, non_neg_integer()}
|
|
def upsert_solar_flux([]), do: {:ok, 0}
|
|
|
|
def upsert_solar_flux(rows) when is_list(rows) do
|
|
stamp_upsert(SolarFluxObservation, rows,
|
|
conflict_target: :valid_time,
|
|
update_fields: @solar_flux_update_fields
|
|
)
|
|
end
|
|
|
|
@spec latest_f107() :: SolarFluxObservation.t() | nil
|
|
def latest_f107 do
|
|
Repo.one(from o in SolarFluxObservation, order_by: [desc: o.valid_time], limit: 1)
|
|
end
|
|
|
|
# ---------- GOES X-ray ----------
|
|
|
|
@xray_update_fields ~w(satellite flux_wm2 electron_contaminated updated_at)a
|
|
|
|
@spec upsert_xray([map()]) :: {:ok, non_neg_integer()}
|
|
def upsert_xray([]), do: {:ok, 0}
|
|
|
|
def upsert_xray(rows) when is_list(rows) do
|
|
stamp_upsert(SolarXrayObservation, rows,
|
|
conflict_target: [:valid_time, :energy_band],
|
|
update_fields: @xray_update_fields
|
|
)
|
|
end
|
|
|
|
@spec latest_xray() :: SolarXrayObservation.t() | nil
|
|
def latest_xray do
|
|
Repo.one(from o in SolarXrayObservation, order_by: [desc: o.valid_time], limit: 1)
|
|
end
|
|
|
|
# ---------- Shared ----------
|
|
|
|
defp stamp_upsert(schema, rows, opts) do
|
|
now = DateTime.truncate(DateTime.utc_now(), :second)
|
|
|
|
stamped =
|
|
Enum.map(rows, fn row ->
|
|
row
|
|
|> Map.put(:inserted_at, now)
|
|
|> Map.put(:updated_at, now)
|
|
end)
|
|
|
|
{count, _} =
|
|
Repo.insert_all(schema, stamped,
|
|
on_conflict: {:replace, Keyword.fetch!(opts, :update_fields)},
|
|
conflict_target: Keyword.fetch!(opts, :conflict_target)
|
|
)
|
|
|
|
{:ok, count}
|
|
end
|
|
end
|