INSIGHTS & IDEAS
arrow

Why Banks Lose Households, Not Accounts

Carl Gold’s Fighting Churn with Data opens with a simple truth: churn is the silent killer of subscription businesses. If your customers leave faster than you acquire them, nothing else matters — not your product, not your marketing, not your funding. You’re dead. You just don’t know it yet.

I’ve spent the last few years thinking about how this applies to banking. And the conclusion I’ve reached is uncomfortable: the banking industry measures churn wrong. Not slightly wrong. Categorically wrong. We’re measuring the right thing at the wrong level.

Every bank I know globally reports churn as account attrition — the percentage of individual accounts closed in a given period. It’s typically 3–4% annually. The number looks manageable. The dashboard is green.

But here’s the question that keeps me up at night: what if the unit of measurement is the problem?

Banking customers don’t exist as isolated accounts. They exist as households — a father with a savings account, a mother with a salary account, children with junior accounts, a business with an operating account. When the father closes his savings account, the account attrition dashboard registers one small closure. What it doesn’t register: the mother’s engagement dropped 40% the same month. The business account’s interbank transfers doubled. Within 18 months, the entire household — worth $25,000-$35,000/year in combined revenue — is gone.

The account attrition was 3–4%. The household erosion, when I’ve modeled it across different banks and markets, is typically 3–4x higher. And of eroding households, roughly 60% lose the entire relationship within 18 months of the first signal.

A bank in this position isn’t losing 3–4% of accounts. It’s losing 12–15% of its most valuable relationships — the multi-product households that generate 3–5x the revenue of single-account customers. The churn dashboard is green. The franchise is bleeding.

This is the churn you’re not measuring.

Why Banking Churn is Different

Gold’s churn framework — behavioral metrics, cohort analysis, metric scoring, logistic regression, XGBoost — has been adopted by hundreds of SaaS companies. It works brilliantly for products with clear subscription boundaries: you’re subscribed, or you’re not. You renewed, or you didn’t.

Banking breaks every assumption in that framework.

Churn is not binary — it’s a decay process. A SaaS customer cancels on a specific date. A banking customer decays over months. Their deposit balance declines by $500/month. Their login frequency drops from 12x/month to 3x/month. Their salary credit moves to another bank. Then one day the account has $12 left and someone closes it. Was the churn date the day the account closed? Or the day the salary credit moved, nine months earlier?

The observation window is multi-horizon. Gold recommends measuring behavioral metrics over a fixed window — typically 28 days — before the churn date. In banking, the signals operate on at least four timescales simultaneously: salary credits are monthly, spending patterns are weekly, savings behavior is seasonal, and life events are irregular. A 28-day window catches some and misses others. You need features at 7-day, 30-day, 90-day, and 365-day horizons — simultaneously.

The unit of analysis is wrong. Gold measures churn at the account level — one subscription, one customer, one churn event. In banking, the meaningful unit isn’t the account. It’s the household. When a father closes his savings account, it’s a $4,500/year loss. When his wife follows three months later, it’s another $3,200. When the business account moves six months after that, it’s $9,500. The churn report shows three small, unrelated account closures over nine months. The reality is one $17,200/year household relationship that collapsed — and the collapse was predictable from the first signal.

This article is about measuring, predicting, and fighting the churn that actually matters — household relationship decay. It combines Gold’s behavioral data science, Marco Peixeiro’s foundation models for time series forecasting, and the banking-specific knowledge that neither book covers.

I’ll include the code. Because if you can’t run it, it’s not a framework — it’s a slide deck.

The Behavioral Metric Stack for Banking

Gold’s core insight is that churn prediction starts with great behavioral metrics — not demographic data, not satisfaction surveys, but quantified measurements of what the customer actually does. He’s right. But banking needs a different metric stack than SaaS. Here’s the metric stack I’ve developed over years of working on this problem, with the code to build it:

Step 1: Build Multi-Horizon Behavioral Features

