mriusero commited on
Commit
f731ddb
·
1 Parent(s): f8d9c8a

feat: general metrics

Browse files
src/production/downtime.py CHANGED
@@ -5,60 +5,60 @@ machine_errors = {
5
  "description": "Calibration Error",
6
  "cause": "The machine is not correctly calibrated.",
7
  "solution": "Recalibrate the machine according to the manufacturer's specifications.",
8
- "downtime": timedelta(hours=2)
9
  },
10
  "E002": {
11
  "description": "Motor Overheating",
12
  "cause": "The motor has exceeded the maximum operating temperature.",
13
  "solution": "Stop the machine and let it cool down. Check the cooling system.",
14
- "downtime": timedelta(hours=3)
15
  },
16
  "E003": {
17
  "description": "Material Jam",
18
  "cause": "Accumulation of material in the processing area.",
19
  "solution": "Clean the processing area and check the feeding mechanisms.",
20
- "downtime": timedelta(minutes=15)
21
  },
22
  "E004": {
23
  "description": "Sensor Error",
24
  "cause": "A sensor is not functioning correctly.",
25
  "solution": "Check the sensor connections and replace if necessary.",
26
- "downtime": timedelta(hours=1, minutes=30)
27
  },
28
  "E005": {
29
  "description": "Power Failure",
30
  "cause": "Electrical supply interrupted.",
31
  "solution": "Check the electrical supply and fuses. Restart the machine.",
32
- "downtime": timedelta(minutes=30)
33
  },
34
  "E006": {
35
  "description": "Software Error",
36
  "cause": "Bug in the machine control software.",
37
  "solution": "Restart the software or update the firmware.",
38
- "downtime": timedelta(hours=1)
39
  },
40
  "E007": {
41
  "description": "Wear and Tear of Parts",
42
  "cause": "The machine parts are worn out.",
43
  "solution": "Inspect the parts and replace if necessary.",
44
- "downtime": timedelta(hours=1)
45
  },
46
  "E008": {
47
  "description": "Communication Error",
48
  "cause": "Communication problem between different machine modules.",
49
  "solution": "Check communication cables and protocols.",
50
- "downtime": timedelta(hours=2)
51
  },
52
  "E009": {
53
  "description": "Low Lubricant Level",
54
  "cause": "The lubricant level is insufficient.",
55
  "solution": "Refill the lubricant reservoir according to specifications.",
56
- "downtime": timedelta(minutes=15)
57
  },
58
  "E010": {
59
  "description": "Positioning Error",
60
  "cause": "The tooling is not positioning correctly.",
61
  "solution": "Check the positioning mechanisms and recalibrate if necessary.",
62
- "downtime": timedelta(hours=1)
63
  }
64
  }
 
5
  "description": "Calibration Error",
6
  "cause": "The machine is not correctly calibrated.",
7
  "solution": "Recalibrate the machine according to the manufacturer's specifications.",
8
+ "downtime": timedelta(minutes=15)
9
  },
10
  "E002": {
11
  "description": "Motor Overheating",
12
  "cause": "The motor has exceeded the maximum operating temperature.",
13
  "solution": "Stop the machine and let it cool down. Check the cooling system.",
14
+ "downtime": timedelta(minutes=20)
15
  },
16
  "E003": {
17
  "description": "Material Jam",
18
  "cause": "Accumulation of material in the processing area.",
19
  "solution": "Clean the processing area and check the feeding mechanisms.",
20
+ "downtime": timedelta(minutes=8)
21
  },
22
  "E004": {
23
  "description": "Sensor Error",
24
  "cause": "A sensor is not functioning correctly.",
25
  "solution": "Check the sensor connections and replace if necessary.",
26
+ "downtime": timedelta(minutes=20)
27
  },
28
  "E005": {
29
  "description": "Power Failure",
30
  "cause": "Electrical supply interrupted.",
31
  "solution": "Check the electrical supply and fuses. Restart the machine.",
32
+ "downtime": timedelta(minutes=10)
33
  },
34
  "E006": {
35
  "description": "Software Error",
36
  "cause": "Bug in the machine control software.",
37
  "solution": "Restart the software or update the firmware.",
38
+ "downtime": timedelta(minutes=15)
39
  },
40
  "E007": {
41
  "description": "Wear and Tear of Parts",
42
  "cause": "The machine parts are worn out.",
43
  "solution": "Inspect the parts and replace if necessary.",
44
+ "downtime": timedelta(minutes=15)
45
  },
46
  "E008": {
47
  "description": "Communication Error",
48
  "cause": "Communication problem between different machine modules.",
49
  "solution": "Check communication cables and protocols.",
50
+ "downtime": timedelta(minutes=20)
51
  },
52
  "E009": {
53
  "description": "Low Lubricant Level",
54
  "cause": "The lubricant level is insufficient.",
55
  "solution": "Refill the lubricant reservoir according to specifications.",
56
+ "downtime": timedelta(minutes=8)
57
  },
58
  "E010": {
59
  "description": "Positioning Error",
60
  "cause": "The tooling is not positioning correctly.",
61
  "solution": "Check the positioning mechanisms and recalibrate if necessary.",
62
+ "downtime": timedelta(minutes=15)
63
  }
64
  }
src/production/flow.py CHANGED
@@ -32,7 +32,7 @@ async def generate_data(state):
32
  if not state["running"]:
33
  break
34
 
35
- if random.random() < 0.2:
36
  error_key = random.choice(list(machine_errors.keys()))
37
  error = machine_errors[error_key]
38
  downtime = error["downtime"]
 
32
  if not state["running"]:
33
  break
34
 
35
+ if random.random() < 0.01: # 0.005
36
  error_key = random.choice(list(machine_errors.keys()))
37
  error = machine_errors[error_key]
38
  downtime = error["downtime"]
src/production/metrics/machine.py CHANGED
@@ -8,29 +8,28 @@ async def machine_metrics(raw_data):
8
  df[col] = pd.to_datetime(df[col], errors='coerce', format="%Y-%m-%d %H:%M:%S")
9
 
10
  opening_time = df['Timestamp'].max() - df['Timestamp'].min()
11
- required_time = opening_time # planned_stop_time = 0 non implémenté
 
12
 
13
  downtime_df = df.dropna(subset=['Downtime Start', 'Downtime End'])
14
  unplanned_stop_time = (downtime_df['Downtime End'] - downtime_df['Downtime Start']).sum()
15
  operating_time = required_time - unplanned_stop_time
16
 
17
- net_time = operating_time # cadency_variance = 0 non implémenté
 
18
 
19
  nok_count = (df['Compliance'] != 'OK').sum()
20
  useful_time = net_time - pd.Timedelta(seconds=nok_count)
21
 
22
- total_parts = len(df)
23
- compliant_parts = (df['Compliance'] == 'OK').sum()
24
-
25
  operating_sec = operating_time.total_seconds()
26
  net_sec = net_time.total_seconds()
27
  required_sec = required_time.total_seconds()
28
 
29
- quality_rate = (compliant_parts / total_parts) * 100 if total_parts > 0 else 0
30
  operating_rate = (net_sec / operating_sec) * 100 if operating_sec > 0 else 0
31
  availability_rate = (operating_sec / required_sec) * 100 if required_sec > 0 else 0
32
 
33
- TRS = (quality_rate / 100) * (operating_rate / 100) * (availability_rate / 100) * 100
34
 
35
  downtime_count = len(downtime_df)
36
  mtbf = operating_time / downtime_count if downtime_count > 0 else pd.Timedelta(0)
@@ -46,7 +45,7 @@ async def machine_metrics(raw_data):
46
  "quality_rate": quality_rate,
47
  "operating_rate": operating_rate,
48
  "availability_rate": availability_rate,
49
- "TRS": TRS,
50
  "MTBF": str(mtbf),
51
  "MTTR": str(mttr)
52
  }
 
8
  df[col] = pd.to_datetime(df[col], errors='coerce', format="%Y-%m-%d %H:%M:%S")
9
 
10
  opening_time = df['Timestamp'].max() - df['Timestamp'].min()
11
+ required_time = opening_time
12
+ # planned_stop_time = 0 non implémenté
13
 
14
  downtime_df = df.dropna(subset=['Downtime Start', 'Downtime End'])
15
  unplanned_stop_time = (downtime_df['Downtime End'] - downtime_df['Downtime Start']).sum()
16
  operating_time = required_time - unplanned_stop_time
17
 
18
+ net_time = operating_time
19
+ # cadency_variance = 0 non implémenté
20
 
21
  nok_count = (df['Compliance'] != 'OK').sum()
22
  useful_time = net_time - pd.Timedelta(seconds=nok_count)
23
 
 
 
 
24
  operating_sec = operating_time.total_seconds()
25
  net_sec = net_time.total_seconds()
26
  required_sec = required_time.total_seconds()
27
 
28
+ quality_rate = (useful_time / net_time) * 100 if net_time else 0
29
  operating_rate = (net_sec / operating_sec) * 100 if operating_sec > 0 else 0
30
  availability_rate = (operating_sec / required_sec) * 100 if required_sec > 0 else 0
31
 
32
+ OEE = (quality_rate / 100) * (operating_rate / 100) * (availability_rate / 100) * 100
33
 
34
  downtime_count = len(downtime_df)
35
  mtbf = operating_time / downtime_count if downtime_count > 0 else pd.Timedelta(0)
 
45
  "quality_rate": quality_rate,
46
  "operating_rate": operating_rate,
47
  "availability_rate": availability_rate,
48
+ "OEE": OEE,
49
  "MTBF": str(mtbf),
50
  "MTTR": str(mttr)
51
  }
src/ui/dashboard.py CHANGED
@@ -17,6 +17,7 @@ def hash_dataframe(df):
17
  """Computes a simple hash to detect changes in the DataFrame."""
18
  return pd.util.hash_pandas_object(df).sum()
19
 
 
20
  async def dataflow(state):
21
  """
22
  Main function that updates data if necessary.
@@ -101,6 +102,8 @@ def init_components(n=TOOLS_COUNT):
101
  - tool_plots: list of tool-related Gradio components
102
  - general_plots: list of general-related Gradio components
103
  """
 
 
