import { logger } from "../utils/logger.utils"; import { findByTiplocArray, supported } from "./pis.services"; import { queryAggregate } from "./dbAccess.services"; import { getFindByHeadcodePipeline, getFindByTrainUidPipeline, } from "../utils/trainService.utils"; import { removeNonAlphanumeric } from "../utils/sanitizer.utils"; import { formatTimetableDetail } from "../utils/processors/timetable/timetableProcessor.utils"; import type { TrainServices, Service, Stop, SimpleService, OB_Pis_SimpleObject, } from "@owlboard/ts-types"; export async function findByHeadcode( headcode: string, date: Date | string ): Promise { const sanitizedHeadcode = removeNonAlphanumeric(headcode); logger.debug( `trainServices.findByHeadcode: Searching for trains by headcode: ${headcode}` ); // If 'now' then generate a new Date now, else use the provided date, then set time to 1200. const searchDate = date === "now" ? new Date() : new Date(date); searchDate.setHours(12, 0, 0); // Get the 'shortDay' const shortDay = getShortDay(searchDate); const query = { headcode: sanitizedHeadcode.toUpperCase(), scheduleStartDate: { $lte: searchDate }, scheduleEndDate: { $gte: searchDate }, daysRun: { $in: [shortDay] }, }; const pipeline = getFindByHeadcodePipeline(query); const result: SimpleService[] = (await queryAggregate( "timetable", pipeline )) as SimpleService[]; const services = filterServices(result); return services; } export async function findByTrainUid( uid: string, date: Date | string = new Date() ) { // Set the correct date - whether a date or "now" was passed to function let queryDate: Date; if (date === 'now') { queryDate = new Date(); } else if (date instanceof Date) { queryDate = date; } else { queryDate = new Date(date); } // Build query const query = { trainUid: uid.toUpperCase(), scheduleStartDate: { $lte: queryDate }, scheduleEndDate: { $gte: queryDate }, daysRun: { $in: [getShortDay(queryDate)] }, }; logger.debug(query) const pipeline = getFindByTrainUidPipeline(query); const result = (await queryAggregate("timetable", pipeline)) as Service[]; let services = filterServices(result) as Service[]; // Check if the operator is on the supported TOC list for PIS Codes - if so, call the fetchPisCode function. let pis: OB_Pis_SimpleObject | null; if (supported.includes(services[0]?.operator)) { pis = await fetchPisCode(services[0]?.stops); } else { pis = null; } console.log(JSON.stringify(services[0])) return formatTimetableDetail(services[0], pis); } // Internal Functions: // Filters out non-passenger stops and then uses the stop array to request a PIS code for the service async function fetchPisCode( stops: Stop[] ): Promise { let tiplocList: string[] = []; for (const stop in stops) { if (stops[stop]["isPublic"]) tiplocList.push(stops[stop]["tiploc"]); } // Check if no public stops - then it should use an ECS headcode let pisData: OB_Pis_SimpleObject | null; if (tiplocList.length) { pisData = await findByTiplocArray(tiplocList); } else { pisData = { toc: "GW", skipCount: 0, code: randomEcsPis(), }; } if (!pisData) { logger.debug(tiplocList, "No PIS found for service") } return pisData; } // Picks a random choice of the ECS PIS Codes function randomEcsPis(): string { const options = ["0015", "9997"]; const randomValue = Math.floor(Math.random() * 2); return options[randomValue]; } // Outputs the standard 'shortday' string from a Date. function getShortDay(day: Date): string { const dayMap = ["su", "m", "t", "w", "th", "f", "s"]; const shortDay = dayMap[day.getDay()]; return shortDay; } // Filters services using their STP indicator so that over-riding entries are returned correctly function filterServices(services: SimpleService[]): SimpleService[] { let stpIndicators: Record< string, { hasC: boolean; hasN: boolean; hasO: boolean; hasP: boolean } > = {}; let filteredServices: SimpleService[] = []; console.log(services) for (const service of services) { const trainUid = service["trainUid"], stpIndicator = service["stpIndicator"]; // Creates the stpIndicators array: if (!stpIndicators[trainUid]) { stpIndicators[trainUid] = { hasC: false, hasN: false, hasO: false, hasP: false, }; } if (stpIndicator === "C") { stpIndicators[trainUid].hasC = true; } if (stpIndicator === "N") { stpIndicators[trainUid].hasN = true; } if (stpIndicator === "O") { stpIndicators[trainUid].hasO = true; } if (stpIndicator === "P") { stpIndicators[trainUid].hasP = true; } } // Iterate each service, and only output one service matching each trainUid, // C > N > O > P is the order, with C being prioritised over other STP types. for (const service of services) { const trainUid = service["trainUid"]; const thisStpIndicators = stpIndicators[trainUid]; const stpIndicator = service["stpIndicator"]; if (stpIndicator === "C") { filteredServices.push(service); } else if (stpIndicator === "N" && !thisStpIndicators.hasC) { filteredServices.push(service); } else if ( stpIndicator === "O" && !thisStpIndicators.hasC && !thisStpIndicators.hasN ) { filteredServices.push(service); } else if ( stpIndicator === "P" && !thisStpIndicators.hasC && !thisStpIndicators.hasN && !thisStpIndicators.hasO ) { filteredServices.push(service); } } return filteredServices; } // Local Types: