perfact-api-pd 0.2__tar.gz → 0.4__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-0.2 → perfact_api_pd-0.4}/PKG-INFO +6 -2
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/pyproject.toml +8 -1
- perfact_api_pd-0.4/src/perfact/api/pd/model/__init__.py +30 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdord.py +2 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdordhd.py +32 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdordhdlc.py +11 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdordlc.py +27 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdordlch.py +7 -2
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdordlct.py +18 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdordprd.py +130 -7
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdordtype.py +9 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdunit.py +69 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitgroup.py +25 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitlc.py +10 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitlch.py +24 -0
- perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitlct.py +25 -0
- perfact_api_pd-0.4/src/perfact/api/pd/services/__init__.py +16 -0
- perfact_api_pd-0.4/src/perfact/api/pd/services/pdunitlch.py +125 -0
- perfact_api_pd-0.4/src/perfact/api/pd/services/pdunitperformance_orm.py +425 -0
- perfact_api_pd-0.4/src/perfact/api/pd/services/pdunitperformance_plainsql.py +191 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/PKG-INFO +6 -2
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/SOURCES.txt +17 -1
- perfact_api_pd-0.4/src/perfact_api_pd.egg-info/requires.txt +10 -0
- perfact_api_pd-0.4/tests/conftest.py +59 -0
- perfact_api_pd-0.4/tests/test_models.py +397 -0
- perfact_api_pd-0.4/tests/test_policy.py +34 -0
- perfact_api_pd-0.4/tests/test_s_pdunitperformance.py +484 -0
- perfact_api_pd-0.4/tests/test_services_pdunitlch.py +256 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/tox.ini +2 -2
- perfact_api_pd-0.2/src/perfact/api/pd/model/__init__.py +0 -16
- perfact_api_pd-0.2/src/perfact/api/pd/model/pdordlc.py +0 -8
- perfact_api_pd-0.2/src/perfact/api/pd/model/pdunit.py +0 -14
- perfact_api_pd-0.2/src/perfact_api_pd.egg-info/requires.txt +0 -5
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/README.md +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/setup.cfg +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/__init__.py +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/policy.py +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/py.typed +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/py.typed +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/dependency_links.txt +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/entry_points.txt +0 -0
- {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: perfact-api-pd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4
|
|
4
4
|
Summary: PerFact API - pd domain models and services
|
|
5
5
|
Author-email: Viktor Dick <viktor.dick@perfact.de>
|
|
6
6
|
License-Expression: GPL-2.0-or-later
|
|
@@ -11,9 +11,13 @@ Requires-Python: >=3.10
|
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: psycopg[binary]
|
|
13
13
|
Requires-Dist: sqlalchemy
|
|
14
|
-
Requires-Dist: perfact-api-base
|
|
14
|
+
Requires-Dist: perfact-api-base
|
|
15
15
|
Requires-Dist: perfact-api-app
|
|
16
16
|
Requires-Dist: perfact-api-pp
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest; extra == "test"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
20
|
+
Requires-Dist: pytest-postgresql; extra == "test"
|
|
17
21
|
|
|
18
22
|
# PerFact API - pd - model
|
|
19
23
|
|
|
@@ -18,13 +18,20 @@ classifiers = [
|
|
|
18
18
|
dependencies = [
|
|
19
19
|
"psycopg[binary]",
|
|
20
20
|
"sqlalchemy",
|
|
21
|
-
"perfact-api-base
|
|
21
|
+
"perfact-api-base",
|
|
22
22
|
"perfact-api-app",
|
|
23
23
|
"perfact-api-pp",
|
|
24
24
|
]
|
|
25
25
|
dynamic = ["version"]
|
|
26
26
|
requires-python = ">=3.10"
|
|
27
27
|
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
test = [
|
|
30
|
+
"pytest",
|
|
31
|
+
"pytest-cov",
|
|
32
|
+
"pytest-postgresql",
|
|
33
|
+
]
|
|
34
|
+
|
|
28
35
|
[project.scripts]
|
|
29
36
|
|
|
30
37
|
[tool.distutils.bdist_wheel]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .pdord import PdOrd, PdOrd_TimeStat
|
|
2
|
+
from .pdordhd import PdOrdHd
|
|
3
|
+
from .pdordhdlc import PdOrdHdLc
|
|
4
|
+
from .pdordlc import PdOrdLc
|
|
5
|
+
from .pdordlch import PdOrdLch
|
|
6
|
+
from .pdordlct import PdOrdLct
|
|
7
|
+
from .pdordprd import PdOrdPrd
|
|
8
|
+
from .pdordtype import PdOrdType
|
|
9
|
+
from .pdunit import PdUnit
|
|
10
|
+
from .pdunitgroup import PdUnitGroup
|
|
11
|
+
from .pdunitlc import PdUnitLc
|
|
12
|
+
from .pdunitlch import PdUnitLch
|
|
13
|
+
from .pdunitlct import PdUnitLct
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"PdUnit",
|
|
17
|
+
"PdUnitGroup",
|
|
18
|
+
"PdUnitLc",
|
|
19
|
+
"PdUnitLct",
|
|
20
|
+
"PdUnitLch",
|
|
21
|
+
"PdOrdLch",
|
|
22
|
+
"PdOrdLct",
|
|
23
|
+
"PdOrdLc",
|
|
24
|
+
"PdOrdPrd",
|
|
25
|
+
"PdOrd_TimeStat",
|
|
26
|
+
"PdOrd",
|
|
27
|
+
"PdOrdHd",
|
|
28
|
+
"PdOrdHdLc",
|
|
29
|
+
"PdOrdType",
|
|
30
|
+
]
|
|
@@ -12,6 +12,7 @@ from sqlalchemy.orm import mapped_column
|
|
|
12
12
|
|
|
13
13
|
class PdOrd(Base):
|
|
14
14
|
# COLS
|
|
15
|
+
num: Mapped[str]
|
|
15
16
|
planmachinetime: Mapped[timedelta | None] = mapped_column(Interval)
|
|
16
17
|
planstarttime: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
17
18
|
starttime: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
@@ -24,6 +25,7 @@ class PdOrd(Base):
|
|
|
24
25
|
# FKS
|
|
25
26
|
pdunit_id: Mapped[int | None] = mapped_column(ForeignKey("pdunit.id"))
|
|
26
27
|
pdordlc_id: Mapped[int | None] = mapped_column(ForeignKey("pdordlc.id"))
|
|
28
|
+
pdordhd_id: Mapped[int | None] = mapped_column(ForeignKey("pdordhd.id"))
|
|
27
29
|
# RELATIONSHIPS
|
|
28
30
|
|
|
29
31
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
4
|
+
from sqlalchemy import DateTime
|
|
5
|
+
from sqlalchemy.orm import mapped_column
|
|
6
|
+
|
|
7
|
+
from .pdordhdlc import PdOrdHdLc
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PdOrdHd(Base):
|
|
11
|
+
num: Mapped[str]
|
|
12
|
+
pdordhdlc_id: Mapped[int] = mapped_column(ForeignKey("pdordhdlc.id"))
|
|
13
|
+
overdeliverytol: Mapped[float] = mapped_column(default=0.0)
|
|
14
|
+
underdeliverytol: Mapped[float] = mapped_column(default=0.0)
|
|
15
|
+
planstarttime: Mapped[datetime.datetime | None] = mapped_column(
|
|
16
|
+
DateTime(timezone=True)
|
|
17
|
+
)
|
|
18
|
+
planstoptime: Mapped[datetime.datetime | None] = mapped_column(
|
|
19
|
+
DateTime(timezone=True)
|
|
20
|
+
)
|
|
21
|
+
mtart_id: Mapped[int | None]
|
|
22
|
+
quantity: Mapped[float | None]
|
|
23
|
+
fileattr_id: Mapped[int | None]
|
|
24
|
+
pprt_id: Mapped[int | None]
|
|
25
|
+
description: Mapped[str | None]
|
|
26
|
+
mtartversion: Mapped[str | None]
|
|
27
|
+
releasedate: Mapped[datetime.datetime | None] = mapped_column(
|
|
28
|
+
DateTime(timezone=True)
|
|
29
|
+
)
|
|
30
|
+
planreference_pdunit_id: Mapped[int | None] = mapped_column(ForeignKey("pdunit.id"))
|
|
31
|
+
|
|
32
|
+
pdordhdlc: Mapped["PdOrdHdLc"] = relationship("PdOrdHdLc")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from perfact.api.base.model import Base, Mapped
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PdOrdHdLc(Base):
|
|
6
|
+
name: Mapped[str]
|
|
7
|
+
deleted: Mapped[bool] = mapped_column(default=False)
|
|
8
|
+
isinitial: Mapped[bool] = mapped_column(default=False)
|
|
9
|
+
lockpdordlc: Mapped[bool] = mapped_column(default=False)
|
|
10
|
+
description: Mapped[str | None]
|
|
11
|
+
seqnum: Mapped[int | None]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from perfact.api.base.model import Base, Mapped
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PdOrdLc(Base):
|
|
6
|
+
name: Mapped[str]
|
|
7
|
+
seqnum: Mapped[int]
|
|
8
|
+
allowprovisioning: Mapped[bool] = mapped_column(default=True)
|
|
9
|
+
deleted: Mapped[bool] = mapped_column(default=False)
|
|
10
|
+
isinitial: Mapped[bool] = mapped_column(default=False)
|
|
11
|
+
intermui: Mapped[bool] = mapped_column(default=False)
|
|
12
|
+
isexecutable: Mapped[bool] = mapped_column(default=False)
|
|
13
|
+
isdemand: Mapped[bool] = mapped_column(default=False)
|
|
14
|
+
mayamendbom: Mapped[bool] = mapped_column(default=False)
|
|
15
|
+
mayassignpdunit: Mapped[bool] = mapped_column(default=True)
|
|
16
|
+
mayeditplantime: Mapped[bool] = mapped_column(default=True)
|
|
17
|
+
maysplitoperation: Mapped[bool] = mapped_column(default=False)
|
|
18
|
+
maystoremtbom: Mapped[bool] = mapped_column(default=False)
|
|
19
|
+
mayplaninpdunit: Mapped[bool] = mapped_column(default=True)
|
|
20
|
+
isfinal: Mapped[bool] = mapped_column(default=False)
|
|
21
|
+
isactive: Mapped[bool] = mapped_column(default=False)
|
|
22
|
+
isexecution: Mapped[bool] = mapped_column(default=False)
|
|
23
|
+
issetup: Mapped[bool] = mapped_column(default=False)
|
|
24
|
+
isdowntime: Mapped[bool] = mapped_column(default=False)
|
|
25
|
+
mayassigntssm: Mapped[bool] = mapped_column(default=False)
|
|
26
|
+
showequipmentassign: Mapped[bool] = mapped_column(default=False)
|
|
27
|
+
description: Mapped[str | None]
|
|
@@ -25,13 +25,16 @@ class PdOrdLch(Base):
|
|
|
25
25
|
pdunit_id: Mapped[int | None] = mapped_column(ForeignKey("pdunit.id"))
|
|
26
26
|
pdord_id: Mapped[int] = mapped_column(ForeignKey("pdord.id"))
|
|
27
27
|
pdordlct_id: Mapped[int] = mapped_column(ForeignKey("pdordlct.id"))
|
|
28
|
+
pdordhd_id: Mapped[int | None] = mapped_column(ForeignKey("pdordhd.id"))
|
|
28
29
|
|
|
29
30
|
# RELATIONSHIPS
|
|
30
31
|
pdordlct: Mapped["PdOrdLct"] = relationship("PdOrdLct")
|
|
31
32
|
pdord: Mapped["PdOrd"] = relationship("PdOrd")
|
|
32
33
|
|
|
33
34
|
@hybrid_method
|
|
34
|
-
def availability_nominator_eligible_def(
|
|
35
|
+
def availability_nominator_eligible_def(
|
|
36
|
+
self, pdunit_id, /
|
|
37
|
+
) -> bool: # pragma: no cover
|
|
35
38
|
"""Python level - boils down to check if this operation history
|
|
36
39
|
is linked to the workcenter and represents a status change
|
|
37
40
|
from a status with pdordlc_isexecution"""
|
|
@@ -47,7 +50,9 @@ class PdOrdLch(Base):
|
|
|
47
50
|
)
|
|
48
51
|
|
|
49
52
|
@hybrid_method
|
|
50
|
-
def availability_denominator_eligible_def(
|
|
53
|
+
def availability_denominator_eligible_def(
|
|
54
|
+
self, pdunit_id, /
|
|
55
|
+
) -> bool: # pragma: no cover
|
|
51
56
|
"""Python level - boils down to check if this operation history
|
|
52
57
|
is linked to the workcenter and represents a status change
|
|
53
58
|
from a status with pdordlc_isactive(True) and pdordlc_isexecution(False)"""
|
|
@@ -11,6 +11,24 @@ from .pdordlc import PdOrdLc
|
|
|
11
11
|
|
|
12
12
|
class PdOrdLct(Base):
|
|
13
13
|
# COLS
|
|
14
|
+
name: Mapped[str]
|
|
15
|
+
validate: Mapped[bool] = mapped_column(default=True)
|
|
16
|
+
deleted: Mapped[bool] = mapped_column(default=False)
|
|
17
|
+
confirmsummary: Mapped[bool] = mapped_column(default=False)
|
|
18
|
+
hideinterminalui: Mapped[bool] = mapped_column(default=False)
|
|
19
|
+
triggeronperiodicsignalfail: Mapped[bool] = mapped_column(default=False)
|
|
20
|
+
isautocontinue: Mapped[bool] = mapped_column(default=False)
|
|
21
|
+
checkassignedqty: Mapped[bool] = mapped_column(default=False)
|
|
22
|
+
confirmunderdelivery: Mapped[bool] = mapped_column(default=False)
|
|
23
|
+
needsconfirm: Mapped[bool] = mapped_column(default=False)
|
|
24
|
+
checktoolstatus: Mapped[bool] = mapped_column(default=False)
|
|
25
|
+
automationsenditreport: Mapped[bool] = mapped_column(default=False)
|
|
26
|
+
definition: Mapped[str | None]
|
|
27
|
+
coded: Mapped[bool | None]
|
|
28
|
+
preference: Mapped[int | None]
|
|
29
|
+
progname: Mapped[str | None]
|
|
30
|
+
blpath: Mapped[str | None]
|
|
31
|
+
|
|
14
32
|
# FKS
|
|
15
33
|
from_pdordlc_id: Mapped[int] = mapped_column(ForeignKey("pdordlc.id"))
|
|
16
34
|
to_pdordlc_id: Mapped[int] = mapped_column(ForeignKey("pdordlc.id"))
|
|
@@ -2,16 +2,18 @@ from datetime import datetime
|
|
|
2
2
|
from typing import Any, Literal, cast, overload
|
|
3
3
|
|
|
4
4
|
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
5
|
+
from perfact.api.pp.model import PpRsrc, PpShft
|
|
5
6
|
from psycopg.types.range import Range
|
|
6
7
|
from sqlalchemy import and_, case, func, select
|
|
7
8
|
from sqlalchemy.dialects.postgresql import TSTZRANGE
|
|
8
9
|
from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
|
|
9
|
-
from sqlalchemy.orm import mapped_column
|
|
10
|
+
from sqlalchemy.orm import Session, mapped_column
|
|
10
11
|
from sqlalchemy.sql import ColumnElement
|
|
11
12
|
from sqlalchemy.sql.elements import SQLCoreOperations
|
|
12
13
|
from sqlalchemy.sql.expression import Extract
|
|
13
14
|
|
|
14
15
|
from .pdord import PdOrd
|
|
16
|
+
from .pdordlch import PdOrdLch
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
class PdOrdPrd(Base):
|
|
@@ -60,7 +62,7 @@ class PdOrdPrd(Base):
|
|
|
60
62
|
search_range = func.tstzrange(starttime, stoptime, "[)")
|
|
61
63
|
condition = and_(
|
|
62
64
|
cast(Any, cls.refrange).is_not(None),
|
|
63
|
-
cls.refrange.op("&&")(search_range),
|
|
65
|
+
cast(Any, cls.refrange).op("&&")(search_range),
|
|
64
66
|
)
|
|
65
67
|
intersection = cls.refrange.op("*")(search_range)
|
|
66
68
|
|
|
@@ -176,31 +178,152 @@ class PdOrdPrd(Base):
|
|
|
176
178
|
return cls.refrange_intersection(starttime, stoptime, True)
|
|
177
179
|
|
|
178
180
|
@hybrid_method
|
|
179
|
-
def performance_eligible_def(
|
|
181
|
+
def performance_eligible_def(
|
|
182
|
+
self, pdunit_id: int, starttime: datetime, stoptime: datetime, /
|
|
183
|
+
) -> bool:
|
|
180
184
|
"""Python-level: check if entry is eligible
|
|
181
185
|
to be taken in account for performance"""
|
|
182
186
|
raise NotImplementedError("Sql expression level only")
|
|
183
187
|
|
|
184
188
|
@performance_eligible_def.expression
|
|
185
|
-
def performance_eligible(
|
|
189
|
+
def performance_eligible(
|
|
190
|
+
cls, pdunit_id: int, starttime: datetime, stoptime: datetime, /
|
|
191
|
+
) -> ColumnElement[bool]:
|
|
186
192
|
"""SQL-level: check if entry is eligible
|
|
187
193
|
to be taken in account for performance"""
|
|
194
|
+
search_range = func.tstzrange(starttime, stoptime, "[)")
|
|
188
195
|
return and_(
|
|
189
196
|
cast(Any, cls.deleted).is_(False),
|
|
190
197
|
cast(Any, cls.pdunit_id) == pdunit_id,
|
|
191
198
|
cast(Any, cls.pdord).has(PdOrd.pdunit_id == pdunit_id),
|
|
199
|
+
cast(Any, cls.refrange).is_not(None),
|
|
200
|
+
cast(Any, cls.refrange).op("&&")(search_range),
|
|
192
201
|
)
|
|
193
202
|
|
|
194
203
|
@hybrid_method
|
|
195
|
-
def quality_eligible_def(
|
|
204
|
+
def quality_eligible_def(
|
|
205
|
+
self, pdunit_id: int, starttime: datetime, stoptime: datetime, /
|
|
206
|
+
) -> bool:
|
|
196
207
|
"""Python-level: check if entry is eligible
|
|
197
208
|
to be taken in account for quality"""
|
|
198
209
|
raise NotImplementedError("Sql expression level only")
|
|
199
210
|
|
|
200
211
|
@quality_eligible_def.expression
|
|
201
|
-
def quality_eligible(
|
|
212
|
+
def quality_eligible(
|
|
213
|
+
cls, pdunit_id: int, starttime: datetime, stoptime: datetime, /
|
|
214
|
+
) -> ColumnElement[bool]:
|
|
202
215
|
"""SQL-level: check if entry is eligible
|
|
203
216
|
to be taken in account for quality"""
|
|
217
|
+
search_range = func.tstzrange(starttime, stoptime, "[)")
|
|
204
218
|
return and_(
|
|
205
|
-
cast(Any, cls.deleted).is_(False),
|
|
219
|
+
cast(Any, cls.deleted).is_(False),
|
|
220
|
+
cast(Any, cls.pdunit_id) == pdunit_id,
|
|
221
|
+
cast(Any, cls.refrange).is_not(None),
|
|
222
|
+
cast(Any, cls.refrange).op("&&")(search_range),
|
|
206
223
|
)
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def oee_performance_computation(
|
|
227
|
+
session: Session, pdunit_id: int, starttime: datetime, stoptime: datetime, /
|
|
228
|
+
) -> float:
|
|
229
|
+
"""OEE function for computation of performance."""
|
|
230
|
+
result = session.execute(
|
|
231
|
+
select(
|
|
232
|
+
func.coalesce(
|
|
233
|
+
func.sum(PdOrdPrd.ideal_time_over_time_range(starttime, stoptime)),
|
|
234
|
+
0.0,
|
|
235
|
+
)
|
|
236
|
+
/ func.nullif(
|
|
237
|
+
func.coalesce(
|
|
238
|
+
func.sum(
|
|
239
|
+
PdOrdPrd.actual_time_over_time_range(starttime, stoptime)
|
|
240
|
+
),
|
|
241
|
+
0.0,
|
|
242
|
+
),
|
|
243
|
+
0.0,
|
|
244
|
+
)
|
|
245
|
+
).where(
|
|
246
|
+
PdOrdPrd.performance_eligible(pdunit_id, starttime, stoptime).is_(True)
|
|
247
|
+
)
|
|
248
|
+
).scalar()
|
|
249
|
+
return float(result or 0.0)
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def oee_quality_computation(
|
|
253
|
+
session: Session, pdunit_id: int, starttime: datetime, stoptime: datetime, /
|
|
254
|
+
) -> float | None:
|
|
255
|
+
"""OEE function computation for quality. None when no production data."""
|
|
256
|
+
return session.execute(
|
|
257
|
+
select(
|
|
258
|
+
func.coalesce(
|
|
259
|
+
func.sum(PdOrdPrd.good_quantity_over_range(starttime, stoptime)),
|
|
260
|
+
0.0,
|
|
261
|
+
)
|
|
262
|
+
/ func.nullif(
|
|
263
|
+
func.coalesce(
|
|
264
|
+
func.sum(
|
|
265
|
+
PdOrdPrd.total_quantity_over_range(starttime, stoptime)
|
|
266
|
+
),
|
|
267
|
+
0.0,
|
|
268
|
+
),
|
|
269
|
+
0.0,
|
|
270
|
+
)
|
|
271
|
+
).where(PdOrdPrd.quality_eligible(pdunit_id, starttime, stoptime).is_(True))
|
|
272
|
+
).scalar()
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def oee_availability_computation(
|
|
276
|
+
session: Session, pdunit_id: int, starttime: datetime, stoptime: datetime, /
|
|
277
|
+
) -> float:
|
|
278
|
+
"""OEE function for computation of availability"""
|
|
279
|
+
search_range = func.tstzrange(starttime, stoptime)
|
|
280
|
+
n_intersection = PdOrdLch.lchtimerange.op("*")(search_range)
|
|
281
|
+
in_seconds = func.extract(
|
|
282
|
+
"epoch", func.upper(n_intersection) - func.lower(n_intersection)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
current_available_time_nq = session.scalar(
|
|
286
|
+
select(
|
|
287
|
+
PdOrdLch.availability_missing_active_operations_nominator(
|
|
288
|
+
pdunit_id, starttime, stoptime
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
current_available_time_n = float(current_available_time_nq or 0.0)
|
|
293
|
+
|
|
294
|
+
current_available_time_dq = session.scalar(
|
|
295
|
+
select(
|
|
296
|
+
PdOrdLch.availability_missing_active_operations_denominator(
|
|
297
|
+
pdunit_id, starttime, stoptime
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
current_available_time_d = float(current_available_time_dq or 0.0)
|
|
302
|
+
|
|
303
|
+
# nominator calculation
|
|
304
|
+
nominator_q = session.execute(
|
|
305
|
+
select(func.coalesce(func.sum(in_seconds), 0.0))
|
|
306
|
+
.where(PdOrdLch.availability_nominator_eligible(pdunit_id).is_(True))
|
|
307
|
+
.where(PdOrdLch.lchtimerange.op("&&")(search_range))
|
|
308
|
+
).scalar()
|
|
309
|
+
nominator = float(nominator_q or 0.0) + current_available_time_n
|
|
310
|
+
|
|
311
|
+
# denominator calculation
|
|
312
|
+
d_intersection = n_intersection.op("*")(PpShft.shiftrange)
|
|
313
|
+
in_seconds = func.extract(
|
|
314
|
+
"epoch", func.upper(d_intersection) - func.lower(d_intersection)
|
|
315
|
+
)
|
|
316
|
+
denominator_q = session.execute(
|
|
317
|
+
select(func.coalesce(func.sum(in_seconds), 1.0))
|
|
318
|
+
.select_from(PdOrdLch)
|
|
319
|
+
.join(PpRsrc, PpRsrc.pdunit_id == pdunit_id)
|
|
320
|
+
.join(PpShft, PpShft.pdunit_id == PpRsrc.id)
|
|
321
|
+
.where(PdOrdLch.availability_denominator_eligible(pdunit_id).is_(True))
|
|
322
|
+
.where(PdOrdLch.lchtimerange.op("&&")(search_range))
|
|
323
|
+
.where(n_intersection.op("&&")(PpShft.shiftrange))
|
|
324
|
+
).scalar()
|
|
325
|
+
denominator = nominator + float(denominator_q or 0.0) + current_available_time_d
|
|
326
|
+
|
|
327
|
+
availability = nominator / denominator
|
|
328
|
+
|
|
329
|
+
return float(availability)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from perfact.api.base.model import Base, Mapped
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PdOrdType(Base):
|
|
6
|
+
name: Mapped[str]
|
|
7
|
+
shortcut: Mapped[str]
|
|
8
|
+
allowemptyclose: Mapped[bool] = mapped_column(default=True)
|
|
9
|
+
enablewhordreplanning: Mapped[bool] = mapped_column(default=False)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
from perfact.api.base import VisibilityAwareModel
|
|
4
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
5
|
+
from sqlalchemy import Interval, select
|
|
6
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
7
|
+
from sqlalchemy.orm import Session, mapped_column
|
|
8
|
+
|
|
9
|
+
from .pdordlch import PdOrdLch
|
|
10
|
+
from .pdunitgroup import PdUnitGroup
|
|
11
|
+
from .pdunitlc import PdUnitLc
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PdUnit(Base, VisibilityAwareModel):
|
|
15
|
+
modtime: Mapped[datetime]
|
|
16
|
+
author: Mapped[str]
|
|
17
|
+
name: Mapped[str | None]
|
|
18
|
+
num: Mapped[str]
|
|
19
|
+
overprodmay: Mapped[bool] = mapped_column(default=True)
|
|
20
|
+
overprodfactor: Mapped[float] = mapped_column(default=1.0)
|
|
21
|
+
seqnum: Mapped[int | None]
|
|
22
|
+
strictsequence: Mapped[bool] = mapped_column(default=True)
|
|
23
|
+
timetrack: Mapped[bool] = mapped_column(default=True)
|
|
24
|
+
qmreport: Mapped[bool] = mapped_column(default=True)
|
|
25
|
+
statereport: Mapped[bool] = mapped_column(default=False)
|
|
26
|
+
wastereport: Mapped[bool] = mapped_column(default=True)
|
|
27
|
+
propagateuseup: Mapped[bool] = mapped_column(default=True)
|
|
28
|
+
propagateoutput: Mapped[bool] = mapped_column(default=True)
|
|
29
|
+
useoutputwhcntr: Mapped[bool] = mapped_column(default=False)
|
|
30
|
+
maychooseorder: Mapped[bool] = mapped_column(default=True)
|
|
31
|
+
allowparallelprocessing: Mapped[bool] = mapped_column(default=False)
|
|
32
|
+
showsetupbarcodes: Mapped[bool] = mapped_column(default=False)
|
|
33
|
+
periodicsignalcheck: Mapped[bool] = mapped_column(default=False)
|
|
34
|
+
perscapacity: Mapped[int] = mapped_column(default=1)
|
|
35
|
+
output_whobjlc_id: Mapped[int] = mapped_column(default=1)
|
|
36
|
+
autoreportgood: Mapped[bool] = mapped_column(default=False)
|
|
37
|
+
autoreportnone: Mapped[bool] = mapped_column(default=False)
|
|
38
|
+
whloc_id: Mapped[int | None]
|
|
39
|
+
periodicsignalextfactor: Mapped[float | None]
|
|
40
|
+
mincycletime: Mapped[timedelta | None] = mapped_column(Interval)
|
|
41
|
+
input_whloc_id: Mapped[int | None]
|
|
42
|
+
extref: Mapped[dict | None] = mapped_column(JSONB)
|
|
43
|
+
fileattr_id: Mapped[int | None]
|
|
44
|
+
tssubjectmatter_id: Mapped[int | None]
|
|
45
|
+
pdplant_id: Mapped[int | None]
|
|
46
|
+
|
|
47
|
+
# FKS
|
|
48
|
+
pdunitgroup_id: Mapped[int | None] = mapped_column(ForeignKey("pdunitgroup.id"))
|
|
49
|
+
pdunitlc_id: Mapped[int | None] = mapped_column(ForeignKey("pdunitlc.id"))
|
|
50
|
+
|
|
51
|
+
# RELATIONSHIPS
|
|
52
|
+
pdunitgroup: Mapped[PdUnitGroup | None] = relationship("PdUnitGroup")
|
|
53
|
+
pdunitlc: Mapped[PdUnitLc | None] = relationship("PdUnitLc")
|
|
54
|
+
|
|
55
|
+
def get_last_pdordlch_entry_for_timestamp(
|
|
56
|
+
self, session: Session, timestamp: datetime
|
|
57
|
+
) -> PdOrdLch | None:
|
|
58
|
+
"""Return the most recent PdOrdLch entry for this unit at or before `timestamp`.
|
|
59
|
+
|
|
60
|
+
Queries the DB with LIMIT 1 instead of loading the full relationship,
|
|
61
|
+
so memory usage is O(1) regardless of history size.
|
|
62
|
+
"""
|
|
63
|
+
return session.scalars(
|
|
64
|
+
select(PdOrdLch)
|
|
65
|
+
.where(PdOrdLch.pdunit_id == self.id)
|
|
66
|
+
.where(PdOrdLch.createtime <= timestamp)
|
|
67
|
+
.order_by(PdOrdLch.createtime.desc())
|
|
68
|
+
.limit(1)
|
|
69
|
+
).first()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PdUnitGroup(Base):
|
|
6
|
+
name: Mapped[str]
|
|
7
|
+
num: Mapped[str]
|
|
8
|
+
whsite_id: Mapped[int]
|
|
9
|
+
sharedorders: Mapped[bool] = mapped_column(default=False)
|
|
10
|
+
deleted: Mapped[bool] = mapped_column(default=False)
|
|
11
|
+
whloc_id: Mapped[int | None]
|
|
12
|
+
parent_pdunitgroup_id: Mapped[int | None] = mapped_column(
|
|
13
|
+
ForeignKey("pdunitgroup.id")
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
parent: Mapped["PdUnitGroup | None"] = relationship(
|
|
17
|
+
"PdUnitGroup",
|
|
18
|
+
foreign_keys="[PdUnitGroup.parent_pdunitgroup_id]",
|
|
19
|
+
back_populates="children",
|
|
20
|
+
remote_side="[PdUnitGroup.id]",
|
|
21
|
+
)
|
|
22
|
+
children: Mapped[list["PdUnitGroup"]] = relationship(
|
|
23
|
+
"PdUnitGroup",
|
|
24
|
+
back_populates="parent",
|
|
25
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from perfact.api.base.model import Base, Mapped
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PdUnitLc(Base):
|
|
6
|
+
name: Mapped[str]
|
|
7
|
+
deleted: Mapped[bool] = mapped_column(default=False)
|
|
8
|
+
isinitial: Mapped[bool] = mapped_column(default=False)
|
|
9
|
+
description: Mapped[str | None]
|
|
10
|
+
seqnum: Mapped[int | None]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
4
|
+
from sqlalchemy import DateTime, Interval
|
|
5
|
+
from sqlalchemy.orm import mapped_column
|
|
6
|
+
|
|
7
|
+
from .pdunit import PdUnit
|
|
8
|
+
from .pdunitlct import PdUnitLct
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PdUnitLch(Base):
|
|
12
|
+
# COLS
|
|
13
|
+
createtime: Mapped[datetime.datetime] = mapped_column(
|
|
14
|
+
DateTime(timezone=True), default=datetime.datetime.now
|
|
15
|
+
)
|
|
16
|
+
lchtimespent: Mapped[datetime.timedelta | None] = mapped_column(Interval)
|
|
17
|
+
|
|
18
|
+
# FKS
|
|
19
|
+
pdunit_id: Mapped[int] = mapped_column(ForeignKey("pdunit.id"))
|
|
20
|
+
pdunitlct_id: Mapped[int] = mapped_column(ForeignKey("pdunitlct.id"))
|
|
21
|
+
|
|
22
|
+
# RELATIONSHIPS
|
|
23
|
+
pdunit: Mapped[PdUnit] = relationship("PdUnit")
|
|
24
|
+
pdunitlct: Mapped[PdUnitLct] = relationship("PdUnitLct")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
|
|
4
|
+
from .pdunitlc import PdUnitLc
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PdUnitLct(Base):
|
|
8
|
+
name: Mapped[str]
|
|
9
|
+
from_pdunitlc_id: Mapped[int] = mapped_column(ForeignKey("pdunitlc.id"))
|
|
10
|
+
to_pdunitlc_id: Mapped[int] = mapped_column(ForeignKey("pdunitlc.id"))
|
|
11
|
+
validate: Mapped[bool] = mapped_column(default=True)
|
|
12
|
+
deleted: Mapped[bool] = mapped_column(default=False)
|
|
13
|
+
hideinofficeui: Mapped[bool] = mapped_column(default=False)
|
|
14
|
+
definition: Mapped[str | None]
|
|
15
|
+
coded: Mapped[bool | None]
|
|
16
|
+
preference: Mapped[int | None]
|
|
17
|
+
progname: Mapped[str | None]
|
|
18
|
+
blpath: Mapped[str | None]
|
|
19
|
+
|
|
20
|
+
from_pdunitlc: Mapped["PdUnitLc"] = relationship(
|
|
21
|
+
"PdUnitLc", foreign_keys=[from_pdunitlc_id]
|
|
22
|
+
)
|
|
23
|
+
to_pdunitlc: Mapped["PdUnitLc"] = relationship(
|
|
24
|
+
"PdUnitLc", foreign_keys=[to_pdunitlc_id]
|
|
25
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .pdunitlch import UnitLchHistoryEntry, get_unit_lch_history
|
|
2
|
+
from .pdunitperformance_orm import (
|
|
3
|
+
get_time_performance_data_v2,
|
|
4
|
+
get_time_performance_data_v3,
|
|
5
|
+
get_time_performance_data_v4,
|
|
6
|
+
)
|
|
7
|
+
from .pdunitperformance_plainsql import get_time_performance_data
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"UnitLchHistoryEntry",
|
|
11
|
+
"get_unit_lch_history",
|
|
12
|
+
"get_time_performance_data",
|
|
13
|
+
"get_time_performance_data_v2",
|
|
14
|
+
"get_time_performance_data_v3",
|
|
15
|
+
"get_time_performance_data_v4",
|
|
16
|
+
]
|