The first mistake most bank data teams make is computing features over a single time window. A customer who logged in 10 times this month looks healthy. But if they logged in 25 times last month, that’s a 60% decline — a decay signal the single-window metric completely misses.

import pandas as pd
import numpy as np

def build_banking_behavioral_features(transactions_df, 
                                       logins_df,
                                       products_df, 
                                       customer_id,
                                       observation_date):
    """
    Build multi-horizon behavioral features for a single customer.
    
    Gold's approach: metrics over one fixed window.
    Banking adaptation: metrics over 4 windows + velocity + ratios.
    """
    
    features = {'customer_id': customer_id}
    
    # --- BALANCE FEATURES (multi-horizon) ---
    for window_days, label in [(7, '7d'), (30, '30d'), (90, '90d'), (365, '1y')]:
        window_start = observation_date - pd.Timedelta(days=window_days)
        window_txns = transactions_df[
            (transactions_df['date'] >= window_start) & 
            (transactions_df['date'] <= observation_date)
        ]
        
        if len(window_txns) > 0:
            # Balance trajectory: end vs start
            balance_start = window_txns.iloc[0]['running_balance']
            balance_end = window_txns.iloc[-1]['running_balance']
            features[f'balance_change_{label}'] = balance_end - balance_start
            features[f'balance_velocity_{label}'] = (
                (balance_end - balance_start) / balance_start 
                if balance_start > 0 else 0
            )
            
            # Inflow vs outflow ratio (Gold's ratio metrics, Ch. 7.1)
            inflows = window_txns[window_txns['amount'] > 0]['amount'].sum()
            outflows = abs(window_txns[window_txns['amount'] < 0]['amount'].sum())
            features[f'inflow_outflow_ratio_{label}'] = (
                inflows / outflows if outflows > 0 else 10.0
            )
            
            # Transaction frequency
            features[f'txn_count_{label}'] = len(window_txns)
            
            # Interbank transfer direction (money flowing TO or AWAY)
            interbank_out = window_txns[
                window_txns['category'] == 'interbank_transfer_out'
            ]['amount'].sum()
            interbank_in = window_txns[
                window_txns['category'] == 'interbank_transfer_in'
            ]['amount'].sum()
            features[f'interbank_net_{label}'] = interbank_in + interbank_out
    
    # --- ENGAGEMENT VELOCITY ---
    # Not just current engagement — the RATE OF CHANGE
    # This is what Gold calls "metrics that measure change" (Ch. 7.3)
    if 'txn_count_30d' in features and 'txn_count_90d' in features:
        avg_monthly_90d = features['txn_count_90d'] / 3
        features['engagement_velocity'] = (
            (features['txn_count_30d'] - avg_monthly_90d) / avg_monthly_90d
            if avg_monthly_90d > 0 else 0
        )
    
    # --- SALARY CREDIT FEATURES ---
    salary_txns = transactions_df[
        transactions_df['category'] == 'salary_credit'
    ].sort_values('date')
    
    if len(salary_txns) > 0:
        features['salary_credit_active'] = 1
        last_salary = salary_txns.iloc[-1]['date']
        features['days_since_last_salary'] = (observation_date - last_salary).days
        # Salary regularity: std dev of days between salary credits
        if len(salary_txns) > 2:
            salary_gaps = salary_txns['date'].diff().dt.days.dropna()
            features['salary_regularity'] = salary_gaps.std()
        else:
            features['salary_regularity'] = 99  # Unknown = high risk
    else:
        features['salary_credit_active'] = 0
        features['days_since_last_salary'] = 999
        features['salary_regularity'] = 99
    
    # --- PRODUCT DENSITY ---
    # Gold's cross-product equivalent: how deep is the relationship?
    active_products = products_df[
        (products_df['status'] == 'active') & 
        (products_df['customer_id'] == customer_id)
    ]
    features['product_count'] = len(active_products)
    features['has_savings'] = int('savings' in active_products['type'].values)
    features['has_credit_card'] = int('credit_card' in active_products['type'].values)
    features['has_loan'] = int('loan' in active_products['type'].values)
    features['has_business_account'] = int('business' in active_products['type'].values)
    
    # --- DIGITAL ENGAGEMENT ---
    recent_logins = logins_df[
        (logins_df['date'] >= observation_date - pd.Timedelta(days=30)) &
        (logins_df['date'] <= observation_date)
    ]
    features['login_count_30d'] = len(recent_logins)
    features['mobile_ratio'] = (
        len(recent_logins[recent_logins['channel'] == 'mobile']) / len(recent_logins)
        if len(recent_logins) > 0 else 0
    )
    
    return features

