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.
Files changed (42) hide show
  1. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/PKG-INFO +6 -2
  2. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/pyproject.toml +8 -1
  3. perfact_api_pd-0.4/src/perfact/api/pd/model/__init__.py +30 -0
  4. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdord.py +2 -0
  5. perfact_api_pd-0.4/src/perfact/api/pd/model/pdordhd.py +32 -0
  6. perfact_api_pd-0.4/src/perfact/api/pd/model/pdordhdlc.py +11 -0
  7. perfact_api_pd-0.4/src/perfact/api/pd/model/pdordlc.py +27 -0
  8. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdordlch.py +7 -2
  9. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdordlct.py +18 -0
  10. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/pdordprd.py +130 -7
  11. perfact_api_pd-0.4/src/perfact/api/pd/model/pdordtype.py +9 -0
  12. perfact_api_pd-0.4/src/perfact/api/pd/model/pdunit.py +69 -0
  13. perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitgroup.py +25 -0
  14. perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitlc.py +10 -0
  15. perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitlch.py +24 -0
  16. perfact_api_pd-0.4/src/perfact/api/pd/model/pdunitlct.py +25 -0
  17. perfact_api_pd-0.4/src/perfact/api/pd/services/__init__.py +16 -0
  18. perfact_api_pd-0.4/src/perfact/api/pd/services/pdunitlch.py +125 -0
  19. perfact_api_pd-0.4/src/perfact/api/pd/services/pdunitperformance_orm.py +425 -0
  20. perfact_api_pd-0.4/src/perfact/api/pd/services/pdunitperformance_plainsql.py +191 -0
  21. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/PKG-INFO +6 -2
  22. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/SOURCES.txt +17 -1
  23. perfact_api_pd-0.4/src/perfact_api_pd.egg-info/requires.txt +10 -0
  24. perfact_api_pd-0.4/tests/conftest.py +59 -0
  25. perfact_api_pd-0.4/tests/test_models.py +397 -0
  26. perfact_api_pd-0.4/tests/test_policy.py +34 -0
  27. perfact_api_pd-0.4/tests/test_s_pdunitperformance.py +484 -0
  28. perfact_api_pd-0.4/tests/test_services_pdunitlch.py +256 -0
  29. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/tox.ini +2 -2
  30. perfact_api_pd-0.2/src/perfact/api/pd/model/__init__.py +0 -16
  31. perfact_api_pd-0.2/src/perfact/api/pd/model/pdordlc.py +0 -8
  32. perfact_api_pd-0.2/src/perfact/api/pd/model/pdunit.py +0 -14
  33. perfact_api_pd-0.2/src/perfact_api_pd.egg-info/requires.txt +0 -5
  34. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/README.md +0 -0
  35. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/setup.cfg +0 -0
  36. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/__init__.py +0 -0
  37. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/policy.py +0 -0
  38. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/model/py.typed +0 -0
  39. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact/api/pd/py.typed +0 -0
  40. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/dependency_links.txt +0 -0
  41. {perfact_api_pd-0.2 → perfact_api_pd-0.4}/src/perfact_api_pd.egg-info/entry_points.txt +0 -0
  42. {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.2
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-model
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-model",
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(self, pdunit_id, /) -> bool:
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(self, pdunit_id, /) -> bool:
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(self, pdunit_id: int, /) -> bool:
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(cls, pdunit_id: int, /) -> ColumnElement[bool]:
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(self, pdunit_id: int, /) -> bool:
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(cls, pdunit_id: int, /) -> ColumnElement[bool]:
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), cast(Any, cls.pdunit_id) == pdunit_id
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
+ ]