Sync map progress chip to what's actually loadable
The "Updating propagation +Nh" chip was double-misleading in prod: the label's +Nh was relative to the HRRR run time (which lags wall clock by ~2h), and the progress broadcast fired before the hour was fetched + persisted — so the chip could read "+8h" while the map timeline only extended to +4h. Reframe the label as "through now" / "through +Nh" / "through Nh ago" by computing the offset from valid_time → now in PipelineStatus. running_detail, so it matches the semantic the user reads off the timeline. Move the PubSub broadcast in PropagationGridWorker to fire after Propagation.replace_scores/2 succeeds, so the chip only advances once that forecast hour is readable from ScoresFile.
This commit is contained in:
parent
9d2d67305e
commit
11617e730b
4 changed files with 72 additions and 28 deletions
|
|
@ -45,16 +45,38 @@ defmodule Microwaveprop.Propagation.PipelineStatus do
|
|||
@doc """
|
||||
Format a forecast-progress payload (as broadcast by
|
||||
`PropagationGridWorker`) into a short detail line for the running
|
||||
chip ("now", "+3h", …).
|
||||
chip ("through now", "through +3h", …).
|
||||
|
||||
Returns `nil` when the payload has no `forecast_hour` so callers can
|
||||
The label expresses how far the pipeline has advanced relative to
|
||||
*now*, not relative to the HRRR run time: the map can scrub the
|
||||
timeline through this instant. This is the semantic the user expects
|
||||
when watching the chip advance — HRRR lagging the wall clock by
|
||||
~2 hours means f00's valid_time is actually "2h ago", and "+Nh" in
|
||||
worker-frame coordinates is Nh-2 in user-frame coordinates.
|
||||
|
||||
Returns `nil` when the payload has no `valid_time` so callers can
|
||||
fall back to the base detail (e.g. "HRRR run") from `current/0`.
|
||||
"""
|
||||
@spec running_detail(map() | nil) :: String.t() | nil
|
||||
def running_detail(%{forecast_hour: 0}), do: "now"
|
||||
def running_detail(%{forecast_hour: fh}) when is_integer(fh) and fh > 0, do: "+#{fh}h"
|
||||
def running_detail(%{valid_time: %DateTime{} = valid_time}) do
|
||||
diff_minutes = DateTime.diff(valid_time, DateTime.utc_now(), :minute)
|
||||
"through " <> format_offset(diff_minutes)
|
||||
end
|
||||
|
||||
def running_detail(_), do: nil
|
||||
|
||||
defp format_offset(diff) when diff >= -30 and diff <= 30, do: "now"
|
||||
|
||||
defp format_offset(diff) when diff > 30 do
|
||||
hours = div(diff + 30, 60)
|
||||
"+#{hours}h"
|
||||
end
|
||||
|
||||
defp format_offset(diff) do
|
||||
hours = div(-diff + 30, 60)
|
||||
"#{hours}h ago"
|
||||
end
|
||||
|
||||
@spec current() :: t()
|
||||
def current do
|
||||
case running_workers() do
|
||||
|
|
|
|||
|
|
@ -157,14 +157,6 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do
|
|||
defp process_forecast_hour(points, run_time, forecast_hour, valid_time) do
|
||||
label = "f#{String.pad_leading(Integer.to_string(forecast_hour), 2, "0")}"
|
||||
|
||||
# Emit progress so the map page's pipeline status chip can show
|
||||
# which forecast hour is currently being scored ("now", "+1h", …).
|
||||
Phoenix.PubSub.broadcast(
|
||||
Microwaveprop.PubSub,
|
||||
"propagation:pipeline",
|
||||
{:propagation_pipeline_progress, %{forecast_hour: forecast_hour, valid_time: valid_time}}
|
||||
)
|
||||
|
||||
case timed(label, fn ->
|
||||
HrrrClient.fetch_grid(points, run_time, forecast_hour: forecast_hour)
|
||||
end) do
|
||||
|
|
@ -234,6 +226,18 @@ defmodule Microwaveprop.Workers.PropagationGridWorker do
|
|||
{:ok, count} ->
|
||||
Logger.info("PropagationGrid: #{label} → #{count} scores for #{valid_time}")
|
||||
warm_cache(valid_time)
|
||||
|
||||
# Broadcast progress *after* persistence so the map's
|
||||
# pipeline chip only advances to "through +Nh" once that
|
||||
# hour is actually readable from the scores file. Emitting
|
||||
# this before the fetch would push the chip ahead of the
|
||||
# map by the full forecast-hour wall time (~10 minutes).
|
||||
Phoenix.PubSub.broadcast(
|
||||
Microwaveprop.PubSub,
|
||||
"propagation:pipeline",
|
||||
{:propagation_pipeline_progress, %{forecast_hour: forecast_hour, valid_time: valid_time}}
|
||||
)
|
||||
|
||||
:ok
|
||||
|
||||
error ->
|
||||
|
|
|
|||
|
|
@ -166,18 +166,34 @@ defmodule Microwaveprop.Propagation.PipelineStatusTest do
|
|||
end
|
||||
|
||||
describe "running_detail/1" do
|
||||
test "forecast hour 0 renders as 'now'" do
|
||||
assert PipelineStatus.running_detail(%{forecast_hour: 0}) == "now"
|
||||
test "valid_time within half an hour of now renders as 'through now'" do
|
||||
now = DateTime.utc_now()
|
||||
assert PipelineStatus.running_detail(%{valid_time: now}) == "through now"
|
||||
assert PipelineStatus.running_detail(%{valid_time: DateTime.add(now, 20 * 60, :second)}) == "through now"
|
||||
assert PipelineStatus.running_detail(%{valid_time: DateTime.add(now, -20 * 60, :second)}) == "through now"
|
||||
end
|
||||
|
||||
test "positive forecast hours render as +Nh" do
|
||||
assert PipelineStatus.running_detail(%{forecast_hour: 1}) == "+1h"
|
||||
assert PipelineStatus.running_detail(%{forecast_hour: 18}) == "+18h"
|
||||
test "valid_time in the future renders as 'through +Nh'" do
|
||||
now = DateTime.utc_now()
|
||||
|
||||
assert PipelineStatus.running_detail(%{valid_time: DateTime.add(now, 3 * 3600, :second)}) ==
|
||||
"through +3h"
|
||||
|
||||
assert PipelineStatus.running_detail(%{valid_time: DateTime.add(now, 12 * 3600, :second)}) ==
|
||||
"through +12h"
|
||||
end
|
||||
|
||||
test "returns nil when no forecast hour is provided" do
|
||||
test "valid_time in the past renders as 'through Nh ago'" do
|
||||
now = DateTime.utc_now()
|
||||
|
||||
assert PipelineStatus.running_detail(%{valid_time: DateTime.add(now, -2 * 3600, :second)}) ==
|
||||
"through 2h ago"
|
||||
end
|
||||
|
||||
test "returns nil when no valid_time is provided" do
|
||||
assert PipelineStatus.running_detail(nil) == nil
|
||||
assert PipelineStatus.running_detail(%{}) == nil
|
||||
assert PipelineStatus.running_detail(%{forecast_hour: 3}) == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -218,23 +218,25 @@ defmodule MicrowavepropWeb.MapLiveTest do
|
|||
assert html =~ "Updating propagation"
|
||||
assert html =~ ~s(data-pipeline-state="running")
|
||||
|
||||
Phoenix.PubSub.broadcast(
|
||||
Microwaveprop.PubSub,
|
||||
"propagation:pipeline",
|
||||
{:propagation_pipeline_progress, %{forecast_hour: 3, valid_time: ~U[2026-04-14 18:00:00Z]}}
|
||||
)
|
||||
|
||||
html = render(lv)
|
||||
assert html =~ "+3h"
|
||||
future = DateTime.add(DateTime.utc_now(), 3 * 3600, :second)
|
||||
|
||||
Phoenix.PubSub.broadcast(
|
||||
Microwaveprop.PubSub,
|
||||
"propagation:pipeline",
|
||||
{:propagation_pipeline_progress, %{forecast_hour: 0, valid_time: ~U[2026-04-14 15:00:00Z]}}
|
||||
{:propagation_pipeline_progress, %{forecast_hour: 5, valid_time: future}}
|
||||
)
|
||||
|
||||
html = render(lv)
|
||||
assert html =~ "now"
|
||||
assert html =~ "through +3h"
|
||||
|
||||
Phoenix.PubSub.broadcast(
|
||||
Microwaveprop.PubSub,
|
||||
"propagation:pipeline",
|
||||
{:propagation_pipeline_progress, %{forecast_hour: 2, valid_time: DateTime.utc_now()}}
|
||||
)
|
||||
|
||||
html = render(lv)
|
||||
assert html =~ "through now"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue