mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
navi-offroute: Auto bypass — trust _auto_eligible_modes vehicle judgment (PR 48)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e36cc204c5
commit
b4f2d46cd6
2 changed files with 28 additions and 31 deletions
|
|
@ -754,26 +754,6 @@ class OffrouteRouter:
|
|||
return CATEGORY_ELIGIBLE_MODES.get(f"{key}:*")
|
||||
return None
|
||||
|
||||
def _bypass_eligible(self, lat: float, lon: float,
|
||||
category: Optional[str], snap_cache: dict) -> bool:
|
||||
"""True when this endpoint is clearly ON a paved road. Gates the Valhalla bypass
|
||||
in _route_auto. Two safe paths:
|
||||
1. positive OSM category hint (CATEGORY_ELIGIBLE_MODES grants "vehicle"), OR
|
||||
2. cached auto-snap is tight (<= AUTO_SNAP_TIGHT_M) AND the snapped road is a
|
||||
paved highway class.
|
||||
Deliberately EXCLUDES the relaxed-snap-with-flat case (d_auto <= 100m + flat
|
||||
terrain), which can grant 'vehicle' eligibility up to 100m off a road — bypassing
|
||||
there would silently re-snap a wilderness/park-lot click to the road and lose any
|
||||
walk-then-drive leg the unified flow would have produced."""
|
||||
typed = self._eligible_modes_from_category(category)
|
||||
if typed is not None:
|
||||
return "vehicle" in typed
|
||||
snap = snap_cache.get((lat, lon, "auto"))
|
||||
if snap is None:
|
||||
return False # tagged-endpoint short-circuit didn't pre-populate; safe default
|
||||
return (snap["snap_distance_m"] <= AUTO_SNAP_TIGHT_M
|
||||
and snap.get("road_class") in PAVED_HIGHWAY_CLASSES)
|
||||
|
||||
def _is_terrain_flat(self, lat: float, lon: float) -> bool:
|
||||
"""True if the DEM is flat (max-min < FLAT_TERRAIN_DELTA_M) across the center
|
||||
and four cardinal points FLAT_SAMPLE_RADIUS_M away. Conservative: any DEM read
|
||||
|
|
@ -859,15 +839,15 @@ class OffrouteRouter:
|
|||
seed_set = sorted(start_eligible | end_eligible)
|
||||
|
||||
# === Valhalla bypass: pure road↔road skips the raster pipeline entirely ===
|
||||
# When BOTH endpoints are clearly ON a paved road -- by vehicle-eligible category hint
|
||||
# OR a tight (<=5m) auto-snap to a paved highway (_bypass_eligible) -- the trip is
|
||||
# on-road end-to-end, so hand it to the same inline-Valhalla path explicit vehicle
|
||||
# requests use, saving the ~7.5s raster build + unified A*. Untagged raw-coordinate
|
||||
# clicks now qualify via the tight-snap path; the relaxed (<=100m) snap is excluded so
|
||||
# an off-road click isn't silently re-snapped to the road. Boundary-mode MVUM
|
||||
# exclusions aren't applied here (urban roads aren't MVUM-gated).
|
||||
if (self._bypass_eligible(start_lat, start_lon, start_category, snap_cache)
|
||||
and self._bypass_eligible(end_lat, end_lon, end_category, snap_cache)):
|
||||
# Fires when both endpoints have "vehicle" in _auto_eligible_modes's judgment (computed
|
||||
# above) -- covers tagged road categories AND raw map clicks that land tight OR
|
||||
# relaxed+flat+paved near a road. The _is_terrain_flat check inside
|
||||
# _spatial_eligible_modes gates out wilderness/mountain false positives, so a relaxed
|
||||
# (<=100m) snap only grants vehicle on benign flat ground (lot/shoulder/field) where a
|
||||
# pure road route is the right answer. Hands off to the same inline-Valhalla path
|
||||
# explicit vehicle requests use, saving the ~7.5s raster build + unified A*. Boundary-mode
|
||||
# MVUM exclusions aren't applied here (urban roads aren't MVUM-gated).
|
||||
if "vehicle" in start_eligible and "vehicle" in end_eligible:
|
||||
_bt0 = time.perf_counter()
|
||||
bypass = self._route_D_network_only(start_lat, start_lon, end_lat, end_lon, "vehicle")
|
||||
if bypass.get("status") == "ok":
|
||||
|
|
|
|||
|
|
@ -1405,15 +1405,32 @@ def test_route_auto_untagged_tight_snap_fires_bypass(monkeypatch):
|
|||
assert out["summary"]["auto_bypass"] is True
|
||||
|
||||
|
||||
def test_route_auto_untagged_relaxed_snap_skips_bypass(monkeypatch):
|
||||
def test_route_auto_untagged_relaxed_snap_fires_bypass(monkeypatch):
|
||||
# PR #48: a relaxed (50m) snap that still grants "vehicle" (paved + flat-terrain grace in
|
||||
# _spatial_eligible_modes) now fires the bypass -- we trust _auto_eligible_modes's judgment.
|
||||
r = object.__new__(OffrouteRouter)
|
||||
monkeypatch.setattr(OffrouteRouter, "_spatial_eligible_modes", _spatial_relaxed_paved)
|
||||
monkeypatch.setattr(OffrouteRouter, "_fetch_auto_rasters", _bp_fetch_should_not_run)
|
||||
monkeypatch.setattr(_p4router.requests, "post", lambda *a, **k: _BypassOKResp())
|
||||
out = r._route_auto(42.5558, -114.4701, 42.5644, -114.4631, "pragmatic") # no categories
|
||||
assert out["status"] == "ok"
|
||||
assert out["selected_mode"] == "vehicle"
|
||||
assert out["summary"]["auto_bypass"] is True
|
||||
|
||||
|
||||
def test_route_auto_untagged_no_vehicle_in_eligibility_skips_bypass(monkeypatch):
|
||||
# Safety gate (preserved from PR #47 test 6): when _spatial_eligible_modes withholds
|
||||
# "vehicle" -- e.g. _is_terrain_flat rejected a non-flat wilderness click near a road --
|
||||
# the bypass declines and the unified flow runs (preserving any walk-then-drive plan).
|
||||
r = object.__new__(OffrouteRouter)
|
||||
monkeypatch.setattr(OffrouteRouter, "_spatial_eligible_modes",
|
||||
lambda self, lat, lon, cache: frozenset({"foot"}))
|
||||
monkeypatch.setattr(OffrouteRouter, "_route_D_network_only",
|
||||
lambda *a, **k: (_ for _ in ()).throw(AssertionError("D must not run")))
|
||||
monkeypatch.setattr(OffrouteRouter, "_fetch_auto_rasters", _bp_fetch_sentinel)
|
||||
out = r._route_auto(42.5558, -114.4701, 42.5644, -114.4631, "pragmatic") # no categories
|
||||
assert out["status"] == "error"
|
||||
assert "Failed to load terrain" in out["message"] # relaxed snap rejected -> unified flow
|
||||
assert "Failed to load terrain" in out["message"] # no vehicle -> unified flow
|
||||
|
||||
|
||||
def test_route_auto_e2e_http_in_town_fires_bypass(client, monkeypatch):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue