From ab02bbb5f5b63285c98b6ff457eab19e5148f1d6 Mon Sep 17 00:00:00 2001 From: Johan Girod Date: Tue, 24 Nov 2020 17:22:34 +0100 Subject: [PATCH] =?UTF-8?q?:fire:=20Mise=20=C3=A0=20jour=20du=20site=20mon?= =?UTF-8?q?-entreprise=20suite=20aux=20refacto=20de=20evaluateRule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mon-entreprise/source/actions/actions.ts | 4 +- .../source/components/Distribution.tsx | 10 +- .../source/components/EngineValue.tsx | 26 +- .../source/components/Notifications.tsx | 31 ++- mon-entreprise/source/components/PaySlip.tsx | 87 ++++-- .../source/components/PaySlipSections.tsx | 2 +- .../source/components/SchemeComparaison.tsx | 19 +- .../source/components/SearchBar.tsx | 8 +- .../source/components/StackedBarChart.tsx | 4 +- .../source/components/TargetSelection.tsx | 120 ++++---- .../source/components/conversation/Aide.tsx | 4 +- .../components/conversation/AnswerList.tsx | 10 +- .../components/conversation/Conversation.tsx | 8 +- .../components/conversation/DateInput.tsx | 4 +- .../components/conversation/Explicable.tsx | 2 +- .../source/components/conversation/Input.tsx | 35 +-- .../conversation/InputSuggestions.tsx | 32 +-- .../conversation/ParagrapheInput.tsx | 9 +- .../components/conversation/Question.tsx | 28 +- .../components/conversation/RuleInput.tsx | 111 ++++---- .../components/conversation/TextInput.tsx | 6 +- .../conversation/select/SelectCommune.tsx | 14 +- .../AutoEntrepreneurExplanation.tsx | 17 +- .../IndépendantExplanation.tsx | 29 +- .../SalaryExplanation.tsx | 23 +- .../source/components/utils/EngineContext.tsx | 29 +- .../components/utils/useNextQuestion.tsx | 9 +- mon-entreprise/source/rules/base.yaml | 3 +- .../conventions-collectives/bâtiment.yaml | 10 +- .../spectacle-vivant.yaml | 10 +- .../rules/conventions-collectives/sport.yaml | 3 +- mon-entreprise/source/rules/dirigeant.yaml | 36 +-- .../rules/entreprise-établissement.yaml | 15 +- mon-entreprise/source/rules/impôt.yaml | 2 +- mon-entreprise/source/rules/index.ts | 19 +- .../source/rules/profession-libérale.yaml | 14 +- mon-entreprise/source/rules/salarié.yaml | 98 +++---- .../source/rules/situation-personnelle.yaml | 2 +- .../source/selectors/simulationSelectors.ts | 4 +- .../source/sites/mon-entreprise.fr/App.tsx | 5 +- .../mon-entreprise.fr/pages/Documentation.tsx | 4 +- .../AideDéclarationIndépendant/index.tsx | 36 +-- .../formulaire-détachement.yaml | 4 +- .../pages/Gérer/DemandeMobilite/index.tsx | 31 ++- .../pages/Simulateurs/ArtisteAuteur.tsx | 38 +-- .../pages/Simulateurs/ChômagePartiel.tsx | 24 +- .../Simulateurs/configs/indépendant.yaml | 2 + mon-entreprise/source/utils.ts | 3 +- mon-entreprise/test/conversation.test.js | 1 + mon-entreprise/test/cycles.test.js | 15 +- .../test/regressions/simulations-salarié.yaml | 108 ++++---- .../test/regressions/simulations.jest.js | 17 +- publicodes/README.md | 256 +++++++++--------- publicodes/source/AST/graph.ts | 46 +++- publicodes/source/AST/types.ts | 16 +- publicodes/source/components/index.tsx | 1 + .../components/mecanisms/Composantes.js | 19 +- .../mecanisms/{Product.js => Product.tsx} | 21 +- .../source/components/mecanisms/Variations.js | 1 + .../source/components/mecanisms/common.tsx | 46 +++- .../source/components/rule/Algorithm.tsx | 88 ------ publicodes/source/components/rule/Rule.tsx | 85 +++--- .../source/components/rule/RuleSource.tsx | 109 ++++---- publicodes/source/error.ts | 29 +- publicodes/source/evaluation.tsx | 13 +- publicodes/source/grammar.ne | 2 +- publicodes/source/index.ts | 69 +++-- publicodes/source/mecanisms/applicable.tsx | 8 +- publicodes/source/mecanisms/inversion.ts | 4 +- publicodes/source/mecanisms/nonApplicable.tsx | 13 +- .../source/mecanisms/one-possibility.tsx | 12 +- publicodes/source/mecanisms/operation.tsx | 6 +- publicodes/source/mecanisms/parDéfaut.tsx | 9 +- publicodes/source/mecanisms/plafond.tsx | 7 +- publicodes/source/mecanisms/plancher.tsx | 7 +- publicodes/source/mecanisms/recalcul.ts | 8 +- publicodes/source/mecanisms/situation.tsx | 19 +- publicodes/source/mecanisms/sum.tsx | 5 +- publicodes/source/mecanisms/trancheUtils.ts | 6 +- publicodes/source/mecanisms/unité.tsx | 22 +- .../source/mecanisms/variableTemporelle.ts | 2 +- publicodes/source/mecanisms/variations.ts | 11 +- publicodes/source/nodeUnits.ts | 10 +- publicodes/source/parse.tsx | 20 +- publicodes/source/parsePublicodes.ts | 13 +- publicodes/source/reference.ts | 4 +- .../{replacement.ts => replacement.tsx} | 98 ++++--- publicodes/source/{rule.ts => rule.tsx} | 46 ++-- publicodes/source/temporal.ts | 12 +- publicodes/source/translateRules.ts | 19 +- publicodes/source/units.ts | 2 +- publicodes/test/cycles.test.js | 4 +- publicodes/tsconfig.json | 1 - tsconfig.json | 2 +- 94 files changed, 1241 insertions(+), 1115 deletions(-) rename publicodes/source/components/mecanisms/{Product.js => Product.tsx} (65%) delete mode 100644 publicodes/source/components/rule/Algorithm.tsx rename publicodes/source/{replacement.ts => replacement.tsx} (64%) rename publicodes/source/{rule.ts => rule.tsx} (76%) diff --git a/mon-entreprise/source/actions/actions.ts b/mon-entreprise/source/actions/actions.ts index a138b5647..4fda1d8c3 100644 --- a/mon-entreprise/source/actions/actions.ts +++ b/mon-entreprise/source/actions/actions.ts @@ -71,7 +71,9 @@ export const validateStepWithValue = ( dottedName: DottedName, value: unknown ): ThunkResult => dispatch => { - dispatch(updateSituation(dottedName, value)) + if (value !== undefined) { + dispatch(updateSituation(dottedName, value)) + } dispatch({ type: 'STEP_ACTION', name: 'fold', diff --git a/mon-entreprise/source/components/Distribution.tsx b/mon-entreprise/source/components/Distribution.tsx index 0f987e8b0..3144d8f6e 100644 --- a/mon-entreprise/source/components/Distribution.tsx +++ b/mon-entreprise/source/components/Distribution.tsx @@ -1,4 +1,4 @@ -import { EngineContext } from 'Components/utils/EngineContext' +import { EngineContext, useEngine } from 'Components/utils/EngineContext' import { max } from 'ramda' import { useContext } from 'react' import { useSelector } from 'react-redux' @@ -14,11 +14,11 @@ export default function Distribution() { const targetUnit = useSelector(targetUnitSelector) const engine = useContext(EngineContext) const distribution = (getCotisationsBySection( - useContext(EngineContext).getParsedRules() + useEngine().getParsedRules() ).map(([section, cotisations]) => [ section, cotisations - .map(c => engine.evaluate(c, { unit: targetUnit })) + .map(c => engine.evaluate({ valeur: c, unité: targetUnit })) .reduce( (acc, evaluation) => acc + ((evaluation?.nodeValue as number) || 0), 0 @@ -65,8 +65,8 @@ export function DistributionBranch({ value={value} maximum={maximum} title={} - icon={icon ?? branche.icons} - description={branche.summary} + icon={icon ?? branche.rawNode.icônes} + description={branche.rawNode.résumé} unit="€" /> ) diff --git a/mon-entreprise/source/components/EngineValue.tsx b/mon-entreprise/source/components/EngineValue.tsx index 490bf2646..abc839804 100644 --- a/mon-entreprise/source/components/EngineValue.tsx +++ b/mon-entreprise/source/components/EngineValue.tsx @@ -4,42 +4,44 @@ import { useTranslation } from 'react-i18next' import { DottedName } from 'Rules' import { coerceArray } from '../utils' import RuleLink from './RuleLink' -import { EngineContext } from './utils/EngineContext' +import { EngineContext, useEngine } from './utils/EngineContext' -export type ValueProps = { +export type ValueProps = { expression: string unit?: string + engine?: Engine displayedUnit?: string precision?: number - engine?: Engine linkToRule?: boolean } & React.HTMLProps -export default function Value({ +export default function Value({ expression, unit, + engine, displayedUnit, precision, - engine, linkToRule = true, ...props -}: ValueProps) { +}: ValueProps) { const { language } = useTranslation().i18n if (expression === null) { throw new TypeError('expression cannot be null') } - const evaluation = (engine ?? useContext(EngineContext)).evaluate( - expression, - { unit } - ) + const e = engine ?? useEngine() + const isRule = expression in e.getParsedRules() + const evaluation = e.evaluate({ + valeur: expression, + ...(unit && { unité: unit }) + }) const value = formatValue(evaluation, { displayedUnit, language, precision }) - if ('dottedName' in evaluation && linkToRule) { + if (isRule && linkToRule) { return ( - + {value} ) diff --git a/mon-entreprise/source/components/Notifications.tsx b/mon-entreprise/source/components/Notifications.tsx index eef88d995..c66a26214 100644 --- a/mon-entreprise/source/components/Notifications.tsx +++ b/mon-entreprise/source/components/Notifications.tsx @@ -1,6 +1,10 @@ import { hideNotification } from 'Actions/actions' import animate from 'Components/ui/animate' -import { useInversionFail, EngineContext } from 'Components/utils/EngineContext' +import { + useInversionFail, + EngineContext, + useEngine +} from 'Components/utils/EngineContext' import { useContext } from 'react' import emoji from 'react-easy-emoji' import { useTranslation } from 'react-i18next' @@ -9,7 +13,7 @@ import { RootState } from 'Reducers/rootReducer' import './Notifications.css' import { Markdown } from './utils/markdown' import { ScrollToElement } from './utils/Scroll' -import { EvaluatedRule } from 'publicodes' +import Engine, { EvaluatedRule, ASTNode } 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 @@ -20,17 +24,20 @@ type Notification = Pick & { sévérité: 'avertissement' | 'information' } +export function getNotifications(engine: Engine) { + return Object.values(engine.getParsedRules()) + .filter( + (rule: ASTNode & { nodeKind: 'rule' }) => + rule.rawNode['type'] === 'notification' + ) + .map(node => engine.evaluateNode(node)) + .filter(node => !!node.nodeValue) + .map(node => node.rawNode) +} export default function Notifications() { const { t } = useTranslation() - const engine = useContext(EngineContext) - const notifications = Object.values(engine.getParsedRules()) - .filter(rule => rule['type'] === 'notification') - .filter( - notification => - ![null, false].includes( - engine.evaluate(notification.dottedName).isApplicable - ) - ) + const engine = useEngine() + const inversionFail = useInversionFail() const hiddenNotifications = useSelector( (state: RootState) => state.simulation?.hiddenNotifications @@ -49,7 +56,7 @@ export default function Notifications() { sévérité: 'avertissement' } ] - : ((notifications as any) as Array) + : ((getNotifications(engine) as any) as Array) if (!messages?.length) return null return ( diff --git a/mon-entreprise/source/components/PaySlip.tsx b/mon-entreprise/source/components/PaySlip.tsx index 68ca9c90c..b857dc400 100644 --- a/mon-entreprise/source/components/PaySlip.tsx +++ b/mon-entreprise/source/components/PaySlip.tsx @@ -1,7 +1,13 @@ import Value from 'Components/EngineValue' import RuleLink from 'Components/RuleLink' -import { EngineContext, useEvaluation } from 'Components/utils/EngineContext' -import { formatValue, ParsedRule, ParsedRules } from 'publicodes' +import { EngineContext, useEngine } from 'Components/utils/EngineContext' +import { + ASTNode, + EvaluatedNode, + formatValue, + ParsedRules, + reduceAST +} from 'publicodes' import { Fragment, useContext } from 'react' import { Trans, useTranslation } from 'react-i18next' import { DottedName } from 'Rules' @@ -21,9 +27,9 @@ export const SECTION_ORDER = [ type Section = typeof SECTION_ORDER[number] -function getSection(rule: ParsedRule): Section { +function getSection(rule: ASTNode & { nodeKind: 'rule' }): Section { const section = ('protection sociale . ' + - rule.cotisation?.branche) as Section + rule.rawNode.cotisation?.branche) as Section if (SECTION_ORDER.includes(section)) { return section } @@ -31,24 +37,43 @@ function getSection(rule: ParsedRule): Section { } export function getCotisationsBySection( - parsedRules: ParsedRules + parsedRules: ParsedRules ): Array<[Section, DottedName[]]> { - const cotisations = [ - ...parsedRules['contrat salarié . cotisations . patronales'].formule - .explanation.explanation, - ...parsedRules['contrat salarié . cotisations . salariales'].formule - .explanation.explanation - ] + function findCotisations(dottedName: DottedName) { + return reduceAST>( + (acc, node) => { + if ( + node.nodeKind === 'reference' && + node.dottedName !== 'contrat salarié . cotisations' && + node.dottedName?.startsWith('contrat salarié . ') && + node.dottedName !== + 'contrat salarié . cotisations . patronales . réductions de cotisations' + ) { + return [...acc, node] + } + }, + [], + parsedRules[dottedName] + ) + } + + const cotisations = ([ + ...findCotisations('contrat salarié . cotisations . patronales'), + ...findCotisations('contrat salarié . cotisations . salariales') + ] as Array) .map(cotisation => cotisation.dottedName) .filter(Boolean) + .map( + dottedName => + dottedName.replace(/ . (salarié|employeur)$/, '') as DottedName + ) .reduce((acc, cotisation: DottedName) => { const sectionName = getSection(parsedRules[cotisation]) return { ...acc, [sectionName]: (acc[sectionName] ?? new Set()).add(cotisation) } - }, {}) as Record> - + }, {} as Record>) return Object.entries(cotisations) .map(([section, dottedNames]) => [section, [...dottedNames.values()]]) .sort( @@ -59,7 +84,7 @@ export function getCotisationsBySection( } export default function PaySlip() { - const parsedRules = useContext(EngineContext).getParsedRules() + const parsedRules = useEngine().getParsedRules() const cotisationsBySection = getCotisationsBySection(parsedRules) return ( @@ -122,10 +147,12 @@ export default function PaySlip() { {/* Total cotisation */} @@ -152,18 +179,34 @@ export default function PaySlip() { ) } +function findReferenceInNode(dottedName: DottedName, node: EvaluatedNode) { + return reduceAST<(EvaluatedNode & { nodeKind: 'reference' }) | null>( + (acc, node) => { + if ( + node.nodeKind === 'reference' && + node.dottedName?.startsWith(dottedName) + ) { + return node as EvaluatedNode & { nodeKind: 'reference' } + } else if (node.nodeKind === 'reference') { + return acc + } + }, + null, + node + ) +} function Cotisation({ dottedName }: { dottedName: DottedName }) { const language = useTranslation().i18n.language - const partSalariale = useEvaluation( - 'contrat salarié . cotisations . salariales' - )?.formule.explanation.explanation.find( - (cotisation: ParsedRule) => cotisation.dottedName === dottedName + const engine = useContext(EngineContext) + const cotisationsSalariales = engine.evaluateNode( + engine.getParsedRules()['contrat salarié . cotisations . salariales'] ) - const partPatronale = useEvaluation( - 'contrat salarié . cotisations . patronales' - )?.formule.explanation.explanation.find( - (cotisation: ParsedRule) => cotisation.dottedName === dottedName + const cotisationsPatronales = engine.evaluateNode( + engine.getParsedRules()['contrat salarié . cotisations . patronales'] ) + const partSalariale = findReferenceInNode(dottedName, cotisationsSalariales) + const partPatronale = findReferenceInNode(dottedName, cotisationsPatronales) + if (!partPatronale?.nodeValue && !partSalariale?.nodeValue) { return null } diff --git a/mon-entreprise/source/components/PaySlipSections.tsx b/mon-entreprise/source/components/PaySlipSections.tsx index bf8f9d7dd..3a28ea94e 100644 --- a/mon-entreprise/source/components/PaySlipSections.tsx +++ b/mon-entreprise/source/components/PaySlipSections.tsx @@ -66,7 +66,7 @@ export const SalaireNetSection = () => { type LineProps = { rule: DottedName negative?: boolean -} & Omit +} & Omit, 'expression'> export function Line({ rule, diff --git a/mon-entreprise/source/components/SchemeComparaison.tsx b/mon-entreprise/source/components/SchemeComparaison.tsx index e8de1335b..15b5c27eb 100644 --- a/mon-entreprise/source/components/SchemeComparaison.tsx +++ b/mon-entreprise/source/components/SchemeComparaison.tsx @@ -24,7 +24,8 @@ import { useDispatch, useSelector } from 'react-redux' import { situationSelector } from 'Selectors/simulationSelectors' import InfoBulle from 'Components/ui/InfoBulle' import './SchemeComparaison.css' -import { EngineContext, useEvaluation } from './utils/EngineContext' +import { EngineContext, useEngine } from './utils/EngineContext' +import { DottedName } from 'Rules' type SchemeComparaisonProps = { hideAutoEntrepreneur?: boolean @@ -39,9 +40,11 @@ export default function SchemeComparaison({ useEffect(() => { dispatch(setSimulationConfig(dirigeantComparaison)) }, []) - const plafondAutoEntrepreneurDépassé = useEvaluation( - 'dirigeant . auto-entrepreneur . contrôle seuil de CA dépassé' - ).isApplicable + const engine = useEngine() + const plafondAutoEntrepreneurDépassé = + engine.evaluate( + 'dirigeant . auto-entrepreneur . contrôle seuil de CA dépassé' + ).nodeValue === true const [showMore, setShowMore] = useState(false) const [conversationStarted, setConversationStarted] = useState( @@ -51,13 +54,13 @@ export default function SchemeComparaison({ setConversationStarted ]) - const parsedRules = useContext(EngineContext).getParsedRules() + const parsedRules = engine.getParsedRules() const situation = useSelector(situationSelector) const displayResult = useSelector(situationSelector)['entreprise . charges'] != undefined const assimiléEngine = useMemo( () => - new Engine(parsedRules).setSituation({ + new Engine(parsedRules).setSituation({ ...situation, dirigeant: "'assimilé salarié'" }), @@ -65,7 +68,7 @@ export default function SchemeComparaison({ ) const autoEntrepreneurEngine = useMemo( () => - new Engine(parsedRules).setSituation({ + new Engine(parsedRules).setSituation({ ...situation, dirigeant: "'auto-entrepreneur'" }), @@ -73,7 +76,7 @@ export default function SchemeComparaison({ ) const indépendantEngine = useMemo( () => - new Engine(parsedRules).setSituation({ + new Engine(parsedRules).setSituation({ ...situation, dirigeant: "'indépendant'" }), diff --git a/mon-entreprise/source/components/SearchBar.tsx b/mon-entreprise/source/components/SearchBar.tsx index ce798cb4b..416f60ddb 100644 --- a/mon-entreprise/source/components/SearchBar.tsx +++ b/mon-entreprise/source/components/SearchBar.tsx @@ -4,7 +4,7 @@ import { DottedName } from 'Rules' import Worker from 'worker-loader!./SearchBar.worker.js' import RuleLink from './RuleLink' import './SearchBar.css' -import { EngineContext } from './utils/EngineContext' +import { EngineContext, useEngine } from './utils/EngineContext' import { utils } from 'publicodes' const worker = new Worker() @@ -62,7 +62,7 @@ function highlightMatches(str: string, matches: Matches) { export default function SearchBar({ showListByDefault = false }: SearchBarProps) { - const rules = useContext(EngineContext).getParsedRules() + const rules = useEngine().getParsedRules() const [input, setInput] = useState('') const [results, setResults] = useState< Array<{ @@ -78,8 +78,8 @@ export default function SearchBar({ .filter(utils.ruleWithDedicatedDocumentationPage) .map(rule => ({ title: - rule.title ?? - rule.name + (rule.acronyme ? ` (${rule.acronyme})` : ''), + rule.title + + (rule.rawNode.acronyme ? ` (${rule.rawNode.acronyme})` : ''), dottedName: rule.dottedName, espace: rule.dottedName.split(' . ').reverse() })), diff --git a/mon-entreprise/source/components/StackedBarChart.tsx b/mon-entreprise/source/components/StackedBarChart.tsx index 10cf9947e..d3cf3dd6c 100644 --- a/mon-entreprise/source/components/StackedBarChart.tsx +++ b/mon-entreprise/source/components/StackedBarChart.tsx @@ -1,6 +1,6 @@ import RuleLink from 'Components/RuleLink' import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting' -import { EvaluatedRule, Evaluation, Types } from 'publicodes' +import { EvaluatedNode, EvaluatedRule } from 'publicodes' import React from 'react' import { animated, useSpring } from 'react-spring' import { DottedName } from 'Rules' @@ -82,7 +82,7 @@ export function roundedPercentages(values: Array) { type StackedBarChartProps = { data: Array<{ color?: string - value: Evaluation + value: EvaluatedNode['nodeValue'] legend: React.ReactNode key: string }> diff --git a/mon-entreprise/source/components/TargetSelection.tsx b/mon-entreprise/source/components/TargetSelection.tsx index 3070381f2..17ee83192 100644 --- a/mon-entreprise/source/components/TargetSelection.tsx +++ b/mon-entreprise/source/components/TargetSelection.tsx @@ -7,12 +7,18 @@ import AnimatedTargetValue from 'Components/ui/AnimatedTargetValue' import { ThemeColorsContext } from 'Components/utils/colors' import { EngineContext, - useEvaluation, + useEngine, useInversionFail } from 'Components/utils/EngineContext' import { SitePathsContext } from 'Components/utils/SitePathsContext' -import { EvaluatedNode } from 'publicodes' -import { EvaluatedRule, formatValue } from 'publicodes' +import { + ASTNode, + EvaluatedNode, + EvaluatedRule, + evaluateRule, + formatValue, + reduceAST +} from 'publicodes' import { isNil } from 'ramda' import { Fragment, useCallback, useContext } from 'react' import emoji from 'react-easy-emoji' @@ -20,11 +26,8 @@ import { Trans, useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import { useLocation } from 'react-router-dom' import { RootState } from 'Reducers/rootReducer' -import { DottedName, ParsedRule } from 'Rules' -import { - situationSelector, - targetUnitSelector -} from 'Selectors/simulationSelectors' +import { DottedName } from 'Rules' +import { targetUnitSelector } from 'Selectors/simulationSelectors' import CurrencyInput from './CurrencyInput/CurrencyInput' import './TargetSelection.css' @@ -84,13 +87,15 @@ type TargetProps = { } const Target = ({ dottedName }: TargetProps) => { const activeInput = useSelector((state: RootState) => state.activeTargetInput) - const target = useEvaluation(dottedName, { - unit: useSelector(targetUnitSelector) + const engine = useEngine() + const target = evaluateRule(engine, dottedName, { + unité: useSelector(targetUnitSelector), + arrondi: 'oui' }) const dispatch = useDispatch() const onSuggestionClick = useCallback( value => { - dispatch(updateSituation(target.dottedName, value)) + dispatch(updateSituation(dottedName, value)) }, [target.dottedName, dispatch] ) @@ -103,7 +108,6 @@ const Target = ({ dottedName }: TargetProps) => { return null } const isActiveInput = activeInput === target.dottedName - return (
  • { @@ -153,7 +156,7 @@ const Target = ({ dottedName }: TargetProps) => { ) } -const Header = ({ target }: { target: ParsedRule }) => { +const Header = ({ target }: { target: EvaluatedRule }) => { const sitePaths = useContext(SitePathsContext) const { t } = useTranslation() const { pathname } = useLocation() @@ -166,11 +169,11 @@ const Header = ({ target }: { target: ParsedRule }) => { - {target.title || target.name} + {target.title} {hackyShowPeriod && ' ' + t('mensuel')} -

    {target.summary}

    +

    {target.résumé}

    ) @@ -190,26 +193,23 @@ function TargetInputOrValue({ const { language } = useTranslation().i18n const colors = useContext(ThemeColorsContext) const dispatch = useDispatch() - const situationValue = useSelector(situationSelector)[target.dottedName] const targetUnit = useSelector(targetUnitSelector) const engine = useContext(EngineContext) const value = - typeof situationValue === 'string' - ? Math.round( - engine.evaluate(situationValue, { unit: targetUnit }) - .nodeValue as number - ) - : situationValue != null - ? situationValue - : target?.nodeValue != null - ? Math.round(+target.nodeValue) - : undefined + (engine.evaluate({ + valeur: target.dottedName, + unité: targetUnit, + arrondi: 'oui' + }).nodeValue as number) ?? undefined const blurValue = useInversionFail() && !isActiveInput const onChange = useCallback( evt => dispatch( - updateSituation(target.dottedName, +evt.target.value + ' ' + targetUnit) + updateSituation(target.dottedName, { + valeur: evt.target.value, + unité: targetUnit + }) ), [targetUnit, target, dispatch] ) @@ -267,11 +267,17 @@ function TargetInputOrValue({ } function TitreRestaurant() { const targetUnit = useSelector(targetUnitSelector) - const titresRestaurant = useEvaluation( - 'contrat salarié . frais professionnels . titres-restaurant . montant', - { unit: targetUnit } - ) const { language } = useTranslation().i18n + + const titresRestaurant = evaluateRule( + useEngine(), + 'contrat salarié . frais professionnels . titres-restaurant . montant', + { + unité: targetUnit, + arrondi: 'oui' + } + ) + if (!titresRestaurant?.nodeValue) return null return ( @@ -279,10 +285,7 @@ function TitreRestaurant() { +{' '} - {formatValue(titresRestaurant, { - displayedUnit: '€', - language - })} + {formatValue(titresRestaurant, { displayedUnit: '€', language })} {' '} en titres-restaurant {emoji(' 🍽')} @@ -292,35 +295,46 @@ function TitreRestaurant() { } function AidesGlimpse() { const targetUnit = useSelector(targetUnitSelector) - const aides = useEvaluation('contrat salarié . aides employeur', { - unit: targetUnit - }) const { language } = useTranslation().i18n + const dottedName = 'contrat salarié . aides employeur' + const engine = useEngine() + const aides = evaluateRule(engine, dottedName, { + unité: targetUnit, + arrondi: 'oui' + }) + + if (!aides?.nodeValue) return null // Dans le cas où il n'y a qu'une seule aide à l'embauche qui s'applique, nous // faisons un lien direct vers cette aide, plutôt qu'un lien vers la liste qui // est une somme des aides qui sont toutes nulle sauf l'aide active. - const aidesDetail = aides?.formule.explanation.explanation - const aidesNotNul = aidesDetail?.filter( - (node: EvaluatedNode) => node.nodeValue !== false + const aideLink = reduceAST( + (acc, node) => { + if (node.nodeKind === 'somme') { + const aidesNotNul = (node.explanation as EvaluatedNode[]).filter( + ({ nodeValue }) => nodeValue !== false + ) + console.log('aidesNotNul', aidesNotNul, node.explanation) + if (aidesNotNul.length === 1) { + return (aidesNotNul[0] as ASTNode & { nodeKind: 'reference' }) + .dottedName as DottedName + } else { + return acc + } + } + }, + aides.dottedName, + engine.evaluateNode(engine.getParsedRules()[dottedName]) ) - const aideLink = aidesNotNul?.length === 1 ? aidesNotNul[0] : aides - - if (!aides?.nodeValue) return null return (
    - + en incluant{' '} - - {formatValue(aides, { - displayedUnit: '€', - language - })} - + {formatValue(aides, { displayedUnit: '€', language })} {' '} - d'aides {emoji(aides?.icons ?? '')} + d'aides {emoji(aides.icônes ?? '')}
    diff --git a/mon-entreprise/source/components/conversation/Aide.tsx b/mon-entreprise/source/components/conversation/Aide.tsx index 8ede2419d..8d62ba5b3 100644 --- a/mon-entreprise/source/components/conversation/Aide.tsx +++ b/mon-entreprise/source/components/conversation/Aide.tsx @@ -19,8 +19,8 @@ export default function Aide() { if (!explained) return null const rule = rules[explained], - text = rule.description, - refs = rule.références + text = rule.rawNode.description, + refs = rule.rawNode.références return ( diff --git a/mon-entreprise/source/components/conversation/AnswerList.tsx b/mon-entreprise/source/components/conversation/AnswerList.tsx index 9c3fd7266..c2ec2474c 100644 --- a/mon-entreprise/source/components/conversation/AnswerList.tsx +++ b/mon-entreprise/source/components/conversation/AnswerList.tsx @@ -1,8 +1,9 @@ import { goToQuestion, resetSimulation } from 'Actions/actions' import Overlay from 'Components/Overlay' -import { useEvaluation } from 'Components/utils/EngineContext' +import { EngineContext, useEngine } from 'Components/utils/EngineContext' import { useNextQuestions } from 'Components/utils/useNextQuestion' -import { formatValue } from 'publicodes' +import { evaluateRule, formatValue } from 'publicodes' +import { useContext } from 'react' import emoji from 'react-easy-emoji' import { Trans, useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' @@ -68,13 +69,14 @@ function StepsTable({ onClose: () => void }) { const dispatch = useDispatch() - const evaluatedRules = useEvaluation(rules) + const engine = useEngine() + const evaluatedRules = rules.map(rule => evaluateRule(engine, rule)) const language = useTranslation().i18n.language return ( {evaluatedRules - .filter(rule => rule.isApplicable !== false) + .filter(rule => rule.nodeValue !== false) .map(rule => ( dispatch( // TODO: Skiping a question shouldn't be equivalent to answering the // default value (for instance the question shouldn't appear in the // answered questions). validateStepWithValue( - currentQuestion, - rules[currentQuestion].defaultValue + currentQuestion, //TODO + undefined ) ) const goToPrevious = () => @@ -77,7 +78,7 @@ export default function Conversation({ customEndMessages }: ConversationProps) {

    - {rules[currentQuestion].question}{' '} + {rules[currentQuestion].rawNode.question}{' '}

    @@ -87,7 +88,6 @@ export default function Conversation({ customEndMessages }: ConversationProps) { value={situation[currentQuestion]} onChange={onChange} onSubmit={submit} - rules={rules} />
    diff --git a/mon-entreprise/source/components/conversation/DateInput.tsx b/mon-entreprise/source/components/conversation/DateInput.tsx index 15deeeb7d..deac43ba6 100644 --- a/mon-entreprise/source/components/conversation/DateInput.tsx +++ b/mon-entreprise/source/components/conversation/DateInput.tsx @@ -1,5 +1,5 @@ import { RuleInputProps } from 'Components/conversation/RuleInput' -import { Rule } from 'publicodes' +import { EvaluatedRule } from 'publicodes' import { useCallback, useMemo } from 'react' import styled from 'styled-components' import InputSuggestions from './InputSuggestions' @@ -9,7 +9,7 @@ type DateInputProps = { id: RuleInputProps['id'] onSubmit: RuleInputProps['onSubmit'] value: RuleInputProps['value'] - suggestions: Rule['suggestions'] + suggestions: EvaluatedRule['suggestions'] } export default function DateInput({ diff --git a/mon-entreprise/source/components/conversation/Explicable.tsx b/mon-entreprise/source/components/conversation/Explicable.tsx index a73452d6a..fe8a7a1dc 100644 --- a/mon-entreprise/source/components/conversation/Explicable.tsx +++ b/mon-entreprise/source/components/conversation/Explicable.tsx @@ -18,7 +18,7 @@ export function ExplicableRule({ dottedName }: { dottedName: DottedName }) { if (dottedName == null) return null const rule = rules[dottedName] - if (rule.description == null) return null + if (rule.rawNode.description == null) return null //TODO montrer les variables de type 'une possibilité' diff --git a/mon-entreprise/source/components/conversation/Input.tsx b/mon-entreprise/source/components/conversation/Input.tsx index 8ec6002c2..71276df44 100644 --- a/mon-entreprise/source/components/conversation/Input.tsx +++ b/mon-entreprise/source/components/conversation/Input.tsx @@ -1,7 +1,9 @@ -import { formatValue, Unit } from 'publicodes' -import { useCallback, useState } from 'react' +import { formatValue } from 'publicodes' +import { Unit } from 'publicodes/dist/types/AST/types' +import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import NumberFormat from 'react-number-format' +import { serialize } from 'storage/serializeSimulation' import { currencyFormat, debounce } from '../../utils' import InputSuggestions from './InputSuggestions' import { InputCommonProps } from './RuleInput' @@ -13,14 +15,19 @@ export default function Input({ onSubmit, id, value, - defaultValue, - autoFocus, - unit -}: InputCommonProps & { unit?: Unit; onSubmit: (source: string) => void }) { + missing, + unit, + autoFocus +}: InputCommonProps & { + onSubmit: (source: string) => void + unit: Unit | undefined +}) { const debouncedOnChange = useCallback(debounce(550, onChange), []) const { language } = useTranslation().i18n + const unité = formatValue({ nodeValue: value ?? 0, unit }, { language }) + .replace(/[\d,.]/g, '') + .trim() const { thousandSeparator, decimalSeparator } = currencyFormat(language) - // const [currentValue, setCurrentValue] = useState(value) return (
    @@ -31,13 +38,12 @@ export default function Input({ onChange(value) }} onSecondClick={() => onSubmit?.('suggestion')} - unit={unit} /> { if (floatValue !== value) { - debouncedOnChange(floatValue) + debouncedOnChange({ valeur: floatValue, unité }) } }} - value={value} + value={!missing && value} autoComplete="off" /> - - {formatValue({ nodeValue: value ?? 0, unit }, { language }).replace( - /[\d,.]*/g, - '' - )} - +  {unité}
    ) diff --git a/mon-entreprise/source/components/conversation/InputSuggestions.tsx b/mon-entreprise/source/components/conversation/InputSuggestions.tsx index 8e1e51ffa..fcf5873e9 100644 --- a/mon-entreprise/source/components/conversation/InputSuggestions.tsx +++ b/mon-entreprise/source/components/conversation/InputSuggestions.tsx @@ -1,23 +1,20 @@ -import { serializeValue } from 'publicodes' +import { ASTNode } from 'publicodes' import { toPairs } from 'ramda' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { Unit } from 'publicodes' type InputSuggestionsProps = { - suggestions?: Record - onFirstClick: (val: string) => void - onSecondClick?: (val: string) => void - unit?: Unit + suggestions?: Record + onFirstClick: (val: ASTNode) => void + onSecondClick?: (val: ASTNode) => void } export default function InputSuggestions({ suggestions = {}, onSecondClick = x => x, - onFirstClick, - unit + onFirstClick }: InputSuggestionsProps) { - const [suggestion, setSuggestion] = useState() + const [suggestion, setSuggestion] = useState() const { t, i18n } = useTranslation() return ( @@ -30,18 +27,11 @@ export default function InputSuggestions({ margin-bottom: 0.4rem; `} > - {toPairs(suggestions).map(([text, value]: [string, number]) => { - const valueWithUnit: string = serializeValue( - { - nodeValue: value, - unit - }, - { language: i18n.language } - ) + {toPairs(suggestions).map(([text, value]: [string, ASTNode]) => { return ( -

    - - ) : ( -

    - -

    + + ) } - +const encodeRuleName = name => + name + ?.replace(/\s\.\s/g, '/') + .replace(/-/g, '\u2011') // replace with a insecable tiret to differenciate from space + .replace(/\s/g, '-') // TODO: This formating function should be in the core code. We need to think // about the different options of the formatting options and our use cases // (putting a value in the URL #1169, importing a value in the Studio, showing a value diff --git a/publicodes/source/error.ts b/publicodes/source/error.ts index a0b9419bc..6853da6fa 100644 --- a/publicodes/source/error.ts +++ b/publicodes/source/error.ts @@ -48,13 +48,14 @@ export function typeWarning( message: string, originalError?: Error ) { - console.warn( - `\n[ Erreur de type ] -➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\` -✖️ ${message} - ${originalError ? originalError.message : ''} -` - ) + // DESACTIVE EN ATTENDANT L'INFÉRENCE DE TYPE + // console.warn( + // `\n[ Erreur de type ] + // ➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\` + // ✖️ ${message} + // ${originalError ? originalError.message : ''} + // ` + // ) } export function warning( @@ -62,13 +63,13 @@ export function warning( message: string, solution?: string ) { - console.warn( - `\n[ Avertissement ] -➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\` -⚠️ ${message} -💡 ${solution ? solution : ''} -` - ) + // console.warn( + // `\n[ Avertissement ] + // ➡️ Dans la règle \`${coerceArray(rules).slice(-1)[0]}\` + // ⚠️ ${message} + // 💡 ${solution ? solution : ''} + // ` + // ) } export class InternalError extends EngineError { diff --git a/publicodes/source/evaluation.tsx b/publicodes/source/evaluation.tsx index f0f43b72a..b676e8ba5 100644 --- a/publicodes/source/evaluation.tsx +++ b/publicodes/source/evaluation.tsx @@ -14,10 +14,10 @@ import { ASTNode, ConstantNode, Evaluation, - EvaluationDecoration, + EvaluatedNode, NodeKind } from './AST/types' -import { typeWarning } from './error' +import { InternalError, typeWarning } from './error' import { convertNodeToUnit, simplifyNodeUnit } from './nodeUnits' import parse from './parse' import { @@ -32,6 +32,9 @@ import { export const makeJsx = (node: ASTNode): JSX.Element => { const Component = node.jsx + if (!Component) { + throw new InternalError(node) + } return } @@ -115,7 +118,7 @@ export const defaultNode = (nodeValue: Evaluation) => nodeValue, type: typeof nodeValue, // eslint-disable-next-line - jsx: ({ nodeValue }: ASTNode & EvaluationDecoration) => ( + jsx: ({ nodeValue }: EvaluatedNode) => ( {nodeValue} ), isDefault: true, @@ -165,7 +168,7 @@ export function evaluateObject( }, temporalExplanations) const sameUnitTemporalExplanation: Temporal = convertNodesToSameUnit( + EvaluatedNode & { nodeValue: number }> = convertNodesToSameUnit( temporalExplanation.map(x => x.value), this.cache._meta.contextRule, node.nodeKind @@ -189,7 +192,7 @@ export function evaluateObject( if (sameUnitTemporalExplanation.length === 1) { return { ...baseEvaluation, - explanation: (sameUnitTemporalExplanation[0] as any).value + explanation: (sameUnitTemporalExplanation[0] as any).value.explanation } } return { diff --git a/publicodes/source/grammar.ne b/publicodes/source/grammar.ne index 26448e435..e585fe9aa 100644 --- a/publicodes/source/grammar.ne +++ b/publicodes/source/grammar.ne @@ -18,7 +18,7 @@ const letter = '[a-zA-Z\u00C0-\u017F€$%]'; const letterOrNumber = '[a-zA-Z\u00C0-\u017F0-9\'°]'; const word = `${letter}(?:[-']?${letterOrNumber}+)*`; const wordOrNumber = `(?:${word}|${letterOrNumber}+)` -const words = `${word}(?:[\\s]?${wordOrNumber}+)*` +const words = `${word}(?:[,\\s]?${wordOrNumber}+)*` const periodWord = `\\| ${word}(?:[\\s]${word})*` const numberRegExp = '-?(?:[1-9][0-9]+|[0-9])(?:\\.[0-9]+)?'; diff --git a/publicodes/source/index.ts b/publicodes/source/index.ts index 0dcf0f078..505c9e6e7 100644 --- a/publicodes/source/index.ts +++ b/publicodes/source/index.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/ban-types */ import { map } from 'ramda' -import { ASTNode, EvaluationDecoration, NodeKind } from './AST/types' +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 { Rule, RuleNode } from './rule' @@ -30,18 +31,21 @@ export type EvaluationOptions = Partial<{ unit: string }> -// export { default as cyclesLib } from './AST/index' +export * as cyclesLib from './AST/graph' +export { reduceAST, updateAST } from './AST' export * from './components' -export { formatValue, serializeValue } from './format' +export { formatValue } from './format' export { default as translateRules } from './translateRules' +export { ASTNode, EvaluatedNode } export { parsePublicodes } export { utils } +export { Rule } export type evaluationFunction = ( this: Engine, node: ASTNode & { nodeKind: Kind } -) => ASTNode & { nodeKind: Kind } & EvaluationDecoration -type ParsedRules = Record< +) => ASTNode & { nodeKind: Kind } & EvaluatedNode +export type ParsedRules = Record< Name, RuleNode & { dottedName: Name } > @@ -51,7 +55,7 @@ export default class Engine { cache: Cache private warnings: Array = [] - constructor(rules: string | Record | Record) { + constructor(rules: string | Record | ParsedRules) { this.cache = emptyCache() this.resetCache() if (typeof rules === 'string') { @@ -69,6 +73,7 @@ export default class Engine { this.parsedRules = parsePublicodes( rules as Record ) as ParsedRules + } private resetCache() { @@ -76,24 +81,25 @@ export default class Engine { } setSituation( - situation: Partial> = {} + situation: Partial> = {} ) { this.resetCache() this.parsedSituation = map(value => { + if (value && typeof value === 'object' && 'nodeKind' in value) { + return value as ASTNode + } return disambiguateReference(this.parsedRules)( parse(value, { - dottedName: "'''situation", + dottedName: "situation'''", parsedRules: {} }) ) }, situation) + return this } - evaluate( - expression: Name - ): RuleNode & EvaluationDecoration & { dottedName: Name } - evaluate(expression: string): ASTNode & EvaluationDecoration { + evaluate(expression: string | Object): EvaluatedNode { /* TODO EN ATTENDANT d'AVOIR une meilleure gestion d'erreur, on va mocker console.warn @@ -104,14 +110,11 @@ export default class Engine { this.warnings.push(warning) originalWarn(warning) } - if (this.parsedRules[expression]) { - // TODO : No replacement here. Is this what we want ? - return this.evaluateNode(this.parsedRules[expression]) - } const result = this.evaluateNode( + // TODO : No replacement here. Is this what we want ? disambiguateReference(this.parsedRules)( parse(expression, { - dottedName: "'''evaluation", + dottedName: "evaluation'''", parsedRules: {} }) ) @@ -128,11 +131,11 @@ export default class Engine { return !!this.cache._meta.inversionFail } - getParsedRules(): Record { + getParsedRules(): ParsedRules { return this.parsedRules } - evaluateNode(node: N): N & EvaluationDecoration { + evaluateNode(node: N): N & EvaluatedNode { if (!node.nodeKind) { throw Error('The provided node must have a "nodeKind" attribute') } else if (!evaluationFunctions[node.nodeKind]) { @@ -142,3 +145,31 @@ export default class Engine { return evaluationFunctions[node.nodeKind].call(this, node) } } + +// This function is an util for allowing smother migration to the new Engine API +export function evaluateRule( + engine: Engine, + dottedName: DottedName, + modifiers: Object = {} +): EvaluatedRule { + const evaluation = simplifyNodeUnit( + engine.evaluate({ valeur: dottedName, ...modifiers }) + ) + const rule = engine.getParsedRules()[dottedName] as RuleNode & { dottedName: DottedName } + return { + ...rule.rawNode, + ...rule, + ...evaluation + } as EvaluatedRule +} + +export type EvaluatedRule = EvaluatedNode & + Omit< + (ASTNode & { + nodeKind: 'rule' + }) & + (ASTNode & { + nodeKind: 'rule' + })['rawNode'] & { dottedName: Name }, + 'nodeKind' + > diff --git a/publicodes/source/mecanisms/applicable.tsx b/publicodes/source/mecanisms/applicable.tsx index ac346c916..1c7901c3c 100644 --- a/publicodes/source/mecanisms/applicable.tsx +++ b/publicodes/source/mecanisms/applicable.tsx @@ -1,7 +1,7 @@ import React from 'react' import { evaluationFunction } from '..' import parse from '../parse' -import { InfixMecanism } from '../components/mecanisms/common' +import { InfixMecanism, Mecanism } from '../components/mecanisms/common' import { bonus, makeJsx, mergeMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import { ASTNode } from '../AST/types' @@ -18,10 +18,10 @@ export type ApplicableSiNode = { function MecanismApplicable({ explanation }) { return ( -

    - Applicable si : + {makeJsx(explanation.condition)} -

    + +
    ) } diff --git a/publicodes/source/mecanisms/inversion.ts b/publicodes/source/mecanisms/inversion.ts index 7e5388594..f191f7bff 100644 --- a/publicodes/source/mecanisms/inversion.ts +++ b/publicodes/source/mecanisms/inversion.ts @@ -64,12 +64,12 @@ export const evaluateInversion: evaluationFunction<'inversion'> = function( } this.parsedSituation[node.explanation.ruleToInverse] = { unit: unit, - jsx: null, + jsx: () => n, nodeKind: 'unité', explanation: { nodeKind: 'constant', nodeValue: n, - jsx: null, + jsx: () => n, type: 'number' } as ConstantNode } as UnitéNode diff --git a/publicodes/source/mecanisms/nonApplicable.tsx b/publicodes/source/mecanisms/nonApplicable.tsx index 392808a6c..fc8784110 100644 --- a/publicodes/source/mecanisms/nonApplicable.tsx +++ b/publicodes/source/mecanisms/nonApplicable.tsx @@ -1,6 +1,6 @@ import React from 'react' import { evaluationFunction } from '..' -import { InfixMecanism } from '../components/mecanisms/common' +import { InfixMecanism, Mecanism } from '../components/mecanisms/common' import { ASTNode } from '../AST/types' import { bonus, makeJsx, mergeMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' @@ -16,10 +16,13 @@ export type NonApplicableSiNode = { function MecanismNonApplicable({ explanation }) { return ( -

    - Non applicable si : - {makeJsx(explanation.applicable)} -

    + + {makeJsx(explanation.condition)} + +
    ) } diff --git a/publicodes/source/mecanisms/one-possibility.tsx b/publicodes/source/mecanisms/one-possibility.tsx index 5227eb23a..6e28628c1 100644 --- a/publicodes/source/mecanisms/one-possibility.tsx +++ b/publicodes/source/mecanisms/one-possibility.tsx @@ -1,4 +1,6 @@ import { ASTNode } from '../AST/types' +import { Mecanism } from '../components/mecanisms/common' +import { makeJsx } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import parse from '../parse' import { Context } from '../parsePublicodes' @@ -21,12 +23,20 @@ export const mecanismOnePossibility = (v, context: Context) => { ...v, explanation: v.possibilités.map(p => parse(p, context)), nodeKind: 'une possibilité', + jsx: (node: PossibilityNode) => ( + +
      + {node.explanation.map(node => ( +
    • {makeJsx(node)}
    • + ))} +
    +
    + ), context: context.dottedName } as PossibilityNode } registerEvaluationFunction<'une possibilité'>('une possibilité', node => ({ ...node, nodeValue: null, - jsx: null, missingVariables: { [node.context]: 1 } })) diff --git a/publicodes/source/mecanisms/operation.tsx b/publicodes/source/mecanisms/operation.tsx index dc0583f5d..edcc05229 100644 --- a/publicodes/source/mecanisms/operation.tsx +++ b/publicodes/source/mecanisms/operation.tsx @@ -10,7 +10,7 @@ import { registerEvaluationFunction } from '../evaluationFunctions' import { convertNodeToUnit } from '../nodeUnits' import parse from '../parse' import { liftTemporal2, pureTemporal, temporalAverage } from '../temporal' -import { EvaluationDecoration } from '../AST/types' +import { EvaluatedNode } from '../AST/types' import { inferUnit, serializeUnit } from '../units' const knownOperations = { @@ -57,8 +57,8 @@ const parseOperation = (k, symbol) => (v, context) => { const evaluate: evaluationFunction<'operation'> = function(node) { const explanation = node.explanation.map(node => this.evaluateNode(node)) as [ - ASTNode & EvaluationDecoration, - ASTNode & EvaluationDecoration + EvaluatedNode, + EvaluatedNode ] let [node1, node2] = explanation const missingVariables = mergeAllMissing([node1, node2]) diff --git a/publicodes/source/mecanisms/parDéfaut.tsx b/publicodes/source/mecanisms/parDéfaut.tsx index d965f66be..971e87ef4 100644 --- a/publicodes/source/mecanisms/parDéfaut.tsx +++ b/publicodes/source/mecanisms/parDéfaut.tsx @@ -5,7 +5,7 @@ import { ASTNode } from '../AST/types' import { bonus, makeJsx, mergeMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import parse from '../parse' -import { EvaluationDecoration } from '../AST/types' +import { EvaluatedNode } from '../AST/types' export type ParDéfautNode = { explanation: { @@ -17,7 +17,10 @@ export type ParDéfautNode = { } function ParDéfautComponent({ explanation }) { return ( - +

    Par défaut : {makeJsx(explanation.parDéfaut)} @@ -40,7 +43,7 @@ const evaluate: evaluationFunction<'par défaut'> = function(node) { nodeValue: valeur.nodeValue, explanation, missingVariables: mergeMissing( - (explanation.valeur as EvaluationDecoration).missingVariables, + (explanation.valeur as EvaluatedNode).missingVariables, 'missingVariables' in explanation.parDéfaut ? bonus(explanation.parDéfaut.missingVariables) : {} diff --git a/publicodes/source/mecanisms/plafond.tsx b/publicodes/source/mecanisms/plafond.tsx index 89d68e129..7fbed8e82 100644 --- a/publicodes/source/mecanisms/plafond.tsx +++ b/publicodes/source/mecanisms/plafond.tsx @@ -8,7 +8,7 @@ import { makeJsx, mergeAllMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import { convertNodeToUnit } from '../nodeUnits' import { ASTNode } from '../AST/types' -import { EvaluationDecoration } from '../AST/types' +import { EvaluatedNode } from '../AST/types' function MecanismPlafond({ explanation }) { return ( @@ -43,10 +43,7 @@ const evaluate: evaluationFunction<'plafond'> = function(node) { plafond = this.evaluateNode(plafond) if (valeur.unit) { try { - plafond = convertNodeToUnit( - valeur.unit, - plafond as ASTNode & EvaluationDecoration - ) + plafond = convertNodeToUnit(valeur.unit, plafond as EvaluatedNode) } catch (e) { typeWarning( this.cache._meta.contextRule, diff --git a/publicodes/source/mecanisms/plancher.tsx b/publicodes/source/mecanisms/plancher.tsx index 991b6c75a..37ed1d922 100644 --- a/publicodes/source/mecanisms/plancher.tsx +++ b/publicodes/source/mecanisms/plancher.tsx @@ -7,7 +7,7 @@ import { makeJsx, mergeAllMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import { convertNodeToUnit } from '../nodeUnits' import parse from '../parse' -import { EvaluationDecoration } from '../AST/types' +import { EvaluatedNode } from '../AST/types' function MecanismPlancher({ explanation }) { return ( @@ -41,10 +41,7 @@ const evaluate: evaluationFunction<'plancher'> = function(node) { plancher = this.evaluateNode(plancher) if (valeur.unit) { try { - plancher = convertNodeToUnit( - valeur.unit, - plancher as ASTNode & EvaluationDecoration - ) + plancher = convertNodeToUnit(valeur.unit, plancher as EvaluatedNode) } catch (e) { typeWarning( this.cache._meta.contextRule, diff --git a/publicodes/source/mecanisms/recalcul.ts b/publicodes/source/mecanisms/recalcul.ts index df2d99924..c775fecab 100644 --- a/publicodes/source/mecanisms/recalcul.ts +++ b/publicodes/source/mecanisms/recalcul.ts @@ -6,7 +6,7 @@ import { registerEvaluationFunction } from '../evaluationFunctions' import parse from '../parse' import { ReferenceNode } from '../reference' import { disambiguateRuleReference } from '../ruleUtils' -import { EvaluationDecoration } from '../AST/types' +import { EvaluatedNode } from '../AST/types' import { serializeUnit } from '../units' export type RecalculNode = { @@ -20,7 +20,7 @@ export type RecalculNode = { const evaluateRecalcul: evaluationFunction<'recalcul'> = function(node) { if (this.cache._meta.inRecalcul) { - return (defaultNode(false) as any) as RecalculNode & EvaluationDecoration + return (defaultNode(false) as any) as RecalculNode & EvaluatedNode } const amendedSituation = node.explanation.amendedSituation @@ -32,9 +32,7 @@ const evaluateRecalcul: evaluationFunction<'recalcul'> = function(node) { ([originRule, replacement]) => originRule.nodeValue !== replacement.nodeValue || serializeUnit(originRule.unit) !== serializeUnit(replacement.unit) - ) as Array< - [ReferenceNode & EvaluationDecoration, ASTNode & EvaluationDecoration] - > + ) as Array<[ReferenceNode & EvaluatedNode, EvaluatedNode]> const originalCache = { ...this.cache } const originalSituation = { ...this.parsedSituation } diff --git a/publicodes/source/mecanisms/situation.tsx b/publicodes/source/mecanisms/situation.tsx index 70a6aebac..74857e86d 100644 --- a/publicodes/source/mecanisms/situation.tsx +++ b/publicodes/source/mecanisms/situation.tsx @@ -1,12 +1,21 @@ import { isEmpty } from 'ramda' -import { ASTNode, EvaluationDecoration } from '../AST/types' +import { ASTNode, EvaluatedNode } from '../AST/types' +import { InfixMecanism } from '../components/mecanisms/common' import { makeJsx, mergeAllMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import parse from '../parse' -function MecanismSituation({ explanation, nodeValue, unit }) { - // TODO : vue différente selon si valeur depuis la situation ou calculée - return makeJsx({ ...explanation.valeur, nodeValue, unit }) +function MecanismSituation({ explanation }) { + return explanation.situationValeur ? ( + +

    + Valeur renseignée dans la simulation : + {makeJsx(explanation.situationValeur)} +

    +
    + ) : ( + makeJsx(explanation.valeur) + ) } export type SituationNode = { @@ -36,7 +45,7 @@ parseSituation.nom = 'nom dans la situation' as const registerEvaluationFunction(parseSituation.nom, function evaluate(node) { const explanation = { ...node.explanation } const situationKey = explanation.situationKey - let valeur: ASTNode & EvaluationDecoration + let valeur: EvaluatedNode if (situationKey in this.parsedSituation) { valeur = this.evaluateNode(this.parsedSituation[situationKey]) explanation.situationValeur = valeur diff --git a/publicodes/source/mecanisms/sum.tsx b/publicodes/source/mecanisms/sum.tsx index 731b505cf..642df2553 100644 --- a/publicodes/source/mecanisms/sum.tsx +++ b/publicodes/source/mecanisms/sum.tsx @@ -4,10 +4,7 @@ import { evaluateArray } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import parse from '../parse' -const evaluate = evaluateArray<'somme'>( - (x: any, y: any) => (x === false && y === false ? false : x + y), - false -) +const evaluate = evaluateArray<'somme'>((x: any, y: any) => x + y, 0) export type SommeNode = { explanation: Array diff --git a/publicodes/source/mecanisms/trancheUtils.ts b/publicodes/source/mecanisms/trancheUtils.ts index af4bb78eb..947c70c97 100644 --- a/publicodes/source/mecanisms/trancheUtils.ts +++ b/publicodes/source/mecanisms/trancheUtils.ts @@ -6,10 +6,8 @@ import parse from '../parse' import { convertUnit, inferUnit } from '../units' type TrancheNode = { taux: ASTNode } | { montant: ASTNode } -export type TrancheNodes = [ - ...Array, - TrancheNode & { plafond?: ASTNode } -] +export type TrancheNodes = Array + export const parseTranches = (tranches, context): TrancheNodes => { return tranches .map((t, i) => { diff --git a/publicodes/source/mecanisms/unité.tsx b/publicodes/source/mecanisms/unité.tsx index e8d838665..945e2cd81 100644 --- a/publicodes/source/mecanisms/unité.tsx +++ b/publicodes/source/mecanisms/unité.tsx @@ -1,9 +1,11 @@ import { ASTNode, Unit } from '../AST/types' +import { InfixMecanism } from '../components/mecanisms/common' import { typeWarning } from '../error' import { makeJsx } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' +import { formatValue } from '../format' import parse from '../parse' -import { convertUnit, parseUnit } from '../units' +import { convertUnit, parseUnit, serializeUnit } from '../units' export type UnitéNode = { unit: Unit @@ -11,8 +13,22 @@ export type UnitéNode = { jsx: any nodeKind: 'unité' } -function MecanismUnité({ explanation, nodeValue, unit }) { - return makeJsx({ ...explanation, nodeValue, unit }) +function MecanismUnité(node) { + return node.explanation.nodeKind === 'constant' || + node.explanation.nodeKind === 'reference' ? ( + <> + {makeJsx(node.explanation)} {serializeUnit(node.unit)} + + ) : ( + <> + +

    + Unité : + {serializeUnit(node.unit)} +

    +
    + + ) } export default function parseUnité(v, context): UnitéNode { diff --git a/publicodes/source/mecanisms/variableTemporelle.ts b/publicodes/source/mecanisms/variableTemporelle.ts index 7c12a2a90..7f796ceb2 100644 --- a/publicodes/source/mecanisms/variableTemporelle.ts +++ b/publicodes/source/mecanisms/variableTemporelle.ts @@ -59,7 +59,7 @@ export default function parseVariableTemporelle( const explanation = parse(v.explanation, context) return { nodeKind: 'variable temporelle', - jsx: null, + jsx: () => 'variable temporelle', explanation: { period: { start: v.period.start && parse(v.period.start, context), diff --git a/publicodes/source/mecanisms/variations.ts b/publicodes/source/mecanisms/variations.ts index e52d7f2c4..d9946c773 100644 --- a/publicodes/source/mecanisms/variations.ts +++ b/publicodes/source/mecanisms/variations.ts @@ -20,9 +20,10 @@ export type VariationNode = { explanation: Array<{ condition: ASTNode consequence: ASTNode + satisfied?: boolean }> nodeKind: 'variations' - jsx: any + jsx: Function } export const devariate = (k, v, context): ASTNode => { @@ -51,14 +52,13 @@ export const devariate = (k, v, context): ASTNode => { return explanation } -export default function parseVariations(v, context) { +export default function parseVariations(v, context): VariationNode { const explanation = v.map(({ si, alors, sinon }) => sinon !== undefined ? { consequence: parse(sinon, context), condition: defaultNode(true) } : { consequence: parse(alors, context), condition: parse(si, context) } ) - // TODO - find an appropriate representation return { explanation, jsx: Variations, @@ -163,15 +163,14 @@ const evaluate: evaluationFunction<'variations'> = function(node) { [] ) ) - - return simplifyNodeUnit({ + return { ...node, nodeValue, ...(unit !== undefined && { unit }), explanation, missingVariables, ...(temporalValue.length > 1 && { temporalValue }) - }) + } } registerEvaluationFunction('variations', evaluate) diff --git a/publicodes/source/nodeUnits.ts b/publicodes/source/nodeUnits.ts index ac5acb1e4..81a69161d 100644 --- a/publicodes/source/nodeUnits.ts +++ b/publicodes/source/nodeUnits.ts @@ -1,6 +1,6 @@ import { mapTemporal } from './temporal' -import { convertUnit, simplifyUnit } from './units' -import { ASTNode, EvaluationDecoration, Unit } from './AST/types' +import { convertUnit, serializeUnit, simplifyUnit } from './units' +import { ASTNode, EvaluatedNode, Unit } from './AST/types' export function simplifyNodeUnit(node) { if (!node.unit) { @@ -11,10 +11,10 @@ export function simplifyNodeUnit(node) { return convertNodeToUnit(unit, node) } -export function convertNodeToUnit( +export function convertNodeToUnit( to: Unit | undefined, - node: ASTNode & EvaluationDecoration -) { + node: Node +): Node { const temporalValue = node.temporalValue && node.unit ? mapTemporal( diff --git a/publicodes/source/parse.tsx b/publicodes/source/parse.tsx index 47b46f8d9..c4f603df6 100644 --- a/publicodes/source/parse.tsx +++ b/publicodes/source/parse.tsx @@ -59,7 +59,10 @@ Utilisez leur contrepartie française : 'oui' / 'non'` return parseRule(node, context) } - return parseChainedMecanisms(node, context) + return { + ...parseChainedMecanisms(node, context), + rawNode + } } const compiledGrammar = Grammar.fromCompiled(grammar) @@ -106,7 +109,7 @@ Cela vient probablement d'une erreur dans l'indentation ) } if (isEmpty(rawNode)) { - return { nodeKind: 'constant', nodeValue: null } + return { nodeKind: 'constant', nodeValue: null, jsx: () => null } } const mecanismName = Object.keys(rawNode)[0] @@ -146,11 +149,11 @@ const chainableMecanisms = [ applicable, nonApplicable, parDéfaut, - situation, + arrondi, + unité, plancher, plafond, - unité, - arrondi + situation ] function parseChainedMecanisms(rawNode, context: Context): ASTNode { const parseFn = chainableMecanisms.find(fn => fn.nom in rawNode) @@ -195,7 +198,12 @@ const parseFunctions = { objet: v => ({ type: 'objet', nodeValue: v, - nodeKind: 'constant' + nodeKind: 'constant', + jsx: () => ( + +
    {JSON.stringify(v, null, 2)}
    +
    + ) }), constant: v => ({ type: v.type, diff --git a/publicodes/source/parsePublicodes.ts b/publicodes/source/parsePublicodes.ts index 01a0348d1..6474a0206 100644 --- a/publicodes/source/parsePublicodes.ts +++ b/publicodes/source/parsePublicodes.ts @@ -97,13 +97,16 @@ function transpileRef(object: Record | string | Array) { export const disambiguateReference = (parsedRules: Record) => updateAST(node => { if (node.nodeKind === 'reference') { + const dottedName = disambiguateRuleReference( + parsedRules, + node.contextDottedName, + node.name + ) return { ...node, - dottedName: disambiguateRuleReference( - parsedRules, - node.contextDottedName, - node.name - ) + dottedName, + title: parsedRules[dottedName].title, + acronym: parsedRules[dottedName].rawNode.acronyme } } }) diff --git a/publicodes/source/reference.ts b/publicodes/source/reference.ts index 65a316352..7e1418522 100644 --- a/publicodes/source/reference.ts +++ b/publicodes/source/reference.ts @@ -1,4 +1,4 @@ -import { EvaluationDecoration } from './AST/types' +import { EvaluatedNode } from './AST/types' import { Leaf } from './components/mecanisms/common' import { InternalError } from './error' import { registerEvaluationFunction } from './evaluationFunctions' @@ -8,7 +8,7 @@ import { RuleNode } from './rule' export type ReferenceNode = { nodeKind: 'reference' name: string - explanation?: RuleNode & EvaluationDecoration + explanation?: RuleNode & EvaluatedNode contextDottedName: string dottedName?: string jsx: any diff --git a/publicodes/source/replacement.ts b/publicodes/source/replacement.tsx similarity index 64% rename from publicodes/source/replacement.ts rename to publicodes/source/replacement.tsx index ab7d1afa0..b35b8c223 100644 --- a/publicodes/source/replacement.ts +++ b/publicodes/source/replacement.tsx @@ -2,8 +2,10 @@ import { groupBy } from 'ramda' import { AST } from 'yaml' import { traverseParsedRules, updateAST } from './AST' import { ASTNode } from './AST/types' +import Variations from './components/mecanisms/Variations' import { InternalError, warning } from './error' -import { defaultNode } from './evaluation' +import { defaultNode, makeJsx } from './evaluation' +import { VariationNode } from './mecanisms/variations' import parse from './parse' import { Context } from './parsePublicodes' import { RuleNode } from './rule' @@ -17,6 +19,7 @@ export type ReplacementNode = { replacementNode: ASTNode whiteListedNames: Array jsx: any + rawNode: any blackListedNames: Array } @@ -27,27 +30,39 @@ export function parseReplacements( if (!replacements) { return [] } - return coerceArray(replacements).map(reference => { - if (typeof reference === 'string') { - reference = { règle: reference } + return coerceArray(replacements).map(replacement => { + if (typeof replacement === 'string') { + replacement = { règle: replacement } } - const replacedReference = parse(reference.règle, context) - let replacementNode = parse(reference.par ?? context.dottedName, context) + const replacedReference = parse(replacement.règle, context) + let replacementNode = parse(replacement.par ?? context.dottedName, context) const [whiteListedNames, blackListedNames] = [ - reference.dans ?? [], - reference['sauf dans'] ?? [] + replacement.dans ?? [], + replacement['sauf dans'] ?? [] ] .map(dottedName => coerceArray(dottedName)) .map(refs => refs.map(ref => parse(ref, context))) return { nodeKind: 'replacement', + rawNode: replacement, definitionRule: parse(context.dottedName, context), replacedReference, replacementNode, - jsx: null, + jsx: (node: ReplacementNode) => ( + + Remplace {makeJsx(node.replacedReference)}{' '} + {node.rawNode.par && <>par {makeJsx(node.replacementNode)}} + {node.rawNode.dans && ( + <>dans {node.whiteListedNames.map(makeJsx).join(', ')} + )} + {node.rawNode['sauf dans'] && ( + <>sauf dans {node.blackListedNames.map(makeJsx).join(', ')} + )} + + ), whiteListedNames, blackListedNames } as ReplacementNode @@ -58,10 +73,13 @@ export function parseRendNonApplicable( rules: Rule['rend non applicable'], context: Context ): Array { - return parseReplacements(rules, context).map(replacement => ({ - ...replacement, - replacementNode: defaultNode(false) - })) + return parseReplacements(rules, context).map( + replacement => + ({ + ...replacement, + replacementNode: defaultNode(false) + } as ReplacementNode) + ) } export function inlineReplacements( @@ -78,9 +96,14 @@ export function inlineReplacements( ) return traverseParsedRules( updateAST(node => { - if (node.nodeKind === 'replacement') { + 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 (TODO) + // Nor in ammended situation of recalcul and inversion (for now) return false } if (node.nodeKind === 'reference') { @@ -129,7 +152,9 @@ function replace( +!!r2.blackListedNames.length - +!!r1.blackListedNames.length return criterion1 || criterion2 }) - + if (!applicableReplacements.length) { + return node + } if (applicableReplacements.length > 1) { warning( node.contextDottedName, @@ -143,22 +168,27 @@ ${applicableReplacements.map( ` ) } - return applicableReplacements.reduceRight( - (replacedNode, replacement) => { - return { - nodeKind: 'variations', - explanation: [ - { - condition: replacement.definitionRule, - consequence: replacement.replacementNode - }, - { - condition: defaultNode(true), - consequence: replacedNode - } - ] - } as ASTNode & { nodeKind: 'variations' } - }, - node - ) + + return { + nodeKind: 'variations', + rawNode: node.rawNode, + jsx: Replacement, + explanation: [ + ...applicableReplacements.map(replacement => ({ + condition: replacement.definitionRule, + consequence: replacement.replacementNode + })), + { + condition: defaultNode(true), + consequence: node + } + ] + } +} + +function Replacement(node: VariationNode) { + const applicableReplacement = node.explanation.find(ex => ex.satisfied) + ?.consequence + const replacedNode = node.explanation.slice(-1)[0].consequence + return makeJsx(applicableReplacement || replacedNode) } diff --git a/publicodes/source/rule.ts b/publicodes/source/rule.tsx similarity index 76% rename from publicodes/source/rule.ts rename to publicodes/source/rule.tsx index 0bc140f52..a6021516e 100644 --- a/publicodes/source/rule.ts +++ b/publicodes/source/rule.tsx @@ -1,11 +1,10 @@ -import { filter, map, mapObjIndexed, pick } from 'ramda' -import { ASTNode, EvaluationDecoration } from './AST/types' -import RuleComponent from './components/rule/Rule' -import { bonus, mergeMissing } from './evaluation' +import { filter, mapObjIndexed, pick } from 'ramda' +import { ASTNode, EvaluatedNode } from './AST/types' +import { bonus, makeJsx, mergeMissing } from './evaluation' import { registerEvaluationFunction } from "./evaluationFunctions" -import parseNonApplicable from './mecanisms/nonApplicable' import parse, { mecanismKeys } from './parse' import { Context } from './parsePublicodes' +import { ReferenceNode } from './reference' import { parseRendNonApplicable, parseReplacements, ReplacementNode } from './replacement' import { nameLeaf, ruleParents } from './ruleUtils' import { capitalise0 } from './utils' @@ -21,13 +20,18 @@ export type Rule = { résumé?: string 'icônes'?: string titre?: string + cotisation?: { + branche: string + } type?: string note?: string remplace?: RendNonApplicable | Array 'rend non applicable'?: Remplace | Array suggestions?: Record références?: { [source: string]: string } + API?: string } + type Remplace = { règle: string par?: Object | string | number @@ -41,6 +45,7 @@ export type RuleNode = { title: string nodeKind: "rule" jsx: any + virtualRule: boolean, rawNode: Rule, replacements: Array explanation: { @@ -48,13 +53,12 @@ export type RuleNode = { valeur: ASTNode } suggestions: Record - dependencies: Array } export default function parseRule( rawRule: Rule, context: Context -): RuleNode { +): ReferenceNode { const dottedName = [context.dottedName, rawRule.nom] .filter(Boolean) .join(' . ') @@ -70,7 +74,10 @@ export default function parseRule( } const ruleContext = { ...context, dottedName } - const name = nameLeaf(dottedName) + let name = nameLeaf(dottedName) + if (context.dottedName) { + name = `${nameLeaf(context.dottedName)} (${name})` + } const [parent] = ruleParents(dottedName) const explanation = { valeur: parse(ruleValue, ruleContext), @@ -85,13 +92,18 @@ export default function parseRule( title: capitalise0(rawRule['titre'] || name), suggestions: mapObjIndexed(node => parse(node, ruleContext), rawRule.suggestions ?? {}), nodeKind: "rule", - jsx: RuleComponent, + jsx: node => <> + {capitalise0(node.rawNode.nom)}  + {makeJsx(node.explanation.valeur)} + , explanation, rawNode: rawRule, - dependencies: [] as Array // TODO + virtualRule: !!context.dottedName }) as RuleNode - return context.parsedRules[dottedName] + // We return the parsedReference + return parse(rawRule.nom, context) as ReferenceNode + } @@ -100,19 +112,19 @@ registerEvaluationFunction('rule', function evaluate(node) { return this.cache[node.dottedName] } const explanation = { ...node.explanation } - + this.cache._meta.contextRule.push(node.dottedName) this.cache._meta.parentEvaluationStack ??= [] - let parent: ASTNode & EvaluationDecoration | null = null + let parent: EvaluatedNode | null = null if (explanation.parent && !this.cache._meta.parentEvaluationStack.includes(node.dottedName)) { this.cache._meta.parentEvaluationStack.push(node.dottedName) - parent = this.evaluateNode(explanation.parent) as ASTNode & EvaluationDecoration + parent = this.evaluateNode(explanation.parent) as EvaluatedNode explanation.parent = parent this.cache._meta.parentEvaluationStack.pop() } - let valeur: ASTNode & EvaluationDecoration | null = null + let valeur: EvaluatedNode | null = null if (!parent || parent.nodeValue !== false) { - valeur = this.evaluateNode(explanation.valeur) as ASTNode & EvaluationDecoration + valeur = this.evaluateNode(explanation.valeur) as EvaluatedNode explanation.valeur = valeur } const evaluation = { @@ -122,7 +134,7 @@ registerEvaluationFunction('rule', function evaluate(node) { missingVariables: mergeMissing(valeur?.missingVariables, bonus(parent?.missingVariables)), ...(valeur && 'unit' in valeur && { unit: valeur.unit }), } - + this.cache._meta.contextRule.pop() this.cache[node.dottedName] = evaluation; return evaluation; }) diff --git a/publicodes/source/temporal.ts b/publicodes/source/temporal.ts index 4f8ee292a..d0edb8c8e 100644 --- a/publicodes/source/temporal.ts +++ b/publicodes/source/temporal.ts @@ -6,13 +6,7 @@ import { getRelativeDate, getYear } from './date' -import { - Unit, - Evaluation, - Types, - ASTNode, - EvaluationDecoration -} from './AST/types' +import { Unit, Evaluation, Types, ASTNode, EvaluatedNode } from './AST/types' export type Period = { start: T | null @@ -69,9 +63,7 @@ export function parsePeriod(word: string, date: Date): Period { throw new Error('Non implémenté') } -export type TemporalNode = Temporal< - ASTNode & EvaluationDecoration & { nodeValue: number } -> +export type TemporalNode = Temporal export type Temporal = Array & { value: T }> export function narrowTemporalValue( diff --git a/publicodes/source/translateRules.ts b/publicodes/source/translateRules.ts index 1c8b7df98..11314db6f 100644 --- a/publicodes/source/translateRules.ts +++ b/publicodes/source/translateRules.ts @@ -1,13 +1,13 @@ import { assoc, mapObjIndexed } from 'ramda' -import { RuleNode } from './rule' +import { Rule } from './rule' type Translation = Record type translateAttribute = ( prop: string, - rule: RuleNode, + rule: Rule, translation: Translation, lang: string -) => RuleNode +) => Rule /* Traduction */ const translateSuggestion: translateAttribute = ( @@ -41,7 +41,7 @@ export const attributesToTranslate = [ ] const translateProp = (lang: string, translation: Translation) => ( - rule: RuleNode, + rule: Rule, prop: string ) => { if (prop === 'suggestions' && rule?.suggestions) { @@ -56,8 +56,8 @@ function translateRule( lang: string, translations: { [Name in Names]: Translation }, name: Names, - rule: RuleNode -): RuleNode { + rule: Rule +): Rule { const ruleTrans = translations[name] if (!ruleTrans) { return rule @@ -71,11 +71,10 @@ function translateRule( export default function translateRules( lang: string, translations: Record, - rules: Record -): Record { + rules: Record +): Record { const translatedRules = mapObjIndexed( - (rule: RuleNode, name: string) => - translateRule(lang, translations, name, rule), + (rule: Rule, name: string) => translateRule(lang, translations, name, rule), rules ) diff --git a/publicodes/source/units.ts b/publicodes/source/units.ts index 6118824bc..8266e5b13 100644 --- a/publicodes/source/units.ts +++ b/publicodes/source/units.ts @@ -15,7 +15,7 @@ import i18n from './i18n' import { Evaluation, Unit } from './AST/types' export const parseUnit = (string: string, lng = 'fr'): Unit => { - const [a, ...b] = string.split('/'), + const [a, ...b] = string.split('/').map(u => u.trim()), result = { numerators: a .split('.') diff --git a/publicodes/test/cycles.test.js b/publicodes/test/cycles.test.js index ad491a686..d03c7a006 100644 --- a/publicodes/test/cycles.test.js +++ b/publicodes/test/cycles.test.js @@ -24,7 +24,7 @@ describe('Cyclic dependencies detectron 3000 ™', () => { formule: b + 1 ` const cycles = cyclesInDependenciesGraph(rules) - expect(cycles).to.deep.equal([['d', 'c', 'b', 'a']]) + expect(cycles).to.deep.equal([['a','b','c','d']]) }) @@ -51,6 +51,6 @@ describe('Cyclic dependencies detectron 3000 ™', () => { formule: a ` const cycles = cyclesInDependenciesGraph(rules) - expect(cycles).to.deep.equal([["a . c", "a"]]) + expect(cycles).to.deep.equal([["a", "a . c"]]) }) }) diff --git a/publicodes/tsconfig.json b/publicodes/tsconfig.json index fc57e3c5f..2d0178676 100644 --- a/publicodes/tsconfig.json +++ b/publicodes/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.json", "compilerOptions": { "outDir": "./dist/types", - "jsx": "react", "declaration": true, "emitDeclarationOnly": true }, diff --git a/tsconfig.json b/tsconfig.json index 71c646505..e333c945c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,4 +22,4 @@ "strictPropertyInitialization": true, "types": ["webpack-env"] } -} +} \ No newline at end of file