baoshiwei
2026-04-01 81b0ad0124847f083990d574dc8d20961ec6e713
app/pages/metered_weight_correlation.py
@@ -6,6 +6,118 @@
from datetime import datetime, timedelta
from app.services.extruder_service import ExtruderService
from app.services.main_process_service import MainProcessService
from app.services.data_processing_service import DataProcessingService
# 稳态识别类定义
class SteadyStateDetector:
    def __init__(self):
        self.data_processor = DataProcessingService()
    def preprocess_data(self, df, weight_col='metered_weight', window_size=20):
        if df is None or df.empty:
            return df
        df_processed = df.copy()
        df_processed[weight_col] = df_processed[weight_col].ffill().bfill()
        df_processed['smoothed_weight'] = df_processed[weight_col]
        df_processed['rolling_std'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).std()
        df_processed['rolling_mean'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).mean()
        return df_processed
    def detect_steady_state(self, df, weight_col='smoothed_weight', window_size=20, std_threshold=0.5, duration_threshold=60):
        if df is None or df.empty:
            return df, []
        df['time'] = pd.to_datetime(df['time'])
        df['time_diff'] = df['time'].diff().dt.total_seconds().fillna(0)
        df['is_steady'] = 0
        df['window_std'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).std()
        df['window_mean'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).mean()
        df['fluctuation_range'] = (df['window_std'] / df['window_mean']) * 100
        df['fluctuation_range'] = df['fluctuation_range'].fillna(0)
        df.loc[(df['fluctuation_range'] < std_threshold) & (df['smoothed_weight'] >= 0.1), 'is_steady'] = 1
        steady_segments = []
        current_segment = {}
        for i, row in df.iterrows():
            if row['is_steady'] == 1:
                if not current_segment:
                    current_segment = {
                        'start_time': row['time'],
                        'start_idx': i,
                        'weights': [row['smoothed_weight']]
                    }
                else:
                    current_segment['weights'].append(row['smoothed_weight'])
            else:
                if current_segment:
                    current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time']
                    current_segment['end_idx'] = i-1
                    duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
                    if duration >= duration_threshold:
                        weights_array = np.array(current_segment['weights'])
                        current_segment['duration'] = duration
                        current_segment['mean_weight'] = np.mean(weights_array)
                        current_segment['std_weight'] = np.std(weights_array)
                        current_segment['min_weight'] = np.min(weights_array)
                        current_segment['max_weight'] = np.max(weights_array)
                        current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                        confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                        confidence = max(50, min(100, confidence))
                        current_segment['confidence'] = confidence
                        steady_segments.append(current_segment)
                    current_segment = {}
        if current_segment:
            current_segment['end_time'] = df['time'].iloc[-1]
            current_segment['end_idx'] = len(df) - 1
            duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds()
            if duration >= duration_threshold:
                weights_array = np.array(current_segment['weights'])
                current_segment['duration'] = duration
                current_segment['mean_weight'] = np.mean(weights_array)
                current_segment['std_weight'] = np.std(weights_array)
                current_segment['min_weight'] = np.min(weights_array)
                current_segment['max_weight'] = np.max(weights_array)
                current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100
                confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50
                confidence = max(50, min(100, confidence))
                current_segment['confidence'] = confidence
                steady_segments.append(current_segment)
        for segment in steady_segments:
            df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1
        return df, steady_segments
    def get_steady_state_metrics(self, steady_segments):
        if not steady_segments:
            return {}
        avg_duration = np.mean([seg['duration'] for seg in steady_segments])
        avg_fluctuation = np.mean([seg['fluctuation_range'] for seg in steady_segments])
        avg_confidence = np.mean([seg['confidence'] for seg in steady_segments])
        total_steady_duration = sum([seg['duration'] for seg in steady_segments])
        return {
            'total_steady_segments': len(steady_segments),
            'average_steady_duration': avg_duration,
            'average_fluctuation_range': avg_fluctuation,
            'average_confidence': avg_confidence,
            'total_steady_duration': total_steady_duration
        }
def show_metered_weight_correlation():
    # 初始化服务
@@ -14,6 +126,11 @@
    # 页面标题
    st.title("米重相关性分析")
    # 初始化服务
    extruder_service = ExtruderService()
    main_process_service = MainProcessService()
    steady_state_detector = SteadyStateDetector()
    # 初始化会话状态用于日期同步
    if 'mc_start_date' not in st.session_state:
