Replaces one-size-fits-all "2 days before" reminders with intelligent
scheduling based on task frequency. Infrequent tasks (annual) get 30-day
advance notice while frequent tasks (weekly) only get day-of reminders.
Key features:
- Frequency-aware pre-reminders: annual (30d, 14d, 7d), quarterly (7d, 3d),
monthly (3d), bi-weekly (1d), daily/weekly/once (day-of only)
- Overdue tapering: daily for 3 days, then every 3 days, stops after 14 days
- Reminder log table prevents duplicate notifications per due date/stage
- Admin endpoint displays notification schedules for all frequencies
- Comprehensive test suite (100 random tasks, 61 days each, 10 test functions)
New files:
- internal/notifications/reminder_config.go - Editable schedule configuration
- internal/notifications/reminder_schedule.go - Schedule lookup logic
- internal/notifications/reminder_schedule_test.go - Dynamic test suite
- internal/models/reminder_log.go - TaskReminderLog model
- internal/repositories/reminder_repo.go - Reminder log repository
- migrations/010_add_task_reminder_log.{up,down}.sql
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
64 lines
2.5 KiB
Go
64 lines
2.5 KiB
Go
package notifications
|
|
|
|
// ============================================================
|
|
// REMINDER CONFIGURATION
|
|
// Edit these values to adjust notification behavior
|
|
// ============================================================
|
|
|
|
// OverdueConfig controls when and how often overdue reminders are sent
|
|
var OverdueConfig = struct {
|
|
DailyReminderDays int // Send daily reminders for first N days overdue
|
|
TaperIntervalDays int // After daily period, remind every N days
|
|
MaxOverdueDays int // Stop reminding after N days overdue
|
|
}{
|
|
DailyReminderDays: 3, // Daily for days 1-3
|
|
TaperIntervalDays: 3, // Then every 3 days (4, 7, 10, 13)
|
|
MaxOverdueDays: 14, // Stop after 14 days
|
|
}
|
|
|
|
// FrequencySchedules - EXPLICIT entry for each of the 9 seeded frequencies
|
|
// Key: interval days (matches TaskFrequency.days in DB, 0 = Once/null)
|
|
// Value: array of days before due date to send reminders (0 = day-of)
|
|
//
|
|
// To add a reminder: append the number of days before to the slice
|
|
// Example: FrequencySchedules[30] = []int{7, 3, 0} adds 7-day warning to Monthly
|
|
var FrequencySchedules = map[int][]int{
|
|
0: {0}, // 1. Once (null/0): day-of only
|
|
1: {0}, // 2. Daily: day-of only
|
|
7: {0}, // 3. Weekly: day-of only
|
|
14: {1, 0}, // 4. Bi-Weekly: 1 day before, day-of
|
|
30: {3, 0}, // 5. Monthly: 3 days before, day-of
|
|
90: {7, 3, 0}, // 6. Quarterly: 7d, 3d, day-of
|
|
180: {14, 7, 0}, // 7. Semi-Annually: 14d, 7d, day-of
|
|
365: {30, 14, 7, 0}, // 8. Annually: 30d, 14d, 7d, day-of
|
|
}
|
|
|
|
// HumanReadableSchedule returns admin-friendly description for each frequency
|
|
// Key: interval days (matches FrequencySchedules keys)
|
|
// Value: human-readable description of the reminder schedule
|
|
var HumanReadableSchedule = map[int]string{
|
|
0: "Day-of → Overdue (tapering)",
|
|
1: "Day-of → Overdue (tapering)",
|
|
7: "Day-of → Overdue (tapering)",
|
|
14: "1 day before → Day-of → Overdue",
|
|
30: "3 days before → Day-of → Overdue",
|
|
90: "7d → 3d → Day-of → Overdue",
|
|
180: "14d → 7d → Day-of → Overdue",
|
|
365: "30d → 14d → 7d → Day-of → Overdue",
|
|
}
|
|
|
|
// FrequencyNames maps interval days to frequency names for display
|
|
var FrequencyNames = map[int]string{
|
|
0: "Once",
|
|
1: "Daily",
|
|
7: "Weekly",
|
|
14: "Bi-Weekly",
|
|
30: "Monthly",
|
|
90: "Quarterly",
|
|
180: "Semi-Annually",
|
|
365: "Annually",
|
|
}
|
|
|
|
// OrderedFrequencies defines the display order for frequencies
|
|
var OrderedFrequencies = []int{0, 1, 7, 14, 30, 90, 180, 365}
|