import R from 'ramda' import React from 'react' import {anyNull, val} from './traverse-common-functions' import {Node, Leaf} from './traverse-common-jsx' let transformPercentage = s => R.contains('%')(s) ? +s.replace('%', '') / 100 : +s export let decompose = (recurse, k, v) => { let subProps = R.dissoc('composantes')(v), filter = val(recurse("sys . filter")), isRelevant = c => !filter || !c.attributs || c.attributs['dû par'] == filter, composantes = v.composantes.filter(isRelevant).map(c => ({ ... recurse( R.objOf(k, { ... subProps, ... R.dissoc('attributs')(c) }) ), composante: c.nom ? {nom: c.nom} : c.attributs }) ), nodeValue = anyNull(composantes) ? null : R.reduce(R.add, 0, composantes.map(val)) return { nodeValue, category: 'mecanism', name: 'composantes', type: 'numeric', explanation: composantes, jsx: { composantes.map((c, i) => [
  • {c.jsx}
  • , i < (composantes.length - 1) &&
  • ] ) } } /> } } export let mecanismOneOf = (recurse, k, v) => { let result = R.pipe( R.unless(R.is(Array), () => {throw 'should be array'}), R.reduce( (memo, next) => { let {nodeValue, explanation} = memo, child = recurse(next), {nodeValue: nextValue} = child return {...memo, // c'est un OU logique mais avec une préférence pour null sur false nodeValue: nodeValue || nextValue || ( nodeValue == null ? null : nextValue ), explanation: [...explanation, child] } }, { nodeValue: false, category: 'mecanism', name: 'une de ces conditions', type: 'boolean', explanation: [] }) // Reduce but don't use R.reduced to set the nodeValue : we need to treat all the nodes )(v) return {...result, jsx: {result.explanation.map(item =>
  • {item.jsx}
  • )} } /> } } export let mecanismAllOf = (recurse, k,v) => { return R.pipe( R.unless(R.is(Array), () => {throw 'should be array'}), R.reduce( (memo, next) => { let {nodeValue, explanation} = memo, child = recurse(next), {nodeValue: nextValue} = child return {...memo, // c'est un ET logique avec une possibilité de null nodeValue: ! nodeValue ? nodeValue : nextValue, explanation: [...explanation, child] } }, { nodeValue: true, category: 'mecanism', name: 'toutes ces conditions', type: 'boolean', explanation: [] }) // Reduce but don't use R.reduced to set the nodeValue : we need to treat all the nodes )(v) } export let mecanismNumericalLogic = (recurse, k,v) => { return R.ifElse( R.is(String), rate => ({ //TODO unifier ce code nodeValue: transformPercentage(rate), type: 'numeric', category: 'percentage', percentage: rate, explanation: null, jsx: {rate} }), R.pipe( R.unless( v => R.is(Object)(v) && R.keys(v).length >= 1, () => {throw 'Le mécanisme "logique numérique" et ses sous-logiques doivent contenir au moins une proposition'} ), R.toPairs, R.reduce( (memo, [condition, consequence]) => { let {nodeValue, explanation} = memo, conditionNode = recurse(condition), // can be a 'comparison', a 'variable', TODO a 'negation' childNumericalLogic = mecanismNumericalLogic(recurse, condition, consequence), nextNodeValue = conditionNode.nodeValue == null ? // Si la proposition n'est pas encore résolvable null // Si la proposition est résolvable : conditionNode.nodeValue == true ? // Si elle est vraie childNumericalLogic.nodeValue // Si elle est fausse : false return {...memo, nodeValue: nodeValue == null ? null : nodeValue !== false ? nodeValue // l'une des propositions renvoie déjà une valeur numérique donc différente de false : nextNodeValue, explanation: [...explanation, { nodeValue: nextNodeValue, category: 'condition', text: condition, condition: conditionNode, conditionValue: conditionNode.nodeValue, type: 'boolean', explanation: childNumericalLogic, jsx:
    {conditionNode.jsx}
    {childNumericalLogic.jsx}
    }], } }, { nodeValue: false, category: 'mecanism', name: "logique numérique", type: 'boolean || numeric', // lol ! explanation: [] }), node => ({...node, jsx: {node.explanation.map(item =>
  • {item.jsx}
  • )} } /> }) ))(v) } export let mecanismPercentage = (recurse,k,v) => { let reg = /^(\d+(\.\d+)?)\%$/ if (R.test(reg)(v)) return { category: 'percentage', type: 'numeric', percentage: v, nodeValue: R.match(reg)(v)[1]/100, explanation: null, jsx: {v} } // Si c'est une liste historisée de pourcentages // TODO revoir le test avant le bug de l'an 2100 else if ( R.is(Array)(v) && R.all(R.test(/(19|20)\d\d(-\d\d)?(-\d\d)?/))(R.keys(v)) ) { //TODO sélectionner la date de la simulation en cours let lazySelection = R.first(R.values(v)) return { category: 'percentage', type: 'numeric', percentage: lazySelection, nodeValue: transformPercentage(lazySelection), explanation: null, jsx: {lazySelection} } } else { let node = recurse(v) return { type: 'numeric', category: 'percentage', percentage: node.nodeValue, nodeValue: node.nodeValue, explanation: node, jsx: node.jsx } } } export let mecanismSum = (recurse,k,v) => { let summedVariables = v.map(recurse), nodeValue = summedVariables.reduce( (memo, {nodeValue: nextNodeValue}) => memo == null ? null : nextNodeValue == null ? null : memo + +nextNodeValue, 0) return { nodeValue, category: 'mecanism', name: 'somme', type: 'numeric', explanation: summedVariables, jsx: {summedVariables.map(v =>
  • {v.jsx}
  • )} } /> } } export let mecanismProduct = (recurse,k,v) => { if (v.composantes) { //mécanisme de composantes. Voir known-mecanisms.md/composantes return decompose(recurse,k,v) } let mult = (base, rate, facteur, plafond) => Math.min(base, plafond) * rate * facteur, constantNode = constant => ({nodeValue: constant}), assiette = recurse(v['assiette']), //TODO parser le taux dans le parser ? taux = v['taux'] ? recurse({taux: v['taux']}) : constantNode(1), facteur = v['facteur'] ? recurse(v['facteur']) : constantNode(1), plafond = v['plafond'] ? recurse(v['plafond']) : constantNode(Infinity), //TODO rate == false should be more explicit nodeValue = (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)) return { nodeValue, category: 'mecanism', name: 'multiplication', type: 'numeric', explanation: { assiette, taux, facteur, plafond //TODO introduire 'prorata' ou 'multiplicateur', pour sémantiser les opérandes ? }, jsx:
  • assiette: {assiette.jsx}
  • {taux.nodeValue != 1 &&
  • taux: {taux.jsx}
  • } {facteur.nodeValue != 1 &&
  • facteur: {facteur.jsx}
  • } {plafond.nodeValue != Infinity &&
  • plafond: {plafond.jsx}
  • } } /> } } 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['multiplicateur des tranches'] == null) throw "un barème nécessite pour l'instant une propriété 'multiplicateur des tranches'" let assiette = recurse(v['assiette']), multiplicateur = recurse(v['multiplicateur des tranches']), /* on réécrit en plus bas niveau les tranches : `en-dessous de: 1` devient ``` de: 0 à: 1 ``` */ tranches = v['tranches'].map(t => R.has('en-dessous de')(t) ? {de: 0, 'à': t['en-dessous de'], taux: t.taux} : R.has('au-dessus de')(t) ? {de: t['au-dessus de'], 'à': Infinity, taux: t.taux} : t ), //TODO appliquer retreat() à 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 || R.any( R.pipe( R.values, R.map(val), R.any(R.equals(null)) ) )(tranches), */ // nulled = anyNull([assiette, multiplicateur]), nulled = val(assiette) == null || val(multiplicateur) == null, nodeValue = 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)) ) * transformPercentage(taux) , 0) return { nodeValue, category: 'mecanism', name: 'barème', barème: 'en taux marginaux', type: 'numeric', explanation: { assiette, multiplicateur, tranches }, jsx:
  • assiette: {assiette.jsx}
  • multiplicateur des tranches: {multiplicateur.jsx}
  • {v['tranches'].map(({'en-dessous de': maxOnly, 'au-dessus de': minOnly, de: min, 'à': max, taux}) => )}
    Tranches de l'assiette Taux
    { maxOnly ? 'En dessous de ' + maxOnly : minOnly ? 'Au dessus de ' + minOnly : `De ${min} à ${max}` } {taux}
    } /> } } export let mecanismMax = (recurse,k,v) => { let contenders = v.map(recurse), contenderValues = R.pluck('nodeValue')(contenders), stopEverything = R.contains(null, contenderValues), maxValue = R.max(...contenderValues), nodeValue = stopEverything ? null : maxValue return { type: 'numeric', category: 'mecanism', name: 'le maximum de', nodeValue, explanation: contenders, jsx: {contenders.map((item, i) =>
  • {v[i].description}
    {item.jsx}
  • )} } /> } } export let mecanismComplement = (recurse,k,v) => { if (v.composantes) { //mécanisme de composantes. Voir known-mecanisms.md/composantes return decompose(recurse,k,v) } if (v['cible'] == null) throw "un complément nécessite une propriété 'cible'" let cible = recurse(v['cible']), mini = recurse(v['montant']), nulled = val(cible) == null, nodeValue = nulled ? null : R.subtract(val(mini), R.min(val(cible), val(mini))) return { type: 'numeric', category: 'mecanism', name: 'complément pour atteindre', nodeValue, explanation: { cible, mini }, jsx:
  • montant calculé: {cible.jsx}
  • montant à atteindre: {mini.jsx}
  • } /> } } export let mecanismError = (recurse,k,v) => { throw "Le mécanisme est inconnu !" }