perfact-api-pd 0.2__py2.py3-none-any.whl
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/__init__.py +0 -0
- perfact/api/pd/model/__init__.py +16 -0
- perfact/api/pd/model/pdord.py +47 -0
- perfact/api/pd/model/pdordlc.py +8 -0
- perfact/api/pd/model/pdordlch.py +195 -0
- perfact/api/pd/model/pdordlct.py +74 -0
- perfact/api/pd/model/pdordprd.py +206 -0
- perfact/api/pd/model/pdunit.py +14 -0
- perfact/api/pd/model/policy.py +23 -0
- perfact/api/pd/model/py.typed +0 -0
- perfact/api/pd/py.typed +0 -0
- perfact_api_pd-0.2.dist-info/METADATA +24 -0
- perfact_api_pd-0.2.dist-info/RECORD +16 -0
- perfact_api_pd-0.2.dist-info/WHEEL +6 -0
- perfact_api_pd-0.2.dist-info/entry_points.txt +2 -0
- perfact_api_pd-0.2.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .pdord import PdOrd, PdOrd_TimeStat
|
|
2
|
+
from .pdordlc import PdOrdLc
|
|
3
|
+
from .pdordlch import PdOrdLch
|
|
4
|
+
from .pdordlct import PdOrdLct
|
|
5
|
+
from .pdordprd import PdOrdPrd
|
|
6
|
+
from .pdunit import PdUnit
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"PdUnit",
|
|
10
|
+
"PdOrdLch",
|
|
11
|
+
"PdOrdLct",
|
|
12
|
+
"PdOrdLc",
|
|
13
|
+
"PdOrdPrd",
|
|
14
|
+
"PdOrd_TimeStat",
|
|
15
|
+
"PdOrd",
|
|
16
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, View
|
|
5
|
+
from sqlalchemy import (
|
|
6
|
+
BigInteger,
|
|
7
|
+
DateTime,
|
|
8
|
+
Interval,
|
|
9
|
+
)
|
|
10
|
+
from sqlalchemy.orm import mapped_column
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PdOrd(Base):
|
|
14
|
+
# COLS
|
|
15
|
+
planmachinetime: Mapped[timedelta | None] = mapped_column(Interval)
|
|
16
|
+
planstarttime: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
17
|
+
starttime: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
18
|
+
planstoptime: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
19
|
+
stoptime: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
|
20
|
+
qty: Mapped[float]
|
|
21
|
+
plancycletime: Mapped[timedelta | None] = mapped_column(Interval)
|
|
22
|
+
plansetuptime: Mapped[timedelta | None] = mapped_column(Interval)
|
|
23
|
+
|
|
24
|
+
# FKS
|
|
25
|
+
pdunit_id: Mapped[int | None] = mapped_column(ForeignKey("pdunit.id"))
|
|
26
|
+
pdordlc_id: Mapped[int | None] = mapped_column(ForeignKey("pdordlc.id"))
|
|
27
|
+
# RELATIONSHIPS
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# VIEWS
|
|
31
|
+
class PdOrd_TimeStat(View):
|
|
32
|
+
__tablename__ = "pdord_timestat"
|
|
33
|
+
id: Mapped[int] = mapped_column(
|
|
34
|
+
"pdordtimestat_pdord_id", BigInteger, primary_key=True
|
|
35
|
+
)
|
|
36
|
+
activetime: Mapped[Optional[timedelta]] = mapped_column(
|
|
37
|
+
"pdordtimestat_activetime", Interval, nullable=True
|
|
38
|
+
)
|
|
39
|
+
cycle_time: Mapped[Optional[timedelta]] = mapped_column(
|
|
40
|
+
"pdordtimestat_cycle_time", Interval, nullable=True
|
|
41
|
+
)
|
|
42
|
+
setuptime: Mapped[Optional[timedelta]] = mapped_column(
|
|
43
|
+
"pdordtimestat_setuptime", Interval, nullable=True
|
|
44
|
+
)
|
|
45
|
+
spentindowntime: Mapped[Optional[timedelta]] = mapped_column(
|
|
46
|
+
"pdordtimestat_spentindowntime", Interval, nullable=True
|
|
47
|
+
)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from perfact.api.base.model import Base, Mapped
|
|
2
|
+
from sqlalchemy.orm import mapped_column
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PdOrdLc(Base):
|
|
6
|
+
isexecution: Mapped[bool] = mapped_column(default=False)
|
|
7
|
+
isactive: Mapped[bool] = mapped_column(default=False)
|
|
8
|
+
isfinal: Mapped[bool] = mapped_column(default=False)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import cast
|
|
3
|
+
|
|
4
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
5
|
+
from perfact.api.pp.model import PpRsrc, PpShft
|
|
6
|
+
from psycopg.types.range import Range
|
|
7
|
+
from sqlalchemy import DateTime, and_, case, func, select
|
|
8
|
+
from sqlalchemy.dialects.postgresql import TSTZRANGE
|
|
9
|
+
from sqlalchemy.ext.hybrid import hybrid_method
|
|
10
|
+
from sqlalchemy.orm import mapped_column
|
|
11
|
+
from sqlalchemy.sql import ColumnElement
|
|
12
|
+
|
|
13
|
+
from .pdord import PdOrd
|
|
14
|
+
from .pdordlct import PdOrdLct
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PdOrdLch(Base):
|
|
18
|
+
# COLS
|
|
19
|
+
createtime: Mapped[datetime] = mapped_column(
|
|
20
|
+
DateTime(timezone=True), default=func.now()
|
|
21
|
+
)
|
|
22
|
+
lchtimerange: Mapped[Range[datetime]] = mapped_column(TSTZRANGE)
|
|
23
|
+
|
|
24
|
+
# FKS
|
|
25
|
+
pdunit_id: Mapped[int | None] = mapped_column(ForeignKey("pdunit.id"))
|
|
26
|
+
pdord_id: Mapped[int] = mapped_column(ForeignKey("pdord.id"))
|
|
27
|
+
pdordlct_id: Mapped[int] = mapped_column(ForeignKey("pdordlct.id"))
|
|
28
|
+
|
|
29
|
+
# RELATIONSHIPS
|
|
30
|
+
pdordlct: Mapped["PdOrdLct"] = relationship("PdOrdLct")
|
|
31
|
+
pdord: Mapped["PdOrd"] = relationship("PdOrd")
|
|
32
|
+
|
|
33
|
+
@hybrid_method
|
|
34
|
+
def availability_nominator_eligible_def(self, pdunit_id, /) -> bool:
|
|
35
|
+
"""Python level - boils down to check if this operation history
|
|
36
|
+
is linked to the workcenter and represents a status change
|
|
37
|
+
from a status with pdordlc_isexecution"""
|
|
38
|
+
raise NotImplementedError("Sql expression level only")
|
|
39
|
+
|
|
40
|
+
@availability_nominator_eligible_def.expression
|
|
41
|
+
@classmethod
|
|
42
|
+
def availability_nominator_eligible(cls, pdunit_id, /) -> ColumnElement[bool]:
|
|
43
|
+
"""SQL level - check definition"""
|
|
44
|
+
return and_(
|
|
45
|
+
cls.pdunit_id == pdunit_id,
|
|
46
|
+
cls.pdordlct.has(PdOrdLct.availability_nominator_eligible.is_(True)),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@hybrid_method
|
|
50
|
+
def availability_denominator_eligible_def(self, pdunit_id, /) -> bool:
|
|
51
|
+
"""Python level - boils down to check if this operation history
|
|
52
|
+
is linked to the workcenter and represents a status change
|
|
53
|
+
from a status with pdordlc_isactive(True) and pdordlc_isexecution(False)"""
|
|
54
|
+
raise NotImplementedError("Sql expression level only")
|
|
55
|
+
|
|
56
|
+
@availability_denominator_eligible_def.expression
|
|
57
|
+
@classmethod
|
|
58
|
+
def availability_denominator_eligible(cls, pdunit_id, /) -> ColumnElement[bool]:
|
|
59
|
+
"""SQL level - check definition"""
|
|
60
|
+
return and_(
|
|
61
|
+
cls.pdunit_id == pdunit_id,
|
|
62
|
+
cls.pdordlct.has(PdOrdLct.availability_denominator_eligible.is_(True)),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def availability_valid_intersection_seconds(
|
|
67
|
+
cls, starttime, stoptime, /
|
|
68
|
+
) -> tuple[ColumnElement, ColumnElement[float | None]]:
|
|
69
|
+
search_range = func.tstzrange(cls.modtime, func.now())
|
|
70
|
+
intersection = search_range.op("*")(func.tstzrange(starttime, stoptime))
|
|
71
|
+
|
|
72
|
+
valid_intersection = cast(
|
|
73
|
+
ColumnElement[float | None],
|
|
74
|
+
case(
|
|
75
|
+
(func.isempty(intersection), None),
|
|
76
|
+
else_=func.extract(
|
|
77
|
+
"epoch", func.upper(intersection) - func.lower(intersection)
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
return intersection, valid_intersection
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def availability_missing_active_operations_nominator(
|
|
85
|
+
cls, pdunit_id, starttime, stoptime, /
|
|
86
|
+
) -> ColumnElement[float | None]:
|
|
87
|
+
"""To be used only as SQL method.
|
|
88
|
+
Check if any missing active operations have to be added to availability
|
|
89
|
+
nominator"""
|
|
90
|
+
_, valid_intersection = cls.availability_valid_intersection_seconds(
|
|
91
|
+
starttime, stoptime
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
latest_by_operation = (
|
|
95
|
+
select(
|
|
96
|
+
case(
|
|
97
|
+
(
|
|
98
|
+
cls.pdordlct.has(
|
|
99
|
+
PdOrdLct.availability_active_operation_nominator.is_(True)
|
|
100
|
+
),
|
|
101
|
+
valid_intersection,
|
|
102
|
+
),
|
|
103
|
+
else_=None,
|
|
104
|
+
).label("eligible_seconds"),
|
|
105
|
+
)
|
|
106
|
+
.select_from(cls)
|
|
107
|
+
.join(cls.pdord)
|
|
108
|
+
.where(cls.pdunit_id == pdunit_id)
|
|
109
|
+
.distinct(PdOrd.id)
|
|
110
|
+
.order_by(PdOrd.id, cls.id.desc())
|
|
111
|
+
.subquery()
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return cast(
|
|
115
|
+
ColumnElement[float | None],
|
|
116
|
+
select(func.sum(latest_by_operation.c.eligible_seconds)).scalar_subquery(),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
@classmethod
|
|
120
|
+
def availability_missing_active_operations_denominator(
|
|
121
|
+
cls, pdunit_id, starttime, stoptime, /
|
|
122
|
+
) -> ColumnElement[float | None]:
|
|
123
|
+
"""To be used only as SQL method.
|
|
124
|
+
Check if any missing active operations have to be added to availability
|
|
125
|
+
denominator"""
|
|
126
|
+
intersection, _ = cls.availability_valid_intersection_seconds(
|
|
127
|
+
starttime, stoptime
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
shift_intersection = intersection.op("*")(PpShft.shiftrange)
|
|
131
|
+
shift_overlap_seconds = cast(
|
|
132
|
+
ColumnElement[float],
|
|
133
|
+
select(
|
|
134
|
+
func.coalesce(
|
|
135
|
+
func.sum(
|
|
136
|
+
case(
|
|
137
|
+
(
|
|
138
|
+
func.isempty(shift_intersection),
|
|
139
|
+
0.0,
|
|
140
|
+
),
|
|
141
|
+
else_=func.extract(
|
|
142
|
+
"epoch",
|
|
143
|
+
func.upper(shift_intersection)
|
|
144
|
+
- func.lower(shift_intersection),
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
),
|
|
148
|
+
0.0,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
.select_from(PpRsrc)
|
|
152
|
+
.join(PpShft, PpShft.pprsrc_id == PpRsrc.id)
|
|
153
|
+
.where(PpRsrc.pdunit_id == pdunit_id)
|
|
154
|
+
.where(intersection.op("&&")(PpShft.shiftrange))
|
|
155
|
+
.scalar_subquery(),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# ONLY entries that are in range of:
|
|
159
|
+
# intersection between (modtime, func.now()) and given_range
|
|
160
|
+
# can be removed, used only for ease to read
|
|
161
|
+
has_shift_overlap = (
|
|
162
|
+
select(PpShft.id)
|
|
163
|
+
.select_from(PpRsrc)
|
|
164
|
+
.join(PpShft, PpShft.pprsrc_id == PpRsrc.id)
|
|
165
|
+
.where(PpRsrc.pdunit_id == pdunit_id)
|
|
166
|
+
.where(intersection.op("&&")(PpShft.shiftrange))
|
|
167
|
+
.exists()
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
latest_by_operation = (
|
|
171
|
+
select(
|
|
172
|
+
case(
|
|
173
|
+
(
|
|
174
|
+
cls.pdordlct.has(
|
|
175
|
+
PdOrdLct.availability_active_operation_denominator.is_(True)
|
|
176
|
+
)
|
|
177
|
+
& has_shift_overlap,
|
|
178
|
+
shift_overlap_seconds,
|
|
179
|
+
),
|
|
180
|
+
else_=None,
|
|
181
|
+
).label("eligible_seconds"),
|
|
182
|
+
)
|
|
183
|
+
.select_from(cls)
|
|
184
|
+
.join(cls.pdord)
|
|
185
|
+
.where(cls.pdunit_id == pdunit_id)
|
|
186
|
+
.where(has_shift_overlap)
|
|
187
|
+
.distinct(PdOrd.id)
|
|
188
|
+
.order_by(PdOrd.id, cls.id.desc())
|
|
189
|
+
.subquery()
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
return cast(
|
|
193
|
+
ColumnElement[float | None],
|
|
194
|
+
select(func.sum(latest_by_operation.c.eligible_seconds)).scalar_subquery(),
|
|
195
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
|
|
3
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
4
|
+
from sqlalchemy import and_
|
|
5
|
+
from sqlalchemy.ext.hybrid import hybrid_property
|
|
6
|
+
from sqlalchemy.orm import mapped_column
|
|
7
|
+
from sqlalchemy.sql import ColumnElement
|
|
8
|
+
|
|
9
|
+
from .pdordlc import PdOrdLc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PdOrdLct(Base):
|
|
13
|
+
# COLS
|
|
14
|
+
# FKS
|
|
15
|
+
from_pdordlc_id: Mapped[int] = mapped_column(ForeignKey("pdordlc.id"))
|
|
16
|
+
to_pdordlc_id: Mapped[int] = mapped_column(ForeignKey("pdordlc.id"))
|
|
17
|
+
|
|
18
|
+
# RELATIONSHIPS
|
|
19
|
+
from_pdordlc: Mapped["PdOrdLc"] = relationship(
|
|
20
|
+
"PdOrdLc", foreign_keys=[from_pdordlc_id]
|
|
21
|
+
)
|
|
22
|
+
to_pdordlc: Mapped["PdOrdLc"] = relationship(
|
|
23
|
+
"PdOrdLc", foreign_keys=[to_pdordlc_id]
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
@hybrid_property
|
|
27
|
+
def availability_nominator_eligible_def(self) -> bool:
|
|
28
|
+
"""Python level - check if entry is eligible for availability
|
|
29
|
+
computation"""
|
|
30
|
+
raise NotImplementedError("Sql expression level only")
|
|
31
|
+
|
|
32
|
+
@availability_nominator_eligible_def.expression
|
|
33
|
+
def availability_nominator_eligible(cls) -> ColumnElement[bool]:
|
|
34
|
+
"""SQL level - check definition"""
|
|
35
|
+
return cast(Any, cls.from_pdordlc).has(PdOrdLc.isexecution.is_(True))
|
|
36
|
+
|
|
37
|
+
@hybrid_property
|
|
38
|
+
def availability_denominator_eligible_def(self) -> bool:
|
|
39
|
+
"""Python level - this takes in cosideration that the entries with
|
|
40
|
+
isexecution(True) have been already computed and added to the denominator"""
|
|
41
|
+
raise NotImplementedError("Sql expression level only")
|
|
42
|
+
|
|
43
|
+
@availability_denominator_eligible_def.expression
|
|
44
|
+
def availability_denominator_eligible(cls) -> ColumnElement[bool]:
|
|
45
|
+
"""SQL level - check definition"""
|
|
46
|
+
return and_(
|
|
47
|
+
cast(Any, cls.from_pdordlc).has(PdOrdLc.isactive.is_(True)),
|
|
48
|
+
cast(Any, cls.from_pdordlc).has(PdOrdLc.isexecution.is_(False)),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@hybrid_property
|
|
52
|
+
def availability_active_operation_nominator_def(self) -> bool:
|
|
53
|
+
"""Python level - check if the entry has a valid TO_PDORDLC which
|
|
54
|
+
would then be added to the availability."""
|
|
55
|
+
raise NotImplementedError("Sql expression level only")
|
|
56
|
+
|
|
57
|
+
@availability_active_operation_nominator_def.expression
|
|
58
|
+
def availability_active_operation_nominator(cls) -> ColumnElement[bool]:
|
|
59
|
+
"""SQL level - check definition"""
|
|
60
|
+
return cast(Any, cls.to_pdordlc).has(PdOrdLc.isexecution.is_(True))
|
|
61
|
+
|
|
62
|
+
@hybrid_property
|
|
63
|
+
def availability_active_operation_denominator_def(self) -> bool:
|
|
64
|
+
"""Python level - check if the entry has a valid TO_PDORDLC which
|
|
65
|
+
would then be added to the availability."""
|
|
66
|
+
raise NotImplementedError("Sql expression level only")
|
|
67
|
+
|
|
68
|
+
@availability_active_operation_denominator_def.expression
|
|
69
|
+
def availability_active_operation_denominator(cls) -> ColumnElement[bool]:
|
|
70
|
+
"""SQL level - check definition"""
|
|
71
|
+
return and_(
|
|
72
|
+
cast(Any, cls.to_pdordlc).has(PdOrdLc.isactive.is_(True)),
|
|
73
|
+
cast(Any, cls.to_pdordlc).has(PdOrdLc.isexecution.is_(False)),
|
|
74
|
+
)
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, Literal, cast, overload
|
|
3
|
+
|
|
4
|
+
from perfact.api.base.model import Base, ForeignKey, Mapped, relationship
|
|
5
|
+
from psycopg.types.range import Range
|
|
6
|
+
from sqlalchemy import and_, case, func, select
|
|
7
|
+
from sqlalchemy.dialects.postgresql import TSTZRANGE
|
|
8
|
+
from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
|
|
9
|
+
from sqlalchemy.orm import mapped_column
|
|
10
|
+
from sqlalchemy.sql import ColumnElement
|
|
11
|
+
from sqlalchemy.sql.elements import SQLCoreOperations
|
|
12
|
+
from sqlalchemy.sql.expression import Extract
|
|
13
|
+
|
|
14
|
+
from .pdord import PdOrd
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PdOrdPrd(Base):
|
|
18
|
+
# COLS
|
|
19
|
+
deleted: Mapped[bool]
|
|
20
|
+
refrange: Mapped[Range[datetime]] = mapped_column(TSTZRANGE, index=True)
|
|
21
|
+
quantity: Mapped[float]
|
|
22
|
+
quantitywaste: Mapped[float | None]
|
|
23
|
+
|
|
24
|
+
# FKS
|
|
25
|
+
pdord_id: Mapped[int] = mapped_column(ForeignKey("pdord.id"))
|
|
26
|
+
pdunit_id: Mapped[int | None] = mapped_column(ForeignKey("pdunit.id"))
|
|
27
|
+
|
|
28
|
+
# RELATIONSHIPS
|
|
29
|
+
pdord: Mapped["PdOrd"] = relationship("PdOrd")
|
|
30
|
+
|
|
31
|
+
@hybrid_method
|
|
32
|
+
def refrange_intersection_def(
|
|
33
|
+
self, starttime: datetime, stoptime: datetime, seconds: bool = False, /
|
|
34
|
+
) -> float | Range[datetime] | None:
|
|
35
|
+
"""Python-level: intersection of the refrance with the given range"""
|
|
36
|
+
raise NotImplementedError("Sql expression level only")
|
|
37
|
+
|
|
38
|
+
_refrange_intersection_def_hybrid = cast(Any, refrange_intersection_def)
|
|
39
|
+
|
|
40
|
+
@overload
|
|
41
|
+
@classmethod
|
|
42
|
+
def refrange_intersection(
|
|
43
|
+
cls, starttime: datetime, stoptime: datetime, seconds: Literal[False], /
|
|
44
|
+
) -> SQLCoreOperations[Range[datetime] | None]: ...
|
|
45
|
+
@overload
|
|
46
|
+
@classmethod
|
|
47
|
+
def refrange_intersection(
|
|
48
|
+
cls, starttime: datetime, stoptime: datetime, seconds: Literal[True], /
|
|
49
|
+
) -> SQLCoreOperations[float]: ...
|
|
50
|
+
@_refrange_intersection_def_hybrid.inplace.expression
|
|
51
|
+
@classmethod
|
|
52
|
+
def refrange_intersection(
|
|
53
|
+
cls,
|
|
54
|
+
starttime: datetime,
|
|
55
|
+
stoptime: datetime,
|
|
56
|
+
seconds: bool = False,
|
|
57
|
+
/,
|
|
58
|
+
) -> SQLCoreOperations[float | Range[datetime] | None]:
|
|
59
|
+
"""SQL-level: intersection of the refrance with the given range"""
|
|
60
|
+
search_range = func.tstzrange(starttime, stoptime, "[)")
|
|
61
|
+
condition = and_(
|
|
62
|
+
cast(Any, cls.refrange).is_not(None),
|
|
63
|
+
cls.refrange.op("&&")(search_range),
|
|
64
|
+
)
|
|
65
|
+
intersection = cls.refrange.op("*")(search_range)
|
|
66
|
+
|
|
67
|
+
if seconds:
|
|
68
|
+
length = func.upper(intersection) - func.lower(intersection)
|
|
69
|
+
return case((condition, func.extract("epoch", length)), else_=0.0)
|
|
70
|
+
else:
|
|
71
|
+
return case((condition, intersection), else_=None)
|
|
72
|
+
|
|
73
|
+
@hybrid_method
|
|
74
|
+
def total_quantity_def(self, /) -> float:
|
|
75
|
+
"""Python-level: get total quantity (good + scrap)"""
|
|
76
|
+
raise NotImplementedError("Sql expression level only")
|
|
77
|
+
|
|
78
|
+
@total_quantity_def.expression
|
|
79
|
+
@classmethod
|
|
80
|
+
def total_quantity(cls, /) -> ColumnElement[float]:
|
|
81
|
+
"""SQL-level: get total quantity (good + scrap)"""
|
|
82
|
+
return case(
|
|
83
|
+
(cls.quantitywaste.is_not(None), cls.quantity + cls.quantitywaste),
|
|
84
|
+
else_=cls.quantity,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@hybrid_method
|
|
88
|
+
def good_quantity_over_range_def(
|
|
89
|
+
self, starttime: datetime, stoptime: datetime, /
|
|
90
|
+
) -> float:
|
|
91
|
+
"""Python-level: get good quantity over time range"""
|
|
92
|
+
raise NotImplementedError("Sql expression level only")
|
|
93
|
+
|
|
94
|
+
@good_quantity_over_range_def.expression
|
|
95
|
+
def good_quantity_over_range(
|
|
96
|
+
cls, starttime: datetime, stoptime: datetime, /
|
|
97
|
+
) -> ColumnElement[float]:
|
|
98
|
+
"""SQL-level: get good quantity over time range"""
|
|
99
|
+
return (
|
|
100
|
+
cls.refrange_intersection(starttime, stoptime, True)
|
|
101
|
+
* cls.quantity
|
|
102
|
+
/ cls.actual_time_seconds
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@hybrid_method
|
|
106
|
+
def total_quantity_over_range_def(
|
|
107
|
+
self, starttime: datetime, stoptime: datetime, /
|
|
108
|
+
) -> float:
|
|
109
|
+
"""Python-level: get total quantity (good + scrap) over time range"""
|
|
110
|
+
raise NotImplementedError("Sql expression level only")
|
|
111
|
+
|
|
112
|
+
@total_quantity_over_range_def.expression
|
|
113
|
+
def total_quantity_over_range(
|
|
114
|
+
cls, starttime: datetime, stoptime: datetime, /
|
|
115
|
+
) -> ColumnElement[float]:
|
|
116
|
+
"""SQL-level: get total quantity (good + scrap) over time range"""
|
|
117
|
+
return (
|
|
118
|
+
cls.refrange_intersection(starttime, stoptime, True)
|
|
119
|
+
* cls.total_quantity()
|
|
120
|
+
/ cls.actual_time_seconds
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@hybrid_method
|
|
124
|
+
def ideal_time_over_time_range_def(
|
|
125
|
+
self, starttime: datetime, stoptime: datetime, /
|
|
126
|
+
) -> float:
|
|
127
|
+
"""Python-level: how many units would ideally be produced in the timeframe"""
|
|
128
|
+
raise NotImplementedError("Sql expression level only")
|
|
129
|
+
|
|
130
|
+
@ideal_time_over_time_range_def.expression
|
|
131
|
+
@classmethod
|
|
132
|
+
def ideal_time_over_time_range(
|
|
133
|
+
cls, starttime: datetime, stoptime: datetime, /
|
|
134
|
+
) -> ColumnElement[float]:
|
|
135
|
+
"""SQL-level: how many units would ideally be produced in the timeframe"""
|
|
136
|
+
total_q = cls.total_quantity_over_range(starttime, stoptime)
|
|
137
|
+
|
|
138
|
+
planmachinetime_q = (
|
|
139
|
+
select(PdOrd.planmachinetime)
|
|
140
|
+
.where(PdOrd.id == cls.pdord_id)
|
|
141
|
+
.correlate(cls)
|
|
142
|
+
.scalar_subquery()
|
|
143
|
+
)
|
|
144
|
+
return case(
|
|
145
|
+
(
|
|
146
|
+
planmachinetime_q.is_not(None),
|
|
147
|
+
func.extract("epoch", planmachinetime_q) * total_q,
|
|
148
|
+
),
|
|
149
|
+
else_=0.0,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@hybrid_property
|
|
153
|
+
def actual_time_seconds_def(self, /) -> Extract:
|
|
154
|
+
"""Python-level: get total actual prod time"""
|
|
155
|
+
raise NotImplementedError("Sql expression level only")
|
|
156
|
+
|
|
157
|
+
@actual_time_seconds_def.expression
|
|
158
|
+
def actual_time_seconds(cls, /) -> Extract:
|
|
159
|
+
"""SQL-level: get total actual prod time"""
|
|
160
|
+
return func.extract(
|
|
161
|
+
"epoch", (func.upper(cls.refrange) - func.lower(cls.refrange))
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
@hybrid_method
|
|
165
|
+
def actual_time_over_time_range_def(
|
|
166
|
+
self, starttime: datetime, stoptime: datetime, /
|
|
167
|
+
) -> float:
|
|
168
|
+
"""Python-level: get actual prod time in seconds"""
|
|
169
|
+
raise NotImplementedError("Sql expression level only")
|
|
170
|
+
|
|
171
|
+
@actual_time_over_time_range_def.expression
|
|
172
|
+
def actual_time_over_time_range(
|
|
173
|
+
cls, starttime: datetime, stoptime: datetime, /
|
|
174
|
+
) -> SQLCoreOperations[float]:
|
|
175
|
+
"""SQL-level: get actual prod time in seconds"""
|
|
176
|
+
return cls.refrange_intersection(starttime, stoptime, True)
|
|
177
|
+
|
|
178
|
+
@hybrid_method
|
|
179
|
+
def performance_eligible_def(self, pdunit_id: int, /) -> bool:
|
|
180
|
+
"""Python-level: check if entry is eligible
|
|
181
|
+
to be taken in account for performance"""
|
|
182
|
+
raise NotImplementedError("Sql expression level only")
|
|
183
|
+
|
|
184
|
+
@performance_eligible_def.expression
|
|
185
|
+
def performance_eligible(cls, pdunit_id: int, /) -> ColumnElement[bool]:
|
|
186
|
+
"""SQL-level: check if entry is eligible
|
|
187
|
+
to be taken in account for performance"""
|
|
188
|
+
return and_(
|
|
189
|
+
cast(Any, cls.deleted).is_(False),
|
|
190
|
+
cast(Any, cls.pdunit_id) == pdunit_id,
|
|
191
|
+
cast(Any, cls.pdord).has(PdOrd.pdunit_id == pdunit_id),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
@hybrid_method
|
|
195
|
+
def quality_eligible_def(self, pdunit_id: int, /) -> bool:
|
|
196
|
+
"""Python-level: check if entry is eligible
|
|
197
|
+
to be taken in account for quality"""
|
|
198
|
+
raise NotImplementedError("Sql expression level only")
|
|
199
|
+
|
|
200
|
+
@quality_eligible_def.expression
|
|
201
|
+
def quality_eligible(cls, pdunit_id: int, /) -> ColumnElement[bool]:
|
|
202
|
+
"""SQL-level: check if entry is eligible
|
|
203
|
+
to be taken in account for quality"""
|
|
204
|
+
return and_(
|
|
205
|
+
cast(Any, cls.deleted).is_(False), cast(Any, cls.pdunit_id) == pdunit_id
|
|
206
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from perfact.api.base import VisibilityAwareModel
|
|
4
|
+
from perfact.api.base.model import Base, Mapped
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PdUnit(Base, VisibilityAwareModel):
|
|
8
|
+
modtime: Mapped[datetime]
|
|
9
|
+
author: Mapped[str]
|
|
10
|
+
name: Mapped[str]
|
|
11
|
+
num: Mapped[str]
|
|
12
|
+
overprodmay: Mapped[bool]
|
|
13
|
+
overprodfactor: Mapped[float]
|
|
14
|
+
seqnum: Mapped[int]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from perfact.api.base import AuthInfo, VisibilityPolicy
|
|
2
|
+
from sqlalchemy import literal
|
|
3
|
+
from sqlalchemy.sql.elements import ColumnElement
|
|
4
|
+
|
|
5
|
+
from .pdunit import PdUnit
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PdUnitVisibilityPolicy(VisibilityPolicy):
|
|
9
|
+
"""PdUnit is open by default."""
|
|
10
|
+
|
|
11
|
+
model = PdUnit
|
|
12
|
+
|
|
13
|
+
def filter(self, auth: AuthInfo) -> ColumnElement[bool]:
|
|
14
|
+
return literal(True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register_policies(registry: object) -> None:
|
|
18
|
+
from perfact.api.base import VisibilityPolicyRegistry
|
|
19
|
+
|
|
20
|
+
if isinstance(registry, VisibilityPolicyRegistry):
|
|
21
|
+
registry.register(PdUnit, PdUnitVisibilityPolicy)
|
|
22
|
+
else:
|
|
23
|
+
raise RuntimeError("The Policy Registry was not initialized correctly.")
|
|
File without changes
|
perfact/api/pd/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: perfact-api-pd
|
|
3
|
+
Version: 0.2
|
|
4
|
+
Summary: PerFact API - pd domain models and services
|
|
5
|
+
Author-email: Viktor Dick <viktor.dick@perfact.de>
|
|
6
|
+
License-Expression: GPL-2.0-or-later
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: SQL
|
|
9
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: psycopg[binary]
|
|
13
|
+
Requires-Dist: sqlalchemy
|
|
14
|
+
Requires-Dist: perfact-api-base-model
|
|
15
|
+
Requires-Dist: perfact-api-app
|
|
16
|
+
Requires-Dist: perfact-api-pp
|
|
17
|
+
|
|
18
|
+
# PerFact API - pd - model
|
|
19
|
+
|
|
20
|
+
This repository contains the model related to the Pd-module ('Production Execution') of the PerFact software.
|
|
21
|
+
|
|
22
|
+
## getting started
|
|
23
|
+
As this repository does not contain an own main entrypoint, you have to install this package into the base and run that one.
|
|
24
|
+
Please referer the documentation of the *API Base* to get more informations about how the discovery works and how to include this module to your app bundle.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
perfact/api/pd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
perfact/api/pd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
perfact/api/pd/model/__init__.py,sha256=IokxoiLRN8YgL3zfXH8sUipq2mNpzsAUfAZsSq6fp0s,317
|
|
4
|
+
perfact/api/pd/model/pdord.py,sha256=hSSPWS50A6w9pKXioGJAkvtdkTfMalf5lX4c9d_sh3M,1721
|
|
5
|
+
perfact/api/pd/model/pdordlc.py,sha256=nYLM2XTp7uk3nbEWn7Jij1FNaBQrsL3trnzV4Tdt2R4,288
|
|
6
|
+
perfact/api/pd/model/pdordlch.py,sha256=Ei9AATHC3YR5g9nNUQAVX9rCuvLWeVolPOLR0WBv9IQ,7163
|
|
7
|
+
perfact/api/pd/model/pdordlct.py,sha256=8SNoSMwn4plHnlJM7miK3EONVZPZ9qrmtyG0ZGZ0PC4,3033
|
|
8
|
+
perfact/api/pd/model/pdordprd.py,sha256=GK9xx-4iLF7dym8-TJX-34LbfP7FKTzUv8HDI3ojCds,7703
|
|
9
|
+
perfact/api/pd/model/pdunit.py,sha256=AlKjZA495PRlJfzOBK5BB44FwRlD4F1FfTw7-BFIJHc,358
|
|
10
|
+
perfact/api/pd/model/policy.py,sha256=ymyVWlZwMkNMEET69sW_HQcunErnNFGl4mBzWaTf0Ys,677
|
|
11
|
+
perfact/api/pd/model/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
perfact_api_pd-0.2.dist-info/METADATA,sha256=_V5R78dgymKJN4XKMzPvUxgW5_H18QHm59i5rZWN4tg,993
|
|
13
|
+
perfact_api_pd-0.2.dist-info/WHEEL,sha256=TdQ5LtNwLuxTCjgxN51AgdU5w-KkB9ttmLbzjTH02pg,109
|
|
14
|
+
perfact_api_pd-0.2.dist-info/entry_points.txt,sha256=qG_YI5UUSP_r0rcHtVJQCDfUPnq3Z2IJdjxGq2JxdzI,89
|
|
15
|
+
perfact_api_pd-0.2.dist-info/top_level.txt,sha256=1odO3B1JiDF2Lqgnop8k7K4Xs1y_LdwehM53l1NDOnc,8
|
|
16
|
+
perfact_api_pd-0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
perfact
|