Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |