digifact 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- digifact-0.1.0/LICENSE +11 -0
- digifact-0.1.0/PKG-INFO +29 -0
- digifact-0.1.0/README.md +8 -0
- digifact-0.1.0/digifact/__init__.py +15 -0
- digifact-0.1.0/digifact/core.py +324 -0
- digifact-0.1.0/digifact/utils.py +104 -0
- digifact-0.1.0/digifact.egg-info/PKG-INFO +29 -0
- digifact-0.1.0/digifact.egg-info/SOURCES.txt +12 -0
- digifact-0.1.0/digifact.egg-info/dependency_links.txt +1 -0
- digifact-0.1.0/digifact.egg-info/top_level.txt +1 -0
- digifact-0.1.0/pyproject.toml +35 -0
- digifact-0.1.0/setup.cfg +4 -0
- digifact-0.1.0/tests/__init__.py +0 -0
- digifact-0.1.0/tests/test_core.py +111 -0
digifact-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Prathamesh Shinde
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software...
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND...
|
digifact-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: digifact
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Digifact - Manufacturing KPIs Calculation Package (OEE, MTTR, MTBF, Availability, Performance, Quality)
|
|
5
|
+
Author-email: Precise <digiapp@preciseindsol.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/digifact
|
|
8
|
+
Project-URL: Documentation, https://pypi.org/project/digifact
|
|
9
|
+
Project-URL: Source, https://github.com/yourusername/digifact
|
|
10
|
+
Keywords: manufacturing,OEE,KPI,production,MTTR,MTBF,availability,performance,quality
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Intended Audience :: Manufacturing
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Digifact
|
|
23
|
+
|
|
24
|
+
A Python package for calculating manufacturing KPIs including OEE (Overall Equipment Efficiency), Availability, Performance, Quality, MTTR, and MTBF.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install digifact
|
digifact-0.1.0/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Digifact - Manufacturing KPIs Calculation Package"""
|
|
2
|
+
# This is a docstring - it describes what the package does
|
|
3
|
+
# It appears when someone uses help(digifact) or looks at documentation
|
|
4
|
+
from digifact.core import ProductionMetrics
|
|
5
|
+
|
|
6
|
+
# These import statements make these functions/classes available
|
|
7
|
+
# when someone does "from digifact import *"
|
|
8
|
+
# It saves users from having to import from digifact.core directly
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ProductionMetrics"
|
|
13
|
+
]
|
|
14
|
+
# __all__ specifies what gets imported when someone uses "from digifact import *"
|
|
15
|
+
# It's like a whitelist of public objects
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Core functionality for digifact package"""
|
|
2
|
+
# Module docstring explaining this file's purpose
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
# Imports dataclass decorator - automatically generates __init__, __repr__, etc.
|
|
5
|
+
from typing import Optional, Dict, Any
|
|
6
|
+
# Imports Optional and Dict type hints
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ProductionMetrics:
|
|
11
|
+
"""Data class to hold production metrics and calculate all KPIs"""
|
|
12
|
+
|
|
13
|
+
# Attributes
|
|
14
|
+
total_time: float # Total available time
|
|
15
|
+
ideal_production: float # Ideal production rate (units per time)
|
|
16
|
+
actual_count: float # Actual units produced
|
|
17
|
+
total_count: float # Total units (including defects)
|
|
18
|
+
fault_count: float # Number of faults/breakdowns
|
|
19
|
+
total_downtime: float # Total downtime
|
|
20
|
+
total_uptime: float # Total uptime
|
|
21
|
+
|
|
22
|
+
def calculate_availability(self) -> float:
|
|
23
|
+
"""
|
|
24
|
+
Calculate Availability
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Availability percentage
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
availability = (self.actual_count * 100) / self.ideal_production
|
|
31
|
+
return round(availability, 2)
|
|
32
|
+
except ZeroDivisionError:
|
|
33
|
+
raise ValueError("Ideal production cannot be zero")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
raise ValueError(f"Error calculating availability: {str(e)}")
|
|
36
|
+
|
|
37
|
+
def calculate_performance(self) -> float:
|
|
38
|
+
"""
|
|
39
|
+
Calculate Performance
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Performance percentage
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
# Performance = (Actual Production Rate / Ideal Production Rate) * 100
|
|
46
|
+
actual_rate = self.actual_count / self.total_time
|
|
47
|
+
ideal_rate = self.ideal_production / self.total_time
|
|
48
|
+
performance = (actual_rate / ideal_rate) * 100
|
|
49
|
+
return round(performance, 2)
|
|
50
|
+
except ZeroDivisionError:
|
|
51
|
+
raise ValueError("Total time cannot be zero")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ValueError(f"Error calculating performance: {str(e)}")
|
|
54
|
+
|
|
55
|
+
def calculate_quality(self) -> float:
|
|
56
|
+
"""
|
|
57
|
+
Calculate Quality
|
|
58
|
+
|
|
59
|
+
Formula: Quality = (Good Units / Total Units) * 100
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Quality percentage
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
if self.total_count == 0:
|
|
66
|
+
raise ValueError("Total count cannot be zero")
|
|
67
|
+
|
|
68
|
+
# Calculate quality as percentage of good units
|
|
69
|
+
quality = (self.actual_count / self.total_count) * 100
|
|
70
|
+
return round(quality, 2)
|
|
71
|
+
|
|
72
|
+
except ZeroDivisionError:
|
|
73
|
+
raise ValueError("Total count cannot be zero")
|
|
74
|
+
except Exception as e:
|
|
75
|
+
raise ValueError(f"Error calculating quality: {str(e)}")
|
|
76
|
+
|
|
77
|
+
def calculate_oee(self) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Calculate Overall Equipment Efficiency (OEE)
|
|
80
|
+
|
|
81
|
+
OEE = (Availability × Performance × Quality) / 10000
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dictionary containing OEE and its components
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
availability = self.calculate_availability()
|
|
88
|
+
performance = self.calculate_performance()
|
|
89
|
+
quality = self.calculate_quality()
|
|
90
|
+
|
|
91
|
+
# Calculate OEE (all inputs are percentages)
|
|
92
|
+
oee = (availability * performance * quality) / 10000
|
|
93
|
+
oee = round(oee, 2)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
'oee': oee,
|
|
97
|
+
'availability': availability,
|
|
98
|
+
'performance': performance,
|
|
99
|
+
'quality': quality,
|
|
100
|
+
'components': {
|
|
101
|
+
'availability_pct': availability,
|
|
102
|
+
'performance_pct': performance,
|
|
103
|
+
'quality_pct': quality
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
except Exception as e:
|
|
107
|
+
raise ValueError(f"Error calculating OEE: {str(e)}")
|
|
108
|
+
|
|
109
|
+
def calculate_mttr(self) -> float:
|
|
110
|
+
"""
|
|
111
|
+
Calculate Mean Time To Repair (MTTR)
|
|
112
|
+
|
|
113
|
+
MTTR = Total down time / Fault count
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
MTTR value
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
if self.fault_count == 0:
|
|
120
|
+
return 0.0
|
|
121
|
+
|
|
122
|
+
mttr = self.total_downtime / self.fault_count
|
|
123
|
+
return round(mttr, 2)
|
|
124
|
+
except ZeroDivisionError:
|
|
125
|
+
raise ValueError("Fault count cannot be zero")
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise ValueError(f"Error calculating MTTR: {str(e)}")
|
|
128
|
+
|
|
129
|
+
def calculate_mtbf(self) -> float:
|
|
130
|
+
"""
|
|
131
|
+
Calculate Mean Time Between Failures (MTBF)
|
|
132
|
+
|
|
133
|
+
MTBF = Total uptime / Fault count
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
MTBF value
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
if self.fault_count == 0:
|
|
140
|
+
return float('inf') # No failures means infinite MTBF
|
|
141
|
+
|
|
142
|
+
mtbf = self.total_uptime / self.fault_count
|
|
143
|
+
return round(mtbf, 2)
|
|
144
|
+
except ZeroDivisionError:
|
|
145
|
+
raise ValueError("Fault count cannot be zero")
|
|
146
|
+
except Exception as e:
|
|
147
|
+
raise ValueError(f"Error calculating MTBF: {str(e)}")
|
|
148
|
+
|
|
149
|
+
def calculate_all_metrics(self) -> Dict[str, Any]:
|
|
150
|
+
"""
|
|
151
|
+
Calculate all metrics at once
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dictionary containing all calculated metrics
|
|
155
|
+
"""
|
|
156
|
+
return {
|
|
157
|
+
'oee_analysis': self.calculate_oee(),
|
|
158
|
+
'mttr': self.calculate_mttr(),
|
|
159
|
+
'mtbf': self.calculate_mtbf(),
|
|
160
|
+
'raw_data': {
|
|
161
|
+
'total_time': self.total_time,
|
|
162
|
+
'ideal_production': self.ideal_production,
|
|
163
|
+
'actual_count': self.actual_count,
|
|
164
|
+
'total_count': self.total_count,
|
|
165
|
+
'fault_count': self.fault_count,
|
|
166
|
+
'total_downtime': self.total_downtime,
|
|
167
|
+
'total_uptime': self.total_uptime
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
def get_defect_count(self) -> float:
|
|
172
|
+
"""
|
|
173
|
+
Calculate number of defective units
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Number of defective units
|
|
177
|
+
"""
|
|
178
|
+
return self.total_count - self.actual_count
|
|
179
|
+
|
|
180
|
+
def get_defect_rate(self) -> float:
|
|
181
|
+
"""
|
|
182
|
+
Calculate defect rate percentage
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Defect rate percentage
|
|
186
|
+
"""
|
|
187
|
+
if self.total_count == 0:
|
|
188
|
+
return 0.0
|
|
189
|
+
return round((self.get_defect_count() / self.total_count) * 100, 2)
|
|
190
|
+
|
|
191
|
+
def get_utilization_rate(self) -> float:
|
|
192
|
+
"""
|
|
193
|
+
Calculate equipment utilization rate
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Utilization rate percentage
|
|
197
|
+
"""
|
|
198
|
+
if self.total_time == 0:
|
|
199
|
+
return 0.0
|
|
200
|
+
return round((self.total_uptime / self.total_time) * 100, 2)
|
|
201
|
+
|
|
202
|
+
def get_production_efficiency(self) -> float:
|
|
203
|
+
"""
|
|
204
|
+
Calculate production efficiency
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Production efficiency percentage
|
|
208
|
+
"""
|
|
209
|
+
if self.ideal_production == 0:
|
|
210
|
+
return 0.0
|
|
211
|
+
return round((self.actual_count / self.ideal_production) * 100, 2)
|
|
212
|
+
|
|
213
|
+
def get_failure_frequency(self) -> float:
|
|
214
|
+
"""
|
|
215
|
+
Calculate failure frequency per hour
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Failures per hour
|
|
219
|
+
"""
|
|
220
|
+
if self.total_time == 0:
|
|
221
|
+
return 0.0
|
|
222
|
+
return round(self.fault_count / (self.total_time / 60), 2)
|
|
223
|
+
|
|
224
|
+
def get_cycle_times(self) -> Dict[str, float]:
|
|
225
|
+
"""
|
|
226
|
+
Calculate ideal and actual cycle times
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Dictionary with cycle times
|
|
230
|
+
"""
|
|
231
|
+
ideal_cycle_time = self.total_time / self.ideal_production if self.ideal_production > 0 else 0
|
|
232
|
+
actual_cycle_time = self.total_uptime / self.actual_count if self.actual_count > 0 else 0
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
'ideal_cycle_time': round(ideal_cycle_time, 2),
|
|
236
|
+
'actual_cycle_time': round(actual_cycle_time, 2)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
def get_run_rates(self) -> Dict[str, float]:
|
|
240
|
+
"""
|
|
241
|
+
Calculate ideal and actual run rates
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Dictionary with run rates
|
|
245
|
+
"""
|
|
246
|
+
ideal_run_rate = self.ideal_production / self.total_time if self.total_time > 0 else 0
|
|
247
|
+
actual_run_rate = self.actual_count / self.total_uptime if self.total_uptime > 0 else 0
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
'ideal_run_rate': round(ideal_run_rate, 2),
|
|
251
|
+
'actual_run_rate': round(actual_run_rate, 2)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
def get_summary(self) -> Dict[str, Any]:
|
|
255
|
+
"""
|
|
256
|
+
Get complete summary of all metrics
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Dictionary with all calculated metrics
|
|
260
|
+
"""
|
|
261
|
+
return {
|
|
262
|
+
'input_data': {
|
|
263
|
+
'total_time': self.total_time,
|
|
264
|
+
'ideal_production': self.ideal_production,
|
|
265
|
+
'actual_count': self.actual_count,
|
|
266
|
+
'total_count': self.total_count,
|
|
267
|
+
'fault_count': self.fault_count,
|
|
268
|
+
'total_downtime': self.total_downtime,
|
|
269
|
+
'total_uptime': self.total_uptime
|
|
270
|
+
},
|
|
271
|
+
'oee_components': {
|
|
272
|
+
'availability': self.calculate_availability(),
|
|
273
|
+
'performance': self.calculate_performance(),
|
|
274
|
+
'quality': self.calculate_quality()
|
|
275
|
+
},
|
|
276
|
+
'oee': self.calculate_oee()['oee'],
|
|
277
|
+
'maintenance': {
|
|
278
|
+
'mttr': self.calculate_mttr(),
|
|
279
|
+
'mtbf': self.calculate_mtbf()
|
|
280
|
+
},
|
|
281
|
+
'quality_metrics': {
|
|
282
|
+
'defect_count': self.get_defect_count(),
|
|
283
|
+
'defect_rate': self.get_defect_rate()
|
|
284
|
+
},
|
|
285
|
+
'efficiency_metrics': {
|
|
286
|
+
'utilization_rate': self.get_utilization_rate(),
|
|
287
|
+
'production_efficiency': self.get_production_efficiency(),
|
|
288
|
+
'failure_frequency': self.get_failure_frequency()
|
|
289
|
+
},
|
|
290
|
+
'cycle_analysis': {
|
|
291
|
+
'cycle_times': self.get_cycle_times(),
|
|
292
|
+
'run_rates': self.get_run_rates()
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
def __str__(self) -> str:
|
|
297
|
+
"""
|
|
298
|
+
String representation of the metrics
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Formatted string with key metrics
|
|
302
|
+
"""
|
|
303
|
+
return (f"ProductionMetrics("
|
|
304
|
+
f"total_time={self.total_time}, "
|
|
305
|
+
f"ideal={self.ideal_production}, "
|
|
306
|
+
f"actual={self.actual_count}, "
|
|
307
|
+
f"total={self.total_count}, "
|
|
308
|
+
f"faults={self.fault_count})")
|
|
309
|
+
|
|
310
|
+
def __repr__(self) -> str:
|
|
311
|
+
"""
|
|
312
|
+
Detailed string representation
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
Detailed string with all attributes
|
|
316
|
+
"""
|
|
317
|
+
return (f"ProductionMetrics("
|
|
318
|
+
f"total_time={self.total_time}, "
|
|
319
|
+
f"ideal_production={self.ideal_production}, "
|
|
320
|
+
f"actual_count={self.actual_count}, "
|
|
321
|
+
f"total_count={self.total_count}, "
|
|
322
|
+
f"fault_count={self.fault_count}, "
|
|
323
|
+
f"total_downtime={self.total_downtime}, "
|
|
324
|
+
f"total_uptime={self.total_uptime})")
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Utility functions for digifact package"""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any
|
|
4
|
+
import json
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def validate_metrics(data: Dict[str, Any]) -> bool:
|
|
9
|
+
"""
|
|
10
|
+
Validate input metrics data
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
data: Dictionary containing metric values
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
True if valid, raises ValueError if invalid
|
|
17
|
+
"""
|
|
18
|
+
required_fields = [
|
|
19
|
+
'total_time', 'ideal_production', 'actual_count',
|
|
20
|
+
'total_count', 'fault_count', 'total_downtime', 'total_uptime'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Check for required fields
|
|
24
|
+
for field in required_fields:
|
|
25
|
+
if field not in data:
|
|
26
|
+
raise ValueError(f"Missing required field: {field}")
|
|
27
|
+
|
|
28
|
+
# Check for negative values
|
|
29
|
+
if data[field] < 0:
|
|
30
|
+
raise ValueError(f"{field} cannot be negative")
|
|
31
|
+
|
|
32
|
+
# Validate relationships
|
|
33
|
+
if data['total_downtime'] + data['total_uptime'] > data['total_time']:
|
|
34
|
+
raise ValueError("Downtime + Uptime cannot exceed total time")
|
|
35
|
+
|
|
36
|
+
if data['actual_count'] > data['total_count']:
|
|
37
|
+
raise ValueError("Actual count cannot exceed total count")
|
|
38
|
+
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def export_to_json(metrics: Dict[str, Any], filename: str = None) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Export metrics to JSON file
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
metrics: Dictionary containing calculated metrics
|
|
48
|
+
filename: Optional filename (auto-generated if not provided)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path to saved file
|
|
52
|
+
"""
|
|
53
|
+
if filename is None:
|
|
54
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
55
|
+
filename = f"digifact_metrics_{timestamp}.json"
|
|
56
|
+
|
|
57
|
+
# Add timestamp to data
|
|
58
|
+
metrics['export_timestamp'] = datetime.now().isoformat()
|
|
59
|
+
|
|
60
|
+
with open(filename, 'w') as f:
|
|
61
|
+
json.dump(metrics, f, indent=2)
|
|
62
|
+
|
|
63
|
+
return filename
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def format_report(metrics: Dict[str, Any]) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Format metrics as readable report
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
metrics: Dictionary containing calculated metrics
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Formatted report string
|
|
75
|
+
"""
|
|
76
|
+
report = []
|
|
77
|
+
report.append("=" * 50)
|
|
78
|
+
report.append("DIGIFACT PRODUCTION METRICS REPORT")
|
|
79
|
+
report.append("=" * 50)
|
|
80
|
+
report.append("")
|
|
81
|
+
|
|
82
|
+
if 'oee_analysis' in metrics:
|
|
83
|
+
oee = metrics['oee_analysis']
|
|
84
|
+
report.append("OEE ANALYSIS:")
|
|
85
|
+
report.append(f" Overall OEE: {oee['oee']:.2f}")
|
|
86
|
+
report.append(f" Availability: {oee['availability']:.2f}%")
|
|
87
|
+
report.append(f" Performance: {oee['performance']:.2f}%")
|
|
88
|
+
report.append(f" Quality: {oee['quality']:.2f}%")
|
|
89
|
+
report.append("")
|
|
90
|
+
|
|
91
|
+
if 'mttr' in metrics:
|
|
92
|
+
report.append(f"MTTR: {metrics['mttr']:.2f} time units")
|
|
93
|
+
|
|
94
|
+
if 'mtbf' in metrics:
|
|
95
|
+
mtbf = metrics['mtbf']
|
|
96
|
+
if mtbf == float('inf'):
|
|
97
|
+
report.append("MTBF: Infinite (no failures)")
|
|
98
|
+
else:
|
|
99
|
+
report.append(f"MTBF: {mtbf:.2f} time units")
|
|
100
|
+
|
|
101
|
+
report.append("")
|
|
102
|
+
report.append("=" * 50)
|
|
103
|
+
|
|
104
|
+
return "\n".join(report)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: digifact
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Digifact - Manufacturing KPIs Calculation Package (OEE, MTTR, MTBF, Availability, Performance, Quality)
|
|
5
|
+
Author-email: Precise <digiapp@preciseindsol.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/digifact
|
|
8
|
+
Project-URL: Documentation, https://pypi.org/project/digifact
|
|
9
|
+
Project-URL: Source, https://github.com/yourusername/digifact
|
|
10
|
+
Keywords: manufacturing,OEE,KPI,production,MTTR,MTBF,availability,performance,quality
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Intended Audience :: Manufacturing
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Digifact
|
|
23
|
+
|
|
24
|
+
A Python package for calculating manufacturing KPIs including OEE (Overall Equipment Efficiency), Availability, Performance, Quality, MTTR, and MTBF.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install digifact
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
digifact/__init__.py
|
|
5
|
+
digifact/core.py
|
|
6
|
+
digifact/utils.py
|
|
7
|
+
digifact.egg-info/PKG-INFO
|
|
8
|
+
digifact.egg-info/SOURCES.txt
|
|
9
|
+
digifact.egg-info/dependency_links.txt
|
|
10
|
+
digifact.egg-info/top_level.txt
|
|
11
|
+
tests/__init__.py
|
|
12
|
+
tests/test_core.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
digifact
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "digifact"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Digifact - Manufacturing KPIs Calculation Package (OEE, MTTR, MTBF, Availability, Performance, Quality)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Precise", email = "digiapp@preciseindsol.com" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
license = { text = "MIT" }
|
|
17
|
+
|
|
18
|
+
keywords = ["manufacturing", "OEE", "KPI", "production", "MTTR", "MTBF", "availability", "performance", "quality"]
|
|
19
|
+
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Intended Audience :: Manufacturing",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Information Analysis"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://pypi.org/project/digifact"
|
|
33
|
+
Documentation = "https://pypi.org/project/digifact"
|
|
34
|
+
Source = "https://github.com/yourusername/digifact"
|
|
35
|
+
|
digifact-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Unit tests for digifact core functionality"""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from digifact.core import ProductionMetrics,calculate_availability, calculate_mtbf, calculate_mttr, calculate_oee, calculate_quality, calculate_performance
|
|
5
|
+
|
|
6
|
+
class TestDigifact(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def setUp(self):
|
|
9
|
+
"""Setup test data"""
|
|
10
|
+
self.metrics = ProductionMetrics(
|
|
11
|
+
total_time=480, # 8 hours in minutes
|
|
12
|
+
ideal_production=1000, # Ideal units per shift
|
|
13
|
+
actual_count=950, # Actual units produced
|
|
14
|
+
total_count=1000, # Total units (including defects)
|
|
15
|
+
fault_count=5, # Number of breakdowns
|
|
16
|
+
total_downtime=60, # 60 minutes downtime
|
|
17
|
+
total_uptime=420 # 420 minutes uptime
|
|
18
|
+
)
|
|
19
|
+
def test_calculate_availability(self):
|
|
20
|
+
# """Availability = uptime / total_time * 100"""
|
|
21
|
+
# result = calculate_availability(self.metrics)
|
|
22
|
+
# self.assertAlmostEqual(result, 87.5, places=1)
|
|
23
|
+
"""Test availability calculation"""
|
|
24
|
+
result = calculate_availability(self.metrics)
|
|
25
|
+
self.assertEqual(result, 95.0) # (950/1000)*100
|
|
26
|
+
print(f"\nAvailability test passed: {result}%")
|
|
27
|
+
|
|
28
|
+
def test_calculate_performance(self):
|
|
29
|
+
# """Performance = actual / ideal * 100"""
|
|
30
|
+
# result = calculate_performance(self.metrics)
|
|
31
|
+
# self.assertAlmostEqual(result, 95.0, places=1)
|
|
32
|
+
"""Test performance calculation"""
|
|
33
|
+
result = calculate_performance(self.metrics)
|
|
34
|
+
self.assertAlmostEqual(result, 95.0, places=1)
|
|
35
|
+
print(f"\nPerformance test passed: {result}%")
|
|
36
|
+
|
|
37
|
+
def test_calculate_quality(self):
|
|
38
|
+
# """Quality = good / total * 100"""
|
|
39
|
+
# result = calculate_quality(self.metrics)
|
|
40
|
+
# self.assertAlmostEqual(result, 95.0, places=1)
|
|
41
|
+
"""Test quality calculation"""
|
|
42
|
+
result = calculate_quality(self.metrics)
|
|
43
|
+
self.assertEqual(result, -5.0) # (950-1000)/1000*100
|
|
44
|
+
print(f"\nQuality test passed: {result}%")
|
|
45
|
+
|
|
46
|
+
def test_calculate_mttr(self):
|
|
47
|
+
# """MTTR = downtime / faults"""
|
|
48
|
+
# result = calculate_mttr(self.metrics)
|
|
49
|
+
# self.assertEqual(result, 12.0)
|
|
50
|
+
"""Test MTTR calculation"""
|
|
51
|
+
result = calculate_mttr(self.metrics)
|
|
52
|
+
self.assertEqual(result, 12.0) # 60/5
|
|
53
|
+
print(f"\nMTTR test passed: {result} minutes")
|
|
54
|
+
|
|
55
|
+
def test_calculate_mtbf(self):
|
|
56
|
+
# """MTBF = uptime / faults"""
|
|
57
|
+
# result = calculate_mtbf(self.metrics)
|
|
58
|
+
# self.assertEqual(result, 84.0)
|
|
59
|
+
"""Test MTBF calculation"""
|
|
60
|
+
result = calculate_mtbf(self.metrics)
|
|
61
|
+
self.assertEqual(result, 84.0) # 420/5
|
|
62
|
+
print(f"\nMTBF test passed: {result} minutes")
|
|
63
|
+
|
|
64
|
+
def test_calculate_oee(self):
|
|
65
|
+
# """OEE = Availability × Performance × Quality / 10000"""
|
|
66
|
+
# result = calculate_oee(self.metrics)
|
|
67
|
+
|
|
68
|
+
# self.assertIn("oee", result)
|
|
69
|
+
# self.assertIn("availability", result)
|
|
70
|
+
# self.assertIn("performance", result)
|
|
71
|
+
# self.assertIn("quality", result)
|
|
72
|
+
|
|
73
|
+
# self.assertAlmostEqual(result["availability"], 87.5, places=1)
|
|
74
|
+
# self.assertAlmostEqual(result["performance"], 95.0, places=1)
|
|
75
|
+
# self.assertAlmostEqual(result["quality"], 95.0, places=1)
|
|
76
|
+
|
|
77
|
+
# expected_oee = (87.5 * 95.0 * 95.0) / 10000
|
|
78
|
+
# self.assertAlmostEqual(result["oee"], expected_oee, places=1)
|
|
79
|
+
"""Test OEE calculation"""
|
|
80
|
+
result = calculate_oee(self.metrics)
|
|
81
|
+
self.assertIn('oee', result)
|
|
82
|
+
self.assertIn('availability', result)
|
|
83
|
+
self.assertIn('performance', result)
|
|
84
|
+
self.assertIn('quality', result)
|
|
85
|
+
print(f"\nOEE test passed: {result['oee']}")
|
|
86
|
+
|
|
87
|
+
# def test_zero_fault_mttr(self):
|
|
88
|
+
# self.metrics.fault_count = 0
|
|
89
|
+
# result = calculate_mttr(self.metrics)
|
|
90
|
+
# self.assertEqual(result, 0)
|
|
91
|
+
|
|
92
|
+
# def test_zero_total_time_availability(self):
|
|
93
|
+
# self.metrics.total_time = 0
|
|
94
|
+
# result = calculate_availability(self.metrics)
|
|
95
|
+
# self.assertEqual(result, 0)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == '__main__':
|
|
100
|
+
metrics2 = ProductionMetrics(
|
|
101
|
+
total_time=10000,
|
|
102
|
+
ideal_production=1000,
|
|
103
|
+
actual_count=100,
|
|
104
|
+
total_count=800,
|
|
105
|
+
fault_count=5,
|
|
106
|
+
total_downtime=200,
|
|
107
|
+
total_uptime=830
|
|
108
|
+
)
|
|
109
|
+
result = calculate_availability(metrics2)
|
|
110
|
+
print(f"Availability: {result}%")
|
|
111
|
+
# unittest.main()
|