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("""
|
<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['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()
|