Stage 3 (Elixir foundation) of the HRDPS plan. Lands the scaffolding that lets the Rust prop-grid-rs HRDPS branch be wired up cleanly when it ships, without further DB or Elixir-side changes. What ships: - `grid_tasks.source` column (default 'hrrr', backfill is a no-op). Replaces the (run_time, forecast_hour, kind) unique index with a 4-column key (..., source) so HRRR and HRDPS analysis/forecast rows for the same cycle coexist. - `Grid.hrdps_only_points/0` — Canadian cells inside HRDPS bbox (49-60°N, -141 to -52°W) but outside HRRR's CONUS bbox. Disjoint from `conus_points/0` by construction so the two grids never double-write the same (lat, lon). - `Grid.point_source/1` — `:hrrr | :hrdps | :neither` classification for a (lat, lon) pair. Routes per-QSO enrichment to the right NWP source. - `GridTaskEnqueuer.seed_with_analysis/2` and `seed/2` accept a `:source` option; defaults to "hrrr" so existing callers are unchanged. - `HrdpsGridWorker` cycle seeder mirroring PropagationGridWorker's shape but for HRDPS's 4×/day cadence. `:hrdps` queue added to base Oban config. What's deferred: - The Rust `prop-grid-rs` worker doesn't yet branch on `task.source`. Until that ships, `HrdpsGridWorker` stays out of runtime.exs cron — it would just produce rows that the Rust worker mishandles as HRRR. Module doc explicitly notes the dormant state. - Map overlay extension and FreshnessMonitor HRDPS staleness deferred to follow-up sessions once real data flows. Activation path documented in the updated plan file. Coverage cap recorded as Stage 8 decision: 60°N for v1 (SRTM stops there). Arctic CDEM deferred to a follow-up plan.
66 lines
2.1 KiB
Elixir
66 lines
2.1 KiB
Elixir
defmodule Microwaveprop.Workers.HrdpsGridWorkerTest do
|
|
use Microwaveprop.DataCase, async: false
|
|
use Oban.Testing, repo: Microwaveprop.Repo
|
|
|
|
import Ecto.Query
|
|
|
|
alias Microwaveprop.Repo
|
|
alias Microwaveprop.Workers.HrdpsGridWorker
|
|
|
|
setup do
|
|
original = Application.get_env(:microwaveprop, :hrdps_cycle_available_fn)
|
|
|
|
on_exit(fn ->
|
|
if original,
|
|
do: Application.put_env(:microwaveprop, :hrdps_cycle_available_fn, original),
|
|
else: Application.delete_env(:microwaveprop, :hrdps_cycle_available_fn)
|
|
end)
|
|
end
|
|
|
|
describe "pick_run_time/1" do
|
|
test "returns the now-4h cycle when it's published" do
|
|
Application.put_env(:microwaveprop, :hrdps_cycle_available_fn, fn _dt -> true end)
|
|
|
|
now = ~U[2026-04-29 16:30:00Z]
|
|
run_time = HrdpsGridWorker.pick_run_time(now)
|
|
|
|
# now - 4h = 12:30Z; snap down to cycle = 12:00Z
|
|
assert run_time == ~U[2026-04-29 12:00:00Z]
|
|
end
|
|
|
|
test "falls back to the previous cycle when fresh isn't yet published" do
|
|
Application.put_env(:microwaveprop, :hrdps_cycle_available_fn, fn _dt -> false end)
|
|
|
|
now = ~U[2026-04-29 16:30:00Z]
|
|
run_time = HrdpsGridWorker.pick_run_time(now)
|
|
|
|
# now - 10h = 06:30Z; snap down to cycle = 06:00Z
|
|
assert run_time == ~U[2026-04-29 06:00:00Z]
|
|
end
|
|
end
|
|
|
|
describe "perform/1" do
|
|
test "seeds 19 grid_tasks rows tagged with source='hrdps'" do
|
|
Application.put_env(:microwaveprop, :hrdps_cycle_available_fn, fn _dt -> true end)
|
|
|
|
assert :ok = perform_job(HrdpsGridWorker, %{})
|
|
|
|
sources_count =
|
|
Repo.one(from(g in "grid_tasks", where: g.source == "hrdps", select: count()))
|
|
|
|
assert sources_count == 19
|
|
end
|
|
|
|
test "is idempotent — re-running the worker for the same cycle inserts nothing extra" do
|
|
Application.put_env(:microwaveprop, :hrdps_cycle_available_fn, fn _dt -> true end)
|
|
|
|
assert :ok = perform_job(HrdpsGridWorker, %{})
|
|
assert :ok = perform_job(HrdpsGridWorker, %{})
|
|
|
|
total =
|
|
Repo.one(from(g in "grid_tasks", where: g.source == "hrdps", select: count()))
|
|
|
|
assert total == 19
|
|
end
|
|
end
|
|
end
|