defmodule Microwaveprop.WeatherGridTest do use Microwaveprop.DataCase, async: true alias Microwaveprop.Repo alias Microwaveprop.Weather describe "latest_weather_grid/1" do test "returns grid data for latest valid_time within bounds" do now = DateTime.truncate(DateTime.utc_now(), :second) insert_hrrr_profile(%{ lat: 32.125, lon: -97.375, valid_time: now, surface_temp_c: 25.0, surface_dewpoint_c: 18.0, surface_pressure_mb: 1013.0, hpbl_m: 500.0, pwat_mm: 30.0, min_refractivity_gradient: -280.0, ducting_detected: true }) insert_hrrr_profile(%{ lat: 32.250, lon: -97.375, valid_time: now, surface_temp_c: 26.0, surface_dewpoint_c: 19.0, surface_pressure_mb: 1012.0, hpbl_m: 600.0, pwat_mm: 32.0, min_refractivity_gradient: -150.0, ducting_detected: false }) bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} result = Weather.latest_weather_grid(bounds) assert length(result) == 2 point = Enum.find(result, &(&1.lat == 32.125)) assert point.temperature == 25.0 assert point.dewpoint_depression == 7.0 assert point.bl_height == 500.0 assert point.pwat == 30.0 assert point.refractivity_gradient == -280.0 assert point.ducting == true end test "returns empty list when no data exists" do bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} assert Weather.latest_weather_grid(bounds) == [] end test "only returns points on 0.125 grid" do now = DateTime.truncate(DateTime.utc_now(), :second) insert_hrrr_profile(%{ lat: 32.125, lon: -97.375, valid_time: now, surface_temp_c: 25.0, surface_dewpoint_c: 18.0, surface_pressure_mb: 1013.0, hpbl_m: 500.0, pwat_mm: 30.0 }) insert_hrrr_profile(%{ lat: 32.1337, lon: -97.3821, valid_time: now, surface_temp_c: 25.0, surface_dewpoint_c: 18.0, surface_pressure_mb: 1013.0, hpbl_m: 500.0, pwat_mm: 30.0 }) bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} result = Weather.latest_weather_grid(bounds) assert length(result) == 1 assert hd(result).lat == 32.125 end test "uses most recent valid_time only" do old = DateTime.utc_now() |> DateTime.add(-7200, :second) |> DateTime.truncate(:second) new = DateTime.truncate(DateTime.utc_now(), :second) insert_hrrr_profile(%{ lat: 32.125, lon: -97.375, valid_time: old, surface_temp_c: 20.0, surface_dewpoint_c: 15.0, surface_pressure_mb: 1010.0, hpbl_m: 400.0, pwat_mm: 25.0 }) insert_hrrr_profile(%{ lat: 32.125, lon: -97.375, valid_time: new, surface_temp_c: 28.0, surface_dewpoint_c: 20.0, surface_pressure_mb: 1015.0, hpbl_m: 700.0, pwat_mm: 35.0 }) bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} result = Weather.latest_weather_grid(bounds) assert length(result) == 1 assert hd(result).temperature == 28.0 end test "includes derived upper-air fields and strips raw profile data" do now = DateTime.truncate(DateTime.utc_now(), :second) profile = [ %{"pres" => 1000.0, "tmpc" => 25.0, "dwpc" => 18.0, "hght" => 100.0}, %{"pres" => 925.0, "tmpc" => 20.0, "dwpc" => 14.0, "hght" => 800.0}, %{"pres" => 850.0, "tmpc" => 15.0, "dwpc" => 10.0, "hght" => 1500.0}, %{"pres" => 700.0, "tmpc" => 5.0, "dwpc" => -2.0, "hght" => 3100.0}, %{"pres" => 500.0, "tmpc" => -15.0, "dwpc" => -25.0, "hght" => 5600.0} ] duct_chars = [ %{"base" => 200.0, "strength" => 50.0}, %{"base" => 800.0, "strength" => 30.0} ] insert_hrrr_profile(%{ lat: 32.125, lon: -97.375, valid_time: now, surface_temp_c: 25.0, surface_dewpoint_c: 18.0, surface_pressure_mb: 1013.0, surface_refractivity: 320.0, hpbl_m: 500.0, pwat_mm: 30.0, min_refractivity_gradient: -280.0, ducting_detected: true, profile: profile, duct_characteristics: duct_chars }) bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0} [point] = Weather.latest_weather_grid(bounds) # Derived fields should be present assert is_float(point.surface_rh) assert point.surface_refractivity == 320.0 assert point.temp_850mb == 15.0 assert point.dewpoint_850mb == 10.0 assert is_float(point.lapse_rate) assert point.duct_base_m == 200.0 assert point.duct_strength == 50.0 # Raw bulky fields should be stripped refute Map.has_key?(point, :profile) refute Map.has_key?(point, :duct_characteristics) refute Map.has_key?(point, :surface_temp_c) refute Map.has_key?(point, :surface_dewpoint_c) end end describe "weather_point_detail/3" do test "returns all fields for a single grid point" do now = DateTime.truncate(DateTime.utc_now(), :second) insert_hrrr_profile(%{ lat: 32.125, lon: -97.375, valid_time: now, surface_temp_c: 25.0, surface_dewpoint_c: 18.0, surface_pressure_mb: 1013.0, hpbl_m: 500.0, pwat_mm: 30.0, min_refractivity_gradient: -280.0, ducting_detected: true }) result = Weather.weather_point_detail(32.15, -97.40, now) assert result.lat == 32.125 assert result.temperature == 25.0 assert result.surface_pressure_mb == 1013.0 end test "returns nil when no data at point" do now = DateTime.truncate(DateTime.utc_now(), :second) assert Weather.weather_point_detail(32.15, -97.40, now) == nil end test "includes derived upper-air fields and strips raw profile data" do now = DateTime.truncate(DateTime.utc_now(), :second) profile = [ %{"pres" => 1000.0, "tmpc" => 25.0, "dwpc" => 18.0, "hght" => 100.0}, %{"pres" => 850.0, "tmpc" => 15.0, "dwpc" => 10.0, "hght" => 1500.0}, %{"pres" => 500.0, "tmpc" => -15.0, "dwpc" => -25.0, "hght" => 5600.0} ] duct_chars = [%{"base" => 300.0, "strength" => 40.0}] insert_hrrr_profile(%{ lat: 32.125, lon: -97.375, valid_time: now, surface_temp_c: 25.0, surface_dewpoint_c: 18.0, surface_pressure_mb: 1013.0, surface_refractivity: 320.0, hpbl_m: 500.0, pwat_mm: 30.0, min_refractivity_gradient: -280.0, ducting_detected: true, profile: profile, duct_characteristics: duct_chars }) result = Weather.weather_point_detail(32.15, -97.40, now) # Derived fields assert is_float(result.surface_rh) assert result.surface_refractivity == 320.0 assert result.temp_850mb == 15.0 assert result.duct_base_m == 300.0 assert result.duct_strength == 40.0 # Raw fields stripped refute Map.has_key?(result, :profile) refute Map.has_key?(result, :duct_characteristics) refute Map.has_key?(result, :surface_temp_c) refute Map.has_key?(result, :surface_dewpoint_c) end end defp insert_hrrr_profile(attrs) do now = DateTime.truncate(DateTime.utc_now(), :second) is_gp = rem(round(attrs.lat * 1000), 125) == 0 and rem(round(attrs.lon * 1000), 125) == 0 defaults = %{ id: Ecto.UUID.bingenerate(), run_time: DateTime.add(attrs.valid_time, -3600, :second), profile: [], surface_refractivity: nil, min_refractivity_gradient: nil, ducting_detected: nil, duct_characteristics: nil, is_grid_point: is_gp, inserted_at: now, updated_at: now } entry = Map.merge(defaults, attrs) Repo.insert_all("hrrr_profiles", [entry]) end end