2019-06-19 09:54:47 +00:00
// Reference to a variable
2019-11-04 13:07:19 +00:00
import parseRule from 'Engine/parseRule'
import React from 'react'
2019-11-28 11:03:23 +00:00
import { typeWarning } from './error'
2019-11-04 13:07:19 +00:00
import { evaluateApplicability } from './evaluateRule'
2019-11-04 17:46:26 +00:00
import { evaluateNode , mergeMissing } from './evaluation'
2019-11-04 13:07:19 +00:00
import { getSituationValue } from './getSituationValue'
import { Leaf } from './mecanismViews/common'
2019-11-28 11:03:23 +00:00
import { convertNodeToUnit , getNodeDefaultUnit } from './nodeUnits'
2019-11-05 10:49:04 +00:00
import { disambiguateRuleReference , findRuleByDottedName } from './rules'
2019-11-28 11:03:23 +00:00
import { areUnitConvertible } from './units'
2019-11-11 15:50:05 +00:00
const getApplicableReplacements = (
filter ,
contextRuleName ,
2019-11-04 13:07:19 +00:00
cache ,
situation ,
rules ,
2019-11-11 15:50:05 +00:00
rule
2019-11-04 13:07:19 +00:00
) => {
2019-11-14 12:18:41 +00:00
let missingVariableList = [ ]
2019-10-30 15:52:51 +00:00
const applicableReplacements = rule . replacedBy
2019-11-04 13:07:19 +00:00
. sort (
( replacement1 , replacement2 ) =>
! ! replacement2 . whiteListedNames - ! ! replacement1 . whiteListedNames
)
2019-11-11 15:50:05 +00:00
// Remove remplacement not in whitelist
2019-11-04 13:07:19 +00:00
. filter (
( { whiteListedNames } ) =>
! whiteListedNames ||
2019-11-05 13:17:09 +00:00
whiteListedNames . some ( name => contextRuleName . startsWith ( name ) )
2019-11-04 13:07:19 +00:00
)
. filter (
( { blackListedNames } ) =>
! blackListedNames ||
2019-11-05 13:17:09 +00:00
blackListedNames . every ( name => ! contextRuleName . startsWith ( name ) )
2019-11-04 13:07:19 +00:00
)
2019-11-06 15:13:53 +00:00
. filter ( ( { referenceNode } ) => contextRuleName !== referenceNode . dottedName )
2019-11-11 15:50:05 +00:00
// Remove remplacement defined in a not applicable node
. filter ( ( { referenceNode } ) => {
const referenceRule = rules [ referenceNode . dottedName ]
const {
nodeValue : isApplicable ,
missingVariables
} = evaluateApplicability ( cache , situation , rules , referenceRule )
missingVariableList . push ( missingVariables )
return isApplicable
} )
// Remove remplacement defined in a node whose situation value is false
2019-11-04 10:03:01 +00:00
. filter ( ( { referenceNode } ) => {
2019-11-05 17:56:07 +00:00
const referenceRule = rules [ referenceNode . dottedName ]
const situationValue = getSituationValue (
2019-11-04 17:46:26 +00:00
situation ,
2019-11-05 17:56:07 +00:00
referenceRule . dottedName ,
referenceRule
2019-11-04 17:46:26 +00:00
)
2019-11-05 17:56:07 +00:00
if ( referenceNode . question && situationValue == null ) {
2019-11-14 12:18:41 +00:00
missingVariableList . push ( { [ referenceNode . dottedName ] : 1 } )
2019-11-05 17:56:07 +00:00
}
2019-11-11 15:50:05 +00:00
return situationValue !== false
} )
// Remove remplacement defined in a boolean node whose evaluated value is false
. filter ( ( { referenceNode } ) => {
const referenceRule = rules [ referenceNode . dottedName ]
if ( referenceRule . formule ? . explanation ? . operationType !== 'comparison' ) {
return true
}
const { nodeValue : isApplicable , missingVariables } = evaluateNode (
cache ,
situation ,
rules ,
referenceRule
)
2019-11-04 17:46:26 +00:00
missingVariableList . push ( missingVariables )
2019-11-11 15:50:05 +00:00
return isApplicable
2019-11-04 13:07:19 +00:00
} )
2019-11-04 10:03:01 +00:00
. map ( ( { referenceNode , replacementNode } ) =>
2019-11-04 13:07:19 +00:00
replacementNode != null
? evaluateNode ( cache , situation , rules , replacementNode )
: evaluateReference ( filter ) ( cache , situation , rules , referenceNode )
2019-11-04 10:03:01 +00:00
)
2019-11-28 11:03:23 +00:00
. map ( replacementNode => {
const replacedRuleUnit = getNodeDefaultUnit ( rule , cache )
if ( ! areUnitConvertible ( replacementNode . unit , replacedRuleUnit ) ) {
typeWarning (
contextRuleName ,
` L'unité de la règle de remplacement n'est pas compatible avec celle de la règle remplacée ${ rule . dottedName } `
)
}
return {
... replacementNode ,
unit : replacementNode . unit || replacedRuleUnit
}
} )
2019-11-14 12:18:41 +00:00
missingVariableList = missingVariableList . filter (
missingVariables => ! ! Object . keys ( missingVariables ) . length
)
2019-11-11 15:50:05 +00:00
return [ applicableReplacements , missingVariableList ]
}
let evaluateReference = ( filter , contextRuleName ) => (
cache ,
situation ,
rules ,
node
) => {
let rule = rules [ node . dottedName ]
// When a rule exists in different version (created using the `replace` mecanism), we add
// a redirection in the evaluation of references to use a potential active replacement
const [
applicableReplacements ,
2019-11-14 12:18:41 +00:00
replacementMissingVariableList
2019-11-11 15:50:05 +00:00
] = getApplicableReplacements (
filter ,
contextRuleName ,
cache ,
situation ,
rules ,
rule
)
2019-06-19 09:54:47 +00:00
2019-10-30 15:52:51 +00:00
if ( applicableReplacements . length ) {
2019-11-14 12:18:41 +00:00
if ( applicableReplacements . length > 1 ) {
console . warn ( `
Règle $ { rule . dottedName } : plusieurs remplacements valides ont été trouvés :
\ n \ t$ { applicableReplacements . map ( node => node . rawNode ) . join ( '\n\t' ) }
Par défaut , seul le premier s ' applique . Si vous voulez un autre comportement , vous pouvez :
- Restreindre son applicabilité via "applicable si" sur la règle de définition
- Restreindre sa portée en ajoutant une liste blanche ( via le mot clé "dans" ) ou une liste noire ( via le mot clé "sauf dans" )
` )
}
2019-11-04 10:03:01 +00:00
return applicableReplacements [ 0 ]
2019-10-30 15:52:51 +00:00
}
2019-11-14 12:18:41 +00:00
const addReplacementMissingVariable = node => ( {
... node ,
missingVariables : replacementMissingVariableList . reduce (
mergeMissing ,
node . missingVariables
)
} )
2019-10-30 15:52:51 +00:00
let dottedName = node . dottedName ,
// On va vérifier dans le cache courant, dict, si la variable n'a pas été déjà évaluée
// En effet, l'évaluation dans le cas d'une variable qui a une formule, est coûteuse !
cacheName = dottedName + ( filter ? '.' + filter : '' ) ,
cached = cache [ cacheName ]
2019-11-14 12:18:41 +00:00
if ( cached ) return addReplacementMissingVariable ( cached )
2019-11-05 15:02:08 +00:00
let cacheNode = ( nodeValue , missingVariables , explanation ) => {
2019-10-30 15:52:51 +00:00
cache [ cacheName ] = {
... node ,
nodeValue ,
2019-11-28 11:03:23 +00:00
... ( explanation && {
explanation
} ) ,
... ( explanation ? . unit && { unit : explanation . unit } ) ,
2019-10-30 15:52:51 +00:00
missingVariables
2019-07-03 16:49:31 +00:00
}
2019-11-14 12:18:41 +00:00
return addReplacementMissingVariable ( cache [ cacheName ] )
2019-10-30 15:52:51 +00:00
}
2019-11-05 15:02:08 +00:00
const {
nodeValue : isApplicable ,
missingVariables : condMissingVariables
} = evaluateApplicability ( cache , situation , rules , rule )
if ( ! isApplicable ) {
2019-11-28 11:03:23 +00:00
return cacheNode ( isApplicable , condMissingVariables , rule )
2019-11-05 15:02:08 +00:00
}
const situationValue = getSituationValue ( situation , dottedName , rule )
if ( situationValue !== undefined ) {
2019-11-28 11:03:23 +00:00
const unit = getNodeDefaultUnit ( rule , cache )
2019-11-05 15:02:08 +00:00
return cacheNode ( situationValue , condMissingVariables , {
... rule ,
2019-11-28 11:03:23 +00:00
nodeValue : situationValue ,
unit
2019-11-05 15:02:08 +00:00
} )
}
2019-06-20 13:53:30 +00:00
2019-11-05 15:02:08 +00:00
if ( rule . formule != null ) {
const evaluation = evaluateNode ( cache , situation , rules , rule )
return cacheNode (
evaluation . nodeValue ,
evaluation . missingVariables ,
evaluation
)
2018-06-19 08:25:53 +00:00
}
2019-11-05 15:02:08 +00:00
return cacheNode ( null , { [ dottedName ] : rule . defaultValue ? 1 : 2 } )
2019-10-30 15:52:51 +00:00
}
2019-11-11 18:06:49 +00:00
2019-11-04 13:07:19 +00:00
export let parseReference = (
rules ,
rule ,
parsedRules ,
filter
) => partialReference => {
2019-10-30 15:52:51 +00:00
let dottedName = disambiguateRuleReference ( rules , rule , partialReference )
2019-11-04 13:07:19 +00:00
let inInversionFormula = rule . formule ? . [ 'inversion numérique' ]
2019-10-30 15:52:51 +00:00
let parsedRule =
parsedRules [ dottedName ] ||
// the 'inversion numérique' formula should not exist. The instructions to the evaluation should be enough to infer that an inversion is necessary (assuming it is possible, the client decides this)
( ! inInversionFormula &&
parseRule ( rules , findRuleByDottedName ( rules , dottedName ) , parsedRules ) )
2019-11-28 11:03:23 +00:00
const unit =
parsedRule . unit || parsedRule . formule ? . unit || parsedRule . defaultUnit
2018-06-19 08:25:53 +00:00
return {
2019-11-04 11:20:50 +00:00
evaluate : evaluateReference ( filter , rule . dottedName ) ,
2018-06-19 09:06:30 +00:00
//eslint-disable-next-line react/display-name
2019-11-28 11:03:23 +00:00
jsx : ( nodeValue , explanation , _ , nodeUnit ) => (
2019-07-20 16:30:42 +00:00
< >
< Leaf
classes = "variable filtered"
filter = { filter }
2019-10-30 15:52:51 +00:00
name = { partialReference }
2019-07-20 16:30:42 +00:00
dottedName = { dottedName }
nodeValue = { nodeValue }
2019-11-28 11:03:23 +00:00
unit = { nodeUnit || explanation ? . unit || unit }
2019-07-20 16:30:42 +00:00
/ >
< / >
2018-06-19 08:25:53 +00:00
) ,
2019-06-13 16:17:22 +00:00
name : partialReference ,
2019-07-01 15:59:57 +00:00
category : 'reference' ,
2019-10-30 15:52:51 +00:00
partialReference ,
2019-06-19 09:54:47 +00:00
dottedName ,
2019-11-28 11:03:23 +00:00
unit
2018-06-19 08:25:53 +00:00
}
}
2018-10-01 15:41:55 +00:00
// This function is a wrapper that can apply :
2019-11-28 11:03:23 +00:00
// - unit transformations to the value of the variable.
// See the unité-temporelle.yaml test suite for details
2018-10-01 15:41:55 +00:00
// - filters on the variable to select one part of the variable's 'composantes'
2019-10-30 15:52:51 +00:00
const evaluateTransforms = ( originalEval , rule , parseResult ) => (
cache ,
situation ,
parsedRules ,
node
) => {
// Filter transformation
let filteringSituation = name =>
name == 'sys.filter' ? parseResult . filter : situation ( name )
let filteredNode = originalEval (
2018-06-19 08:25:53 +00:00
cache ,
2019-10-30 15:52:51 +00:00
parseResult . filter ? filteringSituation : situation ,
2018-06-19 08:25:53 +00:00
parsedRules ,
node
2019-10-30 15:52:51 +00:00
)
2019-11-28 11:03:23 +00:00
const { explanation , nodeValue } = filteredNode
if ( ! explanation || nodeValue === null ) {
2019-10-30 15:52:51 +00:00
return filteredNode
}
2019-11-28 11:03:23 +00:00
const unit = parseResult . unit
if ( unit ) {
try {
return convertNodeToUnit ( unit , filteredNode )
} catch ( e ) {
typeWarning (
cache . _meta . contextRule ,
` Impossible de convertir la reference ' ${ filteredNode . name } ' ` ,
e
2019-10-30 15:52:51 +00:00
)
2019-11-28 11:03:23 +00:00
}
2019-10-30 15:52:51 +00:00
}
2019-11-28 11:03:23 +00:00
return filteredNode
2019-10-30 15:52:51 +00:00
}
export let parseReferenceTransforms = (
rules ,
rule ,
parsedRules
) => parseResult => {
const referenceName = parseResult . variable . fragments . join ( ' . ' )
2019-11-28 11:03:23 +00:00
let node = parseReference (
rules ,
rule ,
parsedRules ,
parseResult . filter
) ( referenceName )
2018-06-19 08:25:53 +00:00
return {
... node ,
2018-10-01 15:41:55 +00:00
// Decorate node with the composante filter (either who is paying, either tax free)
... ( parseResult . filter
? {
2019-11-04 13:07:19 +00:00
cotisation : {
... node . cotisation ,
'dû par' : parseResult . filter ,
'impôt sur le revenu' : parseResult . filter
}
}
2018-10-01 15:41:55 +00:00
: { } ) ,
2019-11-28 11:03:23 +00:00
evaluate : evaluateTransforms ( node . evaluate , rule , parseResult ) ,
unit : parseResult . unit || node . unit
2018-06-19 08:25:53 +00:00
}
}