perfact-api-pd 1.2__tar.gz → 1.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 (63) hide show
  1. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/PKG-INFO +1 -1
  2. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/trend.py +91 -15
  3. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact_api_pd.egg-info/PKG-INFO +1 -1
  4. perfact_api_pd-1.4/src/perfact_api_pd.egg-info/scm_version.json +8 -0
  5. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_s_oee_trend.py +77 -31
  6. perfact_api_pd-1.2/src/perfact_api_pd.egg-info/scm_version.json +0 -8
  7. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/README.md +0 -0
  8. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/pyproject.toml +0 -0
  9. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/setup.cfg +0 -0
  10. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/__init__.py +0 -0
  11. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/__init__.py +0 -0
  12. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdbom.py +0 -0
  13. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdord.py +0 -0
  14. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordhd.py +0 -0
  15. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordhdlc.py +0 -0
  16. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordlc.py +0 -0
  17. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordlch.py +0 -0
  18. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordlct.py +0 -0
  19. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordprd.py +0 -0
  20. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordprdxtool.py +0 -0
  21. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdordtype.py +0 -0
  22. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdplant.py +0 -0
  23. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdtool.py +0 -0
  24. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdunit.py +0 -0
  25. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdunitgroup.py +0 -0
  26. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdunitlc.py +0 -0
  27. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdunitlch.py +0 -0
  28. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdunitlct.py +0 -0
  29. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/pdunitxitreport.py +0 -0
  30. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/policy.py +0 -0
  31. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/model/py.typed +0 -0
  32. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/py.typed +0 -0
  33. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/__init__.py +0 -0
  34. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/grafana.py +0 -0
  35. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/__init__.py +0 -0
  36. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/availability.py +0 -0
  37. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/breakdown.py +0 -0
  38. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/prod_metrics.py +0 -0
  39. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/prod_records.py +0 -0
  40. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/state_spans.py +0 -0
  41. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/oee/summary.py +0 -0
  42. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/pdunitdowntime.py +0 -0
  43. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/pdunitlch.py +0 -0
  44. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/pdunitperformance_orm.py +0 -0
  45. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact/api/pd/services/pdunitperformance_plainsql.py +0 -0
  46. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact_api_pd.egg-info/SOURCES.txt +0 -0
  47. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact_api_pd.egg-info/dependency_links.txt +0 -0
  48. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact_api_pd.egg-info/entry_points.txt +0 -0
  49. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact_api_pd.egg-info/requires.txt +0 -0
  50. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact_api_pd.egg-info/scm_file_list.json +0 -0
  51. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/src/perfact_api_pd.egg-info/top_level.txt +0 -0
  52. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/conftest.py +0 -0
  53. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_grafana.py +0 -0
  54. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_models.py +0 -0
  55. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_pdunitdowntime.py +0 -0
  56. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_policy.py +0 -0
  57. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_s_oee_availability.py +0 -0
  58. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_s_oee_breakdown.py +0 -0
  59. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_s_oee_prod_metrics.py +0 -0
  60. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_s_oee_summary.py +0 -0
  61. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_s_pdunitperformance.py +0 -0
  62. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tests/test_services_pdunitlch.py +0 -0
  63. {perfact_api_pd-1.2 → perfact_api_pd-1.4}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: perfact-api-pd
3
- Version: 1.2
3
+ Version: 1.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,11 +11,20 @@ from perfact.api.pd.services.oee.prod_metrics import (
11
11
  )
12
12
  from perfact.api.pd.services.oee.prod_records import get_prod_records
13
13
  from perfact.api.pd.services.oee.state_spans import get_state_spans
14
- from sqlalchemy import Date, func, literal_column, select, union_all
14
+ from sqlalchemy import Date, func, select, union_all
15
15
  from sqlalchemy.engine.row import Row
16
16
  from sqlalchemy.orm import InstrumentedAttribute, Session
17
17
 
18
18
 
19
+ def get_local_timezone():
20
+ """
21
+ Function to get local system timezone
22
+ In the future, to be replaced with a specific field in the database
23
+ Currently returning local timezone, or UTC as fallback
24
+ """
25
+ return datetime.now().astimezone().tzinfo or timezone.utc
26
+
27
+
19
28
  def get_dgkpi_id_by_plugin_name(session: Session, plugin: str) -> int:
