- Fix score_pressure crash on nil pressure_mb (coastal HRRR points) - Set 10-min timeout on grid score upsert transaction (was :infinity) - Single DELETE for prune_old_scores instead of N queries in a loop - Remove dead load_hrrr_refractivity that loaded 95k rows into nil map - Pass selected_time to point_detail to skip latest_valid_time sub-query - Batch station existence checks (1 query per path point, not per station) - Batch solar index upserts via insert_all in chunks of 500 - Batch backfill_distances via single UPDATE FROM VALUES statement - Add is_grid_point boolean + partial index to hrrr_profiles (replaces non-sargable modular arithmetic filter on every weather map query) - Add partial index on contacts(qso_timestamp) WHERE pos1 IS NOT NULL - Move backfill enqueue to Oban worker so UI returns immediately
268 lines
8 KiB
Elixir
268 lines
8 KiB
Elixir
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
|