Skip to content
jorgesandevPublic

About

Accessible pedestrian routing for Tijuana with profile-aware rerouting and live barrier reports.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

42 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Senda — accessibility-routing prototype

A hackathon project exploring a practical question: how should the same urban barrier affect routes for people with different accessibility needs? Built for the Tijuana Sin Barreras track at HackFox 2026 by team Entropyc.

Senda separates a barrier's physical type from its effect on six functional profiles, combines selected profiles using the worst case, and recalculates an active route when a relevant citizen report arrives.

Prototype, not a validated navigation product. Neither route safety nor accessibility has been independently validated. Demo coordinates, reports and geometry are synthetic; do not use them to make navigation decisions.

Senda's local synthetic route demonstration

Actual local application, with a schematic route diagram rather than a street map. No Google Maps credentials or GPS are used.

Try it locally

Requirements: Python 3.12, Node 22, Bun 1.3.14. Run from the repository root in two terminals. Installation downloads dependencies; the running synthetic workflow needs no cloud services.

# Terminal 1 — local API, volatile synthetic data
python3.12 -m venv apps/api/.venv
apps/api/.venv/bin/pip install -r apps/api/requirements.lock
cd apps/api
ROUTING_MODE=synthetic FIRESTORE_ENABLED=false SEED_FIRESTORE=false \
GOOGLE_MAPS_API_KEY= GEMINI_API_KEY= \
.venv/bin/uvicorn main:app --host 127.0.0.1 --port 8080 --no-proxy-headers
# Terminal 2 — local frontend
cd apps/web
bun install --frozen-lockfile
NEXT_PUBLIC_API_URL=http://127.0.0.1:8080 \
NEXT_PUBLIC_DEMO_MODE=1 NEXT_PUBLIC_GOOGLE_MAPS_API_KEY= \
NEXT_TELEMETRY_DISABLED=1 bun dev --hostname 127.0.0.1

Open the local demo. NEXT_PUBLIC_* values are baked into production builds: rebuild when changing modes. Explicit environment values above override any local .env credentials.

Three-minute walkthrough

  1. Focus Buscar destino, enter Zona Rio, and keep the explicit demo origin Centro. Silla de ruedas is selected. Press Buscar.
  2. Add Ceguera and choose Actualizar vista previa, then Iniciar. The diagram, distance, steps and barrier lists reflect the new route. Endpoint barriers may remain: visible warnings are intentional.
  3. Expand route details for Pasos and Barreras. Leer is optional; every instruction is also available as text.
  4. Press the red report button. Choose Usar punto sintético de la ruta, which fills a point on the active route and selects a missing-ramp report. Submit with Enviar reporte.
  5. The local API stores and broadcasts the synthetic report. The active route recalculates, announcing the result or remaining blockers. A second browser tab receives the same report through SSE.
  6. Cancelar (deshacer) removes a recent report only after the API confirms success. Terminar viaje ends the route. Restart the API to reset demo data.

Use Centro, Zona Rio, CECUT, Plaza Rio, Otay, UABC Otay, Aeropuerto, La Mesa, Hospital General, Estadio Caliente, Playas, Garita Chaparral, Parque Teniente Guerrero, Av Revolucion, or Macroplaza. Matching ignores case/accents but does not guess partial names. Both route inputs also accept latitude, longitude, e.g. 32.5331, -117.0382.

What works, and what is simulated

