From 376e72fc47ab63a82a3741c3c5cb1c0aa974ae7c Mon Sep 17 00:00:00 2001 From: Mael Thomas Date: Wed, 8 Mar 2017 17:49:22 +0100 Subject: [PATCH] =?UTF-8?q?[moteur]=20conciliation=20des=20m=C3=A9canismes?= =?UTF-8?q?=20existants=20avec=20le=20nouveau=20parsing=20d'expressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...té_compensatrice_congés_payés.simplifiée.yaml | 6 +- source/.eslintrc | 2 +- source/components/CDD.js | 47 ++++--- source/components/Rule.css | 20 ++- source/components/Rule.js | 53 ++++---- source/engine/expressions.js | 10 +- source/engine/grammar.ne | 17 +-- source/engine/traverse.js | 117 +++++++++++------- 8 files changed, 159 insertions(+), 113 deletions(-) diff --git a/règles/rémunération-travail/cdd/indemnité_compensatrice_congés_payés.simplifiée.yaml b/règles/rémunération-travail/cdd/indemnité_compensatrice_congés_payés.simplifiée.yaml index 619a438f0..8104f5e20 100644 --- a/règles/rémunération-travail/cdd/indemnité_compensatrice_congés_payés.simplifiée.yaml +++ b/règles/rémunération-travail/cdd/indemnité_compensatrice_congés_payés.simplifiée.yaml @@ -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é d’une augmentation de salaire. @@ -27,7 +27,7 @@ # # Comment ? # # mensuel / nombre moyen de jours ouvrés par an - - 789 + - 80 notes: | diff --git a/source/.eslintrc b/source/.eslintrc index 5b700a975..098dedeef 100644 --- a/source/.eslintrc +++ b/source/.eslintrc @@ -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 diff --git a/source/components/CDD.js b/source/components/CDD.js index 252303ba5..dc70df262 100644 --- a/source/components/CDD.js +++ b/source/components/CDD.js @@ -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 => - - ) + let conversation = steps.map(step => ( + + )) return ( -
- -
-
- {conversation} -
- -
- -
+
+ +
+
+ {conversation} +
+ +
+ +
) } } diff --git a/source/components/Rule.css b/source/components/Rule.css index 041ee4064..2bdd3b1a6 100644 --- a/source/components/Rule.css +++ b/source/components/Rule.css @@ -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%; +} diff --git a/source/components/Rule.js b/source/components/Rule.js index b5aafecb5..489333b66 100644 --- a/source/components/Rule.js +++ b/source/components/Rule.js @@ -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' && } - { - explanation.category == 'expression' && - } let Mecanism = ({nodeValue, name, explanation}) => @@ -131,9 +128,12 @@ let Mecanism = ({nodeValue, name, explanation}) => {R.contains(name)(["l'une de ces conditions", 'toutes ces conditions']) &&
    - {explanation.map(item =>
  • - {item.category == 'expression' ? - : + {explanation.map(item =>
  • + {item.category == 'variable' ? + + : item.category == 'comparison' ? + + : }
  • )}
@@ -166,22 +166,25 @@ let Variable = ({nodeValue, variableName}) => +let Comparison = ({nodeValue, text}) => + + {text} + + + + + + + + let Percentage = ({percentage}) => {percentage} -let Expression = ({nodeValue, expression}) => -
-
- {expression} - -
-
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 { } -let JSONView = ({o, rootKey}) => - ''} - theme={theme} - hideRoot={true} - shouldExpandNode={() => true} - data={rootKey ? {[rootKey]: o} : o} /> +let JSONView = ({o, rootKey}) => ( +
+ ''} + theme={theme} + hideRoot={true} + shouldExpandNode={() => true} + data={rootKey ? {[rootKey]: o} : o} + /> +
+) diff --git a/source/engine/expressions.js b/source/engine/expressions.js index f853dd45c..f89244bfd 100644 --- a/source/engine/expressions.js +++ b/source/engine/expressions.js @@ -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) +} diff --git a/source/engine/grammar.ne b/source/engine/grammar.ne index 509423abd..09c810764 100644 --- a/source/engine/grammar.ne +++ b/source/engine/grammar.ne @@ -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("")}) %} diff --git a/source/engine/traverse.js b/source/engine/traverse.js index 7cf3e2c34..802ab8fe3 100644 --- a/source/engine/traverse.js +++ b/source/engine/traverse.js @@ -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 !" }