def process_form(self, instance, field, form, empty_marker = None, emptyReturnsMarker = False):
        """ Return a list of dictionaries fit for AnalysisSpecsResultsField
            consumption. If neither hidemin nor hidemax are specified, only
            services which have float()able entries in result,min and max field
            will be included. If hidemin and/or hidemax specified, results
            might contain empty min and/or max fields.
        """
        value = []
        if 'service' in form:
            for uid, keyword in form['keyword'][0].items():

                hidemin = form['hidemin'][0].get(uid, '') if 'hidemin' in form else ''
                hidemax = form['hidemax'][0].get(uid, '') if 'hidemax' in form else ''
                mins = form['min'][0].get(uid, '') if 'min' in form else ''
                maxs = form['max'][0].get(uid, '') if 'max' in form else ''
                err = form['error'][0].get(uid, '') if 'error' in form else ''
                rangecomment = form['rangecomment'][0].get(uid, '') if 'rangecomment' in form else ''

                if not isnumber(hidemin) and not isnumber(hidemax) and \
                   (not isnumber(mins) or not isnumber(maxs)):
                    # If neither hidemin nor hidemax have been specified,
                    # min and max values are mandatory.
                    continue

                value.append({'keyword': keyword,
                              'uid': uid,
                              'min': mins if isnumber(mins) else '',
                              'max': maxs if isnumber(maxs) else '',
                              'hidemin': hidemin if isnumber(hidemin) else '',
                              'hidemax': hidemax if isnumber(hidemax) else '',
                              'error': err if isnumber(err) else '0',
                              'rangecomment': rangecomment})
        return value, {}
    def process_form(self, instance, field, form, empty_marker = None, emptyReturnsMarker = False):
        """ Return a list of dictionaries fit for AnalysisSpecsResultsField
            consumption. If neither hidemin nor hidemax are specified, only
            services which have float()able entries in result,min and max field
            will be included. If hidemin and/or hidemax specified, results
            might contain empty min and/or max fields.
        """
        value = []
        if 'service' in form:
            for uid, keyword in form['keyword'][0].items():

                hidemin = form['hidemin'][0].get(uid, '') if 'hidemin' in form else ''
                hidemax = form['hidemax'][0].get(uid, '') if 'hidemax' in form else ''
                mins = form['min'][0].get(uid, '') if 'min' in form else ''
                maxs = form['max'][0].get(uid, '') if 'max' in form else ''
                err = form['error'][0].get(uid, '') if 'error' in form else ''
                rangecomment = form['rangecomment'][0].get(uid, '') if 'rangecomment' in form else ''

                if not isnumber(hidemin) and not isnumber(hidemax) and \
                   (not isnumber(mins) or not isnumber(maxs)):
                    # If neither hidemin nor hidemax have been specified,
                    # min and max values are mandatory.
                    continue

                value.append({'keyword': keyword,
                              'uid': uid,
                              'min': mins if isnumber(mins) else '',
                              'max': maxs if isnumber(maxs) else '',
                              'hidemin': hidemin if isnumber(hidemin) else '',
                              'hidemax': hidemax if isnumber(hidemax) else '',
                              'error': err if isnumber(err) else '0',
                              'rangecomment': rangecomment})
        return value, {}
Example #3
0
def _check_conditions(instance, conditions):
    uc = getToolByName(instance, 'uid_catalog')
    for condition in conditions:
        range0 = condition.get('range0', None)
        range1 = condition.get('range1', None)
        discreteresult = condition.get('discreteresult', None)
        analysisservice = condition.get('analysisservice', None)
        and_or = condition.get('and_or', 'no')
        cond_row_idx = condition.get('cond_row_idx', None)
        as_brain = uc(UID=analysisservice)
        if (not discreteresult and (not range0 or not range1)) or \
                (discreteresult and range0 and range1):
            logger.warn(
                _('If range values are empty, discreteresult must contain a '
                  'value, and if discreteresult has a value, ranges must be '
                  'empty. But ranges or discreteresult must conatin a value.'
                  'The given values are: '
                  'discreteresult: %s, range0: %s, range1: %s' %
                  (discreteresult, range0, range1)))
            return False
        if range1 and not (isnumber(range1)):
            logger.warn('The range must be a number. Now its value is: '
                        '%s' % (range1))
            return False
        if range0 and not (isnumber(range0)):
            logger.warn('The range must be a number. Now its value is: '
                        '%s' % (range0))
            return False
        if not as_brain:
            logger.warn('Not correct analysis service with UID. %s' %
                        (analysisservice))
            return False
        if and_or not in ['and', 'or', 'no']:
            logger.warn('Not correct and_or value')
            return False
        if cond_row_idx and not (isnumber(cond_row_idx)):
            logger.warn('The cond_row_idx must be a number. Now its value is: '
                        '%s' % (cond_row_idx))
            return False
    return True
 def process_form(self, instance, field, form, 
                  empty_marker=None, emptyReturnsMarker=False):
     values = BaseWidget.process_form(self, instance, field, form,
                                      empty_marker, emptyReturnsMarker)
     keys = ['minpanic', 'maxpanic']
     for i in range(len(values)):
         for j in range(len(values[i])):
             uid = values[i][j]['uid']
             for key in keys:
                 keyval = form[key][0].get(uid, '') if key in form else ''
                 keyval = keyval if isnumber(keyval) else ''
                 values[i][j][key] = keyval
     return values
Example #5
0
def _check_actions(instance, actions):
    uc = getToolByName(instance, 'uid_catalog')
    for action in actions:
        act_row_idx = action.get('act_row_idx', '0')
        action_name = action.get('action', '')
        an_result_id = action.get('an_result_id', '')
        analyst = action.get('analyst', '')
        otherWS = action.get('otherWS', 'current')
        setresultdiscrete = action.get('setresultdiscrete', '')
        setresulton = action.get('setresulton', '')
        setresultvalue = action.get('setresultvalue', '')
        worksheettemplate = action.get('worksheettemplate', '')
        wt_brain = uc(UID=worksheettemplate)
        if act_row_idx and not (isnumber(act_row_idx)):
            logger.warn('The act_row_idx must be a number. Now its value is: '
                        '%s' % (act_row_idx))
            return False
        if action_name not in [
                'repeat', 'duplicate', 'setresult', 'setvisibility'
        ]:
            logger.warn('Not correct action_name value')
            return False
        if otherWS not in ['current', 'to_another', 'create_another', 'no_ws']:
            logger.warn('Not correct otherWS value')
            return False
        if setresulton not in ['original', 'bew']:
            logger.warn('Not correct setresulton value')
            return False
        if setresultvalue and not (isnumber(setresultvalue)):
            logger.warn(
                'The setresultvalue must be a number. Now its value is: '
                '%s' % (setresultvalue))
            return False
        if worksheettemplate and not wt_brain:
            logger.warn('Not correct worksheet template with UID. %s' %
                        (worksheettemplate))
            return False
    return True
 def process_form(self,
                  instance,
                  field,
                  form,
                  empty_marker=None,
                  emptyReturnsMarker=False):
     values = BaseWidget.process_form(self, instance, field, form,
                                      empty_marker, emptyReturnsMarker)
     keys = ['minpanic', 'maxpanic']
     for i in range(len(values)):
         for j in range(len(values[i])):
             uid = values[i][j]['uid']
             for key in keys:
                 keyval = form[key][0].get(uid, '') if key in form else ''
                 keyval = keyval if isnumber(keyval) else ''
                 values[i][j][key] = keyval
     return values
    def process_form(self, instance, field, form, empty_marker=None, emptyReturnsMarker=False):
        """ Return a list of dictionaries fit for AnalysisSpecsResultsField
            consumption. If neither hidemin nor hidemax are specified, only
            services which have float()able entries in result,min and max field
            will be included. If hidemin and/or hidemax specified, results
            might contain empty min and/or max fields.
        """
        value = []
        if "service" in form:
            for uid, keyword in form["keyword"][0].items():

                hidemin = form["hidemin"][0].get(uid, "") if "hidemin" in form else ""
                hidemax = form["hidemax"][0].get(uid, "") if "hidemax" in form else ""
                mins = form["min"][0].get(uid, "") if "min" in form else ""
                maxs = form["max"][0].get(uid, "") if "max" in form else ""
                err = form["error"][0].get(uid, "") if "error" in form else ""
                rangecomment = form["rangecomment"][0].get(uid, "") if "rangecomment" in form else ""

                if not isnumber(hidemin) and not isnumber(hidemax) and (not isnumber(mins) or not isnumber(maxs)):
                    # If neither hidemin nor hidemax have been specified,
                    # min and max values are mandatory.
                    continue

                value.append(
                    {
                        "keyword": keyword,
                        "uid": uid,
                        "min": mins if isnumber(mins) else "",
                        "max": maxs if isnumber(maxs) else "",
                        "hidemin": hidemin if isnumber(hidemin) else "",
                        "hidemax": hidemax if isnumber(hidemax) else "",
                        "error": err if isnumber(err) else "0",
                        "rangecomment": rangecomment,
                    }
                )
        return value, {}