Area Implementation
Profile effects Six profiles; barrier worst case B > D > L > ·; amenities use strongest relevance CLAVE > UTIL > ·. Attribute rules for slope, width and crossing facilities.
Routing adapter Valhalla pedestrian requests with profile-aware exclusion candidates, explicit coordinate conversion and meter/minute units. Contract-tested with mocked responses; real tiles not exercised in this audit.
Synthetic router Deterministic sampled geometry with attempted B/D detours. No street graph, sidewalk knowledge or guarantee a detour clears every feature.
Route classification Distances to segments of the returned polyline, not just vertices or the direct origin/destination line. All known B barriers on the returned path are checked, including endpoints and large detours.
Reports and live updates Validated multipart reports, retry IDs, local SSE snapshots/events, reconnect reconciliation, duplicate suppression and stale-request protection.
Storage In-memory by default, reset on restart. Optional Firestore persistence, explicit opt-in and surfaced failures. Designed for one API process.
Map Credential-free schematic in demo mode. Google Maps JS in real mode requires a browser key and may incur charges. Text route steps and barrier lists are available without it.
Accessibility Keyboard-operable controls, visible focus, report-dialog focus containment/restoration, status announcements, text scale, contrast/reduced-motion preferences. Speech and vibration are optional. No formal WCAG or assistive-technology certification.
Seed and reports Seed is synthetic_seed; synthetic-mode reports are synthetic_report; real-mode reports are citizen_report_unverified. No automatic promotion to verified observations.
Other hackathon screens Government aggregates and transport views are demonstrations. Vision classification, moderation, community verification and transport usability scoring are not implemented.

Routing rules and their limits

  • B (blocking): eligible barriers in the origin/destination bounding box padded by 0.018° become exclusion candidates. D (difficult): candidates must also be less than 250 m from the direct corridor. L and neutral effects do not change route selection.
  • Candidates within 40 m of an endpoint are not excluded, to avoid excluding the starting/ending road. The 50-location engine limit prioritizes B before D, then corridor distance and ID. These bounds are prototype heuristics, not accessibility standards.
  • features_evitadas means selected corridor candidates more than 35 m from the returned path. It does not establish that those candidates would have been encountered on a baseline route, or prove causal avoidance. No baseline comparison is performed.
  • features_bloqueantes_en_ruta includes all known B barriers within 35 m of the returned path, even if they were outside the candidate box, capped out or near endpoints. Nearby parallel streets can produce false positives.
  • features_aprovechadas includes profile-relevant amenities within 50 m of the path; this does not prove the amenity entrance is reachable.
  • Live rerouting uses a deliberately wider 80 m trigger for new B reports. It reuses the committed origin, destination and profiles, rather than requesting GPS again. It is not continuous turn-by-turn tracking.
  • Valhalla exclusions snap points to graph edges. D is approximated as exclusion, not a graduated penalty. Native wheelchair costing takes precedence when selected; blind costing is used otherwise for BLIND. Multi-profile matrix effects still combine across every selected profile; combined wheelchair/blind requests do not receive blind-specific native narration.
  • A rejected constrained search is retried once without exclusions, clearly marked exclusions_applied=false. Remaining blockers stay visible even when route details are collapsed. A returned path is a proposal, not confirmation of suitability.

GPS denial, unknown/partial/out-of-area geocoding results, unavailable routing and empty routes produce errors. A failed reroute retains the previous path with a failure announcement. Ending a trip or issuing a newer request invalidates older responses. No hidden location or fabricated real route replaces the request.

Architecture

flowchart LR
  UI[Origin + destination + profiles] --> API[FastAPI route adapter]
  API --> Matrix[Worst-case profile effects]
  Matrix --> Engine[Synthetic router or Valhalla]
  Engine --> Audit[Returned-path barrier and amenity checks]
  Audit --> Display[Diagram/map + text + warnings]
  Report[Citizen report form] --> Validate[Validation + persistence]
  Validate --> SSE[SSE snapshot/events]
  SSE --> Decision[Active route + B effect + distance + dedupe]
  Decision --> API
Loading
  • apps/web: Next.js 15, React, TypeScript, Tailwind, Zustand; route/report UI and live-update lifecycle.
  • apps/api: FastAPI, Pydantic, geospatial utilities, matrix, synthetic router, Valhalla adapter and report store.
  • data/fixtures: shared matrix examples consumed by Python and TypeScript tests.
  • services/valhalla: local engine configuration; tiles and image are external prerequisites.
  • Architecture and contracts, product specification, audit evidence and limitations.

