2017-03-14 10:42:44 +00:00
import React from 'react'
2017-06-27 17:53:37 +00:00
import { rules , findRuleByDottedName , disambiguateRuleReference , findRuleByName } from './rules'
2017-04-24 18:03:38 +00:00
import { evaluateVariable } from './variables'
2017-02-22 16:55:36 +00:00
import R from 'ramda'
2017-03-03 09:43:43 +00:00
import knownMecanisms from './known-mecanisms.yaml'
2017-03-07 17:25:25 +00:00
import { Parser } from 'nearley'
import Grammar from './grammar.ne'
2017-03-15 17:13:46 +00:00
import { Node , Leaf } from './traverse-common-jsx'
2017-06-28 07:31:37 +00:00
import { mecanismOneOf , mecanismAllOf , mecanismNumericalLogic , mecanismSum , mecanismProduct , mecanismPercentage , mecanismScale , mecanismMax , mecanismError } from "./mecanisms"
2017-03-07 17:25:25 +00:00
2017-03-08 16:47:12 +00:00
let nearley = ( ) => new Parser ( Grammar . ParserRules , Grammar . ParserStart )
2017-01-09 17:17:51 +00:00
2017-03-06 16:35:30 +00:00
/ *
Dans ce fichier , les règles YAML sont parsées .
Elles expriment un langage orienté expression , les expressions étant
- préfixes quand elles sont des 'mécanismes' ( des mot - clefs représentant des calculs courants dans la loi )
- infixes pour les feuilles : des tests d 'égalité, d' inclusion , des comparaisons sur des variables ou tout simplement la variable elle - même , ou une opération effectuée sur la variable
* /
2017-02-22 16:55:36 +00:00
/ *
- > Notre règle est naturellement un AST ( car notation préfixe dans le YAML )
- > préliminaire : les expression infixes devront être parsées ,
par exemple ainsi : https : //github.com/Engelberg/instaparse#transforming-the-tree
- > Notre règle entière est un AST , qu ' il faut maintenant traiter :
- faire le calcul ( déterminer les valeurs de chaque noeud )
- trouver les branches complètes pour déterminer les autres branches courtcircuitées
- ex . rule . formule est courtcircuitée si rule . non applicable est vrai
2017-04-24 18:03:38 +00:00
- les feuilles de 'une de ces conditions' sont courtcircuitées si l 'une d' elle est vraie
2017-02-22 16:55:36 +00:00
- les feuilles de "toutes ces conditions" sont courtcircuitées si l 'une d' elle est fausse
- ...
( - bonus : utiliser ces informations pour l ' ordre de priorité des variables inconnues )
- si une branche est incomplète et qu 'elle est de type numérique, déterminer les bornes si c' est possible .
Ex . - pour une multiplication , si l 'assiette est connue mais que l ' applicabilité est inconnue ,
les bornes seront [ 0 , multiplication . value = assiette * taux ]
- si taux = effectif entreprise >= 20 ? 1 % : 2 % et que l ' applicabilité est connue ,
bornes = [ assiette * 1 % , assiette * 2 % ]
- transformer l ' arbre en JSX pour afficher le calcul * et son état en prenant en compte les variables renseignées et calculées * de façon sympathique dans un butineur Web tel que Mozilla Firefox .
- surement plein d ' autres applications ...
* /
2017-06-27 17:53:37 +00:00
let fillVariableNode = ( rules , rule , situationGate ) => ( parseResult ) => {
2017-03-08 16:49:22 +00:00
let
{ fragments } = parseResult ,
variablePartialName = fragments . join ( ' . ' ) ,
2017-06-27 17:53:37 +00:00
dottedName = disambiguateRuleReference ( rules , rule , variablePartialName ) ,
variable = findRuleByDottedName ( rules , dottedName ) ,
2017-05-10 14:09:36 +00:00
variableIsCalculable = variable . formule != null ,
2017-03-16 18:30:30 +00:00
//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
2017-05-10 14:09:36 +00:00
parsedRule = variableIsCalculable && treatRuleRoot (
2017-03-16 18:30:30 +00:00
situationGate ,
2017-06-27 17:53:37 +00:00
rules ,
2017-03-16 18:30:30 +00:00
variable
) ,
2017-05-10 14:09:36 +00:00
situationValue = evaluateVariable ( situationGate , dottedName , variable ) ,
nodeValue = situationValue
!= null ? situationValue
: ! variableIsCalculable
? null
: parsedRule . nodeValue ,
explanation = parsedRule ,
missingVariables = variableIsCalculable ? [ ] : ( nodeValue == null ? [ dottedName ] : [ ] )
2017-03-08 16:49:22 +00:00
return {
nodeValue ,
category : 'variable' ,
fragments : fragments ,
2017-05-09 15:14:13 +00:00
dottedName ,
2017-03-08 16:49:22 +00:00
type : 'boolean | numeric' ,
2017-03-16 18:30:30 +00:00
explanation : parsedRule ,
2017-04-24 18:03:38 +00:00
missingVariables ,
2017-03-15 17:13:46 +00:00
jsx : < Leaf
classes = "variable"
2017-03-20 09:00:05 +00:00
name = { fragments . join ( ' . ' ) }
2017-03-15 17:13:46 +00:00
value = { nodeValue }
/ >
2017-03-08 16:49:22 +00:00
}
}
2017-05-09 13:39:42 +00:00
let buildNegatedVariable = variable => {
let nodeValue = variable . nodeValue == null ? null : ! variable . nodeValue
return {
nodeValue ,
category : 'mecanism' ,
name : 'négation' ,
type : 'boolean' ,
explanation : variable ,
jsx : < Node
classes = "inlineExpression negation"
value = { nodeValue }
child = {
< span className = "nodeContent" >
< span className = "operator" > ¬ < / s p a n >
{ variable . jsx }
< / s p a n >
}
/ >
}
}
2017-06-27 17:53:37 +00:00
let treat = ( situationGate , rules , rule ) => rawNode => {
2017-06-27 21:09:03 +00:00
// inner functions
let reTreat = treat ( situationGate , rules , rule ) ,
treatString = rawNode => {
/ * O n a à f a i r e à u n s t r i n g , d o n c à u n e e x p r e s s i o n i n f i x e .
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' .
* /
2017-03-08 16:47:12 +00:00
2017-06-27 21:09:03 +00:00
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 ( 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 == '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 }
< / s p a n > ) ( n o d e )
]
] )
) ,
[ { 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 } < / s p a n >
{ filledExplanation [ 1 ] . jsx }
< / s p a n >
}
/ >
}
}
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 }
< / s p a n > ) ( n o d e )
]
] )
) ,
[ { 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 } < / s p a n >
{ filledExplanation [ 1 ] . jsx }
< / s p a n >
}
/ >
}
}
} ,
treatNumber = rawNode => {
2017-03-09 15:42:52 +00:00
return {
2017-06-27 23:19:38 +00:00
text : "" + rawNode ,
2017-06-27 21:09:03 +00:00
category : 'number' ,
nodeValue : rawNode ,
2017-03-09 15:42:52 +00:00
type : 'numeric' ,
2017-06-27 21:09:03 +00:00
jsx :
< span className = "number" >
{ rawNode }
< / s p a n >
2017-03-09 15:42:52 +00:00
}
2017-06-27 21:09:03 +00:00
} ,
treatOther = rawNode => {
console . log ( ) // eslint-disable-line no-console
throw 'Cette donnée : ' + rawNode + ' doit être un Number, String ou Object'
} ,
2017-06-27 23:02:36 +00:00
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 !'
2017-06-27 21:09:03 +00:00
}
2017-04-24 18:03:38 +00:00
2017-06-27 23:02:36 +00:00
let k = R . head ( mecanisms ) ,
v = rawNode [ k ]
2017-06-28 07:06:52 +00:00
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 )
2017-04-06 16:34:00 +00:00
2017-06-28 07:31:37 +00:00
return action ( reTreat , k , v )
2017-03-03 09:43:43 +00:00
}
2017-02-22 16:55:36 +00:00
2017-06-27 21:09:03 +00:00
let onNodeType = R . cond ( [
[ R . is ( String ) , treatString ] ,
[ R . is ( Number ) , treatNumber ] ,
2017-06-27 22:38:55 +00:00
[ R . is ( Object ) , treatObject ] ,
[ R . T , treatOther ]
2017-06-27 21:09:03 +00:00
] )
return onNodeType ( rawNode )
2017-02-22 16:55:36 +00:00
}
2017-05-07 17:45:44 +00:00
//TODO c'est moche :
export let computeRuleValue = ( formuleValue , condValue ) =>
condValue === undefined
? formuleValue
: formuleValue === 0
? 0
: condValue === null
? null
: condValue === true
? 0
: formuleValue
2017-06-27 17:53:37 +00:00
export let treatRuleRoot = ( situationGate , rules , rule ) => R . pipe (
2017-03-16 18:30:30 +00:00
R . evolve ( { // -> Voilà les attributs que peut comporter, pour l'instant, une Variable.
2017-02-22 16:55:36 +00:00
// 'meta': pas de traitement pour l'instant
// 'cond' : Conditions d'applicabilité de la règle
2017-03-16 18:30:30 +00:00
'non applicable si' : value => {
let
2017-06-27 17:53:37 +00:00
child = treat ( situationGate , rules , rule ) ( value ) ,
2017-03-16 18:30:30 +00:00
nodeValue = child . nodeValue
2017-04-11 15:44:53 +00:00
2017-03-16 18:30:30 +00:00
return {
category : 'ruleProp' ,
rulePropType : 'cond' ,
name : 'non applicable si' ,
type : 'boolean' ,
nodeValue : child . nodeValue ,
explanation : child ,
jsx : < Node
classes = "ruleProp mecanism cond"
name = "non applicable si"
value = { nodeValue }
child = {
2017-04-11 15:44:53 +00:00
child . category === 'variable' ? < div className = "node" > { child . jsx } < / d i v >
: child . jsx
2017-03-16 18:30:30 +00:00
}
/ >
}
2017-03-01 10:21:53 +00:00
}
2017-03-16 18:30:30 +00:00
,
// [n'importe quel mécanisme booléen] : expression booléenne (simple variable, négation, égalité, comparaison numérique, test d'inclusion court / long) || l'une de ces conditions || toutes ces conditions
// 'applicable si': // pareil mais inversé !
2017-02-22 16:55:36 +00:00
2017-03-16 18:30:30 +00:00
// 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
2017-06-27 17:53:37 +00:00
child = treat ( situationGate , rules , rule ) ( value ) ,
2017-03-16 18:30:30 +00:00
nodeValue = child . nodeValue
return {
category : 'ruleProp' ,
rulePropType : 'formula' ,
name : 'formule' ,
type : 'numeric' ,
nodeValue : nodeValue ,
explanation : child ,
shortCircuit : R . pathEq ( [ 'non applicable si' , 'nodeValue' ] , true ) ,
jsx : < Node
classes = "ruleProp mecanism formula"
name = "formule"
value = { nodeValue }
child = {
child . jsx
}
/ >
}
2017-03-01 10:21:53 +00:00
}
,
2017-02-22 16:55:36 +00:00
// TODO les mécanismes de composantes et de variations utilisables un peu partout !
// TODO 'temporal': information concernant les périodes : à définir !
// TODO 'intéractions': certaines variables vont en modifier d'autres : ex. Fillon va réduire voir annuler (set 0) une liste de cotisations
// ... ?
2017-03-16 18:30:30 +00:00
} ) ,
2017-03-20 11:17:49 +00:00
/ * C a l c u l d e l a v a l e u r d e l a v a r i a b l e e n c o m b i n a n t :
- les conditions d 'application (' non applicable si ' )
- la formule
TODO : mettre les conditions d ' application dans "formule" , et traiter la formule comme un mécanisme normal dans treat ( )
* /
2017-03-16 18:30:30 +00:00
r => {
let
formuleValue = r . formule . nodeValue ,
condValue = R . path ( [ 'non applicable si' , 'nodeValue' ] ) ( r ) ,
2017-05-07 17:45:44 +00:00
nodeValue = computeRuleValue ( formuleValue , condValue )
2017-03-16 18:30:30 +00:00
return { ... r , nodeValue }
}
) ( rule )
2017-02-22 16:55:36 +00:00
2017-02-10 14:12:00 +00:00
/ * A n a l y s e t h e s e t o f s e l e c t e d r u l e s , a n d a d d d e r i v e d i n f o r m a t i o n t o t h e m :
- do they need variables that are not present in the user situation ?
- if not , do they have a computed value or are they non applicable ?
* /
2017-03-27 13:02:50 +00:00
2017-06-27 17:53:37 +00:00
export let analyseSituation = ( rules , rootVariable ) => situationGate =>
2017-03-16 18:30:30 +00:00
treatRuleRoot (
situationGate ,
2017-06-27 17:53:37 +00:00
rules ,
findRuleByName ( rules , rootVariable )
2017-02-10 14:12:00 +00:00
)
2017-01-26 12:19:04 +00:00
2017-01-10 14:39:40 +00:00
2017-01-19 15:27:27 +00:00
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Ce qui suit est la première tentative d ' écriture du principe du moteur et de la syntaxe * /
2017-01-09 17:17:51 +00:00
// let types = {
/ *
( expression ) :
| ( variable )
| ( négation )
| ( égalité )
| ( comparaison numérique )
| ( test d ' inclusion court )
* /
// }
/ *
Variable :
- applicable si : ( boolean logic )
- non applicable si : ( boolean logic )
- concerne : ( expression )
- ne concerne pas : ( expression )
( boolean logic ) :
toutes ces conditions : ( [ expression | boolean logic ] )
2017-04-24 18:03:38 +00:00
une de ces conditions : ( [ expression | boolean logic ] )
2017-01-09 17:17:51 +00:00
conditions exclusives : ( [ expression | boolean logic ] )
"If you write a regular expression, walk away for a cup of coffee, come back, and can't easily understand what you just wrote, then you should look for a clearer way to express what you're doing."
Les expressions sont le seul mécanisme relativement embêtant pour le moteur . Dans un premier temps , il les gerera au moyen d 'expressions régulières, puis il faudra probablement mieux s' équiper avec un "javascript parser generator" :
https : //medium.com/@daffl/beyond-regex-writing-a-parser-in-javascript-8c9ed10576a6
( variable ) : ( string )
( négation ) :
! ( variable )
( égalité ) :
( variable ) = ( variable . type )
( comparaison numérique ) :
| ( variable ) < ( variable . type )
| ( variable ) <= ( variable . type )
| ( variable ) > ( variable . type )
| ( variable ) <= ( variable . type )
( test d ' inclusion court ) :
( variable ) ⊂ [ variable . type ]
in Variable . formule :
- composantes
- linéaire
- barème en taux marginaux
- test d 'inclusion: (test d' inclusion )
( test d ' inclusion ) :
variable : ( variable )
possibilités : [ variable . type ]
# pas nécessaire pour le CDD
in Variable
- variations : [ si ]
( si ) :
si : ( expression )
# corps
* /