- Add Weather.GridCache: ETS cache of derived HRRR grid rows keyed by valid_time, cluster-synced via PubSub. Eagerly warmed from PropagationGridWorker after each upsert so /weather map pan/zoom and weather_point_detail hit zero DB on warm cache. - Replace latest_weather_grid DB query path with cache-first lookup + DB fallback. hrrr_profiles is 42M rows partitioned; pulling 3-10k rows per viewport on every pan was the main cost. - ContactLive.Show: defer the heavy enrichment loads (weather, solar, HRRR, terrain, IEMRE, elevation profile, ITU-R propagation analysis, data_sources) into a handle_info(:hydrate) that runs after the shell renders. Initial mount now returns nil placeholders; template already had :if guards for all of them. Shell-to-first-paint goes from ~500ms-2s down to ~20ms. - Cache fetch_queue_counts for 5s in ContactLive.Show — oban_jobs group by query was running on every contact page view. - Backfill stats: wrap count_unprocessed, fetch_stats, fetch_db_stats in Microwaveprop.Cache with 2-5s TTLs; bump refresh debounce from 1s to 2s so bulk enrichment events don't thrash the DB.
85 lines
2.5 KiB
Elixir
85 lines
2.5 KiB
Elixir
defmodule Microwaveprop.Weather.GridCacheTest do
|
|
use ExUnit.Case, async: false
|
|
|
|
alias Microwaveprop.Weather.GridCache
|
|
|
|
setup do
|
|
GridCache.clear()
|
|
:ok
|
|
end
|
|
|
|
describe "fetch/1" do
|
|
test "returns :miss when nothing is cached" do
|
|
assert GridCache.fetch(~U[2026-04-12 12:00:00Z]) == :miss
|
|
end
|
|
|
|
test "returns cached rows after put" do
|
|
rows = [%{lat: 32.0, lon: -97.0, temperature: 25.0}]
|
|
GridCache.put(~U[2026-04-12 12:00:00Z], rows)
|
|
assert {:ok, [%{lat: 32.0, lon: -97.0}]} = GridCache.fetch(~U[2026-04-12 12:00:00Z])
|
|
end
|
|
end
|
|
|
|
describe "fetch_bounds/2" do
|
|
setup do
|
|
rows = [
|
|
%{lat: 32.0, lon: -97.0, temperature: 25.0},
|
|
%{lat: 40.0, lon: -74.0, temperature: 20.0},
|
|
%{lat: 34.0, lon: -98.0, temperature: 28.0}
|
|
]
|
|
|
|
GridCache.put(~U[2026-04-12 12:00:00Z], rows)
|
|
:ok
|
|
end
|
|
|
|
test "returns all rows when bounds are nil" do
|
|
assert {:ok, list} = GridCache.fetch_bounds(~U[2026-04-12 12:00:00Z], nil)
|
|
assert length(list) == 3
|
|
end
|
|
|
|
test "returns only rows inside the bounds" do
|
|
bounds = %{"south" => 31.0, "north" => 35.0, "west" => -100.0, "east" => -95.0}
|
|
assert {:ok, list} = GridCache.fetch_bounds(~U[2026-04-12 12:00:00Z], bounds)
|
|
assert length(list) == 2
|
|
end
|
|
|
|
test "returns :miss when the valid_time is not cached" do
|
|
bounds = %{"south" => 0.0, "north" => 90.0, "west" => -180.0, "east" => 0.0}
|
|
assert GridCache.fetch_bounds(~U[2099-01-01 00:00:00Z], bounds) == :miss
|
|
end
|
|
end
|
|
|
|
describe "fetch_point/3" do
|
|
setup do
|
|
rows = [
|
|
%{lat: 32.0, lon: -97.0, temperature: 25.0},
|
|
%{lat: 33.0, lon: -97.0, temperature: 27.0}
|
|
]
|
|
|
|
GridCache.put(~U[2026-04-12 12:00:00Z], rows)
|
|
:ok
|
|
end
|
|
|
|
test "returns the cached row for a known point" do
|
|
assert {:ok, %{temperature: 25.0}} =
|
|
GridCache.fetch_point(~U[2026-04-12 12:00:00Z], 32.0, -97.0)
|
|
end
|
|
|
|
test "returns :miss for an unknown point" do
|
|
assert GridCache.fetch_point(~U[2026-04-12 12:00:00Z], 40.0, -74.0) == :miss
|
|
end
|
|
end
|
|
|
|
describe "latest_valid_time/0" do
|
|
test "returns the most recent cached valid_time" do
|
|
GridCache.put(~U[2026-04-12 10:00:00Z], [])
|
|
GridCache.put(~U[2026-04-12 14:00:00Z], [])
|
|
GridCache.put(~U[2026-04-12 12:00:00Z], [])
|
|
assert GridCache.latest_valid_time() == ~U[2026-04-12 14:00:00Z]
|
|
end
|
|
|
|
test "returns nil when nothing is cached" do
|
|
assert GridCache.latest_valid_time() == nil
|
|
end
|
|
end
|
|
end
|