Spaces:
Sleeping
Sleeping
mriusero
commited on
Commit
·
f731ddb
1
Parent(s):
f8d9c8a
feat: general metrics
Browse files- src/production/downtime.py +10 -10
- src/production/flow.py +1 -1
- src/production/metrics/machine.py +7 -8
- src/ui/dashboard.py +9 -14
- src/ui/graphs/general_graphs.py +292 -13
- src/ui/graphs/tools_graphs.py +140 -51
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(
|
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(
|
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=
|
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(
|
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=
|
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(
|
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(
|
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(
|
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=
|
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(
|
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.
|
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
|
|
|
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
|
|
|
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 = (
|
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 |
-
|
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 |
-
"
|
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.
|
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 |
-
|
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 |
-
|
161 |
-
|
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.
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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=
|
21 |
-
height=
|
22 |
-
margin=
|
23 |
annotations=[dict(
|
24 |
-
text="No data
|
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=
|
54 |
-
height=
|
55 |
-
margin=
|
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='
|
66 |
showlegend=True,
|
67 |
-
width=
|
68 |
-
height=
|
69 |
-
margin=
|
70 |
annotations=[dict(
|
71 |
-
text="No data
|
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(
|
89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
fig.add_trace(
|
91 |
-
go.Scatter(
|
92 |
-
|
|
|
|
|
|
|
|
|
93 |
fig.update_layout(
|
94 |
template='plotly_dark',
|
95 |
xaxis_title='Timestamp',
|
96 |
yaxis_title='Valeurs',
|
97 |
showlegend=True,
|
98 |
-
width=
|
99 |
-
height=
|
100 |
-
margin=
|
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=
|
111 |
-
height=
|
112 |
-
margin=
|
113 |
annotations=[dict(
|
114 |
-
text="No data
|
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(
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
141 |
fig.update_layout(
|
142 |
template='plotly_dark',
|
143 |
showlegend=False,
|
144 |
-
width=
|
145 |
-
height=
|
146 |
-
margin=
|
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(
|
|
|
|
|
|
|
|
|
166 |
with gr.Row(height=150):
|
167 |
-
pos_cp_gauge = gr.Plot(
|
168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
|
|
|
|
|
|
|
|
174 |
with gr.Row(height=150):
|
175 |
-
ori_cp_gauge = gr.Plot(
|
176 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
177 |
with gr.Column(scale=2):
|
178 |
gr.Markdown("### `Control card`")
|
179 |
with gr.Row(height=400):
|
180 |
-
control_plot = gr.Plot(
|
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 |
+
]
|