⚙️ Nouvelle API d'évaluation

Modifie l'API de la fonction `evaluate` pour transmettre le contexte
avec `this`, ce qui simplifie l'interface de ces fonctions.

L'objet `this` (qui contient `this.parsedRules`, `this.situation`,
`this.evaluate`, etc.) est un interpréteur Publicodes, mais nous n'avons
pas besoin de créer une nouvelle abstraction car cet objet présente
exactement la même interface que l'objet public exposé dans
`publicodes/index.ts` et c'est donc l'interface publique qui est
utilisée dans les appels internes.
pull/1193/head
Maxime Quandalle 2020-11-04 16:47:12 +01:00
parent c66e529fb7
commit bc8c4d823a
30 changed files with 400 additions and 525 deletions

View File

@ -4,8 +4,5 @@
"spellright.documentTypes": ["yaml", "git-commit", "markdown"],
"typescript.tsdk": "node_modules/typescript/lib",
"editor.tabSize": 2,
"eslint.enable": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
"eslint.enable": true
}

View File

@ -1,25 +1,23 @@
import { EvaluatedNode, ParsedRule } from '.'
import Engine, { EvaluatedNode, evaluationFunction } from '.'
import { typeWarning } from './error'
import { evaluateApplicability } from './evaluateRule'
import { mergeMissing, evaluateNode } from './evaluation'
import { mergeMissing } from './evaluation'
import { convertNodeToUnit } from './nodeUnits'
import { serializeUnit, areUnitConvertible } from './units'
import { ParsedRule } from './types'
import { areUnitConvertible, serializeUnit } from './units'
export const evaluateReference = (cache, situation, rules, node) => {
const rule = rules[node.dottedName]
export const evaluateReference: evaluationFunction = function(node) {
const rule = this.parsedRules[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(
] = getApplicableReplacements.call(
this,
node.explanation?.contextRuleName ?? '',
cache,
situation,
rules,
rule
)
if (applicableReplacements.length) {
if (applicableReplacements.length > 1) {
// eslint-disable-next-line no-console
@ -32,7 +30,7 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
- 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]
return this.evaluateNode(applicableReplacements[0])
}
const addReplacementMissingVariable = node => ({
...node,
@ -46,7 +44,7 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
// 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]
const cached = this.cache[cacheName]
if (cached) return addReplacementMissingVariable(cached)
@ -55,7 +53,7 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
missingVariables: EvaluatedNode['missingVariables'],
explanation?: Record<string, unknown>
) => {
cache[cacheName] = {
this.cache[cacheName] = {
...node,
nodeValue,
...(explanation && {
@ -67,14 +65,10 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
...(explanation?.unit && { unit: explanation.unit }),
missingVariables
}
return addReplacementMissingVariable(cache[cacheName])
return addReplacementMissingVariable(this.cache[cacheName])
}
const applicabilityEvaluation = evaluateApplicability(
cache,
situation,
rules,
rule
)
const applicabilityEvaluation = evaluateApplicability.call(this, rule as any)
if (!applicabilityEvaluation.nodeValue) {
return cacheNode(
applicabilityEvaluation.nodeValue,
@ -82,12 +76,12 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
applicabilityEvaluation
)
}
if (situation[dottedName]) {
if (this.parsedSituation[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 situationValue = this.parsedSituation[dottedName]?.nodeKind
? this.evaluateNode(this.parsedSituation[dottedName])
: this.parsedSituation[dottedName]
const unit =
!situationValue.unit || serializeUnit(situationValue.unit) === ''
? rule.unit
@ -106,7 +100,7 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
}
if (rule.defaultValue != null) {
const evaluation = evaluateNode(cache, situation, rules, rule.defaultValue)
const evaluation = this.evaluateNode(rule.defaultValue)
return cacheNode(evaluation.nodeValue ?? evaluation, {
...evaluation.missingVariables,
[dottedName]: 1
@ -114,7 +108,7 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
}
if (rule.formule != null) {
const evaluation = evaluateNode(cache, situation, rules, rule)
const evaluation = this.evaluateNode(rule)
return cacheNode(
evaluation.nodeValue,
evaluation.missingVariables,
@ -130,23 +124,15 @@ Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, v
// 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
) => {
export const evaluateReferenceTransforms: evaluationFunction = function(node) {
// Filter transformation
const filteringSituation = {
...situation,
'_meta.filter': node.explanation.filter
if (node.explanation.filter) {
this.cache._meta.filter = node.explanation.filter
}
const filteredNode = this.evaluateNode(node.explanation.originalNode)
if (node.explanation.filter) {
delete this.cache._meta.filter
}
const filteredNode = evaluateNode(
cache,
node.explanation.filter ? filteringSituation : situation,
parsedRules,
node.explanation.originalNode
)
const { explanation, nodeValue } = filteredNode
if (!explanation || nodeValue === null) {
return filteredNode
@ -157,7 +143,7 @@ export const evaluateReferenceTransforms = (
return convertNodeToUnit(unit, filteredNode)
} catch (e) {
typeWarning(
cache._meta.contextRule,
this.cache._meta.contextRule,
`Impossible de convertir la reference '${filteredNode.name}'`,
e
)
@ -193,13 +179,11 @@ export const getApplicableReplacedBy = (contextRuleName, replacedBy) =>
/**
* Filter-out and apply all possible replacements at runtime.
*/
const getApplicableReplacements = (
contextRuleName,
cache,
situation,
rules,
const getApplicableReplacements = function(
this: Engine<string>,
contextRuleName: string,
rule: ParsedRule
) => {
) {
let missingVariableList: Array<EvaluatedNode['missingVariables']> = []
if (contextRuleName.startsWith('[evaluation]')) {
return [[], []]
@ -210,42 +194,37 @@ const getApplicableReplacements = (
)
// Remove remplacement defined in a not applicable node
.filter(({ referenceNode }) => {
const referenceRule = rules[referenceNode.dottedName]
const referenceRule = this.parsedRules[referenceNode.dottedName]
const {
nodeValue: isApplicable,
missingVariables
} = evaluateApplicability(cache, situation, rules, referenceRule)
} = evaluateApplicability.call(this, referenceRule as any)
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]
const referenceRule = this.parsedRules[referenceNode.dottedName]
const situationValue = this.parsedSituation[referenceRule.dottedName]
if (referenceNode.question && situationValue == null) {
missingVariableList.push({ [referenceNode.dottedName]: 1 })
}
return situationValue?.nodeValue !== false
return (situationValue as any)?.nodeValue !== false
})
// Remove remplacement defined in a boolean node whose evaluated value is false
.filter(({ referenceNode }) => {
const referenceRule = rules[referenceNode.dottedName]
const referenceRule = this.parsedRules[referenceNode.dottedName]
if (referenceRule.formule?.explanation?.operationType !== 'comparison') {
return true
}
const { nodeValue: isApplicable, missingVariables } = evaluateNode(
cache,
situation,
rules,
const { nodeValue: isApplicable, missingVariables } = this.evaluateNode(
referenceRule
)
missingVariableList.push(missingVariables)
return isApplicable
})
.map(({ referenceNode, replacementNode }) =>
replacementNode != null
? evaluateNode(cache, situation, rules, replacementNode)
: evaluateReference(cache, situation, rules, referenceNode)
replacementNode != null ? replacementNode : referenceNode
)
.map(replacementNode => {
const replacedRuleUnit = rule.unit

View File

@ -1,25 +1,15 @@
import { map, pick, pipe } from 'ramda'
import { evaluationFunction } from '.'
import { typeWarning } from './error'
import {
bonus,
evaluateNode,
mergeMissing,
mergeAllMissing
} from './evaluation'
import { bonus, mergeAllMissing, mergeMissing } from './evaluation'
import { convertNodeToUnit } from './nodeUnits'
import { EvaluatedNode, ParsedRule } from './types'
export const evaluateApplicability = (
cache,
situation,
parsedRules,
node: ParsedRule
): EvaluatedNode => {
export const evaluateApplicability: evaluationFunction = function(node: any) {
const evaluatedAttributes = pipe(
pick(['non applicable si', 'applicable si', 'rendu non applicable']) as (
x: any
) => any,
map(value => evaluateNode(cache, situation, parsedRules, value))
map(value => this.evaluateNode(value))
)(node) as any,
{
'non applicable si': notApplicable,
@ -27,7 +17,7 @@ export const evaluateApplicability = (
'rendu non applicable': disabled
} = evaluatedAttributes,
parentDependencies = node.parentDependencies.map(parent =>
evaluateNode(cache, situation, parsedRules, parent)
this.evaluateNode(parent)
)
const anyDisabledParent = parentDependencies.find(
@ -69,14 +59,9 @@ export const evaluateApplicability = (
}
}
export const evaluateFormula = (cache, situation, parsedRules, node) => {
const explanation = evaluateNode(
cache,
situation,
parsedRules,
node.explanation
),
{ nodeValue, unit, missingVariables, temporalValue } = explanation
export const evaluateFormula: evaluationFunction = function(node) {
const explanation = this.evaluateNode(node.explanation)
const { nodeValue, unit, missingVariables, temporalValue } = explanation
return {
...node,
@ -88,14 +73,9 @@ export const evaluateFormula = (cache, situation, parsedRules, node) => {
}
}
export const evaluateRule = (cache, situation, parsedRules, node) => {
cache._meta.contextRule.push(node.dottedName)
const applicabilityEvaluation = evaluateApplicability(
cache,
situation,
parsedRules,
node
)
export const evaluateRule: evaluationFunction = function(node: any) {
this.cache._meta.contextRule.push(node.dottedName)
const applicabilityEvaluation = evaluateApplicability.call(this, node)
const {
missingVariables: condMissing,
nodeValue: isApplicable
@ -104,7 +84,7 @@ export const evaluateRule = (cache, situation, parsedRules, node) => {
// evaluate the formula lazily, only if the applicability is known and true
let evaluatedFormula =
isApplicable && node.formule
? evaluateNode(cache, situation, parsedRules, node.formule)
? this.evaluateNode(node.formule)
: node.formule
if (node.unit) {
@ -124,7 +104,7 @@ export const evaluateRule = (cache, situation, parsedRules, node) => {
)
const temporalValue = evaluatedFormula.temporalValue
cache._meta.contextRule.pop()
this.cache._meta.contextRule.pop()
return {
...node,
...applicabilityEvaluation,
@ -137,9 +117,9 @@ export const evaluateRule = (cache, situation, parsedRules, node) => {
}
}
export const evaluateDisabledBy = (cache, situation, parsedRules, node) => {
export const evaluateDisabledBy: evaluationFunction = function(node) {
const isDisabledBy = node.explanation.isDisabledBy.map(disablerNode =>
evaluateNode(cache, situation, parsedRules, disablerNode)
this.evaluateNode(disablerNode)
)
const nodeValue = isDisabledBy.some(
x => x.nodeValue !== false && x.nodeValue !== null
@ -153,13 +133,8 @@ export const evaluateDisabledBy = (cache, situation, parsedRules, node) => {
}
}
export const evaluateCondition = (cache, situation, parsedRules, node) => {
const explanation = evaluateNode(
cache,
situation,
parsedRules,
node.explanation
)
export const evaluateCondition: evaluationFunction = function(node) {
const explanation = this.evaluateNode(node.explanation)
const nodeValue = explanation.nodeValue
const missingVariables = explanation.missingVariables

View File

@ -1,5 +1,6 @@
import { add, evolve, fromPairs, keys, map, mergeWith, reduce } from 'ramda'
import React from 'react'
import Engine, { evaluationFunction } from '.'
import { typeWarning } from './error'
import {
evaluateReference,
@ -37,15 +38,6 @@ export const mergeAllMissing = missings =>
export const mergeMissing = (left, right) =>
mergeWith(add, left || {}, right || {})
export const evaluateNode = (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) {
const firstNodeWithUnit = nodes.find(node => !!node.unit)
if (!firstNodeWithUnit) {
@ -67,49 +59,49 @@ function convertNodesToSameUnit(nodes, contextRule, mecanismName) {
})
}
export const evaluateArray = (reducer, start) => (
cache,
situation,
parsedRules,
node
) => {
const evaluate = evaluateNode.bind(null, cache, situation, parsedRules)
const evaluatedNodes = convertNodesToSameUnit(
node.explanation.map(evaluate),
cache._meta.contextRule,
node.name
)
const temporalValues = concatTemporals(
evaluatedNodes.map(
({ temporalValue, nodeValue }) => temporalValue ?? pureTemporal(nodeValue)
export const evaluateArray: (
reducer: Parameters<typeof reduce>[0],
start: Parameters<typeof reduce>[1]
) => evaluationFunction = (reducer, start) =>
function(node: any) {
const evaluate = this.evaluateNode.bind(this)
const evaluatedNodes = convertNodesToSameUnit(
node.explanation.map(evaluate),
this.cache._meta.contextRule,
node.name
)
)
const temporalValue = mapTemporal(values => {
if (values.some(value => value === null)) {
return null
}
return reduce(reducer, start, values)
}, temporalValues)
const baseEvaluation = {
...node,
missingVariables: mergeAllMissing(evaluatedNodes),
explanation: evaluatedNodes,
...(evaluatedNodes[0] && { unit: evaluatedNodes[0].unit })
}
if (temporalValue.length === 1) {
const temporalValues = concatTemporals(
evaluatedNodes.map(
({ temporalValue, nodeValue }) =>
temporalValue ?? pureTemporal(nodeValue)
)
)
const temporalValue = mapTemporal(values => {
if (values.some(value => value === null)) {
return null
}
return reduce(reducer, start, values)
}, temporalValues)
const baseEvaluation = {
...node,
missingVariables: mergeAllMissing(evaluatedNodes),
explanation: evaluatedNodes,
...(evaluatedNodes[0] && { unit: evaluatedNodes[0].unit })
}
if (temporalValue.length === 1) {
return {
...baseEvaluation,
nodeValue: temporalValue[0].value
}
}
return {
...baseEvaluation,
nodeValue: temporalValue[0].value
temporalValue,
nodeValue: temporalAverage(temporalValue as any)
}
}
return {
...baseEvaluation,
temporalValue,
nodeValue: temporalAverage(temporalValue)
}
}
export const defaultNode = (nodeValue: EvaluatedNode['nodeValue']) => ({
nodeValue,
@ -121,9 +113,10 @@ export const defaultNode = (nodeValue: EvaluatedNode['nodeValue']) => ({
nodeKind: 'defaultNode'
})
const evaluateDefaultNode = (cache, situation, parsedRules, node) => node
const evaluateExplanationNode = (cache, situation, parsedRules, node) =>
evaluateNode(cache, situation, parsedRules, node.explanation)
const evaluateDefaultNode: evaluationFunction = node => node
const evaluateExplanationNode: evaluationFunction = function(node) {
return this.evaluateNode(node.explanation)
}
export const parseObject = (recurse, objectShape, value) => {
const recurseOne = key => defaultValue => {
@ -139,71 +132,69 @@ export const parseObject = (recurse, objectShape, value) => {
return evolve(transforms as any, objectShape)
}
export const evaluateObject = (objectShape, effect) => (
cache,
situation,
parsedRules,
node
) => {
const evaluate = evaluateNode.bind(null, cache, situation, parsedRules)
const evaluations = map(evaluate, node.explanation)
const temporalExplanations = mapTemporal(
Object.fromEntries,
concatTemporals(
Object.entries(evaluations).map(([key, node]) =>
zipTemporals(pureTemporal(key), liftTemporalNode(node))
export const evaluateObject: (
effet: (this: Engine<string>, explanations: any) => any
) => evaluationFunction = effect =>
function(node: any) {
const evaluate = this.evaluateNode.bind(this)
const evaluations = map(evaluate, node.explanation)
const temporalExplanations = mapTemporal(
Object.fromEntries,
concatTemporals(
Object.entries(evaluations).map(([key, node]) =>
zipTemporals(pureTemporal(key), liftTemporalNode(node))
)
)
)
)
const temporalExplanation = mapTemporal(explanations => {
const evaluation = effect(explanations, cache, situation, parsedRules)
return {
...evaluation,
explanation: {
...explanations,
...evaluation.explanation
const temporalExplanation = mapTemporal(explanations => {
const evaluation = effect.call(this, explanations)
return {
...evaluation,
explanation: {
...explanations,
...evaluation.explanation
}
}
}, temporalExplanations)
const sameUnitTemporalExplanation: Temporal<EvaluatedNode<
string,
number
>> = convertNodesToSameUnit(
temporalExplanation.map(x => x.value),
this.cache._meta.contextRule,
node.name
).map((node, i) => ({
...temporalExplanation[i],
value: simplifyNodeUnit(node)
}))
const temporalValue = mapTemporal(
({ nodeValue }) => nodeValue,
sameUnitTemporalExplanation
)
const nodeValue = temporalAverage(temporalValue)
const baseEvaluation = {
...node,
nodeValue,
unit: sameUnitTemporalExplanation[0].value.unit,
explanation: evaluations,
missingVariables: mergeAllMissing(Object.values(evaluations))
}
if (sameUnitTemporalExplanation.length === 1) {
return {
...baseEvaluation,
explanation: sameUnitTemporalExplanation[0].value.explanation
}
}
}, temporalExplanations)
const sameUnitTemporalExplanation: Temporal<EvaluatedNode<
string,
number
>> = convertNodesToSameUnit(
temporalExplanation.map(x => x.value),
cache._meta.contextRule,
node.name
).map((node, i) => ({
...temporalExplanation[i],
value: simplifyNodeUnit(node)
}))
const temporalValue = mapTemporal(
({ nodeValue }) => nodeValue,
sameUnitTemporalExplanation
)
const nodeValue = temporalAverage(temporalValue)
const baseEvaluation = {
...node,
nodeValue,
unit: sameUnitTemporalExplanation[0].value.unit,
explanation: evaluations,
missingVariables: mergeAllMissing(Object.values(evaluations))
}
if (sameUnitTemporalExplanation.length === 1) {
return {
...baseEvaluation,
explanation: sameUnitTemporalExplanation[0].value.explanation
temporalValue,
temporalExplanation
}
}
return {
...baseEvaluation,
temporalValue,
temporalExplanation
}
}
const evaluationFunctions = {
export const evaluationFunctions = {
rule: evaluateRule,
formula: evaluateFormula,
disabledBy: evaluateDisabledBy,
@ -215,7 +206,10 @@ const evaluationFunctions = {
defaultNode: evaluateDefaultNode
}
export function registerEvaluationFunction(nodeKind, evaluationFunction) {
export function registerEvaluationFunction(
nodeKind: string,
evaluationFunction: any // TODO: type evaluationFunction
) {
if (evaluationFunctions[nodeKind]) {
throw Error(
`Multiple evaluation functions registered for the nodeKind \x1b[4m${nodeKind}`

View File

@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/ban-types */
import { map } from 'ramda'
import { evaluationError, warning } from './error'
import { evaluateNode } from './evaluation'
import { evaluationFunctions } from './evaluation'
import { convertNodeToUnit, simplifyNodeUnit } from './nodeUnits'
import { parse } from './parse'
import parseRules from './parseRules'
import * as utils from './ruleUtils'
import { EvaluatedNode, EvaluatedRule, ParsedRules, Rules } from './types'
import { parseUnit } from './units'
import * as utils from './ruleUtils'
const emptyCache = () => ({
_meta: { contextRule: [] }
@ -16,10 +16,14 @@ const emptyCache = () => ({
type Cache = {
_meta: {
contextRule: Array<string>
inversionFail?: {
given: string
estimated: string
}
inversionFail?:
| {
given: string
estimated: string
}
| true
inRecalcul?: boolean
filter?: string
}
}
@ -30,21 +34,26 @@ export type EvaluationOptions = Partial<{
}>
export * from './components'
export { default as cyclesLib } from './cyclesLib/index'
export { formatValue, serializeValue } from './format'
export { default as translateRules } from './translateRules'
export { default as cyclesLib } from './cyclesLib/index'
export * from './types'
export { parseRules }
export { utils }
export type evaluationFunction = (
this: Engine<string>,
node: EvaluatedNode
) => EvaluatedNode
export default class Engine<Names extends string> {
parsedRules: ParsedRules<Names>
parsedSituation: ParsedSituation<Names> = {}
private cache: Cache
cache: Cache
private warnings: Array<string> = []
constructor(rules: string | Rules<Names> | ParsedRules<Names>) {
this.cache = emptyCache()
this.resetCache()
this.parsedRules =
typeof rules === 'string' || !(Object.values(rules)[0] as any)?.dottedName
? parseRules(rules)
@ -67,10 +76,7 @@ export default class Engine<Names extends string> {
originalWarn(warning)
}
const result = simplifyNodeUnit(
evaluateNode(
this.cache,
this.parsedSituation,
this.parsedRules,
this.evaluateNode(
parse(
this.parsedRules,
{ dottedName: context },
@ -156,4 +162,14 @@ export default class Engine<Names extends string> {
getParsedRules(): ParsedRules<Names> {
return this.parsedRules
}
evaluateNode(node) {
if (!node.nodeKind) {
throw Error('The provided node must have a "nodeKind" attribute')
} else if (!evaluationFunctions[node.nodeKind]) {
throw Error(`Unknown "nodeKind": ${node.nodeKind}`)
}
return evaluationFunctions[node.nodeKind].call(this, node)
}
}

View File

@ -1,8 +1,8 @@
import React from 'react'
import { evaluationFunction } from '..'
import { InfixMecanism } from '../components/mecanisms/common'
import {
bonus,
evaluateNode,
makeJsx,
mergeMissing,
registerEvaluationFunction
@ -19,17 +19,11 @@ function MecanismApplicable({ explanation }) {
)
}
const evaluate = (cache, situation, parsedRules, node) => {
const evaluateAttribute = evaluateNode.bind(
null,
cache,
situation,
parsedRules
)
const condition = evaluateAttribute(node.explanation.condition)
const evaluate: evaluationFunction = function(node) {
const condition = this.evaluateNode(node.explanation.condition)
let valeur = node.explanation.valeur
if (condition.nodeValue !== false) {
valeur = evaluateAttribute(valeur)
valeur = this.evaluateNode(valeur)
}
return {
...node,

View File

@ -1,7 +1,7 @@
import React from 'react'
import { evaluationFunction } from '..'
import { InfixMecanism } from '../components/mecanisms/common'
import {
evaluateNode,
makeJsx,
mergeAllMissing,
registerEvaluationFunction
@ -28,18 +28,12 @@ function roundWithPrecision(n: number, fractionDigits: number) {
return +n.toFixed(fractionDigits)
}
const evaluate = (cache, situation, parsedRules, node) => {
const evaluateAttribute = evaluateNode.bind(
null,
cache,
situation,
parsedRules
)
const valeur = evaluateAttribute(node.explanation.valeur)
const evaluate: evaluationFunction = function(node) {
const valeur = this.evaluateNode(node.explanation.valeur)
const nodeValue = valeur.nodeValue
let arrondi = node.explanation.arrondi
if (nodeValue !== false) {
arrondi = evaluateAttribute(arrondi)
arrondi = this.evaluateNode(arrondi)
}
return {

View File

@ -1,8 +1,8 @@
import { evaluationFunction } from '..'
import Barème from '../components/mecanisms/Barème'
import { evaluationError } from '../error'
import {
defaultNode,
evaluateNode,
mergeAllMissing,
registerEvaluationFunction
} from '../evaluation'
@ -76,31 +76,27 @@ function evaluateBarème(tranches, assiette, evaluate, cache) {
}
})
}
const evaluate = (
cache,
situation,
parsedRules,
node: ReturnType<typeof parse>
) => {
const evaluate = evaluateNode.bind(null, cache, situation, parsedRules)
const assiette = evaluate(node.explanation.assiette)
const multiplicateur = evaluate(node.explanation.multiplicateur)
const evaluate: evaluationFunction = function(node) {
const evaluateNode = this.evaluateNode.bind(this)
const assiette = this.evaluateNode(node.explanation.assiette)
const multiplicateur = this.evaluateNode(node.explanation.multiplicateur)
const temporalTranchesPlafond = liftTemporal2(
(assiette, multiplicateur) =>
evaluatePlafondUntilActiveTranche(
evaluate,
evaluateNode,
{
parsedTranches: node.explanation.tranches,
assiette,
multiplicateur
},
cache
this.cache
),
liftTemporalNode(assiette),
liftTemporalNode(multiplicateur)
)
const temporalTranches = liftTemporal2(
(tranches, assiette) => evaluateBarème(tranches, assiette, evaluate, cache),
(tranches, assiette) =>
evaluateBarème(tranches, assiette, evaluateNode, this.cache),
temporalTranchesPlafond,
liftTemporalNode(assiette)
)

View File

@ -1,28 +1,25 @@
import { add, dissoc, filter, objOf } from 'ramda'
import { evaluationFunction } from '..'
import Composantes from '../components/mecanisms/Composantes'
import { evaluateArray, registerEvaluationFunction } from '../evaluation'
import { inferUnit } from '../units'
import Composantes from '../components/mecanisms/Composantes'
export const evaluateComposantes = (cache, situation, parsedRules, node) => {
export const evaluateComposantes: evaluationFunction = function(node) {
const evaluationFilter = c =>
!situation['_meta.filter'] ||
!this.cache._meta.filter ||
!c.composante ||
((!c.composante['dû par'] ||
!['employeur', 'salarié'].includes(situation['_meta.filter']) ||
c.composante['dû par'] == situation['_meta.filter']) &&
!['employeur', 'salarié'].includes(this.cache._meta.filter as any) ||
c.composante['dû par'] == this.cache._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)
}
)
!['déductible', 'non déductible'].includes(
this.cache._meta.filter as any
) ||
c.composante['impôt sur le revenu'] == this.cache._meta.filter))
return evaluateArray(add as any, 0).call(this, {
...node,
explanation: filter(evaluationFilter, node.explanation)
})
}
export const decompose = (recurse, k, v) => {

View File

@ -1,20 +1,20 @@
import { is, map } from 'ramda'
import React from 'react'
import { evaluationFunction } from '..'
import { Mecanism } from '../components/mecanisms/common'
import {
evaluateNode,
makeJsx,
mergeAllMissing,
registerEvaluationFunction
} from '../evaluation'
const evaluate = (cache, situation, parsedRules, node) => {
const evaluate: evaluationFunction = function(node) {
const [nodeValue, explanation] = node.explanation.reduce(
([nodeValue, explanation], node) => {
if (nodeValue === false) {
return [nodeValue, [...explanation, node]]
}
const evaluatedNode = evaluateNode(cache, situation, parsedRules, node)
const evaluatedNode = this.evaluateNode(node)
return [
nodeValue === false || nodeValue === null
? nodeValue

View File

@ -1,17 +1,15 @@
import { is, map, max, mergeWith, reduce } from 'ramda'
import React from 'react'
import { evaluationFunction } from '..'
import { Mecanism } from '../components/mecanisms/common'
import {
collectNodeMissing,
evaluateNode,
makeJsx,
registerEvaluationFunction
} from '../evaluation'
const evaluate = (cache, situation, parsedRules, node) => {
const evaluateOne = child =>
evaluateNode(cache, situation, parsedRules, child)
const explanation = map(evaluateOne, node.explanation)
const evaluate: evaluationFunction = function(node) {
const explanation = node.explanation.map(child => this.evaluateNode(child))
const anyTrue = explanation.find(e => e.nodeValue === true)
const anyNull = explanation.find(e => e.nodeValue === null)

View File

@ -1,9 +1,9 @@
import React from 'react'
import { evaluationFunction } from '..'
import { Mecanism } from '../components/mecanisms/common'
import { convertToDate, convertToString } from '../date'
import {
defaultNode,
evaluateNode,
makeJsx,
mergeAllMissing,
parseObject,
@ -34,15 +34,9 @@ const objectShape = {
"jusqu'à": defaultNode(todayString)
}
const evaluate = (cache, situation, parsedRules, node) => {
const evaluateAttribute = evaluateNode.bind(
null,
cache,
situation,
parsedRules
)
const from = evaluateAttribute(node.explanation.depuis)
const to = evaluateAttribute(node.explanation["jusqu'à"])
const evaluate: evaluationFunction = function(node) {
const from = this.evaluateNode(node.explanation.depuis)
const to = this.evaluateNode(node.explanation["jusqu'à"])
let nodeValue
if ([from, to].some(({ nodeValue }) => nodeValue === null)) {
nodeValue = null

View File

@ -1,8 +1,8 @@
import { lensPath, over } from 'ramda'
import { evaluationFunction } from '..'
import grille from '../components/mecanisms/Grille'
import {
defaultNode,
evaluateNode,
mergeAllMissing,
registerEvaluationFunction
} from '../evaluation'
@ -52,15 +52,10 @@ const evaluateGrille = (tranches, evaluate) =>
}
})
const evaluate = (
cache,
situation,
parsedRules,
node: ReturnType<typeof parse>
) => {
const evaluate = evaluateNode.bind(null, cache, situation, parsedRules)
const assiette = evaluate(node.explanation.assiette)
const multiplicateur = evaluate(node.explanation.multiplicateur)
const evaluate: evaluationFunction = function(node: any) {
const evaluate = this.evaluateNode.bind(this)
const assiette = this.evaluateNode(node.explanation.assiette)
const multiplicateur = this.evaluateNode(node.explanation.multiplicateur)
const temporalTranchesPlafond = liftTemporal2(
(assiette, multiplicateur) =>
evaluatePlafondUntilActiveTranche(
@ -70,7 +65,7 @@ const evaluate = (
assiette,
multiplicateur
},
cache
this.cache
),
liftTemporalNode(assiette),
liftTemporalNode(multiplicateur)

View File

@ -1,13 +1,14 @@
import { evaluationFunction } from '..'
import InversionNumérique from '../components/mecanisms/InversionNumérique'
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
import { registerEvaluationFunction } from '../evaluation'
import { convertNodeToUnit } from '../nodeUnits'
import uniroot from '../uniroot'
import { parseUnit } from '../units'
export const evaluateInversion = (oldCache, situation, parsedRules, node) => {
export const evaluateInversion: evaluationFunction = function(node) {
// TODO : take applicability into account here
let inversedWith = node.explanation.inversionCandidates.find(
n => situation[n.dottedName] != undefined
n => this.parsedSituation[n.dottedName] != undefined
)
if (!inversedWith) {
return {
@ -21,28 +22,24 @@ export const evaluateInversion = (oldCache, situation, parsedRules, node) => {
nodeValue: null
}
}
inversedWith = evaluateNode(oldCache, situation, parsedRules, inversedWith)
let inversionCache
function resetInversionCache() {
inversionCache = {
_meta: { ...oldCache._meta }
inversedWith = this.evaluateNode(inversedWith)
const originalCache = { ...this.cache }
const originalSituation = { ...this.parsedSituation }
const evaluateWithValue = (n: number) => {
this.cache = {
_meta: { ...originalCache._meta }
}
return inversionCache
this.parsedSituation = {
...originalSituation,
[inversedWith.dottedName]: undefined,
[node.explanation.ruleToInverse]: {
nodeValue: n,
unit: this.parsedRules[node.explanation.ruleToInverse].unit
}
}
return this.evaluateNode(inversedWith)
}
const evaluateWithValue = (n: number) =>
evaluateNode(
resetInversionCache(),
{
...situation,
[inversedWith.dottedName]: undefined,
[node.explanation.ruleToInverse]: {
nodeValue: n,
unit: parsedRules[node.explanation.ruleToInverse].unit
}
},
parsedRules,
inversedWith
)
// si fx renvoie null pour une valeur numérique standard, disons 2000, on peut
// considérer que l'inversion est impossible du fait de variables manquantes
// TODO fx peut être null pour certains x, et valide pour d'autres : on peut implémenter ici le court-circuit
@ -68,13 +65,15 @@ export const evaluateInversion = (oldCache, situation, parsedRules, node) => {
1
)
if (nodeValue === undefined) {
oldCache._meta.inversionFail = true
originalCache._meta.inversionFail = true
} else {
// For performance reason, we transfer the inversion cache
Object.entries(inversionCache).forEach(([k, value]) => {
oldCache[k] = value
Object.entries(this.cache).forEach(([k, value]) => {
originalCache[k] = value
})
}
this.cache = originalCache
this.parsedSituation = originalSituation
return {
...node,
nodeValue: nodeValue ?? null,
@ -103,9 +102,9 @@ export const mecanismInversion = dottedName => (recurse, v) => {
jsx: InversionNumérique,
category: 'mecanism',
name: 'inversion numérique',
nodeKind: 'inversion numérique',
nodeKind: 'inversion',
type: 'numeric'
}
}
registerEvaluationFunction('inversion numérique', evaluateInversion)
registerEvaluationFunction('inversion', evaluateInversion)

View File

@ -1,8 +1,8 @@
import React from 'react'
import { evaluationFunction } from '..'
import { InfixMecanism } from '../components/mecanisms/common'
import {
bonus,
evaluateNode,
makeJsx,
mergeMissing,
registerEvaluationFunction
@ -19,17 +19,11 @@ function MecanismNonApplicable({ explanation }) {
)
}
const evaluate = (cache, situation, parsedRules, node) => {
const evaluateAttribute = evaluateNode.bind(
null,
cache,
situation,
parsedRules
)
const condition = evaluateAttribute(node.explanation.condition)
const evaluate: evaluationFunction = function(node) {
const condition = this.evaluateNode(node.explanation.condition)
let valeur = node.explanation.valeur
if (condition.nodeValue !== true) {
valeur = evaluateAttribute(valeur)
valeur = this.evaluateNode(valeur)
}
return {
...node,

View File

@ -8,10 +8,7 @@ export const mecanismOnePossibility = dottedName => (recurse, v) => ({
nodeKind: 'une possibilité'
})
registerEvaluationFunction(
'une possibilité',
(cache, situation, parsedRules, node) => ({
...node,
missingVariables: { [node.context]: 1 }
})
)
registerEvaluationFunction('une possibilité', node => ({
...node,
missingVariables: { [node.context]: 1 }
}))

View File

@ -12,11 +12,11 @@ import {
subtract
} from 'ramda'
import React from 'react'
import { evaluationFunction } from '..'
import { Operation } from '../components/mecanisms/common'
import { convertToDate } from '../date'
import { typeWarning } from '../error'
import {
evaluateNode,
makeJsx,
mergeAllMissing,
registerEvaluationFunction
@ -50,11 +50,8 @@ const parse = (k, symbol) => (recurse, v) => {
}
}
const evaluate = (cache, situation, parsedRules, node) => {
const explanation = map(
node => evaluateNode(cache, situation, parsedRules, node),
node.explanation
)
const evaluate: evaluationFunction = function(node: any) {
const explanation = map(node => this.evaluateNode(node), node.explanation)
let [node1, node2] = explanation
const missingVariables = mergeAllMissing([node1, node2])
@ -70,7 +67,7 @@ const evaluate = (cache, situation, parsedRules, node) => {
}
} catch (e) {
typeWarning(
cache._meta.contextRule,
this.cache._meta.contextRule,
`Dans l'expression '${
node.operator
}', la partie gauche (unité: ${serializeUnit(

View File

@ -1,8 +1,8 @@
import React from 'react'
import { evaluationFunction } from '..'
import { InfixMecanism } from '../components/mecanisms/common'
import { typeWarning } from '../error'
import {
evaluateNode,
makeJsx,
mergeAllMissing,
registerEvaluationFunction
@ -26,25 +26,19 @@ function MecanismPlafond({ explanation }) {
)
}
const evaluate = (cache, situation, parsedRules, node) => {
const evaluateAttribute = evaluateNode.bind(
null,
cache,
situation,
parsedRules
)
const valeur = evaluateAttribute(node.explanation.valeur)
const evaluate: evaluationFunction = function(node) {
const valeur = this.evaluateNode(node.explanation.valeur)
let nodeValue = valeur.nodeValue
let plafond = node.explanation.plafond
if (nodeValue !== false) {
plafond = evaluateAttribute(plafond)
plafond = this.evaluateNode(plafond)
if (valeur.unit) {
try {
plafond = convertNodeToUnit(valeur.unit, plafond)
} catch (e) {
typeWarning(
cache._meta.contextRule,
this.cache._meta.contextRule,
"L'unité du plafond n'est pas compatible avec celle de la valeur à encadrer",
e
)

View File

@ -1,8 +1,8 @@
import React from 'react'
import { evaluationFunction } from '..'
import { InfixMecanism } from '../components/mecanisms/common'
import { typeWarning } from '../error'
import {
evaluateNode,
makeJsx,
mergeAllMissing,
registerEvaluationFunction
@ -26,24 +26,18 @@ function MecanismPlancher({ explanation }) {
)
}
const evaluate = (cache, situation, parsedRules, node) => {
const evaluateAttribute = evaluateNode.bind(
null,
cache,
situation,
parsedRules
)
const valeur = evaluateAttribute(node.explanation.valeur)
const evaluate: evaluationFunction = function(node) {
const valeur = this.evaluateNode(node.explanation.valeur)
let nodeValue = valeur.nodeValue
let plancher = node.explanation.plancher
if (nodeValue !== false) {
plancher = evaluateAttribute(plancher)
plancher = this.evaluateNode(plancher)
if (valeur.unit) {
try {
plancher = convertNodeToUnit(valeur.unit, plancher)
} catch (e) {
typeWarning(
cache._meta.contextRule,
this.cache._meta.contextRule,
"L'unité du plancher n'est pas compatible avec celle de la valeur à encadrer",
e
)

View File

@ -1,3 +1,4 @@
import { evaluationFunction } from '..'
import Product from '../components/mecanisms/Product'
import { typeWarning } from '../error'
import {
@ -35,13 +36,18 @@ export const mecanismProduct = (recurse, v) => {
}
}
const effect = ({ assiette, taux, facteur, plafond }, cache) => {
const productEffect: evaluationFunction = function({
assiette,
taux,
facteur,
plafond
}: any) {
if (assiette.unit) {
try {
plafond = convertNodeToUnit(assiette.unit, plafond)
} catch (e) {
typeWarning(
cache._meta.contextRule,
this.cache._meta.contextRule,
"Impossible de convertir l'unité du plafond du produit dans celle de l'assiette",
e
)
@ -78,6 +84,6 @@ const effect = ({ assiette, taux, facteur, plafond }, cache) => {
})
}
const evaluate = evaluateObject(objectShape, effect)
const evaluate = evaluateObject(productEffect)
registerEvaluationFunction('produit', evaluate)

View File

@ -1,20 +1,18 @@
import { evaluationFunction } from '..'
import Recalcul from '../components/mecanisms/Recalcul'
import {
defaultNode,
evaluateNode,
registerEvaluationFunction
} from '../evaluation'
import { defaultNode, registerEvaluationFunction } from '../evaluation'
import { EvaluatedNode } from '../types'
import { serializeUnit } from '../units'
const evaluateRecalcul = (cache, situation, parsedRules, node) => {
if (cache._meta.inRecalcul) {
return defaultNode(false)
const evaluateRecalcul: evaluationFunction = function(node) {
if (this.cache._meta.inRecalcul) {
return (defaultNode(false) as any) as EvaluatedNode
}
const amendedSituation = node.explanation.amendedSituation
.map(([originRule, replacement]) => [
evaluateNode(cache, situation, parsedRules, originRule),
evaluateNode(cache, situation, parsedRules, replacement)
this.evaluateNode(originRule),
this.evaluateNode(replacement)
])
.filter(
([originRule, replacement]) =>
@ -22,25 +20,25 @@ const evaluateRecalcul = (cache, situation, parsedRules, node) => {
serializeUnit(originRule.unit) !== serializeUnit(replacement.unit)
)
const originalCache = this.cache
const originalSituation = this.parsedSituation
// Optimisation : no need for recalcul if situation is the same
const recalculCache = Object.keys(amendedSituation).length
? { _meta: { ...cache._meta, inRecalcul: true } } // Create an empty cache
: cache
this.cache = Object.keys(amendedSituation).length
? { _meta: { ...this.cache._meta, inRecalcul: true } } // Create an empty cache
: this.cache
this.parsedSituation = {
...this.parsedSituation,
...Object.fromEntries(
amendedSituation.map(([originRule, replacement]) => [
originRule.dottedName,
replacement
])
)
}
const evaluatedNode = evaluateNode(
recalculCache,
{
...situation,
...Object.fromEntries(
amendedSituation.map(([originRule, replacement]) => [
originRule.dottedName,
replacement
])
)
},
parsedRules,
node.explanation.recalcul
)
const evaluatedNode = this.evaluateNode(node.explanation.recalcul)
this.cache = originalCache
this.parsedSituation = originalSituation
return {
...node,
nodeValue: evaluatedNode.nodeValue,

View File

@ -16,51 +16,49 @@ const objectShape = {
plafond: defaultNode(Infinity)
}
const evaluate = evaluateObject(
objectShape,
({ assiette, abattement, plafond }, cache) => {
const assietteValue = assiette.nodeValue
if (assietteValue == null) return { nodeValue: null }
if (assiette.unit) {
try {
plafond = convertNodeToUnit(assiette.unit, plafond)
if (serializeUnit(abattement.unit) !== '%') {
abattement = convertNodeToUnit(assiette.unit, abattement)
}
} catch (e) {
typeWarning(
cache._meta.contextRule,
"Impossible de convertir les unités de l'allègement entre elles",
e
)
}
}
const nodeValue = abattement
? abattement.nodeValue == null
? assietteValue === 0
? 0
: null
: serializeUnit(abattement.unit) === '%'
? max(
0,
assietteValue -
min(
plafond.nodeValue,
(abattement.nodeValue / 100) * assietteValue
)
)
: max(0, assietteValue - min(plafond.nodeValue, abattement.nodeValue))
: assietteValue
return {
nodeValue,
unit: assiette.unit,
explanation: {
plafond,
abattement
const evaluate = evaluateObject(function({
assiette,
abattement,
plafond
}: any) {
const assietteValue = assiette.nodeValue
if (assietteValue == null) return { nodeValue: null }
if (assiette.unit) {
try {
plafond = convertNodeToUnit(assiette.unit, plafond)
if (serializeUnit(abattement.unit) !== '%') {
abattement = convertNodeToUnit(assiette.unit, abattement)
}
} catch (e) {
typeWarning(
this.cache._meta.contextRule,
"Impossible de convertir les unités de l'allègement entre elles",
e
)
}
}
)
const nodeValue = abattement
? abattement.nodeValue == null
? assietteValue === 0
? 0
: null
: serializeUnit(abattement.unit) === '%'
? max(
0,
assietteValue -
min(plafond.nodeValue, (abattement.nodeValue / 100) * assietteValue)
)
: max(0, assietteValue - min(plafond.nodeValue, abattement.nodeValue))
: assietteValue
return {
nodeValue,
unit: assiette.unit,
explanation: {
plafond,
abattement
}
}
})
export const mecanismReduction = (recurse, v) => {
const explanation = parseObject(recurse, objectShape, v)

View File

@ -1,7 +1,8 @@
import { map } from 'ramda'
import { evaluationFunction } from '..'
import { convertToString, getYear } from '../date'
import { evaluationError } from '../error'
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
import { registerEvaluationFunction } from '../evaluation'
import {
createTemporalEvaluation,
groupByYear,
@ -84,19 +85,19 @@ function getMonthlyCumulatedValuesOverYear(
return cumulatedPeriods
}
function evaluate(cache, situation, parsedRules, node) {
const evaluate = evaluateNode.bind(null, cache, situation, parsedRules)
function recalculWith(newSituation, node) {
return evaluateNode(
{ _meta: cache._meta },
{ ...situation, ...newSituation },
parsedRules,
node
)
const evaluate: evaluationFunction = function(node) {
const recalculWith = (newSituation, node) => {
const originalCache = this.cache
const originalSituation = this.parsedSituation
this.cache = { _meta: originalCache._meta }
this.parsedSituation = { ...originalSituation, ...newSituation }
const res = this.evaluateNode(node)
this.cache = originalCache
this.parsedSituation = originalSituation
return res
}
function regulariseYear(temporalEvaluation: Temporal<Evaluation<number>>) {
const regulariseYear = (temporalEvaluation: Temporal<Evaluation<number>>) => {
if (temporalEvaluation.filter(({ value }) => value !== false).length <= 1) {
return temporalEvaluation
}
@ -107,10 +108,10 @@ function evaluate(cache, situation, parsedRules, node) {
value: Record<string, unknown>
}>).reduce<Record<string, Temporal<Evaluation<number>>>>(
(acc, { dottedName, value }) => {
const evaluation = evaluate(value)
const evaluation = this.evaluateNode(value)
if (!evaluation.unit.denominators.some(unit => unit === 'mois')) {
evaluationError(
cache._meta.contextRule,
this.cache._meta.contextRule,
`Dans le mécanisme régularisation, la valeur cumulée '${dottedName}' n'est pas une variable numérique définie sur le mois`
)
}
@ -147,7 +148,7 @@ function evaluate(cache, situation, parsedRules, node) {
return temporalRégularisée as Temporal<Evaluation<number>>
}
const evaluation = evaluate(node.explanation.rule)
const evaluation = this.evaluateNode(node.explanation.rule)
const temporalValue = evaluation.temporalValue
const evaluationWithRegularisation = groupByYear(
temporalValue as Temporal<Evaluation<number>>

View File

@ -3,7 +3,7 @@ import { evaluateArray, registerEvaluationFunction } from '../evaluation'
import { inferUnit } from '../units'
const evaluate = evaluateArray(
(x, y) => (x === false && y === false ? false : x + y),
(x: any, y: any) => (x === false && y === false ? false : x + y),
false
)

View File

@ -1,15 +1,11 @@
import { path } from 'ramda'
import React from 'react'
import { evaluationFunction } from '..'
import { RuleLinkWithContext } from '../components/RuleLink'
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
import { registerEvaluationFunction } from '../evaluation'
const evaluate = (cache, situation, parsedRules, node) => {
const APIExplanation = evaluateNode(
cache,
situation,
parsedRules,
node.explanation.API
)
const evaluate: evaluationFunction = function(node: any) {
const APIExplanation = this.evaluateNode(node.explanation.API)
const valuePath = node.explanation.chemin.split(' . ')
const nodeValue =
APIExplanation.nodeValue == null

View File

@ -1,7 +1,7 @@
import { evaluationFunction } from '..'
import tauxProgressif from '../components/mecanisms/TauxProgressif'
import {
defaultNode,
evaluateNode,
mergeAllMissing,
registerEvaluationFunction
} from '../evaluation'
@ -29,15 +29,10 @@ export default function parse(parse, v) {
}
}
const evaluate = (
cache,
situation,
parsedRules,
node: ReturnType<typeof parse>
) => {
const evaluate = evaluateNode.bind(null, cache, situation, parsedRules)
const assiette = evaluate(node.explanation.assiette)
const multiplicateur = evaluate(node.explanation.multiplicateur)
const evaluate: evaluationFunction = function(node: any) {
const evaluate = this.evaluateNode.bind(this)
const assiette = this.evaluateNode(node.explanation.assiette)
const multiplicateur = this.evaluateNode(node.explanation.multiplicateur)
const tranches = evaluatePlafondUntilActiveTranche(
evaluate,
{
@ -45,7 +40,7 @@ const evaluate = (
assiette,
multiplicateur
},
cache
this.cache
)
const evaluatedNode = {

View File

@ -1,4 +1,5 @@
import { evaluateNode, registerEvaluationFunction } from '../evaluation'
import { evaluationFunction } from '..'
import { registerEvaluationFunction } from '../evaluation'
import {
createTemporalEvaluation,
narrowTemporalValue,
@ -6,26 +7,14 @@ import {
temporalAverage
} from '../temporal'
function evaluate(
cache: any,
situation: any,
parsedRules: any,
node: ReturnType<typeof parseVariableTemporelle>
) {
const evaluateAttribute = evaluateNode.bind(
null,
cache,
situation,
parsedRules
)
const evaluate: evaluationFunction = function(node: any) {
const start =
node.explanation.period.start &&
evaluateAttribute(node.explanation.period.start)
this.evaluateNode(node.explanation.period.start)
const end =
node.explanation.period.end &&
evaluateAttribute(node.explanation.period.end)
const value = evaluateAttribute(node.explanation.value)
this.evaluateNode(node.explanation.period.end)
const value = this.evaluateNode(node.explanation.value)
const period = {
start: start?.nodeValue || null,
end: end?.nodeValue || null

View File

@ -1,12 +1,8 @@
import { or } from 'ramda'
import { evaluationFunction } from '..'
import Variations from '../components/mecanisms/Variations'
import { typeWarning } from '../error'
import {
bonus,
defaultNode,
evaluateNode,
registerEvaluationFunction
} from '../evaluation'
import { bonus, defaultNode, registerEvaluationFunction } from '../evaluation'
import { convertNodeToUnit } from '../nodeUnits'
import {
liftTemporal2,
@ -82,14 +78,7 @@ const devariateExplanation = (
return explanation
}
function evaluate(
cache,
situation,
parsedRules,
node: ReturnType<typeof parse>
) {
const evaluate = evaluateNode.bind(null, cache, situation, parsedRules)
const evaluate: evaluationFunction = function(node: any) {
const [temporalValue, explanation, unit] = node.explanation.reduce(
(
[evaluation, explanations, unit, previousConditions],
@ -108,7 +97,7 @@ function evaluate(
previousConditions
]
}
const evaluatedCondition = evaluate(condition)
const evaluatedCondition = this.evaluateNode(condition)
const currentCondition = liftTemporal2(
(previousCond, currentCond) =>
previousCond === null ? previousCond : !previousCond && currentCond,
@ -132,13 +121,13 @@ function evaluate(
previousConditions
]
}
let evaluatedConsequence = evaluate(consequence)
let evaluatedConsequence = this.evaluateNode(consequence)
try {
evaluatedConsequence = convertNodeToUnit(unit, evaluatedConsequence)
} catch (e) {
typeWarning(
cache._meta.contexRule,
this.cache._meta.contextRule,
`L'unité de la branche n° ${i +
1} du mécanisme 'variations' n'est pas compatible avec celle d'une branche précédente`,
e

View File

@ -45,16 +45,14 @@ Vérifiez que tous les champs à droite des deux points sont remplis`
syntaxError(
rule.dottedName,
`
Les valeure booléenes true / false ne sont acceptée.
Les valeurs booléennes true / false ne sont acceptées.
Utilisez leur contrepartie française : 'oui' / 'non'`
)
}
const node =
typeof rawNode === 'object' ? rawNode : parseExpression(rule, '' + rawNode)
const parsedNode = parseMecanism(rules, rule, parsedRules)(node)
parsedNode.evaluate = parsedNode.evaluate ?? ((_, __, ___, node) => node)
return parsedNode
return parseMecanism(rules, rule, parsedRules)(node)
}
const compiledGrammar = Grammar.fromCompiled(grammar)

View File

@ -27,6 +27,7 @@ export type ParsedRule<Name extends string = string> = Rule & {
dottedName: Name
name: string
title: string
nodeKind: string
parentDependencies: Array<any>
rawRule: Rule
unit?: Unit
@ -37,7 +38,6 @@ export type ParsedRule<Name extends string = string> = Rule & {
API?: string
icons?: string
formule?: any
evaluate?: () => EvaluatedRule<Name>
explanation?: any
isDisabledBy: Array<any>
replacedBy: Array<{
@ -74,10 +74,11 @@ export type EvaluatedNode<
T extends Types = Types
> = {
nodeValue: Evaluation<T>
explanation?: Record<string, any>
explanation: Record<string, any>
isDefault?: boolean
jsx: React.FunctionComponent<EvaluatedNode>
category?: string
dottedName: Names
missingVariables: Partial<Record<Names, number>>
} & (T extends number
? {