Efficiency-Agent / src /ui /graphs /general_graphs.py
mriusero
core: data shape
f5f591a
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')
]