Completed
Push — master ( 65c723...b6585a )
by Jordi
76:39 queued 72:29
created

DynamicResultsRange.get_match_data()   A

Complexity

Conditions 4

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 23
rs 9.9
c 0
b 0
f 0
cc 4
nop 1
1
# -*- coding: utf-8 -*-
2
3
from bika.lims import api
4
from bika.lims.interfaces import IDynamicResultsRange
5
from zope.interface import implementer
6
7
marker = object()
8
9
DEFAULT_RANGE_KEYS = [
10
    "min",
11
    "warn_min",
12
    "min_operator",
13
    "minpanic",
14
    "max",
15
    "warn_max",
16
    "max",
17
    "maxpanic",
18
    "error",
19
]
20
21
22
@implementer(IDynamicResultsRange)
23
class DynamicResultsRange(object):
24
    """Default Dynamic Results Range Adapter
25
    """
26
27
    def __init__(self, analysis):
28
        self.analysis = analysis
29
        self.analysisrequest = analysis.getRequest()
30
        self.specification = self.analysisrequest.getSpecification()
31
        self.dynamicspec = None
32
        if self.specification:
33
            self.dynamicspec = self.specification.getDynamicAnalysisSpec()
34
35
    @property
36
    def keyword(self):
37
        """Analysis Keyword
38
        """
39
        return self.analysis.getKeyword()
40
41
    @property
42
    def range_keys(self):
43
        """The keys of the result range dict
44
        """
45
        if not self.specification:
46
            return DEFAULT_RANGE_KEYS
47
        # return the subfields of the specification
48
        return self.specification.getField("ResultsRange").subfields
49
50
    def convert(self, value):
51
        # convert referenced UIDs to the Title
52
        if api.is_uid(value):
53
            obj = api.get_object_by_uid(value)
54
            return api.get_title(obj)
55
        return value
56
57
    def get_match_data(self):
58
        """Returns a fieldname -> value mapping of context data
59
60
        The fieldnames are selected from the column names of the dynamic
61
        specifications file. E.g. the column "Method" of teh specifications
62
        file will lookup the value (title) of the Analysis and added to the
63
        mapping like this: `{"Method": "Method-1"}`.
64
65
        :returns: fieldname -> value mapping
66
        :rtype: dict
67
        """
68
        data = {}
69
70
        # Lookup the column names on the Analysis and the Analysis Request
71
        for column in self.dynamicspec.get_header():
72
            an_value = getattr(self.analysis, column, marker)
73
            ar_value = getattr(self.analysisrequest, column, marker)
74
            if an_value is not marker:
75
                data[column] = self.convert(an_value)
76
            elif ar_value is not marker:
77
                data[column] = self.convert(ar_value)
78
79
        return data
80
81
    def get_results_range(self):
82
        """Return the dynamic results range
83
84
        The returning dicitionary should containe at least the `min` and `max`
85
        values to override the ResultsRangeDict data.
86
87
        :returns: An `IResultsRangeDict` compatible dict
88
        :rtype: dict
89
        """
90
        if self.dynamicspec is None:
91
            return {}
92
        # A matching Analysis Keyword is mandatory for any further matches
93
        keyword = self.analysis.getKeyword()
94
        by_keyword = self.dynamicspec.get_by_keyword()
95
        # Get all specs (rows) from the Excel with the same Keyword
96
        specs = by_keyword.get(keyword)
97
        if not specs:
98
            return {}
99
100
        # Generate a match data object, which match both the column names and
101
        # the field names of the Analysis.
102
        match_data = self.get_match_data()
103
104
        rr = {}
105
106
        # Iterate over the rows and return the first where **all** values match
107
        # with the analysis' values
108
        for spec in specs:
109
            skip = False
110
111
            for k, v in match_data.items():
112
                # break if the values do not match
113
                if v != spec[k]:
114
                    skip = True
115
                    break
116
117
            # skip the whole specification row
118
            if skip:
119
                continue
120
121
            # at this point we have a match, update the results range dict
122
            for key in self.range_keys:
123
                value = spec.get(key, marker)
124
                # skip if the range key is not set in the Excel
125
                if value is marker:
126
                    continue
127
                # skip if the value is not floatable
128
                if not api.is_floatable(value):
129
                    continue
130
                # set the range value
131
                rr[key] = value
132
            # return the updated result range
133
            return rr
134
135
        return rr
136
137
    def __call__(self):
138
        return self.get_results_range()
139