def getResultsRange(self):
        """Returns the valid result range for this analysis duplicate, based on
        both on the result and duplicate variation set in the original analysis

        A Duplicate will be out of range if its result does not match with the
        result for the parent analysis plus the duplicate variation in % as the
        margin error.
        :return: A dictionary with the keys min and max
        :rtype: dict
        """
        specs = ResultsRangeDict()
        analysis = self.getAnalysis()
        if not analysis:
            return specs

        result = analysis.getResult()
        if not api.is_floatable(result):
            return specs

        specs.min = specs.max = result
        result = api.to_float(result)
        dup_variation = analysis.getDuplicateVariation()
        dup_variation = api.to_float(dup_variation)
        if not dup_variation:
            return specs

        margin = abs(result) * (dup_variation / 100.0)
        specs.min = str(result - margin)
        specs.max = str(result + margin)
        return specs
Esempio n. 2
0
    def validate_service(self, request, uid):
        """Validates the specs values from request for the service uid. Returns
        a message if the validation failed
        """
        spec_min = get_record_value(request, uid, "min")
        spec_max = get_record_value(request, uid, "max")
        min_panic = get_record_value(request, uid, "min_panic")
        max_panic = get_record_value(request, uid, "max_panic")

        if not min_panic and not max_panic:
            # Neither min_panic nor max_panic values are set, dismiss
            return None

        if min_panic:
            if not api.is_floatable(min_panic):
                return _("'{}' value must be numeric or empty").format(
                    _("Min panic"))

            if api.to_float(min_panic) > api.to_float(spec_min):
                return _("'{}' value must be below '{}' or empty").format(
                    _("Min panic"), _("Min"))

        if max_panic:
            if not api.is_floatable(max_panic):
                return _("'{}' value must be numeric or empty").format(
                    _("Max panic"))

            if api.to_float(max_panic) < api.to_float(spec_max):
                return _("'{}' value must be above '{}' or empty").format(
                    _("Max panic"), _("Max"))

        return None
Esempio n. 3
0
    def validate_service(self, request, uid):
        """Validates the specs values from request for the service uid. Returns
        a non-translated message if the validation failed."""

        result = get_record_value(request, uid, 'result')
        if not result:
            # No result set for this service, dismiss
            return None

        if not api.is_floatable(result):
            return "Expected result value must be numeric"

        spec_min = get_record_value(request, uid, "min", result)
        spec_max = get_record_value(request, uid, "max", result)
        error = get_record_value(request, uid, "error", "0")
        if not api.is_floatable(spec_min):
            return "'Min' value must be numeric"
        if not api.is_floatable(spec_max):
            return "'Max' value must be numeric"
        if api.to_float(spec_min) > api.to_float(result):
            return "'Min' value must be below the expected result"
        if api.to_float(spec_max) < api.to_float(result):
            return "'Max' value must be above the expected result"
        if not api.is_floatable(error) or 0.0 < api.to_float(error) > 100:
            return "% Error must be between 0 and 100"
        return None
Esempio n. 4
0
    def validate_service(self, request, uid):
        """Validates the specs values from request for the service uid. Returns
        a non-translated message if the validation failed.
        """
        spec_min = get_record_value(request, uid, "min")
        spec_max = get_record_value(request, uid, "max")
        error = get_record_value(request, uid, "error", "0")
        warn_min = get_record_value(request, uid, "warn_min")
        warn_max = get_record_value(request, uid, "warn_max")

        if not spec_min and not spec_max:
            # Neither min nor max values have been set, dismiss
            return None

        if not api.is_floatable(spec_min):
            return "'Min' value must be numeric"
        if not api.is_floatable(spec_max):
            return "'Max' value must be numeric"
        if api.to_float(spec_min) > api.to_float(spec_max):
            return "'Max' value must be above 'Min' value"
        if not api.is_floatable(error) or 0.0 < api.to_float(error) > 100:
            return "% Error must be between 0 and 100"

        if warn_min:
            if not api.is_floatable(warn_min):
                return "'Warn Min' value must be numeric or empty"
            if api.to_float(warn_min) > api.to_float(spec_min):
                return "'Warn Min' value must be below 'Min' value"

        if warn_max:
            if not api.is_floatable(warn_max):
                return "'Warn Max' value must be numeric or empty"
            if api.to_float(warn_max) < api.to_float(spec_max):
                return "'Warn Max' value must be above 'Max' value"
        return None
