SECTION 1: LEARNING OBJECTIVES

By the end of this lesson, you will be able to:

  1. Define Data Governance and explain why it’s critical in banking.

  2. Understand Data Governance frameworks used in financial institutions (DAMA-DMBOK, DCAM).

  3. Implement Data Quality checks for banking data (accuracy, completeness, consistency, timeliness).

  4. Understand Data Lineage and why regulators demand it.

  5. Implement Master Data Management for critical banking entities (customer, account, product).

  6. Design Data Quality Monitoring dashboards for compliance.

  7. Understand the role of Data Stewards in banking.

  8. Write Python code for data quality validation and monitoring.


SECTION 2: WHAT IS DATA GOVERNANCE?

2.1 Definition and Importance

Data Governance is the collection of policies, processes, and standards that ensure data is managed properly throughout its lifecycle.

Why Data Governance Matters in Banking:

 
 
Reason Impact
Regulatory Compliance BASEL III, GDPR, CCPA, SOX require data accountability
Risk Management Bad data leads to bad risk decisions
Operational Efficiency Consistent data reduces errors and costs
Customer Trust Accurate data builds customer confidence
Decision Quality Good data enables good decisions

2.2 Data Governance Framework

A typical banking data governance framework includes:

text
┌─────────────────────────────────────────────────────────────────┐
│              DATA GOVERNANCE FRAMEWORK                          │
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │                    GOVERNANCE BODY                         │ │
│  │  Data Governance Council (Executives + Department Heads)  │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              ↓                                   │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │                    POLICIES & STANDARDS                    │ │
│  │  • Data Quality Standards                                 │ │
│  │  • Data Security Policies                                 │ │
│  │  • Data Privacy Policies                                  │ │
│  │  • Data Retention Policies                                │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              ↓                                   │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │                    DATA STEWARDS                          │ │
│  │  Domain experts responsible for data quality              │ │
│  └───────────────────────────────────────────────────────────┘ │
│                              ↓                                   │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │                    IMPLEMENTATION                         │ │
│  │  • Data Quality Monitoring                                │ │
│  │  • Data Lineage Tracking                                  │ │
│  │  • Master Data Management                                 │ │
│  │  • Metadata Management                                    │ │
│  └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

2.3 Data Governance Roles

 
 
Role Responsibilities Example
Data Owner Ultimately accountable for data Head of Risk for risk data
Data Steward Day-to-day data management Domain expert responsible for quality
Data Custodian Technical management DBA managing databases
Data User Consumes data for analysis Data analyst, data scientist

SECTION 3: DATA QUALITY IN BANKING

3.1 The Dimensions of Data Quality

DAMA (Data Management Association) defines six dimensions:

 
 
Dimension Definition Banking Example
Accuracy Data correctly represents reality Customer address is correct
Completeness All required data is present No missing credit scores
Consistency Data agrees across systems Same customer ID in all systems
Timeliness Data is up-to-date Transaction data loaded by 6 AM
Validity Data conforms to format rules State codes are valid (CA, NY, TX)
Uniqueness No duplicate records One record per transaction

3.2 Implementing Data Quality Checks

python
# Example: Data quality monitoring class

