Implémentation alternative du cache

Qui résoud le problème de l'inversion qui vide le cache lors du calcul
de multiples objectifs
pull/138/head
mama 2017-12-12 20:10:22 +01:00
parent f98faf5a24
commit b6707a256b
7 changed files with 63 additions and 60 deletions

View File

@ -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",

View File

@ -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"

View File

@ -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),

View File

@ -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)),

View File

@ -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,

View File

@ -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 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) => {

View File

@ -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)
}