From 6628f663b636675bcaea316f2deaddf337de480e Mon Sep 17 00:00:00 2001
From: baoshiwei <baoshiwei@shlanbao.cn>
Date: 星期五, 13 三月 2026 10:23:31 +0800
Subject: [PATCH] feat(米重分析): 新增稳态识别和预测功能页面并优化现有模型

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

diff --git a/app/pages/metered_weight_regression.py b/app/pages/metered_weight_regression.py
new file mode 100644
index 0000000..d06dc68
--- /dev/null
+++ b/app/pages/metered_weight_regression.py
@@ -0,0 +1,856 @@
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import pandas as pd
+import numpy as np
+import joblib
+import os
+from datetime import datetime, timedelta
+from app.services.extruder_service import ExtruderService
+from app.services.main_process_service import MainProcessService
+from sklearn.linear_model import LinearRegression
+from sklearn.model_selection import train_test_split
+from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
+
+
+# 瀵煎叆绋虫�佽瘑鍒姛鑳�
+class SteadyStateDetector:
+    def __init__(self):
+        pass
+    
+    def detect_steady_state(self, df, weight_col='绫抽噸', window_size=20, std_threshold=0.5, duration_threshold=60):
+        """
+        绋虫�佽瘑鍒�昏緫锛氭爣璁扮背閲嶆暟鎹腑鐨勭ǔ鎬佹
+        :param df: 鍖呭惈绫抽噸鏁版嵁鐨勬暟鎹
+        :param weight_col: 绫抽噸鍒楀悕
+        :param window_size: 婊戝姩绐楀彛澶у皬锛堢锛�
+        :param std_threshold: 鏍囧噯宸槇鍊�
+        :param duration_threshold: 绋虫�佹寔缁椂闂撮槇鍊硷紙绉掞級
+        :return: 鍖呭惈绋虫�佹爣璁扮殑鏁版嵁妗嗗拰绋虫�佷俊鎭�
+        """
+        if df is None or df.empty:
+            return df, []
+        
+        # 纭繚鏃堕棿鍒楁槸datetime绫诲瀷
+        df['time'] = pd.to_datetime(df['time'])
+        
+        # 璁$畻婊氬姩缁熻閲�
+        df['rolling_std'] = df[weight_col].rolling(window=window_size, min_periods=5).std()
+        df['rolling_mean'] = df[weight_col].rolling(window=window_size, min_periods=5).mean()
+        
+        # 璁$畻娉㈠姩鑼冨洿
+        df['fluctuation_range'] = (df['rolling_std'] / df['rolling_mean']) * 100
+        df['fluctuation_range'] = df['fluctuation_range'].fillna(0)
+        
+        # 鏍囪绋虫�佺偣
+        df['is_steady'] = 0
+        steady_condition = (
+            (df['fluctuation_range'] < std_threshold) & 
+            (df[weight_col] >= 0.1) 
+        )
+        df.loc[steady_condition, '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[weight_col]]
+                    }
+                else:
+                    current_segment['weights'].append(row[weight_col])
+            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 show_metered_weight_regression():
+    # 鍒濆鍖栨湇鍔�
+    extruder_service = ExtruderService()
+    main_process_service = MainProcessService()
+
+    # 椤甸潰鏍囬
+    st.title("绫抽噸澶氬厓绾挎�у洖褰掑垎鏋�")
+
+    # 鍒濆鍖栦細璇濈姸鎬佺敤浜庢棩鏈熷悓姝�
+    if 'mr_start_date' not in st.session_state:
+        st.session_state['mr_start_date'] = datetime.now().date() - timedelta(days=7)
+    if 'mr_end_date' not in st.session_state:
+        st.session_state['mr_end_date'] = datetime.now().date()
+    if 'mr_quick_select' not in st.session_state:
+        st.session_state['mr_quick_select'] = "鏈�杩�7澶�"
+    if 'mr_time_offset' not in st.session_state:
+        st.session_state['mr_time_offset'] = 0.0
+    if 'mr_selected_features' not in st.session_state:
+        st.session_state['mr_selected_features'] = [
+            '铻烘潌杞��', '鏈哄ご鍘嬪姏', '娴佺▼涓婚��', '铻烘潌娓╁害', 
+            '鍚庢満绛掓俯搴�', '鍓嶆満绛掓俯搴�', '鏈哄ご娓╁害'
+        ]
+    if 'mr_use_steady_data' not in st.session_state:
+        st.session_state['mr_use_steady_data'] = True
+    if 'mr_steady_window' not in st.session_state:
+        st.session_state['mr_steady_window'] = 20
+    if 'mr_steady_threshold' not in st.session_state:
+        st.session_state['mr_steady_threshold'] = 0.5
+
+    # 瀹氫箟鍥炶皟鍑芥暟
+    def update_dates(qs):
+        st.session_state['mr_quick_select'] = qs
+        today = datetime.now().date()
+        if qs == "浠婂ぉ":
+            st.session_state['mr_start_date'] = today
+            st.session_state['mr_end_date'] = today
+        elif qs == "鏈�杩�3澶�":
+            st.session_state['mr_start_date'] = today - timedelta(days=3)
+            st.session_state['mr_end_date'] = today
+        elif qs == "鏈�杩�7澶�":
+            st.session_state['mr_start_date'] = today - timedelta(days=7)
+            st.session_state['mr_end_date'] = today
+        elif qs == "鏈�杩�30澶�":
+            st.session_state['mr_start_date'] = today - timedelta(days=30)
+            st.session_state['mr_end_date'] = today
+
+    def on_date_change():
+        st.session_state['mr_quick_select'] = "鑷畾涔�"
+
+    # 鏌ヨ鏉′欢鍖哄煙
+    with st.expander("馃攳 鏌ヨ閰嶇疆", expanded=True):
+        # 娣诲姞鑷畾涔� CSS 瀹炵幇鍝嶅簲寮忔崲琛�
+        st.markdown("""
+            <style>
+            /* 寮哄埗鍒楀鍣ㄦ崲琛� */
+            [data-testid="stExpander"] [data-testid="column"] {
+                flex: 1 1 120px !important;
+                min-width: 120px !important;
+            }
+            /* 閽堝鏃ユ湡杈撳叆妗嗗垪绋嶅井鍔犲涓�鐐� */
+            @media (min-width: 768px) {
+                [data-testid="stExpander"] [data-testid="column"]:nth-child(6),
+                [data-testid="stExpander"] [data-testid="column"]:nth-child(7) {
+                    flex: 2 1 180px !important;
+                    min-width: 180px !important;
+                }
+            }
+            </style>
+            """, unsafe_allow_html=True)
+
+        # 鍒涘缓甯冨眬
+        cols = st.columns([1, 1, 1, 1, 1, 1.5, 1.5, 1])
+
+        options = ["浠婂ぉ", "鏈�杩�3澶�", "鏈�杩�7澶�", "鏈�杩�30澶�", "鑷畾涔�"]
+        for i, option in enumerate(options):
+            with cols[i]:
+                # 鏍规嵁褰撳墠閫夋嫨鐘舵�佸喅瀹氭寜閽被鍨�
+                button_type = "primary" if st.session_state['mr_quick_select'] == option else "secondary"
+                if st.button(option, key=f"btn_mr_{option}", width='stretch', type=button_type):
+                    update_dates(option)
+                    st.rerun()
+
+        with cols[5]:
+            start_date = st.date_input(
+                "寮�濮嬫棩鏈�",
+                label_visibility="collapsed",
+                key="mr_start_date",
+                on_change=on_date_change
+            )
+
+        with cols[6]:
+            end_date = st.date_input(
+                "缁撴潫鏃ユ湡",
+                label_visibility="collapsed",
+                key="mr_end_date",
+                on_change=on_date_change
+            )
+
+        with cols[7]:
+            query_button = st.button("馃殌 寮�濮嬪垎鏋�", key="mr_query", width='stretch')
+
+        # 鏁版嵁瀵归綈璋冩暣
+        st.markdown("---")
+        offset_cols = st.columns([2, 4, 2])
+        with offset_cols[0]:
+            st.write("鈴憋笍 **鏁版嵁瀵归綈璋冩暣**")
+        with offset_cols[1]:
+            time_offset = st.slider(
+                "鏃堕棿鍋忕Щ (鍒嗛挓)",
+                min_value=0.0,
+                max_value=5.0,
+                value=st.session_state['mr_time_offset'],
+                step=0.1,
+                help="璋冩暣涓绘祦绋嬪拰娓╁害鏁版嵁鐨勬椂闂村亸绉伙紝浣垮叾涓庢尋鍑烘満绫抽噸鏁版嵁瀵归綈銆�"
+            )
+            st.session_state['mr_time_offset'] = time_offset
+        with offset_cols[2]:
+            st.write(f"褰撳墠鍋忕Щ: {time_offset} 鍒嗛挓")
+
+        # 绋虫�佽瘑鍒厤缃�
+        st.markdown("---")
+        steady_cols = st.columns(3)
+        with steady_cols[0]:
+            st.write("鈿栵笍 **绋虫�佽瘑鍒厤缃�**")
+            st.checkbox(
+                "浠呬娇鐢ㄧǔ鎬佹暟鎹繘琛岃缁�",
+                value=st.session_state['mr_use_steady_data'],
+                key="mr_use_steady_data",
+                help="鍚敤鍚庯紝鍙娇鐢ㄧ背閲嶇ǔ鎬佹椂娈电殑鏁版嵁杩涜妯″瀷璁粌"
+            )
+        
+        with steady_cols[1]:
+            st.write("馃搹 **绋虫�佸弬鏁�**")
+            st.slider(
+                "婊戝姩绐楀彛澶у皬 (绉�)",
+                min_value=5,
+                max_value=60,
+                value=st.session_state['mr_steady_window'],
+                step=5,
+                key="mr_steady_window",
+                help="鐢ㄤ簬绋虫�佽瘑鍒殑婊戝姩绐楀彛澶у皬"
+            )
+        
+        with steady_cols[2]:
+            st.write("馃搳 **绋虫�侀槇鍊�**")
+            st.slider(
+                "娉㈠姩闃堝�� (%)",
+                min_value=0.1,
+                max_value=2.0,
+                value=st.session_state['mr_steady_threshold'],
+                step=0.1,
+                key="mr_steady_threshold",
+                help="绋虫�佽瘑鍒殑娉㈠姩鑼冨洿闃堝��"
+            )
+
+        # 鐗瑰緛閫夋嫨
+        st.markdown("---")
+        st.write("馃搵 **鐗瑰緛閫夋嫨**")
+        feature_cols = st.columns(2)
+        all_features = [
+            '铻烘潌杞��', '鏈哄ご鍘嬪姏', '娴佺▼涓婚��', '铻烘潌娓╁害',
+            '鍚庢満绛掓俯搴�', '鍓嶆満绛掓俯搴�', '鏈哄ご娓╁害'
+        ]
+        for i, feature in enumerate(all_features):
+            with feature_cols[i % 2]:
+                st.session_state['mr_selected_features'] = [
+                    f for f in st.session_state['mr_selected_features'] if f in all_features
+                ]
+                if st.checkbox(
+                    feature, 
+                    key=f"feat_{feature}",
+                    value=feature in st.session_state['mr_selected_features']
+                ):
+                    if feature not in st.session_state['mr_selected_features']:
+                        st.session_state['mr_selected_features'].append(feature)
+                else:
+                    if feature in st.session_state['mr_selected_features']:
+                        st.session_state['mr_selected_features'].remove(feature)
+
+        if not st.session_state['mr_selected_features']:
+            st.warning("鑷冲皯闇�瑕侀�夋嫨涓�涓壒寰佸彉閲�")
+
+    # 杞崲涓篸atetime瀵硅薄
+    start_dt = datetime.combine(start_date, datetime.min.time())
+    end_dt = datetime.combine(end_date, datetime.max.time())
+
+    # 鏌ヨ澶勭悊 - 浠呰幏鍙栨暟鎹苟缂撳瓨鍒颁細璇濈姸鎬�
+    if query_button:
+        with st.spinner("姝e湪鑾峰彇鏁版嵁..."):
+            # 1. 鑾峰彇瀹屾暣鐨勬尋鍑烘満鏁版嵁锛堝寘鍚墍鏈夋椂闂寸偣鐨勮灪鏉嗚浆閫熷拰鏈哄ご鍘嬪姏锛�
+            df_extruder_full = extruder_service.get_extruder_data(start_dt, end_dt)
+
+            # 2. 鑾峰彇涓绘祦绋嬫帶鍒舵暟鎹�
+            df_main_speed = main_process_service.get_cutting_setting_data(start_dt, end_dt)
+
+            df_temp = main_process_service.get_temperature_control_data(start_dt, end_dt)
+
+            # 妫�鏌ユ槸鍚︽湁鏁版嵁
+            has_data = any([
+                df_extruder_full is not None and not df_extruder_full.empty,
+                df_main_speed is not None and not df_main_speed.empty,
+                df_temp is not None and not df_temp.empty
+            ])
+
+            if not has_data:
+                st.warning("鎵�閫夋椂闂存鍐呮湭鎵惧埌浠讳綍鏁版嵁锛岃灏濊瘯璋冩暣鏌ヨ鏉′欢銆�")
+                # 娓呴櫎缂撳瓨鏁版嵁
+                for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp', 'last_query_start', 'last_query_end']:
+                    if key in st.session_state:
+                        del st.session_state[key]
+                return
+
+            # 缂撳瓨鏁版嵁鍒颁細璇濈姸鎬�
+            st.session_state['cached_extruder_full'] = df_extruder_full
+            st.session_state['cached_main_speed'] = df_main_speed
+            st.session_state['cached_temp'] = df_temp
+            st.session_state['last_query_start'] = start_dt
+            st.session_state['last_query_end'] = end_dt
+
+    # 鏁版嵁澶勭悊鍜屽浘琛ㄦ覆鏌� - 姣忔搴旂敤閲嶆柊杩愯鏃舵墽琛岋紙鍖呮嫭璋冩暣鏃堕棿鍋忕Щ鏃讹級
+    if all(key in st.session_state for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp']):
+        with st.spinner("姝e湪鍒嗘瀽鏁版嵁..."):
+            # 鑾峰彇缂撳瓨鏁版嵁
+            df_extruder_full = st.session_state['cached_extruder_full']
+            df_main_speed = st.session_state['cached_main_speed']
+            df_temp = st.session_state['cached_temp']
+
+            # 鑾峰彇褰撳墠鏃堕棿鍋忕Щ閲�
+            offset_delta = timedelta(minutes=st.session_state['mr_time_offset'])
+
+            # 澶勭悊鏁版嵁
+            if df_extruder_full is not None and not df_extruder_full.empty:
+                # 杩囨护鏈哄ご鍘嬪姏澶т簬2鐨勫��
+                df_extruder_filtered = df_extruder_full[df_extruder_full['head_pressure'] <= 2]
+
+                # 涓虹背閲嶆暟鎹垱寤哄亸绉诲悗鐨勬椂闂村垪锛堝彧瀵圭背閲嶆暟鎹繘琛屾椂闂村亸绉伙級
+                df_extruder_filtered['weight_time'] = df_extruder_filtered['time'] - offset_delta
+            else:
+                df_extruder_filtered = None
+
+            # 妫�鏌ユ槸鍚︽湁鏁版嵁
+            has_data = any([
+                df_extruder_filtered is not None and not df_extruder_filtered.empty,
+                df_main_speed is not None and not df_main_speed.empty,
+                df_temp is not None and not df_temp.empty
+            ])
+
+            if not has_data:
+                st.warning("鎵�閫夋椂闂存鍐呮湭鎵惧埌浠讳綍鏁版嵁锛岃灏濊瘯璋冩暣鏌ヨ鏉′欢銆�")
+                return
+
+            # 鏁版嵁鏁村悎涓庨澶勭悊
+            def integrate_data(df_extruder_filtered, df_main_speed, df_temp):
+                # 纭繚鎸ゅ嚭鏈烘暟鎹瓨鍦�
+                if df_extruder_filtered is None or df_extruder_filtered.empty:
+                    return None
+
+                # 鍒涘缓鍙寘鍚背閲嶅拰鍋忕Щ鏃堕棿鐨勪富鏁版嵁闆�
+                df_weight = df_extruder_filtered[['weight_time', 'metered_weight']].copy()
+                df_weight.rename(columns={'weight_time': 'time'}, inplace=True)  # 灏唚eight_time閲嶅懡鍚嶄负time浣滀负鍩哄噯鏃堕棿
+
+                # 鍒涘缓鍖呭惈铻烘潌杞�熷拰鍘熷鏃堕棿鐨勫畬鏁存暟鎹泦
+                df_screw = df_extruder_filtered[['time', 'screw_speed_actual']].copy()
+
+                # 鍒涘缓鍖呭惈鏈哄ご鍘嬪姏鍜屽師濮嬫椂闂寸殑瀹屾暣鏁版嵁闆�
+                df_pressure = df_extruder_filtered[['time', 'head_pressure']].copy()
+
+                # 浣跨敤鍋忕Щ鍚庣殑绫抽噸鏃堕棿鏁村悎铻烘潌杞�熸暟鎹�
+                df_merged = pd.merge_asof(
+                    df_weight.sort_values('time'),
+                    df_screw.sort_values('time'),
+                    on='time',
+                    direction='nearest',
+                    tolerance=pd.Timedelta('1min')
+                )
+
+                # 浣跨敤鍋忕Щ鍚庣殑绫抽噸鏃堕棿鏁村悎鏈哄ご鍘嬪姏鏁版嵁
+                df_merged = pd.merge_asof(
+                    df_merged.sort_values('time'),
+                    df_pressure.sort_values('time'),
+                    on='time',
+                    direction='nearest',
+                    tolerance=pd.Timedelta('1min')
+                )
+
+                # 鏁村悎涓绘祦绋嬫暟鎹�
+                if df_main_speed is not None and not df_main_speed.empty:
+                    df_main_speed = df_main_speed[['time', 'process_main_speed']]
+                    df_merged = pd.merge_asof(
+                        df_merged.sort_values('time'),
+                        df_main_speed.sort_values('time'),
+                        on='time',
+                        direction='nearest',
+                        tolerance=pd.Timedelta('1min')
+                    )
+
+                # 鏁村悎娓╁害鏁版嵁
+                if df_temp is not None and not df_temp.empty:
+                    temp_cols = ['time', 'nakata_extruder_screw_display_temp',
+                               'nakata_extruder_rear_barrel_display_temp',
+                               'nakata_extruder_front_barrel_display_temp',
+                               'nakata_extruder_head_display_temp']
+                    df_temp_subset = df_temp[temp_cols].copy()
+                    df_merged = pd.merge_asof(
+                        df_merged.sort_values('time'),
+                        df_temp_subset.sort_values('time'),
+                        on='time',
+                        direction='nearest',
+                        tolerance=pd.Timedelta('1min')
+                    )
+
+                # 閲嶅懡鍚嶅垪浠ユ彁楂樺彲璇绘��
+                df_merged.rename(columns={
+                    'screw_speed_actual': '铻烘潌杞��',
+                    'head_pressure': '鏈哄ご鍘嬪姏',
+                    'process_main_speed': '娴佺▼涓婚��',
+                    'nakata_extruder_screw_display_temp': '铻烘潌娓╁害',
+                    'nakata_extruder_rear_barrel_display_temp': '鍚庢満绛掓俯搴�',
+                    'nakata_extruder_front_barrel_display_temp': '鍓嶆満绛掓俯搴�',
+                    'nakata_extruder_head_display_temp': '鏈哄ご娓╁害'
+                }, inplace=True)
+
+                # 娓呯悊鏁版嵁
+                df_merged.dropna(subset=['metered_weight'], inplace=True)
+
+                return df_merged
+
+            # 鎵ц鏁版嵁鏁村悎
+            df_analysis = integrate_data(df_extruder_filtered, df_main_speed, df_temp)
+
+            if df_analysis is None or df_analysis.empty:
+                st.warning("鏁版嵁鏁村悎澶辫触锛岃妫�鏌ユ暟鎹川閲忔垨璋冩暣鏃堕棿鑼冨洿銆�")
+                return
+
+            # 閲嶅懡鍚嶇背閲嶅垪
+            df_analysis.rename(columns={'metered_weight': '绫抽噸'}, inplace=True)
+            
+            # 绋虫�佽瘑鍒�
+            steady_detector = SteadyStateDetector()
+            
+            # 鑾峰彇绋虫�佽瘑鍒弬鏁�
+            use_steady_data = st.session_state.get('mr_use_steady_data', True)
+            steady_window = st.session_state.get('mr_steady_window', 20)
+            steady_threshold = st.session_state.get('mr_steady_threshold', 0.5)
+            
+            # 鎵ц绋虫�佽瘑鍒�
+            df_analysis_with_steady, steady_segments = steady_detector.detect_steady_state(
+                df_analysis, 
+                weight_col='绫抽噸',
+                window_size=steady_window,
+                std_threshold=steady_threshold
+            )
+            
+            # 鏇存柊df_analysis涓哄寘鍚ǔ鎬佹爣璁扮殑鏁版嵁
+            df_analysis = df_analysis_with_steady
+            
+            # 绋虫�佹暟鎹彲瑙嗗寲
+            st.subheader("馃搱 绋虫�佹暟鎹垎甯�")
+                        
+            # 鍒涘缓绋虫�佹暟鎹彲瑙嗗寲鍥捐〃
+            fig_steady = go.Figure()
+                        
+            # 娣诲姞鍘熷绫抽噸鏇茬嚎
+            fig_steady.add_trace(go.Scatter(
+                x=df_analysis['time'],
+                y=df_analysis['绫抽噸'],
+                name='鍘熷绫抽噸',
+                mode='lines',
+                line=dict(color='lightgray', width=1)
+            ))
+                        
+            # 娣诲姞绋虫�佹暟鎹偣
+            steady_data_points = df_analysis[df_analysis['is_steady'] == 1]
+            fig_steady.add_trace(go.Scatter(
+                x=steady_data_points['time'],
+                y=steady_data_points['绫抽噸'],
+                name='绋虫�佺背閲�',
+                mode='markers',
+                marker=dict(color='green', size=3, opacity=0.6)
+            ))
+                        
+            # 娣诲姞闈炵ǔ鎬佹暟鎹偣
+            non_steady_data_points = df_analysis[df_analysis['is_steady'] == 0]
+            fig_steady.add_trace(go.Scatter(
+                x=non_steady_data_points['time'],
+                y=non_steady_data_points['绫抽噸'],
+                name='闈炵ǔ鎬佺背閲�',
+                mode='markers',
+                marker=dict(color='red', size=3, opacity=0.6)
+            ))
+                        
+            # 閰嶇疆鍥捐〃甯冨眬
+            fig_steady.update_layout(
+                title="绫抽噸鏁版嵁绋虫�佸垎甯�",
+                xaxis=dict(title="鏃堕棿"),
+                yaxis=dict(title="绫抽噸 (Kg/m)"),
+                legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
+                height=500
+            )
+                        
+            # 鏄剧ず鍥捐〃
+            st.plotly_chart(fig_steady, use_container_width=True)
+            
+            # 鏄剧ず绋虫�佺粺璁�
+            total_data = len(df_analysis)
+            steady_data = len(df_analysis[df_analysis['is_steady'] == 1])
+            steady_ratio = (steady_data / total_data * 100) if total_data > 0 else 0
+            
+            stats_cols = st.columns(3)
+            stats_cols[0].metric("鎬绘暟鎹噺", total_data)
+            stats_cols[1].metric("绋虫�佹暟鎹噺", steady_data)
+            stats_cols[2].metric("绋虫�佹暟鎹瘮渚�", f"{steady_ratio:.1f}%")
+
+            # --- 鍘熷鏁版嵁瓒嬪娍鍥� ---
+            st.subheader("馃搱 鍘熷鏁版嵁瓒嬪娍鍥�")
+
+            # 鍒涘缓瓒嬪娍鍥�
+            fig_trend = go.Figure()
+
+            # 娣诲姞绫抽噸鏁版嵁锛堜娇鐢ㄥ亸绉诲悗鐨勬椂闂达級
+            if df_extruder_filtered is not None and not df_extruder_filtered.empty:
+                fig_trend.add_trace(go.Scatter(
+                    x=df_extruder_filtered['weight_time'],  # 浣跨敤鍋忕Щ鍚庣殑鏃堕棿
+                    y=df_extruder_filtered['metered_weight'],
+                    name='绫抽噸 (Kg/m) [宸插亸绉籡',
+                    mode='lines',
+                    line=dict(color='blue', width=2)
+                ))
+
+                # 娣诲姞铻烘潌杞�燂紙浣跨敤鍘熷鏃堕棿锛�
+                fig_trend.add_trace(go.Scatter(
+                    x=df_extruder_filtered['time'],  # 浣跨敤鍘熷鏃堕棿
+                    y=df_extruder_filtered['screw_speed_actual'],
+                    name='铻烘潌杞�� (RPM)',
+                    mode='lines',
+                    line=dict(color='green', width=1.5),
+                    yaxis='y2'
+                ))
+
+                # 娣诲姞鏈哄ご鍘嬪姏锛堜娇鐢ㄥ師濮嬫椂闂达紝宸茶繃婊ゅぇ浜�2鐨勫�硷級
+                fig_trend.add_trace(go.Scatter(
+                    x=df_extruder_filtered['time'],  # 浣跨敤鍘熷鏃堕棿
+                    y=df_extruder_filtered['head_pressure'],
+                    name='鏈哄ご鍘嬪姏 (鈮�2)',
+                    mode='lines',
+                    line=dict(color='orange', width=1.5),
+                    yaxis='y3'
+                ))
+
+            # 娣诲姞娴佺▼涓婚��
+            if df_main_speed is not None and not df_main_speed.empty:
+                fig_trend.add_trace(go.Scatter(
+                    x=df_main_speed['time'],
+                    y=df_main_speed['process_main_speed'],
+                    name='娴佺▼涓婚�� (M/Min)',
+                    mode='lines',
+                    line=dict(color='red', width=1.5),
+                    yaxis='y4'
+                ))
+
+            # 娣诲姞娓╁害鏁版嵁
+            if df_temp is not None and not df_temp.empty:
+                # 铻烘潌娓╁害
+                fig_trend.add_trace(go.Scatter(
+                    x=df_temp['time'],
+                    y=df_temp['nakata_extruder_screw_display_temp'],
+                    name='铻烘潌娓╁害 (掳C)',
+                    mode='lines',
+                    line=dict(color='purple', width=1),
+                    yaxis='y5'
+                ))
+
+            # 閰嶇疆瓒嬪娍鍥惧竷灞�
+            fig_trend.update_layout(
+                title=f'鍘熷鏁版嵁瓒嬪娍 (绫抽噸鍚戝墠鍋忕Щ {st.session_state["mr_time_offset"]} 鍒嗛挓)',
+                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='铻烘潌杞�� (RPM)',
+                    title_font=dict(color='green'),
+                    tickfont=dict(color='green'),
+                    overlaying='y',
+                    side='right'
+                ),
+                yaxis3=dict(
+                    title='鏈哄ご鍘嬪姏',
+                    title_font=dict(color='orange'),
+                    tickfont=dict(color='orange'),
+                    overlaying='y',
+                    side='right',
+                    anchor='free',
+                    position=0.85
+                ),
+                yaxis4=dict(
+                    title='娴佺▼涓婚�� (M/Min)',
+                    title_font=dict(color='red'),
+                    tickfont=dict(color='red'),
+                    overlaying='y',
+                    side='right',
+                    anchor='free',
+                    position=0.75
+                ),
+                yaxis5=dict(
+                    title='娓╁害 (掳C)',
+                    title_font=dict(color='purple'),
+                    tickfont=dict(color='purple'),
+                    overlaying='y',
+                    side='left',
+                    anchor='free',
+                    position=0.15
+                ),
+                legend=dict(
+                    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'
+            )
+
+            # 鏄剧ず瓒嬪娍鍥�
+            st.plotly_chart(fig_trend, width='stretch', config={'scrollZoom': True})
+
+            # --- 澶氬厓绾挎�у洖褰掑垎鏋� ---
+            st.subheader("馃搳 澶氬厓绾挎�у洖褰掑垎鏋�")
+
+            # 妫�鏌ユ槸鍚﹂�夋嫨浜嗙壒寰�
+            if not st.session_state['mr_selected_features']:
+                st.warning("璇疯嚦灏戦�夋嫨涓�涓壒寰佸彉閲忚繘琛屽洖褰掑垎鏋�")
+            else:
+                # 妫�鏌ユ墍鏈夐�夋嫨鐨勭壒寰佹槸鍚﹀湪鏁版嵁涓�
+                missing_features = [f for f in st.session_state['mr_selected_features'] if f not in df_analysis.columns]
+                if missing_features:
+                    st.warning(f"鏁版嵁涓己灏戜互涓嬬壒寰�: {', '.join(missing_features)}")
+                else:
+                    # 鍑嗗鏁版嵁
+                    # 鏍规嵁閰嶇疆鍐冲畾鏄惁鍙娇鐢ㄧǔ鎬佹暟鎹�
+                    use_steady_data = st.session_state.get('mr_use_steady_data', True)
+                    if use_steady_data:
+                        df_filtered = df_analysis[df_analysis['is_steady'] == 1]
+                        st.info(f"宸茶繃婊ら潪绋虫�佹暟鎹紝浣跨敤 {len(df_filtered)} 鏉$ǔ鎬佹暟鎹繘琛岃缁�")
+                    else:
+                        df_filtered = df_analysis.copy()
+                    
+                    X = df_filtered[st.session_state['mr_selected_features']]
+                    y = df_filtered['绫抽噸']
+
+                    # 娓呯悊鏁版嵁涓殑NaN鍊�
+                    combined = pd.concat([X, y], axis=1)
+                    combined_clean = combined.dropna()
+                    
+                    # 妫�鏌ユ竻鐞嗗悗鐨勬暟鎹噺
+                    if len(combined_clean) < 10:
+                        st.warning("鏁版嵁閲忎笉瓒虫垨鍖呭惈杩囧NaN鍊硷紝鏃犳硶杩涜鏈夋晥鐨勫洖褰掑垎鏋�")
+                    else:
+                        # 閲嶆柊鍒嗙X鍜寉
+                        X_clean = combined_clean[st.session_state['mr_selected_features']]
+                        y_clean = combined_clean['绫抽噸']
+                            
+                        # 鍒嗗壊璁粌闆嗗拰娴嬭瘯闆�
+                        X_train, X_test, y_train, y_test = train_test_split(X_clean, y_clean, test_size=0.2, random_state=42)
+
+                        # 璁粌妯″瀷
+                        model = LinearRegression()
+                        model.fit(X_train, y_train)
+
+                        # 棰勬祴
+                        y_pred = model.predict(X_test)
+                        y_train_pred = model.predict(X_train)
+
+                        # 璁$畻璇勪及鎸囨爣
+                        r2 = r2_score(y_test, y_pred)
+                        mse = mean_squared_error(y_test, y_pred)
+                        mae = mean_absolute_error(y_test, y_pred)
+                        rmse = np.sqrt(mse)
+
+                        # 鏄剧ず妯″瀷鎬ц兘
+                        metrics_cols = st.columns(2)
+                        with metrics_cols[0]:
+                            st.metric("R虏 寰楀垎", f"{r2:.4f}")
+                            st.metric("鍧囨柟璇樊 (MSE)", f"{mse:.6f}")
+                        with metrics_cols[1]:
+                            st.metric("骞冲潎缁濆璇樊 (MAE)", f"{mae:.6f}")
+                            st.metric("鍧囨柟鏍硅宸� (RMSE)", f"{rmse:.6f}")
+
+                        # --- 瀹為檯鍊间笌棰勬祴鍊煎姣� ---
+                        st.subheader("馃攧 瀹為檯鍊间笌棰勬祴鍊煎姣�")
+
+                        # 鍒涘缓瀵规瘮鏁版嵁
+                        compare_df = pd.DataFrame({
+                            '瀹為檯鍊�': y_test,
+                            '棰勬祴鍊�': y_pred
+                        })
+                        compare_df = compare_df.sort_index()
+
+                        # 鍒涘缓瀵规瘮鍥�
+                        fig_compare = go.Figure()
+                        fig_compare.add_trace(go.Scatter(
+                            x=compare_df.index,
+                            y=compare_df['瀹為檯鍊�'],
+                            name='瀹為檯鍊�',
+                            mode='lines+markers',
+                            line=dict(color='blue', width=2)
+                        ))
+                        fig_compare.add_trace(go.Scatter(
+                            x=compare_df.index,
+                            y=compare_df['棰勬祴鍊�'],
+                            name='棰勬祴鍊�',
+                            mode='lines+markers',
+                            line=dict(color='red', width=2, dash='dash')
+                        ))
+                        fig_compare.update_layout(
+                            title='娴嬭瘯闆�: 瀹為檯绫抽噸 vs 棰勬祴绫抽噸',
+                            xaxis=dict(title='鏍锋湰绱㈠紩'),
+                            yaxis=dict(title='绫抽噸 (Kg/m)'),
+                            legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
+                            height=400
+                        )
+                        st.plotly_chart(fig_compare, width='stretch')
+
+                        # --- 娈嬪樊鍒嗘瀽 ---
+                        st.subheader("馃搲 娈嬪樊鍒嗘瀽")
+
+                        # 璁$畻娈嬪樊
+                        residuals = y_test - y_pred
+
+                        # 鍒涘缓娈嬪樊鍥�
+                        fig_residual = go.Figure()
+                        fig_residual.add_trace(go.Scatter(
+                            x=y_pred,
+                            y=residuals,
+                            mode='markers',
+                            marker=dict(color='green', size=8, opacity=0.6)
+                        ))
+                        fig_residual.add_shape(
+                            type="line",
+                            x0=y_pred.min(),
+                            y0=0,
+                            x1=y_pred.max(),
+                            y1=0,
+                            line=dict(color="red", width=2, dash="dash")
+                        )
+                        fig_residual.update_layout(
+                            title='娈嬪樊鍥�',
+                            xaxis=dict(title='棰勬祴鍊�'),
+                            yaxis=dict(title='娈嬪樊'),
+                            height=400
+                        )
+                        st.plotly_chart(fig_residual, width='stretch')
+
+                        # --- 鐗瑰緛閲嶈鎬� ---
+                        st.subheader("鈿栵笍 鐗瑰緛閲嶈鎬у垎鏋�")
+
+                        # 璁$畻鐗瑰緛閲嶈鎬э紙鍩轰簬绯绘暟缁濆鍊硷級
+                        feature_importance = pd.DataFrame({
+                            '鐗瑰緛': st.session_state['mr_selected_features'],
+                            '绯绘暟': model.coef_,
+                            '閲嶈鎬�': np.abs(model.coef_)
+                        })
+                        feature_importance = feature_importance.sort_values('閲嶈鎬�', ascending=False)
+
+                        # 鍒涘缓鐗瑰緛閲嶈鎬у浘
+                        fig_importance = px.bar(
+                            feature_importance,
+                            x='鐗瑰緛',
+                            y='閲嶈鎬�',
+                            title='鐗瑰緛閲嶈鎬э紙鍩轰簬绯绘暟缁濆鍊硷級',
+                            color='閲嶈鎬�',
+                            color_continuous_scale='viridis'
+                        )
+                        fig_importance.update_layout(
+                            xaxis=dict(tickangle=-45),
+                            height=400
+                        )
+                        st.plotly_chart(fig_importance, width='stretch')
+
+                        # 鏄剧ず绯绘暟琛�
+                        st.write("### 妯″瀷绯绘暟")
+                        coef_df = pd.DataFrame({
+                            '鐗瑰緛': ['鎴窛'] + st.session_state['mr_selected_features'],
+                            '绯绘暟': [model.intercept_] + list(model.coef_)
+                        })
+                        st.dataframe(coef_df, use_container_width=True)
+
+                        # --- 妯″瀷淇濆瓨鍔熻兘 ---
+                        st.subheader("馃捑 妯″瀷淇濆瓨")
+
+                        # 鍒涘缓妯″瀷淇濆瓨琛ㄥ崟
+                        st.write("淇濆瓨璁粌濂界殑妯″瀷鏉冮噸:")
+                        model_name = st.text_input(
+                            "妯″瀷鍚嶇О",
+                            value=f"linear_regression_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
+                            help="璇疯緭鍏ユā鍨嬪悕绉帮紝妯″瀷灏嗕繚瀛樹负璇ュ悕绉扮殑.joblib鏂囦欢"
+                        )
+
+                        if st.button("淇濆瓨妯″瀷"):
+                            # 纭繚妯″瀷鐩綍瀛樺湪
+                            model_dir = "saved_models"
+                            os.makedirs(model_dir, exist_ok=True)
+                            
+                            # 淇濆瓨妯″瀷
+                            model_path = os.path.join(model_dir, f"{model_name}.joblib")
+                            try:
+                                # 淇濆瓨妯″瀷鏉冮噸鍜岀浉鍏充俊鎭�
+                                model_info = {
+                                    'model': model,
+                                    'features': st.session_state['mr_selected_features'],
+                                    'scaler': None,  # 绾挎�у洖褰掍笉闇�瑕佹爣鍖栧櫒
+                                    'model_type': 'linear_regression',
+                                    'created_at': datetime.now(),
+                                    'r2_score': r2,
+                                    'mse': mse,
+                                    'mae': mae,
+                                    'rmse': rmse,
+                                    'use_steady_data': use_steady_data
+                                }
+                                joblib.dump(model_info, model_path)
+                                st.success(f"妯″瀷宸叉垚鍔熶繚瀛樺埌: {model_path}")
+                            except Exception as e:
+                                st.error(f"妯″瀷淇濆瓨澶辫触: {e}")
+
+                        # --- 鏁版嵁棰勮 ---
+                        st.subheader("馃攳 鏁版嵁棰勮")
+                        st.dataframe(df_analysis.head(20), use_container_width=True)
+    else:
+        # 鎻愮ず鐢ㄦ埛鐐瑰嚮寮�濮嬪垎鏋愭寜閽�
+        st.info("璇烽�夋嫨鏃堕棿鑼冨洿骞剁偣鍑�'寮�濮嬪垎鏋�'鎸夐挳鑾峰彇鏁版嵁銆�")

--
Gitblit v1.9.3