""" Battery Data Format Detector Auto-detects NASA vs Stanford format with validation. MIT/Stanford Quality: Robust format detection with confidence scoring. """ import pandas as pd import numpy as np from typing import Literal, Dict def detect_battery_format( data: pd.DataFrame, confidence_threshold: float = 0.8 ) -> Dict[str, any]: """ Automatically detect battery data format (NASA vs Stanford). NASA format: cycle_index, voltage, current, temperature, capacity Stanford format: 99 columns of voltage discharge curve values Parameters: ----------- data : DataFrame Battery data to detect format confidence_threshold : float Minimum confidence required (default 0.8) Returns: -------- dict: { 'format': 'nasa' | 'stanford' | 'unknown', 'confidence': float, 'reason': str, 'details': dict } """ detection_scores = { 'nasa': 0.0, 'stanford': 0.0 } details = { 'columns': list(data.columns), 'shape': data.shape, 'column_count': len(data.columns) } # Check 1: Column names nasa_required_cols = {'cycle_index', 'voltage', 'current', 'temperature', 'capacity'} nasa_cols_present = sum(1 for col in nasa_required_cols if col in data.columns) if nasa_cols_present == 5: detection_scores['nasa'] += 0.5 # Strong indicator elif nasa_cols_present >= 3: detection_scores['nasa'] += 0.3 # Moderate indicator # Check 2: Column count # Stanford has exactly 99 columns (voltage measurements) if len(data.columns) == 99: detection_scores['stanford'] += 0.4 elif len(data.columns) > 50: # Likely voltage array detection_scores['stanford'] += 0.2 if len(data.columns) == 5: detection_scores['nasa'] += 0.3 # Check 3: Data patterns # Stanford values are small (voltage deltas, typically < 0.01) # NASA values are absolute (voltage 3-4V, current 0-2A, etc.) if len(data.columns) > 0: first_col_values = data.iloc[:, 0].dropna() if len(first_col_values) > 0: first_col_mean = abs(first_col_values.mean()) first_col_max = abs(first_col_values.max()) # Stanford: small values (voltage deltas) if first_col_max < 0.01: detection_scores['stanford'] += 0.3 # NASA: larger values (cycle indices or voltages) if first_col_max > 1.0: detection_scores['nasa'] += 0.2 # Check 4: Row count # Stanford typically has ~1000 cycles per cell # NASA typically has ~168 cycles if len(data) > 500: detection_scores['stanford'] += 0.1 elif len(data) < 300: detection_scores['nasa'] += 0.1 # Determine format max_score = max(detection_scores['nasa'], detection_scores['stanford']) if detection_scores['nasa'] > detection_scores['stanford'] and detection_scores['nasa'] >= confidence_threshold: format_type = 'nasa' confidence = detection_scores['nasa'] reason = f"NASA format detected: {nasa_cols_present}/5 required columns present" elif detection_scores['stanford'] > detection_scores['nasa'] and detection_scores['stanford'] >= confidence_threshold: format_type = 'stanford' confidence = detection_scores['stanford'] reason = f"Stanford format detected: {len(data.columns)} columns (matrix format)" else: format_type = 'unknown' confidence = max_score reason = f"Format uncertain: NASA score={detection_scores['nasa']:.2f}, Stanford score={detection_scores['stanford']:.2f}" details['detection_scores'] = detection_scores return { 'format': format_type, 'confidence': confidence, 'reason': reason, 'details': details } def validate_format(data: pd.DataFrame, expected_format: str) -> Dict: """ Validate that data matches expected format. Parameters: ----------- data : DataFrame Battery data expected_format : str 'nasa' or 'stanford' Returns: -------- dict: Validation results """ errors = [] warnings = [] if expected_format == 'nasa': # Check required columns required = ['cycle_index', 'voltage', 'current', 'temperature', 'capacity'] missing = [col for col in required if col not in data.columns] if missing: errors.append(f"Missing required columns: {missing}") # Check value ranges if 'voltage' in data.columns: v_min, v_max = data['voltage'].min(), data['voltage'].max() if v_min < 0 or v_max > 5: warnings.append(f"Unusual voltage range: {v_min:.2f}-{v_max:.2f}V") if 'capacity' in data.columns: if data['capacity'].min() < 0: errors.append("Negative capacity values found") elif expected_format == 'stanford': # Check column count if len(data.columns) != 99: warnings.append(f"Expected 99 columns, got {len(data.columns)}") # Check value ranges (should be small voltage deltas) max_abs_value = abs(data.values).max() if max_abs_value > 0.1: warnings.append(f"Values seem large for Stanford format: max={max_abs_value:.4f}") is_valid = len(errors) == 0 return { 'is_valid': is_valid, 'errors': errors, 'warnings': warnings } if __name__ == "__main__": # Test format detection print("Testing format detector...") # Test NASA format print("\n1. NASA B0005:") nasa_data = pd.read_csv('./data/nasa_converted/B0005.csv') result = detect_battery_format(nasa_data) print(f" Format: {result['format']}") print(f" Confidence: {result['confidence']:.2f}") print(f" Reason: {result['reason']}") # Test Stanford format print("\n2. Stanford cell1 (original):") stanford_data = pd.read_csv('./data/stanford/cell1.csv', header=None) result = detect_battery_format(stanford_data) print(f" Format: {result['format']}") print(f" Confidence: {result['confidence']:.2f}") print(f" Reason: {result['reason']}") # Test Stanford converted print("\n3. Stanford cell1 (converted):") stanford_converted = pd.read_csv('./data/stanford_converted/cell1.csv') result = detect_battery_format(stanford_converted) print(f" Format: {result['format']}") print(f" Confidence: {result['confidence']:.2f}") print(f" Reason: {result['reason']}") print("\n✅ Format detector test complete!")