Spaces:
Sleeping
Sleeping
import gradio as gr | |
import pandas as pd | |
import plotly.graph_objects as go | |
class GeneralMetricsDisplay: | |
def __init__(self): | |
self.plots = [] | |
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 | |
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 | |
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 | |
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') | |
] |