Source code for kappa_sdk.analysis_results

from typing import List, Optional, Union, Dict, Any
from xml.etree.ElementTree import ElementTree, Element, fromstring
from .datetime_utils import str_to_datetime
from datetime import datetime

ns_v1 = {'AC': 'KW_XML_Export'}
ns_v2 = {'AA': 'KWKA_XML_Export', 'KWXmlResults': 'KW_XML_Results'}


[docs] class NumericValue: def __init__(self, measure: str, value: float): self.__measure = measure self.__value = value @property def measure(self) -> str: """ Gets a measure as a string. """ return self.__measure @property def value(self) -> float: """ Gets a value as a float. """ return self.__value
[docs] class ResultsXMLParsingException(Exception): def __init__(self, message: str) -> None: self.message: str = message
[docs] class AnalysisResults: """ Wrapper for the results.xml from KW document. Presents analysis results XML in convenient way, supporting both v1 and v2 schemas. .. note:: :py:obj:`Document.analysis_results` property is populated on-demand and is cached for the duration of the :class:`Connection`. If you need to get actual values, use the :py:meth:`Document.get_results_xml` method and instantiate this class with it. """ def __init__(self, xml_string: str): self.__tree: ElementTree = ElementTree(fromstring(xml_string)) self.__root: Element = self.__tree.getroot() def __get_results_element(self, analysis_name: str) -> Element: results_element = self.__root.findall('AC:Results', ns_v1) for elem in results_element: if elem.get('name') == analysis_name: return elem results_container_element = self.__root.find('AA:Results', ns_v2) if results_container_element is not None: for elem in results_container_element.findall('KWXmlResults:Results', ns_v2): if elem.get('name') == analysis_name: return elem raise ResultsXMLParsingException('Saphir document does not contain results for <{}> analysis'.format(analysis_name)) def __find_element(self, element: Element, name: str) -> Optional[Element]: sub_element = element.find('AC:{}'.format(name), ns_v1) if sub_element is not None: return sub_element else: return element.find('KWXmlResults:{}'.format(name), ns_v2) def __find_elements(self, element: Element, name: str) -> List[Element]: sub_elements = element.findall('AC:{}'.format(name), ns_v1) if sub_elements: return sub_elements else: return element.findall('KWXmlResults:{}'.format(name), ns_v2) def get_analysis_names(self) -> List[str]: """ Returns a list of analysis names. """ name_list = list() results_element = self.__root.findall('AC:Results', ns_v1) for elem in results_element: name_list.append(str(elem.get('name'))) if len(name_list) > 0: return name_list results_container_element = self.__root.find('AA:Results', ns_v2) if results_container_element is not None: for elem in results_container_element.findall('KWXmlResults:Results', ns_v2): name_list.append(str(elem.get('name'))) return name_list def get_productivity_index_table_elements(self, analysis_name: Optional[str] = None) -> Union[Dict[str, Dict[str, Any]], Dict[str, Any]]: """Returns a dict with the productivity index table elements for each analysis name. If an analysis name is provided, it will return only the productivity index table elements of this analysis""" results_element = self.__root.find('AA:Results', ns_v2) if results_element is not None: productivity_index_results = results_element.find('KWXmlResults:ProductivityIndexResults', ns_v2) if productivity_index_results is not None: productivity_index_table_elements = productivity_index_results.findall('KWXmlResults:PITableElement', ns_v2) table_elements_dict: Dict[str, Dict[str, Any]] = dict() for element in productivity_index_table_elements: period_name = str(element.find("KWXmlResults:PeriodName", ns_v2).text) # type:ignore[union-attr] if analysis_name is not None and period_name != analysis_name: continue table_elements_dict[period_name] = {} for x in list(element): if x.text != period_name: table_elements_dict[period_name][str(x.tag.split("}")[1])] = x.text return table_elements_dict if analysis_name is None else table_elements_dict[analysis_name] else: raise ValueError("There is no productivity index table in this document") else: raise ValueError("There is no Results section in this document") def get_parameter_value(self, analysis_name: str, category_name: str, parameter_name: str) -> Union[NumericValue, str, datetime]: """ Returns a value of a given parameter. Parameters ---------- analysis_name: The name of analysis. category_name: The name of parameter category. parameter_name: The name of the parameter. """ analysis_element = self.__get_results_element(analysis_name) parameters_element = None for category_element in self.__find_elements(analysis_element, 'Category'): if category_element.get('name') == category_name: parameters_element = category_element break if parameters_element is None: raise ResultsXMLParsingException('Saphir document results does not contain <{}> category'.format(category_name)) for parameter_element in [x for x in list(parameters_element) if x.get('name') == parameter_name]: value_element = self.__find_element(parameter_element, 'doublevalue') if value_element is not None: unit = value_element.get("unit") if unit is not None and value_element.text is not None: return NumericValue(unit, float(value_element.text)) value_element = self.__find_element(parameter_element, 'stringvalue') if value_element is not None: return str(value_element.text) value_element = self.__find_element(parameter_element, 'datetimevalue') if value_element is not None: date_value = str_to_datetime(value_element.text) if date_value is None: raise ValueError(f'Error while parsing {value_element} as datetime') return date_value raise ResultsXMLParsingException('Saphir document results does not contain <{}> parameter in <{}> category'.format(parameter_name, category_name)) def get_period_names(self, analysis_name: str) -> List[str]: """ Returns a list of extracted flow period names for a given analysis. Parameters ---------- analysis_name: The name of analysis. """ analysis_element = self.__get_results_element(analysis_name) name_list = list() if analysis_element is not None: periods_element = self.__find_element(analysis_element, 'Extracted_Flow_Periods') if periods_element is not None: for period_element in self.__find_elements(periods_element, 'Extracted_Period'): if period_element is not None: element = self.__find_element(period_element, 'ProductionEventName') if element is not None and element.text is not None: name_list.append(element.text) return name_list def get_period_property_value(self, analysis_name: str, period_name: str, value_name: str) -> str: """ Returns a value for a property of a given period. Parameters ---------- analysis_name: The name of analysis. period_name: The name of the extracted flow period. value_name: The name of the period property. """ analysis_element = self.__get_results_element(analysis_name) if analysis_element is not None: periods_element = self.__find_element(analysis_element, 'Extracted_Flow_Periods') if periods_element is not None: for period_element in self.__find_elements(periods_element, 'Extracted_Period'): element = self.__find_element(period_element, 'ProductionEventName') if element is not None and element.text == period_name: value_element = self.__find_element(period_element, value_name) if value_element is None: raise ResultsXMLParsingException('Saphir document results does not contain <{}> value in <{}> flow period'.format(value_name, period_name)) value_element_text = value_element.text if value_element_text is None: raise ResultsXMLParsingException('Saphir document results does not contain <{}> value in <{}> flow period'.format(value_name, period_name)) return value_element_text raise ResultsXMLParsingException('Saphir document results does not contain <{}> value in <{}> flow period'.format(value_name, period_name))