Chat autosend, week rollover and ai_runs purge schedules (Feature 20)
shopping_chat_autosend fires at chat_autosend_dow/time for the coming
week and posts its Supply run through SendToChat when a webhook is
stored, list_ready's Chat channel is on and the list exists unsent;
otherwise it logs why (key list_ready:chat:{weekStart}). week_rollover
freezes the previous week at Monday 00:05 through Freeze, judging the
week ended by the fire time. ai_runs_purge calls harness.Purge daily at
03:00 with the fire time less RunRetention. Fake-clock tests cover each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package shopping
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/notify"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/scheduler"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
|
||||
)
|
||||
|
||||
// RolloverTime is when, every Monday in the household zone, week_rollover
|
||||
// freezes the week that has just ended. It is fixed, not a setting.
|
||||
const RolloverTime = "00:05"
|
||||
|
||||
// Reasons Autosend posts nothing.
|
||||
const (
|
||||
AutosendNotConfigured = "no Google Chat webhook is configured"
|
||||
AutosendChatOff = "list_ready is switched off for Google Chat"
|
||||
AutosendNoList = "the week has no Supply run"
|
||||
AutosendAlreadySent = "the Supply run was already sent to Google Chat"
|
||||
AutosendUnconfirmed = "a send of the Supply run to Google Chat is unconfirmed"
|
||||
)
|
||||
|
||||
// AutosendKey identifies the automatic send of the Supply run of the week
|
||||
// starting weekStart, list_ready:chat:YYYY-MM-DD, in the schedule's logs.
|
||||
// The send itself is recorded in the list's sent_to_chat_at.
|
||||
func AutosendKey(weekStart store.LocalDate) string {
|
||||
return string(notify.ListReady) + ":" + string(notify.ChannelChat) + ":" + weekStart.String()
|
||||
}
|
||||
|
||||
// ChatAutosendSchedule is the shopping_chat_autosend schedule: at the
|
||||
// household's chat_autosend_dow and chat_autosend_time it sends the Supply
|
||||
// run of the coming week, the week after the fire date's, to Google Chat
|
||||
// with Autosend. Its period scheduler.WeeklyForNextWeek keys
|
||||
// shopping_chat_autosend:YYYY-Www for that week. A send that fails is the
|
||||
// period's error and is not retried; the household can still send it from
|
||||
// the Supply run.
|
||||
func (s *Service) ChatAutosendSchedule() scheduler.Schedule {
|
||||
return scheduler.Schedule{
|
||||
Name: scheduler.ShoppingChatAutosend,
|
||||
Rule: func(settings household.Settings) scheduler.Rule {
|
||||
return scheduler.WeeklyForNextWeek(time.Weekday(settings.ChatAutosendDow), settings.ChatAutosendTime)
|
||||
},
|
||||
Run: func(ctx context.Context, p scheduler.Period) error {
|
||||
skipped, err := s.Autosend(ctx, p.Week)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log := s.logger.With("period", p.Key, "key", AutosendKey(p.Week), "week_start", p.Week.String())
|
||||
if skipped != "" {
|
||||
log.InfoContext(ctx, "Supply run not sent to Google Chat: "+skipped)
|
||||
return nil
|
||||
}
|
||||
log.InfoContext(ctx, "Supply run sent to Google Chat")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Autosend sends the Supply run of the week starting weekStart to Google
|
||||
// Chat with SendToChat, as the household's send does, when a webhook is
|
||||
// stored, list_ready's Google Chat channel is switched on, and the week has
|
||||
// a Supply run not yet sent and with no send unconfirmed. Otherwise it
|
||||
// posts nothing and returns which of those reasons stopped it; it never
|
||||
// resends. A send that fails is SendToChat's error.
|
||||
func (s *Service) Autosend(ctx context.Context, weekStart store.LocalDate) (skipped string, err error) {
|
||||
settings, err := s.chat.Get(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("shopping: autosend: %w", err)
|
||||
}
|
||||
if settings.URLMasked == nil {
|
||||
return AutosendNotConfigured, nil
|
||||
}
|
||||
on, err := s.notifier.ChannelOn(ctx, notify.ListReady, notify.ChannelChat)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("shopping: autosend: %w", err)
|
||||
}
|
||||
if !on {
|
||||
return AutosendChatOff, nil
|
||||
}
|
||||
l, err := s.Get(ctx, weekStart)
|
||||
if errors.Is(err, ErrListNotFound) {
|
||||
return AutosendNoList, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch {
|
||||
case l.SentToChatAt != nil:
|
||||
return AutosendAlreadySent, nil
|
||||
case l.ChatSendUnconfirmedAt != nil:
|
||||
return AutosendUnconfirmed, nil
|
||||
}
|
||||
if _, err := s.SendToChat(ctx, weekStart, false); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// RolloverSchedule is the week_rollover schedule: every Monday at
|
||||
// RolloverTime it freezes the Supply run of the week before the fire
|
||||
// date's into the Supply archive with Freeze, the week ending by the fire
|
||||
// time. Its period scheduler.Weekly keys week_rollover:YYYY-Www for the
|
||||
// fire date's week. A week with no Supply run has nothing to freeze, and
|
||||
// the period says so; a freeze that fails is the period's error and is not
|
||||
// retried.
|
||||
func (s *Service) RolloverSchedule() scheduler.Schedule {
|
||||
return scheduler.Schedule{
|
||||
Name: scheduler.WeekRollover,
|
||||
Rule: func(household.Settings) scheduler.Rule {
|
||||
return scheduler.Weekly(time.Monday, RolloverTime)
|
||||
},
|
||||
Run: func(ctx context.Context, p scheduler.Period) error {
|
||||
y, m, d := p.Week.Date()
|
||||
last := store.LocalDateOf(time.Date(y, m, d-7, 0, 0, 0, 0, time.UTC))
|
||||
err := s.freeze(ctx, last, p.FireAt)
|
||||
if errors.Is(err, ErrListNotFound) {
|
||||
s.logger.InfoContext(ctx, "week rollover froze nothing: the week has no Supply run",
|
||||
"period", p.Key, "week_start", last.String())
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user