prop/lib/microwaveprop/workers/propagation_grid_worker.ex
Graham McIntire 1e205cb471
Add propagation context, grid worker, and fix merge issues
- Replace stub propagation.ex with real implementation (score_grid_point,
  upsert_scores, latest_scores, latest_valid_time)
- Add PropagationGridWorker (hourly Oban cron) for CONUS grid scoring
- Add mix propagation_grid task for manual triggering
- Fix duplicate extract_grid/2 from parallel merges
- Fix extract_grid to skip outside-grid points instead of erroring
- Add propagation queue to Oban config
2026-03-30 17:18:05 -05:00

64 lines
1.9 KiB
Elixir

defmodule Microwaveprop.Workers.PropagationGridWorker do
@moduledoc """
Hourly Oban worker that downloads the latest HRRR data and computes
propagation scores across the CONUS grid for all bands.
"""
use Oban.Worker,
queue: :propagation,
max_attempts: 3,
unique: [period: 3600, states: [:available, :scheduled, :executing]]
alias Microwaveprop.Propagation
alias Microwaveprop.Propagation.Grid
alias Microwaveprop.Weather.HrrrClient
require Logger
@impl Oban.Worker
def perform(%Oban.Job{}) do
valid_time = HrrrClient.nearest_hrrr_hour(DateTime.utc_now())
points = Grid.conus_points()
Logger.info("PropagationGrid: fetching HRRR for #{valid_time}, #{length(points)} points")
with {:ok, grid_data} <- HrrrClient.fetch_grid(points, valid_time) do
scores =
grid_data
|> Task.async_stream(
fn {{lat, lon}, profile} ->
band_scores = Propagation.score_grid_point(profile, valid_time)
Enum.map(band_scores, fn result ->
%{
lat: lat,
lon: lon,
valid_time: valid_time,
band_mhz: result.band_mhz,
score: result.score,
factors: result.factors
}
end)
end,
max_concurrency: System.schedulers_online() * 2,
timeout: 30_000
)
|> Enum.flat_map(fn
{:ok, results} -> results
{:exit, _reason} -> []
end)
Logger.info("PropagationGrid: computed #{length(scores)} scores, upserting")
case Propagation.upsert_scores(scores) do
{:ok, count} ->
Logger.info("PropagationGrid: upserted #{count} scores for #{valid_time}")
:ok
error ->
Logger.error("PropagationGrid: upsert failed: #{inspect(error)}")
error
end
end
end
end