First step toward physics-based HF / sporadic-E scoring. Polls GIRO's DIDBase tabular endpoint every 10 minutes for two US ionosondes (Millstone Hill MHJ45, Alpena AL945), parses the plain-text response into observation rows, and upserts into a new ionosonde_observations table keyed on (station_code, valid_time). This gets foEs (E-layer sporadic critical frequency) into the database as a direct measurement — the key input for predicting 144 MHz Es openings via ITU-R P.534-6. foF2 + MUFD are the F2-layer inputs for HF MUF predictions. Not yet wired into scoring. Boulder/Wallops/Austin/Idaho/Point Arguello are in the GIRO catalog but were silent when probed — add them back if/when they come online. Next steps: SWPC JSON (Kp, F10.7, sunspot), GOES X-ray flux, D-RAP text, and the P.534-6 Es scoring factor that uses foEs at midpoint for the 144/440 band configs.
52 lines
1.5 KiB
Elixir
52 lines
1.5 KiB
Elixir
defmodule Microwaveprop.Ionosphere do
|
|
@moduledoc """
|
|
Context for ionospheric observations (GIRO ionosonde data). Used by
|
|
the propagation scorer for VHF sporadic-E and HF MUF predictions.
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Microwaveprop.Ionosphere.Observation
|
|
alias Microwaveprop.Repo
|
|
|
|
@doc """
|
|
Upsert a list of parsed `GiroClient` observations for a station.
|
|
Conflicts on `(station_code, valid_time)` replace every column except
|
|
the primary key and inserted_at. Returns `{:ok, count}`.
|
|
"""
|
|
@spec upsert_observations(String.t(), [map()]) :: {:ok, non_neg_integer()}
|
|
def upsert_observations(_station_code, []), do: {:ok, 0}
|
|
|
|
def upsert_observations(station_code, observations) when is_list(observations) do
|
|
now = DateTime.truncate(DateTime.utc_now(), :second)
|
|
|
|
rows =
|
|
Enum.map(observations, fn obs ->
|
|
obs
|
|
|> Map.put(:station_code, station_code)
|
|
|> Map.put(:inserted_at, now)
|
|
|> Map.put(:updated_at, now)
|
|
end)
|
|
|
|
{count, _} =
|
|
Repo.insert_all(Observation, rows,
|
|
on_conflict: {:replace_all_except, [:id, :inserted_at]},
|
|
conflict_target: [:station_code, :valid_time]
|
|
)
|
|
|
|
{:ok, count}
|
|
end
|
|
|
|
@doc """
|
|
Returns the most recent `Observation` row for a station, or nil.
|
|
"""
|
|
@spec latest_observation(String.t()) :: Observation.t() | nil
|
|
def latest_observation(station_code) do
|
|
Repo.one(
|
|
from o in Observation,
|
|
where: o.station_code == ^station_code,
|
|
order_by: [desc: o.valid_time],
|
|
limit: 1
|
|
)
|
|
end
|
|
end
|