diff --git a/lib/microwaveprop/propagation.ex b/lib/microwaveprop/propagation.ex index 758ec4b0..d2366bd6 100644 --- a/lib/microwaveprop/propagation.ex +++ b/lib/microwaveprop/propagation.ex @@ -307,7 +307,7 @@ defmodule Microwaveprop.Propagation do end @doc """ - Variant of `scores_at/3` that always reads from the `.ntms` file on + Variant of `scores_at/3` that always reads from the `.prop` file on disk and overwrites the cache entry, rather than returning whatever the cache happens to hold. Use from update paths (the map's `propagation_updated` handler) where the underlying file has just @@ -385,7 +385,7 @@ defmodule Microwaveprop.Propagation do {snapped_lat, snapped_lon} = snap_to_grid(lat, lon) now = DateTime.utc_now() - # Use the on-disk .ntms file list as the authoritative timeline so + # Use the on-disk .prop file list as the authoritative timeline so # the chart never falls behind the main-map timeline (which also # reads the disk). The cache is still consulted per-hour for a # fast score lookup; a miss falls through to the file. @@ -474,9 +474,12 @@ defmodule Microwaveprop.Propagation do # Rebuild the factor breakdown for a clicked grid cell by rescoring # the persisted HRRR profile. Only analysis hours (f00) persist # profiles, so forecast hours fall back to the most recent analysis - # profile at or before the requested time — the atmospheric breakdown - # still explains what's driving the score even if sampled an hour or - # two earlier. + # profile within `@fallback_profile_lookback_hours` — the atmospheric + # breakdown still explains what's driving the score even if sampled + # an hour or two earlier. Outside the lookback we prefer to return + # an empty map so the UI shows "breakdown unavailable" rather than + # silently attributing scores to a day-old synoptic pattern. + @fallback_profile_lookback_hours 24 defp factors_for(band_mhz, valid_time, lat, lon) do case ProfilesFile.read_point(valid_time, lat, lon) do nil -> factors_from_fallback_profile(band_mhz, valid_time, lat, lon) @@ -485,7 +488,7 @@ defmodule Microwaveprop.Propagation do end defp factors_from_fallback_profile(band_mhz, valid_time, lat, lon) do - case latest_profile_time_at_or_before(valid_time) do + case latest_profile_time_within_lookback(valid_time) do nil -> %{} @@ -507,9 +510,13 @@ defmodule Microwaveprop.Propagation do end end - defp latest_profile_time_at_or_before(%DateTime{} = valid_time) do + defp latest_profile_time_within_lookback(%DateTime{} = valid_time) do + lookback_cutoff = DateTime.add(valid_time, -@fallback_profile_lookback_hours * 3600, :second) + ProfilesFile.list_valid_times() - |> Enum.filter(&(DateTime.compare(&1, valid_time) != :gt)) + |> Enum.filter(fn t -> + DateTime.compare(t, valid_time) != :gt and DateTime.compare(t, lookback_cutoff) != :lt + end) |> case do [] -> nil past -> Enum.max(past, DateTime) diff --git a/lib/microwaveprop/weather/hrrr_point_enqueuer.ex b/lib/microwaveprop/weather/hrrr_point_enqueuer.ex index 175e8952..1ec3eeaf 100644 --- a/lib/microwaveprop/weather/hrrr_point_enqueuer.ex +++ b/lib/microwaveprop/weather/hrrr_point_enqueuer.ex @@ -117,35 +117,48 @@ defmodule Microwaveprop.Weather.HrrrPointEnqueuer do groups = contacts - |> Enum.flat_map(fn contact -> - cond do - is_nil(contact.pos1) -> - [] - - # HRRR archive starts mid-2014; NARR covers the pre-2014 - # window. Pre-coverage contacts are NARR's responsibility. - NarrClient.in_coverage?(contact.qso_timestamp) -> - [] - - true -> - rounded_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) - - contact - |> Radio.contact_path_points() - |> Enum.flat_map(fn {lat, lon} -> - {rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) - - if Weather.has_hrrr_profile?(rlat, rlon, rounded_time) do - [] - else - [{rounded_time, {rlat, rlon}}] - end - end) - end - end) + |> Enum.flat_map(&contact_points/1) |> Enum.group_by(fn {time, _pt} -> time end, fn {_time, pt} -> pt end) |> Map.new(fn {time, pts} -> {time, Enum.uniq(pts)} end) enqueue(groups) end + + # Per-contact expansion shared by `enqueue_for_contacts/1`. Returns a + # flat list of `{valid_time, {lat, lon}}` tuples ready for grouping. + # HRRR archive starts mid-2014; contacts older than that belong to + # NARR, so they're dropped here. + defp contact_points(contact) do + alias Microwaveprop.Radio + alias Microwaveprop.Weather + alias Microwaveprop.Weather.HrrrClient + alias Microwaveprop.Weather.NarrClient + + cond do + is_nil(contact.pos1) -> + [] + + NarrClient.in_coverage?(contact.qso_timestamp) -> + [] + + true -> + rounded_time = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp) + + contact + |> Radio.contact_path_points() + |> Enum.flat_map(&point_for_rounded_time(&1, rounded_time)) + end + end + + defp point_for_rounded_time({lat, lon}, rounded_time) do + alias Microwaveprop.Weather + + {rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon) + + if Weather.has_hrrr_profile?(rlat, rlon, rounded_time) do + [] + else + [{rounded_time, {rlat, rlon}}] + end + end end diff --git a/lib/microwaveprop_web/components/layouts.ex b/lib/microwaveprop_web/components/layouts.ex index e48e8d28..87e476d9 100644 --- a/lib/microwaveprop_web/components/layouts.ex +++ b/lib/microwaveprop_web/components/layouts.ex @@ -106,14 +106,6 @@ defmodule MicrowavepropWeb.Layouts do - - - - <.flash_group flash={@flash} /> """ end diff --git a/lib/microwaveprop_web/components/layouts/root.html.heex b/lib/microwaveprop_web/components/layouts/root.html.heex index ecf43eee..fd323ec6 100644 --- a/lib/microwaveprop_web/components/layouts/root.html.heex +++ b/lib/microwaveprop_web/components/layouts/root.html.heex @@ -46,5 +46,12 @@
{@inner_content} + +