mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 07:50:31 +00:00
161 lines
6.0 KiB
TypeScript
161 lines
6.0 KiB
TypeScript
import { ParkingGetterSetterRepository } from "./ParkingGetterSetterRepository";
|
|
import {
|
|
IParkingStructure,
|
|
IParkingStructureTimestampRecord
|
|
} from "../../entities/ParkingRepositoryEntities";
|
|
import { HistoricalParkingAverageQueryResult, ParkingStructureCountOptions } from "./ParkingGetterRepository";
|
|
import { CircularQueue } from "../../types/CircularQueue";
|
|
import { PARKING_LOGGING_INTERVAL_MS } from "./ParkingRepositoryConstants";
|
|
|
|
// If every 10 minutes, two weeks of data (6x per hour * 24x per day * 7x per week * 2)
|
|
export const MAX_NUM_ENTRIES = 2016;
|
|
|
|
export type ParkingStructureID = string;
|
|
|
|
export class InMemoryParkingRepository implements ParkingGetterSetterRepository {
|
|
private dataLastAdded: Map<ParkingStructureID, Date> = new Map();
|
|
private loggingIntervalMs = PARKING_LOGGING_INTERVAL_MS;
|
|
|
|
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);
|
|
|
|
if (this.shouldLogHistoricalData(lastAdded, now)) {
|
|
const timestampRecord = this.createTimestampRecord(structure, now);
|
|
this.ensureHistoricalDataExists(structure.id);
|
|
this.addRecordToHistoricalData(structure.id, timestampRecord);
|
|
this.dataLastAdded.set(structure.id, new Date(now));
|
|
}
|
|
};
|
|
|
|
clearParkingStructureData = async (): Promise<void> => {
|
|
this.structures.clear();
|
|
this.historicalData.clear();
|
|
this.dataLastAdded.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);
|
|
this.historicalData.delete(id);
|
|
this.dataLastAdded.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 records = this.extractRecordsFromQueue(queue);
|
|
return this.calculateAveragesFromRecords(records, options);
|
|
};
|
|
|
|
private shouldLogHistoricalData = (lastAdded: Date | undefined, currentTime: number): boolean => {
|
|
return !lastAdded || (currentTime - lastAdded.getTime()) >= this.loggingIntervalMs;
|
|
};
|
|
|
|
private createTimestampRecord = (structure: IParkingStructure, timestampMs: number): IParkingStructureTimestampRecord => ({
|
|
id: structure.id,
|
|
spotsAvailable: structure.spotsAvailable,
|
|
timestampMs,
|
|
});
|
|
|
|
private ensureHistoricalDataExists = (structureId: string): void => {
|
|
if (!this.historicalData.has(structureId)) {
|
|
this.historicalData.set(structureId, new CircularQueue<IParkingStructureTimestampRecord>(MAX_NUM_ENTRIES));
|
|
}
|
|
};
|
|
|
|
private addRecordToHistoricalData = (structureId: string, record: IParkingStructureTimestampRecord): void => {
|
|
const sortingCallback = (a: IParkingStructureTimestampRecord, b: IParkingStructureTimestampRecord) => a.timestampMs - b.timestampMs;
|
|
this.historicalData.get(structureId)?.appendWithSorting(record, sortingCallback);
|
|
};
|
|
|
|
private extractRecordsFromQueue = (queue: CircularQueue<IParkingStructureTimestampRecord>): IParkingStructureTimestampRecord[] => {
|
|
const records: IParkingStructureTimestampRecord[] = [];
|
|
for (let i = 0; i < queue.size(); i++) {
|
|
const record = queue.get(i);
|
|
if (record) {
|
|
records.push(record);
|
|
}
|
|
}
|
|
return records;
|
|
};
|
|
|
|
private calculateAveragesFromRecords = (
|
|
records: IParkingStructureTimestampRecord[],
|
|
options: ParkingStructureCountOptions
|
|
): HistoricalParkingAverageQueryResult[] => {
|
|
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(records, currentIntervalStart, currentIntervalEnd);
|
|
|
|
if (recordsInInterval.length > 0) {
|
|
const averageResult = this.calculateAverageForInterval(currentIntervalStart, currentIntervalEnd, recordsInInterval);
|
|
results.push(averageResult);
|
|
}
|
|
|
|
currentIntervalStart = currentIntervalEnd;
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
private getRecordsInTimeRange = (
|
|
records: IParkingStructureTimestampRecord[],
|
|
startMs: number,
|
|
endMs: number
|
|
): IParkingStructureTimestampRecord[] => {
|
|
return records.filter(record =>
|
|
record.timestampMs >= startMs && record.timestampMs < endMs
|
|
);
|
|
};
|
|
|
|
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
|
|
};
|
|
};
|
|
|
|
setLoggingInterval = (intervalMs: number): void => {
|
|
this.loggingIntervalMs = intervalMs;
|
|
};
|
|
}
|