Merge pull request #197 from betagouv/simplify-missing
Améliorations des variables manquantespull/196/merge v0.3.3
commit
c711f3fbd0
|
@ -1,10 +1,16 @@
|
|||
import {
|
||||
add,
|
||||
max,
|
||||
map,
|
||||
pluck,
|
||||
any,
|
||||
equals,
|
||||
reduce,
|
||||
mergeWith,
|
||||
chain,
|
||||
length,
|
||||
flatten,
|
||||
uniq,
|
||||
fromPairs,
|
||||
keys,
|
||||
values,
|
||||
|
@ -17,17 +23,20 @@ export let makeJsx = node =>
|
|||
? node.jsx(node.nodeValue, node.explanation)
|
||||
: node.jsx
|
||||
|
||||
export let collectNodeMissing = node =>
|
||||
node.collectMissing ? node.collectMissing(node) : []
|
||||
export let collectNodeMissing = node => node.missingVariables || {}
|
||||
|
||||
export let bonus = (missings, hasCondition=true) => hasCondition ? map(x=>x+.0001, missings || {}) : missings
|
||||
export let mergeAllMissing = missings => reduce(mergeWith(add),{},map(collectNodeMissing,missings))
|
||||
export let mergeMissing = (left, right) => mergeWith(add, left || {}, right || {})
|
||||
|
||||
export let evaluateNode = (cache, situationGate, parsedRules, node) =>
|
||||
node.evaluate ? node.evaluate(cache, situationGate, parsedRules, node) : node
|
||||
|
||||
export let rewriteNode = (node, nodeValue, explanation, collectMissing) => ({
|
||||
export let rewriteNode = (node, nodeValue, explanation, missingVariables) => ({
|
||||
...node,
|
||||
nodeValue,
|
||||
collectMissing,
|
||||
explanation
|
||||
explanation,
|
||||
missingVariables
|
||||
})
|
||||
|
||||
export let evaluateArray = (reducer, start) => (
|
||||
|
@ -42,11 +51,12 @@ export let evaluateArray = (reducer, start) => (
|
|||
values = pluck('nodeValue', explanation),
|
||||
nodeValue = any(equals(null), values)
|
||||
? null
|
||||
: reduce(reducer, start, values)
|
||||
|
||||
let collectMissing = node =>
|
||||
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
: reduce(reducer, start, values),
|
||||
missingVariables = node.nodeValue == null
|
||||
? mergeAllMissing(explanation)
|
||||
: {}
|
||||
// console.log("".padStart(cache.parseLevel), missingVariables)
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
export let evaluateArrayWithFilter = (evaluationFilter, reducer, start) => (
|
||||
|
@ -64,10 +74,12 @@ export let evaluateArrayWithFilter = (evaluationFilter, reducer, start) => (
|
|||
values = pluck('nodeValue', explanation),
|
||||
nodeValue = any(equals(null), values)
|
||||
? null
|
||||
: reduce(reducer, start, values)
|
||||
: reduce(reducer, start, values),
|
||||
missingVariables = node.nodeValue == null
|
||||
? mergeAllMissing(explanation)
|
||||
: {}
|
||||
|
||||
let collectMissing = node => chain(collectNodeMissing, node.explanation)
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
export let parseObject = (recurse, objectShape, value) => {
|
||||
|
@ -86,11 +98,12 @@ export let evaluateObject = (objectShape, effect) => (
|
|||
node
|
||||
) => {
|
||||
let evaluateOne = child =>
|
||||
evaluateNode(cache, situationGate, parsedRules, child),
|
||||
collectMissing = node => chain(collectNodeMissing, values(node.explanation))
|
||||
evaluateNode(cache, situationGate, parsedRules, child)
|
||||
|
||||
let transforms = map(k => [k, evaluateOne], keys(objectShape)),
|
||||
explanation = evolve(fromPairs(transforms))(node.explanation),
|
||||
nodeValue = effect(explanation)
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
nodeValue = effect(explanation),
|
||||
missingVariables = mergeAllMissing(values(explanation))
|
||||
// console.log("".padStart(cache.parseLevel),map(node => length(flatten(collectNodeMissing(node))) ,explanation))
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {
|
||||
chain,
|
||||
flatten,
|
||||
mergeAll,
|
||||
pluck,
|
||||
groupBy,
|
||||
toPairs,
|
||||
sort,
|
||||
|
@ -22,7 +24,6 @@ import SelectAtmp from 'Components/conversation/select/SelectTauxRisque'
|
|||
import formValueTypes from 'Components/conversation/formValueTypes'
|
||||
|
||||
import { findRuleByDottedName, disambiguateRuleReference } from './rules'
|
||||
import { collectNodeMissing } from './evaluation'
|
||||
|
||||
/*
|
||||
COLLECTE DES VARIABLES MANQUANTES
|
||||
|
@ -39,13 +40,10 @@ import { collectNodeMissing } from './evaluation'
|
|||
missingVariables: {variable: [objectives]}
|
||||
*/
|
||||
|
||||
export let collectMissingVariables = targets => {
|
||||
let missing = chain(collectNodeMissing, targets)
|
||||
return groupBy(identity, missing)
|
||||
}
|
||||
export let collectMissingVariables = targets => mergeAll(pluck('missingVariables', targets))
|
||||
|
||||
export let getNextSteps = (situationGate, analysis) => {
|
||||
let impact = ([, objectives]) => length(objectives)
|
||||
let impact = ([, count]) => count
|
||||
|
||||
let missingVariables = collectMissingVariables(analysis.targets),
|
||||
pairs = toPairs(missingVariables),
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import {
|
||||
flatten,
|
||||
reduce,
|
||||
mergeWith,
|
||||
mergeAll,
|
||||
length,
|
||||
objOf,
|
||||
toPairs,
|
||||
dissoc,
|
||||
|
@ -16,6 +21,7 @@ import {
|
|||
evolve,
|
||||
curry,
|
||||
filter,
|
||||
all,
|
||||
pipe,
|
||||
head,
|
||||
isEmpty,
|
||||
|
@ -40,7 +46,10 @@ import {
|
|||
evaluateArrayWithFilter,
|
||||
evaluateObject,
|
||||
parseObject,
|
||||
collectNodeMissing
|
||||
collectNodeMissing,
|
||||
mergeAllMissing,
|
||||
mergeMissing,
|
||||
bonus
|
||||
} from './evaluation'
|
||||
import {
|
||||
findRuleByName,
|
||||
|
@ -144,23 +153,18 @@ let devariate = (recurse, k, v) => {
|
|||
}
|
||||
|
||||
let explanation = map(evaluateOne, node.explanation),
|
||||
choice = find(node => node.condition.nodeValue, explanation),
|
||||
candidates = filter(node => node.condition.nodeValue !== false, explanation),
|
||||
satisfied = filter(node => node.condition.nodeValue, explanation),
|
||||
choice = head(satisfied),
|
||||
nodeValue = choice ? choice.nodeValue : null
|
||||
|
||||
let collectMissing = node => {
|
||||
let choice = find(node => node.condition.nodeValue, node.explanation),
|
||||
leftMissing = choice
|
||||
? []
|
||||
: uniq(
|
||||
chain(collectNodeMissing, pluck('condition', node.explanation))
|
||||
),
|
||||
rightMissing = choice
|
||||
? collectNodeMissing(choice)
|
||||
: chain(collectNodeMissing, node.explanation)
|
||||
return concat(leftMissing, rightMissing)
|
||||
}
|
||||
let leftMissing = choice
|
||||
? {}
|
||||
: mergeAllMissing(pluck('condition', explanation)),
|
||||
rightMissing = mergeAllMissing(candidates),
|
||||
missingVariables = mergeMissing(bonus(leftMissing), rightMissing)
|
||||
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
// TODO - find an appropriate representation
|
||||
|
@ -221,11 +225,13 @@ export let mecanismOneOf = (recurse, k, v) => {
|
|||
values = pluck('nodeValue', explanation),
|
||||
nodeValue = any(equals(true), values)
|
||||
? true
|
||||
: any(equals(null), values) ? null : false
|
||||
: any(equals(null), values) ? null : false,
|
||||
// Unlike most other array merges of missing variables this is a "flat" merge
|
||||
// because "one of these conditions" tend to be several tests of the same variable
|
||||
// (e.g. contract type is one of x, y, z)
|
||||
missingVariables = nodeValue == null ? reduce(mergeWith(max),{},map(collectNodeMissing,explanation)) : {}
|
||||
|
||||
let collectMissing = node =>
|
||||
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -265,11 +271,10 @@ export let mecanismAllOf = (recurse, k, v) => {
|
|||
values = pluck('nodeValue', explanation),
|
||||
nodeValue = any(equals(false), values)
|
||||
? false // court-circuit
|
||||
: any(equals(null), values) ? null : true
|
||||
: any(equals(null), values) ? null : true,
|
||||
missingVariables = nodeValue == null ? mergeAllMissing(explanation) : {}
|
||||
|
||||
let collectMissing = node =>
|
||||
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -302,27 +307,24 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
|
|||
consequenceNode = mecanismNumericalSwitch(recurse, condition, consequence)
|
||||
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => {
|
||||
let missingOnTheLeft = collectNodeMissing(node.explanation.condition),
|
||||
investigate = node.explanation.condition.nodeValue !== false,
|
||||
missingOnTheRight = investigate
|
||||
? collectNodeMissing(node.explanation.consequence)
|
||||
: []
|
||||
return concat(missingOnTheLeft, missingOnTheRight)
|
||||
}
|
||||
|
||||
let explanation = evolve(
|
||||
{
|
||||
condition: curry(evaluateNode)(cache, situationGate, parsedRules),
|
||||
consequence: curry(evaluateNode)(cache, situationGate, parsedRules)
|
||||
},
|
||||
node.explanation
|
||||
)
|
||||
),
|
||||
leftMissing = explanation.condition.missingVariables,
|
||||
investigate = explanation.condition.nodeValue !== false,
|
||||
rightMissing = investigate
|
||||
? explanation.consequence.missingVariables
|
||||
: {},
|
||||
missingVariables = mergeMissing(bonus(leftMissing), rightMissing)
|
||||
|
||||
return {
|
||||
...node,
|
||||
collectMissing,
|
||||
explanation,
|
||||
missingVariables,
|
||||
nodeValue: explanation.consequence.nodeValue,
|
||||
condValue: explanation.condition.nodeValue
|
||||
}
|
||||
|
@ -360,16 +362,13 @@ export let mecanismNumericalSwitch = (recurse, k, v) => {
|
|||
getFirst('condValue') == null
|
||||
? null
|
||||
: // c'est un true, on renvoie la valeur de la conséquence
|
||||
getFirst('nodeValue')
|
||||
getFirst('nodeValue'),
|
||||
choice = find(node => node.condValue, explanation),
|
||||
missingVariables = choice
|
||||
? choice.missingVariables
|
||||
: mergeAllMissing(explanation)
|
||||
|
||||
let collectMissing = node => {
|
||||
let choice = find(node => node.condValue, node.explanation)
|
||||
return choice
|
||||
? collectNodeMissing(choice)
|
||||
: chain(collectNodeMissing, node.explanation)
|
||||
}
|
||||
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
let explanation = map(parseCondition, terms)
|
||||
|
@ -429,46 +428,38 @@ export let findInversion = (situationGate, rules, v, dottedName) => {
|
|||
}
|
||||
}
|
||||
|
||||
let doInversion = (situationGate, parsedRules, v, dottedName) => {
|
||||
let doInversion = (oldCache, situationGate, parsedRules, v, dottedName) => {
|
||||
let inversion = findInversion(situationGate, parsedRules, v, dottedName)
|
||||
|
||||
if (inversion.inversionChoiceNeeded)
|
||||
return {
|
||||
inversionMissingVariables: [dottedName],
|
||||
missingVariables: {[dottedName]:1},
|
||||
nodeValue: null
|
||||
}
|
||||
let { fixedObjectiveValue, fixedObjectiveRule } = inversion
|
||||
let inversionCache = {}
|
||||
let fx = x => {
|
||||
inversionCache = {}
|
||||
inversionCache = {parseLevel: oldCache.parseLevel+1, op:"<"}
|
||||
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
|
||||
// TODO fx peut être null pour certains x, et valide pour d'autres : on peut implémenter ici le court-circuit
|
||||
if (fx(1000) == null)
|
||||
return {
|
||||
nodeValue: null,
|
||||
inversionMissingVariables: collectNodeMissing(
|
||||
evaluateNode(
|
||||
{},
|
||||
n => (dottedName === n ? 1000 : situationGate(n)),
|
||||
parsedRules,
|
||||
fixedObjectiveRule
|
||||
)
|
||||
)
|
||||
}
|
||||
let attempt = fx(1000)
|
||||
if (attempt.nodeValue == null) {
|
||||
return attempt
|
||||
}
|
||||
|
||||
let tolerancePercentage = 0.00001,
|
||||
// cette fonction détermine la racine d'une fonction sans faire trop d'itérations
|
||||
nodeValue = uniroot(
|
||||
x => fx(x) - fixedObjectiveValue,
|
||||
x => fx(x).nodeValue - fixedObjectiveValue,
|
||||
0,
|
||||
1000000000,
|
||||
tolerancePercentage * fixedObjectiveValue,
|
||||
|
@ -477,7 +468,7 @@ let doInversion = (situationGate, parsedRules, v, dottedName) => {
|
|||
|
||||
return {
|
||||
nodeValue,
|
||||
inversionMissingVariables: [],
|
||||
missingVariables: {},
|
||||
inversionCache
|
||||
}
|
||||
}
|
||||
|
@ -486,14 +477,15 @@ export let mecanismInversion = dottedName => (recurse, k, v) => {
|
|||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let inversion =
|
||||
// avoid the inversion loop !
|
||||
situationGate(dottedName) == undefined &&
|
||||
doInversion(situationGate, parsedRules, v, dottedName)
|
||||
let collectMissing = () => inversion.inversionMissingVariables,
|
||||
nodeValue = inversion.nodeValue
|
||||
situationGate(dottedName) == undefined &&
|
||||
doInversion(cache, situationGate, parsedRules, v, dottedName),
|
||||
nodeValue = inversion.nodeValue,
|
||||
missingVariables = inversion.missingVariables
|
||||
|
||||
let evaluatedNode = rewriteNode(node, nodeValue, null, collectMissing)
|
||||
// rewrite the simulation cache with the definitive inversion values
|
||||
let evaluatedNode = rewriteNode(node, nodeValue, null, missingVariables)
|
||||
|
||||
// TODO - we need this so that ResultsGrid will work, but it's
|
||||
// just not right
|
||||
toPairs(inversion.inversionCache).map(([k, v]) => (cache[k] = v))
|
||||
return evaluatedNode
|
||||
}
|
||||
|
@ -932,8 +924,7 @@ export let mecanismSelection = (recurse, k, v) => {
|
|||
let explanation = recurse(v['cherche'])
|
||||
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => collectNodeMissing(node.explanation),
|
||||
explanation = evaluateNode(
|
||||
let explanation = evaluateNode(
|
||||
cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
|
@ -962,8 +953,10 @@ export let mecanismSelection = (recurse, k, v) => {
|
|||
? sortedSubValues
|
||||
? Number.parseFloat(last(sortedSubValues)[1]) / 100
|
||||
: 0
|
||||
: null
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
: null,
|
||||
missingVariables = explanation.missingVariables
|
||||
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
let SelectionView = buildSelectionView(dataTargetName)
|
||||
|
|
|
@ -5,5 +5,3 @@ export let val = node => node && node.nodeValue
|
|||
export let undefOrTrue = val => val == undefined || val == true
|
||||
|
||||
export let anyNull = any(pipe(val, equals(null)))
|
||||
|
||||
export let applyOrEmpty = func => v => (v ? func(v) : [])
|
||||
|
|
|
@ -19,6 +19,9 @@ import {
|
|||
divide,
|
||||
multiply,
|
||||
map,
|
||||
merge,
|
||||
length,
|
||||
flatten,
|
||||
intersection,
|
||||
keys,
|
||||
is,
|
||||
|
@ -55,14 +58,15 @@ import {
|
|||
import {
|
||||
evaluateNode,
|
||||
rewriteNode,
|
||||
collectNodeMissing,
|
||||
makeJsx
|
||||
makeJsx,
|
||||
mergeMissing,
|
||||
mergeAllMissing,
|
||||
bonus
|
||||
} from './evaluation'
|
||||
import {
|
||||
anyNull,
|
||||
val,
|
||||
undefOrTrue,
|
||||
applyOrEmpty
|
||||
undefOrTrue
|
||||
} from './traverse-common-functions'
|
||||
|
||||
let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart)
|
||||
|
@ -131,53 +135,34 @@ let fillVariableNode = (rules, rule, filter) => parseResult => {
|
|||
// 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 = cache[cacheName],
|
||||
// make parsedRules a dict object, that also serves as a cache of evaluation ?
|
||||
variable = cached
|
||||
? cached
|
||||
: findRuleByDottedName(parsedRules, dottedName),
|
||||
cached = cache[cacheName]
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
let variable = findRuleByDottedName(parsedRules, dottedName),
|
||||
variableIsCalculable = variable.formule != null,
|
||||
parsedRule =
|
||||
variableIsCalculable &&
|
||||
(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),
|
||||
needsEvaluation = (variableIsCalculable && situationValue == null),
|
||||
parsedRule = needsEvaluation
|
||||
? evaluateNode(cache, situation, parsedRules, variable)
|
||||
: variable,
|
||||
// evaluateVariable renvoit la valeur déduite de la situation courante renseignée par l'utilisateur
|
||||
explanation = parsedRule,
|
||||
nodeValue =
|
||||
situationValue != null
|
||||
? situationValue // cette variable a été directement renseignée
|
||||
: variableIsCalculable
|
||||
? parsedRule.nodeValue // la valeur du calcul fait foi
|
||||
: null, // elle restera donc nulle
|
||||
explanation = parsedRule,
|
||||
missingVariables = variableIsCalculable ? [] : [dottedName]
|
||||
|
||||
let collectMissing = node => {
|
||||
let missingName = cacheName + ':missing',
|
||||
cached = cache[missingName]
|
||||
|
||||
if (cached) return cached
|
||||
|
||||
let result =
|
||||
nodeValue != null // notamment si situationValue != null
|
||||
? []
|
||||
missingVariables = nodeValue != null // notamment si situationValue != null
|
||||
? {}
|
||||
: variableIsCalculable
|
||||
? collectNodeMissing(parsedRule)
|
||||
: node.missingVariables
|
||||
cache[missingName] = result
|
||||
return result
|
||||
}
|
||||
? parsedRule.missingVariables
|
||||
: {[dottedName]:1}
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
} else {
|
||||
cache[cacheName] = {
|
||||
...rewriteNode(node, nodeValue, explanation, collectMissing),
|
||||
missingVariables
|
||||
}
|
||||
return cache[cacheName]
|
||||
}
|
||||
cache[cacheName] = rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
return cache[cacheName]
|
||||
}
|
||||
|
||||
let { fragments } = parseResult,
|
||||
|
@ -212,9 +197,10 @@ let buildNegatedVariable = variable => {
|
|||
parsedRules,
|
||||
node.explanation
|
||||
),
|
||||
nodeValue = explanation.nodeValue == null ? null : !explanation.nodeValue
|
||||
let collectMissing = node => collectNodeMissing(node.explanation)
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
nodeValue = explanation.nodeValue == null ? null : !explanation.nodeValue,
|
||||
missingVariables = explanation.missingVariables
|
||||
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
let jsx = (nodeValue, explanation) => (
|
||||
|
@ -317,12 +303,12 @@ let treat = (rules, rule) => rawNode => {
|
|||
nodeValue =
|
||||
value1 == null || value2 == null
|
||||
? null
|
||||
: operatorFunction(value1, value2)
|
||||
: operatorFunction(value1, value2),
|
||||
missingVariables = mergeMissing(
|
||||
explanation[0].missingVariables,
|
||||
explanation[1].missingVariables)
|
||||
|
||||
let collectMissing = node =>
|
||||
chain(collectNodeMissing, node.explanation)
|
||||
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
let fillFiltered = parseResult =>
|
||||
|
@ -425,7 +411,7 @@ let treat = (rules, rule) => rawNode => {
|
|||
sélection: mecanismSelection,
|
||||
'une possibilité': always({
|
||||
'une possibilité': 'oui',
|
||||
collectMissing: () => [rule.dottedName]
|
||||
missingVariables: {[rule.dottedName]:1}
|
||||
}),
|
||||
inversion: mecanismInversion(rule.dottedName),
|
||||
allègement: mecanismReduction
|
||||
|
@ -461,9 +447,12 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
Aujourd'hui, une règle peut avoir (comme propriétés à parser) `non applicable si` et `formule`,
|
||||
qui ont elles-mêmes des propriétés de type mécanisme (ex. barème) ou des expressions en ligne (ex. maVariable + 3).
|
||||
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
|
||||
Lors de ce traitement, des fonctions 'evaluate' et `jsx` sont attachés aux objets de l'AST
|
||||
*/
|
||||
let evaluate = (cache, situationGate, parsedRules, r) => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
// console.log((cache.op || ">").padStart(cache.parseLevel),rule.dottedName)
|
||||
cache.parseLevel++
|
||||
|
||||
let evolveRule = curry(evaluateNode)(cache, situationGate, parsedRules),
|
||||
evaluated = evolve(
|
||||
{
|
||||
|
@ -471,7 +460,7 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
'non applicable si': evolveRule,
|
||||
'applicable si': evolveRule
|
||||
},
|
||||
r
|
||||
node
|
||||
),
|
||||
formuleValue = val(evaluated['formule']),
|
||||
isApplicable = do {
|
||||
|
@ -487,32 +476,32 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
},
|
||||
nodeValue = computeRuleValue(formuleValue, isApplicable)
|
||||
|
||||
return { ...evaluated, nodeValue, isApplicable }
|
||||
}
|
||||
|
||||
let collectMissing = rule => {
|
||||
let {
|
||||
formule,
|
||||
isApplicable,
|
||||
'non applicable si': notApplicable,
|
||||
'applicable si': applicable
|
||||
} = rule
|
||||
} = evaluated
|
||||
|
||||
let condMissing =
|
||||
val(notApplicable) === true
|
||||
? []
|
||||
? {}
|
||||
: val(applicable) === false
|
||||
? []
|
||||
: [
|
||||
...applyOrEmpty(collectNodeMissing)(notApplicable),
|
||||
...applyOrEmpty(collectNodeMissing)(applicable)
|
||||
],
|
||||
? {}
|
||||
: merge(
|
||||
(notApplicable && notApplicable.missingVariables) || {},
|
||||
(applicable && applicable.missingVariables) || {}
|
||||
),
|
||||
collectInFormule = isApplicable !== false,
|
||||
formMissing = applyOrEmpty(() =>
|
||||
applyOrEmpty(collectNodeMissing)(formule)
|
||||
)(collectInFormule)
|
||||
formMissing = (collectInFormule && formule.missingVariables) || {},
|
||||
// On veut abaisser le score des conséquences par rapport aux conditions,
|
||||
// mais seulement dans le cas où une condition est effectivement présente
|
||||
hasCondition = keys(condMissing).length > 0,
|
||||
missingVariables = mergeMissing(bonus(condMissing,hasCondition), formMissing)
|
||||
|
||||
return concat(condMissing, formMissing)
|
||||
cache.parseLevel--
|
||||
// if (keys(condMissing).length) console.log("".padStart(cache.parseLevel-1),{conditions:condMissing, formule:formMissing})
|
||||
// else console.log("".padStart(cache.parseLevel-1),{formule:formMissing})
|
||||
return { ...evaluated, nodeValue, isApplicable, missingVariables }
|
||||
}
|
||||
|
||||
let parsedRoot = evolve({
|
||||
|
@ -524,15 +513,16 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
'applicable si': evolveCond('applicable si', rule, rules),
|
||||
formule: value => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => collectNodeMissing(node.explanation)
|
||||
let explanation = evaluateNode(
|
||||
cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
),
|
||||
nodeValue = explanation.nodeValue
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
nodeValue = explanation.nodeValue,
|
||||
missingVariables = explanation.missingVariables
|
||||
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
let child = treat(rules, rule)(value)
|
||||
|
@ -555,22 +545,22 @@ export let treatRuleRoot = (rules, rule) => {
|
|||
// Pas de propriété explanation et jsx ici car on est parti du (mauvais) principe que 'non applicable si' et 'formule' sont particuliers, alors qu'ils pourraient être rangé avec les autres mécanismes
|
||||
...parsedRoot,
|
||||
evaluate,
|
||||
collectMissing,
|
||||
parsed: true
|
||||
}
|
||||
}
|
||||
|
||||
let evolveCond = (name, rule, rules) => value => {
|
||||
let evaluate = (cache, situationGate, parsedRules, node) => {
|
||||
let collectMissing = node => collectNodeMissing(node.explanation)
|
||||
let explanation = evaluateNode(
|
||||
cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
),
|
||||
nodeValue = explanation.nodeValue
|
||||
return rewriteNode(node, nodeValue, explanation, collectMissing)
|
||||
nodeValue = explanation.nodeValue,
|
||||
missingVariables = explanation.missingVariables
|
||||
|
||||
return rewriteNode(node, nodeValue, explanation, missingVariables)
|
||||
}
|
||||
|
||||
let child = treat(rules, rule)(value)
|
||||
|
@ -622,7 +612,7 @@ export let parseAll = flatRules => {
|
|||
export let analyseMany = (parsedRules, targetNames) => situationGate => {
|
||||
// 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 cache = {parseLevel: 0}
|
||||
|
||||
let parsedTargets = targetNames.map(t => findRule(parsedRules, t)),
|
||||
targets = chain(pt => getTargets(pt, parsedRules), parsedTargets).map(t =>
|
||||
|
|
|
@ -160,19 +160,20 @@ describe('collectMissingVariables', function() {
|
|||
formule: {
|
||||
barème: {
|
||||
assiette: 2008,
|
||||
'multiplicateur des tranches': 1000,
|
||||
variations: [
|
||||
{
|
||||
si: 'dix',
|
||||
'multiplicateur des tranches': 'deux',
|
||||
tranches: [
|
||||
{ 'en-dessous de': 1, taux: 0.1 },
|
||||
{ de: 1, à: 2, taux: 'deux' },
|
||||
{ de: 1, à: 2, taux: 'trois' },
|
||||
,
|
||||
{ 'au-dessus de': 2, taux: 10 }
|
||||
]
|
||||
},
|
||||
{
|
||||
si: '3 > 4',
|
||||
'multiplicateur des tranches': 'quatre',
|
||||
tranches: [
|
||||
{ 'en-dessous de': 1, taux: 0.1 },
|
||||
{ de: 1, à: 2, taux: 1.8 },
|
||||
|
@ -185,14 +186,19 @@ describe('collectMissingVariables', function() {
|
|||
}
|
||||
},
|
||||
{ nom: 'dix', espace: 'top' },
|
||||
{ nom: 'deux', espace: 'top' }
|
||||
{ nom: 'deux', espace: 'top' },
|
||||
{ nom: 'trois', espace: 'top' },
|
||||
{ nom: 'quatre', espace: 'top' }
|
||||
],
|
||||
rules = parseAll(rawRules.map(enrichRule)),
|
||||
analysis = analyse(rules, 'startHere')(stateSelector),
|
||||
result = collectMissingVariables(analysis.targets)
|
||||
|
||||
expect(result).to.have.property('top . dix')
|
||||
// expect(result).to.have.property('top . deux') - this is a TODO
|
||||
expect(result).to.have.property('top . deux')
|
||||
expect(result).not.to.have.property('top . quatre')
|
||||
// TODO
|
||||
// expect(result).to.have.property('top . trois')
|
||||
})
|
||||
|
||||
it('should not report missing variables in irrelevant variations', function() {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { length } from 'ramda'
|
||||
import { expect } from 'chai'
|
||||
import { rules as realRules, enrichRule } from '../source/engine/rules'
|
||||
import { analyse, analyseMany, parseAll } from '../source/engine/traverse'
|
||||
|
@ -105,8 +106,23 @@ describe('inversions', () => {
|
|||
- net
|
||||
- nom: cadre
|
||||
- nom: assiette
|
||||
formule: 67 + brut
|
||||
|
||||
formule:
|
||||
somme:
|
||||
- 1200
|
||||
- brut
|
||||
- taxeOne
|
||||
- nom: taxeOne
|
||||
non applicable si: cadre
|
||||
formule: taxe + taxe
|
||||
- nom: taxe
|
||||
formule:
|
||||
multiplication:
|
||||
assiette: 1200
|
||||
variations:
|
||||
- si: cadre
|
||||
taux: 80%
|
||||
- si: ≠ cadre
|
||||
taux: 70%
|
||||
`,
|
||||
rules = parseAll(yaml.safeLoad(rawRules).map(enrichRule)),
|
||||
stateSelector = name => ({ net: 2000 }[name]),
|
||||
|
|
Loading…
Reference in New Issue