prop/test/microwaveprop/weather/iem_client_test.exs
Graham McIntire 6489f08138
Add weather data schema, context, sounding params, and IEM ingestion
Store surface observations (ASOS) and upper-air soundings (RAOB) alongside
QSOs for atmospheric propagation correlation. Three new tables: weather_stations,
surface_observations, and soundings with JSONB profiles and pre-computed derived
parameters (refractivity, gradients, duct detection, stability indices).

Includes IEM API client for historical data import and import script seeded
with 95 ASOS + 9 sounding stations from PropCast coverage area.
2026-03-28 15:57:19 -05:00

119 lines
3.4 KiB
Elixir

defmodule Microwaveprop.Weather.IemClientTest do
use ExUnit.Case, async: true
alias Microwaveprop.Weather.IemClient
describe "parse_asos_csv/1" do
test "parses valid CSV rows" do
csv = """
station,valid,tmpf,dwpf,relh,sknt,drct,mslp,alti,skyc1
KDFW,2026-03-28 18:53,75.0,55.0,49.12,12,180,1013.2,29.92,SCT
KDFW,2026-03-28 19:53,76.0,54.0,46.00,10,190,1013.0,29.91,FEW
"""
rows = IemClient.parse_asos_csv(csv)
assert length(rows) == 2
first = hd(rows)
assert first.temp_f == 75.0
assert first.dewpoint_f == 55.0
assert first.relative_humidity == 49.12
assert first.wind_speed_kts == 12.0
assert first.wind_direction_deg == 180
assert first.sea_level_pressure_mb == 1013.2
assert first.altimeter_setting == 29.92
assert first.sky_condition == "SCT"
assert first.observed_at == ~U[2026-03-28 18:53:00Z]
end
test "handles null values in CSV" do
csv = """
station,valid,tmpf,dwpf,relh,sknt,drct,mslp,alti,skyc1
KDFW,2026-03-28 18:53,null,null,null,null,null,null,null,null
"""
rows = IemClient.parse_asos_csv(csv)
assert length(rows) == 1
first = hd(rows)
assert first.temp_f == nil
assert first.dewpoint_f == nil
assert first.sky_condition == nil
end
test "skips comment lines and empty lines" do
csv = """
#DEBUG something
station,valid,tmpf,dwpf,relh,sknt,drct,mslp,alti,skyc1
KDFW,2026-03-28 18:53,75.0,55.0,49.0,12,180,1013.2,29.92,SCT
"""
rows = IemClient.parse_asos_csv(csv)
assert length(rows) == 1
end
end
describe "parse_raob_json/1" do
test "parses valid RAOB JSON response" do
json = %{
"profiles" => [
%{
"station" => "KFWD",
"valid" => "2026-03-28 12:00:00+00:00",
"profile" => [
%{"pres" => 1013.0, "hght" => 171.0, "tmpc" => 25.0, "dwpc" => 15.0, "drct" => 180.0, "sknt" => 10.0},
%{"pres" => 925.0, "hght" => 800.0, "tmpc" => 20.0, "dwpc" => 12.0, "drct" => 200.0, "sknt" => 15.0}
]
}
]
}
result = IemClient.parse_raob_json(json)
assert length(result) == 1
sounding = hd(result)
assert sounding.observed_at == ~U[2026-03-28 12:00:00Z]
assert length(sounding.profile) == 2
assert hd(sounding.profile)["pres"] == 1013.0
end
test "returns empty list for empty profiles" do
json = %{"profiles" => []}
assert IemClient.parse_raob_json(json) == []
end
test "handles missing profile key gracefully" do
json = %{}
assert IemClient.parse_raob_json(json) == []
end
end
describe "asos_url/3" do
test "builds correct URL for date range" do
url =
IemClient.asos_url(
"KDFW",
~U[2026-03-28 12:00:00Z],
~U[2026-03-28 18:00:00Z]
)
assert url =~ "station=KDFW"
assert url =~ "year1=2026"
assert url =~ "month1=3"
assert url =~ "day1=28"
assert url =~ "hour1=12"
assert url =~ "year2=2026"
assert url =~ "hour2=18"
assert url =~ "format=onlycomma"
end
end
describe "raob_url/2" do
test "builds correct URL for sounding fetch" do
url = IemClient.raob_url("FWD", ~U[2026-03-28 12:00:00Z])
assert url =~ "station=FWD"
assert url =~ "ts=202603281200"
end
end
end