104
  displays = []
105
  tool_plots = []
106
  general_plots = []
@@ -109,7 +112,7 @@ def init_components(n=TOOLS_COUNT):
109
  main_display = GeneralMetricsDisplay()
110
  displays.append(main_display)
111
  general_plots.extend(
112
- main_display.block(
113
  all_tools_df=pd.DataFrame(),
114
  issues_df=pd.DataFrame(),
115
  efficiency_data={}
@@ -127,11 +130,9 @@ def init_components(n=TOOLS_COUNT):
127
  async def on_tick(state, displays):
128
  """
129
  Tick function called periodically to update plots if data has changed.
130
-
131
  Handles:
132
  - Tool-specific plots (tool_1, tool_2, ..., tool_n)
133
  - General plots (all tools, issues, efficiency)
134
-
135
  Returns two lists of plots separately for tools and general metrics, plus state.
136
  """
137
  async with state.setdefault('lock', asyncio.Lock()):
@@ -146,32 +147,26 @@ async def on_tick(state, displays):
146
  general_plots = []
147
  general_display = displays[0]
148
  general_plots.extend(
149
- general_display.update(
150
  all_tools_df=all_tools_df,
151
  issues_df=issues_df,
152
  efficiency_data=efficiency_data
153
  )
154
  )
 
155
  # Update tool-specific plots
156
  tool_plots = []
157
  for df, display in zip(tool_dfs, displays[1:]):
158
  tool_plots.extend(
159
- [
160
- display.normal_curve(df, cote='pos'),
161
- display.gauge(df, type='cp', cote='pos'),
162
- display.gauge(df, type='cpk', cote='pos'),
163
- display.normal_curve(df, cote='ori'),
164
- display.gauge(df, type='cp', cote='ori'),
165
- display.gauge(df, type='cpk', cote='ori'),
166
- display.control_graph(df),
167
- ]
168
  )
169
  return tool_plots + general_plots + [state]
170
 
171
  def dashboard_ui(state):
172
  """
173
  Creates the Gradio interface and sets a refresh every second.
174
-
175
  The outputs are separated into two groups for tools and general metrics to
176
  preserve layout order and grouping.
177
  """
 
17
  """Computes a simple hash to detect changes in the DataFrame."""
18
  return pd.util.hash_pandas_object(df).sum()
19
 
20
+
21
  async def dataflow(state):
22
  """
23
  Main function that updates data if necessary.
 
102
  - tool_plots: list of tool-related Gradio components
103
  - general_plots: list of general-related Gradio components
104
  """
105
+ print("Initializing components...")
106
+
107
  displays = []
108
  tool_plots = []
109
  general_plots = []
 
112
  main_display = GeneralMetricsDisplay()
113
  displays.append(main_display)
114
  general_plots.extend(
115
+ main_display.general_block(
116
  all_tools_df=pd.DataFrame(),
117
  issues_df=pd.DataFrame(),
118
  efficiency_data={}
 
130
  async def on_tick(state, displays):
131
  """
132
  Tick function called periodically to update plots if data has changed.
 
133
  Handles:
134
  - Tool-specific plots (tool_1, tool_2, ..., tool_n)
135
  - General plots (all tools, issues, efficiency)
 
136
  Returns two lists of plots separately for tools and general metrics, plus state.
137
  """
138
  async with state.setdefault('lock', asyncio.Lock()):
 
147
  general_plots = []
148
  general_display = displays[0]
149
  general_plots.extend(
150
+ general_display.refresh(
151
  all_tools_df=all_tools_df,
152
  issues_df=issues_df,
153
  efficiency_data=efficiency_data
154
  )
155
  )
156
+
157
  # Update tool-specific plots
158
  tool_plots = []
159
  for df, display in zip(tool_dfs, displays[1:]):
160
  tool_plots.extend(
161
+ display.refresh(
162
+ df=df
163
+ )
 
 
 
 
 
 
164
  )
165
  return tool_plots + general_plots + [state]
166
 
167
  def dashboard_ui(state):
168
  """
169
  Creates the Gradio interface and sets a refresh every second.
 
170
  The outputs are separated into two groups for tools and general metrics to
171
  preserve layout order and grouping.
172
  """
src/ui/graphs/general_graphs.py CHANGED
@@ -1,18 +1,297 @@
1
  import gradio as gr
 
 
2
 
3
 
4
  class GeneralMetricsDisplay:
5
  def __init__(self):
6
- self.plot_all_tools = gr.Plot()
7
- self.plot_issues = gr.Plot()
8
- self.plot_efficiency = gr.Plot()
9
-
10
- def block(self, all_tools_df, issues_df, efficiency_data):
11
- return [self.plot_all_tools, self.plot_issues, self.plot_efficiency]
12
-
13
- def update(self, all_tools_df, issues_df, efficiency_data):
14
- #fig_all = px.scatter(tools_df, x="Position", y="Orientation", color="Compliance", title="All Tools Summary")
15
- #fig_issues = px.histogram(issues_df, x="Error Description", title="Error Types Distribution")
16
- #fig_eff = px.bar(x=list(efficiency_data.keys()), y=list(efficiency_data.values()), title="Machine Efficiency")
17
- #return [fig_all, fig_issues, fig_eff]
18
- return self.plot_all_tools, self.plot_issues, self.plot_efficiency
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ import pandas as pd
3
+ import plotly.graph_objects as go
4
 
5
 
6
  class GeneralMetricsDisplay:
7
  def __init__(self):
8
+ self.plots = []
9
+
10
+ @staticmethod
11
+ def kpi_rate(percentage, title="KPI"):
12
+ if percentage is None or not (0 <= percentage <= 100):
13
+ fig = go.Figure()
14
+ fig.update_layout(
15
+ template='plotly_dark',
16
+ width=320,
17
+ height=150,
18
+ margin=dict(l=10, r=10, t=10, b=10),
19
+ annotations=[dict(
20
+ text="No data",
21
+ showarrow=False,
22
+ font=dict(size=16, color="white"),
23
+ x=0.5, y=0.5, xanchor="center", yanchor="middle"
24
+ )]
25
+ )
26
+ return fig
27
+ fig = go.Figure(data=[go.Pie(
28
+ values=[percentage, 100 - percentage],
29
+ labels=['', ''],
30
+ hole=0.6,
31
+ marker_colors=['#2CFCFF', '#444444'],
32
+ textinfo='none',
33
+ hoverinfo='skip',
34
+ sort=False,
35
+ domain=dict(x=[0.4, 0.95], y=[0.15, 0.85])
36
+ )])
37
+ fig.update_layout(
38
+ template='plotly_dark',
39
+ annotations=[
40
+ dict(
41
+ text=f"{percentage:.0f}%",
42
+ x=0.675, y=0.5,
43
+ font_size=20,
44
+ showarrow=False,
45
+ font=dict(color="white"),
46
+ xanchor="center", yanchor="middle"
47
+ ),
48
+ dict(
49
+ text=title,
50
+ x=0.05, y=0.5,
51
+ showarrow=False,
52
+ font=dict(size=16, color="white"),
53
+ xanchor="left", yanchor="middle"
54
+ )
55
+ ],
56
+ showlegend=False,
57
+ margin=dict(l=10, r=10, t=10, b=10),
58
+ width=320,
59
+ height=150
60
+ )
61
+ return fig
62
+
63
+ @staticmethod
64
+ def kpi_value(value, title="Valeur"):
65
+ width = 360
66
+ if value is None or (isinstance(value, str) and (value.strip() == "" or value.strip().isdigit() and len(value.strip()) > 8)):
67
+ fig = go.Figure()
68
+ fig.update_layout(
69
+ template='plotly_dark',
70
+ width=width,
71
+ height=125,
72
+ margin=dict(l=30, r=0, t=0, b=30),
73
+ xaxis=dict(visible=False),
74
+ yaxis=dict(visible=False),
75
+ plot_bgcolor='#111111',
76
+ paper_bgcolor='#111111',
77
+ annotations=[dict(
78
+ text="No data",
79
+ showarrow=False,
80
+ font=dict(size=16, color="white"),
81
+ x=0.5, y=0.5,
82
+ xanchor="center", yanchor="middle"
83
+ )]
84
+ )
85
+ return fig
86
+ try:
87
+ if isinstance(value, (int, float)):
88
+ formatted = f"{int(value)}" if float(value).is_integer() else f"{float(value):.2f}"
89
+ else:
90
+ td = pd.to_timedelta(value)
91
+ total_seconds = int(td.total_seconds())
92
+ hours, remainder = divmod(total_seconds, 3600)
93
+ minutes, seconds = divmod(remainder, 60)
94
+ formatted = f"{hours}h {minutes}m {seconds}s"
95
+ except (ValueError, TypeError):
96
+ try:
97
+ numeric_value = float(value)
98
+ formatted = f"{int(numeric_value)}" if numeric_value.is_integer() else f"{numeric_value:.2f}"
99
+ except (ValueError, TypeError):
100
+ formatted = str(value)
101
+ fig = go.Figure()
102
+ fig.add_annotation(
103
+ text=f"<b>{formatted}</b>",
104
+ x=0.5, y=0.5,
105
+ showarrow=False,
106
+ font=dict(size=24, color="white"),
107
+ xanchor="center", yanchor="middle"
108
+ )
109
+ fig.add_annotation(
110
+ text=title,
111
+ x=0.5, y=1.8,
112
+ showarrow=False,
113
+ font=dict(size=16, color="lightgray"),
114
+ xanchor="center", yanchor="middle"
115
+ )
116
+ fig.update_layout(
117
+ template='plotly_dark',
118
+ width=width,
119
+ height=150,
120
+ margin=dict(l=40, r=0, t=0, b=40),
121
+ xaxis=dict(visible=False),
122
+ yaxis=dict(visible=False),
123
+ plot_bgcolor='#111111',
124
+ paper_bgcolor='#111111'
125
+ )
126
+ return fig
127
+
128
+ @staticmethod
129
+ def get_max_part_id(df):
130
+ if not df.empty and 'Part ID' in df.columns:
131
+ try:
132
+ numeric_ids = pd.to_numeric(df['Part ID'], errors='coerce')
133
+ return int(numeric_ids.dropna().max())
134
+ except Exception:
135
+ return None
136
+ return None
137
+
138
+ @staticmethod
139
+ def pareto(issues_df, error_col='Error Type'):
140
+ if issues_df is None or issues_df.empty:
141
+ fig = go.Figure()
142
+ fig.update_layout(
143
+ template='plotly_dark',
144
+ annotations=[dict(
145
+ text="No data",
146
+ showarrow=False,
147
+ font=dict(size=16, color="white")
148
+ )]
149
+ )
150
+ return fig
151
+ issues_df['Downtime Start'] = pd.to_datetime(issues_df['Downtime Start'], errors='coerce')
152
+ issues_df['Downtime End'] = pd.to_datetime(issues_df['Downtime End'], errors='coerce')
153
+ issues_df['Downtime Duration'] = (issues_df['Downtime End'] - issues_df[
154
+ 'Downtime Start']).dt.total_seconds() / 60
155
+ issues_df = issues_df.dropna(subset=['Downtime Duration'])
156
+ grouped = issues_df.groupby(error_col)['Downtime Duration'].sum().sort_values(ascending=False)
157
+ if grouped.empty:
158
+ fig = go.Figure()
159
+ fig.update_layout(
160
+ template='plotly_dark',
161
+ annotations=[dict(
162
+ text="No Error",
163
+ showarrow=False,
164
+ font=dict(size=16, color="white")
165
+ )]
166
+ )
167
+ return fig
168
+ cumulative = grouped.cumsum() / grouped.sum() * 100
169
+ labels = grouped.index.tolist()
170
+ durations = grouped.values
171
+ fig = go.Figure()
172
+ fig.add_trace(
173
+ go.Bar(
174
+ x=labels,
175
+ y=durations,
176
+ name='Downtime (min)',
177
+ marker_color='#2CFCFF',
178
+ yaxis='y1'
179
+ )
180
+ )
181
+ fig.add_trace(go.Scatter(
182
+ x=labels,
183
+ y=cumulative,
184
+ name='Cumulative %',
185
+ yaxis='y2',
186
+ mode='lines+markers',
187
+ line=dict(color='orange', width=2),
188
+ marker=dict(size=8)
189
+ ))
190
+ fig.update_layout(
191
+ template='plotly_dark',
192
+ title="Pareto of errors by downtime",
193
+ xaxis=dict(title="Errors"),
194
+ yaxis=dict(
195
+ title='Downtime (minutes)',
196
+ showgrid=False,
197
+ side='left'
198
+ ),
199
+ yaxis2=dict(
200
+ title='Cumulative percentage (%)',
201
+ overlaying='y',
202
+ side='right',
203
+ range=[0, 110],
204
+ showgrid=False,
205
+ tickformat='%'
206
+ ),
207
+ legend=dict(x=0.7, y=1.1),
208
+ margin=dict(l=70, r=70, t=50, b=50),
209
+ )
210
+ return fig
211
+
212
+ def general_block(self, all_tools_df, issues_df, efficiency_data):
213
+ header = f"Metrics Summary"
214
+ html_content = f"""
215
+ <div style="display: flex; align-items: center; justify-content: flex-start; width: 100%;">
216
+ <div style="flex: 0 0 2%; border-top: 1px solid white;"></div>
217
+ <h2 style="flex: 0 0 auto; margin: 0 10px;">{header}</h2>
218
+ <div style="flex: 1; border-top: 1px solid white;"></div>
219
+ </div>
220
+ """
221
+ gr.HTML(html_content)
222
+ with gr.Row():
223
+ with gr.Group():
224
+ with gr.Row(height=125):
225
+ total_count = gr.Plot(
226
+ self.kpi_value(
227
+ value=self.get_max_part_id(all_tools_df),
228
+ title="Total Count"
229
+ )
230
+ )
231
+ total_time = gr.Plot(
232
+ self.kpi_value(
233
+ value=efficiency_data.get("opening_time", "0 days 00:00:00"),
234
+ title="Total Time"
235
+ )
236
+ )
237
+ mtbf_plot = gr.Plot(
238
+ self.kpi_value(
239
+ value=efficiency_data.get("MTBF", "0 days 00:00:00"),
240
+ title="MTBF"
241
+ )
242
+ )
243
+ mttr_plot = gr.Plot(
244
+ self.kpi_value(
245
+ value=efficiency_data.get("MTTR", "0 days 00:00:00"),
246
+ title="MTTR"
247
+ )
248
+ )
249
+ with gr.Row():
250
+ with gr.Column(scale=1):
251
+ with gr.Group():
252
+ with gr.Row(height=150):
253
+ oee_plot = gr.Plot(
254
+ self.kpi_rate(
255
+ percentage=efficiency_data.get('OEE', 0),
256
+ title="OEE"
257
+ )
258
+ )
259
+ with gr.Row(height=150):
260
+ quality_rate_plot = gr.Plot(
261
+ self.kpi_rate(
262
+ percentage=efficiency_data.get("quality_rate", 0),
263
+ title="Quality Rate"
264
+ )
265
+ )
266
+ with gr.Row(height=150):
267
+ availability_plot = gr.Plot(
268
+ self.kpi_rate(
269
+ percentage=efficiency_data.get("availability_rate", 0),
270
+ title="Availability"
271
+ )
272
+ )
273
+ with gr.Column(scale=10):
274
+ with gr.Group():
275
+ with gr.Row(height=450):
276
+ pareto = gr.Plot(
277
+ self.pareto(issues_df, error_col='Error Code')
278
+ )
279
+ self.plots = [
280
+ total_count, total_time,
281
+ oee_plot, quality_rate_plot, availability_plot,
282
+ mtbf_plot, mttr_plot,
283
+ pareto,
284
+ ]
285
+ return self.plots
286
+
287
+ def refresh(self, all_tools_df, issues_df, efficiency_data):
288
+ return [
289
+ self.kpi_value(value=self.get_max_part_id(all_tools_df), title="Total Count"),
290
+ self.kpi_value(value=efficiency_data.get("opening_time", "0 days 00:00:00"), title="Total Time"),
291
+ self.kpi_rate(percentage=efficiency_data.get('OEE', 0), title="OEE"),
292
+ self.kpi_rate(percentage=efficiency_data.get("quality_rate", 0), title="Quality Rate"),
293
+ self.kpi_rate(percentage=efficiency_data.get("availability_rate", 0), title="Availability"),
294
+ self.kpi_value(value=efficiency_data.get("MTBF", "0 days 00:00:00"), title="MTBF"),
295
+ self.kpi_value(value=efficiency_data.get("MTTR", "0 days 00:00:00"), title="MTTR"),
296
+ self.pareto(issues_df, error_col='Error Code')
297
+ ]
src/ui/graphs/tools_graphs.py CHANGED
@@ -13,21 +13,23 @@ class ToolMetricsDisplay:
13
 
14
  @staticmethod
15
  def gauge(df, type=None, cote=None):
 
 
 
16
  if df is None or df.empty:
17
  fig = go.Figure()
18
  fig.update_layout(
19
  template='plotly_dark',
20
- width=200,
21
- height=170,
22
- margin=dict(l=30, r=50, t=30, b=0),
23
  annotations=[dict(
24
- text="No data available",
25
  showarrow=False,
26
  font=dict(size=16, color="white")
27
  )]
28
  )
29
  return fig
30
-
31
  column = f"{cote}_rolling_{type}"
32
  idx = df['Timestamp'].idxmax()
33
  value = df.loc[idx, column]
@@ -50,74 +52,110 @@ class ToolMetricsDisplay:
50
  ))
51
  fig.update_layout(
52
  template='plotly_dark',
53
- width=200,
54
- height=170,
55
- margin=dict(l=30, r=50, t=30, b=0),
56
  )
57
  return fig
58
 
59
  def control_graph(self, df):
 
 
 
60
  if df is None or df.empty:
61
  fig = go.Figure()
62
  fig.update_layout(
63
  template='plotly_dark',
64
  xaxis_title='Timestamp',
65
- yaxis_title='Valeurs',
66
  showlegend=True,
67
- width=720,
68
- height=400,
69
- margin=dict(l=70, r=10, t=30, b=70),
70
  annotations=[dict(
71
- text="No data available",
72
  showarrow=False,
73
  font=dict(size=20, color="white")
74
  )]
75
  )
76
  return fig
77
-
78
  fig = go.Figure()
79
- fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.3] * len(df), mode='lines',
80
- line=dict(dash='dot', color=self.pos_color, width=1), name='lsl pos'))
81
- fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.5] * len(df), mode='lines',
82
- line=dict(dash='dot', color=self.pos_color, width=1), name='usl pos'))
83
- fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.2] * len(df), mode='lines',
84
- line=dict(dash='dot', color=self.ori_color, width=1), name='lsl ori'))
85
- fig.add_trace(go.Scatter(x=df['Timestamp'], y=[0.6] * len(df), mode='lines',
86
- line=dict(dash='dot', color=self.ori_color, width=1), name='usl ori'))
87
  fig.add_trace(
88
- go.Scatter(x=df['Timestamp'], y=df['Position'], mode='lines+markers', line=dict(color=self.pos_color),
89
- name='pos'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  fig.add_trace(
91
- go.Scatter(x=df['Timestamp'], y=df['Orientation'], mode='lines+markers', line=dict(color=self.ori_color),
92
- name='ori'))
 
 
 
 
93
  fig.update_layout(
94
  template='plotly_dark',
95
  xaxis_title='Timestamp',
96
  yaxis_title='Valeurs',
97
  showlegend=True,
98
- width=720,
99
- height=400,
100
- margin=dict(l=70, r=10, t=30, b=70)
101
  )
102
  return fig
103
 
104
  def normal_curve(self, df, cote=None):
 
 
 
105
  if df is None or df.empty:
106
  fig = go.Figure()
107
  fig.update_layout(
108
  template='plotly_dark',
109
  showlegend=False,
110
- width=370,
111
- height=250,
112
- margin=dict(l=40, r=40, t=40, b=40),
113
  annotations=[dict(
114
- text="No data available",
115
  showarrow=False,
116
  font=dict(size=16, color="white")
117
  )]
118
  )
119
  return fig
120
-
121
  if cote == 'pos':
122
  color = self.pos_color
123
  lsl = 0.3
@@ -133,17 +171,30 @@ class ToolMetricsDisplay:
133
  x = np.linspace(mu - 3 * std, mu + 3 * std, 100)
134
  y = norm.pdf(x, mu, std)
135
  fig = go.Figure()
136
- fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Normal Curve', line=dict(color=color)))
137
- fig.add_shape(type="line", x0=usl, y0=0, x1=usl, y1=max(y), line=dict(color="red", width=1, dash="dot"),
138
- name='usl')
139
- fig.add_shape(type="line", x0=lsl, y0=0, x1=lsl, y1=max(y), line=dict(color="red", width=1, dash="dot"),
140
- name='lsl')
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  fig.update_layout(
142
  template='plotly_dark',
143
  showlegend=False,
144
- width=370,
145
- height=250,
146
- margin=dict(l=40, r=40, t=40, b=40)
147
  )
148
  return fig
149
 
@@ -162,26 +213,64 @@ class ToolMetricsDisplay:
162
  gr.Markdown("### `Position`")
163
  with gr.Group():
164
  with gr.Row(height=250):
165
- pos_normal_plot = gr.Plot(self.normal_curve(df=df, cote='pos'))
 
 
 
 
166
  with gr.Row(height=150):
167
- pos_cp_gauge = gr.Plot(self.gauge(df=df, type='cp', cote='pos'))
168
- pos_cpk_gauge = gr.Plot(self.gauge(df=df, type='cpk', cote='pos'))
 
 
 
 
 
 
 
 
169
  with gr.Column(scale=1):
170
  gr.Markdown("### `Orientation`")
171
  with gr.Group():
172
  with gr.Row(height=250):
173
- ori_normal_plot = gr.Plot(self.normal_curve(df=df, cote='ori'))
 
 
 
 
174
  with gr.Row(height=150):
175
- ori_cp_gauge = gr.Plot(self.gauge(df=df, type='cp', cote='ori'))
176
- ori_cpk_gauge = gr.Plot(self.gauge(df=df, type='cpk', cote='ori'))
 
 
 
 
 
 
 
 
177
  with gr.Column(scale=2):
178
  gr.Markdown("### `Control card`")
179
  with gr.Row(height=400):
180
- control_plot = gr.Plot(self.control_graph(df=df))
181
-
 
 
 
182
  self.plots = [
183
  pos_normal_plot, pos_cp_gauge, pos_cpk_gauge,
184
  ori_normal_plot, ori_cp_gauge, ori_cpk_gauge,
185
  control_plot
186
  ]
187
- return self.plots
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  @staticmethod
15
  def gauge(df, type=None, cote=None):
16
+ width = 205
17
+ height = 150
18
+ margin = dict(l=30, r=50, t=50, b=0)
19
  if df is None or df.empty:
20
  fig = go.Figure()
21
  fig.update_layout(
22
  template='plotly_dark',
23
+ width=width,
24
+ height=height,
25
+ margin=margin,
26
  annotations=[dict(
27
+ text="No data",
28
  showarrow=False,
29
  font=dict(size=16, color="white")
30
  )]
31
  )
32
  return fig
 
33
  column = f"{cote}_rolling_{type}"
34
  idx = df['Timestamp'].idxmax()
35
  value = df.loc[idx, column]
 
52
  ))