Esempio n. 5
0
 def format_price(self, price):
     """Formats the price with the set decimal mark and currency
     """
     # ensure we have a float
     price = api.to_float(price, default=0.0)
     dm = self.get_decimal_mark()
     cur = self.get_currency_symbol()
     price = "%s %.2f" % (cur, price)
     return price.replace(".", dm)
    def getResultsRange(self):
        """Returns the valid result range for this analysis duplicate, based on
        both on the result and duplicate variation set in the original analysis

        A Duplicate will be out of range if its result does not match with the
        result for the parent analysis plus the duplicate variation in % as the
        margin error.

        If the duplicate is from an analysis with result options and/or string
        results enabled (with non-numeric value), returns an empty result range

        :return: A dictionary with the keys min and max
        :rtype: dict
        """
        # Get the original analysis
        original_analysis = self.getAnalysis()
        if not original_analysis:
            logger.warn("Orphan duplicate: {}".format(repr(self)))
            return {}

        # Return empty if results option enabled (exact match expected)
        if original_analysis.getResultOptions():
            return {}

        # Return empty if non-floatable (exact match expected)
        original_result = original_analysis.getResult()
        if not api.is_floatable(original_result):
            return {}

        # Calculate the min/max based on duplicate variation %
        specs = ResultsRangeDict(uid=self.getServiceUID())
        dup_variation = original_analysis.getDuplicateVariation()
        dup_variation = api.to_float(dup_variation, default=0)
        if not dup_variation:
            # We expect an exact match
            specs.min = specs.max = original_result
            return specs

        original_result = api.to_float(original_result)
        margin = abs(original_result) * (dup_variation / 100.0)
        specs.min = str(original_result - margin)
        specs.max = str(original_result + margin)
        return specs
Esempio n. 7
0
 def get_listing_view_adapters(self):
     """Returns subscriber adapters used to modify the listing behavior,
     sorted from higher to lower priority
     """
     # Allows to override this listing by multiple subscribers without the
     # need of inheritance. We use subscriber adapters here because we need
     # different add-ons to be able to modify columns, etc. without
     # dependencies amongst them.
     adapters = subscribers((self, self.context), IListingViewAdapter)
     return sorted(adapters, key=lambda ad: api.to_float(
         getattr(ad, "priority_order", 1000)))
Esempio n. 8
0
def remove_error_subfield_from_analysis_specs(portal, ut):
    # Update Analysis Specifications
    catalog = api.get_tool('bika_setup_catalog')
    brains = catalog(portal_type='AnalysisSpec')
    for brain in brains:
        specs = api.get_object(brain)
        specs_rr = specs.getResultsRange()
        for spec in specs_rr:
            min = spec.get('min', '')
            max = spec.get('max', '')
            error = api.to_float(spec.get('error', '0'), 0)
            if api.is_floatable(min) and api.is_floatable(max) and error > 0:
                # Add min_warn and max_warn fields
                min = api.to_float(min)
                max = api.to_float(max)
                warn_min = min - (abs(min) * (error/100.0))
                warn_max = max + (abs(max) * (error/100.0))
                spec['warn_min'] = str(warn_min)
                spec['warn_max'] = str(warn_max)
                del spec['error']
        specs.setResultsRange(specs_rr)
