defmodule Microwaveprop.Commercial.PollWorker do @moduledoc false use Oban.Pro.Worker, queue: :commercial, max_attempts: 1 alias Microwaveprop.Commercial alias Microwaveprop.Commercial.SnmpClient alias Microwaveprop.Weather alias Microwaveprop.Weather.IemClient require Logger @impl Oban.Pro.Worker def process(%Oban.Job{}) do links = Commercial.enabled_links() poll_and_record(links, &SnmpClient.poll/3) fetch_weather(links) :ok end @spec poll_and_record([map()], function()) :: :ok def poll_and_record(links, poll_fn) do now = DateTime.utc_now() Enum.each(links, &poll_link(&1, poll_fn, now)) end defp poll_link(link, poll_fn, now) do case poll_fn.(link.host, link.community, link.radio_type) do {:ok, data} -> save_sample(link, data, now) {:error, reason} -> Logger.warning("SNMP poll failed for #{link.label} (#{link.host}): #{inspect(reason)}") end end defp save_sample(link, data, now) do attrs = data |> Map.put(:link_id, link.id) |> Map.put(:sampled_at, now) case Commercial.create_sample(attrs) do {:ok, _sample} -> Logger.info("Recorded sample for #{link.label}") {:error, changeset} -> Logger.warning("Failed to save sample for #{link.label}: #{inspect(changeset.errors)}") end end @doc """ Fetch the last hour of ASOS data for every unique weather station referenced by the given links. Exposed (not private) so tests can exercise the IEM fetch + upsert path without having to drive `perform/1` through the real SNMP layer. """ @spec fetch_weather([Commercial.Link.t()]) :: :ok def fetch_weather(links) do stations = links |> Enum.map(& &1.weather_station) |> Enum.reject(&is_nil/1) |> Enum.uniq() now = DateTime.utc_now() start_dt = DateTime.add(now, -3600, :second) Enum.each(stations, &fetch_station_weather(&1, start_dt, now)) end defp fetch_station_weather(station_code, start_dt, now) do {:ok, station} = Weather.find_or_create_station(%{ station_code: station_code, station_type: "asos", name: station_code, lat: 0.0, lon: 0.0 }) case IemClient.fetch_asos(station_code, start_dt, now) do {:ok, rows} -> ingest_asos(station, station_code, rows) {:error, reason} -> Logger.warning("ASOS fetch failed for #{station_code}: #{inspect(reason)}") end end defp ingest_asos(station, station_code, rows) do rows |> Enum.filter(& &1.observed_at) |> Enum.each(&Weather.upsert_surface_observation(station, &1)) Logger.info("Fetched #{length(rows)} ASOS observations for #{station_code}") end end