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_forecast.py |  716 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 716 insertions(+), 0 deletions(-)

diff --git a/app/pages/metered_weight_forecast.py b/app/pages/metered_weight_forecast.py
new file mode 100644
index 0000000..2ecdfbe
--- /dev/null
+++ b/app/pages/metered_weight_forecast.py
@@ -0,0 +1,716 @@
+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
+
+# 灏濊瘯瀵煎叆torch锛屽鏋滃け璐ュ垯绂佺敤娣卞害瀛︿範妯″瀷鏀寔
+try:
+    import torch
+    TORCH_AVAILABLE = True
+except ImportError:
+    TORCH_AVAILABLE = False
+
+
+# 绋虫�佽瘑鍒被
+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_forecast():
+    # 鍒濆鍖栨湇鍔�
+    extruder_service = ExtruderService()
+    main_process_service = MainProcessService()
+
+    # 椤甸潰鏍囬
+    st.title("绫抽噸棰勬祴鍒嗘瀽")
+
+    # 鍒濆鍖栦細璇濈姸鎬�
+    if 'forecast_start_date' not in st.session_state:
+        st.session_state['forecast_start_date'] = datetime.now().date() - timedelta(days=7)
+    if 'forecast_end_date' not in st.session_state:
+        st.session_state['forecast_end_date'] = datetime.now().date()
+    if 'forecast_quick_select' not in st.session_state:
+        st.session_state['forecast_quick_select'] = "鏈�杩�7澶�"
+    if 'selected_model' not in st.session_state:
+        st.session_state['selected_model'] = None
+    if 'selected_model_file' not in st.session_state:
+        st.session_state['selected_model_file'] = None
+    if 'forecast_use_steady_only' not in st.session_state:
+        st.session_state['forecast_use_steady_only'] = True
+    if 'forecast_steady_window' not in st.session_state:
+        st.session_state['forecast_steady_window'] = 20
+    if 'forecast_steady_threshold' not in st.session_state:
+        st.session_state['forecast_steady_threshold'] = 1.5
+
+    # 瀹氫箟鍥炶皟鍑芥暟
+    def update_dates(qs):
+        st.session_state['forecast_quick_select'] = qs
+        today = datetime.now().date()
+        if qs == "浠婂ぉ":
+            st.session_state['forecast_start_date'] = today
+            st.session_state['forecast_end_date'] = today
+        elif qs == "鏈�杩�3澶�":
+            st.session_state['forecast_start_date'] = today - timedelta(days=3)
+            st.session_state['forecast_end_date'] = today
+        elif qs == "鏈�杩�7澶�":
+            st.session_state['forecast_start_date'] = today - timedelta(days=7)
+            st.session_state['forecast_end_date'] = today
+        elif qs == "鏈�杩�30澶�":
+            st.session_state['forecast_start_date'] = today - timedelta(days=30)
+            st.session_state['forecast_end_date'] = today
+
+    def on_date_change():
+        st.session_state['forecast_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['forecast_quick_select'] == option else "secondary"
+                if st.button(option, key=f"btn_forecast_{option}", width='stretch', type=button_type):
+                    update_dates(option)
+                    st.rerun()
+
+        with cols[5]:
+            start_date = st.date_input(
+                "寮�濮嬫棩鏈�",
+                label_visibility="collapsed",
+                key="forecast_start_date",
+                on_change=on_date_change
+            )
+
+        with cols[6]:
+            end_date = st.date_input(
+                "缁撴潫鏃ユ湡",
+                label_visibility="collapsed",
+                key="forecast_end_date",
+                on_change=on_date_change
+            )
+
+        with cols[7]:
+            query_button = st.button("馃殌 鏌ヨ鏁版嵁", key="forecast_query", width='stretch')
+
+    # 杞崲涓篸atetime瀵硅薄
+    start_dt = datetime.combine(start_date, datetime.min.time())
+    end_dt = datetime.combine(end_date, datetime.max.time())
+
+    # 妯″瀷閫夋嫨鍖哄煙
+    with st.expander("馃搧 妯″瀷閫夋嫨", expanded=True):
+        # 鍒涘缓妯″瀷鐩綍锛堝鏋滀笉瀛樺湪锛�
+        model_dir = "saved_models"
+        os.makedirs(model_dir, exist_ok=True)
+        
+        # 鑾峰彇鎵�鏈夊凡淇濆瓨鐨勬ā鍨嬫枃浠�
+        model_files = [f for f in os.listdir(model_dir) if f.endswith('.joblib')]
+        model_files.sort(reverse=True)  # 鏈�鏂扮殑妯″瀷鎺掑湪鍓嶉潰
+        
+        if not model_files:
+            st.warning("灏氭湭淇濆瓨浠讳綍妯″瀷锛岃鍏堣缁冩ā鍨嬪苟淇濆瓨銆�")
+        else:
+            # 妯″瀷閫夋嫨涓嬫媺妗�
+            selected_model_file = st.selectbox(
+                "閫夋嫨宸蹭繚瀛樼殑妯″瀷",
+                options=model_files,
+                help="閫夋嫨瑕佺敤浜庨娴嬬殑妯″瀷鏂囦欢",
+                key="forecast_selected_model"
+            )
+            
+            # 鍔犺浇骞舵樉绀烘ā鍨嬩俊鎭�
+            if selected_model_file:
+                model_path = os.path.join(model_dir, selected_model_file)
+                model_info = joblib.load(model_path)
+                
+                # 鏄剧ず妯″瀷鍩烘湰淇℃伅
+                st.subheader("馃搳 妯″瀷淇℃伅")
+                info_cols = st.columns(2)
+                
+                with info_cols[0]:
+                    st.metric("妯″瀷绫诲瀷", model_info['model_type'])
+                    st.metric("鍒涘缓鏃堕棿", model_info['created_at'].strftime('%Y-%m-%d %H:%M:%S'))
+                    st.metric("浣跨敤绋虫�佹暟鎹�", "鏄�" if model_info.get('use_steady_data', False) else "鍚�")
+                
+                with info_cols[1]:
+                    st.metric("R虏 寰楀垎", f"{model_info['r2_score']:.4f}")
+                    st.metric("鍧囨柟璇樊 (MSE)", f"{model_info['mse']:.6f}")
+                    st.metric("鍧囨柟鏍硅宸� (RMSE)", f"{model_info['rmse']:.6f}")
+                
+                # 鏄剧ず妯″瀷鐗瑰緛
+                st.write("馃攽 妯″瀷浣跨敤鐨勭壒寰�:")
+                st.code(", ".join(model_info['features']))
+                
+                # 濡傛灉鏄繁搴﹀涔犳ā鍨嬶紝鏄剧ず搴忓垪闀垮害
+                if 'sequence_length' in model_info:
+                    st.metric("搴忓垪闀垮害", model_info['sequence_length'])
+                
+                # 淇濆瓨妯″瀷淇℃伅鍒颁細璇濈姸鎬�
+                st.session_state['selected_model'] = model_info
+                st.session_state['selected_model_file'] = selected_model_file
+        
+        # 绋虫�佽瘑鍒厤缃�
+        st.markdown("---")
+        st.write("鈿栵笍 **绋虫�佽瘑鍒厤缃�**")
+        
+        steady_cols = st.columns(3)
+        with steady_cols[0]:
+            st.checkbox(
+                "浠呴娴嬬ǔ鎬佹暟鎹�",
+                value=st.session_state['forecast_use_steady_only'],
+                key="forecast_use_steady_only",
+                help="鍚敤鍚庯紝鍙澶勪簬绋虫�佹椂娈电殑鏁版嵁杩涜绫抽噸棰勬祴"
+            )
+        
+        with steady_cols[1]:
+            st.slider(
+                "婊戝姩绐楀彛澶у皬 (绉�)",
+                min_value=5,
+                max_value=60,
+                value=st.session_state['forecast_steady_window'],
+                step=5,
+                key="forecast_steady_window",
+                help="鐢ㄤ簬绋虫�佽瘑鍒殑婊戝姩绐楀彛澶у皬"
+            )
+        
+        with steady_cols[2]:
+            st.slider(
+                "娉㈠姩闃堝�� (%)",
+                min_value=0.1,
+                max_value=2.0,
+                value=st.session_state['forecast_steady_threshold'],
+                step=0.1,
+                key="forecast_steady_threshold",
+                help="绋虫�佽瘑鍒殑娉㈠姩鑼冨洿闃堝��"
+            )
+
+    # 棰勬祴鍔熻兘鍖哄煙
+    st.subheader("馃敭 绫抽噸棰勬祴")
+    
+    if query_button and st.session_state['selected_model']:
+        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("鎵�閫夋椂闂存鍐呮湭鎵惧埌浠讳綍鏁版嵁锛岃灏濊瘯璋冩暣鏌ヨ鏉′欢銆�")
+            else:
+                # 鏁版嵁鏁村悎涓庨澶勭悊
+                def integrate_data(df_extruder_full, df_main_speed, df_temp):
+                    # 纭繚鎸ゅ嚭鏈烘暟鎹瓨鍦�
+                    if df_extruder_full is None or df_extruder_full.empty:
+                        return None
+
+                    # 鍒涘缓鍙寘鍚背閲嶅拰鏃堕棿鐨勪富鏁版嵁闆�
+                    df_merged = df_extruder_full[['time', 'metered_weight', 'screw_speed_actual', 'head_pressure']].copy()
+                   
+
+                    # 鏁村悎涓绘祦绋嬫暟鎹�
+                    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_full, df_main_speed, df_temp)
+
+                if df_analysis is None or df_analysis.empty:
+                    st.warning("鏁版嵁鏁村悎澶辫触锛岃妫�鏌ユ暟鎹川閲忔垨璋冩暣鏃堕棿鑼冨洿銆�")
+                else:
+                    # 閲嶅懡鍚嶇背閲嶅垪
+                    df_analysis.rename(columns={'metered_weight': '绫抽噸'}, inplace=True)
+                    
+                    # 绋虫�佽瘑鍒�
+                    steady_detector = SteadyStateDetector()
+                    
+                    # 鑾峰彇绋虫�佽瘑鍒弬鏁�
+                    use_steady_only = st.session_state.get('forecast_use_steady_only', True)
+                    steady_window = st.session_state.get('forecast_steady_window', 20)
+                    steady_threshold = st.session_state.get('forecast_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
+                    
+                    # 鏄剧ず绋虫�佺粺璁′俊鎭�
+                    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
+                    
+                    st.subheader("馃搳 绋虫�佹暟鎹粺璁�")
+                    stats_cols = st.columns(4)
+                    stats_cols[0].metric("鎬绘暟鎹噺", total_data)
+                    stats_cols[1].metric("绋虫�佹暟鎹噺", steady_data)
+                    stats_cols[2].metric("绋虫�佹暟鎹瘮渚�", f"{steady_ratio:.1f}%")
+                    stats_cols[3].metric("绋虫�佹鏁伴噺", len(steady_segments))
+                    
+                    # 鑾峰彇妯″瀷淇℃伅
+                    model_info = st.session_state['selected_model']
+                    required_features = model_info['features']
+                    
+                    # 妫�鏌ユ墍鏈夊繀闇�鐨勭壒寰佹槸鍚﹀湪鏁版嵁涓�
+                    missing_features = [f for f in required_features if f not in df_analysis.columns]
+                    if missing_features:
+                        st.warning(f"鏁版嵁涓己灏戜互涓嬬壒寰�: {', '.join(missing_features)}")
+                    else:
+                        # 鍑嗗鎵�鏈夋暟鎹敤浜庢樉绀�
+                        df_all = df_analysis.dropna(subset=required_features + ['绫抽噸']).copy()
+                        
+                        if len(df_all) == 0:
+                            st.warning("娌℃湁瓒冲鐨勬湁鏁堟暟鎹繘琛岄娴嬶紝璇疯皟鏁存椂闂磋寖鍥存垨妫�鏌ユ暟鎹川閲忋��")
+                        else:
+                            # 鏍规嵁閰嶇疆鍐冲畾鏄惁鍙娇鐢ㄧǔ鎬佹暟鎹繘琛岄娴�
+                            if use_steady_only:
+                                df_pred_steady = df_all[df_all['is_steady'] == 1].copy()
+                                if len(df_pred_steady) > 0:
+                                    df_pred = df_pred_steady
+                                    st.info(f"宸插惎鐢ㄧǔ鎬佽繃婊わ紝浣跨敤 {len(df_pred)} 鏉$ǔ鎬佹暟鎹繘琛岄娴�")
+                                else:
+                                    df_pred = df_all.copy()
+                                    st.warning("鏈壘鍒扮ǔ鎬佹暟鎹紝灏嗕娇鐢ㄦ墍鏈夋暟鎹繘琛岄娴�")
+                            else:
+                                df_pred = df_all.copy()
+                            
+                            # 鎵ц棰勬祴 - 鍙閫夊畾鐨勬暟鎹紙绋虫�佹垨鍏ㄩ儴锛夎繘琛岄娴�
+                            X_pred = df_pred[required_features]
+                            predicted_weights = []
+                            
+                            # 鑾峰彇妯″瀷
+                            model = model_info['model']
+                            
+                            # 妫�鏌ユā鍨嬬被鍨嬪苟鎵ц棰勬祴
+                            if model_info['model_type'] in ['LSTM', 'GRU', 'BiLSTM']:
+                                # 娣卞害瀛︿範妯″瀷棰勬祴
+                                if not TORCH_AVAILABLE:
+                                    st.error("PyTorch 鏈畨瑁咃紝鏃犳硶浣跨敤娣卞害瀛︿範妯″瀷杩涜棰勬祴銆�")
+                                    st.stop()
+                                
+                                # 鏁版嵁鏍囧噯鍖�
+                                scaler_X = model_info['scaler_X']
+                                scaler_y = model_info['scaler_y']
+                                X_scaled = scaler_X.transform(X_pred)
+                                
+                                # 鑾峰彇搴忓垪闀垮害
+                                sequence_length = model_info['sequence_length']
+                                
+                                # 涓烘繁搴﹀涔犳ā鍨嬪垱寤哄簭鍒�
+                                def create_sequences(data, seq_length):
+                                    sequences = []
+                                    for i in range(len(data) - seq_length + 1):
+                                        seq = data[i:i+seq_length]
+                                        sequences.append(seq)
+                                    return np.array(sequences)
+                                
+                                X_sequences = create_sequences(X_scaled, sequence_length)
+                                
+                                # 杞崲涓篜yTorch寮犻噺
+                                import torch
+                                device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+                                X_tensor = torch.tensor(X_sequences, dtype=torch.float32).to(device)
+                                
+                                # 棰勬祴
+                                model.eval()
+                                with torch.no_grad():
+                                    y_pred_scaled_tensor = model(X_tensor)
+                                    y_pred_scaled = y_pred_scaled_tensor.cpu().numpy().ravel()
+                                    
+                                    # 鍙嶅綊涓�鍖�
+                                    predicted = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
+                                    
+                                    # 鐢变簬搴忓垪棰勬祴锛屾垜浠渶瑕佸~鍏呭墠闈㈢殑缂哄け鍊�
+                                    predicted_weights = [np.nan] * (sequence_length - 1) + list(predicted)
+                            
+                            elif model_info['model_type'] in ['SVR', 'MLP']:
+                                # 鏀寔鍚戦噺鏈烘垨澶氬眰鎰熺煡鍣ㄩ娴�
+                                # 鏁版嵁鏍囧噯鍖�
+                                scaler_X = model_info['scaler_X']
+                                scaler_y = model_info['scaler_y']
+                                X_scaled = scaler_X.transform(X_pred)
+                                
+                                # 棰勬祴
+                                y_pred_scaled = model.predict(X_scaled)
+                                
+                                # 鍙嶅綊涓�鍖�
+                                predicted_weights = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
+                            
+                            else:
+                                # 鍏朵粬妯″瀷锛堝闅忔満妫灄銆佹搴︽彁鍗囥�佺嚎鎬у洖褰掔瓑锛�
+                                predicted_weights = model.predict(X_pred)
+                            
+                            # 灏嗛娴嬬粨鏋滄坊鍔犲埌鏁版嵁妗嗕腑
+                            df_pred['棰勬祴绫抽噸'] = predicted_weights
+                            
+                            # 纭繚鏃堕棿鍒楁槸datetime绫诲瀷
+                            df_pred['time'] = pd.to_datetime(df_pred['time'])
+                            
+                            # 鏁版嵁瀵规瘮鍔熻兘
+                            st.subheader("馃搳 棰勬祴缁撴灉瀵规瘮鍒嗘瀽")
+                            
+                            # 璁$畻棰勬祴璇樊
+                            df_pred['璇樊'] = df_pred['棰勬祴绫抽噸'] - df_pred['绫抽噸']
+                            df_pred['缁濆璇樊'] = abs(df_pred['璇樊'])
+                            df_pred['鐩稿璇樊'] = (df_pred['缁濆璇樊'] / df_pred['绫抽噸']) * 100
+                            
+                            # 鏄剧ず璇樊缁熻淇℃伅
+                            error_stats = df_pred.dropna(subset=['棰勬祴绫抽噸']).describe()
+                            
+                            stats_cols = st.columns(3)
+                            with stats_cols[0]:
+                                st.metric("骞冲潎瀹為檯绫抽噸", f"{error_stats['绫抽噸']['mean']:.4f} Kg/m")
+                                st.metric("骞冲潎棰勬祴绫抽噸", f"{error_stats['棰勬祴绫抽噸']['mean']:.4f} Kg/m")
+                            with stats_cols[1]:
+                                st.metric("骞冲潎缁濆璇樊", f"{error_stats['缁濆璇樊']['mean']:.4f} Kg/m")
+                                st.metric("鏈�澶х粷瀵硅宸�", f"{error_stats['缁濆璇樊']['max']:.4f} Kg/m")
+                            with stats_cols[2]:
+                                st.metric("骞冲潎鐩稿璇樊", f"{error_stats['鐩稿璇樊']['mean']:.2f}%")
+                                st.metric("鏈�澶х浉瀵硅宸�", f"{error_stats['鐩稿璇樊']['max']:.2f}%")
+                            
+                            # 鍙鍖栧睍绀�
+                            st.subheader("馃搱 绫抽噸瓒嬪娍瀵规瘮")
+                            
+                            # 鍒涘缓瓒嬪娍鍥� - 浣跨敤鎵�鏈夋暟鎹甦f_all杩涜鏄剧ず
+                            fig = go.Figure()
+                            
+                            # 纭繚鏃堕棿鍒楁槸datetime绫诲瀷
+                            df_all['time'] = pd.to_datetime(df_all['time'])
+                            
+                            # # 娣诲姞瀹炴椂绫抽噸鏁版嵁鐐癸紙绋虫�佹暟鎹敤钃濊壊锛岄潪绋虫�佹暟鎹敤鐏拌壊锛�
+                            # if 'is_steady' in df_all.columns:
+                            #     # 绋虫�佹暟鎹� - 浣跨敤鐐规樉绀�
+                            #     steady_data = df_all[df_all['is_steady'] == 1]
+                            #     non_steady_data = df_all[df_all['is_steady'] == 0]
+                                
+                            #     if len(steady_data) > 0:
+                            #         fig.add_trace(go.Scatter(
+                            #             x=steady_data['time'],
+                            #             y=steady_data['绫抽噸'],
+                            #             name='瀹炴椂绫抽噸锛堢ǔ鎬侊級',
+                            #             mode='markers',
+                            #             marker=dict(color='blue', size=3),
+                            #             hovertemplate='鏃堕棿: %{x}<br>瀹炴椂绫抽噸锛堢ǔ鎬侊級: %{y:.4f} Kg/m<extra></extra>'
+                            #         ))
+                                
+                            #     # 闈炵ǔ鎬佹暟鎹篃鏄剧ず锛屼絾涓嶈繘琛岄娴�
+                            #     if len(non_steady_data) > 0:
+                            #         fig.add_trace(go.Scatter(
+                            #             x=non_steady_data['time'],
+                            #             y=non_steady_data['绫抽噸'],
+                            #             name='瀹炴椂绫抽噸锛堥潪绋虫�侊級',
+                            #             mode='markers',
+                            #             marker=dict(color='lightgray', size=3),
+                            #             hovertemplate='鏃堕棿: %{x}<br>瀹炴椂绫抽噸锛堥潪绋虫�侊級: %{y:.4f} Kg/m<extra></extra>'
+                            #         ))
+                            # else:
+                            # 濡傛灉娌℃湁绋虫�佹爣璁帮紝鏄剧ず鎵�鏈夋暟鎹偣
+                            fig.add_trace(go.Scatter(
+                                x=df_all['time'],
+                                y=df_all['绫抽噸'],
+                                name='瀹炴椂绫抽噸',
+                                mode='lines',
+                                line=dict(color='blue', width=1.5),
+                                # hovertemplate='鏃堕棿: %{x}<br>瀹炴椂绫抽噸: %{y:.4f} Kg/m<extra></extra>'
+                            ))
+                            
+                            # 娣诲姞棰勬祴绫抽噸鏇茬嚎 - 鍙棰勬祴鐨勬暟鎹紙绋虫�佹垨鍏ㄩ儴锛夋樉绀�
+                            fig.add_trace(go.Scatter(
+                                x=df_pred['time'],
+                                y=df_pred['棰勬祴绫抽噸'],
+                                name='棰勬祴绫抽噸',
+                                mode='lines',
+                                line=dict(color='red', width=2, dash='dash'),
+                                marker=dict(size=3),
+                                # hovertemplate='鏃堕棿: %{x}<br>棰勬祴绫抽噸: %{y:.4f} Kg/m<extra></extra>'
+                            ))
+                            
+                            # 娣诲姞鎵�鏈夋尋鍑烘満鍙傛暟鏇茬嚎 - 浣跨敤鎵�鏈夋暟鎹�
+                            colors = ['green', 'orange', 'purple', 'brown', 'pink', 'gray', 'olive', 'cyan', 'magenta', 'yellow', 'lime', 'teal']
+                            for i, feature in enumerate(required_features):
+                                # 涓烘瘡涓壒寰佸垎閰嶄笉鍚岀殑棰滆壊
+                                color = colors[i % len(colors)]
+                                
+                                # 纭繚鐗瑰緛瀛樺湪浜庢墍鏈夋暟鎹腑
+                                if feature in df_all.columns:
+                                    fig.add_trace(go.Scatter(
+                                        x=df_all['time'],
+                                        y=df_all[feature],
+                                        name=feature,
+                                        mode='lines',
+                                        line=dict(color=color, width=1.5),
+                                        yaxis=f'y{i+2}',
+                                        # hovertemplate=f'鏃堕棿: %{{x}}<br>{feature}: %{{y}}<extra></extra>'
+                                    ))
+                            
+                            # 閰嶇疆鍥捐〃甯冨眬
+                            layout = {
+                                'title': '绫抽噸棰勬祴涓庡疄鏃舵暟鎹姣�',
+                                'xaxis': {
+                                    'title': '鏃堕棿',
+                                    'rangeslider': {'visible': True},
+                                    'type': 'date',
+                                    'tickformat': '%Y-%m-%d %H:%M'
+                                },
+                                'yaxis': {
+                                    'title': '绫抽噸 (Kg/m)',
+                                    'title_font': {'color': 'blue'},
+                                    'tickfont': {'color': 'blue'},
+                                    'side': 'left',
+                                    'fixedrange': False  # 鍏佽y杞寸缉鏀�
+                                },
+                                'legend': {
+                                    'orientation': 'h',
+                                    'yanchor': 'bottom',
+                                    'y': 1.02,
+                                    'xanchor': 'right',
+                                    'x': 1
+                                },
+                                'height': 600,
+                                'margin': {'l': 100, 'r': 200, 't': 100, 'b': 100},
+                                'hovermode': 'x unified'
+                            }
+                            
+                            # 娣诲姞棰濆鐨剏杞撮厤缃� - 涓烘墍鏈夌壒寰佸垱寤簓杞�
+                            for i, feature in enumerate(required_features):
+                                layout[f'yaxis{i+2}'] = {
+                                    'title': feature,
+                                    'title_font': {'color': colors[i % len(colors)]},
+                                    'tickfont': {'color': colors[i % len(colors)]},
+                                    'overlaying': 'y',
+                                    'side': 'right',
+                                    'anchor': 'free',
+                                    'position': 1 - (i+1)*0.08,
+                                    'fixedrange': False  # 鍏佽y杞寸缉鏀�
+                                }
+                            
+                            fig.update_layout(layout)
+                            
+                            # 鏄剧ず瓒嬪娍鍥� - 鍚敤瀹屾暣鐨勪氦浜掑姛鑳�
+                            st.plotly_chart(fig, use_container_width=True, config={
+                                'scrollZoom': True, 
+                                'displayModeBar': True,
+                                'modeBarButtonsToAdd': ['pan2d', 'select2d', 'lasso2d', 'resetScale2d'],
+                                'displaylogo': False
+                            })
+                            
+                            # 璇樊鍒嗘瀽鍥�
+                            st.subheader("馃搲 棰勬祴璇樊鍒嗘瀽")
+                            
+                            # 鍒涘缓璇樊鍒嗗竷鐩存柟鍥�
+                            fig_error = px.histogram(df_pred.dropna(subset=['鐩稿璇樊']), x='鐩稿璇樊', nbins=50, 
+                                                   title='棰勬祴鐩稿璇樊鍒嗗竷',
+                                                   labels={'鐩稿璇樊': '鐩稿璇樊 (%)'})
+                            fig_error.update_layout(
+                                xaxis_title='鐩稿璇樊 (%)',
+                                yaxis_title='棰戞',
+                                height=400
+                            )
+                            st.plotly_chart(fig_error, use_container_width=True)
+                            
+                            # 鏁版嵁棰勮
+                            st.subheader("馃攳 鏁版嵁棰勮")
+                            preview_columns = ['time', '绫抽噸', '棰勬祴绫抽噸', '璇樊', '缁濆璇樊', '鐩稿璇樊']
+                            if 'is_steady' in df_pred.columns:
+                                preview_columns.append('is_steady')
+                            preview_columns.extend(required_features)
+                            st.dataframe(df_pred[preview_columns].head(20), 
+                                        use_container_width=True)
+                            
+                            # 瀵煎嚭鏁版嵁
+                            st.subheader("馃捑 瀵煎嚭鏁版嵁")
+                            # 灏嗘暟鎹浆鎹负CSV鏍煎紡
+                            csv = df_pred.to_csv(index=False)
+                            # 鍒涘缓涓嬭浇鎸夐挳
+                            st.download_button(
+                                label="瀵煎嚭棰勬祴缁撴灉鏁版嵁 (CSV)",
+                                data=csv,
+                                file_name=f"metered_weight_forecast_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
+                                mime="text/csv",
+                                help="鐐瑰嚮鎸夐挳瀵煎嚭棰勬祴缁撴灉鏁版嵁"
+                            )
+    elif query_button:
+        st.warning("璇峰厛閫夋嫨涓�涓ā鍨嬨��")
+    else:
+        st.info("璇烽�夋嫨鏃堕棿鑼冨洿鍜屾ā鍨嬶紝鐒跺悗鐐瑰嚮'鏌ヨ鏁版嵁'鎸夐挳寮�濮嬮娴嬪垎鏋愩��")
+
+
+# 椤甸潰鍏ュ彛
+if __name__ == "__main__":
+    show_metered_weight_forecast()
\ No newline at end of file

--
Gitblit v1.9.3