Example #8
0
    def _analysis_data(self, analysis, decimalmark=None):
        keyword = analysis.getKeyword()
        service = analysis.getService()
        andict = {'obj': analysis,
                  'id': analysis.id,
                  'title': analysis.Title(),
                  'keyword': keyword,
                  'scientific_name': service.getScientificName(),
                  'accredited': service.getAccredited(),
                  'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())),
                  'category': to_utf8(service.getCategoryTitle()),
                  'result': analysis.getResult(),
                  'isnumber': isnumber(analysis.getResult()),
                  'unit': to_utf8(service.getUnit()),
                  'formatted_unit': format_supsub(to_utf8(service.getUnit())),
                  'capture_date': analysis.getResultCaptureDate(),
                  'request_id': analysis.aq_parent.getId(),
                  'formatted_result': '',
                  'uncertainty': analysis.getUncertainty(),
                  'formatted_uncertainty': '',
                  'retested': analysis.getRetested(),
                  'remarks': to_utf8(analysis.getRemarks()),
                  'resultdm': to_utf8(analysis.getResultDM()),
                  'outofrange': False,
                  'type': analysis.portal_type,
                  'reftype': analysis.getReferenceType() \
                            if hasattr(analysis, 'getReferenceType')
                            else None,
                  'worksheet': None,
                  'specs': {},
                  'formatted_specs': ''}

        if analysis.portal_type == 'DuplicateAnalysis':
            andict['reftype'] = 'd'

        ws = analysis.getBackReferences('WorksheetAnalysis')
        andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None
        andict['worksheet_url'] = ws[0].absolute_url if ws and len(ws) > 0 else None
        andict['refsample'] = analysis.getSample().id \
                            if analysis.portal_type == 'Analysis' \
                            else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title())

        if analysis.portal_type == 'ReferenceAnalysis':
            # The analysis is a Control or Blank. We might use the
            # reference results instead other specs
            uid = analysis.getServiceUID()
            specs = analysis.aq_parent.getResultsRangeDict().get(uid, {})

        else:
            # Get the specs directly from the analysis. The getResultsRange
            # function already takes care about which are the specs to be used:
            # AR, client or lab.
            specs = analysis.getResultsRange()

        andict['specs'] = specs
        scinot = self.context.bika_setup.getScientificNotationReport()
        andict['formatted_result'] = analysis.getFormattedResult(specs=specs, sciformat=int(scinot), decimalmark=decimalmark)

        fs = ''
        if specs.get('min', None) and specs.get('max', None):
            fs = '%s - %s' % (specs['min'], specs['max'])
        elif specs.get('min', None):
            fs = '> %s' % specs['min']
        elif specs.get('max', None):
            fs = '< %s' % specs['max']
        andict['formatted_specs'] = formatDecimalMark(fs, decimalmark)
        andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot))

        # Out of range?
        if specs:
            adapters = getAdapters((analysis, ), IResultOutOfRange)
            bsc = getToolByName(self.context, "bika_setup_catalog")
            for name, adapter in adapters:
                ret = adapter(specification=specs)
                if ret and ret['out_of_range']:
                    andict['outofrange'] = True
                    break
        return andict
Example #9
0
    def _analysis_data(self, analysis, decimalmark=None):
        if analysis.UID() in self._cache['_analysis_data']:
            return self._cache['_analysis_data'][analysis.UID()]

        keyword = analysis.getKeyword()
        service = analysis.getService()
        andict = {'obj': analysis,
                  'id': analysis.id,
                  'title': analysis.Title(),
                  'keyword': keyword,
                  'scientific_name': service.getScientificName(),
                  'accredited': service.getAccredited(),
                  'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())),
                  'category': to_utf8(service.getCategoryTitle()),
                  'result': analysis.getResult(),
                  'isnumber': isnumber(analysis.getResult()),
                  'unit': to_utf8(service.getUnit()),
                  'formatted_unit': format_supsub(to_utf8(service.getUnit())),
                  'capture_date': analysis.getResultCaptureDate(),
                  'request_id': analysis.aq_parent.getId(),
                  'formatted_result': '',
                  'uncertainty': analysis.getUncertainty(),
                  'formatted_uncertainty': '',
                  'retested': analysis.getRetested(),
                  'remarks': to_utf8(analysis.getRemarks()),
                  'resultdm': to_utf8(analysis.getResultDM()),
                  'outofrange': False,
                  'type': analysis.portal_type,
                  'reftype': analysis.getReferenceType() \
                            if hasattr(analysis, 'getReferenceType')
                            else None,
                  'worksheet': None,
                  'specs': {},
                  'formatted_specs': ''}

        if analysis.portal_type == 'DuplicateAnalysis':
            andict['reftype'] = 'd'

        ws = analysis.getBackReferences('WorksheetAnalysis')
        andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None
        andict['worksheet_url'] = ws[0].absolute_url if ws and len(ws) > 0 else None
        andict['refsample'] = analysis.getSample().id \
                            if analysis.portal_type == 'Analysis' \
                            else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title())

        if analysis.portal_type == 'ReferenceAnalysis':
            # The analysis is a Control or Blank. We might use the
            # reference results instead other specs
            uid = analysis.getServiceUID()
            specs = analysis.aq_parent.getResultsRangeDict().get(uid, {})

        else:
            # Get the specs directly from the analysis. The getResultsRange
            # function already takes care about which are the specs to be used:
            # AR, client or lab.
            specs = analysis.getResultsRange()

        andict['specs'] = specs
        scinot = self.context.bika_setup.getScientificNotationReport()
        fresult =  analysis.getFormattedResult(specs=specs, sciformat=int(scinot), decimalmark=decimalmark)

        # We don't use here cgi.encode because results fields must be rendered
        # using the 'structure' wildcard. The reason is that the result can be
        # expressed in sci notation, that may include <sup></sup> html tags.
        # Please note the default value for the 'html' parameter from
        # getFormattedResult signature is set to True, so the service will
        # already take into account LDLs and UDLs symbols '<' and '>' and escape
        # them if necessary.
        andict['formatted_result'] = fresult;

        fs = ''
        if specs.get('min', None) and specs.get('max', None):
            fs = '%s - %s' % (specs['min'], specs['max'])
        elif specs.get('min', None):
            fs = '> %s' % specs['min']
        elif specs.get('max', None):
            fs = '< %s' % specs['max']
        andict['formatted_specs'] = formatDecimalMark(fs, decimalmark)
        andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot))

        # Out of range?
        if specs:
            adapters = getAdapters((analysis, ), IResultOutOfRange)
            bsc = getToolByName(self.context, "bika_setup_catalog")
            for name, adapter in adapters:
                ret = adapter(specification=specs)
                if ret and ret['out_of_range']:
                    andict['outofrange'] = True
                    break
        self._cache['_analysis_data'][analysis.UID()]  = andict
        return andict
