UNPKG

imicros-feel-interpreter

Version:
333 lines (296 loc) 13.6 kB
# Example expressions - `date and time("2022-04-05T23:59:59") < date("2022-04-06")` w/o context --> `true` - `if a>b then c+4 else d` with context `{a:3,b:2,c:5.1,d:4}` --> `9.1` - `{"Mother's finest":5, "result": 5 + Mother's finest}.result` --> `10` - `"best of " + lower case("IMicros")` w/o context --> `"best of imicros"` - `[{a:3,b:1},{a:4,b:2}][item.a > 3]` w/o context --> `[{a:4,b:2}]` - `[1,2,3,4,5,6,7,8,9][a*(item+1)=6]` with context `{a:2}` --> `[2]` - `a+b > c+d` with context `{a:5,b:4,c:3,d:5}` --> `true` - `flight list[item.status = "cancelled"].flight number` with context `{"flight list": [{ "flight number": 123, status: "boarding"},{ "flight number": 234, status: "cancelled"}]}` --> `[234]` - `{calc:function (a:number,b:number) a-b, y:calc(b:c,a:d)+3}.y` with context `{c:4,d:5}` --> `4` - `deep.a.b + deep.c` with context `{deep:{a:{b:3},c:2}}` --> `5` - `{a:3}.a`w/o context --> `3` - `extract("references are 1234, 1256, 1378", "12[0-9]*")` w/o context --> `["1234","1256"]` - `(a+b)>(8.9) and (c+d)>(8.1)` with context `{a:5,b:4,c:4,d:5}`--> `true` - `@"2022-04-10T13:15:20" + @"P1M"` w/o context --> `"2022-05-10T13:15:20"` - `day of year(@"2022-04-16")` w/o context --> `106` - `@"P7M2Y" + @"P5D"` w/o context --> `"P5D7M2Y"` - `{ "PMT": function (p:number,r:number,n:number) (p*r/12)/(1-(1+r/12)**-n), "MonthlyPayment": PMT(Loan.amount, Loan.rate, Loan.term) + fee }.MonthlyPayment` with context `{Loan: { amount: 600000, rate: 0.0375, term:360 }, fee: 100}` --> `2878.6935494327668` - `decision table( outputs: ["Applicant Risk Rating"], inputs: ["Applicant Age","Medical History"], rule list: [ [>60,"good","Medium"], [>60,"bad","High"], [[25..60],-,"Medium"], [<25,"good","Low"], [<25,"bad","Medium"] ], hit policy: "Unique" ) ` with context `{"Applicant Age": 65, "Medical History": "bad"}` --> `{ "Applicant Risk Rating": "High" }` # Supported expressions (not the complete list - refer to the test cases for a complete list of tested expressions) ## Arithmetic Muliplication: *, Division: /, Addition: +, Subtraction: -, Exponentation: ** - `(x - 2)**2 + 3/a - c*2` Negation: - - `-5` ## Boolean And: and, Or: or Equal to: =, not equal to: !=, less than: <, less than or equal to: <=, greater than: >, greater than or equal to: >= - `5 = 5 and 6 != 5 and 3 <= 4 and date("2022-05-08") > date("2022-05-07")` --> `true` Existence check: is defined(var) - `is defined({x:null}.x)` --> `true` - `is defined({}.x)` --> `false` Negation: not(***expression***) - `{a:5,b:3,result: not(a<b)}.result` --> `true` Type check: ***expression*** instance of ***type*** - `a instance of b` with context `{a:3,b:5}` --> `true` - `a instance of string` with context `{a:"test"}` --> `true` - `a instance of number` with context `{a:3}` --> `true` - `a instance of boolean` with context `{a:true}` --> `true` ## String Concatenate: + (only possible with both terms type string) - `"foo" + "bar"` --> `"foobar"` ## Context and path Context is a defintion in JSON notation with { ***key***: ***value*** }. The key must evaluate to a string, the value can be any expression (including function definitions and complete decision table calls). With the .***name*** notation an attribute of the context is accessed. - `{a:3}.a` --> `3` - `deep.a.b + deep.c` with context `{deep:{a:{b:3},c:2}}` --> `5` - `{calc:function (a:number,b:number) a-b, y:calc(b:c,a:d)+3}.y` with context `{c:4,d:5}` --> `4` - `{calc:function (a:number,b:number) a+b, y:calc(4,5)+3}` --> `{y:12}` ## Filter (Lists) Get element by index (index count is starting with 1) - `[1,2,3,4][2]` --> `2` Negative indices are counted from the end - `[1,2,3,4][-1]` --> `3` - `[1,2,3,4][-0]` --> `4` Reduce list based on logic expression - variable ***item*** is the current element - `[1,2,3,4][item > 2]` --> `[3,4]` - `[1,2,3,4,5,6,7,8,9][a*(item+1)=6]` with context `{a:2}` --> `[2]` - `[1,2,3,4][even(item)]` --> `[2,4]` - `flight list[item.status = "cancelled"].flight number` with context `{"flight list": [{ "flight number": 123, status: "boarding"},{ "flight number": 234, status: "cancelled"}]}` --> `[234]` ## Temporal Date or date and time expressions as well as durations can be written with the @***String*** notation - `@"2022-05-10T13:15:20" - @"P1M"` --> `"2022-04-10T13:15:20"` - `@"13:45:20" - @"PT30M"` --> `"13:15:20"` - `date("2022-05-14") - date("2020-09-10")` --> `"P4D8M1Y"` - `date("2020-09-10")-date("2022-05-14")` --> `"-P4D8M1Y"` Comparison with <,<=,>,>=,= Additon/Subtraction with ***date***|***date and time*** +/- ***duration*** - `date("2022-04-05") < date("2022-04-06")` --> `true` - `date and time("2022-04-15T08:00:00") = date and time("2022-04-15T00:00:00") + @"P8H"` --> `true` - `@"P5D" > @"P2D"` --> `true` - `@"P5D" > @"P4DT23H"` --> `true` Comparison with in ***interval*** - `date("2022-04-05") in [date("2022-04-04")..date("2022-04-06")]` --> `true` - `(date("2022-04-01")+duration("P3D")) in [date("2022-04-04")..date("2022-04-06")]` --> `true` Comparison with between ***date***|***date and time*** and ***date***|***date and time*** - `date("2022-04-05") between date("2022-04-04") and date("2022-04-06")` --> `true` Access of attributes of the temporal type - `@"2022-04-10".month` --> `4` - `date("2022-04-10").day` --> `10` - `date and time("2022-04-10T13:15:20").year` --> `2022` - `date and time("2022-04-10T13:15:20").hour` --> `13` - `date and time("2022-04-10T13:15:20").minute` --> `15` - `date and time("2022-04-10T13:15:20").second` --> `20` - `@"P12D5M".months` --> `5` - `today().year` --> current year - `now().minute` --> current minute - `day of week(@"2022-04-16")` --> `"Saturday"` - `day of year(@"2022-04-16")` --> `106` - `week of year(@"2022-04-16")` --> `15` - `abs(@"-P7M2Y")` --> `"P7M2Y"` ## If if ***condition*** then ***expression*** else ***expression*** - `if 1 > 2 then 3 else 4` ## For for ***name*** in ***iteration context*** return ***expression*** - `for a in [1,2,3] return a*2` --> `[2,4,6]` ## Comments single line comments starting with `//` until the end of the line single line or multiline comments framed with `/*` and `*/`. ``` /* start comment */ decision table( outputs: ["Applicant Risk Rating"], inputs: ["Applicant Age","Medical History"], /* multi line between */ rule list: [ [>60,"good","Medium"], [>60,"bad","High"], [[25..60],-,"Medium"], /**** * important comment ****/ [<25,"good","Low"], [<25,"bad","Medium"] // single line comment ], hit policy: "Unique" ) /* end comment */ ``` # Supported build-in functions ## Conversion - `date(from|year,month,day)` - `time(from|hour,minute,second,offset?)` with offset type duration (e.g. @"PT1H") - missing: date and time(from - with named parameter|date,time) - `years and months duration(from,to)` with from,to type date - `number(from)` with from type string - `string(from)` - `context(entries)` with entries type object with attributes key and value (e.g. {key: "a",value: 1}) ## Temporal - `today()` - `now()` - `day of week(date)` - `day of year(date)` - `week of year(date)` - `month of year(date)` - `abs(duration)` ## Arithmetic - `decimal(n,scale)` - `floor(n)` - `ceiling(n)` - `round up(n,scale?)` - `round down(n,scale?)` - `round half up(n,scale?)` - `round half down(n,scale?)` - `abs(number)` - `modulo(dividend,divisor)` - `sqrt(number)` - `log(number)` - `exp(number)` - `odd(number)` - `even(number)` ## Logical - `is defined(value)` - `not(negand)` ## Ranges - `before(a,b)` with a,b either point or interval - `after(a,b)` with a,b either point or interval - `meets(a,b)` with a,b intervals - `met by(a,b)` with a,b intervals - `overlaps(a,b)` with a,b intervals - `overlaps before(a,b)` with a,b intervals - `overlaps after(a,b)` with a,b intervals - `finishes(a,b)` with a eiter point or interval and b interval - `finished by(a,b)` with a interval and b either point or interval - `includes(a,b)` with a interval and b either point or interval - `during(a,b)` with a eiter point or interval and b interval - `starts(a,b)` with a eiter point or interval and b interval - `started by(a,b)` with a interval and b either point or interval - `coinsides(a,b)` with a,b either both points or both intervals ## Lists - `list contains(list,element)` - `count(list) / count(...item)` - `min(list) / min(...item)` - `max(list) / max(...item)` - `sum(list) / sum(...item)` - `product(list) / product(...item)` - `mean(list) / mean(...item)` - `median(list) / median(...item)` - `stddev(list) / stddev(...item)` - `mode(list) / mode(...item)` - `all(list) / all(...item)` - `and(list)` - `any(list) / any(...item)` - `or(list)` - `sublist(list, startposition, length?)` - `append(list,...item)` - `union(...list)` - `concatenate(...list)` - `insert before(list,position,newItem)` - `remove(list,position)` - `reverse(list)` - `index of(list,match)` - `distinct values(list)` - `flatten(list)` - `sort(list,precedes)` - `string join(list,delimiter?,prefix?,suffix?)` ## Strings - `substring(string,start,length)` - `string length(string)` - `upper case(string)` - `lower case(string)` - `substring before(string,match)` - `substring after(string,match)` - `contains(string,match)` - `starts with(string,match)` - `ends with(string,match)` - `matches(input,pattern)` - `replace(input,pattern,replacement,flags)` - `split(string,delimiter)` - `extract(string,pattern)` ## Context - `get value(context,key)` - `get entries(context)` - `put(context,key,value)` - `put all(entries)` ## Decisions - `boxed expression(context,expression)` - `decision table(output, input, rule list, hit policy)` (supported hit policies: "U"|"Unique","A"|"Any","F"|"First","R"|"Rule order","C"|"Collect","C+"|"C<"|"C>"|"C#") # Complete (complex) decisions Also complex decisions like the example under assets/Sample.dmn can be written as a complex FEEL expression and evaluated - here for example as a context returning the last evaluated context entry. ``` const Interpreter = require("../lib/interpreter.js"); const interpreter = new Interpreter(); let exp = ` { "Lender Acceptable DTI": function () 0.36, "Lender Acceptable PITI": function () 0.28, "DTI": function (d,i) d/i, "PITI": function (pmt,tax,insurance,income) (pmt+tax+insurance)/income, "Credit Score.FICO": Credit Score.FICO, "Credit Score Rating": decision table( inputs: ["Credit Score.FICO"], outputs: ["Credit Score Rating"], rule list: [ [>=750,"Excellent"], [[700..750),"Good"], [[650..700),"Fair"], [[600..650),"Poor"], [< 600,"Bad"] ], hit policy: "U" ).Credit Score Rating, "Client DTI": DTI(d: Applicant Data.Monthly.Repayments + Applicant Data.Monthly.Expenses, i: Applicant Data.Monthly.Income), "Client PITI": PITI( pmt: (Requested Product.Amount*((Requested Product.Rate/100)/12))/(1-(1/(1+(Requested Product.Rate/100)/12)**-Requested Product.Term)), tax: Applicant Data.Monthly.Tax, insurance: Applicant Data.Monthly.Insurance, income: Applicant Data.Monthly.Income ), "Back End Ratio": if Client DTI <= Lender Acceptable DTI() then "Sufficient" else "Insufficient", "Front End Ratio": if Client PITI <= Lender Acceptable PITI() then "Sufficient" else "Insufficient", "Loan PreQualification": decision table( outputs: ["Qualification","Reason"], inputs: ["Credit Score Rating","Back End Ratio","Front End Ratio"], rule list: [ [["Poor","Bad"],-,-,"Not Qualified","Credit Score too low."], [-,"Insufficient","Sufficient","Not Qualified","Debt to income ratio is too high."], [-,"Sufficient","Insufficient","Not Qualified","Mortgage payment to income ratio is too high."], [-,"Insufficient","Insufficient","Not Qualified","Debt to income ratio is too high AND mortgage payment to income ratio is too high."], [["Fair","Good","Excellent"],"Sufficient","Sufficient","Qualified","The borrower has been successfully prequalified for the requested loan."] ], hit policy: "F" ) }.Loan PreQualification ` let success = interpreter.parse(exp); if (!success) console.log(interpreter.error); result = interpreter.evaluate(exp,{ "Credit Score": { FICO: 700 }, "Applicant Data": { Monthly: { Repayments: 1000, Tax: 1000, Insurance: 100, Expenses: 500, Income: 5000 } }, "Requested Product": { Amount: 600000, Rate: 0.0375, Term: 360 } }); console.log(result); // { // Qualification: 'Qualified', // Reason: 'The borrower has been successfully prequalified for the requested loan.' // } ```