Back to projects

AI Loan Underwriting Assistant

The problem

This one started from a tutorial I was using to learn ML. I picked it to go deep on model choices, explainability, and building something that actually deploys, not because I'd found some gap in how underwriting tools work.

What I did

I started with logistic regression, ROC-AUC 0.72, not bad. I moved to XGBoost mainly because it supports monotonic constraints, not just for the AUC bump to 0.77. Without that constraint, the model did something genuinely wrong: a second missed payment was scoring as lower risk than a spotless payment history, everything else held constant. That's the kind of bug you only catch by looking at the model's behavior, not just its score.

While digging into why the model was behaving abnormally, I came across SHAP. It wasn't part of the tutorial, I found it while trying to figure out which features were driving the bad predictions. Liked the concept, built it in.

For the reporting layer, I didn't want the free LLM APIs' rate limits to break the experience. Someone tries the app and just gets an error. So I built a fallback chain across 5 providers in priority order: Groq, Gemini, then three OpenRouter models.

I also switched the backend from Flask to FastAPI partway through, since it's more standard for ML applications now and I wanted to learn it properly.

Honestly, the code isn't the part I want recruiters to look at. That's mostly what any modern LLM can write. It's the thought process: catching the monotonic constraint bug, designing around free-tier limits, building for reliability.

Architecture

flowchart TD
    A[User] --> B[FastAPI API]
    B --> C[Underwriting Pipeline]
    C --> D[XGBoost Prediction + SHAP Explanations]
    D --> E[Provider-Agnostic LLM Client]
    E --> F[Groq / Gemini / OpenRouter]

User to FastAPI API to underwriting pipeline, then XGBoost prediction and SHAP explanations, then a provider-agnostic LLM client that writes the plain-English report.

What came of it

The full pipeline, predict, explain, report, runs end to end and it's live.

  • 30,000 samples, 7 features selected from 23
  • ROC-AUC 0.77, F1 0.53, 80% accuracy
  • 9 tests, all passing, about 3.4s run time
  • Prediction and SHAP run in milliseconds
  • LLM report generation takes about 2 seconds via Groq
  • 5-provider fallback built and configured, but so far only Groq has been exercised, and it's always succeeded

Next time

Keep capturing metrics and the failure cases while building, not after. The fallback path still hasn't been triggered, so I'd want to force it to fail once to prove it works.