Skip to content

Session 13

Working Railway Reservation System in Python built from the Session 1 to 6 specifications

Updated View as Markdown

This session turns the Railway Reservation System (RRS) documents from Sessions 1 to 6 into a program that runs. The scope statement fixed what the system does, the SRS numbered the requirements, the DFDs and ERD fixed the data, the modular design named the modules, and the screen designs fixed the prompts. The program here, rrs.py, implements modules 2 to 5 as a console application with JSON persistence, and every function in it traces back to a requirement id. The manual places this session under Software Change Management because the file you write now is the baseline that Session 14 changes under change control. A baseline you cannot trace is a baseline you cannot change safely.

Objectives

Do not copy. Read for understanding and the viva
  • Implement Train and Schedule Management, Search and Availability, Booking, and Cancellation and Refund from the Session 3 SRS
  • Keep one function per menu action so each requirement maps to one named piece of code
  • Enforce the business rules in code: 10-digit PNR, at most 6 passengers, no booking after departure, refund slabs, waitlist promotion in order
  • Persist data in a JSON file so the demonstration in Session 14 survives a restart
  • Record exactly what was left out of the SRS and why, so the limitations are a decision and not an accident

Problem Statement

Write in lab record

Develop “Railway Reservation System” as per specifications given in Sessions 1, 3, 4, 5 and 6.

Concept

Do not copy. Read for understanding and the viva

Specification to code is a mapping, not a rewrite

Do not design again. Open the Session 3 SRS and the Session 5 structure chart and give each functional requirement a function name before writing any code. The traceability matrix below is written first and filled in as the code is written. In the viva the examiner will pick an FR id and ask where it lives; the answer must be a function name.

Why a console program and a JSON file

The SRS describes a web system with a payment gateway and SMS. A lab session has about three hours. A console menu removes the UI layer, and a JSON file removes the database server, while keeping every entity from the Session 4 ERD as a dictionary with the same attribute names. rrs_data.json holds three top-level maps: trains, schedules, and bookings. Route, Coach, Fare and Passenger records nest inside them, which is the same one-to-many structure the ERD draws.

Business rules belong in one place each

Each rule from the SRS appears in exactly one function: the refund slabs in cancel_ticket, the departure check in book_ticket and find_schedules, the 6-passenger limit as the constant MAX_PASSENGERS, PNR uniqueness in new_pnr, waitlist order in promote_waitlist. When Session 14 changes a rule, the diff touches one function.

Seat allocation model

A Coach record is [coach count, seats per coach] and seat labels are generated on demand as 3A1-17 (class, coach number, seat number). A schedule stores only which labels are taken and by which PNR, so a cancellation frees a label and the next waitlisted PNR takes it. Availability is per class for the whole run, not per segment, and that is listed as a limitation below.

Traceability Matrix

Write in lab record

FR ids follow the numbering of the Session 3 SRS. Module numbers follow Session 5. User Management (FR-1.1 to FR-1.6), Payment, Notification and Reports are not implemented; see Known Limitations.