Esempio n. 9
0
    def add_analysis(self, analysis):
        """Adds an analysis to be consumed by the Analyses Chart machinery (js)

        :param analysis_object: analysis to be rendered in the chart
        """
        analysis_object = api.get_object(analysis)
        result = analysis_object.getResult()
        results_range = analysis_object.getResultsRange()
        range_result = results_range.get('result', None)
        range_min = results_range.get('min', None)
        range_max = results_range.get('max', None)
        # All them must be floatable
        for value in [result, range_result, range_min, range_max]:
            if not api.is_floatable(value):
                return
        cap_date = analysis_object.getResultCaptureDate()
        cap_date = api.is_date(cap_date) and \
                   cap_date.strftime('%Y-%m-%d %I:%M %p') or ''
        if not cap_date:
            return

        # Create json
        ref_sample_id = analysis_object.getSample().getId()
        as_keyword = analysis_object.getKeyword()
        as_name = analysis_object.Title()
        as_ref = '{} ({})'.format(as_name, as_keyword)
        as_rows = self.analyses_dict.get(as_ref, {})
        an_rows = as_rows.get(ref_sample_id, [])
        an_rows.append({
            'date': cap_date,
            'target': api.to_float(range_result),
            'upper': api.to_float(range_max),
            'lower': api.to_float(range_min),
            'result': api.to_float(result),
            'unit': analysis_object.getUnit(),
            'id': api.get_uid(analysis_object)
        })
        as_rows[ref_sample_id] = an_rows
        self.analyses_dict[as_ref] = as_rows
Esempio n. 10
0
    def get_result(self, column_name, result, line):
        result = str(result)
        if result.startswith('--') or result == '' or result == 'ND':
            return 0.0

        if api.is_floatable(result):
            result = api.to_float(result)
            return result > 0.0 and result or 0.0
        self.err("No valid number ${result} in column (${column_name})",
                 mapping={"result": result,
                          "column_name": column_name},
                 numline=self._numline, line=line)
        return
Esempio n. 11
0
    def validate_service(self, request, uid):
        """Validates the specs values from request for the service uid. Returns
        a non-translated message if the validation failed.
        """
        logger.info("Validating......")
        message = BaseValidator.validate_service(self, request, uid)
        if message:
            # Somehow, failed a validation for one or more of the default
            # range fields (min, max, error, warn_min, warn_max)
            return message

        spec_min = get_record_value(request, uid, "min")
        spec_max = get_record_value(request, uid, "max")
        min_panic = get_record_value(request, uid, "minpanic")
        max_panic = get_record_value(request, uid, "maxpanic")

        # minpanic must be below min and below maxpanic
        if not min_panic and not max_panic:
            # Neither min_panic nor max_panic values are set, dismiss
            return None

        if min_panic:
            if not api.is_floatable(min_panic):
                return _b("'{}' value must be numeric or empty").format(
                    _("Min panic"))
            if api.to_float(min_panic) > api.to_float(spec_min):
                return _b("'{}' value must be below '{}").format(
                    _("Min panic"), _("Min"))

        if max_panic:
            if not api.is_floatable(min_panic):
                return _b("'{}' value must be numeric or empty").format(
                    _("Max panic"))
            if api.to_float(min_panic) > api.to_float(spec_max):
                return _b("'{}' value must be above '{}").format(
                    _("Max panic"), _("Max"))

        return None
Esempio n. 12
0
    def isAboveUpperDetectionLimit(self):
        """Returns True if the result is above the Upper Detection Limit or
        if Upper Detection Limit has been manually set
        """
        if self.isUpperDetectionLimit():
            return True

        result = self.getResult()
        if result and str(result).strip().startswith(UDL):
            return True

        if api.is_floatable(result):
            return api.to_float(result) > self.getUpperDetectionLimit()

        return False
Esempio n. 13
0
    def isBelowLowerDetectionLimit(self):
        """Returns True if the result is below the Lower Detection Limit or
        if Lower Detection Limit has been manually set
        """
        if self.isLowerDetectionLimit():
            return True

        result = self.getResult()
        if result and str(result).strip().startswith(LDL):
            return True

        if api.is_floatable(result):
            return api.to_float(result) < self.getLowerDetectionLimit()

        return False