Example #10
0
    def calculate(self, uid=None):
        # TODO Move this function to api.analysis.calculate
        analysis = self.analyses[uid]
        form_result = self.current_results[uid]['result']
        calculation = analysis.getCalculation()
        if analysis.portal_type == 'ReferenceAnalysis':
            deps = {}
        else:
            deps = {}
            for dep in analysis.getDependencies():
                deps[dep.UID()] = dep
        path = '++resource++bika.lims.images'
        mapping = {}

        # values to be returned to form for this UID
        Result = {'uid': uid, 'result': form_result}
        try:
            Result['result'] = float(form_result)
        except:
            if form_result == "0/0":
                Result['result'] = ""

        if calculation:
            """We need first to create the map of available parameters
               acording to the interims, analyses and wildcards:

             params = {
                    <as-1-keyword>              : <analysis_result>,
                    <as-1-keyword>.<wildcard-1> : <wildcard_1_value>,
                    <as-1-keyword>.<wildcard-2> : <wildcard_2_value>,
                    <interim-1>                 : <interim_result>,
                    ...
                    }
            """

            # Get dependent analyses results and wildcard values to the
            # mapping. If dependent analysis without result found,
            # break and abort calculation
            unsatisfied = False
            for dependency_uid, dependency in deps.items():
                if dependency_uid in self.ignore_uids:
                    unsatisfied = True
                    break

                # LIMS-1769. Allow to use LDL and UDL in calculations.
                # https://jira.bikalabs.com/browse/LIMS-1769
                analysisvalues = {}
                if dependency_uid in self.current_results:
                    analysisvalues = self.current_results[dependency_uid]
                else:
                    # Retrieve the result and DLs from the analysis
                    analysisvalues = {
                        'keyword': dependency.getKeyword(),
                        'result': dependency.getResult(),
                        'ldl': dependency.getLowerDetectionLimit(),
                        'udl': dependency.getUpperDetectionLimit(),
                        'belowldl': dependency.isBelowLowerDetectionLimit(),
                        'aboveudl': dependency.isAboveUpperDetectionLimit(),
                    }
                if analysisvalues['result'] == '':
                    unsatisfied = True
                    break
                key = analysisvalues.get('keyword', dependency.getKeyword())

                # Analysis result
                # All result mappings must be float, or they are ignored.
                try:
                    mapping[key] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'RESULT')] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'LDL')] = float(analysisvalues.get('ldl'))
                    mapping['%s.%s' % (key, 'UDL')] = float(analysisvalues.get('udl'))
                    mapping['%s.%s' % (key, 'BELOWLDL')] = int(analysisvalues.get('belowldl'))
                    mapping['%s.%s' % (key, 'ABOVEUDL')] = int(analysisvalues.get('aboveudl'))
                except:
                    # If not floatable, then abort!
                    unsatisfied = True
                    break

            if unsatisfied:
                # unsatisfied means that one or more result on which we depend
                # is blank or unavailable, so we set blank result and abort.
                self.results.append({'uid': uid,
                                     'result': '',
                                     'formatted_result': ''})
                return None

            # Add all interims to mapping
            for i_uid, i_data in self.item_data.items():
                for i in i_data:
                    # if this interim belongs to current analysis and is blank,
                    # return an empty result for this analysis.
                    if i_uid == uid and i['value'] == '':
                        self.results.append({'uid': uid,
                                             'result': '',
                                             'formatted_result': ''})
                        return None
                    # All interims must be float, or they are ignored.
                    try:
                        i['value'] = float(i['value'])
                    except:
                        pass

                    # all interims are ServiceKeyword.InterimKeyword
                    if i_uid in deps:
                        key = "%s.%s" % (deps[i_uid].getKeyword(),
                                         i['keyword'])
                        mapping[key] = i['value']
                    # this analysis' interims get extra reference
                    # without service keyword prefix
                    if uid == i_uid:
                        mapping[i['keyword']] = i['value']

            # Grab values for hidden InterimFields for only for current calculation
            # we can't allow non-floats through here till we change the eval's
            # interpolation
            hidden_fields = []
            c_fields = calculation.getInterimFields()
            s_fields = analysis.getInterimFields()
            for field in c_fields:
                if field.get('hidden', False):
                    hidden_fields.append(field['keyword'])
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass
            # also grab stickier defaults from AnalysisService
            for field in s_fields:
                if field['keyword'] in hidden_fields:
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass

            # convert formula to a valid python string, ready for interpolation
            formula = calculation.getMinifiedFormula()
            formula = formula.replace('[', '%(').replace(']', ')f')
            try:
                formula = eval("'%s'%%mapping" % formula,
                               {"__builtins__": None,
                                'math': math,
                                'context': self.context},
                               {'mapping': mapping})
                # calculate
                result = eval(formula, calculation._getGlobals())
                Result['result'] = result
                self.current_results[uid]['result'] = result
            except TypeError as e:
                # non-numeric arguments in interim mapping?
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Type Error")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]
            except ZeroDivisionError as e:
                Result['result'] = '0/0'
                Result['formatted_result'] = '0/0'
                self.current_results[uid]['result'] = '0/0'
                self.results.append(Result)
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Division by zero")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]
                return None
            except KeyError as e:
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Key Error")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]

        # format result
        try:
            Result['formatted_result'] = format_numeric_result(analysis,
                                                               Result['result'])
        except ValueError:
            # non-float
            Result['formatted_result'] = Result['result']

        self.results.append(Result)

        # if App.config.getConfiguration().debug_mode:
        #     logger.info("calc.py: %s->%s %s" % (analysis.aq_parent.id,
        #                                         analysis.id,
        #                                         Result))

        # LIMS-1808 Uncertainty calculation on DL
        # https://jira.bikalabs.com/browse/LIMS-1808
        flres = Result.get('result', None)
        if flres and isnumber(flres):
            flres = float(flres)
            anvals = self.current_results[uid]
            isldl = anvals.get('isldl', False)
            isudl = anvals.get('isudl', False)
            ldl = anvals.get('ldl', 0)
            udl = anvals.get('udl', 0)
            ldl = float(ldl) if isnumber(ldl) else 0
            udl = float(udl) if isnumber(udl) else 10000000
            belowldl = (isldl or flres < ldl)
            aboveudl = (isudl or flres > udl)
            unc = '' if (belowldl or aboveudl) else analysis.getUncertainty(Result.get('result'))
            if not (belowldl or aboveudl):
                self.uncertainties.append({'uid': uid, 'uncertainty': unc})

        # maybe a service who depends on us must be recalculated.
        if analysis.portal_type == 'ReferenceAnalysis':
            dependents = []
        else:
            dependents = analysis.getDependents()
        if dependents:
            for dependent in dependents:
                dependent_uid = dependent.UID()
                # ignore analyses that no longer exist.
                if dependent_uid in self.ignore_uids or \
                   dependent_uid not in self.analyses:
                    continue
                self.calculate(dependent_uid)

        # Render out of range / in shoulder alert info
        self._render_range_alert(analysis, Result["result"])
