perfact-api-pd-fastapi 1.3__tar.gz → 1.5__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 (29) hide show
  1. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/PKG-INFO +1 -1
  2. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact/api/pd_fastapi/kpi.py +20 -87
  3. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact/api/pd_fastapi/pdunitlch.py +40 -4
  4. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact_api_pd_fastapi.egg-info/PKG-INFO +1 -1
  5. perfact_api_pd_fastapi-1.5/src/perfact_api_pd_fastapi.egg-info/scm_version.json +8 -0
  6. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/test_kpi.py +21 -36
  7. perfact_api_pd_fastapi-1.3/src/perfact_api_pd_fastapi.egg-info/scm_version.json +0 -8
  8. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/README.md +0 -0
  9. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/pyproject.toml +0 -0
  10. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/setup.cfg +0 -0
  11. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact/api/pd_fastapi/__init__.py +0 -0
  12. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact/api/pd_fastapi/grafana.py +0 -0
  13. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact/api/pd_fastapi/pdunit.py +0 -0
  14. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact/api/pd_fastapi/performancedata.py +0 -0
  15. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact/api/pd_fastapi/py.typed +0 -0
  16. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact_api_pd_fastapi.egg-info/SOURCES.txt +0 -0
  17. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact_api_pd_fastapi.egg-info/dependency_links.txt +0 -0
  18. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact_api_pd_fastapi.egg-info/entry_points.txt +0 -0
  19. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact_api_pd_fastapi.egg-info/requires.txt +0 -0
  20. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact_api_pd_fastapi.egg-info/scm_file_list.json +7 -7
  21. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/src/perfact_api_pd_fastapi.egg-info/top_level.txt +0 -0
  22. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/__init__.py +0 -0
  23. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/conftest.py +0 -0
  24. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/helpers.py +0 -0
  25. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/locustfile.py +0 -0
  26. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/test_init.py +0 -0
  27. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/test_pdunit.py +0 -0
  28. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tests/test_performance.py +0 -0
  29. {perfact_api_pd_fastapi-1.3 → perfact_api_pd_fastapi-1.5}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: perfact-api-pd-fastapi
3
- Version: 1.3
3
+ Version: 1.5
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
@@ -1,9 +1,7 @@
1
1
  from datetime import datetime
2
- from typing import Annotated, Optional
2
+ from typing import Annotated, Optional, TypeAlias
3
3
 
4
- from fastapi import HTTPException, Query, status
5
- from perfact.api.base import VisibilityPolicy
6
- from perfact.api.main import Auth, VisibilityRegistryDep
4
+ from perfact.api.main import require_visible, require_visible_list
7
5
  from perfact.api.main.auth import require_roles
8
6
  from perfact.api.main.dbsession import APIRouter, DBSession
9
7
  from perfact.api.pd.model.pdunit import PdUnit
@@ -23,42 +21,12 @@ from perfact.api.pd.services.oee.trend import (
23
21
  get_oee_trend_candidate_days,
24
22
  )
25
23
  from pydantic import BaseModel, BeforeValidator
26
- from sqlalchemy import select
27
24
 
28
25
  routes = APIRouter()
29
26
  routes_assignd = APIRouter()
30
27
 
31
-
32
- def _require_visible_unit(
33
- session: DBSession,
34
- auth: Auth,
35
- visibility: VisibilityRegistryDep,
36
- pdunit_id: int,
37
- ) -> PdUnit:
38
- """Fetch PdUnit with visibility policy applied. Raise 404 if not found or hidden."""
39
- policy = visibility.get(PdUnit)
40
- unit = session.scalar(
41
- select(PdUnit).where(PdUnit.id == pdunit_id).where(policy.filter(auth))
42
- )
43
- if unit is None:
44
- raise HTTPException(status_code=404)
45
- return unit
46
-
47
-
48
- def _filter_visible_pdunit_ids(
49
- session: DBSession,
50
- auth: Auth,
51
- policy: VisibilityPolicy,
52
- pdunit_ids: list[int],
53
- ) -> list[int]:
54
- """Return only PdUnit IDs visible to the current user."""
55
- return list(
56
- session.scalars(
57
- select(PdUnit.id)
58
- .where(PdUnit.id.in_(pdunit_ids))
59
- .where(policy.filter(auth))
60
- ).all()
61
- )
28
+ VisiblePdUnit: TypeAlias = require_visible(PdUnit, "pdunit_id") # type: ignore[valid-type]
29
+ VisiblePdUnits: TypeAlias = require_visible_list(PdUnit, "pdunit_ids") # type: ignore[valid-type]
62
30
 
