@@ -7,8 +7,8 @@ import { executeAssessmentCall } from "../lib/assessment-call";
77const MAX_ATTEMPTS = 3 ;
88const RETRY_INTERVAL_MINUTES = 5 ;
99
10- function getTimeInTimezone ( timezone : string ) {
11- const now = new Date ( ) ;
10+ function getTimeInTimezone ( timezone : string , referenceTime ?: Date ) {
11+ const now = referenceTime || new Date ( ) ;
1212 const formatted = now . toLocaleString ( "en-US" , {
1313 timeZone : timezone ,
1414 hour : "2-digit" ,
@@ -20,26 +20,26 @@ function getTimeInTimezone(timezone: string) {
2020 return `${ h . padStart ( 2 , "0" ) } :${ m . padStart ( 2 , "0" ) } ` ;
2121}
2222
23- function getDayOfWeekInTimezone ( timezone : string ) {
24- const now = new Date ( ) ;
23+ function getDayOfWeekInTimezone ( timezone : string , referenceTime ?: Date ) {
24+ const now = referenceTime || new Date ( ) ;
2525 const dayStr = now . toLocaleString ( "en-US" , { timeZone : timezone , weekday : "short" } ) ;
2626 const dayMap : Record < string , number > = { Sun : 0 , Mon : 1 , Tue : 2 , Wed : 3 , Thu : 4 , Fri : 5 , Sat : 6 } ;
2727 return dayMap [ dayStr ] ?? now . getDay ( ) ;
2828}
2929
30- function getTodayDateStringInTimezone ( timezone : string ) {
31- const now = new Date ( ) ;
30+ function getTodayDateStringInTimezone ( timezone : string , referenceTime ?: Date ) {
31+ const now = referenceTime || new Date ( ) ;
3232 const parts = now . toLocaleDateString ( "en-CA" , { timeZone : timezone } ) . split ( "-" ) ;
3333 return parts . join ( "-" ) ;
3434}
3535
36- function getNowMinutesInTimezone ( timezone : string ) {
37- const timeStr = getTimeInTimezone ( timezone ) ;
36+ function getNowMinutesInTimezone ( timezone : string , referenceTime ?: Date ) {
37+ const timeStr = getTimeInTimezone ( timezone , referenceTime ) ;
3838 const [ h , m ] = timeStr . split ( ":" ) . map ( Number ) ;
3939 return h * 60 + m ;
4040}
4141
42- async function deactivatePastReminders ( ) {
42+ async function deactivatePastReminders ( tickTime : Date ) {
4343 // Find all active reminders with a scheduledDate
4444 const reminders = await prisma . reminder . findMany ( {
4545 where : { active : true , scheduledDate : { not : null } } ,
@@ -48,7 +48,7 @@ async function deactivatePastReminders() {
4848
4949 for ( const reminder of reminders ) {
5050 const tz = reminder . elderlyProfile . timezone || "UTC" ;
51- const todayStr = getTodayDateStringInTimezone ( tz ) ;
51+ const todayStr = getTodayDateStringInTimezone ( tz , tickTime ) ;
5252 if ( reminder . scheduledDate ! < todayStr ) {
5353 await prisma . reminder . update ( {
5454 where : { id : reminder . id } ,
@@ -59,7 +59,7 @@ async function deactivatePastReminders() {
5959 }
6060}
6161
62- async function processReminders ( ) {
62+ async function processReminders ( tickTime : Date ) {
6363 const reminders = await prisma . reminder . findMany ( {
6464 where : {
6565 active : true ,
@@ -78,9 +78,9 @@ async function processReminders() {
7878
7979 for ( const reminder of reminders ) {
8080 const tz = reminder . elderlyProfile . timezone || "UTC" ;
81- const currentTime = getTimeInTimezone ( tz ) ;
82- const currentDay = getDayOfWeekInTimezone ( tz ) ;
83- const todayStr = getTodayDateStringInTimezone ( tz ) ;
81+ const currentTime = getTimeInTimezone ( tz , tickTime ) ;
82+ const currentDay = getDayOfWeekInTimezone ( tz , tickTime ) ;
83+ const todayStr = getTodayDateStringInTimezone ( tz , tickTime ) ;
8484
8585 // If reminder has a specific date, only fire on that date
8686 if ( reminder . scheduledDate && reminder . scheduledDate !== todayStr ) continue ;
@@ -109,7 +109,7 @@ async function processReminders() {
109109 if ( existingLog ) continue ;
110110 } else if ( intervalHours ) {
111111 // Hour-based intervals: first call at scheduledTime, then repeat every N hours
112- const nowMinutes = getNowMinutesInTimezone ( tz ) ;
112+ const nowMinutes = getNowMinutesInTimezone ( tz , tickTime ) ;
113113 const startMinutes = h * 60 + m ;
114114
115115 // Only fire at or after the scheduled start time
@@ -274,7 +274,7 @@ async function processEmergencyCalls() {
274274 }
275275}
276276
277- async function processAssessments ( ) {
277+ async function processAssessments ( tickTime : Date ) {
278278 const configs = await prisma . assessmentConfig . findMany ( {
279279 where : {
280280 active : true ,
@@ -287,13 +287,13 @@ async function processAssessments() {
287287
288288 for ( const config of configs ) {
289289 const tz = config . elderlyProfile . timezone || "UTC" ;
290- const currentTime = getTimeInTimezone ( tz ) ;
290+ const currentTime = getTimeInTimezone ( tz , tickTime ) ;
291291
292292 console . log ( `[Assessments] ${ config . elderlyProfile . name } : scheduled=${ config . scheduledTime } , current=${ currentTime } (tz=${ tz } )` ) ;
293293
294294 if ( config . scheduledTime !== currentTime ) continue ;
295295
296- const todayStr = getTodayDateStringInTimezone ( tz ) ;
296+ const todayStr = getTodayDateStringInTimezone ( tz , tickTime ) ;
297297
298298 const existingSession = await prisma . assessmentSession . findFirst ( {
299299 where : { configId : config . id , date : todayStr } ,
@@ -348,12 +348,15 @@ async function processAssessments() {
348348}
349349
350350cron . schedule ( "* * * * *" , async ( ) => {
351- console . log ( `[${ new Date ( ) . toISOString ( ) } ] Checking reminders...` ) ;
351+ // Snapshot the time at tick start so all functions use the same minute
352+ // even if earlier functions (e.g. making Twilio calls) take a while
353+ const tickTime = new Date ( ) ;
354+ console . log ( `[${ tickTime . toISOString ( ) } ] Checking reminders...` ) ;
352355
353- try { await deactivatePastReminders ( ) ; } catch ( error ) {
356+ try { await deactivatePastReminders ( tickTime ) ; } catch ( error ) {
354357 console . error ( "Error in deactivatePastReminders:" , error ) ;
355358 }
356- try { await processReminders ( ) ; } catch ( error ) {
359+ try { await processReminders ( tickTime ) ; } catch ( error ) {
357360 console . error ( "Error in processReminders:" , error ) ;
358361 }
359362 try { await processRetries ( ) ; } catch ( error ) {
@@ -362,7 +365,7 @@ cron.schedule("* * * * *", async () => {
362365 try { await processEmergencyCalls ( ) ; } catch ( error ) {
363366 console . error ( "Error in processEmergencyCalls:" , error ) ;
364367 }
365- try { await processAssessments ( ) ; } catch ( error ) {
368+ try { await processAssessments ( tickTime ) ; } catch ( error ) {
366369 console . error ( "Error in processAssessments:" , error ) ;
367370 }
368371} ) ;
0 commit comments