Spaces:
Sleeping
Sleeping
mriusero
commited on
Commit
·
05a4c82
1
Parent(s):
4c16235
feat: production metrics
Browse files- data/efficiency.json +14 -0
- data/tool_1.csv +3 -0
- data/tool_2.csv +4 -0
- data/tool_3.csv +4 -0
- data/tool_4.csv +4 -0
- data/tool_all.csv +12 -0
- src/production/flow.py +12 -11
- src/production/metrics/__init__.py +0 -0
- src/production/metrics/machine.py +72 -0
- src/production/metrics/tools.py +52 -0
- src/production/processing.py +9 -0
data/efficiency.json
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"opening_time": "0 days 08:30:15",
|
3 |
+
"required_time": "0 days 08:30:15",
|
4 |
+
"unplanned_stop_time": "0 days 08:30:00",
|
5 |
+
"operating_time": "0 days 00:00:15",
|
6 |
+
"net_time": "0 days 00:00:15",
|
7 |
+
"useful_time": "0 days 00:00:08",
|
8 |
+
"quality_rate": 56.25,
|
9 |
+
"operating_rate": 100.0,
|
10 |
+
"availability_rate": 0.04899559039686428,
|
11 |
+
"TRS": 0.02756001959823616,
|
12 |
+
"MTBF": "0 days 00:00:03",
|
13 |
+
"MTTR": "0 days 01:42:00"
|
14 |
+
}
|
data/tool_1.csv
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
Part ID,Timestamp,Position,Orientation,Tool ID,Compliance,Event,Error Code,Error Description,Downtime Start,Downtime End,pos_rolling_mean,pos_rolling_std,pos_rolling_cp,pos_rolling_cpk,ori_rolling_mean,ori_rolling_std,ori_rolling_cp,ori_rolling_cpk
|
2 |
+
4,2025-06-06 16:24:31,0.3607,0.3622,1,OK,N/A,N/A,N/A,N/A,N/A,0.3607,,,,0.3622,,,
|
3 |
+
8,2025-06-06 21:39:38,0.3889,0.2458,1,OK,N/A,N/A,N/A,N/A,N/A,0.3748,0.019940411229460643,1.6716472368476298,1.2503921331620276,0.304,0.08230722933011414,0.8099734034210165,0.42118616977892853
|
data/tool_2.csv
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Part ID,Timestamp,Position,Orientation,Tool ID,Compliance,Event,Error Code,Error Description,Downtime Start,Downtime End,pos_rolling_mean,pos_rolling_std,pos_rolling_cp,pos_rolling_cpk,ori_rolling_mean,ori_rolling_std,ori_rolling_cp,ori_rolling_cpk
|
2 |
+
1,2025-06-06 16:09:27,0.3956,0.4285,2,OK,N/A,N/A,N/A,N/A,N/A,0.3956,,,,0.4285,,,
|
3 |
+
5,2025-06-06 19:24:33,0.4054,0.3711,2,OK,N/A,N/A,N/A,N/A,N/A,0.40049999999999997,0.006929646455628168,4.810250212153383,4.786198961092618,0.3998,0.04058792924010783,1.6425244626865212,1.6408819382238347
|
4 |
+
9,2025-06-06 21:39:39,0.3808,0.5243,2,OK,N/A,N/A,N/A,N/A,N/A,0.3939333333333333,0.01238439878772209,2.6915584603412515,2.528270580413882,0.4413,0.07739793278893178,0.8613494477749186,0.6834807868093978
|
data/tool_3.csv
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Part ID,Timestamp,Position,Orientation,Tool ID,Compliance,Event,Error Code,Error Description,Downtime Start,Downtime End,pos_rolling_mean,pos_rolling_std,pos_rolling_cp,pos_rolling_cpk,ori_rolling_mean,ori_rolling_std,ori_rolling_cp,ori_rolling_cpk
|
2 |
+
2,2025-06-06 16:09:28,0.3741,0.3253,3,OK,N/A,N/A,N/A,N/A,N/A,0.3741,,,,0.3253,,,
|
3 |
+
6,2025-06-06 19:39:35,0.4245,0.3435,3,OK,N/A,N/A,N/A,N/A,N/A,0.3993,0.035638181771801995,0.935326430140936,0.9287791451299494,0.33440000000000003,0.012869343417595179,5.1802694592421,3.481141076610692
|
4 |
+
10,2025-06-06 21:39:40,0.3902,0.4416,3,OK,N/A,N/A,N/A,N/A,N/A,0.3962666666666667,0.025741859554689,1.294907745981448,1.246564523464808,0.37013333333333337,0.06255736034499322,1.065688614401428,0.9065457813174815
|
data/tool_4.csv
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Part ID,Timestamp,Position,Orientation,Tool ID,Compliance,Event,Error Code,Error Description,Downtime Start,Downtime End,pos_rolling_mean,pos_rolling_std,pos_rolling_cp,pos_rolling_cpk,ori_rolling_mean,ori_rolling_std,ori_rolling_cp,ori_rolling_cpk
|
2 |
+
3,2025-06-06 16:24:30,0.3412,0.9907,4,NOK,N/A,N/A,N/A,N/A,N/A,0.3412,,,,0.9907,,,
|
3 |
+
7,2025-06-06 21:39:37,0.395,0.3865,4,OK,N/A,N/A,N/A,N/A,N/A,0.3681,0.038042344827836284,0.876216581395969,0.5967034919306549,0.6886,0.42723391719291204,0.15604254246641233,-0.06912684631262067
|
4 |
+
11,2025-06-07 00:39:42,0.6491,0.5659,4,NOK,N/A,N/A,N/A,N/A,N/A,0.46176666666666666,0.16445042819443598,0.20269532709225951,0.0774971800582739,0.6477,0.310294763088261,0.21484947410376964,-0.051241599573749144
|
data/tool_all.csv
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Part ID,Timestamp,Position,Orientation,Tool ID,Compliance,Event,Error Code,Error Description,Downtime Start,Downtime End,pos_rolling_mean,pos_rolling_std,pos_rolling_cp,pos_rolling_cpk,ori_rolling_mean,ori_rolling_std,ori_rolling_cp,ori_rolling_cpk
|
2 |
+
1,2025-06-06 16:09:27,0.3956,0.4285,2,OK,N/A,N/A,N/A,N/A,N/A,0.3956,,,,0.4285,,,
|
3 |
+
2,2025-06-06 16:09:28,0.3741,0.3253,3,OK,N/A,N/A,N/A,N/A,N/A,0.38485,0.015202795795510805,2.19257916646991,1.8604034227497193,0.3769,0.07297341981845173,0.9135746526957974,0.8080567803094328
|
4 |
+
3,2025-06-06 16:24:30,0.3412,0.9907,4,NOK,N/A,N/A,N/A,N/A,N/A,0.3703,0.02739835761501045,1.2166179375318231,0.8552824100848719,0.5815,0.35811456267513053,0.1861601666479685,0.017219815414937053
|
5 |
+
4,2025-06-06 16:24:31,0.3607,0.3622,1,OK,N/A,N/A,N/A,N/A,N/A,0.3679,0.022879831001706884,1.4568872178665393,0.9892264209313804,0.526675,0.3122826964466651,0.2134817824530111,0.07826775849183518
|
6 |
+
5,2025-06-06 19:24:33,0.4054,0.3711,2,OK,N/A,N/A,N/A,N/A,N/A,0.3754,0.02595890983843505,1.2840806313052342,0.9681967960041469,0.49555999999999994,0.2792509230065319,0.2387339169693891,0.12466685144141502
|
7 |
+
6,2025-06-06 19:39:35,0.4245,0.3435,3,OK,N/A,N/A,N/A,N/A,N/A,0.38358333333333333,0.03067399006759092,1.0866970113729086,0.9082975853391895,0.47021666666666667,0.257368548324512,0.25903191007864607,0.1680901236485347
|
8 |
+
7,2025-06-06 21:39:37,0.395,0.3865,4,OK,N/A,N/A,N/A,N/A,N/A,0.3852142857142857,0.028331927135973208,1.1765289799510255,1.0025707664868377,0.4582571428571428,0.2370654330007003,0.28121631155930576,0.19930201737795947
|
9 |
+
8,2025-06-06 21:39:38,0.3889,0.2458,1,OK,N/A,N/A,N/A,N/A,N/A,0.385675,0.026262616015926513,1.269231264437591,1.087413885806906,0.4317,0.23197780066204612,0.2873838206776912,0.24183348510027716
|
10 |
+
9,2025-06-06 21:39:39,0.3808,0.5243,2,OK,N/A,N/A,N/A,N/A,N/A,0.3851333333333333,0.024620113728413193,1.3539065538460338,1.15262577950759,0.4419888888888889,0.21917969479655525,0.30416442877405825,0.24030679675532682
|
11 |
+
10,2025-06-06 21:39:40,0.3902,0.4416,3,OK,N/A,N/A,N/A,N/A,N/A,0.38564,0.023267297030620273,1.4326259423028787,1.226900856988185,0.44195,0.2066446345783021,0.3226150381436844,0.25494653389304656
|
12 |
+
11,2025-06-07 00:39:42,0.6491,0.5659,4,NOK,N/A,N/A,N/A,N/A,N/A,0.40959090909090906,0.08244596357063765,0.4043052187118193,0.3655286727353676,0.45321818181818185,0.19957077851319724,0.33405024103896114,0.24516250871886658
|
src/production/flow.py
CHANGED
@@ -5,6 +5,7 @@ import pandas as pd
|
|
5 |
from datetime import datetime, timedelta
|
6 |
|
7 |
from .downtime import machine_errors
|
|
|
8 |
|
9 |
PRODUCTION = False
|
10 |
PROD_STATE = {
|
@@ -32,7 +33,7 @@ def synthetic_data():
|
|
32 |
if not PRODUCTION:
|
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"]
|
@@ -69,7 +70,7 @@ def synthetic_data():
|
|
69 |
|
70 |
print(f" - part {part_id} data generated")
|
71 |
part_id += 1
|
72 |
-
time.sleep(
|
73 |
|
74 |
current_time += timedelta(seconds=1)
|
75 |
|
@@ -79,13 +80,13 @@ def synthetic_data():
|
|
79 |
|
80 |
return data
|
81 |
|
82 |
-
def
|
83 |
"""
|
84 |
Update production data in real-time.
|
85 |
"""
|
86 |
-
|
87 |
for row in data:
|
88 |
-
|
89 |
"Part ID": row.get("part_id", "N/A"),
|
90 |
"Timestamp": row.get("timestamp", "N/A"),
|
91 |
"Position": row.get("position", "N/A"),
|
@@ -98,28 +99,28 @@ def update_display(data):
|
|
98 |
"Downtime Start": row.get("downtime_start", "N/A"),
|
99 |
"Downtime End": row.get("downtime_end", "N/A")
|
100 |
})
|
101 |
-
return pd.DataFrame(
|
102 |
|
103 |
|
104 |
def play_fn():
|
105 |
"""
|
106 |
Start the production simulation and generate synthetic data.
|
107 |
"""
|
108 |
-
print("===
|
109 |
global PRODUCTION
|
110 |
PRODUCTION = True
|
111 |
while PRODUCTION:
|
112 |
data = synthetic_data()
|
113 |
-
|
114 |
-
yield
|
115 |
-
|
116 |
|
117 |
|
118 |
def pause_fn():
|
119 |
"""
|
120 |
Pause the production simulation.
|
121 |
"""
|
122 |
-
print("
|
123 |
global PRODUCTION
|
124 |
PRODUCTION = False
|
125 |
|
|
|
5 |
from datetime import datetime, timedelta
|
6 |
|
7 |
from .downtime import machine_errors
|
8 |
+
from .processing import process
|
9 |
|
10 |
PRODUCTION = False
|
11 |
PROD_STATE = {
|
|
|
33 |
if not PRODUCTION:
|
34 |
break
|
35 |
|
36 |
+
if random.random() < 0.2:
|
37 |
error_key = random.choice(list(machine_errors.keys()))
|
38 |
error = machine_errors[error_key]
|
39 |
downtime = error["downtime"]
|
|
|
70 |
|
71 |
print(f" - part {part_id} data generated")
|
72 |
part_id += 1
|
73 |
+
time.sleep(0.5)
|
74 |
|
75 |
current_time += timedelta(seconds=1)
|
76 |
|
|
|
80 |
|
81 |
return data
|
82 |
|
83 |
+
def compile(data):
|
84 |
"""
|
85 |
Update production data in real-time.
|
86 |
"""
|
87 |
+
raw_data = []
|
88 |
for row in data:
|
89 |
+
raw_data.append({
|
90 |
"Part ID": row.get("part_id", "N/A"),
|
91 |
"Timestamp": row.get("timestamp", "N/A"),
|
92 |
"Position": row.get("position", "N/A"),
|
|
|
99 |
"Downtime Start": row.get("downtime_start", "N/A"),
|
100 |
"Downtime End": row.get("downtime_end", "N/A")
|
101 |
})
|
102 |
+
return pd.DataFrame(raw_data)
|
103 |
|
104 |
|
105 |
def play_fn():
|
106 |
"""
|
107 |
Start the production simulation and generate synthetic data.
|
108 |
"""
|
109 |
+
print("=== STARTING PRODUCTION ===")
|
110 |
global PRODUCTION
|
111 |
PRODUCTION = True
|
112 |
while PRODUCTION:
|
113 |
data = synthetic_data()
|
114 |
+
raw_data = compile(data)
|
115 |
+
yield raw_data
|
116 |
+
process(raw_data)
|
117 |
|
118 |
|
119 |
def pause_fn():
|
120 |
"""
|
121 |
Pause the production simulation.
|
122 |
"""
|
123 |
+
print("--- PAUSE ---")
|
124 |
global PRODUCTION
|
125 |
PRODUCTION = False
|
126 |
|
src/production/metrics/__init__.py
ADDED
File without changes
|
src/production/metrics/machine.py
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
|
5 |
+
def machine_metrics(raw_data):
|
6 |
+
"""
|
7 |
+
Calculate machine efficiency metrics from raw production data.
|
8 |
+
:param raw_data: collection of raw production data containing timestamps, downtime, and compliance information.
|
9 |
+
:return: a dictionary with calculated metrics including opening time, required time, unplanned stop time, operating time, net time, useful time, quality rate, operating rate, availability rate, TRS (Total Resource Score), MTBF (Mean Time Between Failures), and MTTR (Mean Time To Repair).
|
10 |
+
"""
|
11 |
+
df = pd.DataFrame(raw_data)
|
12 |
+
df['Timestamp'] = pd.to_datetime(df['Timestamp'])
|
13 |
+
df['Downtime Start'] = pd.to_datetime(df['Downtime Start'], format="%Y-%m-%d %H:%M:%S", errors='coerce')
|
14 |
+
df['Downtime End'] = pd.to_datetime(df['Downtime End'], format="%Y-%m-%d %H:%M:%S", errors='coerce')
|
15 |
+
|
16 |
+
opening_time = df['Timestamp'].max() - df['Timestamp'].min() # Calculate opening time
|
17 |
+
planned_stop_time = pd.Timedelta(0) # Planned stop time (not implemented)
|
18 |
+
required_time = opening_time - planned_stop_time
|
19 |
+
|
20 |
+
downtime_df = df.dropna(subset=['Downtime Start', 'Downtime End']) # Create a subset for calculating unplanned stop time
|
21 |
+
unplanned_stop_time = (downtime_df['Downtime End'] - downtime_df['Downtime Start']).sum() # Calculate unplanned stop time
|
22 |
+
operating_time = required_time - unplanned_stop_time # Operating time
|
23 |
+
|
24 |
+
cadency_variance = pd.Timedelta(0) # Cadency variance (not implemented)
|
25 |
+
net_time = operating_time - cadency_variance # Net time
|
26 |
+
|
27 |
+
nok_time = df[df['Compliance'] != 'OK']['Timestamp'].count() # Time NOK (non-compliant)
|
28 |
+
useful_time = net_time - pd.Timedelta(seconds=nok_time) # Useful time
|
29 |
+
|
30 |
+
total_parts = df['Part ID'].count() # Compliance metrics
|
31 |
+
compliant_parts = df[df['Compliance'] == 'OK']['Compliance'].count()
|
32 |
+
|
33 |
+
quality_rate = (compliant_parts / total_parts) * 100 # Quality rate
|
34 |
+
operating_rate = (net_time / operating_time) * 100 # Operating rate
|
35 |
+
availability_rate = (operating_time / required_time) * 100 # Availability rate
|
36 |
+
|
37 |
+
# Overall Equipment Effectiveness (OEE)
|
38 |
+
TRS = (quality_rate / 100) * (operating_rate / 100) * (availability_rate / 100) * 100
|
39 |
+
|
40 |
+
# Mean Time Between Failures (MTBF)
|
41 |
+
if len(downtime_df) > 0:
|
42 |
+
mtbf = operating_time / len(downtime_df)
|
43 |
+
else:
|
44 |
+
mtbf = pd.Timedelta(0)
|
45 |
+
|
46 |
+
# Mean Time To Repair (MTTR)
|
47 |
+
if len(downtime_df) > 0:
|
48 |
+
mttr = unplanned_stop_time / len(downtime_df)
|
49 |
+
else:
|
50 |
+
mttr = pd.Timedelta(0)
|
51 |
+
|
52 |
+
results = {
|
53 |
+
"opening_time": str(opening_time),
|
54 |
+
"required_time": str(required_time),
|
55 |
+
"unplanned_stop_time": str(unplanned_stop_time),
|
56 |
+
"operating_time": str(operating_time),
|
57 |
+
"net_time": str(net_time),
|
58 |
+
"useful_time": str(useful_time),
|
59 |
+
"quality_rate": quality_rate,
|
60 |
+
"operating_rate": operating_rate,
|
61 |
+
"availability_rate": availability_rate,
|
62 |
+
"TRS": TRS,
|
63 |
+
"MTBF": str(mtbf),
|
64 |
+
"MTTR": str(mttr)
|
65 |
+
}
|
66 |
+
|
67 |
+
os.makedirs('data', exist_ok=True)
|
68 |
+
|
69 |
+
with open('data/efficiency.json', 'w') as json_file:
|
70 |
+
json.dump(results, json_file, indent=4)
|
71 |
+
|
72 |
+
return results
|
src/production/metrics/tools.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from concurrent.futures import ThreadPoolExecutor
|
3 |
+
|
4 |
+
def stats_metrics(data, column, usl, lsl):
|
5 |
+
"""
|
6 |
+
Calculate rolling mean, standard deviation, Cp, and Cpk for a given column.
|
7 |
+
Args:
|
8 |
+
data (pd.DataFrame): DataFrame containing the production data.
|
9 |
+
column (str): The column for which to calculate metrics.
|
10 |
+
usl (float): Upper specification limit.
|
11 |
+
lsl (float): Lower specification limit.
|
12 |
+
"""
|
13 |
+
rolling_mean = data[column].expanding().mean()
|
14 |
+
rolling_std = data[column].expanding().std()
|
15 |
+
cp = (usl - lsl) / (6 * rolling_std)
|
16 |
+
cpk = np.minimum(
|
17 |
+
(usl - rolling_mean) / (3 * rolling_std),
|
18 |
+
(rolling_mean - lsl) / (3 * rolling_std)
|
19 |
+
)
|
20 |
+
cpk[rolling_std == 0] = 0
|
21 |
+
return rolling_mean, rolling_std, cp, cpk
|
22 |
+
|
23 |
+
|
24 |
+
def process_unique_tool(tool, raw_data, file_id=None):
|
25 |
+
"""
|
26 |
+
Process data for a single tool and save the results to a CSV file.
|
27 |
+
Args:
|
28 |
+
tool (str): Tool ID to process.
|
29 |
+
raw_data (pd.DataFrame): DataFrame containing the raw production data.
|
30 |
+
"""
|
31 |
+
tool_data = raw_data[raw_data['Tool ID'] == tool].copy()
|
32 |
+
tool_data['pos_rolling_mean'], tool_data['pos_rolling_std'], tool_data['pos_rolling_cp'], tool_data['pos_rolling_cpk'] = stats_metrics(tool_data, 'Position', 0.5, 0.3)
|
33 |
+
tool_data['ori_rolling_mean'], tool_data['ori_rolling_std'], tool_data['ori_rolling_cp'], tool_data['ori_rolling_cpk'] = stats_metrics(tool_data, 'Orientation', 0.6, 0.2)
|
34 |
+
tool_data.to_csv(f'./data/tool_{file_id}.csv', index=False)
|
35 |
+
|
36 |
+
|
37 |
+
def tools_metrics(raw_data):
|
38 |
+
"""
|
39 |
+
Process the raw production data to extract tool metrics in parallel.
|
40 |
+
"""
|
41 |
+
tools = raw_data['Tool ID'].unique()
|
42 |
+
|
43 |
+
with ThreadPoolExecutor() as executor:
|
44 |
+
executor.map(lambda tool: process_unique_tool(tool, raw_data, file_id=tool), tools)
|
45 |
+
|
46 |
+
# Calculate metrics for all tools together
|
47 |
+
all_tools_data = raw_data.copy()
|
48 |
+
all_tools_data = all_tools_data[all_tools_data['Tool ID'] != 'N/A']
|
49 |
+
|
50 |
+
all_tools_data['pos_rolling_mean'], all_tools_data['pos_rolling_std'], all_tools_data['pos_rolling_cp'], all_tools_data['pos_rolling_cpk'] = stats_metrics(all_tools_data, 'Position', 0.5, 0.3)
|
51 |
+
all_tools_data['ori_rolling_mean'], all_tools_data['ori_rolling_std'], all_tools_data['ori_rolling_cp'], all_tools_data['ori_rolling_cpk'] = stats_metrics(all_tools_data, 'Orientation', 0.6, 0.2)
|
52 |
+
all_tools_data.to_csv('./data/tool_all.csv', index=False)
|
src/production/processing.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .metrics.tools import tools_metrics
|
2 |
+
from .metrics.machine import machine_metrics
|
3 |
+
|
4 |
+
def process(raw_data):
|
5 |
+
"""
|
6 |
+
Process the raw production data to extract metrics.
|
7 |
+
"""
|
8 |
+
tools_metrics(raw_data)
|
9 |
+
machine_metrics(raw_data)
|