Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/jobs.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { updateBusPositions, initializeRoutes, rebuildGraph } from './services/graphBuilder';
import { processRideReminders, processUniversityReminders } from './services/reminder';
import { initializeReminders, processRideReminders, processUniversityReminders } from './services/reminder';

/**
* Starts background jobs for updating bus positions, initializing routes, and rebuilding the graph.
*/
export function startBackgroundJobs() {
initializeReminders();
initializeRoutes().then(() => {
console.log("Routes initialized. Building initial graph...");
rebuildGraph();
Expand Down
37 changes: 25 additions & 12 deletions src/services/reminder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ export * from "./reminderTypes";

dotenv.config()

// Initialize Firebase
initializeApp({ credential: applicationDefault() });
export function initializeReminders() {
// Initialize Firebase
initializeApp({ credential: applicationDefault() });
}

/** Waiting for a prediction of the right `event` to have a arrival timestamp that is at or after `mustBeAfter` and an
* arrival time less than `thresh`. A bus is xx minutes from the stop notification is then sent. To handle delayed and
Expand Down Expand Up @@ -77,17 +79,27 @@ export type RemindersToTrigger = {
export class ReminderSubscriptions {
subscriptions: Array<{
token: RegistrationToken, subscription: PreThreshold | PostThreshold
}>
}>;
pure: boolean;

constructor() {
/** set mock to true for tests that don't involve firebase */
constructor(options: { mock: boolean }) {
this.pure = options.mock;
this.subscriptions = [];
}

/** Adds a new subscription, uses prediction data to determine a candidate vid. Existing reminders for the same
event and token are removed.
REQUIRES: `predsByStopId` has predictions sorted by arrival timestamp
*/
add(event: BaseEvent, thresh: number, token: RegistrationToken, predsByStopId: Record<string, state.Prediction[]>, now: number) {
add(
event: BaseEvent,
thresh: number,
token: RegistrationToken,
predsByStopId: Record<string, state.Prediction[]>,
now: number,
options?: { noUpdate: boolean },
) {
console.log("Adding a reminder subscription");
const predictions = predsByStopId[event.stpid];
const subscription = preThreshold(event, thresh, null, now);
Expand All @@ -103,18 +115,19 @@ export class ReminderSubscriptions {
}
}
// remove existing
this.remove(event, token, true);
this.remove(event, token, { noUpdate: true });
this.subscriptions.push({ token, subscription });
if (options?.noUpdate || this.pure) return;
sendReminderUpdateToAll(new Set([token]));
}

/** removes all subscriptions that involve both `event` and `token` */
remove(event: BaseEvent, token: RegistrationToken, noUpdate?: boolean) {
remove(event: BaseEvent, token: RegistrationToken, options?: { noUpdate: boolean }) {
console.log("Removing a reminder subscription");
this.subscriptions = this.subscriptions
.filter((s) => s.token != token || !eventsEqual(s.subscription.event, event))
if (!noUpdate)
sendReminderUpdateToAll(new Set([token]));
if (options?.noUpdate || this.pure) return;
sendReminderUpdateToAll(new Set([token]));
}

/** updates the status of all registrations, returning an object representing the
Expand Down Expand Up @@ -323,8 +336,8 @@ export class ReminderSubscriptions {
}
}

export const universityReminderSubscriptions = new ReminderSubscriptions();
export const rideReminderSubscriptions = new ReminderSubscriptions();
export const universityReminderSubscriptions = new ReminderSubscriptions({ mock: false});
export const rideReminderSubscriptions = new ReminderSubscriptions({ mock: false});

export function processUniversityReminders() {
try {
Expand Down Expand Up @@ -365,7 +378,7 @@ function processRemindersHelper(
reminderSubscriptions: ReminderSubscriptions,
predsByStopId: Record<string, state.Prediction[] | undefined>,
predsByVid: Record<string, state.Prediction[] | undefined>,
stopIdToName: Record<string, string>
stopIdToName: Record<string, string>,
) {
const notifications = reminderSubscriptions.process(predsByStopId, predsByVid, Date.now());
for (const [eventKey, tokens] of notifications.reminder) {
Expand Down
20 changes: 10 additions & 10 deletions test/reminder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function createCaches(preds: Prediction[]): { byStop: Record<string, Prediction[

describe('Reminders', () => {
it('should not send a threshold immediately and should move to next stage after sending', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop, byVid } = createCaches([
{ rt: testEvent.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 2 * 60 * 1000, prdctdn: "2" }
]);
Expand Down Expand Up @@ -69,7 +69,7 @@ describe('Reminders', () => {
});

it('should not send a disappeared notification if there never was a bus in the first place', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
// the subscription gets added with no possible candidate vid
subs.add(testEvent, 3, testToken, {}, Date.now());
// will it trigger a disappeared reminder?
Expand All @@ -78,7 +78,7 @@ describe('Reminders', () => {
});

it('should not trigger for busses of other routes', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop, byVid } = createCaches([
{ rt: testEventDiffRt.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 4 * 60 * 1000, prdctdn: "2" }
]);
Expand All @@ -88,7 +88,7 @@ describe('Reminders', () => {
});

it('should only get removed by the remove method if explicitly targeted', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
subs.add(testEvent, 3, testToken, {}, Date.now());
subs.add(testEventDiffRt, 3, testToken, {}, Date.now());
subs.add(testEvent, 3, r.registrationToken("anotherToken"), {}, Date.now());
Expand All @@ -98,7 +98,7 @@ describe('Reminders', () => {
});

it('should send at the stop notifications', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop, byVid } = createCaches([
{ rt: testEvent.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 4 * 60 * 1000, prdctdn: "2" }
]);
Expand All @@ -111,7 +111,7 @@ describe('Reminders', () => {
});

it('should send delayed notifications', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop, byVid } = createCaches([
{ rt: testEvent.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 4 * 60 * 1000, prdctdn: "4" }
]);
Expand All @@ -132,7 +132,7 @@ describe('Reminders', () => {
});

it('should send disappeared notifications (stage 0)', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop } = createCaches([
{ rt: testEvent.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 4 * 60 * 1000, prdctdn: "4" }
]);
Expand All @@ -142,7 +142,7 @@ describe('Reminders', () => {
});

it('should send disappeared notifications (stage 1)', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop, byVid } = createCaches([
{ rt: testEvent.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 5 * 60 * 1000, prdctdn: "5" }
]);
Expand All @@ -160,7 +160,7 @@ describe('Reminders', () => {
});

it('should override some delayed notifications', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop, byVid } = createCaches([
{ rt: testEvent.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 4 * 60 * 1000, prdctdn: "4" }
]);
Expand All @@ -182,7 +182,7 @@ describe('Reminders', () => {
});

it('should override some disappeared notifications', () => {
const subs = new t.ReminderSubscriptions();
const subs = new t.ReminderSubscriptions({ mock: true });
const { byStop, byVid } = createCaches([
{ rt: testEvent.rtid, vid: "vid1", stpid: testEvent.stpid, prdtm: Date.now() + 5 * 60 * 1000, prdctdn: "5" }
]);
Expand Down