Example #11
0
    def calculate(self, uid=None):
        analysis = self.analyses[uid]
        form_result = self.current_results[uid]['result']
        service = analysis.getService()
        calculation = service.getCalculation()
        if analysis.portal_type == 'ReferenceAnalysis':
            deps = {}
        else:
            deps = {}
            for dep in analysis.getDependencies():
                deps[dep.UID()] = dep
        path = '++resource++bika.lims.images'
        mapping = {}

        # values to be returned to form for this UID
        Result = {'uid': uid, 'result': form_result}
        try:
            Result['result'] = float(form_result)
        except:
            if form_result == "0/0":
                Result['result'] = ""

        if calculation:

            '''
             We need first to create the map of available parameters
             acording to the interims, analyses and wildcards:
             params = {
                    <as-1-keyword>              : <analysis_result>,
                    <as-1-keyword>.<wildcard-1> : <wildcard_1_value>,
                    <as-1-keyword>.<wildcard-2> : <wildcard_2_value>,
                    <interim-1>                 : <interim_result>,
                    ...
                    }
            '''

            # Get dependent analyses results and wildcard values to the
            # mapping. If dependent analysis without result found,
            # break and abort calculation
            unsatisfied = False
            for dependency_uid, dependency in deps.items():
                if dependency_uid in self.ignore_uids:
                    unsatisfied = True
                    break

                # LIMS-1769. Allow to use LDL and UDL in calculations.
                # https://jira.bikalabs.com/browse/LIMS-1769
                analysisvalues = {}
                if dependency_uid in self.current_results:
                    analysisvalues = self.current_results[dependency_uid]
                else:
                    # Retrieve the result and DLs from the analysis
                    analysisvalues = {
                        'keyword':  dependency.getKeyword(),
                        'result':   dependency.getResult(),
                        'ldl':      dependency.getLowerDetectionLimit(),
                        'udl':      dependency.getUpperDetectionLimit(),
                        'belowldl': dependency.isBelowLowerDetectionLimit(),
                        'aboveudl': dependency.isAboveUpperDetectionLimit(),
                    }
                if analysisvalues['result']=='':
                    unsatisfied = True
                    break;
                key = analysisvalues.get('keyword',dependency.getService().getKeyword())

                # Analysis result
                # All result mappings must be float, or they are ignored.
                try:
                    mapping[key] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'RESULT')] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'LDL')] = float(analysisvalues.get('ldl'))
                    mapping['%s.%s' % (key, 'UDL')] = float(analysisvalues.get('udl'))
                    mapping['%s.%s' % (key, 'BELOWLDL')] = int(analysisvalues.get('belowldl'))
                    mapping['%s.%s' % (key, 'ABOVEUDL')] = int(analysisvalues.get('aboveudl'))
                except:
                    # If not floatable, then abort!
                    unsatisfied = True
                    break

            if unsatisfied:
                # unsatisfied means that one or more result on which we depend
                # is blank or unavailable, so we set blank result and abort.
                self.results.append({'uid': uid,
                                     'result': '',
                                     'formatted_result': ''})
                return None

            # Add all interims to mapping
            for i_uid, i_data in self.item_data.items():
                for i in i_data:
                    # if this interim belongs to current analysis and is blank,
                    # return an empty result for this analysis.
                    if i_uid == uid and i['value'] == '':
                        self.results.append({'uid': uid,
                                             'result': '',
                                             'formatted_result': ''})
                        return None
                    # All interims must be float, or they are ignored.
                    try:
                        i['value'] = float(i['value'])
                    except:
                        pass

                    # all interims are ServiceKeyword.InterimKeyword
                    if i_uid in deps:
                        key = "%s.%s" % (deps[i_uid].getService().getKeyword(),
                                         i['keyword'])
                        mapping[key] = i['value']
                    # this analysis' interims get extra reference
                    # without service keyword prefix
                    if uid == i_uid:
                        mapping[i['keyword']] = i['value']

            # Grab values for hidden InterimFields for only for current calculation
            # we can't allow non-floats through here till we change the eval's
            # interpolation
            hidden_fields = []
            c_fields = calculation.getInterimFields()
            s_fields = service.getInterimFields()
            for field in c_fields:
                if field.get('hidden', False):
                    hidden_fields.append(field['keyword'])
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass
            # also grab stickier defaults from AnalysisService
            for field in s_fields:
                if field['keyword'] in hidden_fields:
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass

            # convert formula to a valid python string, ready for interpolation
            formula = calculation.getMinifiedFormula()
            formula = formula.replace('[', '%(').replace(']', ')f')
            try:
                formula = eval("'%s'%%mapping" % formula,
                               {"__builtins__": None,
                                'math': math,
                                'context': self.context},
                               {'mapping': mapping})
                # calculate
                result = eval(formula)
                Result['result'] = result
                self.current_results[uid]['result'] = result
            except TypeError as e:
                # non-numeric arguments in interim mapping?
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Type Error")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]
            except ZeroDivisionError as e:
                Result['result'] = '0/0'
                Result['formatted_result'] = '0/0'
                self.current_results[uid]['result'] = '0/0'
                self.results.append(Result)
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Division by zero")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]
                return None
            except KeyError as e:
                alert = {'field': 'Result',
                         'icon': path + '/exclamation.png',
                         'msg': "{0}: {1} ({2}) ".format(
                             t(_("Key Error")),
                             html_quote(str(e.args[0])),
                             formula)}
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [alert, ]

        if analysis.portal_type == 'ReferenceAnalysis':
            # The analysis is a Control or Blank. We might use the
            # reference results instead other specs
            _uid = analysis.getServiceUID()
            specs = analysis.aq_parent.getResultsRangeDict().get(_uid, {})

        else:
            # Get the specs directly from the analysis. The getResultsRange
            # function already takes care about which are the specs to be used:
            # AR, client or lab.
            specs = analysis.getResultsRange()

        # format result
        try:
            Result['formatted_result'] = format_numeric_result(analysis,
                                                               Result['result'])
        except ValueError:
            # non-float
            Result['formatted_result'] = Result['result']
        # calculate Dry Matter result
        # if parent is not an AR, it's never going to be calculable
        dm = hasattr(analysis.aq_parent, 'getReportDryMatter') and \
            analysis.aq_parent.getReportDryMatter() and \
            analysis.getService().getReportDryMatter()
        if dm:
            dry_service = self.context.bika_setup.getDryMatterService()
            # get the UID of the DryMatter Analysis from our parent AR
            dry_analysis = [a for a in
                            analysis.aq_parent.getAnalyses(full_objects=True)
                            if a.getService().UID() == dry_service.UID()]
            if dry_analysis:
                dry_analysis = dry_analysis[0]
                dry_uid = dry_analysis.UID()
                # get the current DryMatter analysis result from the form
                if dry_uid in self.current_results:
                    try:
                        dry_result = float(self.current_results[dry_uid])
                    except:
                        dm = False
                else:
                    try:
                        dry_result = float(dry_analysis.getResult())
                    except:
                        dm = False
            else:
                dm = False
        Result['dry_result'] = dm and dry_result and \
            '%.2f' % ((Result['result'] / dry_result) * 100) or ''

        self.results.append(Result)

        # if App.config.getConfiguration().debug_mode:
        #     logger.info("calc.py: %s->%s %s" % (analysis.aq_parent.id,
        #                                         analysis.id,
        #                                         Result))

        # LIMS-1808 Uncertainty calculation on DL
        # https://jira.bikalabs.com/browse/LIMS-1808
        flres = Result.get('result', None)
        if flres and isnumber(flres):
            flres = float(flres)
            anvals = self.current_results[uid]
            isldl = anvals.get('isldl', False)
            isudl = anvals.get('isudl', False)
            ldl = anvals.get('ldl',0)
            udl = anvals.get('udl',0)
            ldl = float(ldl) if isnumber(ldl) else 0
            udl = float(udl) if isnumber(udl) else 10000000
            belowldl = (isldl or flres < ldl)
            aboveudl = (isudl or flres > udl)
            unc = '' if (belowldl or aboveudl) else analysis.getUncertainty(Result.get('result'))
            if not (belowldl or aboveudl):
                self.uncertainties.append({'uid': uid, 'uncertainty': unc})

        # maybe a service who depends on us must be recalculated.
        if analysis.portal_type == 'ReferenceAnalysis':
            dependents = []
        else:
            dependents = analysis.getDependents()
        if dependents:
            for dependent in dependents:
                dependent_uid = dependent.UID()
                # ignore analyses that no longer exist.
                if dependent_uid in self.ignore_uids or \
                   dependent_uid not in self.analyses:
                    continue
                self.calculate(dependent_uid)

        # These self.alerts are just for the json return.
        # we're placing the entire form's results in kwargs.
        adapters = getAdapters((analysis, ), IFieldIcons)
        for name, adapter in adapters:
            alerts = adapter(result=Result['result'], form_results=self.current_results)
            if alerts:
                if analysis.UID() in self.alerts:
                    self.alerts[analysis.UID()].extend(alerts[analysis.UID()])
                else:
                    self.alerts[analysis.UID()] = alerts[analysis.UID()]
