From 6278d695fa78fcca24d8523dceb448465731518d Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Thu, 13 Nov 2025 22:28:07 -0800 Subject: [PATCH] Add support back in for external source ETA repositories, if we switch back later --- src/entities/InterchangeSystem.ts | 58 +++++++---- .../ApiBasedShuttleRepositoryLoader.ts | 98 ++++++++++++++++--- .../shuttle/ShuttleRepositoryLoader.ts | 2 + 3 files changed, 123 insertions(+), 35 deletions(-) diff --git a/src/entities/InterchangeSystem.ts b/src/entities/InterchangeSystem.ts index 662cfe8..985a819 100644 --- a/src/entities/InterchangeSystem.ts +++ b/src/entities/InterchangeSystem.ts @@ -22,6 +22,7 @@ import { RedisSelfUpdatingETARepository } from "../repositories/shuttle/eta/Redi import { RedisExternalSourceETARepository } from "../repositories/shuttle/eta/RedisExternalSourceETARepository"; import { InMemorySelfUpdatingETARepository } from "../repositories/shuttle/eta/InMemorySelfUpdatingETARepository"; import { BaseRedisETARepository } from "../repositories/shuttle/eta/BaseRedisETARepository"; +import { BaseInMemoryETARepository } from "../repositories/shuttle/eta/BaseInMemoryETARepository"; export interface InterchangeSystemBuilderArguments { name: string; @@ -99,24 +100,33 @@ export class InterchangeSystem { private static async buildRedisShuttleLoaderAndRepositories(args: InterchangeSystemBuilderArguments) { const shuttleRepository = new RedisShuttleRepository(); await shuttleRepository.connect(); - const shuttleDataLoader = new ApiBasedShuttleRepositoryLoader( - args.passioSystemId, - args.id, - shuttleRepository - ); - const timedShuttleDataLoader = new TimedApiBasedRepositoryLoader( - shuttleDataLoader - ); let etaRepository: BaseRedisETARepository; + let shuttleDataLoader: ApiBasedShuttleRepositoryLoader; if (args.useSelfUpdatingEtas) { etaRepository = new RedisSelfUpdatingETARepository(shuttleRepository); (etaRepository as RedisSelfUpdatingETARepository).startListeningForUpdates(); + shuttleDataLoader = new ApiBasedShuttleRepositoryLoader( + args.passioSystemId, + args.id, + shuttleRepository, + ); } else { etaRepository = new RedisExternalSourceETARepository(); + shuttleDataLoader = new ApiBasedShuttleRepositoryLoader( + args.passioSystemId, + args.id, + shuttleRepository, + etaRepository as RedisExternalSourceETARepository, + ); } await etaRepository.connect(); + const timedShuttleDataLoader = new TimedApiBasedRepositoryLoader( + shuttleDataLoader, + ); + + return { shuttleRepository, etaRepository, timedShuttleDataLoader }; } @@ -238,23 +248,33 @@ export class InterchangeSystem { private static buildInMemoryShuttleLoaderAndRepositories(args: InterchangeSystemBuilderArguments) { const shuttleRepository = new UnoptimizedInMemoryShuttleRepository(); - const shuttleDataLoader = new ApiBasedShuttleRepositoryLoader( - args.passioSystemId, - args.id, - shuttleRepository - ); + + let etaRepository: BaseInMemoryETARepository; + let shuttleDataLoader: ApiBasedShuttleRepositoryLoader; + if (args.useSelfUpdatingEtas) { + etaRepository = new InMemorySelfUpdatingETARepository(shuttleRepository); + (etaRepository as InMemorySelfUpdatingETARepository).startListeningForUpdates(); + shuttleDataLoader = new ApiBasedShuttleRepositoryLoader( + args.passioSystemId, + args.id, + shuttleRepository, + ); + } else { + etaRepository = new InMemoryExternalSourceETARepository(); + shuttleDataLoader = new ApiBasedShuttleRepositoryLoader( + args.passioSystemId, + args.id, + shuttleRepository, + etaRepository as InMemoryExternalSourceETARepository, + ); + } + // Note that this loader should not be started, // so the test data doesn't get overwritten const timedShuttleLoader = new TimedApiBasedRepositoryLoader( shuttleDataLoader ); - let etaRepository: ETAGetterRepository; - if (args.useSelfUpdatingEtas) { - etaRepository = new InMemorySelfUpdatingETARepository(shuttleRepository); - } else { - etaRepository = new InMemoryExternalSourceETARepository(); - } return { shuttleRepository, etaRepository, timedShuttleLoader }; } diff --git a/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts b/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts index 4d50dcf..19d4a14 100644 --- a/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts +++ b/src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts @@ -1,9 +1,10 @@ import { ShuttleGetterSetterRepository } from "../../repositories/shuttle/ShuttleGetterSetterRepository"; -import { IRoute, IShuttle, IStop } from "../../entities/ShuttleRepositoryEntities"; +import { IEta, IRoute, IShuttle, IStop } from "../../entities/ShuttleRepositoryEntities"; import { ShuttleRepositoryLoader } from "./ShuttleRepositoryLoader"; import { ICoordinates, IEntityWithId } from "../../entities/SharedEntities"; import { ApiResponseError } from "../ApiResponseError"; import { SHUTTLE_TO_ROUTE_COORDINATE_MAXIMUM_DISTANCE_MILES } from "../../environment"; +import { ExternalSourceETARepository } from "../../repositories/shuttle/eta/ExternalSourceETARepository"; /** * Class which can load data into a repository from the @@ -16,7 +17,8 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader constructor( public passioSystemId: string, public systemIdForConstructedData: string, - public repository: ShuttleGetterSetterRepository, + public shuttleRepository: ShuttleGetterSetterRepository, + public etaRepository?: ExternalSourceETARepository, readonly shuttleToRouteCoordinateMaximumDistanceMiles = SHUTTLE_TO_ROUTE_COORDINATE_MAXIMUM_DISTANCE_MILES, ) { } @@ -34,6 +36,10 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader await this.updateRouteDataForSystem(); await this.updateStopAndPolylineDataForRoutesInSystem(); await this.updateShuttleDataForSystemBasedOnProximityToRoutes(); + + // Because ETA method doesn't support pruning yet, + // add a call to the clear method here + await this.updateEtaDataForExistingStopsForSystem(); } public async updateRouteDataForSystem() { @@ -52,16 +58,16 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader private async updateRouteDataInRepository(routes: IRoute[]) { const routeIdsToPrune = await this.constructExistingEntityIdSet(async () => { - return await this.repository.getRoutes(); + return await this.shuttleRepository.getRoutes(); }); await Promise.all(routes.map(async (route) => { - await this.repository.addOrUpdateRoute(route); + await this.shuttleRepository.addOrUpdateRoute(route); routeIdsToPrune.delete(route.id); })); await Promise.all(Array.from(routeIdsToPrune).map(async (routeId) => { - await this.repository.removeRouteIfExists(routeId); + await this.shuttleRepository.removeRouteIfExists(routeId); })); } @@ -117,7 +123,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader private async updateStopAndPolylineDataInRepository(json: any) { const stopIdsToPrune = await this.constructExistingEntityIdSet(async () => { - return await this.repository.getStops(); + return await this.shuttleRepository.getStops(); }); await this.updateStopDataForSystemAndApiResponse(json, stopIdsToPrune); @@ -125,7 +131,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader await this.updatePolylineDataForExistingRoutesAndApiResponse(json); await Promise.all(Array.from(stopIdsToPrune).map(async (stopId) => { - await this.repository.removeStopIfExists(stopId); + await this.shuttleRepository.removeStopIfExists(stopId); })); } @@ -169,16 +175,16 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader private async updateShuttleDataInRepository(shuttles: IShuttle[]) { const shuttleIdsToPrune = await this.constructExistingEntityIdSet(async () => { - return await this.repository.getShuttles(); + return await this.shuttleRepository.getShuttles(); }); await Promise.all(shuttles.map(async (shuttle) => { - await this.repository.addOrUpdateShuttle(shuttle); + await this.shuttleRepository.addOrUpdateShuttle(shuttle); shuttleIdsToPrune.delete(shuttle.id); })); await Promise.all(Array.from(shuttleIdsToPrune).map(async (shuttleId) => { - await this.repository.removeShuttleIfExists(shuttleId); + await this.shuttleRepository.removeShuttleIfExists(shuttleId); })); } @@ -233,6 +239,66 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader return null; } + public async updateEtaDataForExistingStopsForSystem() { + const stops = await this.shuttleRepository.getStops(); + await Promise.all(stops.map(async (stop) => { + let stopId = stop.id; + await this.updateEtaDataForStopId(stopId); + })); + } + + public async updateEtaDataForStopId(stopId: string) { + try { + const json = await this.fetchEtaDataJson(stopId); + const etas = this.constructEtasFromJson(json, stopId); + if (etas !== null) { + await this.updateEtaDataInRepository(etas); + } else { + console.warn(`ETA update failed for stop ${stopId} with the following JSON: ${JSON.stringify(json)}`); + } + } catch(e: any) { + throw new ApiResponseError(e.message); + } + } + + private async updateEtaDataInRepository(etas: IEta[]) { + await Promise.all(etas.map(async (eta) => { + await this.etaRepository?.addOrUpdateEtaFromExternalSource(eta); + })); + } + + private async fetchEtaDataJson(stopId: string) { + const params = { + eta: "3", + stopIds: stopId, + }; + + const query = new URLSearchParams(params).toString(); + + const response = await fetch(`${this.baseUrl}?${query}`, { + method: "GET", + }); + return await response.json(); + } + + private constructEtasFromJson(json: any, stopId: string): IEta[] | null { + if (json.ETAs && json.ETAs[stopId]) { + return json.ETAs[stopId].map((jsonEta: any) => { + const shuttleId: string = jsonEta.busId; + const eta: IEta = { + secondsRemaining: jsonEta.secondsSpent, + shuttleId: `${shuttleId}`, + stopId: stopId, + updatedTime: new Date(), + systemId: this.systemIdForConstructedData, + }; + return eta; + }); + } + + return null; + } + protected async updateStopDataForSystemAndApiResponse( json: any, setOfIdsToPrune: Set = new Set(), @@ -252,7 +318,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader updatedTime: new Date(), }; - await this.repository.addOrUpdateStop(constructedStop); + await this.shuttleRepository.addOrUpdateStop(constructedStop); setOfIdsToPrune.delete(constructedStop.id); })); @@ -274,7 +340,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader const orderedStopDataArray = jsonOrderedStopData[index]; const stopId = orderedStopDataArray[1]; - let constructedOrderedStop = await this.repository.getOrderedStopByRouteAndStopId(routeId, stopId) + let constructedOrderedStop = await this.shuttleRepository.getOrderedStopByRouteAndStopId(routeId, stopId) if (constructedOrderedStop === null) { constructedOrderedStop = { routeId, @@ -304,7 +370,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader }; } - await this.repository.addOrUpdateOrderedStop(constructedOrderedStop); + await this.shuttleRepository.addOrUpdateOrderedStop(constructedOrderedStop); } })); } @@ -315,7 +381,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader await Promise.all(Object.keys(json.routePoints).map(async (routeId) => { const routePoints = json.routePoints[routeId][0]; - const existingRoute = await this.repository.getRouteById(routeId); + const existingRoute = await this.shuttleRepository.getRouteById(routeId); if (!existingRoute) return; existingRoute.polylineCoordinates = routePoints.map((point: any) => { @@ -325,7 +391,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader }; }); - await this.repository.addOrUpdateRoute(existingRoute); + await this.shuttleRepository.addOrUpdateRoute(existingRoute); })) } } @@ -334,7 +400,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader let filteredShuttles: IShuttle[] = []; await Promise.all(shuttles.map(async (shuttle) => { - const route = await this.repository.getRouteById(shuttle.routeId); + const route = await this.shuttleRepository.getRouteById(shuttle.routeId); if (route != null) { let closestDistanceMiles = Number.MAX_VALUE; diff --git a/src/loaders/shuttle/ShuttleRepositoryLoader.ts b/src/loaders/shuttle/ShuttleRepositoryLoader.ts index ea67d22..d556455 100644 --- a/src/loaders/shuttle/ShuttleRepositoryLoader.ts +++ b/src/loaders/shuttle/ShuttleRepositoryLoader.ts @@ -4,4 +4,6 @@ export interface ShuttleRepositoryLoader extends RepositoryLoader { updateRouteDataForSystem(): Promise; updateStopAndPolylineDataForRoutesInSystem(): Promise; updateShuttleDataForSystemBasedOnProximityToRoutes(): Promise; + updateEtaDataForExistingStopsForSystem(): Promise; + updateEtaDataForStopId(stopId: string): Promise; }