From 81b0ad0124847f083990d574dc8d20961ec6e713 Mon Sep 17 00:00:00 2001
From: baoshiwei <baoshiwei@shlanbao.cn>
Date: 星期三, 01 四月 2026 14:12:55 +0800
Subject: [PATCH] feat(参数调节): 添加优化版挤出机参数调节页面

---
 app/pages/metered_weight_correlation.py |  448 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 448 insertions(+), 0 deletions(-)

diff --git a/app/pages/metered_weight_correlation.py b/app/pages/metered_weight_correlation.py
index 0afb421..d6d862f 100644
--- a/app/pages/metered_weight_correlation.py
+++ b/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="鍕鹃�夊悗锛岀浉鍏虫�у垎鏋愪粎浣跨敤璇嗗埆鍑虹殑绋虫�佹暟鎹�"
+            )
 
     # 杞崲涓篸atetime瀵硅薄
     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_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'
+                    }
+                    
+                    # 娣诲姞棰濆鐨剏杞撮厤缃�
+                    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)
 

--
Gitblit v1.9.3