The key differences from Gold’s SaaS metrics: balance velocity (not just balance level — the direction and speed of change), inflow-outflow ratio (is money flowing toward or away from the bank?), salary credit regularity (the single strongest predictor of deposit stickiness), interbank transfer direction (early warning of relationship migration), and product density (Gold’s “feature usage” adapted for banking’s multi-product model).

Step 2: Household-Level Feature Aggregation

This is where banking churn science diverges most sharply from Gold’s framework. He measures at the individual account level. We need to measure at the household level — because household decay is the churn that kills.

def build_household_features(household_members, 
                              individual_features_dict):
    """
    Aggregate individual behavioral features to household level.
    
    This doesn't exist in Gold's framework because SaaS doesn't
    have households. Banking does.
    """
    
    household = {}
    member_features = [
        individual_features_dict[m] for m in household_members 
        if m in individual_features_dict
    ]
    
    if not member_features:
        return household
    
    # Household deposit mass
    household['hh_total_balance'] = sum(
        f.get('balance_change_30d', 0) for f in member_features
        # Note: in production, use actual balance not change
    )
    
    # Household product density
    household['hh_product_count'] = sum(
        f.get('product_count', 0) for f in member_features
    )
    
    # Weakest link: the member with the worst engagement velocity
    # Household churn often starts with one member's decay
    velocities = [
        f.get('engagement_velocity', 0) for f in member_features
    ]
    household['hh_weakest_velocity'] = min(velocities)
    household['hh_avg_velocity'] = np.mean(velocities)
    
    # Salary diversity: how many salary credits flow into this household?
    household['hh_salary_sources'] = sum(
        f.get('salary_credit_active', 0) for f in member_features
    )
    
    # Business linkage: does the household have a business account?
    household['hh_has_business'] = max(
        f.get('has_business_account', 0) for f in member_features
    )
    
    # Interbank leakage: is money flowing OUT of the household?
    household['hh_interbank_net'] = sum(
        f.get('interbank_net_30d', 0) for f in member_features
    )
    
    # Household engagement dispersion: 
    # High dispersion = some members engaged, others not = risk
    if len(velocities) > 1:
        household['hh_engagement_dispersion'] = np.std(velocities)
    else:
        household['hh_engagement_dispersion'] = 0
    
    # Member count
    household['hh_member_count'] = len(member_features)
    
    return household

The weakest link metric is the one I want to highlight. In a household of four, three members might be highly engaged. The fourth — whose engagement velocity just dropped 40% — is the early warning for the entire household. Traditional individual-level churn models would flag that one person as moderate risk. The household model recognizes: when one member starts leaving, the probability of household collapse increases non-linearly.

The engagement dispersion metric captures this: a household where all members have similar engagement (low dispersion) is stable. A household where one member is highly engaged and another is disengaging (high dispersion) is in tension — and that tension typically resolves toward the exit.

Seeing the Decay Before It Happens

This is where Marco Peixeiro’s work on foundation models transforms the churn problem.

Traditional churn models (Gold’s logistic regression and XGBoost) make a point-in-time prediction: “what is this customer’s churn probability in the next 90 days?” Useful — but it treats the customer’s behavioral history as a feature vector, compressing months of temporal patterns into a single row of numbers.

Foundation time series models don’t compress. They see the temporal pattern directly — and they forecast its trajectory.

Deposit Balance Forecasting with Chronos

