DEV Community

Cover image for Architecting a Credit Decisioning Engine That Survives Audit
James Sanderson
James Sanderson

Posted on

Architecting a Credit Decisioning Engine That Survives Audit

A borrower viewing an AI-generated credit score on a smartphone, illustrating automated lending decisions

Most lending tutorials start with a data model and a loan table. That is the wrong entry point. The service that determines whether your platform is worth building is the credit decisioning engine — the component that turns applicant data into an approve/decline/refer decision and a price. Build it carelessly and every model refresh becomes a regression nightmare, every audit becomes a scramble, and every rejected applicant becomes a compliance risk. This post is about how to architect that engine so none of those things happen.

The one rule: separate three layers

The single most important decision is to stop treating "the decision logic" as one blob. Split it into three layers with explicit contracts between them:

  • Model layer — statistical or ML scores. Outputs a number (or several) plus feature attributions. Owns nothing about business rules.
  • Policy layer — the deterministic hard rules: regulatory rate caps, exposure limits, knockout criteria, mandatory declines. This is code you can reason about and test exhaustively.
  • Strategy layer — routing. Which model and which cutoff apply to this product, geography, and segment. Champion/challenger assignment lives here.

The payoff is operational. Because scoring is isolated from policy, you retrain a model without touching a single rule. Because cutoffs live in the strategy layer, you change one without redeploying code. Teams that collapse these into one service end up terrified of every model update, because a scoring change and a policy change ship as the same risky diff.

Explainability is a storage requirement

"Explainable AI" sounds like a research topic. In lending it is a database schema. For every automated decision, persist:

  • the exact feature values fed to the model
  • the model version identifier
  • the raw score(s)
  • every policy rule that fired
  • the final reason codes

That record is what makes an adverse-action notice defensible and what lets you replay any historical decision deterministically. If you cannot reconstruct a decision from six months ago down to the model version, you do not have an auditable system — you have a liability. Write this logging path before you write the happy path.

A three-layer diagram of a decisioning engine: model layer, policy layer, and strategy layer with an audit log store

Where LLMs belong (and where they don't)

Large language models are genuinely useful in the lending pipeline — just not where people first reach for them. Good uses:

  • Document intake. LLMs read pay stubs, bank statements, and invoices and emit structured data, replacing brittle template parsers that break whenever a bank tweaks a PDF layout.
  • Servicing. Agentic workflows triage collections queues and draft borrower communications with a recommended action attached for human approval.

The hard rule: if LLM integration touches the decision path at all, treat its output as an advisory input to the deterministic policy layer. Never let a free-text model make the final credit call unsupervised. A model that extracts "monthly income: 4,200" is helpful; a model that says "approve" as the last word is an unaudited, non-reproducible decision waiting to fail an exam.

Service boundaries: decisioning vs. servicing

A subtle but important split: decisioning and servicing change at completely different rates. You will re-tune models and cutoffs monthly. You will touch amortization and repayment-schedule logic rarely, and when you do it needs to be rock-solid. Bundling them into one service means your stable, money-critical servicing code inherits the churn of your experimental decisioning code. Keep them as separate services with a clean contract. This is the same reason a loan origination system (LOS) and a loan management system (LMS) are best built apart.

The stack that ages well

For anything touching money, pick boring, strongly-typed, well-supported technology: a JVM language, C#/.NET, or Go against a relational database, so you get the consistency guarantees lending demands. Keep Python for the model layer, where iteration speed matters and the ecosystem lives. Reach for event streaming (Kafka) once you have real-time fraud and servicing events to move between services — not before. Lending platforms run for a decade; treat the transactional stack as a ten-year commitment and reserve novelty for the model layer.

Don't train on data you don't have

The most expensive architectural mistake in lending is building ML on repayment data you have not yet collected. The pragmatic path: ship a rules-based engine with a champion/challenger slot wired in from day one, instrument everything so you accumulate clean labeled repayment data, and introduce ML models only once that data exists. Front-load compliance and data quality — they are miserable to retrofit. If you want the platform-wide version of this, including origination and integrations, read the full playbook on TechCirkle, or see how we approach custom software development for regulated systems.

Frequently Asked Questions

Why separate the model, policy, and strategy layers?

Separation lets you retrain a model without rewriting business rules and change a cutoff without redeploying code. Collapsing them means every scoring change and policy change ship as one high-risk diff, making routine model updates dangerous.

What should I log for each credit decision?

Store the exact feature values, the model version, the raw scores, every policy rule that fired, and the final reason codes. That record makes adverse-action notices defensible and lets you deterministically replay any historical decision during an audit.

Is it safe to use an LLM in the credit decision path?

Only as an advisory input to a deterministic policy layer. LLMs are excellent for document extraction and servicing communications, but a free-text model should never make the final approve or decline decision unsupervised, because that output is neither reproducible nor auditable.

Why keep decisioning and servicing as separate services?

They change at very different speeds. Decisioning models and cutoffs are re-tuned frequently, while amortization and repayment logic changes rarely and must stay stable. Separate services with a clean contract prevent experimental churn from destabilizing money-critical code.

What backend stack suits a lending platform?

Use strongly-typed, well-supported technology such as Java, C#/.NET, or Go with a relational database for the transactional core, Python for the model layer, and Kafka once real-time events justify it. Favor boring, proven choices for anything touching money.

How do I introduce machine learning without enough data?

Launch with a rules-based engine and a champion/challenger slot, instrument the system to collect clean labeled repayment data, and add ML models only once that data exists. Training models before you have repayment history is the costliest early mistake.

Top comments (0)