navi/backend/services/navi_offroute/tests/test_mvum_annotate.py
Matt 53ffba5795 MVUM Layer 1: per-edge annotation + accurate closure counts
Annotate each network-leg segment with its MVUM access status for the selected mode,
using the Layer-0 MVUMSpatialIndex. No routing-decision or Valhalla changes.

- mvum_annotate.py (new): annotate_network_edges(coords,(lat,lon)), mode, spatial_index,
  on_date) -> [EdgeAnnotation{coord_pair_index, matched_features, mvum_status}]. Walks
  consecutive pairs, queries the index (10m buffer), applies a parallelism filter (acute
  angle to the edge <=45deg, both directions), resolves per-mode access via the existing
  check_access/get_mode_field/symbol_to_access (worst/most-restrictive across matches).
  Mode map: foot->open (skip), 2w->e_bike_class1, 4w->atv, vehicle->highclearancevehicle;
  auto is already resolved to a concrete candidate upstream.
- router.py: _route_D_network_only and _build_response annotate the network leg using the
  injected self.spatial_index (getattr-guarded; skipped + debug-logged if None), attach
  edge_mvum to the network feature, and add summary.mvum_closed_crossings +
  summary.mvum_segments_annotated.
- offroute_route.py: inject app.config[MVUM_SPATIAL_INDEX] onto the router per request.
- DirectionsPanel.jsx: warning row when mvum_closed_crossings>0.
- tests/test_mvum_annotate.py: parallel match, perpendicular reject, seasonal closure,
  symbol fallback, summary count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 01:18:03 +00:00

75 lines
2.9 KiB
Python

"""MVUM Layer 1 per-edge annotation tests (isolated; fake spatial index, no DB)."""
from datetime import datetime
from shapely.geometry import LineString
from services.navi_offroute.mvum_annotate import annotate_network_edges
def _feat(geom, fid="trail:1", **cols):
rec = {"geometry": geom, "feature_id": fid}
rec.update(cols)
return rec
class _FakeIndex:
"""Returns a preset list of candidate records per query_buffered_line call (in order)."""
def __init__(self, per_call):
self.per_call = per_call
self.i = 0
def query_buffered_line(self, coords, tolerance_m):
r = self.per_call[self.i] if self.i < len(self.per_call) else []
self.i += 1
return r
# A NE-running edge and a parallel / perpendicular NFS-like feature near it.
EDGE = [(44.000, -114.000), (44.010, -113.990)] # (lat, lon), heading NE
PARALLEL = LineString([(-114.002, 43.998), (-113.988, 44.012)]) # NE (lon, lat)
PERPENDICULAR = LineString([(-113.988, 43.998), (-114.002, 44.012)]) # NW
def test_parallel_match():
idx = _FakeIndex([[_feat(PARALLEL, atv="open", atv_datesopen=None,
seasonal="yearlong", symbol="1")]])
out = annotate_network_edges(EDGE, "4w", idx)
assert len(out) == 1
assert out[0].mvum_status == "open"
assert out[0].matched_features == ["trail:1"]
def test_perpendicular_rejected():
idx = _FakeIndex([[_feat(PERPENDICULAR, atv="open", seasonal="yearlong", symbol="1")]])
out = annotate_network_edges(EDGE, "4w", idx)
assert len(out) == 1
assert out[0].mvum_status == "unknown" # parallelism filter rejected it
assert out[0].matched_features == []
def test_seasonal_closure():
# ATV open only May-Sep; querying mid-January -> closed.
idx = _FakeIndex([[_feat(PARALLEL, atv="open", atv_datesopen="05/01-09/30",
seasonal="seasonal", symbol="4")]])
out = annotate_network_edges(EDGE, "4w", idx, on_date=datetime(2026, 1, 15))
assert out[0].mvum_status == "closed"
def test_symbol_fallback():
# Per-class field NULL, SYMBOL=3 (closed to motorized) -> closed.
idx = _FakeIndex([[_feat(PARALLEL, atv=None, atv_datesopen=None,
seasonal=None, symbol="3", operationalmaintlevel=None)]])
out = annotate_network_edges(EDGE, "4w", idx)
assert out[0].mvum_status == "closed"
def test_summary_count():
# 4 coords -> 3 edges; middle edge closed, others open -> 1 closed.
coords = [(44.000, -114.000), (44.010, -113.990),
(44.020, -113.980), (44.030, -113.970)]
open_f = _feat(PARALLEL, atv="open", seasonal="yearlong", symbol="1")
closed_f = _feat(PARALLEL, atv=None, symbol="3")
idx = _FakeIndex([[open_f], [closed_f], [open_f]])
out = annotate_network_edges(coords, "4w", idx)
assert len(out) == 3
assert sum(1 for e in out if e.mvum_status == "closed") == 1