Example #12
0
    def _analysis_data(self, analysis, decimalmark=None):
        keyword = analysis.getKeyword()
        service = analysis.getService()
        andict = {'obj': analysis,
                  'id': analysis.id,
                  'title': analysis.Title(),
                  'keyword': keyword,
                  'scientific_name': service.getScientificName(),
                  'accredited': service.getAccredited(),
                  'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())),
                  'category': to_utf8(service.getCategoryTitle()),
                  'result': analysis.getResult(),
                  'isnumber': isnumber(analysis.getResult()),
                  'unit': to_utf8(service.getUnit()),
                  'formatted_unit': format_supsub(to_utf8(service.getUnit())),
                  'capture_date': analysis.getResultCaptureDate(),
                  'request_id': analysis.aq_parent.getId(),
                  'formatted_result': '',
                  'uncertainty': analysis.getUncertainty(),
                  'formatted_uncertainty': '',
                  'retested': analysis.getRetested(),
                  'remarks': to_utf8(analysis.getRemarks()),
                  'resultdm': to_utf8(analysis.getResultDM()),
                  'outofrange': False,
                  'type': analysis.portal_type,
                  'reftype': analysis.getReferenceType() \
                            if hasattr(analysis, 'getReferenceType')
                            else None,
                  'worksheet': None,
                  'specs': {},
                  'formatted_specs': ''}

        if analysis.portal_type == 'DuplicateAnalysis':
            andict['reftype'] = 'd'

        ws = analysis.getBackReferences('WorksheetAnalysis')
        andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None
        andict['worksheet_url'] = ws[0].absolute_url if ws and len(
            ws) > 0 else None
        andict['refsample'] = analysis.getSample().id \
                            if analysis.portal_type == 'Analysis' \
                            else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title())

        # Which analysis specs must be used?
        # Try first with those defined at AR Publish Specs level
        if analysis.portal_type == 'ReferenceAnalysis':
            # The analysis is a Control or Blank. We might use the
            # reference results instead other specs
            uid = analysis.getServiceUID()
            specs = analysis.aq_parent.getResultsRangeDict().get(uid, {})

        elif analysis.portal_type == 'DuplicateAnalysis':
            specs = analysis.getAnalysisSpecs()

        else:
            ar = analysis.aq_parent
            specs = ar.getPublicationSpecification()
            if not specs or keyword not in specs.getResultsRangeDict():
                specs = analysis.getAnalysisSpecs()
            specs = specs.getResultsRangeDict().get(keyword, {}) \
                    if specs else {}

        andict['specs'] = specs
        scinot = self.context.bika_setup.getScientificNotationReport()
        andict['formatted_result'] = analysis.getFormattedResult(
            specs=specs, sciformat=int(scinot), decimalmark=decimalmark)

        fs = ''
        if specs.get('min', None) and specs.get('max', None):
            fs = '%s - %s' % (specs['min'], specs['max'])
        elif specs.get('min', None):
            fs = '> %s' % specs['min']
        elif specs.get('max', None):
            fs = '< %s' % specs['max']
        andict['formatted_specs'] = formatDecimalMark(fs, decimalmark)
        andict['formatted_uncertainty'] = format_uncertainty(
            analysis,
            analysis.getResult(),
            decimalmark=decimalmark,
            sciformat=int(scinot))

        # Return specs of current analysis
        andict['specs_dict'] = analysis.getSpecification().getResultsRangeDict(
        ).get(analysis.id)

        # Out of range?
        if specs:
            adapters = getAdapters((analysis, ), IResultOutOfRange)
            bsc = getToolByName(self.context, "bika_setup_catalog")
            for name, adapter in adapters:
                ret = adapter(specification=specs)
                if ret and ret['out_of_range']:
                    andict['outofrange'] = True
                    break
        return andict
