perf: add batch hrrr_profiles_for_contacts/1 to collapse N+1 per-contact queries into one DB round-trip

This commit is contained in:
Graham McInitre 2026-07-16 07:37:34 -05:00
parent 82e02ae0b8
commit 695bbf5cee

View file

@ -1738,6 +1738,79 @@ defmodule Microwaveprop.Weather do
|> Enum.reject(&is_nil/1)
end
@doc """
Batch version of `hrrr_profiles_for_path/1`. Queries all HRRR profiles
for a list of contacts in a single DB round-trip, then maps results
back per-contact. Returns a list of `{contact, [profile]}` tuples.
"""
@spec hrrr_profiles_for_contacts([map()]) :: [{map(), [HrrrProfile.t()]}]
def hrrr_profiles_for_contacts(contacts) do
for_result =
for c <- contacts, c.pos1 != nil, {lat, lon} <- Radio.contact_path_points(c) do
{{lat, lon, c.qso_timestamp}, c}
end
points =
Enum.uniq_by(for_result, fn {key, _} -> key end)
{keys, contact_map} =
Enum.reduce(points, {[], %{}}, fn {{lat, lon, ts} = key, c}, {ks, cm} ->
{[key | ks], Map.update(cm, c, [key], &[key | &1])}
end)
index = find_nearest_hrrrs_batch(keys)
Enum.map(contacts, fn c ->
point_keys = Map.get(contact_map, c, [])
profiles = point_keys |> Enum.map(&Map.get(index, &1)) |> Enum.reject(&is_nil/1)
{c, profiles}
end)
end
# Batch nearest-HRRR lookup: query all profiles in the union bounding
# box once, then match each point to its nearest profile in Elixir.
@spec find_nearest_hrrrs_batch([{float(), float(), DateTime.t()}]) :: %{
{float(), float(), DateTime.t()} => HrrrProfile.t()
}
defp find_nearest_hrrrs_batch([]), do: %{}
defp find_nearest_hrrrs_batch(points) do
d = 0.07
margin_s = 3600
{lats, lons, times} = Enum.unzip(points)
lat_min = Enum.min(lats) - d
lat_max = Enum.max(lats) + d
lon_min = Enum.min(lons) - d
lon_max = Enum.max(lons) + d
time_min = times |> Enum.min_by(&DateTime.to_unix/1) |> DateTime.add(-margin_s, :second)
time_max = times |> Enum.max_by(&DateTime.to_unix/1) |> DateTime.add(margin_s, :second)
profiles =
Repo.all(
from(h in HrrrProfile,
where:
h.lat >= ^lat_min and h.lat <= ^lat_max and
h.lon >= ^lon_min and h.lon <= ^lon_max and
h.valid_time >= ^time_min and h.valid_time <= ^time_max,
select: %{lat: h.lat, lon: h.lon, valid_time: h.valid_time, profile: h}
)
)
Map.new(points, fn {lat, lon, ts} ->
nearest =
Enum.min_by(
profiles,
fn p ->
abs(p.lat - lat) + abs(p.lon - lon) + abs(DateTime.diff(p.valid_time, ts))
end,
fn -> nil end
)
{{lat, lon, ts}, nearest && nearest.profile}
end)
end
@spec find_nearest_native_profile(float(), float(), DateTime.t()) ::
HrrrNativeProfile.t() | nil
def find_nearest_native_profile(lat, lon, timestamp) do