@@ -24,6 +141,16 @@
        st.session_state['mc_quick_select'] = "最近7天"
    if 'mc_time_offset' not in st.session_state:
        st.session_state['mc_time_offset'] = 0.0
    # 初始化稳态识别相关参数
    if 'mc_ss_window_size' not in st.session_state:
        st.session_state['mc_ss_window_size'] = 20
    if 'mc_ss_std_threshold' not in st.session_state:
        st.session_state['mc_ss_std_threshold'] = 1.5
    if 'mc_ss_duration_threshold' not in st.session_state:
        st.session_state['mc_ss_duration_threshold'] = 60
    if 'mc_use_steady_only' not in st.session_state:
        st.session_state['mc_use_steady_only'] = False
    # 定义回调函数
    def update_dates(qs):
@@ -114,6 +241,55 @@
            st.session_state['mc_time_offset'] = time_offset
        with offset_cols[2]:
            st.write(f"当前偏移: {time_offset} 分钟")
        # 稳态参数配置
        st.markdown("---")
        steady_state_cols = st.columns(4)
        with steady_state_cols[0]:
            st.write("⚙️ **稳态参数配置**")
            window_size = st.slider(
                "滑动窗口大小 (秒)",
                min_value=5,
                max_value=60,
                value=st.session_state['mc_ss_window_size'],
                step=5,
                key="mc_ss_window_size",
                help="用于平滑数据和计算统计特征的滑动窗口大小"
            )
        with steady_state_cols[1]:
            st.write("📏 **波动阈值配置**")
            std_threshold = st.slider(
                "标准差阈值",
                min_value=0.1,
                max_value=2.0,
                value=st.session_state['mc_ss_std_threshold'],
                step=0.1,
                key="mc_ss_std_threshold",
                help="米重波动的标准差阈值,低于此值视为稳态"
            )
        with steady_state_cols[2]:
            st.write("⏱️ **持续时间配置**")
            duration_threshold = st.slider(
                "稳态持续时间 (秒)",
                min_value=30,
                max_value=300,
                value=st.session_state['mc_ss_duration_threshold'],
                step=10,
                key="mc_ss_duration_threshold",
                help="稳态持续的最小时间,低于此值不视为稳态段"
            )
        with steady_state_cols[3]:
            st.write("🎯 **分析数据选择**")
            use_steady_only = st.checkbox(
                "仅使用稳态数据",
                value=st.session_state['mc_use_steady_only'],
                key="mc_use_steady_only",
                help="勾选后,相关性分析仅使用识别出的稳态数据"
            )
    # 转换为datetime对象
    start_dt = datetime.combine(start_date, datetime.min.time())
@@ -264,6 +440,32 @@
                
                return df_merged
            # 执行稳态识别
            df_extruder_steady, steady_segments = None, []
            if df_extruder_filtered is not None and not df_extruder_filtered.empty:
                # 数据预处理
                df_processed = steady_state_detector.preprocess_data(
                    df_extruder_filtered,
                    weight_col='metered_weight',
                    window_size=st.session_state['mc_ss_window_size']
                )
                # 稳态检测
                df_extruder_steady, steady_segments = steady_state_detector.detect_steady_state(
                    df_processed,
                    weight_col='smoothed_weight',
                    window_size=st.session_state['mc_ss_window_size'],
                    std_threshold=st.session_state['mc_ss_std_threshold'],
                    duration_threshold=st.session_state['mc_ss_duration_threshold']
                )
                # 将稳态标记添加到df_extruder_filtered中,以便在趋势图中使用
                df_extruder_filtered = df_extruder_filtered.merge(
                    df_extruder_steady[['time', 'is_steady', 'smoothed_weight', 'fluctuation_range']],
                    on='time',
                    how='left'
                )
            # 执行数据整合
            df_analysis = integrate_data(df_extruder_filtered, df_main_speed, df_temp)
            
