Returns Desk: Warranty Rules, Replacements and Advance Exchanges
MediumAI-assisted coding · AI-Assisted Debugging
Returns Desk: Warranty Rules, Replacements and Advance Exchanges
You've joined the support-platform team at an electronics brand. The returns desk service decides what happens when a customer sends back a unit: a refund, a warranty replacement, or an escalation. Customer support has a queue of tickets about wrong refund amounts and wrong warranty dates, and two policy changes are queued behind them. All money is integer cents; all dates are YYYY-MM-DD strings.
Codebase
| File | What it does |
|---|---|
rmadesk/api.py | run(operations) entry point and the Dispatcher that routes each operation |
rmadesk/service.py | ReturnsService: the rma decision, warranty_check, the RMA log |
rmadesk/policy.py | Refund window, warranty end date and restocking fee |
rmadesk/registry.py | UnitRegistry: register, bulk-register and sell units |
rmadesk/inventory.py | Inventory: replacement stock per SKU (new and refurb) |
rmadesk/serials.py | Serial parsing and the manufacture-date decoding |
rmadesk/dates.py | Date parsing, formatting and month arithmetic |
rmadesk/models.py | Unit and Rma dataclasses, unit status constants |
rmadesk/errors.py | RmaError and every error code |
rmadesk/config.py | Policy constants (30-day window, 12/15 months, 15% fee, 90 days) |
rmadesk/holds.py | Placeholder for Part 3 |
tests/ | Existing tests (some currently fail) |
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, date, ...args]. Dates never go backwards from one operation to the next. Errors are returned as "error: <code>" strings, never raised.
- Check order for every operation: not a list or shorter than 2 items gives
invalid arguments; an unparseabledategivesinvalid date; an unknown name givesunknown operation; a wrong number of arguments givesinvalid arguments; then the operation's own errors in the order below. - "N months after D" means the same day-of-month N months later, clamped to the last day of a shorter month (2024-01-31 plus 1 month is 2024-02-29; 2024-02-29 plus 12 months is 2025-02-28).
- A serial looks like
HP200-2314-00017: SKU, thenYYWW, then a 5-digit unit number.YYis the year (2000 + YY) andWWthe manufacture week,01to52. Week 1 starts on January 1st, so weekWWstarts on January 1st plus7 * (WW - 1)days. That start day is the unit's manufacture date.
| Operation | Arguments after date | Returns | Errors (in this order) |
|---|---|---|---|
register | serial, price | true | invalid serial, invalid price, unit exists |
register_batch | sku, yyww, first, count, price, sold_on | count | invalid sku, invalid price, invalid batch, invalid serial, invalid date, sale before manufacture, unit exists |
sell | serial | true | unit not found, already sold, sale before manufacture |
warranty_check | serial | {"covered", "until"} | unit not found |
rma | serial, reason, opened | see below | invalid arguments, invalid reason, unit not found, unit closed, out of warranty, not a defect |
restock | sku, kind, count | {"new": n, "refurb": m} | invalid sku, invalid kind, invalid count |
stock | sku | {"new": n, "refurb": m} | invalid sku |
priceis a positive integer of cents.reasonis"defect"or"changed_mind";openedis a JSON boolean (anything else isinvalid arguments);kindis"new"or"refurb".registerrecords a unit with no sale.sellrecords the sale on the operation'sdate.register_batchregisters unitsfirst .. first+count-1of oneSKU-YYWWrun (count1 to 50000, numbers within 1 to 99999) with one price, and all of them as sold onsold_on(a date, ornullfor unsold).sold_onlater than the operation's date isinvalid date. It is all or nothing.- Failed operations change nothing.
Part 1: Fix the refund and warranty rules (SUP-3310 to SUP-3316)
Goal: make rma refunds and warranty_check follow the policy below. Everything else in the codebase is known-good except where these tickets point.
Tickets from support:
- "Refund quote for a 33.50 unit says a 5.02 fee. Policy says 15%, which is 5.025, and we always round half up." (Fees are charged by whole cents.)
- "A customer returned a defective unit and was still charged a restocking fee."
- "Return on day 30 after purchase was refused; the policy says 30 days."
- "Warranty for a unit sold on January 31st ends on the 28th."
- "Units built in week 14 show a manufacture date a week late, so the unsold-unit warranty is wrong."
Rules
- Warranty. A unit with a recorded sale is covered through
sale date + 12 months, inclusive (a request on that day is still covered). A unit with no recorded sale is covered throughmanufacture date + 15 months, inclusive.warranty_checkreturns{"covered": bool, "until": date}for the operation's date, for any unit, even a closed one. - Refund window. A request on a unit with a recorded sale, made
0..30days after the sale (day 30 included), is a refund, whatever the reason. Otherwise the warranty path applies. - Restocking fee.
opened = trueandreason = "changed_mind": 15% of the price, rounded half up to a whole cent. Anything else: no fee. Refund amount = price - fee. rmaflow. A refund returns{"rma": "RMA-00001", "outcome": "refund", "refund": cents, "fee": cents}and closes the unit (a laterrmaon it isunit closed). RMA ids count up fromRMA-00001and an id is used only by a request that succeeds. On the warranty path: outside warranty isout of warranty; covered butchanged_mindisnot a defect; a covered defect is handled in Part 2 (until then the starter answerserror: not supported).
Example
[["register", "2024-01-02", "HP200-2314-00001", 3350],
["sell", "2024-05-01", "HP200-2314-00001"],
["warranty_check", "2024-05-02", "HP200-2314-00001"],
["rma", "2024-05-31", "HP200-2314-00001", "changed_mind", true]]returns
[true, true, {"covered": true, "until": "2025-05-01"},
{"rma": "RMA-00001", "outcome": "refund", "refund": 2847, "fee": 503}](Day 30 after the sale, 15% of 3350 is 502.5, which rounds up to 503.)
Part 2: Warranty replacements and stock (SUP-3342)
Goal: a covered defect outside the refund window gets a replacement shipped from stock, and the replacement carries a warranty that is never worse than the original's.
Rules
- A request on the warranty path with
reason = "defect"and the unit in warranty ships a replacement. The unit comes fromrefurbstock if any, otherwise fromnewstock, and that stock level drops by one. - The replacement is a new unit with serial
R-<SKU>-<NNNNN>(R-HP200-00001, then00002, ...; one counter per SKU, used only when a replacement actually ships), the same SKU and price, no sale date, andactivestatus. Replacement units are therefore never refundable: any request on one takes the warranty path, andchanged_mindisnot a defect. - Warranty inheritance. The replacement is covered through the later of (a) the original's current warranty end (the
untilthatwarranty_checkreports for the original, which for an original that is itself a replacement is the end it inherited) and (b) the request date plus 90 days.warranty_checkon a replacement reports that date. - The original unit is closed immediately.
- No stock of either kind: the request is backordered: it still opens an RMA (using an id) and returns
{"rma", "outcome": "backorder"}. The unit stays active, no serial is consumed, and nothing is fulfilled later automatically. - A shipped replacement returns
{"rma", "outcome": "replace", "source": "refurb" | "new", "serial": ..., "covers_until": date}.
Example
[["register", "2024-01-02", "HP200-2314-00001", 20000],
["sell", "2024-05-01", "HP200-2314-00001"],
["restock", "2025-04-01", "HP200", "refurb", 1],
["restock", "2025-04-01", "HP200", "new", 1],
["rma", "2025-04-20", "HP200-2314-00001", "defect", true],
["rma", "2025-07-01", "R-HP200-00001", "defect", false]]returns
[true, true, {"new": 0, "refurb": 1}, {"new": 1, "refurb": 1},
{"rma": "RMA-00001", "outcome": "replace", "source": "refurb", "serial": "R-HP200-00001", "covers_until": "2025-07-19"},
{"rma": "RMA-00002", "outcome": "replace", "source": "new", "serial": "R-HP200-00002", "covers_until": "2025-09-29"}]The original's warranty ended 2025-05-01, so the first replacement gets 2025-04-20 + 90 days. The second inherits from that replacement (2025-07-19), and 2025-07-01 + 90 days is later.
Part 3: Advance exchanges and a fraud rule (SUP-3377, RISK-204)
Goal: two policy changes. Customers can ask for an advance exchange, where the replacement ships *before* the defective unit comes back, secured by a card hold. And repeat claims on one serial go to manual review. Part 2's rule 4 ("the original is closed immediately") now applies only to standard requests; a lot of your Part 2 code probably assumes it holds for every replacement.
rma accepts an optional 4th argument mode: "standard" (default, the behaviour so far) or "advance". The rma errors are now, in order: invalid arguments, invalid reason, invalid mode, unit not found, unit closed, return pending, advance not allowed, out of warranty, not a defect.
Rules
- Advance exchange.
advanceis allowed only on the warranty path. A request that would be a refund (Part 1 rule 2) fails withadvance not allowed. A shipped advance replacement is built exactly as in Part 2 (stock, serial, inherited warranty) and its result also has"hold": price. The original is not closed: it becomespending return, and any request on it fails withreturn pending. If there is no stock, an advance request is backordered like any other and creates no hold. - Hold. A hold of
pricecents is opened on the original, with deadline = request date + 14 days. receive_defective(serial): if the serial has an open hold, the hold is released, the original is closed, and it returns{"released": cents}. Otherwiseerror: no open hold. Receiving on the deadline day itself is on time.- Charging. Before executing any operation whose date is valid (even one that then fails, or an unknown operation), every open hold with a deadline before that operation's date is charged: the original is closed and the hold's amount is added to the charged total. A charged hold can no longer be released.
exposure(no arguments) returns[open_holds, held_cents, charged_cents_total].- Fraud review. On the warranty path or the refund path, once the request has passed every error check above, count the RMAs previously opened on the same serial whose date is later than the request date minus 12 months (using the month rule above; a prior RMA exactly 12 months earlier does not count). Every kind of RMA counts, including backorders and earlier reviews. If that count is 2 or more, the request returns
{"rma", "outcome": "manual_review"}: it opens an RMA (using an id) and changes nothing else (no stock, no hold, no status change). recall(sku, yyww, first, last): for each serial of that run fromfirsttolast(inclusive, ascending), filermawith reasondefect,opened = true, modeadvance, on the operation's date. Serials that are unknown or whose request fails with an error areskipped(no RMA opened). Returns{"filed": replacements shipped, "backordered": n, "review": n, "skipped": n}. Errors:invalid sku,invalid batch(non-integer,first < 1,last < first,last > 99999, or more than 50000 serials),invalid serial(badyyww). Hidden tests recall tens of thousands of units and then queryexposurehundreds of times; the whole suite has a few seconds.
Examples
[["register", "2024-01-02", "HP200-2314-00001", 20000],
["sell", "2024-05-01", "HP200-2314-00001"],
["restock", "2024-08-01", "HP200", "new", 2],
["rma", "2024-09-10", "HP200-2314-00001", "defect", true, "advance"],
["rma", "2024-09-11", "HP200-2314-00001", "defect", true],
["exposure", "2024-09-12"],
["receive_defective", "2024-09-24", "HP200-2314-00001"],
["exposure", "2024-09-24"]]returns
[true, true, {"new": 2, "refurb": 0},
{"rma": "RMA-00001", "outcome": "replace", "source": "new", "serial": "R-HP200-00001", "covers_until": "2025-05-01", "hold": 20000},
"error: return pending", [1, 20000, 0], {"released": 20000}, [0, 0, 0]](The same request with receive_defective on 2024-09-25 fails with no open hold: the operation's date is after the deadline, so the sweep charges the hold first, and exposure would read [0, 0, 20000].)
Fraud review: a unit sold 2024-01-02 with no stock gets backorders on 2024-03-01 and 2024-06-01 (RMA-00001, RMA-00002). A defect request on 2024-09-01 returns {"rma": "RMA-00003", "outcome": "manual_review"}.
Notes
dates.py,registry.py,inventory.pyand the error table are the places to learn the conventions.- 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.
Example 1
- Input:
- [["register","2024-01-02","HP200-2314-00001",3350],["sell","2024-05-01","HP200-2314-00001"],["warranty_check","2024-05-02","HP200-2314-00001"],["rma","2024-05-31","HP200-2314-00001","changed_mind",true]]
- Output:
- [true,true,{"covered":true,"until":"2025-05-01"},{"rma":"RMA-00001","outcome":"refund","refund":2847,"fee":503}]
- Explanation
- Day 30 after the sale is still inside the refund window. The opened item pays a 15% fee: 502.5 cents rounds half up to 503.
Example 2
- Input:
- [...register, sell, restock refurb 1, restock new 1..., ["rma","2025-04-20","HP200-2314-00001","defect",true],["rma","2025-07-01","R-HP200-00001","defect",false]]
- Output:
- [..., {"rma":"RMA-00001","outcome":"replace","source":"refurb","serial":"R-HP200-00001","covers_until":"2025-07-19"}, {"rma":"RMA-00002","outcome":"replace","source":"new","serial":"R-HP200-00002","covers_until":"2025-09-29"}]
- Explanation
- The original's warranty ends 2025-05-01, so the first replacement gets 90 days from the request. The second inherits from the first and 2025-07-01 + 90 days is later.
Example 3
- Input:
- [...register, sell, restock new 2..., ["rma","2024-09-10","HP200-2314-00001","defect",true,"advance"],["rma","2024-09-11","HP200-2314-00001","defect",true],["exposure","2024-09-12"],["receive_defective","2024-09-24","HP200-2314-00001"],["exposure","2024-09-24"]]
- Output:
- [..., {"rma":"RMA-00001","outcome":"replace",...,"hold":20000}, "error: return pending", [1,20000,0], {"released":20000}, [0,0,0]]
- Explanation
- The advance replacement ships with a 20000-cent hold due back by 2024-09-24 (deadline inclusive). The original is pending return, so a second request is refused; receiving it on time releases the hold.
Guidelines
- Python 3 standard library only
- Money is integer cents; dates are YYYY-MM-DD strings and never go backwards between operations
- Every operation returns exactly one value; errors are returned as "error: <code>" strings, never raised
- Part 3 inputs recall tens of thousands of units and then read exposure hundreds of times in one run