File size: 8,739 Bytes
dc610d2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""
Feature Audit: Detect Data Leakage in Battery SOH Prediction Features

This script audits all 52 features for causality violations:
- Uses only data ≤ current cycle
- No future information leakage
- Document each feature's causality status
"""

import pandas as pd
import numpy as np
from typing import Dict, List, Tuple
import warnings
warnings.filterwarnings('ignore')


class FeatureAuditor:
    """Audit features for data leakage and causality violations."""
    
    def __init__(self):
        self.leakage_issues = []
        self.safe_features = []
        self.warnings = []
        
    def audit_feature(self, feature_name: str, calculation_method: str, 
                     uses_future_data: bool, description: str) -> Dict:
        """Audit a single feature."""
        status = "❌ LEAKAGE" if uses_future_data else "✅ SAFE"
        result = {
            'feature': feature_name,
            'status': status,
            'leakage': uses_future_data,
            'method': calculation_method,
            'description': description
        }
        
        if uses_future_data:
            self.leakage_issues.append(result)
        else:
            self.safe_features.append(result)
            
        return result
    
    def generate_audit_report(self) -> str:
        """Generate comprehensive audit report."""
        report = []
        report.append("="*80)
        report.append("FEATURE CAUSALITY AUDIT REPORT")
        report.append("="*80)
        report.append(f"\nDate: {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')}")
        report.append(f"\nTotal Features Audited: {len(self.leakage_issues) + len(self.safe_features)}")
        report.append(f"❌ Features with Leakage: {len(self.leakage_issues)}")
        report.append(f"✅ Causal Features: {len(self.safe_features)}")
        
        if self.leakage_issues:
            report.append("\n" + "="*80)
            report.append("🚨 DATA LEAKAGE ISSUES FOUND")
            report.append("="*80)
            for issue in self.leakage_issues:
                report.append(f"\n❌ {issue['feature']}")
                report.append(f"   Method: {issue['method']}")
                report.append(f"   Issue: {issue['description']}")
                report.append(f"   Action Required: FIX OR REMOVE")
        
        report.append("\n" + "="*80)
        report.append("✅ SAFE CAUSAL FEATURES")
        report.append("="*80)
        
        # Group by category
        categories = {}
        for feature in self.safe_features:
            category = feature['method'].split('_')[0] if '_' in feature['method'] else 'Other'
            if category not in categories:
                categories[category] = []
            categories[category].append(feature)
        
        for category, features in categories.items():
            report.append(f"\n[{category.upper()}]")
            for feature in features:
                report.append(f"  ✅ {feature['feature']}")
        
        report.append("\n" + "="*80)
        report.append("RECOMMENDATIONS")
        report.append("="*80)
        report.append("\n1. Remove or fix all features with ❌ LEAKAGE status")
        report.append("2. Retrain model with only causal features")
        report.append("3. Re-validate on independent test set")
        report.append("4. Report honest R² score (even if lower)")
        report.append("5. Document uncertainty quantification methodology")
        
        return "\n".join(report)


def audit_all_features():
    """Audit all features in the engineering pipeline."""
    auditor = FeatureAuditor()
    
    # ========================================================================
    # 1. RAW MEASUREMENTS - SAFE (uses only current cycle)
    # ========================================================================
    auditor.audit_feature(
        'voltage_mean', 'current_cycle', False,
        'Mean voltage within current cycle - CAUSAL'
    )
    auditor.audit_feature(
        'current_mean', 'current_cycle', False,
        'Mean current within current cycle - CAUSAL'
    )
    auditor.audit_feature(
        'temperature_mean', 'current_cycle', False,
        'Mean temperature within current cycle - CAUSAL'
    )
    auditor.audit_feature(
        'capacity', 'current_cycle', False,
        'Capacity at current cycle - CAUSAL'
    )
    
    # Normalization - CHECK: uses min/max across ALL cycles
    auditor.audit_feature(
        'voltage_norm', 'minmax_normalization', True,
        '⚠️ NORMALIZATION LEAKAGE: Uses min/max across all cycles (future data) - FIX: Use only past cycles or fixed bounds'
    )
    auditor.audit_feature(
        'current_norm', 'minmax_normalization', True,
        '⚠️ NORMALIZATION LEAKAGE: Uses min/max across all cycles - FIX: Use only past cycles or fixed bounds'
    )
    auditor.audit_feature(
        'temperature_norm', 'minmax_normalization', True,
        '⚠️ NORMALIZATION LEAKAGE: Uses min/max across all cycles - FIX: Use only past cycles or fixed bounds'
    )
    
    # ========================================================================
    # 2. STATISTICAL FEATURES - SAFE (within current cycle)
    # ========================================================================
    for stat in ['mean', 'std', 'min', 'max', 'range', 'median']:
        for signal in ['voltage', 'current', 'temperature']:
            auditor.audit_feature(
                f'{signal}_{stat}', 'current_cycle', False,
                f'{stat.capitalize()} {signal} within current cycle - CAUSAL'
            )
    
    # ========================================================================
    # 3. TEMPORAL FEATURES - CHECK
    # ========================================================================
    auditor.audit_feature(
        'cycles_from_start', 'temporal', False,
        'Cycles since start - CAUSAL (only uses past)'
    )
    auditor.audit_feature(
        'cycles_to_end', 'temporal', True,
        '🚨 MAJOR LEAKAGE: Uses max(cycle_index) which requires future knowledge - REMOVE'
    )
    auditor.audit_feature(
        'days_since_start', 'temporal', False,
        'Days since start (estimated) - CAUSAL'
    )
    auditor.audit_feature(
        'cumulative_time', 'temporal', False,
        'Cumulative time up to current cycle - CAUSAL'
    )
    
    # ========================================================================
    # 4. DEGRADATION FEATURES - CHECK ROLLING WINDOWS
    # ========================================================================
    auditor.audit_feature(
        'soh_fade_rate', 'diff_backward', False,
        'SOH difference from previous cycle - CAUSAL (uses only past)'
    )
    auditor.audit_feature(
        'soh_rolling_mean_5', 'rolling_window', False,
        'Rolling mean (window=5, center=False) - CAUSAL if backward-looking only'
    )
    auditor.audit_feature(
        'soh_rolling_std_5', 'rolling_window', False,
        'Rolling std (window=5, center=False) - CAUSAL if backward-looking only'
    )
    auditor.audit_feature(
        'soh_ema', 'exponential_moving_average', False,
        'Exponential moving average (backward) - CAUSAL'
    )
    auditor.audit_feature(
        'capacity_fade_rate', 'diff_backward', False,
        'Capacity change from previous cycle - CAUSAL'
    )
    
    # ========================================================================
    # 5. PHYSICS-INFORMED FEATURES - SAFE (current cycle)
    # ========================================================================
    auditor.audit_feature(
        'energy_throughput', 'current_cycle', False,
        'Energy throughput within current cycle - CAUSAL'
    )
    auditor.audit_feature(
        'power_mean', 'current_cycle', False,
        'Mean power (V×I) in current cycle - CAUSAL'
    )
    auditor.audit_feature(
        'temperature_stress', 'current_cycle', False,
        'Temperature stress indicators in current cycle - CAUSAL'
    )
    auditor.audit_feature(
        'charge_discharge_asymmetry', 'cumulative', True,
        '⚠️ POTENTIAL LEAKAGE: Uses sum across all cycles - FIX: Use only past cycles or current cycle ratio'
    )
    
    return auditor


if __name__ == "__main__":
    print("Starting Feature Causality Audit...")
    auditor = audit_all_features()
    
    report = auditor.generate_audit_report()
    print("\n" + report)
    
    # Save report
    with open('./data_leakage_audit.txt', 'w') as f:
        f.write(report)
    
    print(f"\n✓ Audit report saved to: ./data_leakage_audit.txt")
    print(f"✓ Found {len(auditor.leakage_issues)} features with leakage")
    print(f"✓ Found {len(auditor.safe_features)} causal features")