mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-19 08:50:29 +00:00
122 lines
4.8 KiB
TypeScript
122 lines
4.8 KiB
TypeScript
import { SelfUpdatingETARepository } from "./SelfUpdatingETARepository";
|
|
import { ShuttleGetterRepository, ShuttleRepositoryEvent, ShuttleStopArrival, ShuttleTravelTimeDataIdentifier, ShuttleTravelTimeDateFilterArguments } from "../ShuttleGetterRepository";
|
|
import { BaseInMemoryETARepository } from "./BaseInMemoryETARepository";
|
|
import { IShuttle } from "../../../entities/ShuttleRepositoryEntities";
|
|
|
|
export class InMemorySelfUpdatingETARepository extends BaseInMemoryETARepository implements SelfUpdatingETARepository {
|
|
private referenceTime: Date | null = null;
|
|
private travelTimeData: Map<string, Array<{ timestamp: number; seconds: number }>> = new Map();
|
|
|
|
constructor(
|
|
readonly shuttleRepository: ShuttleGetterRepository
|
|
) {
|
|
super();
|
|
|
|
this.setReferenceTime = this.setReferenceTime.bind(this);
|
|
this.getAverageTravelTimeSeconds = this.getAverageTravelTimeSeconds.bind(this);
|
|
this.startListeningForUpdates = this.startListeningForUpdates.bind(this);
|
|
this.handleShuttleUpdate = this.handleShuttleUpdate.bind(this);
|
|
this.handleShuttleWillArriveAtStop = this.handleShuttleWillArriveAtStop.bind(this);
|
|
}
|
|
|
|
setReferenceTime(referenceTime: Date): void {
|
|
this.referenceTime = referenceTime;
|
|
}
|
|
|
|
async getAverageTravelTimeSeconds(
|
|
{ routeId, fromStopId, toStopId }: ShuttleTravelTimeDataIdentifier,
|
|
{ from, to }: ShuttleTravelTimeDateFilterArguments
|
|
): Promise<number | undefined> {
|
|
const key = `${routeId}:${fromStopId}:${toStopId}`;
|
|
const dataPoints = this.travelTimeData.get(key);
|
|
|
|
if (!dataPoints || dataPoints.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
const fromTimestamp = from.getTime();
|
|
const toTimestamp = to.getTime();
|
|
|
|
const filteredPoints = dataPoints.filter(
|
|
(point) => point.timestamp >= fromTimestamp && point.timestamp <= toTimestamp
|
|
);
|
|
|
|
if (filteredPoints.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
const sum = filteredPoints.reduce((acc, point) => acc + point.seconds, 0);
|
|
return sum / filteredPoints.length;
|
|
}
|
|
|
|
startListeningForUpdates(): void {
|
|
this.shuttleRepository.addListener(ShuttleRepositoryEvent.SHUTTLE_UPDATED, this.handleShuttleUpdate);
|
|
this.shuttleRepository.addListener(ShuttleRepositoryEvent.SHUTTLE_WILL_ARRIVE_AT_STOP, this.handleShuttleWillArriveAtStop);
|
|
}
|
|
|
|
private async handleShuttleUpdate(shuttle: IShuttle): Promise<void> {
|
|
const lastStop = await this.shuttleRepository.getShuttleLastStopArrival(shuttle.id);
|
|
if (!lastStop) return;
|
|
|
|
const lastOrderedStop = await this.shuttleRepository.getOrderedStopByRouteAndStopId(shuttle.routeId, lastStop.stopId);
|
|
const nextStop = lastOrderedStop?.nextStop;
|
|
if (!nextStop) return;
|
|
|
|
let referenceCurrentTime = new Date();
|
|
if (this.referenceTime != null) {
|
|
referenceCurrentTime = this.referenceTime;
|
|
}
|
|
const oneWeekAgo = new Date(referenceCurrentTime.getTime() - (60 * 60 * 24 * 7 * 1000));
|
|
|
|
const travelTimeSeconds = await this.getAverageTravelTimeSeconds({
|
|
routeId: shuttle.routeId,
|
|
fromStopId: lastStop.stopId,
|
|
toStopId: nextStop.stopId,
|
|
}, {
|
|
from: oneWeekAgo,
|
|
to: new Date(oneWeekAgo.getTime() + (60 * 60 * 1000))
|
|
});
|
|
if (travelTimeSeconds == undefined) return;
|
|
|
|
const elapsedTimeMs = referenceCurrentTime.getTime() - lastStop.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 handleShuttleWillArriveAtStop(shuttleArrival: ShuttleStopArrival): Promise<void> {
|
|
const lastStopTimestamp = await this.shuttleRepository.getShuttleLastStopArrival(shuttleArrival.shuttleId);
|
|
if (lastStopTimestamp) {
|
|
// disallow cases where this gets triggered multiple times
|
|
if (lastStopTimestamp.stopId === shuttleArrival.stopId) return;
|
|
|
|
const shuttle = await this.shuttleRepository.getShuttleById(lastStopTimestamp.shuttleId);
|
|
if (!shuttle) return;
|
|
|
|
const routeId = shuttle.routeId;
|
|
const fromStopId = lastStopTimestamp.stopId;
|
|
const toStopId = shuttleArrival.stopId;
|
|
|
|
const travelTimeSeconds = (shuttleArrival.timestamp.getTime() - lastStopTimestamp.timestamp.getTime()) / 1000;
|
|
await this.addTravelTimeDataPoint({ routeId, fromStopId, toStopId }, travelTimeSeconds, shuttleArrival.timestamp.getTime());
|
|
}
|
|
}
|
|
|
|
private async addTravelTimeDataPoint(
|
|
{ routeId, fromStopId, toStopId }: ShuttleTravelTimeDataIdentifier,
|
|
travelTimeSeconds: number,
|
|
timestamp = Date.now(),
|
|
): Promise<void> {
|
|
const key = `${routeId}:${fromStopId}:${toStopId}`;
|
|
const dataPoints = this.travelTimeData.get(key) || [];
|
|
dataPoints.push({ timestamp, seconds: travelTimeSeconds });
|
|
this.travelTimeData.set(key, dataPoints);
|
|
}
|
|
}
|