53
  fig.update_layout(
54
  template='plotly_dark',
55
+ width=width,
56
+ height=height,
57
+ margin=margin,
58
  )
59
  return fig
60
 
61
  def control_graph(self, df):
62
+ width = 832
63
+ height = 400
64
+ margin = dict(l=70, r=100, t=30, b=70)
65
  if df is None or df.empty:
66
  fig = go.Figure()
67
  fig.update_layout(
68
  template='plotly_dark',
69
  xaxis_title='Timestamp',
70
+ yaxis_title='Values',
71
  showlegend=True,
72
+ width=width,
73
+ height=height,
74
+ margin=margin,
75
  annotations=[dict(
76
+ text="No data",
77
  showarrow=False,
78
  font=dict(size=20, color="white")
79
  )]
80
  )
81
  return fig
 
82
  fig = go.Figure()
 
 
 
 
 
 
 
 
83
  fig.add_trace(
84
+ go.Scatter(
85
+ x=df['Timestamp'], y=[0.3] * len(df),
86
+ mode='lines',
87
+ line=dict(dash='dot', color=self.pos_color, width=1),
88
+ name='lsl pos'
89
+ )
90
+ )
91
+ fig.add_trace(
92
+ go.Scatter(
93
+ x=df['Timestamp'], y=[0.5] * len(df),
94
+ mode='lines',
95
+ line=dict(dash='dot', color=self.pos_color, width=1),
96
+ name='usl pos'
97
+ )
98
+ )
99
+ fig.add_trace(
100
+ go.Scatter(
101
+ x=df['Timestamp'], y=[0.2] * len(df),
102
+ mode='lines',
103
+ line=dict(dash='dot', color=self.ori_color, width=1),
104
+ name='lsl ori'
105
+ )
106
+ )
107
+ fig.add_trace(
108
+ go.Scatter(
109
+ x=df['Timestamp'], y=[0.6] * len(df),
110
+ mode='lines',
111
+ line=dict(dash='dot', color=self.ori_color, width=1),
112
+ name='usl ori'
113
+ )
114
+ )
115
+ fig.add_trace(
116
+ go.Scatter(
117
+ x=df['Timestamp'], y=df['Position'],
118
+ mode='lines+markers', line=dict(color=self.pos_color),
119
+ name='pos'
120
+ )
121
+ )
122
  fig.add_trace(
123
+ go.Scatter(
124
+ x=df['Timestamp'], y=df['Orientation'],
125
+ mode='lines+markers', line=dict(color=self.ori_color),
126
+ name='ori'
127
+ )
128
+ )
129
  fig.update_layout(
130
  template='plotly_dark',
131
  xaxis_title='Timestamp',
132
  yaxis_title='Valeurs',
133
  showlegend=True,
134
+ width=width,
135
+ height=height,
136
+ margin=margin
137
  )
