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
106 lines
3.2 KiB
Elixir
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
|