Add support for partial PIS matches by skipping first n stops

Signed-off-by: Fred Boniface <fred@fjla.uk>
This commit is contained in:
Fred Boniface 2023-11-29 22:58:08 +00:00
parent 820c8b0dd4
commit e01cdd2f7e
5 changed files with 180 additions and 71 deletions

View File

@ -33,7 +33,7 @@
"zlib": "^1.0.5"
},
"devDependencies": {
"@owlboard/ts-types": "^0.1.0",
"@owlboard/ts-types": "^0.1.2",
"@types/jest": "^29.5.3",
"eslint": "^8.39.0",
"jest": "^29.6.2",

View File

@ -48,13 +48,13 @@ async function random(req, res, next) {
}
async function testingTiplocArray(req, res, next) {
let array = JSON.parse(req.params.array)
return pis.findByTiplocArray(array)
let array = JSON.parse(req.params.array);
res.json(await pis.findByTiplocArray(array));
}
module.exports = {
byStartEndCRS,
byCode,
random,
testingTiplocArray
testingTiplocArray,
};

View File

@ -5,9 +5,9 @@ module.exports = async function authCheck(req, res, next) {
//log.out("authMiddlewares: Checking authentication", "dbug");
logger.logger.debug("Auth check starting");
if (process.env.NODE_ENV === "development") {
logger.logger.warn("DEVELOPMENT MODE - AUTHENTICATION DISABLED")
logger.logger.warn("DEVELOPMENT MODE - AUTHENTICATION DISABLED");
res.isAuthed = true;
return next()
return next();
}
try {
var uuid = req.headers.uuid;

View File

@ -3,8 +3,15 @@
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";
const supported = ["GW", "UK"];
@ -33,7 +40,9 @@ async function findPisByOrigDest(start: string, end: string) {
return search;
}
async function findPisByCode(code: string) {
async function findPisByCode(
code: string
): Promise<OB_Pis_SimpleObject | null> {
logger.debug(`pisServices.findPisByCode: Searching for PIS code: ${code}`);
const cleanCode = clean.removeNonNumeric(code);
const query = {
@ -43,75 +52,73 @@ async function findPisByCode(code: string) {
return await search;
}
export async function findByTiplocArray(tiplocArray: string[]) {
// If any of the TIPLOCS in an array is RDG4AB - Replace it with READING.
// ^^ Double check those though before doing it.
const query = {
tiplocs: tiplocArray,
};
const res = await db.query("pis", query);
// If res is empty then try to find partial match
if (res.length) {
return res;
} else {
let partial = await findPartialMatchByTiploc(tiplocArray);
return partial;
// Queries the database for PIS codes that match the given TIPLOC array
export async function findByTiplocArray(
tiplocArray: string[]
): Promise<OB_Pis_SimpleObject | null> {
// 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;
}
}
async function findPartialMatchByTiploc(tiplocArray: string[]) {
// Do some magic here, similar to findPisByOrigDest but
// ensuring that the stops in the array match the last
// x number of items in the array.
// Uses a pipeline to find an exact match
async function findExactMatchByTiploc(array: string[]): Promise<Document> {
const pipeline = getFullTiplocMatchPipeline(array);
const res = await queryAggregate("pis", pipeline);
return res[0];
}
const pipeline = [
{
$addFields: {
reversedTiplocs: {
$reverseArray: "$tiplocs",
},
reversedQueryArray: {
$reverseArray: tiplocArray,
},
},
},
{
$match: {
$expr: {
$gt: [
{
$indexOfArray: [
"$reversedTiplocs",
{
$arrayElemAt: ["$reversedQueryArray", 0],
}
],
},
-1,
],
},
},
},
{
$project: {
code: 1,
skipStops: {
$subtract: [
{
$size: "$tiplocs",
},
{
$size: tiplocArray,
},
],
},
},
},
];
// Uses a pipeline to find a partial match - only supporting
// codes ending with the correct stops for now.
async function findPartialEndMatchByTiploc(array: string[]): Promise<Document> {
const pipeline = getPartialEndTiplocMatchPipeline(array);
const res = await queryAggregate("pis", pipeline);
return res[0];
}
const res = await queryAggregate('pis', pipeline)
console.log(JSON.stringify(res))
return res
function convertDocument(doc: Document, skipType: string): OB_Pis_SimpleObject {
return {
code: doc.code,
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";
}
});
}
module.exports = {

102
src/utils/pis.utils.ts Normal file
View File

@ -0,0 +1,102 @@
export function getPartialEndTiplocMatchPipeline(query: string[]) {
return [
{
$match: {
tiplocs: {
$all: query,
},
},
},
{
$addFields: {
reversedTiplocs: {
$reverseArray: "$tiplocs",
},
query: {
$literal: query,
},
},
},
{
$addFields: {
reversedQuery: {
$reverseArray: "$query",
},
},
},
{
$match: {
$expr: {
$eq: [
{
$slice: [
"$reversedTiplocs",
0,
{
$size: "$reversedQuery",
},
],
},
"$reversedQuery",
],
},
},
},
{
$addFields: {
skipStops: {
$subtract: [
{
$size: "$tiplocs",
},
{
$size: "$reversedQuery",
},
],
},
},
},
{
$sort: {
skipStops: 1,
},
},
{
$limit: 1,
},
{
$project: {
code: 1,
skipStops: 1,
toc: 1,
_id: 0,
},
},
];
}
export function getFullTiplocMatchPipeline(query: string[]) {
return [
{
$match: {
tiplocs: query,
},
},
{
$limit: 1,
},
{
$addFields: {
skipStops: 0,
},
},
{
$project: {
code: 1,
toc: 1,
skipStops: 1,
_id: 0,
},
},
];
}