Module (Session 5)FR id (Session 3)RequirementFunction in rrs.pyDFD process (Session 4)Screen (Session 6)
2 Train and Schedule ManagementFR-2.2Admin adds a train with type and coaches per classadd_train2.1 Maintain TrainAdd Train form
2 Train and Schedule ManagementFR-2.3Admin defines the route: stations in sequence with times and distanceadd_train (route loop)2.2 Maintain RouteAdd Train form
2 Train and Schedule ManagementFR-2.5Admin sets a per-km fare for each classadd_train (fare loop)2.3 Maintain FareAdd Train form
2 Train and Schedule ManagementFR-2.6Admin creates a schedule (run) of a train on a dateadd_schedule, new_schedule2.4 Create ScheduleAdd Schedule form
3 Search and AvailabilityFR-3.1Search trains by source, destination and datesearch_trains, find_schedules3.1 Search TrainsSearch screen
3 Search and AvailabilityFR-3.4Show seat availability per class, or waitlist positioncheck_availability, print_availability, free_seats3.2 Check AvailabilityAvailability screen
4 BookingFR-4.2Capture up to 6 passengers with age, gender, berth preferencebook_ticket (passenger loop)4.1 Capture PassengersBooking form
4 BookingFR-4.5Allocate seats in order, or waitlist when none are freebook_ticket, free_seats, seat_labels4.2 Allocate SeatsBooking form
4 BookingFR-4.4Compute fare as rate per km x distance x passengersbook_ticket (fare line)4.3 Compute FareBooking form
4 BookingFR-4.8Generate a unique 10-digit PNR and store the bookingnew_pnr, book_ticket4.4 Generate PNRBooking confirmation
4 BookingFR-4.3Refuse booking after the schedule has departedbook_ticket, departure4.1 Capture PassengersBooking form
5 Cancellation and RefundFR-5.1Cancel a booking by PNRcancel_ticket5.1 Cancel BookingCancel screen
5 Cancellation and RefundFR-5.3Compute refund by hours before departurecancel_ticket (refund slabs)5.2 Compute RefundCancel screen
5 Cancellation and RefundFR-5.5Promote the waitlist in order when seats are freedpromote_waitlist5.3 Promote WaitlistCancel screen
4 BookingFR-4.9Look up a booking by PNRpnr_lookup, print_booking4.5 PNR EnquiryPNR status screen
AllNFR-R3Data survives program restartload_data, save_dataD1 to D4 data storesnone
AllNFR-U2Every input validated, no crash on bad inputask, ask_int, ask_datenoneall forms

Program

Write in lab record
rrs.pypython
#!/usr/bin/env python3
"""Railway Reservation System (RRS) - MCS-217 Session 13.

Console implementation of modules 2 to 5 of the RRS specified in
Sessions 1, 3, 4, 5 and 6:
  2. Train and Schedule Management  (Administrator)
  3. Search and Availability        (Passenger)
  4. Booking                        (Passenger)
  5. Cancellation and Refund        (Passenger)

Standard library only. All data lives in rrs_data.json next to this file.
"""
import json
import os
import random
from datetime import date, datetime, timedelta

DATA_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "rrs_data.json")
CLASSES = ("SL", "3A", "2A", "1A")
BERTHS = ("LB", "MB", "UB", "SL", "SU")
CLERKAGE = 60          # flat clerkage in rupees (business rule, Session 3)
MAX_PASSENGERS = 6     # per PNR (business rule, Session 3)

# ---------------------------------------------------------------- persistence


def load_data():
    """Read rrs_data.json; seed two trains and two schedules on first run."""
    if os.path.exists(DATA_FILE):
        with open(DATA_FILE) as fh:
            return json.load(fh)
    data = {"trains": {}, "schedules": {}, "bookings": {}}
    data["trains"]["12951"] = {
        "name": "Mumbai Rajdhani", "type": "Rajdhani",
        "route": [
            {"station_code": "BCT", "name": "Mumbai Central", "sequence": 1,
             "arrival_time": "--", "departure_time": "17:00", "distance_km": 0},
            {"station_code": "KOTA", "name": "Kota Jn", "sequence": 2,
             "arrival_time": "01:35", "departure_time": "01:40", "distance_km": 869},
            {"station_code": "NDLS", "name": "New Delhi", "sequence": 3,
             "arrival_time": "08:35", "departure_time": "--", "distance_km": 1384},
        ],
        "coaches": {"3A": [2, 64], "2A": [1, 48], "1A": [1, 18]},
        "fares": {"3A": 2.10, "2A": 2.90, "1A": 4.80},
    }
    data["trains"]["12137"] = {
        "name": "Punjab Mail", "type": "Mail",
        "route": [
            {"station_code": "CSMT", "name": "Mumbai CSMT", "sequence": 1,
             "arrival_time": "--", "departure_time": "19:35", "distance_km": 0},
            {"station_code": "BPL", "name": "Bhopal Jn", "sequence": 2,
             "arrival_time": "09:15", "departure_time": "09:25", "distance_km": 837},
            {"station_code": "NDLS", "name": "New Delhi", "sequence": 3,
             "arrival_time": "20:35", "departure_time": "--", "distance_km": 1541},
        ],
        "coaches": {"SL": [3, 72], "3A": [1, 64]},
        "fares": {"SL": 0.60, "3A": 1.60},
    }
    in_10_days = (date.today() + timedelta(days=10)).isoformat()
    for sid, tno in (("S001", "12951"), ("S002", "12137")):
        data["schedules"][sid] = new_schedule(tno, in_10_days, data["trains"][tno])
    save_data(data)
    return data


