2019-04-11 16:23:05 +00:00
import { ShowValuesConsumer } from 'Components/rule/ShowValuesContext'
import { evaluateControls } from 'Engine/controls'
import {
chain ,
cond ,
evolve ,
is ,
keys ,
map ,
mergeAll ,
path ,
pick ,
pipe ,
T
} from 'ramda'
import React from 'react'
import {
bonus ,
evaluateNode ,
makeJsx ,
mergeMissing ,
rewriteNode
} from './evaluation'
import { Node } from './mecanismViews/common'
import {
disambiguateRuleReference ,
findParentDependency ,
findRule ,
findRuleByDottedName
} from './rules'
import { anyNull , undefOrTrue , val } from './traverse-common-functions'
import { treatNumber , treatObject , treatOther , treatString } from './treat'
2017-03-07 17:25:25 +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 ...
* /
2018-06-29 09:13:05 +00:00
export let treat = ( rules , rule ) => rawNode => {
2018-01-08 15:07:26 +00:00
let onNodeType = cond ( [
2018-06-29 09:13:05 +00:00
[ is ( String ) , treatString ( rules , rule ) ] ,
2018-01-08 15:07:26 +00:00
[ is ( Number ) , treatNumber ] ,
2018-06-29 09:13:05 +00:00
[ is ( Object ) , treatObject ( rules , rule ) ] ,
2018-01-08 15:07:26 +00:00
[ T , treatOther ]
2017-06-27 21:09:03 +00:00
] )
2017-07-09 16:48:00 +00:00
2017-12-12 19:10:22 +00:00
let defaultEvaluate = ( cache , situationGate , parsedRules , node ) => node
2017-07-09 16:48:00 +00:00
let parsedNode = onNodeType ( rawNode )
2017-10-24 17:33:55 +00:00
return parsedNode . evaluate
? parsedNode
: { ... parsedNode , evaluate : defaultEvaluate }
2017-02-22 16:55:36 +00:00
}
2017-07-17 17:39:49 +00:00
export let treatRuleRoot = ( rules , rule ) => {
2017-11-28 11:45:06 +00:00
/ *
2019-02-12 14:59:01 +00:00
The treatRuleRoot function will traverse the tree of the ` rule ` and produce an AST , an object containing other objects containing other objects ...
2019-03-04 13:21:15 +00:00
Some of the attributes of the rule are dynamic , they need to be parsed . It is the case of ` non applicable si ` , ` applicable si ` , ` formule ` .
2019-02-12 14:59:01 +00:00
These attributes ' values themselves may have mechanism properties ( e . g . ` barème ` ) or inline expressions ( e . g . ` maVariable + 3 ` ) .
These mechanisms or variables are in turn traversed by ` treat() ` . During this processing , 'evaluate' and 'jsx' functions are attached to the objects of the AST . They will be evaluated during the evaluation phase , called "analyse" .
* /
2018-04-10 12:13:37 +00:00
let evaluate = ( cache , situationGate , parsedRules , node ) => {
2018-04-26 12:24:47 +00:00
// console.log((cache.op || ">").padStart(cache.parseLevel),rule.dottedName)
2018-04-13 09:44:49 +00:00
cache . parseLevel ++
2018-04-12 10:25:01 +00:00
2018-11-27 16:57:25 +00:00
let evaluatedAttributes = pipe (
2018-12-19 16:26:11 +00:00
pick ( [ 'parentDependency' , 'non applicable si' , 'applicable si' ] ) ,
2018-11-27 16:57:25 +00:00
map ( value => evaluateNode ( cache , situationGate , parsedRules , value ) )
) ( node ) ,
{
parentDependency ,
'non applicable si' : notApplicable ,
2019-02-12 14:54:23 +00:00
'applicable si' : applicable
2018-11-27 16:57:25 +00:00
} = evaluatedAttributes ,
isApplicable =
val ( parentDependency ) === false
2018-09-19 13:16:04 +00:00
? false
2018-11-27 16:57:25 +00:00
: val ( notApplicable ) === true
2017-10-24 13:57:46 +00:00
? false
2018-11-27 16:57:25 +00:00
: val ( applicable ) === false
2018-11-15 15:21:53 +00:00
? false
2018-11-27 16:57:25 +00:00
: anyNull ( [ notApplicable , applicable , parentDependency ] )
2018-11-15 15:21:53 +00:00
? null
2018-11-27 16:57:25 +00:00
: ! val ( notApplicable ) && undefOrTrue ( val ( applicable ) ) ,
2018-12-19 16:26:11 +00:00
evaluateFormula = ( ) =>
2019-01-21 15:26:07 +00:00
node . formule
? evaluateNode ( cache , situationGate , parsedRules , node . formule )
: { } ,
2019-02-12 14:54:23 +00:00
// evaluate the formula lazily, only if the applicability is known and true
2018-12-19 16:26:11 +00:00
evaluatedFormula =
isApplicable === true
? evaluateFormula ( )
: isApplicable === false
2019-01-21 15:26:07 +00:00
? {
... node . formule ,
missingVariables : { } ,
nodeValue : 0
}
: {
... node . formule ,
missingVariables : { } ,
nodeValue : null
} ,
2018-12-19 16:26:11 +00:00
{
missingVariables : formulaMissingVariables ,
nodeValue
2019-01-21 15:26:07 +00:00
} = evaluatedFormula
2017-11-07 10:07:02 +00:00
2019-02-12 14:59:01 +00:00
// if isApplicable === true
// evaluateControls
// attache them to the node for further usage
// do not output missingVariables for now
2017-11-07 18:46:40 +00:00
let condMissing =
2018-11-27 16:35:42 +00:00
isApplicable === false
2018-11-15 15:21:53 +00:00
? { }
2018-11-27 16:57:25 +00:00
: mergeAll ( [
parentDependency ? . missingVariables || { } ,
notApplicable ? . missingVariables || { } ,
applicable ? . missingVariables || { }
] ) ,
2018-04-13 16:41:45 +00:00
// On veut abaisser le score des conséquences par rapport aux conditions,
// mais seulement dans le cas où une condition est effectivement présente
hasCondition = keys ( condMissing ) . length > 0 ,
2018-04-26 12:24:47 +00:00
missingVariables = mergeMissing (
bonus ( condMissing , hasCondition ) ,
2018-12-19 16:26:11 +00:00
formulaMissingVariables
2018-04-26 12:24:47 +00:00
)
2017-10-24 17:47:16 +00:00
2018-04-12 10:25:01 +00:00
cache . parseLevel --
2018-04-26 12:24:47 +00:00
// if (keys(condMissing).length) console.log("".padStart(cache.parseLevel-1),{conditions:condMissing, formule:formMissing})
// else console.log("".padStart(cache.parseLevel-1),{formule:formMissing})
2018-11-27 16:57:25 +00:00
return {
... node ,
... evaluatedAttributes ,
2019-01-21 15:26:07 +00:00
... { formule : evaluatedFormula } ,
2018-11-27 16:57:25 +00:00
nodeValue ,
isApplicable ,
2019-03-04 13:21:15 +00:00
missingVariables ,
inactiveParent : parentDependency && val ( parentDependency ) == false
2018-11-27 16:57:25 +00:00
}
2017-07-09 14:39:31 +00:00
}
2018-11-26 17:09:14 +00:00
let parentDependency = findParentDependency ( rules , rule )
2018-09-19 13:16:04 +00:00
2018-11-26 15:17:45 +00:00
let root = { ... rule , ... ( parentDependency ? { parentDependency } : { } ) }
2018-01-08 15:07:26 +00:00
let parsedRoot = evolve ( {
2017-10-24 13:57:46 +00:00
// Voilà les attributs d'une règle qui sont aujourd'hui dynamiques, donc à traiter
// Les métadonnées d'une règle n'en font pas aujourd'hui partie
2017-02-22 16:55:36 +00:00
2017-10-24 13:57:46 +00:00
// condition d'applicabilité de la règle
2018-09-19 13:16:04 +00:00
parentDependency : parent => {
2018-11-26 17:09:14 +00:00
let node = treat ( rules , rule ) ( parent . dottedName )
2018-09-19 13:16:04 +00:00
let jsx = ( nodeValue , explanation ) => (
2018-09-19 16:31:24 +00:00
< ShowValuesConsumer >
{ showValues =>
! showValues ? (
< div > Active seulement si { makeJsx ( explanation ) } < / d i v >
) : nodeValue === true ? (
< div > Active car { makeJsx ( explanation ) } < / d i v >
) : nodeValue === false ? (
< div > Non active car { makeJsx ( explanation ) } < / d i v >
) : null
}
< / S h o w V a l u e s C o n s u m e r >
2018-09-19 13:16:04 +00:00
)
return {
2018-11-26 17:09:14 +00:00
evaluate : ( cache , situation , parsedRules ) =>
node . evaluate ( cache , situation , parsedRules , node ) ,
2018-09-19 13:16:04 +00:00
jsx ,
category : 'ruleProp' ,
2018-09-19 16:31:24 +00:00
rulePropType : 'cond' ,
2018-11-26 17:09:14 +00:00
name : 'parentDependency' ,
2018-09-19 13:16:04 +00:00
type : 'numeric' ,
2018-11-26 17:09:14 +00:00
explanation : node
2018-09-19 13:16:04 +00:00
}
} ,
2017-10-24 13:57:46 +00:00
'non applicable si' : evolveCond ( 'non applicable si' , rule , rules ) ,
'applicable si' : evolveCond ( 'applicable si' , rule , rules ) ,
2018-09-19 13:16:04 +00:00
// formule de calcul
2017-10-24 17:33:55 +00:00
formule : value => {
2017-12-12 19:10:22 +00:00
let evaluate = ( cache , situationGate , parsedRules , node ) => {
2018-01-03 15:54:19 +00:00
let explanation = evaluateNode (
cache ,
2017-10-24 17:33:55 +00:00
situationGate ,
parsedRules ,
node . explanation
) ,
2018-04-10 11:23:06 +00:00
nodeValue = explanation . nodeValue ,
2018-04-10 12:13:37 +00:00
missingVariables = explanation . missingVariables
2018-04-10 11:23:06 +00:00
2018-04-10 12:13:37 +00:00
return rewriteNode ( node , nodeValue , explanation , missingVariables )
2017-07-09 16:48:00 +00:00
}
2017-07-17 17:39:49 +00:00
let child = treat ( rules , rule ) ( value )
2017-07-11 14:00:10 +00:00
2017-10-24 17:33:55 +00:00
let jsx = ( nodeValue , explanation ) => makeJsx ( explanation )
2017-07-13 19:55:59 +00:00
2017-03-16 18:30:30 +00:00
return {
2017-07-09 16:48:00 +00:00
evaluate ,
2017-07-13 19:55:59 +00:00
jsx ,
2017-03-16 18:30:30 +00:00
category : 'ruleProp' ,
rulePropType : 'formula' ,
name : 'formule' ,
type : 'numeric' ,
2017-07-13 19:55:59 +00:00
explanation : child
2017-03-16 18:30:30 +00:00
}
2019-02-12 14:54:23 +00:00
} ,
2019-02-12 14:59:01 +00:00
contrôles : map ( control => {
2019-01-29 17:41:44 +00:00
let testExpression = treat ( rules , rule ) ( control . si )
2019-03-04 16:12:40 +00:00
if (
! testExpression . explanation &&
! ( testExpression . category === 'variable' )
)
2018-06-29 13:46:42 +00:00
throw new Error (
'Ce contrôle ne semble pas être compris :' + control [ 'si' ]
)
return {
2018-09-12 17:12:59 +00:00
dottedName : rule . dottedName ,
2018-06-29 13:46:42 +00:00
level : control [ 'niveau' ] ,
test : control [ 'si' ] ,
2018-06-29 16:14:00 +00:00
message : control [ 'message' ] ,
2018-06-29 13:46:42 +00:00
testExpression ,
2019-02-12 14:59:01 +00:00
solution : control [ 'solution' ]
2018-06-29 13:46:42 +00:00
}
2018-06-29 10:36:42 +00:00
} )
2019-02-12 14:59:01 +00:00
} ) ( root )
2018-06-29 09:13:05 +00:00
2017-07-09 14:39:31 +00:00
return {
2017-07-31 14:30:05 +00:00
// Pas de propriété explanation et jsx ici car on est parti du (mauvais) principe que 'non applicable si' et 'formule' sont particuliers, alors qu'ils pourraient être rangé avec les autres mécanismes
2017-07-09 14:39:31 +00:00
... parsedRoot ,
2017-07-11 14:00:10 +00:00
evaluate ,
2019-02-12 14:54:23 +00:00
parsed : true
2017-03-16 18:30:30 +00:00
}
2017-07-09 14:39:31 +00:00
}
2017-02-22 16:55:36 +00:00
2017-10-24 13:57:46 +00:00
let evolveCond = ( name , rule , rules ) => value => {
2017-12-12 19:10:22 +00:00
let evaluate = ( cache , situationGate , parsedRules , node ) => {
2018-01-03 15:54:19 +00:00
let explanation = evaluateNode (
cache ,
2017-10-24 17:33:55 +00:00
situationGate ,
parsedRules ,
node . explanation
) ,
2018-04-10 11:23:06 +00:00
nodeValue = explanation . nodeValue ,
2018-04-10 12:13:37 +00:00
missingVariables = explanation . missingVariables
return rewriteNode ( node , nodeValue , explanation , missingVariables )
2017-10-24 13:57:46 +00:00
}
let child = treat ( rules , rule ) ( value )
let jsx = ( nodeValue , explanation ) => (
< Node
classes = "ruleProp mecanism cond"
name = { name }
value = { nodeValue }
child = {
explanation . category === 'variable' ? (
< div className = "node" > { makeJsx ( explanation ) } < / d i v >
) : (
makeJsx ( explanation )
)
}
/ >
)
return {
evaluate ,
jsx ,
category : 'ruleProp' ,
rulePropType : 'cond' ,
name ,
type : 'boolean' ,
explanation : child
}
}
2017-11-07 18:46:40 +00:00
export let getTargets = ( target , rules ) => {
2018-01-08 15:07:26 +00:00
let multiSimulation = path ( [ 'simulateur' , 'objectifs' ] ) ( target )
2017-11-07 18:46:40 +00:00
let targets = multiSimulation
? // On a un simulateur qui définit une liste d'objectifs
2018-03-14 15:58:12 +00:00
multiSimulation
2018-01-03 15:54:19 +00:00
. map ( n => disambiguateRuleReference ( rules , target , n ) )
. map ( n => findRuleByDottedName ( rules , n ) )
2017-11-07 18:46:40 +00:00
: // Sinon on est dans le cas d'une simple variable d'objectif
2018-03-14 15:58:12 +00:00
[ target ]
2017-11-07 18:46:40 +00:00
return targets
2017-07-09 22:14:22 +00:00
}
2017-11-28 11:45:06 +00:00
export let parseAll = flatRules => {
let treatOne = rule => treatRuleRoot ( flatRules , rule )
2018-01-08 15:07:26 +00:00
return map ( treatOne , flatRules )
2017-11-28 11:45:06 +00:00
}
export let analyseMany = ( parsedRules , targetNames ) => situationGate => {
2018-01-03 15:54:19 +00:00
// TODO: we should really make use of namespaces at this level, in particular
// setRule in Rule.js needs to get smarter and pass dottedName
2018-04-26 12:24:47 +00:00
let cache = { parseLevel : 0 }
2018-06-29 13:46:42 +00:00
2018-09-26 13:00:05 +00:00
let parsedTargets = targetNames . map ( t => {
let parsedTarget = findRule ( parsedRules , t )
if ( ! parsedTarget )
throw new Error (
` L'objectif de calcul " ${ t } " ne semble pas exister dans la base de règles `
)
return parsedTarget
} ) ,
2018-11-15 15:21:53 +00:00
targets = chain ( pt => getTargets ( pt , parsedRules ) , parsedTargets ) . map (
t =>
cache [ t . dottedName ] || // This check exists because it is not done in treatRuleRoot's eval, while it is in treatVariable. This should be merged : we should probably call treatVariable here : targetNames could be expressions (hence with filters) TODO
evaluateNode ( cache , situationGate , parsedRules , t )
2017-11-07 18:46:40 +00:00
)
2019-03-04 13:21:15 +00:00
let controls = evaluateControls ( cache , situationGate , parsedRules )
2019-04-09 15:07:17 +00:00
2019-03-04 13:21:15 +00:00
return { targets , cache , controls }
2017-11-28 11:45:06 +00:00
}
export let analyse = ( parsedRules , target ) => {
return analyseMany ( parsedRules , [ target ] )
2017-07-09 14:39:31 +00:00
}