From 4048393750de17cfa2ae59fec1380a81ea2b2a6b Mon Sep 17 00:00:00 2001
From: baoshiwei <baoshiwei@shlanbao.cn>
Date: 星期一, 02 二月 2026 09:47:24 +0800
Subject: [PATCH] feat: 添加米重分析模块并优化综合看板功能
---
app/pages/comprehensive_dashboard.py | 119 +
app/pages/metered_weight_advanced.py | 635 ++++++++
app/pages/metered_weight_correlation.py | 777 ++++++++++
app/pages/main_process_dashboard.py | 39
.env | 6
app/services/data_processing_service.py | 9
README.md | 22
requirements.txt | 5
app/pages/sorting_dashboard.py | 2
app/pages/metered_weight_regression.py | 620 ++++++++
app/pages/metered_weight_advanced copy.py | 832 +++++++++++
app/pages/metered_weight_advanced pytorch.py | 873 +++++++++++
app/pages/metered_weight_dashboard.py | 430 +++++
dashboard.py | 36
app/services/main_process_service.py | 2
15 files changed, 4,354 insertions(+), 53 deletions(-)
diff --git a/.env b/.env
index 41aea2c..6709d38 100644
--- a/.env
+++ b/.env
@@ -1,6 +1,6 @@
# Database Configuration
-DB_HOST=localhost
-DB_PORT=5433
+DB_HOST=192.168.21.6
+DB_PORT=5432
DB_NAME=aics
DB_USER=aics
-DB_PASSWORD=123456lb
\ No newline at end of file
+DB_PASSWORD=123456
\ No newline at end of file
diff --git a/README.md b/README.md
index 31f7c62..d4bd2ee 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,21 @@
-鏈」鐩噰鐢∕IT璁稿彲璇併�
+鏈」鐩噰鐢∕IT璁稿彲璇侊拷
+
+
+鏈湴娴嬭瘯----postgresql鏁版嵁搴�------------
+鐢ㄦ埛锛歱ostgres
+瀵嗙爜锛�123456
+
+鏁版嵁搴擄細aics
+鐢ㄦ埛鍚嶏細aics
+瀵嗙爜锛�123456lb
+-----------------------------------------
+
+
+姝f柊椤圭洰鐜板満---postgresql-------------
+鐢ㄦ埛锛歱ostgres
+瀵嗙爜锛�123456
+
+鏁版嵁搴擄細aics
+鐢ㄦ埛鍚嶏細aics
+瀵嗙爜锛歭anbaoit-123
+------------------------------------------
\ No newline at end of file
diff --git a/app/pages/comprehensive_dashboard.py b/app/pages/comprehensive_dashboard.py
index 3f1097e..1cd4002 100644
--- a/app/pages/comprehensive_dashboard.py
+++ b/app/pages/comprehensive_dashboard.py
@@ -14,7 +14,7 @@
main_process_service = MainProcessService()
# 椤甸潰鏍囬
- st.title("澶氱淮缁煎悎鍒嗘瀽")
+ st.title("鏉¢噸缁煎悎鍒嗘瀽")
# 鍒濆鍖栦細璇濈姸鎬佺敤浜庢棩鏈熷悓姝�
if 'comp_start_date' not in st.session_state:
@@ -23,6 +23,8 @@
st.session_state['comp_end_date'] = datetime.now().date()
if 'comp_quick_select' not in st.session_state:
st.session_state['comp_quick_select'] = "鏈�杩�7澶�"
+ if 'time_offset' not in st.session_state:
+ st.session_state['time_offset'] = 0
# 瀹氫箟鍥炶皟鍑芥暟
def update_dates(qs):
@@ -93,8 +95,22 @@
on_change=on_date_change
)
- with cols[7]:
- query_button = st.button("馃殌 鏌ヨ", key="comp_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,
+ max_value=60,
+ value=st.session_state['time_offset'],
+ help="鐢变簬鑳庨潰浠庢尋鍑哄埌鍒嗘嫞闇�瑕佹椂闂达紝灏嗕笂娓告暟鎹悜鍚庣Щ鍔紝浣垮叾涓庡垎鎷g绉や笂鐨勯噸閲忔暟鎹湪鏃堕棿杞翠笂瀵归綈銆�"
+ )
+ st.session_state['time_offset'] = time_offset
+ with offset_cols[2]:
+ query_button = st.button("馃殌 寮�濮嬪垎鏋�", key="comp_query", width='stretch')
# 杞崲涓篸atetime瀵硅薄
start_dt = datetime.combine(start_date, datetime.min.time())
@@ -103,20 +119,32 @@
# 鏌ヨ澶勭悊
if query_button:
with st.spinner("姝e湪鑱氬悎澶氭簮鏁版嵁..."):
- # 1. 鑾峰彇鍒嗘嫞纾呯Г鏁版嵁
+ # 鑾峰彇鍋忕Щ閲�
+ offset_delta = timedelta(minutes=st.session_state['time_offset'])
+
+ # 1. 鑾峰彇鍒嗘嫞纾呯Г鏁版嵁 (浣滀负鍩哄噯锛屼笉鍋忕Щ)
df_sorting = sorting_service.get_sorting_scale_data(start_dt, end_dt)
- # 2. 鑾峰彇鎸ゅ嚭鏈烘暟鎹�
+
+ # 2. 鑾峰彇鎸ゅ嚭鏈烘暟鎹� (搴旂敤鍋忕Щ)
df_extruder = extruder_service.get_extruder_data(start_dt, end_dt)
- # 3. 鑾峰彇涓绘祦绋嬫帶鍒舵暟鎹�
+ if df_extruder is not None and not df_extruder.empty:
+ df_extruder['time'] = df_extruder['time'] + offset_delta
+
+ # 3. 鑾峰彇涓绘祦绋嬫帶鍒舵暟鎹� (搴旂敤鍋忕Щ)
df_main_speed = main_process_service.get_cutting_setting_data(start_dt, end_dt)
+ if df_main_speed is not None and not df_main_speed.empty:
+ df_main_speed['time'] = df_main_speed['time'] + offset_delta
+
df_temp = main_process_service.get_temperature_control_data(start_dt, end_dt)
+ if df_temp is not None and not df_temp.empty:
+ df_temp['time'] = df_temp['time'] + offset_delta
# 妫�鏌ユ槸鍚︽湁鏁版嵁
has_data = any([
df_sorting is not None and not df_sorting.empty,
df_extruder is not None and not df_extruder.empty,
df_main_speed is not None and not df_main_speed.empty,
- df_temp is not None and not df_temp.empty
+ df_temp is not None and not df_temp.empty
])
if not has_data:
@@ -207,14 +235,14 @@
# 娣诲姞鎸ゅ嚭鏈虹背閲�
if df_extruder is not None and not df_extruder.empty:
- fig.add_trace(go.Scatter(
- x=df_extruder['time'],
- y=df_extruder['metered_weight'],
- name='鎸ゅ嚭鏈虹背閲� (g/m)',
- mode='lines',
- line=dict(color='green', width=1.5),
- yaxis='y2'
- ))
+ # fig.add_trace(go.Scatter(
+ # x=df_extruder['time'],
+ # y=df_extruder['metered_weight'],
+ # name='鎸ゅ嚭鏈虹背閲� (Kg/m)',
+ # mode='lines',
+ # line=dict(color='green', width=1.5),
+ # yaxis='y2'
+ # ))
# 娣诲姞鎸ゅ嚭鏈哄疄闄呰浆閫�
fig.add_trace(go.Scatter(
x=df_extruder['time'],
@@ -235,14 +263,26 @@
line=dict(color='red', width=1.5),
yaxis='y3' # 鍏辩敤閫熷害杞�
))
+
+ # 娣诲姞瑁佸垏璁℃暟
+ if 'cutting_count' in df_main_speed.columns:
+ fig.add_trace(go.Scatter(
+ x=df_main_speed['time'],
+ y=df_main_speed['cutting_count'],
+ name='瑁佸垏璁℃暟',
+ mode='lines',
+ line=dict(color='purple', width=1.5),
+ yaxis='y5'
+ ))
# 娣诲姞娓╁害璁惧畾鍊�
if df_temp is not None and not df_temp.empty:
temp_fields = {
- 'nakata_extruder_screw_set_temp': '铻烘潌璁惧畾 (掳C)',
- 'nakata_extruder_rear_barrel_set_temp': '鍚庢満绛掕瀹� (掳C)',
- 'nakata_extruder_front_barrel_set_temp': '鍓嶆満绛掕瀹� (掳C)',
- 'nakata_extruder_head_set_temp': '鏈哄ご璁惧畾 (掳C)'
+ 'nakata_extruder_screw_display_temp': '铻烘潌鏄剧ず (掳C)',
+ 'nakata_extruder_rear_barrel_display_temp': '鍚庢満绛掓樉绀� (掳C)',
+ 'nakata_extruder_front_barrel_display_temp': '鍓嶆満绛掓樉绀� (掳C)',
+ 'nakata_extruder_head_display_temp': '鏈哄ご鏄剧ず (掳C)'
+
}
colors = ['#FF4B4B', '#FF8C00', '#FFD700', '#DA70D6']
for i, (field, label) in enumerate(temp_fields.items()):
@@ -257,7 +297,7 @@
# 璁剧疆澶氬潗鏍囪酱甯冨眬
fig.update_layout(
- title='澶氱淮缁煎悎瓒嬪娍鍒嗘瀽',
+ title='鏉¢噸缁煎悎瓒嬪娍鍒嗘瀽',
xaxis=dict(
title='鏃堕棿',
rangeslider=dict(visible=True),
@@ -269,7 +309,7 @@
tickfont=dict(color='blue')
),
yaxis2=dict(
- title='绫抽噸 (g/m)',
+ title='绫抽噸 (Kg/m)',
title_font=dict(color='green'),
tickfont=dict(color='green'),
overlaying='y',
@@ -293,6 +333,15 @@
anchor='free',
position=0.15
),
+ yaxis5=dict(
+ title='瑁佸垏璁℃暟',
+ title_font=dict(color='purple'),
+ tickfont=dict(color='purple'),
+ overlaying='y',
+ side='right',
+ anchor='free',
+ position=0.7
+ ),
legend=dict(
orientation="h",
yanchor="bottom",
@@ -309,21 +358,21 @@
st.plotly_chart(fig, width='stretch', config={'scrollZoom': True})
# 鏁版嵁鎽樿
- st.subheader("馃搳 鏁版嵁鎽樿")
- summary_cols = st.columns(4)
+ # st.subheader("馃搳 鏁版嵁鎽樿")
+ # summary_cols = st.columns(4)
- with summary_cols[0]:
- if df_sorting is not None and not df_sorting.empty:
- st.metric("骞冲潎閲嶉噺", f"{df_sorting['weight'].mean():.2f} kg")
+ # with summary_cols[0]:
+ # if df_sorting is not None and not df_sorting.empty:
+ # st.metric("骞冲潎閲嶉噺", f"{df_sorting['weight'].mean():.2f} kg")
- with summary_cols[1]:
- if df_extruder is not None and not df_extruder.empty:
- st.metric("骞冲潎绫抽噸", f"{df_extruder['metered_weight'].mean():.2f} g/m")
+ # with summary_cols[1]:
+ # if df_extruder is not None and not df_extruder.empty:
+ # st.metric("骞冲潎绫抽噸", f"{df_extruder['metered_weight'].mean():.2f} Kg/m")
- with summary_cols[2]:
- if df_main_speed is not None and not df_main_speed.empty:
- st.metric("骞冲潎涓婚��", f"{df_main_speed['process_main_speed'].mean():.2f} M/Min")
+ # with summary_cols[2]:
+ # if df_main_speed is not None and not df_main_speed.empty:
+ # st.metric("骞冲潎涓婚��", f"{df_main_speed['process_main_speed'].mean():.2f} M/Min")
- with summary_cols[3]:
- if df_temp is not None and not df_temp.empty:
- st.metric("骞冲潎铻烘潌娓╂帶", f"{df_temp['nakata_extruder_screw_set_temp'].mean():.1f} 掳C")
+ # with summary_cols[3]:
+ # if df_temp is not None and not df_temp.empty:
+ # st.metric("骞冲潎铻烘潌娓╂帶", f"{df_temp['nakata_extruder_screw_set_temp'].mean():.1f} 掳C")
diff --git a/app/pages/main_process_dashboard.py b/app/pages/main_process_dashboard.py
index 74fce54..389a18d 100644
--- a/app/pages/main_process_dashboard.py
+++ b/app/pages/main_process_dashboard.py
@@ -111,8 +111,19 @@
fig_speed = px.line(df_speed, x='time', y='process_main_speed',
title="娴佺▼涓婚�熷害 (M/Min)",
labels={'time': '鏃堕棿', 'process_main_speed': '涓婚�熷害 (M/Min)'})
- fig_speed.update_layout(xaxis=dict(rangeslider=dict(visible=True), type='date'))
- st.plotly_chart(fig_speed, width='stretch', config={'scrollZoom': True})
+ fig_speed.update_layout(
+ xaxis=dict(rangeslider=dict(visible=True), type='date'),
+ yaxis=dict(fixedrange=False),
+ hovermode='x unified',
+ dragmode='zoom',
+ )
+ st.plotly_chart(fig_speed, width='stretch', config={
+ 'scrollZoom': True,
+ 'modeBarButtonsToAdd': ['zoom2d', 'zoomIn2d', 'zoomOut2d'],
+ 'doubleClick': 'reset',
+ 'displayModeBar': True,
+ 'toImageButtonOptions': {'format': 'png'}
+ })
else:
st.info("璇ユ椂闂存鍐呮棤涓婚�熷害鏁版嵁")
@@ -126,9 +137,17 @@
title="鐢垫満绾块�� (M/Min)",
xaxis_title="鏃堕棿",
yaxis_title="绾块�� (M/Min)",
- xaxis=dict(rangeslider=dict(visible=True), type='date')
+ xaxis=dict(rangeslider=dict(visible=True), type='date'),
+ yaxis=dict(fixedrange=False),
+ hovermode='x unified',
+ dragmode='zoom'
)
- st.plotly_chart(fig_motor, width='stretch', config={'scrollZoom': True})
+ st.plotly_chart(fig_motor, width='stretch', config={
+ 'scrollZoom': True,
+ 'modeBarButtonsToAdd': ['zoom2d', 'zoomIn2d', 'zoomOut2d'],
+ 'doubleClick': 'reset',
+ 'displayModeBar': True
+ })
else:
st.info("璇ユ椂闂存鍐呮棤鐢垫満鐩戞帶鏁版嵁")
@@ -165,8 +184,16 @@
title="涓敯鎸ゅ嚭鏈烘俯搴� (掳C)",
xaxis_title="鏃堕棿",
yaxis_title="娓╁害 (掳C)",
- xaxis=dict(rangeslider=dict(visible=True), type='date')
+ xaxis=dict(rangeslider=dict(visible=True), type='date'),
+ yaxis=dict(fixedrange=False),
+ hovermode='x unified',
+ dragmode='zoom'
)
- st.plotly_chart(fig_temp, width='stretch', config={'scrollZoom': True})
+ st.plotly_chart(fig_temp, width='stretch', config={
+ 'scrollZoom': True,
+ 'modeBarButtonsToAdd': ['zoom2d', 'zoomIn2d', 'zoomOut2d'],
+ 'doubleClick': 'reset',
+ 'displayModeBar': True
+ })
else:
st.info("璇ユ椂闂存鍐呮棤娓╁害鎺у埗鏁版嵁")
diff --git a/app/pages/metered_weight_advanced copy.py b/app/pages/metered_weight_advanced copy.py
new file mode 100644
index 0000000..f5fe242
--- /dev/null
+++ b/app/pages/metered_weight_advanced copy.py
@@ -0,0 +1,832 @@
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import pandas as pd
+import numpy as np
+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
+
+# 灏濊瘯瀵煎叆娣卞害瀛︿範搴�
+use_deep_learning = False
+try:
+
+ from tensorflow.keras.models import Sequential
+ from tensorflow.keras.layers import LSTM, GRU, Dense, Dropout, Bidirectional
+ from tensorflow.keras.optimizers import Adam
+ use_deep_learning = True
+except ImportError:
+ st.warning("鏈娴嬪埌TensorFlow/Keras锛屾繁搴﹀涔犳ā鍨嬪皢涓嶅彲鐢ㄣ�傝瀹夎tensorflow浠ヤ娇鐢↙STM/GRU妯″瀷銆�")
+
+
+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
+
+ # 榛樿鐗瑰緛鍒楄〃锛堜笉鍐嶅厑璁哥敤鎴烽�夋嫨锛�
+ 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
+ # 娓呴櫎涔嬪墠鐨勭紦瀛樻暟鎹拰鍒嗘瀽鏍囧織
+ for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp', 'last_query_start', 'last_query_end', 'analysis_completed']:
+ if key in st.session_state:
+ del st.session_state[key]
+
+ def on_date_change():
+ st.session_state['ma_quick_select'] = "鑷畾涔�"
+ # 娓呴櫎涔嬪墠鐨勭紦瀛樻暟鎹拰鍒嗘瀽鏍囧織
+ for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp', 'last_query_start', 'last_query_end', 'analysis_completed']:
+ if key in st.session_state:
+ del st.session_state[key]
+
+ # 鏌ヨ鏉′欢鍖哄煙
+ 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']
+ if use_deep_learning:
+ model_options.extend(['LSTM', 'GRU', 'BiLSTM'])
+
+ model_type = st.selectbox(
+ "妯″瀷绫诲瀷",
+ options=model_options,
+ key="ma_model_type",
+ help="閫夋嫨鐢ㄤ簬棰勬祴鐨勬ā鍨嬬被鍨�"
+ )
+
+ with model_cols[1]:
+ # 搴忓垪闀垮害锛堜粎閫傜敤浜庢繁搴﹀涔犳ā鍨嬶級
+ if model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ sequence_length = st.slider(
+ "搴忓垪闀垮害",
+ min_value=5,
+ max_value=30,
+ value=st.session_state['ma_sequence_length'],
+ step=1,
+ help="鐢ㄤ簬娣卞害瀛︿範妯″瀷鐨勬椂闂村簭鍒楅暱搴�",
+ key="ma_sequence_length"
+ )
+ else:
+ st.session_state['ma_sequence_length'] = 10
+ st.write("搴忓垪闀垮害: 10 (榛樿锛屼粎閫傜敤浜庢繁搴﹀涔犳ā鍨�)")
+
+ # 杞崲涓篸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
+ # 璁剧疆鍒嗘瀽瀹屾垚鏍囧織
+ st.session_state['analysis_completed'] = True
+
+ # 鏁版嵁澶勭悊鍜屽垎鏋�
+ if all(key in st.session_state for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp']) and st.session_state.get('analysis_completed', False):
+ 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)
+
+ # --- 鍘熷鏁版嵁瓒嬪娍鍥� ---
+ 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:
+ # 鍑嗗鏁版嵁
+ # 棣栧厛纭繚df_analysis涓病鏈塏aN鍊�
+ df_analysis_clean = df_analysis.dropna(subset=default_features + ['绫抽噸'])
+
+ # 妫�鏌ユ竻鐞嗗悗鐨勬暟鎹噺
+ if len(df_analysis_clean) < 30:
+ st.warning("鏁版嵁閲忎笉瓒筹紝鏃犳硶杩涜鏈夋晥鐨勯娴嬪垎鏋�")
+ else:
+ # 鍒涘缓涓�涓柊鐨凞ataFrame鏉ュ瓨鍌ㄦ墍鏈夌壒寰佸拰鐩爣鍙橀噺
+ all_features = df_analysis_clean[default_features + ['绫抽噸']].copy()
+
+ # 娣诲姞鏃堕棿鐩稿叧鐗瑰緛
+ if 'time' in df_analysis_clean.columns:
+ all_features['hour'] = df_analysis_clean['time'].dt.hour
+ all_features['minute'] = df_analysis_clean['time'].dt.minute
+ all_features['second'] = df_analysis_clean['time'].dt.second
+ all_features['time_of_day'] = all_features['hour'] * 3600 + all_features['minute'] * 60 + all_features['second']
+ else:
+ all_features['hour'] = 0
+ all_features['minute'] = 0
+ all_features['second'] = 0
+ all_features['time_of_day'] = 0
+
+ # 娣诲姞婊炲悗鐗瑰緛
+ for feature in default_features:
+ for lag in [1, 2, 3]:
+ all_features[f'{feature}_lag{lag}'] = all_features[feature].shift(lag)
+ all_features[f'{feature}_diff{lag}'] = all_features[feature].diff(lag)
+
+ # 娣诲姞婊氬姩缁熻鐗瑰緛
+ for feature in default_features:
+ all_features[f'{feature}_rolling_mean'] = all_features[feature].rolling(window=5).mean()
+ all_features[f'{feature}_rolling_std'] = all_features[feature].rolling(window=5).std()
+ all_features[f'{feature}_rolling_min'] = all_features[feature].rolling(window=5).min()
+ all_features[f'{feature}_rolling_max'] = all_features[feature].rolling(window=5).max()
+
+ # 娓呯悊鎵�鏈塏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()
+
+ elif use_deep_learning and model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ # 鍑嗗鏃堕棿搴忓垪鏁版嵁
+ sequence_length = st.session_state['ma_sequence_length']
+
+ def create_sequences(X, y, seq_length):
+ X_seq = []
+ y_seq = []
+ # 纭繚X鍜寉鐨勯暱搴︿竴鑷�
+ min_len = min(len(X), len(y))
+ # 纭繚X鍜寉鐨勯暱搴﹁嚦灏戜负seq_length + 1
+ if min_len <= seq_length:
+ return np.array([]), np.array([])
+ # 鎴柇X鍜寉鍒扮浉鍚岄暱搴�
+ X_trimmed = X[:min_len]
+ y_trimmed = y[:min_len]
+ # 鍒涘缓搴忓垪
+ for i in range(len(X_trimmed) - seq_length):
+ X_seq.append(X_trimmed[i:i+seq_length])
+ y_seq.append(y_trimmed[i+seq_length])
+ return np.array(X_seq), np.array(y_seq)
+
+ # 涓烘繁搴﹀涔犳ā鍨嬪垱寤哄簭鍒�
+ X_train_seq, y_train_seq = create_sequences(X_train_scaled, y_train_scaled, sequence_length)
+ X_test_seq, y_test_seq = create_sequences(X_test_scaled, y_test_scaled, sequence_length)
+
+ # 纭繚搴忓垪鏁版嵁闀垮害涓�鑷�
+ if len(X_train_seq) != len(y_train_seq):
+ min_len_train = min(len(X_train_seq), len(y_train_seq))
+ X_train_seq = X_train_seq[:min_len_train]
+ y_train_seq = y_train_seq[:min_len_train]
+
+ if len(X_test_seq) != len(y_test_seq):
+ min_len_test = min(len(X_test_seq), len(y_test_seq))
+ X_test_seq = X_test_seq[:min_len_test]
+ y_test_seq = y_test_seq[:min_len_test]
+
+ # 妫�鏌ュ垱寤虹殑搴忓垪鏄惁涓虹┖
+ if len(X_train_seq) == 0 or len(y_train_seq) == 0:
+ st.warning(f"鏁版嵁閲忎笉瓒筹紝鏃犳硶鍒涘缓鏈夋晥鐨凩STM搴忓垪銆傞渶瑕佽嚦灏� {sequence_length + 1} 涓牱鏈紝褰撳墠鍙湁 {min(len(X_train_scaled), len(y_train_scaled))} 涓牱鏈��")
+ # 浣跨敤闅忔満妫灄浣滀负澶囬�夋ā鍨�
+ model = RandomForestRegressor(n_estimators=100, random_state=42)
+ model.fit(X_train, y_train)
+ y_pred = model.predict(X_test)
+ else:
+ # 鏋勫缓娣卞害瀛︿範妯″瀷
+ input_shape = (sequence_length, X_train_scaled.shape[1])
+
+ deep_model = Sequential()
+
+ if model_type == 'LSTM':
+ deep_model.add(LSTM(64, return_sequences=True, input_shape=input_shape))
+ deep_model.add(LSTM(32, return_sequences=False))
+ elif model_type == 'GRU':
+ deep_model.add(GRU(64, return_sequences=True, input_shape=input_shape))
+ deep_model.add(GRU(32, return_sequences=False))
+ elif model_type == 'BiLSTM':
+ deep_model.add(Bidirectional(LSTM(64, return_sequences=True), input_shape=input_shape))
+ deep_model.add(Bidirectional(LSTM(32, return_sequences=False)))
+
+ deep_model.add(Dense(32, activation='relu'))
+ deep_model.add(Dropout(0.2))
+ deep_model.add(Dense(1))
+
+ # 缂栬瘧妯″瀷
+ deep_model.compile(optimizer=Adam(learning_rate=0.001), loss='mean_squared_error')
+
+ # 璁粌妯″瀷
+ # 纭繚X_train_seq鍜寉_train_seq闀垮害涓�鑷�
+ min_len_train = min(len(X_train_seq), len(y_train_seq))
+ min_len_test = min(len(X_test_seq), len(y_test_seq))
+
+ if min_len_train > 0 and min_len_test > 0:
+ X_train_seq_trimmed = X_train_seq[:min_len_train]
+ y_train_seq_trimmed = y_train_seq[:min_len_train]
+ X_test_seq_trimmed = X_test_seq[:min_len_test]
+ y_test_seq_trimmed = y_test_seq[:min_len_test]
+
+ history = deep_model.fit(
+ X_train_seq_trimmed, y_train_seq_trimmed,
+ validation_data=(X_test_seq_trimmed, y_test_seq_trimmed),
+ epochs=50,
+ batch_size=32,
+ verbose=0
+ )
+ else:
+ st.warning("鏁版嵁閲忎笉瓒筹紝鏃犳硶璁粌娣卞害瀛︿範妯″瀷")
+ # 浣跨敤闅忔満妫灄浣滀负澶囬�夋ā鍨�
+ model = RandomForestRegressor(n_estimators=100, random_state=42)
+ model.fit(X_train, y_train)
+ y_pred = model.predict(X_test)
+ # 纭繚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]
+ else:
+ y_test_trimmed = y_test
+ y_pred_trimmed = y_pred
+
+ # 棰勬祴
+ if 'X_test_seq_trimmed' in locals():
+ y_pred_scaled = deep_model.predict(X_test_seq_trimmed).ravel()
+ else:
+ y_pred_scaled = deep_model.predict(X_test_seq).ravel()
+ y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
+
+ # 淇濆瓨妯″瀷
+ model = deep_model
+
+ # 璁$畻璇勪及鎸囨爣
+ # 纭繚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("馃敭 绫抽噸棰勬祴")
+
+ # 鍒涘缓棰勬祴琛ㄥ崟锛屼娇鐢╢orm鍖呰浠ラ槻姝㈣緭鍏ユ椂瑙﹀彂閲嶆柊鍒嗘瀽
+ with st.form(key="prediction_form"):
+ st.write("杈撳叆鐗瑰緛鍊艰繘琛岀背閲嶉娴�:")
+ predict_cols = st.columns(2)
+ input_features = {}
+
+ for i, feature in enumerate(default_features):
+ with predict_cols[i % 2]:
+ # 鑾峰彇鐗瑰緛鐨勭粺璁′俊鎭�
+ min_val = df_analysis_clean[feature].min()
+ max_val = df_analysis_clean[feature].max()
+ mean_val = df_analysis_clean[feature].mean()
+
+ input_features[feature] = st.number_input(
+ f"{feature}",
+ key=f"ma_pred_{feature}",
+ value=float(mean_val),
+ min_value=float(min_val),
+ max_value=float(max_val),
+ step=0.1
+ )
+
+ # 棰勬祴鎸夐挳
+ predict_button = st.form_submit_button("棰勬祴绫抽噸")
+
+ if predict_button:
+ # 鍑嗗棰勬祴鏁版嵁
+ input_df = pd.DataFrame([input_features])
+
+ # 娣诲姞鏃堕棿鐗瑰緛锛堜娇鐢ㄥ綋鍓嶆椂闂达級
+ current_time = datetime.now()
+ time_features_input = pd.DataFrame({
+ 'hour': [current_time.hour],
+ 'minute': [current_time.minute],
+ 'second': [current_time.second],
+ 'time_of_day': [current_time.hour * 3600 + current_time.minute * 60 + current_time.second]
+ })
+
+ # 娣诲姞婊炲悗鐗瑰緛锛堜娇鐢ㄨ緭鍏ュ�间綔涓烘浛浠o級
+ for feature in default_features:
+ for lag in [1, 2, 3]:
+ time_features_input[f'{feature}_lag{lag}'] = input_features[feature]
+ time_features_input[f'{feature}_diff{lag}'] = 0.0
+
+ # 娣诲姞婊氬姩缁熻鐗瑰緛锛堜娇鐢ㄨ緭鍏ュ�间綔涓烘浛浠o級
+ for feature in default_features:
+ time_features_input[f'{feature}_rolling_mean'] = input_features[feature]
+ time_features_input[f'{feature}_rolling_std'] = 0.0
+ time_features_input[f'{feature}_rolling_min'] = input_features[feature]
+ time_features_input[f'{feature}_rolling_max'] = input_features[feature]
+
+ # 鍚堝苟鐗瑰緛
+ input_combined = pd.concat([input_df, time_features_input], axis=1)
+
+ # 棰勬祴
+ if model_type in ['SVR', 'MLP']:
+ input_scaled = scaler_X.transform(input_combined)
+ prediction_scaled = model.predict(input_scaled)
+ predicted_weight = scaler_y.inverse_transform(prediction_scaled.reshape(-1, 1)).ravel()[0]
+ elif use_deep_learning and model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ # 涓烘繁搴﹀涔犳ā鍨嬪垱寤哄簭鍒�
+ input_scaled = scaler_X.transform(input_combined)
+ # 閲嶅杈撳叆浠ュ垱寤哄簭鍒�
+ sequence_length = st.session_state['ma_sequence_length']
+ input_seq = np.tile(input_scaled, (sequence_length, 1)).reshape(1, sequence_length, -1)
+ prediction_scaled = model.predict(input_seq).ravel()[0]
+ predicted_weight = scaler_y.inverse_transform(prediction_scaled.reshape(-1, 1)).ravel()[0]
+ else:
+ predicted_weight = model.predict(input_combined)[0]
+
+ # 鏄剧ず棰勬祴缁撴灉
+ st.success(f"棰勬祴绫抽噸: {predicted_weight:.4f} Kg/m")
+
+ # --- 鏁版嵁棰勮 ---
+ 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("璇烽�夋嫨鏃堕棿鑼冨洿骞剁偣鍑�'寮�濮嬪垎鏋�'鎸夐挳鑾峰彇鏁版嵁銆�")
diff --git a/app/pages/metered_weight_advanced pytorch.py b/app/pages/metered_weight_advanced pytorch.py
new file mode 100644
index 0000000..2f35692
--- /dev/null
+++ b/app/pages/metered_weight_advanced pytorch.py
@@ -0,0 +1,873 @@
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import pandas as pd
+import numpy as np
+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
+
+# 灏濊瘯瀵煎叆娣卞害瀛︿範搴�
+use_deep_learning = False
+try:
+
+ import torch
+ import torch.nn as nn
+ import torch.optim as optim
+ use_deep_learning = True
+ # 妫�娴婫PU鏄惁鍙敤
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+ st.success(f"浣跨敤璁惧: {device}")
+except ImportError:
+ st.warning("鏈娴嬪埌PyTorch锛屾繁搴﹀涔犳ā鍨嬪皢涓嶅彲鐢ㄣ�傝瀹夎pytorch浠ヤ娇鐢↙STM/GRU妯″瀷銆�")
+
+
+# PyTorch娣卞害瀛︿範妯″瀷瀹氫箟
+class LSTMModel(nn.Module):
+ def __init__(self, input_dim, hidden_dim=64, num_layers=2):
+ super(LSTMModel, self).__init__()
+ self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True)
+ self.fc1 = nn.Linear(hidden_dim, 32)
+ self.dropout = nn.Dropout(0.2)
+ self.fc2 = nn.Linear(32, 1)
+
+ def forward(self, x):
+ out, _ = self.lstm(x)
+ out = out[:, -1, :]
+ out = torch.relu(self.fc1(out))
+ out = self.dropout(out)
+ out = self.fc2(out)
+ return out
+
+class GRUModel(nn.Module):
+ def __init__(self, input_dim, hidden_dim=64, num_layers=2):
+ super(GRUModel, self).__init__()
+ self.gru = nn.GRU(input_dim, hidden_dim, num_layers, batch_first=True)
+ self.fc1 = nn.Linear(hidden_dim, 32)
+ self.dropout = nn.Dropout(0.2)
+ self.fc2 = nn.Linear(32, 1)
+
+ def forward(self, x):
+ out, _ = self.gru(x)
+ out = out[:, -1, :]
+ out = torch.relu(self.fc1(out))
+ out = self.dropout(out)
+ out = self.fc2(out)
+ return out
+
+class BiLSTMModel(nn.Module):
+ def __init__(self, input_dim, hidden_dim=64, num_layers=2):
+ super(BiLSTMModel, self).__init__()
+ self.bilstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True, bidirectional=True)
+ self.fc1 = nn.Linear(hidden_dim * 2, 32)
+ self.dropout = nn.Dropout(0.2)
+ self.fc2 = nn.Linear(32, 1)
+
+ def forward(self, x):
+ out, _ = self.bilstm(x)
+ out = out[:, -1, :]
+ out = torch.relu(self.fc1(out))
+ out = self.dropout(out)
+ out = self.fc2(out)
+ return out
+
+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
+
+ # 榛樿鐗瑰緛鍒楄〃锛堜笉鍐嶅厑璁哥敤鎴烽�夋嫨锛�
+ 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']
+ if use_deep_learning:
+ model_options.extend(['LSTM', 'GRU', 'BiLSTM'])
+
+ model_type = st.selectbox(
+ "妯″瀷绫诲瀷",
+ options=model_options,
+ key="ma_model_type",
+ help="閫夋嫨鐢ㄤ簬棰勬祴鐨勬ā鍨嬬被鍨�"
+ )
+
+ with model_cols[1]:
+ # 搴忓垪闀垮害锛堜粎閫傜敤浜庢繁搴﹀涔犳ā鍨嬶級
+ if model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ sequence_length = st.slider(
+ "搴忓垪闀垮害",
+ min_value=5,
+ max_value=30,
+ value=st.session_state['ma_sequence_length'],
+ step=1,
+ help="鐢ㄤ簬娣卞害瀛︿範妯″瀷鐨勬椂闂村簭鍒楅暱搴�",
+ key="ma_sequence_length"
+ )
+ else:
+ st.session_state['ma_sequence_length'] = 10
+ st.write("搴忓垪闀垮害: 10 (榛樿锛屼粎閫傜敤浜庢繁搴﹀涔犳ā鍨�)")
+
+ # 杞崲涓篸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)
+
+ # --- 鍘熷鏁版嵁瓒嬪娍鍥� ---
+ 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:
+ # 鍑嗗鏁版嵁
+ X = df_analysis[default_features]
+ y = df_analysis['绫抽噸']
+
+ # 娓呯悊鏁版嵁涓殑NaN鍊�
+ combined = pd.concat([X, y], axis=1)
+ combined_clean = combined.dropna()
+
+ # 妫�鏌ユ竻鐞嗗悗鐨勬暟鎹噺
+ if len(combined_clean) < 30:
+ st.warning("鏁版嵁閲忎笉瓒筹紝鏃犳硶杩涜鏈夋晥鐨勯娴嬪垎鏋�")
+ else:
+ # 閲嶆柊鍒嗙X鍜寉
+ X_clean = combined_clean[default_features]
+ y_clean = combined_clean['绫抽噸']
+
+ # 鐗瑰緛宸ョ▼锛氭坊鍔犳椂闂寸浉鍏崇壒寰�
+ # 纭繚浣跨敤鏃堕棿鍒椾綔涓虹储寮�
+ if 'time' in combined_clean.columns:
+ # 灏唗ime鍒楄缃负绱㈠紩
+ combined_clean = combined_clean.set_index('time')
+
+ # 鍒涘缓鏃堕棿鐗瑰緛
+ time_features = pd.DataFrame(index=combined_clean.index)
+ time_features['hour'] = combined_clean.index.hour
+ time_features['minute'] = combined_clean.index.minute
+ time_features['second'] = combined_clean.index.second
+ time_features['time_of_day'] = time_features['hour'] * 3600 + time_features['minute'] * 60 + time_features['second']
+ else:
+ # 濡傛灉娌℃湁time鍒楋紝鍒涘缓绌虹殑鏃堕棿鐗瑰緛
+ time_features = pd.DataFrame(index=combined_clean.index)
+ time_features['hour'] = 0
+ time_features['minute'] = 0
+ time_features['second'] = 0
+ time_features['time_of_day'] = 0
+
+ # 娣诲姞婊炲悗鐗瑰緛
+ for feature in default_features:
+ for lag in [1, 2, 3]:
+ time_features[f'{feature}_lag{lag}'] = X_clean[feature].shift(lag)
+ time_features[f'{feature}_diff{lag}'] = X_clean[feature].diff(lag)
+
+ # 娣诲姞婊氬姩缁熻鐗瑰緛
+ for feature in default_features:
+ time_features[f'{feature}_rolling_mean'] = X_clean[feature].rolling(window=5).mean()
+ time_features[f'{feature}_rolling_std'] = X_clean[feature].rolling(window=5).std()
+ time_features[f'{feature}_rolling_min'] = X_clean[feature].rolling(window=5).min()
+ time_features[f'{feature}_rolling_max'] = X_clean[feature].rolling(window=5).max()
+
+ # 娓呯悊婊炲悗鐗瑰緛鍜屾粴鍔ㄧ粺璁$壒寰佷骇鐢熺殑NaN鍊�
+ time_features.dropna(inplace=True)
+
+ # 瀵归綈X鍜寉
+ common_index = time_features.index.intersection(y_clean.index)
+ X_final = pd.concat([X_clean.loc[common_index], time_features.loc[common_index]], axis=1)
+ y_final = y_clean.loc[common_index]
+
+ # 妫�鏌ユ渶缁堟暟鎹噺
+ if len(X_final) < 20:
+ st.warning("鐗瑰緛宸ョ▼鍚庢暟鎹噺涓嶈冻锛屾棤娉曡繘琛屾湁鏁堢殑棰勬祴鍒嗘瀽")
+ else:
+ # 鍒嗗壊璁粌闆嗗拰娴嬭瘯闆�
+ 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
+
+ try:
+ 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()
+
+ elif use_deep_learning and model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ # 鍑嗗鏃堕棿搴忓垪鏁版嵁
+ sequence_length = st.session_state['ma_sequence_length']
+
+ def create_sequences(X, y, seq_length):
+ X_seq = []
+ y_seq = []
+ # 纭繚X鍜寉鐨勯暱搴︿竴鑷�
+ min_len = min(len(X), len(y))
+ # 纭繚X鍜寉鐨勯暱搴﹁嚦灏戜负seq_length + 1
+ if min_len <= seq_length:
+ return np.array([]), np.array([])
+ # 鎴柇X鍜寉鍒扮浉鍚岄暱搴�
+ X_trimmed = X[:min_len]
+ y_trimmed = y[:min_len]
+ # 鍒涘缓搴忓垪
+ for i in range(len(X_trimmed) - seq_length):
+ X_seq.append(X_trimmed[i:i+seq_length])
+ y_seq.append(y_trimmed[i+seq_length])
+ return np.array(X_seq), np.array(y_seq)
+
+ # 涓烘繁搴﹀涔犳ā鍨嬪垱寤哄簭鍒�
+ X_train_seq, y_train_seq = create_sequences(X_train_scaled, y_train_scaled, sequence_length)
+ X_test_seq, y_test_seq = create_sequences(X_test_scaled, y_test_scaled, sequence_length)
+
+ # 妫�鏌ュ垱寤虹殑搴忓垪鏄惁涓虹┖
+ if len(X_train_seq) == 0 or len(y_train_seq) == 0:
+ st.warning(f"鏁版嵁閲忎笉瓒筹紝鏃犳硶鍒涘缓鏈夋晥鐨凩STM搴忓垪銆傞渶瑕佽嚦灏� {sequence_length + 1} 涓牱鏈紝褰撳墠鍙湁 {min(len(X_train_scaled), len(y_train_scaled))} 涓牱鏈��")
+ # 浣跨敤闅忔満妫灄浣滀负澶囬�夋ā鍨�
+ model = RandomForestRegressor(n_estimators=100, random_state=42)
+ model.fit(X_train, y_train)
+ y_pred = model.predict(X_test)
+ else:
+ # 杞崲涓篜yTorch寮犻噺骞剁Щ鍔ㄥ埌璁惧
+ X_train_tensor = torch.tensor(X_train_seq, dtype=torch.float32).to(device)
+ y_train_tensor = torch.tensor(y_train_seq, dtype=torch.float32).unsqueeze(1).to(device)
+ X_test_tensor = torch.tensor(X_test_seq, dtype=torch.float32).to(device)
+ y_test_tensor = torch.tensor(y_test_seq, dtype=torch.float32).unsqueeze(1).to(device)
+
+ # 鏋勫缓PyTorch妯″瀷骞剁Щ鍔ㄥ埌璁惧
+ input_dim = X_train_scaled.shape[1]
+
+ if model_type == 'LSTM':
+ deep_model = LSTMModel(input_dim).to(device)
+ elif model_type == 'GRU':
+ deep_model = GRUModel(input_dim).to(device)
+ elif model_type == 'BiLSTM':
+ deep_model = BiLSTMModel(input_dim).to(device)
+
+ # 瀹氫箟鎹熷け鍑芥暟鍜屼紭鍖栧櫒
+ criterion = nn.MSELoss()
+ optimizer = optim.Adam(deep_model.parameters(), lr=0.001)
+
+ # 鏄剧ず浣跨敤鐨勮澶�
+ st.info(f"浣跨敤璁惧: {device}")
+
+ # 璁粌妯″瀷
+ num_epochs = 50
+ batch_size = 32
+
+ for epoch in range(num_epochs):
+ deep_model.train()
+ optimizer.zero_grad()
+
+ # 鍓嶅悜浼犳挱
+ outputs = deep_model(X_train_tensor)
+ loss = criterion(outputs, y_train_tensor)
+
+ # 鍙嶅悜浼犳挱鍜屼紭鍖�
+ loss.backward()
+ optimizer.step()
+
+ # 棰勬祴
+ deep_model.eval()
+ with torch.no_grad():
+ y_pred_scaled_tensor = deep_model(X_test_tensor)
+ y_pred_scaled = y_pred_scaled_tensor.numpy().ravel()
+ y_pred = scaler_y.inverse_transform(y_pred_scaled.reshape(-1, 1)).ravel()
+
+ # 灏唝_test_seq杞崲鍥炲師濮嬪昂搴�
+ y_test_actual = scaler_y.inverse_transform(y_test_seq.reshape(-1, 1)).ravel()
+
+ # 淇濆瓨妯″瀷
+ model = deep_model
+
+ # 璁$畻璇勪及鎸囨爣
+ if model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ # 浣跨敤杞崲鍚庣殑y_test_seq浣滀负鐪熷疄鍊�
+ r2 = r2_score(y_test_actual, y_pred)
+ mse = mean_squared_error(y_test_actual, y_pred)
+ mae = mean_absolute_error(y_test_actual, y_pred)
+ rmse = np.sqrt(mse)
+ else:
+ # 浣跨敤鍘熷鐨剏_test浣滀负鐪熷疄鍊�
+ 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("馃攧 瀹為檯鍊间笌棰勬祴鍊煎姣�")
+
+ # 鍒涘缓瀵规瘮鏁版嵁
+ if model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ # 浣跨敤杞崲鍚庣殑y_test_actual
+ compare_df = pd.DataFrame({
+ '瀹為檯鍊�': y_test_actual,
+ '棰勬祴鍊�': y_pred
+ })
+ else:
+ # 浣跨敤鍘熷鐨剏_test
+ 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=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("馃搲 娈嬪樊鍒嗘瀽")
+
+ # 璁$畻娈嬪樊
+ if model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ # 浣跨敤杞崲鍚庣殑y_test_actual
+ residuals = y_test_actual - y_pred
+ else:
+ # 浣跨敤鍘熷鐨剏_test
+ 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')
+
+ # --- 鐗瑰緛閲嶈鎬э紙濡傛灉妯″瀷鏀寔锛� ---
+ 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("馃敭 绫抽噸棰勬祴")
+
+ # 鍒涘缓棰勬祴琛ㄥ崟
+ st.write("杈撳叆鐗瑰緛鍊艰繘琛岀背閲嶉娴�:")
+ predict_cols = st.columns(2)
+ input_features = {}
+
+ for i, feature in enumerate(default_features):
+ with predict_cols[i % 2]:
+ # 鑾峰彇鐗瑰緛鐨勭粺璁′俊鎭�
+ min_val = X_clean[feature].min()
+ max_val = X_clean[feature].max()
+ mean_val = X_clean[feature].mean()
+
+ input_features[feature] = st.number_input(
+ f"{feature}",
+ key=f"ma_pred_{feature}",
+ value=float(mean_val),
+ min_value=float(min_val),
+ max_value=float(max_val),
+ step=0.1
+ )
+
+ if st.button("棰勬祴绫抽噸"):
+ # 鍑嗗棰勬祴鏁版嵁
+ input_df = pd.DataFrame([input_features])
+
+ # 娣诲姞鏃堕棿鐗瑰緛锛堜娇鐢ㄥ綋鍓嶆椂闂达級
+ current_time = datetime.now()
+ time_features_input = pd.DataFrame({
+ 'hour': [current_time.hour],
+ 'minute': [current_time.minute],
+ 'second': [current_time.second],
+ 'time_of_day': [current_time.hour * 3600 + current_time.minute * 60 + current_time.second]
+ })
+
+ # 娣诲姞婊炲悗鐗瑰緛锛堜娇鐢ㄨ緭鍏ュ�间綔涓烘浛浠o級
+ for feature in default_features:
+ for lag in [1, 2, 3]:
+ time_features_input[f'{feature}_lag{lag}'] = input_features[feature]
+ time_features_input[f'{feature}_diff{lag}'] = 0.0
+
+ # 娣诲姞婊氬姩缁熻鐗瑰緛锛堜娇鐢ㄨ緭鍏ュ�间綔涓烘浛浠o級
+ for feature in default_features:
+ time_features_input[f'{feature}_rolling_mean'] = input_features[feature]
+ time_features_input[f'{feature}_rolling_std'] = 0.0
+ time_features_input[f'{feature}_rolling_min'] = input_features[feature]
+ time_features_input[f'{feature}_rolling_max'] = input_features[feature]
+
+ # 鍚堝苟鐗瑰緛
+ input_combined = pd.concat([input_df, time_features_input], axis=1)
+
+ # 棰勬祴
+ if model_type in ['SVR', 'MLP']:
+ input_scaled = scaler_X.transform(input_combined)
+ prediction_scaled = model.predict(input_scaled)
+ predicted_weight = scaler_y.inverse_transform(prediction_scaled.reshape(-1, 1)).ravel()[0]
+ elif use_deep_learning and model_type in ['LSTM', 'GRU', 'BiLSTM']:
+ # 涓烘繁搴﹀涔犳ā鍨嬪垱寤哄簭鍒�
+ input_scaled = scaler_X.transform(input_combined)
+ # 閲嶅杈撳叆浠ュ垱寤哄簭鍒�
+ input_seq = np.tile(input_scaled, (sequence_length, 1)).reshape(1, sequence_length, -1)
+ # 杞崲涓篜yTorch寮犻噺骞剁Щ鍔ㄥ埌璁惧
+ input_tensor = torch.tensor(input_seq, dtype=torch.float32).to(device)
+ # 棰勬祴
+ model.eval()
+ with torch.no_grad():
+ prediction_scaled_tensor = model(input_tensor)
+ prediction_scaled = prediction_scaled_tensor.cpu().numpy().ravel()[0]
+ predicted_weight = scaler_y.inverse_transform(prediction_scaled.reshape(-1, 1)).ravel()[0]
+ else:
+ predicted_weight = model.predict(input_combined)[0]
+
+ # 鏄剧ず棰勬祴缁撴灉
+ st.success(f"棰勬祴绫抽噸: {predicted_weight:.4f} Kg/m")
+
+ # --- 鏁版嵁棰勮 ---
+ 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("璇烽�夋嫨鏃堕棿鑼冨洿骞剁偣鍑�'寮�濮嬪垎鏋�'鎸夐挳鑾峰彇鏁版嵁銆�")
diff --git a/app/pages/metered_weight_advanced.py b/app/pages/metered_weight_advanced.py
new file mode 100644
index 0000000..d3a4e40
--- /dev/null
+++ b/app/pages/metered_weight_advanced.py
@@ -0,0 +1,635 @@
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import pandas as pd
+import numpy as np
+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
+
+
+
+
+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
+
+ # 榛樿鐗瑰緛鍒楄〃锛堜笉鍐嶅厑璁哥敤鎴烽�夋嫨锛�
+ 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="閫夋嫨鐢ㄤ簬棰勬祴鐨勬ā鍨嬬被鍨�"
+ )
+
+
+
+ # 杞崲涓篸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)
+
+ # --- 鍘熷鏁版嵁瓒嬪娍鍥� ---
+ 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:
+ # 鍑嗗鏁版嵁
+ # 棣栧厛纭繚df_analysis涓病鏈塏aN鍊�
+ df_analysis_clean = df_analysis.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("馃敭 绫抽噸棰勬祴")
+
+ # 鍒涘缓棰勬祴琛ㄥ崟
+ st.write("杈撳叆鐗瑰緛鍊艰繘琛岀背閲嶉娴�:")
+ predict_cols = st.columns(2)
+ input_features = {}
+
+ for i, feature in enumerate(default_features):
+ with predict_cols[i % 2]:
+ # 鑾峰彇鐗瑰緛鐨勭粺璁′俊鎭�
+ min_val = df_analysis_clean[feature].min()
+ max_val = df_analysis_clean[feature].max()
+ mean_val = df_analysis_clean[feature].mean()
+
+ input_features[feature] = st.number_input(
+ f"{feature}",
+ key=f"ma_pred_{feature}",
+ value=float(mean_val),
+ min_value=float(min_val),
+ max_value=float(max_val),
+ step=0.1
+ )
+
+ if st.button("棰勬祴绫抽噸"):
+ # 鍑嗗棰勬祴鏁版嵁
+ input_df = pd.DataFrame([input_features])
+
+ # 鍚堝苟鐗瑰緛
+ input_combined = pd.concat([input_df], axis=1)
+
+ # 棰勬祴
+ if model_type in ['SVR', 'MLP']:
+ input_scaled = scaler_X.transform(input_combined)
+ prediction_scaled = model.predict(input_scaled)
+ predicted_weight = scaler_y.inverse_transform(prediction_scaled.reshape(-1, 1)).ravel()[0]
+
+ else:
+ predicted_weight = model.predict(input_combined)[0]
+
+ # 鏄剧ず棰勬祴缁撴灉
+ st.success(f"棰勬祴绫抽噸: {predicted_weight:.4f} Kg/m")
+
+ # --- 鏁版嵁棰勮 ---
+ 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("璇烽�夋嫨鏃堕棿鑼冨洿骞剁偣鍑�'寮�濮嬪垎鏋�'鎸夐挳鑾峰彇鏁版嵁銆�")
diff --git a/app/pages/metered_weight_correlation.py b/app/pages/metered_weight_correlation.py
new file mode 100644
index 0000000..0afb421
--- /dev/null
+++ b/app/pages/metered_weight_correlation.py
@@ -0,0 +1,777 @@
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import pandas as pd
+import numpy as np
+from datetime import datetime, timedelta
+from app.services.extruder_service import ExtruderService
+from app.services.main_process_service import MainProcessService
+
+def show_metered_weight_correlation():
+ # 鍒濆鍖栨湇鍔�
+ extruder_service = ExtruderService()
+ main_process_service = MainProcessService()
+
+ # 椤甸潰鏍囬
+ st.title("绫抽噸鐩稿叧鎬у垎鏋�")
+
+ # 鍒濆鍖栦細璇濈姸鎬佺敤浜庢棩鏈熷悓姝�
+ if 'mc_start_date' not in st.session_state:
+ st.session_state['mc_start_date'] = datetime.now().date() - timedelta(days=7)
+ if 'mc_end_date' not in st.session_state:
+ st.session_state['mc_end_date'] = datetime.now().date()
+ if 'mc_quick_select' not in st.session_state:
+ st.session_state['mc_quick_select'] = "鏈�杩�7澶�"
+ if 'mc_time_offset' not in st.session_state:
+ st.session_state['mc_time_offset'] = 0.0
+
+ # 瀹氫箟鍥炶皟鍑芥暟
+ def update_dates(qs):
+ st.session_state['mc_quick_select'] = qs
+ today = datetime.now().date()
+ if qs == "浠婂ぉ":
+ st.session_state['mc_start_date'] = today
+ st.session_state['mc_end_date'] = today
+ elif qs == "鏈�杩�3澶�":
+ st.session_state['mc_start_date'] = today - timedelta(days=3)
+ st.session_state['mc_end_date'] = today
+ elif qs == "鏈�杩�7澶�":
+ st.session_state['mc_start_date'] = today - timedelta(days=7)
+ st.session_state['mc_end_date'] = today
+ elif qs == "鏈�杩�30澶�":
+ st.session_state['mc_start_date'] = today - timedelta(days=30)
+ st.session_state['mc_end_date'] = today
+
+ def on_date_change():
+ st.session_state['mc_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['mc_quick_select'] == option else "secondary"
+ if st.button(option, key=f"btn_mc_{option}", width='stretch', type=button_type):
+ update_dates(option)
+ st.rerun()
+
+ with cols[5]:
+ start_date = st.date_input(
+ "寮�濮嬫棩鏈�",
+ label_visibility="collapsed",
+ key="mc_start_date",
+ on_change=on_date_change
+ )
+
+ with cols[6]:
+ end_date = st.date_input(
+ "缁撴潫鏃ユ湡",
+ label_visibility="collapsed",
+ key="mc_end_date",
+ on_change=on_date_change
+ )
+
+ with cols[7]:
+ query_button = st.button("馃殌 寮�濮嬪垎鏋�", key="mc_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['mc_time_offset'],
+ step=0.1,
+ help="璋冩暣涓绘祦绋嬪拰娓╁害鏁版嵁鐨勬椂闂村亸绉伙紝浣垮叾涓庢尋鍑烘満绫抽噸鏁版嵁瀵归綈銆�"
+ )
+ st.session_state['mc_time_offset'] = time_offset
+ with offset_cols[2]:
+ st.write(f"褰撳墠鍋忕Щ: {time_offset} 鍒嗛挓")
+
+ # 杞崲涓篸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['mc_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()
+
+ # 浣跨敤鍋忕Щ鍚庣殑绫抽噸鏃堕棿鏁村悎铻烘潌杞�熸暟鎹�
+ # 鍏抽敭锛氫娇鐢╩erge_asof鏍规嵁鍋忕Щ鍚庣殑绫抽噸鏃堕棿鏌ユ壘鏈�鎺ヨ繎鐨勮灪鏉嗚浆閫熸暟鎹�
+ df_merged = pd.merge_asof(
+ df_weight.sort_values('time'),
+ df_screw.sort_values('time'),
+ on='time',
+ direction='nearest',
+ tolerance=pd.Timedelta('1min')
+ )
+
+ # 浣跨敤鍋忕Щ鍚庣殑绫抽噸鏃堕棿鏁村悎鏈哄ご鍘嬪姏鏁版嵁
+ # 鍏抽敭锛氫娇鐢╩erge_asof鏍规嵁鍋忕Щ鍚庣殑绫抽噸鏃堕棿鏌ユ壘鏈�鎺ヨ繎鐨勬満澶村帇鍔涙暟鎹�
+ 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)
+
+ # --- 鍘熷鏁版嵁瓒嬪娍鍥� ---
+ 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.add_trace(go.Scatter(
+ x=df_temp['time'],
+ y=df_temp['nakata_extruder_rear_barrel_display_temp'],
+ name='鍚庢満绛掓俯搴� (掳C)',
+ mode='lines',
+ line=dict(color='pink', width=1),
+ yaxis='y5'
+ ))
+ # 鍓嶆満绛掓俯搴�
+ fig_trend.add_trace(go.Scatter(
+ x=df_temp['time'],
+ y=df_temp['nakata_extruder_front_barrel_display_temp'],
+ name='鍓嶆満绛掓俯搴� (掳C)',
+ mode='lines',
+ line=dict(color='brown', width=1),
+ yaxis='y5'
+ ))
+ # 鏈哄ご娓╁害
+ fig_trend.add_trace(go.Scatter(
+ x=df_temp['time'],
+ y=df_temp['nakata_extruder_head_display_temp'],
+ name='鏈哄ご娓╁害 (掳C)',
+ mode='lines',
+ line=dict(color='gray', width=1),
+ yaxis='y5'
+ ))
+
+ # 閰嶇疆瓒嬪娍鍥惧竷灞�
+ fig_trend.update_layout(
+ title=f'鍘熷鏁版嵁瓒嬪娍 (绫抽噸鍚戝墠鍋忕Щ {st.session_state["mc_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',
+ dragmode='select',
+ )
+
+ # 鏄剧ず瓒嬪娍鍥�
+ selection = st.plotly_chart(fig_trend, width='stretch', config={'scrollZoom': True}, on_select='rerun' )
+
+ # 璋冭瘯杈撳嚭
+ # st.write("鍘熷 selection 瀵硅薄:", selection)
+
+ # 瀹氫箟鍒嗘瀽鍒�
+ analysis_cols = ['绫抽噸', '铻烘潌杞��', '鏈哄ご鍘嬪姏', '娴佺▼涓婚��', '铻烘潌娓╁害', '鍚庢満绛掓俯搴�', '鍓嶆満绛掓俯搴�', '鏈哄ご娓╁害']
+
+ # 瀹氫箟瑕佸垎鏋愮殑鍙傛暟
+ params = [
+ ('铻烘潌杞��', 'RPM'),
+ ('鏈哄ご鍘嬪姏', ''),
+ ('娴佺▼涓婚��', 'M/Min'),
+ ('铻烘潌娓╁害', '掳C'),
+ ('鍚庢満绛掓俯搴�', '掳C'),
+ ('鍓嶆満绛掓俯搴�', '掳C'),
+ ('鏈哄ご娓╁害', '掳C')
+ ]
+
+ # 姝g‘鎻愬彇
+ selected_data = None
+ if selection.selection and selection.selection.box:
+ boxs = selection.selection.box
+ # 鑾峰彇閫変腑妗嗙殑x杞磋寖鍥�
+ x_range = boxs[0]['x'][0], boxs[0]['x'][1]
+ st.write("x杞磋寖鍥�:", x_range)
+
+ # 杩囨护鍑哄湪x杞磋寖鍥村唴鐨勬暟鎹�
+ # 娉ㄦ剰锛氳繖閲岄渶瑕佷娇鐢╠f_analysis鐨則ime鍒楄繘琛岃繃婊�
+ # 棣栧厛闇�瑕佺‘淇漝f_analysis鏈塼ime鍒�
+ if 'time' in df_analysis.columns:
+ selected_data = df_analysis[
+ (df_analysis['time'] >= x_range[0]) &
+ (df_analysis['time'] <= x_range[1])
+ ].copy() # 浣跨敤copy()閬垮厤鍒囩墖璀﹀憡
+ st.write(f"閫変腑鑼冨洿鍐呯殑鏁版嵁鐐规暟閲�: {len(selected_data)}")
+ # 鏄剧ず鍙敤鐨勫垪鍚嶏紝甯姪璋冭瘯
+ st.write("鍙敤鍒楀悕:", list(selected_data.columns))
+ else:
+ st.warning("鏁版嵁涓己灏憈ime鍒楋紝鏃犳硶杩涜鑼冨洿杩囨护")
+
+ else:
+ st.info("璇蜂娇鐢ㄧ煩褰㈡閫夊伐鍏烽�夋嫨鏃堕棿鑼冨洿锛堝凡鑷姩鍚敤閫夋嫨妯″紡锛�")
+
+ # 娣诲姞缁嗚妭鍒嗘瀽鎸夐挳
+ if selected_data is not None and not selected_data.empty:
+ if st.button("馃攳 缁嗚妭鍒嗘瀽"):
+ st.subheader("馃搳 妗嗛�夎寖鍥寸粏鑺傚垎鏋�")
+
+ # 璁$畻閫変腑鑼冨洿鍐呯殑鐩稿叧绯绘暟鐭╅樀
+ selected_corr_matrix = selected_data[analysis_cols].corr()
+
+ # 鍒涘缓閫変腑鑼冨洿鐨勭儹鍔涘浘
+ selected_fig_heatmap = px.imshow(
+ selected_corr_matrix,
+ text_auto=True,
+ aspect="auto",
+ title="妗嗛�夎寖鍥村弬鏁扮浉鍏虫�х煩闃�",
+ color_continuous_scale=["#0000FF", "#FFFFFF", "#FF0000"],
+ color_continuous_midpoint=0,
+ labels=dict(color="鐩稿叧绯绘暟")
+ )
+
+ # 鑷畾涔夊竷灞�
+ selected_fig_heatmap.update_layout(
+ height=400,
+ margin=dict(l=80, r=80, t=80, b=80),
+ xaxis=dict(tickangle=-45),
+ yaxis=dict(tickangle=0)
+ )
+
+ # 鏄剧ず閫変腑鑼冨洿鐨勭儹鍔涘浘
+ st.plotly_chart(selected_fig_heatmap, width='stretch')
+
+ # 鏄剧ず閫変腑鑼冨洿鐨勫弬鏁颁笌绫抽噸鏁g偣鍥�
+ st.subheader("馃搱 妗嗛�夎寖鍥村弬鏁颁笌绫抽噸鏁g偣鍥�")
+
+ # 鍒涘缓閫変腑鑼冨洿鐨勬暎鐐瑰浘
+ for i in range(0, len(params), 2):
+ row_cols = st.columns(2)
+ for j in range(2):
+ if i + j < len(params):
+ param_name, unit = params[i + j]
+ with row_cols[j]:
+ if param_name in selected_data.columns:
+ # 璁$畻鐩稿叧绯绘暟锛堟坊鍔犻敊璇鐞嗭級
+ try:
+ # 杩囨护鎺塏aN鍊�
+ valid_data = selected_data[[param_name, '绫抽噸']].dropna()
+ if len(valid_data) >= 2: # 鑷冲皯闇�瑕�2涓暟鎹偣
+ corr_coef = np.corrcoef(valid_data['绫抽噸'], valid_data[param_name])[0, 1]
+ else:
+ corr_coef = None
+ except Exception as e:
+ corr_coef = None
+
+ # 鍒涘缓鏁g偣鍥�
+ fig_scatter = px.scatter(
+ selected_data,
+ x=param_name,
+ y='绫抽噸',
+ title=f"{param_name} vs 绫抽噸锛堟閫夎寖鍥达級",
+ labels={param_name: f"{param_name} ({unit})" if unit else param_name, '绫抽噸': '绫抽噸 (Kg/m)'}
+ )
+
+ # 娣诲姞瓒嬪娍绾匡紙娣诲姞閿欒澶勭悊锛�
+ try:
+ # 杩囨护鎺塏aN鍊�
+ valid_data = selected_data[[param_name, '绫抽噸']].dropna()
+ if len(valid_data) >= 2: # 鑷冲皯闇�瑕�2涓暟鎹偣
+ trend_line = np.poly1d(np.polyfit(valid_data[param_name], valid_data['绫抽噸'], 1))(valid_data[param_name])
+ fig_scatter.add_trace(go.Scatter(
+ x=valid_data[param_name],
+ y=trend_line,
+ mode='lines',
+ name='瓒嬪娍绾�',
+ line=dict(color='red', width=2)
+ ))
+ except Exception as e:
+ # 濡傛灉瓒嬪娍绾胯绠楀け璐ワ紝璺宠繃娣诲姞瓒嬪娍绾�
+ pass
+
+ # 娣诲姞鐩稿叧绯绘暟娉ㄩ噴锛堟坊鍔犻敊璇鐞嗭級
+ if corr_coef is not None:
+ fig_scatter.add_annotation(
+ x=0.05, y=0.95,
+ xref='paper', yref='paper',
+ text=f"鐩稿叧绯绘暟: {corr_coef:.4f}",
+ showarrow=False,
+ font=dict(size=12, color="black"),
+ bgcolor="white",
+ bordercolor="black",
+ borderwidth=1
+ )
+ else:
+ fig_scatter.add_annotation(
+ x=0.05, y=0.95,
+ xref='paper', yref='paper',
+ text="鐩稿叧绯绘暟: 鏃犳硶璁$畻",
+ showarrow=False,
+ font=dict(size=12, color="black"),
+ bgcolor="white",
+ bordercolor="black",
+ borderwidth=1
+ )
+
+ # 鏄剧ず鏁g偣鍥�
+ st.plotly_chart(fig_scatter, use_container_width=True)
+ else:
+ st.warning(f"鏁版嵁涓己灏� {param_name} 鍒�")
+
+ # 鏄剧ず閫変腑鑼冨洿鐨勬暟鎹憳瑕�
+ st.subheader("馃搳 妗嗛�夎寖鍥存暟鎹憳瑕�")
+ selected_summary_cols = st.columns(4)
+
+ with selected_summary_cols[0]:
+ if '绫抽噸' in selected_data.columns:
+ st.metric("骞冲潎绫抽噸", f"{selected_data['绫抽噸'].mean():.2f} Kg/m")
+
+ with selected_summary_cols[1]:
+ if '铻烘潌杞��' in selected_data.columns:
+ st.metric("骞冲潎铻烘潌杞��", f"{selected_data['铻烘潌杞��'].mean():.2f} RPM")
+
+ with selected_summary_cols[2]:
+ if '娴佺▼涓婚��' in selected_data.columns:
+ st.metric("骞冲潎娴佺▼涓婚��", f"{selected_data['娴佺▼涓婚��'].mean():.2f} M/Min")
+
+ with selected_summary_cols[3]:
+ if '鏈哄ご鍘嬪姏' in selected_data.columns:
+ st.metric("骞冲潎鏈哄ご鍘嬪姏", f"{selected_data['鏈哄ご鍘嬪姏'].mean():.2f}")
+
+ # 鏄剧ず閫変腑鑼冨洿鐨勬暟鎹瑙�
+ st.subheader("馃攳 妗嗛�夎寖鍥存暟鎹瑙�")
+ st.dataframe(selected_data[analysis_cols].head(10), use_container_width=True)
+
+ # --- 鐩稿叧鎬х煩闃电儹鍔涘浘 ---
+ st.subheader("馃搳 鐩稿叧鎬х煩闃电儹鍔涘浘")
+
+ # 閲嶅懡鍚嶇背閲嶅垪
+ df_analysis.rename(columns={'metered_weight': '绫抽噸'}, inplace=True)
+
+ # 璁$畻鐩稿叧绯绘暟鐭╅樀
+ corr_matrix = df_analysis[analysis_cols].corr()
+
+ # 鍒涘缓鐑姏鍥�
+ fig_heatmap = px.imshow(
+ corr_matrix,
+ text_auto=True,
+ aspect="auto",
+ title="鍙傛暟鐩稿叧鎬х煩闃�",
+ color_continuous_scale=["#0000FF", "#FFFFFF", "#FF0000"],
+ color_continuous_midpoint=0,
+ labels=dict(color="鐩稿叧绯绘暟")
+ )
+
+ # 鑷畾涔夊竷灞�
+ fig_heatmap.update_layout(
+ height=500,
+ margin=dict(l=100, r=100, t=100, b=100),
+ xaxis=dict(tickangle=-45),
+ yaxis=dict(tickangle=0)
+ )
+
+ # 鏄剧ず鐑姏鍥�
+ st.plotly_chart(fig_heatmap, width='stretch')
+
+ # --- 鍙傛暟涓庣背閲嶆暎鐐瑰浘 ---
+ st.subheader("馃搱 鍙傛暟涓庣背閲嶆暎鐐瑰浘")
+
+ # 鍒涘缓鏁g偣鍥�
+ for i in range(0, len(params), 2):
+ row_cols = st.columns(2)
+ for j in range(2):
+ if i + j < len(params):
+ param_name, unit = params[i + j]
+ with row_cols[j]:
+ if param_name in df_analysis.columns:
+ # 璁$畻鐩稿叧绯绘暟锛堟坊鍔犻敊璇鐞嗭級
+ try:
+ # 杩囨护鎺塏aN鍊�
+ valid_data = df_analysis[[param_name, '绫抽噸']].dropna()
+ if len(valid_data) >= 2: # 鑷冲皯闇�瑕�2涓暟鎹偣
+ corr_coef = np.corrcoef(valid_data['绫抽噸'], valid_data[param_name])[0, 1]
+ else:
+ corr_coef = None
+ except Exception as e:
+ corr_coef = None
+
+ # 鍒涘缓鏁g偣鍥�
+ fig_scatter = px.scatter(
+ df_analysis,
+ x=param_name,
+ y='绫抽噸',
+ title=f"{param_name} vs 绫抽噸",
+ labels={param_name: f"{param_name} ({unit})" if unit else param_name, '绫抽噸': '绫抽噸 (Kg/m)'}
+ )
+
+ # 娣诲姞瓒嬪娍绾匡紙娣诲姞閿欒澶勭悊锛�
+ try:
+ # 杩囨护鎺塏aN鍊�
+ valid_data = df_analysis[[param_name, '绫抽噸']].dropna()
+ if len(valid_data) >= 2: # 鑷冲皯闇�瑕�2涓暟鎹偣
+ trend_line = np.poly1d(np.polyfit(valid_data[param_name], valid_data['绫抽噸'], 1))(valid_data[param_name])
+ fig_scatter.add_trace(go.Scatter(
+ x=valid_data[param_name],
+ y=trend_line,
+ mode='lines',
+ name='瓒嬪娍绾�',
+ line=dict(color='red', width=2)
+ ))
+ except Exception as e:
+ # 濡傛灉瓒嬪娍绾胯绠楀け璐ワ紝璺宠繃娣诲姞瓒嬪娍绾�
+ pass
+
+ # 娣诲姞鐩稿叧绯绘暟娉ㄩ噴锛堟坊鍔犻敊璇鐞嗭級
+ if corr_coef is not None:
+ fig_scatter.add_annotation(
+ x=0.05, y=0.95,
+ xref='paper', yref='paper',
+ text=f"鐩稿叧绯绘暟: {corr_coef:.4f}",
+ showarrow=False,
+ font=dict(size=12, color="black"),
+ bgcolor="white",
+ bordercolor="black",
+ borderwidth=1
+ )
+ else:
+ fig_scatter.add_annotation(
+ x=0.05, y=0.95,
+ xref='paper', yref='paper',
+ text="鐩稿叧绯绘暟: 鏃犳硶璁$畻",
+ showarrow=False,
+ font=dict(size=12, color="black"),
+ bgcolor="white",
+ bordercolor="black",
+ borderwidth=1
+ )
+
+ # 鏄剧ず鏁g偣鍥�
+ st.plotly_chart(fig_scatter, use_container_width=True)
+ else:
+ st.warning(f"鏁版嵁涓己灏� {param_name} 鍒�")
+
+ # --- 鐩稿叧鎬х粺璁¤〃鏍� ---
+ st.subheader("馃搵 鐩稿叧鎬х粺璁�")
+
+ # 璁$畻姣忎釜鍙傛暟涓庣背閲嶇殑鐩稿叧绯绘暟锛堟坊鍔犻敊璇鐞嗭級
+ corr_stats = []
+ for param_name, _ in params:
+ if param_name in df_analysis.columns:
+ try:
+ # 杩囨护鎺塏aN鍊�
+ valid_data = df_analysis[[param_name, '绫抽噸']].dropna()
+ if len(valid_data) >= 2: # 鑷冲皯闇�瑕�2涓暟鎹偣
+ corr_coef = np.corrcoef(valid_data['绫抽噸'], valid_data[param_name])[0, 1]
+ corr_stats.append({
+ '鍙傛暟': param_name,
+ '鐩稿叧绯绘暟': corr_coef,
+ '鐩稿叧绋嬪害': '寮�' if abs(corr_coef) > 0.7 else '涓瓑' if abs(corr_coef) > 0.3 else '寮�'
+ })
+ else:
+ corr_stats.append({
+ '鍙傛暟': param_name,
+ '鐩稿叧绯绘暟': None,
+ '鐩稿叧绋嬪害': '鏃犳硶璁$畻'
+ })
+ except Exception as e:
+ corr_stats.append({
+ '鍙傛暟': param_name,
+ '鐩稿叧绯绘暟': None,
+ '鐩稿叧绋嬪害': '鏃犳硶璁$畻'
+ })
+
+ # 鍒涘缓缁熻琛ㄦ牸
+ corr_df = pd.DataFrame(corr_stats)
+ # 鎸夌浉鍏崇郴鏁扮粷瀵瑰�兼帓搴忥紙澶勭悊None鍊硷級
+ try:
+ # 璁$畻鐩稿叧绯绘暟缁濆鍊硷紝瀵逛簬None鍊间娇鐢�-1锛堣繖鏍蜂細鎺掑湪鏈�鍚庯級
+ corr_df['鐩稿叧绯绘暟缁濆鍊�'] = corr_df['鐩稿叧绯绘暟'].apply(lambda x: abs(x) if x is not None else -1)
+ corr_df.sort_values('鐩稿叧绯绘暟缁濆鍊�', ascending=False, inplace=True)
+ corr_df.drop('鐩稿叧绯绘暟缁濆鍊�', axis=1, inplace=True)
+ except Exception as e:
+ # 濡傛灉鎺掑簭澶辫触锛屼繚鎸佸師濮嬮『搴�
+ pass
+
+ # 鏄剧ず琛ㄦ牸
+ st.dataframe(corr_df, use_container_width=True)
+
+ # --- 鏁版嵁鎽樿 ---
+ # st.subheader("馃搳 鏁版嵁鎽樿")
+ # summary_cols = st.columns(4)
+
+ # with summary_cols[0]:
+ # if '绫抽噸' in df_analysis.columns:
+ # st.metric("骞冲潎绫抽噸", f"{df_analysis['绫抽噸'].mean():.2f} Kg/m")
+
+ # with summary_cols[1]:
+ # if '铻烘潌杞��' in df_analysis.columns:
+ # st.metric("骞冲潎铻烘潌杞��", f"{df_analysis['铻烘潌杞��'].mean():.2f} RPM")
+
+ # with summary_cols[2]:
+ # if '娴佺▼涓婚��' in df_analysis.columns:
+ # st.metric("骞冲潎娴佺▼涓婚��", f"{df_analysis['娴佺▼涓婚��'].mean():.2f} M/Min")
+
+ # with summary_cols[3]:
+ # if '鏈哄ご鍘嬪姏' in df_analysis.columns:
+ # st.metric("骞冲潎鏈哄ご鍘嬪姏", f"{df_analysis['鏈哄ご鍘嬪姏'].mean():.2f}")
+
+ # --- 鏁版嵁棰勮 ---
+ st.subheader("馃攳 鏁版嵁棰勮")
+ st.dataframe(df_analysis[analysis_cols].head(20), use_container_width=True)
+ else:
+ # 鎻愮ず鐢ㄦ埛鐐瑰嚮寮�濮嬪垎鏋愭寜閽�
+ st.info("璇烽�夋嫨鏃堕棿鑼冨洿骞剁偣鍑�'寮�濮嬪垎鏋�'鎸夐挳鑾峰彇鏁版嵁銆�")
diff --git a/app/pages/metered_weight_dashboard.py b/app/pages/metered_weight_dashboard.py
new file mode 100644
index 0000000..9824e29
--- /dev/null
+++ b/app/pages/metered_weight_dashboard.py
@@ -0,0 +1,430 @@
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import pandas as pd
+from datetime import datetime, timedelta
+from app.services.extruder_service import ExtruderService
+from app.services.main_process_service import MainProcessService
+
+def show_metered_weight_dashboard():
+ # 鍒濆鍖栨湇鍔�
+ extruder_service = ExtruderService()
+ main_process_service = MainProcessService()
+
+ # 椤甸潰鏍囬
+ st.title("绫抽噸缁煎悎鍒嗘瀽")
+
+ # 鍒濆鍖栦細璇濈姸鎬佺敤浜庢棩鏈熷悓姝�
+ if 'mw_start_date' not in st.session_state:
+ st.session_state['mw_start_date'] = datetime.now().date() - timedelta(days=7)
+ if 'mw_end_date' not in st.session_state:
+ st.session_state['mw_end_date'] = datetime.now().date()
+ if 'mw_quick_select' not in st.session_state:
+ st.session_state['mw_quick_select'] = "鏈�杩�7澶�"
+
+ # 瀹氫箟鍥炶皟鍑芥暟
+ def update_dates(qs):
+ st.session_state['mw_quick_select'] = qs
+ today = datetime.now().date()
+ if qs == "浠婂ぉ":
+ st.session_state['mw_start_date'] = today
+ st.session_state['mw_end_date'] = today
+ elif qs == "鏈�杩�3澶�":
+ st.session_state['mw_start_date'] = today - timedelta(days=3)
+ st.session_state['mw_end_date'] = today
+ elif qs == "鏈�杩�7澶�":
+ st.session_state['mw_start_date'] = today - timedelta(days=7)
+ st.session_state['mw_end_date'] = today
+ elif qs == "鏈�杩�30澶�":
+ st.session_state['mw_start_date'] = today - timedelta(days=30)
+ st.session_state['mw_end_date'] = today
+
+ def on_date_change():
+ st.session_state['mw_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['mw_quick_select'] == option else "secondary"
+ if st.button(option, key=f"btn_mw_{option}", width='stretch', type=button_type):
+ update_dates(option)
+ st.rerun()
+
+ with cols[5]:
+ start_date = st.date_input(
+ "寮�濮嬫棩鏈�",
+ label_visibility="collapsed",
+ key="mw_start_date",
+ on_change=on_date_change
+ )
+
+ with cols[6]:
+ end_date = st.date_input(
+ "缁撴潫鏃ユ湡",
+ label_visibility="collapsed",
+ key="mw_end_date",
+ on_change=on_date_change
+ )
+
+ with cols[7]:
+ query_button = st.button("馃殌 寮�濮嬪垎鏋�", key="mw_query", width='stretch')
+
+ # 杞崲涓篸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 = extruder_service.get_extruder_data(start_dt, end_dt)
+ # 澶勭悊鏈哄ご鍘嬪姏锛屽幓闄よ秴杩�2鐨勫��
+ if df_extruder is not None and not df_extruder.empty:
+ df_extruder = df_extruder[df_extruder['head_pressure'] <= 2]
+
+ # 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)
+ # 鑾峰彇鐢垫満杩愯鐩戣鏁版嵁
+ df_motor = main_process_service.get_motor_monitoring_data(start_dt, end_dt)
+ # 澶勭悊鐢垫満绾块�熸暟鎹紝闄や互10
+ if df_motor is not None and not df_motor.empty:
+ df_motor['m1_line_speed'] = df_motor['m1_line_speed'] / 10
+ df_motor['m2_line_speed'] = df_motor['m2_line_speed'] / 10
+
+ # 妫�鏌ユ槸鍚︽湁鏁版嵁
+ has_data = any([
+ df_extruder is not None and not df_extruder.empty,
+ df_main_speed is not None and not df_main_speed.empty,
+ df_temp is not None and not df_temp.empty,
+ df_motor is not None and not df_motor.empty
+ ])
+
+ if not has_data:
+ st.warning("鎵�閫夋椂闂存鍐呮湭鎵惧埌浠讳綍鏁版嵁锛岃灏濊瘯璋冩暣鏌ヨ鏉′欢銆�")
+ return
+
+ # --- 鍥捐〃1: 绫抽噸涓庡疄闄呭弬鏁板垎鏋� ---
+ st.subheader("馃搱 绫抽噸涓庡疄闄呭弬鏁板垎鏋�")
+ fig1 = go.Figure()
+
+ # 娣诲姞绫抽噸
+ if df_extruder is not None and not df_extruder.empty:
+ fig1.add_trace(go.Scatter(
+ x=df_extruder['time'],
+ y=df_extruder['metered_weight'],
+ name='绫抽噸 (Kg/m)',
+ mode='lines',
+ line=dict(color='blue', width=2),
+ yaxis='y1'
+ ))
+
+ # 娣诲姞鎸ゅ嚭鏈哄疄闄呰浆閫�
+ fig1.add_trace(go.Scatter(
+ x=df_extruder['time'],
+ y=df_extruder['screw_speed_actual'],
+ name='鎸ゅ嚭鏈哄疄闄呰浆閫� (RPM)',
+ mode='lines',
+ line=dict(color='green', width=1.5),
+ yaxis='y2'
+ ))
+
+ # 娣诲姞鎸ゅ嚭鏈烘満澶村帇鍔�
+ fig1.add_trace(go.Scatter(
+ x=df_extruder['time'],
+ y=df_extruder['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:
+ fig1.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:
+ temp_display_fields = {
+ 'nakata_extruder_screw_display_temp': '铻烘潌鏄剧ず (掳C)',
+ 'nakata_extruder_rear_barrel_display_temp': '鍚庢満绛掓樉绀� (掳C)',
+ 'nakata_extruder_front_barrel_display_temp': '鍓嶆満绛掓樉绀� (掳C)',
+ 'nakata_extruder_head_display_temp': '鏈哄ご鏄剧ず (掳C)',
+ }
+ for field, label in temp_display_fields.items():
+ fig1.add_trace(go.Scatter(
+ x=df_temp['time'],
+ y=df_temp[field],
+ name=label,
+ mode='lines',
+ line=dict(width=1),
+ yaxis='y5'
+ ))
+
+ # 娣诲姞鐢垫満绾块�熸暟鎹�
+ if df_motor is not None and not df_motor.empty:
+ fig1.add_trace(go.Scatter(
+ x=df_motor['time'],
+ y=df_motor['m1_line_speed'],
+ name='鎷夊嚭涓�娈电嚎閫� (M/Min)',
+ mode='lines',
+ line=dict(color='cyan', width=1.5),
+ yaxis='y4'
+ ))
+ fig1.add_trace(go.Scatter(
+ x=df_motor['time'],
+ y=df_motor['m2_line_speed'],
+ name='鎷夊嚭浜屾绾块�� (M/Min)',
+ mode='lines',
+ line=dict(color='teal', width=1.5),
+ yaxis='y4'
+ ))
+
+ # 璁剧疆鍥捐〃1甯冨眬
+ fig1.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
+ ),
+ yaxis6=dict(
+ title='鎷夊嚭绾块�� (M/Min)',
+ title_font=dict(color='cyan'),
+ tickfont=dict(color='cyan'),
+ overlaying='y',
+ side='right',
+ anchor='free',
+ position=0.65
+ ),
+ 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'
+ )
+
+ # 鏄剧ず鍥捐〃1
+ st.plotly_chart(fig1, width='stretch', config={'scrollZoom': True})
+
+ # --- 鍥捐〃2: 绫抽噸涓庤瀹氬弬鏁板垎鏋� ---
+ st.subheader("馃搱 绫抽噸涓庤瀹氬弬鏁板垎鏋�")
+ fig2 = go.Figure()
+
+ # 娣诲姞绫抽噸
+ if df_extruder is not None and not df_extruder.empty:
+ fig2.add_trace(go.Scatter(
+ x=df_extruder['time'],
+ y=df_extruder['metered_weight'],
+ name='绫抽噸 (Kg/m)',
+ mode='lines',
+ line=dict(color='blue', width=2),
+ yaxis='y1'
+ ))
+
+ # 娣诲姞鎸ゅ嚭鏈鸿瀹氳浆閫�
+ fig2.add_trace(go.Scatter(
+ x=df_extruder['time'],
+ y=df_extruder['screw_speed_set'],
+ name='鎸ゅ嚭鏈鸿瀹氳浆閫� (RPM)',
+ mode='lines',
+ line=dict(color='green', width=1.5, dash='dash'),
+ yaxis='y2'
+ ))
+
+ # 娣诲姞鎸ゅ嚭鏈烘満澶村帇鍔�
+ fig2.add_trace(go.Scatter(
+ x=df_extruder['time'],
+ y=df_extruder['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:
+ fig2.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:
+ temp_set_fields = {
+ 'nakata_extruder_screw_set_temp': '铻烘潌璁惧畾 (掳C)',
+ 'nakata_extruder_rear_barrel_set_temp': '鍚庢満绛掕瀹� (掳C)',
+ 'nakata_extruder_front_barrel_set_temp': '鍓嶆満绛掕瀹� (掳C)',
+ 'nakata_extruder_head_set_temp': '鏈哄ご璁惧畾 (掳C)',
+ }
+ for field, label in temp_set_fields.items():
+ fig2.add_trace(go.Scatter(
+ x=df_temp['time'],
+ y=df_temp[field],
+ name=label,
+ mode='lines',
+ line=dict(width=1, dash='dash'),
+ yaxis='y5'
+ ))
+
+ # 璁剧疆鍥捐〃2甯冨眬
+ fig2.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=150, t=100, b=100),
+ hovermode='x unified'
+ )
+
+ # 鏄剧ず鍥捐〃2
+ st.plotly_chart(fig2, width='stretch', config={'scrollZoom': True})
+
+ # 鏁版嵁鎽樿
+ # st.subheader("馃搳 鏁版嵁鎽樿")
+ # summary_cols = st.columns(4)
+
+ # with summary_cols[0]:
+ # if df_extruder is not None and not df_extruder.empty:
+ # st.metric("骞冲潎绫抽噸", f"{df_extruder['metered_weight'].mean():.2f} Kg/m")
+
+ # with summary_cols[1]:
+ # if df_extruder is not None and not df_extruder.empty:
+ # st.metric("骞冲潎璁惧畾杞��", f"{df_extruder['screw_speed_set'].mean():.2f} RPM")
+
+ # with summary_cols[2]:
+ # if df_extruder is not None and not df_extruder.empty:
+ # st.metric("骞冲潎瀹為檯杞��", f"{df_extruder['screw_speed_actual'].mean():.2f} RPM")
+
+ # with summary_cols[3]:
+ # if df_extruder is not None and not df_extruder.empty:
+ # st.metric("骞冲潎鏈哄ご鍘嬪姏", f"{df_extruder['head_pressure'].mean():.2f}")
diff --git a/app/pages/metered_weight_regression.py b/app/pages/metered_weight_regression.py
new file mode 100644
index 0000000..d07c22b
--- /dev/null
+++ b/app/pages/metered_weight_regression.py
@@ -0,0 +1,620 @@
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import pandas as pd
+import numpy as np
+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
+
+
+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'] = [
+ '铻烘潌杞��', '鏈哄ご鍘嬪姏', '娴佺▼涓婚��', '铻烘潌娓╁害',
+ '鍚庢満绛掓俯搴�', '鍓嶆満绛掓俯搴�', '鏈哄ご娓╁害'
+ ]
+
+ # 瀹氫箟鍥炶皟鍑芥暟
+ 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("---")
+ 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)
+
+ # --- 鍘熷鏁版嵁瓒嬪娍鍥� ---
+ 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:
+ # 鍑嗗鏁版嵁
+ X = df_analysis[st.session_state['mr_selected_features']]
+ y = df_analysis['绫抽噸']
+
+ # 娓呯悊鏁版嵁涓殑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("杈撳叆鐗瑰緛鍊艰繘琛岀背閲嶉娴�:")
+ predict_cols = st.columns(2)
+ input_features = {}
+
+ for i, feature in enumerate(st.session_state['mr_selected_features']):
+ with predict_cols[i % 2]:
+ # 鑾峰彇鐗瑰緛鐨勭粺璁′俊鎭�
+ min_val = df_analysis[feature].min()
+ max_val = df_analysis[feature].max()
+ mean_val = df_analysis[feature].mean()
+
+ input_features[feature] = st.number_input(
+ f"{feature}",
+ key=f"pred_{feature}",
+ value=float(mean_val),
+ min_value=float(min_val),
+ max_value=float(max_val),
+ step=0.1
+ )
+
+ if st.button("棰勬祴绫抽噸"):
+ # 鍑嗗棰勬祴鏁版嵁
+ input_data = [[input_features[feature] for feature in st.session_state['mr_selected_features']]]
+ # 棰勬祴
+ predicted_weight = model.predict(input_data)[0]
+ # 鏄剧ず棰勬祴缁撴灉
+ st.success(f"棰勬祴绫抽噸: {predicted_weight:.4f} Kg/m")
+
+ # --- 鏁版嵁棰勮 ---
+ st.subheader("馃攳 鏁版嵁棰勮")
+ st.dataframe(df_analysis.head(20), use_container_width=True)
+ else:
+ # 鎻愮ず鐢ㄦ埛鐐瑰嚮寮�濮嬪垎鏋愭寜閽�
+ st.info("璇烽�夋嫨鏃堕棿鑼冨洿骞剁偣鍑�'寮�濮嬪垎鏋�'鎸夐挳鑾峰彇鏁版嵁銆�")
diff --git a/app/pages/sorting_dashboard.py b/app/pages/sorting_dashboard.py
index b4d8576..d505e79 100644
--- a/app/pages/sorting_dashboard.py
+++ b/app/pages/sorting_dashboard.py
@@ -183,7 +183,9 @@
rangeslider=dict(visible=True)
),
yaxis=dict(fixedrange=False),
+ hovermode='x unified',
dragmode='zoom'
+
)
# 閰嶇疆鍥捐〃鍙傛暟
diff --git a/app/services/data_processing_service.py b/app/services/data_processing_service.py
index 3a7bc05..6adee4b 100644
--- a/app/services/data_processing_service.py
+++ b/app/services/data_processing_service.py
@@ -211,20 +211,21 @@
try:
# 璇嗗埆鏋佸�肩偣
extreme_points = self.identify_local_maxima(df)
-
+ # print("璇嗗埆鏋佸�肩偣:", extreme_points)
# 璇嗗埆闃舵鏈�澶у��
- phase_maxima = self.identify_phase_maxima(df)
+ # phase_maxima = self.identify_phase_maxima(df)
+ # print("璇嗗埆闃舵鏈�澶у��:", phase_maxima)
# 璁$畻姣忎釜鏋佸�肩偣鐨勫悎鏍肩巼
if not extreme_points.empty:
extreme_points['pass_rate'] = extreme_points.apply(self.calculate_pass_rate, axis=1)
# 璁$畻鏁翠綋鍚堟牸鐜�
- overall_pass_rate = self.calculate_overall_pass_rate(df)
+ overall_pass_rate = self.calculate_overall_pass_rate(extreme_points)
return {
'extreme_points': extreme_points,
- 'phase_maxima': phase_maxima,
+ 'phase_maxima': pd.DataFrame(),
'overall_pass_rate': overall_pass_rate
}
except Exception as e:
diff --git a/app/services/main_process_service.py b/app/services/main_process_service.py
index b25c3c2..2bb9b4f 100644
--- a/app/services/main_process_service.py
+++ b/app/services/main_process_service.py
@@ -25,7 +25,7 @@
self.db.connect()
query = """
- SELECT time, process_main_speed
+ SELECT time, process_main_speed, cutting_count
FROM public.aics_main_process_cutting_setting
WHERE time BETWEEN %s AND %s
ORDER BY time ASC
diff --git a/dashboard.py b/dashboard.py
index da7aaf1..dc7b0c6 100644
--- a/dashboard.py
+++ b/dashboard.py
@@ -3,6 +3,10 @@
from app.pages.extruder_dashboard import show_extruder_dashboard
from app.pages.main_process_dashboard import show_main_process_dashboard
from app.pages.comprehensive_dashboard import show_comprehensive_dashboard
+from app.pages.metered_weight_dashboard import show_metered_weight_dashboard
+from app.pages.metered_weight_correlation import show_metered_weight_correlation
+from app.pages.metered_weight_regression import show_metered_weight_regression
+from app.pages.metered_weight_advanced import show_metered_weight_advanced
# 璁剧疆椤甸潰閰嶇疆
st.set_page_config(
@@ -35,9 +39,37 @@
comprehensive_page = st.Page(
show_comprehensive_dashboard,
- title="缁煎悎鍒嗘瀽",
+ title="鏉¢噸缁煎悎鍒嗘瀽",
icon="馃寪",
url_path="comprehensive"
+)
+
+metered_weight_page = st.Page(
+ show_metered_weight_dashboard,
+ title="绫抽噸缁煎悎鍒嗘瀽",
+ icon="馃搹",
+ url_path="metered_weight"
+)
+
+metered_weight_correlation_page = st.Page(
+ show_metered_weight_correlation,
+ title="绫抽噸鐩稿叧鎬у垎鏋�",
+ icon="馃搳",
+ url_path="metered_weight_correlation"
+)
+
+metered_weight_regression_page = st.Page(
+ show_metered_weight_regression,
+ title="绫抽噸澶氬厓绾挎�у洖褰掑垎鏋�",
+ icon="馃搱",
+ url_path="metered_weight_regression"
+)
+
+metered_weight_advanced_page = st.Page(
+ show_metered_weight_advanced,
+ title="绫抽噸楂樼骇棰勬祴鍒嗘瀽",
+ icon="馃",
+ url_path="metered_weight_advanced"
)
# 渚ц竟鏍忛〉鑴氫俊鎭�
@@ -47,7 +79,7 @@
# 瀵艰埅閰嶇疆
pg = st.navigation({
- "缁煎悎鍒嗘瀽": [comprehensive_page],
+ "缁煎悎鍒嗘瀽": [comprehensive_page, metered_weight_page, metered_weight_correlation_page, metered_weight_regression_page, metered_weight_advanced_page],
"鍒嗛」鍒嗘瀽": [sorting_page, extruder_page, main_process_page]
})
diff --git a/requirements.txt b/requirements.txt
index 52f0959..36a3f9b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,4 +2,7 @@
psycopg2-binary
pandas
plotly
-python-dotenv
\ No newline at end of file
+python-dotenv
+scikit-learn
+pytorch
+torchvision
\ No newline at end of file
--
Gitblit v1.9.3