""" Basic Analysis Module - adapted from clearskies_agent_v1.1 Compression ratio, ramp rate, flow stats, fill event analysis """ import pandas as pd import numpy as np from typing import Dict, Optional from core import config class DataAnalyzer: """Performs analysis on testing cycle / fill event data""" def __init__(self): self.performance_targets = config.PERFORMANCE_TARGETS def analyze_cycle( self, df_pivot: pd.DataFrame, discharge_tag: str = 'PT130', supply_tag: str = 'PT01T', flow_tag: str = 'FT140', temp_tags: list = None, ) -> Dict: """ Comprehensive analysis of a testing cycle from pivoted data. Args: df_pivot: Wide-format DataFrame with timestamp and tag columns discharge_tag: Discharge pressure sensor supply_tag: Supply pressure sensor flow_tag: Flow meter sensor temp_tags: Temperature sensor tags Returns: Dict with comprehensive cycle analysis """ if temp_tags is None: temp_tags = ['TT110', 'TT130'] analysis = { 'time_range': { 'start': df_pivot['timestamp'].min(), 'end': df_pivot['timestamp'].max(), 'duration_minutes': (df_pivot['timestamp'].max() - df_pivot['timestamp'].min()).total_seconds() / 60, } } # Discharge pressure if discharge_tag in df_pivot.columns: col = df_pivot[discharge_tag].dropna() if len(col) > 0: analysis['discharge_pressure'] = { 'peak': float(col.max()), 'avg': float(col.mean()), 'std': float(col.std()) if len(col) > 1 else 0.0, 'initial': float(col.iloc[0]), 'final': float(col.iloc[-1]), 'unit': 'bar', } # Supply pressure if supply_tag in df_pivot.columns: col = df_pivot[supply_tag].dropna() if len(col) > 0: analysis['supply_pressure'] = { 'avg': float(col.mean()), 'min': float(col.min()), 'max': float(col.max()), 'unit': 'bar', } # Compression ratio if discharge_tag in df_pivot.columns and supply_tag in df_pivot.columns: valid = df_pivot[[discharge_tag, supply_tag]].dropna() if len(valid) > 0 and (valid[supply_tag] != 0).any(): cr = valid[discharge_tag] / valid[supply_tag].replace(0, np.nan) cr = cr.dropna() if len(cr) > 0: analysis['compression_ratio'] = { 'avg': float(cr.mean()), 'max': float(cr.max()), 'min': float(cr.min()), } # Temperature analysis for tag in temp_tags: if tag in df_pivot.columns: col = df_pivot[tag].dropna() if len(col) > 0: analysis[f'temperature_{tag}'] = { 'peak': float(col.max()), 'min': float(col.min()), 'avg': float(col.mean()), 'unit': 'K', } # Flow analysis if flow_tag in df_pivot.columns: col = df_pivot[flow_tag].dropna() if len(col) > 0: analysis['flow'] = { 'avg_flow': float(col.mean()), 'max_flow': float(col.max()), 'unit': 'kg/min', } # Estimate total mass (trapezoidal integration) flow_sorted = df_pivot[['timestamp', flow_tag]].dropna().sort_values('timestamp') if len(flow_sorted) > 1: time_diff_min = flow_sorted['timestamp'].diff().dt.total_seconds().fillna(0) / 60 mass_increment = flow_sorted[flow_tag] * time_diff_min analysis['flow']['total_mass_kg'] = float(mass_increment.sum()) # Pressure ramp rate if discharge_tag in df_pivot.columns: ramp = self._calculate_ramp_rate(df_pivot, discharge_tag) if ramp: analysis['ramp_rate'] = ramp # Motor speed — apply display multiplier (5/3) to convert raw signal to RPM speed_mult = getattr(config, 'MOTOR_SPEED_DISPLAY_MULTIPLIER', 1.0) for speed_tag in ['M130_Speed', 'MC130_VFD_Speed', 'M130_RPM']: if speed_tag in df_pivot.columns: col = df_pivot[speed_tag].dropna() if len(col) > 0: analysis['motor'] = { 'peak_speed': float(col.max()) * speed_mult, 'avg_speed': float(col.mean()) * speed_mult, 'tag': speed_tag, 'unit': 'RPM', } break # Performance vs targets analysis['performance_vs_targets'] = self._compare_to_targets(analysis) return analysis def _calculate_ramp_rate(self, df: pd.DataFrame, pressure_tag: str) -> Optional[Dict]: """Calculate pressure ramp rate in bar/min""" data = df[['timestamp', pressure_tag]].dropna().sort_values('timestamp') if len(data) < 2: return None time_diff_min = data['timestamp'].diff().dt.total_seconds() / 60 pressure_diff = data[pressure_tag].diff() ramp_rate = pressure_diff / time_diff_min valid_rates = ramp_rate.replace([np.inf, -np.inf], np.nan).dropna() if len(valid_rates) == 0: return None return { 'avg': float(valid_rates.mean()), 'max': float(valid_rates.max()), 'min': float(valid_rates.min()), 'unit': 'bar/min', } def _compare_to_targets(self, analysis: Dict) -> Dict: """Compare analysis results against performance targets""" comparison = {} if 'discharge_pressure' in analysis: peak = analysis['discharge_pressure']['peak'] targets = self.performance_targets['pressure'] comparison['pressure'] = { 'achieved': peak, 'phase1_target': targets['phase1'], 'h70_target': targets['h70'], 'meets_phase1': peak >= targets['phase1'], 'meets_h70': peak >= targets['h70'], } if 'flow' in analysis: avg = analysis['flow']['avg_flow'] targets = self.performance_targets['flow'] comparison['flow'] = { 'achieved': avg, 'simplex_target': targets['simplex'], 'pct_of_target': (avg / targets['simplex']) * 100 if targets['simplex'] else 0, } return comparison