import { evaluateControls } from 'Engine/controls' import { convertNodeToUnit, simplifyNodeUnit } from 'Engine/nodeUnits' import { parse } from 'Engine/parse' import { EvaluatedNode, EvaluatedRule, ParsedRules, Rules } from 'Engine/types' import { parseUnit } from 'Engine/units' import { mapObjIndexed } from 'ramda' import { Simulation } from 'Reducers/rootReducer' import { evaluationError, warning } from './error' import { collectDefaults, evaluateNode } from './evaluation' import parseRules from './parseRules' const emptyCache = () => ({ _meta: { contextRule: [] } }) type Cache = { _meta: { contextRule: Array inversionFail?: { given: string estimated: string } } } export type EvaluationOptions = Partial<{ unit: string useDefaultValues: boolean }> export { default as translateRules } from './translateRules' export { parseRules } export default class Engine { parsedRules: ParsedRules defaultValues: Simulation['situation'] situation: Simulation['situation'] = {} cache: Cache cacheWithoutDefault: Cache constructor(rules: string | Rules | ParsedRules) { this.cache = emptyCache() this.cacheWithoutDefault = emptyCache() this.parsedRules = typeof rules === 'string' || !(Object.values(rules)[0] as any)?.dottedName ? parseRules(rules) : (rules as ParsedRules) this.defaultValues = mapObjIndexed( (value, name) => typeof value === 'string' ? this.evaluateExpression(value, `[valeur par défaut] ${name}`, false) : value, collectDefaults(this.parsedRules) ) } private resetCache() { this.cache = emptyCache() this.cacheWithoutDefault = emptyCache() } private situationGate(useDefaultValues = true) { return dottedName => this.situation[dottedName] ?? (useDefaultValues ? this.defaultValues[dottedName] : null) } private evaluateExpression( expression: string, context: string, useDefaultValues: boolean = true ): EvaluatedRule { const result = simplifyNodeUnit( evaluateNode( useDefaultValues ? this.cache : this.cacheWithoutDefault, this.situationGate(useDefaultValues), this.parsedRules, parse( this.parsedRules, { dottedName: context }, this.parsedRules )(expression) ) ) if (Object.keys(result.defaultValue?.missingVariable ?? {}).length) { throw new evaluationError( context, "Impossible d'évaluer l'expression car celle ci fait appel à des variables manquantes" ) } return result } setSituation( situation: Partial> = {} ) { this.resetCache() this.situation = mapObjIndexed( (value, name) => typeof value === 'string' ? this.evaluateExpression(value, `[situation] ${name}`, true) : value, situation ) return this } evaluate(expression: Names, options?: EvaluationOptions): EvaluatedRule evaluate( expression: string, options?: EvaluationOptions ): EvaluatedNode evaluate( expression: string, options?: EvaluationOptions ): EvaluatedNode { let result = this.evaluateExpression( expression, `[evaluation] ${expression}`, options?.useDefaultValues ?? true ) if (result.category === 'reference' && result.explanation) { result = { nodeValue: result.nodeValue, unit: result.unit, ...('temporalValue' in result && { temporalValue: result.temporalValue }), ...result.explanation } } if (options?.unit) { try { return convertNodeToUnit( parseUnit(options.unit), result as EvaluatedNode ) } catch (e) { warning( `[evaluation] ${expression}`, "L'unité demandée est incompatible avec l'expression évaluée" ) } } return result } controls() { return evaluateControls(this.cache, this.situationGate(), this.parsedRules) } inversionFail(): boolean { return !!this.cache._meta.inversionFail } getParsedRules(): ParsedRules { return this.parsedRules } // TODO : this should be private getCache(): Cache { return this.cache } }