Tahoe winter driving in one app: live chain controls, closures, storm timing, and resort snow.
Install · Screenshots · Features · Architecture · Quickstart · API · Tests · Data sources
Every Sierra storm, the same question hits every ski group chat: do we need chains? Answering it today means juggling QuickMap, a weather app, resort sites, and CHP logs. ChainCheck puts the whole picture on one screen and pushes you an alert the moment it changes.
![]() Home · the answer first |
![]() Summit dark theme · 5 AM ready |
![]() Map · routes colored by tier |
![]() Trip brief · AI over live data |
![]() Guide · R0 to R3 in 60 seconds |
| Feature | Details |
|---|---|
| The answer, first | Your route's chain status the moment the app opens: R0 to R3 or Closed, with what that means for your exact car |
| A real map | Every Sierra crossing drawn on its actual highway geometry and colored by its live control level. 80 Caltrans cameras, closures, CHP incidents, resorts, passes. Tap anything anywhere in the app to see it here |
| Push alerts | Watch a route, get a notification the minute a tier changes, a closure goes up, or a storm warning lands on your pass. Nothing promotional, ever |
| Storm timing | NWS pass forecasts plus hourly snow accumulation, split into "before you leave" and "while you are driving" |
| Resort snow | 24 hour totals, base depth, and lifts open across 11 Tahoe resorts, with honest off-season labeling instead of fake zeros |
| AI trip brief | A language model writes the summary but can only narrate verified facts. Every brief is validated: no dropped controls, no invented road states. If validation fails, a deterministic plain-text brief ships instead |
| Built for mountains | Offline caching with honest "as of" timestamps, glove-sized touch targets, and a 60 second interactive guide for first-timers |
Important
The chain rules are code, not prose. R1, R2, and R3 vehicle requirements are encoded as structured logic from the published Caltrans definitions and covered by exhaustive unit tests. The AI presents them; it cannot alter them. And ChainCheck never tells you conditions are safe. That call is always yours.
flowchart LR
subgraph sources["Public data sources"]
CT["Caltrans district feeds"]
CHP["CHP dispatch XML"]
WX["NWS + Open-Meteo"]
RES["11 resort adapters"]
end
subgraph backend["Backend · FastAPI on Cloud Run"]
FEED["ca_roads feed layer"]
SNAP["Sierra snapshot"]
DIFF["Differ: tier changes,<br/>closures, storm warnings"]
API["JSON API"]
FCM["FCM push"]
end
subgraph clients["Clients"]
APP["Compose Multiplatform app<br/>Android · iOS in progress"]
end
CT --> FEED
CHP --> FEED
FEED --> SNAP
WX --> SNAP
RES --> API
SNAP --> API
SNAP --> DIFF
SCHED["Cloud Scheduler<br/>every 2 min in storms"] --> DIFF
DIFF --> FCM
API --> APP
FCM --> APP
Backend · Python 3.12, FastAPI, Cloud Run
- Road data comes from the ca_roads feed layer, which parses the public Caltrans and CHP feeds with per-district caching, conditional GETs, and stale-serve so a flaky feed degrades instead of disappearing.
- Forecasts come from api.weather.gov and Open-Meteo. Both free, no keys.
- Resort conditions come from public JSON feeds where resorts have them and light
per-resort scrapers where they do not. Each adapter fails alone, can be disabled
by config, and reports
nullfor anything unpublished rather than guessing. - Polling is adaptive: every 2 minutes during active weather, every 15 otherwise. A pure differ turns snapshots into events (tier changes, closures, storm warnings) that fan out to watching devices over FCM. Subscriptions are anonymous device tokens in Firestore.
- Route lines are extracted once from OpenStreetMap way data and baked into the package, so the map draws real pavement with zero runtime routing cost.
App · Kotlin, Compose Multiplatform
- One shared codebase for UI, data, and logic. Android ships first; the shared code already declares iOS targets.
- Two-theme design system: "Sierra Light" for daylight glare, "Summit" night gradient with a glowing tier ring for storm mornings. The system theme decides.
- Google Maps with custom cartography, tier-colored route polylines, and a quiet marker language (ringed dots, tier pills, resort chips) instead of pin soup.
- The freshness line shows a live pulse and relative age ("Updated 2 min ago"), and switches to an amber "cached" state in dead zones.
AI safety design · why the brief cannot lie
The trip brief pipeline is fact-assembly first, narration second:
- A pure, unit-tested function assembles everything the model may say: tier, every active control point, closures, alerts, forecast windows, snow split into before-departure and during-drive, and the vehicle ruling from the rules engine.
- Claude narrates those facts only, with one retry on validation failure.
- A validator checks the output: every active control and alert named, the current tier stated, no other tier mentioned, no safety promises. Anything that fails ships as the deterministic plain rendering instead.
- A 25-scenario eval set runs offline in CI and against the live model on demand.
Note
Google Play and the Apple App Store are coming in August 2026. Until then, Android users can install it directly in about a minute.
Android, right now:
- On your Android phone, open the latest release.
- Under Assets, tap the
.apkfile to download it, then open it. - Android will ask you to allow installs from your browser the first time (normal for apps not yet on the Play Store) — allow it, then tap Install.
- Open ChainCheck and pick your route. No account, no sign-up.
Full step-by-step, iPhone status, updating, and troubleshooting: Install guide.
By installing you agree to the terms of use. The privacy policy is short: no accounts, no tracking, location never leaves your phone.
Backend (Python 3.12+, uv recommended):
cd backend
uv venv && uv pip install -e ".[dev]"
uv run uvicorn chaincheck.api.app:app --reloadTip
The API is keyless by default. All core feeds are free and public. Set
ANTHROPIC_API_KEY if you want AI-narrated trip briefs; plain-text briefs work
without it.
Android (JDK 17+, Android SDK):
./gradlew :composeApp:installDebug -PchaincheckBaseUrl=http://10.0.2.2:8000Note
Maps need a Google Maps Android key in local.properties as MAPS_API_KEY=...
(restrict it to your package and signing certificate). Push needs a Firebase
project and its google-services.json in composeApp/. Both files are
gitignored; the app builds and runs without them, minus those two features.
The backend is client-agnostic JSON. The highlights:
| Endpoint | What it returns |
|---|---|
GET /v1/summary |
Every corridor's tier plus pass forecasts, in one call |
GET /v1/routes/{id} |
Control points, closures, and incidents for one corridor |
GET /v1/map |
Everything plottable: route geometry, controls, closures, incidents, webcams, passes, resorts |
GET /v1/resorts |
Snow totals, base, and lifts for 11 resorts |
POST /v1/rules/evaluate |
What a given vehicle must do at a given tier |
POST /v1/tripbrief |
The validated AI (or plain) trip brief |
PUT /v1/subscriptions |
Watch corridors with an FCM token |
Every data payload carries as_of and stale so clients can be honest about
freshness.
cd backend && uv run pytest # 150 tests: feeds, rules table, differ, briefs
uv run python -m evals.run_tripbrief # live-model eval set (needs ANTHROPIC_API_KEY)
./gradlew :composeApp:testDebugUnitTest # app unit testsCI runs the backend suite, lint, and an Android build on every pull request.
- Google Play and Apple App Store releases (August 2026)
- iOS build (shared code is ready, Xcode project in progress)
- Read-only web page for sharing route status with people without the app
- Chain controls, lane closures, cameras: Caltrans public district feeds
- Incidents: CHP dispatch feed
- Forecasts and winter alerts: National Weather Service
- Hourly snow: Open-Meteo
- Route geometry: OpenStreetMap contributors
- Resort conditions: each resort's public snow report, fetched gently and attributed in-app
Warning
ChainCheck is not affiliated with Caltrans, the CHP, the NWS, or any resort. Conditions change faster than any app. Verify before you drive: dial 511 or check quickmap.dot.ca.gov. Whether it is safe to drive is always your decision.
MIT. See LICENSE.




