import { entries , compact , flatMap , groupBy , map , mapValues , values } from 'lodash' ; import { Task } from '../types/tasks' ; import { Aliases , ModuleTitle , RawLesson , Semester } from '../types/modules' ; import { LessonWithModuleCode , ModuleAliases , SemesterModuleData } from '../types/mapper' ; import { Availability , OCCUPIED , VenueInfo , VenueLesson } from '../types/venues' ; import { Cache } from '../types/persist' ; import BaseTask from './BaseTask' ; import config from '../config' ; import { getTimeRange } from '../utils/time' ; import { mergeDualCodedModules , modulesToAvoidMerging } from '../utils/data' ; import { union } from '../utils/set' ; /**
 * Convert module timetable into venue availability and insert it into venues
 */ export function extractVenueAvailability ( timetable ) { const filteredLessons = timetable . filter ( ( lesson ) => lesson . venue ) ; const groupByVenue = groupBy ( filteredLessons , ( lesson ) => lesson . venue ) ; return mapValues ( groupByVenue , ( venueLessons ) => { const lessonWithoutVenue = venueLessons . map ( ( { venue , covidZone , ... lesson } ) => lesson ) ; const groupByDay = groupBy ( lessonWithoutVenue , ( lesson ) => lesson . day ) ; return map ( groupByDay , ( classes , day ) => { const availability = { } ; for ( const lesson of classes ) { getTimeRange ( lesson . startTime , lesson . endTime ) . forEach ( ( time ) => { availability [ time ] = OCCUPIED ; } ) ; } return { day , classes , availability , } ; } ) ; } ) ; } type Input = SemesterModuleData [ ] ; interface Output { venues : VenueInfo ; aliases : ModuleAliases ; } const cacheExpiry = 7 * 24 * 60 ; /**
 * Map timetable lessons to venues to create a map of when each venue is being
 * used, as well as help identify dual-coded modules
 *
 * Output:
 * - <semester>/venues.json
 * - <semester>/venueInformation.json
 */ export default class CollateVenues extends BaseTask implements Task < Input , Output > { semester : Semester ; academicYear : string ; aliasCache : Cache < Aliases > ; get name ( ) { return `Collating venues for semester ${ this . semester }` ; } constructor ( semester , academicYear = config . academicYear ) { super ( academicYear ) ; this . semester = semester ; this . academicYear = academicYear ; this . aliasCache = this . getCache ( `semester-${ semester }-aliases` , cacheExpiry ) ; this . logger = this . rootLogger . child ( { semester , task : CollateVenues . name , year : academicYear , } ) ; } async run ( input ) { this . logger . info ( `Collating venues for ${ this . academicYear } semester ${ this . semester }` ) ; const venueLessons = compact ( flatMap < SemesterModuleData , LessonWithModuleCode | undefined > ( input , ( module ) => { if ( ! module . semesterData ) return undefined ; return module . semesterData . timetable . map ( ( lesson ) => ( { ... lesson , moduleCode : module . moduleCode , } ) ) ; } ) ) ; const venues = extractVenueAvailability ( venueLessons ) ; const moduleCodeToTitle = { } ; input . forEach ( ( module ) => { moduleCodeToTitle [ module . moduleCode ] = module . module . title ; } ) ; const allAliases = { } ; for ( const venue of values ( venues ) ) { for ( const availability of venue ) { const { lessons , aliases } = mergeDualCodedModules ( availability . classes ) ; availability . classes = lessons ; for ( const [ moduleCode , alias ] of entries ( aliases ) ) { const title = moduleCodeToTitle [ moduleCode ] ; const filteredAliases = Array . from ( alias ) . filter ( ( module ) => ! modulesToAvoidMerging . has ( title ) && title === moduleCodeToTitle [ module ] ) ; if ( filteredAliases . length ) { allAliases [ moduleCode ] = union ( allAliases [ moduleCode ] || new Set ( ) , new Set ( filteredAliases ) ) ; } } } } const outputAliases = mapValues ( allAliases , ( moduleCodes ) => Array . from ( moduleCodes ) ) ; const venueList = Object . keys ( venues ) ; await Promise . all ( [ this . io . venueInformation ( this . semester , venues ) , this . io . venueList ( this . semester , venueList ) , this . aliasCache . write ( mapValues ( outputAliases , ( set ) : string [ ] => Array . from ( set ) ) ) , ] ) ; return { venues , aliases : allAliases } ; } }