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_advanced.py | 851 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 851 insertions(+), 0 deletions(-)
diff --git a/app/pages/metered_weight_advanced.py b/app/pages/metered_weight_advanced.py
new file mode 100644
index 0000000..5a9dea8
--- /dev/null
+++ b/app/pages/metered_weight_advanced.py
@@ -0,0 +1,851 @@
+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.preprocessing import StandardScaler, MinMaxScaler
+from sklearn.model_selection import train_test_split
+from sklearn.metrics import r2_score, mean_squared_error, mean_absolute_error
+from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
+from sklearn.svm import SVR
+from sklearn.neural_network import MLPRegressor
+
+# 瀵煎叆绋虫�佽瘑鍒姛鑳�
+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_advanced():
+ # 鍒濆鍖栨湇鍔�
+ extruder_service = ExtruderService()
+ main_process_service = MainProcessService()
+
+ # 椤甸潰鏍囬
+ st.title("绫抽噸楂樼骇棰勬祴鍒嗘瀽")
+
+ # 鍒濆鍖栦細璇濈姸鎬�
+ if 'ma_start_date' not in st.session_state:
+ st.session_state['ma_start_date'] = datetime.now().date() - timedelta(days=7)
+ if 'ma_end_date' not in st.session_state:
+ st.session_state['ma_end_date'] = datetime.now().date()
+ if 'ma_quick_select' not in st.session_state:
+ st.session_state['ma_quick_select'] = "鏈�杩�7澶�"
+ if 'ma_model_type' not in st.session_state:
+ st.session_state['ma_model_type'] = 'RandomForest'
+ if 'ma_sequence_length' not in st.session_state:
+ st.session_state['ma_sequence_length'] = 10
+ if 'ma_use_steady_data' not in st.session_state:
+ st.session_state['ma_use_steady_data'] = True
+ if 'ma_steady_window' not in st.session_state:
+ st.session_state['ma_steady_window'] = 20
+ if 'ma_steady_threshold' not in st.session_state:
+ st.session_state['ma_steady_threshold'] = 0.5
+
+ # 榛樿鐗瑰緛鍒楄〃锛堜笉鍐嶅厑璁哥敤鎴烽�夋嫨锛�
+ default_features = ['铻烘潌杞��', '鏈哄ご鍘嬪姏', '娴佺▼涓婚��', '铻烘潌娓╁害',
+ '鍚庢満绛掓俯搴�', '鍓嶆満绛掓俯搴�', '鏈哄ご娓╁害']
+
+ # 瀹氫箟鍥炶皟鍑芥暟
+ def update_dates(qs):
+ st.session_state['ma_quick_select'] = qs
+ today = datetime.now().date()
+ if qs == "浠婂ぉ":
+ st.session_state['ma_start_date'] = today
+ st.session_state['ma_end_date'] = today
+ elif qs == "鏈�杩�3澶�":
+ st.session_state['ma_start_date'] = today - timedelta(days=3)
+ st.session_state['ma_end_date'] = today
+ elif qs == "鏈�杩�7澶�":
+ st.session_state['ma_start_date'] = today - timedelta(days=7)
+ st.session_state['ma_end_date'] = today
+ elif qs == "鏈�杩�30澶�":
+ st.session_state['ma_start_date'] = today - timedelta(days=30)
+ st.session_state['ma_end_date'] = today
+
+ def on_date_change():
+ st.session_state['ma_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['ma_quick_select'] == option else "secondary"
+ if st.button(option, key=f"btn_ma_{option}", width='stretch', type=button_type):
+ update_dates(option)
+ st.rerun()
+
+ with cols[5]:
+ start_date = st.date_input(
+ "寮�濮嬫棩鏈�",
+ label_visibility="collapsed",
+ key="ma_start_date",
+ on_change=on_date_change
+ )
+
+ with cols[6]:
+ end_date = st.date_input(
+ "缁撴潫鏃ユ湡",
+ label_visibility="collapsed",
+ key="ma_end_date",
+ on_change=on_date_change
+ )
+
+ with cols[7]:
+ query_button = st.button("馃殌 寮�濮嬪垎鏋�", key="ma_query", width='stretch')
+
+ # 妯″瀷閰嶇疆
+ st.markdown("---")
+ st.write("馃 **妯″瀷閰嶇疆**")
+ model_cols = st.columns(2)
+
+ with model_cols[0]:
+ # 妯″瀷绫诲瀷閫夋嫨
+ model_options = ['RandomForest', 'GradientBoosting', 'SVR', 'MLP']
+
+ model_type = st.selectbox(
+ "妯″瀷绫诲瀷",
+ options=model_options,
+ key="ma_model_type",
+ help="閫夋嫨鐢ㄤ簬棰勬祴鐨勬ā鍨嬬被鍨�"
+ )
+
+ # 绋虫�佽瘑鍒厤缃�
+ st.markdown("---")
+ steady_cols = st.columns(3)
+ with steady_cols[0]:
+ st.write("鈿栵笍 **绋虫�佽瘑鍒厤缃�**")
+ st.checkbox(
+ "浠呬娇鐢ㄧǔ鎬佹暟鎹繘琛岃缁�",
+ value=st.session_state['ma_use_steady_data'],
+ key="ma_use_steady_data",
+ help="鍚敤鍚庯紝鍙娇鐢ㄧ背閲嶇ǔ鎬佹椂娈电殑鏁版嵁杩涜妯″瀷璁粌"
+ )
+
+ with steady_cols[1]:
+ st.write("馃搹 **绋虫�佸弬鏁�**")
+ st.slider(
+ "婊戝姩绐楀彛澶у皬 (绉�)",
+ min_value=5,
+ max_value=60,
+ value=st.session_state['ma_steady_window'],
+ step=5,
+ key="ma_steady_window",
+ help="鐢ㄤ簬绋虫�佽瘑鍒殑婊戝姩绐楀彛澶у皬"
+ )
+
+ with steady_cols[2]:
+ st.write("馃搳 **绋虫�侀槇鍊�**")
+ st.slider(
+ "娉㈠姩闃堝�� (%)",
+ min_value=0.1,
+ max_value=2.0,
+ value=st.session_state['ma_steady_threshold'],
+ step=0.1,
+ key="ma_steady_threshold",
+ help="绋虫�佽瘑鍒殑娉㈠姩鑼冨洿闃堝��"
+ )
+
+
+
+ # 杞崲涓篸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']
+
+
+
+ # 妫�鏌ユ槸鍚︽湁鏁版嵁
+ 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("鎵�閫夋椂闂存鍐呮湭鎵惧埌浠讳綍鏁版嵁锛岃灏濊瘯璋冩暣鏌ヨ鏉′欢銆�")
+ return
+
+ # 鏁版嵁鏁村悎涓庨澶勭悊
+ 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("鏁版嵁鏁村悎澶辫触锛岃妫�鏌ユ暟鎹川閲忔垨璋冩暣鏃堕棿鑼冨洿銆�")
+ return
+
+ # 閲嶅懡鍚嶇背閲嶅垪
+ df_analysis.rename(columns={'metered_weight': '绫抽噸'}, inplace=True)
+
+ # 绋虫�佽瘑鍒�
+ steady_detector = SteadyStateDetector()
+
+ # 鑾峰彇绋虫�佽瘑鍒弬鏁�
+ use_steady_data = st.session_state.get('ma_use_steady_data', True)
+ steady_window = st.session_state.get('ma_steady_window', 20)
+ steady_threshold = st.session_state.get('ma_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_full is not None and not df_extruder_full.empty:
+ fig_trend.add_trace(go.Scatter(
+ x=df_extruder_full['time'],
+ y=df_extruder_full['metered_weight'],
+ name='绫抽噸 (Kg/m)',
+ mode='lines',
+ line=dict(color='blue', width=2)
+ ))
+
+ # 娣诲姞铻烘潌杞��
+ fig_trend.add_trace(go.Scatter(
+ x=df_extruder_full['time'],
+ y=df_extruder_full['screw_speed_actual'],
+ name='铻烘潌杞�� (RPM)',
+ mode='lines',
+ line=dict(color='green', width=1.5),
+ yaxis='y2'
+ ))
+
+ # 娣诲姞鏈哄ご鍘嬪姏
+ fig_trend.add_trace(go.Scatter(
+ x=df_extruder_full['time'],
+ y=df_extruder_full['head_pressure'],
+ name='鏈哄ご鍘嬪姏',
+ 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='鍘熷鏁版嵁瓒嬪娍',
+ 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("馃搳 楂樼骇棰勬祴鍒嗘瀽")
+
+ # 妫�鏌ユ墍鏈夐粯璁ょ壒寰佹槸鍚﹀湪鏁版嵁涓�
+ missing_features = [f for f in default_features if f not in df_analysis.columns]
+ if missing_features:
+ st.warning(f"鏁版嵁涓己灏戜互涓嬬壒寰�: {', '.join(missing_features)}")
+ else:
+ try:
+ # 鍑嗗鏁版嵁
+ # 鏍规嵁閰嶇疆鍐冲畾鏄惁鍙娇鐢ㄧǔ鎬佹暟鎹�
+ use_steady_data = st.session_state.get('ma_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()
+
+ # 棣栧厛纭繚df_analysis涓病鏈塏aN鍊�
+ df_analysis_clean = df_filtered.dropna(subset=default_features + ['绫抽噸'])
+
+ # 妫�鏌ユ竻鐞嗗悗鐨勬暟鎹噺
+ if len(df_analysis_clean) < 30:
+ st.warning("鏁版嵁閲忎笉瓒筹紝鏃犳硶杩涜鏈夋晥鐨勯娴嬪垎鏋�")
+ else:
+ # 鍒涘缓涓�涓柊鐨凞ataFrame鏉ュ瓨鍌ㄦ墍鏈夌壒寰佸拰鐩爣鍙橀噺
+ all_features = df_analysis_clean[default_features + ['绫抽噸']].copy()
+
+
+
+
+ # 娓呯悊鎵�鏈塏aN鍊�
+ all_features_clean = all_features.dropna()
+
+ # 妫�鏌ユ竻鐞嗗悗鐨勬暟鎹噺
+ if len(all_features_clean) < 20:
+ st.warning("鐗瑰緛宸ョ▼鍚庢暟鎹噺涓嶈冻锛屾棤娉曡繘琛屾湁鏁堢殑棰勬祴鍒嗘瀽")
+ else:
+ # 鍒嗙鐗瑰緛鍜岀洰鏍囧彉閲�
+ feature_columns = [col for col in all_features_clean.columns if col != '绫抽噸']
+ X_final = all_features_clean[feature_columns]
+ y_final = all_features_clean['绫抽噸']
+
+ # 妫�鏌ユ渶缁堟暟鎹噺
+ if len(X_final) >= 20:
+ # 鍒嗗壊璁粌闆嗗拰娴嬭瘯闆�
+ X_train, X_test, y_train, y_test = train_test_split(X_final, y_final, test_size=0.2, random_state=42)
+
+ # 鏁版嵁鏍囧噯鍖�
+ scaler_X = StandardScaler()
+ scaler_y = MinMaxScaler()
+
+ X_train_scaled = scaler_X.fit_transform(X_train)
+ X_test_scaled = scaler_X.transform(X_test)
+ y_train_scaled = scaler_y.fit_transform(y_train.values.reshape(-1, 1)).ravel()
+ y_test_scaled = scaler_y.transform(y_test.values.reshape(-1, 1)).ravel()
+
+ # 妯″瀷璁粌
+ model = None
+ y_pred = None
+
+ if model_type == 'RandomForest':
+ # 闅忔満妫灄鍥炲綊
+ model = RandomForestRegressor(n_estimators=100, random_state=42)
+ model.fit(X_train, y_train)
+ y_pred = model.predict(X_test)
+
+ elif model_type == 'GradientBoosting':
+ # 姊害鎻愬崌鍥炲綊
+ model = GradientBoostingRegressor(n_estimators=100, random_state=42)
+ model.fit(X_train, y_train)
+ y_pred = model.predict(X_test)
+
+ elif model_type == 'SVR':
+ # 鏀寔鍚戦噺鍥炲綊
+ model = SVR(kernel='rbf', C=1.0, gamma='scale')
+ model.fit(X_train_scaled, y_train_scaled)
+ y_pred_scaled = model.predict(X_test_scaled)
+ y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
+
+ elif model_type == 'MLP':
+ # 澶氬眰鎰熺煡鍣ㄥ洖褰�
+ model = MLPRegressor(hidden_layer_sizes=(100, 50), max_iter=500, random_state=42)
+ model.fit(X_train_scaled, y_train_scaled)
+ y_pred_scaled = model.predict(X_test_scaled)
+ y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
+
+
+
+ # 璁$畻璇勪及鎸囨爣
+ # 纭繚y_test鍜寉_pred闀垮害涓�鑷�
+ min_len = min(len(y_test), len(y_pred))
+ if min_len > 0:
+ y_test_trimmed = y_test[:min_len]
+ y_pred_trimmed = y_pred[:min_len]
+ r2 = r2_score(y_test_trimmed, y_pred_trimmed)
+ mse = mean_squared_error(y_test_trimmed, y_pred_trimmed)
+ mae = mean_absolute_error(y_test_trimmed, y_pred_trimmed)
+ rmse = np.sqrt(mse)
+ else:
+ r2 = 0
+ mse = 0
+ mae = 0
+ rmse = 0
+
+ # 鏄剧ず妯″瀷鎬ц兘
+ 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_trimmed,
+ '棰勬祴鍊�': y_pred_trimmed
+ })
+ 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=f'娴嬭瘯闆�: 瀹為檯绫抽噸 vs 棰勬祴绫抽噸 ({model_type})',
+ 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_trimmed - y_pred_trimmed
+
+ # 鍒涘缓娈嬪樊鍥�
+ 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')
+
+ # --- 鐗瑰緛閲嶈鎬э紙濡傛灉妯″瀷鏀寔锛� ---
+ if model_type in ['RandomForest', 'GradientBoosting']:
+ st.subheader("鈿栵笍 鐗瑰緛閲嶈鎬у垎鏋�")
+
+ # 璁$畻鐗瑰緛閲嶈鎬�
+ feature_importance = pd.DataFrame({
+ '鐗瑰緛': X_train.columns,
+ '閲嶈鎬�': model.feature_importances_
+ })
+ 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.subheader("锟� 妯″瀷淇濆瓨")
+
+ # 鍒涘缓妯″瀷鐩綍锛堝鏋滀笉瀛樺湪锛�
+ model_dir = "saved_models"
+ os.makedirs(model_dir, exist_ok=True)
+
+ # 鍑嗗妯″瀷淇℃伅
+ model_info = {
+ 'model': model,
+ 'features': feature_columns,
+ 'scaler_X': scaler_X if model_type in ['SVR', 'MLP'] else None,
+ 'scaler_y': scaler_y if model_type in ['SVR', 'MLP'] else None,
+ 'model_type': model_type,
+ 'created_at': datetime.now(),
+ 'r2_score': r2,
+ 'mse': mse,
+ 'mae': mae,
+ 'rmse': rmse,
+ 'use_steady_data': use_steady_data
+ }
+
+ # 鐢熸垚妯″瀷鏂囦欢鍚�
+ model_filename = f"advanced_{model_type.lower()}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.joblib"
+ model_path = os.path.join(model_dir, model_filename)
+
+ # 淇濆瓨妯″瀷
+ joblib.dump(model_info, model_path)
+
+ st.success(f"妯″瀷宸叉垚鍔熶繚瀛�: {model_filename}")
+ st.info(f"淇濆瓨璺緞: {model_path}")
+
+ # --- 鏁版嵁棰勮 ---
+ st.subheader("馃攳 鏁版嵁棰勮")
+ st.dataframe(df_analysis.head(20), width='stretch')
+
+ # --- 瀵煎嚭鏁版嵁 ---
+ st.subheader("馃捑 瀵煎嚭鏁版嵁")
+ # 灏嗘暟鎹浆鎹负CSV鏍煎紡
+ csv = df_analysis.to_csv(index=False)
+ # 鍒涘缓涓嬭浇鎸夐挳
+ st.download_button(
+ label="瀵煎嚭鏁村悎鍚庣殑鏁版嵁 (CSV)",
+ data=csv,
+ file_name=f"metered_weight_advanced_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
+ mime="text/csv",
+ help="鐐瑰嚮鎸夐挳瀵煎嚭鏁村悎鍚庣殑绫抽噸鍒嗘瀽鏁版嵁"
+ )
+ except Exception as e:
+ st.error(f"妯″瀷璁粌鎴栭娴嬪け璐�: {str(e)}")
+
+ else:
+ # 鎻愮ず鐢ㄦ埛鐐瑰嚮寮�濮嬪垎鏋愭寜閽�
+ st.info("璇烽�夋嫨鏃堕棿鑼冨洿骞剁偣鍑�'寮�濮嬪垎鏋�'鎸夐挳鑾峰彇鏁版嵁銆�")
--
Gitblit v1.9.3