Chronos, developed by Amazon, tokenizes time series values the way language models tokenize words — converting continuous numbers into discrete bins and processing them through a transformer architecture. Pretrained on billions of time series data points across domains, it can forecast a banking customer’s deposit trajectory zero-shot or fine-tuned.

Here’s how to use it for deposit churn prediction:

import torch
from chronos import ChronosPipeline
import matplotlib.pyplot as plt

# Load the pretrained Chronos model
# Using the "small" model (46M parameters) for speed;
# "large" (710M) for production accuracy
pipeline = ChronosPipeline.from_pretrained(
    "amazon/chronos-t5-small",
    device_map="auto",
    torch_dtype=torch.float32,
)

def forecast_deposit_trajectory(customer_balance_series, 
                                 forecast_horizon=90,
                                 num_samples=20):
    """
    Forecast a customer's deposit balance trajectory.
    
    Instead of a binary churn prediction, this produces a 
    TRAJECTORY — magnitude, direction, uncertainty.
    
    "This customer's balance will decline from $34,000 to $18,000 
    over 90 days with 85% confidence" is more actionable than 
    "churn probability: 0.67"
    """
    
    # Chronos expects a torch tensor
    context = torch.tensor(
        customer_balance_series.values, 
        dtype=torch.float32
    )
    
    # Generate probabilistic forecasts
    forecast = pipeline.predict(
        context, 
        prediction_length=forecast_horizon,
        num_samples=num_samples
    )
    
    # Extract percentiles for uncertainty quantification
    forecast_median = np.median(forecast[0].numpy(), axis=0)
    forecast_low = np.percentile(forecast[0].numpy(), 10, axis=0)
    forecast_high = np.percentile(forecast[0].numpy(), 90, axis=0)
    
    # Key metrics for the churn model
    current_balance = customer_balance_series.iloc[-1]
    projected_balance_90d = forecast_median[-1]
    balance_decline_pct = (
        (projected_balance_90d - current_balance) / current_balance
        if current_balance > 0 else -1
    )
    
    # Probability of balance falling below a threshold
    # (e.g., below $1,000 = effectively churned)
    threshold = 1000
    prob_below_threshold = np.mean(
        forecast[0].numpy()[:, -1] < threshold
    )
    
    return {
        'projected_balance_90d': projected_balance_90d,
        'balance_decline_pct': balance_decline_pct,
        'prob_below_threshold': prob_below_threshold,
        'forecast_median': forecast_median,
        'forecast_low': forecast_low,
        'forecast_high': forecast_high,
    }

What makes this fundamentally different from Gold’s approach: the output isn’t a probability — it’s a trajectory. A churn probability of 0.67 tells the RM “this customer is likely to leave.” A balance trajectory forecast tells the RM “this customer’s balance will decline from $34,000 to $18,000 over the next 90 days, accelerating after month 2, with the decline pattern matching customers who lost their salary credit.” That’s actionable intelligence.

The probabilistic nature of Chronos is critical here. Peixeiro emphasizes that Chronos generates multiple forecast samples (via its tokenization of the probability distribution), producing confidence intervals rather than point estimates. For churn, this means: “There’s an 85% probability the balance drops below $20,000 by month 3” is a very different intervention trigger than “the balance will be $18,000” — the former communicates uncertainty honestly, which matters for resource allocation.

Catching the First Signal

Peixeiro dedicates sections in Chapters 3, 5, 6, and 8 to anomaly detection with foundation models. In banking, anomaly detection on behavioral time series is the earliest possible churn warning — detecting the moment a customer’s pattern breaks from their historical norm, months before the traditional churn model would fire.