Example #13
0
    def _areConditionsMet(self, action_set, analysis, forceuid=False):
        """
        This function returns a boolean as True if the conditions in the
        action_set are met, and returns False otherwise.
        :analysis: the analysis full object which we want to obtain the
            rules for.
        :action_set: a set of rules and actions as a dictionary.
            {'actions': [{'act_row_idx': 0,
                'action': 'setresult',
                'an_result_id': 'set-4',
                'analyst': 'analyst1',
                'otherWS': 'current',
                'setresultdiscrete': '3',
                'setresulton': 'new',
                'setresultvalue': '',
                'worksheettemplate': ''}],
            'conditions': [{'analysisservice': 'dup-2',
                'and_or': 'and',
                'cond_row_idx': 0,
                'discreteresult': '2',
                'range0': '',
                'range1': ''},
                {'analysisservice': 'dup-7',
                'and_or': 'no',
                'cond_row_idx': 1,
                'discreteresult': '2',
                'range0': '',
                'range1': ''}],
            'mother_service_uid': 'ddaa2a7538bb4d188798498d6e675abd',
            'rulenumber': '1',
            'trigger': 'submit'}
        :forceuid: a boolean used to get the analysis service's UID from the
        analysis even if the analysis has been reflected and has a local_id.
        :returns: a Boolean.
        """
        conditions = action_set.get('conditions', [])
        eval_str = ''
        # Getting the analysis local id or its uid instead
        alocalid = analysis.getReflexRuleLocalID() if \
            analysis.getIsReflexAnalysis() and not forceuid else analysis.getServiceUID()
        # Getting the local ids (or analysis service uid) from the condition
        # with the same local id (or analysis service uid) as the analysis
        # attribute
        localids = [cond.get('analysisservice') for cond in conditions
                    if cond.get('analysisservice', '') == alocalid]
        # Now the alocalid could be the UID of the analysis service (if
        # this analysis has not been crated by a previous reflex rule) or it
        # could be a local_id if the analysis has been created by a reflex
        # rule.
        # So, if the analysis was reflexed, and no localid has been found
        # inside the action_set matching the analysis localid, lets look for
        # the analysis service UID.
        # forceuid is True when this second query has been done.
        if not localids and not forceuid and analysis.getIsReflexAnalysis():
            return self._areConditionsMet(action_set, analysis, forceuid=True)
        # action_set will not have any action for this analysis
        elif not localids:
            return False
        # Getting the action_set.rulenumber in order to check the
        # analysis.ReflexRuleActionsTriggered
        rulenumber = action_set.get('rulenumber', '')
        # Getting the reflex rules uid in order to fill the
        # analysis.ReflexRuleActionsTriggered attribute later
        rr_uid = self.UID()
        # Building the possible analysis.ReflexRuleActionsTriggered
        rr_actions_triggered = '.'.join([rr_uid, rulenumber])
        # If the follow condition is met, it means that the action_set for this
        # analysis has already been done by any other analysis from the
        # action_set. (e.g analsys.local_id =='dup-2', but this action has been
        # ran by the analysis with local_id=='dup-1', so we do not have to
        # run it again)
        if rr_actions_triggered in\
                analysis.getReflexRuleActionsTriggered().split('|'):
            return False
        # Check that rules are not repited: lets supose that some conditions
        # are met and an analysis with id analysis-1 is reflexed using a
        # duplicate action. Now we have analysis-1 and analysis1-dup. If the
        # same conditions are met while submitting/verifying analysis1-dup, the
        # duplicated shouldn't trigger the reflex action again.
        if forceuid and analysis.IsReflexAnalysis and rr_actions_triggered in\
                analysis.getReflexRuleActionsTriggered().split('|'):
            return False
        # To save the analysis related in the same action_set
        ans_related_to_set = []
        for condition in conditions:
            # analysisservice can be either a service uid (if it is the first
            # condition in the reflex rule) or it could be a local id such
            # as 'dup-2' if the analysis_set belongs to a derivate rule.
            ans_cond = condition.get('analysisservice', '')
            ans_uid_cond = action_set.get('mother_service_uid', '')
            # Be aware that we already know that the local_id for 'analysis'
            # has been found inside the conditions for this action_set
            if ans_cond != alocalid:
                # If the 'analysisservice' item from the condition is not the
                # same as the local_id from the analysis, the system
                # should look for the possible analysis with this localid
                # (e.g dup-2) and get its analysis object in order to compare
                # the results of the 'analysis variable' with the analysis
                # object obtained here
                curranalysis = _fetch_analysis_for_local_id(
                    analysis, ans_cond)
            else:
                # if the local_id of the 'analysis' is the same as the
                # local_id in the condition, we will use it as the current
                # analysis
                curranalysis = analysis
            if not curranalysis:
                continue
            ans_related_to_set.append(curranalysis)
            # the value of the analysis' result as string
            result = curranalysis.getResult()
            if len(analysis.getResultOptions()) > 0:
                # Discrete result as expacted value
                exp_val = condition.get('discreteresult', '')
            else:
                exp_val = (
                    condition.get('range0', ''),
                    condition.get('range1', '')
                    )
            and_or = condition.get('and_or', '')
            service_uid = curranalysis.getServiceUID()

            # Resolve the conditions
            resolution = \
                ans_uid_cond == service_uid and\
                ((isnumber(result) and isinstance(exp_val, str) and
                    exp_val == result) or
                    (isnumber(result) and len(exp_val) == 2 and
                        isnumber(exp_val[0]) and isnumber(exp_val[1]) and
                        float(exp_val[0]) <= float(result) and
                        float(result) <= float(exp_val[1])))
            # Build a string and then use eval()
            if and_or == 'no':
                eval_str += str(resolution)
            else:
                eval_str += str(resolution) + ' ' + and_or + ' '
        if eval_str and eval(eval_str):
            for an in ans_related_to_set:
                an.addReflexRuleActionsTriggered(rr_actions_triggered)
            return True
        else:
            return False
Example #14
0
def _check_set_values(instance, dic):
    """
    This function checks if the dict values are correct.
    :instance: the object instance. Used for querying
    :dic: is a dictionary with the following format:
    {'actions': [{'act_row_idx': 0,
                   'action': 'repeat',
                   'an_result_id': 'rep-1',
                   'analyst': '',
                   'otherWS': 'current',
                   'setresultdiscrete': '',
                   'setresulton': 'original',
                   'setresultvalue': '',
                   'worksheettemplate': ''}],
      'conditions': [{'analysisservice': '52853cf7d5114b5aa8c159afad2f3da1',
                      'and_or': 'no',
                      'cond_row_idx': 0,
                      'discreteresult': '',
                      'range0': '11',
                      'range1': '12'}],
      'mother_service_uid': '52853cf7d5114b5aa8c159afad2f3da1',
      'rulenumber': '0',
      'trigger': 'submit'},
    These are the checking rules:
        :range0/range1: string or number.
    They are the numeric range within the action will be
    carried on. It is needed to keep it as None or '' if the discreteresult
    is going to be used instead.
        :discreteresult: string
    If discreteresult is not Null, ranges have to be Null.
        :trigger: string.
    So far there are only two options: 'submit'/'verify'. They are defined
    in browser/widgets/reflexrulewidget.py/ReflexRuleWidget/getTriggerVoc.
        :analysisservice: it is the uid of an analysis service
        :actions: It is a list of dictionaries with the following format:
    [{'action':'<action_name>', 'act_row_idx':'X',
                'otherWS':Bool, 'analyst': '<analyst_id>'},
              {'action':'<action_name>', 'act_row_idx':'X',
                'otherWS':Bool, 'analyst': '<analyst_id>'},
        ]
        :'repetition_max': integer or string representing an integer.
    <action_name> options are found in
    browser/widgets/reflexrulewidget.py/ReflexRuleWidget/getActionVoc
    so far.
    """
    uc = getToolByName(instance, 'uid_catalog')
    rulenumber = dic.get('rulenumber', '0')
    if rulenumber and not (isnumber(rulenumber)):
        logger.warn('The range must be a number. Now its value is: '
                    '%s' % (rulenumber))
        return False
    trigger = dic.get('trigger', 'submit')
    if trigger not in ['submit', 'verify']:
        logger.warn('Only available triggers are "verify" or "submit". '
                    '%s has been introduced.' % (trigger))
        return False
    mother_service_uid = dic.get('mother_service_uid', '')
    as_brain = uc(UID=mother_service_uid)
    if not as_brain:
        logger.warn('Not correct analysis service with UID. %s' %
                    (mother_service_uid))
        return False
    # Checking the conditions
    conditions = dic.get('conditions', [])
    if not conditions or not _check_conditions(instance, conditions):
        return False
    # Checking the actions
    actions = dic.get('actions', [])
    if not actions or not _check_actions(instance, actions):
        return False
    return True
