defmodule Microwaveprop.Weather.HrrrNativeProfileTest do use Microwaveprop.DataCase, async: true alias Microwaveprop.Weather.HrrrNativeProfile @valid_attrs %{ valid_time: ~U[2026-03-28 18:00:00Z], run_time: ~U[2026-03-28 18:00:00Z], lat: 32.9, lon: -97.0, level_count: 3, heights_m: [10.0, 100.0, 500.0], temp_k: [295.0, 292.0, 285.0], spfh: [0.010, 0.008, 0.004], pressure_pa: [101_000.0, 99_800.0, 95_000.0], u_wind_ms: [2.0, 3.0, 4.0], v_wind_ms: [1.0, 1.5, 2.0], tke_m2s2: [0.5, 0.3, 0.1], surface_temp_k: 295.0, surface_spfh: 0.010, surface_pressure_pa: 101_325.0 } describe "changeset/2" do test "accepts a full native profile" do changeset = HrrrNativeProfile.changeset(%HrrrNativeProfile{}, @valid_attrs) assert changeset.valid? assert {:ok, profile} = Repo.insert(changeset) assert profile.level_count == 3 assert profile.heights_m == [10.0, 100.0, 500.0] end test "requires valid_time, lat, lon" do changeset = HrrrNativeProfile.changeset(%HrrrNativeProfile{}, %{}) refute changeset.valid? assert "can't be blank" in errors_on(changeset).valid_time assert "can't be blank" in errors_on(changeset).lat assert "can't be blank" in errors_on(changeset).lon end test "rejects mismatched level array lengths" do attrs = Map.put(@valid_attrs, :temp_k, [295.0, 292.0]) changeset = HrrrNativeProfile.changeset(%HrrrNativeProfile{}, attrs) refute changeset.valid? assert errors_on(changeset)[:temp_k] end test "enforces (lat, lon, valid_time) uniqueness" do {:ok, _} = %HrrrNativeProfile{} |> HrrrNativeProfile.changeset(@valid_attrs) |> Repo.insert() {:error, changeset} = %HrrrNativeProfile{} |> HrrrNativeProfile.changeset(@valid_attrs) |> Repo.insert() refute changeset.valid? assert "has already been taken" in errors_on(changeset).lat end end describe "to_skew_t_profile/1" do test "converts native parallel arrays into skew-T map list sorted surface-first" do profile = struct(HrrrNativeProfile, @valid_attrs) levels = HrrrNativeProfile.to_skew_t_profile(profile) assert length(levels) == 3 # Ordered surface-first (descending pressure). pressures = Enum.map(levels, & &1["pres"]) assert pressures == Enum.sort(pressures, :desc) [surface | _] = levels # pressure Pa → hPa assert_in_delta surface["pres"], 1010.0, 0.01 # temperature K → °C assert_in_delta surface["tmpc"], 295.0 - 273.15, 0.01 # geopotential height passes through assert surface["hght"] == 10.0 # Dewpoint derived from SPFH + pressure + temperature via Magnus. # e = q*p/(0.622 + 0.378*q)/100 = 0.010*101000/0.62578/100 ≈ 16.140 hPa # td = 243.5*ln(e/6.112)/(17.67 − ln(e/6.112)) ≈ 14.16 °C assert_in_delta surface["dwpc"], 14.16, 0.1 end test "returns empty list when arrays are nil or empty" do blank = %HrrrNativeProfile{ heights_m: nil, temp_k: nil, spfh: nil, pressure_pa: nil } assert HrrrNativeProfile.to_skew_t_profile(blank) == [] end test "drops levels with missing pressure or temperature" do attrs = @valid_attrs |> Map.put(:temp_k, [295.0, nil, 285.0]) |> Map.put(:pressure_pa, [101_000.0, 99_800.0, nil]) profile = struct(HrrrNativeProfile, attrs) levels = HrrrNativeProfile.to_skew_t_profile(profile) assert length(levels) == 1 assert hd(levels)["hght"] == 10.0 end end end