UNPKG

6.97 kBJavaScriptView Raw
1import _ from 'lodash';
2import { formatDecisionRules } from './formatter';
3import { OPERATORS } from './constants';
4
5const 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 // o1 belongs to o2
32 // | o1 |
33 // | o2 |
34 return decisionRule1;
35 }
36
37 if (o2FromInO1 && o2ToInO1) {
38 // o2 belongs to o1
39 // | o1 |
40 // | o2 |
41 return decisionRule2;
42 }
43
44 if (o2FromInO1 && o1ToInO2) {
45 // overlap 1
46 // | o1 |
47 // | o2 |
48 return {
49 property: decisionRule1.property,
50 operator: OPERATORS.IN,
51 operand: [o2From, o1To]
52 };
53 }
54
55 if (o2ToInO1 && o1FromInO2) {
56 // overlap 2
57 // | o1 |
58 // | o2 |
59 return {
60 property: decisionRule1.property,
61 operator: OPERATORS.IN,
62 operand: [o1From, o2To]
63 };
64 }
65
66 // disjointed
67 // | o1 |
68 // | o2 |
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 // Cyclics makes no sense with single bound limits
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 // o2 after o1, disjointed
84 // | o1 |
85 // |o2
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 // o2 belongs to o1
91 // | o1 |
92 // |o2
93 return {
94 property: decisionRule1.property,
95 operator: OPERATORS.IN,
96 operand: [o2, o1To]
97 };
98 }
99
100 // o2 before o1
101 // | o1 |
102 // |o2
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 // Cyclics makes no sense with single bound limits
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 // o2 before o1, disjointed
118 // | o1 |
119 // o2|
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 // o2 belongs to o1
125 // | o1 |
126 // o2|
127 return {
128 property: decisionRule1.property,
129 operator: OPERATORS.IN,
130 operand: [o1From, o2]
131 };
132 }
133
134 // o2 after o1
135 // | o1 |
136 // o2|
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
171function 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
182export 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}