✨ Simplify Engine constructor
* remove ParsedRules argument to constructor (was undocumented, so no harm for Publicodes users) * replace by Engine.shallowCopy() * simplify EngineContext in mon-entreprise.pull/1472/head
parent
edb999ee85
commit
abba62fcf3
|
@ -2,27 +2,28 @@
|
|||
|
||||
Ce paquet contient les règles publicodes utilisées sur https://mon-entreprise.fr
|
||||
pour le calcul des cotisations sociales, des impôts et des droits sociaux.
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
npm install publicodes modele-social
|
||||
npm install publicodes modele-social
|
||||
```
|
||||
|
||||
### Exemple d'utilisation
|
||||
```js
|
||||
import Engine, { formatValue } from "publicodes";
|
||||
import rules from "modele-social";
|
||||
|
||||
const engine = new Engine(rules);
|
||||
```js
|
||||
import Engine, { formatValue } from 'publicodes'
|
||||
import rules from 'modele-social'
|
||||
|
||||
const engine = new Engine(rules)
|
||||
|
||||
const net = engine
|
||||
.setSituation({
|
||||
"contrat salarié . rémunération . brut de base": "3000 €/mois",
|
||||
})
|
||||
.evaluate("contrat salarié . rémunération . net");
|
||||
|
||||
console.log(formatValue(net));
|
||||
.setSituation({
|
||||
'contrat salarié . rémunération . brut de base': '3000 €/mois',
|
||||
})
|
||||
.evaluate('contrat salarié . rémunération . net')
|
||||
|
||||
console.log(formatValue(net))
|
||||
```
|
||||
|
||||
|
||||
👉 **[Voir le tutoriel complet](https://mon-entreprise.fr/int%C3%A9gration/biblioth%C3%A8que-de-calcul)**
|
||||
|
|
|
@ -4,14 +4,13 @@ import Header from 'Components/layout/Header'
|
|||
import Route404 from 'Components/Route404'
|
||||
import 'Components/ui/index.css'
|
||||
import {
|
||||
engineOptions,
|
||||
engineFactory,
|
||||
EngineProvider,
|
||||
Rules,
|
||||
SituationProvider,
|
||||
} from 'Components/utils/EngineContext'
|
||||
import { SitePathsContext } from 'Components/utils/SitePathsContext'
|
||||
import 'iframe-resizer'
|
||||
import { DottedName } from 'modele-social'
|
||||
import Engine, { Rule } from 'publicodes'
|
||||
import { useContext, useMemo } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -70,16 +69,13 @@ const middlewares = [createSentryMiddleware(Sentry as any)]
|
|||
|
||||
type RootProps = {
|
||||
basename: ProviderProps['basename']
|
||||
rules: Record<DottedName, Rule>
|
||||
rules: Rules
|
||||
}
|
||||
|
||||
export default function Root({ basename, rules }: RootProps) {
|
||||
const { language } = useTranslation().i18n
|
||||
const paths = constructLocalizedSitePath(language as 'fr' | 'en')
|
||||
const engine = useMemo(() => new Engine(rules, engineOptions), [
|
||||
rules,
|
||||
engineOptions,
|
||||
])
|
||||
const engine = useMemo(() => engineFactory(rules), [rules])
|
||||
return (
|
||||
<Provider
|
||||
basename={basename}
|
||||
|
|
|
@ -9,8 +9,6 @@ import SeeAnswersButton from 'Components/conversation/SeeAnswersButton'
|
|||
import Value from 'Components/EngineValue'
|
||||
import InfoBulle from 'Components/ui/InfoBulle'
|
||||
import revenusSVG from 'Images/revenus.svg'
|
||||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
@ -18,7 +16,7 @@ import { useSelector } from 'react-redux'
|
|||
import { situationSelector } from 'Selectors/simulationSelectors'
|
||||
import dirigeantComparaison from '../pages/Simulateurs/configs/rémunération-dirigeant.yaml'
|
||||
import './SchemeComparaison.css'
|
||||
import { engineOptions, useEngine } from './utils/EngineContext'
|
||||
import { useEngine } from './utils/EngineContext'
|
||||
import useSimulationConfig from './utils/useSimulationConfig'
|
||||
|
||||
type SchemeComparaisonProps = {
|
||||
|
@ -45,13 +43,12 @@ export default function SchemeComparaison({
|
|||
setConversationStarted,
|
||||
])
|
||||
|
||||
const parsedRules = engine.getParsedRules()
|
||||
const situation = useSelector(situationSelector)
|
||||
const displayResult =
|
||||
useSelector(situationSelector)['entreprise . charges'] != undefined
|
||||
const assimiléEngine = useMemo(
|
||||
() =>
|
||||
new Engine<DottedName>(parsedRules, engineOptions).setSituation({
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
dirigeant: "'assimilé salarié'",
|
||||
}),
|
||||
|
@ -59,7 +56,7 @@ export default function SchemeComparaison({
|
|||
)
|
||||
const autoEntrepreneurEngine = useMemo(
|
||||
() =>
|
||||
new Engine<DottedName>(parsedRules, engineOptions).setSituation({
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
dirigeant: "'auto-entrepreneur'",
|
||||
}),
|
||||
|
@ -67,7 +64,7 @@ export default function SchemeComparaison({
|
|||
)
|
||||
const indépendantEngine = useMemo(
|
||||
() =>
|
||||
new Engine<DottedName>(parsedRules, engineOptions).setSituation({
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
dirigeant: "'indépendant'",
|
||||
}),
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import Engine from 'publicodes'
|
||||
import Engine, { Rule } from 'publicodes'
|
||||
import React, { createContext, useContext } from 'react'
|
||||
import { DottedName } from 'modele-social'
|
||||
import i18n from '../../locales/i18n'
|
||||
|
||||
export const EngineContext = createContext<Engine>(new Engine({}))
|
||||
export const EngineProvider = EngineContext.Provider
|
||||
export type Rules = Record<DottedName, Rule>
|
||||
|
||||
const unitsTranslations = Object.entries(i18n.getResourceBundle('fr', 'units'))
|
||||
|
||||
export const engineOptions = {
|
||||
const engineOptions = {
|
||||
getUnitKey(unit: string): string {
|
||||
const key = unitsTranslations
|
||||
.find(([, trans]) => trans === unit)?.[0]
|
||||
|
@ -19,6 +17,12 @@ export const engineOptions = {
|
|||
return i18n?.t(`units:${unit}`, { count })
|
||||
},
|
||||
}
|
||||
export function engineFactory(rules: Rules) {
|
||||
return new Engine(rules, engineOptions)
|
||||
}
|
||||
|
||||
export const EngineContext = createContext<Engine>(new Engine())
|
||||
export const EngineProvider = EngineContext.Provider
|
||||
|
||||
export function useEngine(): Engine<DottedName> {
|
||||
return useContext(EngineContext) as Engine<DottedName>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useSimulationProgress } from 'Components/utils/useNextQuestion'
|
|||
import { useParamsFromSituation } from 'Components/utils/useSearchParamsSimulationSharing'
|
||||
import useSimulationConfig from 'Components/utils/useSimulationConfig'
|
||||
import { DottedName } from 'modele-social'
|
||||
import Engine, { formatValue } from 'publicodes'
|
||||
import { formatValue } from 'publicodes'
|
||||
import { partition } from 'ramda'
|
||||
import { useContext } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
@ -229,7 +229,7 @@ function Results() {
|
|||
const progress = useSimulationProgress()
|
||||
const baseEngine = useEngine()
|
||||
const aidesEngines = aides.map((aide) => {
|
||||
const engine = new Engine(baseEngine.parsedRules)
|
||||
const engine = baseEngine.shallowCopy()
|
||||
engine.setSituation({ ...aide.situation, ...baseEngine.parsedSituation })
|
||||
const isActive =
|
||||
typeof engine.evaluate(aide.dottedName).nodeValue === 'number'
|
||||
|
|
|
@ -6,7 +6,7 @@ import rules from 'modele-social'
|
|||
// les variables dans les tests peuvent être exprimées relativement à l'espace de nom de la règle,
|
||||
// comme dans sa formule
|
||||
let parsedRules = parsePublicodes(rules)
|
||||
const engine = new Engine(parsedRules)
|
||||
const engine = new Engine(rules)
|
||||
let runExamples = (examples, rule) =>
|
||||
examples.map((ex) => {
|
||||
const expected = ex['valeur attendue']
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
// renamed the test configuration may be adapted but the persisted snapshot will remain unchanged).
|
||||
|
||||
/* eslint-disable no-undef */
|
||||
import Engine from 'publicodes'
|
||||
import rules from '../../../modele-social'
|
||||
import { engineOptions } from '../../source/components/utils/EngineContext'
|
||||
import { engineFactory } from '../../source/components/utils/EngineContext'
|
||||
import aideDéclarationConfig from '../../source/pages/Gérer/AideDéclarationIndépendant/config.yaml'
|
||||
import artisteAuteurConfig from '../../source/pages/Simulateurs/configs/artiste-auteur.yaml'
|
||||
import autoentrepreneurConfig from '../../source/pages/Simulateurs/configs/auto-entrepreneur.yaml'
|
||||
|
@ -26,7 +25,7 @@ import remunerationDirigeantSituations from './simulations-rémunération-dirige
|
|||
import employeeSituations from './simulations-salarié.yaml'
|
||||
|
||||
const roundResult = (arr) => arr.map((x) => Math.round(x))
|
||||
const engine = new Engine(rules, engineOptions)
|
||||
const engine = engineFactory(rules)
|
||||
const runSimulations = (situations, targets, baseSituation = {}) =>
|
||||
Object.entries(situations).map(([name, situations]) =>
|
||||
situations.forEach((situation) => {
|
||||
|
|
|
@ -26,14 +26,13 @@ describe('identifiant court', () => {
|
|||
})
|
||||
|
||||
describe('useSearchParamsSimulationSharing', () => {
|
||||
const someRules = parsePublicodes(`
|
||||
const engine = new Engine(`
|
||||
rule with:
|
||||
identifiant court: panta
|
||||
formule: 0
|
||||
rule without:
|
||||
formule: 0
|
||||
`)
|
||||
const engine = new Engine(someRules)
|
||||
const dottedNameParamName = getRulesParamNames(engine.getParsedRules())
|
||||
|
||||
describe('getSearchParamsFromSituation', () => {
|
||||
|
@ -92,7 +91,7 @@ rule without:
|
|||
})
|
||||
|
||||
describe('useSearchParamsSimulationSharing hook', () => {
|
||||
const someRules = parsePublicodes(`
|
||||
const parsedRules = parsePublicodes(`
|
||||
rule with:
|
||||
identifiant court: panta
|
||||
formule: 0
|
||||
|
@ -100,9 +99,7 @@ rule without:
|
|||
formule: 0
|
||||
`)
|
||||
|
||||
const dottedNameParamName = getRulesParamNames(
|
||||
new Engine(someRules).getParsedRules()
|
||||
)
|
||||
const dottedNameParamName = getRulesParamNames(parsedRules)
|
||||
let setSearchParams
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -67,6 +67,7 @@ export type ParsedRules<Name extends string> = Record<
|
|||
Name,
|
||||
RuleNode & { dottedName: Name }
|
||||
>
|
||||
|
||||
export default class Engine<Name extends string = string> {
|
||||
parsedRules: ParsedRules<Name>
|
||||
parsedSituation: Record<string, ASTNode> = {}
|
||||
|
@ -75,25 +76,11 @@ export default class Engine<Name extends string = string> {
|
|||
options: Options
|
||||
|
||||
constructor(
|
||||
rules: string | Record<string, Rule> | ParsedRules<Name> = {},
|
||||
rules: string | Record<string, Rule> = {},
|
||||
options: Partial<Options> = {}
|
||||
) {
|
||||
this.options = { ...options, logger: options.logger ?? console }
|
||||
if (typeof rules === 'string') {
|
||||
rules = parsePublicodes(rules, this.options) as ParsedRules<Name>
|
||||
}
|
||||
const firstRuleObject = Object.values(rules)[0] as Rule | RuleNode
|
||||
if (
|
||||
typeof firstRuleObject !== 'object' ||
|
||||
firstRuleObject == null ||
|
||||
!('nodeKind' in firstRuleObject)
|
||||
) {
|
||||
rules = parsePublicodes(
|
||||
rules as Record<string, Rule>,
|
||||
this.options
|
||||
) as ParsedRules<Name>
|
||||
}
|
||||
this.parsedRules = rules as ParsedRules<Name>
|
||||
this.parsedRules = parsePublicodes(rules, this.options) as ParsedRules<Name>
|
||||
this.replacements = getReplacements(this.parsedRules)
|
||||
}
|
||||
|
||||
|
@ -150,6 +137,10 @@ export default class Engine<Name extends string = string> {
|
|||
return this.parsedRules
|
||||
}
|
||||
|
||||
getOptions(): Options {
|
||||
return this.options
|
||||
}
|
||||
|
||||
evaluate<N extends ASTNode = ASTNode>(value: N): N & EvaluatedNode
|
||||
evaluate(value: PublicodesExpression): EvaluatedNode
|
||||
evaluate(value: PublicodesExpression | ASTNode): EvaluatedNode {
|
||||
|
@ -180,6 +171,17 @@ export default class Engine<Name extends string = string> {
|
|||
this.cache.nodes.set(value, evaluatedNode)
|
||||
return evaluatedNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow Engine instance copy. Keeps references to the original Engine instance attributes.
|
||||
*/
|
||||
shallowCopy(): Engine<Name> {
|
||||
const newEngine = new Engine<Name>()
|
||||
newEngine.options = this.options
|
||||
newEngine.parsedRules = this.parsedRules
|
||||
newEngine.replacements = this.replacements
|
||||
return newEngine
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -51,7 +51,7 @@ describe('inversions', () => {
|
|||
assiette: brut
|
||||
taux: 77%
|
||||
|
||||
brut:
|
||||
brut:
|
||||
formule:
|
||||
inversion numérique:
|
||||
unité: €
|
||||
|
@ -147,7 +147,7 @@ describe('inversions', () => {
|
|||
formule:
|
||||
produit:
|
||||
assiette: assiette
|
||||
taux:
|
||||
taux:
|
||||
variations:
|
||||
- si: cadre
|
||||
alors: 80%
|
||||
|
|
|
@ -64,7 +64,7 @@ export const NodeValuePointer = ({ data, unit }: NodeValuePointerProps) => {
|
|||
}}
|
||||
>
|
||||
{formatValue(simplifyNodeUnit({ nodeValue: data, unit }), {
|
||||
formatUnit: engine?.options?.formatUnit,
|
||||
formatUnit: engine?.getOptions()?.formatUnit,
|
||||
})}
|
||||
</small>
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue