diff --git a/CHANGES.rst b/CHANGES.rst index 736c205f2b..e559a43544 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,7 @@ Changelog 2.3.0 (unreleased) ------------------ +- #2089 Fix LDL/UDL cut-off and exponential float conversion - #2078 Replace dynamic code execution with dynamic import in reports - #2083 Lookup workflow action redirect URL from request first - #2082 Include sample ID in form ID for lab, field and qc analyses listings diff --git a/src/bika/lims/content/abstractanalysis.py b/src/bika/lims/content/abstractanalysis.py index 9b4f58d88e..c9c0cf7464 100644 --- a/src/bika/lims/content/abstractanalysis.py +++ b/src/bika/lims/content/abstractanalysis.py @@ -388,7 +388,8 @@ def isBelowLowerDetectionLimit(self): return True if api.is_floatable(result): - return api.to_float(result) < self.getLowerDetectionLimit() + ldl = self.getLowerDetectionLimit() + return api.to_float(result) < api.to_float(ldl, 0.0) return False @@ -405,10 +406,13 @@ def isAboveUpperDetectionLimit(self): return True if api.is_floatable(result): - return api.to_float(result) > self.getUpperDetectionLimit() + udl = self.getUpperDetectionLimit() + return api.to_float(result) > api.to_float(udl, 0.0) return False + # TODO: REMOVE: nowhere used + @deprecated("This Method will be removed in version 2.5") @security.public def getDetectionLimits(self): """Returns a two-value array with the limits of detection (LDL and @@ -416,7 +420,9 @@ def getDetectionLimits(self): the analysis service doesn't allow manual input of detection limits, returns the value set by default in the Analysis Service """ - return [self.getLowerDetectionLimit(), self.getUpperDetectionLimit()] + ldl = self.getLowerDetectionLimit() + udl = self.getUpperDetectionLimit() + return [api.to_float(ldl, 0.0), api.to_float(udl, 0.0)] @security.public def isLowerDetectionLimit(self): @@ -575,8 +581,8 @@ def calculateResult(self, override=False, cascade=False): adl = dependency.isAboveUpperDetectionLimit() mapping[key] = result mapping['%s.%s' % (key, 'RESULT')] = result - mapping['%s.%s' % (key, 'LDL')] = ldl - mapping['%s.%s' % (key, 'UDL')] = udl + mapping['%s.%s' % (key, 'LDL')] = api.to_float(ldl, 0.0) + mapping['%s.%s' % (key, 'UDL')] = api.to_float(udl, 0.0) mapping['%s.%s' % (key, 'BELOWLDL')] = int(bdl) mapping['%s.%s' % (key, 'ABOVEUDL')] = int(adl) except (TypeError, ValueError): @@ -915,6 +921,7 @@ def getFormattedResult(self, specs=None, decimalmark='.', sciformat=1, # Below Lower Detection Limit (LDL)? ldl = self.getLowerDetectionLimit() + ldl = api.to_float(ldl, 0.0) if result < ldl: # LDL must not be formatted according to precision, etc. # Drop trailing zeros from decimal @@ -924,6 +931,7 @@ def getFormattedResult(self, specs=None, decimalmark='.', sciformat=1, # Above Upper Detection Limit (UDL)? udl = self.getUpperDetectionLimit() + udl = api.to_float(udl, 0.0) if result > udl: # UDL must not be formatted according to precision, etc. # Drop trailing zeros from decimal diff --git a/src/bika/lims/content/abstractbaseanalysis.py b/src/bika/lims/content/abstractbaseanalysis.py index 822ac00d86..d638a25ae0 100644 --- a/src/bika/lims/content/abstractbaseanalysis.py +++ b/src/bika/lims/content/abstractbaseanalysis.py @@ -19,6 +19,7 @@ # Some rights reserved, see README and LICENSE. from AccessControl import ClassSecurityInfo +from bika.lims import api from bika.lims import bikaMessageFactory as _ from bika.lims.browser.fields import DurationField from bika.lims.browser.fields import UIDReferenceField @@ -136,14 +137,16 @@ ) ) +# TODO: Use a plain string field when converting to DX! +# # If the value is below this limit, it means that the measurement lacks # accuracy and this will be shown in manage_results and also on the final # report. LowerDetectionLimit = FixedPointField( - 'LowerDetectionLimit', + "LowerDetectionLimit", schemata="Analysis", - default='0.0', - precision=7, + default="0.0", + precision=1000, # avoid precision cut-off done by the field widget=DecimalWidget( label=_("Lower Detection Limit (LDL)"), description=_( @@ -154,14 +157,16 @@ ) ) +# TODO: Use a plain string field when converting to DX! +# # If the value is above this limit, it means that the measurement lacks # accuracy and this will be shown in manage_results and also on the final # report. UpperDetectionLimit = FixedPointField( - 'UpperDetectionLimit', + "UpperDetectionLimit", schemata="Analysis", - default='1000000000.0', - precision=7, + default="1000000000.0", + precision=1000, # avoid precision cut-off done by the field widget=DecimalWidget( label=_("Upper Detection Limit (UDL)"), description=_( @@ -847,23 +852,29 @@ def getAnalysisCategories(self): @security.public def getLowerDetectionLimit(self): - """Returns the Lower Detection Limit for this service as a floatable + """Get the lower detection limit without trailing zeros """ - ldl = self.getField('LowerDetectionLimit').get(self) - try: - return float(ldl) - except ValueError: - return 0 + field = self.getField("LowerDetectionLimit") + value = field.get(self) + # NOTE: This is a workaround to avoid the cut-off done by the field + # if the value is lower than the precision + # => we should use a string instead + # remove trailing zeros and possible trailing dot + value = value.rstrip("0").rstrip(".") + return value @security.public def getUpperDetectionLimit(self): - """Returns the Upper Detection Limit for this service as a floatable + """Get the upper detection limit without trailing zeros """ - udl = self.getField('UpperDetectionLimit').get(self) - try: - return float(udl) - except ValueError: - return 0 + field = self.getField("UpperDetectionLimit") + value = field.get(self) + # NOTE: This is a workaround to avoid the cut-off done by the field + # if the value is lower than the precision + # => we should use a string instead + # remove trailing zeros and possible trailing dot + value = value.rstrip("0").rstrip(".") + return value @security.public def isSelfVerificationEnabled(self): diff --git a/src/senaite/core/tests/test_limitdetections.py b/src/senaite/core/tests/test_limitdetections.py index a6d603a515..c3803977e2 100644 --- a/src/senaite/core/tests/test_limitdetections.py +++ b/src/senaite/core/tests/test_limitdetections.py @@ -382,8 +382,8 @@ def test_ar_manageresults_limitdetections(self): for a in ans: an = a.getObject() idx = asidxs[an.id] - self.assertEqual(an.getLowerDetectionLimit(), float(self.lds[idx]['min'])) - self.assertEqual(an.getUpperDetectionLimit(), float(self.lds[idx]['max'])) + self.assertEqual(an.getLowerDetectionLimit(), self.lds[idx]['min']) + self.assertEqual(an.getUpperDetectionLimit(), self.lds[idx]['max']) self.assertEqual(an.getAllowManualDetectionLimit(), self.lds[idx]['manual']) # Empty result