🔥 Supprime Ramda du moteur
parent
61729eb334
commit
94a3714b79
|
@ -1194,7 +1194,7 @@ contrat salarié . rémunération . brut de base:
|
|||
unité: €/mois
|
||||
suggestions:
|
||||
salaire médian: 2300 €/mois
|
||||
SMIC: contrat salarié . SMIC contractuel
|
||||
SMIC: SMIC contractuel
|
||||
formule:
|
||||
inversion numérique:
|
||||
question: Quel est le salaire ?
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
"dependencies": {
|
||||
"moo": "^0.5.1",
|
||||
"nearley": "^2.19.2",
|
||||
"ramda": "^0.27.0",
|
||||
"yaml": "^1.9.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import graphlib from '@dagrejs/graphlib'
|
||||
import * as R from 'ramda'
|
||||
import parsePublicodes from '../parsePublicodes'
|
||||
import { RuleNode } from '../rule'
|
||||
import { reduceAST } from './index'
|
||||
|
@ -10,9 +9,10 @@ type GraphCyclesWithDependencies = Array<RulesDependencies>
|
|||
function buildRulesDependencies(
|
||||
parsedRules: Record<string, RuleNode>
|
||||
): RulesDependencies {
|
||||
const uniq = <T>(arr: Array<T>): Array<T> => [...new Set(arr)]
|
||||
return Object.entries(parsedRules).map(([name, node]) => [
|
||||
name,
|
||||
R.uniq(buildRuleDependancies(node)),
|
||||
uniq(buildRuleDependancies(node)),
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ export function cyclicDependencies(
|
|||
const rulesDependencies = buildRulesDependencies(parsedRules)
|
||||
const dependenciesGraph = buildDependenciesGraph(rulesDependencies)
|
||||
const cycles = (graphlib as any).alg.findCycles(dependenciesGraph)
|
||||
const rulesDependenciesObject = R.fromPairs(rulesDependencies)
|
||||
const rulesDependenciesObject = Object.fromEntries(rulesDependencies)
|
||||
|
||||
return cycles.map((cycle) => {
|
||||
const c = cycle.reverse()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { mapObjIndexed } from 'ramda'
|
||||
import { InternalError } from '../error'
|
||||
import { TrancheNodes } from '../mecanisms/trancheUtils'
|
||||
import { ReplacementRule } from '../replacement'
|
||||
|
@ -151,7 +150,9 @@ const traverseASTNode: TraverseFunction<NodeKind> = (fn, node) => {
|
|||
const traverseRuleNode: TraverseFunction<'rule'> = (fn, node) => ({
|
||||
...node,
|
||||
replacements: node.replacements.map(fn) as Array<ReplacementRule>,
|
||||
suggestions: mapObjIndexed(fn, node.suggestions),
|
||||
suggestions: Object.fromEntries(
|
||||
Object.entries(node.suggestions).map(([key, value]) => [key, fn(value)])
|
||||
),
|
||||
explanation: {
|
||||
parent: node.explanation.parent && fn(node.explanation.parent),
|
||||
valeur: fn(node.explanation.valeur),
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
import {
|
||||
add,
|
||||
evolve,
|
||||
fromPairs,
|
||||
keys,
|
||||
map,
|
||||
mapObjIndexed,
|
||||
mergeWith,
|
||||
reduce,
|
||||
} from 'ramda'
|
||||
import Engine, { EvaluationFunction } from '.'
|
||||
import {
|
||||
ASTNode,
|
||||
|
@ -34,12 +24,20 @@ export const collectNodeMissing = (
|
|||
): Record<string, number> =>
|
||||
'missingVariables' in node ? node.missingVariables : {}
|
||||
|
||||
export const bonus = (missings, hasCondition = true) =>
|
||||
hasCondition ? map((x) => x + 0.0001, missings || {}) : missings
|
||||
export const bonus = (missings: Record<string, number> = {}) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(missings).map(([key, value]) => [key, value + 0.0001])
|
||||
)
|
||||
export const mergeMissing = (
|
||||
left: Record<string, number> | undefined,
|
||||
right: Record<string, number> | undefined
|
||||
): Record<string, number> => mergeWith(add, left || {}, right || {})
|
||||
left: Record<string, number> | undefined = {},
|
||||
right: Record<string, number> | undefined = {}
|
||||
): Record<string, number> =>
|
||||
Object.fromEntries(
|
||||
[...Object.keys(left), ...Object.keys(right)].map((key) => [
|
||||
key,
|
||||
(left[key] ?? 0) + (right[key] ?? 0),
|
||||
])
|
||||
)
|
||||
|
||||
export const mergeAllMissing = (missings: Array<EvaluatedNode | ASTNode>) =>
|
||||
missings.map(collectNodeMissing).reduce(mergeMissing, {})
|
||||
|
@ -66,8 +64,8 @@ function convertNodesToSameUnit(nodes, contextRule, mecanismName) {
|
|||
}
|
||||
|
||||
export const evaluateArray: <NodeName extends NodeKind>(
|
||||
reducer: Parameters<typeof reduce>[0],
|
||||
start: Parameters<typeof reduce>[1]
|
||||
reducer,
|
||||
start
|
||||
) => EvaluationFunction<NodeName> = (reducer, start) =>
|
||||
function (node: any) {
|
||||
const evaluate = this.evaluateNode.bind(this)
|
||||
|
@ -87,7 +85,7 @@ export const evaluateArray: <NodeName extends NodeKind>(
|
|||
if (values.some((value) => value === null)) {
|
||||
return null
|
||||
}
|
||||
return reduce(reducer, start, values)
|
||||
return values.reduce(reducer, start)
|
||||
}, temporalValues)
|
||||
|
||||
const baseEvaluation = {
|
||||
|
@ -119,27 +117,30 @@ export const defaultNode = (nodeValue: Evaluation) =>
|
|||
} as ConstantNode)
|
||||
|
||||
export const parseObject = (objectShape, value, context) => {
|
||||
const recurseOne = (key) => (defaultValue) => {
|
||||
if (value[key] == null && !defaultValue)
|
||||
throw new Error(
|
||||
`Il manque une clé '${key}' dans ${JSON.stringify(value)} `
|
||||
)
|
||||
return value[key] != null ? parse(value[key], context) : defaultValue
|
||||
}
|
||||
const transforms = fromPairs(
|
||||
map((k) => [k, recurseOne(k)], keys(objectShape)) as any
|
||||
return Object.fromEntries(
|
||||
Object.entries(objectShape).map(([key, defaultValue]) => {
|
||||
if (value[key] == null && !defaultValue) {
|
||||
throw new Error(
|
||||
`Il manque une clé '${key}' dans ${JSON.stringify(value)} `
|
||||
)
|
||||
}
|
||||
|
||||
const parsedValue =
|
||||
value[key] != null ? parse(value[key], context) : defaultValue
|
||||
return [key, parsedValue]
|
||||
})
|
||||
)
|
||||
return evolve(transforms as any, objectShape)
|
||||
}
|
||||
|
||||
export function evaluateObject<NodeName extends NodeKind>(
|
||||
effet: (this: Engine, explanations: any) => any
|
||||
) {
|
||||
return function (node) {
|
||||
const evaluate = this.evaluateNode.bind(this)
|
||||
const evaluations: Record<string, EvaluatedNode> = mapObjIndexed(
|
||||
evaluate as any,
|
||||
(node as any).explanation
|
||||
const evaluations = Object.fromEntries(
|
||||
Object.entries((node as any).explanation).map(([key, value]) => [
|
||||
key,
|
||||
this.evaluateNode(value as any),
|
||||
])
|
||||
)
|
||||
const temporalExplanations = mapTemporal(
|
||||
Object.fromEntries,
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import { memoizeWith } from 'ramda'
|
||||
import { Evaluation, Unit } from './AST/types'
|
||||
import { formatUnit, serializeUnit } from './units'
|
||||
|
||||
const NumberFormat = memoizeWith(
|
||||
(...args) => JSON.stringify(args),
|
||||
Intl.NumberFormat
|
||||
)
|
||||
|
||||
export const numberFormatter = ({
|
||||
style,
|
||||
maximumFractionDigits = 2,
|
||||
|
@ -27,7 +21,7 @@ export const numberFormatter = ({
|
|||
!Number.isInteger(value)
|
||||
? 2
|
||||
: minimumFractionDigits
|
||||
return NumberFormat(language, {
|
||||
return Intl.NumberFormat(language, {
|
||||
style,
|
||||
currency: 'EUR',
|
||||
maximumFractionDigits,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
import { compose, mapObjIndexed } from 'ramda'
|
||||
import { ASTNode, EvaluatedNode, NodeKind } from './AST/types'
|
||||
import { evaluationFunctions } from './evaluationFunctions'
|
||||
import { simplifyNodeUnit } from './nodeUnits'
|
||||
|
@ -104,24 +103,28 @@ export default class Engine<Name extends string = string> {
|
|||
situation: Partial<Record<Name, string | number | object | ASTNode>> = {}
|
||||
) {
|
||||
this.resetCache()
|
||||
this.parsedSituation = mapObjIndexed((value, key) => {
|
||||
if (value && typeof value === 'object' && 'nodeKind' in value) {
|
||||
return value as ASTNode
|
||||
}
|
||||
return compose(
|
||||
inlineReplacements(this.replacements),
|
||||
disambiguateReference(this.parsedRules)
|
||||
)(
|
||||
parse(value, {
|
||||
dottedName: `situation [${key}]`,
|
||||
parsedRules: {},
|
||||
options: this.options,
|
||||
})
|
||||
)
|
||||
}, situation)
|
||||
this.parsedSituation = Object.fromEntries(
|
||||
Object.entries(situation).map(([key, value]) => {
|
||||
const parsedValue =
|
||||
value && typeof value === 'object' && 'nodeKind' in value
|
||||
? (value as ASTNode)
|
||||
: this.parse(value, {
|
||||
dottedName: `situation [${key}]`,
|
||||
parsedRules: {},
|
||||
options: this.options,
|
||||
})
|
||||
return [key, parsedValue]
|
||||
})
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
private parse(...args: Parameters<typeof parse>) {
|
||||
return inlineReplacements(this.replacements)(
|
||||
disambiguateReference(this.parsedRules)(parse(...args))
|
||||
)
|
||||
}
|
||||
|
||||
evaluate(expression: string | Object): EvaluatedNode {
|
||||
/*
|
||||
TODO
|
||||
|
@ -134,16 +137,11 @@ export default class Engine<Name extends string = string> {
|
|||
originalWarn(warning)
|
||||
}
|
||||
const result = this.evaluateNode(
|
||||
compose(
|
||||
inlineReplacements(this.replacements),
|
||||
disambiguateReference(this.parsedRules)
|
||||
)(
|
||||
parse(expression, {
|
||||
dottedName: "evaluation'''",
|
||||
parsedRules: {},
|
||||
options: this.options,
|
||||
})
|
||||
)
|
||||
this.parse(expression, {
|
||||
dottedName: "evaluation'''",
|
||||
parsedRules: {},
|
||||
options: this.options,
|
||||
})
|
||||
)
|
||||
console.warn = originalWarn
|
||||
return result
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { is } from 'ramda'
|
||||
import { EvaluationFunction } from '..'
|
||||
import { ASTNode } from '../AST/types'
|
||||
import { mergeAllMissing } from '../evaluation'
|
||||
|
@ -38,7 +37,7 @@ const evaluate: EvaluationFunction<'toutes ces conditions'> = function (node) {
|
|||
}
|
||||
|
||||
export const mecanismAllOf = (v, context) => {
|
||||
if (!is(Array, v)) throw new Error('should be array')
|
||||
if (!Array.isArray(v)) throw new Error('should be array')
|
||||
const explanation = v.map((node) => parse(node, context))
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { is } from 'ramda'
|
||||
import { EvaluationFunction } from '..'
|
||||
import { ASTNode, EvaluatedNode, Evaluation } from '../AST/types'
|
||||
import { mergeMissing } from '../evaluation'
|
||||
|
@ -57,7 +56,7 @@ const evaluate: EvaluationFunction<'une de ces conditions'> = function (node) {
|
|||
}
|
||||
|
||||
export const mecanismOneOf = (v, context) => {
|
||||
if (!is(Array, v)) throw new Error('should be array')
|
||||
if (!Array.isArray(v)) throw new Error('should be array')
|
||||
const explanation = v.map((node) => parse(node, context))
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { min } from 'ramda'
|
||||
import { ASTNode } from '../AST/types'
|
||||
import { evaluateArray } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
|
@ -17,6 +16,6 @@ export const mecanismMin = (v, context) => {
|
|||
} as MinNode
|
||||
}
|
||||
|
||||
const evaluate = evaluateArray<'minimum'>(min, Infinity)
|
||||
const evaluate = evaluateArray<'minimum'>((a, b) => Math.min(a, b), Infinity)
|
||||
|
||||
registerEvaluationFunction('minimum', evaluate)
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { equals, fromPairs } from 'ramda'
|
||||
import { EvaluationFunction } from '..'
|
||||
import { ASTNode } from '../AST/types'
|
||||
import { convertToDate } from '../date'
|
||||
|
@ -20,8 +19,8 @@ const knownOperations = {
|
|||
'<=': [(a, b) => a <= b, '≤'],
|
||||
'>': [(a, b) => a > b],
|
||||
'>=': [(a, b) => a >= b, '≥'],
|
||||
'=': [(a, b) => equals(a, b)],
|
||||
'!=': [(a, b) => !equals(a, b), '≠'],
|
||||
'=': [(a, b) => a === b],
|
||||
'!=': [(a, b) => a !== b, '≠'],
|
||||
} as const
|
||||
|
||||
export type OperationNode = {
|
||||
|
@ -123,7 +122,7 @@ const evaluate: EvaluationFunction<'operation'> = function (node) {
|
|||
|
||||
registerEvaluationFunction('operation', evaluate)
|
||||
|
||||
const operationDispatch = fromPairs(
|
||||
const operationDispatch = Object.fromEntries(
|
||||
Object.entries(knownOperations).map(([k, [f, symbol]]) => [
|
||||
k,
|
||||
parseOperation(k, symbol),
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { max, min } from 'ramda'
|
||||
import { typeWarning } from '../error'
|
||||
import { defaultNode, evaluateObject, parseObject } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
|
@ -48,12 +47,18 @@ const evaluate = evaluateObject<'allègement'>(function ({
|
|||
? 0
|
||||
: null
|
||||
: serializeUnit(abattement.unit) === '%'
|
||||
? max(
|
||||
? Math.max(
|
||||
0,
|
||||
assietteValue -
|
||||
min(plafond.nodeValue, (abattement.nodeValue / 100) * assietteValue)
|
||||
Math.min(
|
||||
plafond.nodeValue,
|
||||
(abattement.nodeValue / 100) * assietteValue
|
||||
)
|
||||
)
|
||||
: Math.max(
|
||||
0,
|
||||
assietteValue - Math.min(plafond.nodeValue, abattement.nodeValue)
|
||||
)
|
||||
: max(0, assietteValue - min(plafond.nodeValue, abattement.nodeValue))
|
||||
: assietteValue
|
||||
return {
|
||||
nodeValue,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { isEmpty } from 'ramda'
|
||||
import { ASTNode, EvaluatedNode } from '../AST/types'
|
||||
import { mergeAllMissing } from '../evaluation'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
|
@ -50,7 +49,7 @@ registerEvaluationFunction(parseSituation.nom, function evaluate(node) {
|
|||
...node,
|
||||
nodeValue: valeur.nodeValue,
|
||||
missingVariables:
|
||||
isEmpty(missingVariables) && valeur.nodeValue === null
|
||||
Object.keys(missingVariables).length === 0 && valeur.nodeValue === null
|
||||
? { [situationKey]: 1 }
|
||||
: missingVariables,
|
||||
...(unit !== undefined && { unit }),
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { path } from 'ramda'
|
||||
import { EvaluationFunction } from '..'
|
||||
import { ASTNode } from '../AST/types'
|
||||
import { registerEvaluationFunction } from '../evaluationFunctions'
|
||||
|
@ -15,14 +14,14 @@ export type SynchronisationNode = {
|
|||
const evaluate: EvaluationFunction<'synchronisation'> = function (node: any) {
|
||||
const data = this.evaluateNode(node.explanation.data)
|
||||
const valuePath = node.explanation.chemin.split(' . ')
|
||||
const nodeValue =
|
||||
data.nodeValue == null ? null : path(valuePath, data.nodeValue)
|
||||
const path = (obj) => valuePath.reduce((res, prop) => res[prop], obj)
|
||||
const nodeValue = data.nodeValue == null ? null : path(data.nodeValue)
|
||||
// If the API gave a non null value, then some of its props may be null (the
|
||||
// API can be composed of multiple API, some failing). Then this prop will be
|
||||
// set to the default value defined in the API's rule
|
||||
const safeNodeValue =
|
||||
nodeValue == null && data.nodeValue != null
|
||||
? path(valuePath, data.explanation.defaultValue)
|
||||
? path(data.explanation.defaultValue)
|
||||
: nodeValue
|
||||
const missingVariables = {
|
||||
...data.missingVariables,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { evolve } from 'ramda'
|
||||
import { ASTNode, Evaluation } from '../AST/types'
|
||||
import { evaluationError, typeWarning } from '../error'
|
||||
import { mergeAllMissing } from '../evaluation'
|
||||
|
@ -18,13 +17,14 @@ export const parseTranches = (tranches, context): TrancheNodes => {
|
|||
}
|
||||
return { ...t, plafond: t.plafond ?? Infinity }
|
||||
})
|
||||
.map(
|
||||
evolve({
|
||||
taux: (node) => parse(node, context),
|
||||
montant: (node) => parse(node, context),
|
||||
plafond: (node) => parse(node, context),
|
||||
})
|
||||
)
|
||||
.map((node) => ({
|
||||
...node,
|
||||
...(node.taux !== undefined ? { taux: parse(node.taux, context) } : {}),
|
||||
...(node.montant !== undefined
|
||||
? { montant: parse(node.montant, context) }
|
||||
: {}),
|
||||
plafond: parse(node.plafond, context),
|
||||
}))
|
||||
}
|
||||
|
||||
export function evaluatePlafondUntilActiveTranche(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { or } from 'ramda'
|
||||
import { EvaluationFunction } from '..'
|
||||
import { ASTNode, Unit } from '../AST/types'
|
||||
import { typeWarning } from '../error'
|
||||
|
@ -101,8 +100,7 @@ const evaluate: EvaluationFunction<'variations'> = function (node) {
|
|||
pureTemporal(evaluatedCondition.nodeValue)
|
||||
)
|
||||
evaluatedCondition.missingVariables = bonus(
|
||||
evaluatedCondition.missingVariables,
|
||||
true
|
||||
evaluatedCondition.missingVariables
|
||||
)
|
||||
const currentConditionAlwaysFalse = !sometime(
|
||||
(x) => x !== false,
|
||||
|
@ -136,6 +134,7 @@ const evaluate: EvaluationFunction<'variations'> = function (node) {
|
|||
evaluatedConsequence.temporalValue ??
|
||||
pureTemporal(evaluatedConsequence.nodeValue)
|
||||
)
|
||||
const or = (a, b) => a || b
|
||||
return [
|
||||
liftTemporal2(or, evaluation, currentValue),
|
||||
[
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Grammar, Parser } from 'nearley'
|
||||
import { isEmpty } from 'ramda'
|
||||
import { ASTNode } from './AST/types'
|
||||
import { EngineError, syntaxError } from './error'
|
||||
import grammar from './grammar.ne'
|
||||
|
@ -109,7 +108,7 @@ Cela vient probablement d'une erreur dans l'indentation
|
|||
`
|
||||
)
|
||||
}
|
||||
if (isEmpty(rawNode)) {
|
||||
if (keys.length === 0) {
|
||||
return { nodeKind: 'constant', nodeValue: null }
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { partial } from 'ramda'
|
||||
import yaml from 'yaml'
|
||||
import { ParsedRules } from '.'
|
||||
import { transformAST, traverseParsedRules } from './AST'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { groupBy } from 'ramda'
|
||||
import { transformAST } from './AST'
|
||||
import { ASTNode } from './AST/types'
|
||||
import { InternalError, warning } from './error'
|
||||
|
@ -89,15 +88,15 @@ export function parseRendNonApplicable(
|
|||
export function getReplacements(
|
||||
parsedRules: Record<string, RuleNode>
|
||||
): Record<string, Array<ReplacementRule>> {
|
||||
return groupBy(
|
||||
(r: ReplacementRule) => {
|
||||
return Object.values(parsedRules)
|
||||
.flatMap((rule) => rule.replacements)
|
||||
.reduce((acc, r: ReplacementRule) => {
|
||||
if (!r.replacedReference.dottedName) {
|
||||
throw new InternalError(r)
|
||||
}
|
||||
return r.replacedReference.dottedName
|
||||
},
|
||||
Object.values(parsedRules).flatMap((rule) => rule.replacements)
|
||||
)
|
||||
const key = r.replacedReference.dottedName
|
||||
return { ...acc, [key]: [...(acc[key] ?? []), r] }
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function inlineReplacements(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { filter, mapObjIndexed, pick } from 'ramda'
|
||||
import { ASTNode, EvaluatedNode } from './AST/types'
|
||||
import { bonus, mergeMissing } from './evaluation'
|
||||
import { registerEvaluationFunction } from './evaluationFunctions'
|
||||
|
@ -79,7 +78,9 @@ export default function parseRule(
|
|||
}
|
||||
|
||||
const ruleValue = {
|
||||
...pick(mecanismKeys, rawRule),
|
||||
...Object.fromEntries(
|
||||
Object.entries(rawRule).filter(([key]) => mecanismKeys.includes(key))
|
||||
),
|
||||
...('formule' in rawRule && { valeur: rawRule.formule }),
|
||||
'nom dans la situation': dottedName,
|
||||
}
|
||||
|
@ -91,22 +92,24 @@ export default function parseRule(
|
|||
valeur: parse(ruleValue, ruleContext),
|
||||
parent: !!parent && parse(parent, context),
|
||||
}
|
||||
context.parsedRules[dottedName] = filter(Boolean, {
|
||||
context.parsedRules[dottedName] = {
|
||||
dottedName,
|
||||
replacements: [
|
||||
...parseRendNonApplicable(rawRule['rend non applicable'], ruleContext),
|
||||
...parseReplacements(rawRule.remplace, ruleContext),
|
||||
],
|
||||
title: ruleTitle,
|
||||
suggestions: mapObjIndexed(
|
||||
(node) => parse(node, ruleContext),
|
||||
rawRule.suggestions ?? {}
|
||||
suggestions: Object.fromEntries(
|
||||
Object.entries(rawRule.suggestions ?? {}).map(([name, node]) => [
|
||||
name,
|
||||
parse(node, ruleContext),
|
||||
])
|
||||
),
|
||||
nodeKind: 'rule',
|
||||
explanation,
|
||||
rawNode: rawRule,
|
||||
virtualRule: !!context.dottedName,
|
||||
}) as RuleNode
|
||||
} as RuleNode
|
||||
|
||||
// We return the parsedReference
|
||||
return parse(rawRule.nom, context) as ReferenceNode
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { last, pipe, range, take } from 'ramda'
|
||||
import { syntaxError } from './error'
|
||||
import { RuleNode } from './rule'
|
||||
|
||||
const splitName = (str: string) => str.split(' . ')
|
||||
const joinName = (strs) => strs.join(' . ')
|
||||
export const nameLeaf = pipe<string, string[], string>(splitName, last)
|
||||
const joinName = (strs: Array<string>) => strs.join(' . ')
|
||||
export const nameLeaf = (name: string) => splitName(name).slice(-1)?.[0]
|
||||
export const encodeRuleName = (name) =>
|
||||
name
|
||||
?.replace(/\s\.\s/g, '/')
|
||||
|
@ -15,13 +14,12 @@ export const decodeRuleName = (name) =>
|
|||
.replace(/\//g, ' . ')
|
||||
.replace(/-/g, ' ')
|
||||
.replace(/\u2011/g, '-')
|
||||
export function ruleParents<Names extends string>(
|
||||
dottedName: Names
|
||||
): Array<Names> {
|
||||
export function ruleParents(dottedName: string): Array<string> {
|
||||
const fragments = splitName(dottedName) // dottedName ex. [CDD . événements . rupture]
|
||||
return range(1, fragments.length)
|
||||
.map((nbEl) => take(nbEl, fragments))
|
||||
.map(joinName) // -> [ [CDD . événements . rupture], [CDD . événements], [CDD
|
||||
return Array(fragments.length - 1)
|
||||
.fill(0)
|
||||
.map((f, i) => fragments.slice(0, i + 1))
|
||||
.map(joinName)
|
||||
.reverse()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,3 @@
|
|||
import {
|
||||
countBy,
|
||||
equals,
|
||||
flatten,
|
||||
isEmpty,
|
||||
keys,
|
||||
map,
|
||||
pipe,
|
||||
remove,
|
||||
uniq,
|
||||
unnest,
|
||||
without,
|
||||
} from 'ramda'
|
||||
import { Evaluation, Unit } from './AST/types'
|
||||
|
||||
export type getUnitKey = (writtenUnit: string) => string
|
||||
|
@ -53,8 +40,8 @@ export const serializeUnit = (
|
|||
const unit = simplify(rawUnit),
|
||||
{ numerators = [], denominators = [] } = unit
|
||||
|
||||
const n = !isEmpty(numerators)
|
||||
const d = !isEmpty(denominators)
|
||||
const n = numerators.length > 0
|
||||
const d = denominators.length > 0
|
||||
const string =
|
||||
!n && !d
|
||||
? ''
|
||||
|
@ -95,8 +82,8 @@ export const inferUnit = (
|
|||
}
|
||||
if (operator === '*')
|
||||
return simplify({
|
||||
numerators: unnest(units.map((u) => u?.numerators ?? [])),
|
||||
denominators: unnest(units.map((u) => u?.denominators ?? [])),
|
||||
numerators: units.flatMap((u) => u?.numerators ?? []),
|
||||
denominators: units.flatMap((u) => u?.denominators ?? []),
|
||||
})
|
||||
|
||||
if (operator === '-' || operator === '+') {
|
||||
|
@ -106,13 +93,20 @@ export const inferUnit = (
|
|||
return undefined
|
||||
}
|
||||
|
||||
const equals = <T>(a: T, b: T) => {
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
return a.length === b.length && a.every((_, i) => a[i] === b[i])
|
||||
} else {
|
||||
return a === b
|
||||
}
|
||||
}
|
||||
|
||||
export const removeOnce = <T>(
|
||||
element: T,
|
||||
eqFn: (a: T, b: T) => boolean = equals
|
||||
) => (list: Array<T>): Array<T> => {
|
||||
const index = list.findIndex((e) => eqFn(e, element))
|
||||
if (index > -1) return remove<T>(index, 1)(list)
|
||||
else return list
|
||||
return list.filter((_, i) => i !== index)
|
||||
}
|
||||
|
||||
const simplify = (
|
||||
|
@ -262,8 +256,8 @@ export function simplifyUnit(unit: Unit): Unit {
|
|||
return { numerators: ['%'], denominators }
|
||||
}
|
||||
return {
|
||||
numerators: without(['%'], numerators),
|
||||
denominators: without(['%'], denominators),
|
||||
numerators: removePercentages(numerators),
|
||||
denominators: removePercentages(denominators),
|
||||
}
|
||||
}
|
||||
function simplifyUnitWithValue(unit: Unit, value = 1): [Unit, number] {
|
||||
|
@ -273,24 +267,31 @@ function simplifyUnitWithValue(unit: Unit, value = 1): [Unit, number] {
|
|||
return [
|
||||
simplify(
|
||||
{
|
||||
numerators: without(['%'], numerators),
|
||||
denominators: without(['%'], denominators),
|
||||
numerators: removePercentages(numerators),
|
||||
denominators: removePercentages(denominators),
|
||||
},
|
||||
areSameClass
|
||||
),
|
||||
value ? round(value * factor) : value,
|
||||
]
|
||||
}
|
||||
|
||||
const removePercentages = (array: Array<string>) =>
|
||||
array.filter((e) => e !== '%')
|
||||
|
||||
export function areUnitConvertible(a: Unit | undefined, b: Unit | undefined) {
|
||||
if (a == null || b == null) {
|
||||
return true
|
||||
}
|
||||
const countByUnitClass = countBy((unit: string) => {
|
||||
const classIndex = convertibleUnitClasses.findIndex((unitClass) =>
|
||||
unitClass.has(unit)
|
||||
)
|
||||
return classIndex === -1 ? unit : '' + classIndex
|
||||
})
|
||||
|
||||
const countByUnitClass = (units: Array<string>) =>
|
||||
units.reduce((counters, unit) => {
|
||||
const classIndex = convertibleUnitClasses.findIndex((unitClass) =>
|
||||
unitClass.has(unit)
|
||||
)
|
||||
const key = classIndex === -1 ? unit : '' + classIndex
|
||||
return { ...counters, [key]: 1 + (counters[key] ?? 0) }
|
||||
}, {})
|
||||
|
||||
const [numA, denomA, numB, denomB] = [
|
||||
a.numerators,
|
||||
|
@ -298,12 +299,9 @@ export function areUnitConvertible(a: Unit | undefined, b: Unit | undefined) {
|
|||
b.numerators,
|
||||
b.denominators,
|
||||
].map(countByUnitClass)
|
||||
const unitClasses = pipe(
|
||||
map(keys),
|
||||
flatten,
|
||||
uniq
|
||||
)([numA, denomA, numB, denomB])
|
||||
return unitClasses.every(
|
||||
const uniq = <T>(arr: Array<T>): Array<T> => [...new Set(arr)]
|
||||
const unitClasses = [numA, denomA, numB, denomB].map(Object.keys).flat()
|
||||
return uniq(unitClasses).every(
|
||||
(unitClass) =>
|
||||
(numA[unitClass] || 0) - (denomA[unitClass] || 0) ===
|
||||
(numB[unitClass] || 0) - (denomB[unitClass] || 0) || unitClass === '%'
|
||||
|
|
Loading…
Reference in New Issue