AdvModelApi 的使用
数据库数据获取
class AdvModelApi:
def __init__(self,
org_id: str,
adv_model_stub: RpcAdvModelApiStub,
input_stub: RpcAdvModelInputApiStub,
output_stub: RpcAdvModelOutputApiStub,
file_api: FileApi,
rpc_ts_query_api: TsQueryApi):
self.__adv_model_stub = adv_model_stub
self.__input_stub = input_stub
self.__output_stub = output_stub
self.__file_api = file_api
self.__rpc_ts_query_api = rpc_ts_query_api
self.org_id = org_id
def query_model_config_file(self, model_id: str,
config_name: str = 'default',
write_to: Union[str, IO, IOBase] = None) -> RpcAdvModelConfig:
"""
查询对应配置文件的信息
:param model_id: 模型id
:param config_name: 配置名
:param write_to:如果该参数不为空,会将内容写入到对应的位置。如果为str,写入到对应文件。如果是IO对象,会写入到对应对象中。
:return: RpcAdvModelConfig对象,包含了
string model_id;
string config_name;
string path;
string create_time;
string modify_time;
"""
request = RpcQueryModelConfigRequest(model_id=model_id, config_name=config_name, org_id=self.org_id)
resp = self.__adv_model_stub.QueryModelConfig(request)
# 同时写入到对应文件
if write_to is not None:
self.__file_api.get_and_write_file(resp.path, write_to)
return resp
def add_or_update_input(self,
input_name: str,
model_id: str,
input_metric_id: str,
desc: str = None) -> str:
"""
添加或更新输入信息
:param input_name: 输入名称
:param model_id: 模型id
:param input_metric_id: 输入关联的scada 指标id
:param desc: 描述
:return: 成功后的id
"""
request = RpcAddInputRequest(input_name=input_name, model_id=model_id, input_metric_id=input_metric_id,
desc=desc, org_id=self.org_id)
resp = self.__input_stub.AddInput(request)
return resp.value
def delete_input(self,
input_name: str,
model_id: str):
"""
添加或更新输入信息
:param input_name: 输入名称
:param model_id: 模型id
:return: 成功不会返回任何信息,失败会抛出异常
"""
request = RpcDeleteInputRequest(input_name=input_name, model_id=model_id, org_id=self.org_id)
self.__input_stub.DeleteInput(request)
def query_inputs(self, model_id: str) -> list[dict]:
"""
查询模型的相关输入信息
:param model_id:
:return: 输入信息的dict列表
"""
request = RpcQueryInputsRequest(model_id=model_id, org_id=self.org_id)
resp = self.__input_stub.QueryInputs(request)
return json.loads(resp.inputs_json_list)
def add_output(self,
output_name: str,
model_id: str,
output_type: OutputType,
desc: str = "") -> outputApi.RpcAddOutputResp:
"""
:param output_name: 输出指标名称,同一模型下不能重复
:param model_id: 模型id
:param output_type: 输出数据类型
:param desc: 描述、备注
:return: 返回响应对象 {
output_name: 输出指标名称,同一模型下不能重复
model_id: 模型id
output_ts_name: 如果类型为时序数据,这里是其序列名
output_type: 输出数据类型
desc: 描述、备注
}
"""
output_type_v = output_type.value
request = outputApi.RpcAddOutputRequest(output_name=output_name,
model_id=model_id,
output_type=output_type_v,
desc=desc,
org_id=self.org_id)
resp = self.__output_stub.AddOutput(request)
return resp
# 模型输出查询
def query_output(self,
model_id: str,
model_types: Union[str, list[str]] = None,
model_name: str = None,
output_name: str = None,
output_names: list[str] = None,
sort: list[str] = None) -> list[dict]:
"""
查询模型的输出
:param model_id: 模型id,必传
:param model_types: 模型类型
:param model_name: 模型名称,模糊查询
:param output_name: 输出的名称,模糊查询
:param output_names: 输出的名称,根据列表精确匹配,可以为空
:param sort: 排序规则,可以不传
:return: 输出信息列表的dict
"""
request = outputApi.RpcQueryAdvModelOutputsRequest(model_ids=to_list(model_id),
model_types=to_list(model_types),
model_name=model_name,
output_name=output_name,
output_names=to_list(output_names),
sort=sort,
org_id=self.org_id)
resp = self.__output_stub.QueryModelOutputs(request)
return json.loads(resp.output_list_json)
# 会将数据类型做转换
# def query_latest_output_detail_content(self,
# model_id: str,
# output_name: str,
# output_detail_name: str = None) -> Union[dict, None]:
# self.query_latest_output_detail(model_id, output_name, output_detail_name)
# if len(content) != 0:
# return content[0]
# else:
# return None
# 时序输出数据查询
def query_ts_output(self,
model_id: str,
output_name: str,
from_time: datetime,
to_time: datetime = datetime.now(),
down_sampling: RpcDownSampling = None,
result_tz: Union[str, pytz.timezone, dateutil.tz.tzfile] = None) -> Union[TsResp, None]:
output_list = self.query_output(model_id=model_id, output_names=[output_name])
if len(output_list) == 0:
raise Exception(f'output:{output_name} not found in model:{model_id}')
output = next((x for x in output_list if x['outputName'] == output_name), None)
if output is None:
raise Exception(f'output:{output_name} not found in model:{model_id}')
# output = output_list[0]
if output['outputType'] != OutputType.timeSeries.value:
raise Exception(f'outputType of output:{output_name} is not timeSeries,can not query timeSeries data')
ts_name = output['outputTsName']
ts_list = list(self.__rpc_ts_query_api.query_ts_data(from_time, to_time, [ts_name], down_sampling, result_tz))
if len(ts_list) == 0:
return None
else:
return ts_list[0]
# 删除某条输出的详情
def delete_output_detail(self,
model_id: str,
output_name: str,
output_detail_name: str):
"""
删除某条输出的详情
:param model_id: 模型id
:param output_name: 输出名称
:param output_detail_name: 输出详情名称
:return: 没有返回信息,失败会抛出异常
"""
request = outputApi.RpcDeleteOutputDetailRequest(model_id=model_id, output_name=output_name,
output_detail_name=output_detail_name, org_id=self.org_id)
self.__output_stub.DeleteOutputDetail(request)
# 最新的输出信息
def query_latest_output_detail(self,
model_id: str,
output_name: str,
output_detail_name: str = None) -> Union[dict, None]:
"""
如果outputType类型为table,那么可以通过table来访问其数据的dataFrame
:param model_id:
:param output_name:
:param output_detail_name:
:return:
"""
page = self.query_output_detail_history(model_id=model_id, output_names=[output_name],
output_detail_names=[output_detail_name],
sort=['createTime;desc'], page=0, size=1)
content = page['content']
if len(content) != 0:
return content[0]
else:
return None
# 查询输出详情
def query_output_detail_history(self,
model_id: str,
output_names: Union[str, list[str]] = None,
output_detail_name_like: str = None,
output_detail_names: list[str] = [],
output_types: list[OutputType] = [],
sort: list[str] = None,
page: int = 0,
size: int = 10) -> dict:
"""
查询输出详情。
如果outputType类型为table,那么content list中,可以通过table来访问其数据的dataFrame
:param model_id: 模型id,必传
:param output_names: 输出名称,可以为str或str的列表
:param output_detail_name_like: 根据输出详情名称模糊查询
:param output_detail_names: 输出详情名称,list
:param output_types: 输出类型
:param sort: 排序,默认为空
:param page: 页码
:param size: 每页大小
:return: {
content:[]
size:
page:
totalPage
}
"""
request = outputApi.RpcQueryAdvModelOutputsDetailRequest(model_ids=[model_id],
output_names=to_list(output_names),
output_detail_name=output_detail_name_like,
output_detail_names=to_list(output_detail_names),
output_types=to_list(output_types),
sort=sort,
page=page, size=size,
org_id=self.org_id)
resp = self.__output_stub.QueryModelOutputDetails(request)
output_list: dict = json.loads(resp.output_detail_list_json)
for output in output_list['content']:
if output['outputType'] == OutputType.table.value:
output['table'] = pd.read_json(path_or_buf=output['content'], orient='table')
if output['outputType'] == OutputType.timeSeries.value:
# 时序数据暂时不通过此处查询
continue
elif output['outputType'] == OutputType.text.value:
output['data'] = output['content']
return output_list
# 上传数据
def upload_output_detail(self,
output_name: str,
model_id: str,
output_type: OutputType,
data: Union[pd.DataFrame, pd.Series, bytes, str] = None,
file_path: str = None,
output_detail_name: str = None,
ext: str = "",
desc: str = "") -> str:
"""
如果上传的类型是时序数据,那么data必须为pandas的Series的数据。Series的数据支持int、float、str和bool
:param output_name: 输出指标名称,同一模型下不能重复
:param model_id: 模型id
:param output_type: 输出数据类型
:param data: 数据内容、备注
:param file_path: 描述、备注
:param output_detail_name:
该详情的名称,如果留空,服务端会自动使用当前时间作为名称。
建议根据业务自行命名,比如2021-09-01排水控制
:param ext: 描述、备注
:param desc: 描述、备注
:return: 改调消息的 output_detail_name
:raise
如果类型为table,但data类型不是DataFrame
"""
request = outputApi.RpcUploadOutputDetailRequest(
output_name=output_name,
model_id=model_id,
output_type=output_type.value,
output_detail_name=output_detail_name,
org_id=self.org_id
)
if ext is not None:
request.ext = ext
if desc is not None:
request.desc = desc
# 处理table类型
if output_type == OutputType.table:
self.__handle_table_output_upload(request, data)
# 处理时序数据
elif output_type == OutputType.timeSeries:
self.__handle_ts_output_upload(request, output_type, data)
# 处理其它类型
elif output_type == OutputType.text or output_type == OutputType.html or \
output_type == OutputType.csv or output_type == OutputType.image or \
output_type == OutputType.file:
self.__handle_file_or_bytes_output_upload(request, output_type, data, file_path, output_detail_name)
else:
raise Exception('not supported data type:' + output_type.value)
resp = self.__output_stub.UploadOutputDetail(request)
return resp.detail_id
# 处理table类型的数据
@staticmethod
def __handle_table_output_upload(request: outputApi.RpcUploadOutputDetailRequest,
data: Union[pd.DataFrame, pd.Series, bytes, str]):
if data is None:
raise Exception("data is None")
if not isinstance(data, pd.DataFrame):
raise Exception('OutputType.table only support data type is DataFrame')
df: pd.DataFrame = data
json = df.to_json(date_format='iso', date_unit='ms', orient="table")
request.content = bytes(json, 'utf-8')
# 处理时序数据上次
@staticmethod
def __handle_ts_output_upload(request: outputApi.RpcUploadOutputDetailRequest,
output_type: OutputType,
data: Union[pd.DataFrame, pd.Series, bytes, str]):
if not isinstance(data, pd.Series):
raise Exception('OutputType.timeSeries only support data type is pandas.Series')
series: pd.Series = data
if isinstance(series.index.dtype, pd.PeriodIndex):
series = series.to_timestamp()
# grpc的时序格式
rpc_s = outputApi.RpcUploadOutputDetailRequest.TimeSeries()
# 转换格式
for (time, value) in series.items():
t: pd.Timestamp = time
# point = outputApi.RpcUploadOutputDetailRequest.Point()
point = rpc_s.points.add()
point.time = int(t.timestamp() * 1000)
if value is None:
point.null = grpc_struct.NULL_VALUE
elif series.dtype in FLOAT_TYPES:
point.double = value
elif series.dtype in INT_TYPES:
point.int = value
elif 'boolean' == series.dtype:
point.bool = value
elif 'string' == series.dtype:
point.str = value
else:
# 无法识别,通通转为double
point.double = float(value)
# grpc python就是这么奇怪……
# https://stackoverflow.com/questions/18376190/attributeerror-assignment-not-allowed-to-composite-field-task-in-protocol-mes
request.time_series.CopyFrom(rpc_s)
# 处理其它类型的数据
@staticmethod
def __handle_file_or_bytes_output_upload(request: outputApi.RpcUploadOutputDetailRequest,
output_type: OutputType,
data: Union[pd.DataFrame, pd.Series, bytes, str],
file_path: str,
output_detail_name: str = None):
if data is not None:
if isinstance(data, bytes):
request.content = data
if isinstance(data, str):
request.content = bytes(data, 'utf-8')
if isinstance(data, pd.DataFrame):
raise Exception('OutputType ' + output_type.value + ' not support data type:DataFrame')
# 处理文件
elif file_path is not None:
with open(file_path, 'rb') as file:
request.file_name = os.path.basename(file.name)
request.content = file.read()