From a26a4ad898daeaf7c2e97fc2f7a7a48ebd8dc1bc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 30 Jul 2026 14:04:37 +0000 Subject: [PATCH] Defer weekend document reminders to Monday Reminders that fall due on Saturday or Sunday no longer send over the weekend or spend the escalation ladder unused. SQL keeps calendar cadence and rolls weekend due times to Monday at the same clock hour. The first debounced notice is unchanged. Co-authored-by: Sacha Al Himdani Signed-off-by: Cursor Agent --- .../document_version_approval_decision.go | 42 ++++++++++++++++- pkg/coredata/document_version_signature.go | 46 +++++++++++++++++-- pkg/probod/probod.go | 2 +- pkg/probodconfig/notifications_config.go | 6 ++- 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/pkg/coredata/document_version_approval_decision.go b/pkg/coredata/document_version_approval_decision.go index 984526fb2..12747b470 100644 --- a/pkg/coredata/document_version_approval_decision.go +++ b/pkg/coredata/document_version_approval_decision.go @@ -420,6 +420,7 @@ func (d *DocumentVersionApprovalDecisions) LoadNextDueGroupForNotification( debounceBefore time.Time, reminderInterval time.Duration, ) error { + // Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour. q := ` WITH next_group AS ( SELECT @@ -432,7 +433,25 @@ WITH next_group AS ( AND notification_count < @max_notifications AND ( (notification_count = 0 AND created_at < @debounce_before) - OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count)) + OR ( + notification_count > 0 + AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5 + AND @now::timestamptz > ( + SELECT + (due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc) + WHEN 6 THEN interval '2 days' + WHEN 7 THEN interval '1 day' + ELSE interval '0' + END) AT TIME ZONE 'UTC' + FROM ( + SELECT + ( + last_notified_at + + make_interval(secs => @reminder_interval_seconds * notification_count) + ) AT TIME ZONE 'UTC' AS due_at_utc + ) AS reminder + ) + ) ) AND EXISTS ( SELECT 1 @@ -531,6 +550,7 @@ func (d DocumentVersionApprovalDecisions) ClaimForNotification( ids[i] = decision.ID } + // Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour. q := ` UPDATE document_version_approval_decisions SET @@ -542,7 +562,25 @@ WHERE AND notification_count < @max_notifications AND ( (notification_count = 0 AND created_at < @debounce_before) - OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count)) + OR ( + notification_count > 0 + AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5 + AND @now::timestamptz > ( + SELECT + (due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc) + WHEN 6 THEN interval '2 days' + WHEN 7 THEN interval '1 day' + ELSE interval '0' + END) AT TIME ZONE 'UTC' + FROM ( + SELECT + ( + last_notified_at + + make_interval(secs => @reminder_interval_seconds * notification_count) + ) AT TIME ZONE 'UTC' AS due_at_utc + ) AS reminder + ) + ) ) RETURNING id ` diff --git a/pkg/coredata/document_version_signature.go b/pkg/coredata/document_version_signature.go index 8fd3b06a4..dbc94f7e7 100644 --- a/pkg/coredata/document_version_signature.go +++ b/pkg/coredata/document_version_signature.go @@ -412,7 +412,9 @@ WHERE // request gets: the first notice plus three reminders. A request is due for its // next email once it is past its scheduled offset — the first email after the // debounce delay, then reminders at 1x, 2x and 3x the reminder interval after -// the previous email — and stops once it reaches this cap. +// the previous email — and stops once it reaches this cap. Reminder sends that +// fall on Saturday or Sunday are deferred to Monday at the same clock time; +// the first debounced notice is unchanged. const documentNotificationMaxCount = 4 func remainingNotificationIDs(all []gid.GID, claimed []gid.GID) []gid.GID { @@ -445,6 +447,7 @@ func (pvss *DocumentVersionSignatures) LoadNextDueGroupForNotification( debounceBefore time.Time, reminderInterval time.Duration, ) error { + // Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour. q := ` WITH next_group AS ( SELECT @@ -457,7 +460,25 @@ WITH next_group AS ( AND notification_count < @max_notifications AND ( (notification_count = 0 AND requested_at < @debounce_before) - OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count)) + OR ( + notification_count > 0 + AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5 + AND @now::timestamptz > ( + SELECT + (due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc) + WHEN 6 THEN interval '2 days' + WHEN 7 THEN interval '1 day' + ELSE interval '0' + END) AT TIME ZONE 'UTC' + FROM ( + SELECT + ( + last_notified_at + + make_interval(secs => @reminder_interval_seconds * notification_count) + ) AT TIME ZONE 'UTC' AS due_at_utc + ) AS reminder + ) + ) ) AND EXISTS ( SELECT 1 @@ -554,6 +575,7 @@ func (pvss DocumentVersionSignatures) ClaimForNotification( ids[i] = signature.ID } + // Reminders skip Sat/Sun; a weekend due time waits until Monday same UTC hour. q := ` UPDATE document_version_signatures SET @@ -565,7 +587,25 @@ WHERE AND notification_count < @max_notifications AND ( (notification_count = 0 AND requested_at < @debounce_before) - OR (notification_count > 0 AND last_notified_at < @now::timestamptz - make_interval(secs => @reminder_interval_seconds * notification_count)) + OR ( + notification_count > 0 + AND EXTRACT(ISODOW FROM (@now::timestamptz AT TIME ZONE 'UTC')) BETWEEN 1 AND 5 + AND @now::timestamptz > ( + SELECT + (due_at_utc + CASE EXTRACT(ISODOW FROM due_at_utc) + WHEN 6 THEN interval '2 days' + WHEN 7 THEN interval '1 day' + ELSE interval '0' + END) AT TIME ZONE 'UTC' + FROM ( + SELECT + ( + last_notified_at + + make_interval(secs => @reminder_interval_seconds * notification_count) + ) AT TIME ZONE 'UTC' AS due_at_utc + ) AS reminder + ) + ) ) RETURNING id ` diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index 51828dc74..1ea6f2142 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -179,7 +179,7 @@ func New() *Implm { Document: DocumentNotificationConfig{ Interval: 300, // 5 minutes DebounceDelay: 900, // 15 minutes - ReminderInterval: 86400, // 1 day base cadence (1x, 2x, 3x) + ReminderInterval: 86400, // 1 day base cadence (1x, 2x, 3x; weekend reminders → Monday) }, }, CustomDomains: CustomDomainsConfig{ diff --git a/pkg/probodconfig/notifications_config.go b/pkg/probodconfig/notifications_config.go index 4523edd4c..d1181ebaf 100644 --- a/pkg/probodconfig/notifications_config.go +++ b/pkg/probodconfig/notifications_config.go @@ -40,7 +40,9 @@ type DocumentNotificationConfig struct { // DebounceDelay is how long a request must have been pending before its // first notification is sent. DebounceDelay int `json:"debounce-delay"` - // ReminderInterval is the base reminder cadence. Reminders are sent at - // 1x, 2x and 3x this interval after the previous email, then stop. + // ReminderInterval is the base reminder cadence in seconds. Reminders are + // sent at 1x, 2x and 3x this interval after the previous email, then stop. + // Reminder sends that fall on Saturday or Sunday are deferred to Monday at + // the same clock time; the first (debounced) notice is unchanged. ReminderInterval int `json:"reminder-interval"` }