Example #15
0
    def _analysis_data(self, analysis, decimalmark=None):
        if analysis.UID() in self._cache['_analysis_data']:
            return self._cache['_analysis_data'][analysis.UID()]

        keyword = analysis.getKeyword()
        service = analysis.getService()
        andict = {'obj': analysis,
                  'id': analysis.id,
                  'title': analysis.Title(),
                  'keyword': keyword,
                  'scientific_name': service.getScientificName(),
                  'accredited': service.getAccredited(),
                  'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())),
                  'category': to_utf8(service.getCategoryTitle()),
                  'result': analysis.getResult(),
                  'isnumber': isnumber(analysis.getResult()),
                  'unit': to_utf8(service.getUnit()),
                  'formatted_unit': format_supsub(to_utf8(service.getUnit())),
                  'capture_date': analysis.getResultCaptureDate(),
                  'request_id': analysis.aq_parent.getId(),
                  'formatted_result': '',
                  'uncertainty': analysis.getUncertainty(),
                  'formatted_uncertainty': '',
                  'retested': analysis.getRetested(),
                  'remarks': to_utf8(analysis.getRemarks()),
                  'resultdm': to_utf8(analysis.getResultDM()),
                  'outofrange': False,
                  'type': analysis.portal_type,
                  'reftype': analysis.getReferenceType() \
                            if hasattr(analysis, 'getReferenceType')
                            else None,
                  'worksheet': None,
                  'specs': {},
                  'formatted_specs': ''}

        if analysis.portal_type == 'DuplicateAnalysis':
            andict['reftype'] = 'd'

        ws = analysis.getBackReferences('WorksheetAnalysis')
        andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None
        andict['worksheet_url'] = ws[0].absolute_url if ws and len(ws) > 0 else None
        andict['refsample'] = analysis.getSample().id \
                            if analysis.portal_type == 'Analysis' \
                            else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title())

        if analysis.portal_type == 'ReferenceAnalysis':
            # The analysis is a Control or Blank. We might use the
            # reference results instead other specs
            uid = analysis.getServiceUID()
            specs = analysis.aq_parent.getResultsRangeDict().get(uid, {})

        else:
            # Get the specs directly from the analysis. The getResultsRange
            # function already takes care about which are the specs to be used:
            # AR, client or lab.
            specs = analysis.getResultsRange()

        andict['specs'] = specs
        scinot = self.context.bika_setup.getScientificNotationReport()
        fresult =  analysis.getFormattedResult(specs=specs, sciformat=int(scinot), decimalmark=decimalmark)

        # We don't use here cgi.encode because results fields must be rendered
        # using the 'structure' wildcard. The reason is that the result can be
        # expressed in sci notation, that may include <sup></sup> html tags.
        # Please note the default value for the 'html' parameter from
        # getFormattedResult signature is set to True, so the service will
        # already take into account LDLs and UDLs symbols '<' and '>' and escape
        # them if necessary.
        andict['formatted_result'] = fresult;

        fs = ''
        if specs.get('min', None) and specs.get('max', None):
            fs = '%s - %s' % (specs['min'], specs['max'])
        elif specs.get('min', None):
            fs = '> %s' % specs['min']
        elif specs.get('max', None):
            fs = '< %s' % specs['max']
        andict['formatted_specs'] = formatDecimalMark(fs, decimalmark)
        andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot))

        # Out of range?
        if specs:
            adapters = getAdapters((analysis, ), IResultOutOfRange)
            bsc = getToolByName(self.context, "bika_setup_catalog")
            for name, adapter in adapters:
                ret = adapter(specification=specs)
                if ret and ret['out_of_range']:
                    andict['outofrange'] = True
                    break
        self._cache['_analysis_data'][analysis.UID()]  = andict
        return andict
