fix(rust): snap HRRR point lookups to 0.125° grid
hrrr-point-rs was marking fetch tasks 'done' with 0 profiles inserted for ~13,900 historical contacts. The wgrib2 subprocess resamples HRRR to the 0.125° CONUS prop grid, but the lookup keyed on the requested point (rounded to 0.01° by Weather.round_to_hrrr_grid/2) — those keys never match a 0.125° cell exactly, so every point fell through the `cell.is_empty()` skip. Snap to the nearest 0.125° cell for lookup, store at the original (lat, lon) so Weather.has_hrrr_profile?/3's 0.07° search window finds it on the next backfill tick. TDD: added snap_key unit tests.
This commit is contained in:
parent
d6d50f7729
commit
7062344434
1 changed files with 48 additions and 7 deletions
|
|
@ -18,7 +18,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::decoder::{self, CellValues};
|
||||
use crate::fetcher::{self, HrrrClient, Product};
|
||||
use crate::grid::wgrib2_grid_spec;
|
||||
use crate::grid::{wgrib2_grid_spec, STEP};
|
||||
|
||||
// wgrib2 filter patterns are derived from static SURFACE_MESSAGES /
|
||||
// GRID_PRESSURE_LEVELS tables and never change at runtime. Building
|
||||
|
|
@ -104,11 +104,16 @@ pub async fn process_batch(
|
|||
}
|
||||
|
||||
// For each requested point, pull the merged cell and build an
|
||||
// hrrr_profiles row. Skip the point silently if extract_grid
|
||||
// produced nothing for its snapped key (e.g. off-CONUS).
|
||||
// hrrr_profiles row. Elixir snaps points to 0.01° (see
|
||||
// `Weather.round_to_hrrr_grid/2`), but wgrib2 resamples to the 0.125°
|
||||
// CONUS prop grid — so we must re-snap before keying into `sfc_grid`
|
||||
// or the lookup misses and the task completes with zero inserts.
|
||||
// Store the profile at the original requested (lat, lon) so
|
||||
// `Weather.has_hrrr_profile?/3` (0.07° window) finds it on the
|
||||
// next backfill tick and flips the contact to `:complete`.
|
||||
let mut inserted = 0u32;
|
||||
for &(lat, lon) in points {
|
||||
let key = ((lat * 1000.0).round() as i32, (lon * 1000.0).round() as i32);
|
||||
let key = snap_key(lat, lon);
|
||||
let mut cell: CellValues = sfc_grid.get(&key).cloned().unwrap_or_default();
|
||||
if let Some(prs) = prs_grid.get(&key) {
|
||||
cell.extend(prs.iter().map(|(k, v)| (k.clone(), *v)));
|
||||
|
|
@ -210,7 +215,43 @@ async fn upsert_profile(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Snap a requested (lat, lon) to the nearest 0.125° grid cell and
|
||||
/// return its integer-millidegree key. The wgrib2 subprocess resamples
|
||||
/// HRRR to that grid (see `wgrib2_grid_spec()`), so a request on the
|
||||
/// HRRR native ~3 km grid (e.g. 44.94, -91.63 after
|
||||
/// `Weather.round_to_hrrr_grid/2`, which rounds to 0.01°) never matches
|
||||
/// a 0.125° cell key exactly and the point is silently dropped —
|
||||
/// producing the "task done with 0 profiles inserted" pattern that
|
||||
/// wedged 13k+ contacts at `hrrr_status = :queued`.
|
||||
fn snap_key(lat: f64, lon: f64) -> (i32, i32) {
|
||||
let snap_lat = (lat / STEP).round() * STEP;
|
||||
let snap_lon = (lon / STEP).round() * STEP;
|
||||
(
|
||||
(snap_lat * 1000.0).round() as i32,
|
||||
(snap_lon * 1000.0).round() as i32,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn snap_key_on_grid_is_identity() {
|
||||
assert_eq!(snap_key(45.0, -91.625), (45_000, -91_625));
|
||||
assert_eq!(snap_key(25.0, -125.0), (25_000, -125_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snap_key_rounds_to_nearest_0_125_cell() {
|
||||
// 44.94 → 45.0 (0.06 away vs 44.875 which is 0.065 away)
|
||||
assert_eq!(snap_key(44.94, -91.63), (45_000, -91_625));
|
||||
// 44.83 → 44.875 (0.045 vs 44.75 which is 0.08)
|
||||
assert_eq!(snap_key(44.83, -91.5), (44_875, -91_500));
|
||||
// 44.73 → 44.75 (0.02 vs 44.625 which is 0.105)
|
||||
assert_eq!(snap_key(44.73, -91.38), (44_750, -91_375));
|
||||
}
|
||||
}
|
||||
|
||||
// Integration tests for process_batch + upsert_profile live in
|
||||
// tests/hrrr_points_live.rs (network + DB gated). No unit tests here
|
||||
// — the two public functions are thin glue over fetcher/decoder which
|
||||
// carry their own test coverage.
|
||||
// tests/hrrr_points_live.rs (network + DB gated).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue