Batch 42 · Synthetic · n=100 · 2026-09-02
Batch 42 · 100 at-risk subscriptions · ₹2,77,823 at risk
A failed debit gives you three attempts.
Then it stops charging itself.
Most dunning systems spend those three on a fixed schedule and hope. Vasooli diagnoses each failure first, refuses the ones no retry can fix, and spends what is left where it can actually land, inside the RBI e-mandate envelope, with every decision written to an audit trail.
On this batch it recovers the same money as the fixed schedule while spending half the retry budget — and leaves 5 more subscriptions still charging automatically, worth ₹13,195 a month of recurring revenue the baseline pushes onto a manual, customer-initiated path.
The attempt ledger
Every one of the 300 retries this batch was allowed to spend.
105 of them were gone before the batch arrived, on records that had already been retried. 195 were left to allocate, and how each arm allocated them is the whole argument.
Baseline
generic dunning cadence, T+1 / T+3 / T+5, failure class not consultedBaseline: of the 300 attempts this batch of 100 subscriptions could ever hold, 105 were already spent before it arrived, leaving 195 available. This arm spent 160 of them (82%), 29 of which recovered the money, and left 35 unspent.
Vasooli
diagnose, then spend only where a retry can landVasooli: of the 300 attempts this batch of 100 subscriptions could ever hold, 105 were already spent before it arrived, leaving 195 available. This arm spent 77 of them (39%), 25 of which recovered the money, and left 118 unspent.
Hover any cell to see which subscription it belongs to and what the engine decided.
Recurring revenue kept alive
₹13,195/mo
5 fewer subscriptions halted
Recoverable subscriptions each arm drove to halted: baseline 27, here 22. A halted subscription stops auto-charging and returns to active only if the customer updates the payment method themselves, so this recurs — against a one-cycle recovery difference of ₹1,304.
Attempts preserved
86
83 fewer spent than baseline
Retries the engine declined to spend on debits that could not have succeeded. Worth ₹765 each here against the baseline's ₹360 — 112% more per attempt — but the point is the budget they leave intact.
Sent to a person
6
not auto-actioned
Above the RBI cap, above the mandate's own limit, or unclassifiable. Escalation is the product working, not a failure.
Three layers, one answer
The baseline tried to present 3 debits the network was never going to settle.
They cost it no retries — the breaker refused them at the action boundary, before presentation, so all three spent zero attempts. That is the layering working, and it is worth being precise about: the waste prevented here is the attempt that would have been spent, not one that was. Above ₹15,000 the RBI e-mandate framework requires additional factor authentication, so an unattended presentation is declined. Three independent layers refuse it here: the stopping rule declines to schedule it, the money-side breaker refuses it at the action boundary, and the simulated world declines it on presentation. Both arms therefore recover ₹0.00 above the cap — which is why the raw and compliance-adjusted numbers on this page are identical, and why there is no adjustment left to argue about.
An earlier build credited the baseline with ₹73,653.24 of above-cap recoveries and subtracted them in the report. That made the compliance headline a correction to a simulator bug rather than a measurement of behaviour. It is logged as defect 19.
Recovered
Refused by the breaker
Recoverable subs halted
What each rule is holding back
Change the policy, run it again, see the cost.
Every option below is a real batch the engine ran, not a projection. Compared against the shipped policy on identical records and draws.
Rule 6 off. Nothing above the cap is recovered anyway -- the breaker catches it at the action boundary and the network would decline it regardless. The cost moves one layer down, into the refusals row.
| Measure | As shipped | Without the RBI cap rule | Change |
|---|---|---|---|
| Recovered, within envelope | ₹58,875 | ₹58,875 | No change |
| Recovered per attempt | ₹765 | ₹765 | No change |
| Attempts spent | 77 | 77 | No change |
| Wasted attempts | 52 | 52 | No change |
| Refused by the money breaker | 0 | 2 | +2 |
Escalation queue
₹2,18,948 still owed, and a route for every rupee of it.
Each route is a field on the decision, not a phrase parsed out of one. The baseline produces none of this: it spends the attempts, halts the subscription, and says nothing about what to do next.
₹2,18,948 unrecovered = ₹88,338 re-mandate + ₹73,653 AFA link + ₹50,965 winback + ₹4,494 cap upgrade + ₹1,497 human review + ₹0 unassigned
Nothing is unassigned. Every rupee the engine declined to chase has a route attached as a field on the decision, so the queue reconciles to the total exactly.
- 29₹88,338RE_MANDATE_LINKOpsmandate dead: send a new mandate registration link
- 2₹73,653AFA_PAYMENT_LINKOpsabove the RBI AFA-free cap: customer-present link with AFA
- 35₹50,965WINBACK_CAMPAIGNGrowthbudget exhausted: fresh invoice + winback nudge
- 6₹4,494MANDATE_UPGRADEFinanceabove the mandate's cap: request an upgrade or split it
- 3₹1,497HUMAN_REVIEWSupportunclassifiable: a person reads the bank's own text
Where Vasooli lost
The baseline recovered 5 records this arm did not.
None of these was refused. Every one was scheduled by rule 8 against a live mandate, and the moment this engine chose lost to the baseline’s fixed schedule on the same draw. The timing model is a model; sometimes T+1 is the better guess.
| Subscription | Failure | Amount |
|---|---|---|
| sub_SYN0099 | insufficient funds | ₹2,499.00 |
| sub_SYN0075 | insufficient funds | ₹499.00 |
| sub_SYN0073 | insufficient funds | ₹299.00 |
| sub_SYN0037 | insufficient funds | ₹199.00 |
| sub_SYN0038 | insufficient funds | ₹199.00 |
It reconciles
₹4,999.00 won (1 record) − ₹3,695.00 lost (5 records) = ₹1,304.00
That is the entire headline recovery delta, accounted for record by record. The gap between the arms is not built out of the rupees recovered — it is built out of the attempts saved getting there, which is why recovery per attempt is the metric and gross recovery is not.
And the crude question, since choosing a metric is exactly where a reader should be suspicious: which arm simply collected more money, budget ignored? Across 40 seeds the sequencer wins that one in 25, ties 1 and loses 14 — median +₹1,047, worst seed −₹12,695. That is the weakest number in this project and vasooli experiments prints it unprompted. The argument for the rate is that the budget is three deep and does not refill, so the arm that collects the same money on half of it starts next cycle with something left. Judge that claim on the rate; the total is here so the choice is visible rather than convenient.
One decision, end to end
sub_SYN0056 · ₹31,045.08
Everything above this is an aggregate. This is the mechanism it aggregates, for one subscription, in the order it actually happened.
What the bank sent
GATEWAY_ERROR / bank_unavailable
ICICI is currently not responding. Please retry after some time.
Classified
BANK_DOWNTIME
via code_map
Rules evaluated
1234567rule 6 fired
Refused
HUMAN_REVIEW
afa payment link
Written to the ledger
fdfcb95d4fc72389…
before anything was attempted
escalated: amount ₹31,045.08 exceeds the RBI e-mandate AFA-free cap — an unattended debit would be declined on presentation; routed to a customer-present payment link with AFA, 1 attempt(s) preserved
Exception list
75 of 100 records were not recovered.
₹2,18,948 still at risk. Every one is listed. Nothing is filtered out of this view.
Audit trail
chain intact, unkeyed — detects accidental edits, but anyone with write access could rebuild it; set VASOOLI_LEDGER_KEY to prevent that
Every decision above is written to an append-only chain before it is acted on. Editing any historical row breaks every hash after it, and verification reports the first broken index rather than a bare pass or fail.
Where the model sits
A language model reads free-text bank error descriptions, which is a genuine language problem. It is allowed to answer UNKNOWN, and UNKNOWN goes to a person.
No language model participates in any decision to move money. Timing is a deterministic scorer: reproducible, testable, explainable to a regulator.