diff --git a/publicodes/core/source/AST/index.ts b/publicodes/core/source/AST/index.ts index bbbdcf15f3..b07baa71dd 100644 --- a/publicodes/core/source/AST/index.ts +++ b/publicodes/core/source/AST/index.ts @@ -174,8 +174,6 @@ export const traverseASTNode: TraverseFunction = (fn, node) => { return traverseUnitéNode(fn, node) case 'variations': return traverseVariationNode(fn, node) - case 'variable temporelle': - return traverseVariableTemporelle(fn, node) case 'replacementRule': return traverseReplacementNode(fn, node) default: @@ -382,17 +380,3 @@ const traverseVariationNode: TraverseFunction<'variations'> = (fn, node) => ({ consequence: fn(consequence), })), }) - -const traverseVariableTemporelle: TraverseFunction<'variable temporelle'> = ( - fn, - node -) => ({ - ...node, - explanation: { - period: { - end: node.explanation.period.end && fn(node.explanation.period.end), - start: node.explanation.period.start && fn(node.explanation.period.start), - }, - value: fn(node.explanation.value), - }, -}) diff --git a/publicodes/core/source/AST/types.ts b/publicodes/core/source/AST/types.ts index 66886e52ae..516a2fae79 100644 --- a/publicodes/core/source/AST/types.ts +++ b/publicodes/core/source/AST/types.ts @@ -23,12 +23,10 @@ import { SommeNode } from '../mecanisms/sum' import { SynchronisationNode } from '../mecanisms/synchronisation' import { TauxProgressifNode } from '../mecanisms/tauxProgressif' import { UnitéNode } from '../mecanisms/unité' -import { VariableTemporelleNode } from '../mecanisms/variableTemporelle' import { VariationNode } from '../mecanisms/variations' import { ReferenceNode } from '../reference' import { ReplacementRule } from '../replacement' import { RuleNode } from '../rule' -import { Temporal } from '../temporal' export type ConstantNode = { type: 'boolean' | 'objet' | 'number' | 'string' @@ -64,7 +62,6 @@ export type ASTNode = ( | SynchronisationNode | TauxProgressifNode | UnitéNode - | VariableTemporelleNode | VariationNode | ConstantNode | ReplacementRule @@ -109,12 +106,10 @@ export type Unit = { } // Idée : une évaluation est un n-uple : (value, unit, missingVariable, isApplicable) -// Une temporalEvaluation est une liste d'evaluation sur chaque période. : [(Evaluation, Period)] type EvaluationDecoration = { nodeValue: Evaluation missingVariables: Record unit?: Unit - temporalValue?: Temporal } export type Types = number | boolean | string | Record export type Evaluation = T | false | null diff --git a/publicodes/core/source/evaluation.ts b/publicodes/core/source/evaluation.ts index 447b67c426..ebefa3bd2c 100644 --- a/publicodes/core/source/evaluation.ts +++ b/publicodes/core/source/evaluation.ts @@ -7,17 +7,8 @@ import { NodeKind, } from './AST/types' import { warning } from './error' -import { convertNodeToUnit, simplifyNodeUnit } from './nodeUnits' +import { convertNodeToUnit } from './nodeUnits' import parse from './parse' -import { - concatTemporals, - liftTemporalNode, - mapTemporal, - pureTemporal, - Temporal, - temporalAverage, - zipTemporals, -} from './temporal' export const collectNodeMissing = ( node: EvaluatedNode | ASTNode @@ -75,37 +66,17 @@ export const evaluateArray: ( node.explanation.map(evaluate), node.name ) + const values = evaluatedNodes.map(({ nodeValue }) => nodeValue) + const nodeValue = values.some((value) => value === null) + ? null + : values.reduce(reducer, start) - const temporalValues = concatTemporals( - evaluatedNodes.map( - ({ temporalValue, nodeValue }) => - temporalValue ?? pureTemporal(nodeValue) - ) - ) - const temporalValue = mapTemporal((values) => { - if (values.some((value) => value === null)) { - return null - } - return values.reduce(reducer, start) - }, temporalValues) - - const baseEvaluation = { + return { ...node, missingVariables: mergeAllMissing(evaluatedNodes), explanation: evaluatedNodes, ...(evaluatedNodes[0] && { unit: evaluatedNodes[0].unit }), - } - if (temporalValue.length === 1) { - return { - ...baseEvaluation, - nodeValue: temporalValue[0].value, - } - } - - return { - ...baseEvaluation, - temporalValue, - nodeValue: temporalAverage(temporalValue as any), + nodeValue, } } @@ -132,71 +103,3 @@ export const parseObject = (objectShape, value, context) => { }) ) } - -export function evaluateObject( - effet: (this: Engine, explanations: any) => any -) { - return function (node) { - const evaluations = Object.fromEntries( - Object.entries((node as any).explanation).map(([key, value]) => [ - key, - this.evaluate(value as any), - ]) - ) - const temporalExplanations = mapTemporal( - Object.fromEntries, - concatTemporals( - Object.entries(evaluations).map(([key, node]) => - zipTemporals(pureTemporal(key), liftTemporalNode(node as ASTNode)) - ) - ) - ) - const temporalExplanation = mapTemporal((explanations) => { - const evaluation = effet.call(this, explanations) - return { - ...evaluation, - explanation: { - ...explanations, - ...evaluation.explanation, - }, - } - }, temporalExplanations) - - const sameUnitTemporalExplanation: Temporal< - ASTNode & EvaluatedNode & { nodeValue: number } - > = convertNodesToSameUnit - .call( - this, - temporalExplanation.map((x) => x.value), - node.nodeKind - ) - .map((node, i) => ({ - ...temporalExplanation[i], - value: simplifyNodeUnit(node), - })) - - const temporalValue = mapTemporal( - ({ nodeValue }) => nodeValue, - sameUnitTemporalExplanation - ) - const nodeValue = temporalAverage(temporalValue) - const baseEvaluation = { - ...node, - nodeValue, - unit: sameUnitTemporalExplanation[0].value.unit, - explanation: evaluations, - missingVariables: mergeAllMissing(Object.values(evaluations)), - } - if (sameUnitTemporalExplanation.length === 1) { - return { - ...baseEvaluation, - explanation: (sameUnitTemporalExplanation[0] as any).value.explanation, - } - } - return { - ...baseEvaluation, - temporalValue, - temporalExplanation, - } - } as EvaluationFunction -} diff --git a/publicodes/core/source/grammar.ne b/publicodes/core/source/grammar.ne index 93c12ff1f0..2a2207d592 100644 --- a/publicodes/core/source/grammar.ne +++ b/publicodes/core/source/grammar.ne @@ -7,8 +7,7 @@ @{% const { - string, date, variable, temporalNumericValue, binaryOperation, - unaryOperation, boolean, number, numberWithUnit, JSONObject + string, date, variable, binaryOperation, unaryOperation, boolean, number, numberWithUnit, JSONObject } = require('./grammarFunctions') const moo = require("moo"); @@ -61,11 +60,6 @@ main -> NumericValue -> AdditionSubstraction {% id %} | Negation {% id %} - | TemporalNumericValue {% id %} - -TemporalNumericValue -> - NumericValue %space %periodWord %space %date {% ([value,,word,,dateString]) => temporalNumericValue(value, word, date([dateString])) %} - | NumericValue %space %periodWord %colon Date {% ([value,,word,,date]) => temporalNumericValue(value, word, date) %} NumericTerminal -> Variable {% id %} diff --git a/publicodes/core/source/grammarFunctions.js b/publicodes/core/source/grammarFunctions.js index 70d7c87228..0433059093 100644 --- a/publicodes/core/source/grammarFunctions.js +++ b/publicodes/core/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 { parsePeriod } from './temporal' export let binaryOperation = (operationType) => ([A, , operator, , B]) => ({ [operator]: { @@ -17,13 +16,6 @@ export let unaryOperation = (operationType) => ([operator, , A]) => ({ }, }) -export let temporalNumericValue = (variable, word, date) => ({ - temporalValue: { - explanation: variable, - period: parsePeriod(word.value.slice(2), date), - }, -}) - export let variable = ([firstFragment, nextFragment], _, reject) => { const fragments = [firstFragment, ...nextFragment].map(({ value }) => value) if (!nextFragment.length && ['oui', 'non'].includes(firstFragment)) { diff --git "a/publicodes/core/source/mecanisms/bar\303\250me.ts" "b/publicodes/core/source/mecanisms/bar\303\250me.ts" index 40ba14ff85..087f92bdaa 100644 --- "a/publicodes/core/source/mecanisms/bar\303\250me.ts" +++ "b/publicodes/core/source/mecanisms/bar\303\250me.ts" @@ -3,12 +3,6 @@ import { ASTNode } from '../AST/types' import { defaultNode, mergeAllMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import parse from '../parse' -import { - liftTemporal2, - liftTemporalNode, - mapTemporal, - temporalAverage, -} from '../temporal' import { convertUnit, parseUnit } from '../units' import { evaluatePlafondUntilActiveTranche, @@ -78,44 +72,28 @@ const evaluate: EvaluationFunction<'barème'> = function (node) { const evaluate = this.evaluate.bind(this) const assiette = this.evaluate(node.explanation.assiette) const multiplicateur = this.evaluate(node.explanation.multiplicateur) - const temporalTranchesPlafond = liftTemporal2( - (assiette, multiplicateur) => - evaluatePlafondUntilActiveTranche.call(this, { - parsedTranches: node.explanation.tranches, - assiette, - multiplicateur, - }), - liftTemporalNode(assiette as any), - liftTemporalNode(multiplicateur as any) - ) - const temporalTranches = liftTemporal2( - (tranches, assiette) => evaluateBarème(tranches, assiette, evaluate), - temporalTranchesPlafond, - liftTemporalNode(assiette as any) + const tranches = evaluateBarème( + evaluatePlafondUntilActiveTranche.call(this, { + parsedTranches: node.explanation.tranches, + assiette, + multiplicateur, + }), + assiette, + evaluate ) - const temporalValue = mapTemporal( - (tranches) => - tranches.reduce( - (value, { nodeValue }) => - nodeValue == null ? null : value + nodeValue, - 0 - ), - temporalTranches + const nodeValue = tranches.reduce( + (value, { nodeValue }) => (nodeValue == null ? null : value + nodeValue), + 0 ) + return { ...node, - nodeValue: temporalAverage(temporalValue), - ...(temporalValue.length > 1 - ? { - temporalValue, - } - : { missingVariables: mergeAllMissing(temporalTranches[0].value) }), + nodeValue, + missingVariables: mergeAllMissing(tranches), explanation: { assiette, multiplicateur, - ...(temporalTranches.length > 1 - ? { temporalTranches } - : { tranches: temporalTranches[0].value }), + tranches, }, unit: assiette.unit, } as any diff --git a/publicodes/core/source/mecanisms/grille.ts b/publicodes/core/source/mecanisms/grille.ts index b7f5633df0..05763dd95e 100644 --- a/publicodes/core/source/mecanisms/grille.ts +++ b/publicodes/core/source/mecanisms/grille.ts @@ -3,12 +3,6 @@ import { ASTNode } from '../AST/types' import { defaultNode, mergeAllMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import parse from '../parse' -import { - liftTemporal2, - liftTemporalNode, - mapTemporal, - temporalAverage, -} from '../temporal' import { evaluatePlafondUntilActiveTranche, parseTranches, @@ -37,78 +31,58 @@ export default function parseGrille(v, context): GrilleNode { nodeKind: 'grille', } } -const evaluateGrille = (tranches, evaluate) => - tranches.map((tranche) => { - if (tranche.isActive === false) { - return tranche - } - const montant = evaluate(tranche.montant) - return { - ...tranche, - montant, - nodeValue: montant.nodeValue, - unit: montant.unit, - missingVariables: mergeAllMissing([montant, tranche]), - } - }) const evaluate: EvaluationFunction<'grille'> = function (node) { const evaluate = this.evaluate.bind(this) const assiette = this.evaluate(node.explanation.assiette) const multiplicateur = this.evaluate(node.explanation.multiplicateur) - const temporalTranchesPlafond = liftTemporal2( - (assiette, multiplicateur) => - evaluatePlafondUntilActiveTranche.call(this, { - parsedTranches: node.explanation.tranches, - assiette, - multiplicateur, - }), - liftTemporalNode(assiette as any), - liftTemporalNode(multiplicateur as any) - ) - const temporalTranches = mapTemporal( - (tranches) => evaluateGrille(tranches, evaluate), - temporalTranchesPlafond - ) + const tranches = evaluatePlafondUntilActiveTranche + .call(this, { + parsedTranches: node.explanation.tranches, + assiette, + multiplicateur, + }) + .map((tranche) => { + if (tranche.isActive === false) { + return tranche + } + const montant = evaluate(tranche.montant) + return { + ...tranche, + montant, + nodeValue: montant.nodeValue, + unit: montant.unit, + missingVariables: mergeAllMissing([montant, tranche]), + } + }) + + let activeTranches + const activeTranche = tranches.find((tranche) => tranche.isActive) + if (activeTranche) { + activeTranches = [activeTranche] + } else if (tranches[tranches.length - 1].isAfterActive === false) { + activeTranches = [{ nodeValue: false }] + } else { + activeTranches = tranches.filter((tranche) => tranche.isActive === null) + } - const activeTranches = mapTemporal((tranches) => { - const activeTranche = tranches.find((tranche) => tranche.isActive) - if (activeTranche) { - return [activeTranche] - } - const lastTranche = tranches[tranches.length - 1] - if (lastTranche.isAfterActive === false) { - return [{ nodeValue: false }] - } - return tranches.filter((tranche) => tranche.isActive === null) - }, temporalTranches) - const temporalValue = mapTemporal( - (tranches) => - !tranches[0] - ? false - : tranches[0].isActive === null - ? null - : tranches[0].nodeValue, - activeTranches - ) + const nodeValue = !activeTranches[0] + ? false + : activeTranches[0].isActive === null + ? null + : activeTranches[0].nodeValue return { ...node, - nodeValue: temporalAverage(temporalValue), - ...(temporalValue.length > 1 - ? { - temporalValue, - } - : { missingVariables: mergeAllMissing(activeTranches[0].value) }), + nodeValue, + missingVariables: mergeAllMissing(activeTranches), explanation: { ...node.explanation, assiette, multiplicateur, - ...(temporalTranches.length > 1 - ? { temporalTranches } - : { tranches: temporalTranches[0].value }), + tranches, }, - unit: activeTranches[0]?.value[0]?.unit ?? undefined, + unit: activeTranches[0]?.unit ?? undefined, } as any } diff --git a/publicodes/core/source/mecanisms/operation.ts b/publicodes/core/source/mecanisms/operation.ts index f093548b06..d7b2b69fd5 100644 --- a/publicodes/core/source/mecanisms/operation.ts +++ b/publicodes/core/source/mecanisms/operation.ts @@ -1,13 +1,11 @@ import { EvaluationFunction } from '..' -import { ASTNode } from '../AST/types' +import { ASTNode, EvaluatedNode } from '../AST/types' import { convertToDate } from '../date' import { warning } from '../error' import { mergeAllMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import { convertNodeToUnit } from '../nodeUnits' import parse from '../parse' -import { liftTemporal2, pureTemporal, temporalAverage } from '../temporal' -import { EvaluatedNode } from '../AST/types' import { inferUnit, serializeUnit } from '../units' const knownOperations = { @@ -74,7 +72,26 @@ const evaluate: EvaluationFunction<'operation'> = function (node) { ) } } - const baseNode = { + + const operatorFunction = knownOperations[node.operationKind][0] + + const a = node1.nodeValue as string | false + const b = node2.nodeValue as string | false + + const nodeValue = + !['≠', '='].includes(node.operator) && a === false && b === false + ? false + : ['<', '>', '≤', '≥', '∕', '×'].includes(node.operator) && + (a === false || b === false) + ? false + : a !== false && + b !== false && + ['≠', '=', '<', '>', '≤', '≥'].includes(node.operator) && + [a, b].every((value) => value.match?.(/[\d]{2}\/[\d]{2}\/[\d]{4}/)) + ? operatorFunction(convertToDate(a), convertToDate(b)) + : operatorFunction(a, b) + + return { ...node, explanation, ...((node.operationKind === '*' || @@ -84,40 +101,7 @@ const evaluate: EvaluationFunction<'operation'> = function (node) { unit: inferUnit(node.operationKind, [node1.unit, node2.unit]), }), missingVariables, - } - - const operatorFunction = knownOperations[node.operationKind][0] - - const temporalValue = liftTemporal2( - (a: string | false, b: string | false) => { - if (!['≠', '='].includes(node.operator) && a === false && b === false) { - return false - } - if ( - ['<', '>', '≤', '≥', '∕', '×'].includes(node.operator) && - (a === false || b === false) - ) { - return false - } - if ( - a !== false && - b !== false && - ['≠', '=', '<', '>', '≤', '≥'].includes(node.operator) && - [a, b].every((value) => value.match?.(/[\d]{2}\/[\d]{2}\/[\d]{4}/)) - ) { - return operatorFunction(convertToDate(a), convertToDate(b)) - } - return operatorFunction(a, b) - }, - node1.temporalValue ?? (pureTemporal(node1.nodeValue) as any), - node2.temporalValue ?? (pureTemporal(node2.nodeValue) as any) - ) - const nodeValue = temporalAverage(temporalValue, baseNode.unit) - - return { - ...baseNode, nodeValue, - ...(temporalValue.length > 1 && { temporalValue }), } } diff --git a/publicodes/core/source/mecanisms/product.ts b/publicodes/core/source/mecanisms/product.ts index 612df9b95a..2d636700ed 100644 --- a/publicodes/core/source/mecanisms/product.ts +++ b/publicodes/core/source/mecanisms/product.ts @@ -1,7 +1,7 @@ import { EvaluationFunction } from '..' import { ASTNode } from '../AST/types' import { warning } from '../error' -import { defaultNode, evaluateObject, parseObject } from '../evaluation' +import { defaultNode, mergeAllMissing, parseObject } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import { convertNodeToUnit, simplifyNodeUnit } from '../nodeUnits' import { areUnitConvertible, convertUnit, inferUnit } from '../units' @@ -32,12 +32,12 @@ export const mecanismProduct = (v, context) => { } as ProductNode } -const productEffect: EvaluationFunction = function ({ - assiette, - taux, - facteur, - plafond, -}: any) { +const evaluateProduit: EvaluationFunction<'produit'> = function (node) { + const assiette = this.evaluate(node.explanation.assiette) + const taux = this.evaluate(node.explanation.taux) + const facteur = this.evaluate(node.explanation.facteur) + let plafond = this.evaluate(node.explanation.plafond) + if (assiette.unit) { try { plafond = convertNodeToUnit(assiette.unit, plafond) @@ -72,16 +72,20 @@ const productEffect: EvaluationFunction = function ({ nodeValue = convertUnit(unit, assiette.unit, nodeValue) unit = assiette.unit } + return simplifyNodeUnit({ + ...node, + missingVariables: mergeAllMissing([assiette, taux, facteur, plafond]), nodeValue, unit, - explanation: { - plafondActif: assiette.nodeValue > plafond.nodeValue, + assiette, + taux, + facteur, + plafond, + plafondActif: (assiette.nodeValue as any) > (plafond as any).nodeValue, }, }) } -const evaluate = evaluateObject<'produit'>(productEffect) - -registerEvaluationFunction('produit', evaluate) +registerEvaluationFunction('produit', evaluateProduit) diff --git a/publicodes/core/source/mecanisms/recalcul.ts b/publicodes/core/source/mecanisms/recalcul.ts index f9eb7e1ca7..8db2f94230 100644 --- a/publicodes/core/source/mecanisms/recalcul.ts +++ b/publicodes/core/source/mecanisms/recalcul.ts @@ -62,9 +62,6 @@ const evaluateRecalcul: EvaluationFunction<'recalcul'> = function (node) { }, missingVariables: evaluatedNode.missingVariables, ...('unit' in evaluatedNode && { unit: evaluatedNode.unit }), - ...(evaluatedNode.temporalValue && { - temporalValue: evaluatedNode.temporalValue, - }), } } diff --git a/publicodes/core/source/mecanisms/variableTemporelle.ts b/publicodes/core/source/mecanisms/variableTemporelle.ts deleted file mode 100644 index f16edd8769..0000000000 --- a/publicodes/core/source/mecanisms/variableTemporelle.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { EvaluationFunction } from '..' -import { ASTNode } from '../AST/types' -import { registerEvaluationFunction } from '../evaluationFunctions' -import parse from '../parse' -import { - createTemporalEvaluation, - narrowTemporalValue, - Temporal, - temporalAverage, -} from '../temporal' - -export type VariableTemporelleNode = { - explanation: { - period: { - start: ASTNode | undefined - end: ASTNode | undefined - } - value: ASTNode - } - nodeKind: 'variable temporelle' -} - -const evaluate: EvaluationFunction<'variable temporelle'> = function ( - node: any -) { - const start = - node.explanation.period.start && - this.evaluate(node.explanation.period.start) - const end = - node.explanation.period.end && this.evaluate(node.explanation.period.end) - const value = this.evaluate(node.explanation.value) - const period = { - start: start?.nodeValue || null, - end: end?.nodeValue || null, - } - const temporalValue = value.temporalValue - ? narrowTemporalValue(period, value.temporalValue) - : createTemporalEvaluation(value.nodeValue, period) - // TODO explanation missingVariables / period missing variables - return { - ...node, - nodeValue: temporalAverage(temporalValue as Temporal, value.unit), - temporalValue, - explanation: { - period: { start, end }, - value, - }, - ...('unit' in value && { unit: value.unit }), - } -} - -export default function parseVariableTemporelle( - v, - context -): VariableTemporelleNode { - const explanation = parse(v.explanation, context) - return { - nodeKind: 'variable temporelle', - explanation: { - period: { - start: v.period.start && parse(v.period.start, context), - end: v.period.end && parse(v.period.end, context), - }, - value: explanation, - }, - } -} - -registerEvaluationFunction('variable temporelle', evaluate) diff --git a/publicodes/core/source/mecanisms/variations.ts b/publicodes/core/source/mecanisms/variations.ts index 1c8c3b9c01..845b9dd1a2 100644 --- a/publicodes/core/source/mecanisms/variations.ts +++ b/publicodes/core/source/mecanisms/variations.ts @@ -1,17 +1,10 @@ import { EvaluationFunction } from '..' -import { ASTNode, Unit } from '../AST/types' +import { ASTNode, EvaluatedNode, Unit } from '../AST/types' import { warning } from '../error' import { bonus, defaultNode, mergeAllMissing } from '../evaluation' import { registerEvaluationFunction } from '../evaluationFunctions' import { convertNodeToUnit } from '../nodeUnits' import parse from '../parse' -import { - liftTemporal2, - pureTemporal, - sometime, - Temporal, - temporalAverage, -} from '../temporal' export type VariationNode = { explanation: Array<{ @@ -62,12 +55,12 @@ export default function parseVariations(v, context): VariationNode { } const evaluate: EvaluationFunction<'variations'> = function (node) { - const [temporalValue, explanation, unit] = node.explanation.reduce< + const [nodeValue, explanation, unit] = node.explanation.reduce< [ - Temporal, + EvaluatedNode['nodeValue'], VariationNode['explanation'], Unit | undefined, - Temporal + boolean | null ] >( ( @@ -75,11 +68,7 @@ const evaluate: EvaluationFunction<'variations'> = function (node) { { condition, consequence }, i: number ) => { - const previousConditionsAlwaysTrue = !sometime( - (value) => value !== true, - previousConditions - ) - if (previousConditionsAlwaysTrue) { + if (previousConditions === true) { return [ evaluation, [...explanations, { condition, consequence }], @@ -88,24 +77,19 @@ const evaluate: EvaluationFunction<'variations'> = function (node) { ] } const evaluatedCondition = this.evaluate(condition) - const currentCondition = liftTemporal2( - (previousCond, currentCond) => - previousCond === null - ? previousCond - : !previousCond && - (currentCond === null ? null : currentCond !== false), - previousConditions, - evaluatedCondition.temporalValue ?? - pureTemporal(evaluatedCondition.nodeValue) - ) + const currentCondition = + previousConditions === null + ? previousConditions + : !previousConditions && + (evaluatedCondition.nodeValue === null + ? null + : evaluatedCondition.nodeValue !== false) + evaluatedCondition.missingVariables = bonus( evaluatedCondition.missingVariables ) - const currentConditionAlwaysFalse = !sometime( - (x) => x !== false, - currentCondition - ) - if (currentConditionAlwaysFalse) { + + if (currentCondition === false) { return [ evaluation, [...explanations, { condition: evaluatedCondition, consequence }], @@ -128,15 +112,8 @@ const evaluate: EvaluationFunction<'variations'> = function (node) { ) } } - const currentValue = liftTemporal2( - (cond, value) => cond && value, - currentCondition, - evaluatedConsequence.temporalValue ?? - pureTemporal(evaluatedConsequence.nodeValue) - ) - const or = (a, b) => a || b return [ - liftTemporal2(or, evaluation, currentValue), + currentCondition && evaluatedConsequence.nodeValue, [ ...explanations, { @@ -146,13 +123,12 @@ const evaluate: EvaluationFunction<'variations'> = function (node) { }, ], unit || evaluatedConsequence.unit, - liftTemporal2(or, previousConditions, currentCondition), + previousConditions || currentCondition, ] }, - [pureTemporal(false), [], undefined, pureTemporal(false)] + [false, [], undefined, false] ) - const nodeValue = temporalAverage(temporalValue, unit) const missingVariables = mergeAllMissing( explanation.reduce( (values, { condition, consequence }) => [ @@ -170,7 +146,6 @@ const evaluate: EvaluationFunction<'variations'> = function (node) { ...(unit !== undefined && { unit }), explanation, missingVariables, - ...(temporalValue.length > 1 && { temporalValue }), } } diff --git a/publicodes/core/source/nodeUnits.ts b/publicodes/core/source/nodeUnits.ts index 504ae21e37..fc2eeb369a 100644 --- a/publicodes/core/source/nodeUnits.ts +++ b/publicodes/core/source/nodeUnits.ts @@ -1,5 +1,4 @@ import { EvaluatedNode, Unit } from './AST/types' -import { mapTemporal } from './temporal' import { convertUnit, simplifyUnit } from './units' export function simplifyNodeUnit(node) { @@ -15,20 +14,12 @@ export function convertNodeToUnit( to: Unit | undefined, node: Node ): Node { - const temporalValue = - node.temporalValue && node.unit - ? mapTemporal( - (value) => convertUnit(node.unit, to, value as number), - node.temporalValue - ) - : node.temporalValue return { ...node, nodeValue: node.unit && typeof node.nodeValue === 'number' ? convertUnit(node.unit, to, node.nodeValue) : node.nodeValue, - ...(temporalValue && { temporalValue }), unit: to, } } diff --git a/publicodes/core/source/parse.ts b/publicodes/core/source/parse.ts index 46bbb96ba5..68a8c871bb 100644 --- a/publicodes/core/source/parse.ts +++ b/publicodes/core/source/parse.ts @@ -28,7 +28,6 @@ import { mecanismSum } from './mecanisms/sum' import { mecanismSynchronisation } from './mecanisms/synchronisation' import tauxProgressif from './mecanisms/tauxProgressif' import unité from './mecanisms/unité' -import variableTemporelle from './mecanisms/variableTemporelle' import variations, { devariate } from './mecanisms/variations' import { Context } from './parsePublicodes' import parseReference from './reference' @@ -190,7 +189,6 @@ const parseFunctions = { somme: mecanismSum, multiplication: mecanismProduct, produit: mecanismProduct, - temporalValue: variableTemporelle, barème, grille, 'taux progressif': tauxProgressif, diff --git a/publicodes/core/source/temporal.ts b/publicodes/core/source/temporal.ts deleted file mode 100644 index 14cbd2e796..0000000000 --- a/publicodes/core/source/temporal.ts +++ /dev/null @@ -1,430 +0,0 @@ -import { - convertToDate, - getDifferenceInDays, - getDifferenceInMonths, - getDifferenceInYears, - getRelativeDate, - getYear, -} from './date' -import { Unit, Evaluation, Types, ASTNode, EvaluatedNode } from './AST/types' - -export type Period = { - start: T | null - end: T | null -} - -export function parsePeriod(word: string, date: Date): Period { - const startWords = [ - 'depuis', - 'depuis le', - 'depuis la', - 'à partir de', - 'à partir du', - 'du', - ] - const endWords = [ - "jusqu'à", - "jusqu'au", - "jusqu'à la", - 'avant', - 'avant le', - 'avant la', - 'au', - ] - const intervalWords = ['le', 'en'] - if (!startWords.concat(endWords, intervalWords).includes(word)) { - throw new SyntaxError( - `Le mot clé '${word}' n'est pas valide. Les mots clés possible sont les suivants :\n\t ${startWords.join( - ', ' - )}` - ) - } - if (word === 'le') { - return { - start: date, - end: date, - } - } - if (word === 'en') { - return { start: null, end: null } - } - if (startWords.includes(word)) { - return { - start: date, - end: null, - } - } - if (endWords.includes(word)) { - return { - start: null, - end: date, - } - } - throw new Error('Non implémenté') -} - -export type TemporalNode = Temporal -export type Temporal = Array & { value: T }> - -export function narrowTemporalValue( - period: Period, - temporalValue: Temporal> -): Temporal> { - return liftTemporal2( - (value, filter) => filter && value, - temporalValue, - createTemporalEvaluation(true, period) - ) -} - -// Returns a temporal value that's true for the given period and false otherwise. -export function createTemporalEvaluation( - value: Evaluation, - period: Period = { start: null, end: null } -): Temporal> { - const temporalValue = [{ ...period, value }] - if (period.start != null) { - temporalValue.unshift({ - start: null, - end: getRelativeDate(period.start, -1), - value: false, - }) - } - if (period.end != null) { - temporalValue.push({ - start: getRelativeDate(period.end, 1), - end: null, - value: false, - }) - } - return temporalValue -} - -export function pureTemporal(value: T): Temporal { - return [{ start: null, end: null, value }] -} - -export function mapTemporal( - fn: (value: T1) => T2, - temporalValue: Temporal -): Temporal { - return temporalValue.map(({ start, end, value }) => ({ - start, - end, - value: fn(value), - })) -} -export function sometime( - fn: (value: T1) => boolean, - temporalValue: Temporal -): boolean { - return temporalValue.some(({ start, end, value }) => fn(value)) -} - -export function liftTemporal2( - fn: (value1: T1, value2: T2) => T3, - temporalValue1: Temporal, - temporalValue2: Temporal -): Temporal { - return mapTemporal( - ([a, b]) => fn(a, b), - zipTemporals(temporalValue1, temporalValue2) - ) -} - -export function concatTemporals( - temporalValues: Array> -): Temporal> { - return temporalValues.reduce( - (values, value) => liftTemporal2((a, b) => [...a, b], values, value), - pureTemporal([]) as Temporal> - ) -} - -export function liftTemporalNode( - node: N -): Temporal>> { - if (!('temporalValue' in node)) { - return pureTemporal(node) - } - const { temporalValue, ...baseNode } = node as N & { - temporalValue: Temporal> - } - return mapTemporal( - (nodeValue) => ({ - ...baseNode, - nodeValue, - }), - temporalValue - ) -} - -export function zipTemporals( - temporalValue1: Temporal, - temporalValue2: Temporal, - acc: Temporal<[T1, T2]> = [] -): Temporal<[T1, T2]> { - if (!temporalValue1.length && !temporalValue2.length) { - return acc - } - const [value1, ...rest1] = temporalValue1 - const [value2, ...rest2] = temporalValue2 - console.assert(value1.start === value2.start) - const endDateComparison = compareEndDate(value1.end, value2.end) - - // End dates are equals - if (endDateComparison === 0) { - return zipTemporals(rest1, rest2, [ - ...acc, - { ...value1, value: [value1.value, value2.value] }, - ]) - } - // Value1 lasts longuer than value1 - if (endDateComparison > 0) { - console.assert(value2.end !== null) - return zipTemporals( - [ - { ...value1, start: getRelativeDate(value2.end as string, 1) }, - ...rest1, - ], - rest2, - [ - ...acc, - { - ...value2, - value: [value1.value, value2.value], - }, - ] - ) - } - - // Value2 lasts longuer than value1 - if (endDateComparison < 0) { - console.assert(value1.end !== null) - return zipTemporals( - rest1, - [ - { ...value2, start: getRelativeDate(value1.end as string, 1) }, - ...rest2, - ], - [ - ...acc, - { - ...value1, - value: [value1.value, value2.value], - }, - ] - ) - } - throw new EvalError('All case should have been covered') -} - -function beginningOfNextYear(date: string): string { - return `01/01/${getYear(date) + 1}` -} - -function endsOfPreviousYear(date: string): string { - return `31/12/${getYear(date) - 1}` -} - -function splitStartsAt( - fn: (date: string) => string, - temporal: Temporal -): Temporal { - return temporal.reduce((acc, period) => { - const { start, end } = period - const newStart = start === null ? start : fn(start) - if (compareEndDate(newStart, end) !== -1) { - return [...acc, period] - } - console.assert(newStart !== null) - return [ - ...acc, - { ...period, end: getRelativeDate(newStart as string, -1) }, - { ...period, start: newStart }, - ] - }, [] as Temporal) -} - -function splitEndsAt( - fn: (date: string) => string, - temporal: Temporal -): Temporal { - return temporal.reduce((acc, period) => { - const { start, end } = period - const newEnd = end === null ? end : fn(end) - if (compareStartDate(start, newEnd) !== -1) { - return [...acc, period] - } - console.assert(newEnd !== null) - return [ - ...acc, - { ...period, end: newEnd }, - { ...period, start: getRelativeDate(newEnd as string, 1) }, - ] - }, [] as Temporal) -} - -export function groupByYear(temporalValue: Temporal): Array> { - return ( - // First step: split period by year if needed - splitEndsAt( - endsOfPreviousYear, - splitStartsAt(beginningOfNextYear, temporalValue) - ) - // Second step: group period by year - .reduce((acc, period) => { - const [currentTemporal, ...otherTemporal] = acc - if (currentTemporal === undefined) { - return [[period]] - } - const firstPeriod = currentTemporal[0] - console.assert( - firstPeriod !== undefined && - firstPeriod.end !== null && - period.start !== null, - 'invariant non verifié' - ) - if ( - (firstPeriod.end as string).slice(-4) !== - (period.start as string).slice(-4) - ) { - return [[period], ...acc] - } - return [[...currentTemporal, period], ...otherTemporal] - }, [] as Array>) - .reverse() - ) -} - -function simplify(temporalValue: Temporal): Temporal { - return temporalValue -} - -function compareStartDate( - dateA: string | null, - dateB: string | null -): -1 | 0 | 1 { - if (dateA == dateB) { - return 0 - } - if (dateA == null) { - return -1 - } - if (dateB == null) { - return 1 - } - return convertToDate(dateA) < convertToDate(dateB) ? -1 : 1 -} - -function compareEndDate( - dateA: string | null, - dateB: string | null -): -1 | 0 | 1 { - if (dateA == dateB) { - return 0 - } - if (dateA == null) { - return 1 - } - if (dateB == null) { - return -1 - } - return convertToDate(dateA) < convertToDate(dateB) ? -1 : 1 -} - -export function temporalAverage( - temporalValue: Temporal>, - unit?: Unit -): Evaluation { - temporalValue = temporalValue.filter(({ value }) => value !== false) - if (!temporalValue.length) { - return false - } - if (temporalValue.length === 1) { - return temporalValue[0].value - } - - if (temporalValue.some(({ value }) => value == null)) { - return null - } - - const temporalNumber = temporalValue as Temporal - const first = temporalNumber[0] - const last = temporalNumber[temporalNumber.length - 1] - - // La variable est définie sur un interval infini - if (first.start == null || last.end == null) { - if (first.start != null) { - return last.value - } - if (last.end != null) { - return first.value - } - return (first.value + last.value) / 2 - } - - let totalWeight = 0 - const weights = temporalNumber.map(({ start, end, value }) => { - ;[start, end] = [start, end] as [string, string] - let weight = 0 - if (unit?.denominators.includes('mois')) { - weight = getDifferenceInMonths(start, end) - } else if (unit?.denominators.includes('année')) { - weight = getDifferenceInYears(start, end) - } else { - weight = getDifferenceInDays(start, end) - } - totalWeight += weight - return value * weight - }) - return weights.reduce( - (average, weightedValue) => average + weightedValue / totalWeight, - 0 - ) -} - -export function temporalCumul( - temporalValue: Temporal>, - unit: Unit -): Evaluation { - temporalValue = temporalValue.filter(({ value }) => value !== false) - if (!temporalValue.length) { - return false - } - - if (temporalValue.some(({ value }) => value == null)) { - return null - } - - const temporalNumber = temporalValue as Temporal - const first = temporalNumber[0] - const last = temporalNumber[temporalNumber.length - 1] - - // La variable est définie sur un interval infini - if (first.start == null || last.end == null) { - if (first.start != null) { - return !last.value ? last.value : last.value > 0 ? Infinity : -Infinity - } - if (last.end != null) { - return !last.value ? last.value : last.value > 0 ? Infinity : -Infinity - } - return null - } - if (temporalNumber.some(({ value }) => value == null)) { - return null - } - - return temporalNumber.reduce((acc, { start, end, value }) => { - ;[start, end] = [start, end] as [string, string] - let weight = 1 - if (unit?.denominators.includes('mois')) { - weight = getDifferenceInMonths(start, end) - } else if (unit?.denominators.includes('année')) { - weight = getDifferenceInYears(start, end) - } else if (unit?.denominators.includes('jour')) { - weight = getDifferenceInDays(start, end) - } - return value * weight + acc - }, 0) -} diff --git "a/publicodes/core/test/m\303\251canismes/grille.yaml" "b/publicodes/core/test/m\303\251canismes/grille.yaml" index 4796dde593..33fac7ea4b 100644 --- "a/publicodes/core/test/m\303\251canismes/grille.yaml" +++ "b/publicodes/core/test/m\303\251canismes/grille.yaml" @@ -59,7 +59,7 @@ Grille avec valeur manquante: situation: assiette: 3000 valeur attendue: 300 - - nom: 'assiette au delà du plagond' + - nom: 'assiette au delà du plafond' situation: assiette: 5000 valeur attendue: false diff --git a/publicodes/core/test/period.test.js b/publicodes/core/test/period.test.js deleted file mode 100644 index 51c0f2ec57..0000000000 --- a/publicodes/core/test/period.test.js +++ /dev/null @@ -1,132 +0,0 @@ -import { expect } from 'chai' -import { - concatTemporals, - createTemporalEvaluation, - groupByYear, - zipTemporals, -} from '../source/temporal' - -const neverEnding = (value) => [{ start: null, end: null, value: value }] -describe('Periods : zip', () => { - it('should zip two empty temporalValue', () => { - const result = zipTemporals([], []) - expect(result).to.deep.equal([]) - }) - - it('should zip constant temporalValue', () => { - const result = zipTemporals(neverEnding(1), neverEnding(2)) - expect(result).to.deep.equal(neverEnding([1, 2])) - }) - - it('should zip changing temporalValue', () => { - const value1 = createTemporalEvaluation(true, { - start: null, - end: '01/08/2020', - }) - const value2 = neverEnding(1) - expect(zipTemporals(value1, value2)).to.deep.equal([ - { start: null, end: '01/08/2020', value: [true, 1] }, - { start: '02/08/2020', end: null, value: [false, 1] }, - ]) - expect(zipTemporals(value2, value1)).to.deep.equal([ - { start: null, end: '01/08/2020', value: [1, true] }, - { start: '02/08/2020', end: null, value: [1, false] }, - ]) - }) - - it('should zip two overlapping temporalValue', () => { - const value1 = createTemporalEvaluation(1, { - start: '01/07/2019', - end: '30/06/2020', - }) - const value2 = createTemporalEvaluation(2, { - start: '01/01/2019', - end: '31/12/2019', - }) - - expect(zipTemporals(value1, value2)).to.deep.equal([ - { start: null, end: '31/12/2018', value: [false, false] }, - { start: '01/01/2019', end: '30/06/2019', value: [false, 2] }, - { start: '01/07/2019', end: '31/12/2019', value: [1, 2] }, - { start: '01/01/2020', end: '30/06/2020', value: [1, false] }, - { start: '01/07/2020', end: null, value: [false, false] }, - ]) - }) -}) - -describe('Periods : concat', () => { - it('should merge concat overlapping temporalValue', () => { - const value1 = createTemporalEvaluation(10) - const value2 = [ - { start: null, end: '14/04/2019', value: 100 }, - { start: '15/04/2019', end: '08/08/2019', value: 2000 }, - { start: '09/08/2019', end: null, value: 200 }, - ] - - expect(concatTemporals([value1, value2])).to.deep.equal([ - { start: null, end: '14/04/2019', value: [10, 100] }, - { start: '15/04/2019', end: '08/08/2019', value: [10, 2000] }, - { start: '09/08/2019', end: null, value: [10, 200] }, - ]) - }) -}) - -describe('Periods : groupByYear', () => { - const invariants = (temporalYear) => { - const startDate = temporalYear[0].start - const endDate = temporalYear.slice(-1)[0].end - expect( - startDate === null || startDate.startsWith('01/01'), - 'starts at the beginning of a year' - ) - expect( - endDate === null || endDate.startsWith('31/12'), - 'stops at the end of a year' - ) - } - it('should handle constant value', () => { - const value = createTemporalEvaluation(10) - expect(groupByYear(value)).to.deep.equal([value]) - }) - it('should handle changing value', () => { - const value = createTemporalEvaluation(10, { - start: '06/06/2020', - end: '20/12/2020', - }) - const result = groupByYear(value) - expect(result).to.have.length(3) - result.forEach(invariants) - }) - it('should handle changing value over several years', () => { - const value = createTemporalEvaluation(10, { - start: '06/06/2020', - end: '20/12/2022', - }) - const result = groupByYear(value) - expect(result).to.have.length(5) - result.forEach(invariants) - }) - it('should handle complex case', () => { - const result = groupByYear( - concatTemporals([ - createTemporalEvaluation(1, { - start: '06/06/2020', - end: '20/12/2022', - }), - createTemporalEvaluation(2, { - start: '01/01/1991', - end: '20/12/1992', - }), - createTemporalEvaluation(3, { - start: '31/01/1990', - end: '20/12/2021', - }), - createTemporalEvaluation(4, { - start: '31/12/2020', - end: '01/01/2021', - }), - ]) - ) - result.forEach(invariants) - }) -}) diff --git a/publicodes/core/test/temporal.test.js b/publicodes/core/test/temporal.test.js deleted file mode 100644 index 51c0f2ec57..0000000000 --- a/publicodes/core/test/temporal.test.js +++ /dev/null @@ -1,132 +0,0 @@ -import { expect } from 'chai' -import { - concatTemporals, - createTemporalEvaluation, - groupByYear, - zipTemporals, -} from '../source/temporal' - -const neverEnding = (value) => [{ start: null, end: null, value: value }] -describe('Periods : zip', () => { - it('should zip two empty temporalValue', () => { - const result = zipTemporals([], []) - expect(result).to.deep.equal([]) - }) - - it('should zip constant temporalValue', () => { - const result = zipTemporals(neverEnding(1), neverEnding(2)) - expect(result).to.deep.equal(neverEnding([1, 2])) - }) - - it('should zip changing temporalValue', () => { - const value1 = createTemporalEvaluation(true, { - start: null, - end: '01/08/2020', - }) - const value2 = neverEnding(1) - expect(zipTemporals(value1, value2)).to.deep.equal([ - { start: null, end: '01/08/2020', value: [true, 1] }, - { start: '02/08/2020', end: null, value: [false, 1] }, - ]) - expect(zipTemporals(value2, value1)).to.deep.equal([ - { start: null, end: '01/08/2020', value: [1, true] }, - { start: '02/08/2020', end: null, value: [1, false] }, - ]) - }) - - it('should zip two overlapping temporalValue', () => { - const value1 = createTemporalEvaluation(1, { - start: '01/07/2019', - end: '30/06/2020', - }) - const value2 = createTemporalEvaluation(2, { - start: '01/01/2019', - end: '31/12/2019', - }) - - expect(zipTemporals(value1, value2)).to.deep.equal([ - { start: null, end: '31/12/2018', value: [false, false] }, - { start: '01/01/2019', end: '30/06/2019', value: [false, 2] }, - { start: '01/07/2019', end: '31/12/2019', value: [1, 2] }, - { start: '01/01/2020', end: '30/06/2020', value: [1, false] }, - { start: '01/07/2020', end: null, value: [false, false] }, - ]) - }) -}) - -describe('Periods : concat', () => { - it('should merge concat overlapping temporalValue', () => { - const value1 = createTemporalEvaluation(10) - const value2 = [ - { start: null, end: '14/04/2019', value: 100 }, - { start: '15/04/2019', end: '08/08/2019', value: 2000 }, - { start: '09/08/2019', end: null, value: 200 }, - ] - - expect(concatTemporals([value1, value2])).to.deep.equal([ - { start: null, end: '14/04/2019', value: [10, 100] }, - { start: '15/04/2019', end: '08/08/2019', value: [10, 2000] }, - { start: '09/08/2019', end: null, value: [10, 200] }, - ]) - }) -}) - -describe('Periods : groupByYear', () => { - const invariants = (temporalYear) => { - const startDate = temporalYear[0].start - const endDate = temporalYear.slice(-1)[0].end - expect( - startDate === null || startDate.startsWith('01/01'), - 'starts at the beginning of a year' - ) - expect( - endDate === null || endDate.startsWith('31/12'), - 'stops at the end of a year' - ) - } - it('should handle constant value', () => { - const value = createTemporalEvaluation(10) - expect(groupByYear(value)).to.deep.equal([value]) - }) - it('should handle changing value', () => { - const value = createTemporalEvaluation(10, { - start: '06/06/2020', - end: '20/12/2020', - }) - const result = groupByYear(value) - expect(result).to.have.length(3) - result.forEach(invariants) - }) - it('should handle changing value over several years', () => { - const value = createTemporalEvaluation(10, { - start: '06/06/2020', - end: '20/12/2022', - }) - const result = groupByYear(value) - expect(result).to.have.length(5) - result.forEach(invariants) - }) - it('should handle complex case', () => { - const result = groupByYear( - concatTemporals([ - createTemporalEvaluation(1, { - start: '06/06/2020', - end: '20/12/2022', - }), - createTemporalEvaluation(2, { - start: '01/01/1991', - end: '20/12/1992', - }), - createTemporalEvaluation(3, { - start: '31/01/1990', - end: '20/12/2021', - }), - createTemporalEvaluation(4, { - start: '31/12/2020', - end: '01/01/2021', - }), - ]) - ) - result.forEach(invariants) - }) -}) diff --git a/publicodes/ui-react/source/Explanation.tsx b/publicodes/ui-react/source/Explanation.tsx index bafe2d6895..ff32415442 100644 --- a/publicodes/ui-react/source/Explanation.tsx +++ b/publicodes/ui-react/source/Explanation.tsx @@ -64,7 +64,6 @@ const UIComponents = { 'une possibilité': UnePossibilité, 'résoudre référence circulaire': RésoudreRéférenceCirculaire, unité: Unité, - 'variable temporelle': () => '[variable temporelle]', variations: Variations, } as const diff --git a/publicodes/ui-react/source/index.tsx b/publicodes/ui-react/source/index.tsx index fe4dddea51..4a79e5a5a2 100644 --- a/publicodes/ui-react/source/index.tsx +++ b/publicodes/ui-react/source/index.tsx @@ -67,7 +67,6 @@ export function Documentation({ defaultEngine, state?.situation ) - console.log(engine) return (