EdgeVerve / feel

Expression Language for creating and executing business rules in decision table based on DMN 1.1 specification for conformance level 3
MIT License
93 stars 48 forks source link

Add new DMN 1.2 numeric built-in functions #27

Closed PaulChernoch-Shell closed 3 years ago

PaulChernoch-Shell commented 3 years ago

DMN 1.2 adds new numeric built-in functions to those already present in DMN 1.1. It is easy to add these to the library.

Here is my take, which adds the following functions (and also validates arguments to be numbers):

Here is the text of utils\built-in-functions\numbers\index.js after the changes:


// Validate that arg is a number and throw if it is not.
function num(arg, functionName) {
  if (typeof arg === 'number') {
    return arg;
  }
  throw new Error(`Type Error : "${functionName}" built-in function expects a number but "${typeof arg}" encountered`);
}

const decimal = (n, scale) => num(n, 'decimal').toFixed(num(scale, 'decimal'));

const floor = n => Math.floor(num(n, 'floor'));

const ceiling = n => Math.ceil(num(n, 'ceiling'));

const abs = n => Math.abs(num(n, 'abs'));

const modulo = (n, modulus) => num(n, 'modulus') % num(modulus, 'modulus');

const sqrt = n => Math.sqrt(num(n, 'sqrt'));

const log = n => Math.log(num(n, 'log'));

const exp = n => Math.exp(num(n, 'exp'));

const odd = n => num(n, 'odd') % 2 === 1;

const even = n => num(n, 'even') % 2 === 0;

module.exports = {
  decimal,
  floor,
  ceiling,
  abs,
  modulo,
  sqrt,
  log,
  exp,
  odd,
  even,
};
deostroll commented 3 years ago

I am not implementing this since, we are taking a decision to stick to DMN v1.1 at the moment. To move to a higher version is a lot of work. (Striving for TCK compliance, etc). We are not able to commit to this at the moment.

But, we have a plugin functionality which enables us to extend feel functions with externally defined functions. It is documented in oe-business-rules module. In short, we can define the functions (in javascript), in the project, and, add them as part of payload so it becomes available inside of the engine during execution. (It is different from external functions mentioned in the specs).

Here is a very short documentation:

(1) We can define functions in our project. For e.g. (code taken from here)

module.exports = () => ({
  hasProperty: (obj, propName) => {
    var result = (typeof obj === 'undefined') ? false : (typeof obj[propName] === 'undefined' ? false : true);
    console.log('hasProperty', 'obj:', obj, 'property:', propName, 'result:', result);
    return result;
  }
});

(2) We next define a js-feel plugin as follows: (code taken from here

const jsFeelExtFnPlugin = externalFns =>
  function () {
    const executeDecisionTable = this.decisionTable.execute_decision_table;
    this.decisionTable.execute_decision_table = (id, table, data, cb) => {
      const { options } = data;
      const extFns = externalFns(options);
      const modifiedData = Object.assign({}, data, extFns);
      executeDecisionTable(id, table, modifiedData, cb);
    };
  };

module.exports = { jsFeelExtFnPlugin };

So this caches the original decision table api, modified the payload adding the external functions to it, and then finally calls the original api).

(3) After that we must call the use() api as shown here: https://github.com/EdgeVerve/oe-business-rule/blob/master/server/boot/04_jsfeel-init.js#L44

(4) We can then use this from within our decision tables as shown here:

image

I suggest you try this out. Do not hesitate to ask questions here regarding this. I am closing this issue for now.