Real routing mode

This is a separate integration path; the synthetic demo does not validate it.

  1. Follow Valhalla setup: choose an immutable engine image, obtain an OSM extract and build compatible tiles locally. No tiles are committed. Record the image digest and extract checksum.
  2. Run the API with ROUTING_MODE=valhalla, VALHALLA_URL=http://localhost:8002, and FIRESTORE_ENABLED=false. Coordinates need no geocoder. Text inputs require GOOGLE_MAPS_API_KEY with Google Geocoding enabled; requests may be billable.
  3. Run/rebuild the web app with NEXT_PUBLIC_DEMO_MODE=0, the local API URL and, optionally, NEXT_PUBLIC_GOOGLE_MAPS_API_KEY for the street map. Without a Maps key, use textual route details.
  4. Firestore is optional: FIRESTORE_ENABLED=true, FIREBASE_PROJECT_ID and Application Default Credentials are required. Initialization/read/write failures do not silently switch to memory. FIREBASE_CREDENTIALS_JSON is a legacy unused setting; use ADC. Seed writes require separate SEED_FIRESTORE=true opt-in. Do not use that setting against existing live data.

Directions use Valhalla's supported es-ES locale directly; routing does not call an LLM. See the Valhalla API reference for native costing and exclusion semantics. Historical deployment notes are in infra/deploy.md; deployment is unnecessary for portfolio review.

Verification

# From repository root
apps/api/.venv/bin/pip install -r apps/api/requirements-dev.txt
apps/api/.venv/bin/python -m pytest -q apps/api/tests
cd apps/web
bun run typecheck
bun run lint
bun run test
bun run build

# Browser test starts its own local API and frontend; ports 8080/3000 must be free.
bunx playwright install chromium
bun run test:e2e
# Optional: refresh the README screenshot from the tested UI
CAPTURE_DEMO=1 bun run test:e2e

CI installs the frozen web lockfile and Python lock, then runs API tests, web typecheck/lint/unit tests/build and the synthetic browser walkthrough. External API responses are mocked; the browser walkthrough rejects external requests and unexpected GPS calls. Python tests forcibly disable Firestore and cloud API keys.

Public-service limitations

Report defenses include finite, regional coordinates; kind/subtype allowlists; 500-character descriptions; validated JPEG/PNG/WebP images (16 MP max); 6 MB whole-body, 5 MB upload and ~450 KB encoded-photo limits; per-peer rate limits; a 1,000-report process cap; and retry-ID conflict detection. SSE clients and queues are bounded; overflow forces a fresh snapshot. Undo persistence failures retain the report and show an error.

This remains a local portfolio prototype, not a hardened public service. Report/delete endpoints have no ownership authentication or moderation. An attacker knowing a recent report ID can delete it. Rate limits and SSE are process-local; configure trusted proxy handling explicitly and never trust arbitrary forwarded headers. Firestore writes are synchronous, cross-process broadcasts and cross-instance idempotency are not implemented, and externally edited Firestore rows are not watched. Authentication, moderation, shared rate limiting and a proper distributed event/persistence layer are prerequisites for public deployment.

My contribution

Jorge Alejandro Sandoval Romo — software development: the web experience, API integration, accessibility-profile model, routing/report workflow and deployment setup. This portfolio revision adds reproducible local execution, regression tests and explicit implementation limits. Bernardo Morales contributed co-ideation and field research. The matrix and UI have not yet been validated with disabled participants.

Next steps before use beyond a demo: participatory field validation, a documented real-tile integration run, accessibility testing with assistive technologies, and authenticated/moderated reports.

Credits

Jorge Alejandro Sandoval Romo · Bernardo Morales

MIT — LICENSE. Real routing data: © OpenStreetMap contributors, subject to ODbL attribution requirements.

About

Accessible pedestrian routing for Tijuana with profile-aware rerouting and live barrier reports.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages