import R from 'ramda' import React from 'react' import {anyNull, val} from './traverse-common-functions' import {Node, Leaf} from './traverse-common-jsx' import {evaluateNode, collectNodeMissing} from './traverse' let transformPercentage = s => R.contains('%')(s) ? +s.replace('%', '') / 100 : +s let evaluateArray = (reducer, start) => (situationGate, parsedRules, node) => { let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), explanation = R.map(evaluateOne, node.explanation), values = R.pluck("nodeValue",explanation), nodeValue = R.any(R.equals(null),values) ? null : R.reduce(reducer, start, values) let collectMissing = node => R.chain(collectNodeMissing,node.explanation) return { ...node, nodeValue, collectMissing, explanation, jsx: { ...node.jsx, value: nodeValue } } } let parseObject = (recurse, objectShape, value) => { let recurseOne = key => defaultValue => { if (!value[key] && ! defaultValue) throw "Il manque une valeur '"+key+"'" return value[key] ? recurse(value[key]) : defaultValue } let transforms = R.fromPairs(R.map(k => [k,recurseOne(k)],R.keys(objectShape))) return R.evolve(transforms,objectShape) } let evaluateObject = (objectShape, effect) => (situationGate, parsedRules, node) => { let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), collectMissing = node => R.chain(collectNodeMissing,R.values(node.explanation)) let transforms = R.map(k => [k,evaluateOne], R.keys(objectShape)), explanation = R.evolve(R.fromPairs(transforms))(node.explanation), nodeValue = effect(explanation) return { ...node, nodeValue, collectMissing, explanation, jsx: { ...node.jsx, value: nodeValue } } } 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, explanation = v.composantes.filter(isRelevant).map(c => ({ ... recurse( R.objOf(k, { ... subProps, ... R.dissoc('attributs')(c) }) ), composante: c.nom ? {nom: c.nom} : c.attributs }) ) return { evaluate: evaluateArray(R.add,0), category: 'mecanism', name: 'composantes', type: 'numeric', explanation, jsx: { explanation.map((c, i) => [
  • {c.jsx}
  • , i < (explanation.length - 1) &&
  • ] ) } } /> } } export let mecanismOneOf = (recurse, k, v) => { if (!R.is(Array,v)) throw 'should be array' let explanation = R.map(recurse, v) return { evaluate: evaluateArray(R.or,false), explanation, category: 'mecanism', name: 'une de ces conditions', type: 'boolean', jsx: {explanation.map(item =>
  • {item.jsx}
  • )} } /> } } export let mecanismAllOf = (recurse, k,v) => { if (!R.is(Array,v)) throw 'should be array' let explanation = R.map(recurse, v) return { evaluate: evaluateArray(R.and,true), explanation, category: 'mecanism', name: 'toutes ces conditions', type: 'boolean', jsx: {explanation.map(item =>
  • {item.jsx}
  • )} } /> } } export let mecanismNumericalLogic = (recurse, k,v) => { if (R.is(String,v)) { // This seems an undue limitation return mecanismPercentage(recurse,k,v) } if (!R.is(Object,v) || R.keys(v).length == 0) { throw 'Le mécanisme "logique numérique" et ses sous-logiques doivent contenir au moins une proposition' } let parseCondition = ([condition, consequence]) => { let conditionNode = recurse(condition), // can be a 'comparison', a 'variable', TODO a 'negation' childNumericalLogic = mecanismNumericalLogic(recurse, condition, consequence) let collectMissing = node => R.concat(collectNodeMissing(conditionNode),collectNodeMissing(childNumericalLogic)) let evaluate = (situationGate, parsedRules, node) => ({ ...evaluateNode(situationGate, parsedRules, childNumericalLogic), condValue: evaluateNode(situationGate, parsedRules, conditionNode).nodeValue }) return { evaluate, collectMissing, category: 'condition', text: condition, condition: conditionNode, type: 'boolean', explanation: childNumericalLogic, jsx:
    {conditionNode.jsx}
    {childNumericalLogic.jsx}
    } } let evaluateTerms = (situationGate, parsedRules, node) => { let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), explanation = R.map(evaluateOne, node.explanation), choice = R.find(node => node.condValue, explanation), nodeValue = choice ? choice.nodeValue : null let collectMissing = node => R.chain(collectNodeMissing,node.explanation) return { ...node, nodeValue, collectMissing, explanation, jsx: { ...node.jsx, value: nodeValue } } } let terms = R.toPairs(v) let explanation = R.map(parseCondition,terms) return { evaluate: evaluateTerms, category: 'mecanism', name: "logique numérique", type: 'boolean || numeric', // lol ! explanation, jsx: {explanation.map(item =>
  • {item.jsx}
  • )} } /> } } 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} } 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 explanation = v.map(recurse) return { evaluate: evaluateArray(R.add,0), explanation, category: 'mecanism', name: 'somme', type: 'numeric', jsx: {explanation.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) } // Preprocessing step to parse percentages let wrap = x => ({taux: x}), value = R.evolve({taux:wrap},v) let constantNode = constant => ({nodeValue: constant}) let objectShape = { assiette:false, taux:constantNode(1), facteur:constantNode(1), plafond:constantNode(Infinity) } 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)) } let explanation = parseObject(recurse,objectShape,value), evaluate = evaluateObject(objectShape,effect) return { evaluate, category: 'mecanism', name: 'multiplication', type: 'numeric', explanation, jsx:
  • assiette: {explanation.assiette.jsx}
  • {explanation.taux.nodeValue != 1 &&
  • taux: {explanation.taux.jsx}
  • } {explanation.facteur.nodeValue != 1 &&
  • facteur: {explanation.facteur.jsx}
  • } {explanation.plafond.nodeValue != Infinity &&
  • plafond: {explanation.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 explanation = v.map(recurse) return { evaluate: evaluateArray(R.max,Number.NEGATIVE_INFINITY), type: 'numeric', category: 'mecanism', name: 'le maximum de', explanation, jsx: {explanation.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) } let objectShape = {cible:false,montant:false} let effect = ({cible,montant}) => { let nulled = val(cible) == null return nulled ? null : R.subtract(val(montant), R.min(val(cible), val(montant))) } let explanation = parseObject(recurse,objectShape,v) return { evaluate: evaluateObject(objectShape,effect), explanation, type: 'numeric', category: 'mecanism', name: 'complément pour atteindre', jsx:
  • montant calculé: {explanation.cible.jsx}
  • montant à atteindre: {explanation.montant.jsx}
  • } /> } } export let mecanismError = (recurse,k,v) => { throw "Le mécanisme est inconnu !" }