Skip to content

Session 14

Change control applied to the Session 13 RRS, from demonstration to CCB decision to rrs_v2.py

Updated View as Markdown

This session takes the rrs.py baseline from Session 13, shows it to another person, records every change they ask for, and then implements one of them the way a maintained product does it: a numbered change request, an impact analysis, a decision by a change control board, an implementation in a new version, verification, a release note, and a second demonstration to the same person. Most software cost is spent after the first release, so the skill being assessed here is not coding the change. It is proving that the change was chosen, bounded and checked before the code moved. The deliverable is the paper trail plus rrs_v2.py and a diff against the baseline.

Objectives

Do not copy. Read for understanding and the viva
  • Run a structured demonstration and capture suggestions as change requests with priority and effort, not as a wish list
  • Fill a change request form and an impact analysis that names every artefact from Sessions 1 to 13 the change touches
  • Record a change control board decision with reasons for accepting one request and deferring the rest
  • Implement the accepted change in a new version, keeping the baseline untouched so the diff is reviewable
  • Verify the change, regression-test the old behaviour, write the release note, and get sign-off from the same reviewer

Problem Statement

Write in lab record

Demonstrate the Software developed in Session 13 to any other person and make a list of Changes suggested by him/her. Implement changes in Software following the Change Control process and demonstrate the updated Software to the same person.

Concept

Do not copy. Read for understanding and the viva

Why a process and not just an edit

A request that arrives as “can you add Tatkal?” hides three decisions: is it in scope, what else does it break, and is it worth doing now. Change control makes each decision visible and signed. The baseline (rrs.py, the Session 3 SRS, the Session 4 data dictionary, the Session 13 traceability matrix) is frozen; every change is a numbered request against it.

The steps applied in this session

  1. Change request raised: who, what, why, priority, estimated effort.
  2. Impact analysis: which requirements, data, code, tests and documents change.
  3. Change control board (CCB) decision: accept, defer or reject, with a reason.
  4. Implementation in a new version identifier. The baseline file is not edited.
  5. Verification: new tests for the change, old tests re-run for regression.
  6. Release note and re-demonstration; the requester signs off.

Impact analysis is the step students skip

Ask, for the change: which FR ids change or are added, which entity attributes change, which functions change, which existing tests could break, which pages of the record need an update. If the answer to the last question is “none”, the analysis is incomplete, because at least the traceability matrix gains a row.

Version identification

rrs.py is version 1.0, the baseline. rrs_v2.py is version 1.1, one accepted change. Keeping both files means the diff between them is the implementation record. In a real project this is a commit on a branch; in the lab record it is a copy with a new name.

Demonstration Record

Write in lab record
ItemValue
SoftwareRRS version 1.0, rrs.py from Session 13
Demonstrated toNikhil Sharma, MCA second year, same study centre, has used IRCTC as a passenger
Demonstrated byStudent (author of the Session 13 build)
Date and duration2026-09-20, 40 minutes
EnvironmentmacOS 27.0, Python 3.13.5, terminal, fresh rrs_data.json
Scenario shownAdmin adds a schedule; passenger searches BCT to NDLS, books 2 passengers in 2A, books until 1A is waitlisted, looks up PNR, cancels and sees the waitlist promoted
Reviewer was askedWhat is missing, what is confusing, what would you change first

Suggested Changes

Write in lab record

Priority: H = blocks real use, M = useful, L = cosmetic. Effort estimated in hours against the Session 2 baseline of about 60 LOC per hour for this code base.

CR idSuggestion by reviewerPriorityEffortType
CR-01Show which berth was allotted (LB, UB) and honour the berth preferenceM3 hEnhancement
CR-02Availability should be per segment; a BCT to KOTA booking should not block KOTA to NDLSH8 hDefect against SRS intent
CR-03Tatkal quota: reserve 10% of seats per class, bookable only the day before departure, at a 30% premiumH3 hNew requirement
CR-04Passenger login so a person sees only their own PNRsM5 hDeferred module (FR-1.1 to FR-1.6)
CR-05Occupancy report per schedule for the administratorL2 hDeferred module 8
CR-06Print the cancellation confirmation with the refund amount in the PNR lookupL1 hEnhancement

Change Request Form

Write in lab record
FieldEntry
CR idCR-03
TitleTatkal quota with day-before booking window and fare premium
Raised byNikhil Sharma (reviewer), 2026-09-20
Raised againstRRS 1.0, rrs.py, SRS Session 3
DescriptionReserve 10% of seats in each coach class (rounded up) as a Tatkal pool. A Tatkal booking is accepted only when the booking date is exactly one day before the run date. Fare is the normal fare plus 30%. Tatkal bookings are not waitlisted; if the pool is full the booking is refused. General bookings never use the Tatkal pool
ReasonEvery real Indian train has a Tatkal quota; the reviewer said the demo felt incomplete without it. It is a real requirement the SRS missed
PriorityHigh
Estimated effort3 hours: 1 design and impact analysis, 1 code, 1 test and record
Requested forVersion 1.1
StatusProposed on 2026-09-20, Accepted on 2026-09-22, Implemented on 2026-09-25, Verified on 2026-09-26

Impact Analysis

Write in lab record
ArtefactImpact of CR-03Change needed
Session 1 scopeBooking module gains a quota conceptOne sentence added to the scope of Booking
Session 3 SRSNew requirementsAdd FR-4.10 (Tatkal pool per class), FR-4.11 (day-before window), FR-4.12 (30% premium, no waitlist). Business rule list gains the 10% figure
Session 4 data dictionaryBooking entityAdd attribute quota with values G or T. Coach and Seat unchanged: the pool is the last 10% of labels, derived, not stored
Session 4 DFDProcess 4.2 Allocate Seats and 4.3 Compute FareBoth read quota; no new process or store
Session 5 designBooking module interfaceInput gains quota; Cancellation module unchanged because promotion already uses the general pool only
Session 6 screensBooking form and Availability screenOne new prompt (Quota) and one new column (Tatkal free count)
Session 13 codefree_seats, print_availability, book_ticket, constants, imports9 hunks, 28 lines added, 11 removed. cancel_ticket, promote_waitlist, add_train, add_schedule, search unchanged
Session 13 testsValidation checklistAll 12 rows re-run. Rows for seat order and waitlist now assume the class has more than 10 seats
Session 13 traceabilityNew rows FR-4.10 to FR-4.12Map to free_seats, book_ticket
Data filerrs_data.jsonOld bookings have no quota key; nothing reads it back, so old files still load
Side effect foundA class with fewer than 10 seats loses ceil(10%) = 1 seat to TatkalThe 1-seat Demo Express class from Session 13 has 0 general seats in 1.1; the seed trains (18 to 216 seats per class) are unaffected. Recorded as a known behaviour, not a defect, since real coaches have 18 or more seats
RiskDate comparison uses the calendar day, not a clock timeTatkal opens at 00:00 the day before, not at 10:00 as on IRCTC. Accepted for 1.1

CCB Decision

Write in lab record

CCB for a lab project: the student (developer), the reviewer (customer), and one more student acting as the quality member. Met on 2026-09-22.

CR idDecisionReason
CR-03Accepted for 1.1High priority, three hours, touches one module, does not change any existing rule. Reviewer ranked it first
CR-02Deferred to 1.2High priority but eight hours and changes the seat model in free_seats for every caller; must be done alone, with a redesign of the allocation table in Session 4
CR-04Deferred to 1.2Reinstates module 1, which the Session 13 build cut on purpose; needs the User entity and password hashing, five hours
CR-01DeferredDepends on CR-02: berth allocation only makes sense once seats are tracked per segment
CR-05DeferredLow priority; the data is already readable from the JSON file
CR-06RejectedThe refund is already visible at cancellation time; the lookup line shows status CANCELLED. Not worth a version

One change per version is the rule applied: a single-change diff can be reviewed and reverted; a bundle cannot.

Implementation

Write in lab record

Version 1.1 is rrs_v2.py, a copy of rrs.py with only CR-03 applied. Two constants carry the numbers from the change request so they are not scattered in the code.

rrs_v2.pypython
#!/usr/bin/env python3
"""Railway Reservation System (RRS) - MCS-217 Session 14 (v2, CR-03 Tatkal quota).

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 math
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)
TATKAL_SHARE = 0.10    # CR-03: seats per class reserved for Tatkal
TATKAL_PREMIUM = 0.30  # CR-03: fare premium on Tatkal bookings

# ---------------------------------------------------------------- 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, quota="G"):
    """Unallocated seat labels of this class in the General or Tatkal pool.

    CR-03: the last 10% of each class (rounded up) is the Tatkal pool.
    """
    labels = seat_labels(data["trains"][sched["train_no"]], cls)
    cut = len(labels) - math.ceil(len(labels) * TATKAL_SHARE)
    pool = labels[cut:] if quota == "T" else labels[:cut]
    return [s for s in pool 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])
        tatkal = len(free_seats(data, sched, cls, "T"))
        print("   %-3s %s  Tatkal %d" % (cls, "AVAILABLE %d" % free if free else "WL %d" % (wl + 1), tatkal))


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()
    quota = ask("Quota (G=General/T=Tatkal): ", lambda s: s.upper() in "GT").upper()
    if quota == "T" and date.today() != date.fromisoformat(sched["run_date"]) - timedelta(days=1):
        print("Tatkal opens only on the day before departure. Booking refused.")
        return
    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"]
    premium = 1 + TATKAL_PREMIUM if quota == "T" else 1
    fare = round(train["fares"][cls] * km * n * premium, 2)
    free = free_seats(data, sched, cls, quota)
    if quota == "T" and len(free) < n:
        print("No Tatkal seats left in %s. Booking refused." % cls)
        return
    status = "CONFIRMED" if len(free) >= n else "WAITLISTED"
    print("Fare: %d km x Rs %.2f/km x %d passengers x %.2f = Rs %.2f  [%s]" % (
        km, train["fares"][cls], n, premium, 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, "quota": quota, "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 14 build, v2)")
    try:
        run_menu("Main menu", roles, data)
    except EOFError:
        pass
    print("Bye.")


if __name__ == "__main__":
    main()

Diff Summary

Output of diff -u session-13/rrs.py session-14/rrs_v2.py, changed hunks only. Nine hunks, 28 lines added, 11 removed. Everything else in the file is byte-identical.

@@ -1,5 +1,5 @@
-"""Railway Reservation System (RRS) - MCS-217 Session 13.
+"""Railway Reservation System (RRS) - MCS-217 Session 14 (v2, CR-03 Tatkal quota).
@@ -11,6 +11,7 @@
 import json
+import math
 import os
@@ -20,6 +21,8 @@
 MAX_PASSENGERS = 6     # per PNR (business rule, Session 3)
+TATKAL_SHARE = 0.10    # CR-03: seats per class reserved for Tatkal
+TATKAL_PREMIUM = 0.30  # CR-03: fare premium on Tatkal bookings
@@ -113,12 +116,17 @@
-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 free_seats(data, sched, cls, quota="G"):
+    """Unallocated seat labels of this class in the General or Tatkal pool.
+
+    CR-03: the last 10% of each class (rounded up) is the Tatkal pool.
+    """
+    labels = seat_labels(data["trains"][sched["train_no"]], cls)
+    cut = len(labels) - math.ceil(len(labels) * TATKAL_SHARE)
+    pool = labels[cut:] if quota == "T" else labels[:cut]
+    return [s for s in pool if s not in sched["allocated"][cls]]
@@ -156,7 +164,8 @@
         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)))
+        tatkal = len(free_seats(data, sched, cls, "T"))
+        print("   %-3s %s  Tatkal %d" % (cls, "AVAILABLE %d" % free if free else "WL %d" % (wl + 1), tatkal))
@@ -261,6 +270,10 @@
     cls = ask("Class (%s): " % "/".join(train["coaches"]), lambda s: s.upper() in train["coaches"]).upper()
+    quota = ask("Quota (G=General/T=Tatkal): ", lambda s: s.upper() in "GT").upper()
+    if quota == "T" and date.today() != date.fromisoformat(sched["run_date"]) - timedelta(days=1):
+        print("Tatkal opens only on the day before departure. Booking refused.")
+        return
     n = ask_int("Number of passengers (1-%d): " % MAX_PASSENGERS, 1, MAX_PASSENGERS)
@@ -270,10 +283,15 @@
-    fare = round(train["fares"][cls] * km * n, 2)
-    free = free_seats(data, sched, cls)
+    premium = 1 + TATKAL_PREMIUM if quota == "T" else 1
+    fare = round(train["fares"][cls] * km * n * premium, 2)
+    free = free_seats(data, sched, cls, quota)
+    if quota == "T" and len(free) < n:
+        print("No Tatkal seats left in %s. Booking refused." % cls)
+        return
     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))
+    print("Fare: %d km x Rs %.2f/km x %d passengers x %.2f = Rs %.2f  [%s]" % (
+        km, train["fares"][cls], n, premium, fare, status))
@@ -285,7 +303,7 @@
-                             "class": cls, "booking_time": datetime.now().isoformat(timespec="seconds"),
+                             "class": cls, "quota": quota, "booking_time": datetime.now().isoformat(timespec="seconds"),
@@ -367,7 +385,7 @@
-    print("Railway Reservation System (Session 13 build)")
+    print("Railway Reservation System (Session 14 build, v2)")

Verification

Write in lab record

Run on 2026-09-26 at 09:05 with a fresh data file. New tests for CR-03 first, then the Session 13 checklist re-run on 1.1 as regression.

Test idTestExpectedObservedPass
T-19-1Availability on a 12951 run: 2A has 48 seatsGeneral 43, Tatkal 52A AVAILABLE 43 Tatkal 5Yes
T-19-21A has 18 seats, 3A has 128Tatkal 2 and 131A ... Tatkal 2, 3A ... Tatkal 13Yes
T-20-1Tatkal on S001, 10 days outRefused before passenger entryTatkal opens only on the day before departure. Booking refused.Yes
T-20-2Tatkal on a run dated tomorrowAcceptedBooked, status CONFIRMEDYes
T-21-1Tatkal fare, 2A BCT to NDLS, 1 passenger2.90 x 1384 x 1 x 1.30 = 5217.68Rs 5217.68Yes
T-21-2Seat comes from the Tatkal poolLabel 2A1-44 or later (pool is 44 to 48)seat 2A1-44Yes
T-21-3Tatkal count drops, general count unchangedTatkal 4, General 432A AVAILABLE 43 Tatkal 4Yes
R-1General booking fare unchanged8027.20 for 2 passengers in 2Ax 1.00 = Rs 8027.20Yes
R-2General waitlist and promotion on cancellationAs in Session 13Promoted in orderYes
R-3Refund slabs 427.50, 243.75, 0.00UnchangedUnchangedYes
R-4Old 1.0 data file loads in 1.1Bookings without quota key still printLoaded and printedYes

Re-demonstration Transcript

Repeated menu listings removed. The schedule created is for 2026-09-27, the day after the run.

Railway Reservation System (Session 14 build, v2)
Choice: 1
Choice: 2
12137 Punjab Mail      CSMT > BPL > NDLS  classes: SL, 3A
12951 Mumbai Rajdhani  BCT > KOTA > NDLS  classes: 3A, 2A, 1A
Train number: 12951
Run date (YYYY-MM-DD): 2026-09-27
Schedule S003 created: 12951 on 2026-09-27.
Choice: 0
Choice: 2
Choice: 2
Schedule id: S003
12951 Mumbai Rajdhani on 2026-09-27
   3A  AVAILABLE 115  Tatkal 13
   2A  AVAILABLE 43  Tatkal 5
   1A  AVAILABLE 16  Tatkal 2
Choice: 3
Schedule id: S001
From station code: BCT
To station code: NDLS
Class (3A/2A/1A): 2A
Quota (G=General/T=Tatkal): T
Tatkal opens only on the day before departure. Booking refused.
Choice: 3
Schedule id: S003
From station code: BCT
To station code: NDLS
Class (3A/2A/1A): 2A
Quota (G=General/T=Tatkal): T
Number of passengers (1-6): 1
Passenger 1 name: Priya Menon
Passenger 1 age: 27
Passenger 1 gender (M/F/O): F
Passenger 1 berth preference (LB/MB/UB/SL/SU): LB
Fare: 1384 km x Rs 2.90/km x 1 passengers x 1.30 = Rs 5217.68  [CONFIRMED]
Confirm booking (Y/N): Y
Booked. PNR 3361509606  status CONFIRMED
PNR 3361509606  12951 Mumbai Rajdhani  2026-09-27  BCT > NDLS  class 2A  fare Rs 5217.68  CONFIRMED
   Priya Menon   27 F  pref LB  seat 2A1-44
Choice: 2
Schedule id: S003
12951 Mumbai Rajdhani on 2026-09-27
   3A  AVAILABLE 115  Tatkal 13
   2A  AVAILABLE 43  Tatkal 4
   1A  AVAILABLE 16  Tatkal 2
Choice: 0
Choice: 0
Bye.

Release Note

Write in lab record

RRS 1.1, 2026-09-26. Implements CR-03. Adds a Tatkal quota of 10% of seats per class (rounded up), bookable only on the day before the run date, at a 30% fare premium, with no waitlist. New prompt “Quota (G=General/T=Tatkal)” on the booking form and a Tatkal free count on the availability screen. Booking records gain a quota attribute. Data files from 1.0 load unchanged. Known behaviour: classes with fewer than 10 seats have one seat moved to Tatkal. Deferred: CR-01, CR-02, CR-04, CR-05. Rejected: CR-06.

Re-demonstration and Sign-off

Write in lab record
ItemValue
Version shownRRS 1.1, rrs_v2.py
Shown toNikhil Sharma, the same reviewer as on 2026-09-20
Date and duration2026-09-26, 20 minutes
ScenarioTranscript above: availability with Tatkal counts, Tatkal refused 10 days out, Tatkal accepted for tomorrow at premium fare, count reduced
Reviewer remarksAccepted CR-03 as implemented. Asked that CR-02 be next. Noted the Tatkal window opens at midnight rather than 10:00, agreed to leave it for 1.1
Regression resultAll 12 Session 13 checklist rows pass on 1.1
Sign-offReviewer: Nikhil Sharma, 2026-09-26. Developer: student, 2026-09-26. Quality member: 2026-09-26
Status of CR-03Closed

Viva Questions

Do not copy. Read for understanding and the viva
  • Q: Why implement only one of six suggestions? A: One change per version keeps the diff reviewable and reversible; the others were deferred with reasons, not dropped.
  • Q: What is a baseline? A: The frozen set of artefacts a change is measured against: here rrs.py, the SRS, the data dictionary and the Session 13 checklist.
  • Q: What did the impact analysis find that the request did not mention? A: The data dictionary needs a quota attribute, the traceability matrix gains three rows, and classes under 10 seats lose a seat to Tatkal.
  • Q: Why is TATKAL_SHARE a constant and not typed by the admin? A: The change request fixed it at 10%; making it configurable is a different change request.
  • Q: How was regression checked? A: The 12 rows of the Session 13 validation checklist were re-run on rrs_v2.py with the same inputs plus quota G.
  • Q: Why is cancel_ticket not in the diff? A: promote_waitlist calls free_seats with the default general pool, so the cancellation path already excludes Tatkal seats. Impact analysis showed no edit was needed.
  • Q: Who sits on a CCB? A: Someone for the customer, someone for development and someone for quality; each has a reason to say no.
  • Q: What does the release note give the reviewer that the diff does not? A: The behaviour change in plain words, compatibility with old data, and what is still open.

Common Mistakes

Do not copy. Read for understanding and the viva
  • Editing rrs.py in place. The baseline must survive so the diff exists and the change can be reverted.
  • Implementing every suggestion at once, so the second demonstration cannot say which change caused which behaviour.
  • A change request without priority, effort or a named requester. The CCB has nothing to decide on.
  • Impact analysis that lists only code. The SRS, data dictionary, DFD and traceability matrix all change.
  • No regression run. Verifying only the new prompt misses the case where the general pool shrank.
  • Sign-off by the developer alone. The problem statement says the same person must see the updated software.

Session Summary

Write in lab record
  • Demonstration record with reviewer, date, environment and scenario
  • Table of six suggested changes with priority, effort and type
  • Change request form and impact analysis for CR-03
  • CCB decision table with the reason for each deferral and the rejection
  • Listing of rrs_v2.py and the diff summary against rrs.py
  • Verification table, re-demonstration transcript, release note and sign-off table
Navigation

Type to search…

↑↓ navigate↵ selectEsc close