mon-entreprise/source/engine/mecanisms.js

983 lines
24 KiB
JavaScript
Raw Normal View History

2018-01-08 15:07:26 +00:00
import {
objOf,
toPairs,
dissoc,
add,
find,
uniq,
chain,
pluck,
concat,
map,
any,
equals,
is,
keys,
evolve,
curry,
filter,
pipe,
head,
isEmpty,
propEq,
prop,
has,
max,
min,
subtract,
values,
sortBy,
last
2018-01-08 15:07:26 +00:00
} from 'ramda'
import React from 'react'
2017-10-24 16:25:31 +00:00
import { anyNull, val } from './traverse-common-functions'
import { Node } from './mecanismViews/common'
import {
makeJsx,
evaluateNode,
rewriteNode,
evaluateArray,
evaluateArrayWithFilter,
evaluateObject,
parseObject,
collectNodeMissing
} from './evaluation'
import {
findRuleByName,
disambiguateRuleReference,
findRuleByDottedName
} from './rules'
import 'react-virtualized/styles.css'
import Somme from './mecanismViews/Somme'
2018-02-21 12:46:38 +00:00
import Allègement from './mecanismViews/Allègement'
import buildSelectionView from './mecanismViews/Selection'
import uniroot from './uniroot'
2017-10-24 16:25:31 +00:00
let constantNode = constant => ({
nodeValue: constant,
jsx: nodeValue => <span className="value">{nodeValue}</span>
})
2017-07-28 12:24:29 +00:00
let decompose = (recurse, k, v) => {
2018-01-08 15:07:26 +00:00
let subProps = dissoc('composantes')(v),
2017-10-24 16:25:31 +00:00
explanation = v.composantes.map(c => ({
...recurse(
2018-01-08 15:07:26 +00:00
objOf(k, {
2017-10-24 16:25:31 +00:00
...subProps,
2018-01-08 15:07:26 +00:00
...dissoc('attributs')(c)
2017-10-24 16:25:31 +00:00
})
),
composante: c.nom ? { nom: c.nom } : c.attributs
}))
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism composantes"
name="composantes"
value={nodeValue}
child={
<ul>
2017-10-24 16:25:31 +00:00
{explanation.map((c, i) => [
<li className="composante" key={JSON.stringify(c.composante)}>
<ul className="composanteAttributes">
2018-01-08 15:07:26 +00:00
{toPairs(c.composante).map(([k, v]) => (
<li key={k}>
<span>{k}: </span>
<span>{v}</span>
</li>
2017-10-24 16:25:31 +00:00
))}
</ul>
2017-10-24 16:25:31 +00:00
<div className="content">{makeJsx(c)}</div>
</li>,
2017-10-24 16:25:31 +00:00
i < explanation.length - 1 && (
<li key="+" className="composantesSymbol">
<i className="fa fa-plus-circle" aria-hidden="true" />
</li>
)
])}
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
2017-10-24 16:25:31 +00:00
let filter = situationGate => c =>
!situationGate('sys.filter') ||
!c.composante ||
!c.composante['dû par'] ||
c.composante['dû par'] == situationGate('sys.filter')
return {
explanation,
jsx,
2018-01-08 15:07:26 +00:00
evaluate: evaluateArrayWithFilter(filter, add, 0),
category: 'mecanism',
name: 'composantes',
type: 'numeric'
}
}
2017-07-28 12:24:29 +00:00
let devariate = (recurse, k, v) => {
2018-01-08 15:07:26 +00:00
let subProps = dissoc('variations')(v),
2017-10-24 16:25:31 +00:00
explanation = v.variations.map(c => ({
...recurse(
2018-01-08 15:07:26 +00:00
objOf(k, {
2017-10-24 16:25:31 +00:00
...subProps,
2018-01-08 15:07:26 +00:00
...dissoc('si')(c)
2017-10-24 16:25:31 +00:00
})
),
condition: recurse(c.si)
}))
2017-07-28 12:24:29 +00:00
let evaluate = (cache, situationGate, parsedRules, node) => {
2017-07-28 12:24:29 +00:00
let evaluateOne = child => {
let condition = evaluateNode(
cache,
situationGate,
parsedRules,
child.condition
)
2017-10-24 16:25:31 +00:00
return {
...evaluateNode(cache, situationGate, parsedRules, child),
2017-10-24 16:25:31 +00:00
condition
2017-07-28 12:24:29 +00:00
}
2017-10-24 16:25:31 +00:00
}
2017-07-28 12:24:29 +00:00
2018-01-08 15:07:26 +00:00
let explanation = map(evaluateOne, node.explanation),
choice = find(node => node.condition.nodeValue, explanation),
2017-07-28 12:24:29 +00:00
nodeValue = choice ? choice.nodeValue : null
let collectMissing = node => {
2018-01-08 15:07:26 +00:00
let choice = find(node => node.condition.nodeValue, node.explanation),
2017-10-24 16:25:31 +00:00
leftMissing = choice
? []
2018-01-08 15:07:26 +00:00
: uniq(
chain(collectNodeMissing, pluck('condition', node.explanation))
),
2017-10-24 16:25:31 +00:00
rightMissing = choice
? collectNodeMissing(choice)
2018-01-08 15:07:26 +00:00
: chain(collectNodeMissing, node.explanation)
return concat(leftMissing, rightMissing)
2017-07-28 12:24:29 +00:00
}
2017-10-24 16:25:31 +00:00
return rewriteNode(node, nodeValue, explanation, collectMissing)
2017-07-28 12:24:29 +00:00
}
// TODO - find an appropriate representation
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
2017-07-28 12:24:29 +00:00
<Node
classes="mecanism variations"
name="variations"
value={nodeValue}
child={
<ul>
{explanation.map(c => (
<li className="variation" key={JSON.stringify(c.condition)}>
<div className="condition">
2017-10-24 16:25:31 +00:00
{makeJsx(c.condition)}
<div className="content">{makeJsx(c)}</div>
2017-07-28 12:24:29 +00:00
</div>
</li>
))}
2017-07-28 12:24:29 +00:00
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
2017-07-28 12:24:29 +00:00
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')
2018-01-08 15:07:26 +00:00
let explanation = map(recurse, v)
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism conditions list"
2017-10-24 16:25:31 +00:00
name="une de ces conditions"
value={nodeValue}
child={
<ul>
2017-10-24 16:25:31 +00:00
{explanation.map(item => (
<li key={item.name || item.text}>{makeJsx(item)}</li>
))}
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
let evaluate = (cache, situationGate, parsedRules, node) => {
let evaluateOne = child =>
evaluateNode(cache, situationGate, parsedRules, child),
2018-01-08 15:07:26 +00:00
explanation = map(evaluateOne, node.explanation),
values = pluck('nodeValue', explanation),
nodeValue = any(equals(true), values)
2017-10-24 16:25:31 +00:00
? true
2018-01-08 15:07:26 +00:00
: any(equals(null), values) ? null : false
2017-10-24 16:25:31 +00:00
let collectMissing = node =>
2018-01-08 15:07:26 +00:00
node.nodeValue == null ? chain(collectNodeMissing, node.explanation) : []
2017-10-24 16:25:31 +00:00
return rewriteNode(node, nodeValue, explanation, collectMissing)
}
return {
evaluate,
jsx,
explanation,
category: 'mecanism',
name: 'une de ces conditions',
type: 'boolean'
}
}
2017-10-24 16:25:31 +00:00
export let mecanismAllOf = (recurse, k, v) => {
if (!is(Array, v)) throw new Error('should be array')
2018-01-08 15:07:26 +00:00
let explanation = map(recurse, v)
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism conditions list"
2017-10-24 16:25:31 +00:00
name="toutes ces conditions"
value={nodeValue}
child={
<ul>
2017-10-24 16:25:31 +00:00
{explanation.map(item => (
<li key={item.name || item.text}>{makeJsx(item)}</li>
))}
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
let evaluate = (cache, situationGate, parsedRules, node) => {
let evaluateOne = child =>
evaluateNode(cache, situationGate, parsedRules, child),
2018-01-08 15:07:26 +00:00
explanation = map(evaluateOne, node.explanation),
values = pluck('nodeValue', explanation),
nodeValue = any(equals(false), values)
? false // court-circuit
2018-01-08 15:07:26 +00:00
: any(equals(null), values) ? null : true
let collectMissing = node =>
2018-01-08 15:07:26 +00:00
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'
}
}
2017-10-24 16:25:31 +00:00
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
2018-01-08 15:07:26 +00:00
if (is(String, v)) return recurse(v)
2018-01-08 15:07:26 +00:00
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
2018-01-08 15:07:26 +00:00
let terms = toPairs(v)
// la conséquence peut être un 'string' ou un autre aiguillage numérique
let parseCondition = ([condition, consequence]) => {
2017-10-24 16:25:31 +00:00
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,
2017-10-24 16:25:31 +00:00
missingOnTheRight = investigate
? collectNodeMissing(node.explanation.consequence)
: []
2018-01-08 15:07:26 +00:00
return concat(missingOnTheLeft, missingOnTheRight)
}
2018-01-08 15:07:26 +00:00
let explanation = evolve(
2017-10-24 16:25:31 +00:00
{
2018-01-08 15:07:26 +00:00
condition: curry(evaluateNode)(cache, situationGate, parsedRules),
consequence: curry(evaluateNode)(cache, situationGate, parsedRules)
2017-10-24 16:25:31 +00:00
},
node.explanation
)
return {
...node,
collectMissing,
explanation,
nodeValue: explanation.consequence.nodeValue,
condValue: explanation.condition.nodeValue
}
}
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, { condition, consequence }) => (
<div className="condition">
2017-10-24 16:25:31 +00:00
{makeJsx(condition)}
<div>{makeJsx(consequence)}</div>
</div>
)
return {
2017-10-24 16:25:31 +00:00
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),
2018-01-08 15:07:26 +00:00
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
2018-01-08 15:07:26 +00:00
isEmpty(nonFalsyTerms)
2017-10-24 16:25:31 +00:00
? 0
: // c'est un 'null', on renvoie null car des variables sont manquantes
getFirst('condValue') == null
2017-10-24 16:25:31 +00:00
? null
: // c'est un true, on renvoie la valeur de la conséquence
getFirst('nodeValue')
let collectMissing = node => {
2018-01-08 15:07:26 +00:00
let choice = find(node => node.condValue, node.explanation)
2017-10-24 16:25:31 +00:00
return choice
? collectNodeMissing(choice)
2018-01-08 15:07:26 +00:00
: chain(collectNodeMissing, node.explanation)
}
2017-10-24 16:25:31 +00:00
return rewriteNode(node, nodeValue, explanation, collectMissing)
}
2018-01-08 15:07:26 +00:00
let explanation = map(parseCondition, terms)
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism numericalSwitch list"
name="aiguillage numérique"
value={nodeValue}
child={
<ul>
2017-10-24 16:25:31 +00:00
{explanation.map(item => (
<li key={item.name || item.text}>{makeJsx(item)}</li>
))}
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
return {
evaluate: evaluateTerms,
jsx,
explanation,
category: 'mecanism',
2017-10-24 16:25:31 +00:00
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,
2018-01-08 15:07:26 +00:00
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
2018-01-08 15:07:26 +00:00
toPairs(inversion.inversionCache).map(([k, v]) => (cache[k] = v))
return evaluatedNode
}
return {
evaluate,
jsx: nodeValue => (
<Node
classes="mecanism inversion"
name="inversion"
value={nodeValue}
child={
<div>
<div>avec</div>
<ul>
{v.avec
.map(recurse)
.map(el => <li key={el.name}>{makeJsx(el)}</li>)}
</ul>
</div>
}
/>
),
category: 'mecanism',
name: 'inversion',
type: 'numeric'
}
}
2017-10-24 16:25:31 +00:00
export let mecanismSum = (recurse, k, v) => {
let explanation = v.map(recurse)
2018-01-08 15:07:26 +00:00
let evaluate = evaluateArray(add, 0)
return {
evaluate,
2017-10-24 16:25:31 +00:00
jsx: (nodeValue, explanation) => (
<Somme nodeValue={nodeValue} explanation={explanation} />
),
explanation,
category: 'mecanism',
name: 'somme',
type: 'numeric'
}
}
export let mecanismReduction = (recurse, k, v) => {
let objectShape = {
assiette: false,
abattement: constantNode(0),
franchise: constantNode(0)
}
let effect = ({ assiette, abattement, franchise, décote }) => {
let v_assiette = val(assiette)
if (v_assiette == null) return null
let montantFranchiséDécoté =
val(franchise) && v_assiette < val(franchise)
? 0
: décote
? do {
let plafond = val(décote.plafond),
taux = val(décote.taux)
v_assiette > plafond
? v_assiette
: max(0, (1 + taux) * v_assiette - taux * plafond)
}
: v_assiette
return abattement
? val(abattement) == null
? montantFranchiséDécoté === 0 ? 0 : null
: max(0, montantFranchiséDécoté - val(abattement))
: montantFranchiséDécoté
}
let base = parseObject(recurse, objectShape, v),
explanation = v.décote
? {
...base,
décote: map(recurse, v.décote)
}
: base,
evaluate = evaluateObject(objectShape, effect)
return {
evaluate,
2018-02-21 12:46:38 +00:00
jsx: Allègement,
explanation,
category: 'mecanism',
name: 'allègement',
type: 'numeric'
}
}
2017-10-24 16:25:31 +00:00
export let mecanismProduct = (recurse, k, v) => {
if (v.composantes) {
//mécanisme de composantes. Voir known-mecanisms.md/composantes
return decompose(recurse, k, v)
}
if (v.variations) {
2017-10-24 16:25:31 +00:00
return devariate(recurse, k, v)
}
let objectShape = {
2017-10-24 16:25:31 +00:00
assiette: false,
taux: constantNode(1),
facteur: constantNode(1),
plafond: constantNode(Infinity)
}
2017-10-24 16:25:31 +00:00
let effect = ({ assiette, taux, facteur, plafond }) => {
let mult = (base, rate, facteur, plafond) =>
Math.min(base, plafond) * rate * facteur
return val(taux) === 0 ||
val(taux) === false ||
val(assiette) === 0 ||
val(facteur) === 0
? 0
: anyNull([taux, assiette, facteur, plafond])
? null
: mult(val(assiette), val(taux), val(facteur), val(plafond))
}
2017-10-24 16:25:31 +00:00
let explanation = parseObject(recurse, objectShape, v),
evaluate = evaluateObject(objectShape, effect)
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism multiplication"
name="multiplication"
value={nodeValue}
child={
<ul className="properties">
<li key="assiette">
<span className="key">assiette: </span>
<span className="value">{makeJsx(explanation.assiette)}</span>
</li>
2017-10-24 16:25:31 +00:00
{(explanation.taux.nodeValue != 1 ||
explanation.taux.category == 'calcExpression') && (
<li key="taux">
<span className="key">taux: </span>
<span className="value">{makeJsx(explanation.taux)}</span>
</li>
)}
2017-10-24 16:25:31 +00:00
{(explanation.facteur.nodeValue != 1 ||
explanation.taux.category == 'calcExpression') && (
<li key="facteur">
<span className="key">facteur: </span>
<span className="value">{makeJsx(explanation.facteur)}</span>
</li>
)}
2017-10-24 16:25:31 +00:00
{explanation.plafond.nodeValue != Infinity && (
<li key="plafond">
<span className="key">plafond: </span>
<span className="value">{makeJsx(explanation.plafond)}</span>
</li>
)}
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
return {
evaluate,
jsx,
explanation,
category: 'mecanism',
name: 'multiplication',
type: 'numeric'
}
}
2017-10-24 16:25:31 +00:00
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.
2017-10-24 16:25:31 +00:00
if (v.composantes) {
//mécanisme de composantes. Voir known-mecanisms.md/composantes
return decompose(recurse, k, v)
2017-07-28 12:24:29 +00:00
}
if (v.variations) {
2017-10-24 16:25:31 +00:00
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 =>
2018-01-08 15:07:26 +00:00
has('en-dessous de')(t)
? { de: 0, à: t['en-dessous de'], taux: t.taux }
2018-01-08 15:07:26 +00:00
: has('au-dessus de')(t)
? { de: t['au-dessus de'], à: Infinity, taux: t.taux }
: t
)
2018-01-08 15:07:26 +00:00
.map(evolve({ taux: recurse }))
let objectShape = {
2017-10-24 16:25:31 +00:00
assiette: false,
'multiplicateur des tranches': constantNode(1)
}
2017-10-24 16:25:31 +00:00
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 :
/*
2018-01-08 15:07:26 +00:00
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
2017-10-24 16:25:31 +00:00
return nulled
? null
: tranches.reduce(
(memo, { de: min, à: max, taux }) =>
val(assiette) < min * val(multiplicateur)
? memo + 0
: memo +
2017-10-24 16:25:31 +00:00
(Math.min(val(assiette), max * val(multiplicateur)) -
min * val(multiplicateur)) *
taux.nodeValue,
0
)
2017-10-24 16:25:31 +00:00
}
let explanation = {
2017-10-24 16:25:31 +00:00
...parseObject(recurse, objectShape, v),
tranches
},
evaluate = evaluateObject(objectShape, effect)
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism barème"
name="barème"
value={nodeValue}
child={
<ul className="properties">
<li key="assiette">
<span className="key">assiette: </span>
<span className="value">{makeJsx(explanation.assiette)}</span>
</li>
<li key="multiplicateur">
<span className="key">multiplicateur des tranches: </span>
2017-10-24 16:25:31 +00:00
<span className="value">
{makeJsx(explanation['multiplicateur des tranches'])}
</span>
</li>
<table className="tranches">
<thead>
<tr>
<th>Tranches de l'assiette</th>
<th>Taux</th>
</tr>
2017-10-24 16:25:31 +00:00
{explanation.tranches.map(
({
'en-dessous de': maxOnly,
'au-dessus de': minOnly,
de: min,
à: max,
taux
}) => (
<tr
key={min || minOnly || 0}
style={{
fontWeight:
explanation.assiette.nodeValue *
explanation['multiplicateur des tranches'].nodeValue >
min
? ' bold'
: ''
}}
>
<td key="tranche">
2017-10-24 16:25:31 +00:00
{maxOnly
? 'En dessous de ' + maxOnly
: minOnly
? 'Au dessus de ' + minOnly
: `De ${min} à ${max}`}
</td>
<td key="taux"> {makeJsx(taux)} </td>
2017-10-24 16:25:31 +00:00
</tr>
)
)}
</thead>
</table>
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
return {
evaluate,
jsx,
explanation,
category: 'mecanism',
name: 'barème',
barème: 'en taux marginaux',
type: 'numeric'
}
}
2017-10-24 16:25:31 +00:00
export let mecanismMax = (recurse, k, v) => {
let explanation = v.map(recurse)
2018-01-08 15:07:26 +00:00
let evaluate = evaluateArray(max, Number.NEGATIVE_INFINITY)
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism list maximum"
name="le maximum de"
value={nodeValue}
child={
<ul>
2017-10-24 16:25:31 +00:00
{explanation.map((item, i) => (
<li key={i}>
<div className="description">{v[i].description}</div>
{makeJsx(item)}
</li>
))}
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
return {
evaluate,
jsx,
explanation,
type: 'numeric',
category: 'mecanism',
name: 'le maximum de'
}
}
2017-10-24 16:25:31 +00:00
export let mecanismMin = (recurse, k, v) => {
let explanation = v.map(recurse)
2018-01-08 15:07:26 +00:00
let evaluate = evaluateArray(min, Infinity)
2017-10-24 16:25:31 +00:00
let jsx = (nodeValue, explanation) => (
<Node
classes="mecanism list minimum"
name="le minimum de"
value={nodeValue}
child={
<ul>
2017-10-24 16:25:31 +00:00
{explanation.map((item, i) => (
<li key={i}>
<div className="description">{v[i].description}</div>
{makeJsx(item)}
</li>
))}
</ul>
}
/>
2017-10-24 16:25:31 +00:00
)
return {
evaluate,
jsx,
explanation,
type: 'numeric',
category: 'mecanism',
name: 'le minimum de'
}
}
2017-10-24 16:25:31 +00:00
export let mecanismComplement = (recurse, k, v) => {
if (v.composantes) {
//mécanisme de composantes. Voir known-mecanisms.md/composantes
return decompose(recurse, k, v)
}
2017-10-24 16:25:31 +00:00
let objectShape = { cible: false, montant: false }
let effect = ({ cible, montant }) => {
let nulled = val(cible) == null
2018-01-08 15:07:26 +00:00
return nulled ? null : subtract(val(montant), min(val(cible), val(montant)))
}
2017-10-24 16:25:31 +00:00
let explanation = parseObject(recurse, objectShape, v)
return {
2017-10-24 16:25:31 +00:00
evaluate: evaluateObject(objectShape, effect),
explanation,
type: 'numeric',
category: 'mecanism',
name: 'complément pour atteindre',
2017-10-24 16:25:31 +00:00
jsx: (nodeValue, explanation) => (
<Node
classes="mecanism list complement"
name="complément"
value={nodeValue}
child={
<ul className="properties">
<li key="cible">
<span className="key">cible: </span>
<span className="value">{makeJsx(explanation.cible)}</span>
</li>
<li key="mini">
<span className="key">montant à atteindre: </span>
<span className="value">{makeJsx(explanation.montant)}</span>
</li>
</ul>
}
/>
)
}
}
2017-10-24 16:25:31 +00:00
export let mecanismSelection = (recurse, k, v) => {
if (v.composantes) {
//mécanisme de composantes. Voir known-mecanisms.md/composantes
return decompose(recurse, k, v)
}
let dataSourceName = v['données']
let dataSearchField = v['dans']
let dataTargetName = v['renvoie']
let explanation = recurse(v['cherche'])
let evaluate = (cache, situationGate, parsedRules, node) => {
let collectMissing = node => collectNodeMissing(node.explanation),
explanation = evaluateNode(
cache,
situationGate,
parsedRules,
node.explanation
),
dataSource = findRuleByName(parsedRules, dataSourceName),
data = dataSource ? dataSource['data'] : null,
dataKey = explanation.nodeValue,
2017-10-24 16:25:31 +00:00
dataItems =
data && dataKey && dataSearchField
2018-01-08 15:07:26 +00:00
? filter(item => item[dataSearchField] == dataKey, data)
2017-10-24 16:25:31 +00:00
: null,
dataItemValues =
2018-01-08 15:07:26 +00:00
dataItems && !isEmpty(dataItems) ? values(dataItems) : null,
// TODO - over-specific! transform the JSON instead
2017-10-24 16:25:31 +00:00
dataItemSubValues =
dataItemValues && dataItemValues[0][dataTargetName]
? dataItemValues[0][dataTargetName]['taux']
: null,
sortedSubValues = dataItemSubValues
2018-01-08 15:07:26 +00:00
? sortBy(pair => pair[0], toPairs(dataItemSubValues))
2017-10-24 16:25:31 +00:00
: null,
// return 0 if we found a match for the lookup but not for the specific field,
// so that component sums don't sum to null
2017-10-24 16:25:31 +00:00
nodeValue = dataItems
? sortedSubValues
2018-01-08 15:07:26 +00:00
? Number.parseFloat(last(sortedSubValues)[1]) / 100
2017-10-24 16:25:31 +00:00
: 0
: null
return rewriteNode(node, nodeValue, explanation, collectMissing)
}
let SelectionView = buildSelectionView(dataTargetName)
return {
evaluate,
explanation,
jsx: (nodeValue, explanation) => (
<SelectionView nodeValue={nodeValue} explanation={explanation} />
)
}
}
2017-10-24 16:25:31 +00:00
export let mecanismError = (recurse, k, v) => {
throw new Error("Le mécanisme '" + k + "' est inconnu !" + v)
}