1 | import _ from 'lodash';
|
2 | import { formatDecisionRules } from './formatter';
|
3 | import { OPERATORS } from './constants';
|
4 |
|
5 | const REDUCER_FROM_DECISION_RULE = {
|
6 | [OPERATORS.IS]: {
|
7 | [OPERATORS.IS]: (decisionRule1, decisionRule2) => {
|
8 | if (decisionRule1.operand && decisionRule1.operand != decisionRule2.operand) {
|
9 | throw new Error(`Operator "${OPERATORS.IS}" can't have different value. Set to "${decisionRule1.operand}" and receive "${decisionRule2.operand}"`);
|
10 | }
|
11 | return {
|
12 | property: decisionRule1.property,
|
13 | operator: OPERATORS.IS,
|
14 | operand: decisionRule2.operand
|
15 | };
|
16 | }
|
17 | },
|
18 | [OPERATORS.IN]: {
|
19 | [OPERATORS.IN]: (decisionRule1, decisionRule2) => {
|
20 | const [o1From, o1To] = decisionRule1.operand;
|
21 | const [o2From, o2To] = decisionRule2.operand;
|
22 |
|
23 | const o1IsCyclic = o1From > o1To;
|
24 | const o2IsCyclic = o2From > o2To;
|
25 | const o2FromInO1 = o1IsCyclic ? (o2From >= o1From || o2From <= o1To) : (o2From >= o1From && o2From <= o1To);
|
26 | const o2ToInO1 = o1IsCyclic ? (o2To >= o1From || o2To <= o1To) : (o2To >= o1From && o2To <= o1To);
|
27 | const o1FromInO2 = o2IsCyclic ? (o1From >= o2From || o1From <= o2To) : (o1From >= o2From && o1From <= o2To);
|
28 | const o1ToInO2 = o2IsCyclic ? (o1To >= o2From || o1To <= o2To) : (o1To >= o2From && o1To <= o2To);
|
29 |
|
30 | if (o1FromInO2 && o1ToInO2) {
|
31 |
|
32 |
|
33 |
|
34 | return decisionRule1;
|
35 | }
|
36 |
|
37 | if (o2FromInO1 && o2ToInO1) {
|
38 |
|
39 |
|
40 |
|
41 | return decisionRule2;
|
42 | }
|
43 |
|
44 | if (o2FromInO1 && o1ToInO2) {
|
45 |
|
46 |
|
47 |
|
48 | return {
|
49 | property: decisionRule1.property,
|
50 | operator: OPERATORS.IN,
|
51 | operand: [o2From, o1To]
|
52 | };
|
53 | }
|
54 |
|
55 | if (o2ToInO1 && o1FromInO2) {
|
56 |
|
57 |
|
58 |
|
59 | return {
|
60 | property: decisionRule1.property,
|
61 | operator: OPERATORS.IN,
|
62 | operand: [o1From, o2To]
|
63 | };
|
64 | }
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | throw new Error(`Unable to reduce decision rules '${formatDecisionRules([decisionRule1])}' and '${formatDecisionRules([decisionRule2])}': the resulting rule is not fulfillable.`);
|
70 | },
|
71 | [OPERATORS.GTE]: (decisionRule1, decisionRule2) => {
|
72 | const [o1From, o1To] = decisionRule1.operand;
|
73 | const o2 = decisionRule2.operand;
|
74 |
|
75 | const o1IsCyclic = o1From > o1To;
|
76 |
|
77 | if (o1IsCyclic) {
|
78 |
|
79 | throw new Error(`Unable to reduce decision rules '${formatDecisionRules([decisionRule1])}' and '${formatDecisionRules([decisionRule2])}': the resulting rule is not fulfillable.`);
|
80 | }
|
81 |
|
82 | if (o2 >= o1To) {
|
83 |
|
84 |
|
85 |
|
86 | throw new Error(`Unable to reduce decision rules '${formatDecisionRules([decisionRule1])}' and '${formatDecisionRules([decisionRule2])}': the resulting rule is not fulfillable.`);
|
87 | }
|
88 |
|
89 | if (o2 >= o1From && o2 < o1To) {
|
90 |
|
91 |
|
92 |
|
93 | return {
|
94 | property: decisionRule1.property,
|
95 | operator: OPERATORS.IN,
|
96 | operand: [o2, o1To]
|
97 | };
|
98 | }
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | return decisionRule1;
|
104 | },
|
105 | [OPERATORS.LT]: (decisionRule1, decisionRule2) => {
|
106 | const [o1From, o1To] = decisionRule1.operand;
|
107 | const o2 = decisionRule2.operand;
|
108 |
|
109 | const o1IsCyclic = o1From > o1To;
|
110 |
|
111 | if (o1IsCyclic) {
|
112 |
|
113 | throw new Error(`Unable to reduce decision rules '${formatDecisionRules([decisionRule1])}' and '${formatDecisionRules([decisionRule2])}': the resulting rule is not fulfillable.`);
|
114 | }
|
115 |
|
116 | if (o2 < o1From) {
|
117 |
|
118 |
|
119 |
|
120 | throw new Error(`Unable to reduce decision rules '${formatDecisionRules([decisionRule1])}' and '${formatDecisionRules([decisionRule2])}': the resulting rule is not fulfillable.`);
|
121 | }
|
122 |
|
123 | if (o2 >= o1From && o2 < o1To) {
|
124 |
|
125 |
|
126 |
|
127 | return {
|
128 | property: decisionRule1.property,
|
129 | operator: OPERATORS.IN,
|
130 | operand: [o1From, o2]
|
131 | };
|
132 | }
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | return decisionRule1;
|
138 | }
|
139 | },
|
140 | [OPERATORS.GTE]: {
|
141 | [OPERATORS.IN]: (decisionRule1, decisionRule2) => REDUCER_FROM_DECISION_RULE[OPERATORS.IN][OPERATORS.GTE](decisionRule2, decisionRule1),
|
142 | [OPERATORS.GTE]: (decisionRule1, decisionRule2) => ({
|
143 | property: decisionRule1.property,
|
144 | operator: OPERATORS.GTE,
|
145 | operand: Math.max(decisionRule1.operand, decisionRule2.operand)
|
146 | }),
|
147 | [OPERATORS.LT]: (decisionRule1, decisionRule2) => {
|
148 | const newLowerBound = decisionRule1.operand;
|
149 | const newUpperBound = decisionRule2.operand;
|
150 | if (newUpperBound < newLowerBound) {
|
151 | throw new Error(`Unable to reduce decision rules '${formatDecisionRules([decisionRule1])}' and '${formatDecisionRules([decisionRule2])}': the resulting rule is not fulfillable.`);
|
152 | }
|
153 | return {
|
154 | property: decisionRule1.property,
|
155 | operator: OPERATORS.IN,
|
156 | operand: [newLowerBound, newUpperBound]
|
157 | };
|
158 | }
|
159 | },
|
160 | [OPERATORS.LT]: {
|
161 | [OPERATORS.IN]: (decisionRule1, decisionRule2) => REDUCER_FROM_DECISION_RULE[OPERATORS.IN][OPERATORS.LT](decisionRule2, decisionRule1),
|
162 | [OPERATORS.GTE]: (decisionRule1, decisionRule2) => REDUCER_FROM_DECISION_RULE[OPERATORS.GTE][OPERATORS.LT](decisionRule2, decisionRule1),
|
163 | [OPERATORS.LT]: (decisionRule1, decisionRule2) => ({
|
164 | property: decisionRule1.property,
|
165 | operator: OPERATORS.LT,
|
166 | operand: Math.min(decisionRule1.operand, decisionRule2.operand)
|
167 | })
|
168 | }
|
169 | };
|
170 |
|
171 | function decisionRuleReducer(decisionRule1, decisionRule2) {
|
172 | if (!decisionRule1 || !decisionRule2) {
|
173 | return decisionRule1 || decisionRule2;
|
174 | }
|
175 | const reducer = REDUCER_FROM_DECISION_RULE[decisionRule1.operator][decisionRule2.operator];
|
176 | if (!reducer) {
|
177 | throw new Error(`Unable to reduce decision rules '${formatDecisionRules([decisionRule1])}' and '${formatDecisionRules([decisionRule2])}': incompatible operators.`);
|
178 | }
|
179 | return reducer(decisionRule1, decisionRule2);
|
180 | }
|
181 |
|
182 | export function reduceDecisionRules(decisionRules) {
|
183 | const properties = _.uniq(_.map(decisionRules, ({ property }) => property));
|
184 | return _.map(properties, ((currentPropery) => _(decisionRules)
|
185 | .filter(({ property }) => property == currentPropery)
|
186 | .reduce(decisionRuleReducer, undefined)));
|
187 | }
|