/** * Note: all here is strictly based on duck typing. * We don't exepect the parent rule to explain the type of the contained formula, for example. */ import R from 'ramda' import { ArrondiExplanation } from './mecanisms/arrondi' import { ParsedRule, ParsedRules } from './types' type OnOff = 'oui' | 'non' export function isOnOff(a: string): a is OnOff { return a === 'oui' || a === 'non' } type WannabeDottedName = string export function isWannabeDottedName(a: string): a is WannabeDottedName { return typeof a === 'string' } type ASTNode = { [_: string]: {} | undefined } // [XXX] - Vaudrait-il mieux utiliser les DottedNames directement ici? // A priori non car on peut imaginer cette lib comme étant indépendante des règles existantes et // fonctionnant par ex en "mode serveur". type RuleNode = ASTNode & ParsedRule type RuleProp = ASTNode & { category: 'ruleProp' rulePropType: string } export function isRuleProp(node: ASTNode): node is RuleProp { return ( (node as RuleProp).category === 'ruleProp' && typeof (node as RuleProp).rulePropType === 'string' ) } type CondRuleProp = RuleProp & { rulePropType: 'cond' } export function isCondRuleProp(node: ASTNode): node is CondRuleProp { return isRuleProp(node) && (node as CondRuleProp).rulePropType === 'cond' } // [XXX] - What about 'rend non applicable'? Unclear what to do in this case, it seems it would create a graph edge in the contrary sense? type ApplicableSi = CondRuleProp & { dottedName: 'applicable si' explanation: ASTNode } export function isApplicableSi(node: ASTNode): node is ApplicableSi { const applicableSi = node as ApplicableSi return ( isCondRuleProp(node) && applicableSi.dottedName === 'applicable si' && applicableSi.explanation !== undefined ) } type NonApplicableSi = CondRuleProp & { dottedName: 'non applicable si' explanation: ASTNode } export function isNonApplicableSi(node: ASTNode): node is NonApplicableSi { const nonApplicableSi = node as NonApplicableSi return ( isCondRuleProp(node) && nonApplicableSi.dottedName === 'non applicable si' && nonApplicableSi.explanation !== undefined ) } type Formule = RuleProp & { name: 'formule' rulePropType: 'formula' explanation: FormuleExplanation } export function isFormule( node: ASTNode ): node is Formule { const formule = node as Formule return ( isRuleProp(node) && formule.name === 'formule' && formule.rulePropType === 'formula' && isFormuleExplanation(formule.explanation) ) } type FormuleExplanation = | Value | Operation | Possibilities | Possibilities2 | Reference | RecalculBroken | AnyMechanism export function isFormuleExplanation( node: ASTNode ): node is FormuleExplanation { return ( isValue(node) || isOperation(node) || isReference(node) || isPossibilities(node) || isPossibilities2(node) || isRecalculBroken(node) || isAnyMechanism(node) ) } type Value = ASTNode & { nodeValue: number | string | boolean } export function isValue(node: ASTNode): node is Value { const value = node as Value return ( typeof value.nodeValue === 'string' || typeof value.nodeValue === 'number' || typeof value.nodeValue === 'boolean' ) } type Operation = ASTNode & { operationType: 'comparison' | 'calculation' explanation: Array } export function isOperation(node: ASTNode): node is Operation { return R.includes((node as Operation).operationType, [ 'comparison', 'calculation' ]) } type Possibilities = ASTNode & { possibilités: Array 'choix obligatoire'?: OnOff 'une possibilité': OnOff } export function isPossibilities(node: ASTNode): node is Possibilities { const possibilities = node as Possibilities return ( possibilities.possibilités instanceof Array && possibilities.possibilités.every(it => typeof it === 'string') && (possibilities['choix obligatoire'] === undefined || isOnOff(possibilities['choix obligatoire'])) && isOnOff(possibilities['une possibilité']) ) } type Possibilities2 = ASTNode & { [index: number]: string // short dotted name 'choix obligatoire'?: OnOff 'une possibilité': OnOff } export function isPossibilities2(node: ASTNode): node is Possibilities2 { const possibilities2 = node as Possibilities2 return ( R.all( ([k, v]) => isNaN(parseInt(k, 10)) || typeof v === 'string', R.toPairs(possibilities2) ) && (possibilities2['choix obligatoire'] === undefined || isOnOff(possibilities2['choix obligatoire'])) && isOnOff(possibilities2['une possibilité']) ) } type Reference = ASTNode & { category: 'reference' name: Name partialReference: Name dottedName: Name } export function isReference( node: ASTNode ): node is Reference { const reference = node as Reference return ( reference.category === 'reference' && isWannabeDottedName(reference.name) && isWannabeDottedName(reference.partialReference) && isWannabeDottedName(reference.dottedName) ) } type RecalculBroken = ASTNode & { avec: Record // Note: TS doesn't allow `Record`! règle?: Name evaluate: Function } export function isRecalculBroken( node: ASTNode ): node is RecalculBroken { const recalculBroken = node as RecalculBroken // Very defensive because we don't want to take risks with this not-well-defined kind of node: return ( R.all( key => R.includes(key, ['avec', 'règle', 'evaluate']), R.keys(recalculBroken) ) && typeof recalculBroken.avec === 'object' && recalculBroken.evaluate instanceof Function ) } type AbstractMechanism = ASTNode & { category: 'mecanism' name: string } export function isAbstractMechanism(node: ASTNode): node is AbstractMechanism { return ( (node as AbstractMechanism).category === 'mecanism' && typeof (node as AbstractMechanism).name === 'string' ) } type RecalculMech = AbstractMechanism & { explanation: { recalcul: Reference amendedSituation: Record> } } export function isRecalculMech( node: ASTNode ): node is RecalculMech { const recalculMech = node as RecalculMech const isReferenceSpec = isReference as ( node: ASTNode ) => node is Reference return ( typeof recalculMech.explanation === 'object' && typeof recalculMech.explanation.recalcul === 'object' && isReferenceSpec(recalculMech.explanation.recalcul as ASTNode) && typeof recalculMech.explanation.amendedSituation === 'object' // [XXX] - We would like to do // && R.all(isDottedName, R.keys(recalculMech.explanation.amendedSituation)) // but it seems there is no simple way to get a type's guard in Typescript // apart if it's built as a class. Or we could rebuild everything here with // passing this guard ƒ as a context everywhere along with the ASTNodes, // with a context monad for example. Overkill. ) } type EncadrementMech = AbstractMechanism & { name: 'encadrement' explanation: { valeur: ASTNode plafond: ASTNode plancher: ASTNode } } export function isEncadrementMech(node: ASTNode): node is EncadrementMech { const encadrementMech = node as EncadrementMech return ( isAbstractMechanism(encadrementMech) && encadrementMech.name == 'encadrement' && typeof encadrementMech.explanation === 'object' && encadrementMech.explanation.valeur !== undefined && encadrementMech.explanation.plafond !== undefined && encadrementMech.explanation.plancher !== undefined ) } type SommeMech = AbstractMechanism & { name: 'somme' explanation: Array } export function isSommeMech(node: ASTNode): node is SommeMech { const sommeMech = node as SommeMech return ( isAbstractMechanism(sommeMech) && sommeMech.name === 'somme' && sommeMech.explanation instanceof Array ) } type ProduitMech = AbstractMechanism & { name: 'produit' explanation: { assiette: ASTNode plafond: ASTNode facteur: ASTNode taux: ASTNode } } export function isProduitMech(node: ASTNode): node is ProduitMech { const produitMech = node as ProduitMech return ( isAbstractMechanism(produitMech) && produitMech.name === 'produit' && typeof produitMech.explanation === 'object' && typeof produitMech.explanation.assiette === 'object' && typeof produitMech.explanation.plafond === 'object' && typeof produitMech.explanation.facteur === 'object' && typeof produitMech.explanation.taux === 'object' ) } type VariationsMech = AbstractMechanism & { name: 'variations' explanation: { condition: ASTNode consequence: ASTNode }[] } export function isVariationsMech(node: ASTNode): node is VariationsMech { const variationsMech = node as VariationsMech return ( isAbstractMechanism(variationsMech) && variationsMech.name === 'variations' && variationsMech.explanation instanceof Array && variationsMech.explanation.every( variation => typeof variation === 'object' && variation.condition !== undefined && variation.consequence !== undefined ) ) } type AllegementMech = AbstractMechanism & { name: 'allègement' explanation: { abattement: ASTNode assiette: ASTNode décote: | undefined | { plafond: ASTNode taux: ASTNode } franchise: ASTNode plafond: ASTNode } } export function isAllegementMech(node: ASTNode): node is AllegementMech { const allegementMech = node as AllegementMech return ( isAbstractMechanism(allegementMech) && allegementMech.name === 'allègement' && typeof allegementMech.explanation === 'object' && allegementMech.explanation.abattement !== undefined && allegementMech.explanation.assiette !== undefined && allegementMech.explanation.franchise !== undefined && allegementMech.explanation.plafond !== undefined && (allegementMech.explanation.décote === undefined || (typeof allegementMech.explanation.décote === 'object' && allegementMech.explanation.décote.plafond !== undefined && allegementMech.explanation.décote.taux !== undefined)) ) } type BaremeMech = AbstractMechanism & { name: 'barème' explanation: { assiette: ASTNode multiplicateur: ASTNode tranches: { plafond: ASTNode taux: ASTNode }[] } } export function isBaremeMech(node: ASTNode): node is BaremeMech { const baremeMech = node as BaremeMech return ( isAbstractMechanism(baremeMech) && baremeMech.name === 'barème' && typeof baremeMech.explanation === 'object' && baremeMech.explanation.assiette !== undefined && baremeMech.explanation.multiplicateur !== undefined && baremeMech.explanation.tranches instanceof Array && baremeMech.explanation.tranches.every( tranche => typeof tranche === 'object' && tranche.plafond !== undefined && tranche.taux !== undefined ) ) } type InversionNumMech = AbstractMechanism & { name: 'inversion numérique' avec: Array // Actually: short dotted name explanation: { avec: Array> } } export function isInversionNumMech( node: ASTNode ): node is InversionNumMech { const inversionNumMech = node as InversionNumMech const isReferenceSpec = isReference as ( node: ASTNode ) => node is Reference return ( isAbstractMechanism(inversionNumMech) && inversionNumMech.name === 'inversion numérique' && inversionNumMech.avec instanceof Array && typeof inversionNumMech.explanation === 'object' && inversionNumMech.explanation.avec instanceof Array && R.all(isReferenceSpec, inversionNumMech.explanation.avec) ) } type ArrondiMech = AbstractMechanism & { name: 'arrondi' explanation: ArrondiExplanation } export function isArrondiMech(node: ASTNode): node is ArrondiMech { const arrondiMech = node as ArrondiMech return ( isAbstractMechanism(arrondiMech) && arrondiMech.name === 'arrondi' && typeof arrondiMech.explanation === 'object' && arrondiMech.explanation.decimals !== undefined && arrondiMech.explanation.value !== undefined ) } type MaxMech = AbstractMechanism & { name: 'le maximum de' explanation: Array } export function isMaxMech(node: ASTNode): node is MaxMech { const maxMech = node as MaxMech return ( isAbstractMechanism(maxMech) && maxMech.name === 'le maximum de' && maxMech.explanation instanceof Array ) } type MinMech = AbstractMechanism & { name: 'le minimum de' explanation: Array } export function isMinMech(node: ASTNode): node is MinMech { const minMech = node as MinMech return ( isAbstractMechanism(minMech) && minMech.name === 'le minimum de' && minMech.explanation instanceof Array ) } type ComposantesMech = AbstractMechanism & { name: 'composantes' explanation: Array } export function isComposantesMech(node: ASTNode): node is ComposantesMech { const composantesMech = node as ComposantesMech return ( isAbstractMechanism(composantesMech) && composantesMech.name === 'composantes' && composantesMech.explanation instanceof Array ) } type UneConditionsMech = AbstractMechanism & { name: 'une de ces conditions' explanation: Array } export function isUneConditionsMech(node: ASTNode): node is UneConditionsMech { const uneConditionsMech = node as UneConditionsMech return ( isAbstractMechanism(uneConditionsMech) && uneConditionsMech.name === 'une de ces conditions' && uneConditionsMech.explanation instanceof Array ) } type ToutesConditionsMech = AbstractMechanism & { name: 'toutes ces conditions' explanation: Array } export function isToutesConditionsMech( node: ASTNode ): node is ToutesConditionsMech { const toutesConditionsMech = node as ToutesConditionsMech return ( isAbstractMechanism(toutesConditionsMech) && toutesConditionsMech.name === 'toutes ces conditions' && toutesConditionsMech.explanation instanceof Array ) } type SyncMech = AbstractMechanism & { name: 'synchronisation' API: {} } export function isSyncMech(node: ASTNode): node is SyncMech { const syncMech = node as SyncMech return isAbstractMechanism(syncMech) && syncMech.name === 'synchronisation' } type GrilleMech = AbstractMechanism & { name: 'grille' explanation: { assiette: ASTNode multiplicateur: ASTNode tranches: { montant: ASTNode plafond: ASTNode }[] } } export function isGrilleMech(node: ASTNode): node is GrilleMech { const grilleMech = node as GrilleMech return ( isAbstractMechanism(grilleMech) && grilleMech.name === 'grille' && typeof grilleMech.explanation === 'object' && grilleMech.explanation.assiette !== undefined && grilleMech.explanation.multiplicateur !== undefined && grilleMech.explanation.tranches instanceof Array && grilleMech.explanation.tranches.every( tranche => typeof tranche === 'object' && tranche.montant !== undefined && tranche.plafond !== undefined ) ) } type TauxProgMech = AbstractMechanism & { name: 'taux progressif' explanation: { assiette: ASTNode multiplicateur: ASTNode tranches: { plafond: ASTNode taux: ASTNode }[] } } export function isTauxProgMech(node: ASTNode): node is TauxProgMech { const tauxProgMech = node as TauxProgMech return ( isAbstractMechanism(tauxProgMech) && tauxProgMech.name === 'taux progressif' && typeof tauxProgMech.explanation === 'object' && tauxProgMech.explanation.assiette !== undefined && tauxProgMech.explanation.multiplicateur !== undefined && tauxProgMech.explanation.tranches instanceof Array && tauxProgMech.explanation.tranches.every( tranche => typeof tranche === 'object' && tranche.plafond !== undefined && tranche.taux !== undefined ) ) } type DureeMech = AbstractMechanism & { name: 'Durée' explanation: { depuis: ASTNode "jusqu'à": ASTNode } } export function isDureeMech(node: ASTNode): node is DureeMech { const dureeMech = node as DureeMech return ( isAbstractMechanism(dureeMech) && dureeMech.name === 'Durée' && typeof dureeMech.explanation === 'object' && dureeMech.explanation.depuis !== undefined && dureeMech.explanation["jusqu'à"] !== undefined ) } type AnyMechanism = | RecalculMech | EncadrementMech | SommeMech | ProduitMech | VariationsMech | AllegementMech | BaremeMech | InversionNumMech | ArrondiMech | MaxMech | MinMech | ComposantesMech | UneConditionsMech | ToutesConditionsMech | SyncMech | GrilleMech | TauxProgMech | DureeMech export function isAnyMechanism( node: ASTNode ): node is AnyMechanism { return ( isRecalculMech(node) || isEncadrementMech(node) || isSommeMech(node) || isProduitMech(node) || isVariationsMech(node) || isAllegementMech(node) || isBaremeMech(node) || isInversionNumMech(node) || isArrondiMech(node) || isMaxMech(node) || isMinMech(node) || isComposantesMech(node) || isUneConditionsMech(node) || isToutesConditionsMech(node) || isSyncMech(node) || isGrilleMech(node) || isTauxProgMech(node) || isDureeMech(node) ) } function logVisit(depth: number, typeName: string, obj: {}): void { return let cleanRepr = obj if (typeof obj === 'object') { cleanRepr = JSON.stringify(obj, null) } console.log(' '.repeat(depth) + `visiting ${typeName} node ${cleanRepr}`) } export function ruleDependenciesOfNode( depth: number, node: ASTNode ): Array { function ruleDependenciesOfApplicableSi( depth: number, applicableSi: ApplicableSi ): Array { logVisit(depth, 'applicable si', '') debugger return ruleDependenciesOfNode(depth + 1, applicableSi.explanation) } function ruleDependenciesOfNonApplicableSi( depth: number, nonApplicableSi: NonApplicableSi ): Array { logVisit(depth, 'non applicable si', '') return ruleDependenciesOfNode(depth + 1, nonApplicableSi.explanation) } function ruleDependenciesOfFormule( depth: number, formule: Formule ): Array { logVisit(depth, 'formule', '') return ruleDependenciesOfNode(depth + 1, formule.explanation) } function ruleDependenciesOfValue(depth: number, value: Value): Array { logVisit(depth, 'value', value.nodeValue) return [] } function ruleDependenciesOfOperation( depth: number, operation: Operation ): Array { logVisit(depth, 'operation', operation.operationType) return R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), operation.explanation ) } function ruleDependenciesOfPossibilities( depth: number, possibilities: Possibilities ): Array { logVisit(depth, 'possibilities', possibilities.possibilités) return [] } function ruleDependenciesOfPossibilities2( depth: number, possibilities: Possibilities2 ): Array { logVisit( depth, 'possibilities2', R.filter(([k, _]) => !isNaN(parseInt(k, 10)), R.toPairs(possibilities)) ) return [] } function ruleDependenciesOfReference( depth: number, reference: Reference ): Array { logVisit(depth, 'reference', reference.dottedName) return [reference.dottedName] } function ruleDependenciesOfRecalculBroken( depth: number, recalculBroken: RecalculBroken ): Array { logVisit(depth, 'recalcul broken', recalculBroken.règle || '') return recalculBroken.règle ? [recalculBroken.règle] : [] } function ruleDependenciesOfRecalculMech( depth: number, recalculMech: RecalculMech ): Array { logVisit( depth, 'recalcul', recalculMech.explanation.recalcul.partialReference as string ) return [recalculMech.explanation.recalcul.partialReference] } function ruleDependenciesOfEncadrementMech( depth: number, encadrementMech: EncadrementMech ): Array { logVisit(depth, 'encadrement mechanism', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), [ encadrementMech.explanation.plafond, encadrementMech.explanation.plancher, encadrementMech.explanation.valeur ] ) return result } function ruleDependenciesOfSommeMech( depth: number, sommeMech: SommeMech ): Array { logVisit(depth, 'somme mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), sommeMech.explanation ) return result } function ruleDependenciesOfProduitMech( depth: number, produitMech: ProduitMech ): Array { logVisit(depth, 'produit mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), [ produitMech.explanation.assiette, produitMech.explanation.plafond, produitMech.explanation.facteur, produitMech.explanation.taux ] ) return result } function ruleDependenciesOfVariationsMech( depth: number, variationsMech: VariationsMech ): Array { logVisit(depth, 'variations mech', '??') function ruleOfVariation({ condition, consequence }: { condition: ASTNode consequence: ASTNode }): Array { return R.concat( ruleDependenciesOfNode(depth + 1, condition), ruleDependenciesOfNode(depth + 1, consequence) ) } const result = R.chain(ruleOfVariation, variationsMech.explanation) return result } function ruleDependenciesOfAllegementMech( depth: number, allegementMech: AllegementMech ): Array { logVisit(depth, 'allegement mech', '??') const subNodes = R.concat( [ allegementMech.explanation.abattement, allegementMech.explanation.assiette, allegementMech.explanation.franchise, allegementMech.explanation.plafond ], allegementMech.explanation.décote ? [ allegementMech.explanation.décote.plafond, allegementMech.explanation.décote.taux ] : [] ) const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), subNodes ) return result } function ruleDependenciesOfBaremeMech( depth: number, baremeMech: BaremeMech ): Array { logVisit(depth, 'barème mech', '??') const tranchesNodes = R.chain( ({ plafond, taux }) => [plafond, taux], baremeMech.explanation.tranches ) const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), R.concat( [ baremeMech.explanation.assiette, baremeMech.explanation.multiplicateur ], tranchesNodes ) ) return result } /** * Returns 0 dependency for _inversion numérique_ as it's not creating a logical dependency. */ function ruleDependenciesOfInversionNumMech( depth: number, inversionNumMech: InversionNumMech ): Array { logVisit(depth, 'inversion numérique', inversionNumMech.avec) return [] } function ruleDependenciesOfArrondiMech( depth: number, arrondiMech: ArrondiMech ): Array { logVisit(depth, 'arrondi mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), [arrondiMech.explanation.decimals, arrondiMech.explanation.value] ) return result } function ruleDependenciesOfMaxMech( depth: number, maxMech: MaxMech ): Array { logVisit(depth, 'max mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), maxMech.explanation ) return result } function ruleDependenciesOfMinMech( depth: number, minMech: MinMech ): Array { logVisit(depth, 'min mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), minMech.explanation ) return result } function ruleDependenciesOfComposantesMech( depth: number, composantesMech: ComposantesMech ): Array { logVisit(depth, 'composantes mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), composantesMech.explanation ) return result } function ruleDependenciesOfUneConditionsMech( depth: number, uneConditionsMech: UneConditionsMech ): Array { logVisit(depth, 'une conditions mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), uneConditionsMech.explanation ) return result } function ruleDependenciesOfToutesConditionsMech( depth: number, toutesConditionsMech: ToutesConditionsMech ): Array { logVisit(depth, 'toutes conditions mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), toutesConditionsMech.explanation ) return result } function ruleDependenciesOfSyncMech(depth: number, _: SyncMech): Array { logVisit(depth, 'sync mech', '??') return [] } function ruleDependenciesOfGrilleMech( depth: number, grilleMech: GrilleMech ): Array { logVisit(depth, 'grille mech', '??') const tranchesNodes = R.chain( ({ montant, plafond }) => [montant, plafond], grilleMech.explanation.tranches ) const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), R.concat( [ grilleMech.explanation.assiette, grilleMech.explanation.multiplicateur ], tranchesNodes ) ) return result } function ruleDependenciesOfTauxProgMech( depth: number, tauxProgMech: TauxProgMech ): Array { logVisit(depth, 'taux progressif mech', '??') const tranchesNodes = R.chain( ({ plafond, taux }) => [plafond, taux], tauxProgMech.explanation.tranches ) const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), R.concat( [ tauxProgMech.explanation.assiette, tauxProgMech.explanation.multiplicateur ], tranchesNodes ) ) return result } function ruleDependenciesOfDureeMech( depth: number, dureeMech: DureeMech ): Array { logVisit(depth, 'durée mech', '??') const result = R.chain( R.partial>(ruleDependenciesOfNode, [ depth + 1 ]), [dureeMech.explanation.depuis, dureeMech.explanation["jusqu'à"]] ) return result } if (isApplicableSi(node)) { return ruleDependenciesOfApplicableSi(depth, node) } else if (isNonApplicableSi(node)) { return ruleDependenciesOfNonApplicableSi(depth, node) } else if (isFormule(node)) { return ruleDependenciesOfFormule(depth, node) } else if (isValue(node)) { return ruleDependenciesOfValue(depth, node) } else if (isOperation(node)) { return ruleDependenciesOfOperation(depth, node) } else if (isReference(node)) { return ruleDependenciesOfReference(depth, node) } else if (isPossibilities(node)) { return ruleDependenciesOfPossibilities(depth, node) } else if (isPossibilities2(node)) { return ruleDependenciesOfPossibilities2(depth, node) } else if (isRecalculBroken(node)) { return ruleDependenciesOfRecalculBroken(depth, node) } else if (isRecalculMech(node)) { return ruleDependenciesOfRecalculMech(depth, node) } else if (isEncadrementMech(node)) { return ruleDependenciesOfEncadrementMech(depth, node) } else if (isSommeMech(node)) { return ruleDependenciesOfSommeMech(depth, node) } else if (isProduitMech(node)) { return ruleDependenciesOfProduitMech(depth, node) } else if (isVariationsMech(node)) { return ruleDependenciesOfVariationsMech(depth, node) } else if (isAllegementMech(node)) { return ruleDependenciesOfAllegementMech(depth, node) } else if (isBaremeMech(node)) { return ruleDependenciesOfBaremeMech(depth, node) } else if (isInversionNumMech(node)) { return ruleDependenciesOfInversionNumMech(depth, node) } else if (isArrondiMech(node)) { return ruleDependenciesOfArrondiMech(depth, node) } else if (isMaxMech(node)) { return ruleDependenciesOfMaxMech(depth, node) } else if (isMinMech(node)) { return ruleDependenciesOfMinMech(depth, node) } else if (isComposantesMech(node)) { return ruleDependenciesOfComposantesMech(depth, node) } else if (isUneConditionsMech(node)) { return ruleDependenciesOfUneConditionsMech(depth, node) } else if (isToutesConditionsMech(node)) { return ruleDependenciesOfToutesConditionsMech(depth, node) } else if (isSyncMech(node)) { return ruleDependenciesOfSyncMech(depth, node) } else if (isGrilleMech(node)) { return ruleDependenciesOfGrilleMech(depth, node) } else if (isTauxProgMech(node)) { return ruleDependenciesOfTauxProgMech(depth, node) } else if (isDureeMech(node)) { return ruleDependenciesOfDureeMech(depth, node) } if (node.avec) { // [XXX] - How come we meet this? Souldn't `mecanismRecalcul` (in `mecanisms.js`) build a `Recalcul` type node? console.error( `Visited a non-parsed recalcul node, to investigate: règle ${node.règle}` ) } else { debugger throw new Error( `This node doesn't have a visitor method defined: ${JSON.stringify( node, null, 4 )}` ) } return [] } function ruleDependenciesOfRuleNode( depth: number, rule: RuleNode ): Array { logVisit(depth, 'Rule', rule.dottedName) return R.chain, Name>( R.identity, R.map(x => (x ? ruleDependenciesOfNode(depth + 1, x) : []), [ rule.formule, rule['applicable si'], rule['non applicable si'] ]) ) } export function buildRulesDependencies( parsedRules: ParsedRules ): Array<[Name, Array]> { // This stringPairs thing is necessary because `toPairs` is strictly considering that // object keys are strings (same for `Object.entries`). Maybe we should build our own // `toPairs`? const stringPairs: Array<[string, RuleNode]> = R.toPairs(parsedRules) const pairs: Array<[Name, RuleNode]> = stringPairs as Array< [Name, RuleNode] > // const pairsResults: Array> = R.map( // ([_, ruleNode]: [Name, RuleNode]): Array => // ruleDependenciesOfRule(0, ruleNode), // pairs // ) return R.map( ([dottedName, ruleNode]: [Name, RuleNode]): [Name, Array] => [ dottedName, ruleDependenciesOfRuleNode(0, ruleNode) ], pairs ) }