class DataQualityMonitor:
    """Monitor data quality for banking datasets."""
    
    def __init__(self, dataset_name):
        self.dataset_name = dataset_name
        self.issues = []
        self.metrics = {}
    
    def check_accuracy(self, df, column, validation_rule):
        """
        Check if data is accurate (validated against a rule).
        
        Args:
            df: DataFrame
            column: Column to check
            validation_rule: Function that returns True for valid values
        """
        valid_count = df[column].apply(validation_rule).sum()
        total_count = len(df)
        accuracy = valid_count / total_count
        
        self.metrics['accuracy'] = accuracy
        
        if accuracy < 0.95:
            self.issues.append({
                'dimension': 'Accuracy',
                'column': column,
                'issue': f'Accuracy is {accuracy:.2%} (threshold: 95%)',
                'invalid_count': total_count - valid_count
            })
    
    def check_completeness(self, df, column, threshold=0.95):
        """Check for missing values."""
        null_count = df[column].isnull().sum()
        total_count = len(df)
        completeness = 1 - (null_count / total_count)
        
        self.metrics[f'completeness_{column}'] = completeness
        
        if completeness < threshold:
            self.issues.append({
                'dimension': 'Completeness',
                'column': column,
                'issue': f'{null_count} missing values ({1-completeness:.2%})',
                'null_count': null_count
            })
    
    def check_consistency(self, df, column1, column2):
        """Check that columns are consistent."""
        inconsistent = df[df[column1] != df[column2]].shape[0]
        consistency = 1 - (inconsistent / len(df))
        
        self.metrics['consistency'] = consistency
        
        if inconsistent > 0:
            self.issues.append({
                'dimension': 'Consistency',
                'issue': f'Inconsistent values between {column1} and {column2}',
                'inconsistent_count': inconsistent
            })
    
    def check_validity(self, df, column, valid_values):
        """Check that values are in a valid set."""
        invalid_count = df[~df[column].isin(valid_values)].shape[0]
        validity = 1 - (invalid_count / len(df))
        
        self.metrics['validity'] = validity
        
        if invalid_count > 0:
            self.issues.append({
                'dimension': 'Validity',
                'column': column,
                'issue': f'{invalid_count} invalid values found',
                'invalid_count': invalid_count
            })
    
    def check_uniqueness(self, df, unique_keys):
        """Check for duplicate records."""
        duplicates = df.duplicated(subset=unique_keys).sum()
        uniqueness = 1 - (duplicates / len(df))
        
        self.metrics['uniqueness'] = uniqueness
        
        if duplicates > 0:
            self.issues.append({
                'dimension': 'Uniqueness',
                'issue': f'{duplicates} duplicate records found based on {unique_keys}',
                'duplicate_count': duplicates
            })
    
    def check_timeliness(self, df, date_column, max_age_days=1):
        """Check that data is timely (not too old)."""
        if pd.api.types.is_datetime64_any_dtype(df[date_column]):
            max_date = df[date_column].max()
            age_days = (pd.Timestamp.now() - max_date).days
            timeliness = max(0, 1 - (age_days / max_age_days))
            
            self.metrics['timeliness'] = timeliness
            
            if age_days > max_age_days:
                self.issues.append({
                    'dimension': 'Timeliness',
                    'issue': f'Data is {age_days} days old (threshold: {max_age_days} days)',
                    'age_days': age_days
                })
    
    def generate_report(self):
        """Generate quality report."""
        print(f"\n{'='*60}")
        print(f"DATA QUALITY REPORT: {self.dataset_name}")
        print(f"{'='*60}")
        
        print("\n📊 METRICS:")
        for metric, value in self.metrics.items():
            if isinstance(value, float):
                print(f"  {metric}: {value:.2%}")
            else:
                print(f"  {metric}: {value}")
        
        if self.issues:
            print(f"\n⚠️ ISSUES FOUND: {len(self.issues)}")
            for idx, issue in enumerate(self.issues, 1):
                print(f"  {idx}. [{issue.get('dimension', 'General')}] {issue['issue']}")
                if 'invalid_count' in issue:
                    print(f"     → {issue['invalid_count']} records affected")
                if 'null_count' in issue:
                    print(f"     → {issue['null_count']} null values")
                if 'duplicate_count' in issue:
                    print(f"     → {issue['duplicate_count']} duplicates")
        else:
            print("\n✅ No issues found! All quality checks passed.")
        
        print(f"\n{'='*60}")
        return self.issues

# Usage example
from datetime import datetime, timedelta
import pandas as pd

# Create sample data
sample_data = pd.DataFrame({
    'customer_id': [1, 2, 3, 4, 5],
    'name': ['John Smith', 'Jane Doe', 'Bob Johnson', 'Alice Williams', 'Charlie Brown'],
    'state': ['CA', 'NY', 'TX', 'CA', 'FL'],
    'credit_score': [780, 720, 680, 820, None],
    'email': ['john@email.com', 'jane@email.com', 'bob@email.com', None, 'charlie@email.com'],
    'last_transaction': [
        pd.Timestamp.now() - timedelta(days=1),
        pd.Timestamp.now() - timedelta(days=0.5),
        pd.Timestamp.now() - timedelta(days=2),
        pd.Timestamp.now() - timedelta(days=3),
        pd.Timestamp.now() - timedelta(days=4)
    ]
})

# Run quality checks
monitor = DataQualityMonitor('customers_sample')
monitor.check_completeness(sample_data, 'credit_score', threshold=0.80)
monitor.check_completeness(sample_data, 'email', threshold=0.95)
monitor.check_validity(sample_data, 'state', ['CA', 'NY', 'TX', 'FL', 'IL'])
monitor.check_uniqueness(sample_data, ['customer_id'])
monitor.check_timeliness(sample_data, 'last_transaction', max_age_days=3)

