2017-02-10 14:12:00 +00:00
import { rules , findRuleByName , parentName } from './rules'
2017-01-20 10:52:39 +00:00
import { recognizeExpression } from './expressions'
2017-02-22 16:55:36 +00:00
import R from 'ramda'
2017-01-09 17:17:51 +00:00
2017-01-19 15:27:27 +00:00
// L'objectif de la simulation : quelles règles voulons nous calculer ?
let selectedRules = rules . filter ( rule =>
2017-02-10 14:12:00 +00:00
R . contains ( rule . name ,
2017-02-07 19:10:04 +00:00
[
2017-03-02 15:31:24 +00:00
// 'CIF CDD',
2017-02-22 16:55:36 +00:00
// 'fin de contrat',
2017-03-02 15:31:24 +00:00
'majoration chômage CDD'
2017-02-07 19:10:04 +00:00
]
2017-01-19 15:27:27 +00:00
)
)
2017-01-09 17:17:51 +00:00
2017-03-01 19:27:35 +00:00
let knownVariable = ( situationGate , variableName ) => R . or (
situationGate ( variableName ) ,
situationGate ( parentName ( variableName ) ) // pour 'usage', 'motif' ( le parent de 'usage') = 'usage'
2017-02-10 14:12:00 +00:00
) != null
2017-01-10 18:22:44 +00:00
2017-02-07 19:10:04 +00:00
let transformPercentage = s =>
2017-03-01 10:21:53 +00:00
R . contains ( '%' ) ( s ) ?
+ s . replace ( '%' , '' ) / 100
: + s
2017-01-26 12:19:04 +00:00
2017-02-10 14:12:00 +00:00
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
- les feuilles de "l'une de ces conditions" sont courtcircuitées si l 'une d' elle est vraie
- 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 ...
* /
let treat = ( situationGate , rule ) => rawNode => {
if ( R . is ( String ) ( rawNode ) ) { // it's an expression
let [ variableName , evaluation ] = recognizeExpression ( rule , rawNode ) ,
2017-03-01 19:27:35 +00:00
known = knownVariable ( situationGate , variableName ) ,
value = known ?
2017-02-22 16:55:36 +00:00
evaluation ( situationGate )
: null
return {
expression : rawNode ,
nodeValue : value , // null, true or false
category : 'expression' ,
type : 'boolean' ,
2017-03-01 19:27:35 +00:00
explanation : null ,
missingVariables : known ? [ ] : [ variableName ]
2017-02-22 16:55:36 +00:00
}
}
if ( ! R . is ( Object ) ( rawNode ) ) throw 'node should be string or object'
let pairs = R . toPairs ( rawNode ) ,
[ k , v ] = R . head ( pairs )
if ( pairs . length !== 1 ) throw 'OUPS !'
if ( k === "l'une de ces conditions" ) {
return R . pipe (
R . unless ( R . is ( Array ) , ( ) => { throw 'should be array' } ) ,
R . reduce ( ( memo , next ) => {
let { nodeValue , explanation } = memo ,
child = treat ( situationGate , rule ) ( 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 : "l'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 )
}
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 = treat ( situationGate , rule ) ( 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 )
}
2017-03-02 15:31:24 +00:00
//TODO perf: declare this closure somewhere else ?
let treatNumericalLogicRec =
R . ifElse (
R . is ( String ) ,
rate => ( {
nodeValue : transformPercentage ( rate ) ,
type : 'numeric' ,
category : 'percentage' ,
percentage : rate ,
explanation : null
} ) ,
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 ,
[ variableName , evaluation ] = recognizeExpression ( rule , condition ) ,
childNumericalLogic = treatNumericalLogicRec ( consequence ) ,
known = knownVariable ( situationGate , variableName ) ,
nextNodeValue = ! known ?
// Si la proposition n'est pas encore résolvable
null
// Si la proposition est résolvable
: evaluation ( situationGate ) ?
// 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 ,
// condition: condition,
explanation : [ ... explanation , {
nodeValue : nextNodeValue ,
category : 'condition' ,
condition ,
conditionValue : evaluation ( situationGate ) ,
type : 'boolean' ,
explanation : childNumericalLogic
} ] ,
missingVariables : known ? [ ] : [ variableName ]
}
} , {
nodeValue : false ,
category : 'mecanism' ,
name : "logique numérique" ,
type : 'boolean || numeric' , // lol !
explanation : [ ]
} )
) )
if ( k === "logique numérique" ) {
return treatNumericalLogicRec ( v )
}
if ( k === "taux" ) {
// debugger
//TODO gérer les taux historisés
if ( R . is ( String ) ( v ) )
return {
type : 'numeric' ,
category : 'percentage' ,
percentage : v ,
nodeValue : transformPercentage ( v ) ,
explanation : null
}
else {
let node = treat ( situationGate , rule ) ( v )
return {
type : 'numeric' ,
category : 'percentage' ,
percentage : node . nodeValue ,
nodeValue : node . nodeValue ,
explanation : node
}
}
}
2017-03-01 10:21:53 +00:00
if ( k === 'multiplication' ) {
let base = v [ 'assiette' ] ,
[ baseVariableName ] = recognizeExpression ( rule , base ) ,
2017-03-02 15:31:24 +00:00
baseValue = situationGate ( baseVariableName ) ,
rateNode = treat ( situationGate , rule ) ( { taux : v [ 'taux' ] } ) ,
rate = rateNode . nodeValue
2017-03-01 10:21:53 +00:00
return {
2017-03-02 15:31:24 +00:00
nodeValue : baseValue && rate && + baseValue * rate , // null * 6 = 0 :-o
2017-03-01 10:21:53 +00:00
category : 'mecanism' ,
name : 'multiplication' ,
type : 'numeric' ,
explanation : {
base : {
type : 'numeric' ,
category : 'variable' ,
// name: 'base', déduit de la propriété de l'objet
2017-03-02 15:31:24 +00:00
nodeValue : baseValue == null ? null : + baseValue ,
2017-03-01 10:21:53 +00:00
explanation : null ,
2017-03-01 19:27:35 +00:00
variableName : baseVariableName ,
missingVariables : baseValue == null ? [ baseVariableName ] : [ ]
2017-03-01 10:21:53 +00:00
} ,
2017-03-02 15:31:24 +00:00
rate : rateNode ,
2017-03-01 10:21:53 +00:00
//TODO limit: 'plafond'
//TODO multiplier: 'multiplicateur'
}
}
}
2017-02-22 16:55:36 +00:00
throw 'Mécanisme inconnu !' + JSON . stringify ( rawNode )
}
let treatRuleRoot = ( situationGate , rule ) => R . evolve ( { // -> Voilà les attributs que peut comporter, pour l'instant, une Variable.
// 'meta': pas de traitement pour l'instant
// 'cond' : Conditions d'applicabilité de la règle
2017-03-01 10:21:53 +00:00
'non applicable si' : value => {
let child = treat ( situationGate , rule ) ( value )
return {
category : 'ruleProp' ,
rulePropType : 'cond' ,
name : 'non applicable si' ,
type : 'boolean' ,
nodeValue : child . nodeValue ,
explanation : child
}
}
2017-02-22 16:55:36 +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-03-01 10:21:53 +00:00
// note: pour certaines variables booléennes, ex. appartenance à régime Alsace-Moselle, la formule et le non applicable si se rejoignent
2017-02-22 16:55:36 +00:00
// [n'importe quel mécanisme numérique] : multiplication || barème en taux marginaux || le maximum de || le minimum de || ...
2017-03-01 10:21:53 +00:00
'formule' : value => {
let child = treat ( situationGate , rule ) ( value )
return {
category : 'ruleProp' ,
rulePropType : 'formula' ,
name : 'formule' ,
type : 'numeric' ,
nodeValue : child . nodeValue ,
2017-03-01 19:27:35 +00:00
explanation : child ,
shortCircuit : R . pathEq ( [ 'non applicable si' , 'nodeValue' ] , true )
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
// ... ?
} ) ( rule )
let deriveRuleOld = ( situationGate , rule ) => pipe ( // eslint-disable-line no-unused-vars
toPairs ,
reduce ( ( { missingVariables , computedValue } , [ key , value ] ) => {
2017-01-17 09:04:34 +00:00
if ( key === 'concerne' ) {
2017-02-10 14:12:00 +00:00
let [ variableName , evaluation ] = recognizeExpression ( rule , value )
2017-01-10 18:22:44 +00:00
// Si cette variable a été renseignée
2017-01-26 12:19:04 +00:00
if ( knownVariable ( situationGate , variableName ) ) {
2017-01-10 18:22:44 +00:00
// Si l'expression n'est pas vraie...
2017-01-26 12:19:04 +00:00
if ( ! evaluation ( situationGate ) ) {
2017-01-10 18:22:44 +00:00
// On court-circuite toute la variable, et on n'a besoin d'aucune information !
2017-02-22 16:55:36 +00:00
return reduced ( { missingVariables : [ ] } )
2017-01-10 18:22:44 +00:00
} else {
// Sinon, on continue
2017-02-07 19:10:04 +00:00
return { missingVariables }
2017-01-10 18:22:44 +00:00
}
// sinon on demande la valeur de cette variable
2017-02-07 19:10:04 +00:00
} else return { missingVariables : [ ... missingVariables , variableName ] }
2017-01-10 18:22:44 +00:00
}
if ( key === 'non applicable si' ) {
let conditions = value [ 'l\'une de ces conditions' ]
2017-02-22 16:55:36 +00:00
let [ subVariableNames , reduced ] = reduce ( ( [ variableNames ] , expression ) => {
2017-02-10 14:12:00 +00:00
let [ variableName , evaluation ] = recognizeExpression ( rule , expression )
2017-01-26 12:19:04 +00:00
if ( knownVariable ( situationGate , variableName ) ) {
if ( evaluation ( situationGate ) ) {
2017-02-22 16:55:36 +00:00
return reduced ( [ [ ] , true ] )
2017-01-09 17:17:51 +00:00
} else {
2017-01-10 18:22:44 +00:00
return [ variableNames ]
2017-01-09 17:17:51 +00:00
}
}
return [ [ ... variableNames , variableName ] ]
2017-01-10 18:22:44 +00:00
} , [ [ ] , null ] ) ( conditions )
2017-02-07 19:10:04 +00:00
2017-02-22 16:55:36 +00:00
if ( reduced ) return reduced ( { missingVariables : [ ] } )
2017-02-07 19:10:04 +00:00
else return { missingVariables : [ ... missingVariables , ... subVariableNames ] }
2017-01-10 18:22:44 +00:00
}
2017-01-17 09:04:34 +00:00
if ( key === 'formule' ) {
2017-02-16 13:15:45 +00:00
if ( value [ 'multiplication' ] ) {
let { assiette , taux } = value [ 'multiplication' ]
2017-01-10 18:22:44 +00:00
// A propos de l'assiette
2017-02-10 14:12:00 +00:00
let [ assietteVariableName ] = recognizeExpression ( rule , assiette ) ,
2017-01-26 12:19:04 +00:00
assietteValue = situationGate ( assietteVariableName ) ,
2017-01-10 18:22:44 +00:00
unknownAssiette = assietteValue == undefined
// Arrivés là, cette formule devrait être calculable !
2017-02-07 19:10:04 +00:00
let { missingVariables : tauxMissingVariables = [ ] , computedValue } = typeof taux !== 'string' ?
do {
let numericalLogic = taux [ 'logique numérique' ]
if ( ! numericalLogic ) throw 'On ne sait pas pour l\'instant traiter ce mécanisme de taux'
let treatNumericalLogic = numericalLogic => {
if ( typeof numericalLogic == 'string' ) {
return new Object ( { computedValue : assietteValue * transformPercentage ( numericalLogic ) } )
} else {
2017-02-22 16:55:36 +00:00
return pipe (
toPairs ( ) ,
reduce ( ( { missingVariables } , [ expression , subLogic ] ) => {
2017-02-10 14:12:00 +00:00
let [ variableName , evaluation ] = recognizeExpression ( rule , expression )
2017-02-07 19:10:04 +00:00
if ( knownVariable ( situationGate , variableName ) ) {
if ( evaluation ( situationGate ) ) {
2017-02-22 16:55:36 +00:00
return reduced ( treatNumericalLogic ( subLogic ) )
2017-02-07 19:10:04 +00:00
} else {
return { missingVariables }
}
} else return { missingVariables : [ ... missingVariables , variableName ] }
} , { missingVariables : [ ] } )
) ( numericalLogic )
} }
treatNumericalLogic ( numericalLogic )
} : ( { computedValue : assietteValue * transformPercentage ( taux ) } )
let formulaResult = {
missingVariables : [
... missingVariables ,
... ( unknownAssiette ? [ assietteVariableName ] : [ ] ) ,
... tauxMissingVariables
] ,
computedValue
2017-01-10 18:22:44 +00:00
}
2017-01-09 17:17:51 +00:00
2017-02-22 16:55:36 +00:00
return computedValue != null ? reduced ( formulaResult ) : formulaResult
2017-02-07 19:10:04 +00:00
2017-01-10 18:22:44 +00:00
}
}
2017-01-09 17:17:51 +00:00
2017-02-07 19:10:04 +00:00
return { missingVariables }
2017-02-10 14:12:00 +00:00
} , { missingVariables : [ ] }
2017-01-10 18:22:44 +00:00
)
2017-02-10 14:12:00 +00:00
)
2017-01-09 17:17:51 +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-01-26 12:19:04 +00:00
export let analyseSituation = situationGate =>
2017-02-22 16:55:36 +00:00
selectedRules . map (
rule => treatRuleRoot ( situationGate , rule )
2017-02-10 14:12:00 +00:00
)
2017-01-26 12:19:04 +00:00
2017-01-10 14:39:40 +00:00
export let variableType = name => {
2017-01-10 18:22:44 +00:00
if ( name == null ) return null
let found = findRuleByName ( name )
2017-01-10 14:39:40 +00:00
// tellement peu de variables pour l'instant
// que c'est très simpliste
2017-01-10 18:22:44 +00:00
if ( ! found ) return 'boolean'
2017-02-22 16:55:36 +00:00
let { rule } = found
2017-01-10 18:22:44 +00:00
if ( typeof rule . formule [ 'somme' ] !== 'undefined' ) return 'numeric'
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 ] )
l ' une de ces conditions : ( [ expression | boolean logic ] )
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
* /