def detect_behavioral_anomalies(customer_daily_balances,
                                 customer_daily_logins,
                                 customer_monthly_salary,
                                 pipeline,
                                 sensitivity=0.05):
    """
    Detect anomalies in customer behavioral time series.
    
    When a customer's pattern breaks from their historical norm,
    the foundation model flags it — months before the traditional
    churn model fires.
    
    Adapted from Peixeiro Ch. 3.8 (TimeGPT anomaly detection)
    and Ch. 5.8 (Chronos anomaly detection).
    """
    
    anomalies = []
    
    for series_name, series_data in [
        ('balance', customer_daily_balances),
        ('logins', customer_daily_logins),
        ('salary', customer_monthly_salary)
    ]:
        if len(series_data) < 30:  # Need minimum history
            continue
            
        # Use the model to forecast what the NEXT values should be
        # based on historical pattern
        context = torch.tensor(series_data.values[:-7], dtype=torch.float32)
        expected = pipeline.predict(context, prediction_length=7, num_samples=50)
        
        # Compare expected vs actual for the last 7 days
        actual = series_data.values[-7:]
        expected_median = np.median(expected[0].numpy(), axis=0)
        expected_std = np.std(expected[0].numpy(), axis=0)
        
        for i in range(len(actual)):
            if expected_std[i] > 0:
                z_score = abs(actual[i] - expected_median[i]) / expected_std[i]
                if z_score > 2.5:  # Significant deviation
                    anomalies.append({
                        'series': series_name,
                        'date': series_data.index[-7 + i],
                        'expected': expected_median[i],
                        'actual': actual[i],
                        'z_score': z_score,
                        'direction': 'below' if actual[i] < expected_median[i] else 'above'
                    })
    
    return anomalies

The signals this catches that traditional models miss:

Salary credit didn’t arrive. The model has learned that this customer receives a salary credit on the 25th of every month, ±2 days. On the 28th, no credit has arrived. Z-score: 3.1. The traditional churn model won’t notice for another month — it only looks at monthly aggregates. The anomaly detector fires within 3 days.

Spending pattern shifted to interbank transfers. The customer typically makes 30–40 point-of-sale transactions per month and 1–2 interbank transfers. This month: 18 POS transactions and 8 interbank transfers. The money isn’t disappearing — it’s migrating. The anomaly detector sees the shift in transaction composition, not just volume.

Balance trajectory diverged from seasonal pattern. This customer’s balance typically dips in December (holiday spending) and recovers in January. This January, the balance didn’t recover. The model expected a $4,000 increase; the actual was a $200 increase. Z-score: 2.8. Something changed — and the foundation model, having learned this customer’s seasonal pattern from years of history, caught the break.

The Churn Prediction Model

Now we bring Gold’s machine learning framework together with the foundation model outputs and the banking-specific features. XGBoost remains the right algorithm for the final churn prediction — but the feature set is fundamentally richer than anything Gold’s book covers.

import xgboost as xgb
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import roc_auc_score, precision_recall_curve

