marfoerst/commuter

GitHub: marfoerst/commuter

一个自托管的通勤时间优化工具,通过定时采样 Google Routes API 生成热力图,帮助用户找到每周最佳出发时段并提供实时 REST API 供外部集成。

Stars: 0 | Forks: 0

# 通勤优化器 自托管通勤优化器。会在您的每周通勤时间窗口内对 Google Routes API 进行采样,将结果存储在 SQLite 中,并以热力图的形式显示最佳出发时间。它提供了一个小型 REST API,以便您喜欢的任何工具——仪表板、通知系统、聊天助手——都能消费这些数据。 ## 功能特性 - FastAPI 后端 + 无需构建的 HTML/JS UI - 两条命名路线——`morning`(家 → 公司)和 `evening`(公司 → 家),每条路线都有自己的采样窗口。Evening(傍晚)路线的地址会自动从 Morning(早晨)路线中反转。 - **严格的到达截止时间**(可选,按方向设置)。设置后,系统会推荐**仍能准时到达的最迟安全出发时间**,并显示按出发时间排名的前 3 个备选方案条。 - Google Routes API v2(`computeRoutes`,支持交通感知),用于获取实时行驶时间 - APScheduler 每天在可配置的时间(默认为 04:00)运行一次完整的每周重新计算 - 实时仪表板端点:每次请求都会并行发起两次 Routes API 调用——“立即出发”和建议时段——因此每次页面查看时数据都是最新的 - 在单个绑定挂载卷上持久化 SQLite 数据 - 单个 Docker 容器;可在任何运行 Docker 的地方运行(已在 Synology Container Manager 和 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 ``` ## 快速开始 1. 获取已启用 **Routes API** 的 Google Cloud API 密钥(Cloud Console → APIs & Services → Credentials)。将密钥限制为仅限 Routes API 和您服务器的 IP。 2. 将 `.env.example` 复制到 `.env` 并填入 `GOOGLE_API_KEY`。可以选择设置 `DEFAULT_ORIGIN` / `DEFAULT_DESTINATION`,以便在首次启动时自动填充这两条路线。 3. 构建并运行: docker compose up -d --build 4. 打开 。在 **Setup**(设置)选项卡上,输入起点和终点,保存,然后点击 **Save & Recompute now**(保存并立即重新计算)以填充热力图。每日自动重新计算将随后使它们保持最新状态。 ## 快速开始 (本地, 无 Docker) ``` 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 ``` ## 配置(环境变量) | 变量 | 默认值 | 用途 | | --------------------------- | ---------------------- | --------------------------------------------- | | `GOOGLE_API_KEY` | _(必填)_ | 启用了 Routes API 的 Google Cloud 密钥 | | `DEFAULT_ORIGIN` | _(空)_ | 首次启动时自动填充家庭住址 | | `DEFAULT_DESTINATION` | _(空)_ | 首次启动时自动填充公司地址 | | `TIME_WINDOW_START` | `07:00` | 早晨采样窗口开始时间 (HH:MM) | | `TIME_WINDOW_END` | `09:00` | 早晨采样窗口结束时间 | | `EVENING_TIME_WINDOW_START` | `16:00` | 傍晚采样窗口开始时间 | | `EVENING_TIME_WINDOW_END` | `18:30` | 傍晚采样窗口结束时间 | | `INTERVAL_MINUTES` | `10` | 采样间隔步长 | | `DEFAULT_WEEKDAYS` | `Mon,Tue,Wed,Thu,Fri` | 需要采样的天数 | | `SCHEDULER_HOUR` | `4` | 每日重新计算的小时 (使用 `TZ` 时区) | | `SCHEDULER_MINUTE` | `0` | 每日重新计算的分钟 | | `CONCURRENT_REQUESTS` | `10` | 每次重新计算时最大并行 Routes API 调用数 | | `API_KEY` | _(空)_ | 如果设置,所有 `/api/*` 调用都需要 `X-API-Key` | | `TZ` | `UTC` | 容器时区 | | `DATA_DIR` | `/app/data` | SQLite + 持久化路径 | ## REST API 所有端点均返回 JSON。如果设置了 `API_KEY`,请在每个请求中发送 `X-API-Key: `。 ### `GET /api/health` ``` { "status": "ok" } ``` ### `GET /api/config` 返回 `{ "morning": route|null, "evening": route|null }`。 ### `POST /api/config` 一个家/公司地址对以及每个方向的采样窗口。Evening 路线将自动反转地址后进行存储。 ``` { "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` 是可选的。当在某个方向上设置后,该方向的推荐将变为“在此时间之前到达的最晚出发时间”。 ### `POST /api/recompute` 重新对两个方向进行采样。返回 `{ "status": "ok", "samples": {"morning": n, "evening": m} }`。 ### `GET /api/commute/today` 两个方向的实时负载(在请求时查询 Routes API): ``` { "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` 是来自今天快照的前 3 个候选时段,如果设置了截止时间则根据截止时间进行过滤;当应用了截止时间时,按最晚出发时间优先排序,否则按最短行驶时间优先排序。`best_departure_time` 即为 `alternatives[0]`。 ### `GET /api/commute/today/{direction}` 形状相同但是扁平化的。`direction` 可以是 `morning` 或 `evening`。当上游消费者希望每次轮询只获取一个方向时(例如,它以自己的节奏轮询每个端点),这非常方便。 ### `GET /api/commute/heatmap` `{ "morning": [...], "evening": [...] }`,其中每个列表元素是 `{ "day": "Mon", "time": "07:00", "duration": 35.0 }`。 ### `GET /api/commute/heatmap/{direction}` 单个方向的扁平化列表。 ## 成本考量 每日重新计算会在每个方向上进行 `len(weekdays) × slots` 次 Routes API 调用,且 `routingPreference: TRAFFIC_AWARE_OPTIMAL`。在默认设置下(5 天 × 约 13 个时段 × 2 个方向 ≈ 130 次调用/天),您完全在 Google 针对个人用户的免费额度内,但请根据当前的 Routes API 定价进行核实。 每次加载仪表板也会执行 2 次实时 Routes API 调用(一次用于“立即出发”,一次用于“今日最佳剩余时段”)。为了降低成本,消费者应保持 `scan_interval >= 300` 秒的轮询间隔。 ## 架构说明 - **采样逻辑** 会选择每个工作日/时间段的下一次未来出现时间,以便 Google Routes API 始终获得有效的未来 `departureTime`。 - **双层新鲜度**:每日 06:00 的批处理填充热力图(显示每周模式);实时仪表板端点在请求时再次查询 Google 以获取“立即出发”和今日最佳时段,因此每次查看的数字都能反映当前交通状况。 - **Schema 迁移** 是幂等的:现有的单路线部署将在下次启动时自动迁移到命名路线模型。 ## 许可证 MIT — 请参阅 [LICENSE](LICENSE)。
标签:API采样, APScheduler, AV绕过, Docker, FastAPI, Google Routes API, NAS工具, Python, REST API, SQLite, Web界面, 交通路况, 仪表盘, 出行建议, 出行规划, 单容器, 多模态安全, 安全防御评估, 数据可视化, 数据持久化, 无后门, 热力图, 生活效率工具, 群晖, 自托管, 自驾工具, 请求拦截, 路线优化, 逆向工具, 通勤优化, 通勤助手