Challenge 5 asked for one of three small starting points: check the leverage and debt-service covenants, track quarterly headroom, or link a breach to the agreement definition. We wanted all three in one workflow a lender could actually use, because in practice they are one job: find the breach, prove it from the agreement, and decide what to do next. The last step is the one spreadsheets never answer. Knowing a borrower is $12,000 short on cash flow is useful; knowing the cheapest way the agreement lets them fix it and being able to show that the fix really works is what an analyst takes to a credit committee.
Covenant Monitor follows the lender's credit cycle in five steps, all in one web app.
- Set the terms. Four covenants: maximum leverage, minimum debt-service coverage (DSCR), minimum fixed-charge coverage (FCCR) and minimum current ratio. Each can be a maintenance test, a springing test (applies only when the revolver is drawn past a trigger, 35% by default) or off, per borrower. A Covenant Designer recommends limits from a borrower's statements (CSV or PDF).
- Collect the reports. A reporting tracker shows, per quarter, whether each borrower's financials, compliance certificate and annual statements arrived on time, late (with days late) or are missing. In Q3 2026 it flags 2 of 9 borrowers: BOR-03 14 days late for the third quarter running, and BOR-07's certificate 17 days overdue.
- Read the statements. A financial-statements PDF is read by LlamaExtract, which only extracts each line item with its page citation. Our code does every calculation and tie-out, and an analyst confirms each value before any covenant test runs.
- Find the breach and the cure. The overview ranks borrowers requiring attention, most urgent first. Each borrower page shows every covenant against its limit, the dollar shortfall, the change since last quarter, and the exact agreement sentence behind the limit. For every breach, a Gurobi optimisation model proposes the cheapest cure the agreement allows (sponsor equity cure, early repayment, asset sale), and our exact covenant engine re-checks it before it is shown. When no cure exists, it explains which terms block it and sizes the smallest waiver. Example: BOR-09 in Q3 2026 fails three covenants; the cheapest verified cure is selling Division A plus a $68,333.34 equity cure, $78,333.34 in total.
- Prepare the call. A one-page call prep sheet per borrower, printable to one US-letter page, with the covenant scorecard, trend, cure, reporting status and up to six questions to ask the owner.
We built the app using a variety of tools optimized for specific functions: • Covenant engine: Node.js 22, built on the starter pack's backend, CSV parser and exact-money utilities. Every amount is held in whole cents as BigInt, so no floating-point rounding can move a result across a limit. Ratios are cut off at 4 decimals in the cautious direction. • Evidence: each covenant limit is matched to its sentence in the loan policy text, returned with character offsets and a SHA-256 hash. The server checks every quotation against its document before returning it. • Cure planner: a mixed-integer model in Python with Gurobi 13 (gurobipy, served by FastAPI), in exact integer cents with the engine's rounding. It finds the cheapest cure under seven cited constraints (leverage, DSCR, cure cap, cure frequency, minimum liquidity, current ratio, FCCR), uses Gurobi's IIS to explain why no cure exists, and FeasRelax to size the smallest waiver. Gurobi proposes; the exact engine decides: every plan is re-checked in BigInt cents before it is shown. • Statements: LlamaExtract (LlamaCloud) reads PDFs and returns line items with page citations; our code computes the covenant inputs and tie-outs; an analyst confirms each value. • Front end: plain HTML, CSS and JavaScript, with no framework, a shared design system and a strict Content-Security-Policy. • Deployment: Vercel, one serverless function running the same request handler as the local server. Vercel has no solver, so it serves cure plans solved offline with Gurobi and re-checks each one with the exact engine on every request. • Testing: 123 Node tests (including a test that API responses stay byte-identical), Python tests for the model, and headless-browser checks of layout at 1440, 1280, 1024 and 390 px. • Tools: we built with Claude Code as our coding assistant; the app itself uses no LLM.
While building, we ran into a few challenges: • Missing inputs. The organizer data covers only leverage and DSCR. FCCR, the current ratio, springing tests and reporting dates needed fields it doesn't have, so we added a clearly labelled synthetic fixture and policy addendum, and never treat them as if the source contained them. • Solver and engine agreeing to the cent. An optimiser works in floating point; covenant tests are exact. We modelled in integer cents with the engine's rounding (premium rounded up, debt-service relief rounded down), and still re-check every plan, so a rounding difference can never show a cure that doesn't work. • No solver in production. Vercel can't run Gurobi, so we solve offline, store the solutions, and re-check them with the exact engine on every request. Live what-if re-solving works only where a solver is connected. • Citations that survive checkout. Evidence uses character offsets, and a Windows checkout changes line endings, which moves every offset. We keep separate baselines and check each quotation against its document before returning it. • Making the cure easy to find. The cure planner first appeared about 5,800 px down a long borrower page. We moved a one-line summary to the top of the page and the overview, and the section now starts at about 2,700 px.
Some of the accomplishments within the project we're most proud of: • All three suggested MVPs, plus the next decision. Covenant checks, quarterly headroom and agreement evidence are all in, and the cure planner answers what to do about a breach. • Every cure is proven, not just proposed. We brute-forced all 30 breached borrower-quarters independently of Gurobi, with every early repayment in $100 steps, with and without the asset sale. All 90 cure options matched the solver's answers to the cent, including the 2 quarters with no cure. • Traceable numbers. Every limit links to its agreement sentence with character offsets and a hash, and every figure on the product page is refreshed from the live API. • Honest by design. Synthetic data, synthetic cure terms and "not a legal determination of default" are stated wherever a result is shown.
Through the process, we took away a few key lessons: • Optimisation and verification are separate jobs. Letting Gurobi search and an exact engine decide gave us speed and certainty at once, and made every answer explainable. • "No cure" is a result too. An infeasible model, explained with the terms that block it and a sized waiver, is as useful to a lender as a plan. • Test data shapes the story. Because the organizer dataset is formula-driven, every borrower has the same DSCR each quarter. Our engine and solver were right, but the analysis looked flat, which taught us to check the data's shape before trusting a dashboard. • Findability matters as much as features. Our best feature was invisible until we put a one-line summary where the analyst's eyes already are.
As we move forward, we'd look toward optimizing the model by adding some of the listed features: • A realistic second portfolio with varied debt service, cash flow and cure terms per borrower, so results range from passing to cheaply curable to waiver-only, offered beside the organizer data. • A hosted solver so what-if cure terms re-solve live in production, not only from stored solutions. • Statement upload in production, with access control, so borrowers' PDFs flow straight into the covenant tests. • Team workflow: shared review notes and an audit trail (today, review notes stay in one browser). • Merge identical cure options into one card when, for example, the cheapest plan is already an equity cure only.
Built With
- claude-code
- css
- fastapi
- gurobi
- html
- javascript
- llamacloud
- llamaextract
- node.js
- playwright
- python
- supabase
- vercel