← All work

Mandate Retry Sequencer

A recovery pipeline for failed UPI Autopay mandate debits. An ML model scores each case, an LLM agent handles the ambiguous middle, and five compliance rules run before and after every agent proposal.

Area
Fintech · AI agent
Role
Solo: design, build, deploy
Stack
Python, FastAPI, LightGBM, SHAP, Claude API, React, d3
Links
GitHub · Live demo
Mandate Retry Sequencer dashboard: a rail showing 500 debits in, 187 stopped by a rule, 248 routed to the agent, 266 executed and ₹44L recovered, above a Sankey of where the 500 went
The live dashboard on the committed synthetic batch.

Problem

According to the reports collected in the repo's SOURCES.md, more than 20 million UPI Autopay mandates are revoked every month in India because the customer's balance was short. Payment gateways already retry them.

What merchants can't easily show is which retries were allowed, which were refused, what a wrong retry cost, and which debits the system left alone. A recovery figure on its own doesn't answer those questions.

Users

  • Payments and collections operations at a merchant running UPI Autopay: they need more of the failed debits recovered.
  • The compliance reviewer who signs off: they need to see that no retry broke a rule, and why each decision was taken.

Constraints

  • NPCI peak-hour windows. Effective 1 August 2025, non-customer-initiated debits (which is what a mandate debit is) are restricted to non-peak hours. Blocked: 10:00–13:00 and 17:00–21:30 IST, about 40% of the day.
  • Five hard rules: revoked mandate, attempt cap, cooling period, recovery horizon and the NPCI peak window. No score and no agent decision may override one.
  • Not every constant is equally trustworthy. In policy.py, each one is graded as regulation, industry convention or assumption. The primary NPCI circular could not be fetched, and the repo says so.
  • Synthetic data only. No real merchant data is used, so the scorer's AUC describes this pipeline on generated data.
  • Must reproduce without an API key, so a reviewer on a fresh clone gets the same numbers.

What I built

500 failed mandate debits
        │
  [ SCORER ]      LightGBM · P(recover | features)
        │
  [ GUARDRAILS ]  five hard rules, evaluated first and always
        │
  [ DECIDER ]     LLM agent, only for the ambiguous band (0.15–0.65);
        │         guardrails re-validate every proposal
  [ EXECUTOR ]    simulated rail over a 14-day clock
        │
  [ LEDGER ]      append-only: input, score, rules fired, action, outcome, ₹
        │
  [ DASHBOARD ]   where the 500 went, rule fire-counts, NPCI 24h clock,
                  and every record it failed to recover
  • Dashboard. It shows the pipeline as a rail, a Sankey of where all 500 debits went (a test pins that edges sum to 500), the rulebook with fire and override counts, and a 24-hour clock with every executed debit as a tick.
  • Explainability. Select any unrecovered record to see its SHAP explanation, or call /explain/<id>. No API key is needed.
  • Independent total check. verify_totals re-derives the headline figure from the ledger without importing the ledger code, so the two totals are computed independently.

Metrics

Replay: where the 500 debits went

  • Recovered170
  • Stopped by a rule187
  • Not retried, score too low128
  • Ran out of time15
Each dot is one record from the committed run, following its real path through the pipeline. ₹44,25,090 recovered; 11 agent proposals vetoed by a rule.
MeasureValue
Records in the batch500
Amount at risk₹1,26,32,606
Recovered₹44,25,090 (35.0%)
Records recovered170
Not recovered, listed in full₹82,07,516
Stopped by a hard rule187
Routed to the agent248
Agent proposals vetoed by a rule11
Debits executed266
…of which inside an NPCI peak window0
Automated tests214

All figures come from the committed seed on synthetic data, and a fresh clone with no API key reproduces every one.

Trade-offs I made

  • Rules run before the score, and again after the agent.Some debits the model rates as likely to succeed are never attempted. I accepted lower recovery in exchange for zero compliance breaches.
  • The agent only sees the ambiguous middle (score 0.15–0.65).Clear cases are handled deterministically, which keeps the agent's decisions few, cheap and auditable.
  • Blocked retries are deferred.A retry that falls inside a peak window moves to the end of that window. It never runs inside the window, and every deferral is recorded.
  • No API key required.A committed response cache, a deterministic fallback policy and an ablation script mean anyone can reproduce the numbers. The cost is that with an empty cache the ablation shows a ₹0 agent contribution, and the script prints a note saying so next to that zero.
  • The dashboard lists every unrecovered record.They're shown largest first, next to the recovered total, so anyone can check the headline number against them.

What I'd do next

  • Run the ablation with a key (--populate) to measure the agent's actual rupee contribution.
  • Replace the secondary sources for the NPCI rules with the primary circular.
  • Test the synthetic generator's assumptions against real failure distributions from a merchant or gateway.