Passed
Push — master ( b60410...062e8c )
by Jordi
05:05
created

alysis.get_method_instrument_constraints()   F

Complexity

Conditions 36

Size

Total Lines 241
Code Lines 176

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 176
dl 0
loc 241
rs 0
c 0
b 0
f 0
cc 36
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like build.bika.lims.utils.analysis.get_method_instrument_constraints() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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