def save_data(data):
    """Write the whole data dictionary back to rrs_data.json."""
    with open(DATA_FILE, "w") as fh:
        json.dump(data, fh, indent=1)


def new_schedule(train_no, run_date, train):
    """Build a Schedule record with empty seat maps and waitlists per class."""
    return {"train_no": train_no, "run_date": run_date, "status": "SCHEDULED",
            "allocated": {c: {} for c in train["coaches"]},
            "waitlist": {c: [] for c in train["coaches"]}}

# ---------------------------------------------------------------- input helpers


def ask(prompt, check=lambda s: True, error="Invalid value, try again."):
    """Prompt until check(value) is true; returns the stripped string."""
    while True:
        value = input(prompt).strip()
        if value and check(value):
            return value
        print(error)


def ask_int(prompt, lo, hi):
    """Prompt for an integer in the closed range lo..hi."""
    return int(ask(prompt, lambda s: s.isdigit() and lo <= int(s) <= hi,
                   "Enter a number from %d to %d." % (lo, hi)))


def ask_date(prompt):
    """Prompt for a date in YYYY-MM-DD form."""
    def ok(s):
        try:
            date.fromisoformat(s)
            return True
        except ValueError:
            return False
    return ask(prompt, ok, "Use the form YYYY-MM-DD.")

# ---------------------------------------------------------------- domain helpers


def seat_labels(train, cls):
    """All seat labels of a class in allocation order, e.g. 3A1-17."""
    coaches, per_coach = train["coaches"][cls]
    return ["%s%d-%d" % (cls, c, s) for c in range(1, coaches + 1)
            for s in range(1, per_coach + 1)]


def free_seats(data, sched, cls):
    """Seat labels of this class not yet allocated on this schedule."""
    train = data["trains"][sched["train_no"]]
    return [s for s in seat_labels(train, cls) if s not in sched["allocated"][cls]]


def departure(data, sched):
    """Datetime at which the schedule leaves its first station."""
    first = data["trains"][sched["train_no"]]["route"][0]
    return datetime.fromisoformat(sched["run_date"] + " " + first["departure_time"])


def stop_index(train, code):
    """Index of a station code in the train route, or -1."""
    for i, stop in enumerate(train["route"]):
        if stop["station_code"] == code:
            return i
    return -1


def new_pnr(data):
    """Unique 10-digit PNR."""
    while True:
        pnr = str(random.randint(10 ** 9, 10 ** 10 - 1))
        if pnr not in data["bookings"]:
            return pnr


def find_schedules(data, src, dst, run_date):
    """Schedules on run_date whose route has src before dst and not yet departed."""
    found = []
    for sid, sched in sorted(data["schedules"].items()):
        train = data["trains"][sched["train_no"]]
        i, j = stop_index(train, src), stop_index(train, dst)
        if sched["run_date"] == run_date and 0 <= i < j and departure(data, sched) > datetime.now():
            found.append((sid, sched, train, i, j))
    return found


def print_availability(data, sched):
    """One line per class: free seats or waitlist length."""
    for cls in sched["allocated"]:
        free, wl = len(free_seats(data, sched, cls)), len(sched["waitlist"][cls])
        print("   %-3s %s" % (cls, "AVAILABLE %d" % free if free else "WL %d" % (wl + 1)))


def promote_waitlist(data, sched, cls):
    """Move waitlisted bookings of this class to CONFIRMED while seats allow."""
    wl = sched["waitlist"][cls]
    while wl:
        booking = data["bookings"][wl[0]]
        free = free_seats(data, sched, cls)
        if len(free) < len(booking["passengers"]):
            return
        for p, seat in zip(booking["passengers"], free):
            p["seat"] = seat
            sched["allocated"][cls][seat] = wl[0]
        booking["status"] = "CONFIRMED"
        print("Waitlisted PNR %s promoted to CONFIRMED." % wl.pop(0))

# ---------------------------------------------------------------- admin actions


def add_train(data):
    """Admin: add a train with its route stations, coaches and per-km fares."""
    train_no = ask("Train number (5 digits): ", lambda s: s.isdigit() and len(s) == 5)
    if train_no in data["trains"]:
        print("Train %s already exists." % train_no)
        return
    train = {"name": ask("Train name: "), "type": ask("Type (Rajdhani/Mail/Express): "),
             "route": [], "coaches": {}, "fares": {}}
    stops = ask_int("Number of stations on the route (2-20): ", 2, 20)
    for seq in range(1, stops + 1):
        print("Station %d of %d" % (seq, stops))
        train["route"].append({
            "station_code": ask("  code: ").upper(), "name": ask("  name: "), "sequence": seq,
            "arrival_time": "--" if seq == 1 else ask("  arrival HH:MM: "),
            "departure_time": "--" if seq == stops else ask("  departure HH:MM: "),
            "distance_km": 0 if seq == 1 else ask_int("  distance from origin (km): ", 1, 5000)})
    for cls in CLASSES:
        n = ask_int("Coaches of class %s (0-10): " % cls, 0, 10)
        if n:
            train["coaches"][cls] = [n, ask_int("  seats per %s coach (1-80): " % cls, 1, 80)]
            train["fares"][cls] = float(ask("  fare per km for %s (rupees): " % cls,
                                            lambda s: s.replace(".", "", 1).isdigit()))
    data["trains"][train_no] = train
    save_data(data)
    print("Train %s %s added with %d stations." % (train_no, train["name"], stops))


def add_schedule(data):
    """Admin: create a run of an existing train on a given date."""
    list_trains(data)
    train_no = ask("Train number: ", lambda s: s in data["trains"], "No such train.")
    run_date = ask_date("Run date (YYYY-MM-DD): ")
    if any(s["train_no"] == train_no and s["run_date"] == run_date for s in data["schedules"].values()):
        print("That train already runs on %s." % run_date)
        return
    sid = "S%03d" % (len(data["schedules"]) + 1)
    data["schedules"][sid] = new_schedule(train_no, run_date, data["trains"][train_no])
    save_data(data)
    print("Schedule %s created: %s on %s." % (sid, train_no, run_date))


def list_trains(data):
    """Admin: print every train with its route and classes."""
    for train_no, t in sorted(data["trains"].items()):
        stations = " > ".join(s["station_code"] for s in t["route"])
        print("%s %-16s %s  classes: %s" % (train_no, t["name"], stations, ", ".join(t["coaches"])))

# ---------------------------------------------------------------- passenger actions


def search_trains(data):
    """Passenger: list trains between two stations on a date with availability."""
    src, dst = ask("From station code: ").upper(), ask("To station code: ").upper()
    run_date = ask_date("Journey date (YYYY-MM-DD): ")
    found = find_schedules(data, src, dst, run_date)
    if not found:
        print("No trains found for %s to %s on %s." % (src, dst, run_date))
        return
    for sid, sched, train, i, j in found:
        km = train["route"][j]["distance_km"] - train["route"][i]["distance_km"]
        print("%s  %s %s  dep %s  arr %s  %d km" % (sid, sched["train_no"], train["name"],
              train["route"][i]["departure_time"], train["route"][j]["arrival_time"], km))
        print_availability(data, sched)


def check_availability(data):
    """Passenger: seat availability per class for one schedule."""
    sid = ask("Schedule id: ", lambda s: s in data["schedules"], "No such schedule.")
    sched = data["schedules"][sid]
    print("%s %s on %s" % (sched["train_no"], data["trains"][sched["train_no"]]["name"], sched["run_date"]))
    print_availability(data, sched)


def book_ticket(data):
    """Passenger: book up to six passengers, allocate seats, generate PNR."""
    sid = ask("Schedule id: ", lambda s: s in data["schedules"], "No such schedule.")
    sched = data["schedules"][sid]
    train = data["trains"][sched["train_no"]]
    if departure(data, sched) <= datetime.now():
        print("This train has already departed. Booking refused.")
        return
    src = ask("From station code: ", lambda s: stop_index(train, s.upper()) >= 0, "Not on route.").upper()
    dst = ask("To station code: ",
              lambda s: stop_index(train, s.upper()) > stop_index(train, src), "Must be after %s." % src).upper()
    cls = ask("Class (%s): " % "/".join(train["coaches"]), lambda s: s.upper() in train["coaches"]).upper()
    n = ask_int("Number of passengers (1-%d): " % MAX_PASSENGERS, 1, MAX_PASSENGERS)
    passengers = []
    for k in range(1, n + 1):
        passengers.append({"name": ask("Passenger %d name: " % k),
                           "age": ask_int("Passenger %d age: " % k, 1, 120),
                           "gender": ask("Passenger %d gender (M/F/O): " % k, lambda s: s.upper() in "MFO").upper(),
                           "berth_preference": ask("Passenger %d berth preference (LB/MB/UB/SL/SU): " % k,
                                                   lambda s: s.upper() in BERTHS).upper(), "seat": None})
    km = train["route"][stop_index(train, dst)]["distance_km"] - train["route"][stop_index(train, src)]["distance_km"]
    fare = round(train["fares"][cls] * km * n, 2)
    free = free_seats(data, sched, cls)
    status = "CONFIRMED" if len(free) >= n else "WAITLISTED"
    print("Fare: %d km x Rs %.2f/km x %d passengers = Rs %.2f  [%s]" % (km, train["fares"][cls], n, fare, status))
    if ask("Confirm booking (Y/N): ", lambda s: s.upper() in "YN").upper() != "Y":
        print("Booking abandoned.")
        return
    pnr = new_pnr(data)
    if status == "CONFIRMED":
        for p, seat in zip(passengers, free):
            p["seat"] = seat
            sched["allocated"][cls][seat] = pnr
    else:
        sched["waitlist"][cls].append(pnr)
    data["bookings"][pnr] = {"pnr": pnr, "schedule_id": sid, "from_station": src, "to_station": dst,
                             "class": cls, "booking_time": datetime.now().isoformat(timespec="seconds"),
                             "status": status, "total_fare": fare, "passengers": passengers}
    save_data(data)
    print("Booked. PNR %s  status %s" % (pnr, status))
    print_booking(data, pnr)


def cancel_ticket(data):
    """Passenger: cancel by PNR, compute refund, promote waitlist."""
    pnr = ask("PNR: ", lambda s: s in data["bookings"], "No such PNR.")
    booking = data["bookings"][pnr]
    if booking["status"] == "CANCELLED":
        print("PNR %s is already cancelled." % pnr)
        return
    sched = data["schedules"][booking["schedule_id"]]
    hours = (departure(data, sched) - datetime.now()).total_seconds() / 3600
    if booking["status"] == "WAITLISTED":
        refund, rule = booking["total_fare"] - CLERKAGE, "waitlisted, fare minus clerkage"
        sched["waitlist"][booking["class"]].remove(pnr)
    elif hours > 48:
        refund, rule = booking["total_fare"] - CLERKAGE, "more than 48 h before departure"
    elif hours >= 12:
        refund, rule = booking["total_fare"] * 0.5, "between 48 h and 12 h before departure"
    else:
        refund, rule = 0.0, "less than 12 h before departure"
    refund = round(max(refund, 0.0), 2)
    print("Refund: Rs %.2f (%s)" % (refund, rule))
    if ask("Confirm cancellation (Y/N): ", lambda s: s.upper() in "YN").upper() != "Y":
        print("Cancellation abandoned.")
        return
    if booking["status"] == "CONFIRMED":
        for p in booking["passengers"]:
            del sched["allocated"][booking["class"]][p["seat"]]
            p["seat"] = None
    booking["status"] = "CANCELLED"
    booking["refund"] = {"amount": refund, "reason": rule,
                         "processed_at": datetime.now().isoformat(timespec="seconds")}
    promote_waitlist(data, sched, booking["class"])
    save_data(data)
    print("PNR %s cancelled." % pnr)


def pnr_lookup(data):
    """Passenger: show a booking by PNR."""
    print_booking(data, ask("PNR: ", lambda s: s in data["bookings"], "No such PNR."))


def print_booking(data, pnr):
    """Print one booking with its passengers and seats."""
    b = data["bookings"][pnr]
    sched = data["schedules"][b["schedule_id"]]
    print("PNR %s  %s %s  %s  %s > %s  class %s  fare Rs %.2f  %s" % (
        pnr, sched["train_no"], data["trains"][sched["train_no"]]["name"], sched["run_date"],
        b["from_station"], b["to_station"], b["class"], b["total_fare"], b["status"]))
    for p in b["passengers"]:
        print("   %-12s %3d %s  pref %-2s  seat %s" % (p["name"], p["age"], p["gender"],
                                                       p["berth_preference"], p["seat"] or "--"))

# ---------------------------------------------------------------- menus


def run_menu(title, actions, data):
    """Print a numbered menu and dispatch until the user chooses 0."""
    while True:
        print("\n== %s ==" % title)
        for k, (label, _) in enumerate(actions, 1):
            print(" %d. %s" % (k, label))
        print(" 0. Back")
        choice = ask_int("Choice: ", 0, len(actions))
        if choice == 0:
            return
        actions[choice - 1][1](data)


def main():
    """Entry point: role selection."""
    data = load_data()
    admin = [("Add train", add_train), ("Add schedule", add_schedule), ("List trains", list_trains)]
    passenger = [("Search trains", search_trains), ("Check availability", check_availability),
                 ("Book ticket", book_ticket), ("Cancel ticket", cancel_ticket), ("PNR lookup", pnr_lookup)]
    roles = [("Administrator", lambda d: run_menu("Administrator", admin, d)),
             ("Passenger", lambda d: run_menu("Passenger", passenger, d))]
    print("Railway Reservation System (Session 13 build)")
    try:
        run_menu("Main menu", roles, data)
    except EOFError:
        pass
    print("Bye.")


if __name__ == "__main__":
    main()

How to Run

Python 3.8 or later, no packages to install. The first run creates rrs_data.json beside the script with two trains (12951 Mumbai Rajdhani and 12137 Punjab Mail) and one schedule each, dated 10 days from today, so a demonstration works immediately. Delete the JSON file to start again.

python3 rrs.py

Menu tree: Main menu offers Administrator and Passenger. Administrator: Add train, Add schedule, List trains. Passenger: Search trains, Check availability, Book ticket, Cancel ticket, PNR lookup. Enter 0 to go back.

Sample Output

Run on 2026-09-26 with Python 3.13 on a fresh data file. Repeated menu listings are removed; each Choice: line shows the option typed. The run adds a train with one 1A seat so the waitlist can be shown in a few steps.

Railway Reservation System (Session 13 build)
Choice: 1
Choice: 1
Train number (5 digits): 22222
Train name: Demo Express
Type (Rajdhani/Mail/Express): Express
Number of stations on the route (2-20): 2
Station 1 of 2
  code: AGC
  name: Agra Cantt
  departure HH:MM: 06:10
Station 2 of 2
  code: NDLS
  name: New Delhi
  arrival HH:MM: 09:00
  distance from origin (km): 195
Coaches of class SL (0-10): 0
Coaches of class 3A (0-10): 0
Coaches of class 2A (0-10): 0
Coaches of class 1A (0-10): 1
  seats per 1A coach (1-80): 1
  fare per km for 1A (rupees): 2.50
Train 22222 Demo Express added with 2 stations.
Choice: 2
12137 Punjab Mail      CSMT > BPL > NDLS  classes: SL, 3A
12951 Mumbai Rajdhani  BCT > KOTA > NDLS  classes: 3A, 2A, 1A
22222 Demo Express     AGC > NDLS  classes: 1A
Train number: 22222
Run date (YYYY-MM-DD): 2026-10-08
Schedule S003 created: 22222 on 2026-10-08.
Choice: 0
Choice: 2
Choice: 1
From station code: BCT
To station code: NDLS
Journey date (YYYY-MM-DD): 2026-10-06
S001  12951 Mumbai Rajdhani  dep 17:00  arr 08:35  1384 km
   3A  AVAILABLE 128
   2A  AVAILABLE 48
   1A  AVAILABLE 18
