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
|
|
|
|