20
29
  """
21
30
  Getter for the dgkpi id by requested plugin name
@@ -41,14 +50,31 @@ def parse_stored_modtime(value: object) -> datetime | None:
41
50
  return None
42
51
 
43
52
 
53
+ def compute_window_start(value: date, lookback_months: int) -> date:
54
+ year = value.year
55
+ month = value.month - lookback_months
56
+ while month <= 0:
57
+ month += 12
58
+ year -= 1
59
+
60
+ if month == 12:
61
+ next_month_start = date(year + 1, 1, 1)
62
+ else:
63
+ next_month_start = date(year, month + 1, 1)
64
+
65
+ last_day_of_month = (next_month_start - timedelta(days=1)).day
66
+ day = min(value.day, last_day_of_month)
67
+ return date(year, month, day)
68
+
69
+
44
70
  def get_oee_trend_candidate_days(session: Session, lookback_months: int = 6):
45
71
  """
46
72
  Distinct past (pdunit_id, day) candidates for OEE.
47
73
  Query involving entries from PdOrdPrd and PdOrdLch
48
74
  lookback_months is capped into [0, 6] and measured back from yesterday.
49
75
  """
50
- window_end = func.current_date() - 1
51
- window_start = window_end - (lookback_months * literal_column("interval '1 month'"))
76
+ window_end = datetime.now(get_local_timezone()).date()
77
+ window_start = compute_window_start(window_end, lookback_months)
52
78
 
53
79
  pdordprd_start = (
54
80
  select(
@@ -116,6 +142,7 @@ def _upsert_latest_dgval(
116
142
  identstring: str,
117
143
  reftime: datetime,
118
144
  value: float | None,
145
+ current_session_time: datetime | None = None,
119
146
  existing: DgVal | None = None,
120
147
  ) -> DgVal:
121
148
  """
@@ -129,6 +156,9 @@ def _upsert_latest_dgval(
129
156
  ).scalar_one_or_none()
130
157
  if existing is not None:
131
158
  existing.reftime = reftime
159
+ if current_session_time:
160
+ # specifically modify modtime if requested
161
+ existing.modtime = current_session_time
132
162
  existing.value = value
133
163
  stats.updated += 1
134
164
  return existing
