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天"
|
|
# 定义回调函数
|
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("""
|
<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['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
|
)
|
|
with cols[7]:
|
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("正在聚合多源数据..."):
|
# 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)
|
# 3. 获取主流程控制数据
|
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_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='挤出机米重 (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['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 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)'
|
}
|
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='米重 (g/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
|
),
|
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} g/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")
|