India's national delayed-payments dashboard tracks ₹55,244 crore that small businesses say they are owed. Its front page is six coloured tiles of counts. This is what it hides, and what it should look like — rebuilt on nothing but its own published numbers.
MSME Samadhaan is the portal where a micro or small enterprise files a complaint when a buyer pays late — and where the Ministry, 37 state Facilitation Councils and Parliament go to see how the system is doing. An MSME owner wants to know: if I file in my state, against this kind of buyer, will anything happen, and how long will it take? A council chair wants to know which of the thousands of pending applications to hear first. A minister is asked in Parliament how much money is stuck and whether it is getting better. The front page answers none of these. It answers "how many rows are in each table."
Behind the tiles sit eleven separate reports — by state, by council, by buyer category, by railway zone, by ministry, by ageing — each a long HTML table with two numbers crammed into every cell (a count and a rupee amount, separated by a space). Nothing links them. Read together, they disagree in ways the front page never mentions:
| THE NUMBER | FRONT-PAGE TILE | STATE REPORT, SUMMED | WHAT THE GAP MEANS |
|---|---|---|---|
| Applications filed | 2,34,360 | 2,32,253 | Two thousand applications belong to no state — or the reports refresh on different clocks. Nobody says which. |
| Amount claimed | not shown | ₹50,495 cr | The single most important number in the system is absent from the page that everyone lands on. |
| Money still pending, by age | not shown | ₹8,747 cr | Published only inside a 36-row table with 22 columns. Never summarised. Never charted. |
| Applications filed, Dec 2025 → Aug 2026 | 2,56,892 → 2,56,892 | — | The headline froze. New filings moved to a separate ODR portal on 15 October 2025. The dashboard does not say so, so the freeze reads as a system that stopped working. |
# every cell on the portal holds two numbers as one string: "53322 10339.56" = count, ₹ crore def split_cell(s): parts = s.split() return int(parts[0]), float(parts[1]) # count, amount_cr states = [parse_row(r) for r in statewise_age_report.rows] assert abs(sum(s.filed for s in states) - welcome_tile.filed) < 100, \ "state report and front page disagree — publish the gap, don't hide it" # they do: 2,107 apart
| # | THE QUESTION | WHO ASKS IT | THE FORM THAT ANSWERS IT |
|---|---|---|---|
| Q1 | How much money is stuck, and how much has come out? | Minister · MSME owner | Four numbers with meaning, then a funnel from claimed to recovered |
| Q2 | How old is the stuck money? | Council chair · Minister | Ageing buckets of pending value — the shape says everything |
| Q3 | Where is it stuck? | Everyone | States ranked by pending money, switchable to disposal rate |
| Q4 | Who owes it? | Minister · MSME owner | Buyer categories — claimed against still pending, private buyers included |
| Q5 | Which councils clear their queue, and which don't? | Ministry · Council chair | Disposal rate per council, largest twelve, as a dot plot |
| Q6 | Is the headline even alive? | Anyone reading the tile | The filed count over time, with the note the portal never wrote |
| BEFORE | AFTER | THE RULE |
|---|---|---|
| Six equal tiles of counts | Four numbers with meaning, money first | A dashboard has one headline. Choose it, size it, and let the rest support it. |
| Counts only | ₹ crore on every chart, counts as the secondary label | The unit of a delayed-payment system is money, not rows. |
| 2,34,360 / 64740 / 26554.40 | Indian grouping everywhere, one decimal rule | One number format per screen. The reader should never re-parse. |
| Six decorative hues | One accent; red reserved for "stuck"; grey for context | Colour encodes state or identity. Never both, never neither. |
| No age, no place, no time | Ageing buckets, states, councils, a trend | Answer the readers' questions, in the order the money cares about. |
| "More info" walls of tables | Toggle metrics on the same chart; the table stays one click away | Drill by changing the question, not by changing the page. |
| A frozen headline, unexplained | The freeze charted, with the reason written on the chart | Annotate the surprise. A number that stopped moving is either broken or explained. |
| Totals that disagree across reports | Reconciliation checks before publish; the gap shown, not hidden | If two reports disagree, the dashboard says so. Silence is a lie of omission. |
| Default palette | Palette validated for colour-vision deficiency; every series also labelled in text | Identity is never carried by colour alone. |
# nightly: read the eleven reports, reconcile, publish (simplified) REPORTS = ["StateWise_AgeRpt", "CategoryAmtAge_Rpt", "Council_Report", "ReportAllAmount", ...] frames = {name: parse_two_number_cells(fetch(name)) for name in REPORTS} # three reconciliations that have to hold — or the gap gets published as a number, not swallowed gap_filed = frames["StateWise_AgeRpt"].filed.sum() - welcome.filed gap_amount = welcome.claimed_cr - sum(stage.amount_cr for stage in funnel_stages) gap_cat = frames["CategoryAmtAge_Rpt"].filed.sum() - frames["ReportAllAmount"].filed.sum() snapshot = dict(as_of=today, funnel=funnel, ageing=ageing_buckets, states=top_states, categories=cats, councils=council_rates, gaps=dict(filed=gap_filed, amount=gap_amount)) publish(snapshot) # the dashboard reads one JSON, never a table alert_if(pending_over_a_year.amount_cr > last.pending_over_a_year.amount_cr * 1.05) # the WATCH
Two design choices in that pipeline matter more than the code. Gaps are published, not swallowed — the front page would carry a small line reading "reports disagree by 2,107 applications" instead of pretending they agree. And the alert is on money over a year old, because that is the number a delayed-payment system exists to shrink; everything else is activity.
| READER | BEFORE | AFTER |
|---|---|---|
| The MSME owner in Lucknow | Sees 2,34,360 applications and a green "disposed" tile. Files. | Sees that Uttar Pradesh has disposed of under 5% of filings and that 7,587 applications there are more than a year old. Files with a lawyer, or goes to the ODR portal, or negotiates. |
| The council chair in Kolkata | Opens an 800-row table of applications sorted by number. | Sees ₹124 crore of her state's pending value is over a year old, and which buyer categories hold it. Hears those first. |
| The minister at question hour | Reads out the filed count, which sounds like reach. | Reads out that 26% of claimed value has been resolved, 55% of pending value is over a year old, and that the headline froze in October because intake moved — and says what the ODR portal has cleared since. |
A dashboard is not a place to put numbers. It is a place to put answers. Six tiles of counts is what you get when a database is asked to describe itself. The same eleven tables, read as a ledger — traced for gaps, aged, ranked by money and place — say something a minister could act on tomorrow morning: ₹4,769 crore has been waiting more than a year, most of it in four states, and the number on the front page stopped being true last October.
Have a dashboard that reports activity instead of money? The Leak Ledger starts by reading every table you already publish and making them agree — then builds the six answers your readers actually came for. Two weeks, fixed fee.
Start with a TRACE → Case 01 → Case 02 →