perfact-api-pd-fastapi 1.4__tar.gz → 1.6__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.4 → perfact_api_pd_fastapi-1.6}/PKG-INFO +1 -1
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/kpi.py +20 -87
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/pdunitlch.py +48 -7
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/PKG-INFO +1 -1
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/SOURCES.txt +1 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/scm_file_list.json +1 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/scm_version.json +2 -2
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/test_kpi.py +21 -36
- perfact_api_pd_fastapi-1.6/tests/test_pdunitlch.py +76 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/README.md +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/pyproject.toml +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/setup.cfg +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/__init__.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/grafana.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/pdunit.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/performancedata.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/py.typed +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/dependency_links.txt +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/entry_points.txt +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/requires.txt +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact_api_pd_fastapi.egg-info/top_level.txt +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/__init__.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/conftest.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/helpers.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/locustfile.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/test_init.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/test_pdunit.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tests/test_performance.py +0 -0
- {perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/tox.ini +0 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
228
|
+
return get_failure_times(session, [u.id for u in units], starttime, endtime)
|
|
296
229
|
|
|
297
230
|
|
|
298
231
|
def mount(app: APIRouter):
|
{perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/pdunitlch.py
RENAMED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from typing import Optional, Sequence
|
|
3
3
|
|
|
4
|
-
from fastapi import HTTPException
|
|
4
|
+
from fastapi import HTTPException, Query
|
|
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
|
|
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
|
|
12
19
|
|
|
13
20
|
routes = APIRouter()
|
|
14
21
|
|
|
22
|
+
MAX_SAMPLE_COUNT = 9_000
|
|
23
|
+
|
|
15
24
|
|
|
16
25
|
class PdUnitLcGet(PydanticBase):
|
|
17
26
|
name: str
|
|
18
27
|
seqnum: Optional[int]
|
|
28
|
+
color: Optional[str]
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
class PdUnitLctGet(PydanticBase):
|
|
@@ -31,6 +41,7 @@ class PdOrdLcGet(PydanticBase):
|
|
|
31
41
|
isfinal: bool
|
|
32
42
|
issetup: bool
|
|
33
43
|
isdowntime: bool
|
|
44
|
+
color: Optional[str]
|
|
34
45
|
|
|
35
46
|
|
|
36
47
|
class PdOrdLctGet(PydanticBase):
|
|
@@ -51,6 +62,7 @@ class PdUnitLchEntryGet(BaseModel):
|
|
|
51
62
|
pdunitlct_name: str
|
|
52
63
|
pdunitlc_name: str
|
|
53
64
|
pdord_num: Optional[str]
|
|
65
|
+
pdordlc_name: Optional[str]
|
|
54
66
|
|
|
55
67
|
|
|
56
68
|
@routes.get(
|
|
@@ -118,20 +130,49 @@ def pdordlch_get_all(session: DBSession, auth: Auth) -> Sequence[PdOrdLch]:
|
|
|
118
130
|
def pdunitlch_get(
|
|
119
131
|
session: DBSession,
|
|
120
132
|
auth: Auth,
|
|
133
|
+
visibility: VisibilityRegistryDep,
|
|
121
134
|
pdunit_id: Optional[int] = None,
|
|
122
135
|
pdunit_num: Optional[str] = None,
|
|
123
|
-
|
|
136
|
+
sample_count: Optional[int] = Query(default=None, ge=1, le=MAX_SAMPLE_COUNT),
|
|
124
137
|
starttime: Optional[datetime.datetime] = None,
|
|
125
138
|
endtime: Optional[datetime.datetime] = None,
|
|
126
139
|
) -> list[UnitLchHistoryEntry]:
|
|
140
|
+
# Resolve unit from id or num
|
|
141
|
+
if pdunit_id is not None:
|
|
142
|
+
unit = session.get(PdUnit, pdunit_id)
|
|
143
|
+
elif pdunit_num is not None:
|
|
144
|
+
unit = session.execute(
|
|
145
|
+
select(PdUnit).where(PdUnit.num == pdunit_num)
|
|
146
|
+
).scalar_one_or_none()
|
|
147
|
+
else:
|
|
148
|
+
raise HTTPException(status_code=422, detail="Provide pdunit_id or pdunit_num")
|
|
149
|
+
|
|
150
|
+
if unit is None:
|
|
151
|
+
raise HTTPException(
|
|
152
|
+
status_code=404,
|
|
153
|
+
detail=f"PdUnit not found: {pdunit_id or pdunit_num!r}",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Visibility check applies regardless of which lookup path was used
|
|
157
|
+
try:
|
|
158
|
+
policy = visibility.get(PdUnit)
|
|
159
|
+
except KeyError:
|
|
160
|
+
raise RuntimeError("No visibility policy registered for PdUnit")
|
|
161
|
+
if (
|
|
162
|
+
session.scalar(
|
|
163
|
+
select(PdUnit).where(PdUnit.id == unit.id).where(policy.filter(auth))
|
|
164
|
+
)
|
|
165
|
+
is None
|
|
166
|
+
):
|
|
167
|
+
raise HTTPException(status_code=404)
|
|
168
|
+
|
|
127
169
|
try:
|
|
128
170
|
return get_unit_lch_history(
|
|
129
171
|
session,
|
|
130
|
-
pdunit_id=
|
|
131
|
-
pdunit_num=pdunit_num,
|
|
172
|
+
pdunit_id=unit.id,
|
|
132
173
|
starttime=starttime,
|
|
133
174
|
endtime=endtime,
|
|
134
|
-
|
|
175
|
+
sample_count=sample_count,
|
|
135
176
|
)
|
|
136
177
|
except ValueError as exc:
|
|
137
178
|
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
|
@@ -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(
|
|
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
|
-
|
|
136
|
-
|
|
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(
|
|
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
|
-
|
|
312
|
-
|
|
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(
|
|
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)]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
|
|
4
|
+
from perfact.api.main.dbsession import APIRouter
|
|
5
|
+
from perfact.api.pd.services.pdunitlch import UnitLchHistoryEntry
|
|
6
|
+
from perfact.api.pd_fastapi import pdunitlch
|
|
7
|
+
|
|
8
|
+
MODTIME = datetime.datetime(2024, 1, 1, tzinfo=datetime.timezone.utc)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_pdunitlcget_exposes_color() -> None:
|
|
12
|
+
row = SimpleNamespace(
|
|
13
|
+
id=1, author=None, modtime=MODTIME, name="Production", seqnum=1, color="#0f0"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
serialized = pdunitlch.PdUnitLcGet.model_validate(row)
|
|
17
|
+
|
|
18
|
+
assert serialized.color == "#0f0"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_pdordlcget_exposes_color() -> None:
|
|
22
|
+
row = SimpleNamespace(
|
|
23
|
+
id=1,
|
|
24
|
+
author=None,
|
|
25
|
+
modtime=MODTIME,
|
|
26
|
+
name="Running",
|
|
27
|
+
isactive=True,
|
|
28
|
+
isexecution=False,
|
|
29
|
+
isfinal=False,
|
|
30
|
+
issetup=False,
|
|
31
|
+
isdowntime=False,
|
|
32
|
+
color="#f00",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
serialized = pdunitlch.PdOrdLcGet.model_validate(row)
|
|
36
|
+
|
|
37
|
+
assert serialized.color == "#f00"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_pdunitlchentryget_exposes_pdordlc_name() -> None:
|
|
41
|
+
entry = UnitLchHistoryEntry(
|
|
42
|
+
createtime=datetime.datetime(2024, 1, 1, tzinfo=datetime.timezone.utc),
|
|
43
|
+
pdunitlct_name="start",
|
|
44
|
+
pdunitlc_name="running",
|
|
45
|
+
pdord_num="ORD-1",
|
|
46
|
+
pdordlc_name="active",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
serialized = pdunitlch.PdUnitLchEntryGet.model_validate(entry)
|
|
50
|
+
|
|
51
|
+
assert serialized.pdordlc_name == "active"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_pdunitlchentryget_pdordlc_name_none_when_no_active_operation() -> None:
|
|
55
|
+
entry = UnitLchHistoryEntry(
|
|
56
|
+
createtime=datetime.datetime(2024, 1, 1, tzinfo=datetime.timezone.utc),
|
|
57
|
+
pdunitlct_name="start",
|
|
58
|
+
pdunitlc_name="running",
|
|
59
|
+
pdord_num=None,
|
|
60
|
+
pdordlc_name=None,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
serialized = pdunitlch.PdUnitLchEntryGet.model_validate(entry)
|
|
64
|
+
|
|
65
|
+
assert serialized.pdordlc_name is None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_mount_includes_pdunitlch_route() -> None:
|
|
69
|
+
from .helpers import collect_paths
|
|
70
|
+
|
|
71
|
+
app_router = APIRouter()
|
|
72
|
+
|
|
73
|
+
pdunitlch.mount(app_router)
|
|
74
|
+
|
|
75
|
+
paths = collect_paths(app_router)
|
|
76
|
+
assert "/pdunitlch" in paths
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/__init__.py
RENAMED
|
File without changes
|
{perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/grafana.py
RENAMED
|
File without changes
|
{perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/src/perfact/api/pd_fastapi/pdunit.py
RENAMED
|
File without changes
|
|
File without changes
|
{perfact_api_pd_fastapi-1.4 → perfact_api_pd_fastapi-1.6}/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
|