| | import gradio as gr |
| | import pandas as pd |
| | import plotly.graph_objects as go |
| |
|
| |
|
| | class GeneralMetricsDisplay: |
| | def __init__(self): |
| | self.plots = [] |
| |
|
| | @staticmethod |
| | def kpi_rate(percentage, title="KPI"): |
| | if percentage is None or not (0 <= percentage <= 100): |
| | fig = go.Figure() |
| | fig.update_layout( |
| | template='plotly_dark', |
| | width=320, |
| | height=150, |
| | margin=dict(l=10, r=10, t=10, b=10), |
| | annotations=[dict( |
| | text="No data", |
| | showarrow=False, |
| | font=dict(size=16, color="white"), |
| | x=0.5, y=0.5, xanchor="center", yanchor="middle" |
| | )] |
| | ) |
| | return fig |
| | fig = go.Figure(data=[go.Pie( |
| | values=[percentage, 100 - percentage], |
| | labels=['', ''], |
| | hole=0.6, |
| | marker_colors=['#2CFCFF', '#444444'], |
| | textinfo='none', |
| | hoverinfo='skip', |
| | sort=False, |
| | domain=dict(x=[0.4, 0.95], y=[0.15, 0.85]) |
| | )]) |
| | fig.update_layout( |
| | template='plotly_dark', |
| | annotations=[ |
| | dict( |
| | text=f"{percentage:.0f}%", |
| | x=0.675, y=0.5, |
| | font_size=20, |
| | showarrow=False, |
| | font=dict(color="white"), |
| | xanchor="center", yanchor="middle" |
| | ), |
| | dict( |
| | text=title, |
| | x=0.05, y=0.5, |
| | showarrow=False, |
| | font=dict(size=16, color="white"), |
| | xanchor="left", yanchor="middle" |
| | ) |
| | ], |
| | showlegend=False, |
| | margin=dict(l=10, r=10, t=10, b=10), |
| | width=320, |
| | height=150 |
| | ) |
| | return fig |
| |
|
| | @staticmethod |
| | def kpi_value(value, title="Valeur"): |
| | width = 360 |
| | if value is None or (isinstance(value, str) and (value.strip() == "" or value.strip().isdigit() and len(value.strip()) > 8)): |
| | fig = go.Figure() |
| | fig.update_layout( |
| | template='plotly_dark', |
| | width=width, |
| | height=125, |
| | margin=dict(l=30, r=0, t=0, b=30), |
| | xaxis=dict(visible=False), |
| | yaxis=dict(visible=False), |
| | plot_bgcolor='#111111', |
| | paper_bgcolor='#111111', |
| | annotations=[dict( |
| | text="No data", |
| | showarrow=False, |
| | font=dict(size=16, color="white"), |
| | x=0.5, y=0.5, |
| | xanchor="center", yanchor="middle" |
| | )] |
| | ) |
| | return fig |
| | try: |
| | if isinstance(value, (int, float)): |
| | formatted = f"{int(value)}" if float(value).is_integer() else f"{float(value):.2f}" |
| | else: |
| | td = pd.to_timedelta(value) |
| | total_seconds = int(td.total_seconds()) |
| | hours, remainder = divmod(total_seconds, 3600) |
| | minutes, seconds = divmod(remainder, 60) |
| | formatted = f"{hours}h {minutes}m {seconds}s" |
| | except (ValueError, TypeError): |
| | try: |
| | numeric_value = float(value) |
| | formatted = f"{int(numeric_value)}" if numeric_value.is_integer() else f"{numeric_value:.2f}" |
| | except (ValueError, TypeError): |
| | formatted = str(value) |
| | fig = go.Figure() |
| | fig.add_annotation( |
| | text=f"<b>{formatted}</b>", |
| | x=0.5, y=0.5, |
| | showarrow=False, |
| | font=dict(size=24, color="white"), |
| | xanchor="center", yanchor="middle" |
| | ) |
| | fig.add_annotation( |
| | text=title, |
| | x=0.5, y=1.8, |
| | showarrow=False, |
| | font=dict(size=16, color="lightgray"), |
| | xanchor="center", yanchor="middle" |
| | ) |
| | fig.update_layout( |
| | template='plotly_dark', |
| | width=width, |
| | height=150, |
| | margin=dict(l=40, r=0, t=0, b=40), |
| | xaxis=dict(visible=False), |
| | yaxis=dict(visible=False), |
| | plot_bgcolor='#111111', |
| | paper_bgcolor='#111111' |
| | ) |
| | return fig |
| |
|
| | @staticmethod |
| | def get_max_part_id(df): |
| | if not df.empty and 'Part ID' in df.columns: |
| | try: |
| | numeric_ids = pd.to_numeric(df['Part ID'], errors='coerce') |
| | return int(numeric_ids.dropna().max()) |
| | except Exception: |
| | return None |
| | return None |
| |
|
| | @staticmethod |
| | def pareto(issues_df, error_col='Error Type'): |
| | if issues_df is None or issues_df.empty: |
| | fig = go.Figure() |
| | fig.update_layout( |
| | template='plotly_dark', |
| | annotations=[dict( |
| | text="No Error", |
| | showarrow=False, |
| | font=dict(size=16, color="white") |
| | )] |
| | ) |
| | return fig |
| | issues_df['Downtime Start'] = pd.to_datetime(issues_df['Downtime Start'], errors='coerce') |
| | issues_df['Downtime End'] = pd.to_datetime(issues_df['Downtime End'], errors='coerce') |
| | issues_df['Downtime Duration'] = (issues_df['Downtime End'] - issues_df[ |
| | 'Downtime Start']).dt.total_seconds() / 60 |
| | issues_df = issues_df.dropna(subset=['Downtime Duration']) |
| | grouped = issues_df.groupby(error_col)['Downtime Duration'].sum().sort_values(ascending=False) |
| | if grouped.empty: |
| | fig = go.Figure() |
| | fig.update_layout( |
| | template='plotly_dark', |
| | annotations=[dict( |
| | text="No Error", |
| | showarrow=False, |
| | font=dict(size=16, color="white") |
| | )] |
| | ) |
| | return fig |
| | cumulative = grouped.cumsum() / grouped.sum() * 100 |
| | labels = grouped.index.tolist() |
| | durations = grouped.values |
| | fig = go.Figure() |
| | fig.add_trace( |
| | go.Bar( |
| | x=labels, |
| | y=durations, |
| | name='Downtime (min)', |
| | marker_color='#2CFCFF', |
| | yaxis='y1' |
| | ) |
| | ) |
| | fig.add_trace(go.Scatter( |
| | x=labels, |
| | y=cumulative, |
| | name='Cumulative %', |
| | yaxis='y2', |
| | mode='lines+markers', |
| | line=dict(color='orange', width=2), |
| | marker=dict(size=8) |
| | )) |
| | fig.update_layout( |
| | template='plotly_dark', |
| | title="Pareto of errors by downtime", |
| | xaxis=dict(title="Errors"), |
| | yaxis=dict( |
| | title='Downtime (minutes)', |
| | showgrid=False, |
| | side='left' |
| | ), |
| | yaxis2=dict( |
| | title='Cumulative percentage (%)', |
| | overlaying='y', |
| | side='right', |
| | range=[0, 110], |
| | showgrid=False, |
| | tickformat='%' |
| | ), |
| | legend=dict(x=0.7, y=1.1), |
| | margin=dict(l=70, r=70, t=50, b=50), |
| | ) |
| | return fig |
| |
|
| | def general_block(self, all_tools_df, issues_df, status): |
| | header = f"Metrics Summary" |
| | html_content = f""" |
| | <div style="display: flex; align-items: center; justify-content: flex-start; width: 100%;"> |
| | <div style="flex: 0 0 2%; border-top: 1px solid white;"></div> |
| | <h2 style="flex: 0 0 auto; margin: 0 10px;">{header}</h2> |
| | <div style="flex: 1; border-top: 1px solid white;"></div> |
| | </div> |
| | """ |
| | gr.HTML(html_content) |
| | with gr.Row(): |
| | with gr.Group(): |
| | with gr.Row(height=125): |
| | total_count = gr.Plot( |
| | self.kpi_value( |
| | value=self.get_max_part_id(all_tools_df), |
| | title="Total Count (parts)" |
| | ) |
| | ) |
| | total_time = gr.Plot( |
| | self.kpi_value( |
| | value=status.get("opening_time", "0 days 00:00:00"), |
| | title="Total Time" |
| | ) |
| | ) |
| | mtbf_plot = gr.Plot( |
| | self.kpi_value( |
| | value=status.get("MTBF", "0 days 00:00:00"), |
| | title="MTBF" |
| | ) |
| | ) |
| | mttr_plot = gr.Plot( |
| | self.kpi_value( |
| | value=status.get("MTTR", "0 days 00:00:00"), |
| | title="MTTR" |
| | ) |
| | ) |
| | with gr.Row(): |
| | with gr.Column(scale=1): |
| | with gr.Group(): |
| | with gr.Row(height=150): |
| | oee_plot = gr.Plot( |
| | self.kpi_rate( |
| | percentage=status.get('OEE', 0), |
| | title="OEE" |
| | ) |
| | ) |
| | with gr.Row(height=150): |
| | quality_rate_plot = gr.Plot( |
| | self.kpi_rate( |
| | percentage=status.get("quality_rate", 0), |
| | title="Quality Rate" |
| | ) |
| | ) |
| | with gr.Row(height=150): |
| | availability_plot = gr.Plot( |
| | self.kpi_rate( |
| | percentage=status.get("availability_rate", 0), |
| | title="Availability" |
| | ) |
| | ) |
| | with gr.Column(scale=10): |
| | with gr.Group(): |
| | with gr.Row(height=450): |
| | pareto = gr.Plot( |
| | self.pareto(issues_df, error_col='Error Code') |
| | ) |
| | self.plots = [ |
| | total_count, total_time, |
| | oee_plot, quality_rate_plot, availability_plot, |
| | mtbf_plot, mttr_plot, |
| | pareto, |
| | ] |
| | return self.plots |
| |
|
| | def refresh(self, all_tools_df, issues_df, status): |
| | return [ |
| | self.kpi_value(value=self.get_max_part_id(all_tools_df), title="Total Count (parts)"), |
| | self.kpi_value(value=status.get("opening_time", "0 days 00:00:00"), title="Total Time"), |
| | self.kpi_rate(percentage=status.get('OEE', 0), title="OEE"), |
| | self.kpi_rate(percentage=status.get("quality_rate", 0), title="Quality Rate"), |
| | self.kpi_rate(percentage=status.get("availability_rate", 0), title="Availability"), |
| | self.kpi_value(value=status.get("MTBF", "0 days 00:00:00"), title="MTBF"), |
| | self.kpi_value(value=status.get("MTTR", "0 days 00:00:00"), title="MTTR"), |
| | self.pareto(issues_df, error_col='Error Code') |
| | ] |