wizata-dsapi 2.2.0.dev29__tar.gz → 2.2.0.dev31__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.
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/__init__.py +1 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/connection.py +33 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/connection_types.py +13 -0
- wizata_dsapi-2.2.0.dev31/wizata_dsapi/inventory_availability.py +138 -0
- wizata_dsapi-2.2.0.dev31/wizata_dsapi/version.py +1 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/wizata_dsapi_client.py +33 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi.egg-info/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi.egg-info/SOURCES.txt +1 -0
- wizata_dsapi-2.2.0.dev29/wizata_dsapi/version.py +0 -1
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/LICENSE.txt +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/README.rst +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/setup.cfg +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/setup.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/api_config.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/api_dto.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/api_interface.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/bucket.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/business_label.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/connection_discovery.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/connection_policy.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/context.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/dashboard.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/dataframe_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/datapoint.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/datastore.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/deployment.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/ds_dataframe.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/dsapi_json_encoder.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/edge_config.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/edge_device.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/edge_module.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/edge_package.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/evaluation.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/execution.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/execution_log.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/experiment.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/formula.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/graylog_log.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/group_system.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/ilogger.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/insight.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/inventory.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/mlmodel.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/mobile_asset.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/model_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/models/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/models/common.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/notification.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/paged_query_result.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/pipeline.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/pipeline_image.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/planned_movement.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/plot.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/plots/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/plots/common.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/plots/theme.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/product.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/python_package.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/request.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/script.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/scripts/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/scripts/common.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/search.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/solution_component.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/streamlit_utils.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/template.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/template_config.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/trigger.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/twin.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/twinregistration.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/user.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/wizard_function.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/wizard_request.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi/words.py +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi.egg-info/dependency_links.txt +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi.egg-info/requires.txt +0 -0
- {wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi.egg-info/top_level.txt +0 -0
|
@@ -84,6 +84,7 @@ from .inventory import (InventoryBalance, InventoryStorage, InventoryStorageData
|
|
|
84
84
|
from .planned_movement import (PlannedMovement, PlannedMovementSet, PLANNED_MOVEMENT_TYPES,
|
|
85
85
|
PLANNED_MOVEMENT_SNAPSHOT_TYPES, PLANNED_MOVEMENT_DATED_TYPES,
|
|
86
86
|
planned_movement_direction, is_dated_planned_movement)
|
|
87
|
+
from .inventory_availability import InventoryAvailability, InventoryAvailabilityProduct, InventoryProjectionDay
|
|
87
88
|
from .dashboard import Dashboard, DashboardType
|
|
88
89
|
from .mobile_asset import MobileAssetTile, MobileAssetTileType, MobileAssetConfig
|
|
89
90
|
from .graylog_log import GraylogLog
|
|
@@ -79,6 +79,9 @@ class Connection:
|
|
|
79
79
|
# read-only summaries a search adds: [ConnectionChannel], without their rows
|
|
80
80
|
self.readers: Optional[list] = None
|
|
81
81
|
self.writers: Optional[list] = None
|
|
82
|
+
# read-only, set by ConnectionClient.mark_in_use (DEV-6165): whether its device runs
|
|
83
|
+
# it. False for an Edge store kept on a device that left Connectivity + Pipelines.
|
|
84
|
+
self.in_use: Optional[bool] = None
|
|
82
85
|
|
|
83
86
|
@property
|
|
84
87
|
def system(self) -> bool:
|
|
@@ -113,6 +116,8 @@ class Connection:
|
|
|
113
116
|
if self.edge_device_id is not None:
|
|
114
117
|
obj["edgeDeviceId"] = self.edge_device_id
|
|
115
118
|
obj["system"] = self.system
|
|
119
|
+
if self.in_use is not None:
|
|
120
|
+
obj["inUse"] = self.in_use
|
|
116
121
|
obj["config"] = dict(self.config or {})
|
|
117
122
|
if include_secrets:
|
|
118
123
|
obj["secrets"] = dict(self.secrets or {})
|
|
@@ -180,6 +185,10 @@ class ConnectionChannel:
|
|
|
180
185
|
- ``signals``: on create only, the policy rows to write with it (read back from search).
|
|
181
186
|
- ``signal_count`` / ``mapped_count``: read-only, how many rows and how many name a
|
|
182
187
|
datapoint.
|
|
188
|
+
- ``received`` / ``dropped`` / ``last_seen`` / ``in_sync``: read-only, a reader's summary
|
|
189
|
+
of its seen names (DEV-6163): names received, dropped under the stored policy, the
|
|
190
|
+
newest time any was seen, and whether the edge runs the stored policy (None until it
|
|
191
|
+
has reported).
|
|
183
192
|
"""
|
|
184
193
|
|
|
185
194
|
def __init__(self):
|
|
@@ -196,6 +205,10 @@ class ConnectionChannel:
|
|
|
196
205
|
self.signals: Optional[list] = None
|
|
197
206
|
self.signal_count: Optional[int] = None
|
|
198
207
|
self.mapped_count: Optional[int] = None
|
|
208
|
+
self.received: Optional[int] = None
|
|
209
|
+
self.dropped: Optional[int] = None
|
|
210
|
+
self.last_seen: Optional[datetime] = None
|
|
211
|
+
self.in_sync: Optional[bool] = None
|
|
199
212
|
self.created_at: Optional[datetime] = None
|
|
200
213
|
self.updated_at: Optional[datetime] = None
|
|
201
214
|
|
|
@@ -226,6 +239,14 @@ class ConnectionChannel:
|
|
|
226
239
|
obj["signalCount"] = self.signal_count
|
|
227
240
|
if self.mapped_count is not None:
|
|
228
241
|
obj["mappedCount"] = self.mapped_count
|
|
242
|
+
if self.received is not None:
|
|
243
|
+
obj["received"] = self.received
|
|
244
|
+
if self.dropped is not None:
|
|
245
|
+
obj["dropped"] = self.dropped
|
|
246
|
+
if self.last_seen is not None:
|
|
247
|
+
obj["lastSeen"] = _ts_out(self.last_seen)
|
|
248
|
+
if self.in_sync is not None:
|
|
249
|
+
obj["inSync"] = self.in_sync
|
|
229
250
|
if self.created_at is not None:
|
|
230
251
|
obj["createdAt"] = _ts_out(self.created_at)
|
|
231
252
|
if self.updated_at is not None:
|
|
@@ -291,6 +312,9 @@ class ConnectionSignal:
|
|
|
291
312
|
node, the name written). In a ``list`` policy a row with no ``tag`` lets the
|
|
292
313
|
datapoint through.
|
|
293
314
|
- A row with both always renames, whatever the mode.
|
|
315
|
+
- ``datapoint`` / ``status`` / ``last_received``: read-only, what a search with
|
|
316
|
+
``include`` adds (DEV-6164): the datapoint the row produces, what becomes of it under
|
|
317
|
+
the device's cloud and edge writers, and when a reader last received its name.
|
|
294
318
|
"""
|
|
295
319
|
|
|
296
320
|
def __init__(self, tag: str = None, hardware_id: str = None):
|
|
@@ -300,6 +324,9 @@ class ConnectionSignal:
|
|
|
300
324
|
self.hardware_id: Optional[str] = hardware_id
|
|
301
325
|
self.created_at: Optional[datetime] = None
|
|
302
326
|
self.updated_at: Optional[datetime] = None
|
|
327
|
+
self.datapoint: Optional[str] = None
|
|
328
|
+
self.status: Optional[str] = None
|
|
329
|
+
self.last_received: Optional[datetime] = None
|
|
303
330
|
|
|
304
331
|
def to_dict(self) -> dict:
|
|
305
332
|
obj = {}
|
|
@@ -309,6 +336,12 @@ class ConnectionSignal:
|
|
|
309
336
|
obj["channelId"] = str(self.channel_id)
|
|
310
337
|
obj["tag"] = self.tag
|
|
311
338
|
obj["hardwareId"] = self.hardware_id
|
|
339
|
+
if self.datapoint is not None:
|
|
340
|
+
obj["datapoint"] = self.datapoint
|
|
341
|
+
if self.status is not None:
|
|
342
|
+
obj["status"] = self.status
|
|
343
|
+
if self.last_received is not None:
|
|
344
|
+
obj["lastReceived"] = _ts_out(self.last_received)
|
|
312
345
|
if self.created_at is not None:
|
|
313
346
|
obj["createdAt"] = _ts_out(self.created_at)
|
|
314
347
|
if self.updated_at is not None:
|
|
@@ -126,6 +126,19 @@ class ConnectionType:
|
|
|
126
126
|
def directions(self) -> list:
|
|
127
127
|
return ([_READ] if self.reader else []) + ([_WRITE] if self.writer else [])
|
|
128
128
|
|
|
129
|
+
def exists_on(self, device_type) -> bool:
|
|
130
|
+
"""
|
|
131
|
+
Whether a device of ``device_type`` runs a connection of this type (DEV-6165). A type
|
|
132
|
+
that lists ``device_types`` exists on those only: the Edge store is kept on a device
|
|
133
|
+
that leaves Connectivity + Pipelines, but not rendered. The render and the payload's
|
|
134
|
+
``inUse`` both read this.
|
|
135
|
+
|
|
136
|
+
:param device_type: an ``EdgeDeviceType`` or its value; None (unknown) counts as every type.
|
|
137
|
+
"""
|
|
138
|
+
if not self.device_types or device_type is None:
|
|
139
|
+
return True
|
|
140
|
+
return getattr(device_type, "value", device_type) in self.device_types
|
|
141
|
+
|
|
129
142
|
def channel(self, direction) -> Optional[ChannelType]:
|
|
130
143
|
direction = getattr(direction, "value", direction)
|
|
131
144
|
return self.reader if direction == _READ else self.writer if direction == _WRITE else None
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Production Planner availability (DEV-6137, epic DEV-6044, release 12.2).
|
|
3
|
+
|
|
4
|
+
What a facility can still promise, per product, and how its stock is projected over the
|
|
5
|
+
next days. Computed on read from the stock (balances + movements) and the planned
|
|
6
|
+
movements (DEV-6136); nothing here is stored.
|
|
7
|
+
|
|
8
|
+
- ``total``: stock under the facility, read at the moment of the last availability
|
|
9
|
+
snapshot written (``snapshot_as_of``), so the figures below hold at one moment and ATP
|
|
10
|
+
matches the source's.
|
|
11
|
+
- ``allocated``, ``hold``, ``open_demand``: the snapshot's figures (``allocated``,
|
|
12
|
+
``hold``, ``openDemand`` planned movements). Without a snapshot they come from the open
|
|
13
|
+
customer order lines (picked not shipped, ordered not picked) and ``from_snapshot`` is
|
|
14
|
+
False.
|
|
15
|
+
- ``available`` = total - allocated - hold; ``atp`` = available - open demand.
|
|
16
|
+
- ``dated``: the unpicked part of the open customer order lines, capped at the open
|
|
17
|
+
demand (lines are read more often than the snapshot); ``undated`` = open demand - dated,
|
|
18
|
+
due at once.
|
|
19
|
+
- ``projection``: one entry per day of the horizon, the level at the end of the day =
|
|
20
|
+
available - undated - dated due by then + supply (open work and purchase orders) due by
|
|
21
|
+
then. ``net_requirement`` is the lowest level when it is below zero.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from datetime import date, datetime
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
from .inventory import _ts_in, _ts_out, _uuid_str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _date_in(value):
|
|
31
|
+
if value is None or isinstance(value, date):
|
|
32
|
+
return value
|
|
33
|
+
return date.fromisoformat(str(value)[:10])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class InventoryProjectionDay:
|
|
37
|
+
"""The projected level at the end of ``day``, with what comes in and goes out that day."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, day=None, incoming: float = None, outgoing: float = None, level: float = None):
|
|
40
|
+
self.day: Optional[date] = _date_in(day)
|
|
41
|
+
self.incoming = incoming
|
|
42
|
+
self.outgoing = outgoing
|
|
43
|
+
self.level = level
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict:
|
|
46
|
+
return {"date": self.day.isoformat() if self.day else None, "incoming": self.incoming,
|
|
47
|
+
"outgoing": self.outgoing, "level": self.level}
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, data: dict) -> "InventoryProjectionDay":
|
|
51
|
+
return cls(day=data.get("date"), incoming=data.get("incoming"), outgoing=data.get("outgoing"),
|
|
52
|
+
level=data.get("level"))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class InventoryAvailabilityProduct:
|
|
56
|
+
"""One product's availability at a facility, in ``unit`` (its stock total's unit)."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, key: str = None, name: str = None, unit=None, unit_symbol: str = None):
|
|
59
|
+
self.key = key
|
|
60
|
+
self.name = name
|
|
61
|
+
self.unit: Optional[str] = _uuid_str(unit)
|
|
62
|
+
self.unit_symbol = unit_symbol
|
|
63
|
+
self.total: Optional[float] = None
|
|
64
|
+
self.allocated: Optional[float] = None
|
|
65
|
+
self.hold: Optional[float] = None
|
|
66
|
+
self.open_demand: Optional[float] = None
|
|
67
|
+
self.available: Optional[float] = None
|
|
68
|
+
self.atp: Optional[float] = None
|
|
69
|
+
self.dated: Optional[float] = None
|
|
70
|
+
self.undated: Optional[float] = None
|
|
71
|
+
self.demand_explained: Optional[float] = None
|
|
72
|
+
self.allocation_explained: Optional[float] = None
|
|
73
|
+
self.supply: Optional[float] = None
|
|
74
|
+
self.net_requirement: Optional[float] = None
|
|
75
|
+
self.lowest: Optional[InventoryProjectionDay] = None
|
|
76
|
+
self.projection: list = []
|
|
77
|
+
self.from_snapshot: bool = False
|
|
78
|
+
|
|
79
|
+
_NUMBERS = (("total", "total"), ("allocated", "allocated"), ("hold", "hold"), ("openDemand", "open_demand"),
|
|
80
|
+
("available", "available"), ("atp", "atp"), ("dated", "dated"), ("undated", "undated"),
|
|
81
|
+
("demandExplained", "demand_explained"), ("allocationExplained", "allocation_explained"),
|
|
82
|
+
("supply", "supply"), ("netRequirement", "net_requirement"))
|
|
83
|
+
|
|
84
|
+
def to_dict(self) -> dict:
|
|
85
|
+
obj = {"product": self.key, "name": self.name, "unit": self.unit, "unitSymbol": self.unit_symbol}
|
|
86
|
+
for json_name, attr in self._NUMBERS:
|
|
87
|
+
obj[json_name] = getattr(self, attr)
|
|
88
|
+
obj["fromSnapshot"] = self.from_snapshot
|
|
89
|
+
obj["lowest"] = self.lowest.to_dict() if self.lowest is not None else None
|
|
90
|
+
obj["projection"] = [day.to_dict() for day in self.projection]
|
|
91
|
+
return obj
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def from_dict(cls, data: dict) -> "InventoryAvailabilityProduct":
|
|
95
|
+
obj = cls(key=data.get("product"), name=data.get("name"), unit=data.get("unit"),
|
|
96
|
+
unit_symbol=data.get("unitSymbol"))
|
|
97
|
+
for json_name, attr in cls._NUMBERS:
|
|
98
|
+
setattr(obj, attr, data.get(json_name))
|
|
99
|
+
obj.from_snapshot = bool(data.get("fromSnapshot"))
|
|
100
|
+
obj.lowest = InventoryProjectionDay.from_dict(data["lowest"]) if data.get("lowest") else None
|
|
101
|
+
obj.projection = [InventoryProjectionDay.from_dict(d) for d in data.get("projection") or []]
|
|
102
|
+
return obj
|
|
103
|
+
|
|
104
|
+
def __repr__(self) -> str:
|
|
105
|
+
return (f"InventoryAvailabilityProduct({self.key!r}, total={self.total}, atp={self.atp}, "
|
|
106
|
+
f"net_requirement={self.net_requirement})")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class InventoryAvailability:
|
|
110
|
+
"""
|
|
111
|
+
Availability of a facility's products at ``as_of`` (DEV-6137). ``snapshot_as_of`` is the
|
|
112
|
+
moment of the last availability snapshot written at or before it (None without one):
|
|
113
|
+
the stock and the snapshot's figures are read there.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, facility: str = None, as_of=None, snapshot_as_of=None, horizon_days: int = None):
|
|
117
|
+
self.facility = facility
|
|
118
|
+
self.as_of: Optional[datetime] = _ts_in(as_of)
|
|
119
|
+
self.snapshot_as_of: Optional[datetime] = _ts_in(snapshot_as_of)
|
|
120
|
+
self.horizon_days = horizon_days
|
|
121
|
+
self.products: list = []
|
|
122
|
+
|
|
123
|
+
def product(self, key: str) -> Optional[InventoryAvailabilityProduct]:
|
|
124
|
+
return next((p for p in self.products if p.key == key), None)
|
|
125
|
+
|
|
126
|
+
def to_dict(self) -> dict:
|
|
127
|
+
return {"facility": self.facility,
|
|
128
|
+
"asOf": _ts_out(self.as_of) if isinstance(self.as_of, datetime) else self.as_of,
|
|
129
|
+
"snapshotAsOf": _ts_out(self.snapshot_as_of) if isinstance(self.snapshot_as_of, datetime)
|
|
130
|
+
else self.snapshot_as_of,
|
|
131
|
+
"horizonDays": self.horizon_days, "products": [p.to_dict() for p in self.products]}
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_dict(cls, data: dict) -> "InventoryAvailability":
|
|
135
|
+
obj = cls(facility=data.get("facility"), as_of=data.get("asOf"), snapshot_as_of=data.get("snapshotAsOf"),
|
|
136
|
+
horizon_days=data.get("horizonDays"))
|
|
137
|
+
obj.products = [InventoryAvailabilityProduct.from_dict(p) for p in data.get("products") or []]
|
|
138
|
+
return obj
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0.dev31"
|
|
@@ -58,6 +58,7 @@ import ast
|
|
|
58
58
|
from .deployment import Deployment, DeploymentStatus
|
|
59
59
|
from .product import ProductKind, Product, ProductComponent
|
|
60
60
|
from .inventory import InventoryBalance, InventoryStorage, InventoryStock, InventoryCount, InventoryHistory
|
|
61
|
+
from .inventory_availability import InventoryAvailability
|
|
61
62
|
from .planned_movement import PlannedMovement, PlannedMovementSet
|
|
62
63
|
from .connection import EdgeDeviceKey
|
|
63
64
|
from .edge_config import EdgeConfig
|
|
@@ -3233,6 +3234,38 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
3233
3234
|
expected=(200, 404))
|
|
3234
3235
|
return response.json().get("deleted", 0) if response.status_code == 200 else 0
|
|
3235
3236
|
|
|
3237
|
+
def get_inventory_availability(self, facility: str, as_of=None, products: list = None,
|
|
3238
|
+
horizon_days: int = None, sources: list = None) -> InventoryAvailability:
|
|
3239
|
+
"""
|
|
3240
|
+
What a facility can still promise per product, and its stock projected per day
|
|
3241
|
+
(DEV-6137). The stock under the facility twin and its children and the figures of the
|
|
3242
|
+
last availability snapshot (``allocated``, ``hold``, ``openDemand`` planned movements)
|
|
3243
|
+
are read at that snapshot's moment (``snapshot_as_of``): available = total - allocated
|
|
3244
|
+
- hold, ATP = available - open demand. The open customer order lines date the demand
|
|
3245
|
+
(capped at the open demand; the rest is due at once) and the open work and purchase
|
|
3246
|
+
orders come in when due. Without a snapshot, the lines give allocated and open demand.
|
|
3247
|
+
|
|
3248
|
+
:param facility: hardware id of the facility Twin.
|
|
3249
|
+
:param as_of: datetime, ISO 8601, epoch ms or a relative ``now-1d``; default now.
|
|
3250
|
+
:param products: product keys to keep.
|
|
3251
|
+
:param horizon_days: days projected (UTC days, today first); default 14, at most 120.
|
|
3252
|
+
:param sources: keep the planned movements of those sources.
|
|
3253
|
+
"""
|
|
3254
|
+
body = {"facility": facility}
|
|
3255
|
+
if as_of is not None:
|
|
3256
|
+
if isinstance(as_of, datetime.datetime):
|
|
3257
|
+
if as_of.tzinfo is None:
|
|
3258
|
+
as_of = as_of.replace(tzinfo=datetime.timezone.utc)
|
|
3259
|
+
as_of = as_of.isoformat()
|
|
3260
|
+
body["asOf"] = as_of
|
|
3261
|
+
for name, value in (("products", products), ("sources", sources)):
|
|
3262
|
+
if value is not None:
|
|
3263
|
+
body[name] = list(value)
|
|
3264
|
+
if horizon_days is not None:
|
|
3265
|
+
body["horizonDays"] = horizon_days
|
|
3266
|
+
response = self._planner_request("POST", "planner/inventory/availability", body=body)
|
|
3267
|
+
return InventoryAvailability.from_dict(response.json())
|
|
3268
|
+
|
|
3236
3269
|
# ------------------------------------------------------------ edge (DEV-6013/6014)
|
|
3237
3270
|
|
|
3238
3271
|
@staticmethod
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.2.0.dev29"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wizata_dsapi-2.2.0.dev29 → wizata_dsapi-2.2.0.dev31}/wizata_dsapi.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|