⚙️ Réorganise l'API publique de la lib
parent
c9325b8eed
commit
c65a04cf86
|
@ -121,7 +121,8 @@
|
|||
"@types/color-convert": "^1.9.0",
|
||||
"@types/iframe-resizer": "^3.5.7",
|
||||
"@types/js-yaml": "^3.12.2",
|
||||
"@types/ramda": "^0.26.33",
|
||||
"@types/nearley": "^2.11.1",
|
||||
"@types/ramda": "^0.26.43",
|
||||
"@types/raven-for-redux": "^1.1.1",
|
||||
"@types/react": "^16.9.11",
|
||||
"@types/react-addons-css-transition-group": "^15.0.5",
|
||||
|
|
|
@ -2,7 +2,7 @@ import { coerceArray } from '../utils'
|
|||
export function syntaxError(
|
||||
rules: string[] | string,
|
||||
message: string,
|
||||
originalError: Error
|
||||
originalError?: Error
|
||||
) {
|
||||
throw new Error(
|
||||
`\n[ Erreur syntaxique ]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { bonus, evaluateNode, mergeMissing } from 'Engine/evaluation'
|
||||
import { map, mergeAll, pick, pipe } from 'ramda'
|
||||
import { Rule } from 'Types/rule'
|
||||
import { typeWarning } from './error'
|
||||
import { convertNodeToUnit } from './nodeUnits'
|
||||
import { anyNull, undefOrTruthy, val } from './traverse-common-functions'
|
||||
|
@ -9,12 +10,14 @@ export const evaluateApplicability = (
|
|||
cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
node
|
||||
node: Rule
|
||||
) => {
|
||||
let evaluatedAttributes = pipe(
|
||||
pick(['non applicable si', 'applicable si', 'rendu non applicable']),
|
||||
pick(['non applicable si', 'applicable si', 'rendu non applicable']) as (
|
||||
x: any
|
||||
) => any,
|
||||
map(value => evaluateNode(cache, situationGate, parsedRules, value))
|
||||
)(node),
|
||||
)(node) as any,
|
||||
{
|
||||
'non applicable si': notApplicable,
|
||||
'applicable si': applicable,
|
||||
|
@ -51,39 +54,43 @@ export const evaluateApplicability = (
|
|||
|
||||
export default (cache, situationGate, parsedRules, node) => {
|
||||
cache._meta.contextRule.push(node.dottedName)
|
||||
let applicabilityEvaluation = evaluateApplicability(
|
||||
cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
node
|
||||
),
|
||||
{
|
||||
missingVariables: condMissing,
|
||||
nodeValue: isApplicable
|
||||
} = applicabilityEvaluation,
|
||||
evaluateFormula = () =>
|
||||
node.formule
|
||||
? evaluateNode(cache, situationGate, parsedRules, node.formule)
|
||||
: {},
|
||||
// evaluate the formula lazily, only if the applicability is known and true
|
||||
evaluatedFormula = isApplicable
|
||||
? evaluateFormula()
|
||||
: isApplicable === false
|
||||
? {
|
||||
...node.formule,
|
||||
missingVariables: {},
|
||||
nodeValue: 0
|
||||
}
|
||||
: {
|
||||
...node.formule,
|
||||
missingVariables: {},
|
||||
nodeValue: null
|
||||
},
|
||||
{ missingVariables: formulaMissingVariables, nodeValue } = evaluatedFormula,
|
||||
missingVariables = mergeMissing(
|
||||
bonus(condMissing, !!Object.keys(condMissing).length),
|
||||
formulaMissingVariables
|
||||
)
|
||||
const applicabilityEvaluation = evaluateApplicability(
|
||||
cache,
|
||||
situationGate,
|
||||
parsedRules,
|
||||
node
|
||||
)
|
||||
const {
|
||||
missingVariables: condMissing,
|
||||
nodeValue: isApplicable
|
||||
} = applicabilityEvaluation
|
||||
|
||||
const evaluateFormula = () =>
|
||||
node.formule
|
||||
? evaluateNode(cache, situationGate, parsedRules, node.formule)
|
||||
: {}
|
||||
// evaluate the formula lazily, only if the applicability is known and true
|
||||
const evaluatedFormula = isApplicable
|
||||
? evaluateFormula()
|
||||
: isApplicable === false
|
||||
? {
|
||||
...node.formule,
|
||||
missingVariables: {},
|
||||
nodeValue: 0
|
||||
}
|
||||
: {
|
||||
...node.formule,
|
||||
missingVariables: {},
|
||||
nodeValue: null
|
||||
}
|
||||
let {
|
||||
missingVariables: formulaMissingVariables,
|
||||
nodeValue
|
||||
} = evaluatedFormula
|
||||
const missingVariables = mergeMissing(
|
||||
bonus(condMissing, !!Object.keys(condMissing).length),
|
||||
formulaMissingVariables
|
||||
)
|
||||
const unit =
|
||||
node.unit ||
|
||||
(node.defaultUnit &&
|
|
@ -1,22 +0,0 @@
|
|||
L'objectif dans un premier temps est de faire une démonstration d'interface de saisie pour calculer les obligations du CDD. Le CDD est un contrat d'exception, ce qui le rend assez complexe.
|
||||
|
||||
Les Variables du système social comportent des entrées particulières (des cléfs de l'objet) que l'on nommera "mécanismes". Elles diffèrent des entrées simples (telle la description d'une variable) car elles sont susceptibles de comporter des variables, et la description de calculs.
|
||||
|
||||
Voici une liste des mécanismes qui sont à traverser pour notamment :
|
||||
- d'extraire les variables utilisées et donc que l'utilisateur devra saisir
|
||||
- d'attribuer une note à ces variables pour déterminer la prochaine saisie (ou "question")
|
||||
- de court-circuiter les branches rendues inutiles par la situation courante saisie par l'utilisateur, ce qui influence la note.
|
||||
|
||||
Dans un premier temps, l'idée est de créer un formulaire à questions unitaires optimisées pour que la saisie de données inutiles soit évitée.
|
||||
|
||||
- Il faut résoudre le calcul des variables.
|
||||
- ça introduit l'assiette 2300€
|
||||
- ça nous donne son intervalle en % vu que l'assiette est constante, et donc un avancement du formulaire
|
||||
- ça nous donne des résultats !!!!!! :))
|
||||
|
||||
- Il faut donner des explications à TOUTES les variables. Aucune n'est triviale !
|
||||
|
||||
- Il faut ajouter la notion d'entité.
|
||||
- Ce qui permettra de regrouper les questions par thème. Exemple, demander "Quel type de CDD" et ordonner les réponses possibles par influence (e.g. si c'est un CDD d'usage )
|
||||
|
||||
-----------------------------------------
|
|
@ -1,69 +0,0 @@
|
|||
// This file exports the functions of the public computing library
|
||||
import { safeLoad } from 'js-yaml'
|
||||
import { collectDefaults, enrichRule, rulesFr } from './rules'
|
||||
import { analyseMany, parseAll } from './traverse'
|
||||
|
||||
// The public evaluation function takes a nested object of input values
|
||||
let inputToStateSelector = rules => input => dottedName =>
|
||||
({
|
||||
...collectDefaults(rules),
|
||||
...input
|
||||
}[dottedName])
|
||||
|
||||
let enrichRules = input => {
|
||||
const rules = typeof input === 'string' ? safeLoad(input) : input
|
||||
const rulesList = Array.isArray(rules)
|
||||
? rules
|
||||
: Object.entries(rules).map(([dottedName, rule]) => ({
|
||||
dottedName,
|
||||
...rule
|
||||
}))
|
||||
return rulesList.map(enrichRule)
|
||||
}
|
||||
|
||||
class Engine {
|
||||
situation = {}
|
||||
parsedRules
|
||||
constructor(rules = rulesFr) {
|
||||
this.parsedRules = parseAll(rules)
|
||||
this.defaultValues = collectDefaults(rules)
|
||||
}
|
||||
evaluate(targets, { defaultUnits, situation, withDefaultValues = true }) {
|
||||
this.evaluation = analyseMany(
|
||||
this.parsedRules,
|
||||
targets,
|
||||
defaultUnits
|
||||
)(
|
||||
dottedName =>
|
||||
situation[dottedName] ||
|
||||
(withDefaultValues && this.defaultValues[dottedName])
|
||||
)
|
||||
return this.evaluation.targets.map(({ nodeValue }) => nodeValue)
|
||||
}
|
||||
getLastEvaluationExplanations() {
|
||||
return this.evaluation
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
evaluate: (targetInput, input, config, defaultUnits = []) => {
|
||||
let rules = config
|
||||
? [
|
||||
...(config.base ? enrichRules(config.base) : rulesFr),
|
||||
...(config.extra ? enrichRules(config.extra) : [])
|
||||
]
|
||||
: rulesFr
|
||||
|
||||
let evaluation = analyseMany(
|
||||
parseAll(rules),
|
||||
Array.isArray(targetInput) ? targetInput : [targetInput],
|
||||
defaultUnits
|
||||
)(inputToStateSelector(rules)(input))
|
||||
if (config?.debug) return evaluation
|
||||
|
||||
let values = evaluation.targets.map(t => t.nodeValue)
|
||||
|
||||
return Array.isArray(targetInput) ? values : values[0]
|
||||
},
|
||||
Engine
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
import { safeLoad } from 'js-yaml'
|
||||
import { Simulation } from 'Reducers/rootReducer'
|
||||
import { DottedName, Rule } from 'Types/rule'
|
||||
import { evaluateNode } from './evaluation'
|
||||
import { collectDefaults, enrichRule, rulesFr } from './rules'
|
||||
import { parseAll } from './traverse'
|
||||
import { parseUnit } from './units'
|
||||
|
||||
const emptyCache = {
|
||||
_meta: { contextRule: [], defaultUnits: [] }
|
||||
}
|
||||
|
||||
type EngineConfig = {
|
||||
rules?: string | Array<any> | object
|
||||
extra?: string | Array<any> | object
|
||||
}
|
||||
|
||||
let enrichRules = input => {
|
||||
const rules = typeof input === 'string' ? safeLoad(input) : input
|
||||
const rulesList = Array.isArray(rules)
|
||||
? rules
|
||||
: Object.entries(rules).map(([dottedName, rule]) => ({
|
||||
dottedName,
|
||||
...(rule as any)
|
||||
}))
|
||||
return rulesList.map(enrichRule)
|
||||
}
|
||||
|
||||
export default class Engine {
|
||||
parsedRules: Record<DottedName, Rule>
|
||||
defaultValues: Simulation['situation']
|
||||
situation: Simulation['situation'] = {}
|
||||
cache = { ...emptyCache }
|
||||
|
||||
constructor(config: EngineConfig = {}) {
|
||||
const rules = config
|
||||
? [
|
||||
...(config.rules ? enrichRules(config.rules) : rulesFr),
|
||||
...(config.extra ? enrichRules(config.extra) : [])
|
||||
]
|
||||
: rulesFr
|
||||
this.parsedRules = parseAll(rules) as any
|
||||
this.defaultValues = collectDefaults(rules)
|
||||
}
|
||||
|
||||
private resetCache() {
|
||||
this.cache = { ...emptyCache }
|
||||
}
|
||||
|
||||
setSituation(situation: Simulation['situation'] = {}) {
|
||||
this.situation = situation
|
||||
this.resetCache()
|
||||
}
|
||||
|
||||
setDefaultUnits(defaultUnits = []) {
|
||||
this.cache._meta.defaultUnits = defaultUnits.map(unit =>
|
||||
parseUnit(unit)
|
||||
) as any
|
||||
}
|
||||
|
||||
evaluate(expression: string | Array<string>) {
|
||||
const results = (Array.isArray(expression) ? expression : [expression]).map(
|
||||
expr =>
|
||||
this.cache[expr] ||
|
||||
(this.parsedRules[expr]
|
||||
? evaluateNode(
|
||||
this.cache,
|
||||
this.situationGate,
|
||||
this.parsedRules,
|
||||
this.parsedRules[expr]
|
||||
// TODO: To support expressions (with operations, unit conversion,
|
||||
// etc.) it should be enough to replace the above line with :
|
||||
// parse(this.parsedRules, { dottedName: '' }, this.parsedRules)(expr)
|
||||
// But currently there are small side effects (null values converted
|
||||
// to 0), so we need to modify a little bit the engine before enabling
|
||||
// publicode expressions in the UI.
|
||||
)
|
||||
: null)
|
||||
)
|
||||
|
||||
return Array.isArray(expression) ? results : results[0]
|
||||
}
|
||||
|
||||
situationGate = (dottedName: string) =>
|
||||
this.situation[dottedName] || this.defaultValues[dottedName]
|
||||
}
|
|
@ -25,7 +25,7 @@ import {
|
|||
subtract
|
||||
} from 'ramda'
|
||||
import React from 'react'
|
||||
import { syntaxError } from './error.ts'
|
||||
import { syntaxError } from './error'
|
||||
import grammar from './grammar.ne'
|
||||
import {
|
||||
mecanismAllOf,
|
||||
|
@ -69,7 +69,7 @@ Utilisez leur contrepartie française : 'oui' / 'non'`
|
|||
|
||||
const compiledGrammar = Grammar.fromCompiled(grammar)
|
||||
|
||||
const parseExpression = (rule, rawNode) => {
|
||||
export const parseExpression = (rule, rawNode) => {
|
||||
/* Strings correspond to infix expressions.
|
||||
* Indeed, a subset of expressions like simple arithmetic operations `3 + (quantity * 2)` or like `salary [month]` are more explicit that their prefixed counterparts.
|
||||
* This function makes them prefixed operations. */
|
|
@ -1,13 +1,13 @@
|
|||
import { ShowValuesConsumer } from 'Components/rule/ShowValuesContext'
|
||||
import RuleLink from 'Components/RuleLink'
|
||||
import evaluate from 'Engine/evaluateRule'
|
||||
import { parse } from 'Engine/parse'
|
||||
import { evolve, map } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { coerceArray } from '../utils'
|
||||
import evaluate from './evaluateRule'
|
||||
import { evaluateNode, makeJsx, mergeAllMissing } from './evaluation'
|
||||
import { Node } from './mecanismViews/common'
|
||||
import { parse } from './parse'
|
||||
import { disambiguateRuleReference, findParentDependencies } from './rules'
|
||||
|
||||
export default (rules, rule, parsedRules) => {
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import Value from 'Components/Value'
|
||||
import React, { createContext, useContext, useMemo } from 'react'
|
||||
import Engine from '.'
|
||||
|
||||
const EngineContext = createContext(new Engine())
|
||||
|
||||
type InputProps = {
|
||||
rules?: any
|
||||
situation?: any
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function Provider({ rules, situation, children }: InputProps) {
|
||||
const engine = useMemo(() => new Engine({ rules }), [rules])
|
||||
if (!Object.is(situation, engine.situation)) {
|
||||
engine.setSituation(situation)
|
||||
}
|
||||
return (
|
||||
<EngineContext.Provider value={engine}>{children}</EngineContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useEvaluation(expression: string) {
|
||||
const engine = useContext(EngineContext)
|
||||
return engine.evaluate(expression)
|
||||
}
|
||||
|
||||
export function Evaluation({ expression }) {
|
||||
const value = useEvaluation(expression)
|
||||
return <Value {...value} />
|
||||
}
|
||||
|
||||
export default {
|
||||
Provider,
|
||||
useEvaluation,
|
||||
Evaluation
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
import baremeIr from '!!raw-loader!./exemples/bareme-ir.yaml'
|
||||
import douche from '!!raw-loader!./exemples/douche.yaml'
|
||||
import { ControlledEditor } from '@monaco-editor/react'
|
||||
import { formatValue } from 'Engine/format'
|
||||
import Engine from 'Engine/index'
|
||||
import { buildFlatRules } from 'Engine/rules'
|
||||
import Engine from 'Engine/react'
|
||||
import { safeLoad } from 'js-yaml'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useLocation } from 'react-router'
|
||||
import styled from 'styled-components'
|
||||
|
@ -63,75 +61,69 @@ export function Studio() {
|
|||
)
|
||||
const [targets, setTargets] = useState<string[]>([])
|
||||
const [currentTarget, setCurrentTarget] = useState('')
|
||||
const [analysis, setAnalysis] = useState()
|
||||
const engine = useRef<any>(null)
|
||||
const [rules, setRules] = useState(editorValue)
|
||||
|
||||
try {
|
||||
setTargets(Object.keys(safeLoad(editorValue) ?? {}))
|
||||
} catch {}
|
||||
|
||||
function updateResult() {
|
||||
engine.current = new Engine.Engine(buildFlatRules(safeLoad(editorValue)))
|
||||
engine.current.evaluate(
|
||||
[targets.includes(currentTarget) ? currentTarget : targets[0]],
|
||||
{ defaultUnits: [], situation: {} }
|
||||
)
|
||||
setAnalysis(engine.current.getLastEvaluationExplanations()?.targets?.[0])
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<section>
|
||||
<ControlledEditor
|
||||
<Engine.Provider rules={rules}>
|
||||
<Layout>
|
||||
<section>
|
||||
<ControlledEditor
|
||||
css={`
|
||||
height: 100%;
|
||||
`}
|
||||
language="yaml"
|
||||
value={editorValue}
|
||||
onChange={(ev, newValue) => setEditorValue(newValue ?? '')}
|
||||
options={{ minimap: { enabled: false } }}
|
||||
/>
|
||||
</section>
|
||||
<section
|
||||
css={`
|
||||
height: 100%;
|
||||
`}
|
||||
language="yaml"
|
||||
value={editorValue}
|
||||
onChange={(ev, newValue) => setEditorValue(newValue ?? '')}
|
||||
options={{ minimap: { enabled: false } }}
|
||||
/>
|
||||
</section>
|
||||
<section
|
||||
css={`
|
||||
padding: 30px 20px;
|
||||
`}
|
||||
>
|
||||
<div
|
||||
css={`
|
||||
background: var(--lighterColor);
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
padding: 30px 20px;
|
||||
`}
|
||||
>
|
||||
<label htmlFor="objectif">Que voulez-vous calculer ? </label>
|
||||
<select
|
||||
id="objectif"
|
||||
onChange={e => {
|
||||
setCurrentTarget(e.target.value)
|
||||
}}
|
||||
<div
|
||||
css={`
|
||||
padding: 5px;
|
||||
background: var(--lighterColor);
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
`}
|
||||
>
|
||||
{targets.map(target => (
|
||||
<option key={target} value={target}>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<br />
|
||||
<button
|
||||
css="display: block; margin-top: 1rem"
|
||||
className="ui__ button small"
|
||||
onClick={() => updateResult()}
|
||||
>
|
||||
{emoji('▶️')} Calculer
|
||||
</button>
|
||||
</div>
|
||||
<Results analysis={analysis} />
|
||||
</section>
|
||||
</Layout>
|
||||
<label htmlFor="objectif">Que voulez-vous calculer ? </label>
|
||||
<select
|
||||
id="objectif"
|
||||
onChange={e => {
|
||||
setCurrentTarget(e.target.value)
|
||||
}}
|
||||
css={`
|
||||
padding: 5px;
|
||||
`}
|
||||
>
|
||||
{targets.map(target => (
|
||||
<option key={target} value={target}>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<br />
|
||||
<br />
|
||||
<button
|
||||
className="ui__ button small"
|
||||
onClick={() => setRules(editorValue)}
|
||||
>
|
||||
{emoji('▶️')} Recalculer
|
||||
</button>
|
||||
</div>
|
||||
<Results
|
||||
rule={targets.includes(currentTarget) ? currentTarget : targets[0]}
|
||||
/>
|
||||
</section>
|
||||
</Layout>
|
||||
</Engine.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -153,18 +145,15 @@ const Layout = styled.div`
|
|||
}
|
||||
`
|
||||
|
||||
export const Results = ({ analysis }) => {
|
||||
export const Results = ({ rule }) => {
|
||||
const analysis = Engine.useEvaluation(rule)
|
||||
return analysis ? (
|
||||
<div>
|
||||
<h2>Résultats</h2>
|
||||
{analysis.isApplicable === false ? (
|
||||
<>❌ Cette règle n'est pas applicable</>
|
||||
<>{emoji('❌')} Cette règle n'est pas applicable</>
|
||||
) : (
|
||||
formatValue({
|
||||
language: 'fr',
|
||||
value: analysis.nodeValue,
|
||||
unit: analysis.unit
|
||||
})
|
||||
<Engine.Evaluation expression={rule} />
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
declare module '*.ne' {
|
||||
const content: any
|
||||
export default content
|
||||
}
|
|
@ -10,6 +10,7 @@ export type Rule = {
|
|||
summary?: string
|
||||
title?: string
|
||||
defaultValue: any
|
||||
parentDependencies: Array<Rule>
|
||||
icons: string
|
||||
formule: any
|
||||
}
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
import { expect } from 'chai'
|
||||
import Lib from '../source/engine/index'
|
||||
import Engine from '../source/engine/index'
|
||||
import co2 from './rules/co2.yaml'
|
||||
import sasuRules from './rules/sasu.yaml'
|
||||
|
||||
describe('library', function() {
|
||||
it('should evaluate one target with no input data', function() {
|
||||
let target = 'contrat salarié . rémunération . net'
|
||||
let value = Lib.evaluate(target, {
|
||||
let engine = new Engine()
|
||||
engine.setSituation({
|
||||
'contrat salarié . rémunération . brut de base': 2300
|
||||
})
|
||||
expect(value).to.be.within(1798, 1800)
|
||||
expect(engine.evaluate(target).nodeValue).to.be.within(1798, 1800)
|
||||
})
|
||||
|
||||
it('should let the user replace the default rules', function() {
|
||||
let rules = `
|
||||
- nom: yo
|
||||
yo:
|
||||
formule: 200
|
||||
- nom: ya
|
||||
ya:
|
||||
formule: yo + 1
|
||||
- nom: yi
|
||||
yi:
|
||||
formule: yo + 2
|
||||
`
|
||||
let engine = new Engine({ rules })
|
||||
|
||||
let values = Lib.evaluate(['ya', 'yi'], {}, { base: rules })
|
||||
|
||||
expect(values[0]).to.equal(201)
|
||||
expect(values[1]).to.equal(202)
|
||||
expect(engine.evaluate('ya').nodeValue).to.equal(201)
|
||||
expect(engine.evaluate('yi').nodeValue).to.equal(202)
|
||||
})
|
||||
|
||||
it('should let the user add rules to the default ones', function() {
|
||||
let rules = `
|
||||
yo:
|
||||
|
@ -34,60 +35,54 @@ yo:
|
|||
ya:
|
||||
formule: contrat salarié . rémunération . net + yo
|
||||
`
|
||||
|
||||
let value = Lib.evaluate(
|
||||
'ya',
|
||||
{
|
||||
'contrat salarié . rémunération . brut de base': 2300
|
||||
},
|
||||
{ extra: rules }
|
||||
)
|
||||
|
||||
expect(value).to.be.closeTo(1799, 1)
|
||||
let engine = new Engine({ extra: rules })
|
||||
engine.setSituation({
|
||||
'contrat salarié . rémunération . brut de base': 2300
|
||||
})
|
||||
expect(engine.evaluate('ya').nodeValue).to.be.closeTo(1799, 1)
|
||||
})
|
||||
|
||||
it('should let the user extend the rules constellation in a serious manner', function() {
|
||||
let CA = 550 * 16
|
||||
let salaireTotal = Lib.evaluate(
|
||||
'salaire total',
|
||||
{
|
||||
'chiffre affaires': CA
|
||||
},
|
||||
{ extra: sasuRules }
|
||||
)
|
||||
let engine = new Engine({ extra: sasuRules })
|
||||
engine.setSituation({
|
||||
'chiffre affaires': CA
|
||||
})
|
||||
let salaireTotal = engine.evaluate('salaire total').nodeValue
|
||||
|
||||
let salaireNetAprèsImpôt = Lib.evaluate(
|
||||
engine.setSituation({
|
||||
'contrat salarié . prix du travail': salaireTotal
|
||||
})
|
||||
let salaireNetAprèsImpôt = engine.evaluate(
|
||||
'contrat salarié . rémunération . net après impôt'
|
||||
).nodeValue
|
||||
|
||||
engine.setSituation({
|
||||
'contrat salarié . rémunération . net après impôt': salaireNetAprèsImpôt,
|
||||
'chiffre affaires': CA
|
||||
})
|
||||
let [revenuDisponible, dividendes] = engine.evaluate([
|
||||
'contrat salarié . rémunération . net après impôt',
|
||||
{
|
||||
'contrat salarié . prix du travail': salaireTotal
|
||||
}
|
||||
)
|
||||
let [revenuDisponible, dividendes] = Lib.evaluate(
|
||||
['contrat salarié . rémunération . net après impôt', 'dividendes . net'],
|
||||
{
|
||||
'contrat salarié . rémunération . net après impôt': salaireNetAprèsImpôt,
|
||||
'chiffre affaires': CA
|
||||
},
|
||||
{ extra: sasuRules }
|
||||
)
|
||||
'dividendes . net'
|
||||
])
|
||||
|
||||
expect(revenuDisponible).to.be.closeTo(2324, 1)
|
||||
expect(dividendes).to.be.closeTo(2507, 1)
|
||||
expect(revenuDisponible.nodeValue).to.be.closeTo(2324, 1)
|
||||
expect(dividendes.nodeValue).to.be.closeTo(2507, 1)
|
||||
}).timeout(5000)
|
||||
|
||||
it('should let the user define a simplified revenue tax system', function() {
|
||||
let règles = `
|
||||
- nom: revenu imposable
|
||||
let rules = `
|
||||
revenu imposable:
|
||||
question: Quel est votre revenu imposable ?
|
||||
unité: €
|
||||
|
||||
- nom: revenu abattu
|
||||
revenu abattu:
|
||||
formule:
|
||||
allègement:
|
||||
assiette: revenu imposable
|
||||
abattement: 10%
|
||||
|
||||
|
||||
- nom: impôt sur le revenu
|
||||
impôt sur le revenu:
|
||||
formule:
|
||||
barème:
|
||||
assiette: revenu abattu
|
||||
|
@ -102,8 +97,7 @@ ya:
|
|||
plafond: 153783
|
||||
- taux: 45%
|
||||
|
||||
|
||||
- nom: impôt sur le revenu à payer
|
||||
impôt sur le revenu à payer:
|
||||
formule:
|
||||
allègement:
|
||||
assiette: impôt sur le revenu
|
||||
|
@ -112,28 +106,22 @@ ya:
|
|||
plafond: 1177
|
||||
`
|
||||
|
||||
let target = 'impôt sur le revenu à payer'
|
||||
|
||||
let value = Lib.evaluate(
|
||||
target,
|
||||
{ 'revenu imposable': '48000' },
|
||||
{ base: règles }
|
||||
)
|
||||
expect(value).to.equal(7253.26)
|
||||
let engine = new Engine({ rules })
|
||||
engine.setSituation({
|
||||
'revenu imposable': '48000'
|
||||
})
|
||||
let value = engine.evaluate('impôt sur le revenu à payer')
|
||||
expect(value.nodeValue).to.equal(7253.26)
|
||||
})
|
||||
it('should let let user define a rule base on a completely different subject', function() {
|
||||
let targets = 'impact'
|
||||
|
||||
let value = Lib.evaluate(
|
||||
targets,
|
||||
{
|
||||
'nombre de douches': 30,
|
||||
'chauffage . type': 'gaz',
|
||||
'durée de la douche': 10
|
||||
},
|
||||
{ base: co2, debug: false }
|
||||
)
|
||||
//console.log(JSON.stringify(value.targets[0], null, 4))
|
||||
expect(value).to.be.within(20, 21)
|
||||
it('should let the user define a rule base on a completely different subject', function() {
|
||||
let engine = new Engine({ rules: co2 })
|
||||
engine.setSituation({
|
||||
'nombre de douches': 30,
|
||||
'chauffage . type': 'gaz',
|
||||
'durée de la douche': 10
|
||||
})
|
||||
let value = engine.evaluate('douche . impact')
|
||||
expect(value.nodeValue).to.be.within(20, 21)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -11,7 +11,7 @@ import autoentrepreneurConfig from '../../source/components/simulationConfigs/au
|
|||
import independantConfig from '../../source/components/simulationConfigs/indépendant.yaml'
|
||||
import remunerationDirigeantConfig from '../../source/components/simulationConfigs/rémunération-dirigeant.yaml'
|
||||
import employeeConfig from '../../source/components/simulationConfigs/salarié.yaml'
|
||||
import Lib from '../../source/engine/index'
|
||||
import Engine from '../../source/engine'
|
||||
import artisteAuteurSituations from './simulations-artiste-auteur.yaml'
|
||||
import autoEntrepreneurSituations from './simulations-auto-entrepreneur.yaml'
|
||||
import independentSituations from './simulations-indépendant.yaml'
|
||||
|
@ -19,7 +19,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 Lib.Engine()
|
||||
const engine = new Engine()
|
||||
const runSimulations = (
|
||||
situations,
|
||||
targets,
|
||||
|
@ -29,10 +29,9 @@ const runSimulations = (
|
|||
) =>
|
||||
Object.entries(situations).map(([name, situations]) =>
|
||||
situations.forEach(situation => {
|
||||
const res = engine.evaluate(targets, {
|
||||
situation: { ...baseSituation, ...situation },
|
||||
defaultUnits
|
||||
})
|
||||
engine.setSituation({ ...baseSituation, ...situation })
|
||||
engine.setDefaultUnits(defaultUnits)
|
||||
const res = engine.evaluate(targets).map(node => node.nodeValue)
|
||||
// Stringify is not required, but allows the result to be displayed in a single
|
||||
// line in the snapshot, which considerably reduce the number of lines of this snapshot
|
||||
// and improve its readability.
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"Components": ["components"],
|
||||
"Components/*": ["components/*"],
|
||||
"Ui/*": ["components/ui/*"],
|
||||
"Engine": ["engine"],
|
||||
"Engine/*": ["engine/*"],
|
||||
"Images/*": ["images/*"],
|
||||
"Reducers/*": ["reducers/*"],
|
||||
|
|
23
yarn.lock
23
yarn.lock
|
@ -1200,6 +1200,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.0.tgz#9ca52cda363f699c69466c2a6ccdaad913ea7a73"
|
||||
integrity sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM=
|
||||
|
||||
"@types/nearley@^2.11.1":
|
||||
version "2.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/nearley/-/nearley-2.11.1.tgz#6ac3f57c00ca28071a1774ec72d2e45750f21420"
|
||||
integrity sha512-oaAg5gn74VFpPYs6Ou2pjDao3WJxnlnH29q9rLOxSGb0PTw2QtBQcTAN9xs1OAHrtI9En5kIXKM96stf7//c9w==
|
||||
|
||||
"@types/node@*":
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.1.tgz#6d11a8c2d58405b3db9388ab740106cbfa64c3c9"
|
||||
|
@ -1215,12 +1220,12 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||
|
||||
"@types/ramda@^0.26.33":
|
||||
version "0.26.39"
|
||||
resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.26.39.tgz#bb392cdb4d431cf6884b3ef11bef635c5d20274f"
|
||||
integrity sha512-3bu32X02VpjJhsYPUWkdOQGoBXjb/UveZgGg4IYMm+SPAXio96BOYrRhVELfM4AoP00sxoi/f2tqrXdwtR4jjg==
|
||||
"@types/ramda@^0.26.43":
|
||||
version "0.26.43"
|
||||
resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.26.43.tgz#62e235ea17133b8629bc891a26851cf0ea2b4204"
|
||||
integrity sha512-VK2EaHR/fpeMNPDboGSPAmH+a6HN1pflWqRt67Jii2n8vGpYt6vxIBc1ZdoYrA/jQXRaGGpJKRiSPcHALRD3/A==
|
||||
dependencies:
|
||||
ts-toolbelt "^4.12.0"
|
||||
ts-toolbelt "^6.3.3"
|
||||
|
||||
"@types/raven-for-redux@^1.1.1":
|
||||
version "1.1.1"
|
||||
|
@ -11030,10 +11035,10 @@ trough@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e"
|
||||
integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==
|
||||
|
||||
ts-toolbelt@^4.12.0:
|
||||
version "4.14.6"
|
||||
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-4.14.6.tgz#9a232f62276caeee4fa9e81e0c4bffa047de0765"
|
||||
integrity sha512-SONcnRd93+LuYGfn/CZg5A5qhCODohZslAVZKHHu5bnwUxoXLqd2k2VIdwRUXYfKnY+UCeNbI2pTPz+Dno6Mpg==
|
||||
ts-toolbelt@^6.3.3:
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.3.5.tgz#5cb4e0454ab954faa9b6e4d5bce366fdb262e364"
|
||||
integrity sha512-Xvh/gvBBCRU1qGeholaN8kgiwBH4neyun6VIDDsJf/jNwz4PXyR8ZY/5qdpB1DuMBrWMG2oTT1oWcOzGPOnluQ==
|
||||
|
||||
tslib@^1.9.0:
|
||||
version "1.10.0"
|
||||
|
|
Loading…
Reference in New Issue