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.data_query_service import DataQueryService from app.services.extruder_service import ExtruderService from app.services.main_process_service import MainProcessService def show_comprehensive_dashboard(): # 初始化服务 sorting_service = DataQueryService() extruder_service = ExtruderService() main_process_service = MainProcessService() # 页面标题 st.title("条重综合分析") # 初始化会话状态用于日期同步 if 'comp_start_date' not in st.session_state: st.session_state['comp_start_date'] = datetime.now().date() - timedelta(days=7) if 'comp_end_date' not in st.session_state: 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): st.session_state['comp_quick_select'] = qs today = datetime.now().date() if qs == "今天": st.session_state['comp_start_date'] = today st.session_state['comp_end_date'] = today elif qs == "最近3天": st.session_state['comp_start_date'] = today - timedelta(days=3) st.session_state['comp_end_date'] = today elif qs == "最近7天": st.session_state['comp_start_date'] = today - timedelta(days=7) st.session_state['comp_end_date'] = today elif qs == "最近30天": st.session_state['comp_start_date'] = today - timedelta(days=30) st.session_state['comp_end_date'] = today def on_date_change(): st.session_state['comp_quick_select'] = "自定义" # 查询条件区域 with st.expander("🔍 查询配置", expanded=True): # 添加自定义 CSS 实现响应式换行 st.markdown(""" """, 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['comp_quick_select'] == option else "secondary" if st.button(option, key=f"btn_comp_{option}", width='stretch', type=button_type): update_dates(option) st.rerun() with cols[5]: start_date = st.date_input( "开始日期", label_visibility="collapsed", key="comp_start_date", on_change=on_date_change ) with cols[6]: end_date = st.date_input( "结束日期", label_visibility="collapsed", key="comp_end_date", on_change=on_date_change ) # 在第二行添加时间偏移配置 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="由于胎面从挤出到分拣需要时间,将上游数据向后移动,使其与分拣磅秤上的重量数据在时间轴上对齐。" ) st.session_state['time_offset'] = time_offset with offset_cols[2]: query_button = st.button("🚀 开始分析", key="comp_query", width='stretch') # 转换为datetime对象 start_dt = datetime.combine(start_date, datetime.min.time()) end_dt = datetime.combine(end_date, datetime.max.time()) # 查询处理 if query_button: with st.spinner("正在聚合多源数据..."): # 获取偏移量 offset_delta = timedelta(minutes=st.session_state['time_offset']) # 1. 获取分拣磅秤数据 (作为基准,不偏移) df_sorting = sorting_service.get_sorting_scale_data(start_dt, end_dt) # 2. 获取挤出机数据 (应用偏移) df_extruder = extruder_service.get_extruder_data(start_dt, end_dt) 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 ]) if not has_data: st.warning("所选时间段内未找到任何数据,请尝试调整查询条件。") return # 创建综合分析图表 fig = go.Figure() # 添加分拣磅秤重量 (基图) if df_sorting is not None and not df_sorting.empty: # 检查是否包含阈值相关字段 has_thresholds = all(col in df_sorting.columns for col in ['over_difference', 'under_difference']) if has_thresholds: # 分离正常和异常数据点 # 复制数据以避免修改原始数据,并处理可能的零值/空值 plot_df = df_sorting.copy() is_out_of_range = (plot_df['weight'] > plot_df['over_difference']) | (plot_df['weight'] < plot_df['under_difference']) normal_points = plot_df[~is_out_of_range] anomaly_points = plot_df[is_out_of_range] # 1.1 正常重量以蓝色数据点显示 if not normal_points.empty: fig.add_trace(go.Scatter( x=normal_points['time'], y=normal_points['weight'], name='分拣重量 (正常)', mode='markers', marker=dict(size=5, color='blue', opacity=0.7) )) # 1.2 异常重量以红色三角显示 if not anomaly_points.empty: fig.add_trace(go.Scatter( x=anomaly_points['time'], y=anomaly_points['weight'], name='分拣重量 (异常)', mode='markers', marker=dict( size=7, color='red', symbol='triangle-up', line=dict(width=1, color='darkred') ) )) else: # 如果没有阈值,则全部显示为普通蓝色点 fig.add_trace(go.Scatter( x=df_sorting['time'], y=df_sorting['weight'], name='分拣重量 (kg)', mode='markers', marker=dict(size=5, color='blue', opacity=0.7) )) # 2. 显示上下限和标准值的曲线 if 'baseline_value' in df_sorting.columns: fig.add_trace(go.Scatter( x=df_sorting['time'], y=df_sorting['baseline_value'], name='基准值', mode='lines', line=dict(color='green', width=2), opacity=0.6 )) if 'over_difference' in df_sorting.columns: fig.add_trace(go.Scatter( x=df_sorting['time'], y=df_sorting['over_difference'], name='上限阈值', mode='lines', line=dict(color='red', width=1.5), opacity=0.5 )) if 'under_difference' in df_sorting.columns: fig.add_trace(go.Scatter( x=df_sorting['time'], y=df_sorting['under_difference'], name='下限阈值', mode='lines', line=dict(color='orange', width=1.5), opacity=0.5 )) # 添加挤出机米重 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='挤出机米重 (Kg/m)', # mode='lines', # line=dict(color='green', width=1.5), # yaxis='y2' # )) # 添加挤出机实际转速 fig.add_trace(go.Scatter( x=df_extruder['time'], y=df_extruder['screw_speed_actual'], name='挤出机实际转速 (RPM)', 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.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='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_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()): fig.add_trace(go.Scatter( x=df_temp['time'], y=df_temp[field], name=label, mode='lines', line=dict(width=1), yaxis='y4' )) # 设置多坐标轴布局 fig.update_layout( title='条重综合趋势分析', xaxis=dict( title='时间', rangeslider=dict(visible=True), type='date' ), yaxis=dict( title='重量 (kg)', title_font=dict(color='blue'), tickfont=dict(color='blue') ), yaxis2=dict( title='米重 (Kg/m)', title_font=dict(color='green'), tickfont=dict(color='green'), overlaying='y', side='right' ), yaxis3=dict( title='速度 (RPM / M/Min)', title_font=dict(color='red'), tickfont=dict(color='red'), overlaying='y', side='right', anchor='free', position=0.85 ), yaxis4=dict( title='温度 (°C)', title_font=dict(color='purple'), tickfont=dict(color='purple'), overlaying='y', side='left', 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", y=1.02, xanchor="right", x=1 ), height=700, margin=dict(l=100, r=100, t=100, b=100), hovermode='x unified' ) # 显示图表 st.plotly_chart(fig, width='stretch', config={'scrollZoom': True}) # 数据摘要 # 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[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[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")