mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-19 08:50:29 +00:00
Implement updateEtasBasedOnHistoricalData and add a test for it
This commit is contained in:
@@ -425,21 +425,48 @@ export class RedisShuttleRepository extends EventEmitter implements ShuttleGette
|
|||||||
public async addOrUpdateShuttle(
|
public async addOrUpdateShuttle(
|
||||||
shuttle: IShuttle,
|
shuttle: IShuttle,
|
||||||
travelTimeTimestamp = Date.now(),
|
travelTimeTimestamp = Date.now(),
|
||||||
|
referenceCurrentTime = new Date(),
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const key = this.createShuttleKey(shuttle.id);
|
const key = this.createShuttleKey(shuttle.id);
|
||||||
await this.redisClient.hSet(key, this.createRedisHashFromShuttle(shuttle));
|
await this.redisClient.hSet(key, this.createRedisHashFromShuttle(shuttle));
|
||||||
|
|
||||||
await this.updateHistoricalEtasForShuttle(shuttle, travelTimeTimestamp);
|
await this.updateHistoricalEtasForShuttle(shuttle, travelTimeTimestamp);
|
||||||
await this.updateEtasBasedOnHistoricalData(shuttle);
|
await this.updateEtasBasedOnHistoricalData(shuttle, referenceCurrentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateEtasBasedOnHistoricalData(
|
private async updateEtasBasedOnHistoricalData(
|
||||||
shuttle: IShuttle,
|
shuttle: IShuttle,
|
||||||
|
referenceCurrentTime: Date = new Date(),
|
||||||
) {
|
) {
|
||||||
// Based on historical data for the key provided by this.createHistoricalEtaTimeSeriesKey(routeId, fromStopId, toStopId);
|
const oneWeekAgo = new Date(referenceCurrentTime.getTime() - (60 * 60 * 24 * 7 * 1000))
|
||||||
// Call this.addOrUpdateEta with the correct information
|
|
||||||
// "Historical data" being averaged time taken for the current hour of the same day
|
const lastStopArrival = await this.getShuttleLastStopArrival(shuttle)
|
||||||
// in the past week, based on the reference time
|
if (lastStopArrival == undefined) return;
|
||||||
|
|
||||||
|
const lastOrderedStop = await this.getOrderedStopByRouteAndStopId(shuttle.routeId, lastStopArrival.stopId);
|
||||||
|
const nextStop = lastOrderedStop?.nextStop;
|
||||||
|
if (nextStop == null) return;
|
||||||
|
|
||||||
|
const travelTimeSeconds = await this.getAverageTravelTimeSeconds({
|
||||||
|
routeId: shuttle.routeId,
|
||||||
|
fromStopId: lastStopArrival.stopId,
|
||||||
|
toStopId: nextStop.stopId,
|
||||||
|
}, {
|
||||||
|
from: oneWeekAgo,
|
||||||
|
to: new Date(oneWeekAgo.getTime() + (60 * 60 * 1000))
|
||||||
|
});
|
||||||
|
if (travelTimeSeconds == undefined) return;
|
||||||
|
|
||||||
|
const elapsedTimeMs = referenceCurrentTime.getTime() - lastStopArrival.timestamp.getTime();
|
||||||
|
const predictedTimeSeconds = travelTimeSeconds - (elapsedTimeMs / 1000);
|
||||||
|
|
||||||
|
await this.addOrUpdateEta({
|
||||||
|
secondsRemaining: predictedTimeSeconds,
|
||||||
|
shuttleId: shuttle.id,
|
||||||
|
stopId: nextStop.stopId,
|
||||||
|
systemId: nextStop.systemId,
|
||||||
|
updatedTime: new Date(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateHistoricalEtasForShuttle(
|
private async updateHistoricalEtasForShuttle(
|
||||||
@@ -469,7 +496,7 @@ export class RedisShuttleRepository extends EventEmitter implements ShuttleGette
|
|||||||
public async getAverageTravelTimeSeconds(
|
public async getAverageTravelTimeSeconds(
|
||||||
{ routeId, fromStopId, toStopId }: ShuttleTravelTimeDataIdentifier,
|
{ routeId, fromStopId, toStopId }: ShuttleTravelTimeDataIdentifier,
|
||||||
{ from, to }: ShuttleTravelTimeDateFilterArguments,
|
{ from, to }: ShuttleTravelTimeDateFilterArguments,
|
||||||
): Promise<number> {
|
): Promise<number | undefined> {
|
||||||
const timeSeriesKey = this.createHistoricalEtaTimeSeriesKey(routeId, fromStopId, toStopId);
|
const timeSeriesKey = this.createHistoricalEtaTimeSeriesKey(routeId, fromStopId, toStopId);
|
||||||
const fromTimestamp = from.getTime();
|
const fromTimestamp = from.getTime();
|
||||||
const toTimestamp = to.getTime();
|
const toTimestamp = to.getTime();
|
||||||
@@ -493,7 +520,8 @@ export class RedisShuttleRepository extends EventEmitter implements ShuttleGette
|
|||||||
|
|
||||||
throw new Error(`No historical data found for route ${routeId} from stop ${fromStopId} to stop ${toStopId}`);
|
throw new Error(`No historical data found for route ${routeId} from stop ${fromStopId} to stop ${toStopId}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to get average travel time: ${error instanceof Error ? error.message : String(error)}`);
|
console.warn(`Failed to get average travel time: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { beforeEach, describe, it, expect, afterEach } from "@jest/globals";
|
import { beforeEach, describe, it, expect, afterEach } from "@jest/globals";
|
||||||
import { RedisShuttleRepository } from "../RedisShuttleRepository";
|
import { RedisShuttleRepository } from "../RedisShuttleRepository";
|
||||||
import { generateMockShuttles } from "../../../../testHelpers/mockDataGenerators";
|
import { generateMockShuttles } from "../../../../testHelpers/mockDataGenerators";
|
||||||
|
import { IOrderedStop } from "../../../entities/ShuttleRepositoryEntities";
|
||||||
|
|
||||||
describe("RedisShuttleRepository", () => {
|
describe("RedisShuttleRepository", () => {
|
||||||
let repository: RedisShuttleRepository;
|
let repository: RedisShuttleRepository;
|
||||||
@@ -44,22 +45,27 @@ describe("RedisShuttleRepository", () => {
|
|||||||
await repository.addOrUpdateStop(stop1);
|
await repository.addOrUpdateStop(stop1);
|
||||||
await repository.addOrUpdateStop(stop2);
|
await repository.addOrUpdateStop(stop2);
|
||||||
|
|
||||||
const orderedStop1 = {
|
const orderedStop1: IOrderedStop = {
|
||||||
routeId: route.id,
|
routeId: route.id,
|
||||||
stopId: stop1.id,
|
stopId: stop1.id,
|
||||||
position: 1,
|
position: 1,
|
||||||
systemId: systemId,
|
systemId: systemId,
|
||||||
updatedTime: new Date(),
|
updatedTime: new Date(),
|
||||||
};
|
};
|
||||||
const orderedStop2 = {
|
const orderedStop2: IOrderedStop = {
|
||||||
routeId: route.id,
|
routeId: route.id,
|
||||||
stopId: stop2.id,
|
stopId: stop2.id,
|
||||||
position: 2,
|
position: 2,
|
||||||
systemId: systemId,
|
systemId: systemId,
|
||||||
updatedTime: new Date(),
|
updatedTime: new Date(),
|
||||||
};
|
};
|
||||||
|
orderedStop1.nextStop = orderedStop2;
|
||||||
|
orderedStop1.previousStop = orderedStop2;
|
||||||
|
orderedStop2.nextStop = orderedStop1;
|
||||||
|
orderedStop2.previousStop = orderedStop1;
|
||||||
await repository.addOrUpdateOrderedStop(orderedStop1);
|
await repository.addOrUpdateOrderedStop(orderedStop1);
|
||||||
await repository.addOrUpdateOrderedStop(orderedStop2);
|
await repository.addOrUpdateOrderedStop(orderedStop2);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
route,
|
route,
|
||||||
systemId,
|
systemId,
|
||||||
@@ -90,7 +96,7 @@ describe("RedisShuttleRepository", () => {
|
|||||||
|
|
||||||
it("updates how long the shuttle took to get from one stop to another", async () => {
|
it("updates how long the shuttle took to get from one stop to another", async () => {
|
||||||
const { route, systemId, stop2, stop1 } = await setupRouteAndOrderedStops();
|
const { route, systemId, stop2, stop1 } = await setupRouteAndOrderedStops();
|
||||||
|
|
||||||
// Start the shuttle at stop 1, then have it move to stop 2
|
// Start the shuttle at stop 1, then have it move to stop 2
|
||||||
const shuttle = {
|
const shuttle = {
|
||||||
id: "sh1",
|
id: "sh1",
|
||||||
@@ -120,6 +126,39 @@ describe("RedisShuttleRepository", () => {
|
|||||||
});
|
});
|
||||||
expect(travelTime).toEqual(15 * 60)
|
expect(travelTime).toEqual(15 * 60)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("adds an ETA entry based on historical data", async () => {
|
||||||
|
const { route, systemId, stop1, stop2 } = await setupRouteAndOrderedStops();
|
||||||
|
|
||||||
|
// Start the shuttle at stop 1, then have it move to stop 2
|
||||||
|
const shuttle = {
|
||||||
|
id: "sh1",
|
||||||
|
name: "Shuttle 1",
|
||||||
|
routeId: route.id,
|
||||||
|
systemId: systemId,
|
||||||
|
coordinates: stop1.coordinates,
|
||||||
|
orientationInDegrees: 0,
|
||||||
|
updatedTime: new Date(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstStopArrivalTime = new Date(2025, 0, 1, 12, 0, 0);
|
||||||
|
await repository.addOrUpdateShuttle(shuttle, firstStopArrivalTime.getTime());
|
||||||
|
|
||||||
|
shuttle.coordinates = stop2.coordinates;
|
||||||
|
// 15-minute travel time
|
||||||
|
const secondStopArrivalTime = new Date(2025, 0, 1, 12, 15, 0);
|
||||||
|
await repository.addOrUpdateShuttle(shuttle, secondStopArrivalTime.getTime());
|
||||||
|
|
||||||
|
shuttle.coordinates = stop1.coordinates;
|
||||||
|
await repository.addOrUpdateShuttle(
|
||||||
|
shuttle,
|
||||||
|
new Date(2025, 0, 8, 12, 0, 0).getTime(),
|
||||||
|
new Date(2025, 0, 8, 12, 7, 30),
|
||||||
|
);
|
||||||
|
|
||||||
|
const eta = await repository.getEtaForShuttleAndStopId(shuttle.id, stop2.id);
|
||||||
|
expect(eta?.secondsRemaining).toEqual(7 * 60 + 30);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getArrivedStopIfExists", () => {
|
describe("getArrivedStopIfExists", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user