Example #16
0
    def calculate(self, uid=None):
        analysis = self.analyses[uid]
        form_result = self.current_results[uid]['result']
        service = analysis.getService()
        calculation = service.getCalculation()
        if analysis.portal_type == 'ReferenceAnalysis':
            deps = {}
        else:
            deps = {}
            for dep in analysis.getDependencies():
                deps[dep.UID()] = dep
        path = '++resource++bika.lims.images'
        mapping = {}

        # values to be returned to form for this UID
        Result = {'uid': uid, 'result': form_result}
        try:
            Result['result'] = float(form_result)
        except:
            if form_result == "0/0":
                Result['result'] = ""

        if calculation:
            '''
             We need first to create the map of available parameters
             acording to the interims, analyses and wildcards:
             params = {
                    <as-1-keyword>              : <analysis_result>,
                    <as-1-keyword>.<wildcard-1> : <wildcard_1_value>,
                    <as-1-keyword>.<wildcard-2> : <wildcard_2_value>,
                    <interim-1>                 : <interim_result>,
                    ...
                    }
            '''

            # Get dependent analyses results and wildcard values to the
            # mapping. If dependent analysis without result found,
            # break and abort calculation
            unsatisfied = False
            for dependency_uid, dependency in deps.items():
                if dependency_uid in self.ignore_uids:
                    unsatisfied = True
                    break

                # LIMS-1769. Allow to use LDL and UDL in calculations.
                # https://jira.bikalabs.com/browse/LIMS-1769
                analysisvalues = {}
                if dependency_uid in self.current_results:
                    analysisvalues = self.current_results[dependency_uid]
                else:
                    # Retrieve the result and DLs from the analysis
                    analysisvalues = {
                        'keyword': dependency.getKeyword(),
                        'result': dependency.getResult(),
                        'ldl': dependency.getLowerDetectionLimit(),
                        'udl': dependency.getUpperDetectionLimit(),
                        'belowldl': dependency.isBelowLowerDetectionLimit(),
                        'aboveudl': dependency.isAboveUpperDetectionLimit(),
                    }
                if analysisvalues['result'] == '':
                    unsatisfied = True
                    break
                key = analysisvalues.get('keyword',
                                         dependency.getService().getKeyword())

                # Analysis result
                # All result mappings must be float, or they are ignored.
                try:
                    mapping[key] = float(analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'RESULT')] = float(
                        analysisvalues.get('result'))
                    mapping['%s.%s' % (key, 'LDL')] = float(
                        analysisvalues.get('ldl'))
                    mapping['%s.%s' % (key, 'UDL')] = float(
                        analysisvalues.get('udl'))
                    mapping['%s.%s' % (key, 'BELOWLDL')] = int(
                        analysisvalues.get('belowldl'))
                    mapping['%s.%s' % (key, 'ABOVEUDL')] = int(
                        analysisvalues.get('aboveudl'))
                except:
                    # If not floatable, then abort!
                    unsatisfied = True
                    break

            if unsatisfied:
                # unsatisfied means that one or more result on which we depend
                # is blank or unavailable, so we set blank result and abort.
                self.results.append({
                    'uid': uid,
                    'result': '',
                    'formatted_result': ''
                })
                return None

            # Add all interims to mapping
            for i_uid, i_data in self.item_data.items():
                for i in i_data:
                    # if this interim belongs to current analysis and is blank,
                    # return an empty result for this analysis.
                    if i_uid == uid and i['value'] == '':
                        self.results.append({
                            'uid': uid,
                            'result': '',
                            'formatted_result': ''
                        })
                        return None
                    # All interims must be float, or they are ignored.
                    try:
                        i['value'] = float(i['value'])
                    except:
                        pass

                    # all interims are ServiceKeyword.InterimKeyword
                    if i_uid in deps:
                        key = "%s.%s" % (deps[i_uid].getService().getKeyword(),
                                         i['keyword'])
                        mapping[key] = i['value']
                    # this analysis' interims get extra reference
                    # without service keyword prefix
                    if uid == i_uid:
                        mapping[i['keyword']] = i['value']

            # Grab values for hidden InterimFields for only for current calculation
            # we can't allow non-floats through here till we change the eval's
            # interpolation
            hidden_fields = []
            c_fields = calculation.getInterimFields()
            s_fields = service.getInterimFields()
            for field in c_fields:
                if field.get('hidden', False):
                    hidden_fields.append(field['keyword'])
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass
            # also grab stickier defaults from AnalysisService
            for field in s_fields:
                if field['keyword'] in hidden_fields:
                    try:
                        mapping[field['keyword']] = float(field['value'])
                    except ValueError:
                        pass

            # convert formula to a valid python string, ready for interpolation
            formula = calculation.getMinifiedFormula()
            formula = formula.replace('[', '%(').replace(']', ')f')
            try:
                formula = eval("'%s'%%mapping" % formula, {
                    "__builtins__": None,
                    'math': math,
                    'context': self.context
                }, {'mapping': mapping})
                # calculate
                result = eval(formula)
                Result['result'] = result
                self.current_results[uid]['result'] = result
            except TypeError as e:
                # non-numeric arguments in interim mapping?
                alert = {
                    'field':
                    'Result',
                    'icon':
                    path + '/exclamation.png',
                    'msg':
                    "{0}: {1} ({2}) ".format(t(_("Type Error")),
                                             html_quote(str(e.args[0])),
                                             formula)
                }
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [
                        alert,
                    ]
            except ZeroDivisionError as e:
                Result['result'] = '0/0'
                Result['formatted_result'] = '0/0'
                self.current_results[uid]['result'] = '0/0'
                self.results.append(Result)
                alert = {
                    'field':
                    'Result',
                    'icon':
                    path + '/exclamation.png',
                    'msg':
                    "{0}: {1} ({2}) ".format(t(_("Division by zero")),
                                             html_quote(str(e.args[0])),
                                             formula)
                }
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [
                        alert,
                    ]
                return None
            except KeyError as e:
                alert = {
                    'field':
                    'Result',
                    'icon':
                    path + '/exclamation.png',
                    'msg':
                    "{0}: {1} ({2}) ".format(t(_("Key Error")),
                                             html_quote(str(e.args[0])),
                                             formula)
                }
                if uid in self.alerts:
                    self.alerts[uid].append(alert)
                else:
                    self.alerts[uid] = [
                        alert,
                    ]

        # format result
        belowmin = False
        abovemax = False
        # Some analyses will not have AnalysisSpecs, eg, ReferenceAnalysis
        if hasattr(analysis, 'getAnalysisSpecs'):
            specs = analysis.getAnalysisSpecs()
            specs = specs.getResultsRangeDict() if specs is not None else {}
            specs = specs.get(analysis.getKeyword(), {})
            hidemin = specs.get('hidemin', '')
            hidemax = specs.get('hidemax', '')
            if Result.get('result', ''):
                fresult = Result['result']
                try:
                    belowmin = hidemin and fresult < float(hidemin) or False
                except ValueError:
                    belowmin = False
                    pass
                try:
                    abovemax = hidemax and fresult > float(hidemax) or False
                except ValueError:
                    abovemax = False
                    pass

        if belowmin is True:
            Result['formatted_result'] = '< %s' % hidemin
        elif abovemax is True:
            Result['formatted_result'] = '> %s' % hidemax
        else:
            try:
                Result['formatted_result'] = format_numeric_result(
                    analysis, Result['result'])
            except ValueError:
                # non-float
                Result['formatted_result'] = Result['result']

        # calculate Dry Matter result
        # if parent is not an AR, it's never going to be calculable
        dm = hasattr(analysis.aq_parent, 'getReportDryMatter') and \
            analysis.aq_parent.getReportDryMatter() and \
            analysis.getService().getReportDryMatter()
        if dm:
            dry_service = self.context.bika_setup.getDryMatterService()
            # get the UID of the DryMatter Analysis from our parent AR
            dry_analysis = [
                a for a in analysis.aq_parent.getAnalyses(full_objects=True)
                if a.getService().UID() == dry_service.UID()
            ]
            if dry_analysis:
                dry_analysis = dry_analysis[0]
                dry_uid = dry_analysis.UID()
                # get the current DryMatter analysis result from the form
                if dry_uid in self.current_results:
                    try:
                        dry_result = float(self.current_results[dry_uid])
                    except:
                        dm = False
                else:
                    try:
                        dry_result = float(dry_analysis.getResult())
                    except:
                        dm = False
            else:
                dm = False
        Result['dry_result'] = dm and dry_result and \
            '%.2f' % ((Result['result'] / dry_result) * 100) or ''

        self.results.append(Result)

        # if App.config.getConfiguration().debug_mode:
        #     logger.info("calc.py: %s->%s %s" % (analysis.aq_parent.id,
        #                                         analysis.id,
        #                                         Result))

        # LIMS-1808 Uncertainty calculation on DL
        # https://jira.bikalabs.com/browse/LIMS-1808
        flres = Result.get('result', None)
        if flres and isnumber(flres):
            flres = float(flres)
            anvals = self.current_results[uid]
            isldl = anvals.get('isldl', False)
            isudl = anvals.get('isudl', False)
            ldl = anvals.get('ldl', 0)
            udl = anvals.get('udl', 0)
            ldl = float(ldl) if isnumber(ldl) else 0
            udl = float(udl) if isnumber(udl) else 10000000
            belowldl = (isldl or flres < ldl)
            aboveudl = (isudl or flres > udl)
            unc = '' if (belowldl or aboveudl) else analysis.getUncertainty(
                Result.get('result'))
            if not (belowldl or aboveudl):
                self.uncertainties.append({'uid': uid, 'uncertainty': unc})

        # maybe a service who depends on us must be recalculated.
        if analysis.portal_type == 'ReferenceAnalysis':
            dependents = []
        else:
            dependents = analysis.getDependents()
        if dependents:
            for dependent in dependents:
                dependent_uid = dependent.UID()
                # ignore analyses that no longer exist.
                if dependent_uid in self.ignore_uids or \
                   dependent_uid not in self.analyses:
                    continue
                self.calculate(dependent_uid)

        # These self.alerts are just for the json return.
        # we're placing the entire form's results in kwargs.
        adapters = getAdapters((analysis, ), IFieldIcons)
        for name, adapter in adapters:
            alerts = adapter(result=Result['result'],
                             form_results=self.current_results)
            if alerts:
                if analysis.UID() in self.alerts:
                    self.alerts[analysis.UID()].extend(alerts[analysis.UID()])
                else:
                    self.alerts[analysis.UID()] = alerts[analysis.UID()]