def get_formatted_interval(results_range, default=_marker): """Returns a string representation of the interval defined by the results range passed in :param results_range: a dict or a ResultsRangeDict """ if not isinstance(results_range, Mapping): if default is not _marker: return default api.fail("Type not supported") results_range = ResultsRangeDict(results_range) min_str = results_range.min if api.is_floatable(results_range.min) else None max_str = results_range.max if api.is_floatable(results_range.max) else None if min_str is None and max_str is None: if default is not _marker: return default api.fail("Min and max values are not floatable or not defined") min_operator = results_range.min_operator max_operator = results_range.max_operator if max_str is None: return "{}{}".format(MIN_OPERATORS.getValue(min_operator), min_str) if min_str is None: return "{}{}".format(MAX_OPERATORS.getValue(max_operator), max_str) # Both values set. Return an interval min_bracket = min_operator == 'geq' and '[' or '(' max_bracket = max_operator == 'leq' and ']' or ')' return "{}{};{}{}".format(min_bracket, min_str, max_str, max_bracket)
def get_field_value(instance, field_name, default=_marker): """Returns the value of a Schema field from the instance passed in """ instance = api.get_object(instance) field = instance.Schema() and instance.Schema().getField(field_name) or None if not field: if default is not _marker: return default api.fail("No field {} found for {}".format(field_name, repr(instance))) return instance.Schema().getField(field_name).get(instance)
def set_field_value(instance, field_name, value): """Sets the value to a Schema field """ if field_name == "id": logger.warn("Assignment of id is not allowed") return logger.info("Field {} = {}".format(field_name, repr(value))) instance = api.get_object(instance) field = instance.Schema() and instance.Schema().getField(field_name) or None if not field: api.fail("No field {} found for {}".format(field_name, repr(instance))) field.set(instance, value)
def get_age(datetime_from, datetime_to=None): """Returns the elapsed time in years, months and days between the two dates passed in.""" if not datetime_to: datetime_to = DateTime() if not bapi.is_date(datetime_from) or not bapi.is_date(datetime_to): bapi.fail("Only DateTime and datetype types are supported") dfrom = DT2dt(bapi.to_date(datetime_from)).replace(tzinfo=None) dto = DT2dt(bapi.to_date(datetime_to)).replace(tzinfo=None) diff = relativedelta(dto, dfrom) return (diff.years, diff.months, diff.days)
def get_obj_from_field(instance, fieldname, default=_marker): """ Get an object from a Reference Field of any instance :param fieldname: Reference Field Name :return: """ if not fieldname: return default field = instance.getField(fieldname, None) if not field: if default is not _marker: return default api.fail("{} doesn't have field called {} ".format( repr(instance), fieldname)) return field.get(instance)
def get_obj_from_field(instance, fieldname, default=_marker): """ Get an object from a Reference Field of any instance :param fieldname: Reference Field Name :return: """ if not fieldname: return default field = instance.getField(fieldname, None) if not field: if default is not _marker: return default api.fail("{} doesn't have field called {} ".format(repr(instance), fieldname)) return field.get(instance)
def get_xls_specifications(): """Returns the specifications from the xlsx file""" worksheet_name = "Analysis Specifications" curr_dirname = os.path.dirname(os.path.abspath(__file__)) filename = "{}/resources/results_ranges.xlsx".format(curr_dirname) workbook = load_workbook(filename=filename) worksheet = workbook.get_sheet_by_name("Analysis Specifications") if not worksheet: api.fail("Worksheet '{}' not found".format(worksheet_name)) columns = [] raw_specifications = list() for row in worksheet.rows: cell_values = map(lambda cell: cell.value, row) if not columns: columns = cell_values continue raw_specifications.append(dict(zip(columns, cell_values))) return raw_specifications
def to_age_str(years=0, months=0, days=0): """Returns a string representation of an age """ if not bapi.is_floatable(years): bapi.fail("Years are not floatable") if not bapi.is_floatable(months): bapi.fail("Months are not floatabla") if not bapi.is_floatable(days): bapi.fail("Days are not floatable") age_arr = list() if years: age_arr.append("{}y".format(years)) if months: age_arr.append("{}m".format(months)) if days: age_arr.append("{}d".format(days)) return ' '.join(age_arr)
def get_specification_for(spec, default=_marker): """Returns a plain dictionary with specification values (min, max, etc.) It looks through an excel file provided as-is to find the record that better fits with the gender and age from the analysis request and for the analysis passed in :param: Analysis object or analysis uid or analysis brain """ analysis = api.get_object_by_uid(spec.get(('analysis_uid'))) if not analysis or not IRequestAnalysis.providedBy(analysis): if default is not _marker: return default api.fail("Type {} not supported: ".format(repr(analysis))) request = analysis.getRequest() gender = _api.get_field_value(request, "Gender") if not gender or gender.lower() not in GENDERS.keys(): # If no gender is specified or not a valid value, assume any gender = 'a' dob = _api.get_field_value(request, "DateOfBirth") sampled = request.getDateSampled() if not dob or not sampled: if default is not _marker: return default api.fail("No DateSampled or Date of Birth set") specification = request.getSpecification() if not specification: # This should never happen, Since this function has been triggered, we # assume an specification has been set to the AR if default is not _marker: return default api.fail("Specification not set for request {}".format(request.id)) years, months, days = _api.get_age(dob, sampled) return get_analysisspec(analysis_keyword=analysis.getKeyword(), gender=gender, years=years, months=months, days=days)
def is_out_of_range(brain_or_object, result=_marker): """Checks if the result for the analysis passed in is out of range and/or out of shoulders range. min max warn min max warn ·········|---------------|=====================|---------------|········· ----- out-of-range -----><----- in-range ------><----- out-of-range ----- <-- shoulder --><----- in-range ------><-- shoulder --> :param brain_or_object: A single catalog brain or content object :param result: Tentative result. If None, use the analysis result :type brain_or_object: ATContentType/DexterityContentType/CatalogBrain :returns: Tuple of two elements. The first value is `True` if the result is out of range and `False` if it is in range. The second value is `True` if the result is out of shoulder range and `False` if it is in shoulder range :rtype: (bool, bool) """ analysis = api.get_object(brain_or_object) if not IAnalysis.providedBy(analysis) and \ not IReferenceAnalysis.providedBy(analysis): api.fail("{} is not supported. Needs to be IAnalysis or " "IReferenceAnalysis".format(repr(analysis))) if result is _marker: result = api.safe_getattr(analysis, "getResult", None) if result in [None, '']: # Empty result return False, False if IDuplicateAnalysis.providedBy(analysis): # Result range for duplicate analyses is calculated from the original # result, applying a variation % in shoulders. If the analysis has # result options enabled or string results enabled, system returns an # empty result range for the duplicate: result must match %100 with the # original result original = analysis.getAnalysis() original_result = original.getResult() # Does original analysis have a valid result? if original_result in [None, '']: return False, False # Does original result type matches with duplicate result type? if api.is_floatable(result) != api.is_floatable(original_result): return True, True # Does analysis has result options enabled or non-floatable? if analysis.getResultOptions() or not api.is_floatable(original_result): # Let's always assume the result is 'out from shoulders', cause we # consider the shoulders are precisely the duplicate variation % out_of_range = original_result != result return out_of_range, out_of_range elif not api.is_floatable(result): # A non-duplicate with non-floatable result. There is no chance to know # if the result is out-of-range return False, False # Convert result to a float result = api.to_float(result) # Note that routine analyses, duplicates and reference analyses all them # implement the function getResultRange: # - For routine analyses, the function returns the valid range based on the # specs assigned during the creation process. # - For duplicates, the valid range is the result of the analysis the # the duplicate was generated from +/- the duplicate variation. # - For reference analyses, getResultRange returns the valid range as # indicated in the Reference Sample from which the analysis was created. result_range = api.safe_getattr(analysis, "getResultsRange", None) if not result_range: # No result range defined or the passed in object does not suit return False, False # Maybe there is a custom adapter adapters = getAdapters((analysis,), IResultOutOfRange) for name, adapter in adapters: ret = adapter(result=result, specification=result_range) if not ret or not ret.get('out_of_range', False): continue if not ret.get('acceptable', True): # Out of range + out of shoulders return True, True # Out of range, but in shoulders return True, False result_range = ResultsRangeDict(result_range) # The assignment of result as default fallback for min and max guarantees # the result will be in range also if no min/max values are defined specs_min = api.to_float(result_range.min, result) specs_max = api.to_float(result_range.max, result) in_range = False min_operator = result_range.min_operator if min_operator == "geq": in_range = result >= specs_min else: in_range = result > specs_min max_operator = result_range.max_operator if in_range: if max_operator == "leq": in_range = result <= specs_max else: in_range = result < specs_max # If in range, no need to check shoulders if in_range: return False, False # Out of range, check shoulders. If no explicit warn_min or warn_max have # been defined, no shoulders must be considered for this analysis. Thus, use # specs' min and max as default fallback values warn_min = api.to_float(result_range.warn_min, specs_min) warn_max = api.to_float(result_range.warn_max, specs_max) in_shoulder = warn_min <= result <= warn_max return True, not in_shoulder
def is_out_of_range(brain_or_object, result=_marker): """Checks if the result for the analysis passed in is out of range and/or out of shoulders range. min max warn min max warn ·········|---------------|=====================|---------------|········· ----- out-of-range -----><----- in-range ------><----- out-of-range ----- <-- shoulder --><----- in-range ------><-- shoulder --> :param brain_or_object: A single catalog brain or content object :param result: Tentative result. If None, use the analysis result :type brain_or_object: ATContentType/DexterityContentType/CatalogBrain :returns: Tuple of two elements. The first value is `True` if the result is out of range and `False` if it is in range. The second value is `True` if the result is out of shoulder range and `False` if it is in shoulder range :rtype: (bool, bool) """ analysis = api.get_object(brain_or_object) if not IAnalysis.providedBy(analysis) and \ not IReferenceAnalysis.providedBy(analysis): api.fail("{} is not supported. Needs to be IAnalysis or " "IReferenceAnalysis".format(repr(analysis))) if result is _marker: result = api.safe_getattr(analysis, "getResult", None) if not api.is_floatable(result): # Result is empty/None or not a valid number return False, False result = api.to_float(result) # Note that routine analyses, duplicates and reference analyses all them # implement the function getResultRange: # - For routine analyses, the function returns the valid range based on the # specs assigned during the creation process. # - For duplicates, the valid range is the result of the analysis the # the duplicate was generated from +/- the duplicate variation. # - For reference analyses, getResultRange returns the valid range as # indicated in the Reference Sample from which the analysis was created. result_range = api.safe_getattr(analysis, "getResultsRange", None) if not result_range: # No result range defined or the passed in object does not suit return False, False # Maybe there is a custom adapter adapters = getAdapters((analysis, ), IResultOutOfRange) for name, adapter in adapters: ret = adapter(result=result, specification=result_range) if not ret or not ret.get('out_of_range', False): continue if not ret.get('acceptable', True): # Out of range + out of shoulders return True, True # Out of range, but in shoulders return True, False # The assignment of result as default fallback for min and max guarantees # the result will be in range also if no min/max values are defined specs_min = api.to_float(result_range.get('min', result), result) specs_max = api.to_float(result_range.get('max', result), result) if specs_min <= result <= specs_max: # In range, no need to check shoulders return False, False # Out of range, check shoulders. If no explicit warn_min or warn_max have # been defined, no shoulders must be considered for this analysis. Thus, use # specs' min and max as default fallback values warn_min = api.to_float(result_range.get('warn_min', specs_min), specs_min) warn_max = api.to_float(result_range.get('warn_max', specs_max), specs_max) in_shoulder = warn_min <= result <= warn_max return True, not in_shoulder