Skip to content
Build Notes  /  Field notes

Replacing a loan management platform with a tracker that reads the loan documents

A client was pricing an off-the-shelf loan management platform for document imaging and deadline tracking. We built that part instead. Here is how it reads a closing binder, and the part we told them not to build.

A commercial real estate developer carries debt on most of its properties, and every loan arrives with a closing binder: the note, the loan agreement, the mortgage, guaranties, indemnities, reserve agreements, sometimes a swap. Spread across those documents are dozens of obligations per loan. Financial statements due so many days after quarter end. Insurance evidence at renewal. Covenant tests. Reserve deposits. Extension notices with windows that open and close on specific dates.

The firm tracked these in a hand-kept spreadsheet per loan. It was evaluating an off-the-shelf loan management platform for document imaging and deadline tracking, expected a significant quote, and asked whether we could build it instead.

The obligation that justified the effort was an extension notice. The loan agreement required notice no more than 180 days and no less than 60 days before the original maturity date. Miss the near edge of that window and the extension option is gone, with the loan due 60 days later. A maturity column in a spreadsheet does not catch that. A system that models the window does.

What the binders actually contained

Before designing anything, we measured the documents. Across the first four real closing binders: 164 documents, 2,310 pages, and about 55 percent of the pages were scans with no text layer at all.

Two findings shaped everything after. One binder was a PDF Portfolio that reported itself as a single page while holding 92 embedded documents, so any pipeline that trusted the page count would have silently dropped a large share of that deal. And more than half the pages were pictures of paper, so the system had to read images, not just text.

What we built

A Next.js and TypeScript app on Supabase and Vercel. The core loop:

  1. Upload a binder. The browser uploads straight to storage, because binders run 40MB and up. The server registers a manifest of documents, pages, and scans.
  2. Read it. Claude reads the documents in page-budgeted batches and proposes every obligation with its document, section, page, and verbatim clause. A second reviewer pass re-reads key documents and returns corrections, additions, and gray-area passages worth a lawyer's eye.
  3. Schedule it. A deterministic engine turns obligations into dated occurrences and sends reminders at 30, 14, 7, and 1 day out, with a weekly nag on anything past due.
  4. Prove it. Every date explains itself back to the clause. Click a citation and the source document opens at that page with the quoted language highlighted.

Around that loop sits the product people use day to day: a portfolio calendar, a pipeline with custom stages, a review queue, team roles, and per-person notification settings narrowed by loan and category.

The rule that carries the weight

Extraction is probabilistic. Scheduling is not. The model reads documents and proposes obligations. It does not get a vote on whether a payment is late. The scheduling engine is dependency-free and pinned to specific dates by tests. When we ported the prototype engine to TypeScript, we ran both versions over the same loan and compared the output: 264 occurrences, identical on every field.

The companion rule: undated obligations are flagged, never guessed. A confidently wrong date is worse than an honest gap, so the rule is enforced in the prompt, in application validation, and in a database constraint. When a binder leaves a date blank (an unexecuted draft still reading "August ___", for example), the app asks once, in plain language, and every obligation anchored to that fact dates itself from the answer. Duties triggered by an event rather than a date, like a lender's demand, move to a standing list with the trigger named, so they stop clogging the review queue.

The hard parts

Scanned pages. "No text layer" is an accurate description of a scan and a useless answer to someone who wants the clause highlighted. Scanned pages are now read with OCR during ingestion, and every word's position is stored so the viewer can draw highlight boxes on the image. We built a harness that reproduces exactly what the viewer does and ran it across every loan. It started at 138 of 161 quotes located and finished at 161 of 161.

Long jobs on serverless. A 348-page binder took 305 seconds in one model call, past the platform's 300-second limit. Extraction now runs in batches on the server, so closing the browser tab loses nothing. When the self-chaining job later hit the platform's loop guard on a large binder, we moved dispatch into Postgres: a trigger fires each step through pg_net, and a pg_cron sweep every minute restarts anything stalled.

"Done" has to mean done. An early version emailed "your loan is ready" when extraction finished while OCR was still reading half the pages. Completion is now a gate that waits for every job and sends exactly once.

Combined binders. A 367-page binder uploaded as one PDF failed with a generic error, five times. It is now cut into its documents along the PDF's own bookmarks: that binder became 35 documents, and a 1,149-page one became 67. A review pass then caught a case where an agreement's internal bookmarks would have cut it into fragments and dropped 84 pages without an error. Fixed before anyone uploaded again.

Amendments. A team member missed a loan modification in one package and asked whether it could be added later. An Add documents button now reads the new document against the existing schedule and applies only what it changes: new maturity, moved deadlines, new fees. Anything it deletes goes to review with the deleting language quoted. Removing a document puts back what it changed, unless someone has already worked the item.

The AI connector

The tracker is also an MCP server, so staff can ask Claude what is due, what is overdue, and why a date is what it is, from claude.ai, Claude Desktop, or Claude Code. Every answer carries document, section, page, and clause. Sign-in runs through the app's own OAuth flow with a consent screen, and write access (mark complete, answer a question) is optional per connection and audited. Before release, a review pass caught a bug that would have failed every approval click and a cross-tenant write path. Both were fixed first, and the connector passed 28 of 28 checks in production.

What changed

For one loan, the firm's hand-kept tickler had 10 line items. The extraction produced 37 obligations with page citations, matched or beat 9 of those 10 rows, and caught a quarterly guarantor reporting requirement that the spreadsheet's own notes said had never been tracked. The one miss was a letter agreement that was never in the binder. On another loan, a careful hand-built reference set missed 22 obligations the model found.

Model usage for reading that 348-page binder measured about five dollars.

What we did not build, on purpose

The platform's headline feature is covenant math: ingesting property financials and computing lender-adjusted debt service coverage and debt yield to the penny. The tracker extracts those definitions verbatim and reminds the team when each test is due. It does not compute them, and it does not model rate curves or value hedges.

Our recommendation was plain: use the tracker for binder reading and date compliance, and if covenant analytics matter, price that piece on its own against loan count and floating-rate exposure. It is a different product with a much higher accuracy bar, and building it on the strength of a good tickler would be the wrong call.

What other operators can take from this

  • Measure your documents before you design anything. Page counts lie.
  • Let the model read, and let deterministic code decide dates.
  • Make every date explain itself back to the clause.
  • Be honest about where document intelligence ends and financial analytics begins.

If your loan obligations live in a spreadsheet someone updates by hand, book a Systems Audit.


J

Filed by

John McIntosh

Founder, Salvus AI

Talk to us

Want this kind of work shipped for you?

Salvus AI deploys per-client isolated MCP servers and the automations that run on them. Client success with real numbers, no shared tenancy.