* navi-offroute: Auto classify-once / route-once (kill 4-mode contest) PR 1 of the Auto rewrite. _route_auto no longer routes all four modes and keeps a min-time winner; it classifies each endpoint (category map, spatial probe only as untagged-click fallback), picks the first AUTO_MODE_PRIORITY mode in the eligible intersection, and routes ONCE. This removes the measured ~3.03s in-town 4-mode probe (single-mode probing line in journald) -- in-town Auto drops from ~6s toward ~1s. Trade-off (intentional, PR1): no routing-failure fall-through and no min-time refinement -- if the capability-picked mode cannot route, the error is returned. The hybrid path (unchanged here, still gated on the 24km MIN_HYBRID_DISTANCE_KM) recovers road->offroad plans. Semantic hybrid gate is PR 2. Scope: router.py (_route_auto contest loop only) + test_offroute.py (contest tests -> capability-pick tests + new tagged-no-spatial and untagged-spatial-once tests). _try_hybrid_auto body, the hybrid gate, AUTO_MODE_PRIORITY/MODE_PROFILES, and all other modules untouched. Full offroute suite: 84 passed. Design: recon_refactor/auto-rewrite-plan.md (artifacts dir). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Auto: foot-as-last-resort fallback when picked mode fails to route If the capability-picked mode cannot route, retry foot ONCE (foot always routes modulo bbox limits) instead of surfacing a wall to the user. On success, ship the foot route tagged with auto_fallback_from=<picked mode> for the UI; on foot failure, return the original error. No fallback when the picked mode is already foot. selected_mode_set still reflects the original capability intersection. Tests: no_fallthrough -> falls_back_to_foot_on_error; + both-fail returns error; + no-fallback-when-picked-is-foot. Full offroute suite: 86 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt <mj@k7zvx.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| config | ||
| deploy | ||
| scripts | ||
| services | ||
| shared | ||
| .gitignore | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
navi-backend
Monorepo of small, single-responsibility HTTP services extracted from the recon
codebase as part of the recon ↔ Navi decoupling project. Each service owns a
slice of the /api/* surface that navi.echo6.co depends on, runs behind the
existing Caddy/Authentik edge, and is fronted by the navi.echo6.co nginx vhost.
See HANDOFF-recon-navi-decoupling-v3.md for the full plan. This repo is
extraction #1: navi-traffic.
Layout
navi-backend/
├── shared/ # cross-service helpers, imported by every service
│ ├── auth.py # get_user_id(req), require_auth decorator (Authentik header)
│ └── admin_info.py # build_info_response(), mask_key(), time_dependency()
├── services/
│ └── navi_traffic/ # extraction #1 — TomTom traffic tile proxy (:8421)
│ ├── app.py # Flask factory (create_app) + gunicorn entry
│ ├── traffic.py # /api/traffic/flow/<z>/<x>/<y>.png (ported from recon)
│ ├── admin.py # /api/admin/navi-traffic/info (§4.5 admin convention)
│ └── tests/
└── deploy/
├── systemd/navi-traffic.service
└── nginx/navi-traffic.conf.snippet
Service directories use an underscore (navi_traffic) so they're importable
Python packages; the service name stays navi-traffic (hyphen) in systemd,
nginx, and the admin-info service field.
Setup
Single workspace, single virtualenv:
python -m venv .venv
.venv/bin/pip install -e .
Test
.venv/bin/pytest services/navi_traffic/tests/ -v
Run (local)
TOMTOM_API_KEY=... .venv/bin/gunicorn 'services.navi_traffic.app:create_app()' \
--bind 127.0.0.1:8421 --workers 2
Run (local) — navi-geo (extraction #6)
.venv/bin/pytest services/navi_geo/tests/ -v
# All paths/URLs are env-overridable (see deploy/env/navi-geo.env.example).
# No secrets — landclass is HTTP-delegated to navi-landclass (:8424).
.venv/bin/gunicorn 'services.navi_geo.app:create_app()' \
--bind 127.0.0.1:8426 --workers 2
navi-geo serves /api/geocode, /api/reverse?lat=&lon=, and the reverse
enrichment bundle /api/reverse/<lat>/<lon> (Central's 9-key contract). All
public. The reverse bundle fans out to Photon, the SpatiaLite timezone DB,
navi-landclass (HTTP), and the planet-DEM PMTiles — each degrading to null
independently, never 5xx.
Run (local) — navi-admin (extraction #7)
.venv/bin/pytest services/navi_admin/tests/ -v
# No secrets — read-only HTTP fan-out over localhost (see
# deploy/env/navi-admin.env.example). Owns no DB.
.venv/bin/gunicorn 'services.navi_admin.app:create_app()' \
--bind 127.0.0.1:8427 --workers 2
navi-admin is the fleet admin front door: /api/admin/fleet fans out to every
navi-* service's localhost /api/admin/<svc>/info + recon's /api/health
(merged, never 5xx — failures land in errors[]); /api/admin/recon/info wraps
recon's health into the uniform shape; /api/admin/navi-admin/info self-describes.
All @require_auth. The per-service admin endpoints stay localhost-only; this is
the single edge-exposed admin surface (needs a Caddy @authed_api edit — see
deploy/caddy/navi-admin.caddy.notes.md).
Run (local) — navi-offroute (extraction #8)
.venv/bin/pytest services/navi_offroute/tests/ -v
# All paths/URLs env-overridable (deploy/env/navi-offroute.env.example).
# No secrets — PADUS via libpq peer-auth (dbname=padus). DEM via shared/dem.py.
# Needs osmium-tool on the host + scikit-image/rasterio in the venv.
.venv/bin/gunicorn 'services.navi_offroute.app:create_app()' \
--bind 127.0.0.1:8428 --workers 2 --timeout 130
navi-offroute serves POST /api/offroute (off-network effort-based routing —
in-Python least-cost path over a DEM/friction/barriers/trails/MVUM cost grid,
stitched to the road network via Valhalla) and GET /api/mvum (Motor Vehicle
Use Map road/trail access lookup). Both public. The ^~ /api/offroute nginx
block needs a long proxy_read_timeout (130s); routes can take ~2 min.
The admin-info convention (§4.5)
Every service exposes GET /api/admin/<service-name>/info, gated by require_auth,
returning a uniform shape: service, version (git SHA), port, config, env
(names + masked values), dependencies (upstream health checks), filesystem,
runtime (uptime / request count / last error). No aggregator — a future admin
panel fans out to each service in parallel.