Courier Dispatch: Matching, Batching and SLA Reports
MediumAI-assisted coding · AI-Assisted Feature Work
Courier Dispatch: Matching, Batching and SLA Reports
You've joined the logistics team behind a same-day delivery service. The dispatcher assigns each new order to the nearest idle courier on a city grid and predicts when it will arrive. Ops has reported two wrong-courier bugs and a bad completion bug; after that, two features are queued: batching two nearby orders onto one courier, and a report on how many deliveries broke their time promise.
Codebase
| File | What it does |
|---|---|
dispatch/api.py | run(operations) entry point and the Dispatcher that routes each operation |
dispatch/service.py | DispatchService: intake, dispatching, completion and queries |
dispatch/matcher.py | Picks the courier for an order |
dispatch/eta.py | Predicts delivery times from a route |
dispatch/fleet.py | Fleet: registry of couriers |
dispatch/orders.py | OrderBook: every order, in placement order |
dispatch/geo.py | Point, manhattan distance and coordinate parsing |
dispatch/models.py | Courier and Order dataclasses and order states |
dispatch/reports.py | sla_report (stub for Part 3) |
dispatch/config.py, dispatch/errors.py | Constants and error codes |
tests/ | Existing unit and end-to-end tests (some currently fail) |
How your code is run
run(operations: list[list]) -> list executes the operations in order against a fresh service and returns one result per operation. Every operation is [name, ts, ...args]; ts is an integer number of seconds and never decreases. Errors are returned as strings, never raised.
| Operation | Arguments after ts | Returns | Errors (checked in this order) |
|---|---|---|---|
add_courier | courier_id, x, y | true | invalid arguments, invalid location, courier exists |
place_order | order_id, px, py, dx, dy and optionally sla_seconds | true | invalid arguments, invalid sla, order exists, invalid location |
dispatch | none | list of [order_id, courier_id] pairs, in assignment order | |
complete | order_id | true | order not found, order not active |
status | order_id | "pending", "assigned" or "delivered" | order not found |
eta | order_id | integer time, or null for a pending order | order not found |
set_batching *(Part 2)* | radius | true | invalid radius |
sla_report *(Part 3)* | none | report object |
- Each error is returned as
"error: <code>". An unknown operation name returns"error: unknown operation"; a wrong number of arguments, a non-integerts, or an empty or non-string id returns"error: invalid arguments". - The city is a grid. A location
(x, y)is valid when both are integers (not booleans) from0to49. Every location in the API is given asxtheny:place_ordertakes the pickup(px, py)and then the drop-off(dx, dy). - Distance is Manhattan:
|x1 - x2| + |y1 - y2|blocks. A courier rides one block every 30 seconds. dispatchgoes through pending orders in placement order (the orderplace_orderwas called, notts) and gives each the idle courier with the shortest ride to its pickup; ties go to the smaller courier id (plain string comparison). When no idle courier is left it stops, and the remaining orders stay pending for the nextdispatch.- An assigned order's
etaists_of_dispatch + 30 * (blocks from the courier's position to the pickup + blocks from the pickup to the drop-off). Once an order is delivered,etareturns the time it was completed. - A courier's position is where they signed on until they finish a delivery;
completemoves an idle-again courier to the drop-off of the order just completed. completeonly works on an assigned order. A pending order or one already delivered returnsorder not active, and nothing changes.
Worked example
[["add_courier", 0, "c1", 6, 0],
["add_courier", 0, "c2", 0, 9],
["place_order", 5, "o1", 4, 2, 8, 2],
["dispatch", 10],
["eta", 10, "o1"],
["complete", 300, "o1"],
["status", 301, "o1"],
["eta", 301, "o1"],
["complete", 302, "o1"]]returns
[true, true, true, [["o1", "c1"]], 250, true, "delivered", 300, "error: order not active"]c1 is 4 blocks from the pickup and c2 is 11. The ETA is 10 + 30 * (4 + 4) = 250.
Part 1 — Fix the matching and completion bugs (OPS-3310, OPS-3318, OPS-3331)
Goal: dispatch picks the right courier, predicts the right arrival, and refuses a repeat completion.
Reports:
- "A courier 4 blocks from the restaurant was passed over for one 11 blocks away, and the ETA we showed the customer was far off. It only happens when the pickup isn't on the diagonal."
- "Two couriers equally close to a pickup: the winner depends on who signed on first, not on id. The rule is lowest id."
- "Tapping *complete* twice in the courier app changed the delivery time and moved the courier somewhere they weren't."
Rules: the rules in the section above are the contract; in particular distance is Manhattan on (x, y) for every location, ties go to the smaller id, and complete on anything that is not currently assigned returns order not active.
Part 2 — Batch two nearby orders (OPS-3350)
Goal: when batching is on, one courier may carry two orders whose pickups are close together.
set_batching takes a radius in blocks (a non-negative integer) or null. Batching starts off; with it off nothing in Part 1 changes.
Rules
set_batching radiusreturnstruefor an integerradius >= 0(booleans are not integers here) and turns batching on;nullturns it off. Anything else returnsinvalid radius. The setting only affects laterdispatchcalls; orders already assigned stay as they are.- With batching on,
dispatchstill walks the pending orders in placement order and picks the courier for the first one exactly as before. It then looks for a partner: the earliest-placed remaining pending order (not the nearest) whose pickup is withinradiusblocks of the first order's pickup, inclusive (<= radius). If there is one, the same courier takes both. A courier carries at most 2 orders. Both orders are consumed, so neither is considered again in that call. - The result lists the first order and then its partner, both paired with the courier.
- The courier's route is: ride to the first pickup, then the partner's pickup, then the first order's drop-off, then the partner's drop-off. Each order's
etais the time the courier reaches its own drop-off, 30 seconds per block, starting at the dispatchtsfrom the courier's current position. - A courier is idle again only when all its orders are delivered, and then sits at the drop-off of the order completed last (orders may be completed in either order). A courier that is still carrying an order is never matched.
Example
[["add_courier", 0, "c1", 0, 0],
["set_batching", 1, 1],
["place_order", 2, "o1", 2, 0, 2, 5],
["place_order", 3, "o2", 3, 0, 3, 6],
["dispatch", 10],
["eta", 10, "o1"],
["eta", 10, "o2"],
["complete", 300, "o1"],
["place_order", 301, "o3", 0, 1, 0, 2],
["dispatch", 302],
["complete", 360, "o2"],
["dispatch", 361]]returns [true, true, true, true, [["o1","c1"],["o2","c1"]], 280, 340, true, true, [], true, [["o3","c1"]]]. The pickups are 1 block apart (within radius 1). The route is (0,0)→(2,0)→(3,0)→(2,5)→(3,6): 2, 1, 6 and 2 blocks, so o1 arrives at 10 + 30*9 = 280 and o2 at 10 + 30*11 = 340. c1 is still carrying o2 at ts 302, so o3 waits.
Part 3 — SLA breach report (OPS-3377)
Goal: sla_report summarizes which deliveries broke their promised window.
Every order has an SLA in seconds: the sixth place_order argument, or 1800 when omitted. It must be a positive integer (booleans excluded) or the call returns invalid sla. The order's deadline is placed_at + sla_seconds, where placed_at is the ts of its place_order.
Rules. sla_report at time now returns an object with exactly these keys:
delivered_on_time: delivered orders withdelivered_at <= deadline(exactly on the deadline counts as on time).delivered_late: delivered orders withdelivered_at > deadline.overdue_open: orders that are not delivered (pending or assigned) andnow > deadline.at_risk: ids of assigned orders withnow <= deadlinewhose predictedeta > deadline, in placement order. Pending orders are never at risk, since they have no ETA yet.breaches: one[order_id, seconds_late]entry for each late delivered order (delivered_at - deadline) and each overdue open order (now - deadline), sorted byseconds_latedescending, thenorder_idascending.breach_percent:len(breaches) * 100 // total_orders(integer division, rounded down),0when there are no orders.
The report is read-only and can be requested any number of times.
Example
[["add_courier", 0, "c1", 0, 0],
["place_order", 0, "o1", 1, 0, 1, 1],
["place_order", 100, "o2", 0, 5, 0, 6, 600],
["place_order", 100, "o3", 5, 5, 6, 5, 600],
["dispatch", 200],
["eta", 200, "o1"],
["complete", 1900, "o1"],
["dispatch", 1901],
["sla_report", 1950]]returns
[true, true, true, true, [["o1", "c1"]], 260, true, [["o2", "c1"]],
{"delivered_on_time": 0, "delivered_late": 1, "overdue_open": 2, "at_risk": [],
"breaches": [["o2", 1250], ["o3", 1250], ["o1", 100]], "breach_percent": 100}]o1 was due at 1800 and delivered at 1900 (100 late). o2 and o3 were due at 700; at 1950 both are 1250 seconds overdue.
Notes
fleet.py,models.pyandgeo.pyare known-good.- You may use the AI assistant freely. Later parts must not break earlier ones: with batching off, every Part 1 behavior is unchanged.
- Parts 1-3 are all in scope; there is no stretch part.
Example 1
- Input:
- [["add_courier",0,"c1",6,0],["add_courier",0,"c2",0,9],["place_order",5,"o1",4,2,8,2],["dispatch",10],["eta",10,"o1"],["complete",300,"o1"],["status",301,"o1"],["eta",301,"o1"],["complete",302,"o1"]]
- Output:
- [true,true,true,[["o1","c1"]],250,true,"delivered",300,"error: order not active"]
- Explanation
- c1 is 4 blocks from the pickup, c2 is 11, so c1 takes the order. ETA is 10 + 30 * (4 + 4) = 250. Completing again is refused and the delivery time stays 300.
Example 2
- Input:
- [["add_courier",0,"c1",0,0],["set_batching",1,1],["place_order",2,"o1",2,0,2,5],["place_order",3,"o2",3,0,3,6],["dispatch",10],["eta",10,"o1"],["eta",10,"o2"],["complete",300,"o1"],["place_order",301,"o3",0,1,0,2],["dispatch",302],["complete",360,"o2"],["dispatch",361]]
- Output:
- [true,true,true,true,[["o1","c1"],["o2","c1"]],280,340,true,true,[],true,[["o3","c1"]]]
- Explanation
- The two pickups are 1 block apart, so c1 carries both along (0,0)->(2,0)->(3,0)->(2,5)->(3,6). The courier stays busy until o2 is also delivered, so o3 waits until ts 361.
Example 3
- Input:
- [["add_courier",0,"c1",0,0],["place_order",0,"o1",1,0,1,1],["place_order",100,"o2",0,5,0,6,600],["place_order",100,"o3",5,5,6,5,600],["dispatch",200],["eta",200,"o1"],["complete",1900,"o1"],["dispatch",1901],["sla_report",1950]]
- Output:
- [true,true,true,true,[["o1","c1"]],260,true,[["o2","c1"]],{"delivered_on_time":0,"delivered_late":1,"overdue_open":2,"at_risk":[],"breaches":[["o2",1250],["o3",1250],["o1",100]],"breach_percent":100}]
- Explanation
- o1 (due at 1800) was delivered at 1900, 100 seconds late. o2 and o3 were due at 700 and are 1250 seconds overdue at ts 1950. Three of three orders breached, so 100%.
Guidelines
- Python 3 standard library only
- Locations are integer (x, y) grid cells from 0 to 49; distance is Manhattan and a courier rides one block per 30 seconds
- Timestamps are integer seconds and never decrease from one operation to the next
- Every operation returns exactly one value; errors are returned as "error: <code>" strings, never raised
- With batching off, behavior must be identical to Part 1