Spaces:
Sleeping
Sleeping
| """ | |
| Advanced Analysis Module - adapted from clearskies_agent_v1.1 | |
| Peak pressure detection, plateau detection, downsampling | |
| """ | |
| import pandas as pd | |
| import numpy as np | |
| from typing import Dict, List, Optional | |
| class AdvancedAnalyzer: | |
| """Advanced analysis functions for cycle detail views""" | |
| def find_peak_pressure_blocks( | |
| self, | |
| df: pd.DataFrame, | |
| pressure_tag: str = 'PT130', | |
| threshold_bar: float = 900.0, | |
| ) -> Dict: | |
| """Find time blocks where pressure exceeded a threshold""" | |
| if pressure_tag not in df.columns: | |
| # Try long format | |
| pressure_data = df[df.get('tag_name', pd.Series()) == pressure_tag].copy() | |
| if pressure_data.empty: | |
| return {'error': f'No data for {pressure_tag}', 'num_blocks': 0} | |
| pressure_data = pressure_data.sort_values('timestamp') | |
| values = pressure_data['value'] | |
| timestamps = pressure_data['timestamp'] | |
| else: | |
| # Wide format | |
| pressure_data = df[['timestamp', pressure_tag]].dropna(subset=[pressure_tag]).copy() | |
| pressure_data = pressure_data.sort_values('timestamp') | |
| values = pressure_data[pressure_tag] | |
| timestamps = pressure_data['timestamp'] | |
| above = values > threshold_bar | |
| if not above.any(): | |
| return { | |
| 'threshold': threshold_bar, | |
| 'sensor': pressure_tag, | |
| 'num_blocks': 0, | |
| 'total_points_above': 0, | |
| 'message': f'No data points exceeded {threshold_bar} bar', | |
| } | |
| # Group consecutive above-threshold points | |
| above_df = pd.DataFrame({'timestamp': timestamps[above].values, 'value': values[above].values}) | |
| blocks = self._group_consecutive_timestamps(above_df) | |
| block_stats = [] | |
| for block in blocks: | |
| mask = (above_df['timestamp'] >= block['start']) & (above_df['timestamp'] <= block['end']) | |
| block_data = above_df[mask] | |
| block_stats.append({ | |
| 'start_time': block['start'], | |
| 'end_time': block['end'], | |
| 'duration_minutes': (block['end'] - block['start']).total_seconds() / 60, | |
| 'num_points': len(block_data), | |
| 'peak_pressure': block_data['value'].max(), | |
| 'avg_pressure': block_data['value'].mean(), | |
| }) | |
| return { | |
| 'threshold': threshold_bar, | |
| 'sensor': pressure_tag, | |
| 'num_blocks': len(block_stats), | |
| 'total_points_above': int(above.sum()), | |
| 'percentage_above': (above.sum() / len(values)) * 100, | |
| 'blocks': block_stats, | |
| 'overall_peak': values.max(), | |
| } | |
| def detect_constant_periods( | |
| self, | |
| df: pd.DataFrame, | |
| tag: str, | |
| tolerance: float = 0.01, | |
| min_duration_minutes: float = 2.0, | |
| ) -> List[Dict]: | |
| """ | |
| Detect periods where a sensor value is constant within tolerance. | |
| Works with both wide-format (tag as column) and long-format data. | |
| """ | |
| if tag in df.columns: | |
| # Wide format | |
| sensor_data = df[['timestamp', tag]].dropna(subset=[tag]).sort_values('timestamp') | |
| timestamps = sensor_data['timestamp'].values | |
| values = sensor_data[tag].values | |
| else: | |
| # Long format | |
| sensor_data = df[df.get('tag_name', pd.Series()) == tag].sort_values('timestamp') | |
| if sensor_data.empty: | |
| return [] | |
| timestamps = sensor_data['timestamp'].values | |
| values = sensor_data['value'].values | |
| if len(values) < 2: | |
| return [] | |
| constant_periods = [] | |
| period_start = 0 | |
| period_value = values[0] | |
| for i in range(1, len(values)): | |
| # Check if value is constant within tolerance | |
| denom = abs(period_value) + 1e-10 | |
| if abs(values[i] - period_value) / denom <= tolerance: | |
| continue | |
| # Period ended — check duration | |
| duration_sec = (pd.Timestamp(timestamps[i - 1]) - pd.Timestamp(timestamps[period_start])).total_seconds() | |
| duration_min = duration_sec / 60 | |
| if duration_min >= min_duration_minutes: | |
| constant_periods.append({ | |
| 'start': pd.Timestamp(timestamps[period_start]), | |
| 'end': pd.Timestamp(timestamps[i - 1]), | |
| 'duration_minutes': duration_min, | |
| 'value': float(period_value), | |
| }) | |
| period_start = i | |
| period_value = values[i] | |
| # Check last period | |
| duration_sec = (pd.Timestamp(timestamps[-1]) - pd.Timestamp(timestamps[period_start])).total_seconds() | |
| duration_min = duration_sec / 60 | |
| if duration_min >= min_duration_minutes: | |
| constant_periods.append({ | |
| 'start': pd.Timestamp(timestamps[period_start]), | |
| 'end': pd.Timestamp(timestamps[-1]), | |
| 'duration_minutes': duration_min, | |
| 'value': float(period_value), | |
| }) | |
| return constant_periods | |
| def _group_consecutive_timestamps(self, df: pd.DataFrame, max_gap_seconds: float = 2.0) -> List[Dict]: | |
| """Group consecutive timestamps into blocks""" | |
| if df.empty: | |
| return [] | |
| blocks = [] | |
| current_start = df.iloc[0]['timestamp'] | |
| current_end = df.iloc[0]['timestamp'] | |
| for i in range(1, len(df)): | |
| t = df.iloc[i]['timestamp'] | |
| gap = (t - current_end).total_seconds() if hasattr(t - current_end, 'total_seconds') else 0 | |
| if gap <= max_gap_seconds: | |
| current_end = t | |
| else: | |
| blocks.append({'start': current_start, 'end': current_end}) | |
| current_start = t | |
| current_end = t | |
| blocks.append({'start': current_start, 'end': current_end}) | |
| return blocks | |