Credit Risk Assessor
ML-powered credit scoring tool that replaces gut-feel lending decisions with an auditable, explainable model. Returns a default probability, a 300–850 score, a risk tier, and SHAP-driven explanations that tell loan officers exactly which factors drove the decision.
Overview
Credit Risk Assessor is an ML-powered credit scoring tool built for Kenyan microfinance lenders. It takes 13 applicant features — income, employment, loan details, credit history — and returns a default probability, a 300–850 credit score, a risk tier, and a SHAP-based explanation of which factors drove the decision.
The goal was to replace gut-feel lending decisions with an auditable, explainable model that loan officers can actually trust.

The Problem
Loan default rates at microfinance institutions are high partly because credit decisions rely heavily on a loan officer's intuition. There's no standardised scoring, no audit trail for decisions, and no way to explain to an applicant why their application was declined.
A model that just outputs a number isn't enough — it needs to explain itself.
Technical Decisions
Random Forest with SHAP explainability
A Random Forest classifier was chosen for its robustness to missing data and its native compatibility with SHAP's TreeExplainer, which runs fast on tree-based models. The trained model achieves 86.4% accuracy and 93.0% ROC-AUC on the test set.
SHAP values are computed per applicant and mapped to human-readable feature names. The top contributing factors (positive and negative) are surfaced to the loan officer alongside actionable recommendations — if loan-to-income ratio is the top risk factor, the system suggests a lower loan amount.
Normalised PostgreSQL schema
Five related tables — applicants, loans, credit histories, risk assessments, and applications — with proper foreign keys. A single join query returns a flat denormalised row for Streamlit's data model, but the underlying schema stays clean and queryable.
Imbalanced-Learn for class imbalance
Loan default datasets are typically imbalanced — far fewer defaults than repayments. SMOTE oversampling during training was used to prevent the model from learning to always predict "no default."
Credit score standardisation
Default probability maps to a 300–850 scale (FICO-like) so the output is interpretable by institutions already familiar with credit scoring conventions.
Streamlit with caching
@st.cache_resource loads the model once per session; @st.cache_data caches feature importance and metrics. This keeps the UI responsive even on constrained hosting.
Key Features
- Single applicant scoring — real-time default probability, credit score, and risk tier (Low / Review / High)
- SHAP explanations — per-applicant feature importance showing exactly what drove the score
- Actionable recommendations — context-specific advice generated from top SHAP contributors
- Batch CSV processing — score multiple applicants from an uploaded file
- Applications management — browse, search, filter, and update application status
- Reviewer annotations — loan officers can add notes and change status (Approved / Rejected)
- Analytics dashboard — risk distribution, application trends, loan intent breakdown
- Model metrics — accuracy, precision, recall, F1, ROC-AUC displayed in-app


Tradeoffs
Streamlit is fast to build with but limits UI customisation. The interface works well for internal loan officers but isn't polished enough for member-facing use. A React frontend consuming a FastAPI model service would be more flexible.
The model is trained on a fixed dataset. It will need retraining as the institution's portfolio grows and repayment patterns shift — there's no automated retraining pipeline yet.
Retrospective
SHAP explainability was the feature that got the most positive feedback. Loan officers immediately understood why a decision was made, which built trust in the model faster than accuracy numbers alone ever would. If I rebuilt this, I'd invest in a feature monitoring pipeline early — model drift is the slow-moving risk that's easy to ignore until it's a problem.