Implémentation alternative du cache
Qui résoud le problème de l'inversion qui vide le cache lors du calcul de multiples objectifspull/138/head
parent
f98faf5a24
commit
b6707a256b
|
@ -15,6 +15,8 @@
|
|||
"classnames": "^2.2.5",
|
||||
"dedent-js": "^1.0.1",
|
||||
"deep-assign": "^2.0.0",
|
||||
"flow": "^0.2.3",
|
||||
"global": "^4.3.2",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"install": "^0.10.1",
|
||||
"js-yaml": "^3.9.1",
|
||||
|
|
|
@ -6,7 +6,6 @@ import { connect } from "react-redux"
|
|||
import { withRouter } from "react-router"
|
||||
|
||||
import "./Results.css"
|
||||
import { clearDict } from "Engine/traverse"
|
||||
import { encodeRuleName } from "Engine/rules"
|
||||
import RuleValueVignette from "./rule/RuleValueVignette"
|
||||
import ProgressTip from "Components/ProgressTip"
|
||||
|
|
|
@ -39,8 +39,8 @@ export let subCell = (row, name, payer) => {
|
|||
}
|
||||
|
||||
export let byBranch = analysis => {
|
||||
let sal = analysis.dict["contrat salarié . cotisations salariales"]
|
||||
let pat = analysis.dict["contrat salarié . cotisations patronales"]
|
||||
let sal = analysis.cache["contrat salarié . cotisations salariales"]
|
||||
let pat = analysis.cache["contrat salarié . cotisations patronales"]
|
||||
|
||||
let l1 = sal ? sal.explanation.formule.explanation.explanation : [],
|
||||
l2 = pat ? pat.explanation.formule.explanation.explanation : [],
|
||||
|
@ -71,7 +71,7 @@ export default class ResultsGrid extends Component {
|
|||
|
||||
let extract = x => typeof x == 'string' ? +x : ((x && x.nodeValue) || 0),
|
||||
fromEval = name => R.find(R.propEq("dottedName", name), analysis.targets),
|
||||
fromDict = name => analysis.dict[name],
|
||||
fromDict = name => analysis.cache[name],
|
||||
get = name =>
|
||||
extract(situationGate(name) || fromEval(name) || fromDict(name))
|
||||
let results = byBranch(analysis),
|
||||
|
|
|
@ -8,8 +8,8 @@ export let makeJsx = node =>
|
|||
export let collectNodeMissing = (node) =>
|
||||
node.collectMissing ? node.collectMissing(node) : []
|
||||
|
||||
export let evaluateNode = (situationGate, parsedRules, node) =>
|
||||
node.evaluate ? node.evaluate(situationGate, parsedRules, node) : node
|
||||
export let evaluateNode = (cache, situationGate, parsedRules, node) =>
|
||||
node.evaluate ? node.evaluate(cache, situationGate, parsedRules, node) : node
|
||||
|
||||
export let rewriteNode = (node, nodeValue, explanation, collectMissing) =>
|
||||
({
|
||||
|
@ -19,8 +19,8 @@ export let rewriteNode = (node, nodeValue, explanation, collectMissing) =>
|
|||
explanation
|
||||
})
|
||||
|
||||
export let evaluateArray = (reducer, start) => (situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(situationGate, parsedRules, child),
|
||||
export let evaluateArray = (reducer, start) => (cache, situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child),
|
||||
explanation = R.map(evaluateOne, node.explanation),
|
||||
values = R.pluck("nodeValue",explanation),
|
||||
nodeValue = R.any(R.equals(null),values) ? null : R.reduce(reducer, start, values)
|
||||
|
@ -29,8 +29,8 @@ export let evaluateArray = (reducer, start) => (situationGate, parsedRules, node
|
|||
return rewriteNode(node,nodeValue,explanation,collectMissing)
|
||||
}
|
||||
|
||||
export let evaluateArrayWithFilter = (filter, reducer, start) => (situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(situationGate, parsedRules, child),
|
||||
export let evaluateArrayWithFilter = (filter, reducer, start) => (cache, situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child),
|
||||
explanation = R.map(evaluateOne, R.filter(filter(situationGate),node.explanation)),
|
||||
values = R.pluck("nodeValue",explanation),
|
||||
nodeValue = R.any(R.equals(null),values) ? null : R.reduce(reducer, start, values)
|
||||
|
@ -48,8 +48,8 @@ export let parseObject = (recurse, objectShape, value) => {
|
|||
return R.evolve(transforms,objectShape)
|
||||
}
|
||||
|
||||
export let evaluateObject = (objectShape, effect) => (situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(situationGate, parsedRules, child),
|
||||
export let evaluateObject = (objectShape, effect) => (cache, situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child),
|
||||
collectMissing = node => R.chain(collectNodeMissing,R.values(node.explanation))
|
||||
|
||||
let transforms = R.map(k => [k,evaluateOne], R.keys(objectShape)),
|
||||
|
|
|
@ -101,11 +101,11 @@ let devariate = (recurse, k, v) => {
|
|||
condition: recurse(c.si)
|
||||
}))
|
||||
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => {
|
||||
let condition = evaluateNode(situationGate, parsedRules, child.condition)
|
||||
let condition = evaluateNode(cache, situationGate, parsedRules, child.condition)
|
||||
return {
|
||||
...evaluateNode(situationGate, parsedRules, child),
|
||||
...evaluateNode(cache, situationGate, parsedRules, child),
|
||||
condition
|
||||
}
|
||||
}
|
||||
|
@ -184,8 +184,8 @@ export let mecanismOneOf = (recurse, k, v) => {
|
|||
/>
|
||||
)
|
||||
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(situationGate, parsedRules, child),
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child),
|
||||
explanation = R.map(evaluateOne, node.explanation),
|
||||
values = R.pluck('nodeValue', explanation),
|
||||
nodeValue = R.any(R.equals(true), values)
|
||||
|
@ -230,8 +230,8 @@ export let mecanismAllOf = (recurse, k, v) => {
|
|||
)
|
||||
|
||||
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(situationGate, parsedRules, child),
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child),
|
||||
explanation = R.map(evaluateOne, node.explanation),
|
||||
values = R.pluck('nodeValue', explanation),
|
||||
nodeValue = R.any(R.equals(false), values)
|
||||
|
@ -272,7 +272,7 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
|
|||
let conditionNode = recurse(condition), // can be a 'comparison', a 'variable', TODO a 'negation'
|
||||
consequenceNode = mecanismNumericalSwitch(recurse, condition, consequence)
|
||||
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => {
|
||||
let missingOnTheLeft = collectNodeMissing(node.explanation.condition),
|
||||
investigate = node.explanation.condition.nodeValue !== false,
|
||||
|
@ -284,8 +284,8 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
|
|||
|
||||
let explanation = R.evolve(
|
||||
{
|
||||
condition: R.curry(evaluateNode)(situationGate, parsedRules),
|
||||
consequence: R.curry(evaluateNode)(situationGate, parsedRules)
|
||||
condition: R.curry(evaluateNode)(cache, situationGate, parsedRules),
|
||||
consequence: R.curry(evaluateNode)(cache, situationGate, parsedRules)
|
||||
},
|
||||
node.explanation
|
||||
)
|
||||
|
@ -317,8 +317,8 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
|
|||
}
|
||||
}
|
||||
|
||||
let evaluateTerms = (situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(situationGate, parsedRules, child),
|
||||
let evaluateTerms = (cache, situationGate, parsedRules, node) => {
|
||||
let evaluateOne = child => evaluateNode(cache, situationGate, parsedRules, child),
|
||||
explanation = R.map(evaluateOne, node.explanation),
|
||||
nonFalsyTerms = R.filter(node => node.condValue !== false, explanation),
|
||||
getFirst = prop => R.pipe(R.head, R.prop(prop))(nonFalsyTerms),
|
||||
|
@ -401,12 +401,15 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {
|
|||
nodeValue: null
|
||||
}
|
||||
let { fixedObjectiveValue, fixedObjectiveRule } = inversion
|
||||
let fx = x =>
|
||||
clearDict() && evaluateNode(
|
||||
let inversionCache = {}
|
||||
let fx = x => {
|
||||
inversionCache = {}
|
||||
return evaluateNode(inversionCache, // with an empty cache
|
||||
n => dottedName === n ? x : situationGate(n),
|
||||
parsedRules,
|
||||
fixedObjectiveRule
|
||||
).nodeValue
|
||||
}
|
||||
|
||||
// si fx renvoie null pour une valeur numérique standard, disons 1000, on peut
|
||||
// considérer que l'inversion est impossible du fait de variables manquantes
|
||||
|
@ -415,7 +418,7 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {
|
|||
return {
|
||||
nodeValue: null,
|
||||
inversionMissingVariables: collectNodeMissing(
|
||||
evaluateNode(
|
||||
evaluateNode({},
|
||||
n =>
|
||||
dottedName === n ? 1000 : situationGate(n),
|
||||
parsedRules,
|
||||
|
@ -436,14 +439,15 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {
|
|||
|
||||
return {
|
||||
nodeValue,
|
||||
inversionMissingVariables: []
|
||||
inversionMissingVariables: [],
|
||||
inversionCache
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export let mecanismInversion = dottedName => (recurse, k, v) => {
|
||||
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let inversion =
|
||||
// avoid the inversion loop !
|
||||
situationGate(dottedName) == undefined &&
|
||||
|
@ -452,7 +456,11 @@ export let mecanismInversion = dottedName => (recurse, k, v) => {
|
|||
collectMissing = () => inversion.inversionMissingVariables,
|
||||
nodeValue = inversion.nodeValue
|
||||
|
||||
return rewriteNode(node, nodeValue, null, collectMissing)
|
||||
let evaluatedNode = rewriteNode(node, nodeValue, null, collectMissing)
|
||||
// rewrite the simulation cache with the definitive inversion values
|
||||
|
||||
R.toPairs(inversion.inversionCache).map(([k,v])=>cache[k] = v)
|
||||
return evaluatedNode
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -829,9 +837,9 @@ export let mecanismSelection = (recurse, k, v) => {
|
|||
let dataTargetName = v['renvoie']
|
||||
let explanation = recurse(v['cherche'])
|
||||
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => collectNodeMissing(node.explanation),
|
||||
explanation = evaluateNode(situationGate, parsedRules, node.explanation),
|
||||
explanation = evaluateNode(cache, situationGate, parsedRules, node.explanation),
|
||||
dataSource = findRuleByName(parsedRules, dataSourceName),
|
||||
data = dataSource ? dataSource['data'] : null,
|
||||
dataKey = explanation.nodeValue,
|
||||
|
|
|
@ -78,9 +78,9 @@ par exemple ainsi : https://github.com/Engelberg/instaparse#transforming-the-tre
|
|||
|
||||
// TODO - this is becoming overly specific
|
||||
let fillFilteredVariableNode = (rules, rule) => (filter, parseResult) => {
|
||||
let evaluateFiltered = originalEval => (situation, parsedRules, node) => {
|
||||
let evaluateFiltered = originalEval => (cache, situation, parsedRules, node) => {
|
||||
let newSituation = name => (name == 'sys.filter' ? filter : situation(name))
|
||||
return originalEval(newSituation, parsedRules, node)
|
||||
return originalEval(cache, newSituation, parsedRules, node)
|
||||
}
|
||||
let node = fillVariableNode(rules, rule, filter)(parseResult),
|
||||
// Decorate node with who's paying
|
||||
|
@ -93,19 +93,13 @@ let fillFilteredVariableNode = (rules, rule) => (filter, parseResult) => {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: dirty, dirty
|
||||
// ne pas laisser trop longtemps cette "optimisation" qui tue l'aspect fonctionnel de l'algo
|
||||
var dict
|
||||
|
||||
export let clearDict = () => dict = {}
|
||||
|
||||
let fillVariableNode = (rules, rule, filter) => parseResult => {
|
||||
let evaluate = (situation, parsedRules, node) => {
|
||||
let evaluate = (cache, situation, parsedRules, node) => {
|
||||
let dottedName = node.dottedName,
|
||||
// On va vérifier dans le cache courant, dict, si la variable n'a pas été déjà évaluée
|
||||
// En effet, l'évaluation dans le cas d'une variable qui a une formule, est coûteuse !
|
||||
cacheName = dottedName + (filter ? '.' + filter : ''),
|
||||
cached = dict[cacheName],
|
||||
cached = cache[cacheName],
|
||||
// make parsedRules a dict object, that also serves as a cache of evaluation ?
|
||||
variable = cached
|
||||
? cached
|
||||
|
@ -113,7 +107,7 @@ let fillVariableNode = (rules, rule, filter) => parseResult => {
|
|||
variableIsCalculable = variable.formule != null,
|
||||
parsedRule =
|
||||
variableIsCalculable &&
|
||||
(cached ? cached : evaluateNode(situation, parsedRules, variable)),
|
||||
(cached ? cached : evaluateNode(cache, situation, parsedRules, variable)),
|
||||
// evaluateVariable renvoit la valeur déduite de la situation courante renseignée par l'utilisateur
|
||||
situationValue = evaluateVariable(situation, dottedName, variable),
|
||||
nodeValue =
|
||||
|
@ -137,11 +131,11 @@ let fillVariableNode = (rules, rule, filter) => parseResult => {
|
|||
}
|
||||
else {
|
||||
|
||||
dict[cacheName] = {
|
||||
cache[cacheName] = {
|
||||
...rewriteNode(node, nodeValue, explanation, collectMissing),
|
||||
missingVariables
|
||||
}
|
||||
return dict[cacheName]
|
||||
return cache[cacheName]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,8 +159,8 @@ let fillVariableNode = (rules, rule, filter) => parseResult => {
|
|||
}
|
||||
|
||||
let buildNegatedVariable = variable => {
|
||||
let evaluate = (situation, parsedRules, node) => {
|
||||
let explanation = evaluateNode(situation, parsedRules, node.explanation),
|
||||
let evaluate = (cache, situation, parsedRules, node) => {
|
||||
let explanation = evaluateNode(cache, situation, parsedRules, node.explanation),
|
||||
nodeValue = explanation.nodeValue == null ? null : !explanation.nodeValue
|
||||
let collectMissing = node => collectNodeMissing(node.explanation)
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
|
@ -250,7 +244,7 @@ let treat = (rules, rule) => rawNode => {
|
|||
parseResult.category == 'calcExpression' ||
|
||||
parseResult.category == 'comparison'
|
||||
) {
|
||||
let evaluate = (situation, parsedRules, node) => {
|
||||
let evaluate = (cache, situation, parsedRules, node) => {
|
||||
let operatorFunctionName = {
|
||||
'*': 'multiply',
|
||||
'/': 'divide',
|
||||
|
@ -264,7 +258,7 @@ let treat = (rules, rule) => rawNode => {
|
|||
'!=': 'equals'
|
||||
}[node.operator],
|
||||
explanation = R.map(
|
||||
R.curry(evaluateNode)(situation, parsedRules),
|
||||
R.curry(evaluateNode)(cache, situation, parsedRules),
|
||||
node.explanation
|
||||
),
|
||||
value1 = explanation[0].nodeValue,
|
||||
|
@ -400,7 +394,7 @@ let treat = (rules, rule) => rawNode => {
|
|||
[R.T, treatOther]
|
||||
])
|
||||
|
||||
let defaultEvaluate = (situationGate, parsedRules, node) => node
|
||||
let defaultEvaluate = (cache, situationGate, parsedRules, node) => node
|
||||
let parsedNode = onNodeType(rawNode)
|
||||
|
||||
return parsedNode.evaluate
|
||||
|
@ -422,9 +416,9 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
Ces mécanismes où variables sont descendues à leur tour grâce à `treat()`.
|
||||
Lors de ce traitement, des fonctions 'evaluate', `collectMissingVariables` et `jsx` sont attachés aux objets de l'AST
|
||||
*/
|
||||
let evaluate = (situationGate, parsedRules, r) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, r) => {
|
||||
|
||||
let evolveRule = R.curry(evaluateNode)(situationGate, parsedRules),
|
||||
let evolveRule = R.curry(evaluateNode)(cache, situationGate, parsedRules),
|
||||
evaluated = R.evolve(
|
||||
{
|
||||
formule: evolveRule,
|
||||
|
@ -483,9 +477,9 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
'non applicable si': evolveCond('non applicable si', rule, rules),
|
||||
'applicable si': evolveCond('applicable si', rule, rules),
|
||||
formule: value => {
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => collectNodeMissing(node.explanation)
|
||||
let explanation = evaluateNode(
|
||||
let explanation = evaluateNode(cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
|
@ -520,9 +514,9 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
}
|
||||
|
||||
let evolveCond = (name, rule, rules) => value => {
|
||||
let evaluate = (situationGate, parsedRules, node) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => collectNodeMissing(node.explanation)
|
||||
let explanation = evaluateNode(
|
||||
let explanation = evaluateNode(cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
|
@ -578,16 +572,17 @@ export let parseAll = flatRules => {
|
|||
}
|
||||
|
||||
export let analyseMany = (parsedRules, targetNames) => situationGate => {
|
||||
clearDict()
|
||||
// TODO: we should really make use of namespaces at this level, in particular
|
||||
// setRule in Rule.js needs to get smarter and pass dottedName
|
||||
let cache = {}
|
||||
|
||||
let parsedTargets = targetNames.map(t => findRuleByName(parsedRules, t)),
|
||||
targets = R.chain(pt => getTargets(pt, parsedRules), parsedTargets).map(t =>
|
||||
evaluateNode(situationGate, parsedRules, t)
|
||||
evaluateNode(cache, situationGate, parsedRules, t)
|
||||
)
|
||||
|
||||
// Don't use 'dict' for anything else than ResultsGrid
|
||||
return {targets, dict}
|
||||
return {targets, cache}
|
||||
}
|
||||
|
||||
export let analyse = (parsedRules, target) => {
|
||||
|
|
|
@ -28,7 +28,6 @@ describe('Mécanismes', () =>
|
|||
missing = collectMissingVariables(analysis.targets),
|
||||
target = analysis.targets[0]
|
||||
|
||||
// console.log('JSON.stringify(analysis', JSON.stringify(analysis))
|
||||
if (isFloat(valeur)) {
|
||||
expect(target.nodeValue).to.be.closeTo(valeur,0.001)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue