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.
83 lines
2.5 KiB
Elixir
83 lines
2.5 KiB
Elixir
defmodule Microwaveprop.IonosphereTest do
|
|
use Microwaveprop.DataCase, async: true
|
|
|
|
alias Microwaveprop.Ionosphere
|
|
alias Microwaveprop.Ionosphere.Observation
|
|
|
|
@valid_attrs %{
|
|
station_code: "MHJ45",
|
|
valid_time: ~U[2026-04-15 18:00:00Z],
|
|
confidence_score: 80,
|
|
fo_f2_mhz: 7.113,
|
|
fo_e_mhz: 3.10,
|
|
fo_es_mhz: 3.08,
|
|
hm_f2_km: 242.0,
|
|
mufd_mhz: 23.00
|
|
}
|
|
|
|
describe "upsert_observations/2" do
|
|
test "inserts new observations and returns the inserted count" do
|
|
assert {:ok, 2} =
|
|
Ionosphere.upsert_observations("MHJ45", [
|
|
Map.delete(@valid_attrs, :station_code),
|
|
@valid_attrs
|
|
|> Map.delete(:station_code)
|
|
|> Map.put(:valid_time, ~U[2026-04-15 18:07:30Z])
|
|
])
|
|
|
|
assert Repo.aggregate(Observation, :count, :id) == 2
|
|
end
|
|
|
|
test "re-upserting the same (station, valid_time) replaces fields and does not duplicate" do
|
|
{:ok, 1} = Ionosphere.upsert_observations("MHJ45", [Map.delete(@valid_attrs, :station_code)])
|
|
|
|
updated =
|
|
@valid_attrs
|
|
|> Map.delete(:station_code)
|
|
|> Map.put(:fo_es_mhz, 9.5)
|
|
|
|
assert {:ok, 1} = Ionosphere.upsert_observations("MHJ45", [updated])
|
|
assert Repo.aggregate(Observation, :count, :id) == 1
|
|
|
|
[row] = Repo.all(Observation)
|
|
assert row.fo_es_mhz == 9.5
|
|
end
|
|
|
|
test "stamps station_code on each observation" do
|
|
{:ok, 1} =
|
|
Ionosphere.upsert_observations("AL945", [
|
|
Map.delete(@valid_attrs, :station_code)
|
|
])
|
|
|
|
[row] = Repo.all(Observation)
|
|
assert row.station_code == "AL945"
|
|
end
|
|
|
|
test "returns 0 and does nothing for an empty list" do
|
|
assert {:ok, 0} = Ionosphere.upsert_observations("MHJ45", [])
|
|
assert Repo.aggregate(Observation, :count, :id) == 0
|
|
end
|
|
end
|
|
|
|
describe "latest_observation/1" do
|
|
test "returns the most recent observation for a station" do
|
|
older = Map.delete(@valid_attrs, :station_code)
|
|
|
|
newer =
|
|
@valid_attrs
|
|
|> Map.delete(:station_code)
|
|
|> Map.put(:valid_time, ~U[2026-04-15 19:00:00Z])
|
|
|> Map.put(:fo_es_mhz, 10.0)
|
|
|
|
{:ok, _} = Ionosphere.upsert_observations("MHJ45", [older, newer])
|
|
|
|
row = Ionosphere.latest_observation("MHJ45")
|
|
assert row.valid_time == ~U[2026-04-15 19:00:00Z]
|
|
assert row.fo_es_mhz == 10.0
|
|
end
|
|
|
|
test "returns nil when no observations exist for the station" do
|
|
assert Ionosphere.latest_observation("MHJ45") == nil
|
|
end
|
|
end
|
|
end
|