delphi-pump-analytics / analysis /basic_analysis.py
pandeydigant31's picture
Initial deploy: DELPHI Pump Analytics (renamed from MURPHY)
199bfa3 verified
Raw
History Blame Contribute Delete
7 kB
"""
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