import { entries , uniq } from 'lodash' ; import { Module , ModuleCode , PrereqTree } from '../../types/modules' ; import { ModuleWithoutTree } from '../../types/mapper' ; import rootLogger , { Logger } from '../logger' ; import { MODULE_REGEX , OPERATORS_REGEX } from './constants' ; import parseString from './parseString' ; import normalizeString from './normalizeString' ; import { flattenTree } from './tree' ; /**
 * Generate the following fields for modules:
 *
 * FulfillRequirements: modules that cannot be taken until this module is fulfilled
 * PrereqTree: different format of ParsedPrerequisite
 */ export type PrereqTreeMap = { [ moduleCode ] : PrereqTree ; } ; const logger = rootLogger . child ( { service : 'requisite-tree' , } ) ; const RESTRICTED_KEYWORDS = [ 'USP' , 'Cohort' , 'cohort' , 'AY20' , 'Qualifying English Test' , 'grade' , 'Grade' , 'At least a B-' , 'Honours eligibility requirements' , 'MC' , '4 out of the 5' , '4 of the 5' , 'not' , 'NOT' , 'Not' , ] ; function parse ( data , subLogger ) { const results = { } ; for ( const module of data ) { const { moduleCode , prerequisite : value } = module ; if ( value && MODULE_REGEX . exec ( value ) && ! RESTRICTED_KEYWORDS . some ( ( keyword ) => value . includes ( keyword ) ) ) { const normalizedValue = normalizeString ( value , moduleCode ) ; const moduleLog = subLogger . child ( { moduleCode } ) ; const parsedValue = parseString ( normalizedValue , moduleLog ) ; if ( parsedValue ) { results [ module . moduleCode ] = parsedValue ; } } } return results ; } /**
 * Insert the PrereqTree and FulfillRequirements properties to Module objects
 */ export function insertRequisiteTree ( modules , prerequisites ) { const fulfillModulesMap = { } ; for ( const module of modules ) { fulfillModulesMap [ module . moduleCode ] = new Set ( ) ; } for ( const [ moduleCode , prereqs ] of entries ( prerequisites ) ) { for ( const fulfillsModule of flattenTree ( prereqs ) ) { if ( fulfillModulesMap [ fulfillsModule ] ) { fulfillModulesMap [ fulfillsModule ] . add ( moduleCode ) ; } } } for ( const module of modules ) { const { moduleCode } = module ; if ( prerequisites [ moduleCode ] ) { module . prereqTree = prerequisites [ moduleCode ] ; } if ( fulfillModulesMap [ moduleCode ] . size > 0 ) { module . fulfillRequirements = Array . from ( fulfillModulesMap [ moduleCode ] ) ; } } return modules ; } export default async function generatePrereqTree ( allModules ) { const moduleCodes = uniq ( allModules . map ( ( module ) => module . moduleCode ) ) ; for ( const moduleCode of moduleCodes ) { const isModule = MODULE_REGEX . test ( moduleCode ) ; if ( ! isModule ) { throw new Error ( `Module ${ moduleCode }'s module code does not match regex.` ) ; } if ( OPERATORS_REGEX . test ( moduleCode ) ) { throw new Error ( `Module ${ moduleCode }'s module code contains operators.` ) ; } } const prerequisites = parse ( allModules , logger ) ; const modules = insertRequisiteTree ( allModules , prerequisites ) ; return modules ; }