))}
}
/>
)
return {
explanation,
evaluate,
jsx,
category: 'mecanism',
name: 'variations',
type: 'numeric'
}
}
export let mecanismOneOf = (recurse, k, v) => {
if (!is(Array, v)) throw new Error('should be array')
let explanation = map(recurse, v)
let jsx = (nodeValue, explanation) => (
{explanation.map(item => (
{makeJsx(item)}
))}
}
/>
)
let evaluate = (cache, situationGate, parsedRules, node) => {
let evaluateOne = child =>
evaluateNode(cache, situationGate, parsedRules, child),
explanation = map(evaluateOne, node.explanation),
values = pluck('nodeValue', explanation),
nodeValue = any(equals(true), values)
? true
: any(equals(null), values) ? null : false
let collectMissing = node =>
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
return rewriteNode(node, nodeValue, explanation, collectMissing)
}
return {
evaluate,
jsx,
explanation,
category: 'mecanism',
name: 'une de ces conditions',
type: 'boolean'
}
}
export let mecanismAllOf = (recurse, k, v) => {
if (!is(Array, v)) throw new Error('should be array')
let explanation = map(recurse, v)
let jsx = (nodeValue, explanation) => (
{explanation.map(item => (
{makeJsx(item)}
))}
}
/>
)
let evaluate = (cache, situationGate, parsedRules, node) => {
let evaluateOne = child =>
evaluateNode(cache, situationGate, parsedRules, child),
explanation = map(evaluateOne, node.explanation),
values = pluck('nodeValue', explanation),
nodeValue = any(equals(false), values)
? false // court-circuit
: any(equals(null), values) ? null : true
let collectMissing = node =>
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
return rewriteNode(node, nodeValue, explanation, collectMissing)
}
return {
evaluate: evaluate,
jsx,
explanation,
category: 'mecanism',
name: 'toutes ces conditions',
type: 'boolean'
}
}
export let mecanismNumericalSwitch = (recurse, k, v) => {
// Si "l'aiguillage" est une constante ou une référence directe à une variable;
// l'utilité de ce cas correspond à un appel récursif au mécanisme
if (is(String, v)) return recurse(v)
if (!is(Object, v) || keys(v).length == 0) {
throw new Error(
'Le mécanisme "aiguillage numérique" et ses sous-logiques doivent contenir au moins une proposition'
)
}
// les termes sont les couples (condition, conséquence) de l'aiguillage numérique
let terms = toPairs(v)
// la conséquence peut être un 'string' ou un autre aiguillage numérique
let parseCondition = ([condition, consequence]) => {
let conditionNode = recurse(condition), // can be a 'comparison', a 'variable', TODO a 'negation'
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
)
return {
...node,
collectMissing,
explanation,
nodeValue: explanation.consequence.nodeValue,
condValue: explanation.condition.nodeValue
}
}
let jsx = (nodeValue, { condition, consequence }) => (
{makeJsx(condition)}
{makeJsx(consequence)}
)
return {
evaluate,
jsx,
explanation: { condition: conditionNode, consequence: consequenceNode },
category: 'condition',
text: condition,
condition: conditionNode,
type: 'boolean'
}
}
let evaluateTerms = (cache, situationGate, parsedRules, node) => {
let evaluateOne = child =>
evaluateNode(cache, situationGate, parsedRules, child),
explanation = map(evaluateOne, node.explanation),
nonFalsyTerms = filter(node => node.condValue !== false, explanation),
getFirst = o => pipe(head, prop(o))(nonFalsyTerms),
nodeValue =
// voilà le "numérique" dans le nom de ce mécanisme : il renvoie zéro si aucune condition n'est vérifiée
isEmpty(nonFalsyTerms)
? 0
: // c'est un 'null', on renvoie null car des variables sont manquantes
getFirst('condValue') == null
? null
: // c'est un true, on renvoie la valeur de la conséquence
getFirst('nodeValue')
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)
}
let explanation = map(parseCondition, terms)
let jsx = (nodeValue, explanation) => (
{explanation.map(item => (
{makeJsx(item)}
))}
}
/>
)
return {
evaluate: evaluateTerms,
jsx,
explanation,
category: 'mecanism',
name: 'aiguillage numérique',
type: 'boolean || numeric' // lol !
}
}
export let findInversion = (situationGate, rules, v, dottedName) => {
let inversions = v.avec
if (!inversions)
throw new Error(
"Une formule d'inversion doit préciser _avec_ quoi on peut inverser la variable"
)
/*
Quelle variable d'inversion possible a sa valeur renseignée dans la situation courante ?
Ex. s'il nous est demandé de calculer le salaire de base, est-ce qu'un candidat à l'inversion, comme
le salaire net, a été renseigné ?
*/
let fixedObjective = inversions
.map(i =>
disambiguateRuleReference(
rules,
rules.find(propEq('dottedName', dottedName)),
i
)
)
.find(name => situationGate(name) != undefined)
if (fixedObjective == null) return { inversionChoiceNeeded: true }
//par exemple, fixedObjective = 'salaire net', et v('salaire net') == 2000
return {
fixedObjective,
fixedObjectiveValue: situationGate(fixedObjective),
fixedObjectiveRule: findRuleByDottedName(rules, fixedObjective)
}
}
let doInversion = (situationGate, parsedRules, v, dottedName) => {
let inversion = findInversion(situationGate, parsedRules, v, dottedName)
if (inversion.inversionChoiceNeeded)
return {
inversionMissingVariables: [dottedName],
nodeValue: null
}
let { fixedObjectiveValue, fixedObjectiveRule } = inversion
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
// 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 tolerancePercentage = 0.00001,
// cette fonction détermine la racine d'une fonction sans faire trop d'itérations
nodeValue = uniroot(
x => fx(x) - fixedObjectiveValue,
0,
1000000000,
tolerancePercentage * fixedObjectiveValue,
100
)
return {
nodeValue,
inversionMissingVariables: [],
inversionCache
}
}
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
let evaluatedNode = rewriteNode(node, nodeValue, null, collectMissing)
// rewrite the simulation cache with the definitive inversion values
toPairs(inversion.inversionCache).map(([k, v]) => (cache[k] = v))
return evaluatedNode
}
return {
evaluate,
jsx: nodeValue => (
)}
}
/>
)
return {
evaluate,
jsx,
explanation,
category: 'mecanism',
name: 'multiplication',
type: 'numeric'
}
}
export let mecanismScale = (recurse, k, v) => {
// Sous entendu : barème en taux marginaux.
// A étendre (avec une propriété type ?) quand les règles en contiendront d'autres.
if (v.composantes) {
//mécanisme de composantes. Voir known-mecanisms.md/composantes
return decompose(recurse, k, v)
}
if (v.variations) {
return devariate(recurse, k, v)
}
/* on réécrit en une syntaxe plus bas niveau mais plus régulière les tranches :
`en-dessous de: 1`
devient
```
de: 0
à: 1
```
*/
let tranches = v['tranches']
.map(
t =>
has('en-dessous de')(t)
? { de: 0, à: t['en-dessous de'], taux: t.taux }
: has('au-dessus de')(t)
? { de: t['au-dessus de'], à: Infinity, taux: t.taux }
: t
)
.map(evolve({ taux: recurse }))
let objectShape = {
assiette: false,
'multiplicateur des tranches': constantNode(1)
}
let effect = ({
assiette,
'multiplicateur des tranches': multiplicateur,
tranches
}) => {
// TODO traiter la récursion 'de', 'à', 'taux' pour qu'ils puissent contenir des calculs
// ou pour les cas où toutes les tranches n'ont pas un multiplicateur commun (ex. plafond
// sécurité sociale). Il faudra alors vérifier leur nullité comme ça :
/*
nulled = assiette.nodeValue == null || any(
pipe(
values, map(val), any(equals(null))
)
)(tranches),
*/
// nulled = anyNull([assiette, multiplicateur]),
let nulled = val(assiette) == null || val(multiplicateur) == null
return nulled
? null
: tranches.reduce(
(memo, { de: min, à: max, taux }) =>
val(assiette) < min * val(multiplicateur)
? memo + 0
: memo +
(Math.min(val(assiette), max * val(multiplicateur)) -
min * val(multiplicateur)) *
taux.nodeValue,
0
)
}
let explanation = {
...parseObject(recurse, objectShape, v),
tranches
},
evaluate = evaluateObject(objectShape, effect)
let jsx = (nodeValue, explanation) => (
assiette: {makeJsx(explanation.assiette)}
multiplicateur des tranches:
{makeJsx(explanation['multiplicateur des tranches'])}