prop/test/microwaveprop/workers/hrrr_fetch_worker_test.exs
Graham McIntire 8bd0989fbe
Add HRRR model profile fetching for QSOs
HRRR provides hourly 3km-resolution atmospheric profiles, filling the
temporal gaps (12-hourly soundings) and spatial gaps (only 9 sounding
stations) in our current weather data.

- Add hrrr_profiles table and hrrr_queued flag on QSOs
- HrrrClient fetches GRIB2 data via HTTP Range requests + wgrib2
- HrrrFetchWorker derives refractivity/ducting params via SoundingParams
- QsoWeatherEnqueueWorker now also enqueues HRRR jobs
- QSO show page displays HRRR section with collapsible profile
- Dockerfile builds wgrib2 from source for production
2026-03-29 15:54:22 -05:00

106 lines
3.2 KiB
Elixir

defmodule Microwaveprop.Workers.HrrrFetchWorkerTest do
use Microwaveprop.DataCase, async: true
alias Microwaveprop.Weather
alias Microwaveprop.Weather.HrrrProfile
alias Microwaveprop.Workers.HrrrFetchWorker
@sample_profile [
%{"pres" => 1000.0, "hght" => 110.0, "tmpc" => 25.0, "dwpc" => 18.0},
%{"pres" => 975.0, "hght" => 350.0, "tmpc" => 23.0, "dwpc" => 16.0},
%{"pres" => 950.0, "hght" => 590.0, "tmpc" => 21.0, "dwpc" => 14.0}
]
@sample_client_result %{
surface_temp_c: 25.0,
surface_dewpoint_c: 18.0,
surface_pressure_mb: 1013.0,
hpbl_m: 1500.0,
pwat_mm: 25.0,
run_time: ~U[2026-03-28 18:00:00Z],
profile: @sample_profile
}
describe "perform/1" do
test "skips if HRRR profile already exists" do
Weather.upsert_hrrr_profile(%{
valid_time: ~U[2026-03-28 18:00:00Z],
lat: 32.90,
lon: -97.04,
profile: @sample_profile
})
job =
HrrrFetchWorker.new(%{
"lat" => 32.90,
"lon" => -97.04,
"valid_time" => "2026-03-28T18:00:00Z"
})
assert :ok = HrrrFetchWorker.perform(%Oban.Job{args: job.changes.args})
end
test "stores profile when client returns success" do
# We test the perform logic by mocking at the module level
# The actual HTTP calls are tested in HrrrClientTest
# Here we verify the worker's coordination logic
lat = 32.90
lon = -97.04
valid_time = ~U[2026-03-28 18:00:00Z]
# Verify no profile exists initially
refute Weather.has_hrrr_profile?(lat, lon, valid_time)
# Simulate what perform does after a successful fetch
attrs = build_profile_attrs(lat, lon, valid_time, @sample_client_result)
{:ok, profile} = Weather.upsert_hrrr_profile(attrs)
assert profile.lat == lat
assert profile.lon == lon
assert profile.hpbl_m == 1500.0
assert profile.surface_temp_c == 25.0
assert Weather.has_hrrr_profile?(lat, lon, valid_time)
end
test "rounds lat/lon before checking existence" do
# Profile at rounded coordinates
Weather.upsert_hrrr_profile(%{
valid_time: ~U[2026-03-28 18:00:00Z],
lat: 32.90,
lon: -97.04,
profile: []
})
# Worker called with slightly different coords that round to same
job =
HrrrFetchWorker.new(%{
"lat" => 32.904,
"lon" => -97.039,
"valid_time" => "2026-03-28T18:00:00Z"
})
# Should skip because rounded coords match
assert :ok = HrrrFetchWorker.perform(%Oban.Job{args: job.changes.args})
# Only one profile should exist
count = Repo.aggregate(HrrrProfile, :count)
assert count == 1
end
end
defp build_profile_attrs(lat, lon, valid_time, client_result) do
%{
valid_time: valid_time,
lat: lat,
lon: lon,
run_time: client_result.run_time,
profile: client_result.profile,
hpbl_m: client_result.hpbl_m,
pwat_mm: client_result.pwat_mm,
surface_temp_c: client_result.surface_temp_c,
surface_dewpoint_c: client_result.surface_dewpoint_c,
surface_pressure_mb: client_result.surface_pressure_mb
}
end
end