import R from 'ramda' import React from 'react' import {anyNull, val} from './traverse-common-functions' import {Node, Leaf} from './traverse-common-jsx' import {makeJsx, evaluateNode, rewriteNode, evaluateArray, evaluateArrayWithFilter, evaluateObject, parseObject, collectNodeMissing} from './evaluation' import {findRuleByName} from './rules' import 'react-virtualized/styles.css' import {Table, Column} from 'react-virtualized' import taux_versement_transport from 'Règles/rémunération-travail/cotisations/ok/liste-taux.json' let constantNode = constant => ({nodeValue: constant, jsx: nodeValue => {nodeValue}}) let decompose = (recurse, k, v) => { let subProps = R.dissoc('composantes')(v), explanation = v.composantes.map(c => ({ ... recurse( R.objOf(k, { ... subProps, ... R.dissoc('attributs')(c) }) ), composante: c.nom ? {nom: c.nom} : c.attributs }) ) let jsx = (nodeValue, explanation) => { explanation.map((c, i) => [
  • {makeJsx(c)}
  • , i < (explanation.length - 1) &&
  • ]) } } /> let filter = situationGate => c => (!situationGate("sys.filter") || !c.composante || !c.composante['dû par']) || c.composante['dû par'] == situationGate("sys.filter") return { explanation, jsx, evaluate: evaluateArrayWithFilter(filter,R.add,0), category: 'mecanism', name: 'composantes', type: 'numeric' } } let devariate = (recurse, k, v) => { let subProps = R.dissoc('variations')(v), explanation = v.variations.map(c => ({ ... recurse( R.objOf(k, { ... subProps, ... R.dissoc('si')(c) }) ), condition: recurse(c.si) }) ) let evaluate = (situationGate, parsedRules, node) => { let evaluateOne = child => { let condition = evaluateNode(situationGate, parsedRules, child.condition) return { ...evaluateNode(situationGate, parsedRules, child), condition } } let explanation = R.map(evaluateOne, node.explanation), choice = R.find(node => node.condition.nodeValue, explanation), nodeValue = choice ? choice.nodeValue : null let collectMissing = node => { let choice = R.find(node => node.condition.nodeValue, node.explanation), leftMissing = choice ? [] : R.chain(collectNodeMissing,R.pluck("condition",node.explanation)), rightMissing = choice ? collectNodeMissing(choice) : R.chain(collectNodeMissing,node.explanation) return R.concat(leftMissing,rightMissing) } return rewriteNode(node,nodeValue,explanation,collectMissing) } // TODO - find an appropriate representation let jsx = (nodeValue, explanation) => { explanation.map((c, i) => [
  • {makeJsx(c.condition)}
    {makeJsx(c)}
  • ]) } } /> return { explanation, evaluate, jsx, category: 'mecanism', name: 'variations', type: 'numeric' } } export let mecanismOneOf = (recurse, k, v) => { if (!R.is(Array,v)) throw 'should be array' let explanation = R.map(recurse, v) let jsx = (nodeValue, explanation) => {explanation.map(item =>
  • {makeJsx(item)}
  • )} } /> let evaluate = (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(true),values) ? true : (R.any(R.equals(null),values) ? null : false) let collectMissing = node => node.nodeValue == null ? R.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 (!R.is(Array,v)) throw 'should be array' let explanation = R.map(recurse, v) let jsx = (nodeValue, explanation) => {explanation.map(item =>
  • {makeJsx(item)}
  • )} } /> return { evaluate: evaluateArray(R.and,true), 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 (R.is(String,v)) return recurse(v) if (!R.is(Object,v) || R.keys(v).length == 0) { throw '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 = R.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 = (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 R.concat(missingOnTheLeft, missingOnTheRight) } let explanation = R.evolve({ condition: R.curry(evaluateNode)(situationGate, parsedRules), consequence: R.curry(evaluateNode)(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 = (situationGate, parsedRules, node) => { let evaluateOne = child => evaluateNode(situationGate, parsedRules, child), explanation = R.map(evaluateOne, node.explanation), choice = R.find(node => node.condValue, explanation), nonFalsyTerms = R.filter(node => node.condValue !== false, explanation), getFirst = (prop) => R.pipe(R.head, R.prop(prop))(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 R.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 = R.find(node => node.condValue, node.explanation) return choice ? collectNodeMissing(choice) : R.chain(collectNodeMissing,node.explanation) } return rewriteNode(node,nodeValue,explanation,collectMissing) } let explanation = R.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 mecanismSum = (recurse,k,v) => { let explanation = v.map(recurse) let evaluate = evaluateArray(R.add,0) let jsx = (nodeValue, explanation) => {explanation.map(v =>
  • {makeJsx(v)}
  • )} } /> return { evaluate, jsx, explanation, category: 'mecanism', name: 'somme', type: 'numeric' } } 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) { return devariate(recurse,k,v) } 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,v), evaluate = evaluateObject(objectShape,effect) let jsx = (nodeValue, explanation) =>
  • assiette: {makeJsx(explanation.assiette)}
  • {(explanation.taux.nodeValue != 1 || explanation.taux.category == 'calcExpression') &&
  • taux: {makeJsx(explanation.taux)}
  • } {(explanation.facteur.nodeValue != 1 || explanation.taux.category == 'calcExpression') &&
  • facteur: {makeJsx(explanation.facteur)}
  • } {explanation.plafond.nodeValue != Infinity &&
  • plafond: {makeJsx(explanation.plafond)}
  • } } /> 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 plus bas niveau les tranches : `en-dessous de: 1` devient ``` de: 0 à: 1 ``` */ let 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) 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 || R.any( R.pipe( R.values, R.map(val), R.any(R.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)) ) * recurse(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'])}
  • {explanation.tranches.map(({'en-dessous de': maxOnly, 'au-dessus de': minOnly, de: min, 'à': max, taux}) => min ? ' bold' : '')}} > )}
    Tranches de l'assiette Taux
    { maxOnly ? 'En dessous de ' + maxOnly : minOnly ? 'Au dessus de ' + minOnly : `De ${min} à ${max}` } {taux}
    } /> return { evaluate, jsx, explanation, category: 'mecanism', name: 'barème', barème: 'en taux marginaux', type: 'numeric' } } export let mecanismMax = (recurse,k,v) => { let explanation = v.map(recurse) let evaluate = evaluateArray(R.max,Number.NEGATIVE_INFINITY) let jsx = (nodeValue, explanation) => {explanation.map((item, i) =>
  • {v[i].description}
    {makeJsx(item)}
  • )} } /> return { evaluate, jsx, explanation, type: 'numeric', category: 'mecanism', name: 'le maximum de' } } export let mecanismMin = (recurse,k,v) => { let explanation = v.map(recurse) let evaluate = evaluateArray(R.min,Infinity) let jsx = (nodeValue, explanation) => {explanation.map((item, i) =>
  • {v[i].description}
    {makeJsx(item)}
  • )} } /> return { evaluate, jsx, explanation, type: 'numeric', category: 'mecanism', name: 'le minimum de' } } 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é: {makeJsx(explanation.cible)}
  • montant à atteindre: {makeJsx(explanation.montant)}
  • } /> } } 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 = (situationGate, parsedRules, node) => { let collectMissing = node => collectNodeMissing(node.explanation), explanation = evaluateNode(situationGate, parsedRules, node.explanation), dataSource = findRuleByName(parsedRules, dataSourceName), data = dataSource ? dataSource['data'] : null, dataKey = explanation.nodeValue, dataItems = (data && dataKey && dataSearchField) ? R.filter(item => item[dataSearchField] == dataKey, data) : null, dataItemValues = (dataItems && !R.isEmpty(dataItems)) ? R.values(dataItems) : null, // TODO - over-specific! transform the JSON instead dataItemSubValues = dataItemValues && dataItemValues[0][dataTargetName] ? dataItemValues[0][dataTargetName]["taux"] : null, sortedSubValues = dataItemSubValues ? R.sortBy(pair => pair[0], R.toPairs(dataItemSubValues)) : 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 nodeValue = dataItems ? (sortedSubValues ? Number.parseFloat(R.last(sortedSubValues)[1])/100 : 0) : null return rewriteNode(node,nodeValue,explanation,collectMissing) } let indexOf = explanation => explanation.nodeValue ? R.findIndex(x => x['nomLaposte'] == explanation.nodeValue, taux_versement_transport) : 0 let indexOffset = 8 let jsx = (nodeValue, explanation) => index == indexOf(explanation) ? { fontWeight: "bold" } : {} } rowGetter={ ({ index }) => { // transformation de données un peu crade du fichier taux.json qui gagnerait à être un CSV let line = taux_versement_transport[index], getLastTaux = dataTargetName => { let lastTaux = R.values(R.path([dataTargetName, 'taux'], line)) return (lastTaux && lastTaux.length && lastTaux[0]) || 0 } return { nom: line['nomLaposte'], taux: getLastTaux(dataTargetName) } } } > } /> return { evaluate, explanation, jsx } } export let mecanismError = (recurse,k,v) => { throw "Le mécanisme '"+k+"' est inconnu !"+v }