# Generate report
monitor.generate_report()

3.3 Continuous Data Quality Monitoring

python
# Example: Automated data quality monitoring

class ContinuousQualityMonitor:
    """Continuous monitoring with alerting."""
    
    def __init__(self, db_connection, alert_webhook=None):
        self.conn = db_connection
        self.alert_webhook = alert_webhook
        self.monitoring_history = []
    
    def check_rule(self, rule_name, sql_query, expected_value, threshold_pct=10):
        """
        Check a data quality rule.
        
        Args:
            rule_name: Name of the rule
            sql_query: SQL query that returns a metric
            expected_value: What the metric should be
            threshold_pct: How much deviation is allowed
        """
        try:
            # Run the query
            result = pd.read_sql(sql_query, self.conn)
            actual_value = result.iloc[0, 0]
            
            # Calculate deviation
            deviation_pct = abs((actual_value - expected_value) / expected_value) * 100
            
            # Record history
            self.monitoring_history.append({
                'rule_name': rule_name,
                'timestamp': datetime.now(),
                'actual_value': actual_value,
                'expected_value': expected_value,
                'deviation_pct': deviation_pct,
                'status': 'PASS' if deviation_pct <= threshold_pct else 'FAIL'
            })
            
            # Alert if failed
            if deviation_pct > threshold_pct:
                self.send_alert(rule_name, actual_value, expected_value, deviation_pct)
            
            return deviation_pct <= threshold_pct
            
        except Exception as e:
            self.monitoring_history.append({
                'rule_name': rule_name,
                'timestamp': datetime.now(),
                'status': 'ERROR',
                'error_message': str(e)
            })
            self.send_alert(rule_name, 'ERROR', '', str(e))
            return False
    
    def send_alert(self, rule_name, actual, expected, detail):
        """Send alert via webhook or logging."""
        message = f"""
        ⚠️ DATA QUALITY ALERT
        Rule: {rule_name}
        Expected: {expected}
        Actual: {actual}
        Detail: {detail}
        Time: {datetime.now()}
        """
        print(message)
        # In production, send to Slack, email, etc.
    
    def generate_dashboard(self, days=30):
        """Generate quality dashboard for the last N days."""
        recent = [h for h in self.monitoring_history 
                 if h['timestamp'] > datetime.now() - timedelta(days=days)]
        
        dashboard = {
            'total_checks': len(recent),
            'passed_checks': len([h for h in recent if h.get('status') == 'PASS']),
            'failed_checks': len([h for h in recent if h.get('status') == 'FAIL']),
            'error_checks': len([h for h in recent if h.get('status') == 'ERROR'])
        }
        
        print(f"\n{'='*60}")
        print(f"QUALITY DASHBOARD (Last {days} days)")
        print(f"{'='*60}")
        print(f"Total checks: {dashboard['total_checks']}")
        print(f"✅ Passed: {dashboard['passed_checks']}")
        print(f"❌ Failed: {dashboard['failed_checks']}")
        print(f"⚠️ Errors: {dashboard['error_checks']}")
        
        return dashboard

# Example usage
monitor = ContinuousQualityMonitor(db_connection)

# Define quality rules
rules = [
    ("row_count", "SELECT COUNT(*) FROM transactions", expected=1000000, threshold=5),
    ("null_customer_id", "SELECT COUNT(*) FROM transactions WHERE customer_id IS NULL", expected=0, threshold=0),
    ("negative_amount", "SELECT COUNT(*) FROM transactions WHERE amount < 0", expected=0, threshold=0),
    ("recent_transactions", "SELECT COUNT(*) FROM transactions WHERE transaction_date > '2024-01-01'", expected=5000, threshold=10)
]

for rule in rules:
    monitor.check_rule(*rule)

# Generate dashboard
monitor.generate_dashboard()

SECTION 4: DATA LINEAGE

4.1 What Is Data Lineage?

Data lineage tracks the complete lifecycle of data—where it came from, how it was transformed, and where it was moved.

Data Lineage Diagram:

text
Source System          ETL Process              Destination
    ↓                      ↓                       ↓
[OLTP DB] → [Extract] → [Staging] → [Transform] → [DW]
    ↓                      ↓                       ↓
    └──────────────────[Lineage Tracking]─────────┘

4.2 Why Data Lineage Matters in Banking

 
 
