fix(pskr): drop spots with <6-char grids on either end

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.
This commit is contained in:
Graham McIntire 2026-05-05 09:29:25 -05:00
parent b08fc5e449
commit 1a3dc4fa0a
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 25 additions and 11 deletions

View file

@ -152,16 +152,26 @@ defmodule Microwaveprop.Pskr do
} }
end end
# Keep locators at the precision the spotter reported. Maidenhead # Require ≥ 6-char locators on both ends. 4-char grids cover
# validity already enforces minimum 4 chars + even length + the # ~70 km × 100 km, so the midpoint and path distance error
# right alphabet at each position, so anything that passes is a # dwarfs any HRRR cell match (3 km native) and the resulting
# legal 4/6/8/10-char locator we can store as-is. Uppercase for # calibration sample geometry is meaningless. Drop the spot
# consistent indexing — the unique key is case-sensitive. # 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 defp fetch_grid(json, key) do
case Map.get(json, key) do case Map.get(json, key) do
grid when is_binary(grid) -> grid when is_binary(grid) ->
upper = String.upcase(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 -> other ->
{:error, {:missing_grid, key, other}} {:error, {:missing_grid, key, other}}

View file

@ -36,12 +36,16 @@ defmodule Microwaveprop.PskrTest do
assert DateTime.to_unix(spot.transmit_time) == 1_662_407_697 assert DateTime.to_unix(spot.transmit_time) == 1_662_407_697
end end
test "accepts 4-char grids verbatim" do test "rejects 4-char grids on either end (insufficient precision)" do
json = @sample |> Map.merge(%{"sl" => "EM12", "rl" => "DM43"}) |> Jason.encode!() # 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 {:error, _} = Pskr.parse_spot(sender_short)
assert spot.sender_grid == "EM12" assert {:error, _} = Pskr.parse_spot(receiver_short)
assert spot.receiver_grid == "DM43"
end end
test "preserves extended-square (8-char) precision" do test "preserves extended-square (8-char) precision" do