Home / Projects
DA · BA-01Live

Customer Churn: Revenue at Risk

Identifying the customer segments most likely to cancel and quantifying the financial impact, so retention budget goes where it moves the needle, with a live, cross-filterable dashboard, not just a static write-up.

Data sourceTelco Customer Churn (IBM sample dataset): 7,043 customer records with contract, tenure, payment and charge attributes
ArchitectureCSV → PostgreSQL (raw → staging) → CTE/window-function segmentation views → quality gate → dashboard
StoragePostgreSQL 16 in Docker, schema and views versioned as SQL under sql/
Stack
SQLPostgreSQLCTEsWindow functionsPlaywright
StatusLive: 9 quality checks green, dashboard verified against the SQL layer to the cent

Context

A telecom business loses a meaningful share of its customer base every month. Commercial leadership needs to know which segments concentrate the most cancellation risk, and what that risk is worth in revenue, to spend the retention budget efficiently instead of across the board.

Business Problem

Which customer characteristics, namely contract type, tenure, payment method, best explain cancellation, and how much revenue is at risk in each segment? A segment can churn at a high rate and still be worth ignoring if it carries little revenue; a lower-rate segment attached to high monthly charges can matter far more. Revenue at risk answers that directly: each segment's current active MRR weighted by its own historical churn rate, a projection kept distinct from revenue already lost.

Architecture

Telco CSV · 7,043 rows raw.customers staging.customers marts · CTEs + RANK() quality gate · 9 checks Interactive dashboard

The analytical logic lives in SQL, not in the dashboard. Segmentation views are defined once in PostgreSQL with CTEs, and revenue at risk is computed with window functions (RANK() OVER (ORDER BY revenue_at_risk DESC)), so the dashboard reads from a modeled layer rather than re-deriving the numbers. A 9-check quality gate sits between staging and the marts and RAISE EXCEPTIONs on violation.

The Numbers (evidence)

All figures below come from query output on the built database, not estimation.

Overview: 7,043 customers · 26.54% churn rate · $316,986/mo active MRR · $139,131/mo already lost to churned customers.

ContractCustomersChurn rateActive MRRRevenue at risk
Month-to-month3,87542.71%$136,447$58,277/mo
One year1,47311.27%$81,698$9,207/mo
Two year1,6952.83%$98,841$2,797/mo

Month-to-month churns 15× more than two-year contracts and carries almost 5× the revenue at risk of the other two combined ($58,277 vs. $12,005/mo): 83% of the $70,281/mo at risk across all three contract types sits in this one segment.

TenureChurn rateActive MRRRevenue at riskRank by rateRank by size
0-6 months52.94%$31,172$16,502/mo1st4th
25-48 months20.39%$77,631$15,829/mo4th2nd
49+ months9.51%$145,931$13,878/mo5th1st
13-24 months28.71%$39,748$11,412/mo3rd3rd
7-12 months35.89%$22,504$8,077/mo2nd5th

The 0-6 month band has the highest churn rate, but 25-48 months ranks a close 2nd in dollar risk on a much lower rate, because it carries 2.5× the exposure. Ranking by churn rate alone would have misdirected the budget.

By payment method: Electronic check churns at 45.29%, 2.4-3.0× every other method (16.71% bank transfer, 15.24% credit card, 19.11% mailed check), and alone accounts for $43,504/mo of revenue at risk, more than the other three payment methods combined ($36,989/mo).

Highest-risk single segment (min. 30 customers): Month-to-month + 0-6 months + Electronic check: 680 customers, 67.65% churn, $9,063/mo at risk.

Quality gate: 9 checks green, including reconciliation: segment sums equal the global total on all three dimensions (no fan-out double-count), and revenue_at_risk never exceeds active_mrr in any row. The dashboard's browser-side aggregation reconciles to the SQL layer to the cent, verified with an automated Playwright check, not eyeballed.

Interactive Dashboard

Cross-filter by contract, tenure and payment method: every KPI and bar recomputes live in your browser from the same 7,043 rows the SQL model reads. Self-contained HTML/JS, no CDN, no server: open it full-screen ↗.

Methodology

Structured with CRISP-DM:

  1. Business Understanding: frame the question in revenue terms, and define "revenue at risk" before computing it.
  2. Data Understanding: profile in SQL: churn rate and distribution across contract, tenure, payment method.
  3. Data Preparation: rawstaging, with every cleansing rule commented with the problem it fixes (including the TotalCharges-blank-means-not-billed-yet rule for 11 brand-new customers).
  4. Modeling: segmentation views with CTEs; RANK() for exposure ranking; a minimum-N=30 rule gates the micro-segment table so no "top segment" claim rests on a handful of rows.
  5. Evaluation: 9 fail-loud quality checks; the dashboard is verified byte-for-byte against the SQL layer.
  6. Deployment: a self-contained interactive dashboard, plus CSV exports for Tableau Public / Power BI Desktop.

Challenges

Recommendations

Tech Stack

CategoryTool
DatabasePostgreSQL 16 (Docker Compose)
ModelingSQL: CTEs, window functions (RANK()), quality-gate DO blocks
DashboardSelf-contained HTML/JS (no CDN, no build step), verified with Playwright
BI exportsCSV, for Tableau Public / Power BI Desktop
VersioningGit / GitHub

View the code on GitHub ↗

← Previous
Interactive Dashboard + Narrative