Self-hosted commute optimizer. Samples the Google Routes API across your weekly commute windows, stores results in SQLite, and shows the best time to leave as a heatmap. Exposes a small REST API so anything you like — a dashboard, a notification system, a chat assistant — can consume the data.
- FastAPI backend + a no-build HTML/JS UI
- Two named routes —
morning(home → office) andevening(office → home), each with its own sampling window. Evening addresses are auto-reversed from morning. - Hard arrival deadline (optional, per direction). When set, the system recommends the latest safe departure that still arrives in time, and surfaces a top-3 alternatives strip ranked by departure time.
- Google Routes API v2 (
computeRoutes, traffic-aware) for live travel times - APScheduler runs a full weekly recompute every day at a configurable hour (default 04:00)
- Live dashboard endpoint: every request makes two Routes API calls in parallel — "leave now" and the recommended slot — so the numbers are fresh on every page view
- SQLite persistence on a single bind-mounted volume
- Single Docker container; runs anywhere Docker runs (tested on Synology Container Manager and Docker Desktop)
commuter/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── .env.example
└── app/
├── main.py # FastAPI app + lifespan
├── config.py # env config
├── api/routes.py # REST endpoints
├── services/
│ ├── google_routes.py # Routes API client
│ └── sampling.py # parallel sampler
├── scheduler/jobs.py # APScheduler daily job
├── db/ # SQLite schema + DAOs
└── static/ # index.html, styles.css, app.js
-
Get a Google Cloud API key with the Routes API enabled (Cloud Console → APIs & Services → Credentials). Restrict the key to the Routes API and your server's IP.
-
Copy
.env.exampleto.envand fill inGOOGLE_API_KEY. Optionally setDEFAULT_ORIGIN/DEFAULT_DESTINATIONto auto-seed both routes on first start. -
Build and run:
docker compose up -d --build
-
Open http://localhost:8080. On the Setup tab, enter origin and destination, save, then click Save & Recompute now to populate the heatmaps. The daily auto-recompute then keeps them current.
python -m venv .venv
.venv\Scripts\activate # or: source .venv/bin/activate
pip install -r requirements.txt
# PowerShell:
$env:GOOGLE_API_KEY = "your_key"
$env:DATA_DIR = ".\data"
# bash:
# export GOOGLE_API_KEY=your_key
# export DATA_DIR=./data
uvicorn app.main:app --host 0.0.0.0 --port 8080| Variable | Default | Purpose |
|---|---|---|
GOOGLE_API_KEY |
(required) | Google Cloud key with Routes API enabled |
DEFAULT_ORIGIN |
(empty) | Auto-seed home address on first start |
DEFAULT_DESTINATION |
(empty) | Auto-seed office address on first start |
TIME_WINDOW_START |
07:00 |
Morning sampling window start (HH:MM) |
TIME_WINDOW_END |
09:00 |
Morning sampling window end |
EVENING_TIME_WINDOW_START |
16:00 |
Evening sampling window start |
EVENING_TIME_WINDOW_END |
18:30 |
Evening sampling window end |
INTERVAL_MINUTES |
10 |
Sampling step |
DEFAULT_WEEKDAYS |
Mon,Tue,Wed,Thu,Fri |
Days to sample |
SCHEDULER_HOUR |
4 |
Daily recompute hour (in TZ) |
SCHEDULER_MINUTE |
0 |
Daily recompute minute |
CONCURRENT_REQUESTS |
10 |
Max parallel Routes API calls per recompute |
API_KEY |
(empty) | If set, all /api/* calls need X-API-Key |
TZ |
UTC |
Container time zone |
DATA_DIR |
/app/data |
SQLite + persistence path |
All endpoints return JSON. If API_KEY is set, send X-API-Key: <key> on
every request.
{ "status": "ok" }Returns { "morning": route|null, "evening": route|null }.
One home/office pair plus per-direction sampling windows. The evening route is stored automatically with the addresses reversed.
{
"origin": "Berliner Str. 1, 10115 Berlin",
"destination": "Alexanderplatz, Berlin",
"morning": {
"time_window_start": "07:00",
"time_window_end": "09:00",
"interval_minutes": 10,
"weekdays": "Mon,Tue,Wed,Thu,Fri",
"arrival_deadline": "09:00"
},
"evening": {
"time_window_start": "16:00",
"time_window_end": "18:30",
"interval_minutes": 10,
"weekdays": "Mon,Tue,Wed,Thu,Fri",
"arrival_deadline": null
}
}arrival_deadline is optional. When set on a direction, that direction's
recommendation becomes "the latest departure that still arrives by this time."
Resamples both directions. Returns
{ "status": "ok", "samples": {"morning": n, "evening": m} }.
Live payload (Routes API queried at request time) for both directions:
{
"morning": {
"name": "morning",
"day_of_week": "Thu",
"origin": "…",
"destination": "…",
"arrival_deadline": "09:00",
"best_departure_time": "08:00",
"optimal_duration": 59,
"arrival_time": "08:59",
"buffer_minutes": 1,
"current_duration": 42,
"time_savings": 14,
"live": true,
"alternatives": [
{ "departure_time": "08:00", "duration_minutes": 59, "arrival_time": "08:59", "buffer_minutes": 1 },
{ "departure_time": "07:50", "duration_minutes": 62, "arrival_time": "08:52", "buffer_minutes": 8 },
{ "departure_time": "07:40", "duration_minutes": 64, "arrival_time": "08:44", "buffer_minutes": 16 }
]
},
"evening": { "…": "same shape (no deadline → alternatives sorted by shortest drive)" }
}alternatives is the top 3 candidate slots from today's snapshot, filtered
by deadline if set, sorted latest-departure-first when a deadline applies and
shortest-drive-first otherwise. best_departure_time is alternatives[0].
Same shape but flat. direction is morning or evening. Handy when an
upstream consumer wants only one direction per poll (e.g. it polls each
endpoint with its own cadence).
Best departure within the next N minutes from now, with the same
live-rechecked top-3 alternatives. Designed for dashboard tiles and
homescreen widgets — the response's best object is exactly what you
want to display ("leave at HH:MM, drive N min, arrive HH:MM").
{
"name": "morning",
"day_of_week": "Tue",
"window_minutes": 60,
"window_end_time": "07:30",
"arrival_deadline": "09:00",
"best": {
"departure_time": "07:10",
"duration_minutes": 61,
"arrival_time": "08:11",
"buffer_minutes": 49,
"incident_severity": "clear",
"delta_minutes": 0,
"live": true
},
"candidates": [ … top 3 latest-feasible in the window … ]
}{ "morning": [...], "evening": [...] } where each list element is
{ "day": "Mon", "time": "07:00", "duration": 35.0 }.
The flat list for a single direction.
- docs/ARCHITECTURE.md — file layout, components, data flow
- docs/OPERATIONS.md — runtime behavior, refresh layers, incident detection, cost management
- docs/INTEGRATIONS.md — recipes for Home Assistant, AI assistant skills, and shell scripts
- CHANGELOG.md — feature history
MIT — see LICENSE.