towerops/lib/towerops/workers/weather_sync_worker.ex

77 lines
2 KiB
Elixir

defmodule Towerops.Workers.WeatherSyncWorker do
@moduledoc """
Oban worker that periodically fetches weather data for all sites with coordinates.
Runs every 15 minutes. Batches API calls with a small delay between each
to stay well within OpenWeatherMap's free tier (60 calls/min).
Also cleans up observations older than 30 days on each run.
"""
use Oban.Worker,
queue: :weather,
unique: [period: 600, states: [:available, :scheduled, :executing]]
alias Towerops.Sites
alias Towerops.Weather
require Logger
@impl Oban.Worker
def perform(%Oban.Job{}) do
api_key = Application.get_env(:towerops, :openweathermap_api_key)
if is_nil(api_key) or api_key == "" do
Logger.debug("Weather sync skipped: no OpenWeatherMap API key configured")
:ok
else
sync_all_sites()
Weather.cleanup_old_observations(30)
schedule_next()
:ok
end
end
defp sync_all_sites do
sites = Sites.list_sites_with_coordinates()
total = length(sites)
if total > 0 do
Logger.info("Weather sync: fetching weather for #{total} sites")
results = fetch_all_sites(sites)
ok_count = Enum.count(results, &(&1 == :ok))
Logger.info("Weather sync complete: #{ok_count}/#{total} sites updated")
end
end
defp fetch_all_sites(sites) do
sites
|> Enum.with_index(1)
|> Enum.map(fn {site, idx} ->
if idx > 1, do: Process.sleep(1_100)
fetch_site_weather(site)
end)
end
defp fetch_site_weather(site) do
case Weather.fetch_and_store(site) do
{:ok, _obs} ->
:ok
{:error, reason} ->
Logger.warning("Weather fetch failed for site #{site.name}: #{inspect(reason)}")
:error
end
end
defp schedule_next do
# Schedule next run in 15 minutes
case %{} |> new(schedule_in: 900) |> Oban.insert() do
{:ok, _job} = result ->
result
{:error, changeset} = error ->
Logger.error("Failed to schedule next weather sync job: #{inspect(changeset)}")
error
end
end
end