AccessCity is an accessibility-focused urban routing system. It combines a .NET 9 geospatial API, PostGIS-backed OSM ingestion, Redis/Kafka worker paths, route graph preprocessing, hazard/risk scoring, vector tile support, and an Expo React Native client.
The project is built for safe, reproducible route decisions: AI assistance is limited to text normalization, report summarization, and explanations. It does not generate routes or dynamically change routing edge costs.
AccessCity is an active pre-production system. The local and Kubernetes deployment paths are wired for API replicas, background workers, Kafka, Redis, PostGIS/PgBouncer, observability, SLO alerts, and route graph artifact profiling, but it is not an emergency service and has not yet been proven at global production scale.
Current verified baseline:
- Distributed k6 run: 6 API pods, 2 worker pods, 3 Postgres instances, 3 PgBouncer pods, 3 Kafka brokers, Redis; 145,334 requests, 440 req/s, 0 HTTP failures, safe-path p95 167.56 ms in the checked-in scenario.
- City graph profile: Birmingham OSM extract, 661,852 nodes, 1,428,512 directed edges, 1,419 shards, 225.7 MB packed source shard artifacts.
- Full backend test suite: 264 xUnit tests passing in Release mode on the current branch.
See K6 load results, route graph preprocessing, and distributed load testing for methodology and limits.
- Accessibility-aware routing for standard, manual wheelchair, power wheelchair, and stroller profiles.
- OSM import into a routable pedestrian graph with accessibility tags such as surface, width, incline, kerb, and crossing metadata where available.
- Safe-path scoring using deterministic route costs, nearby hazards, infrastructure signals, lighting/environmental inputs, and profile-specific penalties.
- Hazard reporting, moderation-oriented admin workflows, and live hazard alert broadcasting.
- Map overlays, POI queries, vector tile endpoints, and offline map bundle support.
- Route graph preprocessing with packed artifacts, source shard manifests, ALT landmarks, optional contraction hierarchy support, distributed cache coalescing, and profile quality gates.
AccessCity is a modular monolith with independently scalable deployment roles:
- API: authentication, HTTP endpoints, WebSocket hazard alerts, cache lookups, job submission, and fast read paths.
- Workers: route jobs, OSM import jobs, tile warming, graph artifact warmup, and CPU-heavy background work.
- PostGIS: source of truth for users, hazards, OSM graph data, imports, and spatial read models.
- Redis: distributed cache for route results, risk summaries, tile data, job status, and packed graph artifacts.
- Kafka: route/import job handoff and multi-replica worker coordination.
- Kubernetes: KEDA-driven API/worker scaling, pod disruption budgets, topology spread, probes, and separate migration jobs.
Module boundaries are documented in Modular Architecture. Scaling guardrails are documented in SLO and Scaling.
- .NET 9 SDK
- Node.js 20 LTS
- Docker Desktop or Docker Engine with Compose
- Optional:
kubectl,kustomize, and a Kubernetes cluster for distributed validation
Run the API, PostGIS, Redis, Kafka, and the routing/import worker:
docker compose --profile worker up -d --buildThe API is exposed through the local gateway at:
http://localhost:8080
Useful local endpoints:
GET /healthGET /health/ready- Development OpenAPI:
http://localhost:8080/openapi/v1.json - Development Scalar UI:
http://localhost:8080/scalar/v1
Stop the stack:
docker compose --profile worker downKeep only the backing services in Docker, then run the API locally:
docker compose up -d db redis kafka
cd AccessCity.API
export ASPNETCORE_ENVIRONMENT=Development
export ConnectionStrings__DefaultConnection="Host=localhost;Port=5432;Database=accesscitydb;Username=accesscity;Password=accesscity123"
export ConnectionStrings__Redis="localhost:6379"
export Messaging__UseKafka=false
export Routing__DispatchJobsToWorker=false
dotnet runThe default development launch settings expose HTTP on port 5005 unless overridden.
cd AccessCity.App
npm ci
npm run webFor simulator, device, and QR workflows:
npx expo startThe client API base URL is resolved in AccessCity.App/services/apiConfig.ts. Override it with EXPO_PUBLIC_API_URL, EXPO_PUBLIC_API_HOST, or EXPO_PUBLIC_API_PORT.
Most runtime settings use ASP.NET configuration keys and can be supplied through environment variables with __ separators.
High-value settings:
| Setting | Purpose |
|---|---|
ConnectionStrings__DefaultConnection |
PostGIS primary connection string. |
READONLY_DATABASE_URL |
Optional read-only Postgres/PgBouncer path for hot geospatial reads. |
ConnectionStrings__Redis |
Redis L2 cache and distributed job status path. |
Messaging__UseKafka |
Enables Kafka-backed job publishing and worker consumption. |
Kafka__BootstrapServers |
Kafka broker list. |
Jwt__Key / Jwt__PreviousKeys |
Current and previous JWT signing keys for rotation. |
Routing__AsyncFirstForCacheMiss |
Returns 202 Accepted for cold route misses instead of blocking API pods. |
Routing__DispatchJobsToWorker |
Sends route jobs to workers rather than computing in the API process. |
Routing__RouteGraphPackedArtifactsEnabled |
Uses compact binary graph artifacts for cache/storage. |
Routing__RouteGraphMaxDistributedSnapshotBytes |
Prevents oversized merged route graph bundles from being written to Redis/L2. |
Routing__RouteGraphCorridorSlicingEnabled / Routing__RouteGraphCorridorPaddingMetres |
Loads a route corridor of reusable graph cells instead of the whole padded rectangle for city-scale routes. |
Routing__RouteGraphAdaptiveCorridorWideningEnabled |
Retries a wider route graph corridor when the first slice lacks endpoint coverage or a connected accessible path. |
Routing__RouteGraphProfileFailOnQualityGate |
Fails graph profiling when configured artifact/latency budgets are exceeded. |
Secret rotation is documented in Secret Rotation. Do not use the checked-in development JWT placeholder in production.
Backend format, build, and tests:
dotnet format CodeConquerors.sln --verify-no-changes --verbosity minimal
dotnet build CodeConquerors.sln --configuration Release
dotnet test AccessCity.Tests/AccessCity.Tests.csproj --configuration Release --no-build --verbosity normalFrontend lint and tests:
cd AccessCity.App
npm ci
npm run lint
npm run test:ciDependency vulnerability checks:
dotnet list CodeConquerors.sln package --vulnerable --include-transitive
cd AccessCity.App
npm audit --audit-level=high --registry=https://registry.npmjs.orgContainer and Kubernetes config checks:
docker compose config --quiet
docker compose --profile worker config --quiet
kubectl kustomize deploy/kubernetes >/tmp/accesscity-kustomize.yamlCI gates are documented in CI/CD. Test architecture is documented in Test Architecture.
Profile a real city OSM extract through the offline graph preprocessing path:
tools/profile-city-route-graph.shThe script downloads a Birmingham extract by default, builds source shards, packs graph artifacts, profiles warmup routes, and writes:
data/route-graph-artifacts/profile-report.json
Use fail mode in release validation:
Routing__RouteGraphProfileFailOnQualityGate=true \
Routing__RouteGraphProfileMaxRedisPayloadBytes=8388608 \
Routing__RouteGraphProfileMaxArtifactUnpackMilliseconds=150 \
tools/profile-city-route-graph.shThe profile report is intentionally strict. A failed quality gate does not mean the app is broken; it means a city graph or route bundle exceeds the configured production budget and should be split, cached differently, or preprocessed more aggressively before rollout.
Render manifests:
kubectl kustomize deploy/kubernetesApply the base stack:
kubectl apply -k deploy/kubernetesThe Kubernetes manifests include:
- API and worker deployments
- migration job
- service and ingress
- KEDA scaled objects
- pod disruption budgets
- topology spread constraints
- secret examples and ExternalSecret examples
- k6 distributed load and soak test jobs
Before production, replace example secrets, configure a managed Postgres/PostGIS or CloudNativePG path, point READONLY_DATABASE_URL at a read pooler/replica, and run the distributed k6 job in Distributed Load Testing.
The local observability stack is under deploy/observability:
- OpenTelemetry Collector
- Prometheus
- Grafana provisioning
- API performance alerts
- SLO burn-rate alerts
SLOs include safe-path p95, API 5xx rate, route computation saturation, external dependency fallback rate, and shared cache hit ratio. See SLO and Scaling.
- Production JWT keys must come from a secret manager or Kubernetes Secret/ExternalSecret.
- JWT key rotation supports
Jwt__PreviousKeys. - External APIs are guarded by timeout, bulkhead, circuit-breaker, and fallback paths.
- Public Overpass, OSRM, Police, weather, and environmental APIs should not sit on hot production request paths; use background enrichment and cached signals.
- Security reporting and deployment hardening expectations are documented in Security.
- The verified load tests prove the checked-in distributed path, not unlimited linear scale.
- Long cross-city route bundles can exceed the Redis/L2 payload budget; these are now flagged and kept out of distributed cache.
- City-scale routing still needs stronger graph preprocessing for the next tier: route-level slicing, boundary overlays, CCH/CRP-style customization, and persistent versioned graph releases.
- Accessibility quality is bounded by OSM/source data completeness. Missing width, kerb, surface, incline, curb ramp, and obstruction tags reduce route confidence.
- AccessCity remains pre-production and should not be represented as an emergency service or globally proven production system.
AccessCity.API/ .NET 9 API, modules, workers, routing, geospatial services
AccessCity.App/ Expo React Native client
AccessCity.Tests/ xUnit integration, routing, architecture, stress, and benchmark tests
AccessCity.SoakTestRunner/ standalone soak/allocation harness
deploy/kubernetes/ Kubernetes production manifests and load/soak jobs
deploy/observability/ OpenTelemetry, Prometheus, Grafana, and alerts
docs/ architecture, operations, scaling, security, and test reports
tools/ city route graph profiling tooling
data/ local OSM extracts and generated graph artifacts
- CI/CD
- Accessibility Vision Evaluation
- Distributed Load Testing
- Geospatial Query Audit
- K6 Load Test Results
- Modular Architecture
- Multi-Instance Deployment
- Route Graph Preprocessing
- Scalability and Modularity Report
- Secret Rotation
- SLO and Scaling
- Soak and Chaos Testing
- Stress Test Report
- Test Architecture
- Three-Minute Demo Walkthrough
Open an issue or pull request with a clear problem statement, reproduction steps, and the validation commands you ran. For routing changes, include profile-specific route quality tests or benchmark fixtures whenever behavior changes.
See Contributing and Code of Conduct.
AccessCity is licensed under the MIT License.
