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.data_processing_service import DataProcessingService def show_extruder_dashboard(): # 初始化服务 extruder_service = ExtruderService() processing_service = DataProcessingService() # 页面标题 st.title("挤出机数据分析") # 初始化会话状态用于日期同步 if 'extruder_start_date' not in st.session_state: st.session_state['extruder_start_date'] = datetime.now().date() - timedelta(days=7) if 'extruder_end_date' not in st.session_state: st.session_state['extruder_end_date'] = datetime.now().date() if 'extruder_quick_select' not in st.session_state: st.session_state['extruder_quick_select'] = "最近7天" # 定义回调函数 def update_dates(qs): st.session_state['extruder_quick_select'] = qs today = datetime.now().date() if qs == "今天": st.session_state['extruder_start_date'] = today st.session_state['extruder_end_date'] = today elif qs == "最近3天": st.session_state['extruder_start_date'] = today - timedelta(days=3) st.session_state['extruder_end_date'] = today elif qs == "最近7天": st.session_state['extruder_start_date'] = today - timedelta(days=7) st.session_state['extruder_end_date'] = today elif qs == "最近30天": st.session_state['extruder_start_date'] = today - timedelta(days=30) st.session_state['extruder_end_date'] = today def on_date_change(): st.session_state['extruder_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['extruder_quick_select'] == option else "secondary" if st.button(option, key=f"btn_extruder_{option}", width='stretch', type=button_type): update_dates(option) st.rerun() with cols[5]: start_date = st.date_input( "开始日期", label_visibility="collapsed", key="extruder_start_date", on_change=on_date_change ) with cols[6]: end_date = st.date_input( "结束日期", label_visibility="collapsed", key="extruder_end_date", on_change=on_date_change ) with cols[7]: query_button = st.button("🚀 查询", key="extruder_query", width='stretch') # 转换为datetime对象(包含时间) start_datetime = datetime.combine(start_date, datetime.min.time()) end_datetime = datetime.combine(end_date, datetime.max.time()) # 查询按钮处理 if query_button: # 验证日期范围 if start_datetime > end_datetime: st.error("开始日期不能晚于结束日期!") else: # 显示加载状态 with st.spinner("正在查询数据..."): # 查询数据 raw_data = extruder_service.get_extruder_data(start_datetime, end_datetime) if raw_data is None or raw_data.empty: st.warning("未查询到数据,请检查日期范围或数据库连接!") st.session_state['extruder_results'] = None else: # 清洗数据 cleaned_data = processing_service.clean_data(raw_data) # 检测换批事件 batch_changes = extruder_service.detect_batch_changes(cleaned_data) # 缓存结果 st.session_state['extruder_results'] = { 'cleaned_data': cleaned_data, 'batch_changes': batch_changes, } # 显示数据概览 st.subheader("数据概览") col1, col2, col3, col4 = st.columns(4) with col1: st.metric("总记录数", len(cleaned_data)) with col2: st.metric("换批次数", len(batch_changes)) with col3: st.metric("数据时间范围", f"{cleaned_data['time'].min()} 至 {cleaned_data['time'].max()}") # 显示换批分析 st.subheader("换批分析") if not batch_changes.empty: # 准备展示数据 batch_display = batch_changes[['batch_id', 'compound_code', 'start_time', 'end_time', 'duration_minutes']].copy() # 格式化时间 batch_display['start_time'] = batch_display['start_time'].dt.strftime('%Y-%m-%d %H:%M:%S') batch_display['end_time'] = batch_display['end_time'].dt.strftime('%Y-%m-%d %H:%M:%S') # 修改列名 batch_display.columns = ['批号', '胶料号', '开始时间', '结束时间', '持续时长(分钟)'] # 显示数据表格 st.dataframe(batch_display, use_container_width=True) else: st.warning("未检测到换批事件") # 显示换料操作可视化图表 st.subheader("换料操作可视化") if not batch_changes.empty: # 创建换料操作可视化图表 fig = go.Figure() # 添加关键参数趋势线 fig.add_trace(go.Scatter( x=cleaned_data['time'], y=cleaned_data['screw_speed_actual'], name='实际转速', line=dict(color='blue', width=2), opacity=0.8 )) fig.add_trace(go.Scatter( x=cleaned_data['time'], y=cleaned_data['head_pressure'], name='机头压力', line=dict(color='red', width=2), opacity=0.8, yaxis='y2' )) fig.add_trace(go.Scatter( x=cleaned_data['time'], y=cleaned_data['extruder_current'], name='挤出机电流', line=dict(color='green', width=2), opacity=0.8, yaxis='y3' )) fig.add_trace(go.Scatter( x=cleaned_data['time'], y=cleaned_data['metered_weight'], name='米重', line=dict(color='orange', width=2), opacity=0.8, yaxis='y4' )) # 添加换料事件标记 for i, row in batch_changes.iterrows(): # 添加垂直线 fig.add_shape( type="line", x0=row['start_time'], y0=0, x1=row['start_time'], y1=1, yref="paper", line=dict(color="purple", width=2, dash="dash") ) # 添加注释 fig.add_annotation( x=row['start_time'], y=1, yref='paper', text=f'换料: {row["compound_code"]}\n批号: {row["batch_id"]}', showarrow=True, arrowhead=1, ax=0, ay=-60 ) # 配置图表布局 fig.update_layout( title='换料操作关键参数变化趋势', xaxis_title='时间', xaxis=dict( rangeslider=dict(visible=True), type='date' ), yaxis_title='实际转速 (rpm)', yaxis2=dict( title='机头压力 (MPa)', overlaying='y', side='right', position=0.85 ), yaxis3=dict( title='挤出机电流 (A)', overlaying='y', side='right', position=0.92 ), yaxis4=dict( title='米重 (kg)', overlaying='y', side='right', position=1 ), legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ), hovermode='x unified', height=700 ) # 显示图表 st.plotly_chart(fig, width='stretch', config={'scrollZoom': True}) # 添加数据导出功能 import io # 准备导出数据 export_data = [] for i, row in batch_changes.iterrows(): # 获取换料前后的数据 before_change = cleaned_data[cleaned_data['time'] < row['start_time']].tail(5) after_change = cleaned_data[cleaned_data['time'] >= row['start_time']].head(5) # 添加换料事件记录 export_data.append({ 'event_type': '换料事件', 'batch_id': row['batch_id'], 'compound_code': row['compound_code'], 'time': row['start_time'], 'screw_speed': '', 'head_pressure': '', 'extruder_current': '', 'metered_weight': '' }) # 添加换料前数据 for _, before_row in before_change.iterrows(): export_data.append({ 'event_type': '换料前', 'batch_id': row['batch_id'], 'compound_code': row['compound_code'], 'time': before_row['time'], 'screw_speed': before_row['screw_speed_actual'], 'head_pressure': before_row['head_pressure'], 'extruder_current': before_row['extruder_current'], 'metered_weight': before_row['metered_weight'] }) # 添加换料后数据 for _, after_row in after_change.iterrows(): export_data.append({ 'event_type': '换料后', 'batch_id': row['batch_id'], 'compound_code': row['compound_code'], 'time': after_row['time'], 'screw_speed': after_row['screw_speed_actual'], 'head_pressure': after_row['head_pressure'], 'extruder_current': after_row['extruder_current'], 'metered_weight': after_row['metered_weight'] }) # 转换为DataFrame export_df = pd.DataFrame(export_data) # 创建CSV数据 csv_buffer = io.StringIO() export_df.to_csv(csv_buffer, index=False, encoding='utf-8-sig') csv_data = csv_buffer.getvalue() # 添加下载按钮 st.download_button( label="下载换料操作分析数据", data=csv_data, file_name=f"换料操作分析_{start_date}_{end_date}.csv", mime="text/csv" ) else: st.warning("未检测到换批事件,无法生成换料操作图表") # 显示原始数据 st.subheader("原始数据") st.dataframe(cleaned_data, use_container_width=True) # 数据库连接状态 st.sidebar.subheader("数据库状态") if extruder_service.db.is_connected(): st.sidebar.success("数据库连接正常") else: st.sidebar.warning("数据库未连接") if __name__ == "__main__": show_extruder_dashboard()