277 @spec/@type annotations added to 58 files covering all public APIs: contexts (propagation, radio, weather, terrain, beacons, commercial), GRIB2 decoders, terrain analysis, duct detection, rain scatter, CSV/ADIF import, weather clients, and all Ecto schemas. Dialyzer passes with 0 errors.
62 lines
1.7 KiB
Elixir
62 lines
1.7 KiB
Elixir
defmodule Microwaveprop.Propagation.FreshnessMonitor do
|
|
@moduledoc """
|
|
Monitors propagation score freshness and enqueues grid worker jobs
|
|
when data is stale. Checks every 5 minutes. Covers missed cron ticks,
|
|
slow deploys, worker crashes, and any other gap in hourly scoring.
|
|
"""
|
|
|
|
use GenServer
|
|
|
|
alias Microwaveprop.Propagation
|
|
alias Microwaveprop.Workers.PropagationGridWorker
|
|
|
|
require Logger
|
|
|
|
@check_interval to_timeout(minute: 5)
|
|
@stale_threshold_minutes 120
|
|
|
|
@spec start_link(keyword()) :: GenServer.on_start() | :ignore
|
|
def start_link(_opts) do
|
|
if Application.get_env(:microwaveprop, :start_freshness_monitor, true) do
|
|
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
|
|
else
|
|
:ignore
|
|
end
|
|
end
|
|
|
|
@impl true
|
|
def init(:ok) do
|
|
send(self(), :check)
|
|
{:ok, %{}}
|
|
end
|
|
|
|
@impl true
|
|
def handle_info(:check, state) do
|
|
check_freshness()
|
|
Process.send_after(self(), :check, @check_interval)
|
|
{:noreply, state}
|
|
end
|
|
|
|
defp check_freshness do
|
|
case Propagation.latest_valid_time() do
|
|
nil ->
|
|
Logger.info("FreshnessMonitor: no scores found, enqueuing grid worker")
|
|
enqueue_if_not_queued()
|
|
|
|
latest ->
|
|
age = DateTime.diff(DateTime.utc_now(), latest, :minute)
|
|
|
|
if age > @stale_threshold_minutes do
|
|
Logger.info("FreshnessMonitor: scores are #{age}m old, enqueuing grid worker")
|
|
enqueue_if_not_queued()
|
|
end
|
|
end
|
|
end
|
|
|
|
defp enqueue_if_not_queued do
|
|
# Oban unique constraint on PropagationGridWorker prevents duplicates —
|
|
# if a job is already available/scheduled/executing/retryable within the
|
|
# last hour, insert is a no-op.
|
|
Oban.insert(PropagationGridWorker.new(%{}))
|
|
end
|
|
end
|