fix(status): align NARR done-count tolerance with fetcher snap
count_narr_done/0 checked narr_profiles.valid_time within qso_timestamp
± 30 min — but NARR analyses live at 3-hour marks (00Z/03Z/.../21Z) and
NarrClient.snap_to_analysis_hour/1 floors qso_timestamp to the previous
mark before fetching. A QSO at 18:50Z snaps to 18:00Z on the fetcher
side; the profile stored at 18:00Z fell outside the widget's [18:20,
19:20] window, so the status page showed 6/208 done when actual
coverage was 208/208.
Fix: use equality against the Postgres equivalent of the snap
(date_trunc('hour', ts) - make_interval(hours => hour % 3)).
Comment cites NarrClient.snap_to_analysis_hour/1 so future changes keep
them in sync.
This commit is contained in:
parent
432ae83743
commit
fbbfe037bc
2 changed files with 47 additions and 3 deletions
|
|
@ -110,8 +110,13 @@ defmodule MicrowavepropWeb.StatusLive do
|
|||
)
|
||||
end
|
||||
|
||||
# "Done" means a narr_profiles row within 0.15° + 30 min of the contact,
|
||||
# matching `Weather.find_nearest_narr/3` lookup tolerance.
|
||||
# "Done" means a narr_profiles row within 0.15° of the contact and
|
||||
# stamped at the floor-snapped 3-hour analysis mark.
|
||||
#
|
||||
# NARR analyses live at 00/03/06/09/12/15/18/21 UTC. The fetcher calls
|
||||
# `NarrClient.snap_to_analysis_hour/1`, which floors qso_timestamp to the
|
||||
# previous 3-hour mark — so a contact at 18:50Z yields a profile valid at
|
||||
# 18:00Z. The SQL snap below must stay in sync with that Elixir function.
|
||||
defp count_narr_done do
|
||||
coverage_end = NarrClient.coverage_end()
|
||||
|
||||
|
|
@ -127,7 +132,8 @@ defmodule MicrowavepropWeb.StatusLive do
|
|||
FROM narr_profiles p
|
||||
WHERE p.lat BETWEEN ((c.pos1->>'lat')::float - 0.15) AND ((c.pos1->>'lat')::float + 0.15)
|
||||
AND p.lon BETWEEN ((c.pos1->>'lon')::float - 0.15) AND ((c.pos1->>'lon')::float + 0.15)
|
||||
AND p.valid_time BETWEEN (c.qso_timestamp - INTERVAL '30 minutes') AND (c.qso_timestamp + INTERVAL '30 minutes')
|
||||
AND p.valid_time = date_trunc('hour', c.qso_timestamp)
|
||||
- make_interval(hours => CAST(EXTRACT(HOUR FROM c.qso_timestamp) AS integer) % 3)
|
||||
)
|
||||
""",
|
||||
[coverage_end]
|
||||
|
|
|
|||
|
|
@ -98,6 +98,44 @@ defmodule MicrowavepropWeb.StatusLiveTest do
|
|||
assert narr_row =~ "0 / 1"
|
||||
end
|
||||
|
||||
# NARR analyses are archived at 3-hour marks (00/03/06/09/12/15/18/21 UTC).
|
||||
# `NarrClient.snap_to_analysis_hour/1` floors the QSO timestamp to the
|
||||
# previous 3-hour mark before fetch. The status numerator query must mirror
|
||||
# that snap — a naive ±30 min window around the raw qso_timestamp misses
|
||||
# the stored profile whenever the contact lands more than 30 min past the
|
||||
# last analysis hour.
|
||||
test "NARR progress counts contacts whose qso_timestamp doesn't land on a 3-hour analysis mark",
|
||||
%{conn: conn} do
|
||||
{:ok, _} =
|
||||
%Contact{}
|
||||
|> Contact.changeset(%{
|
||||
station1: "W5OFF",
|
||||
station2: "K5OFF",
|
||||
qso_timestamp: ~U[2010-06-15 18:50:00Z],
|
||||
mode: "CW",
|
||||
band: Decimal.new("1296"),
|
||||
grid1: "EM12",
|
||||
grid2: "EM00",
|
||||
pos1: %{"lat" => 33.0, "lon" => -97.0},
|
||||
pos2: %{"lat" => 30.3, "lon" => -97.7},
|
||||
distance_km: Decimal.new("300")
|
||||
})
|
||||
|> Ecto.Changeset.change(%{hrrr_status: :unavailable})
|
||||
|> Repo.insert()
|
||||
|
||||
{:ok, _} =
|
||||
Repo.insert(%NarrProfile{
|
||||
valid_time: ~U[2010-06-15 18:00:00Z],
|
||||
lat: 33.0,
|
||||
lon: -97.0,
|
||||
ducting_detected: false
|
||||
})
|
||||
|
||||
{:ok, lv, _html} = live(conn, ~p"/status")
|
||||
narr_row = lv |> element(~s|[data-progress-key="narr"]|) |> render()
|
||||
assert narr_row =~ "1 / 1"
|
||||
end
|
||||
|
||||
# NARR eligibility is defined by qso_timestamp, not hrrr_status. A
|
||||
# pre-2014 contact whose hrrr_status is still :queued (the ping-pong
|
||||
# window before reconcile flips it) must still be counted — otherwise
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue