|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
# |
|
3
|
|
|
# This file is part of SENAITE.CORE. |
|
4
|
|
|
# |
|
5
|
|
|
# SENAITE.CORE is free software: you can redistribute it and/or modify it under |
|
6
|
|
|
# the terms of the GNU General Public License as published by the Free Software |
|
7
|
|
|
# Foundation, version 2. |
|
8
|
|
|
# |
|
9
|
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
|
10
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
11
|
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
|
12
|
|
|
# details. |
|
13
|
|
|
# |
|
14
|
|
|
# You should have received a copy of the GNU General Public License along with |
|
15
|
|
|
# this program; if not, write to the Free Software Foundation, Inc., 51 |
|
16
|
|
|
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|
17
|
|
|
# |
|
18
|
|
|
# Copyright 2018-2019 by it's authors. |
|
19
|
|
|
# Some rights reserved, see README and LICENSE. |
|
20
|
|
|
|
|
21
|
|
|
import copy |
|
22
|
|
|
import math |
|
23
|
|
|
|
|
24
|
|
|
import zope.event |
|
25
|
|
|
from Products.Archetypes.event import ObjectInitializedEvent |
|
26
|
|
|
from Products.CMFCore.utils import getToolByName |
|
27
|
|
|
from Products.CMFPlone.utils import _createObjectByType |
|
28
|
|
|
from bika.lims import bikaMessageFactory as _ |
|
29
|
|
|
from bika.lims.interfaces import IAnalysisService |
|
30
|
|
|
from bika.lims.utils import formatDecimalMark |
|
31
|
|
|
from bika.lims.utils import to_unicode |
|
32
|
|
|
|
|
33
|
|
|
|
|
34
|
|
|
def duplicateAnalysis(analysis): |
|
35
|
|
|
""" |
|
36
|
|
|
Duplicate an analysis consist on creating a new analysis with |
|
37
|
|
|
the same analysis service for the same sample. It is used in |
|
38
|
|
|
order to reduce the error procedure probability because both |
|
39
|
|
|
results must be similar. |
|
40
|
|
|
:base: the analysis object used as the creation base. |
|
41
|
|
|
""" |
|
42
|
|
|
ar = analysis.aq_parent |
|
43
|
|
|
kw = analysis.getKeyword() |
|
44
|
|
|
# Rename the analysis to make way for it's successor. |
|
45
|
|
|
# Support multiple duplicates by renaming to *-0, *-1, etc |
|
46
|
|
|
cnt = [x for x in ar.objectValues("Analysis") if x.getId().startswith(kw)] |
|
47
|
|
|
a_id = "{0}-{1}".format(kw, len(cnt)) |
|
48
|
|
|
dup = create_analysis(ar, analysis, id=a_id, Retested=True) |
|
49
|
|
|
return dup |
|
50
|
|
|
|
|
51
|
|
|
|
|
52
|
|
|
def copy_analysis_field_values(source, analysis, **kwargs): |
|
53
|
|
|
src_schema = source.Schema() |
|
54
|
|
|
dst_schema = analysis.Schema() |
|
55
|
|
|
# Some fields should not be copied from source! |
|
56
|
|
|
# BUT, if these fieldnames are present in kwargs, the value will |
|
57
|
|
|
# be set accordingly. |
|
58
|
|
|
IGNORE_FIELDNAMES = [ |
|
59
|
|
|
'UID', 'id', 'allowDiscussion', 'subject', 'location', 'contributors', |
|
60
|
|
|
'creators', 'effectiveDate', 'expirationDate', 'language', 'rights', |
|
61
|
|
|
'creation_date', 'modification_date', 'IsReflexAnalysis', |
|
62
|
|
|
'OriginalReflexedAnalysis', 'ReflexAnalysisOf', 'ReflexRuleAction', |
|
63
|
|
|
'ReflexRuleLocalID', 'ReflexRuleActionsTriggered', 'Hidden'] |
|
64
|
|
|
for field in src_schema.fields(): |
|
65
|
|
|
fieldname = field.getName() |
|
66
|
|
|
if fieldname in IGNORE_FIELDNAMES and fieldname not in kwargs: |
|
67
|
|
|
continue |
|
68
|
|
|
if fieldname not in dst_schema: |
|
69
|
|
|
continue |
|
70
|
|
|
value = kwargs.get(fieldname, field.get(source)) |
|
71
|
|
|
if value: |
|
72
|
|
|
# Campbell's mental note:never ever use '.set()' directly to a |
|
73
|
|
|
# field. If you can't use the setter, then use the mutator in order |
|
74
|
|
|
# to give the value. We have realized that in some cases using |
|
75
|
|
|
# 'set' when the value is a string, it saves the value |
|
76
|
|
|
# as unicode instead of plain string. |
|
77
|
|
|
mutator_name = analysis.getField(fieldname).mutator |
|
78
|
|
|
mutator = getattr(analysis, mutator_name) |
|
79
|
|
|
mutator(value) |
|
80
|
|
|
|
|
81
|
|
|
def create_analysis(context, source, **kwargs): |
|
82
|
|
|
"""Create a new Analysis. The source can be an Analysis Service or |
|
83
|
|
|
an existing Analysis, and all possible field values will be set to the |
|
84
|
|
|
values found in the source object. |
|
85
|
|
|
:param context: The analysis will be created inside this object. |
|
86
|
|
|
:param source: The schema of this object will be used to populate analysis. |
|
87
|
|
|
:param kwargs: The values of any keys which match schema fieldnames will |
|
88
|
|
|
be inserted into the corrosponding fields in the new analysis. |
|
89
|
|
|
:returns: Analysis object that was created |
|
90
|
|
|
:rtype: Analysis |
|
91
|
|
|
""" |
|
92
|
|
|
an_id = kwargs.get('id', source.getKeyword()) |
|
93
|
|
|
analysis = _createObjectByType("Analysis", context, an_id) |
|
94
|
|
|
copy_analysis_field_values(source, analysis, **kwargs) |
|
95
|
|
|
|
|
96
|
|
|
# AnalysisService field is not present on actual AnalysisServices. |
|
97
|
|
|
if IAnalysisService.providedBy(source): |
|
98
|
|
|
analysis.setAnalysisService(source) |
|
99
|
|
|
else: |
|
100
|
|
|
analysis.setAnalysisService(source.getAnalysisService()) |
|
101
|
|
|
|
|
102
|
|
|
# Set the interims from the Service |
|
103
|
|
|
service_interims = analysis.getAnalysisService().getInterimFields() |
|
104
|
|
|
# Avoid references from the analysis interims to the service interims |
|
105
|
|
|
service_interims = copy.deepcopy(service_interims) |
|
106
|
|
|
analysis.setInterimFields(service_interims) |
|
107
|
|
|
|
|
108
|
|
|
analysis.unmarkCreationFlag() |
|
109
|
|
|
zope.event.notify(ObjectInitializedEvent(analysis)) |
|
110
|
|
|
return analysis |
|
111
|
|
|
|
|
112
|
|
|
def get_significant_digits(numeric_value): |
|
113
|
|
|
""" |
|
114
|
|
|
Returns the precision for a given floatable value. |
|
115
|
|
|
If value is None or not floatable, returns None. |
|
116
|
|
|
Will return positive values if the result is below 1 and will |
|
117
|
|
|
return 0 values if the result is above or equal to 1. |
|
118
|
|
|
:param numeric_value: the value to get the precision from |
|
119
|
|
|
:returns: the numeric_value's precision |
|
120
|
|
|
Examples: |
|
121
|
|
|
numeric_value Returns |
|
122
|
|
|
0 0 |
|
123
|
|
|
0.22 1 |
|
124
|
|
|
1.34 0 |
|
125
|
|
|
0.0021 3 |
|
126
|
|
|
0.013 2 |
|
127
|
|
|
2 0 |
|
128
|
|
|
22 0 |
|
129
|
|
|
""" |
|
130
|
|
|
try: |
|
131
|
|
|
numeric_value = float(numeric_value) |
|
132
|
|
|
except (TypeError, ValueError): |
|
133
|
|
|
return None |
|
134
|
|
|
if numeric_value == 0: |
|
135
|
|
|
return 0 |
|
136
|
|
|
significant_digit = int(math.floor(math.log10(abs(numeric_value)))) |
|
137
|
|
|
return 0 if significant_digit > 0 else abs(significant_digit) |
|
138
|
|
|
|
|
139
|
|
|
|
|
140
|
|
|
def _format_decimal_or_sci(result, precision, threshold, sciformat): |
|
141
|
|
|
# Current result's precision is above the threshold? |
|
142
|
|
|
sig_digits = get_significant_digits(result) |
|
143
|
|
|
|
|
144
|
|
|
# Note that if result < 1, sig_digits > 0. Otherwise, sig_digits = 0 |
|
145
|
|
|
# Eg: |
|
146
|
|
|
# result = 0.2 -> sig_digit = 1 |
|
147
|
|
|
# 0.002 -> sig_digit = 3 |
|
148
|
|
|
# 0 -> sig_digit = 0 |
|
149
|
|
|
# 2 -> sig_digit = 0 |
|
150
|
|
|
# See get_significant_digits signature for further details! |
|
151
|
|
|
# |
|
152
|
|
|
# Also note if threshold is negative, the result will always be expressed |
|
153
|
|
|
# in scientific notation: |
|
154
|
|
|
# Eg. |
|
155
|
|
|
# result=12345, threshold=-3, sig_digit=0 -> 1.2345e4 = 1.2345·10⁴ |
|
156
|
|
|
# |
|
157
|
|
|
# So, if sig_digits is > 0, the power must be expressed in negative |
|
158
|
|
|
# Eg. |
|
159
|
|
|
# result=0.0012345, threshold=3, sig_digit=3 -> 1.2345e-3=1.2345·10-³ |
|
160
|
|
|
sci = sig_digits >= threshold and abs( |
|
161
|
|
|
threshold) > 0 and sig_digits <= precision |
|
162
|
|
|
sign = '-' if sig_digits > 0 else '' |
|
163
|
|
|
if sig_digits == 0 and abs(threshold) > 0 and abs(int(float(result))) > 0: |
|
164
|
|
|
# Number >= 1, need to check if the number of non-decimal |
|
165
|
|
|
# positions is above the threshold |
|
166
|
|
|
sig_digits = int(math.log(abs(float(result)), 10)) if abs( |
|
167
|
|
|
float(result)) >= 10 else 0 |
|
168
|
|
|
sci = sig_digits >= abs(threshold) |
|
169
|
|
|
|
|
170
|
|
|
formatted = '' |
|
171
|
|
|
if sci: |
|
172
|
|
|
# First, cut the extra decimals according to the precision |
|
173
|
|
|
prec = precision if precision and precision > 0 else 0 |
|
174
|
|
|
nresult = str("%%.%sf" % prec) % result |
|
175
|
|
|
|
|
176
|
|
|
if sign: |
|
177
|
|
|
# 0.0012345 -> 1.2345 |
|
178
|
|
|
res = float(nresult) * (10 ** sig_digits) |
|
179
|
|
|
else: |
|
180
|
|
|
# Non-decimal positions |
|
181
|
|
|
# 123.45 -> 1.2345 |
|
182
|
|
|
res = float(nresult) / (10 ** sig_digits) |
|
183
|
|
|
res = int(res) if res.is_integer() else res |
|
184
|
|
|
|
|
185
|
|
|
# Scientific notation |
|
186
|
|
|
if sciformat == 2: |
|
187
|
|
|
# ax10^b or ax10^-b |
|
188
|
|
|
formatted = "%s%s%s%s" % (res, "x10^", sign, sig_digits) |
|
189
|
|
|
elif sciformat == 3: |
|
190
|
|
|
# ax10<super>b</super> or ax10<super>-b</super> |
|
191
|
|
|
formatted = "%s%s%s%s%s" % ( |
|
192
|
|
|
res, "x10<sup>", sign, sig_digits, "</sup>") |
|
193
|
|
|
elif sciformat == 4: |
|
194
|
|
|
# ax10^b or ax10^-b |
|
195
|
|
|
formatted = "%s%s%s%s" % (res, "·10^", sign, sig_digits) |
|
196
|
|
|
elif sciformat == 5: |
|
197
|
|
|
# ax10<super>b</super> or ax10<super>-b</super> |
|
198
|
|
|
formatted = "%s%s%s%s%s" % ( |
|
199
|
|
|
res, "·10<sup>", sign, sig_digits, "</sup>") |
|
200
|
|
|
else: |
|
201
|
|
|
# Default format: aE^+b |
|
202
|
|
|
sig_digits = "%02d" % sig_digits |
|
203
|
|
|
formatted = "%s%s%s%s" % (res, "e", sign, sig_digits) |
|
204
|
|
|
else: |
|
205
|
|
|
# Decimal notation |
|
206
|
|
|
prec = precision if precision and precision > 0 else 0 |
|
207
|
|
|
formatted = str("%%.%sf" % prec) % result |
|
208
|
|
|
if float(formatted) == 0 and '-' in formatted: |
|
209
|
|
|
# We don't want things like '-0.00' |
|
210
|
|
|
formatted = formatted.replace('-', '') |
|
211
|
|
|
return formatted |
|
212
|
|
|
|
|
213
|
|
|
|
|
214
|
|
|
def format_uncertainty(analysis, result, decimalmark='.', sciformat=1): |
|
215
|
|
|
""" |
|
216
|
|
|
Returns the formatted uncertainty according to the analysis, result |
|
217
|
|
|
and decimal mark specified following these rules: |
|
218
|
|
|
|
|
219
|
|
|
If the "Calculate precision from uncertainties" is enabled in |
|
220
|
|
|
the Analysis service, and |
|
221
|
|
|
|
|
222
|
|
|
a) If the the non-decimal number of digits of the result is above |
|
223
|
|
|
the service's ExponentialFormatPrecision, the uncertainty will |
|
224
|
|
|
be formatted in scientific notation. The uncertainty exponential |
|
225
|
|
|
value used will be the same as the one used for the result. The |
|
226
|
|
|
uncertainty will be rounded according to the same precision as |
|
227
|
|
|
the result. |
|
228
|
|
|
|
|
229
|
|
|
Example: |
|
230
|
|
|
Given an Analysis with an uncertainty of 37 for a range of |
|
231
|
|
|
results between 30000 and 40000, with an |
|
232
|
|
|
ExponentialFormatPrecision equal to 4 and a result of 32092, |
|
233
|
|
|
this method will return 0.004E+04 |
|
234
|
|
|
|
|
235
|
|
|
b) If the number of digits of the integer part of the result is |
|
236
|
|
|
below the ExponentialFormatPrecision, the uncertainty will be |
|
237
|
|
|
formatted as decimal notation and the uncertainty will be |
|
238
|
|
|
rounded one position after reaching the last 0 (precision |
|
239
|
|
|
calculated according to the uncertainty value). |
|
240
|
|
|
|
|
241
|
|
|
Example: |
|
242
|
|
|
Given an Analysis with an uncertainty of 0.22 for a range of |
|
243
|
|
|
results between 1 and 10 with an ExponentialFormatPrecision |
|
244
|
|
|
equal to 4 and a result of 5.234, this method will return 0.2 |
|
245
|
|
|
|
|
246
|
|
|
If the "Calculate precision from Uncertainties" is disabled in the |
|
247
|
|
|
analysis service, the same rules described above applies, but the |
|
248
|
|
|
precision used for rounding the uncertainty is not calculated from |
|
249
|
|
|
the uncertainty neither the result. The fixed length precision is |
|
250
|
|
|
used instead. |
|
251
|
|
|
|
|
252
|
|
|
For further details, visit |
|
253
|
|
|
https://jira.bikalabs.com/browse/LIMS-1334 |
|
254
|
|
|
|
|
255
|
|
|
If the result is not floatable or no uncertainty defined, returns |
|
256
|
|
|
an empty string. |
|
257
|
|
|
|
|
258
|
|
|
The default decimal mark '.' will be replaced by the decimalmark |
|
259
|
|
|
specified. |
|
260
|
|
|
|
|
261
|
|
|
:param analysis: the analysis from which the uncertainty, precision |
|
262
|
|
|
and other additional info have to be retrieved |
|
263
|
|
|
:param result: result of the analysis. Used to retrieve and/or |
|
264
|
|
|
calculate the precision and/or uncertainty |
|
265
|
|
|
:param decimalmark: decimal mark to use. By default '.' |
|
266
|
|
|
:param sciformat: 1. The sci notation has to be formatted as aE^+b |
|
267
|
|
|
2. The sci notation has to be formatted as ax10^b |
|
268
|
|
|
3. As 2, but with super html entity for exp |
|
269
|
|
|
4. The sci notation has to be formatted as a·10^b |
|
270
|
|
|
5. As 4, but with super html entity for exp |
|
271
|
|
|
By default 1 |
|
272
|
|
|
:returns: the formatted uncertainty |
|
273
|
|
|
""" |
|
274
|
|
|
try: |
|
275
|
|
|
result = float(result) |
|
276
|
|
|
except ValueError: |
|
277
|
|
|
return "" |
|
278
|
|
|
|
|
279
|
|
|
objres = None |
|
280
|
|
|
try: |
|
281
|
|
|
objres = float(analysis.getResult()) |
|
282
|
|
|
except ValueError: |
|
283
|
|
|
pass |
|
284
|
|
|
|
|
285
|
|
|
if result == objres: |
|
286
|
|
|
# To avoid problems with DLs |
|
287
|
|
|
uncertainty = analysis.getUncertainty() |
|
288
|
|
|
else: |
|
289
|
|
|
uncertainty = analysis.getUncertainty(result) |
|
290
|
|
|
|
|
291
|
|
|
if uncertainty is None or uncertainty == 0: |
|
292
|
|
|
return "" |
|
293
|
|
|
|
|
294
|
|
|
# Scientific notation? |
|
295
|
|
|
# Get the default precision for scientific notation |
|
296
|
|
|
threshold = analysis.getExponentialFormatPrecision() |
|
297
|
|
|
precision = analysis.getPrecision(result) |
|
298
|
|
|
formatted = _format_decimal_or_sci(uncertainty, precision, threshold, |
|
299
|
|
|
sciformat) |
|
300
|
|
|
return formatDecimalMark(formatted, decimalmark) |
|
301
|
|
|
|
|
302
|
|
|
|
|
303
|
|
|
def format_numeric_result(analysis, result, decimalmark='.', sciformat=1): |
|
304
|
|
|
""" |
|
305
|
|
|
Returns the formatted number part of a results value. This is |
|
306
|
|
|
responsible for deciding the precision, and notation of numeric |
|
307
|
|
|
values in accordance to the uncertainty. If a non-numeric |
|
308
|
|
|
result value is given, the value will be returned unchanged. |
|
309
|
|
|
|
|
310
|
|
|
The following rules apply: |
|
311
|
|
|
|
|
312
|
|
|
If the "Calculate precision from uncertainties" is enabled in |
|
313
|
|
|
the Analysis service, and |
|
314
|
|
|
|
|
315
|
|
|
a) If the non-decimal number of digits of the result is above |
|
316
|
|
|
the service's ExponentialFormatPrecision, the result will |
|
317
|
|
|
be formatted in scientific notation. |
|
318
|
|
|
|
|
319
|
|
|
Example: |
|
320
|
|
|
Given an Analysis with an uncertainty of 37 for a range of |
|
321
|
|
|
results between 30000 and 40000, with an |
|
322
|
|
|
ExponentialFormatPrecision equal to 4 and a result of 32092, |
|
323
|
|
|
this method will return 3.2092E+04 |
|
324
|
|
|
|
|
325
|
|
|
b) If the number of digits of the integer part of the result is |
|
326
|
|
|
below the ExponentialFormatPrecision, the result will be |
|
327
|
|
|
formatted as decimal notation and the resulta will be rounded |
|
328
|
|
|
in accordance to the precision (calculated from the uncertainty) |
|
329
|
|
|
|
|
330
|
|
|
Example: |
|
331
|
|
|
Given an Analysis with an uncertainty of 0.22 for a range of |
|
332
|
|
|
results between 1 and 10 with an ExponentialFormatPrecision |
|
333
|
|
|
equal to 4 and a result of 5.234, this method will return 5.2 |
|
334
|
|
|
|
|
335
|
|
|
If the "Calculate precision from Uncertainties" is disabled in the |
|
336
|
|
|
analysis service, the same rules described above applies, but the |
|
337
|
|
|
precision used for rounding the result is not calculated from |
|
338
|
|
|
the uncertainty. The fixed length precision is used instead. |
|
339
|
|
|
|
|
340
|
|
|
For further details, visit |
|
341
|
|
|
https://jira.bikalabs.com/browse/LIMS-1334 |
|
342
|
|
|
|
|
343
|
|
|
The default decimal mark '.' will be replaced by the decimalmark |
|
344
|
|
|
specified. |
|
345
|
|
|
|
|
346
|
|
|
:param analysis: the analysis from which the uncertainty, precision |
|
347
|
|
|
and other additional info have to be retrieved |
|
348
|
|
|
:param result: result to be formatted. |
|
349
|
|
|
:param decimalmark: decimal mark to use. By default '.' |
|
350
|
|
|
:param sciformat: 1. The sci notation has to be formatted as aE^+b |
|
351
|
|
|
2. The sci notation has to be formatted as ax10^b |
|
352
|
|
|
3. As 2, but with super html entity for exp |
|
353
|
|
|
4. The sci notation has to be formatted as a·10^b |
|
354
|
|
|
5. As 4, but with super html entity for exp |
|
355
|
|
|
By default 1 |
|
356
|
|
|
:result: should be a string to preserve the decimal precision. |
|
357
|
|
|
:returns: the formatted result as string |
|
358
|
|
|
""" |
|
359
|
|
|
try: |
|
360
|
|
|
result = float(result) |
|
361
|
|
|
except ValueError: |
|
362
|
|
|
return result |
|
363
|
|
|
|
|
364
|
|
|
# continuing with 'nan' result will cause formatting to fail. |
|
365
|
|
|
if math.isnan(result): |
|
366
|
|
|
return result |
|
367
|
|
|
|
|
368
|
|
|
# Scientific notation? |
|
369
|
|
|
# Get the default precision for scientific notation |
|
370
|
|
|
threshold = analysis.getExponentialFormatPrecision() |
|
371
|
|
|
precision = analysis.getPrecision(result) |
|
372
|
|
|
formatted = _format_decimal_or_sci(result, precision, threshold, sciformat) |
|
373
|
|
|
return formatDecimalMark(formatted, decimalmark) |
|
374
|
|
|
|
|
375
|
|
|
|
|
376
|
|
|
def get_method_instrument_constraints(context, uids): |
|
377
|
|
|
""" |
|
378
|
|
|
Returns a dictionary with the constraints and rules for |
|
379
|
|
|
methods, instruments and results to be applied to each of the |
|
380
|
|
|
analyses specified in the param uids (an array of uids). |
|
381
|
|
|
See docs/imm_results_entry_behaviour.png for further details |
|
382
|
|
|
""" |
|
383
|
|
|
constraints = {} |
|
384
|
|
|
uc = getToolByName(context, 'uid_catalog') |
|
385
|
|
|
analyses = uc(portal_type=['Analysis', 'ReferenceAnalysis'], |
|
386
|
|
|
UID=uids) |
|
387
|
|
|
cached_servs = {} |
|
388
|
|
|
for analysis in analyses: |
|
389
|
|
|
if not analysis: |
|
390
|
|
|
continue |
|
391
|
|
|
analysis = analysis.getObject() |
|
392
|
|
|
auid = analysis.UID() |
|
393
|
|
|
suid = analysis.getServiceUID() |
|
394
|
|
|
refan = analysis.portal_type == 'ReferenceAnalysis' |
|
395
|
|
|
cachedkey = "qc" if refan else "re" |
|
396
|
|
|
if suid in cached_servs.get(cachedkey, []): |
|
397
|
|
|
constraints[auid] = cached_servs[cachedkey][suid] |
|
398
|
|
|
continue |
|
399
|
|
|
|
|
400
|
|
|
if not cached_servs.get(cachedkey, None): |
|
401
|
|
|
cached_servs[cachedkey] = {suid: {}} |
|
402
|
|
|
else: |
|
403
|
|
|
cached_servs[cachedkey][suid] = {} |
|
404
|
|
|
constraints[auid] = {} |
|
405
|
|
|
|
|
406
|
|
|
allowed_instruments = analysis.getAllowedInstruments() |
|
407
|
|
|
|
|
408
|
|
|
# Analysis allows manual/instrument entry? |
|
409
|
|
|
s_mentry = analysis.getManualEntryOfResults() |
|
410
|
|
|
s_ientry = analysis.getInstrumentEntryOfResults() |
|
411
|
|
|
s_instrums = allowed_instruments if s_ientry else [] |
|
412
|
|
|
s_instrums = [instr.UID() for instr in s_instrums] |
|
413
|
|
|
a_dinstrum = analysis.getInstrument() if s_ientry else None |
|
414
|
|
|
s_methods = analysis.getAllowedMethods() |
|
415
|
|
|
s_dmethod = analysis.getMethod() |
|
416
|
|
|
dmuid = s_dmethod.UID() if s_dmethod else '' |
|
417
|
|
|
diuid = a_dinstrum.UID() if a_dinstrum else '' |
|
418
|
|
|
|
|
419
|
|
|
# To take into account ASs with no method assigned by default or |
|
420
|
|
|
# ASs that have an instrument assigned by default that doesn't have |
|
421
|
|
|
# a method associated. |
|
422
|
|
|
if s_mentry or not s_dmethod: |
|
423
|
|
|
s_methods += [None] |
|
424
|
|
|
|
|
425
|
|
|
for method in s_methods: |
|
426
|
|
|
# Method manual entry? |
|
427
|
|
|
m_mentry = method.isManualEntryOfResults() if method else True |
|
428
|
|
|
|
|
429
|
|
|
instrs = [] |
|
430
|
|
|
if method: |
|
431
|
|
|
# Instruments available for this method and analysis? |
|
432
|
|
|
instrs = [i for i in method.getInstruments() |
|
433
|
|
|
if i.UID() in s_instrums] |
|
434
|
|
|
else: |
|
435
|
|
|
# What about instruments without a method assigned? |
|
436
|
|
|
instrs = [i for i in allowed_instruments |
|
437
|
|
|
if i.UID() in s_instrums and not i.getMethods()] |
|
438
|
|
|
|
|
439
|
|
|
instuids = [i.UID() for i in instrs] |
|
440
|
|
|
v_instrobjs = [i for i in instrs if i.isValid()] |
|
441
|
|
|
v_instrs = [i.UID() for i in v_instrobjs] |
|
442
|
|
|
muid = method.UID() if method else '' |
|
443
|
|
|
|
|
444
|
|
|
# PREMISES |
|
445
|
|
|
# p1: Analysis allows manual entry? |
|
446
|
|
|
# p2: Analysis allows instrument entry? |
|
447
|
|
|
# p3: Method selected and non empty? |
|
448
|
|
|
# p4: Method allows manual entry? |
|
449
|
|
|
# p5: At least one instrument available for this method? |
|
450
|
|
|
# p6: Valid instruments available? |
|
451
|
|
|
# p7: All instruments valid? |
|
452
|
|
|
# p8: Methods allow the service's default instrument? |
|
453
|
|
|
# p9: Default instrument valid? |
|
454
|
|
|
premises = [ |
|
455
|
|
|
"R" if not refan else 'Q', |
|
456
|
|
|
"Y" if s_mentry else "N", |
|
457
|
|
|
"Y" if s_ientry else "N", |
|
458
|
|
|
"Y" if method else "N", |
|
459
|
|
|
"Y" if m_mentry else "N", |
|
460
|
|
|
"Y" if instrs else "N", |
|
461
|
|
|
"Y" if v_instrs or not instrs else "N", |
|
462
|
|
|
"Y" if len(v_instrs) == len(instrs) else "N", |
|
463
|
|
|
"Y" if diuid in instuids else "N", |
|
464
|
|
|
"Y" if a_dinstrum and a_dinstrum.isValid() else "N", |
|
465
|
|
|
] |
|
466
|
|
|
tprem = ''.join(premises) |
|
467
|
|
|
|
|
468
|
|
|
fiuid = v_instrs[0] if v_instrs else '' |
|
469
|
|
|
instrtitle = to_unicode(a_dinstrum.Title()) if a_dinstrum else '' |
|
470
|
|
|
iinstrs = ', '.join([to_unicode(i.Title()) for i in instrs |
|
471
|
|
|
if i.UID() not in v_instrs]) |
|
472
|
|
|
dmeth = to_unicode(method.Title()) if method else '' |
|
473
|
|
|
m1 = _("Invalid instruments are not displayed: %s") % iinstrs |
|
474
|
|
|
m2 = _("Default instrument %s is not valid") % instrtitle |
|
475
|
|
|
m3 = _("No valid instruments available: %s ") % iinstrs |
|
476
|
|
|
m4 = _("Manual entry of results for method %s is not allowed " |
|
477
|
|
|
"and no valid instruments found: %s") % (dmeth, iinstrs) |
|
478
|
|
|
m5 = _("The method %s is not valid: no manual entry allowed " |
|
479
|
|
|
"and no instrument assigned") % dmeth |
|
480
|
|
|
m6 = _("The method %s is not valid: only instrument entry for " |
|
481
|
|
|
"this analysis is allowed, but the method has no " |
|
482
|
|
|
"instrument assigned") % dmeth |
|
483
|
|
|
m7 = _("Only instrument entry for this analysis is allowed, " |
|
484
|
|
|
"but there is no instrument assigned") |
|
485
|
|
|
|
|
486
|
|
|
""" |
|
487
|
|
|
Matrix dict keys char positions: (True: Y, False: N) |
|
488
|
|
|
0: (R)egular analysis or (Q)C analysis |
|
489
|
|
|
1: Analysis allows manual entry? |
|
490
|
|
|
2: Analysis allows instrument entry? |
|
491
|
|
|
3: Method is not None? |
|
492
|
|
|
4: Method allows manual entry? |
|
493
|
|
|
5: At least one instrument avialable for the method? |
|
494
|
|
|
6: Valid instruments available? |
|
495
|
|
|
7: All instruments valid? |
|
496
|
|
|
8: Method allows the service's default instrument? |
|
497
|
|
|
9: Default instrument valid? |
|
498
|
|
|
|
|
499
|
|
|
Matrix dict values array indexes: |
|
500
|
|
|
0: Method list visible? YES:1, NO:0, YES(a):2, YES(r):3 |
|
501
|
|
|
1: Add "None" in methods list? YES:1, NO:0, NO(g):2 |
|
502
|
|
|
2: Instr. list visible? YES:1, NO:0 |
|
503
|
|
|
3: Add "None" in instrums list? YES: 1, NO:0 |
|
504
|
|
|
4: UID of the selected instrument or '' if None |
|
505
|
|
|
5: Results field editable? YES: 1, NO:0 |
|
506
|
|
|
6: Alert message string |
|
507
|
|
|
|
|
508
|
|
|
See docs/imm_results_entry_behaviour.png for further details |
|
509
|
|
|
""" |
|
510
|
|
|
matrix = { |
|
511
|
|
|
# Regular analyses |
|
512
|
|
|
'RYYYYYYYY': [1, 1, 1, 1, diuid, 1, ''], # B1 |
|
513
|
|
|
'RYYYYYYYN': [1, 1, 1, 1, '', 1, ''], # B2 |
|
514
|
|
|
'RYYYYYYNYY': [1, 1, 1, 1, diuid, 1, m1], # B3 |
|
515
|
|
|
'RYYYYYYNYN': [1, 1, 1, 1, '', 1, m2], # B4 |
|
516
|
|
|
'RYYYYYYNN': [1, 1, 1, 1, '', 1, m1], # B5 |
|
517
|
|
|
'RYYYYYN': [1, 1, 1, 1, '', 1, m3], # B6 |
|
518
|
|
|
'RYYYYN': [1, 1, 1, 1, '', 1, ''], # B7 |
|
519
|
|
|
'RYYYNYYYY': [1, 1, 1, 0, diuid, 1, ''], # B8 |
|
520
|
|
|
'RYYYNYYYN': [1, 1, 1, 0, fiuid, 1, ''], # B9 |
|
521
|
|
|
'RYYYNYYNYY': [1, 1, 1, 0, diuid, 1, m1], # B10 |
|
522
|
|
|
'RYYYNYYNYN': [1, 1, 1, 1, '', 0, m2], # B11 |
|
523
|
|
|
'RYYYNYYNN': [1, 1, 1, 0, fiuid, 1, m1], # B12 |
|
524
|
|
|
'RYYYNYN': [1, 1, 1, 1, '', 0, m4], # B13 |
|
525
|
|
|
'RYYYNN': [1, 1, 1, 1, '', 0, m5], # B14 |
|
526
|
|
|
'RYYNYYYYY': [1, 1, 1, 1, diuid, 1, ''], # B15 |
|
527
|
|
|
'RYYNYYYYN': [1, 1, 1, 1, '', 1, ''], # B16 |
|
528
|
|
|
'RYYNYYYNYY': [1, 1, 1, 1, diuid, 1, m1], # B17 |
|
529
|
|
|
'RYYNYYYNYN': [1, 1, 1, 1, '', 1, m2], # B18 |
|
530
|
|
|
'RYYNYYYNN': [1, 1, 1, 1, '', 1, m1], # B19 |
|
531
|
|
|
'RYYNYYN': [1, 1, 1, 1, '', 1, m3], # B20 |
|
532
|
|
|
'RYYNYN': [1, 1, 1, 1, '', 1, ''], # B21 |
|
533
|
|
|
'RYNY': [2, 0, 0, 0, '', 1, ''], # B22 |
|
534
|
|
|
'RYNN': [0, 0, 0, 0, '', 1, ''], # B23 |
|
535
|
|
|
'RNYYYYYYY': [3, 2, 1, 1, diuid, 1, ''], # B24 |
|
536
|
|
|
'RNYYYYYYN': [3, 2, 1, 1, '', 1, ''], # B25 |
|
537
|
|
|
'RNYYYYYNYY': [3, 2, 1, 1, diuid, 1, m1], # B26 |
|
538
|
|
|
'RNYYYYYNYN': [3, 2, 1, 1, '', 1, m2], # B27 |
|
539
|
|
|
'RNYYYYYNN': [3, 2, 1, 1, '', 1, m1], # B28 |
|
540
|
|
|
'RNYYYYN': [3, 2, 1, 1, '', 1, m3], # B29 |
|
541
|
|
|
'RNYYYN': [3, 2, 1, 1, '', 0, m6], # B30 |
|
542
|
|
|
'RNYYNYYYY': [3, 2, 1, 0, diuid, 1, ''], # B31 |
|
543
|
|
|
'RNYYNYYYN': [3, 2, 1, 0, fiuid, 1, ''], # B32 |
|
544
|
|
|
'RNYYNYYNYY': [3, 2, 1, 0, diuid, 1, m1], # B33 |
|
545
|
|
|
'RNYYNYYNYN': [3, 2, 1, 1, '', 0, m2], # B34 |
|
546
|
|
|
'RNYYNYYNN': [3, 2, 1, 0, fiuid, 1, m1], # B35 |
|
547
|
|
|
'RNYYNYN': [3, 2, 1, 1, '', 0, m3], # B36 |
|
548
|
|
|
'RNYYNN': [3, 2, 1, 1, '', 0, m6], # B37 |
|
549
|
|
|
'RNYNYYYYY': [3, 1, 1, 0, diuid, 1, ''], # B38 |
|
550
|
|
|
'RNYNYYYYN': [3, 1, 1, 0, fiuid, 1, ''], # B39 |
|
551
|
|
|
'RNYNYYYNYY': [3, 1, 1, 0, diuid, 1, m1], # B40 |
|
552
|
|
|
'RNYNYYYNYN': [3, 1, 1, 1, '', 0, m2], # B41 |
|
553
|
|
|
'RNYNYYYNN': [3, 1, 1, 0, fiuid, 1, m1], # B42 |
|
554
|
|
|
'RNYNYYN': [3, 1, 1, 0, '', 0, m3], # B43 |
|
555
|
|
|
'RNYNYN': [3, 1, 1, 0, '', 0, m7], # B44 |
|
556
|
|
|
# QC Analyses |
|
557
|
|
|
'QYYYYYYYY': [1, 1, 1, 1, diuid, 1, ''], # C1 |
|
558
|
|
|
'QYYYYYYYN': [1, 1, 1, 1, '', 1, ''], # C2 |
|
559
|
|
|
'QYYYYYYNYY': [1, 1, 1, 1, diuid, 1, ''], # C3 |
|
560
|
|
|
'QYYYYYYNYN': [1, 1, 1, 1, diuid, 1, ''], # C4 |
|
561
|
|
|
'QYYYYYYNN': [1, 1, 1, 1, '', 1, ''], # C5 |
|
562
|
|
|
'QYYYYYN': [1, 1, 1, 1, '', 1, ''], # C6 |
|
563
|
|
|
'QYYYYN': [1, 1, 1, 1, '', 1, ''], # C7 |
|
564
|
|
|
'QYYYNYYYY': [1, 1, 1, 0, diuid, 1, ''], # C8 |
|
565
|
|
|
'QYYYNYYYN': [1, 1, 1, 0, fiuid, 1, ''], # C9 |
|
566
|
|
|
'QYYYNYYNYY': [1, 1, 1, 0, diuid, 1, ''], # C10 |
|
567
|
|
|
'QYYYNYYNYN': [1, 1, 1, 0, diuid, 1, ''], # C11 |
|
568
|
|
|
'QYYYNYYNN': [1, 1, 1, 0, fiuid, 1, ''], # C12 |
|
569
|
|
|
'QYYYNYN': [1, 1, 1, 0, fiuid, 1, ''], # C13 |
|
570
|
|
|
'QYYYNN': [1, 1, 1, 1, '', 0, m5], # C14 |
|
571
|
|
|
'QYYNYYYYY': [1, 1, 1, 1, diuid, 1, ''], # C15 |
|
572
|
|
|
'QYYNYYYYN': [1, 1, 1, 1, '', 1, ''], # C16 |
|
573
|
|
|
'QYYNYYYNYY': [1, 1, 1, 1, diuid, 1, ''], # C17 |
|
574
|
|
|
'QYYNYYYNYN': [1, 1, 1, 1, diuid, 1, ''], # C18 |
|
575
|
|
|
'QYYNYYYNN': [1, 1, 1, 1, fiuid, 1, ''], # C19 |
|
576
|
|
|
'QYYNYYN': [1, 1, 1, 1, diuid, 1, ''], # C20 |
|
577
|
|
|
'QYYNYN': [1, 1, 1, 1, '', 1, ''], # C21 |
|
578
|
|
|
'QYNY': [2, 0, 0, 0, '', 1, ''], # C22 |
|
579
|
|
|
'QYNN': [0, 0, 0, 0, '', 1, ''], # C23 |
|
580
|
|
|
'QNYYYYYYY': [3, 2, 1, 1, diuid, 1, ''], # C24 |
|
581
|
|
|
'QNYYYYYYN': [3, 2, 1, 1, '', 1, ''], # C25 |
|
582
|
|
|
'QNYYYYYNYY': [3, 2, 1, 1, diuid, 1, ''], # C26 |
|
583
|
|
|
'QNYYYYYNYN': [3, 2, 1, 1, diuid, 1, ''], # C27 |
|
584
|
|
|
'QNYYYYYNN': [3, 2, 1, 1, '', 1, ''], # C28 |
|
585
|
|
|
'QNYYYYN': [3, 2, 1, 1, '', 1, ''], # C29 |
|
586
|
|
|
'QNYYYN': [3, 2, 1, 1, '', 0, m6], # C30 |
|
587
|
|
|
'QNYYNYYYY': [3, 2, 1, 0, diuid, 1, ''], # C31 |
|
588
|
|
|
'QNYYNYYYN': [3, 2, 1, 0, fiuid, 1, ''], # C32 |
|
589
|
|
|
'QNYYNYYNYY': [3, 2, 1, 0, diuid, 1, ''], # C33 |
|
590
|
|
|
'QNYYNYYNYN': [3, 2, 1, 0, diuid, 1, ''], # C34 |
|
591
|
|
|
'QNYYNYYNN': [3, 2, 1, 0, fiuid, 1, ''], # C35 |
|
592
|
|
|
'QNYYNYN': [3, 2, 1, 0, fiuid, 1, ''], # C36 |
|
593
|
|
|
'QNYYNN': [3, 2, 1, 1, '', 0, m5], # C37 |
|
594
|
|
|
'QNYNYYYYY': [3, 1, 1, 0, diuid, 1, ''], # C38 |
|
595
|
|
|
'QNYNYYYYN': [3, 1, 1, 0, fiuid, 1, ''], # C39 |
|
596
|
|
|
'QNYNYYYNYY': [3, 1, 1, 0, diuid, 1, ''], # C40 |
|
597
|
|
|
'QNYNYYYNYN': [3, 1, 1, 0, diuid, 1, ''], # C41 |
|
598
|
|
|
'QNYNYYYNN': [3, 1, 1, 0, fiuid, 1, ''], # C42 |
|
599
|
|
|
'QNYNYYN': [3, 1, 1, 0, fiuid, 1, ''], # C43 |
|
600
|
|
|
'QNYNYN': [3, 1, 1, 1, '', 0, m7], # C44 |
|
601
|
|
|
} |
|
602
|
|
|
targ = [v for k, v in matrix.items() if tprem.startswith(k)] |
|
603
|
|
|
if not targ: |
|
604
|
|
|
targ = [[1, 1, 1, 1, '', 0, 'Key not found: %s' % tprem], ] |
|
605
|
|
|
targ = targ[0] |
|
606
|
|
|
atitle = analysis.Title() if analysis else "None" |
|
607
|
|
|
mtitle = method.Title() if method else "None" |
|
608
|
|
|
instdi = {} |
|
609
|
|
|
if refan and instrs: |
|
610
|
|
|
instdi = {i.UID(): i.Title() for i in instrs} |
|
611
|
|
|
elif not refan and v_instrobjs: |
|
612
|
|
|
instdi = {i.UID(): i.Title() for i in v_instrobjs} |
|
613
|
|
|
targ += [instdi, mtitle, atitle, tprem] |
|
614
|
|
|
constraints[auid][muid] = targ |
|
615
|
|
|
cached_servs[cachedkey][suid][muid] = targ |
|
616
|
|
|
return constraints |
|
617
|
|
|
|