defmodule Silmataivas.WeatherPoller do require Logger alias Silmataivas.{Users, Notifications.NtfyNotifier} @api_url "https://api.openweathermap.org/data/2.5/forecast" # Check forecasts within the next 24 hours @alert_window_hours 24 def check_all do Logger.info("🔄 Checking weather forecast for all users...") Users.list_users_with_locations() |> Enum.each(&check_user_weather/1) end def check_user_weather(%{user_id: user_id, location: %{latitude: lat, longitude: lon}} = _user) do case fetch_forecast(lat, lon) do {:ok, forecasts} -> case find_first_alert_entry(forecasts) do nil -> :ok entry -> NtfyNotifier.send_alert(user_id, entry) end {:error, reason} -> Logger.error("❌ Error fetching forecast for user #{user_id}: #{inspect(reason)}") end end # Add this clause to handle users with missing location data def check_user_weather(%{user_id: user_id} = user) do Logger.warning( "⚠️ User #{user_id} has missing or incomplete location data: #{inspect(user)}", [] ) :ok end # Add a catch-all clause to handle unexpected data formats def check_user_weather(invalid_user) do Logger.error("❌ Invalid user data structure: #{inspect(invalid_user)}") :ok end defp fetch_forecast(lat, lon) do api_key = Application.fetch_env!(:silmataivas, :openweathermap_api_key) Req.get( url: @api_url, params: [ lat: lat, lon: lon, units: "metric", appid: api_key ] ) |> case do {:ok, %{status: 200, body: %{"list" => forecast_list}}} -> {:ok, forecast_list} {:ok, %{status: code, body: body}} -> {:error, {code, body}} error -> error end end defp dangerous_conditions?( %{ "main" => %{"temp" => temp}, "wind" => %{"speed" => speed}, "dt_txt" => time_str } = entry ) do rain_mm = get_in(entry, ["rain", "3h"]) || 0.0 wind_kmh = speed * 3.6 cond do wind_kmh > 80 -> log_reason("Wind", wind_kmh, time_str) rain_mm > 40 -> log_reason("Rain", rain_mm, time_str) temp < 0 -> log_reason("Temperature", temp, time_str) true -> false end end defp find_first_alert_entry(forecast_list) do now = DateTime.utc_now() forecast_list |> Enum.take_while(fn %{"dt" => ts} -> forecast_time = DateTime.from_unix!(ts) DateTime.diff(forecast_time, now, :hour) <= @alert_window_hours end) |> Enum.find(&dangerous_conditions?/1) end defp log_reason(type, value, time_str) do Logger.info("🚨 #{type} threshold exceeded: #{value} at #{time_str}") true end end