[moteur] conciliation des mécanismes existants avec le nouveau parsing d'expressions

pull/6/head
Mael Thomas 2017-03-08 17:49:22 +01:00
parent 1a9d112eca
commit 376e72fc47
8 changed files with 159 additions and 113 deletions

View File

@ -1,4 +1,4 @@
- Cotisation: Indemnité compensatrice congés payés simplifiée
- Cotisation: simplifiée
attache: Salariat . CDD
non applicable si:
l'une de ces conditions:
@ -14,7 +14,7 @@
assiette: salaire de base
taux: 10%
# prorata: congés non pris / 25
prorata: 0.12 # 3/25
# prorata: 0.12 # 3/25
#
# - description: Méthode "maintien du salaire"
# note: Cette méthode sera le plus souvent favorable au salarié lorsque celui-ci a bénéficié dune augmentation de salaire.
@ -27,7 +27,7 @@
# # Comment ?
# # mensuel / nombre moyen de jours ouvrés par an
- 789
- 80
notes: |

View File

@ -12,7 +12,7 @@ rules:
no-console: 1
no-global-assign: 0
no-unsafe-negation: 0
no-undef: 0
no-undef: 1
parser: babel-eslint

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'
import React, {Component} from 'react'
import './CDD.css'
import IntroCDD from './IntroCDD'
import Results from './Results'
@ -8,43 +8,42 @@ import './conversation/conversation.css'
import {START_CONVERSATION} from '../actions'
import Aide from './Aide'
let situationSelector = formValueSelector('conversation')
@reduxForm(
{form: 'conversation', destroyOnUnmount: false}
)
@connect(state => ({
@reduxForm({form: 'conversation', destroyOnUnmount: false})
@connect(
state => ({
situation: variableName => situationSelector(state, variableName),
steps: state.steps,
themeColours: state.themeColours,
analysedSituation: state.analysedSituation
}), dispatch => ({
startConversation: () => dispatch({type: START_CONVERSATION})
}))
analysedSituation: state.analysedSituation,
}),
dispatch => ({
startConversation: () => dispatch({type: START_CONVERSATION}),
}),
)
export default class CDD extends Component {
componentDidMount() {
this.props.startConversation()
}
render() {
let {steps} = this.props
let conversation = steps.map(step =>
<step.component key={step.name} {...step}/>
)
let conversation = steps.map(step => (
<step.component key={step.name} {...step} />
))
return (
<div id="sim">
<IntroCDD />
<div id="conversation">
<section id="questions-answers">
{conversation}
</section>
<Aide />
</div>
<Results {...this.props}/>
</div>
<div id="sim">
<IntroCDD />
<div id="conversation">
<section id="questions-answers">
{conversation}
</section>
<Aide />
</div>
<Results {...this.props} />
</div>
)
}
}

View File

@ -85,14 +85,12 @@
border: 1px solid black;
background: #d5911a
}
.expression {
.mecanism li {
margin-bottom: .6em;
}
.expression > div > .name {
padding: 0 1em;
border: 1px solid black;
background: #6666ea;
}
#rule-rules .value {
padding-left: 1em;
font-weight: bold;
@ -111,6 +109,12 @@
background: #6666ea;
}
.comparison .name {
padding: 0 1em;
border: 1px solid black;
background: #407ee7;
}
.rate .name {
padding: 0 1em;
border: 1px solid black;
@ -123,3 +127,7 @@
vertical-align: sub;
display: block;
}
.json {
font-size: 60%;
}

View File

@ -14,9 +14,9 @@ let testingSituationGate = v => R.path(v.split('.'))(
"Salariat ":{
" CDD ":{
" événements": "_",
" motif":"usage",
" motif":"saisonnier",
" engagement employeur complément formation":"non",
" durée contrat":"2"
" durée contrat": 2
},
" contrat aidé":"non",
" salaire de base": 1481,
@ -118,9 +118,6 @@ let RuleProp = ({nodeValue, explanation, name}) =>
{
explanation.category == 'mecanism' && <Mecanism {...explanation}/>
}
{
explanation.category == 'expression' && <Expression {...explanation}/>
}
</div>
let Mecanism = ({nodeValue, name, explanation}) =>
@ -131,9 +128,12 @@ let Mecanism = ({nodeValue, name, explanation}) =>
</div>
{R.contains(name)(["l'une de ces conditions", 'toutes ces conditions']) &&
<ul>
{explanation.map(item => <li key={item.expression + item.name}>
{item.category == 'expression' ?
<Expression {...item} /> : <Mecanism {...item} />
{explanation.map(item => <li key={item.variableName + item.name}>
{item.category == 'variable' ?
<Variable {...item} />
: item.category == 'comparison' ?
<Comparison {...item} />
: <Mecanism {...item} />
}
</li>)}
</ul>
@ -166,22 +166,25 @@ let Variable = ({nodeValue, variableName}) =>
</span>
let Comparison = ({nodeValue, text}) =>
<span className="comparison" >
<span className="name">{text}</span>
<NodeValue data={nodeValue}/>
</span>
let Percentage = ({percentage}) =>
<span className="rate" >
<span className="name">{percentage}</span>
</span>
let Expression = ({nodeValue, expression}) =>
<div className="expression node" >
<div>
<span className="name">{expression}</span>
<NodeValue data={nodeValue}/>
</div>
</div>
let NodeValue = ({data}) => do {
console.log('NodeValue', data)
let valeur = data == null ?
'?'
: ( R.is(Number)(data) ?
@ -203,13 +206,17 @@ let Formula = ({explanation, nodeValue}) => do {
</div>
}
let JSONView = ({o, rootKey}) =>
<JSONTree
getItemString={() => ''}
theme={theme}
hideRoot={true}
shouldExpandNode={() => true}
data={rootKey ? {[rootKey]: o} : o} />
let JSONView = ({o, rootKey}) => (
<div className="json">
<JSONTree
getItemString={() => ''}
theme={theme}
hideRoot={true}
shouldExpandNode={() => true}
data={rootKey ? {[rootKey]: o} : o}
/>
</div>
)

View File

@ -120,7 +120,11 @@ export let knownVariable = (situationGate, variableName) =>
|| situationGate(parentName(variableName)) != null
// pour 'usage', 'motif' ( le parent de 'usage') = 'usage'
export let evaluateVariable = (situationGate, variableName) => {
let value = situationGate(variableName)
export let evaluateVariable = (situationGate, variableName) => //console.log('variableName', variableName, situationGate(parentName(variableName))) ||
situationGate(variableName) == 'oui'
|| situationGate(parentName(variableName)) == nameLeaf(variableName)
return R.is(Number)(value)
? value
: value == 'oui' ||
situationGate(parentName(variableName)) == nameLeaf(variableName)
}

View File

@ -1,25 +1,28 @@
@{% function buildNode(type, d){return ({nodeType: type, explanation: d})} %}
main ->
CalcExpression {% id %}
| Variable {% id %}
| ModifiedVariable {% id %}
| Comparison {% id %}
Comparison -> Comparable _ ComparisonOperator _ Comparable {% d => ({nodeType: 'Comparison', operator: d[2][0], explanation: [d[0], d[4]]}) %}
Comparison -> Comparable _ ComparisonOperator _ Comparable {% d => ({
category: 'comparison',
type: 'boolean',
operator: d[2][0],
explanation: [d[0], d[4]]
}) %}
Comparable -> (int | CalcExpression | Variable) {% d => d[0][0] %}
ComparisonOperator -> ">" | "<" | ">=" | "<=" | "="
ModifiedVariable -> Variable _ Modifier {% d => ({nodeType: 'ModifiedVariable', modifier: d[2], variable: d[0] }) %}
ModifiedVariable -> Variable _ Modifier {% d => ({category: 'modifiedVariable', modifier: d[2], variable: d[0] }) %}
Modifier -> "[" TemporalModifier "]" {% d =>d[1][0] %}
TemporalModifier -> "annuel" | "mensuel" | "jour ouvré" {% id %}
CalcExpression -> Term _ ArithmeticOperator _ Term {% d => ({
nodeType: 'CalcExpression',
category: 'calcExpression',
operator: d[2],
explanation: [d[0], d[4]],
type: 'numeric'
@ -38,7 +41,7 @@ ArithmeticOperator -> "+" {% id %}
Variable -> VariableFragment (_ Dot _ VariableFragment {% d => d[3] %}):* {% d => ({
nodeType: 'Variable',
category: 'variable',
fragments: [d[0], ...d[1]],
type: 'numeric | boolean'
}) %}
@ -54,4 +57,4 @@ Dot -> [\.] {% d => null %}
_ -> [\s] {% d => null %}
int -> [0-9]:+ {% d => ({nodeType: 'value', value: d[0].join("")}) %}
int -> [0-9]:+ {% d => ({category: 'value', nodeValue: +d[0].join("")}) %}

View File

@ -7,7 +7,6 @@ import Grammar from './grammar.ne'
let nearley = () => new Parser(Grammar.ParserRules, Grammar.ParserStart)
console.log('a', nearley().feed('allez on essaie plusieurs combinaisons accentuées'))
/*
Dans ce fichier, les règles YAML sont parsées.
Elles expriment un langage orienté expression, les expressions étant
@ -23,8 +22,8 @@ let selectedRules = rules.filter(rule =>
[
'CIF CDD',
'fin de contrat',
// 'majoration chômage CDD',
// 'Indemnité compensatrice congés payés simplifiée'
'majoration chômage CDD',
'simplifiée'
]
)
)
@ -63,6 +62,25 @@ par exemple ainsi : https://github.com/Engelberg/instaparse#transforming-the-tre
*/
let fillVariableNode = (rule, situationGate) => (parseResult) => {
let
{fragments} = parseResult,
variablePartialName = fragments.join(' . '),
variableName = completeVariableName(rule, variablePartialName),
known = knownVariable(situationGate, variableName),
nodeValue = !known ? null : evaluateVariable(situationGate, variableName)
return {
nodeValue,
category: 'variable',
fragments: fragments,
variableName,
type: 'boolean | numeric',
explanation: null,
missingVariables: known ? [] : [variableName]
}
}
let treat = (situationGate, rule) => rawNode => {
if (R.is(String)(rawNode)) {
@ -72,45 +90,47 @@ let treat = (situationGate, rule) => rawNode => {
Cet objet est alors rebalancé à 'treat'.
*/
let [parseResults, ...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 (!R.contains(parseResults.nodeType)(['Variable', 'CalcExpression', 'ModifiedVariable', 'Comparison']))
if (!R.contains(parseResult.category)(['variable', 'calcExpression', 'modifiedVariable', 'comparison']))
throw "Attention ! Erreur de traitement de l'expression : " + rawNode
if (parseResults.nodeType == 'Variable') {
if (parseResult.category == 'variable')
return fillVariableNode(rule, situationGate)(parseResult, rawNode)
if (parseResult.category == 'comparison') {
let
variablePartialName = parseResults.fragments.join(' . '),
variableName = completeVariableName(rule, variablePartialName),
known = knownVariable(situationGate, variableName)
debugger
// variablePartialName = parseResult.fragments.join(' . '),
// variableName = completeVariableName(rule, variablePartialName),
// known = knownVariable(situationGate, variableName)
filledExplanation = parseResult.explanation.map(
R.when(R.propEq('category', 'variable'), fillVariableNode(rule, situationGate))
),
[{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 {
expression: rawNode,
nodeValue: !known ? null : evaluateVariable(situationGate, variableName),
category: 'expression',
type: 'boolean | numeric',
explanation: null,
missingVariables: known ? [] : [variableName]
text: rawNode,
nodeValue: nodeValue,
category: 'comparison',
type: 'boolean',
explanation: filledExplanation
}
}
// if (parseResults.nodeType == 'CalcExpression') {
//
// let
// variablePartialName = parseResults.fragments.join(' . '),
// variableName = completeVariableName(rule, variablePartialName),
// known = knownVariable(situationGate, variableName)
//
// return {
// expression: rawNode,
// nodeValue: situationGate(baseVariableName),
// category: 'expression',
// type: 'boolean | numeric',
// explanation: null,
// missingVariables: known ? [] : [variableName]
// }
// }
}
//TODO C'est pas bien ça. Devrait être traité par le parser plus haut !
@ -123,7 +143,6 @@ let treat = (situationGate, rule) => rawNode => {
}
if (!R.is(Object)(rawNode)) {
console.log('This node : ', rawNode)
throw ' should be string or object'
}
@ -195,15 +214,15 @@ let treat = (situationGate, rule) => rawNode => {
),
R.toPairs,
R.reduce( (memo, [condition, consequence]) => {
let {nodeValue, explanation} = memo,
[variableName, evaluation] = recognizeExpression(rule, condition),
let
{nodeValue, explanation} = memo,
conditionNode = treat(situationGate, rule)(condition), // can be a 'comparison', a 'variable', TODO a 'negation'
childNumericalLogic = treatNumericalLogicRec(consequence),
known = knownVariable(situationGate, variableName),
nextNodeValue = !known ?
nextNodeValue = conditionNode.nodeValue == null ?
// Si la proposition n'est pas encore résolvable
null
// Si la proposition est résolvable
: evaluation(situationGate) ?
: conditionNode.nodeValue == true ?
// Si elle est vraie
childNumericalLogic.nodeValue
// Si elle est fausse
@ -215,16 +234,15 @@ let treat = (situationGate, rule) => rawNode => {
: nodeValue !== false ?
nodeValue // l'une des propositions renvoie déjà une valeur numérique donc différente de false
: nextNodeValue,
// condition: condition,
explanation: [...explanation, {
nodeValue: nextNodeValue,
category: 'condition',
condition,
conditionValue: evaluation(situationGate),
text: condition,
condition: conditionNode,
conditionValue: conditionNode.nodeValue,
type: 'boolean',
explanation: childNumericalLogic
}],
missingVariables: known ? [] : [variableName]
}
}, {
nodeValue: false,
@ -262,17 +280,25 @@ let treat = (situationGate, rule) => rawNode => {
}
if (k === 'multiplication') {
let base = v['assiette'],
parsed = nearley().feed(base),
baseVariableFound = parsed.results[0].nodeType == 'Variable',
variablePartialName = baseVariableFound && parsed.results[0].fragments.join(' . '),
baseVariableFound = parsed.results[0].category == 'variable'
if (!baseVariableFound) throw "L'assiette d'une multiplication doit pour le moment être une variable"
let
variablePartialName = parsed.results[0].fragments.join(' . '),
baseVariableName = completeVariableName(rule, variablePartialName),
baseValue = situationGate(baseVariableName),
rateNode = treat(situationGate, rule)({taux: v['taux']}),
rate = rateNode.nodeValue
return {
nodeValue: ((baseValue && rate) || null) && +baseValue * rate, // null * 6 = 0 :-o
nodeValue: (rate === 0 || rate === false || baseValue === 0) ?
0
: (rate == null || baseValue == null) ?
null
: +baseValue * rate,
category: 'mecanism',
name: 'multiplication',
type: 'numeric',
@ -309,7 +335,6 @@ let treat = (situationGate, rule) => rawNode => {
}
}
console.log('rawNode', rawNode)
throw "Le mécanisme qui vient d'être loggué est inconnu !"
}