Click to expand / collapse
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#2E86C1','primaryTextColor':'#fff','primaryBorderColor':'#1B4F72','lineColor':'#2E86C1','secondaryColor':'#F39C12','tertiaryColor':'#27AE60'}}}%%
flowchart LR
A["🗃️ Source Data<br/><i>customers · loans · card_transactions</i>"] --> B[("🗄️ PostgreSQL<br/><i>3 tables · 1.15M records</i>")]
A --> C["🐼 Pandas<br/>DataFrames"]
B --> D["📊 SQL Reporting Layer<br/><i>Business Qs A–E</i>"]
C --> E["📈 Plotly + Seaborn<br/><i>Visual Analytics & Storytelling</i>"]
D --> F{"🧠 Insights"}
E --> F
F --> G["💼 Portfolio<br/>Decisions"]
F --> H["🛡️ Fraud<br/>Controls"]
F --> I["🎯 Unified<br/>Risk Score"]
LendScope is a full-year FY2026 analytics engagement for a mid-sized retail bank. It blends a loan book (250 K applications) with a card transaction book (750 K transactions) and delivers insight across two complementary tracks:
| Track | Deliverable | Purpose |
|---|---|---|
| 🐍 Visual Analytics (Python) | LendScope_FY2026_Analysis.ipynb |
Interactive charts, trend storytelling, cross-product risk narratives for business stakeholders |
| 🐘 Reporting Layer (PostgreSQL) | sql/03_business_questions.sql |
Production-ready SQL queries powering BI dashboards and scheduled reports |
Together they cover 32 business questions spanning core loan KPIs, portfolio quality, regional and segment behaviour, card fraud, and unified customer value.
| KPI | Value | KPI | Value |
|---|---|---|---|
| 📝 Total Applications | 250,000 | 💵 Total Funded | $2,647.91 M |
| 📥 Amount Received | $537.39 M | 📈 Avg Interest Rate | 11.95 % |
| ⚖️ Avg DTI | 19.04 % | ✅ Good Loans | 87.60 % |
| ⛔ Charged Off | 12.40 % | 📆 MTD Applications | 24,929 |
| KPI | Value | KPI | Value |
|---|---|---|---|
| 🛒 Purchase Volume | $69.66 M | 🧾 Transactions | 638,408 |
| 🚨 Fraud Rate | 0.439 % | 💸 Fraud Exposure | $524,529 |
| 🟦 Credit Card Spend | $38.43 M | 🟨 Debit Card Spend | $31.23 M |
| 🎯 Avg Ticket Size | ~$109 | 🌐 Top Fraud Channel | ONLINE |
🏦 lendscope/
│
├── 📄 README.md ← you are here
├── 📄 .gitignore
├── 📄 requirements.txt
│
├── 📂 data/
│ ├── 📊 customers.csv ← 150,000 records · 13 columns
│ ├── 📊 loans.csv ← 250,000 records · 27 columns
│ └── 📊 card_transactions.csv ← 750,000 records · 14 columns
│
├── 📂 sql/
│ ├── 🗄️ 01_schema.sql ← tables + indexes
│ ├── 📥 02_load_data.sql ← \copy from CSV
│ └── ❓ 03_business_questions.sql ← all 32 queries (A–E)
│
├── 📂 notebooks/
│ └── 🐍 LendScope_FY2026_Analysis.ipynb ← visual analytics & storytelling
│
└── 📂 reports/
└── 📈 figures/ ← exported chart PNGs
├── 🖼️ good_vs_bad_loans.png
├── 🖼️ charge_off_by_grade.png
├── 🖼️ monthly_trend.png
├── 🖼️ top_states_funded.png
├── 🖼️ loan_term_distribution.png
├── 🖼️ employment_length.png
├── 🖼️ loan_purpose.png
├── 🖼️ home_ownership_treemap.png
├── 🖼️ card_spend_by_type.png
├── 🖼️ monthly_card_spend.png
├── 🖼️ merchant_categories.png
├── 🖼️ fraud_by_channel.png
└── 🖼️ score_band_risk.png
| Layer | Technology | Why |
|---|---|---|
| 🐍 Language | Python 3.13 | Primary analytics language |
| 🐼 Data wrangling | pandas 3.0 · numpy 2.5 | Fast columnar ops on 1.15 M records |
| 📊 Static charts | matplotlib 3.11 · seaborn 0.13 | Publication-ready figures |
| 🌐 Interactive charts | plotly 7.1 | Dashboards + hover drill-down |
| 🗄️ Database | PostgreSQL 16 | Reporting layer, indexing, scheduled queries |
| 📓 Notebook | Jupyter | Narrative + reproducible visual analytics |
git clone https://github.com/<your-username>/lendscope.git
cd lendscope
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtcreatedb lendscope
psql -U postgres -d lendscope -f sql/01_schema.sql
psql -U postgres -d lendscope -f sql/02_load_data.sql
psql -U postgres -d lendscope -f sql/03_business_questions.sql
⚠️ \copyis a psql client-side meta-command — run viapsql, not a generic SQL runner.
jupyter notebook notebooks/LendScope_FY2026_Analysis.ipynberDiagram
CUSTOMERS ||--o{ LOANS : "applies for"
CUSTOMERS ||--o{ CARD_TRANSACTIONS : "makes"
CUSTOMERS {
varchar customer_id PK
varchar first_name
varchar last_name
char gender
date date_of_birth
char address_state
varchar city
varchar employment_title
varchar employment_length
varchar home_ownership
numeric annual_income
smallint credit_score
date signup_date
}
LOANS {
varchar loan_id PK
varchar customer_id FK
char address_state
varchar application_type
varchar emp_length
varchar emp_title
char grade
varchar sub_grade
varchar home_ownership
varchar purpose
date issue_date
varchar loan_status
smallint term_months
varchar verification_status
numeric annual_income
numeric dti
numeric int_rate
numeric installment
numeric loan_amount
numeric funded_amount
smallint total_acc
numeric total_payment
numeric total_rec_prncp
numeric total_rec_int
date last_payment_date
date next_payment_date
date last_credit_pull_date
}
CARD_TRANSACTIONS {
varchar transaction_id PK
varchar customer_id FK
varchar card_type
smallint card_last4
date transaction_date
time transaction_time
varchar merchant_category
varchar transaction_type
varchar channel
numeric amount
char state
varchar city
smallint is_international
smallint is_fraud
}
The Jupyter notebook LendScope_FY2026_Analysis.ipynb is the storytelling layer of the project — where data becomes decisions. It focuses on:
- 📉 Time-series trend analysis — monthly origination, funded vs. received, card-spend seasonality
- 🗺️ Regional and segment heat-maps — state-level funded volume, employment-tenure risk, home-ownership mix
- 🎯 Interactive Plotly dashboards — hover drill-downs across grades, purposes, and merchant categories
- 🧠 Cross-product risk narrative — how credit score correlates with both default and fraud
- 💎 Customer-value views — 360° relationship snapshots for VIP identification
Below is a snapshot of the visual library included in the notebook:
| Chart | Type | Interactive |
|---|---|---|
| Charge-Off Rate by Credit Grade | Bar + colour scale | ✅ Plotly |
| Monthly Funded vs. Received | Dual-line | ✅ Plotly |
| Monthly Applications Trend | Area | ✅ Plotly |
| Top 15 States by Funded Amount | Bar + heat colour | ✅ Plotly |
| Loan Term Distribution | Donut | ✅ Plotly |
| Applications by Employment Length | Bar + heat colour | ✅ Plotly |
| Funded Amount by Loan Purpose | Bar (ranked) | ✅ Plotly |
| Home Ownership Breakdown | Treemap | ✅ Plotly |
| Credit vs. Debit Spend | Donut | ✅ Plotly |
| Monthly Card Spend by Type | Multi-line | ✅ Plotly |
| Merchant Category Spend | Bar (ranked) | ✅ Plotly |
| Fraud Rate by Channel × Card | Grouped bar | ✅ Plotly |
| Credit Score Band vs. Risk | Dual-axis bar | ⚡ Matplotlib |
The SQL reporting layer in sql/03_business_questions.sql is the BI-ready component of LendScope — designed to be dropped into any dashboard tool (Power BI, Tableau, Metabase, Looker) or scheduled reporting job.
Each query is indexed, parameterisable, and written for readability with dashboard-ready output columns.
A1–A6 · Totals, MTD, Averages
SELECT COUNT(*) AS total_applications FROM loans;
SELECT COUNT(*) AS mtd_applications
FROM loans
WHERE date_trunc('month', issue_date) =
(SELECT date_trunc('month', MAX(issue_date)) FROM loans);| Metric | Value |
|---|---|
| 📝 Total Applications | 250,000 |
| 📆 MTD Applications | 24,929 |
| Metric | Value |
|---|---|
| 💵 Total Funded | $2,647,905,650 |
| 📆 MTD Funded | $264,799,500 |
| Metric | Value |
|---|---|
| 📥 Total Received | $537,388,700 |
| 📆 MTD Received | $19,179,081 |
💡 The Received / Funded ratio is only 20.3 % — the portfolio is young. Most FY2026 vintage loans are still Current, not yet Fully Paid.
11.95 % — weighted across all grades, reflects healthy pricing.
19.04 % — comfortably under the typical 36 % retail-lending ceiling.
SELECT
COUNT(*) AS total_applications,
ROUND(SUM(funded_amount),2) AS total_funded_amount,
ROUND(SUM(total_payment),2) AS total_amount_received,
ROUND(AVG(int_rate)*100,2) AS avg_interest_rate_pct,
ROUND(AVG(dti)*100,2) AS avg_dti_pct
FROM loans;B1–B3 · Bucketing, Net Gain/Loss, Grade Risk
SELECT
CASE WHEN loan_status IN ('Fully Paid','Current') THEN 'Good Loan' ELSE 'Bad Loan' END AS bucket,
COUNT(*) AS n,
ROUND(100.0*COUNT(*)/SUM(COUNT(*)) OVER (), 2) AS pct
FROM loans GROUP BY 1;| Bucket | Applications | Share |
|---|---|---|
| ✅ Good Loan (Fully Paid + Current) | 218,996 | 87.60 % |
| ⛔ Bad Loan (Charged Off) | 31,004 | 12.40 % |
| Bucket | Funded | Received | Net |
|---|---|---|---|
| ✅ Good | $2,317.71 M | $516.56 M | +$516.56 M |
| ⛔ Bad | $330.19 M | $20.83 M | +$20.83 M |
📸 Chart — Good vs. Bad Loan Split
![]()
Two-panel figure: pie chart showing application share (87.6% good / 12.4% bad) alongside a grouped bar chart comparing funded vs received amounts by loan bucket.
| Grade | Total Loans | Charge-Off Rate | Avg Interest Rate |
|---|---|---|---|
| 🟢 A | 39,809 | 3.07 % | 6.30 % |
| 🟢 B | 60,088 | 6.10 % | 8.60 % |
| 🟡 C | 64,937 | 11.07 % | 11.50 % |
| 🟠 D | 45,208 | 17.08 % | 14.81 % |
| 🔴 E | 25,024 | 23.92 % | 18.20 % |
| 🔴 F | 9,959 | 32.30 % | 21.90 % |
| 🔴 G | 4,975 | 40.30 % | 25.92 % |
📸 Chart — Charge-Off Rate by Credit Grade
![]()
Vertical bar chart with grades A → G on the x-axis and charge-off rate on the y-axis. Bar colour encodes average interest rate (light → dark red), showing pricing tracks risk closely.
✅ Insight: Grade is well-calibrated — every grade step costs ~5 pp of charge-off, and pricing (interest rate) tracks it closely.
C1–C6 · Trend, Region, Term, Employment, Purpose, Home Ownership
SELECT
date_trunc('month', issue_date)::date AS issue_month,
COUNT(*) AS applications,
ROUND(SUM(funded_amount), 2) AS funded_amount,
ROUND(SUM(total_payment), 2) AS amount_received
FROM loans
GROUP BY 1
ORDER BY 1;📸 Chart — Monthly Funded vs Amount Received (2026)
![]()
Dual-line chart (Jan → Dec) showing both funded amount and amount received rising steadily into Q4, with a pronounced November–December peak.
💡 Executive view: Both funded amount and amount received rise steadily into Q4, with a pronounced November–December peak.
SELECT
address_state,
COUNT(*) AS applications,
ROUND(SUM(funded_amount), 2) AS total_funded_amount,
ROUND(AVG(int_rate)*100, 2) AS avg_int_rate_pct,
ROUND(100.0 * SUM(CASE WHEN loan_status='Charged Off' THEN 1 ELSE 0 END) / COUNT(*), 2) AS charge_off_rate_pct
FROM loans
GROUP BY address_state
ORDER BY total_funded_amount DESC
LIMIT 15;📸 Chart — Top 15 States by Funded Amount
![]()
Bar chart of the top 15 states by total funded amount, with bar colour encoding charge-off rate (green = low risk, red = high risk).
| Rank | State | Applications | Funded ($) | Charge-Off % |
|---|---|---|---|---|
| 1 | 🥇 CA | — | highest | ~12 % |
| 2 | 🥈 TX | — | high | ~12 % |
| 3 | 🥉 NY | — | high | ~12 % |
| 4–15 | FL · IL · PA · OH · GA · NC · MI · NJ · VA · WA · AZ · MA | ... | ... | ... |
SELECT
term_months,
COUNT(*) AS applications,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) AS pct_of_applications,
ROUND(SUM(funded_amount), 2) AS total_funded_amount
FROM loans
GROUP BY term_months
ORDER BY term_months;📸 Chart — Loan Term Distribution (36 vs 60 months)
![]()
Donut chart with a 0.5 hole showing the split between 36-month and 60-month terms.
| Term | Applications | Share |
|---|---|---|
| 🍩 36 months | ~176,000 | ~70 % |
| 🍩 60 months | ~74,000 | ~30 % |
SELECT
emp_length,
COUNT(*) AS applications,
ROUND(SUM(funded_amount), 2) AS total_funded_amount,
ROUND(100.0 * SUM(CASE WHEN loan_status='Charged Off' THEN 1 ELSE 0 END) / COUNT(*), 2) AS charge_off_rate_pct
FROM loans
GROUP BY emp_length
ORDER BY CASE emp_length
WHEN '< 1 year' THEN 0 WHEN '1 year' THEN 1 WHEN '2 years' THEN 2
WHEN '3 years' THEN 3 WHEN '4 years' THEN 4 WHEN '5 years' THEN 5
WHEN '6 years' THEN 6 WHEN '7 years' THEN 7 WHEN '8 years' THEN 8
WHEN '9 years' THEN 9 ELSE 10 END;📸 Chart — Applications by Employment Length
![]()
Bar chart of application counts by employment tenure, with bar colour encoding charge-off rate.
✅ Insight: 10+ years tenure → lowest charge-off. < 1 year tenure → highest charge-off. Strong underwriting signal.
SELECT
purpose,
COUNT(*) AS applications,
ROUND(SUM(funded_amount), 2) AS total_funded_amount,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) AS pct_of_applications
FROM loans
GROUP BY purpose
ORDER BY total_funded_amount DESC;📸 Chart — Total Funded Amount by Loan Purpose
![]()
Ranked bar chart of total funded amount by loan purpose, ordered descending.
Top purposes by funded amount:
- 🥇
debt_consolidation - 🥈
credit_card - 🥉
home_improvement major_purchase·small_business·medical·car·moving·other·vacation·house·renewable_energy
SELECT
home_ownership,
COUNT(*) AS applications,
ROUND(SUM(funded_amount), 2) AS total_funded_amount,
ROUND(SUM(total_payment), 2) AS total_amount_received,
ROUND(100.0 * SUM(CASE WHEN loan_status='Charged Off' THEN 1 ELSE 0 END) / COUNT(*), 2) AS charge_off_rate_pct
FROM loans
GROUP BY home_ownership
ORDER BY total_funded_amount DESC;📸 Chart — Funded Amount by Home Ownership
![]()
Treemap visualising funded amount by home-ownership category (MORTGAGE, RENT, OWN, OTHER), with tile colour encoding total amount received.
Groups: MORTGAGE · RENT · OWN · OTHER.
D1–D8 · Spend, Seasonality, Categories, Fraud, VIPs
SELECT
card_type,
COUNT(*) AS num_transactions,
ROUND(SUM(amount), 2) AS total_spend,
ROUND(AVG(amount), 2) AS avg_transaction_amount
FROM card_transactions
WHERE transaction_type = 'PURCHASE'
GROUP BY card_type;📸 Chart — Total Purchase Spend: Credit vs Debit
![]()
Donut chart showing the split of purchase spend between CREDIT (blue) and DEBIT (amber) cards.
| Card Type | Transactions | Total Spend | Avg Ticket |
|---|---|---|---|
| 🟦 CREDIT | 351,396 | $38,425,728 | $109.35 |
| 🟨 DEBIT | 287,012 | $31,231,838 | $108.82 |
SELECT
date_trunc('month', transaction_date)::date AS txn_month,
card_type,
ROUND(SUM(amount), 2) AS total_spend,
COUNT(*) AS num_transactions
FROM card_transactions
WHERE transaction_type = 'PURCHASE'
GROUP BY 1, 2
ORDER BY 1, 2;📸 Chart — Monthly Card Spend by Type (2026)
![]()
Multi-line chart showing monthly purchase spend for CREDIT and DEBIT cards. Both lines rise sharply into November–December.
Both card types rise sharply into Nov–Dec, mirroring retail holiday seasonality.
SELECT
merchant_category,
COUNT(*) AS num_transactions,
ROUND(SUM(amount), 2) AS total_spend,
ROUND(AVG(amount), 2) AS avg_transaction_amount
FROM card_transactions
WHERE transaction_type = 'PURCHASE'
GROUP BY merchant_category
ORDER BY total_spend DESC;📸 Chart — Total Spend by Merchant Category
![]()
Ranked bar chart of total purchase spend across all 13 merchant categories.
Ranked descending (top 5):
- 🥇 Travel
- 🥈 Electronics
- 🥉 Online Retail
- Utilities
- Healthcare
SELECT
card_type,
channel,
COUNT(*) AS total_transactions,
SUM(is_fraud) AS fraud_transactions,
ROUND(100.0 * SUM(is_fraud) / COUNT(*), 3) AS fraud_rate_pct,
ROUND(SUM(CASE WHEN is_fraud = 1 THEN amount ELSE 0 END), 2) AS fraud_dollar_exposure
FROM card_transactions
GROUP BY card_type, channel
ORDER BY fraud_rate_pct DESC;📸 Chart — Fraud Rate by Channel and Card Type
![]()
Grouped bar chart comparing CREDIT vs DEBIT fraud rates across ATM, MOBILE, ONLINE, and POS channels. The ONLINE channel dominates for both card types.
| Card | Channel | Transactions | Fraud Txns | Fraud Rate % |
|---|---|---|---|---|
| 🟦 CREDIT | ONLINE | 133,500 | 1,094 | 0.819 % |
| 🟨 DEBIT | ONLINE | 109,326 | 870 | 0.796 % |
| 🟨 DEBIT | MOBILE | 64,696 | 189 | 0.292 % |
| 🟨 DEBIT | ATM | 15,494 | 42 | 0.271 % |
| 🟦 CREDIT | MOBILE | 79,199 | 211 | 0.266 % |
| 🟨 DEBIT | POS | 147,732 | 390 | 0.264 % |
| 🟦 CREDIT | POS | 181,365 | 460 | 0.254 % |
| 🟦 CREDIT | ATM | 18,688 | 38 | 0.203 % |
SELECT
state,
COUNT(*) AS total_transactions,
SUM(is_fraud) AS fraud_transactions,
ROUND(100.0 * SUM(is_fraud) / COUNT(*), 3) AS fraud_rate_pct
FROM card_transactions
GROUP BY state
ORDER BY fraud_rate_pct DESC
LIMIT 15;SELECT
merchant_category,
card_type,
COUNT(*) AS num_transactions,
ROUND(SUM(amount), 2) AS total_spend
FROM card_transactions
WHERE transaction_type = 'PURCHASE'
GROUP BY merchant_category, card_type
ORDER BY merchant_category, card_type;Behavioural split by merchant category — Credit skews Travel/Electronics, Debit skews Grocery/Utilities.
SELECT
channel,
COUNT(*) AS num_transactions,
ROUND(SUM(amount), 2) AS total_spend,
ROUND(AVG(amount), 2) AS avg_transaction_amount,
ROUND(100.0 * SUM(is_fraud) / COUNT(*), 3) AS fraud_rate_pct
FROM card_transactions
GROUP BY channel
ORDER BY total_spend DESC;| Channel | Transactions | Total Spend | Avg Ticket | Fraud Rate % |
|---|---|---|---|---|
| ONLINE | highest | highest | — | ~0.8 % |
| POS | ~329 K | mid | mid | ~0.26 % |
| MOBILE | ~145 K | mid | mid | ~0.28 % |
| ATM | ~34 K | lowest | lowest | ~0.24 % |
SELECT
t.customer_id,
c.first_name, c.last_name, c.address_state, c.credit_score,
COUNT(*) AS num_transactions,
ROUND(SUM(t.amount), 2) AS total_spend
FROM card_transactions t
JOIN customers c ON c.customer_id = t.customer_id
WHERE t.transaction_type = 'PURCHASE'
GROUP BY t.customer_id, c.first_name, c.last_name, c.address_state, c.credit_score
ORDER BY total_spend DESC
LIMIT 20;Used for VIP / cross-sell targeting lists.
🚨 Overall fraud rate: 0.439 % · $524,528.90 total exposure.
E1–E3 · Fraud×Default, Customer 360, Score-Band Risk
WITH loan_risk AS (
SELECT customer_id,
MAX(CASE WHEN loan_status = 'Charged Off' THEN 1 ELSE 0 END) AS has_charge_off
FROM loans
GROUP BY customer_id
)
SELECT
lr.has_charge_off,
COUNT(*) AS total_transactions,
ROUND(100.0 * SUM(t.is_fraud) / COUNT(*), 3) AS fraud_rate_pct
FROM card_transactions t
JOIN loan_risk lr ON lr.customer_id = t.customer_id
GROUP BY lr.has_charge_off;| Has Charge-Off? | Fraud Rate % |
|---|---|
| ❌ No | 0.429 % |
| ✅ Yes | 0.433 % |
📌 Only a ~1 % relative difference — loan default status is not a strong predictor of card fraud. Fraud and credit risk are separable signals.
SELECT
c.customer_id, c.first_name, c.last_name, c.address_state, c.credit_score,
COALESCE(l.total_funded, 0) AS total_loan_funded,
COALESCE(l.total_loan_payment, 0) AS total_loan_repaid,
COALESCE(t.total_card_spend, 0) AS total_card_spend,
COALESCE(l.total_funded, 0) + COALESCE(t.total_card_spend, 0) AS total_relationship_value
FROM customers c
LEFT JOIN (
SELECT customer_id, SUM(funded_amount) AS total_funded, SUM(total_payment) AS total_loan_payment
FROM loans GROUP BY customer_id
) l ON l.customer_id = c.customer_id
LEFT JOIN (
SELECT customer_id, SUM(amount) AS total_card_spend
FROM card_transactions WHERE transaction_type = 'PURCHASE' GROUP BY customer_id
) t ON t.customer_id = c.customer_id
ORDER BY total_relationship_value DESC
LIMIT 25;Top 5 customers by relationship value:
| # | Customer | State | Score | Loan Funded | Card Spend | Total |
|---|---|---|---|---|---|---|
| 1 | Aisha White | FL | 707 | $274,100 | $148.83 | $274,248.83 |
| 2 | Liam Thompson | CA | 793 | $270,850 | $224.56 | $271,074.56 |
| 3 | Priya Gonzalez | IL | 758 | $255,700 | $645.36 | $256,345.36 |
| 4 | Mia Scott | MA | 724 | $240,000 | $503.18 | $240,503.18 |
| 5 | James Lopez | WV | 762 | $228,700 | $409.63 | $229,109.63 |
WITH bands AS (
SELECT customer_id,
CASE
WHEN credit_score < 580 THEN '1. Poor (<580)'
WHEN credit_score < 670 THEN '2. Fair (580-669)'
WHEN credit_score < 740 THEN '3. Good (670-739)'
WHEN credit_score < 800 THEN '4. Very Good (740-799)'
ELSE '5. Exceptional (800+)'
END AS score_band
FROM customers
)
SELECT
b.score_band,
ROUND(100.0 * SUM(CASE WHEN l.loan_status = 'Charged Off' THEN 1 ELSE 0 END)
/ NULLIF(COUNT(l.loan_id), 0), 2) AS loan_charge_off_rate_pct,
ROUND(100.0 * SUM(t.is_fraud) / NULLIF(COUNT(t.transaction_id), 0), 3) AS card_fraud_rate_pct
FROM bands b
LEFT JOIN loans l ON l.customer_id = b.customer_id
LEFT JOIN card_transactions t ON t.customer_id = b.customer_id
GROUP BY b.score_band
ORDER BY b.score_band;📸 Chart — Credit Score Band vs Loan Risk & Fraud Risk
![]()
Dual-axis bar chart showing loan charge-off rate (red, left axis) and card fraud rate (purple, right axis) across five credit-score bands. Both metrics decline as score improves.
| Score Band | Loan Charge-Off % | Card Fraud % |
|---|---|---|
| 🔴 Poor (<580) | 12.26 % | 0.723 % |
| 🟠 Fair (580–669) | 12.32 % | 0.503 % |
| 🟡 Good (670–739) | 12.45 % | 0.400 % |
| 🟢 Very Good (740–799) | 12.47 % | 0.392 % |
| 🟢 Exceptional (800+) | 12.26 % | 0.382 % |
✅ Insight: Card-fraud rate drops ~47 % from Poor → Exceptional. Credit score is a genuinely shared risk signal across lending & cards.
| # | Finding | Impact |
|---|---|---|
| 1️⃣ | Underwriting grade is well-calibrated — charge-off climbs 3 % → 40 % across A → G | 🟢 Risk model working |
| 2️⃣ | Concentration risk — CA + TX + NY dominate funded volume | 🟠 Regional exposure |
| 3️⃣ | Employment tenure & home ownership are strong differentiators | 🟢 Sharpens scoring |
| 4️⃣ | Q4 seasonality is shared across loans & cards | 🔵 Plan capacity |
| 5️⃣ | Fraud is concentrated in ONLINE (card-not-present) | 🚨 3× the POS rate |
| 6️⃣ | Credit score predicts both default AND fraud | 🟣 Unify risk models |
| 🎯 Action | Why | Owner |
|---|---|---|
| 1. Tighten underwriting at grades E–G and for sub-1-year tenure / renter segments |
Grades E–G charge off at 24–40 %; the current interest-rate premium only partially covers loss-given-default. | 🧠 Risk |
| 2. Diversify origination outside CA / TX / NY | Top-3-state concentration amplifies regional macro shocks. | 📈 Strategy |
| 3. Invest in 3-D Secure, device fingerprinting, and step-up auth for ONLINE card transactions | Online fraud runs at ~0.8 % vs ~0.26 % POS — 3× lift. | 🛡️ Fraud Ops |
| 4. Build a unified customer risk score from credit score + loan performance + card behaviour | Score band predicts both default & fraud. One score → many decisions (cross-sell, credit line, collections). | 🧠 Data Science |
| 5. Pre-position Q4 capacity — underwriting throughput + fraud-ops headcount | Loans & card spend both peak in Nov–Dec. | ⚙️ Operations |
- ✅ PostgreSQL schema + indexes
- ✅ Business-question reporting layer (SQL)
- ✅ Visual analytics notebook (Pandas + Plotly)
- ✅ Cross-product risk analysis
- 🔜 Streamlit executive dashboard
- 🔜 dbt models for warehouse
- 🔜 ML default-prediction model (XGBoost)
- 🔜 Airflow DAG for monthly refresh
Released under the MIT License — see LICENSE for details.
All data used in this project is anonymised and aggregated for analysis.