Soumyadeep Das

Project 04 · 2026

OpenRail

Your journey, unreserved — every boardable train between two stations.

OpenRail homepage: Your Journey, Unreserved, with a station-pair search for unreserved trains.

Project details

Type
Unreserved train discovery + journey planner
Role
Design, Flask backend, browser engine
Data
eRail timetables · 700+ cached pairs

OpenRail answers two questions with one data source: which unreserved trains run directly between two stations, and how to build a whole multi-leg journey out of them — General (II) class, MEMU, locals and passengers. A thin Flask API on Vercel serves station lists and pair timetables scraped from eRail (cached 7 days per pair, 30 for stations, with 700+ pairs committed so cold starts never scrape); the real planning runs in the browser — Dijkstra over a 140-junction corridor graph, then a greedy earliest-arrival simulation that respects running days and change buffers. No booking, no waitlist, no tension.

Built with

  • Python
  • Flask
  • Vanilla JS
  • Vercel

The problem, in structure

  1. Booking-first tools skip the unreserved. Journey planners assume a reservation exists — General (II) class, MEMU and locals never show up.
    • millions ride them daily
    • no ticket needed to board
  2. “Trains between stations” stops at one hop. No direct train means a dead end, even when two locals get you there.
  3. Timetables live locked in apps. No open API — the data exists on rendered pages only.
  4. Serverless punishes heavy planners. A 30-second function cap and a read-only filesystem kill naive designs.
  5. Repeat searches re-fetch everything. Without caching, every query scrapes from scratch.

How it answers each one

  1. Unreserved is the filter, not an afterthought. Class bitmaps (bit 8 = II/General) and type strings keep only boardable trains.
  2. Multi-leg by design. Corridor routes from a 140-junction graph feed a earliest-arrival simulation that chains locals end to end.
  3. Parse the pages, serve the data. eRail’s timetable rows are parsed into clean JSON behind three small endpoints.
  4. Thin server, fat browser. Flask only serves pairs; Dijkstra and the simulator run client-side, well inside the 30-second cap.
  5. Cache everywhere. 7-day pair JSON, 30-day station mirror, 700+ committed pairs, and localStorage memoisation — the second search is instant.

Architecture

Traveller 1. Enters Stations Unreserved Only (no booking · no waitlist) 3. Plans In-Browser 2. Asks for Trains Flask on Vercel (thin server, 30 s cap) graph.py 140 junctions — corridor map 4. Cached Edges 5. Station Mirror Pair Timetables (cache/edges/*.json) Station Mirror (~9,100 stations)

Next phase

  • Live running status layered onto the planner.
  • Real fares instead of the ₹30 + ₹0.20/km estimate.
  • Holiday and special-train patterns in the simulation.
  • More committed pairs for colder corridors.
  • An offline-ready PWA for stations with weak signal.
Portfolio Live project Source code