perfact-api-pd-fastapi 1.7__tar.gz → 1.8__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.
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/PKG-INFO +1 -1
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/kpi.py +19 -13
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/PKG-INFO +1 -1
- perfact_api_pd_fastapi-1.8/src/perfact_api_pd_fastapi.egg-info/scm_version.json +8 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_kpi.py +43 -31
- perfact_api_pd_fastapi-1.7/src/perfact_api_pd_fastapi.egg-info/scm_version.json +0 -8
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/README.md +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/pyproject.toml +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/setup.cfg +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/__init__.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/grafana.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/pdunit.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/pdunitlch.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/performancedata.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/py.typed +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/SOURCES.txt +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/dependency_links.txt +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/entry_points.txt +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/requires.txt +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/scm_file_list.json +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/top_level.txt +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/__init__.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/conftest.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/helpers.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/locustfile.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_init.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_pdunit.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_pdunitlch.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_performance.py +0 -0
- {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tox.ini +0 -0
|
@@ -6,7 +6,10 @@ from perfact.api.main.auth import require_roles
|
|
|
6
6
|
from perfact.api.main.dbsession import APIRouter, DBSession
|
|
7
7
|
from perfact.api.pd.model.pdunit import PdUnit
|
|
8
8
|
from perfact.api.pd.services import get_failure_times
|
|
9
|
-
from perfact.api.pd.services.oee.availability import
|
|
9
|
+
from perfact.api.pd.services.oee.availability import (
|
|
10
|
+
compute_availability,
|
|
11
|
+
compute_productive_seconds,
|
|
12
|
+
)
|
|
10
13
|
from perfact.api.pd.services.oee.breakdown import oee_breakdown_computation
|
|
11
14
|
from perfact.api.pd.services.oee.prod_metrics import (
|
|
12
15
|
compute_performance,
|
|
@@ -67,18 +70,17 @@ def oee_performance(
|
|
|
67
70
|
session: DBSession,
|
|
68
71
|
starttime: datetime,
|
|
69
72
|
stoptime: datetime,
|
|
70
|
-
) -> float:
|
|
73
|
+
) -> float | None:
|
|
71
74
|
"""
|
|
72
|
-
Route for OEE
|
|
75
|
+
Route for OEE performance
|
|
73
76
|
Uses the performance_computation function
|
|
74
|
-
Returns single floating point value
|
|
77
|
+
Returns a single floating point value, or null when there is no productive
|
|
78
|
+
(isexecution) time in the window.
|
|
75
79
|
"""
|
|
76
80
|
records = get_prod_records(session, unit.id, starttime, stoptime)
|
|
77
81
|
spans = get_state_spans(session, unit.id, starttime, stoptime)
|
|
78
|
-
|
|
79
|
-
return compute_performance(
|
|
80
|
-
records, starttime, stoptime, isexecution_spans=isexecution_spans
|
|
81
|
-
)
|
|
82
|
+
execution_seconds = compute_productive_seconds(spans, starttime, stoptime)
|
|
83
|
+
return compute_performance(records, starttime, stoptime, execution_seconds)
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
@routes.get("/kpi/oee/quality", tags=["pd"], summary="Route for OEE quality")
|
|
@@ -108,7 +110,7 @@ class OEECalculationResponse(BaseModel):
|
|
|
108
110
|
"""
|
|
109
111
|
|
|
110
112
|
availability: float
|
|
111
|
-
performance: float
|
|
113
|
+
performance: float | None
|
|
112
114
|
quality: float | None
|
|
113
115
|
oee: float | None
|
|
114
116
|
planned_machine_time_seconds: float
|
|
@@ -132,12 +134,16 @@ def oee_calculation(
|
|
|
132
134
|
shifts = list(session.scalars(unit.shifts_in_range_stmt(starttime, stoptime)).all())
|
|
133
135
|
availability = compute_availability(spans, shifts, starttime, stoptime)
|
|
134
136
|
prod_records = get_prod_records(session, unit.id, starttime, stoptime)
|
|
135
|
-
|
|
137
|
+
execution_seconds = compute_productive_seconds(spans, starttime, stoptime)
|
|
136
138
|
performance = compute_performance(
|
|
137
|
-
prod_records, starttime, stoptime,
|
|
139
|
+
prod_records, starttime, stoptime, execution_seconds
|
|
138
140
|
)
|
|
139
141
|
quality = compute_quality(prod_records, starttime, stoptime)
|
|
140
|
-
oee =
|
|
142
|
+
oee = (
|
|
143
|
+
availability * performance * quality
|
|
144
|
+
if performance is not None and quality is not None
|
|
145
|
+
else None
|
|
146
|
+
)
|
|
141
147
|
summary = oee_workcenter_time_computation(session, unit.id, starttime, stoptime)
|
|
142
148
|
return OEECalculationResponse(
|
|
143
149
|
availability=availability,
|
|
@@ -156,7 +162,7 @@ class OEEBreakdownResponse(BaseModel):
|
|
|
156
162
|
setup_time_deviation_seconds: float
|
|
157
163
|
productive_time_seconds: float
|
|
158
164
|
availability: float
|
|
159
|
-
performance: float
|
|
165
|
+
performance: float | None
|
|
160
166
|
quality: float | None
|
|
161
167
|
oee: float | None
|
|
162
168
|
pdordprd_quantitytotal: float
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from datetime import datetime, timezone
|
|
2
|
+
from datetime import datetime, timedelta, timezone
|
|
3
3
|
from unittest.mock import Mock
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
@@ -151,35 +151,41 @@ def test_oee_calculation_returns_combined_values(
|
|
|
151
151
|
assert result.pdordprd_quantity == 9.0
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
def
|
|
154
|
+
def test_oee_performance_route_denominator_is_productive_time(
|
|
155
155
|
monkeypatch: pytest.MonkeyPatch,
|
|
156
156
|
) -> None:
|
|
157
|
-
"""oee_performance passes only isexecution spans
|
|
158
|
-
|
|
159
|
-
Non-execution spans (e.g. setup) must be excluded so that production records
|
|
160
|
-
recorded during setup are not included in the performance numerator/denominator.
|
|
157
|
+
"""oee_performance passes Productive Time (only isexecution spans) as the
|
|
158
|
+
performance denominator; setup/other active spans do not count.
|
|
161
159
|
"""
|
|
162
160
|
session: Session = Mock()
|
|
163
|
-
pdunit_id = 7
|
|
164
161
|
unit = Mock()
|
|
165
|
-
unit.id =
|
|
162
|
+
unit.id = 7
|
|
166
163
|
starttime = datetime(2025, 1, 1, tzinfo=timezone.utc)
|
|
167
164
|
stoptime = datetime(2025, 1, 2, tzinfo=timezone.utc)
|
|
168
165
|
|
|
169
|
-
dummy_range: PsycopgRange[datetime] = PsycopgRange(None, None)
|
|
170
166
|
exec_span = StateSpan(
|
|
171
|
-
pdord_id=1,
|
|
167
|
+
pdord_id=1,
|
|
168
|
+
range=PsycopgRange(starttime, starttime + timedelta(hours=1), bounds="[)"),
|
|
169
|
+
isactive=True,
|
|
170
|
+
isexecution=True,
|
|
172
171
|
)
|
|
173
172
|
setup_span = StateSpan(
|
|
174
|
-
pdord_id=1,
|
|
173
|
+
pdord_id=1,
|
|
174
|
+
range=PsycopgRange(
|
|
175
|
+
starttime + timedelta(hours=1),
|
|
176
|
+
starttime + timedelta(hours=2),
|
|
177
|
+
bounds="[)",
|
|
178
|
+
),
|
|
179
|
+
isactive=True,
|
|
180
|
+
isexecution=False,
|
|
175
181
|
)
|
|
176
|
-
|
|
182
|
+
captured: dict[str, float] = {}
|
|
177
183
|
|
|
178
184
|
monkeypatch.setattr(kpi, "get_prod_records", lambda *_: [])
|
|
179
185
|
monkeypatch.setattr(kpi, "get_state_spans", lambda *_: [exec_span, setup_span])
|
|
180
186
|
|
|
181
|
-
def fake_compute_performance(records, start, stop,
|
|
182
|
-
|
|
187
|
+
def fake_compute_performance(records, start, stop, execution_seconds):
|
|
188
|
+
captured["execution_seconds"] = execution_seconds
|
|
183
189
|
return 0.82
|
|
184
190
|
|
|
185
191
|
monkeypatch.setattr(kpi, "compute_performance", fake_compute_performance)
|
|
@@ -187,41 +193,47 @@ def test_oee_performance_route_passes_only_isexecution_spans(
|
|
|
187
193
|
coro = kpi.oee_performance(unit, session, starttime, stoptime)
|
|
188
194
|
asyncio.run(coro) # type: ignore[arg-type]
|
|
189
195
|
|
|
190
|
-
|
|
191
|
-
assert
|
|
196
|
+
# only the 1 h isexecution span contributes; the setup span is excluded
|
|
197
|
+
assert captured["execution_seconds"] == pytest.approx(3600.0)
|
|
192
198
|
|
|
193
199
|
|
|
194
|
-
def
|
|
200
|
+
def test_oee_calculation_denominator_is_productive_time(
|
|
195
201
|
monkeypatch: pytest.MonkeyPatch,
|
|
196
202
|
) -> None:
|
|
197
|
-
"""oee_calculation
|
|
198
|
-
|
|
199
|
-
Non-execution spans must be excluded so the work-centre performance matches
|
|
200
|
-
the per-operation breakdown performance for single-operation scenarios.
|
|
203
|
+
"""oee_calculation uses Productive Time (only isexecution spans) as the
|
|
204
|
+
performance denominator.
|
|
201
205
|
"""
|
|
202
206
|
session: Session = Mock()
|
|
203
207
|
session.scalars.return_value.all.return_value = [] # type: ignore[attr-defined]
|
|
204
|
-
pdunit_id = 3
|
|
205
208
|
unit = Mock()
|
|
206
|
-
unit.id =
|
|
209
|
+
unit.id = 3
|
|
207
210
|
starttime = datetime(2025, 2, 1, tzinfo=timezone.utc)
|
|
208
211
|
stoptime = datetime(2025, 2, 2, tzinfo=timezone.utc)
|
|
209
212
|
|
|
210
|
-
dummy_range: PsycopgRange[datetime] = PsycopgRange(None, None)
|
|
211
213
|
exec_span = StateSpan(
|
|
212
|
-
pdord_id=1,
|
|
214
|
+
pdord_id=1,
|
|
215
|
+
range=PsycopgRange(starttime, starttime + timedelta(hours=1), bounds="[)"),
|
|
216
|
+
isactive=True,
|
|
217
|
+
isexecution=True,
|
|
213
218
|
)
|
|
214
219
|
setup_span = StateSpan(
|
|
215
|
-
pdord_id=1,
|
|
220
|
+
pdord_id=1,
|
|
221
|
+
range=PsycopgRange(
|
|
222
|
+
starttime + timedelta(hours=1),
|
|
223
|
+
starttime + timedelta(hours=2),
|
|
224
|
+
bounds="[)",
|
|
225
|
+
),
|
|
226
|
+
isactive=True,
|
|
227
|
+
isexecution=False,
|
|
216
228
|
)
|
|
217
|
-
|
|
229
|
+
captured: dict[str, float] = {}
|
|
218
230
|
|
|
219
231
|
monkeypatch.setattr(kpi, "get_state_spans", lambda *_: [exec_span, setup_span])
|
|
220
232
|
monkeypatch.setattr(kpi, "compute_availability", lambda *_, **__: 0.9)
|
|
221
233
|
monkeypatch.setattr(kpi, "get_prod_records", lambda *_: [])
|
|
222
234
|
|
|
223
|
-
def fake_compute_performance(records, start, stop,
|
|
224
|
-
|
|
235
|
+
def fake_compute_performance(records, start, stop, execution_seconds):
|
|
236
|
+
captured["execution_seconds"] = execution_seconds
|
|
225
237
|
return 0.8
|
|
226
238
|
|
|
227
239
|
monkeypatch.setattr(kpi, "compute_performance", fake_compute_performance)
|
|
@@ -242,8 +254,8 @@ def test_oee_calculation_performance_uses_only_isexecution_spans(
|
|
|
242
254
|
coro = kpi.oee_calculation(unit, session, starttime, stoptime)
|
|
243
255
|
asyncio.run(coro) # type: ignore[arg-type]
|
|
244
256
|
|
|
245
|
-
|
|
246
|
-
assert
|
|
257
|
+
# only the 1 h isexecution span contributes; the setup span is excluded
|
|
258
|
+
assert captured["execution_seconds"] == pytest.approx(3600.0)
|
|
247
259
|
|
|
248
260
|
|
|
249
261
|
def test_mount_includes_kpi_routes() -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/__init__.py
RENAMED
|
File without changes
|
{perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/grafana.py
RENAMED
|
File without changes
|
{perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/pdunit.py
RENAMED
|
File without changes
|
{perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/pdunitlch.py
RENAMED
|
File without changes
|
|
File without changes
|
{perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|