# The Dispute Clock — every dispute has a clock

Part of **The Engine Room** on simranjaiswal.in: automation engines that stop money going missing
between systems. This one takes a disputed invoice — the one state where chasing makes things worse —
and gives it a cause, an owner team and a deadline in working days, escalates it when the deadline
passes, and measures the cash each answer releases. It is raced against the same disputes with no clock.

Live case, with the engine running in the browser: https://simranjaiswal.in/work/dispute-clock

## What it does

1. **Logged.** A dispute arrives with an invoice amount and a cause code: `PRICE`, `QUANTITY`, `QUALITY`,
   `PO_MISMATCH`, `TAX` or `OTHER`.
2. **Triage.** The desk opens up to N disputes per working day (default 6), oldest first; each takes 1–2
   working days to confirm the cause and hand over. The queue in front of the desk is the first place
   disputes hide, so its capacity is a parameter.
3. **Route.** `PRICE → PRICING (SLA 3 wd)`, `QUANTITY → OPS (5)`, `QUALITY → QA (7)`, `PO_MISMATCH → SALES (4)`,
   `TAX → FINANCE (3)`, `OTHER → TRIAGE (5)`. One team, one printed deadline. The SLA multiplier scales all six.
4. **The clock.** The owner's time to answer is drawn around its SLA: `exp(normal(log SLA, 0.45))` working
   days, times the multiplier. A shorter clock is a faster owner — the deadline shapes the behaviour.
5. **Escalate.** Still open at the deadline → the owner's manager is paged and the resolution hazard doubles,
   modelled as halving the remaining drawn duration (rounded up). Counted per lane.
6. **Resolve.** `CREDIT` (a partial credit, 5–100% of the invoice), `RE_INVOICE` or `UPHELD`, with printed
   probabilities per cause. Cash unlocks on the day of the answer: invoice − credit.
7. **No clock.** The same 300 disputes and the same random draws: desk capacity 2 a day, no deadline, no
   escalation, every owner duration × 2.2. Its "within SLA" is measured against the clock it never had.
8. **Tie-out.** 300 = resolved by day 120 + with an owner + still in triage, for each policy, and the cash
   unlocked equals Σ (amount − credit) over the resolved. If either breaks, nothing is published.

The big number is cash unlocked by day 120 with the clock minus without.

## The synthetic disputes

There is no client data here. `build_disputes()` draws 300 disputes raised over 120 days (amounts
lognormal, clipped ₹5,000–₹4,00,000; cause shares 30 / 20 / 15 / 20 / 10 / 5%) and makes every random
draw at generation time, in a printed order, so the two policies replay exactly the same disputes and
durations and differ only in the desk's capacity, the multiplier, the deadline and escalation. Day 0 is
a Monday; days 5 and 6 of every week are the weekend, and both the desk and the owners work only on
working days. Randomness is mulberry32, a 32-bit generator the browser port implements bit-for-bit, so
the page's reference run reproduces `results.json` exactly (the page's status line says so when it does).

## Run it

```
python3 engine.py                                   # reference: seed 42, desk 6/day, SLA ×1.0, escalation on
python3 engine.py --triage 12                       # double the desk: almost nothing moves
python3 engine.py --sla-mult 0.5                    # a tighter clock: fewer escalations, cash sooner
python3 engine.py --triage 2 --sla-mult 2.2 --no-escalation   # the clock dialled down to no clock: the gap is zero
```

Stdlib only. `results.json` carries the book, both policies (median / p90 / mean days, % within SLA,
escalations, the day-120 snapshot, outcomes, open-by-age, the days histogram, by-cause and by-lane tables,
and the 150-day cumulative cash series), the comparison, and sample journeys through both worlds.

`parity.mjs` extracts the in-browser port from the case page, runs it on the reference parameters and
deep-compares every value against `results.json`: `node parity.mjs` prints `MATCH`.

## Reference run (seed 42)

See `results.json`. Headline: median and p90 days to resolve under each policy, the share answered within
SLA, escalations, where the 300 disputes are on day 120, and the cash unlocked by day 120 with the clock
minus without.

## Where the shape comes from

The dispute handling behind the collections work on the site — the procurement startup's ledger
(https://simranjaiswal.in/work/stuck-money) and the logistics receivables
(https://simranjaiswal.in/work/shine-receivables), where disputes were where invoices went to wait.
Outcome figures in those cases are as reported there; nothing in this repository is a client's data.

MIT.
