1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.decide = exports.reduceDecisionRules = exports.formatProperty = exports.formatDecisionRules = undefined;
|
7 |
|
8 | var _lodash = require('lodash');
|
9 |
|
10 | var _lodash2 = _interopRequireDefault(_lodash);
|
11 |
|
12 | var _reducer = require('./reducer');
|
13 |
|
14 | var _time = require('./time');
|
15 |
|
16 | var _errors = require('./errors');
|
17 |
|
18 | var _formatter = require('./formatter');
|
19 |
|
20 | var _timezones = require('./timezones');
|
21 |
|
22 | var _timezones2 = _interopRequireDefault(_timezones);
|
23 |
|
24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
25 |
|
26 | function _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 |
|
28 | function _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 |
|
30 | var DECISION_FORMAT_VERSION = '1.1.0';
|
31 |
|
32 | var 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 |
|
47 | if (from < to) {
|
48 | return context_val >= from && context_val < to;
|
49 | }
|
50 |
|
51 | else {
|
52 | return context_val >= from || context_val < to;
|
53 | }
|
54 | }
|
55 | };
|
56 |
|
57 | var 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 |
|
81 | function decideRecursion(node, context) {
|
82 |
|
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 |
|
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 |
|
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 |
|
129 | if (matchingChild && matchingChild.error) {
|
130 | return matchingChild;
|
131 | }
|
132 |
|
133 | if (_lodash2.default.isUndefined(matchingChild)) {
|
134 |
|
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 |
|
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 |
|
165 | function checkContext(configuration) {
|
166 |
|
167 | var expectedProperties = _lodash2.default.difference(_lodash2.default.keys(configuration.context), configuration.output);
|
168 |
|
169 |
|
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 |
|
218 | function decide(configuration, trees, context) {
|
219 | checkContext(configuration)(context);
|
220 |
|
221 |
|
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 |
|
251 | exports.formatDecisionRules = _formatter.formatDecisionRules;
|
252 | exports.formatProperty = _formatter.formatProperty;
|
253 | exports.reduceDecisionRules = _reducer.reduceDecisionRules;
|
254 | exports.decide = decide; |
\ | No newline at end of file |