💚 corrige pour faire passer les tests de non regression

refacto-evaluation-règle
Johan Girod 2020-11-30 11:43:58 +01:00
parent 553eef41e8
commit e8722a46bc
61 changed files with 598 additions and 867 deletions

View File

@ -13,7 +13,7 @@ import { RootState } from 'Reducers/rootReducer'
import './Notifications.css'
import { Markdown } from './utils/markdown'
import { ScrollToElement } from './utils/Scroll'
import Engine, { EvaluatedRule, ASTNode } from 'publicodes'
import Engine, { EvaluatedRule, ASTNode, evaluateRule } from 'publicodes'
// To add a new notification to a simulator, you should create a publicode rule
// with the "type: notification" attribute. The display can be customized with
@ -30,9 +30,8 @@ export function getNotifications(engine: Engine) {
(rule: ASTNode & { nodeKind: 'rule' }) =>
rule.rawNode['type'] === 'notification'
)
.map(node => engine.evaluateNode(node))
.map(node => evaluateRule(engine, node.dottedName))
.filter(node => !!node.nodeValue)
.map(node => node.rawNode)
}
export default function Notifications() {
const { t } = useTranslation()

View File

@ -1,11 +1,15 @@
import { formatValue } from 'publicodes'
import { Evaluation } from 'publicodes/dist/types/AST/types'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { debounce as debounceFn } from '../utils'
import { InputCommonProps } from './conversation/RuleInput'
import './PercentageField.css'
type PercentageFieldProps = InputCommonProps & { debounce: number }
type PercentageFieldProps = InputCommonProps & {
debounce: number
value: Evaluation<string>
}
export default function PercentageField({
onChange,

View File

@ -100,7 +100,8 @@ const Target = ({ dottedName }: TargetProps) => {
[target.dottedName, dispatch]
)
const isSmallTarget = !!target.question !== !!target.formule
const isSmallTarget =
!target.question || !!target.question !== !!target.formule
if (
target.nodeValue === false ||
(isSmallTarget && !target.question && !target.nodeValue)

View File

@ -85,7 +85,6 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
<fieldset>
<RuleInput
dottedName={currentQuestion}
value={situation[currentQuestion]}
onChange={onChange}
onSubmit={submit}
/>

View File

@ -1,14 +1,17 @@
import { RuleInputProps } from 'Components/conversation/RuleInput'
import {
InputCommonProps,
RuleInputProps
} from 'Components/conversation/RuleInput'
import { EvaluatedRule } from 'publicodes'
import { useCallback, useMemo } from 'react'
import styled from 'styled-components'
import InputSuggestions from './InputSuggestions'
type DateInputProps = {
onChange: RuleInputProps['onChange']
id: RuleInputProps['id']
onChange: InputCommonProps['onChange']
id: InputCommonProps['id']
onSubmit: RuleInputProps['onSubmit']
value: RuleInputProps['value']
value: InputCommonProps['value']
suggestions: EvaluatedRule['suggestions']
}
@ -28,7 +31,7 @@ export default function DateInput({
const handleDateChange = useCallback(
evt => {
if (!evt.target.value) {
return onChange(null)
return
}
const [year, month, day] = evt.target.value.split('-')
if (+year < 1700) {

View File

@ -1,5 +1,5 @@
import { formatValue } from 'publicodes'
import { Unit } from 'publicodes/dist/types/AST/types'
import { Evaluation, Unit } from 'publicodes/dist/types/AST/types'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import NumberFormat from 'react-number-format'
@ -21,6 +21,7 @@ export default function Input({
}: InputCommonProps & {
onSubmit: (source: string) => void
unit: Unit | undefined
value: Evaluation<number>
}) {
const debouncedOnChange = useCallback(debounce(550, onChange), [])
const { language } = useTranslation().i18n
@ -43,7 +44,6 @@ export default function Input({
autoFocus={autoFocus}
className="suffixed ui__"
id={id}
placeholder={missing && value}
thousandSeparator={thousandSeparator}
decimalSeparator={decimalSeparator}
allowEmptyFormatting={true}
@ -54,8 +54,8 @@ export default function Input({
debouncedOnChange({ valeur: floatValue, unité })
}
}}
value={!missing && value}
autoComplete="off"
{...{ [missing ? 'placeholder' : 'value']: value || '' }}
/>
<span className="suffix">&nbsp;{unité}</span>
</div>

View File

@ -1,3 +1,4 @@
import { Evaluation } from 'publicodes/dist/types/AST/types'
import { useCallback } from 'react'
import { debounce } from '../../utils'
import { InputCommonProps } from './RuleInput'
@ -8,7 +9,7 @@ export default function ParagrapheInput({
id,
missing,
autoFocus
}: InputCommonProps) {
}: InputCommonProps & { value: Evaluation<string> }) {
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
return (
@ -19,11 +20,14 @@ export default function ParagrapheInput({
rows={6}
style={{ resize: 'none' }}
id={id}
placeholder={missing && value?.replace('\\n', '\n')}
onChange={({ target }) => {
debouncedOnChange(`'${target.value.replace(/\n/g, '\\n')}'`)
}}
defaultValue={!missing && value?.replace('\\n', '\n')}
{...{
[missing ? 'placeholder' : 'defaultValue']: (
(value as string) || ''
).replace('\\n', '\n')
}}
autoComplete="off"
/>
</div>

View File

@ -13,6 +13,7 @@ import {
ParsedRules,
reduceAST,
} from 'publicodes'
import { Evaluation } from 'publicodes/dist/types/AST/types'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { DottedName } from 'Rules'
@ -29,18 +30,18 @@ export type RuleInputProps<Name extends string = DottedName> = {
isTarget?: boolean
autoFocus?: boolean
id?: string
value: Value
className?: string
onSubmit?: (source: string) => void
}
export type InputCommonProps<Name extends string = string> = Pick<
RuleInputProps<Name>,
'dottedName' | 'value' | 'onChange' | 'autoFocus' | 'className'
'dottedName' | 'onChange' | 'autoFocus' | 'className'
> &
Pick<EvaluatedRule<Name>, 'title' | 'question' | 'suggestions'> & {
key: string
id: string
value: any //TODO EvaluatedRule['nodeValue']
missing: boolean
required: boolean
}
@ -161,13 +162,22 @@ export default function RuleInput<Name extends string = DottedName>({
}
if (rule.type === 'texte') {
return <TextInput {...commonProps} />
return <TextInput {...commonProps} value={value as Evaluation<string>} />
}
if (rule.type === 'paragraphe') {
return <ParagrapheInput {...commonProps} />
return (
<ParagrapheInput {...commonProps} value={value as Evaluation<string>} />
)
}
return <Input {...commonProps} onSubmit={onSubmit} unit={rule.unit} />
return (
<Input
{...commonProps}
onSubmit={onSubmit}
unit={rule.unit}
value={value as Evaluation<number>}
/>
)
}
const getVariant = (node: ASTNode & { nodeKind: 'rule' }) =>

View File

@ -1,3 +1,4 @@
import { Evaluation } from 'publicodes/dist/types/AST/types'
import { useCallback } from 'react'
import { debounce } from '../../utils'
import { InputCommonProps } from './RuleInput'
@ -8,7 +9,7 @@ export default function TextInput({
id,
missing,
autoFocus
}: InputCommonProps) {
}: InputCommonProps & { value: Evaluation<string> }) {
const debouncedOnChange = useCallback(debounce(1000, onChange), [])
return (
@ -18,11 +19,12 @@ export default function TextInput({
className="ui__"
type="text"
id={id}
placeholder={missing && value}
onChange={({ target }) => {
debouncedOnChange(`'${target.value}'`)
}}
defaultValue={!missing && value}
{...{
[missing ? 'placeholder' : 'defaultValue']: (value as string) || ''
}}
autoComplete="off"
/>
</div>

View File

@ -91,7 +91,7 @@ export default function Select({ onChange, value, id }: InputCommonProps) {
try {
taux = await tauxVersementTransport(commune.code)
} catch (error) {
console.log(
console.warn(
'Erreur dans la récupération du taux de versement transport à partir du code commune',
error
)

View File

@ -133,7 +133,7 @@ export default function Select(props) {
.then(json => setOptions(json))
.catch(
error =>
console.log('Erreur dans la récupération des codes risques', error) // eslint-disable-line no-console
console.warn('Erreur dans la récupération des codes risques', error) // eslint-disable-line no-console
)
}, [])

View File

@ -1,2 +1,2 @@
import { createContext } from 'react'
export const IsEmbeddedContext = createContext(false)
import { createContext } from 'react';
export const IsEmbeddedContext = createContext(false);

View File

@ -91,6 +91,7 @@ export function getNextQuestions(
liste: whitelist = [],
'liste noire': blacklist = []
} = questionConfig
let nextSteps = difference(getNextSteps(missingVariables), answeredQuestions)
nextSteps = nextSteps.filter(
step =>

View File

@ -883,30 +883,12 @@ contrat salarié . CSG et CRDS . revenus de remplacement:
contrat salarié . CSG et CRDS . revenus de remplacement . CRDS:
titre.en: '[automatic] CRDS replacement income'
titre.fr: CRDS revenus de remplacement
contrat salarié . CSG et CRDS . revenus de remplacement . CRDS . montant:
titre.en: '[automatic] DRES'
titre.fr: CRDS
? contrat salarié . CSG et CRDS . revenus de remplacement . CRDS . rémunération nette
: titre.en: '[automatic] take-home pay'
titre.fr: rémunération nette
contrat salarié . CSG et CRDS . revenus de remplacement . CSG déductible:
titre.en: '[automatic] Deductible MSA replacement income'
titre.fr: CSG déductible revenus de remplacement
? contrat salarié . CSG et CRDS . revenus de remplacement . CSG déductible . montant
: titre.en: '[automatic] deductible MSA'
titre.fr: CSG déductible
? contrat salarié . CSG et CRDS . revenus de remplacement . CSG déductible . rémunération nette
: titre.en: '[automatic] take-home pay'
titre.fr: rémunération nette
contrat salarié . CSG et CRDS . revenus de remplacement . CSG non déductible:
titre.en: '[automatic] Non-deductible CSG replacement income'
titre.fr: CSG non déductible revenus de remplacement
? contrat salarié . CSG et CRDS . revenus de remplacement . CSG non déductible . montant
: titre.en: '[automatic] non-deductible MSA'
titre.fr: CSG non déductible
? contrat salarié . CSG et CRDS . revenus de remplacement . CSG non déductible . rémunération nette
: titre.en: '[automatic] take-home pay'
titre.fr: rémunération nette
contrat salarié . FNAL:
description.en:
The National Housing Fund (Fnal) is a contribution to ensure the
@ -1294,16 +1276,13 @@ contrat salarié . apprentissage . ancienneté . moins de deux ans:
titre.en: less than two years
titre.fr: moins de deux ans
contrat salarié . apprentissage . ancienneté . moins de quatre ans:
titre.en: less than four years
titre.fr: moins de quatre ans
? contrat salarié . apprentissage . ancienneté . moins de quatre ans . information
: description.en: '[automatic] The maximum duration of the contract may be
description.en: '[automatic] The maximum duration of the contract may be
extended to 4 years when the apprentice is recognised as a disabled worker.'
description.fr:
La durée maximale du contrat peut être portée à 4 ans lorsque la
qualité de travailleur handicapé est reconnue à l'apprenti.
titre.en: '[automatic] information'
titre.fr: information
titre.en: less than four years
titre.fr: moins de quatre ans
contrat salarié . apprentissage . ancienneté . moins de trois ans:
titre.en: less than three years
titre.fr: moins de trois ans
@ -1471,8 +1450,8 @@ contrat salarié . contribution d'équilibre technique:
titre.en: technical equilibrium contribution
titre.fr: contribution d'équilibre technique
contrat salarié . convention collective:
question.en: 'Which "convention collective" is applicable to the company ? [beta] '
question.fr: "Quelle convention collective est applicable à l'entreprise ? [beta] "
question.en: '[automatic] Which collective agreement is applicable to the company?'
question.fr: Quelle convention collective est applicable à l'entreprise ?
titre.en: convention collective
titre.fr: convention collective
contrat salarié . convention collective . BTP:
@ -1489,22 +1468,36 @@ contrat salarié . convention collective . BTP:
contrat salarié . convention collective . BTP . OPPBTP:
titre.en: '[automatic] OPPBTP'
titre.fr: OPPBTP
contrat salarié . convention collective . BTP . catégorie du salarié:
contrat salarié . convention collective . BTP . catégorie:
question.en: '[automatic] To which category does the employee belong?'
question.fr: À quelle catégorie la salarié appartient-t'il ?
titre.en: '[automatic] employee category'
titre.fr: catégorie du salarié
contrat salarié . convention collective . BTP . catégorie du salarié . cadre:
titre.en: '[automatic] category'
titre.fr: catégorie
contrat salarié . convention collective . BTP . catégorie . cadre:
titre.en: '[automatic] Framework'
titre.fr: Cadre
contrat salarié . convention collective . BTP . catégorie du salarié . etam:
description.en: '[automatic] Employee, technician, master angent'
? contrat salarié . convention collective . BTP . catégorie . cadre . prévoyance complémentaire
: titre.en: '[automatic] supplementary pension'
titre.fr: prévoyance complémentaire
contrat salarié . convention collective . BTP . catégorie . etam:
description.en: '[automatic] Employee, technician, master angel'
description.fr: Employé, technicien, angent de maîtrise
note.en: '[automatic] Conventional distribution fixed by Article 5 of the
Building and Public Works Agreement of 13 December 1990.'
note.fr:
Répartition conventionnelle fixée par larticle 5 de lAccord du BTP du
13 décembre 1990.
titre.en: '[automatic] ETAM'
titre.fr: ETAM
contrat salarié . convention collective . BTP . catégorie du salarié . ouvrier:
? contrat salarié . convention collective . BTP . catégorie . etam . prévoyance complémentaire
: titre.en: '[automatic] supplementary pension'
titre.fr: prévoyance complémentaire
contrat salarié . convention collective . BTP . catégorie . ouvrier:
titre.en: '[automatic] Worker'
titre.fr: Ouvrier
? contrat salarié . convention collective . BTP . catégorie . ouvrier . prévoyance complémentaire
: titre.en: '[automatic] supplementary pension'
titre.fr: prévoyance complémentaire
contrat salarié . convention collective . BTP . congés intempéries:
titre.en: '[automatic] bad weather'
titre.fr: congés intempéries
@ -1546,30 +1539,6 @@ contrat salarié . convention collective . BTP . congés intempéries:
contrat salarié . convention collective . BTP . cotisations conventionnelles:
titre.en: '[automatic] conventional contributions'
titre.fr: cotisations conventionnelles
contrat salarié . convention collective . BTP . prévoyance complémentaire:
titre.en: '[automatic] complementary providence'
titre.fr: prévoyance complémentaire
? contrat salarié . convention collective . BTP . prévoyance complémentaire . cadre
: titre.en: '[automatic] framework'
titre.fr: cadre
? contrat salarié . convention collective . BTP . prévoyance complémentaire . etam
: titre.en: '[automatic] etam'
titre.fr: etam
? contrat salarié . convention collective . BTP . prévoyance complémentaire . ouvrier
: titre.en: '[automatic] worker'
titre.fr: ouvrier
contrat salarié . convention collective . BTP . retraite complémentaire:
titre.en: '[automatic] retirement supplement'
titre.fr: retraite complémentaire
contrat salarié . convention collective . BTP . retraite complémentaire . etam:
description.en:
'[automatic] Conventional distribution fixed by Article 5 of the
Building and Public Works Agreement of 13 December 1990.'
description.fr:
Répartition conventionnelle fixée par larticle 5 de lAccord du
BTP du 13 décembre 1990.
titre.en: '[automatic] etam'
titre.fr: etam
contrat salarié . convention collective . HCR:
description.en: The company is a hotel, café, restaurant or similar.
description.fr: L'entreprise est un hôtel, café, restaurant ou assimilé.
@ -2183,6 +2152,11 @@ contrat salarié . frais professionnels . titres-restaurant . montant unitaire:
suggestions.moyenne.fr: moyenne
titre.en: '[automatic] unitary amount'
titre.fr: montant unitaire
contrat salarié . frais professionnels . titres-restaurant . nombre:
question.en: '[automatic] How many meal vouchers are distributed to the employee?'
question.fr: Combien de titres-restaurant sont distribués au salarié ?
titre.en: '[automatic] number'
titre.fr: nombre
contrat salarié . frais professionnels . titres-restaurant . part déductible:
titre.en: '[automatic] Restaurant vouchers (deductible)'
titre.fr: Titres-restaurant (déductible)
@ -2201,11 +2175,6 @@ contrat salarié . frais professionnels . titres-restaurant . part déductible:
suggestions.60%.fr: 60%
titre.en: '[automatic] employer contribution rate'
titre.fr: taux participation employeur
? contrat salarié . frais professionnels . titres-restaurant . titres-restaurant par mois
: question.en: '[automatic] How many meal vouchers are distributed to the employee?'
question.fr: Combien de titres-restaurant sont distribués au salarié ?
titre.en: '[automatic] meal vouchers per month'
titre.fr: titres-restaurant par mois
contrat salarié . intermittents du spectacle:
question.en: To which "intermittent" status is the employee attached?
question.fr: A quel statut d'intermittent est rattaché l'employé ?
@ -2668,9 +2637,6 @@ contrat salarié . professionnalisation:
contrat salarié . prévoyance:
titre.en: '[automatic] foresight'
titre.fr: prévoyance
contrat salarié . prévoyance . employeur:
titre.en: '[automatic] employer'
titre.fr: employeur
contrat salarié . prévoyance . exonération fiscale:
titre.en: '[automatic] tax-exempt pension'
titre.fr: prévoyance exonérée d'impôt
@ -2680,9 +2646,6 @@ contrat salarié . prévoyance . part déductible:
contrat salarié . prévoyance . plafond exonération sociale employeur:
titre.en: "[automatic] ceiling employer's social security exemption"
titre.fr: plafond exonération sociale employeur
contrat salarié . prévoyance . salarié:
titre.en: '[automatic] employee'
titre.fr: salarié
contrat salarié . prévoyance obligatoire cadre:
titre.en: mandatory life insurance for "cadres"
titre.fr: Prévoyance obligatoire pour les cadres
@ -2697,9 +2660,6 @@ contrat salarié . retraite complémentaire:
contrat salarié . retraite supplémentaire:
titre.en: '[automatic] additional pension'
titre.fr: retraite supplémentaire
contrat salarié . retraite supplémentaire . employeur:
titre.en: '[automatic] Employer Supplementary Retirement'
titre.fr: Retraite supplémentaire employeur
contrat salarié . retraite supplémentaire . exonération fiscale:
titre.en: '[automatic] tax-exempt supplementary pension'
titre.fr: retraite supplémentaire exonérée d'impôt
@ -2709,9 +2669,6 @@ contrat salarié . retraite supplémentaire . part déductible:
? contrat salarié . retraite supplémentaire . plafond d'exonération sociale employeur
: titre.en: "[automatic] employer's social security ceiling"
titre.fr: plafond d'exonération sociale employeur
contrat salarié . retraite supplémentaire . salarié:
titre.en: '[automatic] employee'
titre.fr: salarié
contrat salarié . réduction générale:
description.en: >
[automatic] Within the framework of the responsibility and solidarity pact,
@ -3157,9 +3114,6 @@ contrat salarié . rémunération . net imposable . base:
? contrat salarié . rémunération . net imposable . heures supplémentaires et complémentaires défiscalisées
: titre.en: tax-free overtime hours
titre.fr: heures supplémentaires et complémentaires défiscalisées
? contrat salarié . rémunération . net imposable . heures supplémentaires et complémentaires défiscalisées . plafond brut
: titre.en: gross cap
titre.fr: plafond brut
contrat salarié . rémunération . primes:
description.en: |
contrat de travail, de la convention collective, d'un usage d'entreprise, ou
@ -5191,6 +5145,9 @@ dirigeant . indépendant . PL . retraite CNAVPL:
existantes (CIPAV, CARPIMKO, CARCDSF, CAVEC etc..)
titre.en: '[automatic] basic retirement (CNAVPL)'
titre.fr: retraite de base (CNAVPL)
dirigeant . indépendant . PL . retraite CNAVPL . remplace:
titre.en: '[automatic] basic retirement (CNAVPL)'
titre.fr: retraite de base (CNAVPL)
dirigeant . indépendant . PL . régime général:
description.en: |
[automatic] Unregulated liberal professions affiliated to the general scheme
@ -5403,6 +5360,11 @@ dirigeant . indépendant . conjoint collaborateur . cotisations . assiette:
: titre.en: basic retirement
titre.fr: retraite de base
dirigeant . indépendant . contrats madelin:
question.en:
'[automatic] Have you subscribed to private complementary contracts
("Madelins contracts")'
question.fr: Avez-vous souscrit à des contrats de complémentaire privée dits
("contrats Madelins")
titre.en: Madelin Contracts
titre.fr: Contrats Madelin
dirigeant . indépendant . contrats madelin . contrôle montant charges:
@ -5420,20 +5382,20 @@ dirigeant . indépendant . contrats madelin . montant:
titre.en: Sum of subscriptions to Madelin contracts
titre.fr: Somme des cotisations à contrats Madelin
dirigeant . indépendant . contrats madelin . mutuelle:
titre.en: Madelin healthcare contract
titre.fr: Contrat Madelin mutuelle
dirigeant . indépendant . contrats madelin . mutuelle . montant:
description.en: |
If you subscribe to a Madelin-law healthcare contract, you can deduct
part of the subscriptions from taxable earnings that you declare for a
self-employed occupation.
description.en: >
[automatic] If you contribute under a mutual insurance contract such as the
Madelin law,
you can deduct part of these contributions from your profits; and
taxable persons that you declare for your self-employed activity.
description.fr: |
Si vous cotisez au titre d'un contrat de mutuelle de type loi Madelin,
vous pouvez déduire une partie de ces cotisations des bénéfices
imposables que vous déclarez pour votre activité non salariée.
question.en: How much do you subscribe to a Madelin healthcare contract?
question.en: '[automatic] How much do you pay to a Madelin Mutual contract?'
question.fr: Quel est le montant que vous versez à un contrat de mutuelle Madelin ?
titre.en: Subscription to a Madelin healthcare contract
titre.en: '[automatic] Subscription to a Madelin mutual insurance contract'
titre.fr: Souscription à un contrat de mutuelle Madelin
dirigeant . indépendant . contrats madelin . mutuelle . plafond:
titre.en: ceiling
@ -5445,20 +5407,20 @@ dirigeant . indépendant . contrats madelin . part non-déductible fiscalement:
titre.en: Part of the Madelin contract subscription which is not fiscally deductible
titre.fr: Part de la cotisation à contrat Madelin qui n'est pas déductible fiscalement
dirigeant . indépendant . contrats madelin . retraite:
titre.en: Madelin pension contract
titre.fr: Contrat Madelin retraite
dirigeant . indépendant . contrats madelin . retraite . montant:
description.en: |
If you subscribe to a Madelin-law pension contract, you can deduct
part of the subscriptions to taxable earnings that you declare for your
self-employed activity.
description.en: >
[automatic] If you are contributing under a pension contract such as the
Madelin law,
you can deduct part of these contributions from your profits; and
taxable persons that you declare for your self-employed activity.
description.fr: |
Si vous cotisez au titre d'un contrat retraite de type loi Madelin,
vous pouvez déduire une partie de ces cotisations des bénéfices
imposables que vous déclarez pour votre activité non salariée.
question.en: How much do you subscribe to a Madelin pension contract?
question.en: '[automatic] How much do you pay into your Madelin retraite contract?'
question.fr: Quel est le montant que vous versez à votre contrat Madelin retraite ?
titre.en: Subscription to a Madelin pension contract
titre.en: '[automatic] Madelin Retirement Subscription'
titre.fr: Souscription à une retraite Madelin
dirigeant . indépendant . contrats madelin . retraite . plafond:
titre.en: ceiling
@ -6077,9 +6039,6 @@ entreprise . catégorie d'activité . libérale règlementée:
question.fr: Est-ce une activité libérale règlementée ?
titre.en: regulated liberal
titre.fr: libérale règlementée
? entreprise . catégorie d'activité . libérale règlementée . type d'activité libérale règlementée
: titre.en: type of regulated liberal activity
titre.fr: type d'activité libérale règlementée
entreprise . catégorie d'activité . restauration ou hébergement:
description.en: Your profits are classified in BIC - provision of housing or
accommodation food.
@ -6270,9 +6229,6 @@ entreprise . effectif . seuil:
question.fr: Quel est l'effectif de l'entreprise ?
titre.en: range
titre.fr: seuil d'effectif
entreprise . effectif . seuil . 251 et plus:
titre.en: 251 or more
titre.fr: 251 et plus
entreprise . effectif . seuil . moins de 11:
titre.en: less than 11
titre.fr: moins de 11
@ -6288,6 +6244,9 @@ entreprise . effectif . seuil . moins de 5:
entreprise . effectif . seuil . moins de 50:
titre.en: less than 50
titre.fr: moins de 50
entreprise . effectif . seuil . plus de 250:
titre.en: '[automatic] 251 and more'
titre.fr: 251 et plus
entreprise . franchise de TVA:
description.en: |
[automatic] The VAT exemption is a device that exempts businesses from the

View File

@ -8,8 +8,9 @@ contrat salarié . convention collective . BTP:
(employés, techniciens et agents de maîtrise) et les cadres.
rend non applicable: CDD . compensation pour congés non pris
contrat salarié . convention collective . BTP . catégorie du salarié:
contrat salarié . convention collective . BTP . catégorie:
question: À quelle catégorie la salarié appartient-t'il ?
par défaut: "'ouvrier'"
formule:
une possibilité:
choix obligatoire: oui
@ -18,42 +19,30 @@ contrat salarié . convention collective . BTP . catégorie du salarié:
- etam
- cadre
contrat salarié . convention collective . BTP . catégorie du salarié . ouvrier:
contrat salarié . convention collective . BTP . catégorie . ouvrier:
titre: Ouvrier
icônes: 👨‍🔧
formule: catégorie = 'ouvrier'
contrat salarié . convention collective . BTP . catégorie du salarié . etam:
contrat salarié . convention collective . BTP . catégorie . ouvrier . prévoyance complémentaire:
produit:
assiette: rémunération . brut de base
plafond: 3 * plafond sécurité sociale
composantes:
- attributs:
nom: employeur
remplace: prévoyance . employeur
taux: 1.72%
- attributs:
nom: salarié
remplace: prévoyance . salarié
taux: 0.87%
contrat salarié . convention collective . BTP . catégorie . etam:
titre: ETAM
description: Employé, technicien, angent de maîtrise
icônes: 👷‍♂️
contrat salarié . convention collective . BTP . catégorie du salarié . cadre:
formule: catégorie du salarié = 'cadre'
titre: Cadre
icônes: 👩‍💼
remplace:
- règle: statut cadre
par: oui
contrat salarié . convention collective . BTP . retraite complémentaire:
valeur: oui
non applicable si: catégorie du salarié = 'etam'
remplace:
- règle: retraite complémentaire . employeur . taux tranche 1
par: 4.72%
- règle: retraite complémentaire . employeur . taux tranche 2
par: 12.95%
- règle: retraite complémentaire . salarié . taux tranche 1
par: 3.15%
- règle: retraite complémentaire . salarié . taux tranche 2
par: 8.64%
contrat salarié . convention collective . BTP . retraite complémentaire . etam:
valeur: oui
applicable si: catégorie du salarié = 'etam'
description: >-
Répartition conventionnelle fixée par larticle 5 de lAccord du BTP du 13 décembre 1990.
formule: catégorie = 'etam'
remplace:
- règle: retraite complémentaire . employeur . taux tranche 1
par: 4.47%
@ -63,73 +52,58 @@ contrat salarié . convention collective . BTP . retraite complémentaire . etam
par: 3.40%
- règle: retraite complémentaire . salarié . taux tranche 2
par: 8.89%
note: >-
Répartition conventionnelle fixée par larticle 5 de lAccord du BTP du 13 décembre 1990.
contrat salarié . convention collective . BTP . prévoyance complémentaire: oui
contrat salarié . convention collective . BTP . catégorie . etam . prévoyance complémentaire:
produit:
assiette: rémunération . brut de base
plafond: 3 * plafond sécurité sociale
composantes:
- attributs:
nom: employeur
remplace: prévoyance . employeur
taux: 1.25%
- attributs:
nom: salarié
remplace: prévoyance . salarié
taux: 0.60%
contrat salarié . convention collective . BTP . prévoyance complémentaire . ouvrier:
valeur: oui
applicable si: catégorie du salarié = 'ouvrier'
contrat salarié . convention collective . BTP . catégorie . cadre:
formule: catégorie = 'cadre'
titre: Cadre
icônes: 👩‍💼
remplace:
- règle: prévoyance . employeur
par:
barème:
assiette: rémunération . brut de base
tranches:
- taux: 1.72%
plafond: 3 * plafond sécurité sociale
- règle: prévoyance . salarié
par:
barème:
assiette: rémunération . brut de base
tranches:
- taux: 0.87%
plafond: 3 * plafond sécurité sociale
- règle: statut cadre
par: oui
contrat salarié . convention collective . BTP . prévoyance complémentaire . etam:
valeur: oui
applicable si: catégorie du salarié = 'etam'
remplace:
- règle: prévoyance . employeur
par:
barème:
assiette: rémunération . brut de base
tranches:
- taux: 1.25%
plafond: 3 * plafond sécurité sociale
- règle: prévoyance . salarié
par:
barème:
assiette: rémunération . brut de base
tranches:
- taux: 0.60%
plafond: 3 * plafond sécurité sociale
contrat salarié . convention collective . BTP . prévoyance complémentaire . cadre:
valeur: oui
applicable si: catégorie du salarié = 'cadre'
remplace:
- règle: prévoyance . employeur
par:
barème:
assiette: rémunération . brut de base
tranches:
- taux: 1.50%
plafond: plafond sécurité sociale
- taux: 50% * 2.40%
plafond: 4 * plafond sécurité sociale
- taux: 50% * 3.60%
plafond: 8 * plafond sécurité sociale
- règle: prévoyance . salarié
par:
barème:
assiette: rémunération . brut de base
tranches:
- taux: 0%
plafond: plafond sécurité sociale
- taux: 50% * 2.40%
plafond: 4 * plafond sécurité sociale
- taux: 50% * 3.60%
plafond: 8 * plafond sécurité sociale
contrat salarié . convention collective . BTP . catégorie . cadre . prévoyance complémentaire:
barème:
assiette: rémunération . brut de base
multiplicateur: plafond sécurité sociale
composantes:
- attributs:
nom: employeur
remplace: prévoyance . employeur
tranches:
- taux: 1.50%
plafond: 1
- taux: 50% * 2.40%
plafond: 4
- taux: 50% * 3.60%
plafond: 8
- attributs:
nom: salarié
remplace: prévoyance . salarié
tranches:
- taux: 0%
plafond: 1
- taux: 50% * 2.40%
plafond: 4
- taux: 50% * 3.60%
plafond: 8
contrat salarié . convention collective . BTP . cotisations conventionnelles:
remplace: cotisations . patronales . conventionnelles

View File

@ -689,36 +689,34 @@ dirigeant . indépendant . conjoint collaborateur . cotisations:
- indemnités journalières maladie
dirigeant . indépendant . conjoint collaborateur . cotisations . assiette retraite:
formule:
le maximum de:
- cotisations . assiette
- 5.25% * plafond sécurité sociale temps plein
- 200 heures/an * SMIC horaire
arrondi: oui
le maximum de:
- cotisations . assiette
- 5.25% * plafond sécurité sociale temps plein
- 200 heures/an * SMIC horaire
unité: €/an
arrondi: oui
dirigeant . indépendant . conjoint collaborateur . cotisations . retraite de base:
unité: €/an
formule:
barème:
assiette: assiette retraite
multiplicateur: plafond sécurité sociale temps plein
tranches:
- taux: 17.75%
plafond: 1
- taux: 0.6%
arrondi: oui
barème:
assiette: assiette retraite
multiplicateur: plafond sécurité sociale temps plein
tranches:
- taux: 17.75%
plafond: 1
- taux: 0.6%
arrondi: oui
dirigeant . indépendant . conjoint collaborateur . cotisations . retraite complémentaire:
unité: €/an
formule:
barème:
assiette: retraite complémentaire . assiette
tranches:
- taux: 7%
plafond: cotisations et contributions . retraite complémentaire . plafond
- taux: 8%
plafond: 4 * plafond sécurité sociale temps plein
arrondi: oui
barème:
assiette: retraite complémentaire . assiette
tranches:
- taux: 7%
plafond: cotisations et contributions . retraite complémentaire . plafond
- taux: 8%
plafond: 4 * plafond sécurité sociale temps plein
arrondi: oui
dirigeant . indépendant . conjoint collaborateur . cotisations . retraite complémentaire . assiette:
titre: assiette retraite complémentaire
@ -738,20 +736,18 @@ dirigeant . indépendant . conjoint collaborateur . cotisations . invalidité et
- 20% * plafond sécurité sociale temps plein
dirigeant . indépendant . conjoint collaborateur . cotisations . invalidité et décès:
unité: €/an
formule:
produit:
assiette: assiette
taux: 1.3%
plafond: plafond sécurité sociale temps plein
arrondi: oui
produit:
assiette: assiette
taux: 1.3%
plafond: plafond sécurité sociale temps plein
arrondi: oui
dirigeant . indépendant . conjoint collaborateur . cotisations . indemnités journalières maladie:
unité: €/an
formule:
produit:
assiette: 40% * plafond sécurité sociale temps plein
taux: cotisations et contributions . indemnités journalières maladie . taux
arrondi: oui
produit:
assiette: 40% * plafond sécurité sociale temps plein
taux: cotisations et contributions . indemnités journalières maladie . taux
arrondi: oui
dirigeant . indépendant . cotisations et contributions . cotisations:
références:
@ -1043,12 +1039,11 @@ dirigeant . indépendant . cotisations et contributions . invalidité et décès
Cotisation minimale: https://www.secu-independants.fr/cotisations/calcul-des-cotisations/cotisations-minimales/
dirigeant . indépendant . cotisations et contributions . exonération de cotisations minimales:
applicable si: situation personnelle . RSA
remplace:
formule: situation personnelle . RSA
rend non applicable:
- invalidité et décès . assiette . plancher
- maladie . assiette . plancher
- retraite de base . assiette . plancher
formule: non
références:
secu-independants.fr: https://www.secu-independants.fr/cotisations/calcul-des-cotisations/cotisations-minimales/
@ -1059,7 +1054,7 @@ dirigeant . indépendant . cotisations et contributions . CSG et CRDS:
composantes:
- attributs:
nom: non déductible
arrondi: oui
arrondi: oui
composantes:
- taux: 2.9%
- attributs:
@ -1068,7 +1063,7 @@ dirigeant . indépendant . cotisations et contributions . CSG et CRDS:
taux: 2.9%
- attributs:
nom: déductible
arrondi: oui
arrondi: oui
composantes:
- taux: 6.8%
- attributs:
@ -1112,17 +1107,17 @@ dirigeant . indépendant . cotisations et contributions . CSG et CRDS . assiette
- dirigeant . indépendant . IJSS . imposable
dirigeant . indépendant . cotisations et contributions . formation professionnelle:
formule:
produit:
assiette: plafond sécurité sociale temps plein
taux:
variations:
- si: entreprise . catégorie d'activité = 'artisanale'
alors: 0.29%
- si: conjoint collaborateur
alors: 0.34%
- sinon: 0.25%
arrondi: oui
produit:
assiette: plafond sécurité sociale temps plein
taux:
variations:
- si: entreprise . catégorie d'activité = 'artisanale'
alors: 0.29%
- si: conjoint collaborateur
alors: 0.34%
- sinon: 0.25%
unité: €/an
arrondi: oui
note: Le taux n'est pas majoré pour les artisans avec conjoint collaborateur
références:
@ -1153,16 +1148,16 @@ dirigeant . indépendant . cotisations et contributions . exonérations:
dirigeant . indépendant . cotisations et contributions . exonérations . ZFU:
applicable si: établissement . ZFU
formule:
produit:
assiette: maladie
taux: taux
# TODO : Le plafond est proratisé en début / fin d'exonération
plafond:
recalcul:
avec:
dirigeant . indépendant . revenu professionnel: 3042 heures/an * SMIC horaire
arrondi: oui
produit:
assiette: maladie
taux: taux
# TODO : Le plafond est proratisé en début / fin d'exonération
plafond:
recalcul:
avec:
dirigeant . indépendant . revenu professionnel: 3042 heures/an * SMIC horaire
arrondi: oui
unité: €/an
dirigeant . indépendant . cotisations et contributions . exonérations . âge:
question: Bénéficiez-vous du dispositif d'exonération "âge"

View File

@ -59,10 +59,9 @@ aide déclaration revenu indépendant 2019 . ACRE:
par défaut: non
aide déclaration revenu indépendant 2019 . nature de l'activité . libérale:
remplace:
- règle: dirigeant . indépendant . PL . CIPAV
par: non
- entreprise . catégorie d'activité . libérale
rend non applicable: dirigeant . indépendant . PL . CIPAV
remplace: entreprise . catégorie d'activité . libérale
formule: nature de l'activité = 'libérale'
titre: Libérale rattachée au régime général
description: |
Ce sont les professions "intellectuelles", qui ne sont rattachée à aucune
@ -76,6 +75,7 @@ aide déclaration revenu indépendant 2019 . nature de l'activité . libérale:
aide déclaration revenu indépendant 2019 . nature de l'activité . commerciale ou industrielle:
remplace: entreprise . catégorie d'activité . commerciale ou industrielle
formule: nature de l'activité = 'commerciale ou industrielle'
description: |
### Activité commerciale
- Achats de biens pour leur revente en l'état (commerce en gros ou de détail)
@ -86,6 +86,7 @@ aide déclaration revenu indépendant 2019 . nature de l'activité . commerciale
Activité de production ou de transformation grâce à l'utilisation d'outils industriels, extraction, industries minières, manutention, magasinage et stockage
aide déclaration revenu indépendant 2019 . nature de l'activité . artisanale:
formule: nature de l'activité = 'artisanale'
remplace: entreprise . catégorie d'activité . artisanale
description: |
C'est une activité de service, de production, de transformation, ou de réparation exercée par un professionnel qualifié, et qui nécessite des compétences et un savoir-faire spécifiques.
@ -106,6 +107,7 @@ aide déclaration revenu indépendant 2019 . SMIC 2019:
formule: 10.03 €/heure
aide déclaration revenu indépendant 2019 . période:
formule: oui
remplace:
- règle: période . début d'année
par: 01/01/2019

View File

@ -1,15 +1,14 @@
impôt:
icônes: 🏛️
unité: €/an
description: Cet ensemble de formules est un modèle simplifié de l'impôt sur le revenu.
titre: impôts sur le revenu
formule:
somme:
- produit:
assiette: revenu imposable
taux: taux d'imposition
- dirigeant . auto-entrepreneur . impôt . versement libératoire . montant
arrondi: oui
somme:
- produit:
assiette: revenu imposable
taux: taux d'imposition
- dirigeant . auto-entrepreneur . impôt . versement libératoire . montant
arrondi: oui
unité: €/an
impôt . taux d'imposition:
formule:

View File

@ -145,6 +145,7 @@ dirigeant . indépendant . PL . régime général:
dirigeant . indépendant . PL . régime général . taux spécifique retraite complémentaire:
titre: taux spécifique profession libérale non reglementée
question: Avez-vous opté pour des taux spécifiques de cotisation retraite complémentaire ?
par défaut: non
description: |
Les professions libérales non règlementées qui ont débuté leur activité à
compter du 1er janvier 2019 ou ceux qui ont débuté leur activité avant la
@ -320,7 +321,7 @@ dirigeant . indépendant . PL . retraite CNAVPL:
une de ces conditions:
- régime général
- PL . CNBF
titre: retraite de base (CNAVPL)
description: |
Toutes les professions libérale (à l'exception des avocats)
@ -330,7 +331,6 @@ dirigeant . indépendant . PL . retraite CNAVPL:
libérales est l'organisme qui fédère les différentes caisses
existantes (CIPAV, CARPIMKO, CARCDSF, CAVEC etc..)
remplace: cotisations et contributions . retraite de base
formule:
somme:
- produit:
@ -348,6 +348,13 @@ dirigeant . indépendant . PL . retraite CNAVPL:
liste des caisses: https://www.cnavpl.fr/regimes-complementaires-et-prevoyance/
Guide CNAVPL (PDF): https://www.cnavpl.fr/statuts-et-documents-de-reference/?wpdmdl=56215
#TODO: On ajoute une exception car la transitivité du remplacement ne fonctionne pas encore
dirigeant . indépendant . PL . retraite CNAVPL . remplace:
titre: retraite de base (CNAVPL)
non applicable si: CARMF . retraite CNAVPL
remplace: cotisations et contributions . retraite de base
formule: retraite CNAVPL
dirigeant . indépendant . PL . PAMC:
applicable si:
une de ces conditions:
@ -715,7 +722,7 @@ dirigeant . indépendant . PL . CARMF . retraite CNAVPL:
applicable si: métier . secteur médecin = 'S1'
description: |
Pour compenser la hausse de la CSG, les médecins de secteur 1 bénéficient d'une participation de l'assurance maladie (avenant n°5 de la convention médicale) au financement de leurs cotisations du régime de base.
remplace: PL . retraite CNAVPL
remplace: cotisations et contributions . retraite de base
formule:
allègement:
assiette: PL . retraite CNAVPL

View File

@ -206,7 +206,6 @@ contrat salarié . activité partielle:
La déclaration d'activité partielle est simplifiée et l'effet est
rétroactif.
par défaut: non
rend non applicable:
- temps de travail . heures supplémentaires
@ -1036,8 +1035,7 @@ contrat salarié . professionnalisation:
devant durer entre 6 et 12 mois. Dans certains cas cette période peut être
prolongée jusqu'à 36 mois.
formule: contrat salarié = 'professionnalisation'
rend non applicable:
- rémunération . contrôle smic
rend non applicable: rémunération . contrôle smic
références:
Contrat de professionnalisation: https://www.service-public.fr/particuliers/vosdroits/F15478
@ -1066,6 +1064,7 @@ contrat salarié . stage:
- contrat salarié . activité partielle
contrat salarié . stage . avertissement:
formule: oui
type: notification
sévérité: avertissement
description: >-
@ -1076,7 +1075,7 @@ contrat salarié . stage . avertissement:
contrat salarié . stage . contrôle gratification minimale:
type: notification
sévérité: avertissement
applicable si: rémunération . brut de base < stage . gratification minimale
formule: rémunération . brut de base < stage . gratification minimale
description: >-
La rémunération du stage est inférieure à la [gratification minimale](https://www.service-public.fr/professionnels-entreprises/vosdroits/F32131).
@ -2837,7 +2836,6 @@ contrat salarié . CSG et CRDS . revenus de remplacement:
Le prélèvement de la CSG et de la CRDS ne peut pas avoir pour effet de
réduire le montant de la rémunération dactivité et des allocations de
chômage à un seuil inférieur au Smic brut.
formule:
somme:
- revenus de remplacement . CSG déductible
@ -2846,57 +2844,32 @@ contrat salarié . CSG et CRDS . revenus de remplacement:
contrat salarié . CSG et CRDS . revenus de remplacement . CSG déductible:
titre: CSG déductible revenus de remplacement
applicable si: rémunération nette >= SMIC temps plein
formule: montant
contrat salarié . CSG et CRDS . revenus de remplacement . CSG déductible . rémunération nette:
formule:
produit:
assiette: CSG et CRDS . assiette revenu remplacements
taux: 3.8%
plafond [ref]:
somme:
- rémunération . net de cotisations
- rémunération . revenus de remplacement
- (- montant)
contrat salarié . CSG et CRDS . revenus de remplacement . CSG déductible . montant:
titre: CSG déductible
formule:
produit:
assiette: CSG et CRDS . assiette revenu remplacements
taux: 3.8%
- (- SMIC temps plein)
plancher: 0€/mois
note:
contrat salarié . CSG et CRDS . revenus de remplacement . CSG non déductible:
titre: CSG non déductible revenus de remplacement
applicable si: rémunération nette >= SMIC temps plein
formule: montant
contrat salarié . CSG et CRDS . revenus de remplacement . CSG non déductible . rémunération nette:
formule:
somme:
- rémunération . net de cotisations
- rémunération . revenus de remplacement
- (- CSG déductible . montant)
- (- montant)
contrat salarié . CSG et CRDS . revenus de remplacement . CSG non déductible . montant:
titre: CSG non déductible
formule:
produit:
assiette: CSG et CRDS . assiette revenu remplacements
taux: CSG . taux non déductible
produit:
assiette: CSG et CRDS . assiette revenu remplacements
taux: CSG . taux non déductible
plafond [ref]:
valeur: CSG déductible . plafond - CSG déductible
contrat salarié . CSG et CRDS . revenus de remplacement . CRDS:
titre: CRDS revenus de remplacement
applicable si: rémunération nette >= SMIC temps plein
formule: montant
contrat salarié . CSG et CRDS . revenus de remplacement . CRDS . rémunération nette:
formule:
somme:
- rémunération . net de cotisations
- rémunération . revenus de remplacement
- (- CSG déductible . montant)
- (- CSG non déductible . montant)
- (- montant)
contrat salarié . CSG et CRDS . revenus de remplacement . CRDS . montant:
titre: CRDS
formule:
produit:
assiette: assiette revenu remplacements
taux: CRDS . taux
produit:
assiette: assiette revenu remplacements
taux: CRDS . taux
plafond:
valeur: CSG non déductible . plafond - CSG non déductible
contrat salarié . FNAL:
titre: Contribution au Fonds National dAide au Logement
@ -3333,6 +3306,7 @@ contrat salarié . régime des impatriés:
contrat salarié . régime des impatriés . information:
type: notification
formule: oui
description: >-
Pour bénéficier de l'exonération de cotisations vieillesse, il faut remplir les conditions suivantes :
- Pouvoir justifier d'une contribution minimale versée ailleurs pour une assurance vieillesse

View File

@ -16,7 +16,10 @@ export const objectifsSelector = createSelector([configSelector], config => {
return objectifs
})
const emptySituation: Partial<Record<DottedName, string | number | Object>> = {}
const emptySituation: Partial<Record<
DottedName,
string | number | Record<string, unknown>
>> = {}
export const situationSelector = (state: RootState) =>
state.simulation?.situation ?? emptySituation

View File

@ -9,4 +9,5 @@ objectifs:
situation:
dirigeant: "'indépendant'"
aide déclaration revenu indépendant 2019: oui
dirigeant . indépendant . PL . CIPAV: non # TODO En attendant la transitivité des remplacements
unité par défaut: '€/an'

View File

@ -1,32 +1,32 @@
import { setSimulationConfig, updateSituation } from 'Actions/actions'
import Aide from 'Components/conversation/Aide'
import { Explicable, ExplicableRule } from 'Components/conversation/Explicable'
import RuleInput from 'Components/conversation/RuleInput'
import { Condition } from 'Components/EngineValue'
import RuleLink from 'Components/RuleLink'
import 'Components/TargetSelection.css'
import Animate from 'Components/ui/animate'
import Warning from 'Components/ui/WarningBlock'
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
import { ScrollToTop } from 'Components/utils/Scroll'
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
import RuleInput from 'Components/conversation/RuleInput'
import { EvaluatedRule, evaluateRule } from 'publicodes'
import { Fragment, useCallback, useEffect, useState, useContext } from 'react'
import { useNextQuestions } from 'Components/utils/useNextQuestion'
import { EvaluatedRule, evaluateRule, formatValue } from 'publicodes'
import { Fragment, useCallback, useContext, useEffect, useState } from 'react'
import emoji from 'react-easy-emoji'
import { Trans } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { RootState } from 'Reducers/rootReducer'
import { DottedName } from 'Rules'
import { situationSelector } from 'Selectors/simulationSelectors'
import styled from 'styled-components'
import Animate from 'Components/ui/animate'
import { CompanySection } from '../Home'
import simulationConfig from './config.yaml'
import { useNextQuestions } from 'Components/utils/useNextQuestion'
import emoji from 'react-easy-emoji'
import RuleLink from 'Components/RuleLink'
import { formatValue } from 'publicodes'
import Skeleton from 'Components/ui/Skeleton'
export default function AideDéclarationIndépendant() {
const dispatch = useDispatch()
const rules = useContext(EngineContext).getParsedRules()
const engine = useEngine()
const company = useSelector(
(state: RootState) => state.inFranceApp.existingCompany
)
@ -38,17 +38,16 @@ export default function AideDéclarationIndépendant() {
threshold: 0.5,
unobserve: false
})
const dottedName = 'dirigeant . rémunération totale'
const value = useSelector(situationSelector)[dottedName]
const [currentIncome, setCurrentIncome] = useState(value)
const displayForm = currentIncome != null
useEffect(() => {
if (resultsInViewPort && displayForm) {
dispatch(updateSituation(dottedName, currentIncome))
} else {
dispatch(updateSituation(dottedName, null))
}
}, [dispatch, resultsInViewPort, displayForm, currentIncome])
const setCurrentIncome = useCallback(
currentIncome => {
dispatch(
updateSituation('dirigeant . rémunération totale', currentIncome)
)
},
[dispatch, updateSituation]
)
const displayForm =
evaluateRule(engine, 'dirigeant . rémunération totale').nodeValue !== null
return (
<div>
@ -113,7 +112,6 @@ export default function AideDéclarationIndépendant() {
<RuleInput
dottedName="dirigeant . rémunération totale"
onChange={setCurrentIncome}
value={currentIncome}
autoFocus
/>
</BigInput>
@ -138,9 +136,11 @@ export default function AideDéclarationIndépendant() {
<SimpleField dottedName="entreprise . date de création" />
<SubSection dottedName="aide déclaration revenu indépendant 2019 . nature de l'activité" />
{/* PLNR */}
<SimpleField dottedName="entreprise . catégorie d'activité . débit de tabac" />
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . déduction tabac" />
<SimpleField dottedName="dirigeant . indépendant . PL . régime général . taux spécifique retraite complémentaire" />
<Condition expression="aide déclaration revenu indépendant 2019 . nature de l'activité">
<SimpleField dottedName="entreprise . catégorie d'activité . débit de tabac" />
<SimpleField dottedName="dirigeant . indépendant . cotisations et contributions . déduction tabac" />
<SimpleField dottedName="dirigeant . indépendant . PL . régime général . taux spécifique retraite complémentaire" />
</Condition>
<h2>
<Trans>Situation personnelle</Trans>
@ -346,7 +346,7 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
const engine = useContext(EngineContext)
const evaluatedRule = evaluateRule(engine, dottedName)
const value = useSelector(situationSelector)[dottedName]
const [currentValue, setCurrentValue] = useState(value)
const situation = useSelector(situationSelector)
const dispatchValue = useCallback(
value => {
@ -359,19 +359,11 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
},
[dispatch, dottedName]
)
const update = (value: unknown) => {
dispatchValue(value)
setCurrentValue(value)
}
useEffect(() => {
setCurrentValue(value)
}, [value])
if (
// TODO
// evaluatedRule.isApplicable === false ||
// evaluatedRule.isApplicable === null
evaluatedRule.nodeValue === false ||
evaluatedRule.nodeValue === null
!(dottedName in situation) &&
evaluatedRule.nodeValue === false &&
!(dottedName in evaluatedRule.missingVariables)
) {
return null
}
@ -395,11 +387,7 @@ function SimpleField({ dottedName, question, summary }: SimpleFieldProps) {
</p>
<p className="ui__ notice">{summary ?? evaluatedRule.résumé}</p>
</div>
<RuleInput
dottedName={dottedName}
onChange={update}
value={currentValue}
/>
<RuleInput dottedName={dottedName} onChange={dispatchValue} />
</Question>
</Animate.fromTop>
</div>
@ -411,8 +399,6 @@ function Results() {
const results = (simulationConfig.objectifs as DottedName[]).map(objectif =>
evaluateRule(engine, objectif, { unité: '€/an' })
)
const onGoingComputation = !results.filter(node => node.nodeValue != null)
.length
return (
<div
className="ui__ card lighter-bg"
@ -424,33 +410,20 @@ function Results() {
</Trans>
{emoji('📄')}
</h1>
{onGoingComputation && (
<h2>
<small>
<Trans i18nKey="aide-déclaration-indépendant.results.ongoing">
Calcul en cours...
</Trans>
</small>
</h2>
)}
<>
<Animate.fromTop>
{results.map(r => (
<Fragment key={r.title}>
<Fragment key={r.dottedName}>
<h4>
{r.title} <small>{r.résumé}</small>
</h4>
{r.description && <p className="ui__ notice">{r.description}</p>}
<p className="ui__ lead" css="margin-bottom: 1rem;">
<RuleLink dottedName={r.dottedName}>
{r.nodeValue != null ? (
formatValue(r, {
displayedUnit: '€',
precision: 0
})
) : (
<Skeleton width={80} />
)}
{formatValue(r, {
displayedUnit: '€',
precision: 0
})}
</RuleLink>
</p>
</Fragment>

View File

@ -171,7 +171,6 @@ function FormulairePublicodes() {
<RuleInput
id={field.dottedName}
dottedName={field.dottedName}
value={situation[field.dottedName]}
onChange={value => onChange(field.dottedName, value)}
/>
</>

View File

@ -6,7 +6,7 @@ import SimulateurWarning from 'Components/SimulateurWarning'
import AidesCovid from 'Components/simulationExplanation/AidesCovid'
import 'Components/TargetSelection.css'
import Animate from 'Components/ui/animate'
import { EngineContext } from 'Components/utils/EngineContext'
import { EngineContext, useEngine } from 'Components/utils/EngineContext'
import { evaluateRule } from 'publicodes'
import { createContext, useContext, useEffect, useState } from 'react'
import { Trans } from 'react-i18next'
@ -60,9 +60,8 @@ type SimpleFieldProps = {
function SimpleField({ dottedName }: SimpleFieldProps) {
const dispatch = useDispatch()
const rule = evaluateRule(useContext(EngineContext), dottedName)
const rule = useEngine().getParsedRules()[dottedName]
const initialRender = useContext(InitialRenderContext)
const value = rule.nodeValue
// TODO
// if (rule.isApplicable === false || rule.isApplicable === null) {
// return null
@ -74,8 +73,10 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
<div className="main">
<div className="header">
<label htmlFor={dottedName}>
<span className="optionTitle">{rule.question || rule.title}</span>
<p className="ui__ notice">{rule.résumé}</p>
<span className="optionTitle">
{rule.rawNode.question || rule.title}
</span>
<p className="ui__ notice">{rule.rawNode.résumé}</p>
</label>
</div>
<div className="targetInputOrValue">
@ -83,7 +84,6 @@ function SimpleField({ dottedName }: SimpleFieldProps) {
className="targetInput"
isTarget
dottedName={dottedName}
value={value}
onChange={(x) => dispatch(updateSituation(dottedName, x))}
useSwitch
/>

View File

@ -35,3 +35,4 @@ questions:
unité par défaut: €/an
situation:
dirigeant: "'assimilé salarié'"
contrat salarié . activité partielle: non #TODO : en attendant que la transitivité du remplacement soit implémentée

View File

@ -8,7 +8,7 @@ describe('DottedNames graph', () => {
expect(
cyclesDependencies,
`\nThe cycles have been found in the rules dependencies graph.\nSee below for a representation of each cycle.\n⬇️ is a node of the cycle.\n↘️ is each of the dependencies of this node.\n\t- ${cyclesDependencies
`\nThe cycles have been found in the rules dependencies graph.\nSee below for a representation of each cycle.\n⬇️ is a node of the cycle.\n\t- ${cyclesDependencies
.map(
(cycleDependencies, idx) =>
'#' +

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`calculate aide-déclaration-indépendant: ACRE 1`] = `"[50000,3177,11384,101,14662,35338]"`;
exports[`calculate aide-déclaration-indépendant: ACRE 1`] = `"[50000,3177,11383,101,14661,35339]"`;
exports[`calculate aide-déclaration-indépendant: ACRE 2`] = `"[15000,949,3261,101,4311,10689]"`;
@ -16,33 +16,33 @@ exports[`calculate aide-déclaration-indépendant: ACRE 5`] = `
Notifications affichées : dirigeant . indépendant . avertissement base forfaitaire"
`;
exports[`calculate aide-déclaration-indépendant: IJSS (indemnité sécurité sociale) 1`] = `"[50000,3024,11424,101,14549,35451]"`;
exports[`calculate aide-déclaration-indépendant: IJSS (indemnité sécurité sociale) 1`] = `"[50000,3024,11425,101,14550,35450]"`;
exports[`calculate aide-déclaration-indépendant: RSA 1`] = `"[500,25,82,101,208,292]"`;
exports[`calculate aide-déclaration-indépendant: RSA 2`] = `"[5000,312,1021,101,1434,3566]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 1`] = `"[50000,3175,17728,138,21041,28959]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 1`] = `"[50000,3175,17724,138,21037,28963]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 2`] = `"[50000,3175,16580,138,19893,30107]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 2`] = `"[50000,3175,16600,138,19913,30087]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 3`] = `"[50000,3175,14664,138,17977,32023]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 3`] = `"[50000,3175,14672,138,17985,32015]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 4`] = `"[50000,3176,17732,118,21026,28974]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 4`] = `"[50000,3176,17729,118,21023,28977]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 5`] = `"[50000,3175,16186,138,19499,30501]"`;
exports[`calculate aide-déclaration-indépendant: conjoint collaborateur 5`] = `"[50000,3175,16199,138,19512,30488]"`;
exports[`calculate aide-déclaration-indépendant: débit de tabac 1`] = `"[50000,3177,5672,101,8950,41050]"`;
exports[`calculate aide-déclaration-indépendant: international 1`] = `"[50000,0,14612,101,14713,35287]"`;
exports[`calculate aide-déclaration-indépendant: international 1`] = `"[50000,0,14610,101,14711,35289]"`;
exports[`calculate aide-déclaration-indépendant: international 2`] = `"[50000,1267,11893,101,13261,36739]"`;
exports[`calculate aide-déclaration-indépendant: nature de l'activité 1`] = `"[50000,3176,11379,118,14673,35327]"`;
exports[`calculate aide-déclaration-indépendant: nature de l'activité 1`] = `"[50000,3176,11380,118,14674,35326]"`;
exports[`calculate aide-déclaration-indépendant: nature de l'activité 2`] = `"[5000,311,1353,118,1782,3218]"`;
exports[`calculate aide-déclaration-indépendant: nature de l'activité 3`] = `"[50000,3177,11384,101,14662,35338]"`;
exports[`calculate aide-déclaration-indépendant: nature de l'activité 3`] = `"[50000,3177,11383,101,14661,35339]"`;
exports[`calculate aide-déclaration-indépendant: nature de l'activité 4`] = `"[5000,312,1354,101,1767,3233]"`;
@ -146,23 +146,23 @@ exports[`calculate simulations-indépendant: impôt sur le revenu 2`] = `"[73025
exports[`calculate simulations-indépendant: impôt sur le revenu 3`] = `"[29085,9085,20000,20787,2079,17921,0,29085]"`;
exports[`calculate simulations-indépendant: inversions 1`] = `"[2000,1384,616,668,0,616,0,2000]"`;
exports[`calculate simulations-indépendant: inversions 1`] = `"[2000,1385,615,667,0,615,0,2000]"`;
exports[`calculate simulations-indépendant: inversions 2`] = `"[50000,16003,33997,35352,3500,30497,0,50000]"`;
exports[`calculate simulations-indépendant: inversions 3`] = `"[14596,4596,10000,10394,0,10000,0,14596]"`;
exports[`calculate simulations-indépendant: inversions 3`] = `"[14597,4597,10000,10394,0,10000,0,14597]"`;
exports[`calculate simulations-indépendant: inversions 4`] = `"[69940,22078,47862,49758,7862,40000,0,69940]"`;
exports[`calculate simulations-indépendant: inversions 5`] = `"[14596,4596,10000,10394,0,10000,1000,15596]"`;
exports[`calculate simulations-indépendant: inversions 5`] = `"[14597,4597,10000,10394,0,10000,1000,15597]"`;
exports[`calculate simulations-indépendant: inversions 6`] = `"[19000,5928,13072,13585,0,13072,1000,20000]"`;
exports[`calculate simulations-indépendant: inversions 7`] = `"[18000,5625,12375,12861,0,12375,2000,20000]"`;
exports[`calculate simulations-indépendant: inversions 7`] = `"[17999,5625,12374,12860,0,12374,2000,20000]"`;
exports[`calculate simulations-indépendant: échelle de revenus 1`] = `"[1859,1359,500,548,0,500,0,1859]"`;
exports[`calculate simulations-indépendant: échelle de revenus 2`] = `"[2467,1467,1000,1064,0,1000,0,2467]"`;
exports[`calculate simulations-indépendant: échelle de revenus 2`] = `"[2466,1466,1000,1064,0,1000,0,2466]"`;
exports[`calculate simulations-indépendant: échelle de revenus 3`] = `"[3075,1575,1500,1581,0,1500,0,3075]"`;
@ -170,9 +170,9 @@ exports[`calculate simulations-indépendant: échelle de revenus 4`] = `"[3682,1
exports[`calculate simulations-indépendant: échelle de revenus 5`] = `"[7428,2428,5000,5199,0,5000,0,7428]"`;
exports[`calculate simulations-indépendant: échelle de revenus 6`] = `"[14596,4596,10000,10394,0,10000,0,14596]"`;
exports[`calculate simulations-indépendant: échelle de revenus 6`] = `"[14597,4597,10000,10394,0,10000,0,14597]"`;
exports[`calculate simulations-indépendant: échelle de revenus 7`] = `"[139595,39595,100000,103788,24909,75091,0,139595]"`;
exports[`calculate simulations-indépendant: échelle de revenus 7`] = `"[139596,39596,100000,103788,24909,75091,0,139596]"`;
exports[`calculate simulations-indépendant: échelle de revenus 8`] = `"[1239955,239955,1000000,1033666,444476,555524,0,1239955]"`;
@ -184,13 +184,13 @@ exports[`calculate simulations-professions-libérales: CIPAV 3`] = `"[3905,0,240
exports[`calculate simulations-professions-libérales: CIPAV 4`] = `"[4473,0,2473,2000,0,2000]"`;
exports[`calculate simulations-professions-libérales: CIPAV 5`] = `"[7934,0,2934,5000,0,5000]"`;
exports[`calculate simulations-professions-libérales: CIPAV 5`] = `"[7935,0,2935,5000,0,5000]"`;
exports[`calculate simulations-professions-libérales: CIPAV 6`] = `"[14188,0,4188,10000,0,10000]"`;
exports[`calculate simulations-professions-libérales: CIPAV 7`] = `"[144691,0,44691,100000,24942,75058]"`;
exports[`calculate simulations-professions-libérales: CIPAV 8`] = `"[1236171,0,236171,1000000,444432,555568]"`;
exports[`calculate simulations-professions-libérales: CIPAV 8`] = `"[1236171,0,236171,1000000,444433,555567]"`;
exports[`calculate simulations-professions-libérales: auxiliaire médical 1`] = `"[30000,0,7751,22249,945,21304]"`;
@ -198,7 +198,7 @@ exports[`calculate simulations-professions-libérales: auxiliaire médical 2`] =
exports[`calculate simulations-professions-libérales: auxiliaire médical 3`] = `"[300000,0,61784,238216,81297,156919]"`;
exports[`calculate simulations-professions-libérales: avocat 1`] = `"[50000,0,11410,38589,4753,33836]"`;
exports[`calculate simulations-professions-libérales: avocat 1`] = `"[50000,0,11410,38590,4753,33837]"`;
exports[`calculate simulations-professions-libérales: avocat 2`] = `"[50000,0,11770,38230,4711,33519]"`;
@ -212,11 +212,11 @@ exports[`calculate simulations-professions-libérales: médecin 2`] = `"[50000,0
exports[`calculate simulations-professions-libérales: médecin 3`] = `"[300000,0,86481,213519,73147,140372]"`;
exports[`calculate simulations-professions-libérales: médecin 4`] = `"[400000,0,106201,293799,115768,178031]"`;
exports[`calculate simulations-professions-libérales: médecin 4`] = `"[400000,0,106201,293800,115768,178032]"`;
exports[`calculate simulations-professions-libérales: médecin 5`] = `"[120000,0,34595,85405,17732,67673]"`;
exports[`calculate simulations-professions-libérales: sage-femme 1`] = `"[50000,0,12383,37617,4638,32979]"`;
exports[`calculate simulations-professions-libérales: sage-femme 1`] = `"[50000,0,12384,37617,4638,32979]"`;
exports[`calculate simulations-professions-libérales: sage-femme 2`] = `
"[20000,0,5148,14852,0,14852]
@ -270,9 +270,9 @@ exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): Co
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): Contrats Madelin 6`] = `"[917,0,0,10757,4,20]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): activités 1`] = `"[917,0,0,0,4,0]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): activités 1`] = `"[917,0,0,0,0,0]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): activités 2`] = `"[917,0,0,0,4,0]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): activités 2`] = `"[917,0,0,0,0,0]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): activités 3`] = `"[917,0,0,10757,4,20]"`;
@ -286,9 +286,9 @@ exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): av
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): échelle de rémunération 1`] = `"[0,0,0,0,0,0]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): échelle de rémunération 2`] = `"[14,0,0,139,0,1]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): échelle de rémunération 2`] = `"[14,0,0,140,0,1]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): échelle de rémunération 3`] = `"[62,0,0,324,0,2]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): échelle de rémunération 3`] = `"[62,0,0,323,0,2]"`;
exports[`calculate simulations-rémunération-dirigeant (assimilé salarié): échelle de rémunération 4`] = `"[204,0,0,2591,2,5]"`;
@ -330,9 +330,9 @@ Notifications affichées : dirigeant . auto-entrepreneur . contrôle seuil de CA
exports[`calculate simulations-rémunération-dirigeant (auto-entrepreneur): Contrats Madelin 6`] = `"[0,0,1446,4195,3,8]"`;
exports[`calculate simulations-rémunération-dirigeant (auto-entrepreneur): activités 1`] = `"[0,0,1298,0,4,0]"`;
exports[`calculate simulations-rémunération-dirigeant (auto-entrepreneur): activités 1`] = `"[0,0,1298,0,0,0]"`;
exports[`calculate simulations-rémunération-dirigeant (auto-entrepreneur): activités 2`] = `"[0,0,1297,0,4,0]"`;
exports[`calculate simulations-rémunération-dirigeant (auto-entrepreneur): activités 2`] = `"[0,0,1297,0,0,0]"`;
exports[`calculate simulations-rémunération-dirigeant (auto-entrepreneur): activités 3`] = `"[0,0,1445,4093,3,8]"`;
@ -378,24 +378,21 @@ exports[`calculate simulations-rémunération-dirigeant (indépendant): ACRE 3`]
Notifications affichées : dirigeant . indépendant . avertissement base forfaitaire"
`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 1`] = `"[0,20620,0,15102,4,29]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 1`] = `"[0,20619,0,15101,4,29]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 2`] = `"[0,20264,0,15647,4,30]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 2`] = `"[0,20619,0,15101,4,29]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 3`] = `"[0,20620,0,15102,4,29]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 3`] = `"[0,20619,0,15101,4,29]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 4`] = `"[0,13769,0,10084,4,21]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 5`] = `"[0,226877,0,57936,4,56]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 6`] = `
"[0,13769,0,10084,4,21]
Notifications affichées : dirigeant . indépendant . contrats madelin . contrôle montant charges"
`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): Contrats Madelin 6`] = `"[0,13769,0,10084,4,21]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): activités 1`] = `"[0,14646,0,0,4,0]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): activités 1`] = `"[0,14646,0,0,0,0]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): activités 2`] = `"[0,14646,0,0,4,0]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): activités 2`] = `"[0,14646,0,0,0,0]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): activités 3`] = `"[0,13757,0,10075,4,21]"`;
@ -411,7 +408,7 @@ exports[`calculate simulations-rémunération-dirigeant (indépendant): échelle
exports[`calculate simulations-rémunération-dirigeant (indépendant): échelle de rémunération 2`] = `"[0,-225,0,0,3,21]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): échelle de rémunération 3`] = `"[0,616,0,471,3,21]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): échelle de rémunération 3`] = `"[0,615,0,471,3,21]"`;
exports[`calculate simulations-rémunération-dirigeant (indépendant): échelle de rémunération 4`] = `"[0,3084,0,2266,3,21]"`;
@ -424,7 +421,7 @@ exports[`calculate simulations-rémunération-dirigeant (indépendant): échelle
exports[`calculate simulations-rémunération-dirigeant (indépendant): échelle de rémunération 8`] = `"[0,69895,0,36431,4,56]"`;
exports[`calculate simulations-salarié: CCN HCR 1`] = `
"[4192,0,2500,2468,2351]
"[3532,0,2500,1998,1887]
Notifications affichées : contrat salarié . convention collective . contrôle décharge"
`;
@ -444,7 +441,7 @@ Notifications affichées : contrat salarié . convention collective . contrôle
`;
exports[`calculate simulations-salarié: CCN compta 1`] = `
"[4132,0,2500,2478,2363]
"[3470,0,2500,2006,1898]
Notifications affichées : contrat salarié . convention collective . contrôle décharge"
`;
@ -486,9 +483,9 @@ exports[`calculate simulations-salarié: activité partielle 6`] = `"[327,3750,3
exports[`calculate simulations-salarié: activité partielle 7`] = `"[427,0,4000,2594,2485]"`;
exports[`calculate simulations-salarié: activité partielle 8`] = `"[409,0,2000,1578,1544]"`;
exports[`calculate simulations-salarié: activité partielle 8`] = `"[409,0,2000,1539,1519]"`;
exports[`calculate simulations-salarié: activité partielle 9`] = `"[1247,0,2000,1540,1506]"`;
exports[`calculate simulations-salarié: activité partielle 9`] = `"[1247,0,2000,1539,1506]"`;
exports[`calculate simulations-salarié: activité partielle 10`] = `"[927,0,6000,4182,3498]"`;
@ -569,7 +566,7 @@ Notifications affichées : contrat salarié . CDD . information"
`;
exports[`calculate simulations-salarié: cdd 4`] = `
"[3744,0,2400,1878,1799]
"[3831,0,2400,1907,1828]
Notifications affichées : contrat salarié . CDD . information, contrat salarié . convention collective . contrôle décharge"
`;
@ -613,7 +610,7 @@ exports[`calculate simulations-salarié: frais pro - titres restaurant 3`] = `"[
exports[`calculate simulations-salarié: heures supplémentaires et complémentaires 1`] = `"[2583,0,2000,1636,1601]"`;
exports[`calculate simulations-salarié: heures supplémentaires et complémentaires 2`] = `"[3105,0,2000,2009,1960]"`;
exports[`calculate simulations-salarié: heures supplémentaires et complémentaires 2`] = `"[2541,0,2000,1606,1572]"`;
exports[`calculate simulations-salarié: heures supplémentaires et complémentaires 3`] = `"[2654,0,2000,1636,1601]"`;
@ -623,12 +620,12 @@ Notifications affichées : contrat salarié . convention collective . contrôle
`;
exports[`calculate simulations-salarié: heures supplémentaires et complémentaires 5`] = `
"[3025,0,2000,1970,1936]
"[2530,0,2000,1601,1566]
Notifications affichées : contrat salarié . convention collective . contrôle décharge"
`;
exports[`calculate simulations-salarié: heures supplémentaires et complémentaires 6`] = `
"[3040,0,2000,1978,1943]
"[2530,0,2000,1601,1566]
Notifications affichées : contrat salarié . convention collective . contrôle décharge"
`;
@ -681,10 +678,10 @@ exports[`calculate simulations-salarié: impôt sur le revenu 5`] = `
Notifications affichées : contrat salarié . rémunération . contrôle salaire élevé"
`;
exports[`calculate simulations-salarié: impôt sur le revenu 6`] = `"[4076,0,3000,2626,2481]"`;
exports[`calculate simulations-salarié: impôt sur le revenu 6`] = `"[4076,0,3000,2626,2485]"`;
exports[`calculate simulations-salarié: impôt sur le revenu 7`] = `
"[41831,0,30000,26966,16383]
"[41831,0,30000,26966,16655]
Notifications affichées : contrat salarié . rémunération . contrôle salaire élevé"
`;

View File

@ -42,4 +42,4 @@ impôt sur le revenu:
impôt . méthode de calcul: "'taux neutre'"
- dirigeant . indépendant . revenu net de cotisations: 20000 €/an
impôt . méthode de calcul: "'taux personnalisé'"
impôt . taux personnalisé: 10
impôt . taux personnalisé: 10%

View File

@ -10,9 +10,9 @@
avec charges:
- dirigeant . rémunération totale: 10000 €/an
entreprise . charges: 2000
entreprise . charges: 2000 €/an
- dirigeant . rémunération totale: 20000 €/an
entreprise . charges: 15000
entreprise . charges: 15000 €/an
ACRE:
- dirigeant . rémunération totale: 10000 €/an
@ -46,29 +46,29 @@ Contrats Madelin:
# cotisations (résultat comptable) n'est pas affecté car l'assiette des
# cotisations ne change pas:
- dirigeant . rémunération totale: 30000 €/an
entreprise . charges: 10000
dirigeant . indépendant . contrats madelin . mutuelle . montant: 3800 # plafond: 10% PSS donc environ 4100
entreprise . charges: 10000 €/an
dirigeant . indépendant . contrats madelin . mutuelle: 3800 €/an # plafond: 10% PSS donc environ 4100
# Cas retraite: la cotisation Madelin est supérieure au plafond => le revenu net de
# cotisations est affecté car l'assiette des cotisations est plus élevée
- dirigeant . rémunération totale: 30000 €/an
entreprise . charges: 10000
dirigeant . indépendant . contrats madelin . mutuelle . montant: 5000 # plafond: 10% PSS donc environ 4100
entreprise . charges: 10000 €/an
dirigeant . indépendant . contrats madelin . mutuelle: 5000 €/an # plafond: 10% PSS donc environ 4100
# Cas mutuelle
- dirigeant . rémunération totale: 30000 €/an
entreprise . charges: 10000
dirigeant . indépendant . contrats madelin . mutuelle . montant: 1000
entreprise . charges: 10000 €/an
dirigeant . indépendant . contrats madelin . mutuelle: 1000 €/an
# Cas global madelin faible
- dirigeant . rémunération totale: 20000 €/an
entreprise . charges: 1000
dirigeant . indépendant . contrats madelin . mutuelle . montant: 200
dirigeant . indépendant . contrats madelin . retraite . montant: 300
entreprise . charges: 1000 €/an
dirigeant . indépendant . contrats madelin . mutuelle: 200 €/an
dirigeant . indépendant . contrats madelin . retraite: 300 €/an
# Cas global madelin grand (plafonds calculés différemment)
- dirigeant . rémunération totale: 300000 €/an
entreprise . charges: 15000
dirigeant . indépendant . contrats madelin . mutuelle . montant: 1500
dirigeant . indépendant . contrats madelin . retraite . montant: 5000
entreprise . charges: 15000 €/an
dirigeant . indépendant . contrats madelin . mutuelle: 1500 €/an
dirigeant . indépendant . contrats madelin . retraite: 5000 €/an
# Cas charges plus faibles que total madelin
- dirigeant . rémunération totale: 20000 €/an
entreprise . charges: 500
dirigeant . indépendant . contrats madelin . mutuelle . montant: 300
dirigeant . indépendant . contrats madelin . retraite . montant: 300
entreprise . charges: 500 €/an
dirigeant . indépendant . contrats madelin . mutuelle: 300 €/an
dirigeant . indépendant . contrats madelin . retraite: 300 €/an

View File

@ -66,7 +66,7 @@ cdd:
- contrat salarié: "'CDD'"
contrat salarié . rémunération . brut de base: 2000 €/mois
contrat salarié . CDD . durée contrat: 6 mois
contrat salarié . CDD . congés non pris: 3 jours
contrat salarié . CDD . congés non pris: 3 jours ouvrés
- contrat salarié: "'CDD'"
contrat salarié . rémunération . brut de base: 2400 €/mois
contrat salarié . CDD . durée contrat: 10 mois
@ -394,13 +394,13 @@ taux spécifiques retraite complémentaire:
CCN batiment:
- contrat salarié . rémunération . brut de base: 2500 €/mois
contrat salarié . convention collective: "'BTP'"
contrat salarié . convention collective . BTP . catégorie du salarié: "'ouvrier'"
contrat salarié . convention collective . BTP . catégorie: "'ouvrier'"
- contrat salarié . rémunération . brut de base: 2500 €/mois
contrat salarié . convention collective: "'BTP'"
contrat salarié . convention collective . BTP . catégorie du salarié: "'etam'"
contrat salarié . convention collective . BTP . catégorie: "'etam'"
- contrat salarié . rémunération . brut de base: 2500 €/mois
contrat salarié . convention collective: "'BTP'"
contrat salarié . convention collective . BTP . catégorie du salarié: "'cadre'"
contrat salarié . convention collective . BTP . catégorie: "'cadre'"
CCN compta:
- contrat salarié . rémunération . brut de base: 2500 €/mois
@ -428,4 +428,4 @@ CCN spectacle vivant:
CCN sport:
- contrat salarié . rémunération . brut de base: 500 €/mois
contrat salarié . convention collective . sport . primes . nombre de manifestations: 2
contrat salarié . convention collective . sport . primes . nombre de manifestations: 2 manifestations

View File

@ -6,7 +6,7 @@
// renamed the test configuration may be adapted but the persisted snapshot will remain unchanged).
/* eslint-disable no-undef */
import Engine from 'publicodes'
import Engine, { evaluateRule } from 'publicodes'
import rules from '../../source/rules'
import artisteAuteurConfig from '../../source/sites/mon-entreprise.fr/pages/Simulateurs/configs/artiste-auteur.yaml'
import autoentrepreneurConfig from '../../source/sites/mon-entreprise.fr/pages/Simulateurs/configs/auto-entrepreneur.yaml'
@ -44,7 +44,7 @@ const runSimulations = (situations, targets, baseSituation = {}) =>
(rule) =>
rule.rawNode['type'] === 'notification'
)
.map(node => engine.evaluateNode(node))
.map(node => evaluateRule(engine, node.dottedName))
.filter(node => !!node.nodeValue)
.map(node => node.dottedName)
@ -60,7 +60,7 @@ const runSimulations = (situations, targets, baseSituation = {}) =>
})
)
it.only('calculate simulations-salarié', () => {
it('calculate simulations-salarié', () => {
runSimulations(
employeeSituations,
employeeConfig.objectifs,

View File

@ -25,8 +25,7 @@ function buildRuleDependancies(rule: RuleNode): Array<string> {
case 'une possibilité':
return acc
case 'recalcul':
node.explanation.amendedSituation.forEach(s => fn(s[1]))
return
return node.explanation.amendedSituation.flatMap(s => fn(s[1]))
case 'reference':
return [...acc, node.dottedName as string]
case 'rule':
@ -85,7 +84,7 @@ export function cyclicDependencies<Names extends string>(
return cycles.map(cycle => {
const c = cycle.reverse()
return c.reduce((acc, current) => {
return [...c, c[0]].reduce((acc, current) => {
const previous = acc.slice(-1)[0]
if (previous && !rulesDependenciesObject[previous].includes(current)) {
return acc

View File

@ -48,10 +48,10 @@ function gatherNodes(node: ASTNode): ASTNode[] {
export function traverseParsedRules(
fn: (n: ASTNode) => ASTNode,
parsedRules: Record<string, RuleNode>
): Record<string, ASTNode> {
): Record<string, RuleNode> {
return Object.fromEntries(
Object.entries(parsedRules).map(([name, rule]) => [name, fn(rule)])
)
) as Record<string, RuleNode>
}
const traverseASTNode: TraverseFunction<NodeKind> = (fn, node) => {

View File

@ -31,7 +31,7 @@ import { Temporal } from '../temporal'
export type ConstantNode = {
type: 'boolean' | 'objet' | 'number' | 'string'
nodeValue: boolean | Object | number | string | null
nodeValue: Evaluation
jsx: any
nodeKind: 'constant'
isDefault: boolean
@ -69,7 +69,7 @@ export type ASTNode = (
| ReplacementNode
) & {
isDefault?: boolean
rawNode?: string | Object
rawNode?: string | Record<string, unknown>
} & (EvaluationDecoration<Types> | {}) // TODO : separate type for evaluated AST Tree
export type MecanismNode = Exclude<
@ -77,9 +77,6 @@ export type MecanismNode = Exclude<
RuleNode | ConstantNode | ReferenceNode
>
export type NodeKind = ASTNode['nodeKind']
export type Value = ASTNode & {
nodeValue: number | string | boolean
}
export type TraverseFunction<Kind extends NodeKind> = (
fn: (n: ASTNode) => ASTNode,
@ -97,11 +94,11 @@ export type Unit = {
// Une temporalEvaluation est une liste d'evaluation sur chaque période. : [(Evaluation, Period)]
type EvaluationDecoration<T extends Types> = {
nodeValue: Evaluation<T>
missingVariables: Partial<Record<string, number>>
missingVariables: Record<string, number>
unit?: Unit
temporalValue?: Temporal<Evaluation>
}
export type Types = number | boolean | string | Object
export type Types = number | boolean | string | Record<string, unknown>
export type Evaluation<T extends Types = Types> = T | false | null
export type EvaluatedNode<T extends Types = Types> = ASTNode &
EvaluationDecoration<T>

View File

@ -1,6 +1,5 @@
import { toPairs } from 'ramda'
import { Trans, useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { makeJsx } from '../../evaluation'
import writtenNumbers from '../../locales/writtenNumbers.yaml'
import colors from './colors'

View File

@ -129,7 +129,7 @@ export const InfixMecanism = ({
`}
>
{prefixed && children}
<div className="value" css={dimValue ? `opacity: 0.5` : ''}>
<div className="value" css={dimValue ? 'opacity: 0.5' : ''}>
{makeJsx(value)}
</div>
{!prefixed && children}

View File

@ -73,7 +73,9 @@ export default function Rule({ dottedName, engine, language }) {
<h3>Effets </h3>
<ul>
{rule.replacements.map(replacement => (
<li>{makeJsx(replacement)}</li>
<li key={replacement.replacedReference.dottedName}>
{makeJsx(replacement)}
</li>
))}
</ul>
</>

View File

@ -1,250 +0,0 @@
import Engine, { EvaluatedNode, evaluationFunction } from '.'
import { typeWarning } from './error'
import { evaluateApplicability } from './evaluateRule'
import { mergeMissing } from './evaluation'
import { convertNodeToUnit } from './nodeUnits'
import { ParsedRule } from './types'
import { areUnitConvertible, serializeUnit } from './units'
export const evaluateReference: evaluationFunction = function(node) {
const rule = this.parsedRules[node.dottedName]
// When a rule exists in different version (created using the `replace` mecanism), we add
// a redirection in the evaluation of references to use a potential active replacement
const [
applicableReplacements,
replacementMissingVariableList
] = getApplicableReplacements.call(
this,
node.explanation?.contextRuleName ?? '',
rule
)
if (applicableReplacements.length) {
if (applicableReplacements.length > 1) {
// eslint-disable-next-line no-console
console.warn(`
Règle ${rule.dottedName}: plusieurs remplacements valides ont été trouvés :
\n\t${applicableReplacements.map(node => node.rawNode).join('\n\t')}
Par défaut, seul le premier s'applique. Si vous voulez un autre comportement, vous pouvez :
- Restreindre son applicabilité via "applicable si" sur la règle de définition
- Restreindre sa portée en ajoutant une liste blanche (via le mot clé "dans") ou une liste noire (via le mot clé "sauf dans")
`)
}
return this.evaluateNode(applicableReplacements[0])
}
const addReplacementMissingVariable = node => ({
...node,
missingVariables: replacementMissingVariableList.reduce(
mergeMissing,
node.missingVariables
)
})
const dottedName = node.dottedName
// On va vérifier dans le cache courant, dict, si la variable n'a pas été déjà évaluée
// En effet, l'évaluation dans le cas d'une variable qui a une formule, est coûteuse !
const cacheName =
dottedName + (node.explanation.filter ? ' .' + node.explanation.filter : '')
const cached = this.cache[cacheName]
if (cached) return addReplacementMissingVariable(cached)
const cacheNode = (
nodeValue: EvaluatedNode['nodeValue'],
missingVariables: EvaluatedNode['missingVariables'],
explanation?: Record<string, unknown>
) => {
this.cache[cacheName] = {
...node,
nodeValue,
...(explanation && {
explanation
}),
...(explanation?.temporalValue
? {
temporalValue: explanation.temporalValue
}
: {}),
...(explanation?.unit ? { unit: explanation.unit } : {}),
missingVariables
}
return addReplacementMissingVariable(this.cache[cacheName])
}
const applicabilityEvaluation = evaluateApplicability.call(this, rule as any)
if (!applicabilityEvaluation.nodeValue) {
return cacheNode(
applicabilityEvaluation.nodeValue,
applicabilityEvaluation.missingVariables,
applicabilityEvaluation
)
}
if (this.parsedSituation[dottedName]) {
// Conditional evaluation is required because some mecanisms like
// "synchronisation" store raw JS objects in the situation.
const situationValue = this.parsedSituation[dottedName]?.nodeKind
? this.evaluateNode(this.parsedSituation[dottedName])
: this.parsedSituation[dottedName]
const unit =
!situationValue.unit || serializeUnit(situationValue.unit) === ''
? rule.unit
: situationValue.unit
return cacheNode(
situationValue?.nodeValue !== undefined
? situationValue.nodeValue
: situationValue,
applicabilityEvaluation.missingVariables,
{
...rule,
...(situationValue?.nodeValue !== undefined && situationValue),
unit
}
)
}
if (rule.defaultValue != null) {
const evaluation = this.evaluateNode(rule.defaultValue)
return cacheNode(evaluation.nodeValue ?? evaluation, {
...evaluation.missingVariables,
[dottedName]: 1
})
}
if (rule.formule != null) {
const evaluation = this.evaluateNode(rule)
return cacheNode(
evaluation.nodeValue,
evaluation.missingVariables,
evaluation
)
}
return cacheNode(null, { [dottedName]: 2 })
}
// This function is a wrapper that can apply :
// - unit transformations to the value of the variable.
// See the unité-temporelle.yaml test suite for details
// - filters on the variable to select one part of the variable's 'composantes'
export const evaluateReferenceTransforms: evaluationFunction = function(node) {
// Filter transformation
if (node.explanation.filter) {
this.cache._meta.filter = node.explanation.filter
}
const filteredNode = this.evaluateNode(node.explanation.originalNode)
if (node.explanation.filter) {
delete this.cache._meta.filter
}
const { explanation, nodeValue } = filteredNode
if (!explanation || nodeValue === null) {
return filteredNode
}
const unit = node.explanation.unit
if (unit) {
try {
return convertNodeToUnit(unit, filteredNode)
} catch (e) {
typeWarning(
this.cache._meta.contextRule,
`Impossible de convertir la reference '${filteredNode.name}'`,
e
)
}
}
return filteredNode
}
/**
* Statically filter out replacements from `replaceBy`.
* Note: whitelist and blacklist filtering are applicable to the replacement
* itself or any parent namespace.
*/
export const getApplicableReplacedBy = (contextRuleName, replacedBy) =>
replacedBy
.sort(
(replacement1, replacement2) =>
+!!replacement2.whiteListedNames - +!!replacement1.whiteListedNames
)
.filter(
({ whiteListedNames }) =>
!whiteListedNames ||
whiteListedNames.some(name => contextRuleName.startsWith(name))
)
.filter(
({ blackListedNames }) =>
!blackListedNames ||
blackListedNames.every(name => !contextRuleName.startsWith(name))
)
.filter(({ referenceNode }) => contextRuleName !== referenceNode.dottedName)
/**
* Filter-out and apply all possible replacements at runtime.
*/
const getApplicableReplacements = function(
this: Engine<string>,
contextRuleName: string,
rule: ParsedRule
) {
let missingVariableList: Array<EvaluatedNode['missingVariables']> = []
if (contextRuleName.startsWith('[evaluation]')) {
return [[], []]
}
const applicableReplacements = getApplicableReplacedBy(
contextRuleName,
rule.replacedBy
)
// Remove remplacement defined in a not applicable node
.filter(({ referenceNode }) => {
const referenceRule = this.parsedRules[referenceNode.dottedName]
const {
nodeValue: isApplicable,
missingVariables
} = evaluateApplicability.call(this, referenceRule as any)
missingVariableList.push(missingVariables)
return isApplicable
})
// Remove remplacement defined in a node whose situation value is false
.filter(({ referenceNode }) => {
const referenceRule = this.parsedRules[referenceNode.dottedName]
const situationValue = this.parsedSituation[referenceRule.dottedName]
if (referenceNode.question && situationValue == null) {
missingVariableList.push({ [referenceNode.dottedName]: 1 })
}
return (situationValue as any)?.nodeValue !== false
})
// Remove remplacement defined in a boolean node whose evaluated value is false
.filter(({ referenceNode }) => {
const referenceRule = this.parsedRules[referenceNode.dottedName]
if (referenceRule.formule?.explanation?.operationType !== 'comparison') {
return true
}
const { nodeValue: isApplicable, missingVariables } = this.evaluateNode(
referenceRule
)
missingVariableList.push(missingVariables)
return isApplicable
})
.map(({ referenceNode, replacementNode }) =>
replacementNode != null ? replacementNode : referenceNode
)
.map(replacementNode => {
const replacedRuleUnit = rule.unit
if (!areUnitConvertible(replacementNode.unit, replacedRuleUnit)) {
typeWarning(
contextRuleName,
`L'unité de la règle de remplacement n'est pas compatible avec celle de la règle remplacée ${rule.dottedName}`
)
}
return {
...replacementNode,
unit: replacementNode.unit || replacedRuleUnit
}
})
missingVariableList = missingVariableList.filter(
missingVariables => !!Object.keys(missingVariables).length
)
return [applicableReplacements, missingVariableList]
}

View File

@ -38,14 +38,20 @@ export const makeJsx = (node: ASTNode): JSX.Element => {
return <Component {...node} />
}
export const collectNodeMissing = node => node?.missingVariables || {}
export const collectNodeMissing = (
node: EvaluatedNode | ASTNode
): Record<string, number> =>
'missingVariables' in node ? node.missingVariables : {}
export const bonus = (missings, hasCondition = true) =>
hasCondition ? map(x => x + 0.0001, missings || {}) : missings
export const mergeAllMissing = missings =>
export const mergeMissing = (
left: Record<string, number> | undefined,
right: Record<string, number> | undefined
): Record<string, number> => mergeWith(add, left || {}, right || {})
export const mergeAllMissing = (missings: Array<EvaluatedNode | ASTNode>) =>
missings.map(collectNodeMissing).reduce(mergeMissing, {})
export const mergeMissing = (left, right) =>
mergeWith(add, left || {}, right || {})
function convertNodesToSameUnit(nodes, contextRule, mecanismName) {
const firstNodeWithUnit = nodes.find(node => !!node.unit)
@ -144,7 +150,7 @@ export function evaluateObject<NodeName extends NodeKind>(
) {
return function(node) {
const evaluate = this.evaluateNode.bind(this)
const evaluations = mapObjIndexed(
const evaluations: Record<string, EvaluatedNode> = mapObjIndexed(
evaluate as any,
(node as any).explanation
)

View File

@ -1,7 +1,6 @@
/* Those are postprocessor functions for the Nearley grammar.ne.
The advantage of putting them here is to get prettier's JS formatting, since Nealrey doesn't support it https://github.com/kach/nearley/issues/310 */
import { normalizeDateString } from './date'
import { parseUnit } from './units'
import { parsePeriod } from './temporal'
export let binaryOperation = operationType => ([A, , operator, , B]) => ({

View File

@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/ban-types */
import { map } from 'ramda'
import { compose, map, mapObjIndexed } from 'ramda'
import { ASTNode, EvaluatedNode, NodeKind } from './AST/types'
import { evaluationFunctions } from './evaluationFunctions'
import { simplifyNodeUnit } from './nodeUnits'
import parse from './parse'
import parsePublicodes, { disambiguateReference } from './parsePublicodes'
import { getReplacements, inlineReplacements, ReplacementNode } from './replacement'
import { Rule, RuleNode } from './rule'
import * as utils from './ruleUtils'
@ -52,6 +53,7 @@ export type ParsedRules<Name extends string> = Record<
export default class Engine<Name extends string = string> {
parsedRules: ParsedRules<Name>
parsedSituation: Record<string, ASTNode> = {}
replacements: Record<string, Array<ReplacementNode>> = {}
cache: Cache
private warnings: Array<string> = []
@ -73,7 +75,7 @@ export default class Engine<Name extends string = string> {
this.parsedRules = parsePublicodes(
rules as Record<string, Rule>
) as ParsedRules<Name>
this.replacements = getReplacements(this.parsedRules)
}
private resetCache() {
@ -84,16 +86,18 @@ export default class Engine<Name extends string = string> {
situation: Partial<Record<Name, string | number | object | ASTNode>> = {}
) {
this.resetCache()
this.parsedSituation = map(value => {
this.parsedSituation = mapObjIndexed((value, key) => {
if (value && typeof value === 'object' && 'nodeKind' in value) {
return value as ASTNode
}
return disambiguateReference(this.parsedRules)(
return compose(
inlineReplacements(this.replacements),
disambiguateReference(this.parsedRules)
)(
parse(value, {
dottedName: "situation'''",
dottedName: `situation [${key}]`,
parsedRules: {}
})
)
}))
}, situation)
return this
@ -111,8 +115,10 @@ export default class Engine<Name extends string = string> {
originalWarn(warning)
}
const result = this.evaluateNode(
// TODO : No replacement here. Is this what we want ?
disambiguateReference(this.parsedRules)(
compose(
inlineReplacements(this.replacements),
disambiguateReference(this.parsedRules)
)(
parse(expression, {
dottedName: "evaluation'''",
parsedRules: {}

View File

@ -4,7 +4,7 @@ import parse from '../parse'
import { InfixMecanism, Mecanism } from '../components/mecanisms/common'
import { bonus, makeJsx, mergeMissing } from '../evaluation'
import { registerEvaluationFunction } from '../evaluationFunctions'
import { ASTNode } from '../AST/types'
import { ASTNode, EvaluatedNode } from '../AST/types'
export type ApplicableSiNode = {
explanation: {
@ -39,7 +39,7 @@ const evaluate: evaluationFunction<'applicable si'> = function(node) {
condition.nodeValue == null || condition.nodeValue === false
? condition.nodeValue
: 'nodeValue' in valeur
? valeur.nodeValue
? (valeur as EvaluatedNode).nodeValue
: null,
explanation: { valeur, condition },
missingVariables: mergeMissing(

View File

@ -1,11 +1,17 @@
import { is, map, max, mergeWith, reduce } from 'ramda'
import { is, isEmpty, map, max, mergeWith, reduce } from 'ramda'
import React from 'react'
import { evaluationFunction } from '..'
import { Mecanism } from '../components/mecanisms/common'
import { ASTNode } from '../AST/types'
import { collectNodeMissing, makeJsx } from '../evaluation'
import { ASTNode, EvaluatedNode, Evaluation } from '../AST/types'
import {
collectNodeMissing,
makeJsx,
mergeAllMissing,
mergeMissing
} from '../evaluation'
import { registerEvaluationFunction } from '../evaluationFunctions'
import parse from '../parse'
import { InternalError } from '../error'
export type UneDeCesConditionsNode = {
explanation: Array<ASTNode>
@ -14,24 +20,48 @@ export type UneDeCesConditionsNode = {
}
const evaluate: evaluationFunction<'une de ces conditions'> = function(node) {
const explanation = node.explanation.map(child => this.evaluateNode(child))
const anyTrue = explanation.find(e => e.nodeValue === true)
const anyNull = explanation.find(e => e.nodeValue === null)
const { nodeValue, missingVariables } = anyTrue ??
anyNull ?? {
type Calculations = {
explanation: Array<ASTNode | EvaluatedNode>
nodeValue: Evaluation<boolean>
missingVariables: Record<string, number>
}
const calculations = node.explanation.reduce<Calculations>(
(acc, node) => {
if (acc.nodeValue === true) {
return {
...acc,
explanation: [...acc.explanation, node]
}
}
if (acc.nodeValue === null || acc.nodeValue === false) {
const evaluatedNode = this.evaluateNode(node)
return {
nodeValue: evaluatedNode.nodeValue
? true
: evaluatedNode.nodeValue === null
? null
: acc.nodeValue,
missingVariables: evaluatedNode.nodeValue
? {}
: mergeMissing(
acc.missingVariables,
evaluatedNode.missingVariables
),
explanation: [...acc.explanation, evaluatedNode]
}
}
throw new InternalError([node, acc])
},
{
nodeValue: false,
// Unlike most other array merges of missing variables this is a "flat" merge
// because "one of these conditions" tend to be several tests of the same variable
// (e.g. contract type is one of x, y, z)
missingVariables: reduce(
mergeWith(max),
{},
map(collectNodeMissing, explanation)
)
missingVariables: {},
explanation: []
}
return { ...node, nodeValue, explanation, missingVariables }
)
return {
...node,
...calculations
}
}
export const mecanismOneOf = (v, context) => {

View File

@ -1,6 +1,6 @@
import React from 'react'
import { evaluationFunction } from '..'
import { ASTNode } from '../AST/types'
import { ASTNode, Unit } from '../AST/types'
import { Mecanism } from '../components/mecanisms/common'
import { convertToDate, convertToString } from '../date'
import {
@ -10,6 +10,7 @@ import {
parseObject
} from '../evaluation'
import { registerEvaluationFunction } from '../evaluationFunctions'
import { parseUnit } from '../units'
export type DuréeNode = {
explanation: {
@ -17,6 +18,7 @@ export type DuréeNode = {
"jusqu'à": ASTNode
}
jsx: any
unit: Unit
nodeKind: 'durée'
}
@ -75,6 +77,7 @@ export default (v, context) => {
return {
jsx: MecanismDurée,
explanation,
unit: parseUnit('jours'),
nodeKind: 'durée'
} as DuréeNode
}

View File

@ -9,7 +9,7 @@ import {
mapTemporal,
temporalAverage
} from '../temporal'
import { parseUnit } from '../units'
import {
evaluatePlafondUntilActiveTranche,
parseTranches,

View File

@ -38,15 +38,20 @@ export const evaluateInversion: evaluationFunction<'inversion'> = function(
candidate =>
this.parsedSituation[candidate.dottedName as string] != undefined
)
if (inversionGoal === undefined) {
const missingVariables = {
...Object.fromEntries(
node.explanation.inversionCandidates.map(candidate => [
candidate.dottedName,
1
])
),
[node.explanation.ruleToInverse]: 1
}
return {
...node,
missingVariables: {
...Object.fromEntries(
node.explanation.inversionCandidates.map(name => [name, 1])
),
[node.explanation.ruleToInverse]: 1
},
missingVariables,
nodeValue: null
}
}

View File

@ -1,7 +1,7 @@
import React from 'react'
import { evaluationFunction } from '..'
import { InfixMecanism, Mecanism } from '../components/mecanisms/common'
import { ASTNode } from '../AST/types'
import { ASTNode, EvaluatedNode } from '../AST/types'
import { bonus, makeJsx, mergeMissing } from '../evaluation'
import { registerEvaluationFunction } from '../evaluationFunctions'
import parse from '../parse'
@ -30,7 +30,7 @@ function MecanismNonApplicable({ explanation }) {
const evaluate: evaluationFunction<'non applicable si'> = function(node) {
const condition = this.evaluateNode(node.explanation.condition)
let valeur = node.explanation.valeur
if (condition.nodeValue !== true) {
if (condition.nodeValue === false || condition.nodeValue === null) {
valeur = this.evaluateNode(valeur)
}
return {
@ -38,10 +38,10 @@ const evaluate: evaluationFunction<'non applicable si'> = function(node) {
nodeValue:
condition.nodeValue === null
? null
: condition.nodeValue === true
: condition.nodeValue !== false
? false
: 'nodeValue' in valeur
? valeur.nodeValue
? (valeur as EvaluatedNode).nodeValue
: null,
explanation: { valeur, condition },
missingVariables: mergeMissing(

View File

@ -26,8 +26,8 @@ export const mecanismOnePossibility = (v, context: Context) => {
jsx: (node: PossibilityNode) => (
<Mecanism name="une possibilité parmis" value={null}>
<ul>
{node.explanation.map(node => (
<li>{makeJsx(node)}</li>
{node.explanation.map((node, i) => (
<li key={i}>{makeJsx(node)}</li>
))}
</ul>
</Mecanism>

View File

@ -30,7 +30,10 @@ function ParDéfautComponent({ explanation }) {
}
const evaluate: evaluationFunction<'par défaut'> = function(node) {
const explanation = { ...node.explanation }
const explanation: {
parDéfaut: EvaluatedNode | ASTNode
valeur: EvaluatedNode | ASTNode
} = { ...node.explanation }
let valeur = this.evaluateNode(explanation.valeur)
explanation.valeur = valeur
if (valeur.nodeValue === null) {

View File

@ -40,10 +40,10 @@ const evaluate: evaluationFunction<'plafond'> = function(node) {
let nodeValue = valeur.nodeValue
let plafond = node.explanation.plafond
if (nodeValue !== false) {
plafond = this.evaluateNode(plafond)
const evaluatedPlafond = this.evaluateNode(plafond)
if (valeur.unit) {
try {
plafond = convertNodeToUnit(valeur.unit, plafond as EvaluatedNode)
plafond = convertNodeToUnit(valeur.unit, evaluatedPlafond)
} catch (e) {
typeWarning(
this.cache._meta.contextRule,
@ -53,7 +53,7 @@ const evaluate: evaluationFunction<'plafond'> = function(node) {
}
}
}
plafond
if (
typeof nodeValue === 'number' &&
'nodeValue' in plafond &&

View File

@ -38,10 +38,10 @@ const evaluate: evaluationFunction<'plancher'> = function(node) {
let nodeValue = valeur.nodeValue
let plancher = node.explanation.plancher
if (nodeValue !== false) {
plancher = this.evaluateNode(plancher)
const evaluatedPlancher = this.evaluateNode(plancher)
if (valeur.unit) {
try {
plancher = convertNodeToUnit(valeur.unit, plancher as EvaluatedNode)
plancher = convertNodeToUnit(valeur.unit, evaluatedPlancher)
} catch (e) {
typeWarning(
this.cache._meta.contextRule,

View File

@ -1,4 +1,5 @@
import { isEmpty } from 'ramda'
import { EvaluatedRule } from '..'
import { ASTNode, EvaluatedNode } from '../AST/types'
import { InfixMecanism } from '../components/mecanisms/common'
import { makeJsx, mergeAllMissing } from '../evaluation'
@ -32,7 +33,6 @@ export default function parseSituation(v, context) {
situationKey: v[parseSituation.nom],
valeur: parse(v.valeur, context)
}
return {
jsx: MecanismSituation,
nodeKind: parseSituation.nom,
@ -59,7 +59,9 @@ registerEvaluationFunction(parseSituation.nom, function evaluate(node) {
valeur.unit ??
('unit' in explanation.valeur ? explanation.valeur.unit : undefined)
const missingVariables = mergeAllMissing(
[explanation.situationValeur, explanation.valeur].filter(Boolean)
[explanation.situationValeur, explanation.valeur].filter(Boolean) as Array<
EvaluatedRule
>
)
return {
...node,

View File

@ -59,7 +59,7 @@ registerEvaluationFunction(parseUnité.nom, function evaluate(node) {
} catch (e) {
typeWarning(
this.cache._meta.contextRule,
`Erreur lors de la conversion d'unité explicite`,
'Erreur lors de la conversion d\'unité explicite',
e
)
}

View File

@ -1,7 +1,7 @@
import { or } from 'ramda'
import { evaluationFunction } from '..'
import Variations from '../components/mecanisms/Variations'
import { ASTNode, Unit } from '../AST/types'
import { ASTNode, EvaluatedNode, Unit } from '../AST/types'
import { typeWarning } from '../error'
import { bonus, defaultNode } from '../evaluation'
import { registerEvaluationFunction } from '../evaluationFunctions'
@ -23,7 +23,7 @@ export type VariationNode = {
satisfied?: boolean
}>
nodeKind: 'variations'
jsx: Function
jsx: (n: any) => unknown
}
export const devariate = (k, v, context): ASTNode => {
@ -95,7 +95,10 @@ const evaluate: evaluationFunction<'variations'> = function(node) {
const evaluatedCondition = this.evaluateNode(condition)
const currentCondition = liftTemporal2(
(previousCond, currentCond) =>
previousCond === null ? previousCond : !previousCond && currentCond,
previousCond === null
? previousCond
: !previousCond &&
(currentCond === null ? null : currentCond !== false),
previousConditions,
evaluatedCondition.temporalValue ??
pureTemporal(evaluatedCondition.nodeValue)

View File

@ -67,7 +67,10 @@ Utilisez leur contrepartie française : 'oui' / 'non'`
const compiledGrammar = Grammar.fromCompiled(grammar)
function parseExpression(rawNode, context: Context): Object | undefined {
function parseExpression(
rawNode,
context: Context
): Record<string, unknown> | undefined {
/* 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. */

View File

@ -1,7 +1,12 @@
import yaml from 'yaml'
import { ParsedRules } from '.'
import { traverseParsedRules, updateAST } from './AST'
import parse from './parse'
import { inlineReplacements } from './replacement'
import {
getReplacements,
inlineReplacements,
ReplacementNode
} from './replacement'
import { Rule, RuleNode } from './rule'
import { disambiguateRuleReference } from './ruleUtils'
@ -13,10 +18,10 @@ export type Context = {
type RawRule = Omit<Rule, 'nom'> | string | undefined | number
export type RawPublicodes = Record<string, RawRule> | string
export default function parsePublicodes<Names extends string>(
export default function parsePublicodes(
rawRules: RawPublicodes,
partialContext: Partial<Context> = {}
) {
): ParsedRules<string> {
// STEP 1: parse Yaml
let rules =
typeof rawRules === 'string'
@ -41,7 +46,7 @@ export default function parsePublicodes<Names extends string>(
}
if (typeof rule !== 'object') {
rule = {
formule: rule
formule: '' + rule
}
}
parse({ nom: dottedName, ...rule }, context)
@ -52,10 +57,14 @@ export default function parsePublicodes<Names extends string>(
parsedRules = traverseParsedRules(
disambiguateReference(parsedRules),
parsedRules
) as Record<string, RuleNode>
)
// STEP 5: Inline replacements
parsedRules = inlineReplacements(parsedRules)
const replacements = getReplacements(parsedRules)
parsedRules = traverseParsedRules(
inlineReplacements(replacements),
parsedRules
)
// TODO STEP 6: check for cycle
@ -73,7 +82,7 @@ function transpileRef(object: Record<string, any> | string | Array<any>) {
if (!object || typeof object !== 'object') {
return object
}
object as Record<string, any>
object
return Object.entries(object).reduce((obj, [key, value]) => {
const match = /\[ref( (.+))?\]$/.exec(key)

View File

@ -1,15 +1,12 @@
import { groupBy } from 'ramda'
import { AST } from 'yaml'
import { traverseParsedRules, updateAST } from './AST'
import { updateAST } from './AST'
import { ASTNode } from './AST/types'
import Variations from './components/mecanisms/Variations'
import { InternalError, warning } from './error'
import { defaultNode, makeJsx } from './evaluation'
import { VariationNode } from './mecanisms/variations'
import parse from './parse'
import { Context } from './parsePublicodes'
import { RuleNode } from './rule'
import { Rule } from './rule'
import { Rule, RuleNode } from './rule'
import { coerceArray } from './utils'
export type ReplacementNode = {
@ -36,7 +33,7 @@ export function parseReplacements(
}
const replacedReference = parse(replacement.règle, context)
let replacementNode = parse(replacement.par ?? context.dottedName, context)
const replacementNode = parse(replacement.par ?? context.dottedName, context)
const [whiteListedNames, blackListedNames] = [
replacement.dans ?? [],
@ -82,10 +79,10 @@ export function parseRendNonApplicable(
)
}
export function inlineReplacements(
export function getReplacements(
parsedRules: Record<string, RuleNode>
): Record<string, RuleNode> {
const replacements: Record<string, Array<ReplacementNode>> = groupBy(
): Record<string, Array<ReplacementNode>> {
return groupBy(
(r: ReplacementNode) => {
if (!r.replacedReference.dottedName) {
throw new InternalError(r)
@ -94,27 +91,38 @@ export function inlineReplacements(
},
Object.values(parsedRules).flatMap(rule => rule.replacements)
)
return traverseParsedRules(
updateAST(node => {
if (
node.nodeKind === 'replacement' ||
node.nodeKind === 'inversion' ||
node.nodeKind === 'une possibilité' ||
node.nodeKind === 'recalcul'
) {
// We don't want to replace references in replacements...
// Nor in ammended situation of recalcul and inversion (for now)
return false
}
if (node.nodeKind === 'reference') {
if (!node.dottedName) {
throw new InternalError(node)
}
export function inlineReplacements(
replacements: Record<string, Array<ReplacementNode>>
): (n: ASTNode) => ASTNode {
return updateAST((n, fn) => {
if (
n.nodeKind === 'replacement' ||
n.nodeKind === 'inversion' ||
n.nodeKind === 'une possibilité'
) {
return false
}
if (n.nodeKind === 'recalcul') {
// We don't replace references in recalcul keys
return {
...n,
explanation: {
recalcul: fn(n.explanation.recalcul),
amendedSituation: n.explanation.amendedSituation.map(
([name, value]) => [name, fn(value)]
)
}
return replace(node, replacements[node.dottedName] ?? [])
}
}),
parsedRules
) as Record<string, RuleNode>
}
if (n.nodeKind === 'reference') {
if (!n.dottedName) {
throw new InternalError(n)
}
return replace(n, replacements[n.dottedName] ?? [])
}
})
}
function replace(
@ -145,7 +153,7 @@ function replace(
.sort((r1, r2) => {
// Replacement with whitelist conditions have precedence over the others
const criterion1 =
(+!!r2.whiteListedNames.length as number) -
(+!!r2.whiteListedNames.length ) -
+!!r1.whiteListedNames.length
// Replacement with blacklist condition have precedence over the others
const criterion2 =

View File

@ -1,7 +1,7 @@
import { filter, mapObjIndexed, pick } from 'ramda'
import { ASTNode, EvaluatedNode } from './AST/types'
import { bonus, makeJsx, mergeMissing } from './evaluation'
import { registerEvaluationFunction } from "./evaluationFunctions"
import { registerEvaluationFunction } from './evaluationFunctions'
import parse, { mecanismKeys } from './parse'
import { Context } from './parsePublicodes'
import { ReferenceNode } from './reference'
@ -10,7 +10,7 @@ import { nameLeaf, ruleParents } from './ruleUtils'
import { capitalise0 } from './utils'
export type Rule = {
formule?: Object | string
formule?: Record<string, unknown> | string
question?: string
description?: string
unité?: string
@ -27,14 +27,14 @@ export type Rule = {
note?: string
remplace?: RendNonApplicable | Array<RendNonApplicable>
'rend non applicable'?: Remplace | Array<string>
suggestions?: Record<string, string | number | object>
suggestions?: Record<string, string | number | Record<string, unknown>>
références?: { [source: string]: string }
API?: string
}
type Remplace = {
règle: string
par?: Object | string | number
par?: Record<string, unknown> | string | number
dans?: Array<string> | string
'sauf dans'?: Array<string> | string
} | string
@ -43,10 +43,10 @@ type RendNonApplicable = Exclude<Remplace, {par: any}>
export type RuleNode = {
dottedName: string
title: string
nodeKind: "rule"
nodeKind: 'rule'
jsx: any
virtualRule: boolean,
rawNode: Rule,
virtualRule: boolean
rawNode: Rule
replacements: Array<ReplacementNode>
explanation: {
parent: ASTNode | false
@ -86,12 +86,12 @@ export default function parseRule(
context.parsedRules[dottedName] = filter(Boolean, {
dottedName,
replacements: [
...parseRendNonApplicable(rawRule["rend non applicable"], ruleContext),
...parseRendNonApplicable(rawRule['rend non applicable'], ruleContext),
...parseReplacements(rawRule.remplace, ruleContext),
],
title: capitalise0(rawRule['titre'] || name),
suggestions: mapObjIndexed(node => parse(node, ruleContext), rawRule.suggestions ?? {}),
nodeKind: "rule",
nodeKind: 'rule',
jsx: node => <>
<code className="ui__ light-bg">{capitalise0(node.rawNode.nom)}</code>&nbsp;
{makeJsx(node.explanation.valeur)}

View File

@ -27,7 +27,7 @@ export function ruleParents<Names extends string>(
export function disambiguateRuleReference<R extends Record<string, RuleNode>>(
rules: R,
contextName: string = '',
contextName = '',
partialName: string
): keyof R {
const possibleDottedName = [

View File

@ -51,6 +51,6 @@ describe('Cyclic dependencies detectron 3000 ™', () => {
formule: a
`
const cycles = cyclesInDependenciesGraph(rules)
expect(cycles).to.deep.equal([["a", "a . c"]])
expect(cycles).to.deep.equal([['a', 'a . c']])
})
})