Choice: 3
Schedule id: S001
From station code: BCT
To station code: NDLS
Class (3A/2A/1A): 2A
Number of passengers (1-6): 2
Passenger 1 name: Asha Verma
Passenger 1 age: 34
Passenger 1 gender (M/F/O): F
Passenger 1 berth preference (LB/MB/UB/SL/SU): LB
Passenger 2 name: Rohan Verma
Passenger 2 age: 8
Passenger 2 gender (M/F/O): M
Passenger 2 berth preference (LB/MB/UB/SL/SU): UB
Fare: 1384 km x Rs 2.90/km x 2 passengers = Rs 8027.20  [CONFIRMED]
Confirm booking (Y/N): Y
Booked. PNR 2030868043  status CONFIRMED
PNR 2030868043  12951 Mumbai Rajdhani  2026-10-06  BCT > NDLS  class 2A  fare Rs 8027.20  CONFIRMED
   Asha Verma    34 F  pref LB  seat 2A1-1
   Rohan Verma    8 M  pref UB  seat 2A1-2
Choice: 3
Schedule id: S003
From station code: AGC
To station code: NDLS
Class (1A): 1A
Number of passengers (1-6): 1
Passenger 1 name: Kiran Rao
Passenger 1 age: 41
Passenger 1 gender (M/F/O): M
Passenger 1 berth preference (LB/MB/UB/SL/SU): LB
Fare: 195 km x Rs 2.50/km x 1 passengers = Rs 487.50  [CONFIRMED]
Confirm booking (Y/N): Y
Booked. PNR 9478409106  status CONFIRMED
PNR 9478409106  22222 Demo Express  2026-10-08  AGC > NDLS  class 1A  fare Rs 487.50  CONFIRMED
   Kiran Rao     41 M  pref LB  seat 1A1-1
Choice: 3
Schedule id: S003
From station code: AGC
To station code: NDLS
Class (1A): 1A
Number of passengers (1-6): 1
Passenger 1 name: Meera Nair
Passenger 1 age: 29
Passenger 1 gender (M/F/O): F
Passenger 1 berth preference (LB/MB/UB/SL/SU): LB
Fare: 195 km x Rs 2.50/km x 1 passengers = Rs 487.50  [WAITLISTED]
Confirm booking (Y/N): Y
Booked. PNR 3409290220  status WAITLISTED
PNR 3409290220  22222 Demo Express  2026-10-08  AGC > NDLS  class 1A  fare Rs 487.50  WAITLISTED
   Meera Nair    29 F  pref LB  seat --
Choice: 5
PNR: 9478409106
PNR 9478409106  22222 Demo Express  2026-10-08  AGC > NDLS  class 1A  fare Rs 487.50  CONFIRMED
   Kiran Rao     41 M  pref LB  seat 1A1-1
Choice: 4
PNR: 9478409106
Refund: Rs 427.50 (more than 48 h before departure)
Confirm cancellation (Y/N): Y
Waitlisted PNR 3409290220 promoted to CONFIRMED.
PNR 9478409106 cancelled.
Choice: 5
PNR: 3409290220
PNR 3409290220  22222 Demo Express  2026-10-08  AGC > NDLS  class 1A  fare Rs 487.50  CONFIRMED
   Meera Nair    29 F  pref LB  seat 1A1-1
Choice: 4
PNR: 9478409106
PNR 9478409106 is already cancelled.
Choice: 0
Choice: 0
Bye.

Check by hand: fare 1384 x 2.90 x 2 = 8027.20; refund 487.50 - 60 = 427.50 because departure is 12 days away. Bad input is rejected and the prompt repeats, for example Schedule id: S999 prints No such schedule. and asks again.

Known Limitations

Write in lab record
SRS item not implementedWhyEffect on the demonstration
Module 1 User Management (FR-1.1 to FR-1.6)Role is chosen from a menu; no registration, login or password hashAnyone can act as Administrator. Acceptable in a single-user lab build
Module 6 PaymentNeeds an external gateway; the SRS marks it as an external actorFare is computed and stored in total_fare; no Payment record with mode or txn_ref
Module 7 NotificationNeeds SMS and email servicesThe console line printed after booking stands in for the confirmation message
Module 8 ReportsAdmin-only reads over the same data; no new rule to proveOccupancy or revenue can be read from rrs_data.json by hand
Segment-wise availabilityA seat is held for the whole run, even for a BCT to KOTA bookingAvailability can be under-reported on long routes
Berth preferenceStored on the Passenger record but not used in allocationSeat labels are allocated in order
Schedule status changes and delay noticesOut of scope for one user role per modulestatus is always SCHEDULED
ConcurrencySingle process, file rewritten after each actionTwo clerks running two copies could double-book

Validation Checklist

Write in lab record