138
  return fig
139
 
140
  def normal_curve(self, df, cote=None):
141
+ width = 410
142
+ height= 250
143
+ margin= dict(l=20, r=70, t=20, b=20)
144
  if df is None or df.empty:
145
  fig = go.Figure()
146
  fig.update_layout(
147
  template='plotly_dark',
148
  showlegend=False,
149
+ width=width,
150
+ height=height,
151
+ margin=margin,
152
  annotations=[dict(
153
+ text="No data",
154
  showarrow=False,
155
  font=dict(size=16, color="white")
156
  )]
157
  )
158
  return fig
 
159
  if cote == 'pos':
160
  color = self.pos_color
161
  lsl = 0.3
 
171
  x = np.linspace(mu - 3 * std, mu + 3 * std, 100)
172
  y = norm.pdf(x, mu, std)
173
  fig = go.Figure()
174
+ fig.add_trace(
175
+ go.Scatter(
176
+ x=x, y=y,
177
+ mode='lines',
178
+ name='Normal Curve',
179
+ line=dict(color=color)
180
+ )
181
+ )
182
+ fig.add_shape(
183
+ type="line", x0=usl, y0=0, x1=usl, y1=max(y),
184
+ line=dict(color="red", width=1, dash="dot"),
185
+ name='usl'
186
+ )
187
+ fig.add_shape(
188
+ type="line", x0=lsl, y0=0, x1=lsl, y1=max(y),
189
+ line=dict(color="red", width=1, dash="dot"),
190
+ name='lsl'
191
+ )
192
  fig.update_layout(
193
  template='plotly_dark',
194
  showlegend=False,
195
+ width=width,
196
+ height=height,
197
+ margin=margin
198
  )
