Merge pull request #197 from betagouv/simplify-missing

Améliorations des variables manquantes
pull/196/merge v0.3.3
Laurent Bossavit 2018-04-23 15:02:31 +02:00 committed by GitHub
commit c711f3fbd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 194 additions and 180 deletions

View File

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

View File

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

View File

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

View File

@ -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) : [])

View File

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

View File

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

View File

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