2020-05-08 10:04:00 +00:00
import { Leaf } from './components/mecanisms/common'
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'
2020-04-23 07:30:03 +00:00
import { convertNodeToUnit } from './nodeUnits'
2020-05-08 10:04:00 +00:00
import parseRule from './parseRule'
2020-03-26 15:03:19 +00:00
import { disambiguateRuleReference } from './ruleUtils'
2020-09-09 11:31:34 +00:00
import { EvaluatedNode , ParsedRule } from './types'
2020-04-23 07:30:03 +00:00
import { areUnitConvertible , serializeUnit } from './units'
2020-05-08 10:04:00 +00:00
2020-10-08 09:21:50 +00:00
/ * *
* Statically filter out replacements from ` replaceBy ` .
* Note : whitelist and blacklist filtering are applicable to the replacement
* itself or any parent namespace .
* /
export const getApplicableReplacedBy = ( contextRuleName , replacedBy ) = >
replacedBy
2019-11-04 13:07:19 +00:00
. sort (
( replacement1 , replacement2 ) = >
2020-09-09 11:31:34 +00:00
+ ! ! replacement2 . whiteListedNames - + ! ! replacement1 . whiteListedNames
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 )
2020-10-08 09:21:50 +00:00
/ * *
* Filter - out and apply all possible replacements at runtime .
* /
const getApplicableReplacements = (
filter ,
contextRuleName ,
cache ,
situation ,
rules ,
rule : ParsedRule
) = > {
let missingVariableList : Array < EvaluatedNode [ ' missingVariables ' ] > = [ ]
if ( contextRuleName . startsWith ( '[evaluation]' ) ) {
return [ [ ] , [ ] ]
}
const applicableReplacements = getApplicableReplacedBy (
contextRuleName ,
rule . replacedBy
)
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 ]
2020-06-14 11:18:16 +00:00
const situationValue = situation [ referenceRule . dottedName ]
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
}
2020-04-23 07:30:03 +00:00
return situationValue ? . nodeValue !== false
2019-11-11 15:50:05 +00:00
} )
// 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 )
2020-09-09 11:31:34 +00:00
: evaluateReference ( filter , '' ) ( cache , situation , rules , referenceNode )
2019-11-04 10:03:01 +00:00
)
2019-11-28 11:03:23 +00:00
. map ( replacementNode = > {
2020-04-23 07:30:03 +00:00
const replacedRuleUnit = rule . unit
2019-11-28 11:03:23 +00:00
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 ]
}
2020-09-09 11:31:34 +00:00
const evaluateReference = ( filter : string , contextRuleName : string ) = > (
2019-11-11 15:50:05 +00:00
cache ,
situation ,
rules ,
node
) = > {
2020-09-09 11:31:34 +00:00
const rule = rules [ node . dottedName ]
2019-11-11 15:50:05 +00:00
// 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 ) {
2020-05-06 20:08:49 +00:00
// eslint-disable-next-line no-console
2019-11-14 12:18:41 +00:00
console . warn ( `
2020-05-19 16:01:16 +00:00
R è gle $ { rule . dottedName } : plusieurs remplacements valides ont é t é trouv é s :
\ n \ t $ { applicableReplacements . map ( node = > node . rawNode ) . join ( '\n\t' ) }
2019-11-14 12:18:41 +00:00
2020-05-19 16:01:16 +00:00
Par d é faut , seul le premier s ' applique . Si vous voulez un autre comportement , vous pouvez :
2019-11-14 12:18:41 +00:00
- 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
)
} )
2020-09-09 11:31:34 +00:00
const 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 !
const cacheName = dottedName + ( filter ? ' .' + filter : '' )
const cached = cache [ cacheName ]
2019-10-30 15:52:51 +00:00
2019-11-14 12:18:41 +00:00
if ( cached ) return addReplacementMissingVariable ( cached )
2020-09-09 11:31:34 +00:00
const cacheNode = (
nodeValue : EvaluatedNode [ 'nodeValue' ] ,
missingVariables : EvaluatedNode [ 'missingVariables' ] ,
explanation? : Record < string , unknown >
) = > {
2019-10-30 15:52:51 +00:00
cache [ cacheName ] = {
. . . node ,
nodeValue ,
2019-11-28 11:03:23 +00:00
. . . ( explanation && {
explanation
} ) ,
2020-04-23 07:30:03 +00:00
. . . ( explanation ? . temporalValue && {
temporalValue : explanation.temporalValue
} ) ,
2019-11-28 11:03:23 +00:00
. . . ( 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
}
2020-04-23 07:30:03 +00:00
const applicabilityEvaluation = evaluateApplicability (
cache ,
situation ,
rules ,
rule
)
if ( ! applicabilityEvaluation . nodeValue ) {
return cacheNode (
applicabilityEvaluation . nodeValue ,
applicabilityEvaluation . missingVariables ,
applicabilityEvaluation
)
2019-11-05 15:02:08 +00:00
}
2020-10-04 23:39:11 +00:00
if ( situation [ dottedName ] ) {
// Conditional evaluation is required because some mecanisms like
// "synchronisation" store raw JS objects in the situation.
const situationValue = situation [ dottedName ] ? . evaluate
? evaluateNode ( cache , situation , rules , situation [ dottedName ] )
: situation [ dottedName ]
2020-04-23 07:30:03 +00:00
const unit =
! situationValue . unit || serializeUnit ( situationValue . unit ) === ''
? rule . unit
: situationValue . unit
return cacheNode (
situationValue ? . nodeValue !== undefined
? situationValue . nodeValue
: situationValue ,
applicabilityEvaluation . missingVariables ,
{
. . . rule ,
. . . ( situationValue ? . nodeValue !== undefined && situationValue ) ,
unit
}
)
2019-11-05 15:02:08 +00:00
}
2019-06-20 13:53:30 +00:00
2020-05-06 20:08:49 +00:00
if ( rule . defaultValue != null ) {
const evaluation = evaluateNode ( cache , situation , rules , rule . defaultValue )
return cacheNode ( evaluation . nodeValue ? ? evaluation , {
. . . evaluation . missingVariables ,
[ dottedName ] : 1
} )
}
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 ,
2020-09-09 11:31:34 +00:00
evaluation
2019-11-05 15:02:08 +00:00
)
2018-06-19 08:25:53 +00:00
}
2019-11-05 15:02:08 +00:00
2020-09-23 12:34:26 +00:00
return cacheNode ( null , { [ dottedName ] : 2 } )
2019-10-30 15:52:51 +00:00
}
2019-11-11 18:06:49 +00:00
2020-09-09 11:31:34 +00:00
export const parseReference = (
2019-11-04 13:07:19 +00:00
rules ,
rule ,
parsedRules ,
filter
) = > partialReference = > {
2020-09-09 11:31:34 +00:00
const dottedName = disambiguateRuleReference (
2020-03-26 15:03:19 +00:00
rules ,
rule . dottedName ,
partialReference
)
2019-10-30 15:52:51 +00:00
2020-09-09 11:31:34 +00:00
const inInversionFormula = rule . formule ? . [ 'inversion numérique' ]
2019-10-30 15:52:51 +00:00
2020-09-09 11:31:34 +00:00
const parsedRule =
2019-10-30 15:52:51 +00:00
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)
2020-03-26 15:03:19 +00:00
( ! inInversionFormula && parseRule ( rules , dottedName , parsedRules ) )
2020-04-23 07:30:03 +00:00
const unit = parsedRule . unit
2018-06-19 08:25:53 +00:00
return {
2019-11-04 11:20:50 +00:00
evaluate : evaluateReference ( filter , rule . dottedName ) ,
2020-09-09 11:31:34 +00:00
jsx : Leaf ,
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 ,
2020-04-23 07:30:03 +00:00
explanation : parsedRule ,
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'
2020-05-19 16:01:16 +00:00
const evaluateTransforms = ( originalEval , _ , parseResult ) = > (
2019-10-30 15:52:51 +00:00
cache ,
situation ,
parsedRules ,
node
) = > {
// Filter transformation
2020-09-09 11:31:34 +00:00
const filteringSituation = {
. . . situation ,
'_meta.filter' : parseResult . filter
}
const 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
}
2020-09-09 11:31:34 +00:00
export const parseReferenceTransforms = (
2019-10-30 15:52:51 +00:00
rules ,
rule ,
parsedRules
) = > parseResult = > {
const referenceName = parseResult . variable . fragments . join ( ' . ' )
2020-09-09 11:31:34 +00:00
const node = parseReference (
2019-11-28 11:03:23 +00:00
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 : {
2020-09-09 11:31:34 +00:00
. . . ( node as any ) . cotisation ,
2019-11-04 13:07:19 +00:00
'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
}
}