Campus Dining: Meal Swipes, Guest Passes and Block Plans
EasyAI-assisted coding · AI-Assisted Debugging
Campus Dining: Meal Swipes, Guest Passes and Block Plans
You've joined the small team that runs the university dining card system. Students swipe their ID at three dining halls, spend dining dollars at retail counters, and bring guests. The Registrar's office has filed three bug reports against the swipe rules, and the dining director has two changes queued behind them. All money is integer cents.
Codebase
| File | What it does |
|---|---|
dining/api.py | run(operations) entry point and the Dispatcher that routes each operation |
dining/swipes.py | SwipeDesk: swipes, guest swipes, retail purchases, balance; keeps every swipe by id |
dining/accounts.py | StudentRegistry: enrol, look up, and the lazy weekly refill (roll_week) |
dining/clock.py | Timestamp parsing, week_start, week_index, meal_period |
dining/plans.py | The plan catalogue |
dining/models.py | Student and Swipe records |
dining/config.py | Halls, retail locations, meal periods, swipe values, semester dates, guest cap |
dining/errors.py | DiningError and every error code |
dining/refunds.py | Placeholder for Part 2 (routes exist, answer "not supported") |
dining/conversion.py | Placeholder for Part 3 (same) |
tests/test_swipes.py | Example tests (two of them currently fail) |
How your code is run
run(operations: list[list]) -> list executes the operations in order against a fresh system and returns one result per operation. Every operation is [name, ts, ...args], where ts is a local campus time string "YYYY-MM-DD HH:MM" (24-hour clock). Timestamps never decrease. Errors are returned as "error: <code>" strings, never raised. Use datetime for everything calendar-related.
| Operation | Arguments after ts | Returns | Errors (checked in this order) |
|---|---|---|---|
enroll | student, plan, dollars | true | invalid arguments (empty or non-string id), invalid plan, invalid amount, student exists |
swipe | student, hall | receipt (below) | unknown student, unknown hall, closed, already swiped, no swipes or dining dollars |
guest_swipe | student, hall | receipt | unknown student, unknown hall, closed, no guest swipes |
buy | student, location, cents | new dining-dollar balance | unknown student, unknown location, invalid amount, insufficient dining dollars |
balance | student | {"plan", "swipes_left", "guests_left", "dollars"} | unknown student |
An unknown operation name returns "error: unknown operation". A wrong argument count, a malformed or impossible timestamp ("2026-10-19 7:00", "2026-02-30 12:00", a non-string) or a non-list operation returns "error: invalid arguments". A valid amount is an integer (not true/false): > 0 for buy, >= 0 for the starting dollars in enroll.
Time rules
- A week runs from Sunday 00:00 to Saturday 23:59. Weekly allowances are refilled lazily by
StudentRegistry.roll_weekthe first time a student is touched in a new week. - Meal periods (start inclusive, end exclusive; outside them the halls are
closed): breakfast 07:00-10:30, lunch 11:00-15:00, dinner 17:00-21:00, late-night 21:00-24:00. - Halls are
north,south,west. Retail locations arecafe,market,grill.
Plans and swipes
| Plan | Weekly swipes | Guest swipes per semester |
|---|---|---|
w7 | 7 | 4 |
w14 | 14 | 8 |
swipespends one plan swipe. A student may swipe once per meal period per hall per day; a second swipe in the same (day, period, hall) isalready swiped, but another hall, another period or another day is fine.- Automatic fallback: when the student has no plan swipes left,
swipeis paid with dining dollars at the period's swipe value (breakfast 800, lunch 1200, dinner 1500, late-night 1000 cents) as long asdollars >= value. Otherwiseno swipes or dining dollars. - Unused swipes do not roll over. Each new week starts at exactly the plan's weekly swipes.
guest_swipespends one guest swipe (a semester allowance, never refilled weekly). Guest swipes do not use plan swipes or dining dollars and are not limited by the once-per-period rule.- Swipe ids are
sw-1,sw-2, ... in the order swipes (own or guest) succeed; a rejected swipe does not use an id. A receipt is{"id", "period", "paid", "cents"}wherepaidis"swipe","dollars"or"guest", andcentsis what was charged in dining dollars (0 unlesspaidis"dollars"). balancefirst brings the student's week up to date, then reports.
Worked example
[["enroll", "2026-10-17 09:00", "ana", "w7", 5000],
["swipe", "2026-10-17 12:00", "ana", "north"],
["swipe", "2026-10-17 12:30", "ana", "north"],
["swipe", "2026-10-17 12:40", "ana", "south"],
["guest_swipe", "2026-10-17 18:00", "ana", "west"],
["balance", "2026-10-17 23:59", "ana"],
["balance", "2026-10-18 00:00", "ana"]]returns
[true,
{"id": "sw-1", "period": "lunch", "paid": "swipe", "cents": 0},
"error: already swiped",
{"id": "sw-2", "period": "lunch", "paid": "swipe", "cents": 0},
{"id": "sw-3", "period": "dinner", "paid": "guest", "cents": 0},
{"plan": "w7", "swipes_left": 5, "guests_left": 3, "dollars": 5000},
{"plan": "w7", "swipes_left": 7, "guests_left": 3, "dollars": 5000}](2026-10-17 is a Saturday, 2026-10-18 a Sunday.)
Part 1: Fix the swipe rules (DINE-418)
Goal: make the system behave as described above. Reports from the field: "My swipes didn't refill on Sunday." "I skipped a week and now I have more than 7 swipes." "I couldn't swipe at the second hall for lunch."
Rules
- Weeks start on Sunday 00:00 (see the Time rules). Saturday 23:59 belongs to the old week.
- A new week resets the student to the plan's weekly swipes, whatever was left over, however many weeks were skipped.
- The once-per-period rule is per (day, period, hall).
tests/test_swipes.py has two failing tests that show the symptoms. Everything else in the table and rules above already works and must keep working.
Part 2: Guest-swipe transfers and refunds (DINE-433)
Goal: students can give guest swipes to each other, and the front desk can refund a swipe that was charged in error. Both operations replace the "not supported" stubs in dining/refunds.py.
| Operation | Arguments after ts | Returns | Errors (in this order) |
|---|---|---|---|
transfer_guest | from_student, to_student, count | {"from": <sender's guest swipes left>, "to": <receiver's>} | invalid count, unknown student (sender first), same student, not enough guest swipes, over guest cap |
refund | swipe_id | {"swipe", "credited", "amount"} | unknown swipe, already refunded |
Rules
countmust be an integer> 0(not a boolean). A student may hold at most 10 guest swipes: a transfer that would take the receiver above 10 isover guest cap. Nothing changes on any error.- A refund of a guest swipe gives the guest swipe back to the student who was charged (
"credited": "guest_swipe","amount": 1), even if they hold more than 10 afterwards. - A refund of a swipe paid with dining dollars gives back exactly the cents charged, in any week (
"credited": "dollars","amount": <cents>). - A refund of a plan swipe charged earlier in the *current* week restores one plan swipe (
"credited": "week_swipe","amount": 1). A plan swipe charged in an earlier, finished week cannot come back as a swipe (that would be a rollover); it is paid back in dining dollars at the swipe value of its meal period ("credited": "dollars"). - Refunding a student's own swipe (plan or dollars) frees its (day, period, hall) slot so the student may swipe there again. Guest swipes never held a slot, so refunding one frees nothing.
- A swipe can be refunded once. Refunded swipes keep their ids; the next new swipe still gets the next number.
Example (after enrolling ana and bo, both w7, with 5000 dollars and 0)
[["transfer_guest", "2026-10-19 10:00", "ana", "bo", 3],
["swipe", "2026-10-19 12:00", "ana", "north"],
["guest_swipe", "2026-10-19 12:05", "ana", "north"],
["refund", "2026-10-19 12:30", "sw-2"],
["swipe", "2026-10-19 12:40", "ana", "north"]]returns [{"from": 1, "to": 7}, {sw-1}, {sw-2, "paid": "guest"}, {"swipe": "sw-2", "credited": "guest_swipe", "amount": 1}, "error: already swiped"]. Refunding sw-1 instead would have freed the slot.
Part 3: Block plans and mid-semester conversion (DINE-471)
Goal: the director is introducing block plans: one pool of swipes for the whole semester instead of a weekly allowance. Existing weekly students may convert mid-semester, and their remaining allowance is prorated. This changes what swipes_left means for these students, so earlier rules need to be revisited.
| Plan | Swipes | Guest swipes per semester |
|---|---|---|
b100 | 100 for the semester | 6 |
b150 | 150 for the semester | 10 |
| Operation | Arguments after ts | Returns | Errors (in this order) |
|---|---|---|---|
convert_plan | student, plan | {"plan", "swipes_left"} | unknown student, invalid plan (not a block plan), already on block plan, outside semester |
Rules
enrollaccepts block plans; enrolling directly gets the full block, whatever the date.- A block plan never refills: no weekly reset, nothing expires.
balancereports the remaining block asswipes_left. Swipes, the once-per-period rule, guest swipes, transfers and the dining-dollar fallback work as before (when the block reaches 0, further swipes are paid with dining dollars). - The semester starts Sunday 2026-08-30 and has 15 weeks (week 0 is the week starting 2026-08-30;
week_indexinclock.pycomputes it).convert_planoutside weeks 0-14 (before 08-30 00:00 or from 2026-12-13 00:00) isoutside semester. - Conversion of a weekly student at
ts: first bring their week up to date. Letusedbe the plan swipes already spent this week (the plan's weekly swipes minus what is left now; dining-dollar and guest swipes do not count). Letweeks_leftbe15 - week_index(the current week counts). The new balance ismax(0, block_total * weeks_left // 15 - used), rounded down, never negative. Guest swipes and dining dollars are unchanged; the old weekly allowance is gone. - Refunds (Part 2) now depend on where the swipe came from. A refunded block swipe always goes back to the block (
"credited": "block_swipe"). A weekly plan swipe still counts as current only if its week is the current week: then it restores one swipe to whichever pool the student has *now* ("week_swipe"for a weekly plan,"block_swipe"after converting); otherwise it pays dining dollars at the period's swipe value, exactly as in Part 2. Dollar and guest refunds are unchanged.
Example
[["enroll", "2026-10-17 09:00", "ana", "w7", 4000],
["swipe", "2026-10-21 12:00", "ana", "north"],
["swipe", "2026-10-21 17:30", "ana", "north"],
["guest_swipe", "2026-10-21 18:00", "ana", "north"],
["convert_plan", "2026-10-21 19:00", "ana", "b150"],
["convert_plan", "2026-10-21 19:01", "ana", "b100"]]returns [true, {sw-1}, {sw-2}, {sw-3, "paid": "guest"}, {"plan": "b150", "swipes_left": 78}, "error: already on block plan"]. Wednesday 2026-10-21 is in week 7, so 8 weeks remain: 150 * 8 // 15 = 80, minus 2 plan swipes already used this week (the guest swipe is not one).
Earlier Parts must keep behaving exactly as specified for weekly plans.
Example 1
- Input:
- [["enroll","2026-10-17 09:00","ana","w7",5000],["swipe","2026-10-17 12:00","ana","north"],["swipe","2026-10-17 12:30","ana","north"],["swipe","2026-10-17 12:40","ana","south"],["guest_swipe","2026-10-17 18:00","ana","west"],["balance","2026-10-17 23:59","ana"],["balance","2026-10-18 00:00","ana"]]
- Output:
- [true,{"id":"sw-1","period":"lunch","paid":"swipe","cents":0},"error: already swiped",{"id":"sw-2","period":"lunch","paid":"swipe","cents":0},{"id":"sw-3","period":"dinner","paid":"guest","cents":0},{"plan":"w7","swipes_left":5,"guests_left":3,"dollars":5000},{"plan":"w7","swipes_left":7,"guests_left":3,"dollars":5000}]
- Explanation
- A second lunch swipe at the same hall is refused, a different hall is fine, guest swipes are separate. Saturday 23:59 is still the old week; Sunday 00:00 starts a new week and refills to 7.
Example 2
- Input:
- [...enroll ana (w7) and bo (w7)..., ["transfer_guest","2026-10-19 10:00","ana","bo",3],["swipe","2026-10-19 12:00","ana","north"],["guest_swipe","2026-10-19 12:05","ana","north"],["refund","2026-10-19 12:30","sw-2"],["swipe","2026-10-19 12:40","ana","north"]]
- Output:
- [..., {"from":1,"to":7}, {"id":"sw-1",...}, {"id":"sw-2",...,"paid":"guest"}, {"swipe":"sw-2","credited":"guest_swipe","amount":1}, "error: already swiped"]
- Explanation
- Refunding the guest swipe returns a guest swipe but does not free Ana's own lunch slot at north.
Example 3
- Input:
- [["enroll","2026-10-17 09:00","ana","w7",4000],["swipe","2026-10-21 12:00","ana","north"],["swipe","2026-10-21 17:30","ana","north"],["guest_swipe","2026-10-21 18:00","ana","north"],["convert_plan","2026-10-21 19:00","ana","b150"]]
- Output:
- [true,{...},{...},{...},{"plan":"b150","swipes_left":78}]
- Explanation
- Wednesday Oct 21 is in semester week 7, so 8 of 15 weeks remain: 150 * 8 // 15 = 80, minus the 2 plan swipes already used this week (the guest swipe does not count) leaves 78.
Guidelines
- Python 3 standard library only
- Timestamps are "YYYY-MM-DD HH:MM" local campus time and never decrease from one operation to the next
- Dining dollars are integer cents; there is no floating-point money
- Every operation returns exactly one value; errors are returned as "error: <code>" strings, never raised