Paying a kitchen twice for the same week
A payout screen works out which days nobody has been paid for yet. Show it only the most recent runs and the ones it cannot see stop counting as payment — so a year that was settled comes back as a gap, priced, on the bill.
The last two posts were about numbers that came out too small. This one comes out too big, from exactly the same cause, and it is the more expensive direction.
A hall pays its kitchens on a schedule. The payout screen has to work out which days have not been settled yet, and the way it does that is sensible: walk through the payout runs, note the period each one covers, and whatever is left over is what is owed. Give it every run and it is right. Give it the most recent two hundred and the older runs stop counting as cover — so periods that were paid for, invoiced and closed come back as gaps.
Then the block underneath does its job and prices them.
What it looked like
Measured on 250 consecutive weekly runs:
- All the runs: nothing owing.
- The newest 200: one gap, 2024-01-01 to 2024-12-15. Three hundred and fifty days.
A year that had been paid for, presented as a debt. On a payout screen that means paying a kitchen twice, and the kitchen has no reason at all to query it.
Two hundred runs is not far away
It is tempting to file this as theoretical. It is not. Payout schedules run off close-of-day times, so a kitchen paid daily reaches 200 runs in about seven months. A hall that opened last spring and pays its best tenants nightly is already there. Weekly payouts take four years, which is a different kind of problem: the bug arrives long after everyone has stopped wondering whether the screen is right.
The shape, one more time
Three posts, three directions — hiding money owed, understating a rate, inventing a debt — and one cause. Somewhere a query says "give me the most recent N of these" and the answer is used for a calculation rather than for a list. The limit was almost always added for an honest reason: a page that was slow to draw. Then, later, a total was computed off the same fetch because the rows were conveniently already there.
What makes it hard to spot in review is that both halves are correct on their own. Capping a list is right. The sum is right. The join between them is where the fault lives, and nothing at either end looks wrong.
What we changed
The cover is now read by its own small query scoped to the window being asked about — cheap, complete, and it cannot be short. The list of runs a person scrolls keeps its cap, and the payload now says how many were shown against how many there are, so the screen can print it instead of implying there is nothing more.
How money moves between a hall and its kitchens is set out in paying vendors in a food hall, and what we charge for any of this is on the pricing page.