wizata-dsapi 2.2.0.dev5__tar.gz → 2.2.0.dev7__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.dev5 → wizata_dsapi-2.2.0.dev7}/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/__init__.py +3 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/datapoint.py +6 -0
- wizata_dsapi-2.2.0.dev7/wizata_dsapi/inventory.py +148 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/request.py +15 -1
- wizata_dsapi-2.2.0.dev7/wizata_dsapi/version.py +1 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/wizata_dsapi_client.py +59 -3
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi.egg-info/PKG-INFO +1 -1
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi.egg-info/SOURCES.txt +1 -0
- wizata_dsapi-2.2.0.dev5/wizata_dsapi/version.py +0 -1
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/LICENSE.txt +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/README.rst +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/setup.cfg +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/setup.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/api_config.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/api_dto.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/api_interface.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/bucket.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/business_label.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/connection.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/connection_types.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/context.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/dashboard.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/dataframe_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/datastore.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/deployment.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/ds_dataframe.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/dsapi_json_encoder.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/edge_config.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/edge_device.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/edge_module.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/evaluation.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/execution.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/execution_log.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/experiment.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/graylog_log.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/group_system.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/ilogger.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/insight.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/mlmodel.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/mobile_asset.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/model_toolkit.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/models/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/models/common.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/notification.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/paged_query_result.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/pipeline.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/pipeline_image.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/plot.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/plots/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/plots/common.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/plots/theme.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/product.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/script.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/scripts/__init__.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/scripts/common.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/search.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/solution_component.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/streamlit_utils.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/template.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/template_config.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/trigger.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/twin.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/twinregistration.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/user.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/wizard_function.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/wizard_request.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi/words.py +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi.egg-info/dependency_links.txt +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi.egg-info/requires.txt +0 -0
- {wizata_dsapi-2.2.0.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi.egg-info/top_level.txt +0 -0
|
@@ -66,6 +66,9 @@ from .connection import Connection, ConnectionMapping, ConnectionDirection, Tran
|
|
|
66
66
|
from .connection_types import ConnectionType, CONNECTION_TYPES, TRANSFORM_SCHEMAS, get_connection_type, \
|
|
67
67
|
list_connection_types
|
|
68
68
|
from .product import ProductKind, Product, ProductComponent, BUILTIN_PRODUCT_KINDS, is_valid_product_key
|
|
69
|
+
from .inventory import (InventoryBalance, inventory_mode, INVENTORY_OPTION, INVENTORY_MODES,
|
|
70
|
+
INVENTORY_MOVEMENTS, INVENTORY_BALANCES, INVENTORY_STOCK,
|
|
71
|
+
PRODUCT_TAG, PRODUCT_MESSAGE_KEY)
|
|
69
72
|
from .dashboard import Dashboard, DashboardType
|
|
70
73
|
from .mobile_asset import MobileAssetTile, MobileAssetTileType, MobileAssetConfig
|
|
71
74
|
from .graylog_log import GraylogLog
|
|
@@ -198,6 +198,11 @@ class BusinessType(Enum):
|
|
|
198
198
|
- "event" represents a state or probability that have a start and stop timestamp.
|
|
199
199
|
- "text" represents a time-series emitted as text/string value.
|
|
200
200
|
- "scoped" represents a time-series with values split by a scope tag (e.g. anomaly probabilities per category).
|
|
201
|
+
- "inventoryMovement" represents stock movements of a storage twin: each point is a signed quantity
|
|
202
|
+
(+ in, - out) of one product, named by its ``productKey`` tag (Production Planner).
|
|
203
|
+
|
|
204
|
+
The order is the backend's: the SQL selector sends a value's position as the stored
|
|
205
|
+
integer, so a new value is always appended last.
|
|
201
206
|
"""
|
|
202
207
|
TELEMETRY = "telemetry"
|
|
203
208
|
SET_POINTS = "setPoint"
|
|
@@ -206,6 +211,7 @@ class BusinessType(Enum):
|
|
|
206
211
|
EVENT = "event"
|
|
207
212
|
TEXT = "text"
|
|
208
213
|
SCOPED = "scoped"
|
|
214
|
+
INVENTORY_MOVEMENT = "inventoryMovement"
|
|
209
215
|
|
|
210
216
|
|
|
211
217
|
class InputModeType(Enum):
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Production Planner inventory (epic DEV-6044, release 12.2).
|
|
3
|
+
|
|
4
|
+
Two facts, and stock is derived from both:
|
|
5
|
+
|
|
6
|
+
- **Movements** are points on a DataPoint of business type ``inventoryMovement``, one
|
|
7
|
+
per storage Twin and category. Each point is a signed quantity (+ in, - out) in the
|
|
8
|
+
DataPoint's unit, and its ``productKey`` tag names the product.
|
|
9
|
+
- **Balances** (:class:`InventoryBalance`) are absolute levels counted at a moment
|
|
10
|
+
("460 L of coke in the tank at 16:00"), stored in PostgreSQL on the same DataPoint.
|
|
11
|
+
|
|
12
|
+
A query chooses what it reads with ``options={"inventory": ...}``:
|
|
13
|
+
|
|
14
|
+
- ``movements`` (the default): the DataPoint's own points, one series per product.
|
|
15
|
+
- ``balances``: the recorded balances, one series per product.
|
|
16
|
+
- ``stock``: the level. Take the last balance before each moment, then add the
|
|
17
|
+
movements after it. A balance at T already counts every movement at or before T.
|
|
18
|
+
A product never counted starts from 0.
|
|
19
|
+
|
|
20
|
+
``Request.products`` keeps only some products. Columns are named
|
|
21
|
+
``<hardwareId>|<productKey>``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from datetime import datetime, timezone
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
#: Request option that chooses what an inventoryMovement DataPoint returns.
|
|
28
|
+
INVENTORY_OPTION = "inventory"
|
|
29
|
+
INVENTORY_MOVEMENTS = "movements"
|
|
30
|
+
INVENTORY_BALANCES = "balances"
|
|
31
|
+
INVENTORY_STOCK = "stock"
|
|
32
|
+
INVENTORY_MODES = (INVENTORY_MOVEMENTS, INVENTORY_BALANCES, INVENTORY_STOCK)
|
|
33
|
+
|
|
34
|
+
#: Time-series tag that carries the product key on a movement point.
|
|
35
|
+
PRODUCT_TAG = "productKey"
|
|
36
|
+
#: Message key that carries it to the writers (``Scope``, ``EventType`` style).
|
|
37
|
+
PRODUCT_MESSAGE_KEY = "ProductKey"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def inventory_mode(options) -> str:
|
|
41
|
+
"""The inventory mode a request's options ask for; ``movements`` when unset."""
|
|
42
|
+
mode = None
|
|
43
|
+
if isinstance(options, dict):
|
|
44
|
+
mode = options.get(INVENTORY_OPTION)
|
|
45
|
+
if mode is None:
|
|
46
|
+
return INVENTORY_MOVEMENTS
|
|
47
|
+
if mode not in INVENTORY_MODES:
|
|
48
|
+
raise ValueError(f"options.{INVENTORY_OPTION} must be one of {', '.join(INVENTORY_MODES)} "
|
|
49
|
+
f"but received {mode!r}"[:300])
|
|
50
|
+
return mode
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _ts_out(value):
|
|
54
|
+
if value is None:
|
|
55
|
+
return None
|
|
56
|
+
if value.tzinfo is None:
|
|
57
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
58
|
+
return value.isoformat()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _ts_in(value):
|
|
62
|
+
"""A datetime from ISO 8601 or epoch milliseconds; anything else is kept as sent,
|
|
63
|
+
for the server to refuse with a message that names it."""
|
|
64
|
+
if value is None or isinstance(value, datetime):
|
|
65
|
+
return value
|
|
66
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
67
|
+
return datetime.fromtimestamp(value / 1000, tz=timezone.utc)
|
|
68
|
+
try:
|
|
69
|
+
return datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
70
|
+
except ValueError:
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _uuid_str(value):
|
|
75
|
+
if value is None or value == "":
|
|
76
|
+
return None
|
|
77
|
+
return str(value).lower()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class InventoryBalance:
|
|
81
|
+
"""
|
|
82
|
+
An absolute stock level: ``quantity`` of ``product`` on the inventoryMovement
|
|
83
|
+
DataPoint ``datapoint`` (a hardware id), true at ``timestamp`` (when it was
|
|
84
|
+
counted, not when it was recorded: that is ``created_at``).
|
|
85
|
+
|
|
86
|
+
PostgreSQL ``"InventoryBalance"``, PK (``datapoint``, ``product``, ``timestamp``).
|
|
87
|
+
Writing the same key again replaces the quantity.
|
|
88
|
+
|
|
89
|
+
On write only:
|
|
90
|
+
|
|
91
|
+
- ``twin`` (a Twin hardware id) may replace ``datapoint``. The DataPoint is then
|
|
92
|
+
the Twin's inventoryMovement DataPoint of the product's category.
|
|
93
|
+
- ``unit`` (a backend unit id of the product's category) converts ``quantity`` to
|
|
94
|
+
the DataPoint's unit. It is stored in that unit, so a read never carries ``unit``.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def route(cls):
|
|
99
|
+
return "planner/inventory/balances"
|
|
100
|
+
|
|
101
|
+
def __init__(self, datapoint: str = None, product: str = None, timestamp=None,
|
|
102
|
+
quantity: float = None, unit=None, twin: str = None):
|
|
103
|
+
self.datapoint: Optional[str] = datapoint
|
|
104
|
+
self.product: Optional[str] = product
|
|
105
|
+
self.timestamp: Optional[datetime] = _ts_in(timestamp)
|
|
106
|
+
self.quantity: Optional[float] = quantity
|
|
107
|
+
self.unit: Optional[str] = _uuid_str(unit)
|
|
108
|
+
self.twin: Optional[str] = twin
|
|
109
|
+
self.created_at: Optional[datetime] = None
|
|
110
|
+
self.created_by: Optional[str] = None
|
|
111
|
+
self.updated_at: Optional[datetime] = None
|
|
112
|
+
self.updated_by: Optional[str] = None
|
|
113
|
+
|
|
114
|
+
def to_dict(self) -> dict:
|
|
115
|
+
obj = {}
|
|
116
|
+
for name, value in (("datapoint", self.datapoint), ("product", self.product),
|
|
117
|
+
("quantity", self.quantity), ("unit", self.unit),
|
|
118
|
+
("twin", self.twin), ("createdBy", self.created_by),
|
|
119
|
+
("updatedBy", self.updated_by)):
|
|
120
|
+
if value is not None:
|
|
121
|
+
obj[name] = value
|
|
122
|
+
for name, value in (("timestamp", self.timestamp), ("createdAt", self.created_at),
|
|
123
|
+
("updatedAt", self.updated_at)):
|
|
124
|
+
if value is not None:
|
|
125
|
+
obj[name] = _ts_out(value) if isinstance(value, datetime) else value
|
|
126
|
+
return obj
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def from_dict(cls, data: dict) -> "InventoryBalance":
|
|
130
|
+
obj = cls()
|
|
131
|
+
obj.datapoint = data.get("datapoint")
|
|
132
|
+
obj.product = data.get("product")
|
|
133
|
+
obj.timestamp = _ts_in(data.get("timestamp"))
|
|
134
|
+
obj.quantity = data.get("quantity")
|
|
135
|
+
obj.unit = _uuid_str(data.get("unit"))
|
|
136
|
+
obj.twin = data.get("twin")
|
|
137
|
+
obj.created_at = _ts_in(data.get("createdAt") or data.get("created_at"))
|
|
138
|
+
obj.created_by = data.get("createdBy") or data.get("created_by")
|
|
139
|
+
obj.updated_at = _ts_in(data.get("updatedAt") or data.get("updated_at"))
|
|
140
|
+
obj.updated_by = data.get("updatedBy") or data.get("updated_by")
|
|
141
|
+
return obj
|
|
142
|
+
|
|
143
|
+
def key(self) -> tuple:
|
|
144
|
+
return self.datapoint, self.product, self.timestamp
|
|
145
|
+
|
|
146
|
+
def __repr__(self) -> str:
|
|
147
|
+
return (f"InventoryBalance(datapoint={self.datapoint!r}, product={self.product!r}, "
|
|
148
|
+
f"timestamp={self.timestamp!r}, quantity={self.quantity!r})")
|
|
@@ -164,6 +164,8 @@ class Request(ApiDto):
|
|
|
164
164
|
:ivar uuid.UUID twin_id: technical id of wizata_dsapi.Twin registered on template_id.
|
|
165
165
|
:ivar dict filters: filters to apply to the query pre-aggregation. nested dictionary using as first key=datapoint then value=another dictionary with key=operator (gt,lt,gte,lte,eq,neq) and value is the float value to compare too).
|
|
166
166
|
:ivar dict options: dict representing query options.
|
|
167
|
+
:ivar list scopes: scope filters for SCOPED datapoints.
|
|
168
|
+
:ivar list products: product keys to keep on inventoryMovement datapoints (all when None).
|
|
167
169
|
"""
|
|
168
170
|
|
|
169
171
|
@classmethod
|
|
@@ -198,7 +200,8 @@ class Request(ApiDto):
|
|
|
198
200
|
field=None,
|
|
199
201
|
bucket=None,
|
|
200
202
|
tags=None,
|
|
201
|
-
scopes=None
|
|
203
|
+
scopes=None,
|
|
204
|
+
products=None):
|
|
202
205
|
if request_id is None:
|
|
203
206
|
request_id = uuid.uuid4()
|
|
204
207
|
self.request_id = request_id
|
|
@@ -242,6 +245,7 @@ class Request(ApiDto):
|
|
|
242
245
|
self.bucket = bucket
|
|
243
246
|
self.tags = tags
|
|
244
247
|
self.scopes = scopes
|
|
248
|
+
self.products = products
|
|
245
249
|
|
|
246
250
|
self.null = null
|
|
247
251
|
|
|
@@ -623,6 +627,8 @@ class Request(ApiDto):
|
|
|
623
627
|
query['tags'] = self.tags
|
|
624
628
|
if self.scopes is not None:
|
|
625
629
|
query['scopes'] = self.scopes
|
|
630
|
+
if self.products is not None:
|
|
631
|
+
query['products'] = self.products
|
|
626
632
|
|
|
627
633
|
if self.target_feat is not None:
|
|
628
634
|
query["target_feat"] = {
|
|
@@ -758,6 +764,14 @@ class Request(ApiDto):
|
|
|
758
764
|
if "scopes" in json_data.keys():
|
|
759
765
|
self.scopes = json_data["scopes"]
|
|
760
766
|
|
|
767
|
+
if "products" in json_data.keys():
|
|
768
|
+
products = json_data["products"]
|
|
769
|
+
if products is not None and (not isinstance(products, list) or not products
|
|
770
|
+
or not all(isinstance(p, str) for p in products)):
|
|
771
|
+
raise ValueError(f"'products' must be a non-empty list of product keys (leave it out "
|
|
772
|
+
f"for every product), got {products!r}"[:300])
|
|
773
|
+
self.products = products
|
|
774
|
+
|
|
761
775
|
if "connections" in json_data.keys():
|
|
762
776
|
self.connections = json_data["connections"]
|
|
763
777
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0.dev7"
|
|
@@ -55,6 +55,7 @@ from .version import __version__
|
|
|
55
55
|
import ast
|
|
56
56
|
from .deployment import Deployment, DeploymentStatus
|
|
57
57
|
from .product import ProductKind, Product, ProductComponent
|
|
58
|
+
from .inventory import InventoryBalance
|
|
58
59
|
from .api_config import _registry
|
|
59
60
|
|
|
60
61
|
from .streamlit_utils import get_streamlit_token, get_streamlit_domain
|
|
@@ -773,7 +774,8 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
773
774
|
field=None,
|
|
774
775
|
bucket: str = None,
|
|
775
776
|
tags: dict = None,
|
|
776
|
-
scopes: list = None
|
|
777
|
+
scopes: list = None,
|
|
778
|
+
products: list = None) -> Request:
|
|
777
779
|
"""
|
|
778
780
|
Build the Request behind a time-series query.
|
|
779
781
|
|
|
@@ -816,6 +818,9 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
816
818
|
if scopes is not None:
|
|
817
819
|
request.scopes = scopes
|
|
818
820
|
|
|
821
|
+
if products is not None:
|
|
822
|
+
request.products = products
|
|
823
|
+
|
|
819
824
|
if options is not None:
|
|
820
825
|
request.options = options
|
|
821
826
|
|
|
@@ -847,7 +852,8 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
847
852
|
field=None,
|
|
848
853
|
bucket: str = None,
|
|
849
854
|
tags: dict = None,
|
|
850
|
-
scopes: list = None
|
|
855
|
+
scopes: list = None,
|
|
856
|
+
products: list = None) -> pandas.DataFrame:
|
|
851
857
|
"""
|
|
852
858
|
Query a dataframe from API.
|
|
853
859
|
:param agg_method:
|
|
@@ -864,13 +870,15 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
864
870
|
:param field: by default 'value' if none, accept str or list (e.g. value, reliability, eventId, ...)
|
|
865
871
|
:param bucket: specify on which bucket the query applies.
|
|
866
872
|
:param scopes: list of scope filters for SCOPED datapoints (e.g. ["normal", {"vibration": {"gte": 0.6}}]).
|
|
873
|
+
:param products: product keys to keep on inventoryMovement datapoints (all when None).
|
|
874
|
+
``options={"inventory": "movements" | "balances" | "stock"}`` chooses what they return.
|
|
867
875
|
:return: dataframe
|
|
868
876
|
"""
|
|
869
877
|
return self._query(request=self._build_request(
|
|
870
878
|
datapoints=datapoints, start=start, end=end, interval=interval,
|
|
871
879
|
agg_method=agg_method, template=template, twin=twin, null=null,
|
|
872
880
|
filters=filters, options=options, group=group, field=field,
|
|
873
|
-
bucket=bucket, tags=tags, scopes=scopes))
|
|
881
|
+
bucket=bucket, tags=tags, scopes=scopes, products=products))
|
|
874
882
|
|
|
875
883
|
def batch_query(self,
|
|
876
884
|
step: timedelta,
|
|
@@ -2867,6 +2875,54 @@ class WizataDSAPIClient(ApiInterface, ApiDtoInterface):
|
|
|
2867
2875
|
"PUT", f"planner/products/{self._planner_key(product)}/components/", body=body)
|
|
2868
2876
|
return [ProductComponent.from_dict(c) for c in response.json()]
|
|
2869
2877
|
|
|
2878
|
+
# Production Planner - inventory balances (epic DEV-6044)
|
|
2879
|
+
|
|
2880
|
+
def set_inventory_balances(self, balances: list) -> list:
|
|
2881
|
+
"""
|
|
2882
|
+
Record absolute stock levels (a count, an ERP figure). Writing the same
|
|
2883
|
+
(datapoint, product, timestamp) again replaces the quantity.
|
|
2884
|
+
|
|
2885
|
+
:param balances: list of InventoryBalance (or dicts). Each names its
|
|
2886
|
+
inventoryMovement DataPoint by ``datapoint``, or by ``twin`` (the DataPoint
|
|
2887
|
+
of the product's category on that Twin). ``unit`` converts the quantity to
|
|
2888
|
+
the DataPoint's unit.
|
|
2889
|
+
:return: the stored balances, quantities in the DataPoint's unit.
|
|
2890
|
+
"""
|
|
2891
|
+
body = [b.to_dict() if isinstance(b, InventoryBalance) else b for b in balances]
|
|
2892
|
+
response = self._planner_request("POST", "planner/inventory/balances/", body=body)
|
|
2893
|
+
return [InventoryBalance.from_dict(b) for b in response.json()]
|
|
2894
|
+
|
|
2895
|
+
def search_inventory_balances(self, search_query=None) -> PagedQueryResult:
|
|
2896
|
+
"""
|
|
2897
|
+
List recorded balances, newest first by default.
|
|
2898
|
+
|
|
2899
|
+
:param search_query: SearchQuery or dict; filters on ``datapoint`` and
|
|
2900
|
+
``product`` (eq / in) and ``timestamp`` (gte / gt / lt / lte).
|
|
2901
|
+
:return: PagedQueryResult with InventoryBalance results.
|
|
2902
|
+
"""
|
|
2903
|
+
if search_query is None:
|
|
2904
|
+
search_query = SearchQuery()
|
|
2905
|
+
elif isinstance(search_query, dict):
|
|
2906
|
+
search_query = SearchQuery.from_dict(search_query)
|
|
2907
|
+
response = self._planner_request("POST", "planner/inventory/balances/search",
|
|
2908
|
+
body=search_query.to_dict())
|
|
2909
|
+
return PagedQueryResult.from_dict(response.json(), dto_class=InventoryBalance)
|
|
2910
|
+
|
|
2911
|
+
def delete_inventory_balance(self, datapoint: str, product: str, timestamp):
|
|
2912
|
+
"""
|
|
2913
|
+
Delete one recorded balance (a count entered by mistake).
|
|
2914
|
+
|
|
2915
|
+
:param timestamp: datetime or ISO string, exactly as stored.
|
|
2916
|
+
"""
|
|
2917
|
+
if isinstance(timestamp, datetime.datetime):
|
|
2918
|
+
if timestamp.tzinfo is None:
|
|
2919
|
+
timestamp = timestamp.replace(tzinfo=datetime.timezone.utc)
|
|
2920
|
+
timestamp = timestamp.isoformat()
|
|
2921
|
+
self._planner_request("DELETE", "planner/inventory/balances/",
|
|
2922
|
+
params={"datapoint": datapoint, "product": product,
|
|
2923
|
+
"timestamp": timestamp},
|
|
2924
|
+
expected=(204,))
|
|
2925
|
+
|
|
2870
2926
|
|
|
2871
2927
|
def api() -> WizataDSAPIClient:
|
|
2872
2928
|
"""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.2.0.dev5"
|
|
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.dev5 → wizata_dsapi-2.2.0.dev7}/wizata_dsapi.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|