Regulatory Requirement Lineage Need
SR 11-7 (Model Risk) Must trace data from source to model
BASEL III Must validate risk data accuracy
GDPR/CCPA Must know where customer data lives
SOX Must audit all financial data

4.3 Implementing Data Lineage

python
# Example: Simple data lineage tracking

class DataLineageTracker:
    """Track data movement and transformations."""
    
    def __init__(self):
        self.lineage = []
    
    def record_extract(self, source, table, rows, timestamp):
        """Record extraction step."""
        self.lineage.append({
            'step': 'extract',
            'source': source,
            'table': table,
            'rows': rows,
            'timestamp': timestamp
        })
    
    def record_transform(self, input_table, output_table, transform_logic):
        """Record transformation step."""
        self.lineage.append({
            'step': 'transform',
            'input_table': input_table,
            'output_table': output_table,
            'transform_logic': transform_logic,
            'timestamp': datetime.now()
        })
    
    def record_load(self, source_table, target_table, target_system, rows):
        """Record load step."""
        self.lineage.append({
            'step': 'load',
            'source_table': source_table,
            'target_table': target_table,
            'target_system': target_system,
            'rows': rows,
            'timestamp': datetime.now()
        })
    
    def get_lineage_report(self, table_name):
        """Get complete lineage for a table."""
        relevant_steps = [l for l in self.lineage 
                         if l.get('table') == table_name or l.get('input_table') == table_name]
        
        return relevant_steps
    
    def export_lineage(self, filename='lineage.json'):
        """Export lineage for audit purposes."""
        with open(filename, 'w') as f:
            json.dump(self.lineage, f, indent=2, default=str)

# Example usage
tracker = DataLineageTracker()

# Track ETL pipeline
tracker.record_extract('OLTP System', 'transactions', 1000000, datetime.now())
tracker.record_transform('transactions', 'cleaned_transactions', 
                        'Remove duplicates, standardize dates')
tracker.record_load('cleaned_transactions', 'fact_transactions', 'Data Warehouse', 950000)

# Get lineage report
report = tracker.get_lineage_report('fact_transactions')
for step in report:
    print(f"{step['step'].upper()}: {step}")

SECTION 5: MASTER DATA MANAGEMENT (MDM)

5.1 What Is Master Data?

Master Data is the core data that is critical to business operations and consistent across the organization.

 
 
Master Data Type Banking Example
Customer Name, address, phone, ID
Account Account number, type, balance
Product Loan types, credit card programs
Branch Location, manager, hours
Employee Name, role, branch
Counterparty Company information for trading

5.2 The Challenge: Single Customer View

In banking, customers often have multiple accounts across different systems. MDM creates a single, consistent view.

text
Customer Master Data Management:

Customer ID: CUST001
├── Legal Name: John Smith
├── Date of Birth: 1980-05-15
├── Tax ID: XXX-XX-XXXX
├── Addresses:
│   ├── Primary: 100 Main St, New York, NY
│   └── Mailing: PO Box 123, New York, NY
├── Contact:
│   ├── Phone: 555-0101
│   └── Email: john.smith@email.com
├── Relationships:
│   ├── Accounts:
│   │   ├── Checking (CHK1001) - Balance: $25,000
│   │   ├── Savings (SAV1001) - Balance: $75,000
│   │   └── Credit Card (CC1001) - Balance: -$5,000
│   └── Loans:
│       └── Mortgage (L001) - Balance: $250,000
└── Customer Segment: Premium

5.3 Implementing MDM

python
# Example: Master Data Management implementation