def build_banking_churn_model(features_df, target_col='churned_90d'):
    """
    XGBoost churn model with banking-specific features.
    
    Gold's approach (Ch. 9.5) + foundation model outputs + 
    household features + deposit-specific signals.
    
    Feature groups:
    1. Gold-style behavioral metrics (engagement, usage)
    2. Banking-specific metrics (balance velocity, salary, CASA)
    3. Foundation model outputs (trajectory, anomaly scores)
    4. Household-level aggregates (density, weakest link)
    """
    
    feature_groups = {
        # Group 1: Behavioral engagement (Gold-style)
        'engagement': [
            'login_count_30d', 'mobile_ratio', 
            'txn_count_7d', 'txn_count_30d', 'txn_count_90d',
            'engagement_velocity',
        ],
        
        # Group 2: Banking-specific
        'banking': [
            'balance_velocity_30d', 'balance_velocity_90d',
            'inflow_outflow_ratio_30d', 'interbank_net_30d',
            'salary_credit_active', 'days_since_last_salary',
            'salary_regularity', 'product_count',
            'has_savings', 'has_credit_card', 'has_loan',
            'has_business_account',
        ],
        
        # Group 3: Foundation model outputs
        'foundation_model': [
            'projected_balance_90d', 'balance_decline_pct',
            'prob_below_threshold',
            'anomaly_count_30d', 'max_anomaly_zscore',
            'salary_anomaly_detected',
        ],
        
        # Group 4: Household-level
        'household': [
            'hh_product_count', 'hh_weakest_velocity',
            'hh_salary_sources', 'hh_has_business',
            'hh_interbank_net', 'hh_engagement_dispersion',
            'hh_member_count',
        ],
    }
    
    all_features = [f for group in feature_groups.values() for f in group]
    available_features = [f for f in all_features if f in features_df.columns]
    
    X = features_df[available_features].fillna(0)
    y = features_df[target_col]
    
    # Time-series aware cross-validation (not random split!)
    # Gold emphasizes this in Ch. 9.4
    tscv = TimeSeriesSplit(n_splits=5)
    
    auc_scores = []
    feature_importance_sum = np.zeros(len(available_features))
    
    for train_idx, val_idx in tscv.split(X):
        X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
        y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
        
        model = xgb.XGBClassifier(
            n_estimators=300,
            max_depth=6,
            learning_rate=0.05,
            subsample=0.8,
            colsample_bytree=0.8,
            scale_pos_weight=len(y_train[y_train==0]) / max(len(y_train[y_train==1]), 1),
            eval_metric='auc',
            early_stopping_rounds=20,
            random_state=42,
        )
        
        model.fit(
            X_train, y_train,
            eval_set=[(X_val, y_val)],
            verbose=False,
        )
        
        y_pred = model.predict_proba(X_val)[:, 1]
        auc = roc_auc_score(y_val, y_pred)
        auc_scores.append(auc)
        feature_importance_sum += model.feature_importances_
    
    # Feature importance ranking
    importance_df = pd.DataFrame({
        'feature': available_features,
        'importance': feature_importance_sum / len(auc_scores),
        'group': [
            next(g for g, feats in feature_groups.items() if f in feats)
            for f in available_features
        ]
    }).sort_values('importance', ascending=False)
    
    print(f"\nCross-validated AUC: {np.mean(auc_scores):.4f} "
          f"(+/- {np.std(auc_scores):.4f})")
    print(f"\nTop 10 features:")
    print(importance_df.head(10).to_string(index=False))
    print(f"\nImportance by feature group:")
    print(importance_df.groupby('group')['importance'].sum()
          .sort_values(ascending=False).to_string())
    
    return model, importance_df

The foundation model features dominate. balance_decline_pct (the Chronos-predicted trajectory) and prob_below_threshold (the probability of effective closure) consistently rank in the top 3 features. They capture the temporal shape of the decay — something that snapshot features like balance_velocity_30d approximate but can't fully represent. The foundation model has learned decay patterns from the customer's full history, including seasonality, lifecycle, and external shocks.

salary_anomaly_detected is the single strongest early warning. When the foundation model's anomaly detector fires on a missed or delayed salary credit, the 90-day churn probability jumps 4-6x. This makes intuitive sense — a salary credit moving to another bank is the single most definitive signal that the primary banking relationship is shifting. But it's only detectable through temporal anomaly detection. Gold's snapshot metrics would need 2-3 months of missing salary data before the feature value becomes extreme enough to matter.

Household features add 3–5 AUC points. The jump from individual-only features (AUC ~0.82) to individual + household features (AUC ~0.86) is consistent across banks. The hh_weakest_velocity feature is the key contributor — confirming that household churn follows the weakest link, not the average. When one member starts decaying, the household-level model detects the risk before individual-level models would flag any single member.

Feature group importance typically ranks: Foundation model outputs (35–40%), Banking-specific (25–30%), Household (15–20%), Behavioral engagement (10–15%). The traditional Gold-style engagement metrics (logins, transaction counts) are the least important group — not because they don’t matter, but because the banking-specific metrics (balance velocity, salary credit, interbank direction) capture the same signal with more precision.

The Banking Churn Playbook

Gold’s Chapter 11 provides the strategic framework for fighting churn — five intervention categories. Here’s how they adapt to banking when you have household-level, temporally-aware churn intelligence:

