Sanish Kumar
← Back to selected work

(Debt clearing, checkable certificates, and failure cascades)

CashFlow

CashFlow started with a question about group expenses: what does ‘simplify debts’ actually change? It now has three cycle-clearing solvers, independently checkable optimality certificates, and a simulation of what happens when some parties cannot pay. The graph stays visible throughout.

Role
Independent engineering project
Timeline
2026
Stack
TypeScript · React · Canvas · Node.js · PostgreSQL · Redis · Socket.IO

See it in action

CashFlow's graph interface comparing original debts, cancelled loops, and simplified obligations

Loads on request · 842 KB

Recorded app demo using included sample obligations. It compares debt-clearing modes and then explores a supply-chain solvency scenario; these are simulated obligations, not confirmed real-world payments.

More screenshots and results
Two supply-chain graphs comparing six firms unable to pay before clearing with three after the keep-solvent plan

Same sample debts and available cash, two clearing choices. Dark nodes cannot pay in the simulation; rings mark firms preserved by the plan. This demonstrates the model, not a financial forecast.

What I wanted to solve

Cancelling a closed loop preserves net balances and creates no new counterparties. That sounds harmless until someone cannot pay: cancelling the loop can leave less for a creditor outside it. I wanted the app to show both questions—how much debt can disappear, and who is worse off under the resulting payment failures—instead of treating maximum clearing as the only objective.

What made it difficult

  • Money must stay in integer minor units while the graph is reduced thousands of times.
  • Cycle-only clearing must preserve every party's net position and never introduce a counterparty.
  • Full path simplification has to disclose every relationship it creates.
  • Large-network results should carry a certificate that can be checked without trusting or rerunning the solver.
  • A plan that preserves net positions can still change recoveries when someone defaults.
  • The search for fewer failures must report a bound, not imply that a heuristic always finds the optimum.
  • Group roles govern expenses, exports, membership, and settlement actions.
  • A calculated obligation is not proof that money moved between people.

How it works

  1. 1

    Expense ledger

    The server stores group expenses, roles, audit events, and integer-minor-unit amounts in PostgreSQL.

  2. 2

    Obligation graph

    Members are nodes and the actual who-owes-whom relationships are directed, capacity-limited edges.

  3. 3

    Three cycle solvers

    Cycle cancelling, shortest-path routing, and network simplex solve the maximum-clearing problem. The large-network paths split work by strongly connected component.

  4. 4

    Independent verifier

    Routing and simplex return a potential per party. A separate linear-pass verifier checks conservation, unchanged counterparties, and maximum cycle clearing without running a solver.

  5. 5

    Path compensation

    The aggressive mode shortens open chains toward the net-position floor and records any new counterparty pairs it creates.

  6. 6

    Default cascade

    An Eisenberg–Noe payment model with Rogers–Veraart recovery rates calculates actual collections and the waves of parties that become unable to pay.

  7. 7

    Partial-clearing search

    The rescue search tries smaller cycle cancellations, resolves the whole payment network after each candidate, and compares failures with a max-flow-based unavoidable-failure bound.

  8. 8

    Browser engine and graph

    The dependency-free clearing package and hand-built canvas renderer let a user switch modes without a server round trip.

  9. 9

    Product boundary

    The surrounding app handles authorization, realtime updates, exports, receipt parsing, and the separate workflow for recording payments.

Choices I made

Keep obligations, not only final balances

A balance-only solver loses the relationships that existed before simplification. I keep the obligation graph so the engine can prove that cycle mode created no new counterparties and can name the pairs introduced by path mode.

Separate maximum clearing from fewer failures

Maximum circulation answers how much can cancel without changing net positions or counterparties. It does not answer who can still pay afterwards. I keep the clearing objective separate from the cascade model so ‘no new counterparty’ is not presented as ‘no one can be harmed’.

Make an optimum cheap to check

Routing and simplex produce a certificate alongside the result. The verifier checks the remaining obligations and vertex potentials in one pass. Component-level potentials are shifted to cover edges between components too, so the certificate applies to the whole input graph.

Keep three solvers with different trade-offs

Cycle cancelling is the reference and default. The routing solver takes advantage of unit edge costs to handle large pure-clearing problems; network simplex also supports general integer costs, supplies, and unbounded arcs. Randomized tests require them to agree on the clearing objective.

