diff --git a/mon-entreprise/source/components/Notifications.tsx b/mon-entreprise/source/components/Notifications.tsx index c66a26214..71a321536 100644 --- a/mon-entreprise/source/components/Notifications.tsx +++ b/mon-entreprise/source/components/Notifications.tsx @@ -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() diff --git a/mon-entreprise/source/components/PercentageField.tsx b/mon-entreprise/source/components/PercentageField.tsx index cc9d4203b..acb6cbe4a 100644 --- a/mon-entreprise/source/components/PercentageField.tsx +++ b/mon-entreprise/source/components/PercentageField.tsx @@ -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 +} export default function PercentageField({ onChange, diff --git a/mon-entreprise/source/components/TargetSelection.tsx b/mon-entreprise/source/components/TargetSelection.tsx index 17ee83192..3769f675e 100644 --- a/mon-entreprise/source/components/TargetSelection.tsx +++ b/mon-entreprise/source/components/TargetSelection.tsx @@ -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) diff --git a/mon-entreprise/source/components/conversation/Conversation.tsx b/mon-entreprise/source/components/conversation/Conversation.tsx index 431c9d6ee..97eeac26f 100644 --- a/mon-entreprise/source/components/conversation/Conversation.tsx +++ b/mon-entreprise/source/components/conversation/Conversation.tsx @@ -85,7 +85,6 @@ export default function Conversation({ customEndMessages }: ConversationProps) {
diff --git a/mon-entreprise/source/components/conversation/DateInput.tsx b/mon-entreprise/source/components/conversation/DateInput.tsx index deac43ba6..7b9eb59f4 100644 --- a/mon-entreprise/source/components/conversation/DateInput.tsx +++ b/mon-entreprise/source/components/conversation/DateInput.tsx @@ -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) { diff --git a/mon-entreprise/source/components/conversation/Input.tsx b/mon-entreprise/source/components/conversation/Input.tsx index 71276df44..4d949c125 100644 --- a/mon-entreprise/source/components/conversation/Input.tsx +++ b/mon-entreprise/source/components/conversation/Input.tsx @@ -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 }) { 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 || '' }} />  {unité} diff --git a/mon-entreprise/source/components/conversation/ParagrapheInput.tsx b/mon-entreprise/source/components/conversation/ParagrapheInput.tsx index e2fdd4d52..352cccbd4 100644 --- a/mon-entreprise/source/components/conversation/ParagrapheInput.tsx +++ b/mon-entreprise/source/components/conversation/ParagrapheInput.tsx @@ -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 }) { 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" /> diff --git a/mon-entreprise/source/components/conversation/RuleInput.tsx b/mon-entreprise/source/components/conversation/RuleInput.tsx index af49e93fd..2dffb3ca8 100644 --- a/mon-entreprise/source/components/conversation/RuleInput.tsx +++ b/mon-entreprise/source/components/conversation/RuleInput.tsx @@ -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 = { isTarget?: boolean autoFocus?: boolean id?: string - value: Value className?: string onSubmit?: (source: string) => void } export type InputCommonProps = Pick< RuleInputProps, - 'dottedName' | 'value' | 'onChange' | 'autoFocus' | 'className' + 'dottedName' | 'onChange' | 'autoFocus' | 'className' > & Pick, 'title' | 'question' | 'suggestions'> & { key: string id: string + value: any //TODO EvaluatedRule['nodeValue'] missing: boolean required: boolean } @@ -161,13 +162,22 @@ export default function RuleInput({ } if (rule.type === 'texte') { - return + return } /> } if (rule.type === 'paragraphe') { - return + return ( + } /> + ) } - return + return ( + } + /> + ) } const getVariant = (node: ASTNode & { nodeKind: 'rule' }) => diff --git a/mon-entreprise/source/components/conversation/TextInput.tsx b/mon-entreprise/source/components/conversation/TextInput.tsx index 2b620c108..39b881efc 100644 --- a/mon-entreprise/source/components/conversation/TextInput.tsx +++ b/mon-entreprise/source/components/conversation/TextInput.tsx @@ -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 }) { 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" /> diff --git a/mon-entreprise/source/components/conversation/select/SelectCommune.tsx b/mon-entreprise/source/components/conversation/select/SelectCommune.tsx index 8dc4bd6a6..c595450a7 100644 --- a/mon-entreprise/source/components/conversation/select/SelectCommune.tsx +++ b/mon-entreprise/source/components/conversation/select/SelectCommune.tsx @@ -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 ) diff --git a/mon-entreprise/source/components/conversation/select/SelectTauxRisque.js b/mon-entreprise/source/components/conversation/select/SelectTauxRisque.js index 16c39d2e4..783d1cb49 100644 --- a/mon-entreprise/source/components/conversation/select/SelectTauxRisque.js +++ b/mon-entreprise/source/components/conversation/select/SelectTauxRisque.js @@ -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 ) }, []) diff --git a/mon-entreprise/source/components/utils/embeddedContext.js b/mon-entreprise/source/components/utils/embeddedContext.js index aa0c62217..4db73c802 100644 --- a/mon-entreprise/source/components/utils/embeddedContext.js +++ b/mon-entreprise/source/components/utils/embeddedContext.js @@ -1,2 +1,2 @@ -import { createContext } from 'react' -export const IsEmbeddedContext = createContext(false) +import { createContext } from 'react'; +export const IsEmbeddedContext = createContext(false); diff --git a/mon-entreprise/source/components/utils/useNextQuestion.tsx b/mon-entreprise/source/components/utils/useNextQuestion.tsx index 7fea3dce0..5692b3fbc 100644 --- a/mon-entreprise/source/components/utils/useNextQuestion.tsx +++ b/mon-entreprise/source/components/utils/useNextQuestion.tsx @@ -91,6 +91,7 @@ export function getNextQuestions( liste: whitelist = [], 'liste noire': blacklist = [] } = questionConfig + let nextSteps = difference(getNextSteps(missingVariables), answeredQuestions) nextSteps = nextSteps.filter( step => diff --git a/mon-entreprise/source/locales/rules-en.yaml b/mon-entreprise/source/locales/rules-en.yaml index 55b370ddc..200b7415f 100644 --- a/mon-entreprise/source/locales/rules-en.yaml +++ b/mon-entreprise/source/locales/rules-en.yaml @@ -926,30 +926,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 @@ -1337,16 +1319,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 @@ -1514,8 +1493,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: @@ -1532,22 +1511,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 l’article 5 de l’Accord 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 @@ -1589,30 +1582,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 l’article 5 de l’Accord 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é. @@ -2226,6 +2195,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) @@ -2244,11 +2218,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é ? @@ -2711,9 +2680,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 @@ -2723,9 +2689,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 @@ -2740,9 +2703,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 @@ -2752,9 +2712,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, @@ -3200,9 +3157,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 @@ -5251,6 +5205,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 @@ -5447,6 +5404,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: @@ -5464,20 +5426,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 @@ -5489,20 +5451,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 @@ -6105,9 +6067,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. @@ -6298,9 +6257,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 @@ -6316,6 +6272,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 diff --git a/mon-entreprise/source/rules/conventions-collectives/bâtiment.yaml b/mon-entreprise/source/rules/conventions-collectives/bâtiment.yaml index e64aebc64..785acc408 100644 --- a/mon-entreprise/source/rules/conventions-collectives/bâtiment.yaml +++ b/mon-entreprise/source/rules/conventions-collectives/bâtiment.yaml @@ -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 l’article 5 de l’Accord 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 l’article 5 de l’Accord 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 diff --git a/mon-entreprise/source/rules/dirigeant.yaml b/mon-entreprise/source/rules/dirigeant.yaml index 1d3e68448..3087f6dac 100644 --- a/mon-entreprise/source/rules/dirigeant.yaml +++ b/mon-entreprise/source/rules/dirigeant.yaml @@ -691,36 +691,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 @@ -740,20 +738,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: @@ -1048,12 +1044,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 - indemnités journalières maladie . 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/ @@ -1064,7 +1059,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: @@ -1073,7 +1068,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: @@ -1117,17 +1112,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: @@ -1158,16 +1153,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" diff --git a/mon-entreprise/source/rules/déclaration-revenu-indépendant.yaml b/mon-entreprise/source/rules/déclaration-revenu-indépendant.yaml index 8295644a5..aae643b19 100644 --- a/mon-entreprise/source/rules/déclaration-revenu-indépendant.yaml +++ b/mon-entreprise/source/rules/déclaration-revenu-indépendant.yaml @@ -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 diff --git a/mon-entreprise/source/rules/impôt.yaml b/mon-entreprise/source/rules/impôt.yaml index 6960fee5d..e66e75a9a 100644 --- a/mon-entreprise/source/rules/impôt.yaml +++ b/mon-entreprise/source/rules/impôt.yaml @@ -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: diff --git a/mon-entreprise/source/rules/profession-libérale.yaml b/mon-entreprise/source/rules/profession-libérale.yaml index 83a86eeec..f0a8743f9 100644 --- a/mon-entreprise/source/rules/profession-libérale.yaml +++ b/mon-entreprise/source/rules/profession-libérale.yaml @@ -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 @@ -324,7 +325,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) @@ -334,7 +335,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: @@ -352,6 +352,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: @@ -719,7 +726,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 diff --git a/mon-entreprise/source/rules/salarié.yaml b/mon-entreprise/source/rules/salarié.yaml index e0cf668d4..db0da14de 100644 --- a/mon-entreprise/source/rules/salarié.yaml +++ b/mon-entreprise/source/rules/salarié.yaml @@ -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 @@ -1061,8 +1060,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 @@ -1091,6 +1089,7 @@ contrat salarié . stage: - contrat salarié . activité partielle contrat salarié . stage . avertissement: + formule: oui type: notification sévérité: avertissement description: >- @@ -1101,7 +1100,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). @@ -2863,7 +2862,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 d’activité et des allocations de chômage à un seuil inférieur au Smic brut. - formule: somme: - revenus de remplacement . CSG déductible @@ -2872,57 +2870,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 d’Aide au Logement @@ -3359,6 +3332,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 diff --git a/mon-entreprise/source/selectors/simulationSelectors.ts b/mon-entreprise/source/selectors/simulationSelectors.ts index 4372e0234..2bd7f2d2e 100644 --- a/mon-entreprise/source/selectors/simulationSelectors.ts +++ b/mon-entreprise/source/selectors/simulationSelectors.ts @@ -16,7 +16,10 @@ export const objectifsSelector = createSelector([configSelector], config => { return objectifs }) -const emptySituation: Partial> = {} +const emptySituation: Partial +>> = {} export const situationSelector = (state: RootState) => state.simulation?.situation ?? emptySituation diff --git a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/config.yaml b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/config.yaml index 1f66bde2e..83187ed57 100644 --- a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/config.yaml +++ b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/config.yaml @@ -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' diff --git a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/index.tsx b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/index.tsx index c6a84dc2e..870306248 100644 --- a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/index.tsx +++ b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/AideDéclarationIndépendant/index.tsx @@ -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 (
@@ -113,7 +112,6 @@ export default function AideDéclarationIndépendant() { @@ -138,9 +136,11 @@ export default function AideDéclarationIndépendant() { {/* PLNR */} - - - + + + + +

Situation personnelle @@ -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) {

{summary ?? evaluatedRule.résumé}

- + @@ -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 (
{emoji('📄')} - {onGoingComputation && ( -

- - - Calcul en cours... - - -

- )} <> {results.map(r => ( - +

{r.title} {r.résumé}

{r.description &&

{r.description}

}

- {r.nodeValue != null ? ( - formatValue(r, { - displayedUnit: '€', - precision: 0 - }) - ) : ( - - )} + {formatValue(r, { + displayedUnit: '€', + precision: 0 + })}

diff --git a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/DemandeMobilite/index.tsx b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/DemandeMobilite/index.tsx index 659e378c5..0bf0a5667 100644 --- a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/DemandeMobilite/index.tsx +++ b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Gérer/DemandeMobilite/index.tsx @@ -171,7 +171,6 @@ function FormulairePublicodes() { onChange(field.dottedName, value)} /> diff --git a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx index 1e2e4a4e2..9692fa918 100644 --- a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx +++ b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/ArtisteAuteur.tsx @@ -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) {
@@ -83,7 +84,6 @@ function SimpleField({ dottedName }: SimpleFieldProps) { className="targetInput" isTarget dottedName={dottedName} - value={value} onChange={(x) => dispatch(updateSituation(dottedName, x))} useSwitch /> diff --git a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/configs/dirigeant-sasu.yaml b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/configs/dirigeant-sasu.yaml index 6554f8a10..48ed95b75 100644 --- a/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/configs/dirigeant-sasu.yaml +++ b/mon-entreprise/source/sites/mon-entreprise.fr/pages/Simulateurs/configs/dirigeant-sasu.yaml @@ -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 diff --git a/mon-entreprise/test/cycles.test.js b/mon-entreprise/test/cycles.test.js index d23fe7585..00afdcc60 100644 --- a/mon-entreprise/test/cycles.test.js +++ b/mon-entreprise/test/cycles.test.js @@ -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) => '#' + diff --git a/mon-entreprise/test/regressions/simulations-indépendant.yaml b/mon-entreprise/test/regressions/simulations-indépendant.yaml index e6b9d083c..863bb425e 100644 --- a/mon-entreprise/test/regressions/simulations-indépendant.yaml +++ b/mon-entreprise/test/regressions/simulations-indépendant.yaml @@ -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% diff --git a/mon-entreprise/test/regressions/simulations-rémunération-dirigeant.yaml b/mon-entreprise/test/regressions/simulations-rémunération-dirigeant.yaml index bb74f6924..f049a8959 100644 --- a/mon-entreprise/test/regressions/simulations-rémunération-dirigeant.yaml +++ b/mon-entreprise/test/regressions/simulations-rémunération-dirigeant.yaml @@ -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 diff --git a/mon-entreprise/test/regressions/simulations-salarié.yaml b/mon-entreprise/test/regressions/simulations-salarié.yaml index 705497d78..202878556 100644 --- a/mon-entreprise/test/regressions/simulations-salarié.yaml +++ b/mon-entreprise/test/regressions/simulations-salarié.yaml @@ -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 diff --git a/mon-entreprise/test/regressions/simulations.jest.js b/mon-entreprise/test/regressions/simulations.jest.js index 06867c320..cbff5fea8 100644 --- a/mon-entreprise/test/regressions/simulations.jest.js +++ b/mon-entreprise/test/regressions/simulations.jest.js @@ -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, diff --git a/publicodes/source/AST/graph.ts b/publicodes/source/AST/graph.ts index 9ea405aa7..81a9bbd8b 100644 --- a/publicodes/source/AST/graph.ts +++ b/publicodes/source/AST/graph.ts @@ -25,8 +25,7 @@ function buildRuleDependancies(rule: RuleNode): Array { 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( 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 diff --git a/publicodes/source/AST/index.ts b/publicodes/source/AST/index.ts index 6a25c59aa..32a72bf8f 100644 --- a/publicodes/source/AST/index.ts +++ b/publicodes/source/AST/index.ts @@ -48,10 +48,10 @@ function gatherNodes(node: ASTNode): ASTNode[] { export function traverseParsedRules( fn: (n: ASTNode) => ASTNode, parsedRules: Record -): Record { +): Record { return Object.fromEntries( Object.entries(parsedRules).map(([name, rule]) => [name, fn(rule)]) - ) + ) as Record } const traverseASTNode: TraverseFunction = (fn, node) => { diff --git a/publicodes/source/AST/types.ts b/publicodes/source/AST/types.ts index a607947b6..be8f417a2 100644 --- a/publicodes/source/AST/types.ts +++ b/publicodes/source/AST/types.ts @@ -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 } & (EvaluationDecoration | {}) // 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 = ( 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 = { nodeValue: Evaluation - missingVariables: Partial> + missingVariables: Record unit?: Unit temporalValue?: Temporal } -export type Types = number | boolean | string | Object +export type Types = number | boolean | string | Record export type Evaluation = T | false | null export type EvaluatedNode = ASTNode & EvaluationDecoration diff --git a/publicodes/source/components/mecanisms/Composantes.js b/publicodes/source/components/mecanisms/Composantes.js index fc3854cac..7cd8b8f4f 100644 --- a/publicodes/source/components/mecanisms/Composantes.js +++ b/publicodes/source/components/mecanisms/Composantes.js @@ -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' diff --git a/publicodes/source/components/mecanisms/common.tsx b/publicodes/source/components/mecanisms/common.tsx index 1a9979bfe..47cfdc7ca 100644 --- a/publicodes/source/components/mecanisms/common.tsx +++ b/publicodes/source/components/mecanisms/common.tsx @@ -129,7 +129,7 @@ export const InfixMecanism = ({ `} > {prefixed && children} -
+
{makeJsx(value)}
{!prefixed && children} diff --git a/publicodes/source/components/rule/Rule.tsx b/publicodes/source/components/rule/Rule.tsx index e721f623a..596bb4a88 100644 --- a/publicodes/source/components/rule/Rule.tsx +++ b/publicodes/source/components/rule/Rule.tsx @@ -73,7 +73,9 @@ export default function Rule({ dottedName, engine, language }) {

Effets

    {rule.replacements.map(replacement => ( -
  • {makeJsx(replacement)}
  • +
  • + {makeJsx(replacement)} +
  • ))}
diff --git a/publicodes/source/evaluateReference.ts b/publicodes/source/evaluateReference.ts deleted file mode 100644 index 168fc6a32..000000000 --- a/publicodes/source/evaluateReference.ts +++ /dev/null @@ -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 - ) => { - 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, - contextRuleName: string, - rule: ParsedRule -) { - let missingVariableList: Array = [] - 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] -} diff --git a/publicodes/source/evaluation.tsx b/publicodes/source/evaluation.tsx index b676e8ba5..0ad23a6cf 100644 --- a/publicodes/source/evaluation.tsx +++ b/publicodes/source/evaluation.tsx @@ -38,14 +38,20 @@ export const makeJsx = (node: ASTNode): JSX.Element => { return } -export const collectNodeMissing = node => node?.missingVariables || {} +export const collectNodeMissing = ( + node: EvaluatedNode | ASTNode +): Record => + '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 | undefined, + right: Record | undefined +): Record => mergeWith(add, left || {}, right || {}) + +export const mergeAllMissing = (missings: Array) => 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( ) { return function(node) { const evaluate = this.evaluateNode.bind(this) - const evaluations = mapObjIndexed( + const evaluations: Record = mapObjIndexed( evaluate as any, (node as any).explanation ) diff --git a/publicodes/source/grammarFunctions.js b/publicodes/source/grammarFunctions.js index 6e683d822..5d09ba77a 100644 --- a/publicodes/source/grammarFunctions.js +++ b/publicodes/source/grammarFunctions.js @@ -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]) => ({ diff --git a/publicodes/source/index.ts b/publicodes/source/index.ts index 505c9e6e7..e66cbfc7a 100644 --- a/publicodes/source/index.ts +++ b/publicodes/source/index.ts @@ -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 = Record< export default class Engine { parsedRules: ParsedRules parsedSituation: Record = {} + replacements: Record> = {} cache: Cache private warnings: Array = [] @@ -73,7 +75,7 @@ export default class Engine { this.parsedRules = parsePublicodes( rules as Record ) as ParsedRules - + this.replacements = getReplacements(this.parsedRules) } private resetCache() { @@ -84,16 +86,18 @@ export default class Engine { situation: Partial> = {} ) { 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 { 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: {} diff --git a/publicodes/source/mecanisms/applicable.tsx b/publicodes/source/mecanisms/applicable.tsx index 1c7901c3c..b0df1ffe0 100644 --- a/publicodes/source/mecanisms/applicable.tsx +++ b/publicodes/source/mecanisms/applicable.tsx @@ -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( diff --git a/publicodes/source/mecanisms/condition-oneof.tsx b/publicodes/source/mecanisms/condition-oneof.tsx index b0b570bbc..92fce19ef 100644 --- a/publicodes/source/mecanisms/condition-oneof.tsx +++ b/publicodes/source/mecanisms/condition-oneof.tsx @@ -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 @@ -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 + nodeValue: Evaluation + missingVariables: Record + } + const calculations = node.explanation.reduce( + (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) => { diff --git a/publicodes/source/mecanisms/durée.tsx b/publicodes/source/mecanisms/durée.tsx index 3168dfbb5..c5a728f64 100644 --- a/publicodes/source/mecanisms/durée.tsx +++ b/publicodes/source/mecanisms/durée.tsx @@ -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 } diff --git a/publicodes/source/mecanisms/grille.ts b/publicodes/source/mecanisms/grille.ts index f09e2c271..4037eb635 100644 --- a/publicodes/source/mecanisms/grille.ts +++ b/publicodes/source/mecanisms/grille.ts @@ -9,7 +9,7 @@ import { mapTemporal, temporalAverage } from '../temporal' -import { parseUnit } from '../units' + import { evaluatePlafondUntilActiveTranche, parseTranches, diff --git a/publicodes/source/mecanisms/inversion.ts b/publicodes/source/mecanisms/inversion.ts index f191f7bff..cdecbaee9 100644 --- a/publicodes/source/mecanisms/inversion.ts +++ b/publicodes/source/mecanisms/inversion.ts @@ -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 } } diff --git a/publicodes/source/mecanisms/nonApplicable.tsx b/publicodes/source/mecanisms/nonApplicable.tsx index fc8784110..07b2b275d 100644 --- a/publicodes/source/mecanisms/nonApplicable.tsx +++ b/publicodes/source/mecanisms/nonApplicable.tsx @@ -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( diff --git a/publicodes/source/mecanisms/one-possibility.tsx b/publicodes/source/mecanisms/one-possibility.tsx index 6e28628c1..5c80b98cf 100644 --- a/publicodes/source/mecanisms/one-possibility.tsx +++ b/publicodes/source/mecanisms/one-possibility.tsx @@ -26,8 +26,8 @@ export const mecanismOnePossibility = (v, context: Context) => { jsx: (node: PossibilityNode) => (
    - {node.explanation.map(node => ( -
  • {makeJsx(node)}
  • + {node.explanation.map((node, i) => ( +
  • {makeJsx(node)}
  • ))}
diff --git a/publicodes/source/mecanisms/parDéfaut.tsx b/publicodes/source/mecanisms/parDéfaut.tsx index 971e87ef4..6f2e3e44f 100644 --- a/publicodes/source/mecanisms/parDéfaut.tsx +++ b/publicodes/source/mecanisms/parDéfaut.tsx @@ -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) { diff --git a/publicodes/source/mecanisms/plafond.tsx b/publicodes/source/mecanisms/plafond.tsx index 7fbed8e82..de8fa1a85 100644 --- a/publicodes/source/mecanisms/plafond.tsx +++ b/publicodes/source/mecanisms/plafond.tsx @@ -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 && diff --git a/publicodes/source/mecanisms/plancher.tsx b/publicodes/source/mecanisms/plancher.tsx index 37ed1d922..d923c0b04 100644 --- a/publicodes/source/mecanisms/plancher.tsx +++ b/publicodes/source/mecanisms/plancher.tsx @@ -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, diff --git a/publicodes/source/mecanisms/situation.tsx b/publicodes/source/mecanisms/situation.tsx index 74857e86d..0ccf4603d 100644 --- a/publicodes/source/mecanisms/situation.tsx +++ b/publicodes/source/mecanisms/situation.tsx @@ -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, diff --git a/publicodes/source/mecanisms/unité.tsx b/publicodes/source/mecanisms/unité.tsx index 945e2cd81..01c583d06 100644 --- a/publicodes/source/mecanisms/unité.tsx +++ b/publicodes/source/mecanisms/unité.tsx @@ -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 ) } diff --git a/publicodes/source/mecanisms/variations.ts b/publicodes/source/mecanisms/variations.ts index d9946c773..6da181342 100644 --- a/publicodes/source/mecanisms/variations.ts +++ b/publicodes/source/mecanisms/variations.ts @@ -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) diff --git a/publicodes/source/parse.tsx b/publicodes/source/parse.tsx index c4f603df6..fae86bc75 100644 --- a/publicodes/source/parse.tsx +++ b/publicodes/source/parse.tsx @@ -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 | 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. */ diff --git a/publicodes/source/parsePublicodes.ts b/publicodes/source/parsePublicodes.ts index 6474a0206..edaaddf63 100644 --- a/publicodes/source/parsePublicodes.ts +++ b/publicodes/source/parsePublicodes.ts @@ -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 | string | undefined | number export type RawPublicodes = Record | string -export default function parsePublicodes( +export default function parsePublicodes( rawRules: RawPublicodes, partialContext: Partial = {} -) { +): ParsedRules { // STEP 1: parse Yaml let rules = typeof rawRules === 'string' @@ -41,7 +46,7 @@ export default function parsePublicodes( } if (typeof rule !== 'object') { rule = { - formule: rule + formule: '' + rule } } parse({ nom: dottedName, ...rule }, context) @@ -52,10 +57,14 @@ export default function parsePublicodes( parsedRules = traverseParsedRules( disambiguateReference(parsedRules), parsedRules - ) as Record + ) // 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 | Array) { if (!object || typeof object !== 'object') { return object } - object as Record + object return Object.entries(object).reduce((obj, [key, value]) => { const match = /\[ref( (.+))?\]$/.exec(key) diff --git a/publicodes/source/replacement.tsx b/publicodes/source/replacement.tsx index b35b8c223..6537edae6 100644 --- a/publicodes/source/replacement.tsx +++ b/publicodes/source/replacement.tsx @@ -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 -): Record { - const replacements: Record> = groupBy( +): Record> { + 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> +): (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 + } + 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 = diff --git a/publicodes/source/rule.tsx b/publicodes/source/rule.tsx index a6021516e..b10a5b243 100644 --- a/publicodes/source/rule.tsx +++ b/publicodes/source/rule.tsx @@ -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 question?: string description?: string unité?: string @@ -27,14 +27,14 @@ export type Rule = { note?: string remplace?: RendNonApplicable | Array 'rend non applicable'?: Remplace | Array - suggestions?: Record + suggestions?: Record> références?: { [source: string]: string } API?: string } type Remplace = { règle: string - par?: Object | string | number + par?: Record | string | number dans?: Array | string 'sauf dans'?: Array | string } | string @@ -43,10 +43,10 @@ type RendNonApplicable = Exclude export type RuleNode = { dottedName: string title: string - nodeKind: "rule" + nodeKind: 'rule' jsx: any - virtualRule: boolean, - rawNode: Rule, + virtualRule: boolean + rawNode: Rule replacements: Array 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 => <> {capitalise0(node.rawNode.nom)}  {makeJsx(node.explanation.valeur)} diff --git a/publicodes/source/ruleUtils.ts b/publicodes/source/ruleUtils.ts index 9d7c6437d..19f5b2807 100644 --- a/publicodes/source/ruleUtils.ts +++ b/publicodes/source/ruleUtils.ts @@ -27,7 +27,7 @@ export function ruleParents( export function disambiguateRuleReference>( rules: R, - contextName: string = '', + contextName = '', partialName: string ): keyof R { const possibleDottedName = [ diff --git a/publicodes/test/cycles.test.js b/publicodes/test/cycles.test.js index d03c7a006..645eda95a 100644 --- a/publicodes/test/cycles.test.js +++ b/publicodes/test/cycles.test.js @@ -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']]) }) })