fix(path): read HRRR points from on-disk profile store
The summary card's "0 / 9 points" came from `Weather.find_nearest_hrrr` only checking the `hrrr_profiles` DB table, which is populated solely by per-QSO enrichment within ±0.07° / ±1 h. Arbitrary paths off any recently-enriched contact got nothing. Switch to ProfilesFile.read_point as the primary source — the hourly chain publishes full-CONUS f00..f18 cells there — and keep the DB-table query as the fallback when the on-disk store is empty or doesn't cover a point. Cell shape is flattened so the template and build_scoring/6 stay shape-agnostic.
This commit is contained in:
parent
332308b9c3
commit
2e60e65e2f
1 changed files with 53 additions and 5 deletions
|
|
@ -7,6 +7,7 @@ defmodule MicrowavepropWeb.PathLive do
|
|||
alias Microwaveprop.Ionosphere
|
||||
alias Microwaveprop.Propagation
|
||||
alias Microwaveprop.Propagation.BandConfig
|
||||
alias Microwaveprop.Propagation.ProfilesFile
|
||||
alias Microwaveprop.Propagation.Scorer
|
||||
alias Microwaveprop.Propagation.SporadicE
|
||||
alias Microwaveprop.Radio.CallsignClient
|
||||
|
|
@ -251,14 +252,24 @@ defmodule MicrowavepropWeb.PathLive do
|
|||
|
||||
# HRRR profiles along path — 9 evenly-spaced samples so mid-path
|
||||
# ducts that endpoint-only queries miss show up in the duct count.
|
||||
# Source preference: the on-disk grid profile store (full-CONUS
|
||||
# coverage from the hourly chain) is checked first; the per-QSO
|
||||
# `hrrr_profiles` DB table is only the fallback for points the
|
||||
# current chain hasn't published yet.
|
||||
now = DateTime.utc_now()
|
||||
midlat = (src.lat + dst.lat) / 2
|
||||
midlon = (src.lon + dst.lon) / 2
|
||||
|
||||
profile_valid_time = latest_profile_valid_time(now)
|
||||
|
||||
hrrr_points =
|
||||
src
|
||||
|> path_sample_points(dst, 9)
|
||||
|> Task.async_stream(&label_hrrr_point(&1, now), max_concurrency: 9, timeout: 10_000, on_timeout: :kill_task)
|
||||
|> Task.async_stream(&label_hrrr_point(&1, now, profile_valid_time),
|
||||
max_concurrency: 9,
|
||||
timeout: 10_000,
|
||||
on_timeout: :kill_task
|
||||
)
|
||||
|> Enum.flat_map(fn
|
||||
{:ok, nil} -> []
|
||||
{:ok, point} -> [point]
|
||||
|
|
@ -394,13 +405,50 @@ defmodule MicrowavepropWeb.PathLive do
|
|||
Regex.match?(~r/^[A-Ra-r]{2}[0-9]{2}/i, input)
|
||||
end
|
||||
|
||||
defp label_hrrr_point({label, lat, lon}, now) do
|
||||
case Weather.find_nearest_hrrr(lat, lon, now) do
|
||||
nil -> nil
|
||||
profile -> %{label: label, profile: profile}
|
||||
defp label_hrrr_point({label, lat, lon}, now, profile_valid_time) do
|
||||
case profile_valid_time && ProfilesFile.read_point(profile_valid_time, lat, lon) do
|
||||
%{} = cell ->
|
||||
%{label: label, profile: profile_from_cell(cell, profile_valid_time)}
|
||||
|
||||
_ ->
|
||||
case Weather.find_nearest_hrrr(lat, lon, now) do
|
||||
nil -> nil
|
||||
profile -> %{label: label, profile: profile}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The most recent on-disk profile file at or before `now`. Falls back
|
||||
# to the earliest available file if everything cached is in the future
|
||||
# (cold-start state during a missed chain). Returns `nil` when the
|
||||
# store is empty — caller then drops to the DB-table fallback.
|
||||
defp latest_profile_valid_time(now) do
|
||||
case ProfilesFile.list_valid_times() do
|
||||
[] -> nil
|
||||
times -> pick_latest_at_or_before(times, now)
|
||||
end
|
||||
end
|
||||
|
||||
defp pick_latest_at_or_before(times, now) do
|
||||
case Enum.filter(times, fn t -> DateTime.compare(t, now) != :gt end) do
|
||||
[] -> List.first(times)
|
||||
past -> Enum.max(past, DateTime)
|
||||
end
|
||||
end
|
||||
|
||||
# ProfilesFile cells nest the per-cell weather under `:profile`; the
|
||||
# template + scoring code expect a flat HrrrProfile-shaped map. Lift
|
||||
# the inner fields up and stamp `lat`/`lon`/`valid_time` so downstream
|
||||
# code is shape-agnostic.
|
||||
defp profile_from_cell(cell, valid_time) do
|
||||
inner = Map.get(cell, :profile, %{}) || %{}
|
||||
|
||||
inner
|
||||
|> Map.put_new(:lat, Map.get(cell, :lat))
|
||||
|> Map.put_new(:lon, Map.get(cell, :lon))
|
||||
|> Map.put_new(:valid_time, valid_time)
|
||||
end
|
||||
|
||||
# Linear interpolation along the great-circle-approximate path. Good
|
||||
# enough at the path-calculator's typical range (<2000 km); for longer
|
||||
# paths the path deviates from a rhumb line but we're picking
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue