Files
project-inter-server/src/repositories/InMemoryParkingRepository.ts

135 lines
4.8 KiB
TypeScript

import { ParkingGetterSetterRepository } from "./ParkingGetterSetterRepository";
import {
IParkingStructure,
IParkingStructureTimestampRecord
} from "../entities/ParkingRepositoryEntities";
import { HistoricalParkingAverageQueryResult, ParkingStructureCountOptions } from "./ParkingGetterRepository";
import { CircularQueue } from "../types/CircularQueue";
export type ParkingStructureID = string;
// Every 10 minutes
// 6x per hour * 24x per day * 7x per week = 1008 entries for one week
export const PARKING_LOGGING_INTERVAL_MS = 600000;
// This will last two weeks
export const MAX_NUM_ENTRIES = 2016;
export class InMemoryParkingRepository implements ParkingGetterSetterRepository {
private dataLastAdded: Map<ParkingStructureID, Date> = new Map();
constructor(
private structures: Map<ParkingStructureID, IParkingStructure> = new Map(),
private historicalData: Map<ParkingStructureID, CircularQueue<IParkingStructureTimestampRecord>> = new Map(),
) {
}
addOrUpdateParkingStructure = async (structure: IParkingStructure): Promise<void> => {
this.structures.set(structure.id, { ...structure });
await this.addHistoricalDataForStructure(structure);
};
private addHistoricalDataForStructure = async (structure: IParkingStructure): Promise<void> => {
const now = Date.now();
const lastAdded = this.dataLastAdded.get(structure.id);
function parkingLoggingIntervalExceeded() {
return !lastAdded || (now - lastAdded.getTime()) >= PARKING_LOGGING_INTERVAL_MS;
}
if (parkingLoggingIntervalExceeded()) {
const timestampRecord: IParkingStructureTimestampRecord = {
id: structure.id,
spotsAvailable: structure.spotsAvailable,
timestampMs: now,
};
if (!this.historicalData.has(structure.id)) {
this.historicalData.set(structure.id, new CircularQueue<IParkingStructureTimestampRecord>(MAX_NUM_ENTRIES));
}
const sortingCallback = (a: IParkingStructureTimestampRecord, b: IParkingStructureTimestampRecord) => a.timestampMs - b.timestampMs;
this.historicalData.get(structure.id)?.appendWithSorting(timestampRecord, sortingCallback);
this.dataLastAdded.set(structure.id, new Date(now));
}
}
clearParkingStructureData = async (): Promise<void> => {
this.structures.clear();
};
getParkingStructureById = async (id: string): Promise<IParkingStructure | null> => {
const structure = this.structures.get(id);
return structure ? { ...structure } : null;
};
getParkingStructures = async (): Promise<IParkingStructure[]> => Array.from(this.structures.values()).map(structure => ({...structure}));
removeParkingStructureIfExists = async (id: string): Promise<IParkingStructure | null> => {
const structure = this.structures.get(id);
if (structure) {
this.structures.delete(id);
return { ...structure };
}
return null;
};
getHistoricalAveragesOfParkingStructureCounts = async (id: string, options: ParkingStructureCountOptions): Promise<HistoricalParkingAverageQueryResult[]> => {
const queue = this.historicalData.get(id);
if (!queue || queue.size() === 0) {
return [];
}
const results: HistoricalParkingAverageQueryResult[] = [];
const { startUnixEpochMs, endUnixEpochMs, intervalMs } = options;
let currentIntervalStart = startUnixEpochMs;
while (currentIntervalStart < endUnixEpochMs) {
const currentIntervalEnd = Math.min(currentIntervalStart + intervalMs, endUnixEpochMs);
const recordsInInterval = this.getRecordsInTimeRange(queue, currentIntervalStart, currentIntervalEnd);
if (recordsInInterval.length > 0) {
const averageResult = this.calculateAverageForInterval(currentIntervalStart, currentIntervalEnd, recordsInInterval);
results.push(averageResult);
}
currentIntervalStart = currentIntervalEnd;
}
return results;
};
private getRecordsInTimeRange = (
queue: CircularQueue<IParkingStructureTimestampRecord>,
startMs: number,
endMs: number
): IParkingStructureTimestampRecord[] => {
const recordsInInterval: IParkingStructureTimestampRecord[] = [];
for (let i = 0; i < queue.size(); i++) {
const record = queue.get(i);
if (record && record.timestampMs >= startMs && record.timestampMs < endMs) {
recordsInInterval.push(record);
}
}
return recordsInInterval;
};
private calculateAverageForInterval = (
fromMs: number,
toMs: number,
records: IParkingStructureTimestampRecord[]
): HistoricalParkingAverageQueryResult => {
const totalSpotsAvailable = records.reduce((sum, record) => sum + record.spotsAvailable, 0);
const averageSpotsAvailable = totalSpotsAvailable / records.length;
return {
fromUnixEpochMs: fromMs,
toUnixEpochMs: toMs,
averageSpotsAvailable
};
};
}