An academic-aware travel planner for Georgia Tech-Europe students.
Standard map and booking apps optimize for the cheapest or fastest route. Clockwork optimizes for the one thing that actually matters to a student on a weekend trip: getting back in time for class, rested, even if a train runs late.
It plans your trip backward from your next class or deadline — scoring every itinerary for academic risk, flagging tight connections, thin buffers, reservation traps, and last-train risk, and always showing you a backup.
- Manage academic commitments — recurring classes and one-off deadlines/exams.
- Plan both legs of a weekend trip — choose your outbound and return trains independently, from any origin (not just your home base).
- Risk scoring on both ends — each itinerary gets a Safe / Tight / Risky / Won't-make-it badge and a 0–100 safety score, driven by:
- Deadline buffer — arrival + station→campus time vs. your commitment.
- Delay-adjusted arrival — a pessimistic delay model re-checks the deadline.
- Connection risk — flags dangerously short transfers.
- Last-train / no-fallback — warns when there's no later train if this one is disrupted, and surfaces the latest safe departure.
- Reservation traps — high-speed trains (TGV/AVE/…) your pass covers but that still need a paid, sellable seat reservation.
- Schedule conflicts — flags commitments you'd still be travelling through, on either leg.
- Readiness — late-night arrivals before an early commitment.
- Backup routes — earlier fallbacks for the recommended return.
- Live or offline data — swap between a curated offline dataset and live European rail data with one env var.
Next.js 16 (App Router) + React 19 + TypeScript, Tailwind 4
├── src/app/ UI (Server Components) + Server Actions + /api/stations
├── src/lib/ auth (jose JWT cookie + bcrypt), Prisma client, guards, actions
└── src/core/ framework-agnostic domain (unit-tested, no Next/React)
├── providers/ TrainProvider interface → SeedProvider | TransitousProvider
├── risk.ts the risk engine (assessRisk + assessOutbound)
├── conflicts.ts schedule-clash detection
├── planner.ts arrive-by / forward search + ranking + id resolution
└── ... stations, pass-rules, time helpers
The domain logic in src/core/ has no framework imports, so the risk engine is unit-testable in isolation and the data provider is swappable.
There is no official Eurail/Interrail timetable API — a pass is simply valid on most trains. So data flows through a TrainProvider interface:
SeedProvider(default) — offline, deterministic. Synthesizes journeys from ~14 Berlin corridors insrc/data/seed/corridors.ts. Used in tests/CI and for demos.TransitousProvider— live data from Transitous (a public, community-run MOTIS instance). Wrapped in aFallbackProviderthat automatically degrades to the seed dataset on any error.
Deutsche Bahn's own endpoints (
db-vendo-client) are intentionally not used — DB hard-blocks unofficial clients (OPS_BLOCKED). Transitous is openly documented (but requires a descriptiveUser-Agent).
Station ids are provider-specific (seed uses IBNR ids; Transitous uses MOTIS ids). The planner resolves a leftover seed id to the active provider by name at plan time, so switching providers doesn't break existing home bases.
- Custom credentials auth: bcrypt password hashing + a signed
joseJWT in an httpOnly cookie (no NextAuth). - Prisma 6 with SQLite for development (
prisma/schema.prisma). For production, change the datasourceprovidertopostgresqland pointDATABASE_URLat a Postgres instance.
npm install
npx prisma db push # create the SQLite dev database
npm run seed:user # optional: demo@gatech.edu / password12345
npm run devOpen http://localhost:3000.
TRAIN_PROVIDER=transitous npm run devWhen switching to live data, re-pick your stations via the search typeahead (or set your home base in onboarding) so ids match the active provider.
| Variable | Default | Notes |
|---|---|---|
DATABASE_URL |
file:./dev.db |
SQLite for dev; a Postgres URL for production. |
AUTH_SECRET |
dev placeholder | Change in production — signs session cookies. |
TRAIN_PROVIDER |
seed |
seed (offline) or transitous (live, with seed fallback). |
TRANSITOUS_BASE |
api.transitous.org |
Override the MOTIS endpoint if needed. |
See .env for the full list.
| Command | What it does |
|---|---|
npm run dev |
Start the dev server. |
npm run build |
Production build (also typechecks all routes). |
npm test |
Vitest unit suite (risk engine, providers, conflicts). |
npm run db:push |
Sync the Prisma schema to the database. |
npm run db:studio |
Open Prisma Studio. |
npm run seed:user |
Create a demo user with sample commitments. |
npm testThe suite (20 tests) covers the risk-engine truth tables (buffer thresholds, connection detection, delay-adjusted flips, last-train, reservation flagging), seed-provider arrive-by correctness, conflict detection, outbound scoring, and live→seed fallback. It always runs against the seed provider, so it never depends on a live upstream.
- After any
prisma db push/ schema change, restart the dev server — Next's hot reload doesn't re-requirethe regenerated Prisma client, so an old client stays cached and rejects new columns. - To stop a backgrounded dev server:
lsof -ti tcp:3000 | xargs kill.
.icscalendar import for commitments- Google OAuth
- Delay-history tuning from real data
- Shareable trip view