diff --git a/docs/entities/lines.md b/docs/entities/lines.md index fc99303..6f77c74 100644 --- a/docs/entities/lines.md +++ b/docs/entities/lines.md @@ -175,3 +175,226 @@ print(len(df)) # 14 ``` +### Get Line Data Lineviz +This function allows you to pull data from our line model via lineviz API. This function can be called using either of the two methods of calling: + +#### Using Positional Arguments +``` +cli.get_line_data_lineviz(assets, d_vars, i_vars, time_selection, asset_time_offset, filters) +``` + +#### Using Keyword Arguments +``` +cli.get_line_data_lineviz(assets=assets, d_vars=d_vars, i_vars=i_vars, time_selection=time_selection, asset_time_offset=asset_time_offset, filters=filters) +``` + +It will return something like: +``` +[ + { + "i_vals": [ + { + "name": "offset_endtime", + "asset": "SHARED", + "i_pos": 0, + "value": { + "bin_no": 0, + "bin_min": "2024-07-06T00:00:00+00:00", + "bin_max": "2024-07-06T00:00:00+00:00", + "bin_avg": "2024-07-06T00:00:00+00:00" + } + } + ], + "d_vals": [ + { + "name": "quality", + "asset": "F3_Paper_Mill_PM1_Production_Status", + "d_pos": 0, + "value": { + "avg": 90.5265124361114 + }, + "kpi": { + "dependencies": { + "reject_tons": 0.9635, + "good_tons": 1380818.9, + "random": 1445.0002659794823 + }, + "formula": "good_tons / (good_tons + (reject_tons + (random * 100))) * 100 if (good_tons + (reject_tons + (random * 100))) > 0 else None", + "aggregates": { + "reject_tons": "sum", + "good_tons": "sum", + "random": "sum" + } + }, + "type": "kpi" + }, + { + "name": "stats__32RL1BWTACT__val", + "asset": "F1_Paper_Mill_PM2_Production_Status", + "d_pos": 1, + "value": { + "avg": 70.23743333551619 + }, + "type": "continuous" + } + ], + "_count": 2880 + }, + { + "i_vals": [ + { + "name": "offset_endtime", + "asset": "SHARED", + "i_pos": 0, + "value": { + "bin_no": 1, + "bin_min": "2024-07-07T00:00:00+00:00", + "bin_max": "2024-07-07T00:00:00+00:00", + "bin_avg": "2024-07-07T00:00:00+00:00" + } + } + ], + "d_vals": [ + { + "name": "quality", + "asset": "F3_Paper_Mill_PM1_Production_Status", + "d_pos": 0, + "value": { + "avg": 92.36746206062107 + }, + "kpi": { + "dependencies": { + "reject_tons": 313.66733, + "good_tons": 1177192.4, + "random": 969.6047504117041 + }, + "formula": "good_tons / (good_tons + (reject_tons + (random * 100))) * 100 if (good_tons + (reject_tons + (random * 100))) > 0 else None", + "aggregates": { + "reject_tons": "sum", + "good_tons": "sum", + "random": "sum" + } + }, + "type": "kpi" + }, + { + "name": "stats__32RL1BWTACT__val", + "asset": "F1_Paper_Mill_PM2_Production_Status", + "d_pos": 1, + "value": { + "avg": 72.70377143305424 + }, + "type": "continuous" + } + ], + "_count": 1954 + } +] +``` + +Both methods of calling the API are functionally equivalent. The first method exclusively uses positional arguments, while the second method employs named arguments. Providing both positional and keyword values for the same argument in an API call is not allowed. It will throw an error, causing the API call to fail. + +#### assets +A required field, this is a list of strings where the strings used are all machine_names. You can use machines from different lines. +``` +["F3_Paper_Mill_PM1_Production_Status", "F1_Paper_Mill_PM2_Production_Status"] +``` + +#### d_vars +The Dependent variables. These will change depending on the entity you are trying to access. But will always be a list in the following form: +``` +[ + { + "name": "quality", + "asset": "F3_Paper_Mill_PM1_Production_Status", + "aggregate": [ + "avg" + ], + "type": "kpi" + }, + { + "name": "stats__32RL1BWTACT__val", + "asset": "F1_Paper_Mill_PM2_Production_Status", + "aggregate": [ + "avg" + ], + "type": "continuous" + } +] +``` + + +#### i_vars +The indepent variables. These should typically be time based values that are stored on the machine_type you are using. They will always be a list in the following form: +``` +[ + { + "name": "offset_endtime", + "asset": "SHARED", + "time_resolution": "day", + "query_tz": "UTC", + "output_tz": "UTC", + "bin_strategy": "user_defined2", + "bin_count": 50 + } +] +``` + + +#### time_selection +This is the same time selection we use in other places more details can be found [here](/docs/commonly_used_data_types/data_viz_query.md#time_selection). It defaults to one day if none is given and can look like this: +``` +{ + "time_type": "relative", + "relative_start": 7, + "relative_unit": "day", + "ctime_tz": "America/Los_Angeles" +} +``` + + +#### asset_time_offset +This is used to set offsets between machines in a line. This is optional and will defualt to no offset if not given. This is a dictionary that looks like this: +``` +{ + "F3_Paper_Mill_PM1_Production_Status": { + "interval": 0, + "period": "minutes" + }, + "F1_Paper_Mill_PM2_Production_Status": { + "interval": 0, + "period": "minutes" + } +} +``` + + +#### filters +This is optional and a way to to filter the data. This a list of objects that each look like the following: +``` +{ + "asset": "F2_010_BodyMaker_1", + "name": "stats__0_BM 008: Cans Out__val", + "op": "gt", + "value": 35200.0 +} +``` + +##### asset +The name of the asset this filter is looking at. This will be a machine_name. + +##### name +The name of the field you are looking at for this machine. + +##### op +The operation you are filtering with. Options inclue: +lt: less than +gt: greater than +lte: less than or equal to +gte: greater than or equal to +eq: equal to +in: in +ne: not equal to + +##### value +The value you are comparing the field to. diff --git a/smsdk/client.py b/smsdk/client.py index f28a6f4..e1f203c 100644 --- a/smsdk/client.py +++ b/smsdk/client.py @@ -105,9 +105,9 @@ def dict_to_df(data, normalize=True): "endtime": "End Time", "total": "Duration", "shift": "Shift", - "metadata__reason": "Downtime Reason", - "metadata__category": "Downtime Category", - "metadata__downtime_type": "Downtime Type", + "reason": "Downtime Reason", + "category": "Downtime Category", + "downtime_type": "Downtime Type", } downmapinv = { @@ -116,9 +116,9 @@ def dict_to_df(data, normalize=True): "End Time": "endtime", "Duration": "total", "Shift": "shift", - "Downtime Reason": "metadata__reason", - "Downtime Category": "metadata__category", - "Downtime Type": "metadata__downtime_type", + "Downtime Reason": "reason", + "Downtime Category": "category", + "Downtime Type": "downtime_type", } @@ -630,6 +630,57 @@ def get_line_data( limit=limit, offset=offset, **kwargs ) + @version_check_decorator + def get_line_data_lineviz( + self, + assets=None, + d_vars=None, + i_vars=None, + time_selection=ONE_DAY_RELATIVE, + asset_time_offset={}, + filters=[], + **kwargs, + ): + """ + Returns all the lines for the facility + :param assets: A list of assets you wish to get data for + :param asset_time_offset: A dictionary of the time offsets to use for assets + :param d_vars: A list of data viz d_var objects + :param i_vars: A list of data viz i_var objects + :param time_selection: A time selection for your query defaults to one day relative + :param filter: A list of filters on the data + """ + lines = smsdkentities.get("line") + base_url = get_url( + self.config["protocol"], + self.tenant, + self.config["site.domain"], + self.config["port"], + ) + + if i_vars: + kwargs["d_vars"] = d_vars + if i_vars: + kwargs["i_vars"] = i_vars + if time_selection: + kwargs["time_selection"] = time_selection + if assets: + for asset in assets: + if asset_time_offset.get(asset) == None: + asset_time_offset[asset] = {"interval": 0, "period": "minutes"} + + where = [] + if len(filters) > 0: + for filter in filters: + where.append({"nested": [filter]}) + + kwargs["d_vars"] = d_vars + kwargs["i_vars"] = i_vars + kwargs["asset_time_offset"] = asset_time_offset + kwargs["time_selection"] = time_selection + kwargs["where"] = where + return lines(self.session, base_url).get_line_data_lineviz(**kwargs) + @version_check_decorator def create_share_link( self, diff --git a/smsdk/client_v0.py b/smsdk/client_v0.py index 2310ebf..2af8083 100644 --- a/smsdk/client_v0.py +++ b/smsdk/client_v0.py @@ -133,9 +133,9 @@ def convert_to_valid_url( "endtime": "End Time", "total": "Duration", "shift": "Shift", - "metadata__reason": "Downtime Reason", - "metadata__category": "Downtime Category", - "metadata__downtime_type": "Downtime Type", + "reason": "Downtime Reason", + "category": "Downtime Category", + "downtime_type": "Downtime Type", } downmapinv = { @@ -144,9 +144,9 @@ def convert_to_valid_url( "End Time": "endtime", "Duration": "total", "Shift": "shift", - "Downtime Reason": "metadata__reason", - "Downtime Category": "metadata__category", - "Downtime Type": "metadata__downtime_type", + "Downtime Reason": "reason", + "Downtime Category": "category", + "Downtime Type": "downtime_type", } diff --git a/smsdk/config/api_endpoints.json b/smsdk/config/api_endpoints.json index 59589f7..bd3497c 100644 --- a/smsdk/config/api_endpoints.json +++ b/smsdk/config/api_endpoints.json @@ -47,7 +47,8 @@ "current_value": "/v1/recipe/current_value" }, "Line": { - "url": "/v1/datatab/line" + "url": "/v1/datatab/line", + "task": "/v1/linevis/task/async" }, "RawData": { "url": "/v1/datatab/raw_data" diff --git a/smsdk/ma_session.py b/smsdk/ma_session.py index 8857b31..0488ae6 100644 --- a/smsdk/ma_session.py +++ b/smsdk/ma_session.py @@ -34,6 +34,11 @@ import logging log = logging.getLogger(__name__) +try: + NPINFINITY = np.Inf +except AttributeError: + # numpy 2.0 + NPINFINITY = np.inf class MaSession: @@ -45,7 +50,7 @@ def _get_records( self, endpoint: str, method: str = "get", - _limit: float = np.Inf, + _limit: float = NPINFINITY, _offset: int = 0, **url_params: t_.Any, ) -> t_.List[t_.Dict[str, t_.Any]]: @@ -150,7 +155,7 @@ def _get_records_v1( self, endpoint: str, method: str = "post", - limit: float = np.Inf, + limit: float = NPINFINITY, offset: float = 0, db_mode: str = "sql", results_under: str = "results", diff --git a/smsdk/smsdk_entities/cycle/cycleV1.py b/smsdk/smsdk_entities/cycle/cycleV1.py index cfefc58..189e81c 100644 --- a/smsdk/smsdk_entities/cycle/cycleV1.py +++ b/smsdk/smsdk_entities/cycle/cycleV1.py @@ -20,6 +20,12 @@ ENDPOINTS = json.loads(pkg_resources.read_text(config, "api_endpoints.json")) +try: + NPINFINITY = np.Inf +except AttributeError: + # numpy 2.0 + NPINFINITY = np.inf + @smsdkentities.register("cycle_v1") class Cycle(SmsdkEntities, MaSession): @@ -136,7 +142,7 @@ def modify_input_params(self, **kwargs): new_kwargs["select"] = [{"name": i} for i in kwargs["_only"]] new_kwargs["offset"] = kwargs.get("_offset", 0) - new_kwargs["limit"] = kwargs.get("_limit", np.Inf) + new_kwargs["limit"] = kwargs.get("_limit", NPINFINITY) new_kwargs["where"] = where if kwargs.get("_order_by", ""): diff --git a/smsdk/smsdk_entities/downtime/downtimeV1.py b/smsdk/smsdk_entities/downtime/downtimeV1.py index a99c27e..31dc8df 100644 --- a/smsdk/smsdk_entities/downtime/downtimeV1.py +++ b/smsdk/smsdk_entities/downtime/downtimeV1.py @@ -21,6 +21,12 @@ ENDPOINTS = json.loads(pkg_resources.read_text(config, "api_endpoints.json")) +try: + NPINFINITY = np.Inf +except AttributeError: + # numpy 2.0 + NPINFINITY = np.inf + @smsdkentities.register("downtime_v1") class Downtime(SmsdkEntities, MaSession): @@ -133,6 +139,6 @@ def modify_input_params(self, **kwargs): new_kwargs["select"] = [{"name": i} for i in kwargs["_only"]] new_kwargs["offset"] = kwargs.get("_offset", 0) - new_kwargs["limit"] = kwargs.get("_limit", np.Inf) + new_kwargs["limit"] = kwargs.get("_limit", NPINFINITY) return new_kwargs diff --git a/smsdk/smsdk_entities/line/line.py b/smsdk/smsdk_entities/line/line.py index a91f50c..85a5976 100644 --- a/smsdk/smsdk_entities/line/line.py +++ b/smsdk/smsdk_entities/line/line.py @@ -65,3 +65,15 @@ def get_line_data(self, limit=400, offset=0, *args, **kwargs): if not isinstance(records, List): raise ValueError("Error - {}".format(records)) return records + + @mod_util + def get_line_data_lineviz(self, *args, **kwargs): + """ + Get line data via lineviz api + """ + url = "{}{}".format(self.base_url, ENDPOINTS["Line"]["task"]) + records = self._complete_async_task(url, **kwargs) + + if not isinstance(records, List): + raise ValueError("Error - {}".format(records)) + return records diff --git a/smsdk/smsdk_entities/parts/partsV1.py b/smsdk/smsdk_entities/parts/partsV1.py index 74a229c..f2a08db 100644 --- a/smsdk/smsdk_entities/parts/partsV1.py +++ b/smsdk/smsdk_entities/parts/partsV1.py @@ -17,6 +17,12 @@ ENDPOINTS = json.loads(pkg_resources.read_text(config, "api_endpoints.json")) +try: + NPINFINITY = np.Inf +except AttributeError: + # numpy 2.0 + NPINFINITY = np.inf + @smsdkentities.register("part_v1") class Parts(SmsdkEntities, MaSession): @@ -126,6 +132,6 @@ def modify_input_params(self, **kwargs): new_kwargs["where"] = where new_kwargs["select"] = [{"name": i} for i in kwargs["_only"]] new_kwargs["offset"] = kwargs.get("_offset", 0) - new_kwargs["limit"] = kwargs.get("_limit", np.Inf) + new_kwargs["limit"] = kwargs.get("_limit", NPINFINITY) return new_kwargs diff --git a/tests/lines/test_line.py b/tests/lines/test_line.py index ab9ed50..8abcbf7 100644 --- a/tests/lines/test_line.py +++ b/tests/lines/test_line.py @@ -29,7 +29,12 @@ def test_get_utilities(get_session): # Run all_utilites = line.get_utilities(get_session, URL_V1) - expected_list = ["get_utilities", "get_lines", "get_line_data"] + expected_list = [ + "get_utilities", + "get_lines", + "get_line_data", + "get_line_data_lineviz", + ] assert len(all_utilites) == len(expected_list) assert all([a == b for a, b in zip(all_utilites, expected_list)]) @@ -88,3 +93,52 @@ def test_get_line_data(get_client): df2 = get_client.get_line_data(**query) assert df1 == df2 + + +def test_get_line_data_lineviz(get_client): + """Test for line data from lineviz""" + assets = [MACHINE4] + + asset_time_offset = {MACHINE4: {"interval": 0, "period": "minutes"}} + + d_vars = [ + { + "name": "stats__PneumaticPressure__val", + "asset": "JB_NG_PickAndPlace_1_Stage1", + "aggregate": ["avg"], + "type": "continuous", + }, + { + "name": "stats__BLOCKED__val", + "asset": "JB_NG_PickAndPlace_1_Stage1", + "aggregate": ["avg"], + "type": "continuous", + }, + ] + + i_vars = [ + { + "name": "offset_endtime", + "asset": "SHARED", + "time_resolution": "day", + "query_tz": TIME_ZONE, + "output_tz": TIME_ZONE, + "bin_strategy": "user_defined2", + "bin_count": 50, + } + ] + + time_selection = { + "time_type": "absolute", + "start_time": START_DATETIME, + "end_time": END_DATETIME, + "time_zone": TIME_ZONE, + } + + filters = [] + + result = get_client.get_line_data_lineviz( + assets, d_vars, i_vars, time_selection, asset_time_offset, filters + ) + + assert len(result) == 2