Honest pins and paths: geocode precision, type-gated paths, impossible-speed stops (#236)

Backend: _geocode_location now returns {lat, lng, location_type}, so the
precision rides onto the call doc and, unchanged via _resolve_location_pair,
onto the incident pin. Display-only: every correlator read indexes lat/lng.

Frontend: pins styled by precision (rooftop / interpolated ring /
GEOMETRIC_CENTER dashed uncertainty circle labelled with the spoken place /
unverified pre-#23 pins ghosted). liveWall.pathStops() is shared by the map
and the call spine: only police/accident incidents with a pursuit tag draw a
numbered path; others get unnumbered call dots. Segments over >10 min gaps or
talkgroup changes are dashed, stops implying >8 km/min are ghosted off the
path, paths >20 km get a "may be merged" badge. New optional MapView prop
pulseIncidentIds (wired by #238).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-05 03:28:00 -04:00
co-authored by Claude Opus 5.5
parent 6e462720a1
commit 6bf072973c
7 changed files with 499 additions and 74 deletions
@@ -0,0 +1,56 @@
"""
server-26#236: geocode precision must travel with the coordinates.
`_geocode_location` already judged a result by Google's `location_type` but
returned only {lat, lng}, so a GEOMETRIC_CENTER route centroid and a ROOFTOP
address drew the same pin. The type is now part of the returned dict, which
is stored as-is on the call doc and carried unchanged onto the incident pin by
`incident_correlator._resolve_location_pair`.
Display-only: every correlator read of location_coords indexes ["lat"] and
["lng"] directly, so the extra key cannot change a correlation decision.
"""
from unittest.mock import patch
import pytest
import app.internal.intelligence as intel
from app.config import settings
from tests.test_geocode_named_region import _maps_result
@pytest.fixture(autouse=True)
def _api_key():
with patch.object(settings, "google_maps_api_key", "test-key"):
yield
ANCHOR = {"lat": 41.16, "lng": -73.86, "radius_km": 10.0}
@pytest.mark.asyncio
@pytest.mark.parametrize("location_type", ["ROOFTOP", "RANGE_INTERPOLATED", "GEOMETRIC_CENTER"])
async def test_accepted_location_type_is_returned_with_the_coords(location_type):
with _maps_result(41.161, -73.861, location_type=location_type):
coords = await intel._geocode_location("1 Main Street, Ossining", anchor=ANCHOR)
assert coords == {"lat": 41.161, "lng": -73.861, "location_type": location_type}
@pytest.mark.asyncio
@pytest.mark.parametrize("location_type", ["APPROXIMATE", "", "SOMETHING_NEW"])
async def test_rejected_location_type_still_returns_none(location_type):
with _maps_result(41.161, -73.861, location_type=location_type):
coords = await intel._geocode_location("Ossining", anchor=ANCHOR)
assert coords is None
def test_location_type_survives_onto_the_incident_pin():
"""`_resolve_location_pair` stores the call's coords dict as-is, so the
precision reaches the incident without any correlator change."""
from app.internal.incident_correlator import _resolve_location_pair
pin = {"lat": 41.161, "lng": -73.861, "location_type": "GEOMETRIC_CENTER"}
fields = _resolve_location_pair({}, "Route 9 and Main Street", pin)
assert fields["location_coords"] == pin
assert fields["location_coords_source"] == "Route 9 and Main Street"
@@ -69,7 +69,7 @@ async def test_named_region_geocode_accepted_beyond_node_distance():
node_lat=NODE_LAT, node_lon=NODE_LON,
trust_named_region=True,
)
assert coords == {"lat": FAR_LAT, "lng": FAR_LNG}
assert coords == {"lat": FAR_LAT, "lng": FAR_LNG, "location_type": "ROOFTOP"}
@pytest.mark.asyncio
@@ -110,7 +110,7 @@ async def test_named_region_geocode_within_node_distance_is_unaffected():
node_lat=NODE_LAT, node_lon=NODE_LON,
trust_named_region=True,
)
assert coords == {"lat": near_lat, "lng": near_lng}
assert coords == {"lat": near_lat, "lng": near_lng, "location_type": "ROOFTOP"}
@pytest.mark.asyncio