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 traceback when retracting an analysis with a detection limit #2204

Merged
merged 4 commits into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
2.4.0 (unreleased)
------------------

- #2204 Fix traceback when retracting an analysis with a detection limit
- #2202 Fix detection limit set manually is not displayed on result save
- #2203 Fix empty date sampled in samples listing when sampling workflow is enabled
- #2197 Use portal as relative path for sticker icons
Expand Down
33 changes: 13 additions & 20 deletions src/bika/lims/content/abstractanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,14 +346,12 @@ def getLowerDetectionLimit(self):
"""
if self.isLowerDetectionLimit():
result = self.getResult()
try:
# in this case, the result itself is the LDL.
return float(result)
except (TypeError, ValueError):
logger.warn("The result for the analysis %s is a lower "
"detection limit, but not floatable: '%s'. "
"Returnig AS's default LDL." %
(self.id, result))
if api.is_floatable(result):
return result

logger.warn("The result for the analysis %s is a lower detection "
"limit, but not floatable: '%s'. Returning AS's "
"default LDL." % (self.id, result))
return AbstractBaseAnalysis.getLowerDetectionLimit(self)

# Method getUpperDetectionLimit overrides method of class BaseAnalysis
Expand All @@ -366,14 +364,12 @@ def getUpperDetectionLimit(self):
"""
if self.isUpperDetectionLimit():
result = self.getResult()
try:
# in this case, the result itself is the LDL.
return float(result)
except (TypeError, ValueError):
logger.warn("The result for the analysis %s is a lower "
"detection limit, but not floatable: '%s'. "
"Returnig AS's default LDL." %
(self.id, result))
if api.is_floatable(result):
return result

logger.warn("The result for the analysis %s is an upper detection "
"limit, but not floatable: '%s'. Returning AS's "
"default UDL." % (self.id, result))
return AbstractBaseAnalysis.getUpperDetectionLimit(self)

@security.public
Expand Down Expand Up @@ -479,10 +475,7 @@ def setResult(self, value):
if val and val[0] in [LDL, UDL]:
# Strip off the detection limit operand from the result
operand = val[0]

# Convert to float-able if possible
wo_dl = val.replace(operand, "", 1).strip()
val = api.to_float(wo_dl) if api.is_floatable(wo_dl) else val
val = val.replace(operand, "", 1).strip()

# Result becomes the detection limit
selector = self.getDetectionLimitSelector()
Expand Down
2 changes: 1 addition & 1 deletion src/senaite/core/profiles/default/metadata.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<metadata>
<version>2403</version>
<version>2405</version>
<dependencies>
<dependency>profile-Products.ATContentTypes:base</dependency>
<dependency>profile-Products.CMFEditions:CMFEditions</dependency>
Expand Down
76 changes: 76 additions & 0 deletions src/senaite/core/tests/doctests/WorkflowAnalysisRetract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,79 @@ But the retest does not provide `IRetracted`:
>>> retest = analysis.getRetest()
>>> IRetracted.providedBy(retest)
False


Retract an analysis with a result that is a Detection Limit
...........................................................

Allow the user to manually enter the detection limit as the result:

>>> Cu.setAllowManualDetectionLimit(True)

Create the sample:

>>> sample = new_ar([Cu])
>>> cu = sample.getAnalyses(full_objects=True)[0]
>>> cu.setResult("< 10")
>>> success = do_action_for(cu, "submit")
>>> cu.getResult()
'10'

>>> cu.getFormattedResult(html=False)
'< 10'

>>> cu.isLowerDetectionLimit()
True

>>> cu.getDetectionLimitOperand()
'<'

The Detection Limit is not kept on the retest:

>>> success = do_action_for(analysis, "retract")
>>> retest = analysis.getRetest()
>>> retest.getResult()
''

>>> retest.getFormattedResult(html=False)
''

>>> retest.isLowerDetectionLimit()
False

>>> retest.getDetectionLimitOperand()
''

Do the same with Upper Detection Limit (UDL):

>>> sample = new_ar([Cu])
>>> cu = sample.getAnalyses(full_objects=True)[0]
>>> cu.setResult("> 10")
>>> success = do_action_for(cu, "submit")
>>> cu.getResult()
'10'

>>> cu.getFormattedResult(html=False)
'> 10'

>>> cu.isUpperDetectionLimit()
True

>>> cu.getDetectionLimitOperand()
'>'

The Detection Limit is not kept on the retest:

>>> success = do_action_for(analysis, "retract")
>>> retest = analysis.getRetest()
>>> retest.getResult()
''

>>> retest.getFormattedResult(html=False)
''

>>> retest.isUpperDetectionLimit()
False

>>> retest.getDetectionLimitOperand()
''
2 changes: 1 addition & 1 deletion src/senaite/core/tests/test_calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ def test_analysis_method_calculation(self):
or an.isUpperDetectionLimit():
operator = an.getDetectionLimitOperand()
strres = f['analyses'][key].replace(operator, '')
self.assertEqual(an.getResult(), float(strres))
self.assertEqual(an.getResult(), strres)
else:
self.assertEqual(an.getResult(), f['analyses'][key])
elif key == self.calcservice.getKeyword():
Expand Down
45 changes: 45 additions & 0 deletions src/senaite/core/upgrade/v02_04_000.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
# Some rights reserved, see README and LICENSE.

from bika.lims import api
from bika.lims import LDL
from bika.lims import UDL
from bika.lims.interfaces import IRejected
from bika.lims.interfaces import IRetracted
from senaite.core import logger
Expand Down Expand Up @@ -137,3 +139,46 @@ def import_typeinfo(tool):
setup = portal.portal_setup
setup.runImportStepFromProfile("profile-bika.lims:default", "typeinfo")
setup.runImportStepFromProfile(profile, "typeinfo")


def fix_traceback_retract_dl(tool):
"""Migrates the values of LDL and UDL of analyses/services to string, as
well as results that are DetectionLimit and stored as floats
"""
logger.info("Migrate LDL, UDL and result fields to string ...")
cat = api.get_tool("uid_catalog")
query = {"portal_type": ["AnalysisService", "Analysis",
"DuplicateAnalysis", "ReferenceAnalysis"]}
brains = cat.search(query)
total = len(brains)

for num, brain in enumerate(brains):
if num and num % 100 == 0:
logger.info("Migrated {0}/{1} LDL/UDL fields".format(num, total))

obj = api.get_object(brain)

# Migrate UDL to string
field = obj.getField("UpperDetectionLimit")
value = field.get(obj)
if isinstance(value, (int, float)):
field.set(obj, str(value))

# Migrate LDL to string
field = obj.getField("LowerDetectionLimit")
value = field.get(obj)
if isinstance(value, (int, float)):
field.set(obj, str(value))

# Migrate the result
field = obj.getField("Result")
if field and obj.getDetectionLimitOperand() in [LDL, UDL]:
# The result is the detection limit
result = field.get(obj)
if isinstance(result, (int, float)):
field.set(obj, str(result))

# Flush the object from memory
obj._p_deactivate()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add every 1000 iterations an transaction.savepoint() to reduce the memory size of the transaction


logger.info("Migrate LDL, UDL and result fields to string [DONE]")
10 changes: 10 additions & 0 deletions src/senaite/core/upgrade/v02_04_000.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,14 @@
handler="senaite.core.upgrade.v02_04_000.import_typeinfo"
profile="senaite.core:default"/>

<!-- Fix traceback when retracting an analysis with a detection limit
https://github.com/senaite/senaite.core/pull/2204 -->
<genericsetup:upgradeStep
title="SENAITE CORE 2.4.0: Fix traceback when retracting DL"
description="Fix traceback when retracting an analysis with a detection limit"
source="2404"
destination="2405"
handler="senaite.core.upgrade.v02_04_000.fix_traceback_retract_dl"
profile="senaite.core:default"/>

</configure>