refactor(weather): move hrrr_data_fully_present? from enqueuer to Weather
Pure domain check — "does this contact already have HRRR profiles at every path point for its QSO hour" — has no business living in an Oban worker. Weather already owns has_hrrr_profile?/3 and round_to_hrrr_grid/2, so consolidation keeps HRRR knowledge in one context and leaves ContactWeatherEnqueueWorker focused on job coordination.
This commit is contained in:
parent
a81e55e348
commit
baab3c09f5
3 changed files with 99 additions and 17 deletions
|
|
@ -4,9 +4,11 @@ defmodule Microwaveprop.Weather do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
alias Microwaveprop.Propagation.ProfilesFile
|
alias Microwaveprop.Propagation.ProfilesFile
|
||||||
|
alias Microwaveprop.Radio
|
||||||
alias Microwaveprop.Repo
|
alias Microwaveprop.Repo
|
||||||
alias Microwaveprop.Weather.GefsProfile
|
alias Microwaveprop.Weather.GefsProfile
|
||||||
alias Microwaveprop.Weather.GridCache
|
alias Microwaveprop.Weather.GridCache
|
||||||
|
alias Microwaveprop.Weather.HrrrClient
|
||||||
alias Microwaveprop.Weather.HrrrNativeProfile
|
alias Microwaveprop.Weather.HrrrNativeProfile
|
||||||
alias Microwaveprop.Weather.HrrrProfile
|
alias Microwaveprop.Weather.HrrrProfile
|
||||||
alias Microwaveprop.Weather.IemClient
|
alias Microwaveprop.Weather.IemClient
|
||||||
|
|
@ -865,6 +867,33 @@ defmodule Microwaveprop.Weather do
|
||||||
|> Repo.exists?()
|
|> Repo.exists?()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns `true` when every path point for `contact` (pos1 → midpoint → pos2,
|
||||||
|
or just pos1 for one-endpoint contacts) already has an HRRR profile at the
|
||||||
|
nearest HRRR hour. Lets the enrichment enqueuer skip :queued → :queued
|
||||||
|
churn when the backing data is already present.
|
||||||
|
|
||||||
|
Returns `false` if `pos1` is nil — a contact with no position can't be
|
||||||
|
looked up.
|
||||||
|
"""
|
||||||
|
@spec hrrr_data_fully_present?(map()) :: boolean()
|
||||||
|
def hrrr_data_fully_present?(%{pos1: nil}), do: false
|
||||||
|
|
||||||
|
def hrrr_data_fully_present?(contact) do
|
||||||
|
rounded = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp)
|
||||||
|
|
||||||
|
case Radio.contact_path_points(contact) do
|
||||||
|
[] ->
|
||||||
|
false
|
||||||
|
|
||||||
|
points ->
|
||||||
|
Enum.all?(points, fn {lat, lon} ->
|
||||||
|
{rlat, rlon} = round_to_hrrr_grid(lat, lon)
|
||||||
|
has_hrrr_profile?(rlat, rlon, rounded)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec hrrr_for_contact(map()) :: HrrrProfile.t() | nil
|
@spec hrrr_for_contact(map()) :: HrrrProfile.t() | nil
|
||||||
def hrrr_for_contact(%{pos1: nil}), do: nil
|
def hrrr_for_contact(%{pos1: nil}), do: nil
|
||||||
|
|
||||||
|
|
@ -1018,7 +1047,7 @@ defmodule Microwaveprop.Weather do
|
||||||
|
|
||||||
def hrrr_profiles_for_path(contact) do
|
def hrrr_profiles_for_path(contact) do
|
||||||
contact
|
contact
|
||||||
|> Microwaveprop.Radio.contact_path_points()
|
|> Radio.contact_path_points()
|
||||||
|> Enum.map(fn {lat, lon} -> find_nearest_hrrr(lat, lon, contact.qso_timestamp) end)
|
|> Enum.map(fn {lat, lon} -> find_nearest_hrrr(lat, lon, contact.qso_timestamp) end)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
end
|
end
|
||||||
|
|
@ -1093,7 +1122,7 @@ defmodule Microwaveprop.Weather do
|
||||||
|
|
||||||
def narr_profiles_for_path(contact) do
|
def narr_profiles_for_path(contact) do
|
||||||
contact
|
contact
|
||||||
|> Microwaveprop.Radio.contact_path_points()
|
|> Radio.contact_path_points()
|
||||||
|> Enum.map(fn {lat, lon} -> find_nearest_narr(lat, lon, contact.qso_timestamp) end)
|
|> Enum.map(fn {lat, lon} -> find_nearest_narr(lat, lon, contact.qso_timestamp) end)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
end
|
end
|
||||||
|
|
@ -1261,7 +1290,7 @@ defmodule Microwaveprop.Weather do
|
||||||
|
|
||||||
def iemre_for_path(contact) do
|
def iemre_for_path(contact) do
|
||||||
contact
|
contact
|
||||||
|> Microwaveprop.Radio.contact_path_points()
|
|> Radio.contact_path_points()
|
||||||
|> Enum.map(fn {lat, lon} -> find_nearest_iemre(lat, lon, contact.qso_timestamp) end)
|
|> Enum.map(fn {lat, lon} -> find_nearest_iemre(lat, lon, contact.qso_timestamp) end)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -88,25 +88,12 @@ defmodule Microwaveprop.Workers.ContactWeatherEnqueueWorker do
|
||||||
cond do
|
cond do
|
||||||
is_nil(contact.pos1) -> []
|
is_nil(contact.pos1) -> []
|
||||||
NarrClient.in_coverage?(contact.qso_timestamp) -> []
|
NarrClient.in_coverage?(contact.qso_timestamp) -> []
|
||||||
hrrr_data_fully_present?(contact) -> []
|
Weather.hrrr_data_fully_present?(contact) -> []
|
||||||
true -> [contact.id]
|
true -> [contact.id]
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp hrrr_data_fully_present?(contact) do
|
|
||||||
alias Microwaveprop.Weather.HrrrClient
|
|
||||||
|
|
||||||
rounded = HrrrClient.nearest_hrrr_hour(contact.qso_timestamp)
|
|
||||||
|
|
||||||
contact
|
|
||||||
|> Radio.contact_path_points()
|
|
||||||
|> Enum.all?(fn {lat, lon} ->
|
|
||||||
{rlat, rlon} = Weather.round_to_hrrr_grid(lat, lon)
|
|
||||||
Weather.has_hrrr_profile?(rlat, rlon, rounded)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp mark_enrichment_statuses(contact, types, jobs_by_type) do
|
defp mark_enrichment_statuses(contact, types, jobs_by_type) do
|
||||||
ids = [contact.id]
|
ids = [contact.id]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -656,6 +656,72 @@ defmodule Microwaveprop.WeatherTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "hrrr_data_fully_present?/1" do
|
||||||
|
# nearest_hrrr_hour rounds <:30 down; pick 18:15 so it maps to 18:00 to
|
||||||
|
# match the valid_time in @hrrr_profile_attrs.
|
||||||
|
@contact_time ~U[2026-03-28 18:15:00Z]
|
||||||
|
|
||||||
|
test "returns true when a pos1-only contact's snapped point has a profile" do
|
||||||
|
Weather.upsert_hrrr_profile(@hrrr_profile_attrs)
|
||||||
|
|
||||||
|
contact = %{
|
||||||
|
pos1: %{"lat" => 32.9047, "lon" => -97.0382},
|
||||||
|
pos2: nil,
|
||||||
|
qso_timestamp: @contact_time
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Weather.hrrr_data_fully_present?(contact)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns false when a pos1-only contact's snapped point has no profile" do
|
||||||
|
contact = %{
|
||||||
|
pos1: %{"lat" => 40.0, "lon" => -80.0},
|
||||||
|
pos2: nil,
|
||||||
|
qso_timestamp: @contact_time
|
||||||
|
}
|
||||||
|
|
||||||
|
refute Weather.hrrr_data_fully_present?(contact)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns true only when every path point (endpoint + midpoint + endpoint) has a profile" do
|
||||||
|
# Contact from (32.9, -97.04) to (33.1, -96.96) → midpoint ≈ (33.0, -97.0)
|
||||||
|
for {lat, lon} <- [{32.9, -97.04}, {33.0, -97.0}, {33.1, -96.96}] do
|
||||||
|
Weather.upsert_hrrr_profile(%{@hrrr_profile_attrs | lat: lat, lon: lon})
|
||||||
|
end
|
||||||
|
|
||||||
|
contact = %{
|
||||||
|
pos1: %{"lat" => 32.9, "lon" => -97.04},
|
||||||
|
pos2: %{"lat" => 33.1, "lon" => -96.96},
|
||||||
|
qso_timestamp: @contact_time
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Weather.hrrr_data_fully_present?(contact)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns false when the midpoint profile is missing" do
|
||||||
|
# Only endpoints present — midpoint (33.0, -97.0) missing
|
||||||
|
for {lat, lon} <- [{32.9, -97.04}, {33.1, -96.96}] do
|
||||||
|
Weather.upsert_hrrr_profile(%{@hrrr_profile_attrs | lat: lat, lon: lon})
|
||||||
|
end
|
||||||
|
|
||||||
|
contact = %{
|
||||||
|
pos1: %{"lat" => 32.9, "lon" => -97.04},
|
||||||
|
pos2: %{"lat" => 33.1, "lon" => -96.96},
|
||||||
|
qso_timestamp: @contact_time
|
||||||
|
}
|
||||||
|
|
||||||
|
refute Weather.hrrr_data_fully_present?(contact)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "returns false when pos1 is nil" do
|
||||||
|
refute Weather.hrrr_data_fully_present?(%{
|
||||||
|
pos1: nil,
|
||||||
|
pos2: nil,
|
||||||
|
qso_timestamp: @contact_time
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "round_to_hrrr_grid/2" do
|
describe "round_to_hrrr_grid/2" do
|
||||||
test "rounds coordinates to 2 decimal places" do
|
test "rounds coordinates to 2 decimal places" do
|
||||||
assert Weather.round_to_hrrr_grid(32.9047, -97.0382) == {32.90, -97.04}
|
assert Weather.round_to_hrrr_grid(32.9047, -97.0382) == {32.90, -97.04}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue