Déplacement de la génération du JSX dans traverse.js

pull/6/head
Mael Thomas 2017-03-14 11:42:44 +01:00
parent b8b327a9bf
commit 124bfdd1cc
9 changed files with 500 additions and 93 deletions

293
drawing.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -22,7 +22,6 @@
"ramda": "^0.23.0",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"react-hot-loader": "3.0.0-beta.2",
"react-json-tree": "^0.10.0",
"react-redux": "^5.0.2",
"react-router": "^3.0.2",
@ -55,6 +54,7 @@
"json-loader": "^0.5.4",
"nearley-loader": "0.0.2",
"postcss-loader": "^1.2.2",
"react-hot-loader": "^3.0.0-beta.6",
"redux-devtools": "^3.2.0",
"redux-devtools-dock-monitor": "^1.1.1",
"redux-devtools-log-monitor": "^1.0.9",

View File

@ -13,17 +13,6 @@
- motif . jeune vacances
- contrat aidé
# Données de test
# non applicable si:
# l'une de ces conditions:
# - A
# - toutes ces conditions:
# - B
# - C
# - l'une de ces conditions:
# - X
# - Z
formule:
multiplication:
assiette: salaire de base

View File

@ -13,9 +13,13 @@ rules:
no-global-assign: 0
no-unsafe-negation: 0
no-undef: 1
react/jsx-uses-vars: 2
react/jsx-uses-react: 2
parser: babel-eslint
plugins:
- react
env:
browser: true
commonjs: true

View File

@ -121,7 +121,7 @@
background: #407ee7;
}
.multiplicationSign {
.operator {
margin: .1em .6em;
font-size: 150%;
vertical-align: sub;

View File

@ -7,22 +7,11 @@ import R from 'ramda'
import PageTypeIcon from './PageTypeIcon'
import {connect} from 'react-redux'
import {formValueSelector} from 'redux-form'
import mockSituation from '../engine/mockSituation.yaml'
// situationGate function useful for testing :
let testingSituationGate = v =>
R.path(v.split('.'))({
'Salariat ': {
' CDD ': {
' événements': '_',
' motif': 'saisonnier',
' engagement employeur complément formation': 'non',
' durée contrat': '2',
},
' contrat aidé': 'non',
' salaire de base': '1481',
' congés non pris': '3',
},
})
let testingSituationGate = v => // eslint-disable-line no-unused-vars
R.path(v.split('.'))(mockSituation)
@connect(state => ({
situationGate: name => formValueSelector('conversation')(state, name)
@ -68,16 +57,17 @@ export default class Rule extends Component {
</section>
<section id="rule-rules">
{ do {
let cond =
R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond')
cond != null && <section id="declenchement">
<h2>Conditions de déclenchement</h2>
<RuleProp {...cond[1]} />
</section>
let [,cond] =
R.toPairs(rule).find(([,v]) => v.rulePropType == 'cond') || []
cond != null &&
<section id="declenchement">
<h2>Conditions de déclenchement</h2>
{cond.jsx}
</section>
}}
<section id="formule">
<h2>Calcul</h2>
<RuleProp {...rule['formule']}/>
{rule['formule'].jsx}
</section>
</section>
@ -159,9 +149,9 @@ let Multiplication = ({base, rate}) =>
</div>
let Variable = (yo) => do {let {nodeValue, variableName} = yo;
console.log('yo', yo);
<span className="variable" >
let Variable = (yo) => do {
let {nodeValue, variableName} = yo
;<span className="variable" >
<span className="name">{variableName}</span>
<NodeValue data={nodeValue}/>
</span>
@ -186,38 +176,25 @@ let Percentage = ({percentage}) =>
let NodeValue = ({data}) => do {
let valeur = data == null ?
'?'
: ( R.is(Number)(data) ?
Math.round(data)
: ( data ? 'oui' : 'non')
);
<span className={"value " + valeur}>&nbsp;
{valeur}
</span>
}
let Formula = ({explanation, nodeValue}) => do {
<div className="form node" >
<div>
<span className="name">{expression}</span>
<NodeValue data={nodeValue}/>
</div>
</div>
}
// let Formula = ({explanation, nodeValue}) => do {
// <div className="form node" >
// <div>
// <span className="name">{expression}</span>
// <NodeValue data={nodeValue}/>
// </div>
// </div>
// }
let JSONView = ({o, rootKey}) => (
<div className="json">
<JSONTree
getItemString={() => ''}
theme={theme}
hideRoot={true}
shouldExpandNode={() => true}
data={rootKey ? {[rootKey]: o} : o}
/>
</div>
<div className="json">
<JSONTree
getItemString={() => ''}
theme={theme}
hideRoot={true}
shouldExpandNode={() => true}
data={rootKey ? {[rootKey]: o} : o}
/>
</div>
)

View File

@ -0,0 +1,10 @@
"Salariat ":
" CDD ":
" événements": "_"
" motif": "saisonnier"
" engagement employeur complément formation": "non"
" durée contrat": "2"
" contrat aidé": "non"
" salaire de base": "1481"
" congés non pris": "3"

View File

@ -0,0 +1,15 @@
import React from 'react'
import R from 'ramda'
export let NodeValue = ({data}) => do {
let valeur = data == null ?
'?'
: ( R.is(Number)(data) ?
Math.round(data)
: ( data ? 'oui' : 'non')
)
;<span className={"value " + valeur}>&nbsp;
{valeur}
</span>
}

View File

@ -1,10 +1,14 @@
import {rules, findRuleByName, parentName} from './rules'
import React from 'react'
import {rules, findRuleByName} from './rules'
import {completeVariableName, evaluateVariable, knownVariable} from './expressions'
import R from 'ramda'
import knownMecanisms from './known-mecanisms.yaml'
import { Parser } from 'nearley'
import Grammar from './grammar.ne'
import variablesInDevelopment from './variablesInDevelopment.yaml'
import {NodeValue} from './traverse-common-jsx'
let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart)
@ -71,7 +75,12 @@ let fillVariableNode = (rule, situationGate) => (parseResult) => {
variableName,
type: 'boolean | numeric',
explanation: null,
missingVariables: known ? [] : [variableName]
missingVariables: known ? [] : [variableName],
jsx:
<span className="variable" >
<span className="name">{variableName}</span>
<NodeValue data={nodeValue}/>
</span>
}
}
@ -99,7 +108,14 @@ let treat = (situationGate, rule) => rawNode => {
if (parseResult.category == 'calcExpression') {
let
filledExplanation = parseResult.explanation.map(
R.when(R.propEq('category', 'variable'), fillVariableNode(rule, situationGate))
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 = {
@ -115,17 +131,34 @@ let treat = (situationGate, rule) => rawNode => {
return {
text: rawNode,
nodeValue: nodeValue,
nodeValue,
category: 'calcExpression',
type: 'numeric',
explanation: filledExplanation
explanation: filledExplanation,
jsx:
<div className="mecanism node" >
<div>
<span className="name">Éxpression de calcul</span>
<NodeValue data={nodeValue}/>
</div>
{filledExplanation[0].jsx}
<span className="operator">{parseResult.operator}</span>
{filledExplanation[1].jsx}
</div>
}
}
if (parseResult.category == 'comparison') {
//TODO mutualise code for 'comparison' & 'calclExpression'. Harmonise their names
let
filledExplanation = parseResult.explanation.map(
R.when(R.propEq('category', 'variable'), fillVariableNode(rule, situationGate))
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 = {
@ -140,13 +173,18 @@ let treat = (situationGate, rule) => rawNode => {
null
: comparatorFunction(value1, value2)
return {
text: rawNode,
nodeValue: nodeValue,
category: 'comparison',
type: 'boolean',
explanation: filledExplanation
explanation: filledExplanation,
jsx:
<div className="comparison node" >
{filledExplanation[0].jsx}
<span className="operator">{parseResult.operator}</span>
{filledExplanation[1].jsx}
</div>
}
}
}
@ -156,13 +194,17 @@ let treat = (situationGate, rule) => rawNode => {
return {
category: 'number',
nodeValue: rawNode,
type: 'numeric'
type: 'numeric',
jsx:
<span className="number">
{rawNode}
</span>
}
}
if (!R.is(Object)(rawNode)) {
console.log('Cette donnée : ', rawNode)
console.log('Cette donnée : ', rawNode) // eslint-disable-line no-console
throw ' doit être un Number, String ou Object'
}
@ -172,7 +214,7 @@ let treat = (situationGate, rule) => rawNode => {
v = rawNode[k]
if (k === "l'une de ces conditions") {
return R.pipe(
let result = R.pipe(
R.unless(R.is(Array), () => {throw 'should be array'}),
R.reduce( (memo, next) => {
let {nodeValue, explanation} = memo,
@ -193,6 +235,18 @@ let treat = (situationGate, rule) => rawNode => {
explanation: []
}) // Reduce but don't use R.reduced to set the nodeValue : we need to treat all the nodes
)(v)
return {...result,
jsx:
<div className="mecanism node" >
<div>
<span className="name">{result.name}</span>
<NodeValue data={result.nodeValue}/>
</div>
<ul>
{result.explanation.map(item => <li>{item.jsx}</li>)}
</ul>
</div>
}
}
if (k === 'toutes ces conditions') {
return R.pipe(
@ -220,12 +274,16 @@ let treat = (situationGate, rule) => rawNode => {
let treatNumericalLogicRec =
R.ifElse(
R.is(String),
rate => ({
rate => ({ //TODO unifier ce code
nodeValue: transformPercentage(rate),
type: 'numeric',
category: 'percentage',
percentage: rate,
explanation: null
explanation: null,
jsx:
<span className="rate" >
<span className="name">{rate}</span>
</span>
}),
R.pipe(
R.unless(
@ -261,7 +319,13 @@ let treat = (situationGate, rule) => rawNode => {
condition: conditionNode,
conditionValue: conditionNode.nodeValue,
type: 'boolean',
explanation: childNumericalLogic
explanation: childNumericalLogic,
jsx: <span className="condition">
{conditionNode.jsx}
<span>
---> {childNumericalLogic.jsx}
</span>
</span>
}],
}
}, {
@ -270,6 +334,18 @@ let treat = (situationGate, rule) => rawNode => {
name: "logique numérique",
type: 'boolean || numeric', // lol !
explanation: []
}),
node => ({...node,
jsx:
<div className="mecanism node" >
<div>
<span className="name">logique numérique</span>
<NodeValue data={node.nodeValue}/>
</div>
<ul>
{node.explanation.map(item => <li>{item.jsx}</li>)}
</ul>
</div>
})
))
@ -281,11 +357,15 @@ let treat = (situationGate, rule) => rawNode => {
//TODO gérer les taux historisés
if (R.is(String)(v))
return {
type: 'numeric',
category: 'percentage',
type: 'numeric',
percentage: v,
nodeValue: transformPercentage(v),
explanation: null
explanation: null,
jsx:
<span className="rate" >
<span className="name">{v}</span>
</span>
}
else {
let node = reTreat(v)
@ -294,7 +374,8 @@ let treat = (situationGate, rule) => rawNode => {
category: 'percentage',
percentage: node.nodeValue,
nodeValue: node.nodeValue,
explanation: node
explanation: node,
jsx: node.jsx
}
}
}
@ -322,7 +403,14 @@ let treat = (situationGate, rule) => rawNode => {
facteur
//TODO limit: 'plafond'
//TODO introduire 'prorata' ou 'multiplicateur', pour sémantiser les opérandes ?
}
},
jsx:
<div className="multiplication node" >
{base.jsx}
<span className="multiplicationSign">×</span>
{rate && rate.jsx}
{facteur && facteur.jsx}
</div>
}
}
@ -330,14 +418,25 @@ let treat = (situationGate, rule) => rawNode => {
let contenders = v.map(treat(situationGate, rule)),
contenderValues = R.pluck('nodeValue')(contenders),
stopEverything = R.contains(null, contenderValues),
maxValue = R.max(...contenderValues)
maxValue = R.max(...contenderValues),
nodeValue = stopEverything ? null : maxValue
return {
type: 'numeric',
category: 'mecanism',
name: 'le maximum de',
nodeValue: stopEverything ? null : maxValue,
explanation: contenders
nodeValue,
explanation: contenders,
jsx:
<div className="mecanism node" >
<div>
<span className="name">le maximum de</span>
<NodeValue data={nodeValue}/>
</div>
<ul>
{contenders.map(item => <li>{item.jsx}</li>)}
</ul>
</div>
}
}
@ -351,14 +450,24 @@ let treatRuleRoot = (situationGate, rule) => R.evolve({ // -> Voilà les attribu
// 'cond' : Conditions d'applicabilité de la règle
'non applicable si': value => {
let child = treat(situationGate, rule)(value)
let
child = treat(situationGate, rule)(value),
nodeValue = child.nodeValue
return {
category: 'ruleProp',
rulePropType: 'cond',
name: 'non applicable si',
type: 'boolean',
nodeValue: child.nodeValue,
explanation: child
explanation: child,
jsx:
<div className="ruleProp node" >
<div>
<span className="name">non applicable si</span>
<NodeValue data={nodeValue}/>
</div>
{ child.jsx }
</div>
}
}
,
@ -368,15 +477,25 @@ let treatRuleRoot = (situationGate, rule) => R.evolve({ // -> Voilà les attribu
// note: pour certaines variables booléennes, ex. appartenance à régime Alsace-Moselle, la formule et le non applicable si se rejoignent
// [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)
let
child = treat(situationGate, rule)(value),
nodeValue = child.nodeValue
return {
category: 'ruleProp',
rulePropType: 'formula',
name: 'formule',
type: 'numeric',
nodeValue: child.nodeValue,
nodeValue: nodeValue,
explanation: child,
shortCircuit: R.pathEq(['non applicable si', 'nodeValue'], true)
shortCircuit: R.pathEq(['non applicable si', 'nodeValue'], true),
jsx:
<div className="ruleProp node" >
<div>
<span className="name">formula</span>
<NodeValue data={nodeValue}/>
</div>
{ child.jsx }
</div>
}
}
,