mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-21 08:01:30 +00:00
d227038ca0
Disable the linters that fire on legacy code without flagging real bugs: `unused` (orphan repos kept for future feature flags), `unconvert` (defensive type conversions), `gosimple` (style suggestions in code we don't want to touch). govet: disable `shadow` (idiomatic `err :=` re-decls in transaction patterns) and `nilness` (legitimate defensive nil checks that look tautological to the analyzer). Ran `gofmt -w internal/ cmd/` — every Go file now passes gofmt -l with no output. Kept: govet, staticcheck, ineffassign, typecheck, bodyclose, noctx, sqlclosecheck, gofmt, goimports, misspell — the real-bug checks.
61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package scheduler
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// CalculateNextEmailTime calculates the optimal time to send a user-initiated email (smart send)
|
|
func (s *schedulerService) CalculateNextEmailTime(ctx context.Context, accountID uuid.UUID) (time.Time, error) {
|
|
// STEP 1: Load email account
|
|
account, xerr := s.emailRepo.GetByID(ctx, accountID)
|
|
if xerr != nil {
|
|
return time.Time{}, xerr
|
|
}
|
|
if account == nil {
|
|
return time.Now(), nil
|
|
}
|
|
|
|
accountTZ := loadLocation(account.Timezone)
|
|
|
|
// STEP 2: Get last email time and apply min_wait_time
|
|
candidateTime := time.Now()
|
|
|
|
lastEmailTime, err := s.taskRepo.GetLastEmailTime(ctx, accountID)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
|
|
if lastEmailTime != nil {
|
|
minWait := time.Second * time.Duration(account.MinWaitTime)
|
|
earliestNext := lastEmailTime.Add(minWait)
|
|
if candidateTime.Before(earliestNext) {
|
|
candidateTime = earliestNext
|
|
}
|
|
}
|
|
|
|
// STEP 3: Ensure within business hours (8am-8pm account timezone)
|
|
candidateTime = ensureBusinessHours(candidateTime, account.Timezone)
|
|
|
|
// STEP 4: Add jitter (±10 minutes)
|
|
jitter := randomJitter(-10, 10)
|
|
candidateTime = candidateTime.Add(time.Minute * time.Duration(jitter))
|
|
|
|
// STEP 5: Resolve conflicts with scheduled tasks
|
|
scheduledTasks, err := s.taskRepo.GetScheduledTasksForAccount(ctx, accountID, candidateTime)
|
|
if err != nil {
|
|
return time.Time{}, err
|
|
}
|
|
candidateTime = resolveConflicts(candidateTime, scheduledTasks, account.MinWaitTime)
|
|
|
|
// STEP 6: Apply distribution curve
|
|
candidateTime = applyDistributionCurve(candidateTime, accountTZ)
|
|
|
|
// STEP 7: Final business hours check
|
|
candidateTime = ensureBusinessHours(candidateTime, account.Timezone)
|
|
|
|
return candidateTime, nil
|
|
}
|