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 recordDevelop “Railway Reservation System” as per specifications given in Sessions 1, 3, 4, 5 and 6.
Concept
Do not copy. Read for understanding and the vivaSpecification 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 recordFR 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) | Requirement | Function in rrs.py | DFD process (Session 4) | Screen (Session 6) |
|---|---|---|---|---|---|
| 2 Train and Schedule Management | FR-2.2 | Admin adds a train with type and coaches per class | add_train | 2.1 Maintain Train | Add Train form |
| 2 Train and Schedule Management | FR-2.3 | Admin defines the route: stations in sequence with times and distance | add_train (route loop) | 2.2 Maintain Route | Add Train form |
| 2 Train and Schedule Management | FR-2.5 | Admin sets a per-km fare for each class | add_train (fare loop) | 2.3 Maintain Fare | Add Train form |
| 2 Train and Schedule Management | FR-2.6 | Admin creates a schedule (run) of a train on a date | add_schedule, new_schedule | 2.4 Create Schedule | Add Schedule form |
| 3 Search and Availability | FR-3.1 | Search trains by source, destination and date | search_trains, find_schedules | 3.1 Search Trains | Search screen |
| 3 Search and Availability | FR-3.4 | Show seat availability per class, or waitlist position | check_availability, print_availability, free_seats | 3.2 Check Availability | Availability screen |
| 4 Booking | FR-4.2 | Capture up to 6 passengers with age, gender, berth preference | book_ticket (passenger loop) | 4.1 Capture Passengers | Booking form |
| 4 Booking | FR-4.5 | Allocate seats in order, or waitlist when none are free | book_ticket, free_seats, seat_labels | 4.2 Allocate Seats | Booking form |
| 4 Booking | FR-4.4 | Compute fare as rate per km x distance x passengers | book_ticket (fare line) | 4.3 Compute Fare | Booking form |
| 4 Booking | FR-4.8 | Generate a unique 10-digit PNR and store the booking | new_pnr, book_ticket | 4.4 Generate PNR | Booking confirmation |
| 4 Booking | FR-4.3 | Refuse booking after the schedule has departed | book_ticket, departure | 4.1 Capture Passengers | Booking form |
| 5 Cancellation and Refund | FR-5.1 | Cancel a booking by PNR | cancel_ticket | 5.1 Cancel Booking | Cancel screen |
| 5 Cancellation and Refund | FR-5.3 | Compute refund by hours before departure | cancel_ticket (refund slabs) | 5.2 Compute Refund | Cancel screen |
| 5 Cancellation and Refund | FR-5.5 | Promote the waitlist in order when seats are freed | promote_waitlist | 5.3 Promote Waitlist | Cancel screen |
| 4 Booking | FR-4.9 | Look up a booking by PNR | pnr_lookup, print_booking | 4.5 PNR Enquiry | PNR status screen |
| All | NFR-R3 | Data survives program restart | load_data, save_data | D1 to D4 data stores | none |
| All | NFR-U2 | Every input validated, no crash on bad input | ask, ask_int, ask_date | none | all forms |
Program
Write in lab record#!/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.pyMenu 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 implemented | Why | Effect 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 hash | Anyone can act as Administrator. Acceptable in a single-user lab build |
| Module 6 Payment | Needs an external gateway; the SRS marks it as an external actor | Fare is computed and stored in total_fare; no Payment record with mode or txn_ref |
| Module 7 Notification | Needs SMS and email services | The console line printed after booking stands in for the confirmation message |
| Module 8 Reports | Admin-only reads over the same data; no new rule to prove | Occupancy or revenue can be read from rrs_data.json by hand |
| Segment-wise availability | A seat is held for the whole run, even for a BCT to KOTA booking | Availability can be under-reported on long routes |
| Berth preference | Stored on the Passenger record but not used in allocation | Seat labels are allocated in order |
| Schedule status changes and delay notices | Out of scope for one user role per module | status is always SCHEDULED |
| Concurrency | Single process, file rewritten after each action | Two clerks running two copies could double-book |
Validation Checklist
Write in lab recordEach row is a business rule from the Session 3 SRS with the test performed on the run above.
| Rule | Test | Expected | Observed | Pass |
|---|---|---|---|---|
| PNR is a 10-digit unique number | Book three tickets | Three distinct 10-digit values | 2030868043, 9478409106, 3409290220 | Yes |
| Max 6 passengers per PNR | Enter 7 at the passenger count prompt | Prompt repeats with range message | Enter a number from 1 to 6. | Yes |
| Fare = rate per km x distance x passengers | 2A BCT to NDLS, 2 passengers | 2.90 x 1384 x 2 = 8027.20 | Rs 8027.20 | Yes |
| Seats allocated in order | First 2A booking on S001 | 2A1-1 and 2A1-2 | 2A1-1, 2A1-2 | Yes |
| Waitlist when no seats | Second booking on a 1-seat class | Status WAITLISTED, seat shown as -- | WAITLISTED | Yes |
| Refund 100% minus clerkage over 48 h | Cancel 12 days before departure | 487.50 - 60 = 427.50 | Rs 427.50 | Yes |
| Refund 50% between 48 h and 12 h | Demo Express scheduled tomorrow, departs 06:10; cancelled at 09:09 today (21 h before) | 50% of 487.50 = 243.75 | Rs 243.75 | Yes |
| No refund under 12 h | 12951 scheduled today, departs 17:00; cancelled at 09:09 (8 h before), fare 4013.60 | Rs 0.00 | Rs 0.00 | Yes |
| Waitlist promoted in order | Cancel the confirmed 1A ticket | First waitlisted PNR gets the freed seat | PNR 3409290220 promoted, seat 1A1-1 | Yes |
| No booking after departure | Admin adds a 12951 run dated yesterday, passenger tries to book it | Refused | This train has already departed. Booking refused. | Yes |
| Cancelled PNR cannot be cancelled again | Cancel 9478409106 twice | Second attempt refused | is already cancelled. | Yes |
| Data survives restart | Book in one run, look up in the next | Booking found | Found with same seats | Yes |
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 fromdeparture(data, sched)minusdatetime.now(). - Q: How is PNR uniqueness guaranteed? A:
new_pnrdraws a random 10-digit number and retries while it already exists inbookings. - 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_waitlisttakes 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_ticketcomparesdeparture()with the current time and refuses;find_schedulesalso 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 throughask,ask_intorask_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.pywith 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