Lembretes de hábitos não executados
TLDR: Enviar até 4 lembretes de 2 em 2 horas para hábitos não executados no dia, com variação aleatória de mensagem sorteada no primeiro lembrete (8 fluxos), rastreando o horário de cada lembrete em campos dedicados do
HabitExecution.
Contexto
Hoje o HabitNotificationService envia apenas uma notificação no horário agendado do hábito. Se o usuário não executar, não recebe nenhum lembrete. A solicitação é enviar até 4 lembretes a cada 2 horas enquanto a execução permanecer pending, com variação aleatória de mensagem (8 fluxos definidos no Figma), usando campos de timestamp para rastrear cada lembrete individualmente.
Objetivos
- Adicionar os campos
first_reminder,second_reminder,third_reminder,fourth_reminderereminder_variationemHabitExecution - Criar a task periódica
send_habit_remindersrodando a cada 60s - Enviar o lembrete 1 após 2h do
scheduled_time, o 2 após 2h dofirst_reminder, o 3 após 2h dosecond_remindere o 4 após 2h dothird_reminder - No primeiro lembrete, sortear aleatoriamente um dos 8 fluxos de mensagem e persistir em
reminder_variation - Todos os lembretes subsequentes usam o fluxo sorteado no primeiro
- Parar os lembretes se o
statusmudar paracompletedounot-completed
Fora de escopo
— (não registrado na spec original)
Mudanças
apps/habits/models/execution.py
Adicionar 5 campos em HabitExecution:
python
first_reminder = models.DateTimeField(blank=True, null=True)
second_reminder = models.DateTimeField(blank=True, null=True)
third_reminder = models.DateTimeField(blank=True, null=True)
fourth_reminder = models.DateTimeField(blank=True, null=True)
reminder_variation = models.PositiveSmallIntegerField(blank=True, null=True)
apps/habits/migrations/0011_habitexecution_reminders.py (novo)
Migration adicionando os 5 campos nullable.
apps/habits/services.py
Adicionar métodos de lembrete diretamente em ExecutionService:
```python class ExecutionService: REMINDER_INTERVAL = timedelta(hours=2) REMINDER_BODY = “Não se esqueça: {name}”
@classmethod
def send_pending_reminders(cls):
now = timezone.localtime()
cls._send_first_reminders(now)
cls._send_second_reminders(now)
cls._send_third_reminders(now)
cls._send_fourth_reminders(now) ```
Lógica por etapa:
- Primeiro: status='pending' + notification_ids__len > 0 + first_reminder__isnull=True + scheduled_date+scheduled_time + 2h <= now → sorteia reminder_variation (1-8)
- Segundo: first_reminder__isnull=False + second_reminder__isnull=True + first_reminder + 2h <= now
- Terceiro: second_reminder__isnull=False + third_reminder__isnull=True + second_reminder + 2h <= now
- Quarto: third_reminder__isnull=False + fourth_reminder__isnull=True + third_reminder + 2h <= now
Cada lembrete usa REMINDER_FLOWS[reminder_variation][index] para obter título e corpo. Por etapa: buscar execuções elegíveis com prefetch_related('habit__user__push_devices'), criar todas as Notification em bulk_create e atualizar os timestamps com bulk_update.
apps/habits/tasks.py
python
@app.task(...)
def send_habit_reminders():
ExecutionService.send_pending_reminders()
config/celery_defaults.py
python
'send-habit-reminders': {
'task': 'apps.habits.tasks.send_habit_reminders',
'schedule': 60.0,
},
tests/habits/test_reminder_service.py (novo)
Testes Triple-A cobrindo: cada onda disparando após 2h da anterior; nenhum lembrete antes de 2h; nenhum lembrete após o fourth_reminder; nenhum lembrete se status != 'pending'; nenhum lembrete se notification_ids vazio; enable_notifications=False não envia; reminder_variation entre 1 e 8 no primeiro lembrete; mensagem usando o texto do fluxo sorteado.
Como verificar
python manage.py migrate habits— verificar a migration aplicada- Criar hábito com
scheduled_time2h no passado,status='pending'enotification_idspreenchido — rodarsend_habit_reminders→first_reminderpreenchido e notificação criada - Executar o hábito (
status='completed') — rodar novamente → nenhum lembrete adicional make test ARGS="tests/habits/test_reminder_service.py -v"— todos passam
Documentação
- reference/notifications/habit_push_notifications.md — fluxo completo das ondas de lembrete