Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix LDL/UDL cut-off and exponential float conversion #2089

Merged
merged 11 commits into from
Aug 2, 2022
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 13 additions & 5 deletions src/bika/lims/content/abstractanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -405,18 +406,23 @@ 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
UDL) that applies to this analysis in particular. If no value set or
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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
47 changes: 29 additions & 18 deletions src/bika/lims/content/abstractbaseanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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=_(
Expand All @@ -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=_(
Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions src/senaite/core/tests/test_limitdetections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down