mriusero commited on
Commit
05a4c82
·
1 Parent(s): 4c16235

feat: production metrics

Browse files
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.001:
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(1)
73
 
74
  current_time += timedelta(seconds=1)
75
 
@@ -79,13 +80,13 @@ def synthetic_data():
79
 
80
  return data
81
 
82
- def update_display(data):
83
  """
84
  Update production data in real-time.
85
  """
86
- display_data = []
87
  for row in data:
88
- display_data.append({
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(display_data)
102
 
103
 
104
  def play_fn():
105
  """
106
  Start the production simulation and generate synthetic data.
107
  """
108
- print("=== Continuation production ===")
109
  global PRODUCTION
110
  PRODUCTION = True
111
  while PRODUCTION:
112
  data = synthetic_data()
113
- display_data = update_display(data)
114
- yield display_data
115
- time.sleep(1)
116
 
117
 
118
  def pause_fn():
119
  """
120
  Pause the production simulation.
121
  """
122
- print("=== Stopping production ===")
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)