FastAPI + PostGIS/pgRouting-ready backend starter for the hackathon.
Shared Android/backend API contract:
docs/api-contract.md
Android handoff contract for the current PoC:
docs/android-api-handoff.md
Keep external API keys in local .env only. For TMAP, fill TMAP_APP_KEY in .env and keep
TMAP_USE_LIVE=false until the exact TMAP endpoint/response parser is confirmed.
TMAP transit API integration notes are tracked in docs/tmap-transit-api.md.
OSM walking-network notes are tracked in docs/osm-walk-network.md.
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e ".[dev]"
Copy-Item .env.example .env
docker compose up -d postgresFill local secrets in .env if needed. Do not commit .env.
Check that PostgreSQL is reachable:
docker compose psUse this when testing from the same laptop only:
.\.venv\Scripts\python.exe -m uvicorn steptwin_api.main:app --app-dir src --host 127.0.0.1 --port 8000Open:
http://127.0.0.1:8000/docs
Health check:
Invoke-RestMethod http://127.0.0.1:8000/api/v1/healthUse this when testing from an Android device on the same Wi-Fi:
.\.venv\Scripts\python.exe -m uvicorn steptwin_api.main:app --app-dir src --host 0.0.0.0 --port 8000Equivalent helper script:
.\scripts\run-api.ps1 -LanFind the laptop Wi-Fi IP:
ipconfig | Select-String -Pattern "IPv4"Current development example:
http://172.30.1.66:8000
From the laptop, verify both local and LAN URLs:
Invoke-RestMethod http://127.0.0.1:8000/api/v1/health
Invoke-RestMethod http://172.30.1.66:8000/api/v1/healthFrom Android, use the laptop LAN IP, not localhost:
http://172.30.1.66:8000/api/v1/health
http://172.30.1.66:8000/api/v1/routes/preview
Android must be on the same Wi-Fi as the laptop. If the phone cannot reach the backend, allow the Windows firewall rule once:
.\scripts\allow-api-firewall.ps1Remove the firewall rule after testing if needed:
.\scripts\remove-api-firewall.ps1If the server is running in the foreground, press:
Ctrl+C
If a background process is using port 8000, find and stop it:
Get-NetTCPConnection -LocalPort 8000 -State Listen
Stop-Process -Id <OwningProcess>If you know the process ID:
Stop-Process -Id 30264$body = @{
origin = @{
name = "Seoul Station"
coordinate = @{ latitude = 37.5546788; longitude = 126.9706069 }
}
destination = @{
name = "Hoegi Station"
coordinate = @{ latitude = 37.589802; longitude = 127.057936 }
}
vulnerabilities = @{
speed_vulnerability = 0.4
turn_vulnerability = 0.2
strength_vulnerability = 0.7
}
} | ConvertTo-Json -Depth 6
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8000/api/v1/routes/preview `
-ContentType "application/json" `
-Body $bodyFor Seoul Station to Hoegi Station, the response should include a transit-1 segment with
transit.route_name = "수도권1호선", transit.subway_line = "수도권1호선", and a transit line color
such as #0052A4.
The pure walking endpoint still exists:
POST /api/v1/walk-routes/optimize
Use it only for nearby walking-only tests. It does not call TMAP and does not choose subway or bus.
The current walking graph source is OSM from Overpass.
Default routing tables:
PEDESTRIAN_GRAPH_VERTEX_TABLE="osm_pedestrian_vertices"
PEDESTRIAN_GRAPH_EDGE_TABLE="osm_pedestrian_edges"If using the experimental topology rebuild tables:
PEDESTRIAN_GRAPH_VERTEX_TABLE="pedestrian_topology_vertices"
PEDESTRIAN_GRAPH_EDGE_TABLE="pedestrian_topology_edges"Import OSM walkable roads from an Overpass bbox into the OSM-only tables:
.\.venv\Scripts\python.exe scripts\import_osm_walk_network.py `
--south 37.58 `
--west 127.04 `
--north 37.60 `
--east 127.07Useful first dry run:
.\.venv\Scripts\python.exe scripts\import_osm_walk_network.py `
--south 37.58 `
--west 127.04 `
--north 37.60 `
--east 127.07 `
--dry-runBy default, the OSM import replaces:
osm_pedestrian_vertices
osm_pedestrian_edges
Use --append only when adding another bbox to the existing OSM graph:
.\.venv\Scripts\python.exe scripts\import_osm_walk_network.py `
--south 37.56 `
--west 127.02 `
--north 37.61 `
--east 127.08 `
--appendDongdaemun-gu bbox import used during development:
.\.venv\Scripts\python.exe scripts\import_osm_walk_network.py `
--south 37.5607 `
--west 127.0233 `
--north 37.6060 `
--east 127.0773Current OSM road storage:
osm_pedestrian_vertices
id OSM node id, used as pgRouting vertex id
osm_node_id original OSM node id
geom WGS84 point
tags original OSM node tags
osm_pedestrian_edges
id generated as osm_way_id * 10000 + segment_index + 1
osm_way_id original OSM way id
source source OSM node id
target target OSM node id
geom WGS84 LineString between adjacent OSM way nodes
distance_meters computed segment length
stairs_count 1 when highway=steps, otherwise 0
slope_grade derived from OSM incline when available, otherwise 0
roughness_score 0 to 1 bumpiness score derived from smoothness/surface/tracktype/steps
shade_score derived from covered/tunnel/bridge tags
crossing_type derived from crossing/footway tags
surface_type normalized surface bucket
width_meters derived from OSM width when parseable
wheelchair_ok derived from wheelchair/highway=steps tags
tags original OSM way tags
The current route cost already uses slope_grade. roughness_score is stored for bumpy-road
scoring, but a user-facing roughness preference still needs to be wired into the cost model.
Export a standalone visualizer for the currently configured graph tables:
.\.venv\Scripts\python.exe scripts\export_pedestrian_graph_viewer.py `
--output docs\osm_dongdaemun_graph_viewer.htmlGenerated graph viewer HTML files are local inspection artifacts and are ignored by git.
Interactive API docs:
http://127.0.0.1:8000/docs
If DATABASE_URL is empty, the health API still works and reports the database check as disabled.
The pgRouting walk route APIs require PostgreSQL/PostGIS/pgRouting.
src/steptwin_api/
api/ HTTP routers
core/ app config, logging, lifespan
db/ async SQLAlchemy engine/session utilities
schemas/ Pydantic response models