// Finds PIS Codes using DB Lookups const db = require("../services/dbAccess.services"); const clean = require("../utils/sanitizer.utils"); import type { OB_Pis_SimpleObject } from "@owlboard/ts-types"; import { logger } from "../utils/logger.utils"; import { queryAggregate } from "./dbAccess.services"; import { getPartialEndTiplocMatchPipeline, getFullTiplocMatchPipeline, } from "../utils/pis.utils"; import { Document } from "mongodb"; export const supported = ["GW", "UK"]; async function findPisByOrigDest(start: string, end: string) { logger.debug( `pisServices.findPisByOrigDest: Searching for Orig: ${start}, Dest: ${end}` ); const firstCrs = clean.cleanApiEndpointTxt(start.toLowerCase()); const lastCrs = clean.cleanApiEndpointTxt(end.toLowerCase()); const query = { stops: { $all: [ { $elemMatch: { $eq: firstCrs } }, { $elemMatch: { $eq: lastCrs } }, ], }, $expr: { $and: [ { $eq: [{ $arrayElemAt: ["$stops", -1] }, lastCrs] }, { $eq: [{ $arrayElemAt: ["$stops", 0] }, firstCrs] }, ], }, }; const search = await db.query("pis", query); // Check for results, if none then try partial match return search; } async function findPisByCode( code: string ): Promise { logger.debug(`pisServices.findPisByCode: Searching for PIS code: ${code}`); const cleanCode = clean.removeNonNumeric(code); const query = { code: parseInt(cleanCode), }; const search = db.query("pis", query); return await search; } // Queries the database for PIS codes that match the given TIPLOC array export async function findByTiplocArray( tiplocArray: string[] ): Promise { // Firstly fix errant TIPLOCS such as RDNG4AB which will never return a result // currently only works with RDNG4AB - checks for presence of RDNG4AB first to // avoid iterating over every array ever searched for. if (tiplocArray.includes("RDNG4AB")) { fixErrantTiplocs(tiplocArray); } // PERFORMANCE NOTE: // The majority of queries will return a full match, // this means that a more performant pipeline is used // to find a full match and only then are more // complicated pipelines used to find partial matches // if the first pipeline returns nothing. try { const exactMatch = await findExactMatchByTiploc(tiplocArray); if (exactMatch) { return convertDocument(exactMatch, "none"); } else { const partialEnd = await findPartialEndMatchByTiploc(tiplocArray); if (partialEnd) { return convertDocument(partialEnd, "first"); } else { // Here, I should search for a partialStart match. For now return null. return null; } } } catch (err) { logger.error(err, "Error in findByTiplocArray"); return null; } } // Uses a pipeline to find an exact match async function findExactMatchByTiploc(array: string[]): Promise { const pipeline = getFullTiplocMatchPipeline(array); const res = await queryAggregate("pis", pipeline); return res[0]; } // Uses a pipeline to find a partial match - only supporting // codes ending with the correct stops for now. async function findPartialEndMatchByTiploc(array: string[]): Promise { const pipeline = getPartialEndTiplocMatchPipeline(array); const res = await queryAggregate("pis", pipeline); return res[0]; } function convertDocument(doc: Document, skipType: string): OB_Pis_SimpleObject { return { code: doc.code.toString(), toc: doc.toc, skipCount: doc.skipStops, skipType: skipType, }; } // Changes any instance of 'RDNG4AB' to 'RDNGSTN' function fixErrantTiplocs(input: string[]): void { input.forEach((value, index, array) => { if (value === "RDNG4AB") { array[index] = "RDNGSTN"; } // Additional substitutions can be applied here }); } module.exports = { supported, findPisByOrigDest, findPisByCode, findByTiplocArray, };