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.
115 lines
3.7 KiB
Elixir
115 lines
3.7 KiB
Elixir
defmodule Microwaveprop.Weather.SoundingTest do
|
|
use Microwaveprop.DataCase, async: true
|
|
|
|
alias Microwaveprop.Weather.Sounding
|
|
alias Microwaveprop.Weather.Station
|
|
|
|
defp create_sounding_station do
|
|
%Station{}
|
|
|> Station.changeset(%{
|
|
station_code: "FWD",
|
|
station_type: "sounding",
|
|
wmo_number: 72_249,
|
|
name: "Fort Worth",
|
|
lat: 32.83,
|
|
lon: -97.30
|
|
})
|
|
|> Repo.insert!()
|
|
end
|
|
|
|
@sample_profile [
|
|
%{"pres" => 1013.0, "hght" => 171, "tmpc" => 25.0, "dwpc" => 15.0, "drct" => 180, "sknt" => 10},
|
|
%{"pres" => 925.0, "hght" => 800, "tmpc" => 20.0, "dwpc" => 12.0, "drct" => 200, "sknt" => 15},
|
|
%{"pres" => 850.0, "hght" => 1500, "tmpc" => 15.0, "dwpc" => 8.0, "drct" => 220, "sknt" => 20}
|
|
]
|
|
|
|
@valid_sounding_attrs %{
|
|
observed_at: ~U[2026-03-28 12:00:00Z],
|
|
profile: @sample_profile,
|
|
level_count: 3,
|
|
surface_pressure_mb: 1013.0,
|
|
surface_temp_c: 25.0,
|
|
surface_dewpoint_c: 15.0,
|
|
surface_refractivity: 320.5,
|
|
min_refractivity_gradient: -45.0,
|
|
boundary_layer_depth_m: 1200.0,
|
|
precipitable_water_mm: 25.0,
|
|
k_index: 28.0,
|
|
lifted_index: -2.0,
|
|
ducting_detected: false,
|
|
duct_characteristics: nil
|
|
}
|
|
|
|
describe "changeset/2" do
|
|
test "valid attributes with station_id" do
|
|
station = create_sounding_station()
|
|
attrs = Map.put(@valid_sounding_attrs, :station_id, station.id)
|
|
changeset = Sounding.changeset(%Sounding{}, attrs)
|
|
assert changeset.valid?
|
|
end
|
|
|
|
test "requires station_id, observed_at, profile, level_count" do
|
|
changeset = Sounding.changeset(%Sounding{}, %{})
|
|
|
|
assert %{
|
|
station_id: ["can't be blank"],
|
|
observed_at: ["can't be blank"],
|
|
profile: ["can't be blank"],
|
|
level_count: ["can't be blank"]
|
|
} = errors_on(changeset)
|
|
end
|
|
|
|
test "derived params are optional" do
|
|
station = create_sounding_station()
|
|
|
|
attrs = %{
|
|
station_id: station.id,
|
|
observed_at: ~U[2026-03-28 12:00:00Z],
|
|
profile: @sample_profile,
|
|
level_count: 3
|
|
}
|
|
|
|
changeset = Sounding.changeset(%Sounding{}, attrs)
|
|
assert changeset.valid?
|
|
end
|
|
|
|
test "persists to the database with JSONB profile" do
|
|
station = create_sounding_station()
|
|
attrs = Map.put(@valid_sounding_attrs, :station_id, station.id)
|
|
changeset = Sounding.changeset(%Sounding{}, attrs)
|
|
assert {:ok, sounding} = Repo.insert(changeset)
|
|
assert length(sounding.profile) == 3
|
|
assert hd(sounding.profile)["pres"] == 1013.0
|
|
assert sounding.ducting_detected == false
|
|
assert sounding.k_index == 28.0
|
|
end
|
|
|
|
test "persists duct_characteristics as JSONB" do
|
|
station = create_sounding_station()
|
|
|
|
duct_chars = [
|
|
%{"base" => 200, "top" => 500, "strength" => "5.2"}
|
|
]
|
|
|
|
attrs =
|
|
@valid_sounding_attrs
|
|
|> Map.put(:station_id, station.id)
|
|
|> Map.put(:ducting_detected, true)
|
|
|> Map.put(:duct_characteristics, duct_chars)
|
|
|
|
changeset = Sounding.changeset(%Sounding{}, attrs)
|
|
assert {:ok, sounding} = Repo.insert(changeset)
|
|
assert sounding.ducting_detected == true
|
|
assert [%{"base" => 200, "top" => 500}] = sounding.duct_characteristics
|
|
end
|
|
|
|
test "enforces unique station_id + observed_at" do
|
|
station = create_sounding_station()
|
|
attrs = Map.put(@valid_sounding_attrs, :station_id, station.id)
|
|
|
|
assert {:ok, _} = %Sounding{} |> Sounding.changeset(attrs) |> Repo.insert()
|
|
assert {:error, changeset} = %Sounding{} |> Sounding.changeset(attrs) |> Repo.insert()
|
|
assert %{station_id: ["has already been taken"]} = errors_on(changeset)
|
|
end
|
|
end
|
|
end
|