@@ -273,6 +475,26 @@
            # 重命名米重列
            df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True)
            # 如果选择仅使用稳态数据,过滤掉非稳态数据
            if st.session_state['mc_use_steady_only']:
                # 确保df_analysis包含is_steady列
                if 'is_steady' not in df_analysis.columns:
                    # 将稳态标记合并到分析数据中
                    df_analysis = df_analysis.merge(
                        df_extruder_steady[['time', 'is_steady']],
                        on='time',
                        how='left'
                    )
                # 过滤稳态数据
                df_analysis = df_analysis[df_analysis['is_steady'] == 1]
                if df_analysis.empty:
                    st.warning("未找到稳态数据,请调整稳态参数或取消'仅使用稳态数据'选项。")
                    return
            # 缓存稳态数据到会话状态
            st.session_state['cached_steady_segments'] = steady_segments
            st.session_state['cached_extruder_steady'] = df_extruder_steady
            # --- 原始数据趋势图 ---
            st.subheader("📈 原始数据趋势图")
@@ -360,6 +582,24 @@
                    yaxis='y5'
                ))
            
            # 添加稳态区域标记
            for segment in steady_segments:
                # 获取米重数据的y轴范围,用于确定矩形高度
                y_min = df_extruder_filtered['metered_weight'].min() * 0.95
                y_max = df_extruder_filtered['metered_weight'].max() * 1.05
                fig_trend.add_shape(
                    type="rect",
                    x0=segment['start_time'],
                    y0=y_min,
                    x1=segment['end_time'],
                    y1=y_max,
                    fillcolor="rgba(0, 255, 0, 0.2)",
                    line=dict(color="rgba(0, 200, 0, 0.5)", width=1),
                    name="稳态区域",
                    layer="below"
                )
            # 配置趋势图布局
            fig_trend.update_layout(
                title=f'原始数据趋势 (米重向前偏移 {st.session_state["mc_time_offset"]} 分钟)',
@@ -423,6 +663,214 @@
            # 显示趋势图
            selection = st.plotly_chart(fig_trend, width='stretch', config={'scrollZoom': True}, on_select='rerun' )
            # 稳态统计指标
            st.subheader("📊 稳态识别统计")
            steady_metrics = steady_state_detector.get_steady_state_metrics(steady_segments)
            metrics_cols = st.columns(5)
            with metrics_cols[0]:
                st.metric(
                    "稳态段总数",
                    steady_metrics.get('total_steady_segments', 0),
                    help="识别到的稳态段数量"
                )
            with metrics_cols[1]:
                st.metric(
                    "平均稳态时长",
                    f"{steady_metrics.get('average_steady_duration', 0):.2f} 秒",
                    help="所有稳态段的平均持续时间"
                )
            with metrics_cols[2]:
                st.metric(
                    "平均波动范围",
                    f"{steady_metrics.get('average_fluctuation_range', 0):.2f}%",
                    help="稳态段内米重的平均波动范围(相对于均值的百分比)"
                )
            with metrics_cols[3]:
                st.metric(
                    "平均置信度",
                    f"{steady_metrics.get('average_confidence', 0):.1f}%",
                    help="稳态识别结果的平均置信度"
                )
            with metrics_cols[4]:
                st.metric(
                    "总稳态时长",
                    f"{steady_metrics.get('total_steady_duration', 0)/60:.2f} 分钟",
                    help="所有稳态段的总持续时间"
                )
            # --- 稳态数据趋势图 ---
            st.subheader("📊 稳态数据趋势图")
            # 创建稳态数据趋势图
            if df_extruder_steady is not None and not df_extruder_steady.empty:
                fig_steady = go.Figure()
                # 添加原始米重数据
                fig_steady.add_trace(go.Scatter(
                    x=df_extruder_steady['time'],
                    y=df_extruder_steady['metered_weight'],
                    name='原始米重',
                    mode='lines',
                    opacity=0.6,
                    line=dict(color='lightblue', width=1)
                ))
                # 添加平滑后的米重数据
                fig_steady.add_trace(go.Scatter(
                    x=df_extruder_steady['time'],
                    y=df_extruder_steady['smoothed_weight'],
                    name='平滑米重',
                    mode='lines',
                    line=dict(color='blue', width=2)
                ))
                # 添加波动范围(作为面积图)
                fig_steady.add_trace(go.Scatter(
                    x=df_extruder_steady['time'],
                    y=df_extruder_steady['metered_weight'] + df_extruder_steady['rolling_std'],
                    name='波动上限',
                    mode='lines',
                    line=dict(color='rgba(255,0,0,0)'),
                    showlegend=True
                ))
                fig_steady.add_trace(go.Scatter(
                    x=df_extruder_steady['time'],
                    y=df_extruder_steady['metered_weight'] - df_extruder_steady['rolling_std'],
                    name='波动下限',
                    mode='lines',
                    line=dict(color='rgba(255,0,0,0)'),
                    fill='tonexty',
                    fillcolor='rgba(255,0,0,0.1)'
                ))
                # 添加稳态标记
                fig_steady.add_trace(go.Scatter(
                    x=df_extruder_steady['time'],
                    y=df_extruder_steady['is_steady'] * (df_extruder_steady['metered_weight'].max() * 1.1),
                    name='稳态标记',
                    mode='lines',
                    line=dict(color='green', width=1, dash='dash')
                ))
                # 配置稳态数据趋势图布局
                fig_steady.update_layout(
                    title="稳态数据趋势分析",
                    xaxis=dict(
                        title='时间',
                        rangeslider=dict(visible=True),
                        type='date'
                    ),
                    yaxis=dict(
                        title='米重 (Kg/m)',
                        title_font=dict(color='blue'),
                        tickfont=dict(color='blue')
                    ),
                    yaxis2=dict(
                        title='稳态标记',
                        title_font=dict(color='green'),
                        tickfont=dict(color='green'),
                        overlaying='y',
                        side='right',
                        range=[-0.1, 1.1],
                        showgrid=False
                    ),
                    legend=dict(
                        orientation="h",
                        yanchor="bottom",
                        y=1.02,
                        xanchor="right",
                        x=1
                    ),
                    height=500,
                    margin=dict(l=100, r=100, t=100, b=100),
                    hovermode='x unified'
                )
                # 显示稳态数据趋势图
                st.plotly_chart(fig_steady, width='stretch', config={'scrollZoom': True})
            # --- 稳态参数相关性趋势图 ---
            st.subheader("📈 稳态参数相关性趋势图")
            if df_analysis is not None and not df_analysis.empty:
                # 创建稳态参数相关性趋势图
                fig_steady_corr = go.Figure()
                # 选择相关系数最高的前3个参数
                if '米重' in df_analysis.columns:
                    # 计算各参数与米重的相关系数
                    corr_with_weight = df_analysis.corr()[['米重']].sort_values('米重', ascending=False)
                    top_params = corr_with_weight.index[1:4]  # 排除米重本身,取前3个
                    # 添加米重数据
                    fig_steady_corr.add_trace(go.Scatter(
                        x=df_analysis['time'],
                        y=df_analysis['米重'],
                        name='米重',
                        mode='lines',
                        line=dict(color='blue', width=2),
                        yaxis='y'
                    ))
                    # 添加相关参数数据
                    colors = ['red', 'green', 'orange']
                    for i, param in enumerate(top_params):
                        if param in df_analysis.columns:
                            fig_steady_corr.add_trace(go.Scatter(
                                x=df_analysis['time'],
                                y=df_analysis[param],
                                name=param,
                                mode='lines',
                                line=dict(color=colors[i], width=1.5),
                                yaxis=f'y{i+2}'
                            ))
                    # 配置图表布局
                    layout = {
                        'title': f'米重与相关参数趋势(前3个相关参数)',
                        'xaxis': {
                            'title': '时间',
                            'rangeslider': dict(visible=True),
                            'type': 'date'
                        },
                        'yaxis': {
                            'title': '米重 (Kg/m)',
                            'title_font': dict(color='blue'),
                            'tickfont': dict(color='blue')
                        },
                        'legend': {
                            'orientation': "h",
                            'yanchor': "bottom",
                            'y': 1.02,
                            'xanchor': "right",
                            'x': 1
                        },
                        'height': 600,
                        'margin': dict(l=100, r=200, t=100, b=100),
                        'hovermode': 'x unified'
                    }
                    # 添加额外的y轴配置
                    for i, param in enumerate(top_params):
                        layout[f'yaxis{i+2}'] = {
                            'title': param,
                            'title_font': dict(color=colors[i]),
                            'tickfont': dict(color=colors[i]),
                            'overlaying': 'y',
                            'side': 'right',
                            'anchor': 'free',
                            'position': 1 - (i+1)*0.15
                        }
                    fig_steady_corr.update_layout(layout)
                    st.plotly_chart(fig_steady_corr, width='stretch', config={'scrollZoom': True})
            # 调试输出
          #  st.write("原始 selection 对象:", selection)