201 lines
5.6 KiB
TypeScript
201 lines
5.6 KiB
TypeScript
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<SimpleService[]> {
|
|
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<OB_Pis_SimpleObject | null> {
|
|
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:
|