63
31
 
64
32
  def parse_datetime_with_UTC_offset(value: str) -> datetime:
@@ -77,15 +45,9 @@ UTCOffsetDatetime = Annotated[datetime, BeforeValidator(parse_datetime_with_UTC_
77
45
  @routes.get("/kpi/oee/availability", tags=["pd"], summary="Route for OEE availability")
78
46
  @require_roles("PdKpi")
79
47
  def oee_availability(
48
+ unit: VisiblePdUnit,
80
49
  session: DBSession,
81
- auth: Auth,
82
- visibility: VisibilityRegistryDep,
83
- pdunit_id: int,
84
- # ex w timezone "2025-05-20T13:54:15.555103+03:00"
85
- # ex wo timezone "2025-05-20T13:54:15.555103"
86
50
  starttime: datetime,
87
- # ex w timezone "2025-05-22T11:53:13.561411+03:00"
88
- # ex wo timezone "2025-05-22T11:53:13.561411"
89
51
  stoptime: datetime,
90
52
  ) -> float:
91
53
  """
@@ -94,8 +56,7 @@ def oee_availability(
94
56
  Returns single floating point value
95
57
  """
96
58
 
97
- unit = _require_visible_unit(session, auth, visibility, pdunit_id)
98
- spans = get_state_spans(session, pdunit_id, starttime, stoptime)
59
+ spans = get_state_spans(session, unit.id, starttime, stoptime)
99
60
  shifts = list(session.scalars(unit.shifts_in_range_stmt(starttime, stoptime)).all())
100
61
  return compute_availability(spans, shifts, starttime, stoptime)
101
62
 
@@ -103,15 +64,9 @@ def oee_availability(
103
64
  @routes.get("/kpi/oee/performance", tags=["pd"], summary="Route for OEE performance")
104
65
  @require_roles("PdKpi")
105
66
  def oee_performance(
67
+ unit: VisiblePdUnit,
106
68
  session: DBSession,
107
- auth: Auth,
108
- visibility: VisibilityRegistryDep,
109
- pdunit_id: int,
110
- # ex w timezone "2025-05-20T13:54:15.555103+03:00"
111
- # ex wo timezone "2025-05-20T13:54:15.555103"
112
69
  starttime: datetime,
113
- # ex w timezone "2025-05-22T11:53:13.561411+03:00"
114
- # ex wo timezone "2025-05-22T11:53:13.561411"
115
70
  stoptime: datetime,
116
71
  ) -> float:
117
72
  """
@@ -119,9 +74,8 @@ def oee_performance(
119
74
  Uses the performance_computation function
120
75
  Returns single floating point value
121
76
  """
122
- _require_visible_unit(session, auth, visibility, pdunit_id)
123
- records = get_prod_records(session, pdunit_id, starttime, stoptime)
124
- spans = get_state_spans(session, pdunit_id, starttime, stoptime)
77
+ records = get_prod_records(session, unit.id, starttime, stoptime)
78
+ spans = get_state_spans(session, unit.id, starttime, stoptime)
125
79
  isexecution_spans = [s for s in spans if s.isexecution]
126
80
  return compute_performance(
127
81
  records, starttime, stoptime, isexecution_spans=isexecution_spans
@@ -132,15 +86,9 @@ def oee_performance(
132
86
  # response_model=Sequence[PdordlchGet])
133
87
  @require_roles("PdKpi")
134
88
  def oee_quality(
89
+ unit: VisiblePdUnit,
135
90
  session: DBSession,
136
- auth: Auth,
137
- visibility: VisibilityRegistryDep,
138
- pdunit_id: int,
139
- # ex w timezone "2025-05-20T13:54:15.555103+03:00"
140
- # ex wo timezone "2025-05-20T13:54:15.555103"
141
91
  starttime: datetime,
142
- # ex w timezone "2025-05-22T11:53:13.561411+03:00"
143
- # ex wo timezone "2025-05-22T11:53:13.561411"
144
92
  stoptime: datetime,
145
93
  ) -> float | None:
146
94
  """
@@ -148,8 +96,7 @@ def oee_quality(
148
96
  Uses the quality_computation function
149
97
  Returns single floating point value
150
98
  """
151
- _require_visible_unit(session, auth, visibility, pdunit_id)
152
- records = get_prod_records(session, pdunit_id, starttime, stoptime)
99
+ records = get_prod_records(session, unit.id, starttime, stoptime)
153
100
  return compute_quality(records, starttime, stoptime)
154
101
 
155
102
 
@@ -176,26 +123,23 @@ class OEECalculationResponse(BaseModel):
176
123
  @routes.get("/kpi/oee", tags=["pd"], summary="OEE calculation")
177
124
  @require_roles("PdKpi")
178
125
  def oee_calculation(
126
+ unit: VisiblePdUnit,
179
127
  session: DBSession,
180
- auth: Auth,
181
- visibility: VisibilityRegistryDep,
182
- pdunit_id: int,
183
128
  starttime: datetime,
184
129
  stoptime: datetime,
185
130
  ) -> OEECalculationResponse:
186
131
  """OEE with time breakdown and quantity aggregates for one work centre."""
187
- unit = _require_visible_unit(session, auth, visibility, pdunit_id)
188
- spans = get_state_spans(session, pdunit_id, starttime, stoptime)
132
+ spans = get_state_spans(session, unit.id, starttime, stoptime)
189
133
  shifts = list(session.scalars(unit.shifts_in_range_stmt(starttime, stoptime)).all())
190
134
  availability = compute_availability(spans, shifts, starttime, stoptime)
191
- prod_records = get_prod_records(session, pdunit_id, starttime, stoptime)
135
+ prod_records = get_prod_records(session, unit.id, starttime, stoptime)
192
136
  isexecution_spans = [s for s in spans if s.isexecution]
193
137
  performance = compute_performance(
194
138
  prod_records, starttime, stoptime, isexecution_spans=isexecution_spans
195
139
  )
196
140
  quality = compute_quality(prod_records, starttime, stoptime)
197
141
  oee = availability * performance * quality if quality is not None else None
198
- summary = oee_workcenter_time_computation(session, pdunit_id, starttime, stoptime)
142
+ summary = oee_workcenter_time_computation(session, unit.id, starttime, stoptime)
199
143
  return OEECalculationResponse(
200
144
  availability=availability,
201
145
  performance=performance,
@@ -228,16 +172,13 @@ class OEEBreakdownResponse(BaseModel):
228
172
  @routes.get("/kpi/oee/breakdown", tags=["pd"], summary="Per-operation OEE breakdown")
229
173
  @require_roles("PdKpi")
230
174
  def oee_breakdown(
175
+ unit: VisiblePdUnit,
231
176
  session: DBSession,
232
- auth: Auth,
233
- visibility: VisibilityRegistryDep,
234
- pdunit_id: int,
235
177
  starttime: datetime,
236
178
  stoptime: datetime,
237
179
  ) -> list[OEEBreakdownResponse]:
238
180
  """Return one OEE row per operation that had active time on the given unit."""
239
- _require_visible_unit(session, auth, visibility, pdunit_id)
240
- rows = oee_breakdown_computation(session, pdunit_id, starttime, stoptime)
181
+ rows = oee_breakdown_computation(session, unit.id, starttime, stoptime)
241
182
  return [OEEBreakdownResponse(**row.__dict__) for row in rows]
242
183
 
243
184
 
@@ -278,21 +219,13 @@ def oee_trend_aggregation(
278
219
  @require_roles("PdKpi")
279
220
  def pdunit_downtime_calculation(
280
221
  session: DBSession,
281
- auth: Auth,
282
- visibility: VisibilityRegistryDep,
222
+ units: VisiblePdUnits,
283
223
  starttime: datetime,
284
224
  endtime: datetime,
285
- pdunit_ids: list[int] = Query(),
286
225
  ) -> list:
287
- if auth is None:
288
- raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
289
- if not pdunit_ids:
290
- return []
291
- policy = visibility.get(PdUnit)
292
- pdunit_ids = _filter_visible_pdunit_ids(session, auth, policy, pdunit_ids)
293
- if not pdunit_ids:
226
+ if not units:
294
227
  return []
295
- return get_failure_times(session, pdunit_ids, starttime, endtime)
228
+ return get_failure_times(session, [u.id for u in units], starttime, endtime)
296
229
 
297
230
 
298
231
  def mount(app: APIRouter):
@@ -3,9 +3,16 @@ from typing import Optional, Sequence
3
3
 
4
4
  from fastapi import HTTPException
5
5
  from perfact.api.base.model import PydanticBase
6
- from perfact.api.main import Auth, DBSession, require_roles
6
+ from perfact.api.main import Auth, DBSession, VisibilityRegistryDep, require_roles
7
7
  from perfact.api.main.dbsession import APIRouter
8
- from perfact.api.pd.model import PdOrdLc, PdOrdLch, PdOrdLct, PdUnitLc, PdUnitLct
8
+ from perfact.api.pd.model import (
9
+ PdOrdLc,
10
+ PdOrdLch,
11
+ PdOrdLct,
12
+ PdUnit,
13
+ PdUnitLc,
14
+ PdUnitLct,
15
+ )
9
16
  from perfact.api.pd.services.pdunitlch import UnitLchHistoryEntry, get_unit_lch_history
10
17
  from pydantic import BaseModel, ConfigDict
11
18
  from sqlalchemy import select
@@ -118,17 +125,46 @@ def pdordlch_get_all(session: DBSession, auth: Auth) -> Sequence[PdOrdLch]:
118
125
  def pdunitlch_get(
119
126
  session: DBSession,
120
127
  auth: Auth,
128
+ visibility: VisibilityRegistryDep,
121
129
  pdunit_id: Optional[int] = None,
122
130
  pdunit_num: Optional[str] = None,
123
131
  duration_s: Optional[int] = None,
124
132
  starttime: Optional[datetime.datetime] = None,
125
133
  endtime: Optional[datetime.datetime] = None,
126
134
  ) -> list[UnitLchHistoryEntry]:
135
+ # Resolve unit from id or num
136
+ if pdunit_id is not None:
137
+ unit = session.get(PdUnit, pdunit_id)
138
+ elif pdunit_num is not None:
139
+ unit = session.execute(
140
+ select(PdUnit).where(PdUnit.num == pdunit_num)
141
+ ).scalar_one_or_none()
142
+ else:
143
+ raise HTTPException(status_code=422, detail="Provide pdunit_id or pdunit_num")
144
+
145
+ if unit is None:
146
+ raise HTTPException(
147
+ status_code=404,
148
+ detail=f"PdUnit not found: {pdunit_id or pdunit_num!r}",
149
+ )
150
+
151
+ # Visibility check applies regardless of which lookup path was used
152
+ try:
153
+ policy = visibility.get(PdUnit)
154
+ except KeyError:
155
+ raise RuntimeError("No visibility policy registered for PdUnit")
156
+ if (
157
+ session.scalar(
158
+ select(PdUnit).where(PdUnit.id == unit.id).where(policy.filter(auth))
159
+ )
160
+ is None
161
+ ):
162
+ raise HTTPException(status_code=404)
163
+
127
164
  try:
128
165
  return get_unit_lch_history(
129
166
  session,
130
- pdunit_id=pdunit_id,
131
- pdunit_num=pdunit_num,
167
+ pdunit_id=unit.id,
132
168
  starttime=starttime,
133
169
  endtime=endtime,
134
170
  duration_s=duration_s,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: perfact-api-pd-fastapi
3
- Version: 1.3
3
+ Version: 1.5
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.5",
3
+ "distance": 0,
4
+ "node": "ge0c4d91c83ebe49c587a5f576ac6030a84fefce4",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-03"
8
+ }
@@ -31,14 +31,13 @@ def test_oee_performance_route_delegates_to_compute_performance(
31
31
  """oee_performance delegates to get_prod_records, get_state_spans,
32
32
  and compute_performance."""
33
33
  session: Session = Mock()
34
- auth = Mock()
35
- visibility = Mock()
36
34
  pdunit_id = 7
35
+ unit = Mock()
36
+ unit.id = pdunit_id
37
37
  starttime = datetime(2025, 1, 1, tzinfo=timezone.utc)
38
38
  stoptime = datetime(2025, 1, 2, tzinfo=timezone.utc)
39
39
  expected = 0.82
40
40
 
41
- monkeypatch.setattr(kpi, "_require_visible_unit", lambda *_: Mock())
42
41
  monkeypatch.setattr(kpi, "get_prod_records", lambda *_: [])
43
42
  monkeypatch.setattr(kpi, "get_state_spans", lambda *_: [])
44
43
  monkeypatch.setattr(
@@ -47,9 +46,7 @@ def test_oee_performance_route_delegates_to_compute_performance(
47
46
  lambda records, start, stop, isexecution_spans=None: expected,
48
47
  )
49
48
 
50
- coro = kpi.oee_performance(
51
- session, auth, visibility, pdunit_id, starttime, stoptime
52
- )
49
+ coro = kpi.oee_performance(unit, session, starttime, stoptime)
53
50
  result: float = asyncio.run(coro) # type: ignore[arg-type]
54
51
 
55
52
  assert result == expected
@@ -60,18 +57,17 @@ def test_oee_quality_route_delegates_to_compute_quality(
60
57
  ) -> None:
61
58
  """oee_quality delegates to get_prod_records and compute_quality."""
62
59
  session: Session = Mock()
63
- auth = Mock()
64
- visibility = Mock()
65
60
  pdunit_id = 7
61
+ unit = Mock()
62
+ unit.id = pdunit_id
66
63
  starttime = datetime(2025, 1, 1, tzinfo=timezone.utc)
67
64
  stoptime = datetime(2025, 1, 2, tzinfo=timezone.utc)
68
65
  expected = 0.97
69
66
 
70
- monkeypatch.setattr(kpi, "_require_visible_unit", lambda *_: Mock())
71
67
  monkeypatch.setattr(kpi, "get_prod_records", lambda *_: [])
72
68
  monkeypatch.setattr(kpi, "compute_quality", lambda records, start, stop: expected)
73
69
 
74
- coro = kpi.oee_quality(session, auth, visibility, pdunit_id, starttime, stoptime)
70
+ coro = kpi.oee_quality(unit, session, starttime, stoptime)
75
71
  result: float | None = asyncio.run(coro) # type: ignore[arg-type]
76
72
 
77
73
  assert result == expected
@@ -83,9 +79,9 @@ def test_oee_availability_delegates_to_compute_availability(
83
79
  """oee_availability delegates to get_state_spans and compute_availability."""
84
80
  session: Session = Mock()
85
81
  session.scalars.return_value.all.return_value = [] # type: ignore[attr-defined]
86
- auth = Mock()
87
- visibility = Mock()
88
82
  pdunit_id = 7
83
+ unit = Mock()
84
+ unit.id = pdunit_id
89
85
  starttime = datetime(2025, 1, 1, tzinfo=timezone.utc)
90
86
  stoptime = datetime(2025, 1, 2, tzinfo=timezone.utc)
91
87
  expected = 0.91
@@ -99,13 +95,10 @@ def test_oee_availability_delegates_to_compute_availability(
99
95
  captured.append(("compute_availability", start, stop))
100
96
  return expected
101
97
 
102
- monkeypatch.setattr(kpi, "_require_visible_unit", lambda *_: Mock())
103
98
  monkeypatch.setattr(kpi, "get_state_spans", fake_get_state_spans)
104
99
  monkeypatch.setattr(kpi, "compute_availability", fake_compute_availability)
105
100
 
106
- coro = kpi.oee_availability(
107
- session, auth, visibility, pdunit_id, starttime, stoptime
108
- )
101
+ coro = kpi.oee_availability(unit, session, starttime, stoptime)
109
102
  result: float = asyncio.run(coro) # type: ignore[arg-type]
110
103
 
111
104
  assert result == expected
@@ -132,10 +125,9 @@ def test_oee_calculation_returns_combined_values(
132
125
  pdordprd_quantity=9.0,
133
126
  )
134
127
 
135
- auth = Mock()
136
- visibility = Mock()
128
+ unit = Mock()
129
+ unit.id = pdunit_id
137
130
  session.scalars.return_value.all.return_value = [] # type: ignore[attr-defined]
138
- monkeypatch.setattr(kpi, "_require_visible_unit", lambda *_: Mock())
139
131
  monkeypatch.setattr(kpi, "get_state_spans", lambda *_: [])
140
132
  monkeypatch.setattr(kpi, "compute_availability", lambda *_, **__: 0.5)
141
133
  monkeypatch.setattr(kpi, "get_prod_records", lambda *_: [])
@@ -144,7 +136,7 @@ def test_oee_calculation_returns_combined_values(
144
136
  monkeypatch.setattr(kpi, "oee_workcenter_time_computation", lambda *_: fake_summary)
145
137
 
146
138
  result: kpi.OEECalculationResponse = asyncio.run(
147
- kpi.oee_calculation(session, auth, visibility, pdunit_id, starttime, stoptime) # type: ignore[arg-type]
139
+ kpi.oee_calculation(unit, session, starttime, stoptime) # type: ignore[arg-type]
148
140
  )
149
141
 
150
142
  assert result.availability == 0.5
@@ -168,9 +160,9 @@ def test_oee_performance_route_passes_only_isexecution_spans(
168
160
  recorded during setup are not included in the performance numerator/denominator.
169
161
  """
170
162
  session: Session = Mock()
171
- auth = Mock()
172
- visibility = Mock()
173
163
  pdunit_id = 7
164
+ unit = Mock()
165
+ unit.id = pdunit_id
174
166
  starttime = datetime(2025, 1, 1, tzinfo=timezone.utc)
175
167
  stoptime = datetime(2025, 1, 2, tzinfo=timezone.utc)
176
168
 
@@ -183,7 +175,6 @@ def test_oee_performance_route_passes_only_isexecution_spans(
183
175
  )
184
176
  captured_isexecution_spans: list[StateSpan] = []
185
177
 
186
- monkeypatch.setattr(kpi, "_require_visible_unit", lambda *_: Mock())
187
178
  monkeypatch.setattr(kpi, "get_prod_records", lambda *_: [])
188
179
  monkeypatch.setattr(kpi, "get_state_spans", lambda *_: [exec_span, setup_span])
189
180
 
@@ -193,9 +184,7 @@ def test_oee_performance_route_passes_only_isexecution_spans(
193
184
 
194
185
  monkeypatch.setattr(kpi, "compute_performance", fake_compute_performance)
195
186
 
196
- coro = kpi.oee_performance(
197
- session, auth, visibility, pdunit_id, starttime, stoptime
198
- )
187
+ coro = kpi.oee_performance(unit, session, starttime, stoptime)
199
188
  asyncio.run(coro) # type: ignore[arg-type]
200
189
 
201
190
  assert exec_span in captured_isexecution_spans
@@ -212,9 +201,9 @@ def test_oee_calculation_performance_uses_only_isexecution_spans(
212
201
  """
213
202
  session: Session = Mock()
214
203
  session.scalars.return_value.all.return_value = [] # type: ignore[attr-defined]
215
- auth = Mock()
216
- visibility = Mock()
217
204
  pdunit_id = 3
205
+ unit = Mock()
206
+ unit.id = pdunit_id
218
207
  starttime = datetime(2025, 2, 1, tzinfo=timezone.utc)
219
208
  stoptime = datetime(2025, 2, 2, tzinfo=timezone.utc)
220
209
 
@@ -227,7 +216,6 @@ def test_oee_calculation_performance_uses_only_isexecution_spans(
227
216
  )
228
217
  captured_isexecution_spans: list[StateSpan] = []
229
218
 
230
- monkeypatch.setattr(kpi, "_require_visible_unit", lambda *_: Mock())
231
219
  monkeypatch.setattr(kpi, "get_state_spans", lambda *_: [exec_span, setup_span])
232
220
  monkeypatch.setattr(kpi, "compute_availability", lambda *_, **__: 0.9)
233
221
  monkeypatch.setattr(kpi, "get_prod_records", lambda *_: [])
@@ -251,9 +239,7 @@ def test_oee_calculation_performance_uses_only_isexecution_spans(
251
239
  ),
252
240
  )
253
241
 
254
- coro = kpi.oee_calculation(
255
- session, auth, visibility, pdunit_id, starttime, stoptime
256
- )
242
+ coro = kpi.oee_calculation(unit, session, starttime, stoptime)
257
243
  asyncio.run(coro) # type: ignore[arg-type]
258
244
 
259
245
  assert exec_span in captured_isexecution_spans
@@ -308,13 +294,12 @@ def test_oee_breakdown_route_delegates_to_service(
308
294
  captured.append((s, u, start, stop))
309
295
  return [fake_row]
310
296
 
311
- auth = Mock()
312
- visibility = Mock()
313
- monkeypatch.setattr(kpi, "_require_visible_unit", lambda *_: Mock())
297
+ unit = Mock()
298
+ unit.id = pdunit_id
314
299
  monkeypatch.setattr(kpi, "oee_breakdown_computation", fake_breakdown)
315
300
 
316
301
  result: list[kpi.OEEBreakdownResponse] = asyncio.run(
317
- kpi.oee_breakdown(session, auth, visibility, pdunit_id, starttime, stoptime) # type: ignore[arg-type]
302
+ kpi.oee_breakdown(unit, session, starttime, stoptime) # type: ignore[arg-type]
318
303
  )
319
304
 
320
305
  assert captured == [(session, pdunit_id, starttime, stoptime)]
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.3",
3
- "distance": 0,
4
- "node": "ga698854b925b77d27ecddc8ca7ecdb07c647c229",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-06-30"
8
- }
@@ -3,20 +3,20 @@
3
3
  "README.md",
4
4
  "pyproject.toml",
5
5
  "tox.ini",
6
- "src/perfact/api/pd_fastapi/__init__.py",
7
- "src/perfact/api/pd_fastapi/kpi.py",
8
6
  "src/perfact/api/pd_fastapi/performancedata.py",
9
7
  "src/perfact/api/pd_fastapi/grafana.py",
10
- "src/perfact/api/pd_fastapi/pdunit.py",
11
8
  "src/perfact/api/pd_fastapi/py.typed",
9
+ "src/perfact/api/pd_fastapi/kpi.py",
10
+ "src/perfact/api/pd_fastapi/__init__.py",
12
11
  "src/perfact/api/pd_fastapi/pdunitlch.py",
13
- "tests/__init__.py",
14
- "tests/conftest.py",
15
- "tests/test_kpi.py",
16
- "tests/test_init.py",
12
+ "src/perfact/api/pd_fastapi/pdunit.py",
17
13
  "tests/helpers.py",
18
14
  "tests/locustfile.py",
15
+ "tests/test_init.py",
16
+ "tests/test_kpi.py",
17
+ "tests/__init__.py",
19
18
  "tests/test_pdunit.py",
19
+ "tests/conftest.py",
20
20
  "tests/test_performance.py"
21
21
  ]
22
22
  }