prop/test/microwaveprop/weather/grid_cache_test.exs
Graham McIntire 253adaf89b Cache /weather grid, defer contact show mount, cache stats
- 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.
2026-04-12 12:55:50 -05:00

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