@@ -172,6 +202,18 @@ def compute_trend_for_candidates(
172
202
  skipped = 0
173
203
  impacted_days: dict[str, set[int]] = defaultdict(set)
174
204
 
205
+ local_tz = get_local_timezone()
206
+ tz_name = datetime.now(local_tz).tzname()
207
+ current_transaction_time = session.scalar(
208
+ select(func.timezone(tz_name, func.now()))
209
+ )
210
+ if not current_transaction_time:
211
+ raise ValueError(
212
+ "Could not communicate with db to get \
213
+ current transaction time"
214
+ )
215
+ current_transaction_day = current_transaction_time.date().isoformat()
216
+
175
217
  for pdunit_id_c, day_value in candidates:
176
218
  pdunit_id = int(pdunit_id_c) if pdunit_id_c is not None else None
177
219
  if pdunit_id is None or not isinstance(day_value, date):
@@ -183,7 +225,7 @@ def compute_trend_for_candidates(
183
225
  skipped += 1
184
226
  continue
185
227
 
186
- day_start = datetime.combine(day_value, time.min, tzinfo=timezone.utc)
228
+ day_start = datetime.combine(day_value, time.min, tzinfo=local_tz)
187
229
  day_stop = day_start + timedelta(days=1)
188
230
  day_mid = day_start + timedelta(hours=12)
189
231
  identstring = day_value.isoformat()
@@ -200,18 +242,50 @@ def compute_trend_for_candidates(
200
242
  identstring,
201
243
  )
202
244
  ).scalar_one_or_none()
203
- existing_data = existing_oee.data if existing_oee else None
204
- stored_max_modtime = (
205
- parse_stored_modtime((existing_data or {}).get("max_pdordprd_modtime"))
206
- if isinstance(existing_data, dict)
207
- else None
208
- )
209
- needs_refresh = existing_oee is None or (
210
- max_pdordprd_modtime is not None
211
- and (
245
+
246
+ if existing_oee:
247
+ # check if dgval entry needs update
248
+ # first check if the stored modtime is older than the candidate modtime
249
+ # get the stored modtime from the dgval.data field
250
+ stored_max_modtime = (
251
+ parse_stored_modtime(
252
+ (existing_oee.data or {}).get("max_pdordprd_modtime")
253
+ )
254
+ if isinstance(existing_oee.data, dict)
255
+ else None
256
+ )
257
+ # check if the new_max_pdordprd candidate modtime
258
+ # is higher than the stored one
259
+ is_new_max_modtime = max_pdordprd_modtime is not None and (
212
260
  stored_max_modtime is None or stored_max_modtime < max_pdordprd_modtime
213
261
  )
214
- )
262
+
263
+ # check if the existing entry has the same day as the current day
264
+ is_current_day = existing_oee.identstring == current_transaction_day
265
+
266
+ # next, check if the next day in transaction time
267
+ # is syncronized with the data
268
+ # get the start of the next day based on the identstring
269
+ current_identstring_day = date.fromisoformat(existing_oee.identstring)
270
+ next_day_start = datetime.combine(
271
+ current_identstring_day + timedelta(days=1),
272
+ time.min,
273
+ tzinfo=local_tz,
274
+ )
275
+ # check if the day has already started, but the data is not syncronized yet
276
+ # by checking the identstring against the current_transaction_day
277
+ # and the modtime against the next_day_start
278
+ is_day_later = (
279
+ existing_oee.identstring < current_transaction_day
280
+ and existing_oee.modtime < next_day_start
281
+ )
282
+ # compute the necessity of updating the existing dgval entry
283
+ # based on criteria
284
+ needs_refresh = is_new_max_modtime or is_current_day or is_day_later
285
+ else:
286
+ # needs creation
287
+ needs_refresh = True
288
+
215
289
  if not needs_refresh:
216
290
  skipped += 1
217
291
  continue
@@ -246,6 +320,7 @@ def compute_trend_for_candidates(
246
320
  identstring=identstring,
247
321
  reftime=day_mid,
248
322
  value=oee,
323
+ current_session_time=current_transaction_time,
249
324
  existing=existing_oee,
250
325
  ).data = oee_data
251
326
 
@@ -358,11 +433,12 @@ def compute_trend_for_collections(session: Session, impacted_days: dict[str, set
358
433
  pdunitgroup_updated = 0
359
434
  pdplant_created = 0
360
435
  pdplant_updated = 0
436
+ local_tz = get_local_timezone()
361
437
 
362
438
  # compute for pdunitgroup and pdplant
363
439
  for identstring, changed_pdunits in impacted_days.items():
364
440
  day_value = date.fromisoformat(identstring)
365
- day_mid = datetime.combine(day_value, time(hour=12), tzinfo=timezone.utc)
441
+ day_mid = datetime.combine(day_value, time(hour=12), tzinfo=local_tz)
366
442
 
367
443
  memberships = session.execute(
368
444
  select(PdUnit.id, PdUnit.pdunitgroup_id, PdUnit.pdplant_id).where(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: perfact-api-pd
3
- Version: 1.2
3
+ Version: 1.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
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.4",
3
+ "distance": 0,
4
+ "node": "g8c9d4d7ac004f19cc8b564c3a6de4fe62487385e",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-03"
8
+ }
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Iterator
2
- from datetime import date, datetime, timedelta, timezone
2
+ from datetime import date, datetime, timedelta
3
3
  from typing import Any, cast
4
4
 
5
5
  import psycopg
@@ -16,6 +16,8 @@ from sqlalchemy.orm import Session
16
16
  postgresql_proc = factories.postgresql_proc()
17
17
  postgresql = factories.postgresql("postgresql_proc")
18
18
 
19
+ local_timezone = trend.get_local_timezone()
20
+
19
21
 
20
22
  @pytest.fixture
21
23
  def db_session(postgresql: psycopg.Connection[Any]) -> Iterator[Session]:
@@ -152,7 +154,7 @@ def test_get_dgkpi_id_by_plugin_name_returns_int(db_session: Session) -> None:
152
154
  "INSERT INTO dgkpi (dgkpi_id, dgkpi_plugin, dgkpi_lastcheck) \
153
155
  VALUES (42, 'OEE', :now)"
154
156
  ),
155
- {"now": datetime.now(timezone.utc)},
157
+ {"now": datetime.now(local_timezone)},
156
158
  )
157
159
  db_session.commit()
158
160
 
@@ -172,8 +174,9 @@ def test_get_dgkpi_id_by_plugin_name_raises_when_missing(db_session: Session) ->
172
174
  (123, None),
173
175
  ("not-a-date", None),
174
176
  (
175
- "2025-01-01T12:30:00+00:00",
176
- datetime(2025, 1, 1, 12, 30, tzinfo=timezone.utc),
177
+ # "2025-01-01T12:30:00+00:00",
178
+ f"{datetime(2025, 1, 1, 12, 30, tzinfo=local_timezone).isoformat()}",
179
+ datetime(2025, 1, 1, 12, 30, tzinfo=local_timezone),
177
180
  ),
178
181
  ],
179
182
  )
@@ -181,10 +184,51 @@ def test_parse_stored_modtime(value: object, expected: datetime | None) -> None:
181
184
  assert trend.parse_stored_modtime(value) == expected
182
185
 
183
186
 
187
+ @pytest.mark.parametrize(
188
+ ("value", "lookback_months", "expected"),
189
+ [
190
+ (date(2025, 1, 15), 1, date(2024, 12, 15)),
191
+ (date(2025, 1, 15), 13, date(2023, 12, 15)),
192
+ (date(2025, 6, 15), 0, date(2025, 6, 15)),
193
+ (date(2025, 5, 31), 1, date(2025, 4, 30)),
194
+ (date(2025, 3, 31), 1, date(2025, 2, 28)),
195
+ (date(2024, 3, 31), 1, date(2024, 2, 29)),
196
+ (date(2025, 12, 1), 1, date(2025, 11, 1)),
197
+ ],
198
+ )
199
+ def test_compute_window_start(
200
+ value: date, lookback_months: int, expected: date
201
+ ) -> None:
202
+ assert trend.compute_window_start(value, lookback_months) == expected
203
+
204
+
205
+ def test_compute_trend_for_candidates_raises_when_current_transaction_time_missing(
206
+ db_session: Session,
207
+ monkeypatch: pytest.MonkeyPatch,
208
+ ) -> None:
209
+ db_session.add_all(
210
+ [
211
+ DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(local_timezone)),
212
+ DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(local_timezone)),
213
+ DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(local_timezone)),
214
+ DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(local_timezone)),
215
+ ]
216
+ )
217
+ db_session.commit()
218
+
219
+ monkeypatch.setattr(db_session, "scalar", lambda *_args, **_kwargs: None)
220
+
221
+ with pytest.raises(
222
+ ValueError,
223
+ match="Could not communicate with db to get .*current transaction time",
224
+ ):
225
+ trend.compute_trend_for_candidates(db_session, [])
226
+
227
+
184
228
  def test_get_oee_trend_candidate_days_returns_rows_from_query(
185
229
  db_session: Session,
186
230
  ) -> None:
