TimetableAPI-Upgrade (#64)

Partial implementation of: #47

The 2022.2.1 release currently live is based off of the TimetableAPI-Upgrade branch.

Partial PIS code matching is now implemented in cases where x number of stops need skipping at the start of a route.

Reviewed-on: #64
This commit is contained in:
Fred Boniface
2024-02-11 15:53:12 +00:00
parent 90baf1b13a
commit c147d9c50c
40 changed files with 717 additions and 505 deletions

View File

@@ -0,0 +1,7 @@
# Translators
The utilities in the `translators` folder translate the upstream API into the downstream API.
The aim of the translators is to ensure a consistent data format while removing any unused data to keep the response sizes as small as possible.
Translators are kept in separate files so changes can be made in one place. Each translator exports a single function 'transform()'. This function accepts data from the upstream API and uses other functions in the file to build the API response object before returning that object to the caller.

View File

@@ -0,0 +1,220 @@
import type {
StaffLdb,
NrccMessage,
TrainServices,
ServiceLocation,
} from "@owlboard/ts-types";
import { tz } from "moment-timezone";
import { removeNewlineAndPTag } from "../../newSanitizer";
import { logger } from "../../logger.utils";
/// I do not yet have a type defined for any of the input object
export function transform(input: any): StaffLdb | null {
console.time("StaffLdb Transformation");
const data = input.GetBoardResult;
let output: StaffLdb;
try {
output = {
generatedAt: transformDateTime(data?.generatedAt) || new Date(),
locationName: data?.locationName || "Not Found",
stationManagerCode: data?.stationManagerCode || "UK",
nrccMessages: transformNrcc(data?.nrccMessages) || undefined,
trainServices: transformTrainServices(data?.trainServices) || undefined,
busServices: transformTrainServices(data?.busServices) || undefined,
ferryServices: transformTrainServices(data?.ferryServices) || undefined,
};
console.timeEnd("StaffLdb Transformation");
if (output.locationName !== "Not Found") {
return output;
}
} catch (err) {
logger.error(err, "utils/translators/ldb/staffLdb.transform");
}
console.timeEnd("StaffLdb Transformation");
return null;
}
function transformDateTime(input: string): Date {
logger.trace("utils/translators/ldb/staffLdb.transformDateTime: Running");
return new Date(input);
}
function transformNrcc(input: any): NrccMessage[] | undefined {
logger.trace("utils/translators/ldb/staffLdb.transformNrcc: Running");
if (input === undefined) {
return input;
}
let output: NrccMessage[] = [];
let messages = input;
if (!Array.isArray(input?.message)) {
messages = [input?.message];
}
if (messages.length) {
for (const item of messages) {
let message: NrccMessage = {
severity: item?.severity,
xhtmlMessage: removeNewlineAndPTag(item?.xhtmlMessage),
};
output.push(message);
}
return output;
}
return undefined;
}
function transformTrainServices(input: any): TrainServices[] {
logger.trace(
"utils/translators/ldb/staffLdb.transformTrainServices: Running"
);
let services: any = input?.service;
let output: TrainServices[] = [];
if (services === undefined) {
return output;
}
if (!Array.isArray(input.service)) {
services = [input.service];
}
for (const service of services) {
const times = parseTimes(service);
const trainService: TrainServices = {
rid: service?.rid,
uid: service?.uid,
trainid: service?.trainid,
operatorCode: service?.operatorCode || "UK",
platform: service?.platform || "-",
platformIsHidden: service?.platformIsHidden,
serviceIsSupressed: checkIsSupressed(service),
origin: transformLocation(service?.origin),
destination: transformLocation(service?.destination),
length: calculateLength(service),
isCancelled: service?.isCancelled,
cancelReason: service?.cancelReason,
delayReason: service?.delayReason,
arrivalType: service?.arrivalType,
departureType: service?.departureType,
sta: times.sta,
eta: times.eta,
ata: times.ata,
std: times.std,
etd: times.etd,
atd: times.atd,
};
Object.keys(trainService).forEach(
(key) => trainService[key] === undefined && delete trainService[key]
);
output.push(trainService);
}
return output;
}
function checkIsSupressed(service: TrainServices): string | undefined {
logger.trace("utils/translators/ldb/staffStation.checkIsSupressed: Running");
if (
service.serviceIsSupressed === "true" ||
service.isPassengerService === "false"
) {
return "true";
} else {
return undefined;
}
}
function transformLocation(input: any): ServiceLocation[] {
logger.trace("utils/translators/ldb/staffStation.transformLocation: Running");
let output: ServiceLocation[] = [];
let locations: any[] = input.location;
if (!Array.isArray(input.location)) {
locations = [input.location];
}
for (const item of locations) {
const location: ServiceLocation = {
tiploc: item?.tiploc,
};
if (item?.via) {
location.via = item.via;
}
output.push(location);
}
return output;
}
export function calculateLength(input: any): number | undefined {
logger.trace("utils/translators/ldb/staffStation.calculateLength: Running");
let length: number;
if (input?.length) {
length = input.length;
return Number(length);
}
if (input?.formation?.coaches?.coach) {
length = input.formation.coaches.coach.length;
return Number(length);
}
return undefined;
}
function transformUnspecifiedDateTime(input: string): Date | undefined {
logger.trace(
"utils/translators/ldb/staffStation.transformUnspecifiedDateTime: Running"
);
if (!input) {
return undefined;
}
const date = tz(input, "Europe/London"); // Want to be creating a moment object using moment.tz(...)
return date.toDate();
}
function parseTimes(service: TrainServices) {
logger.trace("utils/translators/ldb/staffStation.parseTimes: Running");
let { sta, eta, ata, std, etd, atd } = Object.fromEntries(
Object.entries(service).map(([key, value]) => [
key,
transformUnspecifiedDateTime(value),
])
);
let etaResult: Date | undefined | string = eta;
let ataResult: Date | undefined | string = ata;
let etdResult: Date | undefined | string = etd;
let atdResult: Date | undefined | string = atd;
if (sta) {
if (
eta !== undefined &&
Math.abs(eta.getTime() - sta.getTime()) / 60000 <= 1.5
) {
etaResult = "RT";
}
if (
ata !== undefined &&
Math.abs(ata.getTime() - sta.getTime()) / 60000 <= 1.5
) {
ataResult = "RT";
}
}
if (std) {
if (
etd !== undefined &&
Math.abs(etd.getTime() - std.getTime()) / 60000 <= 1.5
) {
etdResult = "RT";
}
if (
atd !== undefined &&
Math.abs(atd.getTime() - std.getTime()) / 60000 <= 1.5
) {
atdResult = "RT";
}
}
return {
sta: sta,
eta: etaResult,
ata: ataResult,
std: std,
etd: etdResult,
atd: atdResult,
};
}

View File

@@ -0,0 +1,76 @@
import type {
Service,
OB_TrainTT_service,
OB_Pis_SimpleObject,
OB_TrainTT_stopDetail,
Stop,
} from "@owlboard/ts-types";
import { logger } from "../../logger.utils";
export function formatTimetableDetail(
service: Service,
pis: OB_Pis_SimpleObject | null
): OB_TrainTT_service {
const formattedService: OB_TrainTT_service = {
stpIndicator: service.stpIndicator,
operator: service.operator,
trainUid: service.trainUid,
headcode: service.headcode,
powerType: service.powerType,
planSpeed: convertStringToNumber(service.planSpeed),
scheduleStart: service.scheduleStartDate,
scheduleEnd: service.scheduleEndDate,
daysRun: service.daysRun,
stops: formatStops(service.stops),
vstp: service.vstp,
};
if (pis) {
formattedService.pis = pis;
}
return formattedService;
}
function formatStops(stops: Stop[]): OB_TrainTT_stopDetail[] {
// Cleanly coerce Stop[] to OB_TrainTT_stopDetail[]
const formattedStops: OB_TrainTT_stopDetail[] = [];
for (const stop of stops) {
formattedStops.push(formatStopTimes(stop));
}
return formattedStops;
}
function formatStopTimes(stop: Stop): OB_TrainTT_stopDetail {
// Cleanly converts a single stop to a stopdetail object
let formattedStop: OB_TrainTT_stopDetail = {
tiploc: stop.tiploc,
isPublic: false,
};
if (stop.publicArrival) {
formattedStop.publicArrival = stop.publicArrival;
formattedStop.isPublic = true;
}
if (stop.publicDeparture) {
formattedStop.publicDeparture = stop.publicDeparture;
formattedStop.isPublic = true;
}
if (stop.wttArrival) {
formattedStop.wttArrival = stop.wttArrival;
}
if (stop.wttDeparture) {
formattedStop.wttDeparture = stop.wttDeparture;
}
return formattedStop;
}
function convertStringToNumber(str: string): number {
const number = parseFloat(str);
if (isNaN(number)) {
return 0;
} else {
return number;
}
}