commit
38f1750e96
|
@ -1,3 +1,4 @@
|
|||
.tags*
|
||||
.tmp
|
||||
node_modules/
|
||||
dist/
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import {expect} from 'chai'
|
||||
import {enrichRule, collectMissingVariables, getObjectives} from '../source/engine/rules'
|
||||
import {analyseSituation} from '../source/engine/traverse'
|
||||
|
||||
let stateSelector = (state, name) => null
|
||||
|
||||
describe('enrichRule', function() {
|
||||
|
||||
it('should extract the type of the rule', function() {
|
||||
let rule = {cotisation:{}}
|
||||
expect(enrichRule(rule)).to.have.property('type','cotisation')
|
||||
});
|
||||
|
||||
it('should extract the dotted name of the rule', function() {
|
||||
let rule = {espace:"contrat salarié", nom: "CDD"}
|
||||
expect(enrichRule(rule)).to.have.property('name','CDD')
|
||||
expect(enrichRule(rule)).to.have.property('dottedName','contrat salarié . CDD')
|
||||
});
|
||||
|
||||
it('should render Markdown in sub-questions', function() {
|
||||
let rule = {"sous-question":"**wut**"}
|
||||
expect(enrichRule(rule)).to.have.property('subquestion','<p><strong>wut</strong></p>\n')
|
||||
});
|
||||
});
|
||||
|
||||
describe('collectMissingVariables', function() {
|
||||
|
||||
it('should derive objectives from the root rule', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {somme: [3259, "dix"]}, espace: "top"},
|
||||
{nom: "dix", formule: "cinq", espace: "top"},
|
||||
{nom: "cinq", espace: "top"}],
|
||||
rules = rawRules.map(enrichRule),
|
||||
situation = analyseSituation(rules,"startHere")(stateSelector),
|
||||
result = getObjectives(situation)
|
||||
|
||||
expect(result).to.have.lengthOf(1)
|
||||
expect(result[0]).to.have.property('name','dix')
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,139 @@
|
|||
import {expect} from 'chai'
|
||||
import {enrichRule} from '../source/engine/rules'
|
||||
import {treatRuleRoot} from '../source/engine/traverse'
|
||||
import {analyseSituation} from '../source/engine/traverse'
|
||||
|
||||
let stateSelector = (state, name) => null
|
||||
|
||||
describe('treatRuleRoot', function() {
|
||||
|
||||
it('should directly return simple numerical values', function() {
|
||||
let rule = {formule: 3269}
|
||||
expect(treatRuleRoot(stateSelector,[rule],rule)).to.have.property('nodeValue',3269)
|
||||
});
|
||||
|
||||
/* TODO: make this pass
|
||||
it('should directly return simple numerical values', function() {
|
||||
let rule = {formule: "3269"}
|
||||
expect(treatRuleRoot(stateSelector,[rule],rule)).to.have.property('nodeValue',3269)
|
||||
});
|
||||
*/
|
||||
|
||||
});
|
||||
|
||||
describe('analyseSituation', function() {
|
||||
|
||||
it('should directly return simple numerical values', function() {
|
||||
let rule = {name: "startHere", formule: 3269}
|
||||
let rules = [rule]
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3269)
|
||||
});
|
||||
|
||||
it('should compute expressions combining constants', function() {
|
||||
let rule = {name: "startHere", formule: "32 + 69"}
|
||||
let rules = [rule]
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',101)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('analyseSituation on raw rules', function() {
|
||||
|
||||
it('should handle expressions referencing other rules', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: "3259 + dix", espace: "top"},
|
||||
{nom: "dix", formule: 10, espace: "top"}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3269)
|
||||
});
|
||||
|
||||
it('should handle applicability conditions', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: "3259 + dix", espace: "top"},
|
||||
{nom: "dix", formule: 10, espace: "top", "non applicable si" : "vrai"},
|
||||
{nom: "vrai", formule: "2 > 1", espace: "top"}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3259)
|
||||
});
|
||||
|
||||
/* TODO: make this pass
|
||||
it('should handle applicability conditions', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: "3259 + dix", espace: "top"},
|
||||
{nom: "dix", formule: 10, espace: "top", "non applicable si" : "vrai"},
|
||||
{nom: "vrai", formule: "1", espace: "top"}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3259)
|
||||
});
|
||||
*/
|
||||
|
||||
});
|
||||
|
||||
describe('analyseSituation with mecanisms', function() {
|
||||
|
||||
it('should handle n-way "or"', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {"une de ces conditions": ["1 > 2", "1 > 0", "0 > 2"]}}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',true)
|
||||
});
|
||||
|
||||
it('should handle n-way "and"', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {"toutes ces conditions": ["1 > 2", "1 > 0", "0 > 2"]}}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',false)
|
||||
});
|
||||
|
||||
it('should handle switch statements', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {"logique numérique": {
|
||||
"1 > dix":"10",
|
||||
"3 < dix":"11",
|
||||
"3 > dix":"12"
|
||||
}}, espace: "top"},
|
||||
{nom: "dix", formule: 10, espace: "top"}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',11)
|
||||
});
|
||||
|
||||
it('should handle percentages', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {taux: "35%"}, espace: "top"}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',0.35)
|
||||
});
|
||||
|
||||
it('should handle sums', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {"somme": [3200, 60, 9]}}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3269)
|
||||
});
|
||||
|
||||
it('should handle multiplications', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {"multiplication": {assiette:3259, plafond:3200, facteur:1, taux:1.5}}}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',4800)
|
||||
});
|
||||
|
||||
it('should handle progressive scales', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {"barème": {
|
||||
assiette:2008,
|
||||
"multiplicateur des tranches":1000,
|
||||
"tranches":[{"en-dessous de":1, taux: 0.1},{de:1, "à": 2, taux: 1.2}, ,{"au-dessus de":2, taux: 10}]
|
||||
}}}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',100+1200+80)
|
||||
});
|
||||
|
||||
it('should handle max', function() {
|
||||
let rawRules = [
|
||||
{nom: "startHere", formule: {"le maximum de": [3200, 60, 9]}}],
|
||||
rules = rawRules.map(enrichRule)
|
||||
expect(analyseSituation(rules,"startHere")(stateSelector)).to.have.property('nodeValue',3200)
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
var assert = require('assert');
|
||||
|
||||
const utils = require("../source/utils.js")
|
||||
|
||||
describe('capitalise0', function() {
|
||||
it('should turn the first character into its capital', function() {
|
||||
assert.equal("Salaire", utils.capitalise0("salaire"));
|
||||
});
|
||||
});
|
|
@ -45,6 +45,7 @@
|
|||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-env": "^1.4.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"chai": "^4.0.2",
|
||||
"core-js": "^2.4.1",
|
||||
"css-loader": "^0.28.1",
|
||||
"eslint": "^3.19.0",
|
||||
|
@ -54,21 +55,25 @@
|
|||
"html-loader": "^0.4.5",
|
||||
"img-loader": "^2.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^3.4.2",
|
||||
"mocha-webpack": "^0.7.0",
|
||||
"nearley-loader": "0.0.2",
|
||||
"postcss-loader": "^2.0.5",
|
||||
"react-hot-loader": "^3.0.0-beta.6",
|
||||
"redux-devtools": "^3.4.0",
|
||||
"redux-devtools-dock-monitor": "^1.1.2",
|
||||
"redux-devtools-log-monitor": "^1.3.0",
|
||||
"source-map-support": "^0.4.15",
|
||||
"style-loader": "^0.17.0",
|
||||
"url-loader": "^0.5.8",
|
||||
"webpack": "^2.5.1",
|
||||
"webpack": "^2.6.1",
|
||||
"webpack-dev-server": "^2.4.5",
|
||||
"yaml-loader": "^0.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node source/server.js",
|
||||
"compile": "NODE_ENV='production' webpack --config source/webpack.config.js",
|
||||
"surge": "npm run compile && surge --domain scientific-wish.surge.sh -p ./ && rm -rf dist/"
|
||||
"surge": "npm run compile && surge --domain scientific-wish.surge.sh -p ./ && rm -rf dist/",
|
||||
"test": "mocha-webpack --webpack-config source/webpack.config.js --require source-map-support/register \"__tests__/**/*.test.js\""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {Component} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {findRuleByDottedName} from '../engine/rules'
|
||||
import {rules, findRuleByDottedName} from '../engine/rules'
|
||||
import './Aide.css'
|
||||
import {EXPLAIN_VARIABLE} from '../actions'
|
||||
import References from './rule/References'
|
||||
|
@ -23,7 +23,7 @@ export default class Aide extends Component {
|
|||
|
||||
if (!explained) return <section id="helpWrapper" />
|
||||
|
||||
let rule = findRuleByDottedName(explained),
|
||||
let rule = findRuleByDottedName(rule, explained),
|
||||
text = rule.description,
|
||||
refs = rule.références
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import R from 'ramda'
|
|||
import {Redirect, Link, withRouter} from 'react-router-dom'
|
||||
import Aide from './Aide'
|
||||
import {createMarkdownDiv} from 'Engine/marked'
|
||||
import {findRuleByName, decodeRuleName} from 'Engine/rules'
|
||||
import {rules, findRuleByName, decodeRuleName} from 'Engine/rules'
|
||||
import 'Components/conversation/conversation.css'
|
||||
import 'Components/Simulateur.css'
|
||||
import classNames from 'classnames'
|
||||
|
@ -44,7 +44,7 @@ export default class extends React.Component {
|
|||
|
||||
this.encodedName = encodedName
|
||||
this.name = name
|
||||
this.rule = findRuleByName(name)
|
||||
this.rule = findRuleByName(rules, name)
|
||||
|
||||
// C'est ici que la génération du formulaire, et donc la traversée des variables commence
|
||||
if (this.rule.formule)
|
||||
|
|
|
@ -4,7 +4,7 @@ import './Explicable.css'
|
|||
import HoverDecorator from '../HoverDecorator'
|
||||
import {connect} from 'react-redux'
|
||||
import {EXPLAIN_VARIABLE} from '../../actions'
|
||||
import {findRuleByDottedName} from '../../engine/rules'
|
||||
import {rules, findRuleByDottedName} from '../../engine/rules'
|
||||
|
||||
|
||||
@connect(state => ({explained: state.explainedVariable}), dispatch => ({
|
||||
|
@ -18,7 +18,7 @@ export default class Explicable extends React.Component {
|
|||
explain, explained,
|
||||
lightBackground
|
||||
} = this.props,
|
||||
rule = findRuleByDottedName(dottedName)
|
||||
rule = findRuleByDottedName(rules, dottedName)
|
||||
|
||||
// Rien à expliquer ici, ce n'est pas une règle
|
||||
if (!rule) return <span>{label}</span>
|
||||
|
|
|
@ -2,8 +2,8 @@ import React, { Component } from "react"
|
|||
import R from "ramda"
|
||||
import classNames from "classnames"
|
||||
import {
|
||||
rules,
|
||||
decodeRuleName,
|
||||
findRuleByName,
|
||||
disambiguateRuleReference
|
||||
} from "Engine/rules.js"
|
||||
import { analyseSituation } from "Engine/traverse"
|
||||
|
@ -18,13 +18,14 @@ export default class Examples extends Component {
|
|||
return exemples.map(ex => {
|
||||
// les variables dans les tests peuvent être exprimées relativement à l'espace de nom de la règle,
|
||||
// comme dans sa formule
|
||||
// TODO - absolutely don't do this here but as a transformation step in rule parsing
|
||||
let exempleSituation = R.pipe(
|
||||
R.toPairs,
|
||||
R.map(([k, v]) => [disambiguateRuleReference(rule, k), v]),
|
||||
R.map(([k, v]) => [disambiguateRuleReference(rules, rule, k), v]),
|
||||
R.fromPairs
|
||||
)(ex.situation)
|
||||
|
||||
let runExemple = analyseSituation(rule.name)(v => exempleSituation[v]),
|
||||
let runExemple = analyseSituation(rules, rule.name)(v => exempleSituation[v]),
|
||||
exempleCalculatedValue = runExemple["non applicable si"] &&
|
||||
runExemple["non applicable si"].nodeValue
|
||||
? null
|
||||
|
|
|
@ -4,7 +4,7 @@ import {connect} from 'react-redux'
|
|||
import {formValueSelector} from 'redux-form'
|
||||
import R from 'ramda'
|
||||
import './Rule.css'
|
||||
import {decodeRuleName, findRuleByName, disambiguateRuleReference} from 'Engine/rules.js'
|
||||
import {rules, decodeRuleName} from 'Engine/rules.js'
|
||||
import mockSituation from 'Engine/mockSituation.yaml'
|
||||
import {analyseSituation} from 'Engine/traverse'
|
||||
import {START_CONVERSATION} from '../../actions'
|
||||
|
@ -41,7 +41,7 @@ export default class Rule extends Component {
|
|||
}
|
||||
}
|
||||
setRule(name){
|
||||
this.rule = analyseSituation(decodeRuleName(name))(this.props.situationGate)
|
||||
this.rule = analyseSituation(rules, decodeRuleName(name))(this.props.situationGate)
|
||||
}
|
||||
componentWillMount(){
|
||||
let {
|
||||
|
|
|
@ -7,7 +7,7 @@ import formValueTypes from 'Components/conversation/formValueTypes'
|
|||
import {analyseSituation} from './traverse'
|
||||
import {formValueSelector} from 'redux-form'
|
||||
import { STEP_ACTION, START_CONVERSATION} from '../actions'
|
||||
import {findGroup, findRuleByDottedName, parentName, collectMissingVariables, findVariantsAndRecords} from './rules'
|
||||
import {rules, findRuleByDottedName, collectMissingVariables, deprecated_findVariantsAndRecords} from './rules'
|
||||
|
||||
|
||||
export let reduceSteps = (state, action) => {
|
||||
|
@ -58,7 +58,7 @@ let situationGate = state =>
|
|||
let analyse = rootVariable => R.pipe(
|
||||
situationGate,
|
||||
// une liste des objectifs de la simulation (des 'rules' aussi nommées 'variables')
|
||||
analyseSituation(rootVariable)
|
||||
analyseSituation(rules, rootVariable)
|
||||
)
|
||||
|
||||
|
||||
|
@ -111,7 +111,7 @@ let buildNextSteps = analysedSituation => {
|
|||
return R.pipe(
|
||||
R.keys,
|
||||
R.reduce(
|
||||
findVariantsAndRecords
|
||||
deprecated_findVariantsAndRecords
|
||||
, {variantGroups: {}, recordGroups: {}}
|
||||
),
|
||||
// on va maintenant construire la liste des composants React qui afficheront les questions à l'utilisateur pour que l'on obtienne les variables manquantes
|
||||
|
@ -153,7 +153,7 @@ let isVariant = R.path(['formule', 'une possibilité'])
|
|||
|
||||
let buildVariantTree = relevantPaths => path => {
|
||||
let rec = path => {
|
||||
let node = findRuleByDottedName(path),
|
||||
let node = findRuleByDottedName(rules, path),
|
||||
variant = isVariant(node),
|
||||
variants = variant && R.unless(R.is(Array), R.prop('possibilités'))(variant),
|
||||
shouldBeExpanded = variant && variants.find( v => relevantPaths.find(rp => R.contains(path + ' . ' + v)(rp) )),
|
||||
|
@ -175,7 +175,7 @@ export let generateGridQuestions = missingVariables => R.pipe(
|
|||
R.toPairs,
|
||||
R.map( ([variantRoot, relevantVariants]) =>
|
||||
({
|
||||
...constructStepMeta(findRuleByDottedName(variantRoot)),
|
||||
...constructStepMeta(findRuleByDottedName(rules, variantRoot)),
|
||||
component: Question,
|
||||
choices: buildVariantTree(relevantVariants)(variantRoot),
|
||||
objectives: R.pipe(
|
||||
|
@ -192,7 +192,7 @@ export let generateSimpleQuestions = missingVariables => R.pipe(
|
|||
R.values, //TODO exploiter ici les groupes de questions de type 'record' (R.keys): elles pourraient potentiellement êtres regroupées visuellement dans le formulaire
|
||||
R.unnest,
|
||||
R.map(dottedName => {
|
||||
let rule = findRuleByDottedName(dottedName)
|
||||
let rule = findRuleByDottedName(rules, dottedName)
|
||||
if (rule == null) console.log(dottedName)
|
||||
return Object.assign(
|
||||
constructStepMeta(rule),
|
||||
|
|
|
@ -0,0 +1,459 @@
|
|||
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 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: <Node
|
||||
classes="mecanism conditions list"
|
||||
name={result.name}
|
||||
value={result.nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{result.explanation.map(item => <li key={item.name || item.text}>{item.jsx}</li>)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
<span className="percentage" >
|
||||
<span className="name">{rate}</span>
|
||||
</span>
|
||||
}),
|
||||
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: <div className="condition">
|
||||
{conditionNode.jsx}
|
||||
<div>
|
||||
{childNumericalLogic.jsx}
|
||||
</div>
|
||||
</div>
|
||||
}],
|
||||
}
|
||||
}, {
|
||||
nodeValue: false,
|
||||
category: 'mecanism',
|
||||
name: "logique numérique",
|
||||
type: 'boolean || numeric', // lol !
|
||||
explanation: []
|
||||
}),
|
||||
node => ({...node,
|
||||
jsx: <Node
|
||||
classes="mecanism numericalLogic list"
|
||||
name="logique numérique"
|
||||
value={node.nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{node.explanation.map(item => <li key={item.name || item.text}>{item.jsx}</li>)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
})
|
||||
))(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:
|
||||
<span className="percentage" >
|
||||
<span className="name">{v}</span>
|
||||
</span>
|
||||
}
|
||||
// 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:
|
||||
<span className="percentage" >
|
||||
<span className="name">{lazySelection}</span>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
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: <Node
|
||||
classes="mecanism somme"
|
||||
name="somme"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{summedVariables.map(v => <li key={v.name || v.text}>{v.jsx}</li>)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export let mecanismProduct = (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: <Node
|
||||
classes="mecanism multiplication"
|
||||
name="multiplication"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul className="properties">
|
||||
<li key="assiette">
|
||||
<span className="key">assiette: </span>
|
||||
<span className="value">{assiette.jsx}</span>
|
||||
</li>
|
||||
{taux.nodeValue != 1 &&
|
||||
<li key="taux">
|
||||
<span className="key">taux: </span>
|
||||
<span className="value">{taux.jsx}</span>
|
||||
</li>}
|
||||
{facteur.nodeValue != 1 &&
|
||||
<li key="facteur">
|
||||
<span className="key">facteur: </span>
|
||||
<span className="value">{facteur.jsx}</span>
|
||||
</li>}
|
||||
{plafond.nodeValue != Infinity &&
|
||||
<li key="plafond">
|
||||
<span className="key">plafond: </span>
|
||||
<span className="value">{plafond.jsx}</span>
|
||||
</li>}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
let
|
||||
baremeProps = R.dissoc('composantes')(v),
|
||||
composantes = v.composantes.map(c =>
|
||||
({
|
||||
... recurse(
|
||||
{
|
||||
barème: {
|
||||
... baremeProps,
|
||||
... 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: <Node
|
||||
classes="mecanism composantes"
|
||||
name="composantes"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{ composantes.map((c, i) =>
|
||||
[<li className="composante" key={JSON.stringify(c.composante)}>
|
||||
<ul className="composanteAttributes">
|
||||
{R.toPairs(c.composante).map(([k,v]) =>
|
||||
<li>
|
||||
<span>{k}: </span>
|
||||
<span>{v}</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
<div className="content">
|
||||
{c.jsx}
|
||||
</div>
|
||||
</li>,
|
||||
i < (composantes.length - 1) && <li className="composantesSymbol"><i className="fa fa-plus-circle" aria-hidden="true"></i></li>
|
||||
]
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
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: <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">{assiette.jsx}</span>
|
||||
</li>
|
||||
<li key="multiplicateur">
|
||||
<span className="key">multiplicateur des tranches: </span>
|
||||
<span className="value">{multiplicateur.jsx}</span>
|
||||
</li>
|
||||
<table className="tranches">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tranches de l'assiette</th>
|
||||
<th>Taux</th>
|
||||
</tr>
|
||||
{v['tranches'].map(({'en-dessous de': maxOnly, 'au-dessus de': minOnly, de: min, 'à': max, taux}) =>
|
||||
<tr key={min || minOnly || 0}>
|
||||
<td>
|
||||
{ maxOnly ? 'En dessous de ' + maxOnly
|
||||
: minOnly ? 'Au dessus de ' + minOnly
|
||||
: `De ${min} à ${max}` }
|
||||
</td>
|
||||
<td> {taux} </td>
|
||||
</tr>
|
||||
)}
|
||||
</thead>
|
||||
</table>
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
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: <Node
|
||||
classes="mecanism list maximum"
|
||||
name="le maximum de"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{contenders.map((item, i) =>
|
||||
<li key={i}>
|
||||
<div className="description">{v[i].description}</div>
|
||||
{item.jsx}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
export let mecanismError = (recurse,k,v) => {
|
||||
throw "Le mécanisme est inconnu !"
|
||||
}
|
|
@ -46,7 +46,8 @@ export let decodeRuleName = name => name.replace(/\-/g, ' ')
|
|||
|
||||
/* Les variables peuvent être exprimées dans la formule d'une règle relativement à son propre espace de nom, pour une plus grande lisibilité. Cette fonction résoud cette ambiguité.
|
||||
*/
|
||||
export let disambiguateRuleReference = ({ns, name}, partialName) => {
|
||||
|
||||
export let disambiguateRuleReference = (allRules, {ns, name}, partialName) => {
|
||||
let
|
||||
fragments = ns.split(' . '), // ex. [CDD . événements . rupture]
|
||||
pathPossibilities = // -> [ [CDD . événements . rupture], [CDD . événements], [CDD] ]
|
||||
|
@ -56,7 +57,7 @@ export let disambiguateRuleReference = ({ns, name}, partialName) => {
|
|||
found = R.reduce((res, path) =>
|
||||
R.when(
|
||||
R.is(Object), R.reduced
|
||||
)(findRuleByDottedName([...path, partialName].join(' . ')))
|
||||
)(findRuleByDottedName(allRules, [...path, partialName].join(' . ')))
|
||||
, null, pathPossibilities)
|
||||
|
||||
return found && found.dottedName || do {throw `OUUUUPS la référence '${partialName}' dans la règle '${name}' est introuvable dans la base`}
|
||||
|
@ -69,9 +70,8 @@ export let rules = rawRules.map(enrichRule)
|
|||
/****************************************
|
||||
Méthodes de recherche d'une règle */
|
||||
|
||||
export let findRuleByName = search =>
|
||||
rules
|
||||
.map(enrichRule)
|
||||
export let findRuleByName = (allRules, search) =>
|
||||
allRules
|
||||
.find( ({name}) =>
|
||||
name.toLowerCase() === search.toLowerCase()
|
||||
)
|
||||
|
@ -83,21 +83,8 @@ export let searchRules = searchInput =>
|
|||
JSON.stringify(rule).toLowerCase().indexOf(searchInput) > -1)
|
||||
.map(enrichRule)
|
||||
|
||||
export let findRuleByDottedName = dottedName => dottedName &&
|
||||
rules.find(rule => rule.dottedName.toLowerCase() == dottedName.toLowerCase())
|
||||
|
||||
export let findGroup = R.pipe(
|
||||
findRuleByDottedName,
|
||||
found => found && found['une possibilité'] && found,
|
||||
// Is there a way to express this more litterally in ramda ?
|
||||
// R.unless(
|
||||
// R.isNil,
|
||||
// R.when(
|
||||
// R.has('une possibilité'),
|
||||
// R.identity
|
||||
// )
|
||||
// )
|
||||
)
|
||||
export let findRuleByDottedName = (allRules, dottedName) => dottedName &&
|
||||
allRules.find(rule => rule.dottedName.toLowerCase() == dottedName.toLowerCase())
|
||||
|
||||
/*********************************
|
||||
Autres */
|
||||
|
@ -130,12 +117,14 @@ export let getObjectives = analysedSituation => {
|
|||
let formuleType = R.path(["formule", "explanation", "name"])(
|
||||
analysedSituation
|
||||
)
|
||||
return formuleType == "somme"
|
||||
let result = formuleType == "somme"
|
||||
? R.pluck(
|
||||
"explanation",
|
||||
R.path(["formule", "explanation", "explanation"])(analysedSituation)
|
||||
)
|
||||
: formuleType ? [analysedSituation] : null
|
||||
|
||||
return R.reject(R.isNil)(result)
|
||||
}
|
||||
|
||||
|
||||
|
@ -159,16 +148,16 @@ export let collectMissingVariables = (groupMethod='groupByMissingVariable') => a
|
|||
|
||||
let isVariant = R.path(['formule', 'une possibilité'])
|
||||
|
||||
export let findVariantsAndRecords =
|
||||
export let deprecated_findVariantsAndRecords =
|
||||
({variantGroups, recordGroups}, dottedName, childDottedName) => {
|
||||
let child = findRuleByDottedName(dottedName),
|
||||
let child = findRuleByDottedName(rules, dottedName),
|
||||
parentDottedName = parentName(dottedName),
|
||||
parent = findRuleByDottedName(parentDottedName)
|
||||
parent = findRuleByDottedName(rules, parentDottedName)
|
||||
if (isVariant(parent)) {
|
||||
let grandParentDottedName = parentName(parentDottedName),
|
||||
grandParent = findRuleByDottedName(grandParentDottedName)
|
||||
grandParent = findRuleByDottedName(rules, grandParentDottedName)
|
||||
if (isVariant(grandParent))
|
||||
return findVariantsAndRecords({variantGroups, recordGroups}, parentDottedName, childDottedName || dottedName)
|
||||
return deprecated_findVariantsAndRecords({variantGroups, recordGroups}, parentDottedName, childDottedName || dottedName)
|
||||
else
|
||||
return {
|
||||
variantGroups: R.mergeWith(R.concat, variantGroups, {[parentDottedName]: [childDottedName || dottedName]}),
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import React from 'react'
|
||||
import {findRuleByDottedName, disambiguateRuleReference, findRuleByName} from './rules'
|
||||
import {rules, findRuleByDottedName, disambiguateRuleReference, findRuleByName} from './rules'
|
||||
import {evaluateVariable} from './variables'
|
||||
import R from 'ramda'
|
||||
import knownMecanisms from './known-mecanisms.yaml'
|
||||
import { Parser } from 'nearley'
|
||||
import Grammar from './grammar.ne'
|
||||
import {Node, Leaf} from './traverse-common-jsx'
|
||||
import {anyNull, val} from './traverse-common-functions'
|
||||
|
||||
import {mecanismOneOf,mecanismAllOf,mecanismNumericalLogic,mecanismSum,mecanismProduct,mecanismPercentage,mecanismScale,mecanismMax,mecanismError} from "./mecanisms"
|
||||
|
||||
let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart)
|
||||
|
||||
|
@ -20,12 +19,6 @@ let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart)
|
|||
*/
|
||||
|
||||
|
||||
let transformPercentage = s =>
|
||||
R.contains('%')(s) ?
|
||||
+s.replace('%', '') / 100
|
||||
: +s
|
||||
|
||||
|
||||
/*
|
||||
-> Notre règle est naturellement un AST (car notation préfixe dans le YAML)
|
||||
-> préliminaire : les expression infixes devront être parsées,
|
||||
|
@ -54,18 +47,19 @@ par exemple ainsi : https://github.com/Engelberg/instaparse#transforming-the-tre
|
|||
|
||||
*/
|
||||
|
||||
let fillVariableNode = (rule, situationGate) => (parseResult) => {
|
||||
let fillVariableNode = (rules, rule, situationGate) => (parseResult) => {
|
||||
let
|
||||
{fragments} = parseResult,
|
||||
variablePartialName = fragments.join(' . '),
|
||||
dottedName = disambiguateRuleReference(rule, variablePartialName),
|
||||
variable = findRuleByDottedName(dottedName),
|
||||
dottedName = disambiguateRuleReference(rules, rule, variablePartialName),
|
||||
variable = findRuleByDottedName(rules, dottedName),
|
||||
variableIsCalculable = variable.formule != null,
|
||||
//TODO perf : mettre un cache sur les variables !
|
||||
// On le fait pas pour l'instant car ça peut compliquer les fonctionnalités futures
|
||||
// et qu'il n'y a aucun problème de perf aujourd'hui
|
||||
parsedRule = variableIsCalculable && treatRuleRoot(
|
||||
situationGate,
|
||||
rules,
|
||||
variable
|
||||
),
|
||||
|
||||
|
@ -116,601 +110,169 @@ let buildNegatedVariable = variable => {
|
|||
}
|
||||
}
|
||||
|
||||
let treat = (situationGate, rule) => rawNode => {
|
||||
let reTreat = treat(situationGate, rule)
|
||||
let treat = (situationGate, rules, rule) => rawNode => {
|
||||
// inner functions
|
||||
let reTreat = treat(situationGate, rules, rule),
|
||||
treatString = rawNode => {
|
||||
/* On a à faire à un string, donc à une expression infixe.
|
||||
Elle sera traité avec le parser obtenu grâce à NearleyJs et notre grammaire.
|
||||
On obtient un objet de type Variable (avec potentiellement un 'modifier', par exemple temporel (TODO)), CalcExpression ou Comparison.
|
||||
Cet objet est alors rebalancé à 'treat'.
|
||||
*/
|
||||
|
||||
if (R.is(String)(rawNode)) {
|
||||
/* On a à faire à un string, donc à une expression infixe.
|
||||
Elle sera traité avec le parser obtenu grâce à NearleyJs et notre grammaire.
|
||||
On obtient un objet de type Variable (avec potentiellement un 'modifier', par exemple temporel (TODO)), CalcExpression ou Comparison.
|
||||
Cet objet est alors rebalancé à 'treat'.
|
||||
*/
|
||||
let [parseResult, ...additionnalResults] = nearley().feed(rawNode).results
|
||||
|
||||
let [parseResult, ...additionnalResults] = nearley().feed(rawNode).results
|
||||
if (additionnalResults && additionnalResults.length > 0)
|
||||
throw "Attention ! L'expression <" + rawNode + '> ne peut être traitée de façon univoque'
|
||||
|
||||
if (additionnalResults && additionnalResults.length > 0) throw "Attention ! L'expression <" + rawNode + '> ne peut être traitée de façon univoque'
|
||||
if (!R.contains(parseResult.category)(['variable', 'calcExpression', 'modifiedVariable', 'comparison', 'negatedVariable']))
|
||||
throw "Attention ! Erreur de traitement de l'expression : " + rawNode
|
||||
|
||||
if (!R.contains(parseResult.category)(['variable', 'calcExpression', 'modifiedVariable', 'comparison', 'negatedVariable']))
|
||||
throw "Attention ! Erreur de traitement de l'expression : " + rawNode
|
||||
if (parseResult.category == 'variable')
|
||||
return fillVariableNode(rules, rule, situationGate)(parseResult)
|
||||
if (parseResult.category == 'negatedVariable')
|
||||
return buildNegatedVariable(
|
||||
fillVariableNode(rules, rule, situationGate)(parseResult.variable)
|
||||
)
|
||||
|
||||
if (parseResult.category == 'variable')
|
||||
return fillVariableNode(rule, situationGate)(parseResult)
|
||||
if (parseResult.category == 'negatedVariable')
|
||||
return buildNegatedVariable(
|
||||
fillVariableNode(rule, situationGate)(parseResult.variable)
|
||||
)
|
||||
|
||||
if (parseResult.category == 'calcExpression') {
|
||||
let
|
||||
filledExplanation = parseResult.explanation.map(
|
||||
R.cond([
|
||||
[R.propEq('category', 'variable'), fillVariableNode(rule, situationGate)],
|
||||
[R.propEq('category', 'value'), node =>
|
||||
R.assoc('jsx', <span className="value">
|
||||
{node.nodeValue}
|
||||
</span>)(node)
|
||||
]
|
||||
])
|
||||
),
|
||||
[{nodeValue: value1}, {nodeValue: value2}] = filledExplanation,
|
||||
operatorFunctionName = {
|
||||
'*': 'multiply',
|
||||
'/': 'divide',
|
||||
'+': 'add',
|
||||
'-': 'subtract'
|
||||
}[parseResult.operator],
|
||||
operatorFunction = R[operatorFunctionName],
|
||||
nodeValue = value1 == null || value2 == null ?
|
||||
null
|
||||
: operatorFunction(value1, value2)
|
||||
|
||||
return {
|
||||
text: rawNode,
|
||||
nodeValue,
|
||||
category: 'calcExpression',
|
||||
type: 'numeric',
|
||||
explanation: filledExplanation,
|
||||
jsx: <Node
|
||||
classes="inlineExpression calcExpression"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<span className="nodeContent">
|
||||
{filledExplanation[0].jsx}
|
||||
<span className="operator">{parseResult.operator}</span>
|
||||
{filledExplanation[1].jsx}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
if (parseResult.category == 'comparison') {
|
||||
//TODO mutualise code for 'comparison' & 'calclExpression'. Harmonise their names
|
||||
let
|
||||
filledExplanation = parseResult.explanation.map(
|
||||
R.cond([
|
||||
[R.propEq('category', 'variable'), fillVariableNode(rule, situationGate)],
|
||||
[R.propEq('category', 'value'), node =>
|
||||
R.assoc('jsx', <span className="value">
|
||||
{node.nodeValue}
|
||||
</span>)(node)
|
||||
]
|
||||
])
|
||||
),
|
||||
[{nodeValue: value1}, {nodeValue: value2}] = filledExplanation,
|
||||
comparatorFunctionName = {
|
||||
'<': 'lt',
|
||||
'<=': 'lte',
|
||||
'>': 'gt',
|
||||
'>=': 'gte'
|
||||
//TODO '='
|
||||
}[parseResult.operator],
|
||||
comparatorFunction = R[comparatorFunctionName],
|
||||
nodeValue = value1 == null || value2 == null ?
|
||||
null
|
||||
: comparatorFunction(value1, value2)
|
||||
|
||||
return {
|
||||
text: rawNode,
|
||||
nodeValue: nodeValue,
|
||||
category: 'comparison',
|
||||
type: 'boolean',
|
||||
explanation: filledExplanation,
|
||||
jsx: <Node
|
||||
classes="inlineExpression comparison"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<span className="nodeContent">
|
||||
{filledExplanation[0].jsx}
|
||||
<span className="operator">{parseResult.operator}</span>
|
||||
{filledExplanation[1].jsx}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO C'est pas bien ça. Devrait être traité par le parser plus haut !
|
||||
if (R.is(Number)(rawNode)) {
|
||||
return {
|
||||
category: 'number',
|
||||
nodeValue: rawNode,
|
||||
type: 'numeric',
|
||||
jsx:
|
||||
<span className="number">
|
||||
{rawNode}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!R.is(Object)(rawNode)) {
|
||||
console.log() // eslint-disable-line no-console
|
||||
throw 'Cette donnée : ' + rawNode + ' doit être un Number, String ou Object'
|
||||
}
|
||||
|
||||
let mecanisms = R.intersection(R.keys(rawNode), R.keys(knownMecanisms))
|
||||
|
||||
if (mecanisms.length != 1) {
|
||||
console.log('Erreur : On ne devrait reconnaître que un et un seul mécanisme dans cet objet', rawNode)
|
||||
throw 'OUPS !'
|
||||
}
|
||||
|
||||
let k = R.head(mecanisms),
|
||||
v = rawNode[k]
|
||||
|
||||
if (k === 'une de ces conditions') {
|
||||
let result = R.pipe(
|
||||
R.unless(R.is(Array), () => {throw 'should be array'}),
|
||||
R.reduce( (memo, next) => {
|
||||
let {nodeValue, explanation} = memo,
|
||||
child = reTreat(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
|
||||
if (parseResult.category == 'calcExpression') {
|
||||
let
|
||||
filledExplanation = parseResult.explanation.map(
|
||||
R.cond([
|
||||
[R.propEq('category', 'variable'), fillVariableNode(rules, rule, situationGate)],
|
||||
[R.propEq('category', 'value'), node =>
|
||||
R.assoc('jsx', <span className="value">
|
||||
{node.nodeValue}
|
||||
</span>)(node)
|
||||
]
|
||||
])
|
||||
),
|
||||
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: <Node
|
||||
classes="mecanism conditions list"
|
||||
name={result.name}
|
||||
value={result.nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{result.explanation.map(item => <li key={item.name}>{item.jsx}</li>)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
if (k === 'toutes ces conditions') {
|
||||
return R.pipe(
|
||||
R.unless(R.is(Array), () => {throw 'should be array'}),
|
||||
R.reduce( (memo, next) => {
|
||||
let {nodeValue, explanation} = memo,
|
||||
child = reTreat(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)
|
||||
}
|
||||
[{nodeValue: value1}, {nodeValue: value2}] = filledExplanation,
|
||||
operatorFunctionName = {
|
||||
'*': 'multiply',
|
||||
'/': 'divide',
|
||||
'+': 'add',
|
||||
'-': 'subtract'
|
||||
}[parseResult.operator],
|
||||
operatorFunction = R[operatorFunctionName],
|
||||
nodeValue = value1 == null || value2 == null ?
|
||||
null
|
||||
: operatorFunction(value1, value2)
|
||||
|
||||
//TODO perf: declare this closure somewhere else ?
|
||||
let treatNumericalLogicRec =
|
||||
R.ifElse(
|
||||
R.is(String),
|
||||
rate => ({ //TODO unifier ce code
|
||||
nodeValue: transformPercentage(rate),
|
||||
type: 'numeric',
|
||||
category: 'percentage',
|
||||
percentage: rate,
|
||||
explanation: null,
|
||||
jsx:
|
||||
<span className="percentage" >
|
||||
<span className="name">{rate}</span>
|
||||
</span>
|
||||
}),
|
||||
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 = reTreat(condition), // can be a 'comparison', a 'variable', TODO a 'negation'
|
||||
childNumericalLogic = treatNumericalLogicRec(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: <div className="condition">
|
||||
{conditionNode.jsx}
|
||||
<div>
|
||||
{childNumericalLogic.jsx}
|
||||
</div>
|
||||
</div>
|
||||
}],
|
||||
}
|
||||
}, {
|
||||
nodeValue: false,
|
||||
category: 'mecanism',
|
||||
name: "logique numérique",
|
||||
type: 'boolean || numeric', // lol !
|
||||
explanation: []
|
||||
}),
|
||||
node => ({...node,
|
||||
jsx: <Node
|
||||
classes="mecanism numericalLogic list"
|
||||
name="logique numérique"
|
||||
value={node.nodeValue}
|
||||
return {
|
||||
text: rawNode,
|
||||
nodeValue,
|
||||
category: 'calcExpression',
|
||||
type: 'numeric',
|
||||
explanation: filledExplanation,
|
||||
jsx: <Node
|
||||
classes="inlineExpression calcExpression"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{node.explanation.map(item => <li key={item.name}>{item.jsx}</li>)}
|
||||
</ul>
|
||||
<span className="nodeContent">
|
||||
{filledExplanation[0].jsx}
|
||||
<span className="operator">{parseResult.operator}</span>
|
||||
{filledExplanation[1].jsx}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
})
|
||||
))
|
||||
}
|
||||
}
|
||||
if (parseResult.category == 'comparison') {
|
||||
//TODO mutualise code for 'comparison' & 'calclExpression'. Harmonise their names
|
||||
let
|
||||
filledExplanation = parseResult.explanation.map(
|
||||
R.cond([
|
||||
[R.propEq('category', 'variable'), fillVariableNode(rules, rule, situationGate)],
|
||||
[R.propEq('category', 'value'), node =>
|
||||
R.assoc('jsx', <span className="value">
|
||||
{node.nodeValue}
|
||||
</span>)(node)
|
||||
]
|
||||
])
|
||||
),
|
||||
[{nodeValue: value1}, {nodeValue: value2}] = filledExplanation,
|
||||
comparatorFunctionName = {
|
||||
'<': 'lt',
|
||||
'<=': 'lte',
|
||||
'>': 'gt',
|
||||
'>=': 'gte'
|
||||
//TODO '='
|
||||
}[parseResult.operator],
|
||||
comparatorFunction = R[comparatorFunctionName],
|
||||
nodeValue = value1 == null || value2 == null ?
|
||||
null
|
||||
: comparatorFunction(value1, value2)
|
||||
|
||||
if (k === 'logique numérique') {
|
||||
return treatNumericalLogicRec(v)
|
||||
}
|
||||
|
||||
if (k === 'taux') {
|
||||
let reg = /^(\d+(\.\d+)?)\%$/
|
||||
if (R.test(reg)(v))
|
||||
return {
|
||||
text: rawNode,
|
||||
nodeValue: nodeValue,
|
||||
category: 'comparison',
|
||||
type: 'boolean',
|
||||
explanation: filledExplanation,
|
||||
jsx: <Node
|
||||
classes="inlineExpression comparison"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<span className="nodeContent">
|
||||
{filledExplanation[0].jsx}
|
||||
<span className="operator">{parseResult.operator}</span>
|
||||
{filledExplanation[1].jsx}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
},
|
||||
treatNumber = rawNode => {
|
||||
return {
|
||||
category: 'percentage',
|
||||
text: ""+rawNode,
|
||||
category: 'number',
|
||||
nodeValue: rawNode,
|
||||
type: 'numeric',
|
||||
percentage: v,
|
||||
nodeValue: R.match(reg)(v)[1]/100,
|
||||
explanation: null,
|
||||
jsx:
|
||||
<span className="percentage" >
|
||||
<span className="name">{v}</span>
|
||||
<span className="number">
|
||||
{rawNode}
|
||||
</span>
|
||||
}
|
||||
// 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:
|
||||
<span className="percentage" >
|
||||
<span className="name">{lazySelection}</span>
|
||||
</span>
|
||||
},
|
||||
treatOther = rawNode => {
|
||||
console.log() // eslint-disable-line no-console
|
||||
throw 'Cette donnée : ' + rawNode + ' doit être un Number, String ou Object'
|
||||
},
|
||||
treatObject = rawNode => {
|
||||
let mecanisms = R.intersection(R.keys(rawNode), R.keys(knownMecanisms))
|
||||
|
||||
if (mecanisms.length != 1) {
|
||||
console.log('Erreur : On ne devrait reconnaître que un et un seul mécanisme dans cet objet', rawNode)
|
||||
throw 'OUPS !'
|
||||
}
|
||||
}
|
||||
else {
|
||||
let node = reTreat(v)
|
||||
return {
|
||||
type: 'numeric',
|
||||
category: 'percentage',
|
||||
percentage: node.nodeValue,
|
||||
nodeValue: node.nodeValue,
|
||||
explanation: node,
|
||||
jsx: node.jsx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Une simple somme de variables
|
||||
if (k === 'somme') {
|
||||
let
|
||||
summedVariables = v.map(reTreat),
|
||||
nodeValue = summedVariables.reduce(
|
||||
(memo, {nodeValue: nextNodeValue}) => memo == null ? null : nextNodeValue == null ? null : memo + +nextNodeValue,
|
||||
0)
|
||||
let k = R.head(mecanisms),
|
||||
v = rawNode[k]
|
||||
|
||||
return {
|
||||
nodeValue,
|
||||
category: 'mecanism',
|
||||
name: 'somme',
|
||||
type: 'numeric',
|
||||
explanation: summedVariables,
|
||||
jsx: <Node
|
||||
classes="mecanism somme"
|
||||
name="somme"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{summedVariables.map(v => <li key={v.name}>{v.jsx}</li>)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
let dispatch = {
|
||||
'une de ces conditions': mecanismOneOf,
|
||||
'toutes ces conditions': mecanismAllOf,
|
||||
'logique numérique': mecanismNumericalLogic,
|
||||
'taux': mecanismPercentage,
|
||||
'somme': mecanismSum,
|
||||
'multiplication': mecanismProduct,
|
||||
'barème': mecanismScale,
|
||||
'le maximum de': mecanismMax,
|
||||
},
|
||||
action = R.pathOr(mecanismError,[k],dispatch)
|
||||
|
||||
if (k === 'multiplication') {
|
||||
let
|
||||
mult = (base, rate, facteur, plafond) =>
|
||||
Math.min(base, plafond) * rate * facteur,
|
||||
constantNode = constant => ({nodeValue: constant}),
|
||||
assiette = reTreat(v['assiette']),
|
||||
//TODO parser le taux dans le parser ?
|
||||
taux = v['taux'] ? reTreat({taux: v['taux']}) : constantNode(1),
|
||||
facteur = v['facteur'] ? reTreat(v['facteur']) : constantNode(1),
|
||||
plafond = v['plafond'] ? reTreat(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: <Node
|
||||
classes="mecanism multiplication"
|
||||
name="multiplication"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul className="properties">
|
||||
<li key="assiette">
|
||||
<span className="key">assiette: </span>
|
||||
<span className="value">{assiette.jsx}</span>
|
||||
</li>
|
||||
{taux.nodeValue != 1 &&
|
||||
<li key="taux">
|
||||
<span className="key">taux: </span>
|
||||
<span className="value">{taux.jsx}</span>
|
||||
</li>}
|
||||
{facteur.nodeValue != 1 &&
|
||||
<li key="facteur">
|
||||
<span className="key">facteur: </span>
|
||||
<span className="value">{facteur.jsx}</span>
|
||||
</li>}
|
||||
{plafond.nodeValue != Infinity &&
|
||||
<li key="plafond">
|
||||
<span className="key">plafond: </span>
|
||||
<span className="value">{plafond.jsx}</span>
|
||||
</li>}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
if (k === 'barème') {
|
||||
// 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
|
||||
let
|
||||
baremeProps = R.dissoc('composantes')(v),
|
||||
composantes = v.composantes.map(c =>
|
||||
({
|
||||
... reTreat(
|
||||
{
|
||||
barème: {
|
||||
... baremeProps,
|
||||
... 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: <Node
|
||||
classes="mecanism composantes"
|
||||
name="composantes"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{ composantes.map((c, i) =>
|
||||
[<li className="composante" key={JSON.stringify(c.composante)}>
|
||||
<ul className="composanteAttributes">
|
||||
{R.toPairs(c.composante).map(([k,v]) =>
|
||||
<li>
|
||||
<span>{k}: </span>
|
||||
<span>{v}</span>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
<div className="content">
|
||||
{c.jsx}
|
||||
</div>
|
||||
</li>,
|
||||
i < (composantes.length - 1) && <li className="composantesSymbol"><i className="fa fa-plus-circle" aria-hidden="true"></i></li>
|
||||
]
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
return action(reTreat,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 = reTreat(v['assiette']),
|
||||
multiplicateur = reTreat(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: <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">{assiette.jsx}</span>
|
||||
</li>
|
||||
<li key="multiplicateur">
|
||||
<span className="key">multiplicateur des tranches: </span>
|
||||
<span className="value">{multiplicateur.jsx}</span>
|
||||
</li>
|
||||
<table className="tranches">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tranches de l'assiette</th>
|
||||
<th>Taux</th>
|
||||
</tr>
|
||||
{v['tranches'].map(({'en-dessous de': maxOnly, 'au-dessus de': minOnly, de: min, 'à': max, taux}) =>
|
||||
<tr key={min || minOnly}>
|
||||
<td>
|
||||
{ maxOnly ? 'En dessous de ' + maxOnly
|
||||
: minOnly ? 'Au dessus de ' + minOnly
|
||||
: `De ${min} à ${max}` }
|
||||
</td>
|
||||
<td> {taux} </td>
|
||||
</tr>
|
||||
)}
|
||||
</thead>
|
||||
</table>
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
if (k === 'le maximum de') {
|
||||
let contenders = v.map(treat(situationGate, rule)),
|
||||
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: <Node
|
||||
classes="mecanism list maximum"
|
||||
name="le maximum de"
|
||||
value={nodeValue}
|
||||
child={
|
||||
<ul>
|
||||
{contenders.map((item, i) =>
|
||||
<li key={i}>
|
||||
<div className="description">{v[i].description}</div>
|
||||
{item.jsx}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
throw "Le mécanisme est inconnu !"
|
||||
|
||||
let onNodeType = R.cond([
|
||||
[R.is(String), treatString],
|
||||
[R.is(Number), treatNumber],
|
||||
[R.is(Object), treatObject],
|
||||
[R.T, treatOther]
|
||||
])
|
||||
return onNodeType(rawNode)
|
||||
}
|
||||
|
||||
//TODO c'est moche :
|
||||
|
@ -725,7 +287,7 @@ export let computeRuleValue = (formuleValue, condValue) =>
|
|||
? 0
|
||||
: formuleValue
|
||||
|
||||
let treatRuleRoot = (situationGate, rule) => R.pipe(
|
||||
export let treatRuleRoot = (situationGate, rules, rule) => R.pipe(
|
||||
R.evolve({ // -> Voilà les attributs que peut comporter, pour l'instant, une Variable.
|
||||
|
||||
// 'meta': pas de traitement pour l'instant
|
||||
|
@ -733,7 +295,7 @@ let treatRuleRoot = (situationGate, rule) => R.pipe(
|
|||
// 'cond' : Conditions d'applicabilité de la règle
|
||||
'non applicable si': value => {
|
||||
let
|
||||
child = treat(situationGate, rule)(value),
|
||||
child = treat(situationGate, rules, rule)(value),
|
||||
nodeValue = child.nodeValue
|
||||
|
||||
return {
|
||||
|
@ -762,7 +324,7 @@ let treatRuleRoot = (situationGate, rule) => R.pipe(
|
|||
// [n'importe quel mécanisme numérique] : multiplication || barème en taux marginaux || le maximum de || le minimum de || ...
|
||||
'formule': value => {
|
||||
let
|
||||
child = treat(situationGate, rule)(value),
|
||||
child = treat(situationGate, rules, rule)(value),
|
||||
nodeValue = child.nodeValue
|
||||
return {
|
||||
category: 'ruleProp',
|
||||
|
@ -812,23 +374,13 @@ let treatRuleRoot = (situationGate, rule) => R.pipe(
|
|||
- if not, do they have a computed value or are they non applicable ?
|
||||
*/
|
||||
|
||||
export let analyseSituation = rootVariable => situationGate =>
|
||||
export let analyseSituation = (rules, rootVariable) => situationGate =>
|
||||
treatRuleRoot(
|
||||
situationGate,
|
||||
findRuleByName(rootVariable)
|
||||
rules,
|
||||
findRuleByName(rules, rootVariable)
|
||||
)
|
||||
|
||||
export let variableType = name => {
|
||||
if (name == null) return null
|
||||
|
||||
let found = findRuleByName(name)
|
||||
|
||||
// tellement peu de variables pour l'instant
|
||||
// que c'est très simpliste
|
||||
if (!found) return 'boolean'
|
||||
let {rule} = found
|
||||
if (typeof rule.formule['somme'] !== 'undefined') return 'numeric'
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import R from 'ramda'
|
||||
import {parentName, nameLeaf, findRuleByDottedName, splitName, joinName} from './rules'
|
||||
import {splitName, joinName} from './rules'
|
||||
|
||||
|
||||
let evaluateBottomUp = situationGate => startingFragments => {
|
||||
|
|
|
@ -7,8 +7,6 @@ import { euro, months } from './components/conversation/formValueTypes.js'
|
|||
import { EXPLAIN_VARIABLE, POINT_OUT_OBJECTIVES} from './actions'
|
||||
import R from 'ramda'
|
||||
|
||||
import {findGroup, findRuleByDottedName, parentName, findVariantsAndRecords} from './engine/rules'
|
||||
|
||||
import {reduceSteps, generateGridQuestions, generateSimpleQuestions} from './engine/generateQuestions'
|
||||
|
||||
import computeThemeColours from './components/themeColours'
|
||||
|
|
Loading…
Reference in New Issue