187
- now = datetime.now(timezone.utc)
231
+ now = datetime.now(local_timezone)
188
232
  db_session.execute(text("INSERT INTO pdunit (pdunit_id) VALUES (1), (2)"))
189
233
  db_session.execute(
190
234
  text(
@@ -217,16 +261,16 @@ def test_compute_trend_for_candidates_creates_new_dgval(
217
261
  monkeypatch: pytest.MonkeyPatch,
218
262
  ) -> None:
219
263
  day = date(2025, 1, 15)
220
- day_start = datetime(2025, 1, 15, 0, 0, tzinfo=timezone.utc)
264
+ day_start = datetime(2025, 1, 15, 0, 0, tzinfo=local_timezone)
221
265
  day_stop = day_start + timedelta(days=1)
222
- max_mod = datetime(2025, 1, 15, 9, 0, tzinfo=timezone.utc)
266
+ max_mod = datetime(2025, 1, 15, 9, 0, tzinfo=local_timezone)
223
267
 
224
268
  db_session.add_all(
225
269
  [
226
- DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(timezone.utc)),
227
- DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(timezone.utc)),
228
- DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(timezone.utc)),
229
- DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(timezone.utc)),
270
+ DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(local_timezone)),
271
+ DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(local_timezone)),
272
+ DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(local_timezone)),
273
+ DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(local_timezone)),
230
274
  ]
231
275
  )
232
276
  db_session.execute(
@@ -295,7 +339,7 @@ def test_compute_trend_for_candidates_creates_new_dgval(
295
339
  assert oee_row.value is None
296
340
  assert (
297
341
  datetime.fromisoformat(oee_row.data["max_pdordprd_modtime"]).astimezone(
298
- timezone.utc
342
+ local_timezone
299
343
  )
300
344
  == max_mod
301
345
  )
@@ -324,16 +368,16 @@ def test_compute_trend_for_candidates_updates_existing_dgval(
324
368
  monkeypatch: pytest.MonkeyPatch,
325
369
  ) -> None:
326
370
  day = date(2025, 1, 16)
327
- day_start = datetime(2025, 1, 16, 0, 0, tzinfo=timezone.utc)
371
+ day_start = datetime(2025, 1, 16, 0, 0, tzinfo=local_timezone)
328
372
  day_stop = day_start + timedelta(days=1)
329
- new_max_mod = datetime(2025, 1, 16, 10, 0, tzinfo=timezone.utc)
373
+ new_max_mod = datetime(2025, 1, 16, 10, 0, tzinfo=local_timezone)
330
374
 
331
375
  db_session.add_all(
332
376
  [
333
- DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(timezone.utc)),
334
- DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(timezone.utc)),
335
- DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(timezone.utc)),
336
- DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(timezone.utc)),
377
+ DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(local_timezone)),
378
+ DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(local_timezone)),
379
+ DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(local_timezone)),
380
+ DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(local_timezone)),
337
381
  ]
