A full-stack machine learning demo that predicts credit risk scores and default probabilities from real financial data. Built with FastAPI (Python backend) and Next.js 14+ (React frontend), it showcases a complete production-ready pipeline from data cleaning to an animated, explainable UI.
Disclaimer: This is a data science demo built on the Kaggle Give Me Some Credit dataset. It is not for actual lending decisions.
Meridian takes an applicant's financial and credit history data — age, income, debt ratios, past delinquencies, open credit lines — and produces:
- A credit risk score from 100 to 670
- A default probability (chance of going 90+ days past due within 2 years)
- A risk category (Very Low → Very High Risk)
- A per-factor breakdown showing exactly which variables helped or hurt the score
Every prediction is fully explainable. You can see the bin each value fell into, the points it contributed, and how much it mattered relative to other factors.
credit-risk-scorecard/
├── backend/ # FastAPI Python server
│ ├── train_model.py # Trains model & exports artifacts
│ ├── main.py # FastAPI app with /predict endpoint
│ ├── requirements.txt # Python dependencies
│ └── artifacts/
│ └── scorecard_artifacts.pkl # Trained model, bins, scorecard table
├── frontend/ # Next.js 14+ App Router
│ ├── app/
│ │ ├── page.tsx # Landing page
│ │ ├── predict/page.tsx # Wizard + results
│ │ ├── api/predict/route.ts # Proxy to FastAPI
│ │ ├── layout.tsx # Root layout with pill navbar
│ │ └── globals.css # Charcoal + Mint/Coral theme
│ ├── components/
│ │ ├── wizard-form.tsx # 3-step input wizard
│ │ ├── result-card.tsx # Score display + breakdown
│ │ ├── theme-toggle.tsx # Sun/moon icon switcher
│ │ ├── theme-provider.tsx # Dark mode context
│ │ └── app-logo.tsx # Gauge dial SVG logo
│ └── public/
│ └── logo.svg # Standalone logo asset
├── Data/
│ └── cs-training.csv # 150K training records
└── credit-risk-scorecard.ipynb # Original Jupyter notebook
| Technology | Purpose |
|---|---|
| FastAPI | High-performance async API framework |
| Pydantic | Request/response validation |
| scikit-learn | Logistic Regression model |
| optbinning | Optimal binning with monotonic constraints |
| pandas / numpy | Data preprocessing |
| pickle | Model artifact serialization |
| Technology | Purpose |
|---|---|
| Next.js 14+ | App Router, React Server Components |
| TypeScript | Type safety across the stack |
| Tailwind CSS | Utility-first styling |
| shadcn/ui | Accessible, composable UI primitives |
| Framer Motion | Entrance animations, page transitions, micro-interactions |
| Geist | Sans-serif font (Vercel) |
- Animated hero with a gauge dial SVG that draws itself in
- Mini chart preview of what the results look like
- 4-step pipeline explanation (Clean → Bin → WoE → Score)
- Interactive preview card with score, probability, and factor breakdown
- Staggered scroll-reveal animations on every section
- 3-step wizard with slide transitions between steps:
- Personal Information (Age, Income, Dependents)
- Credit Profile (Utilization, Debt Ratio, Open Lines, Real Estate)
- Payment History (30-59, 60-89, 90+ day delinquencies)
- Real-time field validation with animated error messages
- Animated progress indicators per step
- Score counter that animates from 0 to the final score
- Circular gauge arc that draws itself in
- Risk badge with color-coded category
- Probability bar that grows to the score position
- Factor breakdown with staggered reveals:
- Green section: factors helping the score
- Red section: factors hurting the score
- Each bar grows from 0 to its contribution percentage
- Detailed metrics grid with staggered entrance
- Light / Dark mode toggle in the navbar
- Persists preference in
localStorage - Respects system preference on first visit
- Charcoal + Mint/Coral color palette adapts to both modes
- Floating pill-shaped navbar with backdrop blur
- Gauge dial SVG logo next to "Meridian" text
- Mint "Try the predictor" button
- Animated sun/moon theme toggle
The model was trained on the Give Me Some Credit dataset (150,000 records).
- Median imputation for missing
MonthlyIncomeandNumberOfDependents - Missing flags created to preserve the signal of missingness
- 609 exact duplicates removed
- Outliers in
RevolvingUtilizationOfUnsecuredLinesandDebtRatiocapped at 99th percentile
Each of the 10 numeric variables is split into risk segments using OptimalBinning with monotonic constraints. This ensures each bin makes statistical sense and maintains a monotonic relationship with default risk.
Each bin is converted to a WoE value:
WoE = ln( (% of non-defaulters in bin) / (% of defaulters in bin) )
This linearizes the log-odds relationship and makes the model transparent.
A logistic regression model is trained on the WoE-transformed features. Coefficients are stable, interpretable, and regulator-friendly.
The model's log-odds output is converted to a 100–670 score:
Score = Offset - Factor × ln(Odds)
Where:
- Base Score = 385
- PDO = 40 (Points to Double the Odds)
- Base Odds = 1:19
Higher score = Lower risk.
Accepts applicant data and returns the prediction.
Request:
{
"RevolvingUtilizationOfUnsecuredLines": 0.5,
"age": 35,
"NumberOfTime30_59DaysPastDueNotWorse": 0,
"DebtRatio": 0.3,
"MonthlyIncome": 5000,
"NumberOfOpenCreditLinesAndLoans": 5,
"NumberOfTimes90DaysLate": 0,
"NumberRealEstateLoansOrLines": 1,
"NumberOfTime60_89DaysPastDueNotWorse": 0,
"NumberOfDependents": 2
}Response:
{
"score": 670,
"probability": 0.0484,
"risk_category": "Very Low Risk",
"risk_color": "emerald",
"breakdown": [
{
"variable": "RevolvingUtilizationOfUnsecuredLines",
"value": 0.5,
"bin": "[0.49, 0.70)",
"points": -13.22,
"contribution_pct": 82.0
}
],
"base_score": 385.0,
"total_points": 21.45
}Returns service status.
{
"status": "ok",
"model_loaded": true
}- Python 3.10+
- Node.js 18+
git clone <repo-url>
cd credit-risk-scorecardcd backend
# Install dependencies
pip install -r requirements.txt
# Train and export model artifacts (one-time)
python train_model.py
# Start the server
python -m uvicorn main:app --reload --port 8000The API will be available at http://127.0.0.1:8000.
cd frontend
# Install dependencies
npm install
# Start dev server
npm run devThe frontend will be available at http://localhost:3000 (or the next available port).
Navigate to http://localhost:3000 and click "Try the predictor".
cd backend
python -m uvicorn main:app --host 0.0.0.0 --port 8000cd frontend
npm run build
npm start| File | Purpose |
|---|---|
train_model.py |
Loads cs-training.csv, cleans data, fits OptimalBinning models, trains logistic regression, exports scorecard_artifacts.pkl |
main.py |
FastAPI app. Loads artifacts, defines Pydantic schemas, implements /predict and /health endpoints |
requirements.txt |
Python dependencies |
artifacts/scorecard_artifacts.pkl |
Pickled dictionary containing: binning models, LR model, selected variables, scorecard table, preprocessing params, scoring params |
| File | Purpose |
|---|---|
app/page.tsx |
Landing page with hero, pipeline strip, feature list, preview card, CTA, footer |
app/predict/page.tsx |
Predictor page with wizard form and result card switching |
app/layout.tsx |
Root layout with Geist font, pill navbar, theme provider |
app/globals.css |
Charcoal + Mint/Coral CSS variables, light/dark themes |
components/wizard-form.tsx |
Multi-step form with Framer Motion slide transitions |
components/result-card.tsx |
Animated score display with count-up, gauge draw-in, staggered breakdown |
components/theme-provider.tsx |
React context for light/dark mode with localStorage persistence |
components/theme-toggle.tsx |
Animated sun/moon icon button |
components/app-logo.tsx |
Inline gauge dial SVG logo |
public/logo.svg |
Standalone logo asset |
This is the industry standard for credit scorecards because it is:
- Transparent — regulators can inspect bin contributions
- Stable — monotonic bins prevent overfitting
- Interpretable — each feature's contribution is a simple linear term
- FastAPI provides automatic API documentation (
/docs), type validation, and high performance - Next.js App Router enables server-side rendering for the landing page while keeping the wizard fully client-side
The frontend calls /api/predict (an internal Next.js Route Handler) which proxies to FastAPI. This keeps the backend URL server-side and avoids CORS issues.
If you see TypeError: check_array() got an unexpected keyword argument 'force_all_finite', downgrade scikit-learn:
pip install "scikit-learn<1.6"If uvicorn fails with ModuleNotFoundError: No module named 'fastapi', use python -m uvicorn instead of the bare uvicorn command to ensure the correct Python interpreter is used.
- Batch Scoring — Upload a CSV and receive a scored file
- Authentication — Add login/session management for production use
- Database — Store predictions in PostgreSQL for audit trails
- Monitoring — Track PSI (Population Stability Index) and model drift
- Dark Mode Refinement — Further polish dark theme colors
- Animations — Add Framer Motion page transitions between routes
Built using the Give Me Some Credit dataset from Kaggle.
This project is for educational and demonstration purposes only.