From 1a3dc4fa0a3edc257cab061961bd497a400f9787 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 5 May 2026 09:29:25 -0500 Subject: [PATCH] fix(pskr): drop spots with <6-char grids on either end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 4-char locator covers ~70 km × 100 km, which dwarfs HRRR's 3 km native cell and any midpoint we'd derive for the calibration corpus. Storing those samples gives the recalibrator geometry that's indistinguishable from noise. fetch_grid/2 now returns {:error, {:short_grid, key, grid}} for any locator under 6 chars after Maidenhead validation. The spot is dropped before it reaches the aggregator, so neither pskr_spots_hourly nor pskr_calibration_samples ever see it. --- lib/microwaveprop/pskr.ex | 22 ++++++++++++++++------ test/microwaveprop/pskr_test.exs | 14 +++++++++----- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/lib/microwaveprop/pskr.ex b/lib/microwaveprop/pskr.ex index e2238875..7980ff55 100644 --- a/lib/microwaveprop/pskr.ex +++ b/lib/microwaveprop/pskr.ex @@ -152,16 +152,26 @@ defmodule Microwaveprop.Pskr do } end - # Keep locators at the precision the spotter reported. Maidenhead - # validity already enforces minimum 4 chars + even length + the - # right alphabet at each position, so anything that passes is a - # legal 4/6/8/10-char locator we can store as-is. Uppercase for - # consistent indexing — the unique key is case-sensitive. + # Require ≥ 6-char locators on both ends. 4-char grids cover + # ~70 km × 100 km, so the midpoint and path distance error + # dwarfs any HRRR cell match (3 km native) and the resulting + # calibration sample geometry is meaningless. Drop the spot + # entirely rather than store a low-precision row. + # + # Maidenhead validity enforces even length + correct alphabet + # per position, so a passing string is a legal 6/8/10-char + # locator we can store as-is. Uppercase for consistent indexing + # — the unique key is case-sensitive. defp fetch_grid(json, key) do case Map.get(json, key) do grid when is_binary(grid) -> upper = String.upcase(grid) - if Maidenhead.valid?(upper), do: {:ok, upper}, else: {:error, {:bad_grid, key, grid}} + + cond do + not Maidenhead.valid?(upper) -> {:error, {:bad_grid, key, grid}} + String.length(upper) < 6 -> {:error, {:short_grid, key, grid}} + true -> {:ok, upper} + end other -> {:error, {:missing_grid, key, other}} diff --git a/test/microwaveprop/pskr_test.exs b/test/microwaveprop/pskr_test.exs index 1a04a880..4ba9d718 100644 --- a/test/microwaveprop/pskr_test.exs +++ b/test/microwaveprop/pskr_test.exs @@ -36,12 +36,16 @@ defmodule Microwaveprop.PskrTest do assert DateTime.to_unix(spot.transmit_time) == 1_662_407_697 end - test "accepts 4-char grids verbatim" do - json = @sample |> Map.merge(%{"sl" => "EM12", "rl" => "DM43"}) |> Jason.encode!() + test "rejects 4-char grids on either end (insufficient precision)" do + # A 4-char locator is ~70 km × 100 km — the midpoint and + # distance error swamps any HRRR cell match (3 km native). + # Drop the spot rather than store a calibration sample whose + # geometry is meaningless. + sender_short = @sample |> Map.put("sl", "EM12") |> Jason.encode!() + receiver_short = @sample |> Map.put("rl", "DM43") |> Jason.encode!() - assert {:ok, spot} = Pskr.parse_spot(json) - assert spot.sender_grid == "EM12" - assert spot.receiver_grid == "DM43" + assert {:error, _} = Pskr.parse_spot(sender_short) + assert {:error, _} = Pskr.parse_spot(receiver_short) end test "preserves extended-square (8-char) precision" do