199
  return fig
200
 
 
213
  gr.Markdown("### `Position`")
214
  with gr.Group():
215
  with gr.Row(height=250):
216
+ pos_normal_plot = gr.Plot(
217
+ self.normal_curve(
218
+ df=df, cote='pos'
219
+ )
220
+ )
221
  with gr.Row(height=150):
222
+ pos_cp_gauge = gr.Plot(
223
+ self.gauge(
224
+ df=df, type='cp', cote='pos'
225
+ )
226
+ )
227
+ pos_cpk_gauge = gr.Plot(
228
+ self.gauge(
229
+ df=df, type='cpk', cote='pos'
230
+ )
231
+ )
232
  with gr.Column(scale=1):
233
  gr.Markdown("### `Orientation`")
234
  with gr.Group():
235
  with gr.Row(height=250):
236
+ ori_normal_plot = gr.Plot(
237
+ self.normal_curve(
238
+ df=df, cote='ori'
239
+ )
240
+ )
241
  with gr.Row(height=150):
242
+ ori_cp_gauge = gr.Plot(
243
+ self.gauge(
244
+ df=df, type='cp', cote='ori'
245
+ )
246
+ )
247
+ ori_cpk_gauge = gr.Plot(
248
+ self.gauge(
249
+ df=df, type='cpk', cote='ori'
250
+ )
251
+ )
252
  with gr.Column(scale=2):
253
  gr.Markdown("### `Control card`")
254
  with gr.Row(height=400):
255
+ control_plot = gr.Plot(
256
+ self.control_graph(
257
+ df=df
258
+ )
259
+ )
260
  self.plots = [
261
  pos_normal_plot, pos_cp_gauge, pos_cpk_gauge,
262
  ori_normal_plot, ori_cp_gauge, ori_cpk_gauge,
263
  control_plot
264
  ]
265
+ return self.plots
266
+
267
+ def refresh(self, df):
268
+ return [
269
+ self.normal_curve(df, cote='pos'),
270
+ self.gauge(df, type='cp', cote='pos'),
271
+ self.gauge(df, type='cpk', cote='pos'),
272
+ self.normal_curve(df, cote='ori'),
273
+ self.gauge(df, type='cp', cote='ori'),
274
+ self.gauge(df, type='cpk', cote='ori'),
275
+ self.control_graph(df),
276
+ ]