fix(weather): read .sgrid in read_bounds_hrdps_nearest/3
weather_grid_hrdps_at/2 is the only production caller and backs every /weather/cells?source=hrdps request, but it read the legacy chunked .mp.gz dirs exclusively. The pipeline writes <iso>.hrdps.sgrid, so the snap found a valid_time via list_valid_times_hrdps/0 (which does see .sgrid) and then read zero chunks for it. Delegate to read_bounds_hrdps/2, which already does the .sgrid-first, chunk-fallback read. Every existing test for this function hand-wrote legacy chunk dirs, which is how the .sgrid migration slipped past. Added coverage against a real .sgrid artifact, including the mid-migration case where both formats exist for one valid_time.
This commit is contained in:
parent
3d214f60f0
commit
ad600361e0
2 changed files with 67 additions and 1 deletions
|
|
@ -210,7 +210,7 @@ defmodule Microwaveprop.Weather.ScalarFile do
|
|||
when is_integer(window_seconds) and window_seconds >= 0 do
|
||||
case nearest_hrdps_valid_time(target, window_seconds) do
|
||||
nil -> []
|
||||
vt -> read_chunks(list_chunk_files_hrdps(vt), bounds)
|
||||
vt -> read_bounds_hrdps(vt, bounds)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ defmodule Microwaveprop.Weather.ScalarFileTest do
|
|||
use ExUnit.Case, async: false
|
||||
|
||||
alias Microwaveprop.Weather.ScalarFile
|
||||
alias Microwaveprop.Weather.Sgrid
|
||||
|
||||
setup do
|
||||
dir =
|
||||
|
|
@ -296,6 +297,71 @@ defmodule Microwaveprop.Weather.ScalarFileTest do
|
|||
requested = ~U[2026-04-29 14:00:00Z]
|
||||
assert ScalarFile.read_bounds_hrdps_nearest(requested, nil, 6 * 3600) == []
|
||||
end
|
||||
|
||||
# The Rust pipeline writes `<iso>.hrdps.sgrid`, not the legacy chunked
|
||||
# `<iso>.hrdps/` dirs. `weather_grid_hrdps_at/2` is the only production
|
||||
# caller of this function and it backs every `/weather/cells?source=hrdps`
|
||||
# request, so a chunk-only read here means the Canadian overlay renders
|
||||
# nothing on `/weather` and `/weather-ca` is blank end to end.
|
||||
test "reads the dense .sgrid artifact at the exact time" do
|
||||
vt = ~U[2026-04-29 12:00:00Z]
|
||||
hand_write_sgrid_hrdps(vt, 53.0, -60.0, 8.0)
|
||||
|
||||
assert [%{lat: 53.0, lon: -60.0, temperature: 8.0}] =
|
||||
ScalarFile.read_bounds_hrdps_nearest(vt, nil, 6 * 3600)
|
||||
end
|
||||
|
||||
test "snaps to the closest .sgrid within the window when exact time is absent" do
|
||||
requested = ~U[2026-04-29 14:00:00Z]
|
||||
closer = ~U[2026-04-29 13:00:00Z]
|
||||
farther = ~U[2026-04-29 10:00:00Z]
|
||||
|
||||
hand_write_sgrid_hrdps(closer, 53.0, -60.0, 7.0)
|
||||
hand_write_sgrid_hrdps(farther, 53.0, -60.0, 99.0)
|
||||
|
||||
assert [%{temperature: 7.0}] =
|
||||
ScalarFile.read_bounds_hrdps_nearest(requested, nil, 6 * 3600)
|
||||
end
|
||||
|
||||
test "respects bounds when reading a .sgrid" do
|
||||
vt = ~U[2026-04-29 12:00:00Z]
|
||||
hand_write_sgrid_hrdps(vt, 53.0, -60.0, 8.0)
|
||||
|
||||
bounds = %{"south" => 60.0, "north" => 65.0, "west" => -70.0, "east" => -50.0}
|
||||
assert ScalarFile.read_bounds_hrdps_nearest(vt, bounds, 6 * 3600) == []
|
||||
end
|
||||
|
||||
# A valid_time can have both artifacts mid-migration (the chunk dir
|
||||
# hasn't drained yet). The dense file is authoritative — reading both
|
||||
# would double-paint the cell.
|
||||
test "prefers the .sgrid over a co-located legacy chunk dir" do
|
||||
vt = ~U[2026-04-29 12:00:00Z]
|
||||
hand_write_chunk(ScalarFile.dir_for_hrdps(vt), 53.0, -60.0, 99.0)
|
||||
hand_write_sgrid_hrdps(vt, 53.0, -60.0, 8.0)
|
||||
|
||||
assert [%{temperature: 8.0}] = ScalarFile.read_bounds_hrdps_nearest(vt, nil, 6 * 3600)
|
||||
end
|
||||
end
|
||||
|
||||
# Emit a single-cell `<iso>.hrdps.sgrid` byte-for-byte as
|
||||
# `prop_grid_rs::sgrid` writes it, so this test fails if either side
|
||||
# of the cross-language wire format drifts.
|
||||
defp hand_write_sgrid_hrdps(valid_time, lat, lon, temp) do
|
||||
path = Sgrid.path_for_hrdps(valid_time)
|
||||
File.mkdir_p!(Path.dirname(path))
|
||||
|
||||
fields = ["temperature", "dewpoint_depression"]
|
||||
field_table = Enum.map_join(fields, fn name -> name <> :binary.copy(<<0>>, 32 - byte_size(name)) end)
|
||||
|
||||
# flags bit0 = 1 → HRDPS. 1×1 grid anchored on the cell itself.
|
||||
header =
|
||||
<<"SGRD", 1::8, 1::8, length(fields)::little-16, DateTime.to_unix(valid_time)::little-signed-64,
|
||||
lat::little-float-64, lon::little-float-64, 0.5::little-float-64, 0.5::little-float-64, 1::little-16,
|
||||
1::little-16>>
|
||||
|
||||
body = <<temp::little-float-32, 5.0::little-float-32>>
|
||||
|
||||
File.write!(path, header <> field_table <> body)
|
||||
end
|
||||
|
||||
defp hand_write_chunk(dir, lat, lon, temp) do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue