Farmers Market Vendor Desk: Stalls, Fees and Season Passes
EasyAI-assisted coding · AI-Assisted Debugging
Farmers Market Vendor Desk: Stalls, Fees and Season Passes
You've joined the volunteer team that runs the vendor desk of the Riverside Saturday Farmers Market. The desk's scheduling tool decides every week which vendor gets which of the ten stalls, what each vendor owes, and what happens when the market is rained out. Three tickets are open: the stall assignment has been producing complaints, fee and rain-out handling doesn't exist yet, and the board has just approved season passes. Dates are YYYY-MM-DD strings and money is integer cents.
Codebase
| File | What it does |
|---|---|
market/api.py | run(operations) entry point and the Dispatcher that routes each operation |
market/desk.py | MarketDesk: owns the roster, the request board and every assigned week |
market/assigner.py | assign_market: ranks requesters, applies caps, hands out stalls |
market/roster.py | Roster: vendors, their category, years at the market and sign-up order |
market/board.py | RequestBoard: the requests waiting for a Saturday to be assigned |
market/config.py | Stall ids, fees, category caps and the other constants |
market/validation.py | Argument and date checks shared by every operation |
market/models.py, market/errors.py | Records and every error code |
market/billing.py, market/seasons.py | Empty placeholders for Parts 2 and 3 |
tests/ | Unit and end-to-end tests (one of them currently fails) |
How your code is run
run(operations: list[list]) -> list executes the operations in order against a fresh desk and returns one result per operation. Every operation is [name, *args]. Errors are returned as "error: <code>" strings, never raised. Checks run in the order listed in each table; the first failing check wins.
| Operation | Arguments | Returns | Errors (in order) |
|---|---|---|---|
join | vendor_id, category, years | true | unknown category, invalid years, vendor exists |
request | date, vendor_id, preference | true | invalid date, not a market day, unknown vendor, invalid preference, market closed, already requested |
assign | date | {"stalls": [[stall, vendor], ...], "waitlist": [vendor, ...]} | invalid date, not a market day, market closed |
- Unknown operation names return
"error: unknown operation". A wrong number of arguments, or avendor_id/stall id that is not a non-empty string, returns"error: invalid arguments". - Categories are
produce,bakery,flowers,crafts.yearsis an integer>= 0(not a bool). Preferences areanyandcorner. Vendor ids are case-sensitive. - A valid date is a real calendar date written exactly
YYYY-MM-DDand a Saturday (an impossible date isinvalid date; a real non-Saturday isnot a market day). assignruns once per Saturday and is final. Weeks must be assigned in increasing date order:assignandrequestfor a date that is not later than the latest assigned date (cancelled or not) fail withmarket closed. A week with no requests assigns fine and returns empty lists.- In the
assignresult,stallslists[stall, vendor]pairs in stall order (C1, C2, S1 ... S8). There are ten stalls: two corner stallsC1,C2and eight standard stallsS1toS8.
Worked example (the assignment rules are Part 1)
[["join", "v9", "produce", 6], ["join", "v10", "flowers", 6],
["join", "v12", "bakery", 5], ["join", "v13", "bakery", 4], ["join", "v14", "bakery", 3],
["join", "v11", "bakery", 2], ["join", "v15", "produce", 1],
["request", "2024-06-08", "v9", "any"], ["request", "2024-06-08", "v10", "any"],
["request", "2024-06-08", "v12", "any"], ["request", "2024-06-08", "v13", "any"],
["request", "2024-06-08", "v14", "any"], ["request", "2024-06-08", "v11", "corner"],
["request", "2024-06-08", "v15", "corner"],
["assign", "2024-06-08"]]returns fourteen trues (one per join and request) followed by
{"stalls": [["C1", "v15"], ["S1", "v9"], ["S2", "v10"], ["S3", "v12"], ["S4", "v13"], ["S5", "v14"]],
"waitlist": ["v11"]}(v11 is the fourth bakery vendor, so it is waitlisted and its corner request counts for nothing; v9 and v10 have equal years and v9 signed up first.)
Part 1: Fix the stall assignment (SUP-402, SUP-407, SUP-411)
Goal: make assign follow the seniority, cap and corner rules below. The rules live in market/assigner.py; three separate defects there are behind the complaints:
- "Two vendors with the same years keep swapping places depending on their ids."
- "We had four bakeries on the green last Saturday."
- "A bakery that was waitlisted for the cap still walked away with a corner, and a corner stall stayed empty while a corner requester was put in a standard stall."
Rules
- Seniority. Requesters are ranked by
yearsdescending, ties broken by sign-up order (the order in whichjoincalls succeeded, earliest first). It has nothing to do with the vendor id or the order the requests came in. - Caps. At most 3
bakeryvendors are placed in a week (CATEGORY_CAPSinconfig.py; other categories are uncapped). Walking the ranking, a bakery vendor beyond the third is waitlisted. Caps are applied before any stall is handed out: a vendor waitlisted by a cap holds no stall, whatever they asked for. - Corners. Among the vendors who passed the caps, those who asked for
cornerreceiveC1, thenC2, in seniority order. A corner requester who finds no corner left is treated like ananyrequester. - Everyone else, including those corner requesters, in seniority order, takes the lowest free standard stall (
S1,S2, ...). If all standard stalls are gone, the next vendors take any unclaimed corner stall (C1beforeC2). When all ten stalls are taken, the remaining vendors are waitlisted. - The waitlist holds every requester without a stall (cap or no room), in seniority order.
tests/test_assign.py::test_bakery_is_capped_at_three_a_week currently fails.
Part 2: Fees, rain-outs and credits (SUP-418)
Goal: bill vendors for their stalls, and when a market is rained out give each vendor a credit that is applied to their next paid week. Credits are only ever discounts; they never turn into cash.
| Operation | Arguments | Returns | Errors (in order) |
|---|---|---|---|
rain_out | date | total cents of credit issued (int) | invalid date, not a market day, market not assigned, not the latest market, already cancelled |
bill | date, vendor_id | {"fee", "credit_used", "due", "credit_left"} | invalid date, not a market day, unknown vendor, market not assigned, market cancelled, no stall, already billed |
credit | vendor_id | the vendor's credit balance in cents | unknown vendor |
Rules
- A vendor's fee for a week depends on the stall they were assigned (not what they asked for):
STALL_FEE_CENTSinconfig.py: corner4000, standard2500. rain_out(date)cancels an already-assigned week. Only the latest assigned week can be rained out (see the error order). Every vendor who held a stall that week receives a credit equal to the fee of that stall. It returns the total credit issued, which is0for a week with no vendors.bill(date, vendor)returns the stallfee, thecredit_used, thedue(fee - credit_used) andcredit_left, the vendor's whole credit balance after this bill. A vendor with no stall that week (including a waitlisted one) isno stall. A vendor can be billed once per week.- Applying credit. A credit issued by the rain-out of date
Dcan only pay a bill whose market date is later thanD. A bill uses usable credit oldest origin date first, up to the fee, sodueis never negative; any credit it does not use stays on the balance for later bills. Bills may arrive in any order of dates, so what is usable depends on the bill's date, not on when the call was made. credit(vendor)returns the current balance. A rained-out week is closed: it cannot be billed and nobody canrequestit.
Example (ana joined earlier; weeks are Saturdays)
[["request", "2024-06-08", "ana", "corner"], ["assign", "2024-06-08"], ["rain_out", "2024-06-08"],
["request", "2024-06-15", "ana", "any"], ["assign", "2024-06-15"],
["bill", "2024-06-15", "ana"], ["credit", "ana"]]returns [true, {"stalls": [["C1", "ana"]], "waitlist": []}, 4000, true, {"stalls": [["S1", "ana"]], "waitlist": []}, {"fee": 2500, "credit_used": 2500, "due": 0, "credit_left": 1500}, 1500].
Part 3: Season passes change the order of play (SUP-433)
Goal: the board approved season passes. A vendor who pre-pays for the whole season gets priority over seniority for their usual stall, until they miss two weeks in a row. This replaces the pure seniority order from Part 1 and the fee rule from Part 2 for those vendors; everything else stays.
| Operation | Arguments | Returns | Errors (in order) |
|---|---|---|---|
season_pass | vendor_id, stall_id | true | unknown vendor, unknown stall, pass exists, stall reserved |
stall reserved means another vendor already holds a pass for that stall. A vendor has at most one pass.
Rules
- A pass is for one usual stall, bought at the time of the call. The vendor pays nothing for a week in which they are assigned that stall:
feeis0, sodueis0, and a rain-out issues them no credit. If they are assigned any other stall (see rule 4), they pay that stall's normal fee. - Priority. When a week is assigned, every requester who holds a pass with priority is placed first: they get their usual stall regardless of seniority or of what they asked for, and nobody else can have that stall. Category caps still apply, but pass holders are walked first (in seniority order among themselves), so they take their category's cap slots before the other requesters. A pass holder who is waitlisted by a cap gets no stall and their usual stall goes back into the pool. A pass holder who did not request the week gets nothing special: the stall is free for others. Everyone else is assigned exactly as in Part 1, using the stalls that are left.
- Losing priority. Only *held* weeks count: assigned weeks, not rained out, that come after the pass was bought (a week assigned before the purchase does not count). A pass holder misses a held week in which they made no request (a waitlisted vendor did request, so that is not a miss). A rained-out week is skipped entirely: it is neither a miss nor does it interrupt a run of misses. After two consecutive misses (in held-week order) the holder loses priority permanently: from then on they are an ordinary vendor (their preference counts, they can be waitlisted), though the pass still makes their usual stall free of charge when they happen to be assigned it.
- The fee a vendor pays for a week, and any rain-out credit, use the passes as they stand when
billorrain_outis called. requestandassignkeep their errors;season_passdoes not check the date or whether the vendor has requested anything.
Example (bo joined with 9 years, ana with 3; ana buys a pass for C1)
[["season_pass", "ana", "C1"],
["request", "2024-06-08", "bo", "corner"], ["request", "2024-06-08", "ana", "any"],
["assign", "2024-06-08"], ["bill", "2024-06-08", "ana"], ["bill", "2024-06-08", "bo"]]returns [true, true, true, {"stalls": [["C1", "ana"], ["C2", "bo"]], "waitlist": []}, {"fee": 0, "credit_used": 0, "due": 0, "credit_left": 0}, {"fee": 4000, "credit_used": 0, "due": 4000, "credit_left": 0}].
Notes
roster.py,board.py,validation.pyandconfig.pyare known-good.- You may use the AI assistant freely; you are expected to verify what it produces.
- Later parts must not break earlier ones; hidden tests re-check earlier behaviour after Parts 2 and 3.
Example 1
- Input:
- [["join","v9","produce",6],["join","v10","flowers",6],["join","v12","bakery",5],["join","v13","bakery",4],["join","v14","bakery",3],["join","v11","bakery",2],["join","v15","produce",1],["request","2024-06-08","v9","any"],["request","2024-06-08","v10","any"],["request","2024-06-08","v12","any"],["request","2024-06-08","v13","any"],["request","2024-06-08","v14","any"],["request","2024-06-08","v11","corner"],["request","2024-06-08","v15","corner"],["assign","2024-06-08"]]
- Output:
- [true x14, {"stalls":[["C1","v15"],["S1","v9"],["S2","v10"],["S3","v12"],["S4","v13"],["S5","v14"]],"waitlist":["v11"]}]
- Explanation
- v11 is the fourth bakery vendor so it is waitlisted and its corner request counts for nothing; v15 gets the corner. v9 and v10 have equal years and v9 signed up first.
Example 2
- Input:
- [["join","ana","produce",3],["request","2024-06-08","ana","corner"],["assign","2024-06-08"],["rain_out","2024-06-08"],["request","2024-06-15","ana","any"],["assign","2024-06-15"],["bill","2024-06-15","ana"],["credit","ana"]]
- Output:
- [true,true,{"stalls":[["C1","ana"]],"waitlist":[]},4000,true,{"stalls":[["S1","ana"]],"waitlist":[]},{"fee":2500,"credit_used":2500,"due":0,"credit_left":1500},1500]
- Explanation
- The rained-out corner week yields a 4000 credit. The next paid week costs 2500 so 1500 of credit remains.
Example 3
- Input:
- [["join","bo","flowers",9],["join","ana","produce",3],["season_pass","ana","C1"],["request","2024-06-08","bo","corner"],["request","2024-06-08","ana","any"],["assign","2024-06-08"],["bill","2024-06-08","ana"]]
- Output:
- [true,true,true,true,true,{"stalls":[["C1","ana"],["C2","bo"]],"waitlist":[]},{"fee":0,"credit_used":0,"due":0,"credit_left":0}]
- Explanation
- ana's pass puts her in C1 ahead of the more senior bo, and she owes nothing for her pre-paid stall.
Guidelines
- Python 3 standard library only
- Money is integer cents; dates are YYYY-MM-DD strings and are compared as strings
- Every operation returns exactly one value; errors are returned as "error: <code>" strings, never raised
- Weeks are assigned in increasing date order, and an assigned week is final