UNPKG

9.08 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.decide = exports.reduceDecisionRules = exports.formatProperty = exports.formatDecisionRules = undefined;
7
8var _lodash = require('lodash');
9
10var _lodash2 = _interopRequireDefault(_lodash);
11
12var _reducer = require('./reducer');
13
14var _time = require('./time');
15
16var _errors = require('./errors');
17
18var _formatter = require('./formatter');
19
20var _timezones = require('./timezones');
21
22var _timezones2 = _interopRequireDefault(_timezones);
23
24function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
25
26function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
27
28function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
29
30var DECISION_FORMAT_VERSION = '1.1.0';
31
32var OPERATORS = {
33 'is': function is(context, value) {
34 return context === value;
35 },
36 '>=': function _(context, value) {
37 return context * 1 >= value;
38 },
39 '<': function _(context, value) {
40 return context * 1 < value;
41 },
42 '[in[': function _in(context, value) {
43 var context_val = context * 1;
44 var from = value[0];
45 var to = value[1];
46 //the interval is not looping
47 if (from < to) {
48 return context_val >= from && context_val < to;
49 }
50 //the interval IS looping
51 else {
52 return context_val >= from || context_val < to;
53 }
54 }
55};
56
57var VALUE_VALIDATOR = {
58 continuous: function continuous(value) {
59 return _lodash2.default.isFinite(value);
60 },
61 enum: function _enum(value) {
62 return _lodash2.default.isString(value);
63 },
64 timezone: function timezone(value) {
65 return (0, _timezones2.default)(value);
66 },
67 time_of_day: function time_of_day(value) {
68 return _lodash2.default.isFinite(value) && value >= 0 && value < 24;
69 },
70 day_of_week: function day_of_week(value) {
71 return _lodash2.default.isInteger(value) && value >= 0 && value <= 6;
72 },
73 day_of_month: function day_of_month(value) {
74 return _lodash2.default.isInteger(value) && value >= 1 && value <= 31;
75 },
76 month_of_year: function month_of_year(value) {
77 return _lodash2.default.isInteger(value) && value >= 1 && value <= 12;
78 }
79};
80
81function decideRecursion(node, context) {
82 // Leaf
83 if (!(node.children && node.children.length)) {
84 if (node.predicted_value == null) {
85 return {
86 predicted_value: undefined,
87 confidence: undefined,
88 decision_rules: [],
89 error: {
90 name: 'CraftAiNullDecisionError',
91 message: 'Unable to take decision: the decision tree has no valid predicted value for the given context.'
92 }
93 };
94 }
95
96 var leafNode = {
97 predicted_value: node.predicted_value,
98 confidence: node.confidence || 0,
99 decision_rules: []
100 };
101
102 if (!_lodash2.default.isUndefined(node.standard_deviation)) {
103 leafNode.standard_deviation = node.standard_deviation;
104 }
105
106 return leafNode;
107 }
108
109 // Regular node
110 var matchingChild = _lodash2.default.find(node.children, function (child) {
111 var decision_rule = child.decision_rule;
112 var property = decision_rule.property;
113 if (_lodash2.default.isUndefined(context[property])) {
114 // Should not happen
115 return {
116 predicted_value: undefined,
117 confidence: undefined,
118 error: {
119 name: 'CraftAiUnknownError',
120 message: 'Unable to take decision: property \'' + property + '\' is missing from the given context.'
121 }
122 };
123 }
124
125 return OPERATORS[decision_rule.operator](context[property], decision_rule.operand);
126 });
127
128 // matching child property error
129 if (matchingChild && matchingChild.error) {
130 return matchingChild;
131 }
132
133 if (_lodash2.default.isUndefined(matchingChild)) {
134 // Should only happens when an unexpected value for an enum is encountered
135 var operandList = _lodash2.default.uniq(_lodash2.default.map(_lodash2.default.values(node.children), function (child) {
136 return child.decision_rule.operand;
137 }));
138 var property = _lodash2.default.head(node.children).decision_rule.property;
139 return {
140 predicted_value: undefined,
141 confidence: undefined,
142 decision_rules: [],
143 error: {
144 name: 'CraftAiNullDecisionError',
145 message: 'Unable to take decision: value \'' + context[property] + '\' for property \'' + property + '\' doesn\'t validate any of the decision rules.',
146 metadata: {
147 property: property,
148 value: context[property],
149 expected_values: operandList
150 }
151 }
152 };
153 }
154
155 // matching child found: recurse !
156 var result = decideRecursion(matchingChild, context);
157
158 var finalResult = _lodash2.default.extend(result, {
159 decision_rules: [matchingChild.decision_rule].concat(result.decision_rules)
160 });
161
162 return finalResult;
163}
164
165function checkContext(configuration) {
166 // Extract the required properties (i.e. those that are not the output)
167 var expectedProperties = _lodash2.default.difference(_lodash2.default.keys(configuration.context), configuration.output);
168
169 // Build a context validator
170 var validators = _lodash2.default.map(expectedProperties, function (property) {
171 var otherValidator = function otherValidator() {
172 console.warn('WARNING: "' + configuration.context[property].type + '" is not a supported type. Please refer to the documention to see what type you can use');
173 return true;
174 };
175 return {
176 property: property,
177 type: configuration.context[property].type,
178 validator: VALUE_VALIDATOR[configuration.context[property].type] || otherValidator
179 };
180 });
181
182 return function (context) {
183 var _$reduce = _lodash2.default.reduce(validators, function (_ref, _ref2) {
184 var badProperties = _ref.badProperties,
185 missingProperties = _ref.missingProperties;
186 var property = _ref2.property,
187 type = _ref2.type,
188 validator = _ref2.validator;
189
190 var value = context[property];
191 if (value === undefined) {
192 missingProperties.push(property);
193 } else if (!validator(value)) {
194 badProperties.push({ property: property, type: type, value: value });
195 }
196 return { badProperties: badProperties, missingProperties: missingProperties };
197 }, { badProperties: [], missingProperties: [] }),
198 badProperties = _$reduce.badProperties,
199 missingProperties = _$reduce.missingProperties;
200
201 if (missingProperties.length || badProperties.length) {
202 var messages = _lodash2.default.concat(_lodash2.default.map(missingProperties, function (property) {
203 return 'expected property \'' + property + '\' is not defined';
204 }), _lodash2.default.map(badProperties, function (_ref3) {
205 var property = _ref3.property,
206 type = _ref3.type,
207 value = _ref3.value;
208 return '\'' + value + '\' is not a valid value for property \'' + property + '\' of type \'' + type + '\'';
209 }));
210 throw new _errors.CraftAiDecisionError({
211 message: 'Unable to take decision, the given context is not valid: ' + messages.join(', ') + '.',
212 metadata: _lodash2.default.assign({}, missingProperties.length && { missingProperties: missingProperties }, badProperties.length && { badProperties: badProperties })
213 });
214 }
215 };
216}
217
218function decide(configuration, trees, context) {
219 checkContext(configuration)(context);
220 // Convert timezones as integers to the standard +/-hh:mm format
221 // This should only happen when no Time() object is passed to the interpreter
222 var timezoneProperty = (0, _timezones.getTimezoneKey)(configuration.context);
223 if (!_lodash2.default.isUndefined(timezoneProperty)) {
224 context[timezoneProperty] = (0, _time.tzFromOffset)(context[timezoneProperty]);
225 }
226 return {
227 _version: DECISION_FORMAT_VERSION,
228 context: context,
229 output: _lodash2.default.assign.apply(_lodash2.default, _toConsumableArray(_lodash2.default.map(configuration.output, function (output) {
230 var decision = decideRecursion(trees[output], context);
231 if (decision.error) {
232 switch (decision.error.name) {
233 case 'CraftAiNullDecisionError':
234 throw new _errors.CraftAiNullDecisionError({
235 message: decision.error.message,
236 metadata: _lodash2.default.extend(decision.error.metadata, {
237 decision_rules: decision.decision_rules
238 })
239 });
240 default:
241 throw new _errors.CraftAiUnknownError({
242 message: decision.error.message
243 });
244 }
245 }
246 return _defineProperty({}, output, decision);
247 })))
248 };
249}
250
251exports.formatDecisionRules = _formatter.formatDecisionRules;
252exports.formatProperty = _formatter.formatProperty;
253exports.reduceDecisionRules = _reducer.reduceDecisionRules;
254exports.decide = decide;
\No newline at end of file