Three signal sources we already collect but weren't using: * NEXRAD composite reflectivity → rain rate via Marshall-Palmer, taken as max of HRRR-derived and NEXRAD-derived rate so fast convective cells between HRRR hourly analyses can still trigger the rain penalty. Only active on f00 — forecast hours can't see future radar. New Scorer.dbz_to_rain_rate_mmhr/1 with 5 dBZ noise floor and 150 mm/hr hail-safe ceiling. * hrrr_native_profiles.best_duct_band_ghz → Scorer.score_refractivity/4 applies a 1.15× boost when the cell's native-resolution duct supports the target band's frequency. HRRR pressure-level gradients systematically under-read thin trapping layers the native profile can resolve. Sub-band ducts do NOT boost — they're evidence that the gradient we have is all there is at the target frequency. * Commercial LOS link rx_power fading → inverse tropo sensor. Commercial.link_degradation_at/3 computes the average 7-day-baseline vs current delta across enabled links within 75 km, ignoring links where link_state != 1. Scorer.commercial_link_boost/2 adds +2 to +25 to the composite score for 3+ dB of fading. ~150 km radius around DFW is the only zone this helps today, but it's the first *measured* signal in the algorithm vs the model-derived proxies. Also fix a latent test bug exposed by the earlier ERA5 poll-timeout bump: era5_batch_client_test's "uncached path returns error" tests hung for up to an hour when run with direnv's real CDS key. New describe-level setup explicitly unsets the env var so the tests stay hermetic. 1,359 tests, 0 failures.
93 lines
3.1 KiB
Elixir
93 lines
3.1 KiB
Elixir
defmodule Microwaveprop.Weather.Era5BatchClientTest do
|
|
use Microwaveprop.DataCase, async: true
|
|
|
|
alias Microwaveprop.Weather.Era5BatchClient
|
|
alias Microwaveprop.Weather.Era5Profile
|
|
|
|
describe "tile_for_point/2" do
|
|
test "floors positive coordinates to a 2° lattice" do
|
|
assert {32, 96} = Era5BatchClient.tile_for_point(32.75, 97.5)
|
|
assert {32, 96} = Era5BatchClient.tile_for_point(33.99, 97.99)
|
|
assert {34, 98} = Era5BatchClient.tile_for_point(34.0, 98.0)
|
|
end
|
|
|
|
test "floors negative longitudes toward -infinity" do
|
|
assert {32, -98} = Era5BatchClient.tile_for_point(32.75, -97.25)
|
|
assert {32, -98} = Era5BatchClient.tile_for_point(33.0, -97.01)
|
|
assert {32, -100} = Era5BatchClient.tile_for_point(33.0, -99.0)
|
|
end
|
|
|
|
test "floors negative latitudes toward -infinity" do
|
|
assert {-2, 0} = Era5BatchClient.tile_for_point(-1.25, 0.5)
|
|
assert {-34, -60} = Era5BatchClient.tile_for_point(-33.5, -59.5)
|
|
end
|
|
|
|
test "edges land on the tile to the right/north" do
|
|
# 30.0 exactly → tile 30 (south/west edge)
|
|
assert {30, 96} = Era5BatchClient.tile_for_point(30.0, 97.5)
|
|
end
|
|
end
|
|
|
|
describe "fetch_month_into_db/1 idempotency" do
|
|
# This describe block exercises the uncached path which would otherwise
|
|
# hit the real CDS API if the developer has ERA5_CDS_API_KEY set via
|
|
# direnv. Clear the env var for these tests so the expected "API key not
|
|
# configured" short-circuit fires and the tests stay hermetic.
|
|
setup do
|
|
previous = System.get_env("ERA5_CDS_API_KEY")
|
|
System.delete_env("ERA5_CDS_API_KEY")
|
|
|
|
on_exit(fn ->
|
|
if previous, do: System.put_env("ERA5_CDS_API_KEY", previous)
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
test "returns {:ok, 0} when the month-tile already has any profile" do
|
|
# Seed one profile inside the 2014-03, tile (32, -98) window.
|
|
%Era5Profile{}
|
|
|> Era5Profile.changeset(%{
|
|
lat: 32.5,
|
|
lon: -97.25,
|
|
valid_time: ~U[2014-03-15 12:00:00Z],
|
|
profile: []
|
|
})
|
|
|> Repo.insert!()
|
|
|
|
# No CDS API key configured in tests → if the cache check fails, the
|
|
# function would return an ERA5_CDS_API_KEY error. {:ok, 0} means the
|
|
# short-circuit path was taken.
|
|
assert {:ok, 0} =
|
|
Era5BatchClient.fetch_month_into_db(%{
|
|
year: 2014,
|
|
month: 3,
|
|
tile_lat: 32,
|
|
tile_lon: -98
|
|
})
|
|
end
|
|
|
|
test "a profile outside the tile window does not count as cached" do
|
|
# This profile is in tile (30, -100), not (32, -98).
|
|
%Era5Profile{}
|
|
|> Era5Profile.changeset(%{
|
|
lat: 30.5,
|
|
lon: -99.25,
|
|
valid_time: ~U[2014-03-15 12:00:00Z],
|
|
profile: []
|
|
})
|
|
|> Repo.insert!()
|
|
|
|
# The (32, -98) tile is still uncached, so fetch_month_into_db tries
|
|
# to reach CDS and fails with missing API key, which bubbles up
|
|
# (rather than :ok, 0).
|
|
assert {:error, _} =
|
|
Era5BatchClient.fetch_month_into_db(%{
|
|
year: 2014,
|
|
month: 3,
|
|
tile_lat: 32,
|
|
tile_lon: -98
|
|
})
|
|
end
|
|
end
|
|
end
|