class CustomerMDM:
    """Master Data Management for customers."""
    
    def __init__(self):
        self.customer_master = {}
        self.duplicate_groups = {}
    
    def add_customer(self, customer_data):
        """Add or update customer in master."""
        customer_id = customer_data['customer_id']
        
        if customer_id not in self.customer_master:
            self.customer_master[customer_id] = {
                'master_id': customer_id,
                'created_at': datetime.now(),
                'updated_at': datetime.now(),
                'versions': []
            }
        
        # Versioning: keep history
        current = self.customer_master[customer_id]
        current['versions'].append({
            'timestamp': datetime.now(),
            'data': customer_data
        })
        
        # Update current data
        current['current'] = customer_data
        current['updated_at'] = datetime.now()
    
    def link_duplicates(self, primary_id, duplicate_id):
        """Link duplicate customer records."""
        if primary_id not in self.duplicate_groups:
            self.duplicate_groups[primary_id] = {'primary': primary_id, 'duplicates': []}
        
        self.duplicate_groups[primary_id]['duplicates'].append(duplicate_id)
    
    def get_master_customer(self, customer_id):
        """Get master record for a customer, resolving duplicates."""
        # Check if this ID is a duplicate
        for primary, group in self.duplicate_groups.items():
            if customer_id in group['duplicates'] or customer_id == primary:
                return self.customer_master[primary]
        
        # Or return directly
        return self.customer_master.get(customer_id)
    
    def get_golden_record(self, customer_id):
        """Get the golden record (best version) for a customer."""
        master = self.get_master_customer(customer_id)
        if not master:
            return None
        
        # Simple logic: use the most recent version
        latest = max(master['versions'], key=lambda x: x['timestamp'])
        return latest['data']
    
    def check_master_data_quality(self):
        """Check quality of master data."""
        issues = []
        
        for cust_id, master in self.customer_master.items():
            current = master.get('current', {})
            
            # Check required fields
            if not current.get('first_name') or not current.get('last_name'):
                issues.append(f"Customer {cust_id}: Missing name")
            
            if not current.get('tax_id'):
                issues.append(f"Customer {cust_id}: Missing tax ID")
            
            if not current.get('address'):
                issues.append(f"Customer {cust_id}: Missing address")
        
        return issues

# Usage example
mdm = CustomerMDM()

# Add customer data from different sources
mdm.add_customer({
    'customer_id': 'CUST001',
    'first_name': 'John',
    'last_name': 'Smith',
    'tax_id': '123-45-6789',
    'address': '100 Main St, New York, NY',
    'phone': '555-0101',
    'segment': 'Premium',
    'source': 'Core Banking'
})

# Link duplicate records
mdm.link_duplicates('CUST001', 'CUST001_DUP')

# Get golden record
golden = mdm.get_golden_record('CUST001')
print(f"Golden Record: {golden}")

# Check quality
quality_issues = mdm.check_master_data_quality()
print(f"Quality Issues: {quality_issues}")

SECTION 6: BUSINESS RISK & FINANCIAL IMPACT

6.1 The Cost of Poor Data Quality

 
 
Issue Annual Impact (Major Bank)
Regulatory Fines $50M – $500M
Operational Inefficiency $100M – $300M
Lost Revenue $100M – $200M
Customer Churn $50M – $150M
Fraud Losses $100M – $500M

6.2 Case Study: Data Governance Failure

Example: A major bank failed to maintain consistent customer data across systems.

The Problem:

  • Customer addresses were different in 3 systems

  • Compliance team couldn’t properly identify beneficial owners

  • Resulted in FATCA reporting errors

The Impact:

  • $150 million fine from regulators

  • 6 months of intensive remediation work

  • 50+ data quality engineers hired

  • New data governance framework implemented

The Lesson:
Data governance is not optional. It’s a fundamental requirement for modern banking.


SECTION 7: SUMMARY FOR THE DATA PRACTITIONER

7.1 The 1-Minute Elevator Pitch

“Data Governance ensures that banking data is accurate, complete, and trustworthy. We implement data quality checks across six dimensions: accuracy, completeness, consistency, timeliness, validity, and uniqueness. Data lineage tracks where data came from and how it was transformed—crucial for regulatory compliance. Master Data Management creates a single, consistent view of critical entities like customers. Without data governance, banks face regulatory fines, operational inefficiencies, and poor decision-making.”

7.2 Key Takeaways

  1. Data Governance is the framework for managing data across its lifecycle.

  2. Data Quality has six dimensions: accuracy, completeness, consistency, timeliness, validity, uniqueness.

  3. Continuous Monitoring is essential—not a one-time check.

  4. Data Lineage tracks data from source to destination—required by regulators.

  5. Master Data Management creates a single, consistent view of critical entities.

  6. Data Stewards are responsible for data quality in their domain.

  7. Automated Quality Checks catch issues before they affect reporting.

  8. Regulatory Requirements (BASEL III, SR 11-7, GDPR) drive data governance needs.

  9. Poor Data Quality costs millions in fines and lost revenue.

  10. Document Everything—regulators will ask for evidence of data governance.

7.3 Recommended Next Steps

  1. Assess data quality for a dataset you work with

  2. Document data lineage for a key report

  3. Learn about your bank’s data governance framework

  4. Explore data governance tools (Collibra, Alation, Informatica)


[END OF LESSON 5]