78 lines
2.7 KiB
Elixir
78 lines
2.7 KiB
Elixir
defmodule Microwaveprop.Workers.IonosphereFetchWorker do
|
|
@moduledoc """
|
|
Polls GIRO DIDBase for real-time ionosonde measurements (foF2, foE,
|
|
foEs, hmF2, MUFD) from a handful of CONUS stations. Drives the 144
|
|
MHz sporadic-E and HF MUF scoring inputs.
|
|
|
|
Runs on a ~10-minute cron — GIRO publishes each station at 7.5-minute
|
|
cadence so a 10-minute poll guarantees we pick up every new sample
|
|
within one cycle without hammering the endpoint.
|
|
|
|
Pulls a 2-hour window each poll to catch any late-arriving samples
|
|
and to be resilient to the occasional missed run. Upserts are
|
|
idempotent on (station_code, valid_time).
|
|
"""
|
|
|
|
use Oban.Pro.Worker,
|
|
queue: :ionosphere,
|
|
max_attempts: 3,
|
|
unique: [period: 300, states: :incomplete]
|
|
|
|
alias Microwaveprop.Ionosphere
|
|
alias Microwaveprop.Ionosphere.GiroClient
|
|
|
|
require Logger
|
|
|
|
# CONUS stations that have been publishing reliably. Others (Boulder,
|
|
# Wallops, Austin, Idaho, Point Arguello) are in the GIRO catalog but
|
|
# intermittent — add them here once they come back online.
|
|
@stations [
|
|
%{code: "MHJ45", name: "Millstone Hill MA", lat: 42.6, lon: -71.5},
|
|
%{code: "AL945", name: "Alpena MI", lat: 45.1, lon: -83.6}
|
|
]
|
|
|
|
@lookback_seconds 2 * 3600
|
|
|
|
@impl Oban.Pro.Worker
|
|
def process(%Oban.Job{}) do
|
|
now = DateTime.truncate(DateTime.utc_now(), :second)
|
|
from_dt = DateTime.add(now, -@lookback_seconds, :second)
|
|
|
|
Enum.each(@stations, fn station ->
|
|
fetch_and_upsert(station, from_dt, now)
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
@doc false
|
|
@spec stations() :: [map()]
|
|
def stations, do: @stations
|
|
|
|
defp fetch_and_upsert(%{code: code, name: name}, from_dt, to_dt) do
|
|
case GiroClient.fetch(code, from_dt, to_dt) do
|
|
{:ok, observations} ->
|
|
{:ok, count} = Ionosphere.upsert_observations(code, observations)
|
|
Logger.info("IonosphereFetch: #{code} (#{name}) upserted #{count} observations")
|
|
|
|
{:error, reason} ->
|
|
Logger.warning("IonosphereFetch: #{code} (#{name}) failed: #{format_reason(reason)}")
|
|
end
|
|
end
|
|
|
|
# GIRO's TLS cert chains through a CA that isn't always in the pod's
|
|
# bundle, surfacing as a Req.TransportError with a multi-line erl_alert
|
|
# string. Inspecting the full struct dumps ~400 chars of stacktrace-y
|
|
# noise per station per 10-min cycle. Compress to something grep-able.
|
|
defp format_reason("GIRO request failed: " <> tail) do
|
|
cond do
|
|
String.contains?(tail, ":unknown_ca") -> "TLS unknown_ca (CA bundle missing)"
|
|
String.contains?(tail, ":timeout") -> "timeout"
|
|
String.contains?(tail, ":econnrefused") -> "connection refused"
|
|
true -> String.slice(tail, 0, 120)
|
|
end
|
|
end
|
|
|
|
defp format_reason(reason) when is_binary(reason), do: reason
|
|
defp format_reason(reason), do: inspect(reason, printable_limit: 200)
|
|
end
|