navi/backend
Matt d9fe84660d MVUM Layer 0: spatial index foundation
Additive only — no routing logic, no response-format, no Valhalla changes.

- mvum.py: add MVUMSpatialIndex. Loads mvum_roads + mvum_trails from navi.db (read-only),
  decodes the pure-WKB shape blobs with shapely, builds a shapely.strtree.STRtree, and
  keeps a parallel list of full feature records (all columns minus the blob, plus the
  parsed geometry) with a by_id lookup. Exposes query_bbox(min_lat,min_lon,max_lat,max_lon)
  and query_buffered_line(coords, tolerance_m) returning candidate records (coarse bbox +
  buffer; full parallelism filter is a TODO for PR-B). Reports road_count, trail_count,
  bbox, build_time_seconds, memory_estimate_mb (RSS delta).
- app.py: build the index once per process (singleton) at service init; stored on
  app.config[MVUM_SPATIAL_INDEX]. Failure is logged and degrades to None.
- admin.py: GET /api/admin/mvum-spatial/info (Authentik-gated, read-only) returning the
  counts/bbox/build-time/memory stats.
- tests/test_mvum_spatial.py: index loads, query_bbox returns Boise-area features,
  query_buffered_line returns a feature, admin endpoint returns counts.

Diagnostic before coding (read-only): roads_with_shape=150568/null=68,
trails_with_shape=21995/null=6746 (green), shape blobs are pure WKB MultiLineString.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:12:38 +00:00
..
config Add navi-contacts service (extraction #3) 2026-05-22 10:59:37 -06:00
deploy Add navi-offroute service (extraction #8 — final) (#10) 2026-05-22 23:30:43 -06:00
scripts decouple: add scripts/overture_import.py (relocating from recon) 2026-05-23 13:49:32 -06:00
services MVUM Layer 0: spatial index foundation 2026-05-25 22:12:38 +00:00
shared shared: promote dem.py to shared/ (prep for navi-offroute) (#9) 2026-05-22 22:42:47 -06:00
.gitignore Initial scaffold: navi-backend + navi-traffic (extraction #1) 2026-05-21 22:26:50 -06:00
LICENSE Initial scaffold: navi-backend + navi-traffic (extraction #1) 2026-05-21 22:26:50 -06:00
pyproject.toml feat(offroute): numba A* with anisotropic Tobler, exponentially-inflated cost grid (combined #17+#18) 2026-05-25 15:15:36 +00:00
README.md Add navi-offroute service (extraction #8 — final) (#10) 2026-05-22 23:30:43 -06:00

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.