Make the aggressive trade-off visible

Path compensation can clear more debt, but only by shortening chains. The UI reports the extra amount cleared alongside the exact people who would become new counterparties.

Run the comparison in the browser

The dependency-free TypeScript package runs locally in the browser, alongside the canvas graph. Comparing modes does not need a server round trip. Larger solver and rescue benchmarks are measured separately rather than being advertised as instant UI operations.

Verify every proposed rescue against the whole network

A cancellation that helps one creditor can hurt another. The search tries the smallest useful change first and recalculates the full cascade before keeping it. If its failed-party count reaches the proven floor, the result is marked provablyBest; otherwise it reports the remaining gap.

Keep money in integer minor units

Clearing repeatedly adds and subtracts edge capacities. Integer amounts keep the conservation checks exact and prevent fractional-cent drift from entering the instructions.

How I tested it

  • Unit and integration coverage spans the clearing package, web app, and server, including the distinction between calculated plans and confirmed payments.
  • Clearing tests check net-position conservation, integer inputs, no-new-counterparty guarantees, forbidden pairs, and the difference between cycle and path modes.
  • Randomized network tests compare all three clearing solvers. Certificate checks are separate from the optimization algorithms.
  • The rescue benchmark enumerates every whole-number clearing on 2,993 small networks in each of two recovery conditions, giving an exact answer to compare with the heuristic.
  • Seeded scale benchmarks exercise synthetic supply chains; the reported timings are not measurements of customer transaction data.
  • Server tests cover authentication, group authorization, audit scope, settlement confirmation, and fixed-point storage.
  • The verify command runs lint, all three test suites, the server TypeScript build, and the production web build.

Known failure modes

  • A purely hierarchical or acyclic obligation graph gives cycle mode nothing to cancel.
  • Path mode can create relationships between people who did not previously owe each other.
  • Cancelling a loop can shift recovery away from a creditor outside it when someone in the loop cannot pay.
  • A configured circulation iteration limit can stop the reference solver before optimality; the result must report that limit.
  • The rescue heuristic can miss the best plan, and a loose lower bound can leave a substantial reported gap.
  • Currency conversion context does not remove exchange-rate or rounding policy decisions.
  • External receipt OCR can fail and may require consent for third-party data handling.

What works now

  • The documented five-person example reduces ₹4,800 across eight IOUs to ₹550 across four payments in full-simplification mode.
  • Cycle mode guarantees zero new counterparties; path mode reports every new pair it creates.
  • On the documented 18,831-party, 51,375-obligation synthetic network, routing took 1.26 seconds and certificate verification took 3 milliseconds.
  • A larger synthetic fixture with 122,456 obligations took 4.88 seconds with routing; its certificate checked in 3 milliseconds.
  • The rescue search matched the optimum on 99.73% of the 2,993 lossless fixtures and 99.77% with bankruptcy losses. Its misses were one failed party above the optimum.
  • The clearing engine is a standalone dependency-free TypeScript package that runs in the browser.
  • Role-aware groups, recipient-confirmed settlements, an audit history, exports, and live updates.

Three different questions

I keep these objectives separate because doing well on one does not imply doing well on the others. A computed plan also remains separate from the app's recipient-confirmed record of a real payment.

  • Cancel loops: remove the most debt possible while keeping the original counterparty set; routing and simplex can certify this optimum.
  • Simplify all: also shorten open chains toward the net-position floor, disclosing every new pair.
  • Rescue: search for partial cycle clearing that leaves fewer parties unable to pay under the stated cash and recovery assumptions.
  • The optional exact/greedy balance solver answers a fourth, older question: the fewest transfers for a balance vector. Its C++/WASM implementation is not the new clearing engine.

What it doesn't do yet

  • CashFlow records obligations and confirmations. It does not hold funds or connect to a bank.
  • The hosted application is a public beta, not an audited financial product.
  • The large benchmark networks are seeded fixtures, not private production transaction data.
  • Cash positions in the rescue benchmarks are generated. The results are not forecasts of real defaults.
  • The payment model is one-period and pro rata. It does not represent secured creditors, payment due dates, or insolvency law.
  • The optional C++/WebAssembly minimum-transfer solver remains in the repository, but it is not the engine behind the browser's obligation-clearing comparison.
  • A user should not enter financial data they would be uncomfortable storing in a hosted PostgreSQL database.