Each row is a business rule from the Session 3 SRS with the test performed on the run above.

RuleTestExpectedObservedPass
PNR is a 10-digit unique numberBook three ticketsThree distinct 10-digit values2030868043, 9478409106, 3409290220Yes
Max 6 passengers per PNREnter 7 at the passenger count promptPrompt repeats with range messageEnter a number from 1 to 6.Yes
Fare = rate per km x distance x passengers2A BCT to NDLS, 2 passengers2.90 x 1384 x 2 = 8027.20Rs 8027.20Yes
Seats allocated in orderFirst 2A booking on S0012A1-1 and 2A1-22A1-1, 2A1-2Yes
Waitlist when no seatsSecond booking on a 1-seat classStatus WAITLISTED, seat shown as --WAITLISTEDYes
Refund 100% minus clerkage over 48 hCancel 12 days before departure487.50 - 60 = 427.50Rs 427.50Yes
Refund 50% between 48 h and 12 hDemo Express scheduled tomorrow, departs 06:10; cancelled at 09:09 today (21 h before)50% of 487.50 = 243.75Rs 243.75Yes
No refund under 12 h12951 scheduled today, departs 17:00; cancelled at 09:09 (8 h before), fare 4013.60Rs 0.00Rs 0.00Yes
Waitlist promoted in orderCancel the confirmed 1A ticketFirst waitlisted PNR gets the freed seatPNR 3409290220 promoted, seat 1A1-1Yes
No booking after departureAdmin adds a 12951 run dated yesterday, passenger tries to book itRefusedThis train has already departed. Booking refused.Yes
Cancelled PNR cannot be cancelled againCancel 9478409106 twiceSecond attempt refusedis already cancelled.Yes
Data survives restartBook in one run, look up in the nextBooking foundFound with same seatsYes

Viva Questions

Do not copy. Read for understanding and the viva
  • Q: Where is the 48-hour refund rule in the code? A: In cancel_ticket; hours before departure come from departure(data, sched) minus datetime.now().
  • Q: How is PNR uniqueness guaranteed? A: new_pnr draws a random 10-digit number and retries while it already exists in bookings.
  • Q: Why is the schedule the unit of booking and not the train? A: A train runs on many dates; seats belong to one run. That is the Schedule entity from the Session 4 ERD.
  • Q: What happens to a waitlisted booking when a confirmed one is cancelled? A: promote_waitlist takes the first PNR in the class waitlist and allocates freed seats if enough are free for all its passengers.
  • Q: Why is there no login? A: Module 1 was cut for the lab build; role is picked from the menu. It is recorded in Known Limitations, not hidden.
  • Q: How does the program stop a booking after departure? A: book_ticket compares departure() with the current time and refuses; find_schedules also hides departed runs from search.
  • Q: What is stored in rrs_data.json and how does it map to the ERD? A: Three maps: trains (with nested Route, Coach, Fare), schedules (with allocated seats and waitlists), bookings (with nested Passenger and Refund).
  • Q: Why compute fare from a per-km rate and not a fixed fare per pair of stations? A: The Session 4 data dictionary defines Fare as (train_no, class, rate_per_km), so the code follows the data model.

Common Mistakes

Do not copy. Read for understanding and the viva
  • Building a different system from the one specified. The examiner checks the code against your own SRS; new modules that are not in it earn nothing.
  • No traceability. A program without the FR-to-function table cannot be defended in a viva and cannot be changed in Session 14.
  • Hard-coding the demonstration data in the program instead of seeding a data file, so the second run cannot show persistence.
  • Letting bad input crash the program. Every input() goes through ask, ask_int or ask_date.
  • Hiding the gaps. Write the Known Limitations table; an honest gap is a design decision, an undiscovered one is a defect.
  • Refund computed from booking time instead of departure time. The rule is hours before departure.

Session Summary

Write in lab record
  • Traceability matrix from Session 3 FR ids and Session 5 modules to function names
  • Full listing of rrs.py with the module docstring
  • How to run, and the sample session transcript showing add schedule, search, book, waitlist, lookup and cancel with promotion
  • Known Limitations table stating what from the SRS is not built and why
  • Validation checklist with observed values for every business rule
Navigation

Type to search…

↑↓ navigate↵ selectEsc close