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.
Files changed (30) hide show
  1. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/PKG-INFO +1 -1
  2. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/kpi.py +19 -13
  3. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/PKG-INFO +1 -1
  4. perfact_api_pd_fastapi-1.8/src/perfact_api_pd_fastapi.egg-info/scm_version.json +8 -0
  5. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_kpi.py +43 -31
  6. perfact_api_pd_fastapi-1.7/src/perfact_api_pd_fastapi.egg-info/scm_version.json +0 -8
  7. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/README.md +0 -0
  8. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/pyproject.toml +0 -0
  9. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/setup.cfg +0 -0
  10. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/__init__.py +0 -0
  11. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/grafana.py +0 -0
  12. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/pdunit.py +0 -0
  13. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/pdunitlch.py +0 -0
  14. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/performancedata.py +0 -0
  15. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact/api/pd_fastapi/py.typed +0 -0
  16. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/SOURCES.txt +0 -0
  17. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/dependency_links.txt +0 -0
  18. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/entry_points.txt +0 -0
  19. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/requires.txt +0 -0
  20. {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
  21. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/src/perfact_api_pd_fastapi.egg-info/top_level.txt +0 -0
  22. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/__init__.py +0 -0
  23. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/conftest.py +0 -0
  24. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/helpers.py +0 -0
  25. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/locustfile.py +0 -0
  26. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_init.py +0 -0
  27. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_pdunit.py +0 -0
  28. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_pdunitlch.py +0 -0
  29. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tests/test_performance.py +0 -0
  30. {perfact_api_pd_fastapi-1.7 → perfact_api_pd_fastapi-1.8}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: perfact-api-pd-fastapi
3
- Version: 1.7
3
+ Version: 1.8
4
4
  Summary: PerFact API - SQLAlchemy+FastAPI pd apis package
5
5
  Author-email: Viktor Dick <viktor.dick@perfact.de>
6
6
  License-Expression: GPL-2.0-or-later
@@ -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 compute_availability
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 availability
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
- isexecution_spans = [s for s in spans if s.isexecution]
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
- isexecution_spans = [s for s in spans if s.isexecution]
137
+ execution_seconds = compute_productive_seconds(spans, starttime, stoptime)
136
138
  performance = compute_performance(
137
- prod_records, starttime, stoptime, isexecution_spans=isexecution_spans
139
+ prod_records, starttime, stoptime, execution_seconds
138
140
  )
139
141
  quality = compute_quality(prod_records, starttime, stoptime)
140
- oee = availability * performance * quality if quality is not None else None
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: perfact-api-pd-fastapi
3
- Version: 1.7
3
+ Version: 1.8
4
4
  Summary: PerFact API - SQLAlchemy+FastAPI pd apis package
5
5
  Author-email: Viktor Dick <viktor.dick@perfact.de>
6
6
  License-Expression: GPL-2.0-or-later
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.8",
3
+ "distance": 0,
4
+ "node": "gabbf5b824885446fb287a1a5d88e4faed71b933e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-31"
8
+ }
@@ -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 test_oee_performance_route_passes_only_isexecution_spans(
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 to compute_performance.
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 = pdunit_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, range=dummy_range, isactive=True, isexecution=True
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, range=dummy_range, isactive=True, isexecution=False
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
- captured_isexecution_spans: list[StateSpan] = []
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, isexecution_spans=None):
182
- captured_isexecution_spans.extend(isexecution_spans or [])
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
- assert exec_span in captured_isexecution_spans
191
- assert setup_span not in captured_isexecution_spans
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 test_oee_calculation_performance_uses_only_isexecution_spans(
200
+ def test_oee_calculation_denominator_is_productive_time(
195
201
  monkeypatch: pytest.MonkeyPatch,
196
202
  ) -> None:
197
- """oee_calculation passes only isexecution spans to compute_performance.
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 = pdunit_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, range=dummy_range, isactive=True, isexecution=True
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, range=dummy_range, isactive=True, isexecution=False
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
- captured_isexecution_spans: list[StateSpan] = []
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, isexecution_spans=None):
224
- captured_isexecution_spans.extend(isexecution_spans or [])
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
- assert exec_span in captured_isexecution_spans
246
- assert setup_span not in captured_isexecution_spans
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:
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.7",
3
- "distance": 0,
4
- "node": "g4aa2bc0baa5e5d3232ee124539082fc3c08f47a1",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-09"
8
- }