Intervention 1: Product stickiness engineering

Gold’s primary recommendation: make your product stickier by improving the features that correlate with retention. In banking, the data consistently shows three stickiness multipliers:

Salary credit capture — customers with active salary credits churn at one-fifth the rate of those without. This isn’t surprising — salary credit is the deepest form of operational dependency. The intervention: for every new customer, the 30-day onboarding sequence should prioritize salary credit migration above all else. Not savings goals, not card activation — salary credit first.

Second product adoption within 90 days — customers who add a second product within 90 days of opening their first account have 60% lower 12-month churn. The intervention: the AI financial coach should make second-product recommendation its primary mission during the first 90 days, calibrated to the customer’s actual needs (not a random cross-sell banner).

Digital engagement depth — not just logins, but active feature usage: bill pay, savings goals, spending analysis. Customers who use three or more digital features churn at one-third the rate of single-feature users. The intervention: the onboarding journey should guide customers to activate bill pay and set up at least one savings goal before the 30-day mark.

Intervention 2: Proactive coaching at the first anomaly signal

When the foundation model’s anomaly detector fires — salary credit missed, balance trajectory breaking from seasonal norm, interbank transfers accelerating — the response isn’t a retention offer. It’s a coaching intervention.

def generate_coaching_intervention(anomalies, customer_features, 
                                    household_features):
    """
    Convert anomaly signals into coaching actions.
    
    The intervention isn't "please don't leave" — it's 
    "I noticed something changed, let me help."
    """
    
    interventions = []
    
    for anomaly in anomalies:
        if anomaly['series'] == 'salary' and anomaly['direction'] == 'below':
            # Salary credit anomaly — highest priority
            interventions.append({
                'type': 'salary_check',
                'urgency': 'high',
                'channel': 'push_notification',
                'tone': 'empathetic',  # NOT "we noticed you moved your salary"
                'message_template': (
                    "Your financial picture has shifted recently. "
                    "Would you like to review your savings plan to "
                    "make sure it still fits your current situation?"
                ),
                'timing': 'within_48_hours',
            })
            
        elif anomaly['series'] == 'balance' and anomaly['direction'] == 'below':
            # Balance decay — medium priority
            if customer_features.get('product_count', 0) <= 1:
                # Single-product customer losing balance = high risk
                interventions.append({
                    'type': 'engagement_deepening',
                    'urgency': 'high',
                    'channel': 'in_app',
                    'message_template': (
                        "I've been looking at your financial trends. "
                        "There might be an opportunity to optimize your "
                        "savings. Want to take a quick look?"
                    ),
                    'timing': 'next_app_open',
                })
    
    # Household-level intervention
    if household_features.get('hh_engagement_dispersion', 0) > 1.5:
        # One member disengaging while others stable
        interventions.append({
            'type': 'household_stabilization',
            'urgency': 'medium',
            'channel': 'rm_briefing',
            'message_template': (
                "RM briefing: One household member showing decay signals. "
                "Recommend family financial review conversation to reinforce "
                "the relationship across all members."
            ),
            'timing': 'next_business_day',
        })
    
    return sorted(interventions, key=lambda x: 
                  {'high': 0, 'medium': 1, 'low': 2}[x['urgency']])

The critical design principle: the intervention is coaching, not retention. “We noticed you moved your salary credit — here’s a counter-offer” tells the customer they’re being monitored and managed. “Your financial picture has shifted recently — would you like to review your savings plan?” tells the customer they’re being cared for. The behavioral outcome is the same (the customer re-engages), but the emotional register is fundamentally different — and in my experience, the coaching frame converts at 3x the rate of the retention frame.

Intervention 3: Household-level stabilization

When one family member shows churn signals, the traditional bank treats it as an individual case. The AI-native bank treats it as a household risk.

