Ajoute un nouveau mécanisme: résoudre la référence circulaire
Ce mécanisme permet d'activer 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 essaie 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.pull/1463/head
parent
25ca91bf0c
commit
4ca9ee36c2
|
@ -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",
|
||||
|
|
|
@ -28,6 +28,8 @@ function buildRuleDependancies(rule: RuleNode): Array<string> {
|
|||
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
|
||||
|
|
|
@ -114,6 +114,8 @@ const traverseASTNode: TraverseFunction<NodeKind> = (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: {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}'`,
|
||||
|
|
|
@ -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<string>
|
||||
parentEvaluationStack?: Array<string>
|
||||
parentRuleStack: Array<string>
|
||||
evaluationRuleStack: Array<string>
|
||||
inversionFail?:
|
||||
| {
|
||||
given: string
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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`,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, unknown> | 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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import Explanation from '../Explanation'
|
||||
import { Mecanism } from './common'
|
||||
|
||||
export default function MecanismRésoudreRéférenceCirculaire({ explanation }) {
|
||||
return (
|
||||
<Mecanism
|
||||
name="résoudre la référence circulaire"
|
||||
value={explanation.valeur}
|
||||
>
|
||||
<p>
|
||||
{' '}
|
||||
Cette valeur a été retrouvé en résolvant la référence circulaire dans la
|
||||
formule ci dessous :{' '}
|
||||
</p>
|
||||
|
||||
<Explanation node={explanation.valeur} />
|
||||
</Mecanism>
|
||||
)
|
||||
}
|
|
@ -19,7 +19,6 @@ export default function Rule({ dottedName, engine, language }) {
|
|||
return <p>Cette règle est introuvable dans la base</p>
|
||||
}
|
||||
const rule = engine.evaluate(engine.getRule(dottedName))
|
||||
|
||||
const { description, question } = rule.rawNode
|
||||
const { parent, valeur } = rule.explanation
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue