feat(automation): add timezone-safe daily briefing dedupe

This commit is contained in:
William Valentin
2026-02-16 14:01:14 -08:00
parent 71af3b5a42
commit 56854f04bd
10 changed files with 118 additions and 4 deletions
+37
View File
@@ -17,6 +17,7 @@ export class CronScheduler implements ChannelAdapter {
private messageHandler?: (msg: InboundMessage) => void;
private cronInstances: Map<string, Cron> = new Map();
private jobs: Map<string, CronJobConfig> = new Map();
private lastTriggeredLocalDateByJob: Map<string, string> = new Map();
constructor(
private readonly jobConfigs: CronJobConfig[],
@@ -89,6 +90,14 @@ export class CronScheduler implements ChannelAdapter {
triggerJob(jobName: string): void {
const job = this.jobs.get(jobName);
if (!job) {return;}
if (job.once_per_local_day) {
const dayKey = this.localDayKey(Date.now(), job.timezone);
const lastDayKey = this.lastTriggeredLocalDateByJob.get(jobName);
if (lastDayKey === dayKey) {
return;
}
this.lastTriggeredLocalDateByJob.set(jobName, dayKey);
}
const runId = `run-${randomUUID()}`;
const senderId = this.deliveryMode === 'isolated_job' ? `${jobName}:${runId}` : jobName;
@@ -174,9 +183,37 @@ export class CronScheduler implements ChannelAdapter {
}
this.jobs.delete(name);
this.lastTriggeredLocalDateByJob.delete(name);
auditLogger?.cronRemove(name);
return true;
}
private localDayKey(timestamp: number, timezone?: string): string {
const date = new Date(timestamp);
const format = (timeZoneValue?: string): string => {
const parts = new Intl.DateTimeFormat('en-US', {
timeZone: timeZoneValue,
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).formatToParts(date);
const year = parts.find((part) => part.type === 'year')?.value ?? '0000';
const month = parts.find((part) => part.type === 'month')?.value ?? '00';
const day = parts.find((part) => part.type === 'day')?.value ?? '00';
return `${year}-${month}-${day}`;
};
if (!timezone) {
return format();
}
try {
return format(timezone);
} catch {
console.warn(`CronScheduler: invalid timezone '${timezone}' for job local-day dedupe; falling back to system timezone`);
return format();
}
}
}