-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathCalculation.ts
More file actions
143 lines (131 loc) · 4.27 KB
/
Copy pathCalculation.ts
File metadata and controls
143 lines (131 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { Atom } from './Atom';
import { Formula } from './Formula';
import dayjs from 'dayjs';
import { UsageError } from '../Errors';
import { DateCalculation } from './DateCalculation';
/**
* Mathematical operations
*/
export enum operations {
add = 'add',
subtract = 'subtract',
multiply = 'multiply',
divide = 'divide',
modulo = 'modulo',
}
/**
* A function that is supposed to be used as argument for the Array.map() function, to map arguments to their rita-json ready counterparts.
* @param item The item to be mapped
* @example ```typescript
* const jsonArguments = someCalculation.arguments.map(mapParameterToJSONReady);
*/
export function mapArgumentsToJSONReady(
item: Formula | number | Date | string | boolean
) {
if (item instanceof Formula) {
return item.toJsonReady();
} else if (item instanceof Date) {
return dayjs(item).toISOString();
} else {
return item;
}
}
/**
* Class for all calculations.
* Calculations are also possible with Dates (for details see schema)
*/
export class Calculation extends Formula {
/**
* The arguments of the calculation
*/
public arguments: Array<
Atom | number | Date | Calculation | DateCalculation
>;
/**
* The operation of the calculation
*/
public operation: operations;
/**
*
* @param formulaArguments The arguments of the calculation
* @param operation The operation of the calculation
*/
constructor(
formulaArguments: Array<Atom | number | Calculation | DateCalculation>,
operation: operations
) {
super();
this.arguments = formulaArguments;
this.operation = operation;
}
async evaluate(data: Record<string, any>): Promise<Date | number> {
//Get the function matching our operation
let tempFunc: (x1: any, x2: any) => number;
switch (this.operation) {
case operations.add:
tempFunc = (x1, x2) => x1 + x2;
break;
case operations.subtract:
tempFunc = (x1, x2) => x1 - x2;
break;
case operations.multiply:
tempFunc = (x1, x2) => x1 * x2;
break;
case operations.divide:
tempFunc = (x1, x2) => {
if (x2 === 0)
throw new UsageError(
'Division by zero is not allowed',
this
);
return x1 / x2;
};
break;
case operations.modulo:
tempFunc = (x1, x2) => {
if (x2 === 0)
throw new UsageError(
'Division by zero is not allowed',
this
);
return x1 - Math.floor(x1 / x2) * x2;
};
break;
}
// Round to 12 decimals to circumvent javascript weirdness
const func = (x1: any, x2: any) =>
Math.round(tempFunc(x1, x2) * 10e12) / 10e12;
//Now evaluate all arguments if they can be evaluated
let results = await Promise.all(
this.arguments.map(async (item) =>
item instanceof Formula ? await item.evaluate(data) : item
)
);
//Check if dates are involved in th calculation
for (const parameter of results) {
if (parameter instanceof Date) {
throw new UsageError(
'No dates in calculation allowed! Use dateCalculation',
this
);
}
}
//No dates are involved, just reduce the array with the function
return <number>results.splice(1).reduce(func, results[0]);
}
toJsonReady(): Record<string, any> {
return {
type: 'calculation',
operation: this.operation,
arguments: this.arguments.map(mapArgumentsToJSONReady),
};
}
validate(): boolean {
return (
this.arguments.length >= 2 &&
this.arguments
.map((it) => (it instanceof Formula ? it.validate() : !!it))
.reduce((p, c) => p && c)
);
}
}