The RM (or the AI coach, at the digital tier) receives a household briefing: “The Nguyen household ($35,000/year combined CLV) shows risk signals. Minh’s engagement velocity declined 40% this month. Salary credit was 5 days late. Interbank transfers to a competitor increased. However: Lan’s engagement is stable, business account is growing, and education fund contributions are ongoing. Recommended approach: schedule a family financial review. Use the strong relationships (Lan, business) as anchors. Address Minh’s needs directly — possible rate sensitivity or product dissatisfaction.”

This is impossible in a siloed model where Minh’s personal RM doesn’t know about Lan’s accounts or the business relationship. The household view makes the stabilization strategy visible.

Intervention 4: Competitive response at the segment level

When the churn model shows a sudden increase in churn probability for a specific segment — 25–34, single product, balance $10K+, no salary credit — and the foundation model’s covariates identify a competitor rate launch as the trigger, the response isn’t individual. It’s segment-level, deployed through the executive command center within hours, not weeks.

Intervention 5: Graceful migration

Gold’s most under-appreciated insight: not all churn is worth fighting. Some customers are genuinely better served elsewhere. Some relationships have reached their natural end. The AI-native bank identifies which churn is value-destructive (household decay of a $35,000/year relationship) and which is natural lifecycle progression (a customer who outgrew the bank’s product set) — and allocates retention resources accordingly.

The foundation model helps here too: a customer whose balance trajectory shows a gradual, steady decline over 18 months (lifecycle transition) has a fundamentally different pattern than a customer whose balance dropped 40% in 3 weeks (competitive poaching or life crisis). The temporal shape of the decay determines the intervention — or the decision not to intervene.

The Churn Dashboard You Should Actually Build

Replace the traditional churn dashboard (account attrition rate, 30/60/90 day trends, vintage analysis) with:

Household Retention Rate — percentage of multi-product households retained over 12 months, weighted by household CLV. This is the number that should be in the board deck. Not account attrition.

Relationship Depth Trend — is the average customer’s product density and engagement velocity increasing or decreasing? Rising depth = growing moat. Declining depth = churn pipeline filling.

Deposit Stickiness Distribution — what percentage of deposits are Tier A (stable core) vs. Tier E (flight risk)? Track the distribution over time, not just the average score.

Balance Trajectory Forecast — aggregate view: how much deposit balance is the foundation model projecting to leave in the next 90 days? This is the ALCO’s “defend or lose” number.

Anomaly Alert Volume — how many salary credit anomalies, balance trajectory breaks, and interbank transfer accelerations fired this week vs. last week? Rising anomaly volume is the earliest possible indicator of a churn wave — weeks before the churn rate itself moves.

Intervention Conversion Rate — of the customers flagged by the early warning system and contacted by the coach or RM, what percentage stabilized or reversed their decay trajectory? This measures whether your interventions are working.

Why This Matters

Let me close with the math that should haunt every bank CEO.

Let me run the arithmetic on a hypothetical mid-sized bank with 500,000 retail customers. That’s approximately 150,000 households (average size of 3.3 members). Of those, roughly 30,000 are multi-product households (20%) generating an average of $20,000-$30,000/year each.

At a 12–15% household erosion rate, the bank loses approximately 3,600–4,500 high-value households per year. At $25,000/year average, that’s $90-$110M in annual revenue lost — not from account attrition (which the dashboard shows as a manageable 3–4%) but from household collapse (which nobody is measuring).

If the early warning system (foundation model anomaly detection + household behavioral features) catches 70% of that erosion 60 days before closure, and the coaching intervention stabilizes 40% of those flagged households, the bank retains roughly 1,000–1,250 households per year — worth $25-$30M in preserved annual revenue.

The cost of the system: a small data team, cloud compute for the foundation model, and coaching infrastructure. Conservatively: $500K-$1M/year.

The ROI: 25–60x depending on bank size and market.

That’s not a technology investment. It’s arithmetic. And the banks that do this arithmetic first will be the ones that keep the households. The ones that keep measuring account attrition will keep reporting green dashboards while their franchise erodes from the inside.

The churn you’re not measuring is the churn that’s killing you.

Start measuring it.