diff --git a/mon-entreprise/package.json b/mon-entreprise/package.json index 8320a0524..2a8ce2e5b 100644 --- a/mon-entreprise/package.json +++ b/mon-entreprise/package.json @@ -109,7 +109,7 @@ "build:stats": "webpack --config webpack.prod.js --profile --json > stats.json", "build:analyze-bundle": "ANALYZE_BUNDLE=1 yarn run build", "build:dev": "FR_BASE_URL='http://localhost:5000${path}' EN_BASE_URL='http://localhost:5001${path}' yarn run build", - "clean": "rimraf dist node_modules source/data", + "clean": "rimraf dist node_modules 'source/data/!(versement-transport.json)'", "test": "yarn test:file \"./{,!(node_modules)/**/}!(webpack).test.{js,ts}\"", "test:file": "yarn mocha-webpack --webpack-config ./webpack.dev.js --include test/componentTestSetup.js --require mock-local-storage --require test/helpers/browser.js", "test:bundlesize": "bundlesize", diff --git a/publicodes/core/source/AST/graph.ts b/publicodes/core/source/AST/graph.ts index 3d072fa66..5429346ae 100644 --- a/publicodes/core/source/AST/graph.ts +++ b/publicodes/core/source/AST/graph.ts @@ -28,6 +28,8 @@ function buildRuleDependancies(rule: RuleNode): Array { return node.explanation.amendedSituation.flatMap((s) => fn(s[1])) case 'reference': return [...acc, node.dottedName as string] + case 'résoudre référence circulaire': + return [] case 'rule': // Cycle from parent dependancies are ignored at runtime, // so we don' detect them statically diff --git a/publicodes/core/source/AST/index.ts b/publicodes/core/source/AST/index.ts index 65a1d9736..f9f42880a 100644 --- a/publicodes/core/source/AST/index.ts +++ b/publicodes/core/source/AST/index.ts @@ -114,6 +114,8 @@ const traverseASTNode: TraverseFunction = (fn, node) => { return traverseArrayNode(fn, node) case 'durée': return traverseDuréeNode(fn, node) + case 'résoudre référence circulaire': + return traverseRésoudreRéférenceCirculaireNode(fn, node) case 'inversion': return traverseInversionNode(fn, node) case 'operation': @@ -261,6 +263,17 @@ const traversePlancherNode: TraverseFunction<'plancher'> = (fn, node) => ({ }, }) +const traverseRésoudreRéférenceCirculaireNode: TraverseFunction<'résoudre référence circulaire'> = ( + fn, + node +) => ({ + ...node, + explanation: { + ...node.explanation, + valeur: fn(node.explanation.valeur), + }, +}) + const traversePlafondNode: TraverseFunction<'plafond'> = (fn, node) => ({ ...node, explanation: { diff --git a/publicodes/core/source/AST/types.ts b/publicodes/core/source/AST/types.ts index 5cfa47b27..92349e638 100644 --- a/publicodes/core/source/AST/types.ts +++ b/publicodes/core/source/AST/types.ts @@ -1,24 +1,23 @@ import { AbattementNode } from '../mecanisms/abattement' import { ApplicableSiNode } from '../mecanisms/applicable' import { ArrondiNode } from '../mecanisms/arrondi' -import { OperationNode } from '../mecanisms/operation' import { BarèmeNode } from '../mecanisms/barème' -import { ReferenceNode } from '../reference' -import { RuleNode } from '../rule' import { TouteCesConditionsNode } from '../mecanisms/condition-allof' import { UneDeCesConditionsNode } from '../mecanisms/condition-oneof' import { DuréeNode } from '../mecanisms/durée' import { GrilleNode } from '../mecanisms/grille' import { InversionNode } from '../mecanisms/inversion' import { MaxNode } from '../mecanisms/max' -import { PlafondNode } from '../mecanisms/plafond' import { MinNode } from '../mecanisms/min' import { NonApplicableSiNode } from '../mecanisms/nonApplicable' +import { PossibilityNode } from '../mecanisms/one-possibility' +import { OperationNode } from '../mecanisms/operation' import { ParDéfautNode } from '../mecanisms/parDéfaut' +import { PlafondNode } from '../mecanisms/plafond' import { PlancherNode } from '../mecanisms/plancher' import { ProductNode } from '../mecanisms/product' import { RecalculNode } from '../mecanisms/recalcul' -import { PossibilityNode } from '../mecanisms/one-possibility' +import { RésoudreRéférenceCiruclaireNode } from '../mecanisms/résoudre-référence-circulaire' import { SituationNode } from '../mecanisms/situation' import { SommeNode } from '../mecanisms/sum' import { SynchronisationNode } from '../mecanisms/synchronisation' @@ -26,7 +25,9 @@ import { TauxProgressifNode } from '../mecanisms/tauxProgressif' import { UnitéNode } from '../mecanisms/unité' import { VariableTemporelleNode } from '../mecanisms/variableTemporelle' import { VariationNode } from '../mecanisms/variations' +import { ReferenceNode } from '../reference' import { ReplacementRule } from '../replacement' +import { RuleNode } from '../rule' import { Temporal } from '../temporal' export type ConstantNode = { @@ -57,6 +58,7 @@ export type ASTNode = ( | PlancherNode | ProductNode | RecalculNode + | RésoudreRéférenceCiruclaireNode | SituationNode | SommeNode | SynchronisationNode diff --git a/publicodes/core/source/evaluation.ts b/publicodes/core/source/evaluation.ts index aef471908..447b67c42 100644 --- a/publicodes/core/source/evaluation.ts +++ b/publicodes/core/source/evaluation.ts @@ -2,8 +2,8 @@ import Engine, { EvaluationFunction } from '.' import { ASTNode, ConstantNode, - Evaluation, EvaluatedNode, + Evaluation, NodeKind, } from './AST/types' import { warning } from './error' @@ -53,7 +53,7 @@ function convertNodesToSameUnit(this: Engine, nodes, mecanismName) { } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], `Les unités des éléments suivants sont incompatibles entre elles : \n\t\t${ node?.name || node?.rawNode }\n\t\t${firstNodeWithUnit?.name || firstNodeWithUnit?.rawNode}'`, diff --git a/publicodes/core/source/index.ts b/publicodes/core/source/index.ts index 5827307b4..4cbf72475 100644 --- a/publicodes/core/source/index.ts +++ b/publicodes/core/source/index.ts @@ -12,15 +12,18 @@ import { Rule, RuleNode } from './rule' import * as utils from './ruleUtils' import { formatUnit, getUnitKey } from './units' -const emptyCache = () => ({ - _meta: { ruleStack: [] }, +const emptyCache = (): Cache => ({ + _meta: { + parentRuleStack: [], + evaluationRuleStack: [], + }, nodes: new Map(), }) type Cache = { _meta: { - ruleStack: Array - parentEvaluationStack?: Array + parentRuleStack: Array + evaluationRuleStack: Array inversionFail?: | { given: string diff --git a/publicodes/core/source/mecanisms/abattement.ts b/publicodes/core/source/mecanisms/abattement.ts index 956bd0100..33dc9b936 100644 --- a/publicodes/core/source/mecanisms/abattement.ts +++ b/publicodes/core/source/mecanisms/abattement.ts @@ -25,7 +25,7 @@ const evaluateAbattement: EvaluationFunction<'abattement'> = function (node) { } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], "Impossible de convertir les unités de l'allègement entre elles", e ) diff --git a/publicodes/core/source/mecanisms/inversion.ts b/publicodes/core/source/mecanisms/inversion.ts index aafad83dc..dd540ea0f 100644 --- a/publicodes/core/source/mecanisms/inversion.ts +++ b/publicodes/core/source/mecanisms/inversion.ts @@ -1,9 +1,9 @@ -import parse from '../parse' import { EvaluationFunction } from '..' import { ConstantNode, Unit } from '../AST/types' import { mergeMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import { convertNodeToUnit } from '../nodeUnits' +import parse from '../parse' import { Context } from '../parsePublicodes' import { ReferenceNode } from '../reference' import uniroot from '../uniroot' @@ -54,7 +54,6 @@ export const evaluateInversion: EvaluationFunction<'inversion'> = function ( } const evaluatedInversionGoal = this.evaluate(inversionGoal) const unit = 'unit' in node ? node.unit : evaluatedInversionGoal.unit - const originalCache = this.cache const originalSituation = { ...this.parsedSituation } let inversionNumberOfIterations = 0 @@ -63,7 +62,6 @@ export const evaluateInversion: EvaluationFunction<'inversion'> = function ( inversionNumberOfIterations++ this.resetCache() this.cache._meta = { ...originalCache._meta } - this.parsedSituation[node.explanation.ruleToInverse] = { unit: unit, nodeKind: 'unité', @@ -139,9 +137,11 @@ export const evaluateInversion: EvaluationFunction<'inversion'> = function ( // // Uncomment to display the two attempts and their result // console.table([{ x: x1, y: y1 }, { x: x2, y: y2 }]) - // console.log('iteration:', inversionNumberOfIterations) + // console.log('iteration inversion:', inversionNumberOfIterations) + this.cache = originalCache this.parsedSituation = originalSituation + return { ...node, unit, diff --git a/publicodes/core/source/mecanisms/plafond.ts b/publicodes/core/source/mecanisms/plafond.ts index 952a55e23..0743bfe66 100644 --- a/publicodes/core/source/mecanisms/plafond.ts +++ b/publicodes/core/source/mecanisms/plafond.ts @@ -1,4 +1,3 @@ -import { last } from 'ramda' import { EvaluationFunction } from '..' import { ASTNode } from '../AST/types' import { warning } from '../error' @@ -28,7 +27,7 @@ const evaluate: EvaluationFunction<'plafond'> = function (node) { } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], "L'unité du plafond n'est pas compatible avec celle de la valeur à encadrer", e ) diff --git a/publicodes/core/source/mecanisms/plancher.ts b/publicodes/core/source/mecanisms/plancher.ts index 6f06f4425..c4578e305 100644 --- a/publicodes/core/source/mecanisms/plancher.ts +++ b/publicodes/core/source/mecanisms/plancher.ts @@ -26,7 +26,7 @@ const evaluate: EvaluationFunction<'plancher'> = function (node) { } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], "L'unité du plancher n'est pas compatible avec celle de la valeur à encadrer", e ) diff --git a/publicodes/core/source/mecanisms/product.ts b/publicodes/core/source/mecanisms/product.ts index 334aac143..612df9b95 100644 --- a/publicodes/core/source/mecanisms/product.ts +++ b/publicodes/core/source/mecanisms/product.ts @@ -44,7 +44,7 @@ const productEffect: EvaluationFunction = function ({ } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], "Impossible de convertir l'unité du plafond du produit dans celle de l'assiette", e ) diff --git a/publicodes/core/source/mecanisms/résoudre-référence-circulaire.ts b/publicodes/core/source/mecanisms/résoudre-référence-circulaire.ts new file mode 100644 index 000000000..b7e4d70d2 --- /dev/null +++ b/publicodes/core/source/mecanisms/résoudre-référence-circulaire.ts @@ -0,0 +1,109 @@ +import { EvaluationFunction } from '..' +import { ASTNode, ConstantNode, Unit } from '../AST/types' +import { registerEvaluationFunction } from '../evaluationFunctions' +import parse from '../parse' +import { Context } from '../parsePublicodes' +import uniroot from '../uniroot' +import { UnitéNode } from './unité' + +export type RésoudreRéférenceCiruclaireNode = { + explanation: { + ruleToSolve: string + valeur: ASTNode + } + nodeKind: 'résoudre référence circulaire' +} + +export const evaluateRésoudreRéférenceCirculaire: EvaluationFunction<'résoudre référence circulaire'> = function ( + node +) { + const originalCache = this.cache + let inversionNumberOfIterations = 0 + + const evaluateWithValue = ( + n: number, + unit: Unit = { numerators: [], denominators: [] } + ) => { + inversionNumberOfIterations++ + this.resetCache() + + this.parsedSituation[node.explanation.ruleToSolve] = { + unit: unit, + nodeKind: 'unité', + explanation: { + nodeKind: 'constant', + nodeValue: n, + type: 'number', + } as ConstantNode, + } as UnitéNode + return this.evaluate(node.explanation.valeur) + } + + let nodeValue: number | null | undefined = null + + const x0 = 0 + let valeur = evaluateWithValue(x0) + + const y0 = valeur.nodeValue as number + const unit = valeur.unit + const missingVariables = valeur.missingVariables + let i = 0 + if (y0 !== null) { + // The `uniroot` function parameter. It will be called with its `min` and + // `max` arguments, so we can use our cached nodes if the function is called + // with the already computed x1 or x2. + const test = (x: number): number => { + if (x === x0) { + return y0 - x0 + } + valeur = evaluateWithValue(x, unit) + const y = valeur.nodeValue + i++ + return (y as number) - x + } + + const defaultMin = -1_000_000 + const defaultMax = 100_000_000 + + nodeValue = uniroot(test, defaultMin, defaultMax, 1, 30, 2) + } + if (nodeValue === undefined) { + nodeValue = null + this.cache._meta.inversionFail = true + } + if (nodeValue != null) { + originalCache.nodes.forEach((v, k) => this.cache.nodes.set(k, v)) + } + console.log('iteration résoudre référence circulaire :', i) + + this.cache = originalCache + delete this.parsedSituation[node.explanation.ruleToSolve] + return { + ...node, + unit, + nodeValue, + explanation: { + ...node.explanation, + valeur, + inversionNumberOfIterations, + }, + missingVariables, + } +} + +export default function parseRésoudreRéférenceCirculaire(v, context: Context) { + return { + explanation: { + ruleToSolve: context.dottedName, + valeur: parse(v.valeur, context), + }, + nodeKind: 'résoudre référence circulaire', + } as RésoudreRéférenceCiruclaireNode +} + +parseRésoudreRéférenceCirculaire.nom = 'résoudre la référence circulaire' + +registerEvaluationFunction( + 'résoudre référence circulaire', + evaluateRésoudreRéférenceCirculaire +) diff --git a/publicodes/core/source/mecanisms/trancheUtils.ts b/publicodes/core/source/mecanisms/trancheUtils.ts index 2b430e06b..415240676 100644 --- a/publicodes/core/source/mecanisms/trancheUtils.ts +++ b/publicodes/core/source/mecanisms/trancheUtils.ts @@ -63,7 +63,7 @@ export function evaluatePlafondUntilActiveTranche( } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], `L'unité du plafond de la tranche n°${ i + 1 } n'est pas compatible avec celle l'assiette`, @@ -103,7 +103,7 @@ export function evaluatePlafondUntilActiveTranche( ) { evaluationError( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], `Le plafond de la tranche n°${ i + 1 } a une valeur inférieure à celui de la tranche précédente` diff --git a/publicodes/core/source/mecanisms/unité.ts b/publicodes/core/source/mecanisms/unité.ts index ac57c027c..a27b39e41 100644 --- a/publicodes/core/source/mecanisms/unité.ts +++ b/publicodes/core/source/mecanisms/unité.ts @@ -37,7 +37,7 @@ registerEvaluationFunction(parseUnité.nom, function evaluate(node) { } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], "Erreur lors de la conversion d'unité explicite", e ) diff --git a/publicodes/core/source/mecanisms/variations.ts b/publicodes/core/source/mecanisms/variations.ts index 7cff54a1c..1c8c3b9c0 100644 --- a/publicodes/core/source/mecanisms/variations.ts +++ b/publicodes/core/source/mecanisms/variations.ts @@ -120,7 +120,7 @@ const evaluate: EvaluationFunction<'variations'> = function (node) { } catch (e) { warning( this.options.logger, - this.cache._meta.ruleStack[0], + this.cache._meta.evaluationRuleStack[0], `L'unité de la branche n° ${ i + 1 } du mécanisme 'variations' n'est pas compatible avec celle d'une branche précédente`, diff --git a/publicodes/core/source/parse.ts b/publicodes/core/source/parse.ts index 04cabbb0d..46bbb96ba 100644 --- a/publicodes/core/source/parse.ts +++ b/publicodes/core/source/parse.ts @@ -22,6 +22,7 @@ import plafond from './mecanisms/plafond' import plancher from './mecanisms/plancher' import { mecanismProduct } from './mecanisms/product' import { mecanismRecalcul } from './mecanisms/recalcul' +import résoudreRéférenceCirculaire from './mecanisms/résoudre-référence-circulaire' import situation from './mecanisms/situation' import { mecanismSum } from './mecanisms/sum' import { mecanismSynchronisation } from './mecanisms/synchronisation' @@ -147,6 +148,7 @@ ${e.message}` } } +// Chainable mecanisme in their composition order (first one is applyied first) const chainableMecanisms = [ applicable, nonApplicable, @@ -156,6 +158,7 @@ const chainableMecanisms = [ plafond, parDéfaut, situation, + résoudreRéférenceCirculaire, abattement, ] function parseChainedMecanisms(rawNode, context: Context): ASTNode { diff --git a/publicodes/core/source/reference.ts b/publicodes/core/source/reference.ts index 6a4a5afaf..8e98ff256 100644 --- a/publicodes/core/source/reference.ts +++ b/publicodes/core/source/reference.ts @@ -1,8 +1,6 @@ -import { EvaluatedNode } from './AST/types' import { InternalError } from './error' import { registerEvaluationFunction } from './evaluationFunctions' import { Context } from './parsePublicodes' -import { RuleNode } from './rule' export type ReferenceNode = { nodeKind: 'reference' @@ -26,6 +24,7 @@ registerEvaluationFunction('reference', function evaluateReference(node) { if (!node.dottedName) { throw new InternalError(node) } + const explanation = this.evaluate(this.parsedRules[node.dottedName]) return { ...node, diff --git a/publicodes/core/source/rule.ts b/publicodes/core/source/rule.ts index d4493aec1..172deed64 100644 --- a/publicodes/core/source/rule.ts +++ b/publicodes/core/source/rule.ts @@ -1,6 +1,8 @@ import { ASTNode, EvaluatedNode } from './AST/types' +import { warning } from './error' import { bonus, mergeMissing } from './evaluation' import { registerEvaluationFunction } from './evaluationFunctions' +import { capitalise0 } from './format' import parse, { mecanismKeys } from './parse' import { Context } from './parsePublicodes' import { ReferenceNode } from './reference' @@ -10,7 +12,6 @@ import { ReplacementRule, } from './replacement' import { nameLeaf, ruleParents } from './ruleUtils' -import { capitalise0 } from './format' export type Rule = { formule?: Record | string @@ -119,25 +120,53 @@ export default function parseRule( } registerEvaluationFunction('rule', function evaluate(node) { - if (this.cache[node.dottedName]) { - return this.cache[node.dottedName] - } const explanation = { ...node.explanation } - - const verifyParentApplicability = !this.cache._meta.ruleStack.includes( - node.dottedName - ) - this.cache._meta.ruleStack.unshift(node.dottedName) let parent: EvaluatedNode | null = null - if (explanation.parent && verifyParentApplicability) { - parent = this.evaluate(explanation.parent) as EvaluatedNode + if (explanation.parent) { + if (this.cache._meta.parentRuleStack.includes(node.dottedName)) { + parent = { nodeValue: null } as EvaluatedNode + } else { + this.cache._meta.parentRuleStack.unshift(node.dottedName) + parent = this.evaluate(explanation.parent) as EvaluatedNode + this.cache._meta.parentRuleStack.shift() + } explanation.parent = parent } let valeur: EvaluatedNode | null = null if (!parent || parent.nodeValue !== false) { - valeur = this.evaluate(explanation.valeur) as EvaluatedNode + if ( + this.cache._meta.evaluationRuleStack.filter( + (dottedName) => dottedName === node.dottedName + ).length > 15 // I don't know why this magic number, but below, cycle are detected "too early", which leads to blank value in brut-net simulator + ) { + warning( + this.options.logger, + node.dottedName, + ` + Un cycle a été détecté dans lors de l'évaluation de cette règle. + Par défaut cette règle sera évaluée à 'null'. + + Pour indiquer au moteur de résoudre la référence circulaire en trouvant le point fixe + de la fonction, il vous suffit d'ajouter l'attribut suivant niveau de la règle : + + ${node.dottedName}: + "résoudre la référence circulaire: oui" + ... + + ` + ) + + valeur = { nodeValue: null } as EvaluatedNode + } else { + this.cache._meta.evaluationRuleStack.unshift(node.dottedName) + valeur = this.evaluate(explanation.valeur) as EvaluatedNode + this.cache._meta.evaluationRuleStack.shift() + } + explanation.valeur = valeur } + // if (valeur.nodeValue === '') { + const evaluation = { ...node, explanation, @@ -148,7 +177,5 @@ registerEvaluationFunction('rule', function evaluate(node) { ), ...(valeur && 'unit' in valeur && { unit: valeur.unit }), } - this.cache._meta.ruleStack.shift() - this.cache[node.dottedName] = evaluation return evaluation }) diff --git a/publicodes/core/source/uniroot.ts b/publicodes/core/source/uniroot.ts index 3023d7f86..e1fd03744 100644 --- a/publicodes/core/source/uniroot.ts +++ b/publicodes/core/source/uniroot.ts @@ -109,7 +109,9 @@ export default function uniroot( if ((fb > 0 && fc > 0) || (fb < 0 && fc < 0)) { ;(c = a), (fc = fa) // Adjust c for it to have a sign opposite to that of b } - + if (Math.abs(fb) < errorTol) { + return b + } if (Math.abs(fb) < acceptableErrorTol) { fallback = b } diff --git a/publicodes/core/test/mécanismes/résoudre-référence-circulaire.yaml b/publicodes/core/test/mécanismes/résoudre-référence-circulaire.yaml new file mode 100644 index 000000000..841abfefb --- /dev/null +++ b/publicodes/core/test/mécanismes/résoudre-référence-circulaire.yaml @@ -0,0 +1,68 @@ +fx: +x: + résoudre la référence circulaire: oui + valeur: fx + exemples: + - nom: affine + situation: + fx: 200 - x + valeur attendue: 100 + - nom: quadratique + situation: + fx: 0.2 * x * x - 400 * x + 500 + valeur attendue: 2003.743 + # CF https://www.wolframalpha.com/input/?i=x%3D0.2x%C2%B2-400x%2B500 + +CA: + unité: € + plancher: 0€ + formule: + inversion numérique: + avec: + - net + +net: + résoudre la référence circulaire: oui + unité: € + formule: CA - 50% * net + + +net après impôt: + formule: 80% * net + unité: € + +cycle avec inversion et situation vide: + exemples: + - nom: CA + situation: + cycle avec inversion et situation vide: CA + valeur attendue: null + # - nom: net + # situation: + # cycle avec inversion et situation vide: net + # valeur attendue: null + # - nom: net après impôt + # situation: + # cycle avec inversion et situation vide: net après impôt + # valeur attendue: null + +cycle avec la règle à inverser fixée dans la situation: + valeur: net + exemples: + - situation: + CA: 10000 + valeur attendue: 6666.666 + +cycle avec la règle du cycle fixée dans la situation: + valeur: CA + exemples: + - situation: + net: 1000 + valeur attendue: 1500 + +# cycle avec une règle reliée fixée dans la situation: +# valeur: net +# exemples: +# - situation: +# net après impôt: 8000 +# valeur attendue: 10000 diff --git a/publicodes/docs/mecanisms.yaml b/publicodes/docs/mecanisms.yaml index 5615c95cc..4bf0ccc36 100644 --- a/publicodes/docs/mecanisms.yaml +++ b/publicodes/docs/mecanisms.yaml @@ -494,6 +494,7 @@ synchronisation: n'est pas stable. Se référer aux exemples existants. inversion numérique: + chainable: oui description: >- Il est souhaitable de rédiger les règles de calcul en publicodes de la même façon qu'elles sont décrites dans la loi ou les @@ -523,3 +524,37 @@ inversion numérique: (calculée ou saisie), et procéder à l'inversion décrite plus haut à partir de celle-ci. Sinon, ces possibilités d'inversions seront listées comme manquantes. + +résoudre la référence circulaire: + description: | + Active le calcul itératif pour trouver la valeur de la règle qui résout + la référence circulaire. + + Il est possible pour une règle de se référencer elle-même. Par défaut, le + moteur considère qu'il s'agit d'un cycle non voulu, et renvoie 'null' comme valeur + pour la règle en question, en affichant un avertissement. + + Mais dans certains cas, la formule est bonne et le cycle est voulu. La valeur de la + règle attendue est donc celle qui résout l'équation obtenue via la référence cyclique. + + Lorsque l'on active cette fonctionnalité, le moteur va procéder par essai-erreur jusqu'à + trouver cette valeur. + + Note : la résolution de cycle est coûteuse en temps de calcul. Il faut donc veiller à + ne pas la cumuler avec l'évaluation d'un autre mécanisme coûteux comme l'inversion numérique + par exemple. + + + exemples: + base: >- + x: + valeur: 4 * x - 5 + résoudre la référence circulaire: oui + calcul du revenu professionnel: >- + chiffre d'affaires: 10000 €/an + + cotisations: 25% * revenu professionnel + + revenu professionnel: + valeur: chiffre d'affaires - cotisations + résoudre la référence circulaire: oui diff --git a/publicodes/ui-react/source/Explanation.tsx b/publicodes/ui-react/source/Explanation.tsx index 76ce028a2..bafe2d689 100644 --- a/publicodes/ui-react/source/Explanation.tsx +++ b/publicodes/ui-react/source/Explanation.tsx @@ -1,8 +1,10 @@ -import { ConstantNode, Leaf } from './mecanisms/common' +import { useContext } from 'react' +import { EngineContext } from './contexts' import Abattement from './mecanisms/Abattement' import ApplicableSi from './mecanisms/Applicable' import Arrondi from './mecanisms/Arrondi' import Barème from './mecanisms/Barème' +import { ConstantNode, Leaf } from './mecanisms/common' import Composantes from './mecanisms/Composantes' import Durée from './mecanisms/Durée' import Grille from './mecanisms/Grille' @@ -19,6 +21,7 @@ import Recalcul from './mecanisms/Recalcul' import Replacement from './mecanisms/Replacement' import ReplacementRule from './mecanisms/ReplacementRule' import Rule from './mecanisms/Rule' +import RésoudreRéférenceCirculaire from './mecanisms/RésoudreRéférenceCirculaire' import Situation from './mecanisms/Situation' import Somme from './mecanisms/Somme' import Synchronisation from './mecanisms/Synchronisation' @@ -28,8 +31,6 @@ import UneDeCesConditions from './mecanisms/UneDeCesConditions' import UnePossibilité from './mecanisms/UnePossibilité' import Unité from './mecanisms/Unité' import Variations from './mecanisms/Variations' -import { useContext } from 'react' -import { EngineContext } from './contexts' const UIComponents = { constant: ConstantNode, @@ -61,6 +62,7 @@ const UIComponents = { 'toutes ces conditions': ToutesCesConditions, 'une de ces conditions': UneDeCesConditions, 'une possibilité': UnePossibilité, + 'résoudre référence circulaire': RésoudreRéférenceCirculaire, unité: Unité, 'variable temporelle': () => '[variable temporelle]', variations: Variations, diff --git a/publicodes/ui-react/source/mecanisms/RésoudreRéférenceCirculaire.tsx b/publicodes/ui-react/source/mecanisms/RésoudreRéférenceCirculaire.tsx new file mode 100644 index 000000000..c02c285f3 --- /dev/null +++ b/publicodes/ui-react/source/mecanisms/RésoudreRéférenceCirculaire.tsx @@ -0,0 +1,19 @@ +import Explanation from '../Explanation' +import { Mecanism } from './common' + +export default function MecanismRésoudreRéférenceCirculaire({ explanation }) { + return ( + +

+ {' '} + Cette valeur a été retrouvé en résolvant la référence circulaire dans la + formule ci dessous :{' '} +

+ + +
+ ) +} diff --git a/publicodes/ui-react/source/rule/RulePage.tsx b/publicodes/ui-react/source/rule/RulePage.tsx index fc354b0fe..ef9e59fc7 100644 --- a/publicodes/ui-react/source/rule/RulePage.tsx +++ b/publicodes/ui-react/source/rule/RulePage.tsx @@ -19,7 +19,6 @@ export default function Rule({ dottedName, engine, language }) { return

Cette règle est introuvable dans la base

} const rule = engine.evaluate(engine.getRule(dottedName)) - const { description, question } = rule.rawNode const { parent, valeur } = rule.explanation return (