feat(services): 添加主流程服务并重构时间处理逻辑
重构数据查询服务和挤出机服务,添加时区偏移处理
新增MainProcessService用于处理主流程数据查询
优化数据清洗服务中的类型检查和时区处理
移除旧版仪表盘代码,新增分页式仪表盘实现
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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() |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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.main_process_service import MainProcessService |
| | | |
| | | def show_main_process_dashboard(): |
| | | # åå§åæå¡ |
| | | service = MainProcessService() |
| | | |
| | | # 页颿 é¢ |
| | | st.title("主æµç¨æ§å¶æ°æ®åæ") |
| | | |
| | | # åå§åä¼è¯ç¶æç¨äºæ¥æåæ¥ |
| | | if 'main_process_start_date' not in st.session_state: |
| | | st.session_state['main_process_start_date'] = datetime.now().date() - timedelta(days=7) |
| | | if 'main_process_end_date' not in st.session_state: |
| | | st.session_state['main_process_end_date'] = datetime.now().date() |
| | | if 'main_process_quick_select' not in st.session_state: |
| | | st.session_state['main_process_quick_select'] = "æè¿7天" |
| | | |
| | | # å®ä¹åè°å½æ° |
| | | def update_dates(qs): |
| | | st.session_state['main_process_quick_select'] = qs |
| | | today = datetime.now().date() |
| | | if qs == "ä»å¤©": |
| | | st.session_state['main_process_start_date'] = today |
| | | st.session_state['main_process_end_date'] = today |
| | | elif qs == "æè¿3天": |
| | | st.session_state['main_process_start_date'] = today - timedelta(days=3) |
| | | st.session_state['main_process_end_date'] = today |
| | | elif qs == "æè¿7天": |
| | | st.session_state['main_process_start_date'] = today - timedelta(days=7) |
| | | st.session_state['main_process_end_date'] = today |
| | | elif qs == "æè¿30天": |
| | | st.session_state['main_process_start_date'] = today - timedelta(days=30) |
| | | st.session_state['main_process_end_date'] = today |
| | | |
| | | def on_date_change(): |
| | | st.session_state['main_process_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['main_process_quick_select'] == option else "secondary" |
| | | if st.button(option, key=f"btn_main_{option}", width='stretch', type=button_type): |
| | | update_dates(option) |
| | | st.rerun() |
| | | |
| | | with cols[5]: |
| | | st.date_input( |
| | | "å¼å§æ¥æ", |
| | | label_visibility="collapsed", |
| | | key="main_process_start_date", |
| | | on_change=on_date_change |
| | | ) |
| | | |
| | | with cols[6]: |
| | | st.date_input( |
| | | "ç»ææ¥æ", |
| | | label_visibility="collapsed", |
| | | key="main_process_end_date", |
| | | on_change=on_date_change |
| | | ) |
| | | |
| | | with cols[7]: |
| | | query_button = st.button("ð æ¥è¯¢", key="main_process_query", width='stretch') |
| | | |
| | | # 转æ¢ä¸ºdatetime对象 |
| | | start_dt = datetime.combine(st.session_state['main_process_start_date'], datetime.min.time()) |
| | | end_dt = datetime.combine(st.session_state['main_process_end_date'], datetime.max.time()) |
| | | |
| | | if query_button: |
| | | with st.spinner("æ£å¨è·å主æµç¨æ°æ®..."): |
| | | # 1. è·å主éåº¦æ°æ® |
| | | df_speed = service.get_cutting_setting_data(start_dt, end_dt) |
| | | # 2. è·åçµæºçæ§æ°æ® |
| | | df_motor = service.get_motor_monitoring_data(start_dt, end_dt) |
| | | # 3. è·å温度æ§å¶æ°æ® |
| | | df_temp = service.get_temperature_control_data(start_dt, end_dt) |
| | | |
| | | # --- è¶å¿å¾ 1: æµç¨ä¸»é度 --- |
| | | st.subheader("ð æµç¨ä¸»é度è¶å¿") |
| | | if not df_speed.empty: |
| | | 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}) |
| | | else: |
| | | st.info("该æ¶é´æ®µå
æ 主éåº¦æ°æ®") |
| | | |
| | | # --- è¶å¿å¾ 2: çµæºè¿è¡çº¿é --- |
| | | st.subheader("ð çµæºè¿è¡çº¿éè¶å¿") |
| | | if not df_motor.empty: |
| | | fig_motor = go.Figure() |
| | | fig_motor.add_trace(go.Scatter(x=df_motor['time'], y=df_motor['m1_line_speed'], name='æåºä¸æ®µçº¿é')) |
| | | fig_motor.add_trace(go.Scatter(x=df_motor['time'], y=df_motor['m2_line_speed'], name='æåºäºæ®µçº¿é')) |
| | | fig_motor.update_layout( |
| | | title="çµæºçº¿é (M/Min)", |
| | | xaxis_title="æ¶é´", |
| | | yaxis_title="线é (M/Min)", |
| | | xaxis=dict(rangeslider=dict(visible=True), type='date') |
| | | ) |
| | | st.plotly_chart(fig_motor, width='stretch', config={'scrollZoom': True}) |
| | | else: |
| | | st.info("该æ¶é´æ®µå
æ çµæºçæ§æ°æ®") |
| | | |
| | | # --- è¶å¿å¾ 3: ä¸ç°æ¤åºæºæ¸©åº¦æ§å¶ --- |
| | | st.subheader("ð ä¸ç°æ¤åºæºæ¸©åº¦è¶å¿") |
| | | if not df_temp.empty: |
| | | fig_temp = go.Figure() |
| | | |
| | | # èºææ¸©åº¦ |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_screw_set_temp'], |
| | | name='èºæè®¾å®', line=dict(dash='dash'))) |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_screw_display_temp'], |
| | | name='èºææ¾ç¤º')) |
| | | |
| | | # åæºçæ¸©åº¦ |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_rear_barrel_set_temp'], |
| | | name='åæºç设å®', line=dict(dash='dash'))) |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_rear_barrel_display_temp'], |
| | | name='åæºçæ¾ç¤º')) |
| | | |
| | | # åæºçæ¸©åº¦ |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_front_barrel_set_temp'], |
| | | name='åæºç设å®', line=dict(dash='dash'))) |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_front_barrel_display_temp'], |
| | | name='åæºçæ¾ç¤º')) |
| | | |
| | | # æºå¤´æ¸©åº¦ |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_head_set_temp'], |
| | | name='æºå¤´è®¾å®', line=dict(dash='dash'))) |
| | | fig_temp.add_trace(go.Scatter(x=df_temp['time'], y=df_temp['nakata_extruder_head_display_temp'], |
| | | name='æºå¤´æ¾ç¤º')) |
| | | |
| | | fig_temp.update_layout( |
| | | title="ä¸ç°æ¤åºæºæ¸©åº¦ (°C)", |
| | | xaxis_title="æ¶é´", |
| | | yaxis_title="温度 (°C)", |
| | | xaxis=dict(rangeslider=dict(visible=True), type='date') |
| | | ) |
| | | st.plotly_chart(fig_temp, width='stretch', config={'scrollZoom': True}) |
| | | else: |
| | | st.info("该æ¶é´æ®µå
æ æ¸©åº¦æ§å¶æ°æ®") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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.data_processing_service import DataProcessingService |
| | | |
| | | def show_sorting_dashboard(): |
| | | # åå§åæå¡ |
| | | query_service = DataQueryService() |
| | | processing_service = DataProcessingService() |
| | | |
| | | # 页颿 é¢ |
| | | st.title("忣ç£
ç§¤æ°æ®åæ") |
| | | |
| | | # åå§åä¼è¯ç¶æç¨äºæ¥æåæ¥ |
| | | if 'sorting_start_date' not in st.session_state: |
| | | st.session_state['sorting_start_date'] = datetime.now().date() - timedelta(days=7) |
| | | if 'sorting_end_date' not in st.session_state: |
| | | st.session_state['sorting_end_date'] = datetime.now().date() |
| | | if 'sorting_quick_select' not in st.session_state: |
| | | st.session_state['sorting_quick_select'] = "æè¿7天" |
| | | |
| | | # å®ä¹åè°å½æ° |
| | | def update_dates(qs): |
| | | st.session_state['sorting_quick_select'] = qs |
| | | today = datetime.now().date() |
| | | if qs == "ä»å¤©": |
| | | st.session_state['sorting_start_date'] = today |
| | | st.session_state['sorting_end_date'] = today |
| | | elif qs == "æè¿3天": |
| | | st.session_state['sorting_start_date'] = today - timedelta(days=3) |
| | | st.session_state['sorting_end_date'] = today |
| | | elif qs == "æè¿7天": |
| | | st.session_state['sorting_start_date'] = today - timedelta(days=7) |
| | | st.session_state['sorting_end_date'] = today |
| | | elif qs == "æè¿30天": |
| | | st.session_state['sorting_start_date'] = today - timedelta(days=30) |
| | | st.session_state['sorting_end_date'] = today |
| | | |
| | | def on_date_change(): |
| | | st.session_state['sorting_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['sorting_quick_select'] == option else "secondary" |
| | | if st.button(option, key=f"btn_{option}", width='stretch', type=button_type): |
| | | update_dates(option) |
| | | st.rerun() |
| | | |
| | | with cols[5]: |
| | | start_date = st.date_input( |
| | | "å¼å§æ¥æ", |
| | | label_visibility="collapsed", |
| | | key="sorting_start_date", |
| | | on_change=on_date_change |
| | | ) |
| | | |
| | | with cols[6]: |
| | | end_date = st.date_input( |
| | | "ç»ææ¥æ", |
| | | label_visibility="collapsed", |
| | | key="sorting_end_date", |
| | | on_change=on_date_change |
| | | ) |
| | | |
| | | with cols[7]: |
| | | query_button = st.button("ð æ¥è¯¢", key="sorting_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 = query_service.get_sorting_scale_data(start_datetime, end_datetime) |
| | | |
| | | if raw_data is None or raw_data.empty: |
| | | st.warning("æªæ¥è¯¢å°æ°æ®ï¼è¯·æ£æ¥æ¥æèå´ææ°æ®åºè¿æ¥ï¼") |
| | | st.session_state['query_results'] = None |
| | | else: |
| | | # æ¸
æ´æ°æ® |
| | | cleaned_data = processing_service.clean_data(raw_data) |
| | | |
| | | # 计ç®ç»è®¡ä¿¡æ¯ |
| | | stats = processing_service.calculate_statistics(cleaned_data) |
| | | |
| | | # åææå¼ç¹ |
| | | extreme_analysis = processing_service.analyze_extreme_points(cleaned_data) |
| | | extreme_points = extreme_analysis['extreme_points'] |
| | | phase_maxima = extreme_analysis['phase_maxima'] |
| | | overall_pass_rate = extreme_analysis['overall_pass_rate'] |
| | | |
| | | # ç¼åç»æ |
| | | st.session_state['query_results'] = { |
| | | 'cleaned_data': cleaned_data, |
| | | 'stats': stats, |
| | | 'extreme_points': extreme_points, |
| | | 'phase_maxima': phase_maxima, |
| | | 'overall_pass_rate': overall_pass_rate |
| | | } |
| | | |
| | | # æ¾ç¤ºæ°æ®æ¦è§ |
| | | st.subheader("æ°æ®æ¦è§") |
| | | col1, col2, col3, col4 = st.columns(4) |
| | | |
| | | with col1: |
| | | st.metric("æ»è®°å½æ°", stats.get('total_records', 0)) |
| | | |
| | | with col2: |
| | | st.metric("å¹³ååæ ¼æ°", round(stats.get('count_in_range', {}).get('mean', 0), 2)) |
| | | |
| | | with col3: |
| | | st.metric("æ°æ®æ¶é´èå´", f"{cleaned_data['time'].min()} è³ {cleaned_data['time'].max()}") |
| | | |
| | | with col4: |
| | | st.metric("æ´ä½åæ ¼ç", f"{overall_pass_rate}%") |
| | | |
| | | # æ¾ç¤ºè¶å¿å¾ |
| | | st.subheader("æ°æ®è¶å¿å¾") |
| | | fig = px.line( |
| | | cleaned_data, |
| | | x='time', |
| | | y=['count_under', 'count_in_range', 'count_over'], |
| | | labels={ |
| | | 'time': 'æ¶é´', |
| | | 'value': 'æ°é', |
| | | 'variable': 'æ°æ®ç±»å' |
| | | }, |
| | | title='忣ç£
ç§¤æ°æ®è¶å¿', |
| | | color_discrete_map={ |
| | | 'count_under': 'red', |
| | | 'count_in_range': 'green', |
| | | 'count_over': 'blue' |
| | | } |
| | | ) |
| | | |
| | | # èªå®ä¹å¾ä¾ |
| | | fig.for_each_trace(lambda t: t.update(name={ |
| | | 'count_under': 'ä½äºæ å', |
| | | 'count_in_range': '卿 åèå´å
', |
| | | 'count_over': 'é«äºæ å' |
| | | }[t.name])) |
| | | |
| | | # é
ç½®å¾è¡¨ç¼©æ¾åè½ |
| | | fig.update_layout( |
| | | xaxis=dict( |
| | | fixedrange=False, |
| | | rangeslider=dict(visible=True) |
| | | ), |
| | | yaxis=dict(fixedrange=False), |
| | | dragmode='zoom' |
| | | ) |
| | | |
| | | # é
ç½®å¾è¡¨åæ° |
| | | config = {'scrollZoom': True} |
| | | |
| | | # æ¾ç¤ºå¾è¡¨ |
| | | st.plotly_chart(fig, width='stretch', config=config) |
| | | |
| | | # æ¾ç¤ºæå¼ç¹åæ |
| | | st.subheader("æå¼ç¹åæ") |
| | | if not extreme_points.empty: |
| | | # åå¤å±ç¤ºæ°æ® |
| | | display_df = extreme_points[['time', 'count_under', 'count_in_range', 'count_over', 'pass_rate']].copy() |
| | | |
| | | # æ ¼å¼åæ¶é´æ³ |
| | | display_df['time'] = display_df['time'].dt.strftime('%Y-%m-%d %H:%M:%S') |
| | | |
| | | # ä¿®æ¹åå |
| | | display_df.columns = ['æ¶é´æ³', 'è¶
ä¸éæ°å¼', 'èå´å
æ°å¼', 'è¶
ä¸éæ°å¼', 'åæ ¼ç(%)'] |
| | | |
| | | # æ¾ç¤ºæ°æ®è¡¨æ ¼ |
| | | st.dataframe(display_df, use_container_width=True) |
| | | |
| | | # æ¾ç¤ºæå¼ç¹æ°é |
| | | st.info(f"å
±è¯å«å° {len(extreme_points)} 个æå¼ç¹") |
| | | |
| | | # æ·»å 导åºåè½ |
| | | import io |
| | | |
| | | # å建CSVæ°æ® |
| | | csv_buffer = io.StringIO() |
| | | display_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("ééè¶å¿å¾") |
| | | if 'weight' in cleaned_data.columns: |
| | | # å建å¾è¡¨ |
| | | weight_fig = go.Figure() |
| | | |
| | | # æ£æ¥æ¯å¦å
å«éå¼ç¸å
³å段 |
| | | has_thresholds = all(col in cleaned_data.columns for col in ['baseline_value', 'over_difference', 'under_difference']) |
| | | |
| | | # 计ç®å¨æéå¼ |
| | | if has_thresholds: |
| | | # å¤å¶æ°æ®ä»¥é¿å
ä¿®æ¹åå§æ°æ® |
| | | threshold_data = cleaned_data.copy() |
| | | |
| | | # å¤çé¶å¼ |
| | | for col in ['baseline_value', 'over_difference', 'under_difference']: |
| | | threshold_data[col] = threshold_data[col].replace(0, pd.NA) |
| | | |
| | | # ååå¡«å
ç¼ºå¤±å¼ |
| | | threshold_data = threshold_data.ffill() |
| | | |
| | | # 计ç®ä¸ä¸ééå¼ |
| | | threshold_data['upper_threshold'] = threshold_data['over_difference'] |
| | | threshold_data['lower_threshold'] = threshold_data['under_difference'] |
| | | |
| | | # æ è®°è¶
åºéå¼çç¹ |
| | | threshold_data['is_out_of_range'] = (threshold_data['weight'] > threshold_data['upper_threshold']) | (threshold_data['weight'] < threshold_data['lower_threshold']) |
| | | |
| | | # æ·»å åºåå¼çº¿ï¼å¨æï¼ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=threshold_data['time'], |
| | | y=threshold_data['baseline_value'], |
| | | name='åºåå¼', |
| | | line=dict(color='green', width=2), |
| | | opacity=0.7 |
| | | )) |
| | | |
| | | # æ·»å ä¸ééå¼çº¿ï¼å¨æï¼ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=threshold_data['time'], |
| | | y=threshold_data['upper_threshold'], |
| | | name='ä¸ééå¼', |
| | | line=dict(color='red', width=2), |
| | | opacity=0.7 |
| | | )) |
| | | |
| | | # æ·»å ä¸ééå¼çº¿ï¼å¨æï¼ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=threshold_data['time'], |
| | | y=threshold_data['lower_threshold'], |
| | | name='ä¸ééå¼', |
| | | line=dict(color='orange', width=2), |
| | | opacity=0.7 |
| | | )) |
| | | |
| | | # å离æ£å¸¸åå¼å¸¸æ°æ®ç¹ |
| | | normal_data = threshold_data[~threshold_data['is_out_of_range']] |
| | | out_of_range_data = threshold_data[threshold_data['is_out_of_range']] |
| | | |
| | | # æ·»å æ£å¸¸ééç¹ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=normal_data['time'], |
| | | y=normal_data['weight'], |
| | | name='éé (æ£å¸¸)', |
| | | opacity=0.8, |
| | | mode='markers', |
| | | marker=dict( |
| | | size=4, |
| | | color='blue', |
| | | symbol='circle', |
| | | line=dict(width=0, color='blue') |
| | | ) |
| | | )) |
| | | |
| | | # æ·»å å¼å¸¸ééç¹ |
| | | if not out_of_range_data.empty: |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=out_of_range_data['time'], |
| | | y=out_of_range_data['weight'], |
| | | name='éé (å¼å¸¸)', |
| | | opacity=0.8, |
| | | mode='markers', |
| | | marker=dict( |
| | | size=4, |
| | | color='red', |
| | | symbol='triangle-up', |
| | | line=dict(width=2, color='darkred') |
| | | ) |
| | | )) |
| | | else: |
| | | # 没æé弿°æ®ï¼åªæ¾ç¤ºééè¶å¿ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=cleaned_data['time'], |
| | | y=cleaned_data['weight'], |
| | | name='éé', |
| | | line=dict(color='blue', width=2), |
| | | opacity=0.8 |
| | | )) |
| | | st.warning("æ°æ®ä¸ä¸å
å«éå¼ç¸å
³åæ®µï¼æ æ³æ¾ç¤ºéå¼çº¿åå¼å¸¸è¦ç¤ºï¼") |
| | | |
| | | # é
ç½®å¾è¡¨å¸å± |
| | | weight_fig.update_layout( |
| | | title='éééæ¶é´ååè¶å¿', |
| | | xaxis_title='æ¶é´', |
| | | yaxis_title='éé', |
| | | xaxis=dict( |
| | | rangeslider=dict(visible=True), |
| | | type='date', |
| | | fixedrange=False |
| | | ), |
| | | yaxis=dict(fixedrange=False), |
| | | legend=dict( |
| | | orientation="h", |
| | | yanchor="bottom", |
| | | y=1.02, |
| | | xanchor="right", |
| | | x=1 |
| | | ), |
| | | hovermode='x unified', |
| | | height=600, |
| | | dragmode='zoom', |
| | | updatemenus=[ |
| | | dict( |
| | | type="buttons", |
| | | direction="left", |
| | | buttons=list([ |
| | | dict(args=["visible", [True, True, True, True, True]], label="æ¾ç¤ºå
¨é¨", method="restyle"), |
| | | dict(args=["visible", [False, False, False, True, True]], label="ä»
æ¾ç¤ºéé", method="restyle"), |
| | | dict(args=["visible", [True, True, True, False, False]], label="ä»
æ¾ç¤ºéå¼", method="restyle"), |
| | | dict(args=["visible", [True, True, True, True, False]], label="æ¾ç¤ºæ£å¸¸éé", method="restyle"), |
| | | dict(args=["visible", [True, True, True, False, True]], label="æ¾ç¤ºå¼å¸¸éé", method="restyle") |
| | | ]), |
| | | pad={"r": 10, "t": 10}, |
| | | showactive=True, |
| | | x=0.1, |
| | | xanchor="left", |
| | | y=1.1, |
| | | yanchor="top" |
| | | ), |
| | | ] |
| | | ) |
| | | |
| | | # æ¾ç¤ºå¾è¡¨ |
| | | st.plotly_chart(weight_fig, width='stretch', config={'scrollZoom': True}) |
| | | |
| | | # æ¾ç¤ºæ°æ®è¡¨æ ¼ |
| | | st.subheader("åå§æ°æ®") |
| | | st.dataframe(cleaned_data, use_container_width=True) |
| | | |
| | | # æ¾ç¤ºè¯¦ç»ç»è®¡ä¿¡æ¯ |
| | | if stats: |
| | | st.subheader("详ç»ç»è®¡ä¿¡æ¯") |
| | | with st.expander("æ¥ç详ç»ç»è®¡"): |
| | | col_stats1, col_stats2, col_stats3 = st.columns(3) |
| | | |
| | | with col_stats1: |
| | | st.write("**ä½äºæ å**") |
| | | st.write(f"å¹³åå¼: {round(stats['count_under']['mean'], 2)}") |
| | | st.write(f"æ»å: {stats['count_under']['sum']}") |
| | | st.write(f"æå¤§å¼: {stats['count_under']['max']}") |
| | | st.write(f"æå°å¼: {stats['count_under']['min']}") |
| | | |
| | | with col_stats2: |
| | | st.write("**卿 åèå´å
**") |
| | | st.write(f"å¹³åå¼: {round(stats['count_in_range']['mean'], 2)}") |
| | | st.write(f"æ»å: {stats['count_in_range']['sum']}") |
| | | st.write(f"æå¤§å¼: {stats['count_in_range']['max']}") |
| | | st.write(f"æå°å¼: {stats['count_in_range']['min']}") |
| | | |
| | | with col_stats3: |
| | | st.write("**é«äºæ å**") |
| | | st.write(f"å¹³åå¼: {round(stats['count_over']['mean'], 2)}") |
| | | st.write(f"æ»å: {stats['count_over']['sum']}") |
| | | st.write(f"æå¤§å¼: {stats['count_over']['max']}") |
| | | st.write(f"æå°å¼: {stats['count_over']['min']}") |
| | | |
| | | # æ°æ®åºè¿æ¥ç¶æ |
| | | st.sidebar.subheader("æ°æ®åºç¶æ") |
| | | if query_service.db.is_connected(): |
| | | st.sidebar.success("æ°æ®åºè¿æ¥æ£å¸¸") |
| | | else: |
| | | st.sidebar.warning("æ°æ®åºæªè¿æ¥") |
| | | |
| | | if __name__ == "__main__": |
| | | show_sorting_dashboard() |
| | |
| | | # å¤çç¼ºå¤±å¼ |
| | | cleaned_df = cleaned_df.fillna(0) |
| | | |
| | | # ç¡®ä¿æ°æ®ç±»åæ£ç¡® |
| | | cleaned_df['count_under'] = cleaned_df['count_under'].astype(int) |
| | | cleaned_df['count_in_range'] = cleaned_df['count_in_range'].astype(int) |
| | | cleaned_df['count_over'] = cleaned_df['count_over'].astype(int) |
| | | # ç¡®ä¿æ°æ®ç±»åæ£ç¡®ï¼ä»
å½åå卿¶ï¼ |
| | | for col in ['count_under', 'count_in_range', 'count_over']: |
| | | if col in cleaned_df.columns: |
| | | cleaned_df[col] = cleaned_df[col].astype(int) |
| | | |
| | | # ç¡®ä¿timeæ¯datetimeç±»åå¹¶å¤çæ¶åº |
| | | if 'time' in cleaned_df.columns: |
| | | # 转æ¢ä¸ºdatetimeç±»å |
| | | cleaned_df['time'] = pd.to_datetime(cleaned_df['time']) |
| | | |
| | | # å¤çæ¶åº |
| | | # æ£æ¥æ¯å¦å·²ç»ææ¶åºä¿¡æ¯ |
| | | if cleaned_df['time'].dt.tz is None: |
| | | # å¦ææ²¡ææ¶åºä¿¡æ¯ï¼å设æ¯UTCæ¶é´å¹¶æ·»å æ¶åº |
| | | cleaned_df['time'] = cleaned_df['time'].dt.tz_localize('UTC') |
| | | |
| | | # 转æ¢ä¸ºä¸æµ·æ¶åºï¼UTC+8ï¼ |
| | | cleaned_df['time'] = cleaned_df['time'].dt.tz_convert('Asia/Shanghai') |
| | | |
| | | return cleaned_df |
| | | except Exception as e: |
| | |
| | | |
| | | try: |
| | | stats = { |
| | | 'total_records': len(df), |
| | | 'count_under': { |
| | | 'mean': df['count_under'].mean(), |
| | | 'sum': df['count_under'].sum(), |
| | | 'max': df['count_under'].max(), |
| | | 'min': df['count_under'].min() |
| | | }, |
| | | 'count_in_range': { |
| | | 'mean': df['count_in_range'].mean(), |
| | | 'sum': df['count_in_range'].sum(), |
| | | 'max': df['count_in_range'].max(), |
| | | 'min': df['count_in_range'].min() |
| | | }, |
| | | 'count_over': { |
| | | 'mean': df['count_over'].mean(), |
| | | 'sum': df['count_over'].sum(), |
| | | 'max': df['count_over'].max(), |
| | | 'min': df['count_over'].min() |
| | | } |
| | | 'total_records': len(df) |
| | | } |
| | | |
| | | # ä»
å½åå卿¶è®¡ç®ç»è®¡ä¿¡æ¯ |
| | | for col in ['count_under', 'count_in_range', 'count_over']: |
| | | if col in df.columns: |
| | | stats[col] = { |
| | | 'mean': df[col].mean(), |
| | | 'sum': df[col].sum(), |
| | | 'max': df[col].max(), |
| | | 'min': df[col].min() |
| | | } |
| | | return stats |
| | | except Exception as e: |
| | | print(f"计ç®ç»è®¡ä¿¡æ¯å¤±è´¥: {e}") |
| | |
| | | import pandas as pd |
| | | from functools import lru_cache |
| | | from datetime import timedelta |
| | | from app.database.database import DatabaseConnection |
| | | |
| | | class DataQueryService: |
| | | def __init__(self): |
| | | self.db = DatabaseConnection() |
| | | self.timezone_offset = 8 # é»è®¤ä¸å
«åºï¼å京æ¶é´ï¼ |
| | | |
| | | def get_sorting_scale_data(self, start_date, end_date): |
| | | """ |
| | | æ¥è¯¢åæ£ç£
ç§¤æ°æ® |
| | | :param start_date: å¼å§æ¥æ |
| | | :param end_date: ç»ææ¥æ |
| | | :return: å
å«count_under, count_in_range, count_overçæ°æ®æ¡ |
| | | :param start_date: å¼å§æ¥æ (æ¬å°æ¶é´) |
| | | :param end_date: ç»ææ¥æ (æ¬å°æ¶é´) |
| | | :return: å
å«count_under, count_in_range, count_overçæ°æ®æ¡ (è¿åæ¬å°æ¶é´) |
| | | """ |
| | | try: |
| | | # å°æ¬å°æ¶é´è½¬æ¢ä¸ºUTCæ¶é´è¿è¡æ¥è¯¢ |
| | | start_date_utc = start_date - timedelta(hours=self.timezone_offset) |
| | | end_date_utc = end_date - timedelta(hours=self.timezone_offset) |
| | | |
| | | # è¿æ¥æ°æ®åº |
| | | if not self.db.is_connected(): |
| | | if not self.db.connect(): |
| | | return None |
| | | |
| | | connection = self.db.get_connection() |
| | | # connection = self.db.get_connection() |
| | | |
| | | # SQLæ¥è¯¢è¯å¥ |
| | | query = """ |
| | |
| | | """ |
| | | |
| | | # æ§è¡æ¥è¯¢å¹¶è½¬æ¢ä¸ºDataFrame |
| | | df = pd.read_sql(query, connection, params=(start_date, end_date)) |
| | | df = pd.read_sql(query, self.db.get_connection(), params=(start_date_utc, end_date_utc)) |
| | | |
| | | # å°æ¥è¯¢ç»æä¸çUTCæ¶é´è½¬æ¢åæ¬å°æ¶é´ |
| | | if not df.empty and 'time' in df.columns: |
| | | df['time'] = pd.to_datetime(df['time']) + timedelta(hours=self.timezone_offset) |
| | | |
| | | return df |
| | | except Exception as e: |
| | |
| | | import pandas as pd |
| | | from functools import lru_cache |
| | | from datetime import timedelta |
| | | from app.database.database import DatabaseConnection |
| | | |
| | | class ExtruderService: |
| | | def __init__(self): |
| | | self.db = DatabaseConnection() |
| | | self.timezone_offset = 8 # é»è®¤ä¸å
«åºï¼å京æ¶é´ï¼ |
| | | |
| | | def get_extruder_data(self, start_date, end_date): |
| | | """ |
| | | æ¥è¯¢æ¤åºæºæ°æ® |
| | | :param start_date: å¼å§æ¥æ |
| | | :param end_date: ç»ææ¥æ |
| | | :return: å
嫿¤åºæºæ°æ®çæ°æ®æ¡ |
| | | :param start_date: å¼å§æ¥æ (æ¬å°æ¶é´) |
| | | :param end_date: ç»ææ¥æ (æ¬å°æ¶é´) |
| | | :return: å
嫿¤åºæºæ°æ®çæ°æ®æ¡ (è¿åæ¬å°æ¶é´) |
| | | """ |
| | | try: |
| | | # å°æ¬å°æ¶é´è½¬æ¢ä¸ºUTCæ¶é´è¿è¡æ¥è¯¢ |
| | | start_date_utc = start_date - timedelta(hours=self.timezone_offset) |
| | | end_date_utc = end_date - timedelta(hours=self.timezone_offset) |
| | | |
| | | # è¿æ¥æ°æ®åº |
| | | if not self.db.is_connected(): |
| | | if not self.db.connect(): |
| | |
| | | """ |
| | | |
| | | # æ§è¡æ¥è¯¢å¹¶è½¬æ¢ä¸ºDataFrame |
| | | df = pd.read_sql(query, connection, params=(start_date, end_date)) |
| | | df = pd.read_sql(query, connection, params=(start_date_utc, end_date_utc)) |
| | | |
| | | # å°æ¥è¯¢ç»æä¸çUTCæ¶é´è½¬æ¢åæ¬å°æ¶é´ |
| | | if not df.empty and 'time' in df.columns: |
| | | df['time'] = pd.to_datetime(df['time']) + timedelta(hours=self.timezone_offset) |
| | | |
| | | return df |
| | | except Exception as e: |
| | |
| | | batch_df['compound_code_shift'] = batch_df['compound_code'].shift(1) |
| | | # è¥å½åè¡ compound_code ä¸åä¸è¡ä¸åï¼åæ è®°ä¸ºæ¢æ¹ï¼1ï¼ï¼å¦å为 0 ï¼ç¬¬ä¸è¡ç¹æ®å¤ç为 0 |
| | | batch_df['is_batch_change'] = (batch_df['compound_code'] != batch_df['compound_code_shift']).astype(int) |
| | | batch_df['is_batch_change'].iloc[0] = 0 |
| | | # æå°batch_df |
| | | print(batch_df) |
| | | batch_df.loc[batch_df.index[0], 'is_batch_change'] = 0 |
| | | |
| | | # æåæææ¢æ¹äºä»¶çç´¢å¼ |
| | | change_indices = batch_df[batch_df['is_batch_change'] == 1].index.tolist() |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import pandas as pd |
| | | from datetime import timedelta |
| | | from app.database.database import DatabaseConnection |
| | | |
| | | class MainProcessService: |
| | | def __init__(self): |
| | | self.db = DatabaseConnection() |
| | | self.timezone_offset = 8 # é»è®¤ä¸å
«åºï¼å京æ¶é´ï¼ |
| | | |
| | | def _to_utc(self, dt): |
| | | return dt - timedelta(hours=self.timezone_offset) |
| | | |
| | | def _to_local(self, df): |
| | | if not df.empty and 'time' in df.columns: |
| | | df['time'] = pd.to_datetime(df['time']) + timedelta(hours=self.timezone_offset) |
| | | return df |
| | | |
| | | def get_cutting_setting_data(self, start_date, end_date): |
| | | """è·åæµç¨ä¸»éåº¦æ°æ®""" |
| | | try: |
| | | start_utc = self._to_utc(start_date) |
| | | end_utc = self._to_utc(end_date) |
| | | |
| | | if not self.db.is_connected(): |
| | | self.db.connect() |
| | | |
| | | query = """ |
| | | SELECT time, process_main_speed |
| | | FROM public.aics_main_process_cutting_setting |
| | | WHERE time BETWEEN %s AND %s |
| | | ORDER BY time ASC |
| | | """ |
| | | df = pd.read_sql(query, self.db.get_connection(), params=(start_utc, end_utc)) |
| | | return self._to_local(df) |
| | | except Exception as e: |
| | | print(f"è·å主éåº¦æ°æ®å¤±è´¥: {e}") |
| | | return pd.DataFrame() |
| | | |
| | | def get_motor_monitoring_data(self, start_date, end_date): |
| | | """è·åçµæºçæ§çº¿éæ°æ®""" |
| | | try: |
| | | start_utc = self._to_utc(start_date) |
| | | end_utc = self._to_utc(end_date) |
| | | |
| | | if not self.db.is_connected(): |
| | | self.db.connect() |
| | | |
| | | query = """ |
| | | SELECT time, m1_line_speed, m2_line_speed |
| | | FROM public.aics_main_process_motor_monitoring |
| | | WHERE time BETWEEN %s AND %s |
| | | ORDER BY time ASC |
| | | """ |
| | | df = pd.read_sql(query, self.db.get_connection(), params=(start_utc, end_utc)) |
| | | return self._to_local(df) |
| | | except Exception as e: |
| | | print(f"è·åçµæºçæ§æ°æ®å¤±è´¥: {e}") |
| | | return pd.DataFrame() |
| | | |
| | | def get_temperature_control_data(self, start_date, end_date): |
| | | """è·åä¸ç°æ¤åºæºæ¸©åº¦è®¾å®ä¸æ¾ç¤ºæ°æ®""" |
| | | try: |
| | | start_utc = self._to_utc(start_date) |
| | | end_utc = self._to_utc(end_date) |
| | | |
| | | if not self.db.is_connected(): |
| | | self.db.connect() |
| | | |
| | | query = """ |
| | | SELECT |
| | | time, |
| | | nakata_extruder_screw_set_temp, |
| | | nakata_extruder_screw_display_temp, |
| | | nakata_extruder_rear_barrel_set_temp, |
| | | nakata_extruder_rear_barrel_display_temp, |
| | | nakata_extruder_front_barrel_set_temp, |
| | | nakata_extruder_front_barrel_display_temp, |
| | | nakata_extruder_head_set_temp, |
| | | nakata_extruder_head_display_temp |
| | | FROM public.aics_main_process_temperature_control_setting |
| | | WHERE time BETWEEN %s AND %s |
| | | ORDER BY time ASC |
| | | """ |
| | | df = pd.read_sql(query, self.db.get_connection(), params=(start_utc, end_utc)) |
| | | return self._to_local(df) |
| | | except Exception as e: |
| | | print(f"è·å温度æ§å¶æ°æ®å¤±è´¥: {e}") |
| | | return pd.DataFrame() |
| | |
| | | import streamlit as st |
| | | import plotly.express as px |
| | | import pandas as pd |
| | | from datetime import datetime, timedelta |
| | | from app.services.data_query_service import DataQueryService |
| | | from app.services.data_processing_service import DataProcessingService |
| | | from app.services.extruder_service import ExtruderService |
| | | from app.pages.sorting_dashboard import show_sorting_dashboard |
| | | 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 |
| | | |
| | | # 设置页é¢é
ç½® |
| | | st.set_page_config( |
| | |
| | | layout="wide" |
| | | ) |
| | | |
| | | # 左侧èåå¯¼èª |
| | | st.sidebar.title("ç³»ç»å¯¼èª") |
| | | menu = st.sidebar.radio( |
| | | "鿩忿¨¡å", |
| | | ["忣ç£
秤", "æ¤åºæº"], |
| | | index=0, |
| | | key="main_menu" |
| | | # å®ä¹é¡µé¢ |
| | | sorting_page = st.Page( |
| | | show_sorting_dashboard, |
| | | title="忣ç£
秤", |
| | | icon="âï¸", |
| | | url_path="sorting" |
| | | ) |
| | | |
| | | # å³ä¾§å
容åºå |
| | | if menu == "忣ç£
秤": |
| | | # åå§åæå¡ |
| | | query_service = DataQueryService() |
| | | processing_service = DataProcessingService() |
| | | extruder_page = st.Page( |
| | | show_extruder_dashboard, |
| | | title="æ¤åºæº", |
| | | icon="ð", |
| | | url_path="extruder" |
| | | ) |
| | | |
| | | # 页颿 é¢ |
| | | st.title("忣ç£
ç§¤æ°æ®åæ") |
| | | main_process_page = st.Page( |
| | | show_main_process_dashboard, |
| | | title="主æµç¨æ§å¶", |
| | | icon="âï¸", |
| | | url_path="main_process" |
| | | ) |
| | | |
| | | # æ¥è¯¢æ¡ä»¶åºåï¼è¿ç§»å°å³ä¾§é¡¶é¨ï¼ |
| | | st.subheader("æ¥è¯¢é
ç½®") |
| | | col1, col2, col3 = st.columns([2, 2, 1]) |
| | | |
| | | with col1: |
| | | start_date = st.date_input("å¼å§æ¥æ", datetime.now() - timedelta(days=7), key="sorting_start_date") |
| | | |
| | | with col2: |
| | | end_date = st.date_input("ç»ææ¥æ", datetime.now(), key="sorting_end_date") |
| | | |
| | | with col3: |
| | | st.write("") # å ä½ |
| | | query_button = st.button("æ¥è¯¢æ°æ®", key="sorting_query") |
| | | comprehensive_page = st.Page( |
| | | show_comprehensive_dashboard, |
| | | title="综ååæ", |
| | | icon="ð", |
| | | url_path="comprehensive" |
| | | ) |
| | | |
| | | # 转æ¢ä¸ºdatetime对象ï¼å
嫿¶é´ï¼ |
| | | start_datetime = datetime.combine(start_date, datetime.min.time()) |
| | | end_datetime = datetime.combine(end_date, datetime.max.time()) |
| | | # ä¾§è¾¹æ 页èä¿¡æ¯ |
| | | def show_footer(): |
| | | st.sidebar.markdown("---") |
| | | st.sidebar.markdown("© 2026 æ°æ®åæç³»ç»") |
| | | |
| | | # æ¥è¯¢æé®å¤ç |
| | | if query_button: |
| | | # éªè¯æ¥æèå´ |
| | | if start_datetime > end_datetime: |
| | | st.error("å¼å§æ¥æä¸è½æäºç»ææ¥æï¼") |
| | | else: |
| | | # æ¾ç¤ºå è½½ç¶æ |
| | | with st.spinner("æ£å¨æ¥è¯¢æ°æ®..."): |
| | | # æ¥è¯¢æ°æ® |
| | | raw_data = query_service.get_sorting_scale_data(start_datetime, end_datetime) |
| | | |
| | | if raw_data is None or raw_data.empty: |
| | | st.warning("æªæ¥è¯¢å°æ°æ®ï¼è¯·æ£æ¥æ¥æèå´ææ°æ®åºè¿æ¥ï¼") |
| | | st.session_state['query_results'] = None |
| | | else: |
| | | # æ¸
æ´æ°æ® |
| | | cleaned_data = processing_service.clean_data(raw_data) |
| | | |
| | | # 计ç®ç»è®¡ä¿¡æ¯ |
| | | stats = processing_service.calculate_statistics(cleaned_data) |
| | | |
| | | # åææå¼ç¹ |
| | | extreme_analysis = processing_service.analyze_extreme_points(cleaned_data) |
| | | extreme_points = extreme_analysis['extreme_points'] |
| | | phase_maxima = extreme_analysis['phase_maxima'] |
| | | overall_pass_rate = extreme_analysis['overall_pass_rate'] |
| | | |
| | | # ç¼åç»æ |
| | | st.session_state['query_results'] = { |
| | | 'cleaned_data': cleaned_data, |
| | | 'stats': stats, |
| | | 'extreme_points': extreme_points, |
| | | 'phase_maxima': phase_maxima, |
| | | 'overall_pass_rate': overall_pass_rate |
| | | } |
| | | |
| | | # æ¾ç¤ºæ°æ®æ¦è§ |
| | | st.subheader("æ°æ®æ¦è§") |
| | | col1, col2, col3, col4 = st.columns(4) |
| | | |
| | | with col1: |
| | | st.metric("æ»è®°å½æ°", stats.get('total_records', 0)) |
| | | |
| | | with col2: |
| | | st.metric("å¹³ååæ ¼æ°", round(stats.get('count_in_range', {}).get('mean', 0), 2)) |
| | | |
| | | with col3: |
| | | st.metric("æ°æ®æ¶é´èå´", f"{cleaned_data['time'].min()} è³ {cleaned_data['time'].max()}") |
| | | |
| | | with col4: |
| | | st.metric("æ´ä½åæ ¼ç", f"{overall_pass_rate}%") |
| | | |
| | | # æ¾ç¤ºè¶å¿å¾ |
| | | st.subheader("æ°æ®è¶å¿å¾") |
| | | fig = px.line( |
| | | cleaned_data, |
| | | x='time', |
| | | y=['count_under', 'count_in_range', 'count_over'], |
| | | labels={ |
| | | 'time': 'æ¶é´', |
| | | 'value': 'æ°é', |
| | | 'variable': 'æ°æ®ç±»å' |
| | | }, |
| | | title='忣ç£
ç§¤æ°æ®è¶å¿', |
| | | color_discrete_map={ |
| | | 'count_under': 'red', |
| | | 'count_in_range': 'green', |
| | | 'count_over': 'blue' |
| | | } |
| | | ) |
| | | |
| | | # èªå®ä¹å¾ä¾ |
| | | fig.for_each_trace(lambda t: t.update(name={ |
| | | 'count_under': 'ä½äºæ å', |
| | | 'count_in_range': '卿 åèå´å
', |
| | | 'count_over': 'é«äºæ å' |
| | | }[t.name])) |
| | | |
| | | # é
ç½®å¾è¡¨ç¼©æ¾åè½ |
| | | fig.update_layout( |
| | | xaxis=dict( |
| | | fixedrange=False |
| | | ), |
| | | yaxis=dict( |
| | | fixedrange=False |
| | | ), |
| | | dragmode='zoom' |
| | | ) |
| | | |
| | | # é
ç½®å¾è¡¨åæ° |
| | | config = { |
| | | 'scrollZoom': True |
| | | } |
| | | |
| | | # æ¾ç¤ºå¾è¡¨ |
| | | st.plotly_chart(fig, use_container_width=True, config=config) |
| | | |
| | | # æ¾ç¤ºæå¼ç¹åæ |
| | | st.subheader("æå¼ç¹åæ") |
| | | if not extreme_points.empty: |
| | | # åå¤å±ç¤ºæ°æ® |
| | | display_df = extreme_points[['time', 'count_under', 'count_in_range', 'count_over', 'pass_rate']].copy() |
| | | |
| | | # æ ¼å¼åæ¶é´æ³ |
| | | display_df['time'] = display_df['time'].dt.strftime('%Y-%m-%d %H:%M:%S') |
| | | |
| | | # ä¿®æ¹åå |
| | | display_df.columns = ['æ¶é´æ³', 'è¶
ä¸éæ°å¼', 'èå´å
æ°å¼', 'è¶
ä¸éæ°å¼', 'åæ ¼ç(%)'] |
| | | |
| | | # æ¾ç¤ºæ°æ®è¡¨æ ¼ |
| | | st.dataframe(display_df, use_container_width=True) |
| | | |
| | | # æ¾ç¤ºæå¼ç¹æ°é |
| | | st.info(f"å
±è¯å«å° {len(extreme_points)} 个æå¼ç¹") |
| | | |
| | | # æ·»å 导åºåè½ |
| | | import csv |
| | | import io |
| | | |
| | | # å建CSVæ°æ® |
| | | csv_buffer = io.StringIO() |
| | | display_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("ééè¶å¿å¾") |
| | | if 'weight' in cleaned_data.columns: |
| | | # å建ééè¶å¿å¾ |
| | | import plotly.graph_objects as go |
| | | |
| | | # å建å¾è¡¨ |
| | | weight_fig = go.Figure() |
| | | |
| | | # æ£æ¥æ¯å¦å
å«éå¼ç¸å
³å段 |
| | | has_thresholds = all(col in cleaned_data.columns for col in ['baseline_value', 'over_difference', 'under_difference']) |
| | | |
| | | # 计ç®å¨æéå¼ |
| | | if has_thresholds: |
| | | # å¤å¶æ°æ®ä»¥é¿å
ä¿®æ¹åå§æ°æ® |
| | | threshold_data = cleaned_data.copy() |
| | | |
| | | # å¤çé¶å¼ |
| | | for col in ['baseline_value', 'over_difference', 'under_difference']: |
| | | threshold_data[col] = threshold_data[col].replace(0, pd.NA) |
| | | |
| | | # ååå¡«å
ç¼ºå¤±å¼ |
| | | threshold_data = threshold_data.ffill() |
| | | |
| | | # 计ç®ä¸ä¸ééå¼ |
| | | threshold_data['upper_threshold'] = threshold_data['over_difference'] |
| | | threshold_data['lower_threshold'] = threshold_data['under_difference'] |
| | | |
| | | # æ è®°è¶
åºéå¼çç¹ |
| | | threshold_data['is_out_of_range'] = (threshold_data['weight'] > threshold_data['upper_threshold']) | (threshold_data['weight'] < threshold_data['lower_threshold']) |
| | | |
| | | # æ·»å åºåå¼çº¿ï¼å¨æï¼ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=threshold_data['time'], |
| | | y=threshold_data['baseline_value'], |
| | | name='åºåå¼', |
| | | line=dict(color='green', width=2), |
| | | opacity=0.7 |
| | | )) |
| | | |
| | | # æ·»å ä¸ééå¼çº¿ï¼å¨æï¼ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=threshold_data['time'], |
| | | y=threshold_data['upper_threshold'], |
| | | name='ä¸ééå¼', |
| | | line=dict(color='red', width=2), |
| | | opacity=0.7 |
| | | )) |
| | | |
| | | # æ·»å ä¸ééå¼çº¿ï¼å¨æï¼ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=threshold_data['time'], |
| | | y=threshold_data['lower_threshold'], |
| | | name='ä¸ééå¼', |
| | | line=dict(color='orange', width=2), |
| | | opacity=0.7 |
| | | )) |
| | | |
| | | # å离æ£å¸¸åå¼å¸¸æ°æ®ç¹ |
| | | normal_data = threshold_data[~threshold_data['is_out_of_range']] |
| | | out_of_range_data = threshold_data[threshold_data['is_out_of_range']] |
| | | |
| | | # æ·»å æ£å¸¸ééç¹ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=normal_data['time'], |
| | | y=normal_data['weight'], |
| | | name='éé (æ£å¸¸)', |
| | | opacity=0.8, |
| | | mode='markers', |
| | | marker=dict( |
| | | size=4, |
| | | color='blue', |
| | | symbol='circle', |
| | | line=dict( |
| | | width=0, |
| | | color='blue' |
| | | ) |
| | | ) |
| | | )) |
| | | |
| | | # æ·»å å¼å¸¸ééç¹ |
| | | if not out_of_range_data.empty: |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=out_of_range_data['time'], |
| | | y=out_of_range_data['weight'], |
| | | name='éé (å¼å¸¸)', |
| | | opacity=0.8, |
| | | mode='markers', |
| | | marker=dict( |
| | | size=4, |
| | | color='red', |
| | | symbol='triangle-up', |
| | | line=dict( |
| | | width=2, |
| | | color='darkred' |
| | | ) |
| | | ) |
| | | )) |
| | | else: |
| | | # 没æé弿°æ®ï¼åªæ¾ç¤ºééè¶å¿ |
| | | weight_fig.add_trace(go.Scatter( |
| | | x=cleaned_data['time'], |
| | | y=cleaned_data['weight'], |
| | | name='éé', |
| | | line=dict(color='blue', width=2), |
| | | opacity=0.8 |
| | | )) |
| | | st.warning("æ°æ®ä¸ä¸å
å«éå¼ç¸å
³åæ®µï¼æ æ³æ¾ç¤ºéå¼çº¿åå¼å¸¸è¦ç¤ºï¼") |
| | | |
| | | # é
ç½®å¾è¡¨å¸å± |
| | | weight_fig.update_layout( |
| | | title='éééæ¶é´ååè¶å¿', |
| | | xaxis_title='æ¶é´', |
| | | yaxis_title='éé', |
| | | xaxis=dict( |
| | | rangeslider=dict( |
| | | visible=True |
| | | ), |
| | | type='date', |
| | | fixedrange=False |
| | | ), |
| | | yaxis=dict( |
| | | fixedrange=False |
| | | ), |
| | | legend=dict( |
| | | orientation="h", |
| | | yanchor="bottom", |
| | | y=1.02, |
| | | xanchor="right", |
| | | x=1 |
| | | ), |
| | | hovermode='x unified', |
| | | height=600, |
| | | dragmode='zoom', |
| | | # æ·»å èªå®ä¹å·¥å
·æ æé® |
| | | updatemenus=[ |
| | | dict( |
| | | type="buttons", |
| | | direction="left", |
| | | buttons=list([ |
| | | dict( |
| | | args=["visible", [True, True, True, True, True]], |
| | | label="æ¾ç¤ºå
¨é¨", |
| | | method="restyle" |
| | | ), |
| | | dict( |
| | | args=["visible", [False, False, False, True, True]], |
| | | label="ä»
æ¾ç¤ºéé", |
| | | method="restyle" |
| | | ), |
| | | dict( |
| | | args=["visible", [True, True, True, False, False]], |
| | | label="ä»
æ¾ç¤ºéå¼", |
| | | method="restyle" |
| | | ), |
| | | dict( |
| | | args=["visible", [True, True, True, True, False]], |
| | | label="æ¾ç¤ºæ£å¸¸éé", |
| | | method="restyle" |
| | | ), |
| | | dict( |
| | | args=["visible", [True, True, True, False, True]], |
| | | label="æ¾ç¤ºå¼å¸¸éé", |
| | | method="restyle" |
| | | ) |
| | | ]), |
| | | pad={"r": 10, "t": 10}, |
| | | showactive=True, |
| | | x=0.1, |
| | | xanchor="left", |
| | | y=1.1, |
| | | yanchor="top" |
| | | ), |
| | | ] |
| | | ) |
| | | |
| | | # é
ç½®å¾è¡¨åæ° |
| | | config = { |
| | | 'scrollZoom': True, |
| | | 'toImageButtonOptions': { |
| | | 'format': 'png', |
| | | 'filename': 'ééè¶å¿å¾', |
| | | 'height': 600, |
| | | 'width': 1000, |
| | | 'scale': 1 |
| | | } |
| | | } |
| | | |
| | | # æ¾ç¤ºå¾è¡¨ |
| | | st.plotly_chart(weight_fig, use_container_width=True, config=config) |
| | | |
| | | |
| | | # æ¾ç¤ºæ°æ®è¡¨æ ¼ |
| | | st.subheader("åå§æ°æ®") |
| | | st.dataframe(cleaned_data, use_container_width=True) |
| | | |
| | | # æ¾ç¤ºè¯¦ç»ç»è®¡ä¿¡æ¯ |
| | | if stats: |
| | | st.subheader("详ç»ç»è®¡ä¿¡æ¯") |
| | | with st.expander("æ¥ç详ç»ç»è®¡"): |
| | | col_stats1, col_stats2, col_stats3 = st.columns(3) |
| | | |
| | | with col_stats1: |
| | | st.write("**ä½äºæ å**") |
| | | st.write(f"å¹³åå¼: {round(stats['count_under']['mean'], 2)}") |
| | | st.write(f"æ»å: {stats['count_under']['sum']}") |
| | | st.write(f"æå¤§å¼: {stats['count_under']['max']}") |
| | | st.write(f"æå°å¼: {stats['count_under']['min']}") |
| | | |
| | | with col_stats2: |
| | | st.write("**卿 åèå´å
**") |
| | | st.write(f"å¹³åå¼: {round(stats['count_in_range']['mean'], 2)}") |
| | | st.write(f"æ»å: {stats['count_in_range']['sum']}") |
| | | st.write(f"æå¤§å¼: {stats['count_in_range']['max']}") |
| | | st.write(f"æå°å¼: {stats['count_in_range']['min']}") |
| | | |
| | | with col_stats3: |
| | | st.write("**é«äºæ å**") |
| | | st.write(f"å¹³åå¼: {round(stats['count_over']['mean'], 2)}") |
| | | st.write(f"æ»å: {stats['count_over']['sum']}") |
| | | st.write(f"æå¤§å¼: {stats['count_over']['max']}") |
| | | st.write(f"æå°å¼: {stats['count_over']['min']}") |
| | | # 导èªé
ç½® |
| | | pg = st.navigation({ |
| | | "综ååæ": [comprehensive_page], |
| | | "å项åæ": [sorting_page, extruder_page, main_process_page] |
| | | }) |
| | | |
| | | # æ°æ®åºè¿æ¥ç¶æ |
| | | st.sidebar.subheader("æ°æ®åºç¶æ") |
| | | if 'query_service' in locals() and query_service.db.is_connected(): |
| | | st.sidebar.success("æ°æ®åºè¿æ¥æ£å¸¸") |
| | | else: |
| | | st.sidebar.warning("æ°æ®åºæªè¿æ¥") |
| | | # è¿è¡å¯¼èª |
| | | pg.run() |
| | | |
| | | elif menu == "æ¤åºæº": |
| | | # åå§åæå¡ |
| | | extruder_service = ExtruderService() |
| | | processing_service = DataProcessingService() |
| | | |
| | | # 页颿 é¢ |
| | | st.title("æ¤åºæºæ°æ®åæ") |
| | | |
| | | # æ¥è¯¢æ¡ä»¶åºå |
| | | st.subheader("æ¥è¯¢é
ç½®") |
| | | col1, col2, col3 = st.columns([2, 2, 1]) |
| | | |
| | | with col1: |
| | | start_date = st.date_input("å¼å§æ¥æ", datetime.now() - timedelta(days=7), key="extruder_start_date") |
| | | |
| | | with col2: |
| | | end_date = st.date_input("ç»ææ¥æ", datetime.now(), key="extruder_end_date") |
| | | |
| | | with col3: |
| | | st.write("") # å ä½ |
| | | query_button = st.button("æ¥è¯¢æ°æ®", key="extruder_query") |
| | | |
| | | # 转æ¢ä¸º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) |
| | | |
| | | # åæåæ°è¶å¿ |
| | | # trends = extruder_service.analyze_parameter_trends(cleaned_data) |
| | | |
| | | |
| | | # ç¼åç»æ |
| | | st.session_state['extruder_results'] = { |
| | | 'cleaned_data': cleaned_data, |
| | | 'batch_changes': batch_changes, |
| | | # 'trends': trends, |
| | | } |
| | | |
| | | # æ¾ç¤ºæ°æ®æ¦è§ |
| | | 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: |
| | | # åå»ºæ¢ææä½å¯è§åå¾è¡¨ |
| | | import plotly.graph_objects as go |
| | | |
| | | # åå¤å¾è¡¨æ°æ® |
| | | 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, use_container_width=True) |
| | | |
| | | # æ·»å æ°æ®å¯¼åºåè½ |
| | | import csv |
| | | import io |
| | | import pandas as pd |
| | | |
| | | # åå¤å¯¼åºæ°æ® |
| | | 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' in locals() and extruder_service.db.is_connected(): |
| | | st.sidebar.success("æ°æ®åºè¿æ¥æ£å¸¸") |
| | | else: |
| | | st.sidebar.warning("æ°æ®åºæªè¿æ¥") |
| | | |
| | | # 页è |
| | | st.sidebar.markdown("---") |
| | | st.sidebar.markdown("© 2026 æ°æ®åæç³»ç»") |
| | | |
| | | # ç¼åæ¸
çæºå¶ |
| | | def clear_cache(): |
| | | """æ¸
çä¼è¯ç¼åï¼å½åæ¢èåææ´æ°æ¥è¯¢æ¡ä»¶æ¶è°ç¨""" |
| | | if 'query_results' in st.session_state: |
| | | del st.session_state['query_results'] |
| | | if 'extruder_results' in st.session_state: |
| | | del st.session_state['extruder_results'] |
| | | |
| | | # åå§åä¼è¯ç¶æ |
| | | if 'query_results' not in st.session_state: |
| | | st.session_state['query_results'] = None |
| | | if 'extruder_results' not in st.session_state: |
| | | st.session_state['extruder_results'] = None |
| | | |
| | | # çå¬èååæ¢ï¼æ¸
çç¼å |
| | | if 'previous_menu' not in st.session_state: |
| | | st.session_state['previous_menu'] = menu |
| | | elif st.session_state['previous_menu'] != menu: |
| | | clear_cache() |
| | | st.session_state['previous_menu'] = menu |
| | | |
| | | # å
³éæ°æ®åºè¿æ¥ï¼å½åºç¨ç»ææ¶ï¼ |
| | | def on_app_close(): |
| | | if 'query_service' in locals(): |
| | | query_service.close_connection() |
| | | if 'extruder_service' in locals(): |
| | | extruder_service.close_connection() |
| | | |
| | | # 注ååºç¨å
³éåè° |
| | | st.session_state['app_close'] = on_app_close |
| | | # æ¾ç¤ºé¡µè |
| | | show_footer() |