⚙️ Ajout d'un type de nœud serialisable
Ce commit parachève la sortie de l'ensemble des functions "evaluate" de l'AST et ajoute un "nodeKind" sur chaque nœud afin de les associer à la bonne function d'évaluation. L'API pour les mécanismes pourra être améliorée afin de ne pas appeler `registerEvaluationFunction` sur chaque mécanisme mais en standardisant l'interface exportée par les mécanismes, par exemple export { name, parse, evaluate, render } Par ailleurs il devrait être facile de sortir les fonctions `jsx` en se basant sur les mêmes "nodeKind". Enfin, il faudra nettoyer l'AST pour supprimer les attributs inutilisés et ajouter du typage fort.pull/1193/head
parent
33ff99bc30
commit
c66e529fb7
|
@ -4,5 +4,8 @@
|
|||
"spellright.documentTypes": ["yaml", "git-commit", "markdown"],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"editor.tabSize": 2,
|
||||
"eslint.enable": true
|
||||
"eslint.enable": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
import { EvaluatedNode, ParsedRule } from '.'
|
||||
import { typeWarning } from './error'
|
||||
import { evaluateApplicability } from './evaluateRule'
|
||||
import { mergeMissing, evaluateNode } from './evaluation'
|
||||
import { convertNodeToUnit } from './nodeUnits'
|
||||
import { serializeUnit, areUnitConvertible } from './units'
|
||||
|
||||
export const evaluateReference = (cache, situation, rules, node) => {
|
||||
const 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,
|
||||
replacementMissingVariableList
|
||||
] = getApplicableReplacements(
|
||||
node.explanation?.contextRuleName ?? '',
|
||||
cache,
|
||||
situation,
|
||||
rules,
|
||||
rule
|
||||
)
|
||||
|
||||
if (applicableReplacements.length) {
|
||||
if (applicableReplacements.length > 1) {
|
||||
// eslint-disable-next-line no-console
|
||||
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")
|
||||
`)
|
||||
}
|
||||
return applicableReplacements[0]
|
||||
}
|
||||
const addReplacementMissingVariable = node => ({
|
||||
...node,
|
||||
missingVariables: replacementMissingVariableList.reduce(
|
||||
mergeMissing,
|
||||
node.missingVariables
|
||||
)
|
||||
})
|
||||
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 + (node.explanation.filter ? ' .' + node.explanation.filter : '')
|
||||
const cached = cache[cacheName]
|
||||
|
||||
if (cached) return addReplacementMissingVariable(cached)
|
||||
|
||||
const cacheNode = (
|
||||
nodeValue: EvaluatedNode['nodeValue'],
|
||||
missingVariables: EvaluatedNode['missingVariables'],
|
||||
explanation?: Record<string, unknown>
|
||||
) => {
|
||||
cache[cacheName] = {
|
||||
...node,
|
||||
nodeValue,
|
||||
...(explanation && {
|
||||
explanation
|
||||
}),
|
||||
...(explanation?.temporalValue && {
|
||||
temporalValue: explanation.temporalValue
|
||||
}),
|
||||
...(explanation?.unit && { unit: explanation.unit }),
|
||||
missingVariables
|
||||
}
|
||||
return addReplacementMissingVariable(cache[cacheName])
|
||||
}
|
||||
const applicabilityEvaluation = evaluateApplicability(
|
||||
cache,
|
||||
situation,
|
||||
rules,
|
||||
rule
|
||||
)
|
||||
if (!applicabilityEvaluation.nodeValue) {
|
||||
return cacheNode(
|
||||
applicabilityEvaluation.nodeValue,
|
||||
applicabilityEvaluation.missingVariables,
|
||||
applicabilityEvaluation
|
||||
)
|
||||
}
|
||||
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]
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (rule.defaultValue != null) {
|
||||
const evaluation = evaluateNode(cache, situation, rules, rule.defaultValue)
|
||||
return cacheNode(evaluation.nodeValue ?? evaluation, {
|
||||
...evaluation.missingVariables,
|
||||
[dottedName]: 1
|
||||
})
|
||||
}
|
||||
|
||||
if (rule.formule != null) {
|
||||
const evaluation = evaluateNode(cache, situation, rules, rule)
|
||||
return cacheNode(
|
||||
evaluation.nodeValue,
|
||||
evaluation.missingVariables,
|
||||
evaluation
|
||||
)
|
||||
}
|
||||
|
||||
return cacheNode(null, { [dottedName]: 2 })
|
||||
}
|
||||
|
||||
// This function is a wrapper that can apply :
|
||||
// - unit transformations to the value of the variable.
|
||||
// See the unité-temporelle.yaml test suite for details
|
||||
// - filters on the variable to select one part of the variable's 'composantes'
|
||||
|
||||
export const evaluateReferenceTransforms = (
|
||||
cache,
|
||||
situation,
|
||||
parsedRules,
|
||||
node
|
||||
) => {
|
||||
// Filter transformation
|
||||
const filteringSituation = {
|
||||
...situation,
|
||||
'_meta.filter': node.explanation.filter
|
||||
}
|
||||
const filteredNode = evaluateNode(
|
||||
cache,
|
||||
node.explanation.filter ? filteringSituation : situation,
|
||||
parsedRules,
|
||||
node.explanation.originalNode
|
||||
)
|
||||
const { explanation, nodeValue } = filteredNode
|
||||
if (!explanation || nodeValue === null) {
|
||||
return filteredNode
|
||||
}
|
||||
const unit = node.explanation.unit
|
||||
if (unit) {
|
||||
try {
|
||||
return convertNodeToUnit(unit, filteredNode)
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
cache._meta.contextRule,
|
||||
`Impossible de convertir la reference '${filteredNode.name}'`,
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredNode
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
.sort(
|
||||
(replacement1, replacement2) =>
|
||||
+!!replacement2.whiteListedNames - +!!replacement1.whiteListedNames
|
||||
)
|
||||
.filter(
|
||||
({ whiteListedNames }) =>
|
||||
!whiteListedNames ||
|
||||
whiteListedNames.some(name => contextRuleName.startsWith(name))
|
||||
)
|
||||
.filter(
|
||||
({ blackListedNames }) =>
|
||||
!blackListedNames ||
|
||||
blackListedNames.every(name => !contextRuleName.startsWith(name))
|
||||
)
|
||||
.filter(({ referenceNode }) => contextRuleName !== referenceNode.dottedName)
|
||||
|
||||
/**
|
||||
* Filter-out and apply all possible replacements at runtime.
|
||||
*/
|
||||
const getApplicableReplacements = (
|
||||
contextRuleName,
|
||||
cache,
|
||||
situation,
|
||||
rules,
|
||||
rule: ParsedRule
|
||||
) => {
|
||||
let missingVariableList: Array<EvaluatedNode['missingVariables']> = []
|
||||
if (contextRuleName.startsWith('[evaluation]')) {
|
||||
return [[], []]
|
||||
}
|
||||
const applicableReplacements = getApplicableReplacedBy(
|
||||
contextRuleName,
|
||||
rule.replacedBy
|
||||
)
|
||||
// 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
|
||||
.filter(({ referenceNode }) => {
|
||||
const referenceRule = rules[referenceNode.dottedName]
|
||||
const situationValue = situation[referenceRule.dottedName]
|
||||
if (referenceNode.question && situationValue == null) {
|
||||
missingVariableList.push({ [referenceNode.dottedName]: 1 })
|
||||
}
|
||||
return situationValue?.nodeValue !== 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
|
||||
)
|
||||
missingVariableList.push(missingVariables)
|
||||
return isApplicable
|
||||
})
|
||||
.map(({ referenceNode, replacementNode }) =>
|
||||
replacementNode != null
|
||||
? evaluateNode(cache, situation, rules, replacementNode)
|
||||
: evaluateReference(cache, situation, rules, referenceNode)
|
||||
)
|
||||
.map(replacementNode => {
|
||||
const replacedRuleUnit = rule.unit
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
missingVariableList = missingVariableList.filter(
|
||||
missingVariables => !!Object.keys(missingVariables).length
|
||||
)
|
||||
|
||||
return [applicableReplacements, missingVariableList]
|
||||
}
|
|
@ -69,7 +69,26 @@ export const evaluateApplicability = (
|
|||
}
|
||||
}
|
||||
|
||||
export default (cache, situation, parsedRules, node) => {
|
||||
export const evaluateFormula = (cache, situation, parsedRules, node) => {
|
||||
const explanation = evaluateNode(
|
||||
cache,
|
||||
situation,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
),
|
||||
{ nodeValue, unit, missingVariables, temporalValue } = explanation
|
||||
|
||||
return {
|
||||
...node,
|
||||
nodeValue,
|
||||
unit,
|
||||
missingVariables,
|
||||
explanation,
|
||||
temporalValue
|
||||
}
|
||||
}
|
||||
|
||||
export const evaluateRule = (cache, situation, parsedRules, node) => {
|
||||
cache._meta.contextRule.push(node.dottedName)
|
||||
const applicabilityEvaluation = evaluateApplicability(
|
||||
cache,
|
||||
|
@ -103,7 +122,6 @@ export default (cache, situation, parsedRules, node) => {
|
|||
bonus(condMissing, !!Object.keys(condMissing).length),
|
||||
evaluatedFormula.missingVariables
|
||||
)
|
||||
// console.log(node.dottedName, evaluatedFormula.unit)
|
||||
|
||||
const temporalValue = evaluatedFormula.temporalValue
|
||||
cache._meta.contextRule.pop()
|
||||
|
@ -118,3 +136,32 @@ export default (cache, situation, parsedRules, node) => {
|
|||
missingVariables
|
||||
}
|
||||
}
|
||||
|
||||
export const evaluateDisabledBy = (cache, situation, parsedRules, node) => {
|
||||
const isDisabledBy = node.explanation.isDisabledBy.map(disablerNode =>
|
||||
evaluateNode(cache, situation, parsedRules, disablerNode)
|
||||
)
|
||||
const nodeValue = isDisabledBy.some(
|
||||
x => x.nodeValue !== false && x.nodeValue !== null
|
||||
)
|
||||
const explanation = { ...node.explanation, isDisabledBy }
|
||||
return {
|
||||
...node,
|
||||
explanation,
|
||||
nodeValue,
|
||||
missingVariables: mergeAllMissing(isDisabledBy)
|
||||
}
|
||||
}
|
||||
|
||||
export const evaluateCondition = (cache, situation, parsedRules, node) => {
|
||||
const explanation = evaluateNode(
|
||||
cache,
|
||||
situation,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
)
|
||||
const nodeValue = explanation.nodeValue
|
||||
const missingVariables = explanation.missingVariables
|
||||
|
||||
return { ...node, nodeValue, explanation, missingVariables }
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import {
|
||||
add,
|
||||
evolve,
|
||||
filter,
|
||||
fromPairs,
|
||||
keys,
|
||||
map,
|
||||
mergeWith,
|
||||
reduce,
|
||||
dissoc
|
||||
} from 'ramda'
|
||||
import { add, evolve, fromPairs, keys, map, mergeWith, reduce } from 'ramda'
|
||||
import React from 'react'
|
||||
import { typeWarning } from './error'
|
||||
import {
|
||||
evaluateReference,
|
||||
evaluateReferenceTransforms
|
||||
} from './evaluateReference'
|
||||
import {
|
||||
evaluateCondition,
|
||||
evaluateDisabledBy,
|
||||
evaluateFormula,
|
||||
evaluateRule
|
||||
} from './evaluateRule'
|
||||
import { convertNodeToUnit, simplifyNodeUnit } from './nodeUnits'
|
||||
import {
|
||||
concatTemporals,
|
||||
|
@ -21,7 +21,7 @@ import {
|
|||
temporalAverage,
|
||||
zipTemporals
|
||||
} from './temporal'
|
||||
import { EvaluatedNode, ParsedRule, ParsedRules } from './types'
|
||||
import { EvaluatedNode } from './types'
|
||||
|
||||
export const makeJsx = (node: EvaluatedNode): JSX.Element => {
|
||||
const Component = node.jsx
|
||||
|
@ -38,7 +38,12 @@ export const mergeMissing = (left, right) =>
|
|||
mergeWith(add, left || {}, right || {})
|
||||
|
||||
export const evaluateNode = (cache, situation, parsedRules, node) => {
|
||||
return node.evaluate(cache, situation, parsedRules, node)
|
||||
if (!node.nodeKind) {
|
||||
throw Error('A node to evaluate must have a "nodeKind" attribute')
|
||||
} else if (!evaluationFunctions[node.nodeKind]) {
|
||||
throw Error(`Unknown "nodeKind": ${node.nodeKind}`)
|
||||
}
|
||||
return evaluationFunctions[node.nodeKind](cache, situation, parsedRules, node)
|
||||
}
|
||||
|
||||
function convertNodesToSameUnit(nodes, contextRule, mecanismName) {
|
||||
|
@ -106,34 +111,19 @@ export const evaluateArray = (reducer, start) => (
|
|||
}
|
||||
}
|
||||
|
||||
export const evaluateArrayWithFilter = (evaluationFilter, reducer, start) => (
|
||||
cache,
|
||||
situation,
|
||||
parsedRules,
|
||||
node
|
||||
) => {
|
||||
return evaluateArray(reducer, start)(
|
||||
cache,
|
||||
dissoc('_meta.filter', situation),
|
||||
parsedRules,
|
||||
{
|
||||
...node,
|
||||
explanation: filter(evaluationFilter(situation), node.explanation)
|
||||
}
|
||||
)
|
||||
}
|
||||
export const defaultNode = (nodeValue: EvaluatedNode['nodeValue']) => ({
|
||||
nodeValue,
|
||||
// eslint-disable-next-line
|
||||
jsx: ({ nodeValue }: EvaluatedNode) => (
|
||||
<span className="value">{nodeValue}</span>
|
||||
),
|
||||
isDefault: true,
|
||||
nodeKind: 'defaultNode'
|
||||
})
|
||||
|
||||
export const defaultNode = (nodeValue: EvaluatedNode['nodeValue']) => {
|
||||
const defaultNode = {
|
||||
nodeValue,
|
||||
// eslint-disable-next-line
|
||||
jsx: ({ nodeValue }: EvaluatedNode) => (
|
||||
<span className="value">{nodeValue}</span>
|
||||
),
|
||||
isDefault: true
|
||||
}
|
||||
return { ...defaultNode, evaluate: () => defaultNode }
|
||||
}
|
||||
const evaluateDefaultNode = (cache, situation, parsedRules, node) => node
|
||||
const evaluateExplanationNode = (cache, situation, parsedRules, node) =>
|
||||
evaluateNode(cache, situation, parsedRules, node.explanation)
|
||||
|
||||
export const parseObject = (recurse, objectShape, value) => {
|
||||
const recurseOne = key => defaultValue => {
|
||||
|
@ -212,3 +202,24 @@ export const evaluateObject = (objectShape, effect) => (
|
|||
temporalExplanation
|
||||
}
|
||||
}
|
||||
|
||||
const evaluationFunctions = {
|
||||
rule: evaluateRule,
|
||||
formula: evaluateFormula,
|
||||
disabledBy: evaluateDisabledBy,
|
||||
condition: evaluateCondition,
|
||||
reference: evaluateReference,
|
||||
referenceWithTransforms: evaluateReferenceTransforms,
|
||||
parentDependencies: evaluateExplanationNode,
|
||||
constant: evaluateDefaultNode,
|
||||
defaultNode: evaluateDefaultNode
|
||||
}
|
||||
|
||||
export function registerEvaluationFunction(nodeKind, evaluationFunction) {
|
||||
if (evaluationFunctions[nodeKind]) {
|
||||
throw Error(
|
||||
`Multiple evaluation functions registered for the nodeKind \x1b[4m${nodeKind}`
|
||||
)
|
||||
}
|
||||
evaluationFunctions[nodeKind] = evaluationFunction
|
||||
}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import React from 'react'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { bonus, evaluateNode, makeJsx, mergeMissing } from '../evaluation'
|
||||
import {
|
||||
bonus,
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
|
||||
function MecanismApplicable({ explanation }) {
|
||||
return (
|
||||
|
@ -46,13 +52,16 @@ export default function Applicable(recurse, v) {
|
|||
condition: recurse(v['applicable si'])
|
||||
}
|
||||
return {
|
||||
evaluate,
|
||||
// evaluate,
|
||||
jsx: MecanismApplicable,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: Applicable.name,
|
||||
name: Applicable.nom,
|
||||
nodeKind: Applicable.nom,
|
||||
unit: explanation.valeur.unit
|
||||
}
|
||||
}
|
||||
|
||||
Applicable.nom = 'applicable si'
|
||||
|
||||
registerEvaluationFunction(Applicable.nom, evaluate)
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import React from 'react'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { evaluateNode, makeJsx, mergeAllMissing } from '../evaluation'
|
||||
import {
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { EvaluatedNode } from '../types'
|
||||
|
||||
export type ArrondiExplanation = {
|
||||
|
@ -61,14 +66,16 @@ export default function Arrondi(recurse, v) {
|
|||
arrondi: recurse(v.arrondi)
|
||||
}
|
||||
return {
|
||||
evaluate,
|
||||
jsx: MecanismArrondi,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'arrondi',
|
||||
nodeKind: Arrondi.nom,
|
||||
type: 'numeric',
|
||||
unit: explanation.valeur.unit
|
||||
}
|
||||
}
|
||||
|
||||
Arrondi.nom = 'arrondi'
|
||||
|
||||
registerEvaluationFunction(Arrondi.nom, evaluate)
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import Barème from '../components/mecanisms/Barème'
|
||||
import { evaluationError } from '../error'
|
||||
import { defaultNode, evaluateNode, mergeAllMissing } from '../evaluation'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateNode,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import {
|
||||
liftTemporal2,
|
||||
liftTemporalNode,
|
||||
|
@ -22,10 +27,10 @@ export default function parse(parse, v) {
|
|||
}
|
||||
return {
|
||||
explanation,
|
||||
evaluate,
|
||||
jsx: Barème,
|
||||
category: 'mecanism',
|
||||
name: 'barème',
|
||||
nodeKind: 'barème',
|
||||
type: 'numeric',
|
||||
unit: explanation.assiette.unit
|
||||
}
|
||||
|
@ -126,3 +131,5 @@ const evaluate = (
|
|||
unit: assiette.unit
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('barème', evaluate)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { add, dissoc, filter, objOf } from 'ramda'
|
||||
import { evaluateArray, registerEvaluationFunction } from '../evaluation'
|
||||
import { inferUnit } from '../units'
|
||||
import Composantes from '../components/mecanisms/Composantes'
|
||||
|
||||
export const evaluateComposantes = (cache, situation, parsedRules, node) => {
|
||||
const evaluationFilter = c =>
|
||||
!situation['_meta.filter'] ||
|
||||
!c.composante ||
|
||||
((!c.composante['dû par'] ||
|
||||
!['employeur', 'salarié'].includes(situation['_meta.filter']) ||
|
||||
c.composante['dû par'] == situation['_meta.filter']) &&
|
||||
(!c.composante['impôt sur le revenu'] ||
|
||||
!['déductible', 'non déductible'].includes(situation['_meta.filter']) ||
|
||||
c.composante['impôt sur le revenu'] == situation['_meta.filter']))
|
||||
|
||||
return evaluateArray(add, 0)(
|
||||
cache,
|
||||
dissoc('_meta.filter', situation),
|
||||
parsedRules,
|
||||
{
|
||||
...node,
|
||||
explanation: filter(evaluationFilter, node.explanation)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const decompose = (recurse, k, v) => {
|
||||
const subProps = dissoc<Record<string, unknown>>('composantes', v)
|
||||
const explanation = v.composantes.map(c => ({
|
||||
...recurse(
|
||||
objOf(k, {
|
||||
...subProps,
|
||||
...dissoc<Record<string, unknown>>('attributs', c)
|
||||
})
|
||||
),
|
||||
composante: c.nom ? { nom: c.nom } : c.attributs
|
||||
}))
|
||||
|
||||
return {
|
||||
explanation,
|
||||
jsx: Composantes,
|
||||
nodeKind: 'composantes',
|
||||
category: 'mecanism',
|
||||
name: 'composantes',
|
||||
type: 'numeric',
|
||||
unit: inferUnit(
|
||||
'+',
|
||||
explanation.map(e => e.unit)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('composantes', evaluateComposantes)
|
|
@ -1,7 +1,12 @@
|
|||
import { any, equals, is, map, pluck } from 'ramda'
|
||||
import { is, map } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { evaluateNode, makeJsx, mergeAllMissing } from '../evaluation'
|
||||
import {
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
|
||||
const evaluate = (cache, situation, parsedRules, node) => {
|
||||
const [nodeValue, explanation] = node.explanation.reduce(
|
||||
|
@ -42,11 +47,13 @@ export const mecanismAllOf = (recurse, v) => {
|
|||
)
|
||||
|
||||
return {
|
||||
evaluate: evaluate,
|
||||
jsx,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'toutes ces conditions',
|
||||
nodeKind: 'toutes ces conditions',
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('toutes ces conditions', evaluate)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { any, equals, is, map, max, mergeWith, pluck, reduce } from 'ramda'
|
||||
import { is, map, max, mergeWith, reduce } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { collectNodeMissing, evaluateNode, makeJsx } from '../evaluation'
|
||||
import {
|
||||
collectNodeMissing,
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
|
||||
const evaluate = (cache, situation, parsedRules, node) => {
|
||||
const evaluateOne = child =>
|
||||
|
@ -40,11 +45,13 @@ export const mecanismOneOf = (recurse, v) => {
|
|||
)
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
jsx,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'une de ces conditions',
|
||||
nodeKind: 'une de ces conditions',
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('une de ces conditions', evaluate)
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import React from 'react'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { convertToDate, convertToString } from '../date'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
parseObject
|
||||
parseObject,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { parseUnit } from '../units'
|
||||
import React from 'react'
|
||||
|
||||
function MecanismDurée({ nodeValue, explanation, unit }) {
|
||||
return (
|
||||
|
@ -70,12 +71,14 @@ export default (recurse, v) => {
|
|||
const explanation = parseObject(recurse, objectShape, v)
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
jsx: MecanismDurée,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'Durée',
|
||||
nodeKind: 'durée',
|
||||
type: 'numeric',
|
||||
unit: parseUnit('jours')
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('durée', evaluate)
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { lensPath, over } from 'ramda'
|
||||
import grille from '../components/mecanisms/Grille'
|
||||
import { defaultNode, evaluateNode, mergeAllMissing } from '../evaluation'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateNode,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import {
|
||||
liftTemporal2,
|
||||
liftTemporalNode,
|
||||
|
@ -24,10 +29,10 @@ export default function parse(parse, v) {
|
|||
}
|
||||
return {
|
||||
explanation,
|
||||
evaluate,
|
||||
jsx: grille,
|
||||
category: 'mecanism',
|
||||
name: 'grille',
|
||||
nodeKind: 'grille',
|
||||
type: 'numeric',
|
||||
unit: explanation.tranches[0].montant.unit
|
||||
}
|
||||
|
@ -109,3 +114,5 @@ const evaluate = (
|
|||
unit: activeTranches[0].value[0]?.unit ?? node.unit
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('grille', evaluate)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { evaluateNode } from '../evaluation'
|
||||
import InversionNumérique from '../components/mecanisms/InversionNumérique'
|
||||
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import uniroot from '../uniroot'
|
||||
import InversionNumérique from '../components/mecanisms/InversionNumérique'
|
||||
import { parseUnit } from '../units'
|
||||
|
||||
export const evaluateInversion = (oldCache, situation, parsedRules, node) => {
|
||||
|
@ -94,7 +94,6 @@ export const mecanismInversion = dottedName => (recurse, v) => {
|
|||
)
|
||||
}
|
||||
return {
|
||||
evaluate: evaluateInversion,
|
||||
unit: v.unité && parseUnit(v.unité),
|
||||
explanation: {
|
||||
ruleToInverse: dottedName,
|
||||
|
@ -104,6 +103,9 @@ export const mecanismInversion = dottedName => (recurse, v) => {
|
|||
jsx: InversionNumérique,
|
||||
category: 'mecanism',
|
||||
name: 'inversion numérique',
|
||||
nodeKind: 'inversion numérique',
|
||||
type: 'numeric'
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('inversion numérique', evaluateInversion)
|
||||
|
|
|
@ -1,24 +1,14 @@
|
|||
import React from 'react'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { evaluateArray, makeJsx } from '../evaluation'
|
||||
import {
|
||||
evaluateArray,
|
||||
makeJsx,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
|
||||
export const mecanismMax = (recurse, v) => {
|
||||
const explanation = v.map(recurse)
|
||||
|
||||
const max = (a, b) => {
|
||||
if (a === false) {
|
||||
return b
|
||||
}
|
||||
if (b === false) {
|
||||
return a
|
||||
}
|
||||
if (a === null || b === null) {
|
||||
return null
|
||||
}
|
||||
return Math.max(a, b)
|
||||
}
|
||||
const evaluate = evaluateArray(max, false)
|
||||
|
||||
const jsx = ({ nodeValue, explanation, unit }) => (
|
||||
<Mecanism name="le maximum de" value={nodeValue} unit={unit}>
|
||||
<ul>
|
||||
|
@ -33,12 +23,28 @@ export const mecanismMax = (recurse, v) => {
|
|||
)
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
jsx,
|
||||
explanation,
|
||||
type: 'numeric',
|
||||
category: 'mecanism',
|
||||
name: 'le maximum de',
|
||||
nodeKind: 'maximum',
|
||||
unit: explanation[0].unit
|
||||
}
|
||||
}
|
||||
|
||||
const max = (a, b) => {
|
||||
if (a === false) {
|
||||
return b
|
||||
}
|
||||
if (b === false) {
|
||||
return a
|
||||
}
|
||||
if (a === null || b === null) {
|
||||
return null
|
||||
}
|
||||
return Math.max(a, b)
|
||||
}
|
||||
const evaluate = evaluateArray(max, false)
|
||||
|
||||
registerEvaluationFunction('maximum', evaluate)
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { min } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { evaluateArray, makeJsx } from '../evaluation'
|
||||
import {
|
||||
evaluateArray,
|
||||
makeJsx,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
|
||||
export const mecanismMin = (recurse, v) => {
|
||||
const explanation = v.map(recurse)
|
||||
const evaluate = evaluateArray(min, Infinity)
|
||||
const jsx = ({ nodeValue, explanation, unit }) => (
|
||||
<Mecanism name="le minimum de" value={nodeValue} unit={unit}>
|
||||
<ul>
|
||||
|
@ -19,12 +22,16 @@ export const mecanismMin = (recurse, v) => {
|
|||
</Mecanism>
|
||||
)
|
||||
return {
|
||||
evaluate,
|
||||
jsx,
|
||||
explanation,
|
||||
type: 'numeric',
|
||||
category: 'mecanism',
|
||||
name: 'le minimum de',
|
||||
nodeKind: 'minimum',
|
||||
unit: explanation[0].unit
|
||||
}
|
||||
}
|
||||
|
||||
const evaluate = evaluateArray(min, Infinity)
|
||||
|
||||
registerEvaluationFunction('minimum', evaluate)
|
||||
|
|
|
@ -4,8 +4,8 @@ import {
|
|||
bonus,
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
mergeMissing
|
||||
mergeMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
|
||||
function MecanismNonApplicable({ explanation }) {
|
||||
|
@ -54,13 +54,15 @@ export default function NonApplicable(recurse, v) {
|
|||
condition: recurse(v['non applicable si'])
|
||||
}
|
||||
return {
|
||||
evaluate,
|
||||
jsx: MecanismNonApplicable,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'non applicable',
|
||||
nodeKind: 'non applicable',
|
||||
unit: explanation.valeur.unit
|
||||
}
|
||||
}
|
||||
|
||||
NonApplicable.nom = 'non applicable si'
|
||||
|
||||
registerEvaluationFunction('non applicable', evaluate)
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import { registerEvaluationFunction } from '../evaluation'
|
||||
|
||||
// TODO : This isn't a real mecanism, cf. #963
|
||||
export const mecanismOnePossibility = dottedName => (recurse, v) => ({
|
||||
...v,
|
||||
'une possibilité': 'oui',
|
||||
evaluate: (cache, situation, parsedRules, node) => ({
|
||||
...node,
|
||||
missingVariables: { [dottedName]: 1 }
|
||||
})
|
||||
context: dottedName,
|
||||
nodeKind: 'une possibilité'
|
||||
})
|
||||
|
||||
registerEvaluationFunction(
|
||||
'une possibilité',
|
||||
(cache, situation, parsedRules, node) => ({
|
||||
...node,
|
||||
missingVariables: { [node.context]: 1 }
|
||||
})
|
||||
)
|
||||
|
|
|
@ -1,86 +1,31 @@
|
|||
import { map } from 'ramda'
|
||||
import {
|
||||
add,
|
||||
divide,
|
||||
equals,
|
||||
fromPairs,
|
||||
gt,
|
||||
gte,
|
||||
lt,
|
||||
lte,
|
||||
map,
|
||||
multiply,
|
||||
subtract
|
||||
} from 'ramda'
|
||||
import React from 'react'
|
||||
import { Operation } from '../components/mecanisms/common'
|
||||
import { convertToDate } from '../date'
|
||||
import { typeWarning } from '../error'
|
||||
import { evaluateNode, makeJsx, mergeAllMissing } from '../evaluation'
|
||||
import {
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import { liftTemporal2, pureTemporal, temporalAverage } from '../temporal'
|
||||
import { inferUnit, serializeUnit } from '../units'
|
||||
|
||||
export default (k, operatorFunction, symbol) => (recurse, v) => {
|
||||
const evaluate = (cache, situation, parsedRules, node) => {
|
||||
const explanation = map(
|
||||
node => evaluateNode(cache, situation, parsedRules, node),
|
||||
node.explanation
|
||||
)
|
||||
let [node1, node2] = explanation
|
||||
const missingVariables = mergeAllMissing([node1, node2])
|
||||
|
||||
if (node1.nodeValue == null || node2.nodeValue == null) {
|
||||
return { ...node, nodeValue: null, explanation, missingVariables }
|
||||
}
|
||||
if (!['∕', '×'].includes(node.operator)) {
|
||||
try {
|
||||
if (node1.unit) {
|
||||
node2 = convertNodeToUnit(node1.unit, node2)
|
||||
} else if (node2.unit) {
|
||||
node1 = convertNodeToUnit(node2.unit, node1)
|
||||
}
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
cache._meta.contextRule,
|
||||
`Dans l'expression '${
|
||||
node.operator
|
||||
}', la partie gauche (unité: ${serializeUnit(
|
||||
node1.unit
|
||||
)}) n'est pas compatible avec la partie droite (unité: ${serializeUnit(
|
||||
node2.unit
|
||||
)})`,
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
const baseNode = {
|
||||
...node,
|
||||
explanation,
|
||||
unit: inferUnit(k, [node1.unit, node2.unit]),
|
||||
missingVariables
|
||||
}
|
||||
|
||||
const temporalValue = liftTemporal2(
|
||||
(a: string | false, b: string | false) => {
|
||||
if (!['≠', '='].includes(node.operator) && a === false && b === false) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
['<', '>', '≤', '≥', '∕', '×'].includes(node.operator) &&
|
||||
(a === false || b === false)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
a !== false &&
|
||||
b !== false &&
|
||||
['≠', '=', '<', '>', '≤', '≥'].includes(node.operator) &&
|
||||
[a, b].every(value => value.match?.(/[\d]{2}\/[\d]{2}\/[\d]{4}/))
|
||||
) {
|
||||
return operatorFunction(convertToDate(a), convertToDate(b))
|
||||
}
|
||||
return operatorFunction(a, b)
|
||||
},
|
||||
node1.temporalValue ?? pureTemporal(node1.nodeValue),
|
||||
node2.temporalValue ?? pureTemporal(node2.nodeValue)
|
||||
)
|
||||
const nodeValue = temporalAverage(temporalValue, baseNode.unit)
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
nodeValue,
|
||||
...(temporalValue.length > 1 && { temporalValue })
|
||||
}
|
||||
}
|
||||
|
||||
const parse = (k, symbol) => (recurse, v) => {
|
||||
const explanation = v.explanation.map(recurse)
|
||||
const [node1, node2] = explanation
|
||||
const unit = inferUnit(k, [node1.unit, node2.unit])
|
||||
|
@ -96,10 +41,109 @@ export default (k, operatorFunction, symbol) => (recurse, v) => {
|
|||
|
||||
return {
|
||||
...v,
|
||||
evaluate,
|
||||
jsx,
|
||||
nodeKind: 'operation',
|
||||
operationKind: k,
|
||||
operator: symbol || k,
|
||||
explanation,
|
||||
unit
|
||||
}
|
||||
}
|
||||
|
||||
const evaluate = (cache, situation, parsedRules, node) => {
|
||||
const explanation = map(
|
||||
node => evaluateNode(cache, situation, parsedRules, node),
|
||||
node.explanation
|
||||
)
|
||||
let [node1, node2] = explanation
|
||||
const missingVariables = mergeAllMissing([node1, node2])
|
||||
|
||||
if (node1.nodeValue == null || node2.nodeValue == null) {
|
||||
return { ...node, nodeValue: null, explanation, missingVariables }
|
||||
}
|
||||
if (!['∕', '×'].includes(node.operator)) {
|
||||
try {
|
||||
if (node1.unit) {
|
||||
node2 = convertNodeToUnit(node1.unit, node2)
|
||||
} else if (node2.unit) {
|
||||
node1 = convertNodeToUnit(node2.unit, node1)
|
||||
}
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
cache._meta.contextRule,
|
||||
`Dans l'expression '${
|
||||
node.operator
|
||||
}', la partie gauche (unité: ${serializeUnit(
|
||||
node1.unit
|
||||
)}) n'est pas compatible avec la partie droite (unité: ${serializeUnit(
|
||||
node2.unit
|
||||
)})`,
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
const baseNode = {
|
||||
...node,
|
||||
explanation,
|
||||
unit: inferUnit(node.operationKind, [node1.unit, node2.unit]),
|
||||
missingVariables
|
||||
}
|
||||
|
||||
const operatorFunction = knownOperations[node.operationKind][0]
|
||||
|
||||
const temporalValue = liftTemporal2(
|
||||
(a: string | false, b: string | false) => {
|
||||
if (!['≠', '='].includes(node.operator) && a === false && b === false) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
['<', '>', '≤', '≥', '∕', '×'].includes(node.operator) &&
|
||||
(a === false || b === false)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (
|
||||
a !== false &&
|
||||
b !== false &&
|
||||
['≠', '=', '<', '>', '≤', '≥'].includes(node.operator) &&
|
||||
[a, b].every(value => value.match?.(/[\d]{2}\/[\d]{2}\/[\d]{4}/))
|
||||
) {
|
||||
return operatorFunction(convertToDate(a), convertToDate(b))
|
||||
}
|
||||
return operatorFunction(a, b)
|
||||
},
|
||||
node1.temporalValue ?? pureTemporal(node1.nodeValue),
|
||||
node2.temporalValue ?? pureTemporal(node2.nodeValue)
|
||||
)
|
||||
const nodeValue = temporalAverage(temporalValue, baseNode.unit)
|
||||
|
||||
return {
|
||||
...baseNode,
|
||||
nodeValue,
|
||||
...(temporalValue.length > 1 && { temporalValue })
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('operation', evaluate)
|
||||
|
||||
const knownOperations = {
|
||||
'*': [multiply, '×'],
|
||||
'/': [divide, '∕'],
|
||||
'+': [add],
|
||||
'-': [subtract, '−'],
|
||||
'<': [lt],
|
||||
'<=': [lte, '≤'],
|
||||
'>': [gt],
|
||||
'>=': [gte, '≥'],
|
||||
'=': [equals],
|
||||
'!=': [(a, b) => !equals(a, b), '≠']
|
||||
}
|
||||
|
||||
const operationDispatch = fromPairs(
|
||||
Object.entries(knownOperations).map(([k, [f, symbol]]) => [
|
||||
k,
|
||||
parse(k, symbol)
|
||||
])
|
||||
)
|
||||
|
||||
export default operationDispatch
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import React from 'react'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { typeWarning } from '../error'
|
||||
import { evaluateNode, makeJsx, mergeAllMissing } from '../evaluation'
|
||||
import {
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
|
||||
function MecanismPlafond({ explanation }) {
|
||||
|
@ -69,14 +74,16 @@ export default function Plafond(recurse, v) {
|
|||
plafond: recurse(v.plafond)
|
||||
}
|
||||
return {
|
||||
evaluate,
|
||||
jsx: MecanismPlafond,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'plafond',
|
||||
nodeKind: 'plafond',
|
||||
type: 'numeric',
|
||||
unit: explanation.valeur.unit
|
||||
}
|
||||
}
|
||||
|
||||
Plafond.nom = 'plafond'
|
||||
|
||||
registerEvaluationFunction('plafond', evaluate)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import React from 'react'
|
||||
import { InfixMecanism } from '../components/mecanisms/common'
|
||||
import { typeWarning } from '../error'
|
||||
import { evaluateNode, makeJsx, mergeAllMissing } from '../evaluation'
|
||||
import {
|
||||
evaluateNode,
|
||||
makeJsx,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
|
||||
function MecanismPlancher({ explanation }) {
|
||||
|
@ -73,9 +78,12 @@ export default function Plancher(recurse, v) {
|
|||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'plancher',
|
||||
nodeKind: 'plancher',
|
||||
type: 'numeric',
|
||||
unit: explanation.valeur.unit
|
||||
}
|
||||
}
|
||||
|
||||
Plancher.nom = 'plancher'
|
||||
|
||||
registerEvaluationFunction('plancher', evaluate)
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import React from 'react'
|
||||
import { evaluateNode } from '../evaluation'
|
||||
import { RuleLinkWithContext } from '../components/RuleLink'
|
||||
import { Mecanism } from '../components/mecanisms/common'
|
||||
import { registerEvaluationFunction } from '../evaluation'
|
||||
|
||||
const evaluate = (cache, situation, parsedRules, node) => {
|
||||
return { ...node }
|
||||
|
@ -10,12 +7,14 @@ const evaluate = (cache, situation, parsedRules, node) => {
|
|||
export const mecanismPossibility = (recurse, k, v) => {
|
||||
return {
|
||||
explanation: {},
|
||||
evaluate,
|
||||
jsx: function Synchronisation({ explanation }) {
|
||||
return null
|
||||
},
|
||||
category: 'mecanism',
|
||||
name: 'possibilité',
|
||||
nodeKind: 'possibilité',
|
||||
type: 'possibilité'
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('possibilité', evaluate)
|
||||
|
|
|
@ -1,66 +1,30 @@
|
|||
import Product from '../components/mecanisms/Product'
|
||||
import { typeWarning } from '../error'
|
||||
import { defaultNode, evaluateObject, parseObject } from '../evaluation'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateObject,
|
||||
parseObject,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { convertNodeToUnit, simplifyNodeUnit } from '../nodeUnits'
|
||||
import { areUnitConvertible, convertUnit, inferUnit } from '../units'
|
||||
|
||||
const objectShape = {
|
||||
assiette: false,
|
||||
taux: defaultNode(1),
|
||||
facteur: defaultNode(1),
|
||||
plafond: defaultNode(Infinity)
|
||||
}
|
||||
|
||||
export const mecanismProduct = (recurse, v) => {
|
||||
const objectShape = {
|
||||
assiette: false,
|
||||
taux: defaultNode(1),
|
||||
facteur: defaultNode(1),
|
||||
plafond: defaultNode(Infinity)
|
||||
}
|
||||
const effect = ({ assiette, taux, facteur, plafond }, cache) => {
|
||||
if (assiette.unit) {
|
||||
try {
|
||||
plafond = convertNodeToUnit(assiette.unit, plafond)
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
cache._meta.contextRule,
|
||||
"Impossible de convertir l'unité du plafond du produit dans celle de l'assiette",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
const mult = (base, rate, facteur, plafond) =>
|
||||
Math.min(base, plafond === false ? Infinity : plafond) * rate * facteur
|
||||
let nodeValue = [taux, assiette, facteur].some(n => n.nodeValue === false)
|
||||
? false
|
||||
: [taux, assiette, facteur].some(n => n.nodeValue === 0)
|
||||
? 0
|
||||
: [taux, assiette, facteur].some(n => n.nodeValue === null)
|
||||
? null
|
||||
: mult(
|
||||
assiette.nodeValue,
|
||||
taux.nodeValue,
|
||||
facteur.nodeValue,
|
||||
plafond.nodeValue
|
||||
)
|
||||
let unit = inferUnit(
|
||||
'*',
|
||||
[assiette, taux, facteur].map(el => el.unit)
|
||||
)
|
||||
if (areUnitConvertible(unit, assiette.unit)) {
|
||||
nodeValue = convertUnit(unit, assiette.unit, nodeValue)
|
||||
unit = assiette.unit
|
||||
}
|
||||
return simplifyNodeUnit({
|
||||
nodeValue,
|
||||
unit,
|
||||
explanation: {
|
||||
plafondActif: assiette.nodeValue > plafond.nodeValue
|
||||
}
|
||||
})
|
||||
}
|
||||
const explanation = parseObject(recurse, objectShape, v),
|
||||
evaluate = evaluateObject(objectShape, effect)
|
||||
const explanation = parseObject(recurse, objectShape, v)
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
jsx: Product,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'produit',
|
||||
nodeKind: 'produit',
|
||||
type: 'numeric',
|
||||
unit: inferUnit(
|
||||
'*',
|
||||
|
@ -70,3 +34,50 @@ export const mecanismProduct = (recurse, v) => {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
const effect = ({ assiette, taux, facteur, plafond }, cache) => {
|
||||
if (assiette.unit) {
|
||||
try {
|
||||
plafond = convertNodeToUnit(assiette.unit, plafond)
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
cache._meta.contextRule,
|
||||
"Impossible de convertir l'unité du plafond du produit dans celle de l'assiette",
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
const mult = (base, rate, facteur, plafond) =>
|
||||
Math.min(base, plafond === false ? Infinity : plafond) * rate * facteur
|
||||
let nodeValue = [taux, assiette, facteur].some(n => n.nodeValue === false)
|
||||
? false
|
||||
: [taux, assiette, facteur].some(n => n.nodeValue === 0)
|
||||
? 0
|
||||
: [taux, assiette, facteur].some(n => n.nodeValue === null)
|
||||
? null
|
||||
: mult(
|
||||
assiette.nodeValue,
|
||||
taux.nodeValue,
|
||||
facteur.nodeValue,
|
||||
plafond.nodeValue
|
||||
)
|
||||
let unit = inferUnit(
|
||||
'*',
|
||||
[assiette, taux, facteur].map(el => el.unit)
|
||||
)
|
||||
if (areUnitConvertible(unit, assiette.unit)) {
|
||||
nodeValue = convertUnit(unit, assiette.unit, nodeValue)
|
||||
unit = assiette.unit
|
||||
}
|
||||
return simplifyNodeUnit({
|
||||
nodeValue,
|
||||
unit,
|
||||
explanation: {
|
||||
plafondActif: assiette.nodeValue > plafond.nodeValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const evaluate = evaluateObject(objectShape, effect)
|
||||
|
||||
registerEvaluationFunction('produit', evaluate)
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import Recalcul from '../components/mecanisms/Recalcul'
|
||||
import { defaultNode, evaluateNode } from '../evaluation'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateNode,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { serializeUnit } from '../units'
|
||||
|
||||
const evaluateRecalcul = (cache, situation, parsedRules, node) => {
|
||||
|
@ -64,6 +68,8 @@ export const mecanismRecalcul = dottedNameContext => (recurse, v) => {
|
|||
amendedSituation
|
||||
},
|
||||
jsx: Recalcul,
|
||||
evaluate: evaluateRecalcul
|
||||
nodeKind: 'recalcul'
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('recalcul', evaluateRecalcul)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { max, min } from 'ramda'
|
||||
import Allègement from '../components/mecanisms/Allègement'
|
||||
import { typeWarning } from '../error'
|
||||
import { defaultNode, evaluateObject, parseObject } from '../evaluation'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateObject,
|
||||
parseObject,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import { serializeUnit } from '../units'
|
||||
|
||||
|
@ -61,12 +66,14 @@ export const mecanismReduction = (recurse, v) => {
|
|||
const explanation = parseObject(recurse, objectShape, v)
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
jsx: Allègement,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'allègement',
|
||||
nodeKind: 'allègement',
|
||||
type: 'numeric',
|
||||
unit: explanation?.assiette?.unit
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('allègement', evaluate)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { map } from 'ramda'
|
||||
import { convertToString, getYear } from '../date'
|
||||
import { evaluationError } from '../error'
|
||||
import { evaluateNode } from '../evaluation'
|
||||
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
|
||||
import {
|
||||
createTemporalEvaluation,
|
||||
groupByYear,
|
||||
|
@ -46,13 +46,13 @@ export default function parse<Name extends string>(parse, v) {
|
|||
}) as Array<{ dottedName: Name; value: Record<string, unknown> }>
|
||||
|
||||
return {
|
||||
evaluate,
|
||||
explanation: {
|
||||
rule,
|
||||
variables
|
||||
},
|
||||
category: 'mecanism',
|
||||
name: 'taux progressif',
|
||||
nodeKind: 'régularisation',
|
||||
type: 'numeric',
|
||||
unit: rule.unit
|
||||
}
|
||||
|
@ -163,3 +163,5 @@ function evaluate(cache, situation, parsedRules, node) {
|
|||
unit: evaluation.unit
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('régularisation', evaluate)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Somme from '../components/mecanisms/Somme'
|
||||
import { evaluateArray } from '../evaluation'
|
||||
import { evaluateArray, registerEvaluationFunction } from '../evaluation'
|
||||
import { inferUnit } from '../units'
|
||||
|
||||
const evaluate = evaluateArray(
|
||||
|
@ -10,11 +10,11 @@ const evaluate = evaluateArray(
|
|||
export const mecanismSum = (recurse, v) => {
|
||||
const explanation = v.map(recurse)
|
||||
return {
|
||||
evaluate,
|
||||
jsx: Somme,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'somme',
|
||||
nodeKind: 'somme',
|
||||
type: 'numeric',
|
||||
unit: inferUnit(
|
||||
'+',
|
||||
|
@ -22,3 +22,5 @@ export const mecanismSum = (recurse, v) => {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('somme', evaluate)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { path } from 'ramda'
|
||||
import React from 'react'
|
||||
import { evaluateNode } from '../evaluation'
|
||||
import { RuleLinkWithContext } from '../components/RuleLink'
|
||||
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
|
||||
|
||||
const evaluate = (cache, situation, parsedRules, node) => {
|
||||
const APIExplanation = evaluateNode(
|
||||
|
@ -36,7 +36,6 @@ const evaluate = (cache, situation, parsedRules, node) => {
|
|||
export const mecanismSynchronisation = (recurse, v) => {
|
||||
return {
|
||||
explanation: { ...v, API: recurse(v.API) },
|
||||
evaluate,
|
||||
jsx: function Synchronisation({ explanation }) {
|
||||
return (
|
||||
<p>
|
||||
|
@ -46,6 +45,9 @@ export const mecanismSynchronisation = (recurse, v) => {
|
|||
)
|
||||
},
|
||||
category: 'mecanism',
|
||||
name: 'synchronisation'
|
||||
name: 'synchronisation',
|
||||
nodeKind: 'synchronisation'
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('synchronisation', evaluate)
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { defaultNode, evaluateNode, mergeAllMissing } from '../evaluation'
|
||||
import tauxProgressif from '../components/mecanisms/TauxProgressif'
|
||||
import {
|
||||
defaultNode,
|
||||
evaluateNode,
|
||||
mergeAllMissing,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import { parseUnit } from '../units'
|
||||
import {
|
||||
|
@ -14,11 +19,11 @@ export default function parse(parse, v) {
|
|||
tranches: parseTranches(parse, v.tranches)
|
||||
}
|
||||
return {
|
||||
evaluate,
|
||||
jsx: tauxProgressif,
|
||||
explanation,
|
||||
category: 'mecanism',
|
||||
name: 'taux progressif',
|
||||
nodeKind: 'taux progressif',
|
||||
type: 'numeric',
|
||||
unit: parseUnit('%')
|
||||
}
|
||||
|
@ -121,3 +126,5 @@ const evaluate = (
|
|||
nodeValue
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('taux progressif', evaluate)
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { add, dissoc, objOf } from 'ramda'
|
||||
import { evaluateArrayWithFilter } from '../evaluation'
|
||||
import { inferUnit } from '../units'
|
||||
import Composantes from '../components/mecanisms/Composantes'
|
||||
|
||||
export let decompose = (recurse, k, v) => {
|
||||
let subProps = dissoc('composantes')(v),
|
||||
explanation = v.composantes.map(c => ({
|
||||
...recurse(
|
||||
objOf(k, {
|
||||
...subProps,
|
||||
...dissoc('attributs')(c)
|
||||
})
|
||||
),
|
||||
composante: c.nom ? { nom: c.nom } : c.attributs
|
||||
}))
|
||||
|
||||
let filter = situation => c =>
|
||||
!situation['_meta.filter'] ||
|
||||
!c.composante ||
|
||||
((!c.composante['dû par'] ||
|
||||
!['employeur', 'salarié'].includes(situation['_meta.filter']) ||
|
||||
c.composante['dû par'] == situation['_meta.filter']) &&
|
||||
(!c.composante['impôt sur le revenu'] ||
|
||||
!['déductible', 'non déductible'].includes(situation['_meta.filter']) ||
|
||||
c.composante['impôt sur le revenu'] == situation['_meta.filter']))
|
||||
|
||||
return {
|
||||
explanation,
|
||||
jsx: Composantes,
|
||||
evaluate: evaluateArrayWithFilter(filter, add, 0),
|
||||
category: 'mecanism',
|
||||
name: 'composantes',
|
||||
type: 'numeric',
|
||||
unit: inferUnit(
|
||||
'+',
|
||||
explanation.map(e => e.unit)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { evaluateNode } from '../evaluation'
|
||||
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
|
||||
import {
|
||||
createTemporalEvaluation,
|
||||
narrowTemporalValue,
|
||||
|
@ -49,7 +49,7 @@ function evaluate(
|
|||
export default function parseVariableTemporelle(parse, v) {
|
||||
const explanation = parse(v.explanation)
|
||||
return {
|
||||
evaluate,
|
||||
nodeKind: 'variable temporelle',
|
||||
explanation: {
|
||||
period: {
|
||||
start: v.period.start && parse(v.period.start),
|
||||
|
@ -60,3 +60,5 @@ export default function parseVariableTemporelle(parse, v) {
|
|||
unit: explanation.unit
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('variable temporelle', evaluate)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { or } from 'ramda'
|
||||
import Variations from '../components/mecanisms/Variations'
|
||||
import { typeWarning } from '../error'
|
||||
import { bonus, defaultNode, evaluateNode } from '../evaluation'
|
||||
import {
|
||||
bonus,
|
||||
defaultNode,
|
||||
evaluateNode,
|
||||
registerEvaluationFunction
|
||||
} from '../evaluation'
|
||||
import { convertNodeToUnit } from '../nodeUnits'
|
||||
import {
|
||||
liftTemporal2,
|
||||
|
@ -22,10 +27,10 @@ export default function parse(recurse, v) {
|
|||
// TODO - find an appropriate representation
|
||||
return {
|
||||
explanation,
|
||||
evaluate,
|
||||
jsx: Variations,
|
||||
category: 'mecanism',
|
||||
name: 'variations',
|
||||
nodeKind: 'variations',
|
||||
type: 'numeric',
|
||||
unit: inferUnit(
|
||||
'+',
|
||||
|
@ -38,10 +43,10 @@ export function devariate(recurse, k, v) {
|
|||
const explanation = devariateExplanation(recurse, k, v)
|
||||
return {
|
||||
explanation,
|
||||
evaluate,
|
||||
jsx: Variations,
|
||||
category: 'mecanism',
|
||||
name: 'variations',
|
||||
nodeKind: 'variations',
|
||||
type: 'numeric',
|
||||
unit: inferUnit(
|
||||
'+',
|
||||
|
@ -182,3 +187,5 @@ function evaluate(
|
|||
...(temporalValue.length > 1 && { temporalValue })
|
||||
}
|
||||
}
|
||||
|
||||
registerEvaluationFunction('variations', evaluate)
|
||||
|
|
|
@ -1,40 +1,25 @@
|
|||
// This should be the new way to implement mecanisms
|
||||
// In a specific file
|
||||
// TODO import them automatically
|
||||
import { Grammar, Parser } from 'nearley'
|
||||
import {
|
||||
add,
|
||||
difference,
|
||||
divide,
|
||||
equals,
|
||||
fromPairs,
|
||||
gt,
|
||||
gte,
|
||||
lt,
|
||||
lte,
|
||||
multiply,
|
||||
omit,
|
||||
subtract
|
||||
} from 'ramda'
|
||||
import { omit } from 'ramda'
|
||||
import React from 'react'
|
||||
import { EngineError, syntaxError } from './error'
|
||||
import { formatValue } from './format'
|
||||
import grammar from './grammar.ne'
|
||||
import applicable from './mecanisms/applicable'
|
||||
import arrondi from './mecanisms/arrondi'
|
||||
import barème from './mecanisms/barème'
|
||||
import { decompose } from './mecanisms/composantes'
|
||||
import { mecanismAllOf } from './mecanisms/condition-allof'
|
||||
import { mecanismOneOf } from './mecanisms/condition-oneof'
|
||||
import durée from './mecanisms/durée'
|
||||
import plafond from './mecanisms/plafond'
|
||||
import plancher from './mecanisms/plancher'
|
||||
import applicable from './mecanisms/applicable'
|
||||
import nonApplicable from './mecanisms/nonApplicable'
|
||||
import grille from './mecanisms/grille'
|
||||
import { mecanismInversion } from './mecanisms/inversion'
|
||||
import { mecanismMax } from './mecanisms/max'
|
||||
import { mecanismMin } from './mecanisms/min'
|
||||
import nonApplicable from './mecanisms/nonApplicable'
|
||||
import { mecanismOnePossibility } from './mecanisms/one-possibility'
|
||||
import operation from './mecanisms/operation'
|
||||
import operations from './mecanisms/operation'
|
||||
import plafond from './mecanisms/plafond'
|
||||
import plancher from './mecanisms/plancher'
|
||||
import { mecanismProduct } from './mecanisms/product'
|
||||
import { mecanismRecalcul } from './mecanisms/recalcul'
|
||||
import { mecanismReduction } from './mecanisms/reduction'
|
||||
|
@ -42,7 +27,6 @@ import régularisation from './mecanisms/régularisation'
|
|||
import { mecanismSum } from './mecanisms/sum'
|
||||
import { mecanismSynchronisation } from './mecanisms/synchronisation'
|
||||
import tauxProgressif from './mecanisms/tauxProgressif'
|
||||
import { decompose } from './mecanisms/utils'
|
||||
import variableTemporelle from './mecanisms/variableTemporelle'
|
||||
import variations, { devariate } from './mecanisms/variations'
|
||||
import { parseReferenceTransforms } from './parseReference'
|
||||
|
@ -204,28 +188,8 @@ function unfoldChainedMecanisms(rawNode) {
|
|||
)
|
||||
}
|
||||
|
||||
const knownOperations = {
|
||||
'*': [multiply, '×'],
|
||||
'/': [divide, '∕'],
|
||||
'+': [add],
|
||||
'-': [subtract, '−'],
|
||||
'<': [lt],
|
||||
'<=': [lte, '≤'],
|
||||
'>': [gt],
|
||||
'>=': [gte, '≥'],
|
||||
'=': [equals],
|
||||
'!=': [(a, b) => !equals(a, b), '≠']
|
||||
}
|
||||
|
||||
const operationDispatch = fromPairs(
|
||||
Object.entries(knownOperations).map(([k, [f, symbol]]) => [
|
||||
k,
|
||||
operation(k, f, symbol)
|
||||
])
|
||||
)
|
||||
|
||||
const statelessParseFunction = {
|
||||
...operationDispatch,
|
||||
...operations,
|
||||
...chainableMecanisms.reduce((acc, fn) => ({ [fn.nom]: fn, ...acc }), {}),
|
||||
'une de ces conditions': mecanismOneOf,
|
||||
'toutes ces conditions': mecanismAllOf,
|
||||
|
@ -248,6 +212,7 @@ const statelessParseFunction = {
|
|||
type: v.type,
|
||||
constant: true,
|
||||
nodeValue: v.nodeValue,
|
||||
nodeKind: 'constant',
|
||||
unit: v.unit,
|
||||
// eslint-disable-next-line
|
||||
jsx: (node: EvaluatedRule) => (
|
||||
|
|
|
@ -1,233 +1,6 @@
|
|||
import { Leaf } from './components/mecanisms/common'
|
||||
import { typeWarning } from './error'
|
||||
import { evaluateApplicability } from './evaluateRule'
|
||||
import { evaluateNode, mergeMissing } from './evaluation'
|
||||
import { convertNodeToUnit } from './nodeUnits'
|
||||
import parseRule from './parseRule'
|
||||
import { disambiguateRuleReference } from './ruleUtils'
|
||||
import { EvaluatedNode, ParsedRule } from './types'
|
||||
import { areUnitConvertible, serializeUnit } from './units'
|
||||
|
||||
/**
|
||||
* 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
|
||||
.sort(
|
||||
(replacement1, replacement2) =>
|
||||
+!!replacement2.whiteListedNames - +!!replacement1.whiteListedNames
|
||||
)
|
||||
.filter(
|
||||
({ whiteListedNames }) =>
|
||||
!whiteListedNames ||
|
||||
whiteListedNames.some(name => contextRuleName.startsWith(name))
|
||||
)
|
||||
.filter(
|
||||
({ blackListedNames }) =>
|
||||
!blackListedNames ||
|
||||
blackListedNames.every(name => !contextRuleName.startsWith(name))
|
||||
)
|
||||
.filter(({ referenceNode }) => contextRuleName !== referenceNode.dottedName)
|
||||
|
||||
/**
|
||||
* Filter-out and apply all possible replacements at runtime.
|
||||
*/
|
||||
const getApplicableReplacements = (
|
||||
contextRuleName,
|
||||
cache,
|
||||
situation,
|
||||
rules,
|
||||
rule: ParsedRule
|
||||
) => {
|
||||
let missingVariableList: Array<EvaluatedNode['missingVariables']> = []
|
||||
if (contextRuleName.startsWith('[evaluation]')) {
|
||||
return [[], []]
|
||||
}
|
||||
const applicableReplacements = getApplicableReplacedBy(
|
||||
contextRuleName,
|
||||
rule.replacedBy
|
||||
)
|
||||
// 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
|
||||
.filter(({ referenceNode }) => {
|
||||
const referenceRule = rules[referenceNode.dottedName]
|
||||
const situationValue = situation[referenceRule.dottedName]
|
||||
if (referenceNode.question && situationValue == null) {
|
||||
missingVariableList.push({ [referenceNode.dottedName]: 1 })
|
||||
}
|
||||
return situationValue?.nodeValue !== 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
|
||||
)
|
||||
missingVariableList.push(missingVariables)
|
||||
return isApplicable
|
||||
})
|
||||
.map(({ referenceNode, replacementNode }) =>
|
||||
replacementNode != null
|
||||
? evaluateNode(cache, situation, rules, replacementNode)
|
||||
: evaluateReference(cache, situation, rules, referenceNode)
|
||||
)
|
||||
.map(replacementNode => {
|
||||
const replacedRuleUnit = rule.unit
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
missingVariableList = missingVariableList.filter(
|
||||
missingVariables => !!Object.keys(missingVariables).length
|
||||
)
|
||||
|
||||
return [applicableReplacements, missingVariableList]
|
||||
}
|
||||
|
||||
const evaluateReference = (cache, situation, rules, node) => {
|
||||
const 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,
|
||||
replacementMissingVariableList
|
||||
] = getApplicableReplacements(
|
||||
node.explanation?.contextRuleName ?? '',
|
||||
cache,
|
||||
situation,
|
||||
rules,
|
||||
rule
|
||||
)
|
||||
|
||||
if (applicableReplacements.length) {
|
||||
if (applicableReplacements.length > 1) {
|
||||
// eslint-disable-next-line no-console
|
||||
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")
|
||||
`)
|
||||
}
|
||||
return applicableReplacements[0]
|
||||
}
|
||||
const addReplacementMissingVariable = node => ({
|
||||
...node,
|
||||
missingVariables: replacementMissingVariableList.reduce(
|
||||
mergeMissing,
|
||||
node.missingVariables
|
||||
)
|
||||
})
|
||||
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 + (node.explanation.filter ? ' .' + node.explanation.filter : '')
|
||||
const cached = cache[cacheName]
|
||||
|
||||
if (cached) return addReplacementMissingVariable(cached)
|
||||
|
||||
const cacheNode = (
|
||||
nodeValue: EvaluatedNode['nodeValue'],
|
||||
missingVariables: EvaluatedNode['missingVariables'],
|
||||
explanation?: Record<string, unknown>
|
||||
) => {
|
||||
cache[cacheName] = {
|
||||
...node,
|
||||
nodeValue,
|
||||
...(explanation && {
|
||||
explanation
|
||||
}),
|
||||
...(explanation?.temporalValue && {
|
||||
temporalValue: explanation.temporalValue
|
||||
}),
|
||||
...(explanation?.unit && { unit: explanation.unit }),
|
||||
missingVariables
|
||||
}
|
||||
return addReplacementMissingVariable(cache[cacheName])
|
||||
}
|
||||
const applicabilityEvaluation = evaluateApplicability(
|
||||
cache,
|
||||
situation,
|
||||
rules,
|
||||
rule
|
||||
)
|
||||
if (!applicabilityEvaluation.nodeValue) {
|
||||
return cacheNode(
|
||||
applicabilityEvaluation.nodeValue,
|
||||
applicabilityEvaluation.missingVariables,
|
||||
applicabilityEvaluation
|
||||
)
|
||||
}
|
||||
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]
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (rule.defaultValue != null) {
|
||||
const evaluation = evaluateNode(cache, situation, rules, rule.defaultValue)
|
||||
return cacheNode(evaluation.nodeValue ?? evaluation, {
|
||||
...evaluation.missingVariables,
|
||||
[dottedName]: 1
|
||||
})
|
||||
}
|
||||
|
||||
if (rule.formule != null) {
|
||||
const evaluation = evaluateNode(cache, situation, rules, rule)
|
||||
return cacheNode(
|
||||
evaluation.nodeValue,
|
||||
evaluation.missingVariables,
|
||||
evaluation
|
||||
)
|
||||
}
|
||||
|
||||
return cacheNode(null, { [dottedName]: 2 })
|
||||
}
|
||||
|
||||
export const parseReference = (
|
||||
rules,
|
||||
|
@ -249,7 +22,7 @@ export const parseReference = (
|
|||
(!inInversionFormula && parseRule(rules, dottedName, parsedRules))
|
||||
const unit = parsedRule.unit
|
||||
return {
|
||||
evaluate: evaluateReference,
|
||||
nodeKind: 'reference',
|
||||
jsx: Leaf,
|
||||
name: partialReference,
|
||||
category: 'reference',
|
||||
|
@ -260,43 +33,6 @@ export const parseReference = (
|
|||
}
|
||||
}
|
||||
|
||||
// This function is a wrapper that can apply :
|
||||
// - unit transformations to the value of the variable.
|
||||
// See the unité-temporelle.yaml test suite for details
|
||||
// - filters on the variable to select one part of the variable's 'composantes'
|
||||
|
||||
const evaluateReferenceTransforms = (cache, situation, parsedRules, node) => {
|
||||
// Filter transformation
|
||||
const filteringSituation = {
|
||||
...situation,
|
||||
'_meta.filter': node.explanation.filter
|
||||
}
|
||||
const filteredNode = evaluateNode(
|
||||
cache,
|
||||
node.explanation.filter ? filteringSituation : situation,
|
||||
parsedRules,
|
||||
node.explanation.originalNode
|
||||
)
|
||||
const { explanation, nodeValue } = filteredNode
|
||||
if (!explanation || nodeValue === null) {
|
||||
return filteredNode
|
||||
}
|
||||
const unit = node.explanation.unit
|
||||
if (unit) {
|
||||
try {
|
||||
return convertNodeToUnit(unit, filteredNode)
|
||||
} catch (e) {
|
||||
typeWarning(
|
||||
cache._meta.contextRule,
|
||||
`Impossible de convertir la reference '${filteredNode.name}'`,
|
||||
e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredNode
|
||||
}
|
||||
|
||||
type parseReferenceTransformsParameters = {
|
||||
variable: { fragments: Array<string> }
|
||||
filter?: string
|
||||
|
@ -318,6 +54,7 @@ export const parseReferenceTransforms = (rules, rule, parsedRules) => ({
|
|||
|
||||
return {
|
||||
...originalNode,
|
||||
nodeKind: 'referenceWithTransforms',
|
||||
explanation: {
|
||||
originalNode,
|
||||
filter,
|
||||
|
@ -333,7 +70,6 @@ export const parseReferenceTransforms = (rules, rule, parsedRules) => ({
|
|||
}
|
||||
}
|
||||
: {}),
|
||||
evaluate: evaluateReferenceTransforms,
|
||||
unit: unit || originalNode.unit
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { evolve, map } from 'ramda'
|
||||
import { evolve } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Mecanism } from './components/mecanisms/common'
|
||||
import { RuleLinkWithContext } from './components/RuleLink'
|
||||
import { compilationError, warning } from './error'
|
||||
import evaluate from './evaluateRule'
|
||||
import { evaluateNode, makeJsx, mergeAllMissing } from './evaluation'
|
||||
import { makeJsx } from './evaluation'
|
||||
import { parse } from './parse'
|
||||
import {
|
||||
disambiguateRuleReference,
|
||||
|
@ -101,12 +100,11 @@ export default function<Names extends string>(
|
|||
) : null
|
||||
|
||||
return {
|
||||
evaluate: (cache, situation, parsedRules) =>
|
||||
node.evaluate(cache, situation, parsedRules, node),
|
||||
jsx,
|
||||
category: 'ruleProp',
|
||||
rulePropType: 'cond',
|
||||
name: 'parentDependencies',
|
||||
nodeKind: 'parentDependencies',
|
||||
type: 'numeric',
|
||||
explanation: node
|
||||
}
|
||||
|
@ -130,7 +128,7 @@ export default function<Names extends string>(
|
|||
// "synchronisation" mecanism. This should be refactored to not use the
|
||||
// attribute "defaultValue"
|
||||
typeof value === 'object'
|
||||
? { ...value, evaluate: () => value }
|
||||
? { ...value, nodeKind: 'defaultNode' }
|
||||
: value,
|
||||
formule: value => {
|
||||
const child = parse(rules, rule, parsedRules)(value)
|
||||
|
@ -138,7 +136,7 @@ export default function<Names extends string>(
|
|||
const jsx = ({ explanation }) => makeJsx(explanation)
|
||||
|
||||
return {
|
||||
evaluate: evaluateFormula,
|
||||
nodeKind: 'formula',
|
||||
jsx,
|
||||
category: 'ruleProp',
|
||||
rulePropType: 'formula',
|
||||
|
@ -154,7 +152,7 @@ export default function<Names extends string>(
|
|||
// principe que 'non applicable si' et 'formule' sont particuliers, alors
|
||||
// qu'ils pourraient être rangé avec les autres mécanismes
|
||||
...parsedRule,
|
||||
evaluate,
|
||||
nodeKind: 'rule',
|
||||
parsed: true,
|
||||
unit:
|
||||
parsedRule.unit ??
|
||||
|
@ -164,7 +162,7 @@ export default function<Names extends string>(
|
|||
replacedBy: []
|
||||
}
|
||||
parsedRules[dottedName]['rendu non applicable'] = {
|
||||
evaluate: evaluateDisabledBy,
|
||||
nodeKind: 'disabledBy',
|
||||
jsx: ({ explanation: { isDisabledBy } }) => {
|
||||
return (
|
||||
isDisabledBy.length > 0 && (
|
||||
|
@ -215,54 +213,6 @@ export default function<Names extends string>(
|
|||
return parsedRules[dottedName]
|
||||
}
|
||||
|
||||
const evaluateFormula = (cache, situation, parsedRules, node) => {
|
||||
const explanation = evaluateNode(
|
||||
cache,
|
||||
situation,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
),
|
||||
{ nodeValue, unit, missingVariables, temporalValue } = explanation
|
||||
|
||||
return {
|
||||
...node,
|
||||
nodeValue,
|
||||
unit,
|
||||
missingVariables,
|
||||
explanation,
|
||||
temporalValue
|
||||
}
|
||||
}
|
||||
|
||||
const evaluateDisabledBy = (cache, situation, parsedRules, node) => {
|
||||
const isDisabledBy = node.explanation.isDisabledBy.map(disablerNode =>
|
||||
evaluateNode(cache, situation, parsedRules, disablerNode)
|
||||
)
|
||||
const nodeValue = isDisabledBy.some(
|
||||
x => x.nodeValue !== false && x.nodeValue !== null
|
||||
)
|
||||
const explanation = { ...node.explanation, isDisabledBy }
|
||||
return {
|
||||
...node,
|
||||
explanation,
|
||||
nodeValue,
|
||||
missingVariables: mergeAllMissing(isDisabledBy)
|
||||
}
|
||||
}
|
||||
|
||||
const evaluateEvolveCond = (cache, situation, parsedRules, node) => {
|
||||
const explanation = evaluateNode(
|
||||
cache,
|
||||
situation,
|
||||
parsedRules,
|
||||
node.explanation
|
||||
),
|
||||
nodeValue = explanation.nodeValue,
|
||||
missingVariables = explanation.missingVariables
|
||||
|
||||
return { ...node, nodeValue, explanation, missingVariables }
|
||||
}
|
||||
|
||||
const evolveCond = (dottedName, rule, rules, parsedRules) => value => {
|
||||
const child = parse(rules, rule, parsedRules)(value)
|
||||
|
||||
|
@ -277,8 +227,8 @@ const evolveCond = (dottedName, rule, rules, parsedRules) => value => {
|
|||
)
|
||||
|
||||
return {
|
||||
evaluate: evaluateEvolveCond,
|
||||
jsx,
|
||||
nodeKind: 'condition',
|
||||
category: 'ruleProp',
|
||||
rulePropType: 'cond',
|
||||
dottedName,
|
||||
|
|
Loading…
Reference in New Issue