Esempio n. 14
0
    def __init__(self, name, request, context, *arg, **kw):
        super(QueueTask, self).__init__(*arg, **kw)
        if api.is_uid(context):
            context_uid = context
            context_path = kw.get("context_path")
            if not context_path:
                raise ValueError("context_path is missing")

        elif api.is_object(context):
            context_uid = api.get_uid(context)
            context_path = api.get_path(context)

        else:
            raise TypeError("No valid context object")

        # Set defaults
        kw = kw or {}
        task_uid = str(kw.get("task_uid", tmpID()))
        uids = map(str, kw.get("uids", []))
        created = api.to_float(kw.get("created"), default=time.time())
        status = kw.get("status", None)
        min_sec = api.to_int(kw.get("min_seconds"), default=get_min_seconds())
        max_sec = api.to_int(kw.get("max_seconds"), default=get_max_seconds())
        priority = api.to_int(kw.get("priority"), default=10)
        retries = api.to_int(kw.get("retries"), default=get_max_retries())
        unique = self._is_true(kw.get("unique", False))
        chunks = api.to_int(kw.get("chunk_size"), default=get_chunk_size(name))
        username = kw.get("username", self._get_authenticated_user(request))
        err_message = kw.get("error_message", None)

        self.update({
            "task_uid": task_uid,
            "name": name,
            "context_uid": context_uid,
            "context_path": context_path,
            "uids": uids,
            "created": created,
            "status": status and str(status) or None,
            "error_message": err_message and str(err_message) or None,
            "min_seconds": min_sec,
            "max_seconds": max_sec,
            "priority": priority,
            "retries": retries,
            "unique": unique,
            "chunk_size": chunks,
            "username": str(username),
        })
Esempio n. 15
0
    def __call__(self, value, *args, **kwargs):
        # Result Value must be floatable
        if not api.is_floatable(value):
            return _t(_("Result Value must be a number"))

        # Get all records
        instance = kwargs['instance']
        field_name = kwargs['field'].getName()
        request = instance.REQUEST
        records = request.form.get(field_name)

        # Result values must be unique
        value = api.to_float(value)
        values = map(lambda ro: ro.get("ResultValue"), records)
        values = filter(api.is_floatable, values)
        values = map(api.to_float, values)
        duplicates = filter(lambda val: val == value, values)
        if len(duplicates) > 1:
            return _t(_("Result Value must be unique"))

        return True
Esempio n. 16
0
    def extractCredentials(self, request):  # noqa camelCase
        """IExtractionPlugin implementation. Extracts login name from the
        request's "X-Queue-Auth-Token" header. This header contains an
        encrypted token with it's expiration date, together with the user name.

        Returns a dict with {'login': <username>} if:
        - current layer is ISenaiteQueueLayer,
        - the token can be decrypted,
        - the decrypted token contains both expiration and username and
        - the the token has not expired (expiration date)

        Returns an empty dict otherwise
        :param request: the HTTPRequest object to extract credentials from
        :return: a dict {"login": <username>} or empty dict
        """
        # Check if request provides ISenaiteQueueLayer
        if not ISenaiteQueueLayer.providedBy(request):
            return {}

        # Read the magical header that contains the encrypted info
        auth_token = request.getHeader("X-Queue-Auth-Token")
        if not auth_token:
            return {}

        # Decrypt the auth_token
        key = api.get_registry_record("senaite.queue.auth_key")
        token = Fernet(str(key)).decrypt(auth_token)

        # Check if token is valid
        tokens = token.split(":")
        if len(tokens) < 2 or not api.is_floatable(tokens[0]):
            return {}

        # Check if token has expired
        expiration = api.to_float(tokens[0])
        if expiration < time.time():
            return {}

        user_id = "".join(tokens[1:])
        return {"login": user_id}
Esempio n. 17
0
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
Esempio n. 18
0
def to_float_or_none(value):
    """Returns the float if the value is floatable. Otherwise, returns None
    """
    if api.is_floatable(value):
        return api.to_float(value)
    return None
Esempio n. 19
0
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