
At month end, two teams open two files and get two different numbers for the same book.
Risk reports a portfolio position. The NBFC partner's settlement file reports another. Finance needs one number to post to the GL, the close is already running late, and somewhere an analyst is matching rows by hand against a partner statement that arrived in a slightly different format from last month's.
Every lending platform we have looked at hits this somewhere between the first and the fourth co-lending partner. The instinct is to treat it as an operations problem tighter SLAs with the partner, a better template, one more analyst through the close. That buys a quarter or two.
It comes back, because the cause is not effort. It is the data model.
The symptom is the close. The cause is the model.
Co-lending puts two organisations in a position where they each maintain a book about the same borrower, using their own systems, their own timing, and their own definitions. Disagreement between those books is not a defect. It is the normal state of the arrangement.
A reconciliation system's job is not to prevent disagreement. It is to make disagreement legible: to say, at any moment, exactly which loans the two books agree on, which they do not, by how much, why, and who is fixing it.
Most platforms build the opposite. They build a process that assumes agreement and treats every difference as an incident to be chased down manually. That process works while the exception volume is small enough for one person to hold in their head. It stops working at the point the portfolio starts mattering.
Here is where it actually breaks.
Five ways co-lending reconciliation breaks at scale
The partner sends a settlement file. Someone loads it, matches what they can, fixes what they cannot, and posts the result. The file is then archived in a shared drive, or worse, overwritten by next month's.
The consequence surfaces later, when an auditor or a diligence team asks a question about a period six months back. Reproducing that answer requires the file as it arrived, the state of your book at that moment, and the reasoning behind each manual fix. Two of those three are usually gone.
A settlement file is an input, not a truth. It needs to land in immutable storage, versioned, with its arrival timestamp and its checksum, before anything reads it. Late-arriving corrections then become new versions rather than edits to history.
Your system records a disbursal at the moment it happens. The partner records it when their own settlement cycle picks it up. Between those two moments the books legitimately disagree, and the size of that gap changes with weekends, holidays, partner batch windows, and cut-off times.
If the model carries no concept of this lag, every loan sitting inside its normal lag window looks identical to a loan that is genuinely broken. Reconciliation output becomes a list dominated by items that will resolve on their own, and the real breaks are buried in it. Analysts learn to ignore the report, which is a rational response to a report that is mostly false positives.
The fix is to make the lag a modelled attribute per partner and per product, so that "unmatched but within expected window" is a distinct state from "unmatched and overdue."

Both parties compute days past due. They rarely compute it identically.
The differences are small and structural: which date starts the clock, whether a part-payment resets the count, how a holiday or a moratorium is handled, whether the DPD bucket is evaluated at end of day or on a rolling basis. Each choice is defensible. Together they mean your 30-plus DPD bucket and the partner's 30-plus DPD bucket contain different loans.
This is the disagreement with the highest stakes, because DPD buckets flow into provisioning, into partner reporting, and eventually into a regulatory return. Two books that disagree on the balance is a reconciliation problem. Two books that disagree on classification is an audit finding.
Either one party's computation becomes canonical and the other is derived from it, or both are computed from a shared event stream against explicitly documented rules, with the difference between them reported as a first-class output rather than discovered at close.
Ask a lending platform how many open reconciliation exceptions they have right now. The honest answer is usually a range, sourced from a spreadsheet tab and someone's memory.
Unmatched items get worked through email threads and ad-hoc calls with partner ops. There is no owner field, no age, no resolution code, no service level. Nobody can answer the two questions that matter: what is the oldest unresolved break, and which category of break costs us the most time this quarter.
Without resolution codes you also cannot improve. Every exception is handled as a one-off, so the same class of break recurs indefinitely instead of turning into a rule that clears it automatically.

The close deadline arrives before reconciliation finishes, so finance posts on the best available number and books an adjustment later. Do this for a few cycles and the GL carries a suspense balance nobody can decompose, made of accumulated timing differences, genuine breaks, and manual corrections that are indistinguishable after the fact.
Posting should read reconciled state only. Items that are not reconciled post to a clearing account that is itemised every rupee in it traceable to a specific open exception rather than a plug figure.

The target data model
The shape below is what we design toward. It is not exotic. What makes it work is that every disagreement gets a place to live.

Model events, not balances
The foundation is an append-only event ledger: disbursals, repayments, settlements, adjustments, write-offs, each with a business timestamp (when it happened) and a system timestamp (when you learned about it). Balances become derived views over that ledger, computed as of a date.
Keeping both timestamps is what makes the whole model auditable. It lets you reproduce what your book looked like on the 31st using only what you knew on the 31st which is the question every auditor and every Series C diligence team eventually asks, and the one a snapshot-based model cannot answer.
Declare one grain
Loan × partner × as-of date. Written down, enforced in tests.
Most reconciliation logic that behaves unpredictably is logic operating on an undeclared grain a query that silently double-counts when a loan has two partner relationships, or a co-lending split that changes mid-life. Declaring the grain turns those into test failures instead of month-end surprises.
The match is a record, not a lookup
This is the load-bearing change, and the one most often missing.
There should be a match table where each row links one internal loan to one partner record, and carries: match method (exact identifier, fuzzy on a composite key, or manual), confidence, who or what made the match, when, and whether it has since been broken. Unmatched records on both sides are rows in the same structure with a null counterpart.
Once matching is a record rather than a join executed at report time, three things become possible that were not before: you can measure match rate as a metric and watch it move, you can distinguish a system match from a human judgement call, and you can reverse a bad match without rewriting history.
The exception queue is a table
Every unmatched or disputed item becomes a row with an owner, an age, a category, a resolution code, and a target date. Not a spreadsheet tab. A table, with the same operational seriousness as a support ticket queue.
The resolution codes are what turn the queue into an improvement loop. When "partner file missing UTR" is a code rather than a description someone typed, you can count it, and once you can count it you can justify automating it away.

Posting reads reconciled state only
The GL posting layer sits downstream of reconciliation and reads only matched, closed state. The clearing account for open items is itemised against the exception queue, so the suspense balance is decomposable at any moment rather than at the end of an investigation.
Lineage and consent belong in the model
Data lineage from source file through match to posted entry should be queryable, not reconstructed. That is what makes an audit trail real rather than a claim in a policy document.
The same structure carries DPDP obligations. Borrower data crosses an organisational boundary in every co-lending arrangement, and consent needs to be enforceable in the data layer a join condition, not a PDF in a legal folder. We cover that separately in the DPDP piece linked below.
What changes when the model is right
Close time is the headline benefit, but it is not the important one.
The important one is that reconciliation stops being an event and becomes a continuous state. You can answer "are we reconciled with this partner" on the 12th, not just after the close. Breaks surface the day they occur, when the partner ops person still remembers the transaction, rather than three weeks later.
Second, the exception queue becomes evidence. When a partner relationship is generating a disproportionate share of breaks, you have the data to raise it commercially instead of absorbing it as analyst time.
Third and this is the one that matters at the next raise the audit trail exists as a by-product of running the system rather than as a project someone has to staff when diligence starts.
Where to start
Not with a platform rebuild. In order:
Most teams can do the first four inside a quarter without touching the core lending system.
