mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
Auto now also considers "drive in to a trailhead, switch vehicles, continue
on foot/2w/4w" trips and picks one when it is meaningfully faster than the
single-mode winner. Implicit — no new chip; Auto just returns the fastest plan.
Backend:
- mvum_transitions.py: TrailheadIndex (STRtree over trail_entry_points), built
once per process via load_trailheads() (mirrors the MVUMSpatialIndex singleton).
query_trailheads_near_line(coords, buffer_m=2000) with a precise distance filter.
- router.py: _route_auto, after the single-mode probe and only when the winner is
ok AND total_distance_km >= MIN_HYBRID_DISTANCE_KM (8.0), tries hybrids. For each
candidate trailhead near the winning polyline (closest first, capped at 20) and
each (drive, offroad) pair in HYBRID_PAIRS, it routes both legs (annotate_mvum
off) and sums leg times with NO transition cost. A hybrid wins only if it beats
the single-mode winner by >= HYBRID_MIN_TIME_SAVINGS_MIN (15 min); trivial
offroad detours (< HYBRID_MIN_OFFROAD_KM = 0.8 km) are skipped. The winner is
combined into a new "multi" scenario: leg1 features + a kind=transition marker
+ leg2 features; summary carries total_*, per-leg legs[], summed MVUM counts;
selected_mode="hybrid". Each leg is annotated separately.
- app.py / offroute_route.py: load + inject the trailhead index singleton.
Frontend (additive — no api.js signature change):
- DirectionsPanel: per-leg breakdown row for hybrid/multi ("Drive X mi (Ymin)
-> 4W X mi (Zmin) - total Wmin", lucide Repeat between legs); existing Auto
badge still shows.
- MapView: network polylines colored by network_mode (vehicle/auto blue, 4w
orange, 2w green, foot red); transition points rendered as a circle marker with
the lucide Repeat icon + "Switch to <mode>" tooltip; bounds fit skips Points.
Tests: test_mvum_transitions.py — index load, near-line close-only query, short
trip stays single-mode, big-savings hybrid wins, trivial-detour + below-threshold
+ no-trailheads all fall back. 7 new tests; full offroute suite 71 passed.
Note: the DB column is trail_entry_points.highway_class; surfaced as record
"road_class" per the spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
1.9 KiB
Python
67 lines
1.9 KiB
Python
"""navi-offroute Flask application factory + gunicorn entry.
|
|
|
|
Gunicorn entry:
|
|
gunicorn 'services.navi_offroute.app:create_app()' --bind 127.0.0.1:8428 --workers 2
|
|
"""
|
|
import time
|
|
|
|
from flask import Flask
|
|
|
|
from shared.git_sha import git_short_sha
|
|
|
|
from . import offroute_route, admin
|
|
from .mvum import MVUMSpatialIndex
|
|
from .mvum_transitions import load_trailheads
|
|
|
|
# Process-wide singleton: build the MVUM spatial index once per process (per gunicorn
|
|
# worker in prod; once across create_app() calls in tests), not once per app instance.
|
|
_MVUM_INDEX = None
|
|
|
|
|
|
def _get_mvum_index():
|
|
global _MVUM_INDEX
|
|
if _MVUM_INDEX is None:
|
|
_MVUM_INDEX = MVUMSpatialIndex()
|
|
return _MVUM_INDEX
|
|
|
|
|
|
|
|
def create_app():
|
|
app = Flask(__name__)
|
|
|
|
app.config['VERSION'] = git_short_sha()
|
|
app.config['METRICS'] = {
|
|
'start_time': time.time(),
|
|
'request_count': 0,
|
|
'last_error_at': None,
|
|
}
|
|
|
|
@app.before_request
|
|
def _count_request():
|
|
app.config['METRICS']['request_count'] += 1
|
|
|
|
@app.after_request
|
|
def _track_errors(response):
|
|
if response.status_code >= 500:
|
|
app.config['METRICS']['last_error_at'] = time.strftime(
|
|
'%Y-%m-%dT%H:%M:%SZ', time.gmtime()
|
|
)
|
|
return response
|
|
|
|
# Load the MVUM spatial index once at service init (logs its own load line).
|
|
try:
|
|
app.config['MVUM_SPATIAL_INDEX'] = _get_mvum_index()
|
|
except Exception as e:
|
|
app.logger.warning("MVUM spatial index failed to load: %s", e)
|
|
app.config['MVUM_SPATIAL_INDEX'] = None
|
|
|
|
# Layer 3a: trailhead transition index (process-wide singleton, logs its own line).
|
|
try:
|
|
app.config['MVUM_TRAILHEAD_INDEX'] = load_trailheads()
|
|
except Exception as e:
|
|
app.logger.warning("MVUM trailhead index failed to load: %s", e)
|
|
app.config['MVUM_TRAILHEAD_INDEX'] = None
|
|
|
|
app.register_blueprint(offroute_route.bp)
|
|
app.register_blueprint(admin.bp)
|
|
return app
|