338
382
  )
339
383
  db_session.execute(
@@ -357,6 +401,8 @@ def test_compute_trend_for_candidates_updates_existing_dgval(
357
401
  ),
358
402
  {"mod": new_max_mod, "start": day_start, "stop": day_stop},
359
403
  )
404
+
405
+ max_prd_modtime = datetime(2025, 1, 16, 9, 30, tzinfo=local_timezone).isoformat()
360
406
  db_session.add(
361
407
  DgVal(
362
408
  dgkpi_id=20,
@@ -365,7 +411,7 @@ def test_compute_trend_for_candidates_updates_existing_dgval(
365
411
  identstring="2025-01-16",
366
412
  reftime=day_start,
367
413
  value=0.1,
368
- data={"max_pdordprd_modtime": "2025-01-16T09:30:00+00:00"},
414
+ data={"max_pdordprd_modtime": f"{max_prd_modtime}"},
369
415
  )
370
416
  )
371
417
  db_session.commit()
@@ -408,14 +454,14 @@ def test_compute_trend_for_candidates_skips_invalid_and_not_needing_refresh(
408
454
  monkeypatch: pytest.MonkeyPatch,
409
455
  ) -> None:
410
456
  day = date(2025, 1, 17)
411
- day_start = datetime(2025, 1, 17, 0, 0, tzinfo=timezone.utc)
457
+ day_start = datetime(2025, 1, 17, 0, 0, tzinfo=local_timezone)
412
458
 
413
459
  db_session.add_all(
414
460
  [
415
- DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(timezone.utc)),
416
- DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(timezone.utc)),
417
- DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(timezone.utc)),
418
- DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(timezone.utc)),
461
+ DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(local_timezone)),
462
+ DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(local_timezone)),
463
+ DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(local_timezone)),
464
+ DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(local_timezone)),
419
465
  ]
420
466
  )
421
467
  db_session.execute(
@@ -428,7 +474,7 @@ def test_compute_trend_for_candidates_skips_invalid_and_not_needing_refresh(
428
474
  VALUES (4, :mod, FALSE, tstzrange(:start, :stop, '[)'), 4)"
429
475
  ),
430
476
  {
431
- "mod": datetime(2025, 1, 17, 10, 0, tzinfo=timezone.utc),
477
+ "mod": datetime(2025, 1, 17, 10, 0, tzinfo=local_timezone),
432
478
  "start": day_start,
433
479
  "stop": day_start + timedelta(days=1),
434
480
  },
@@ -478,7 +524,7 @@ def test_compute_trend_for_collection_returns_zero_when_no_impacted_ids(
478
524
  assert trend.compute_trend_for_collection(
479
525
  db_session,
480
526
  identstring="2025-01-20",
481
- day_mid=datetime(2025, 1, 20, 12, tzinfo=timezone.utc),
527
+ day_mid=datetime(2025, 1, 20, 12, tzinfo=local_timezone),
482
528
  collection_reftable="pdunitgroup",
483
529
  pdunit_collection_fk="pdunitgroup_id",
484
530
  impacted_collection_ids=set(),
@@ -487,14 +533,14 @@ def test_compute_trend_for_collection_returns_zero_when_no_impacted_ids(
487
533
 
488
534
  def test_compute_trend_for_collection_creates_and_updates(db_session: Session) -> None:
489
535
  identstring = "2025-01-20"
490
- day_mid = datetime(2025, 1, 20, 12, tzinfo=timezone.utc)
536
+ day_mid = datetime(2025, 1, 20, 12, tzinfo=local_timezone)
491
537
 
492
538
  db_session.add_all(
493
539
  [
494
- DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(timezone.utc)),
495
- DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(timezone.utc)),
496
- DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(timezone.utc)),
497
- DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(timezone.utc)),
540
+ DgKpi(id=20, plugin="OEE", lastcheck=datetime.now(local_timezone)),
541
+ DgKpi(id=21, plugin="OEE.A", lastcheck=datetime.now(local_timezone)),
542
+ DgKpi(id=22, plugin="OEE.P", lastcheck=datetime.now(local_timezone)),
543
+ DgKpi(id=23, plugin="OEE.Q", lastcheck=datetime.now(local_timezone)),
498
544
  ]
499
545
  )
500
546
  db_session.execute(
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.2",
3
- "distance": 0,
4
- "node": "g23f9e71ace0327b4a1e5f9488a191b8ffe17585a",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-06-26"
8
- }
File without changes
File without changes
File without changes