Studio Session Billing with Rate Schedules and Exact Rounding
RipplingSoftware Engineer · Technical ScreenLast updated
Build a command-driven billing ledger where technicians' hourly rates change over time, bookings are deduplicated, and fractional cents are kept exactly until an invoice is printed with a configurable rounding mode. Tests rate lookup by time, idempotency rules, and integer-exact money handling.
A recording studio rents out sound technicians by the hour. Each technician has a rate schedule that can change over time, and clients book sessions with a technician. You are building the billing ledger.
Implement:
def run_ledger(commands: list[list[str]], mode: str) -> list[str]:
...
Process commands in order and return one output string per command.
| Command | Effect | Output |
|---|---|---|
["HIRE", tech, rate] | New technician with hourly rate effective from time 0 | "OK" |
["RERATE", tech, rate, at] | Technician's hourly rate becomes rate from second at onward | "OK" |
["BOOK", booking, tech, start, end] | Bill a session from second start to second end | "OK" or "REJECTED" |
["INVOICE"] | Report the running total of all billed sessions | dollars, e.g. "35.13" |
Billing rules
- A session is charged entirely at the rate in force at its start second: the schedule entry with the largest
at <= start. Rate changes in the middle of a session are ignored. - Once a session is billed, its charge is frozen. A later
RERATE, even one with anatin the past, does not change it. RERATEcommands may arrive with anyat, in any order. Two entries with the sameatfor the same technician: the later command wins.- Booking IDs are global. A booking ID that has already been billed is
REJECTED, whatever its payload. - A session longer than 4 hours (14,400 seconds) is
REJECTEDand does not use up its booking ID. Exactly 4 hours is fine. A zero-length session is billed at $0. - Overlapping sessions for the same technician are each billed in full.
- Keep fractional cents exactly. Round only when
INVOICEprints, and printing must not change the stored total.modeis"HALF_UP"or"HALF_EVEN"and decides what happens when the total lands exactly on half a cent.
Rates are strings with exactly two decimals ("0.00" to "9999.99"). Times are integers in [0, 10^9] with start <= end. Every technician referenced exists. Up to 10^5 commands.
Example
mode = "HALF_UP"
["HIRE", "ana", "30.00"] -> OK
["HIRE", "raj", "19.99"] -> OK
["RERATE", "ana", "45.00", "3600"] -> OK
["BOOK", "s1", "ana", "0", "1800"] -> OK # 0.5 h at 30.00 = 15.00
["BOOK", "s2", "ana", "3000", "5400"] -> OK # starts before 3600, so 30.00: 20.00
["BOOK", "s3", "ana", "3600", "3610"] -> OK # 10 s at 45.00 = 0.125
["BOOK", "s1", "raj", "0", "100"] -> REJECTED # s1 already billed
["BOOK", "s4", "raj", "0", "20000"] -> REJECTED # longer than 4 h
["INVOICE"] -> 35.13 # exact 35.125
["BOOK", "s4", "raj", "0", "180"] -> OK # s4 was not used up; 0.9995
["INVOICE"] -> 36.12 # exact 36.1245
With mode = "HALF_EVEN" the first invoice prints 35.12 and the second still prints 36.12.
Hints
Hint 1
Store each technician's schedule so you can find the largest effective time <= a session's start in O(log k).
Hint 2
Never convert money to float. What single integer can you accumulate so that the exact total is that integer divided by a constant?
Hint 3
Compute rounding from divmod(acc, 3600): compare twice the remainder with 3600, and handle equality per mode.
Hint 4
Mark a booking ID as used only after the session passes validation.
Follow-up questions
- How would you bill a session that crosses a rate change pro rata across both rates?
- Commands now come from several API servers concurrently. How do you keep booking IDs idempotent?
- How would you support refunding (voiding) a booking and still produce exact totals?
- What changes if the per-technician schedule can hold millions of entries and rerates are frequent?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.