defmodule Microwaveprop.Workers.GefsFetchWorkerTest do use Microwaveprop.DataCase, async: false alias Microwaveprop.Weather alias Microwaveprop.Weather.GefsProfile alias Microwaveprop.Workers.GefsFetchWorker describe "perform/1" do test "cancels on malformed (non-empty, not a seed) args" do args = %{"some_unrelated" => "garbage"} assert {:cancel, _} = GefsFetchWorker.perform(%Oban.Job{args: args}) end test "cancels when run_hour is not a valid GEFS cycle" do args = %{ "run_time" => "2026-04-18T05:00:00Z", "forecast_hour" => 24 } assert {:cancel, _} = GefsFetchWorker.perform(%Oban.Job{args: args}) end end describe "build_profile_attrs/3" do test "merges lat/lon/valid_time onto a GefsClient profile map" do run_time = ~U[2026-04-18 12:00:00Z] profile = %{ lat: 32.9, lon: -97.0, surface_temp_c: 20.0, surface_dewpoint_c: 10.0, surface_pressure_mb: 1013.0, pwat_mm: 20.0, wind_u_mps: 2.0, wind_v_mps: -1.0, cloud_cover_pct: 50.0, precip_mm: 0.0, profile: [%{"pres" => 1000.0, "tmpc" => 20.0, "dwpc" => 10.0, "hght" => 100.0}] } attrs = GefsFetchWorker.build_profile_attrs(run_time, 24, profile) assert attrs.run_time == run_time assert attrs.forecast_hour == 24 assert attrs.valid_time == ~U[2026-04-19 12:00:00Z] assert attrs.lat == 32.9 assert attrs.surface_temp_c == 20.0 end test "derives SoundingParams-style refractivity metrics when profile is populated" do run_time = ~U[2026-04-18 12:00:00Z] profile = %{ lat: 32.9, lon: -97.0, surface_temp_c: 20.0, surface_dewpoint_c: 15.0, surface_pressure_mb: 1013.0, pwat_mm: 25.0, profile: [ %{"pres" => 1000.0, "tmpc" => 20.0, "dwpc" => 15.0, "hght" => 100.0}, %{"pres" => 925.0, "tmpc" => 18.0, "dwpc" => 12.0, "hght" => 770.0}, %{"pres" => 850.0, "tmpc" => 14.0, "dwpc" => 8.0, "hght" => 1500.0} ] } attrs = GefsFetchWorker.build_profile_attrs(run_time, 24, profile) assert is_float(attrs.surface_refractivity) assert is_float(attrs.min_refractivity_gradient) assert is_boolean(attrs.ducting_detected) end end describe "extended_horizon_hours/0" do test "spans f024 through f168 at 6-hour cadence (25 entries)" do hours = GefsFetchWorker.extended_horizon_hours() assert List.first(hours) == 24 assert List.last(hours) == 168 assert length(hours) == 25 assert Enum.all?(hours, &(rem(&1, 6) == 0)) end end describe "most_recent_available_run/1" do test "returns the run cycle 5 or more hours earlier" do # 15:00 UTC → 10:00 UTC → snaps to 06Z assert GefsFetchWorker.most_recent_available_run(~U[2026-04-18 15:00:00Z]) == ~U[2026-04-18 06:00:00Z] end test "rolls across midnight when now is near 04Z" do # 04:00 UTC → 23:00 the day before → snaps to 18Z previous day assert GefsFetchWorker.most_recent_available_run(~U[2026-04-18 04:00:00Z]) == ~U[2026-04-17 18:00:00Z] end end describe "scoring interop" do alias Microwaveprop.Propagation alias Microwaveprop.Weather.GefsClient test "a GEFS-shaped profile produces non-empty band scores" do raw = %{ "TMP:2 m above ground" => 293.15, "RH:2 m above ground" => 50.0, "PRES:surface" => 101_325.0, "PWAT:entire atmosphere (considered as a single layer)" => 20.0, "UGRD:10 m above ground" => 3.0, "VGRD:10 m above ground" => -1.5, "TCDC:entire atmosphere" => 40.0, "APCP:surface" => 0.0, "TMP:1000 mb" => 293.15, "RH:1000 mb" => 50.0, "HGT:1000 mb" => 110.0, "TMP:925 mb" => 288.15, "RH:925 mb" => 40.0, "HGT:925 mb" => 780.0, "TMP:850 mb" => 283.15, "RH:850 mb" => 35.0, "HGT:850 mb" => 1500.0 } scores = raw |> GefsClient.build_profile() |> Propagation.score_grid_point(~U[2026-04-20 18:00:00Z], 32.9, -97.0) assert is_list(scores) assert scores != [] score = hd(scores) assert is_integer(score.band_mhz) assert is_integer(score.score) assert score.score >= 0 assert score.score <= 100 end end describe "storing profiles" do test "round-trips a full batch through the context" do run_time = ~U[2026-04-18 12:00:00Z] fh = 24 valid_time = DateTime.add(run_time, fh * 3600, :second) batch = [ %{ run_time: run_time, forecast_hour: fh, valid_time: valid_time, lat: 32.9, lon: -97.0, surface_temp_c: 20.0, surface_dewpoint_c: 10.0, surface_pressure_mb: 1013.0, pwat_mm: 20.0, profile: [] }, %{ run_time: run_time, forecast_hour: fh, valid_time: valid_time, lat: 33.0, lon: -97.0, surface_temp_c: 19.5, surface_dewpoint_c: 9.5, surface_pressure_mb: 1012.5, pwat_mm: 19.0, profile: [] } ] {count, _} = Weather.upsert_gefs_profiles_batch(batch) assert count == 2 persisted = Microwaveprop.Repo.all(GefsProfile) assert length(persisted) == 2 end end end