⚙️ Evaluations différées (refactoring partiel)

pull/21/head
Laurent Bossavit 2017-07-09 18:48:00 +02:00
parent 3abc5e1703
commit 3c8c4c9987
3 changed files with 143 additions and 71 deletions

View File

@ -53,26 +53,21 @@ let withFilter = (rules, filter) =>
R.concat(rules,[{name:"filter", nodeValue:filter, ns:"sys", dottedName: "sys . filter"}])
let fillVariableNode = (rules, rule, situationGate) => (parseResult) => {
let variableNode = createVariableNode(rules, rule, situationGate)(parseResult)
return evaluateNode(situationGate,[],variableNode)
return createVariableNode(rules, rule, situationGate)(parseResult)
}
let createVariableNode = (rules, rule, situationGate) => (parseResult) => {
let evaluate = (situation, parsedRules, node) => {
let dottedName = node.dottedName,
variable = findRuleByDottedName(rules, dottedName),
variable = findRuleByDottedName(parsedRules, dottedName),
variableIsCalculable = variable.formule != null,
//TODO perf : mettre un cache sur les variables !
// On le fait pas pour l'instant car ça peut compliquer les fonctionnalités futures
// et qu'il n'y a aucun problème de perf aujourd'hui
parsedRule = variableIsCalculable && evaluateNode(
situationGate,
[],
treatRuleRoot(
situationGate,
rules,
variable
)
parsedRules,
variable
),
situationValue = evaluateVariable(situationGate, dottedName, variable),
@ -167,6 +162,31 @@ let treat = (situationGate, rules, rule) => rawNode => {
)
if (parseResult.category == 'calcExpression') {
let evaluate = (situation, parsedRules, node) => {
let
operatorFunctionName = {
'*': 'multiply',
'/': 'divide',
'+': 'add',
'-': 'subtract'
}[node.operator],
value1 = evaluateNode(situation,parsedRules,node.explanation[0]).nodeValue,
value2 = evaluateNode(situation,parsedRules,node.explanation[1]).nodeValue,
operatorFunction = R[operatorFunctionName],
nodeValue = value1 == null || value2 == null ?
null
: operatorFunction(value1, value2)
return {
...node,
nodeValue,
jsx: {
...node.jsx,
value: nodeValue
}
}
}
let
fillVariable = fillVariableNode(rules, rule, situationGate),
fillFiltered = parseResult => fillVariableNode(withFilter(rules,parseResult.filter), rule, situationGate)(parseResult.variable),
@ -175,33 +195,24 @@ let treat = (situationGate, rules, rule) => rawNode => {
[R.propEq('category', 'variable'), fillVariable],
[R.propEq('category', 'filteredVariable'), fillFiltered],
[R.propEq('category', 'value'), node =>
R.assoc('jsx', <span className="value">
{node.nodeValue}
</span>)(node)
({
evaluate: (situation, parsedRules, me) => ({...node, nodeValue: parseInt(node.nodeValue)}),
jsx: <span className="value">{node.nodeValue}</span>
})
]
])
),
[{nodeValue: value1}, {nodeValue: value2}] = filledExplanation,
operatorFunctionName = {
'*': 'multiply',
'/': 'divide',
'+': 'add',
'-': 'subtract'
}[parseResult.operator],
operatorFunction = R[operatorFunctionName],
nodeValue = value1 == null || value2 == null ?
null
: operatorFunction(value1, value2)
operator = parseResult.operator
return {
evaluate,
operator,
text: rawNode,
nodeValue,
category: 'calcExpression',
type: 'numeric',
explanation: filledExplanation,
jsx: <Node
classes="inlineExpression calcExpression"
value={nodeValue}
child={
<span className="nodeContent">
{filledExplanation[0].jsx}
@ -212,41 +223,61 @@ let treat = (situationGate, rules, rule) => rawNode => {
/>
}
}
if (parseResult.category == 'comparison') {
//TODO mutualise code for 'comparison' & 'calclExpression'. Harmonise their names
let evaluate = (situation, parsedRules, node) => {
let
comparatorFunctionName = {
'<': 'lt',
'<=': 'lte',
'>': 'gt',
'>=': 'gte'
//TODO '='
}[node.operator],
comparatorFunction = R[comparatorFunctionName],
value1 = evaluateNode(situation,parsedRules,node.explanation[0]).nodeValue,
value2 = evaluateNode(situation,parsedRules,node.explanation[1]).nodeValue,
nodeValue = value1 == null || value2 == null ?
null
: comparatorFunction(value1, value2)
return {
...node,
nodeValue,
jsx: {
...node.jsx,
value: nodeValue
}
}
}
let
fillVariable = fillVariableNode(rules, rule, situationGate),
fillFiltered = parseResult => fillVariableNode(withFilter(rules,parseResult.filter), rule, situationGate)(parseResult.variable),
filledExplanation = parseResult.explanation.map(
R.cond([
[R.propEq('category', 'variable'), fillVariableNode(rules, rule, situationGate)],
[R.propEq('category', 'variable'), fillVariable],
[R.propEq('category', 'filteredVariable'), fillFiltered],
[R.propEq('category', 'value'), node =>
R.assoc('jsx', <span className="value">
{node.nodeValue}
</span>)(node)
({
evaluate: (situation, parsedRules, me) => ({...node, nodeValue: parseInt(node.nodeValue)}),
jsx: <span className="value">{node.nodeValue}</span>
})
]
])
),
[{nodeValue: value1}, {nodeValue: value2}] = filledExplanation,
comparatorFunctionName = {
'<': 'lt',
'<=': 'lte',
'>': 'gt',
'>=': 'gte'
//TODO '='
}[parseResult.operator],
comparatorFunction = R[comparatorFunctionName],
nodeValue = value1 == null || value2 == null ?
null
: comparatorFunction(value1, value2)
operator = parseResult.operator
return {
evaluate,
operator,
text: rawNode,
nodeValue: nodeValue,
category: 'comparison',
type: 'boolean',
explanation: filledExplanation,
jsx: <Node
classes="inlineExpression comparison"
value={nodeValue}
child={
<span className="nodeContent">
{filledExplanation[0].jsx}
@ -308,7 +339,14 @@ let treat = (situationGate, rules, rule) => rawNode => {
[R.is(Object), treatObject],
[R.T, treatOther]
])
return onNodeType(rawNode)
let defaultEvaluate = (situationGate, parsedRules, node) => {
return node
}
let parsedNode = onNodeType(rawNode)
return parsedNode.evaluate ? parsedNode :
{...parsedNode, evaluate: defaultEvaluate}
}
//TODO c'est moche :
@ -326,8 +364,9 @@ export let computeRuleValue = (formuleValue, condValue) =>
export let treatRuleRoot = (situationGate, rules, rule) => {
let evaluate = (situationGate, parsedRules, r) => {
let
formuleValue = r.formule.nodeValue,
condValue = R.path(['non applicable si', 'nodeValue'])(r),
formuleValue = r.formule && evaluateNode(situationGate, parsedRules, r.formule).nodeValue,
condition = R.prop('non applicable si',r),
condValue = condition && evaluateNode(situationGate, parsedRules, condition).nodeValue,
nodeValue = computeRuleValue(formuleValue, condValue)
return {...r, nodeValue}
@ -339,21 +378,30 @@ export let treatRuleRoot = (situationGate, rules, rule) => {
// 'cond' : Conditions d'applicabilité de la règle
'non applicable si': value => {
let
child = treat(situationGate, rules, rule)(value),
nodeValue = child.nodeValue
let evaluate = (situationGate, parsedRules, node) => {
let nodeValue = evaluateNode(situationGate, parsedRules, node.explanation).nodeValue
return {
...node,
nodeValue,
jsx: {
...node.jsx,
value: nodeValue
}
}
}
let child = treat(situationGate, rules, rule)(value)
return {
evaluate,
category: 'ruleProp',
rulePropType: 'cond',
name: 'non applicable si',
type: 'boolean',
nodeValue: child.nodeValue,
explanation: child,
jsx: <Node
classes="ruleProp mecanism cond"
name="non applicable si"
value={nodeValue}
child={
child.category === 'variable' ? <div className="node">{child.jsx}</div>
: child.jsx
@ -368,21 +416,30 @@ export let treatRuleRoot = (situationGate, rules, rule) => {
// note: pour certaines variables booléennes, ex. appartenance à régime Alsace-Moselle, la formule et le non applicable si se rejoignent
// [n'importe quel mécanisme numérique] : multiplication || barème en taux marginaux || le maximum de || le minimum de || ...
'formule': value => {
let
child = treat(situationGate, rules, rule)(value),
nodeValue = child.nodeValue
let evaluate = (situationGate, parsedRules, node) => {
let nodeValue = evaluateNode(situationGate, parsedRules, node.explanation).nodeValue
return {
...node,
nodeValue,
jsx: {
...node.jsx,
value: nodeValue
}
}
}
let child = treat(situationGate, rules, rule)(value)
return {
evaluate,
category: 'ruleProp',
rulePropType: 'formula',
name: 'formule',
type: 'numeric',
nodeValue: nodeValue,
explanation: child,
shortCircuit: R.pathEq(['non applicable si', 'nodeValue'], true),
jsx: <Node
classes="ruleProp mecanism formula"
name="formule"
value={nodeValue}
child={
child.jsx
}

View File

@ -17,7 +17,6 @@ describe('collectMissingVariables', function() {
rules = rawRules.map(enrichRule),
situation = analyseSituation(rules,"startHere")(stateSelector),
result = getObjectives(situation)
console.log("situation",situation)
expect(result).to.have.lengthOf(1)
expect(result[0]).to.have.property('name','deux')

View File

@ -23,6 +23,14 @@ describe('analyseSituation', function() {
describe('analyseSituation on raw rules', function() {
it('should handle direct referencing of a variable', function() {
let rawRules = [
{nom: "startHere", formule: "dix", espace: "top"},
{nom: "dix", formule: 10, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',10)
});
it('should handle expressions referencing other rules', function() {
let rawRules = [
{nom: "startHere", formule: "3259 + dix", espace: "top"},
@ -40,22 +48,12 @@ describe('analyseSituation on raw rules', function() {
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3259)
});
it('should handle complements', function() {
it('should handle comparisons', function() {
let rawRules = [
{nom: "startHere", formule: {complément: {cible: "dix", montant: 93}}, espace: "top"},
{nom: "dix", formule: 17, espace: "top"}],
{nom: "startHere", formule: "3259 > dix", espace: "top"},
{nom: "dix", formule: 10, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',93-17)
});
it('should handle components in complements', function() {
let rawRules = [
{nom: "startHere", formule: {complément: {cible: "dix",
composantes: [{montant: 93},{montant: 93}]
}}, espace: "top"},
{nom: "dix", formule: 17, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',2*(93-17))
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',true)
});
/* TODO: make this pass
@ -170,6 +168,24 @@ describe('analyseSituation with mecanisms', function() {
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3200)
});
it('should handle complements', function() {
let rawRules = [
{nom: "startHere", formule: {complément: {cible: "dix", montant: 93}}, espace: "top"},
{nom: "dix", formule: 17, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',93-17)
});
it('should handle components in complements', function() {
let rawRules = [
{nom: "startHere", formule: {complément: {cible: "dix",
composantes: [{montant: 93},{montant: 93}]
}}, espace: "top"},
{nom: "dix", formule: 17, espace: "top"}],
rules = rawRules.map(enrichRule)
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',2*(93-17))
});
it('should handle filtering on components', function() {
let rawRules = [
{nom: "startHere", espace: "top", formule: "composed (salarié)"},