Adds the second ionospheric data source layer. Polls NOAA SWPC's free public JSON services every 5 minutes for three products: * Planetary K-index (1-min cadence) → geomagnetic_observations * 10.7 cm solar flux (hourly) → solar_flux_observations * GOES X-ray flux (1-min, 0.1-0.8nm long-wave band only) → solar_xray_observations All three products feed HF / Es scoring inputs that the propagation engine will start using in a follow-up commit: * Kp drives HF absorption and Es damping (memory notes Kp is inversely correlated with tropo/Es stability). * F10.7 is the SSN proxy for ITU-R P.533 HF MUF prediction. * GOES X-ray long-wave flux is the short-wave fade (SWF) / D-layer absorption proxy — a C-class flare starts attenuating HF within seconds of the X-ray peak. New context `Microwaveprop.SpaceWeather` exposes `upsert_*/1` and `latest_*/0` per product; `SpaceWeatherFetchWorker` pulls all three independently so one endpoint's outage doesn't block the others. Fixtures captured from live SWPC endpoints on 2026-04-15 for the parser tests. Not yet wired into scoring — that's a separate commit.
124 lines
3.8 KiB
Elixir
124 lines
3.8 KiB
Elixir
defmodule Microwaveprop.SpaceWeatherTest do
|
|
use Microwaveprop.DataCase, async: true
|
|
|
|
alias Microwaveprop.SpaceWeather
|
|
alias Microwaveprop.SpaceWeather.GeomagneticObservation
|
|
alias Microwaveprop.SpaceWeather.SolarFluxObservation
|
|
alias Microwaveprop.SpaceWeather.SolarXrayObservation
|
|
|
|
describe "upsert_kp/1" do
|
|
test "inserts new rows and returns the count" do
|
|
rows = [
|
|
%{valid_time: ~U[2026-04-15 18:00:00Z], kp_index: 2, estimated_kp: 1.67},
|
|
%{valid_time: ~U[2026-04-15 18:01:00Z], kp_index: 2, estimated_kp: 2.0}
|
|
]
|
|
|
|
assert {:ok, 2} = SpaceWeather.upsert_kp(rows)
|
|
assert Repo.aggregate(GeomagneticObservation, :count, :id) == 2
|
|
end
|
|
|
|
test "re-upsert replaces by valid_time (no dupes)" do
|
|
row = %{valid_time: ~U[2026-04-15 18:00:00Z], kp_index: 2, estimated_kp: 1.67}
|
|
{:ok, 1} = SpaceWeather.upsert_kp([row])
|
|
|
|
updated = %{row | kp_index: 5, estimated_kp: 5.3}
|
|
assert {:ok, 1} = SpaceWeather.upsert_kp([updated])
|
|
|
|
assert [stored] = Repo.all(GeomagneticObservation)
|
|
assert stored.kp_index == 5
|
|
assert stored.estimated_kp == 5.3
|
|
end
|
|
|
|
test "empty list is a no-op returning {:ok, 0}" do
|
|
assert {:ok, 0} = SpaceWeather.upsert_kp([])
|
|
end
|
|
end
|
|
|
|
describe "upsert_solar_flux/1" do
|
|
test "inserts rows and replaces by valid_time" do
|
|
rows = [
|
|
%{
|
|
valid_time: ~U[2026-04-15 17:00:00Z],
|
|
frequency_mhz: 2800,
|
|
flux_sfu: 112.0,
|
|
reporting_schedule: "Morning",
|
|
ninety_day_mean: nil
|
|
}
|
|
]
|
|
|
|
assert {:ok, 1} = SpaceWeather.upsert_solar_flux(rows)
|
|
|
|
updated = rows |> List.first() |> Map.put(:flux_sfu, 120.0)
|
|
assert {:ok, 1} = SpaceWeather.upsert_solar_flux([updated])
|
|
|
|
assert [stored] = Repo.all(SolarFluxObservation)
|
|
assert stored.flux_sfu == 120.0
|
|
end
|
|
end
|
|
|
|
describe "upsert_xray/1" do
|
|
test "inserts rows and replaces by (valid_time, energy_band)" do
|
|
rows = [
|
|
%{
|
|
valid_time: ~U[2026-04-14 19:49:00Z],
|
|
satellite: 18,
|
|
flux_wm2: 3.5e-7,
|
|
energy_band: "0.1-0.8nm",
|
|
electron_contaminated: false
|
|
}
|
|
]
|
|
|
|
assert {:ok, 1} = SpaceWeather.upsert_xray(rows)
|
|
assert Repo.aggregate(SolarXrayObservation, :count, :id) == 1
|
|
|
|
# Re-upserting updates existing row
|
|
updated = rows |> List.first() |> Map.put(:flux_wm2, 4.0e-7)
|
|
assert {:ok, 1} = SpaceWeather.upsert_xray([updated])
|
|
assert Repo.aggregate(SolarXrayObservation, :count, :id) == 1
|
|
|
|
[stored] = Repo.all(SolarXrayObservation)
|
|
assert stored.flux_wm2 == 4.0e-7
|
|
end
|
|
end
|
|
|
|
describe "latest_kp/0" do
|
|
test "returns the most recent geomagnetic observation, or nil" do
|
|
assert SpaceWeather.latest_kp() == nil
|
|
|
|
{:ok, _} =
|
|
SpaceWeather.upsert_kp([
|
|
%{valid_time: ~U[2026-04-15 17:00:00Z], kp_index: 2, estimated_kp: 2.0},
|
|
%{valid_time: ~U[2026-04-15 18:00:00Z], kp_index: 3, estimated_kp: 3.0}
|
|
])
|
|
|
|
row = SpaceWeather.latest_kp()
|
|
assert row.valid_time == ~U[2026-04-15 18:00:00Z]
|
|
assert row.kp_index == 3
|
|
end
|
|
end
|
|
|
|
describe "latest_f107/0 and latest_xray/0" do
|
|
test "return the most recent rows, or nil" do
|
|
assert SpaceWeather.latest_f107() == nil
|
|
assert SpaceWeather.latest_xray() == nil
|
|
|
|
{:ok, _} =
|
|
SpaceWeather.upsert_solar_flux([
|
|
%{valid_time: ~U[2026-04-15 17:00:00Z], frequency_mhz: 2800, flux_sfu: 112.0}
|
|
])
|
|
|
|
{:ok, _} =
|
|
SpaceWeather.upsert_xray([
|
|
%{
|
|
valid_time: ~U[2026-04-15 17:00:00Z],
|
|
satellite: 18,
|
|
flux_wm2: 1.0e-7,
|
|
energy_band: "0.1-0.8nm"
|
|
}
|
|
])
|
|
|
|
assert %SolarFluxObservation{flux_sfu: 112.0} = SpaceWeather.latest_f107()
|
|
assert %SolarXrayObservation{flux_wm2: 1.0e-7} = SpaceWeather.latest_xray()
|
|
end
|
|
end
|
|
end
|