Streak, consistência e totais diários de hábitos
TLDR: Adicionar contagem de streak, nível de consistência e totais diários de conclusão nos endpoints de execução de hábitos.
Contexto
O frontend precisa de um feedback mais rico sobre o progresso dos hábitos. Os endpoints de execução retornavam apenas dados brutos, sem métricas agregadas. Três informações estavam faltando:
- Quantos dias consecutivos o usuário completou um hábito (streak)
- Em qual fase de consistência o usuário está (iniciando → consolidado)
- Quantos hábitos do dia foram concluídos vs o total (resumo diário)
Objetivos
- Expor
streak(dias consecutivos completados) por hábito - Expor
consistency(nível semântico) por hábito - Alterar o response do
GET /habits/executions/<date>para incluir as contagens detotalecompletedde hábitos junto com a lista
Fora de escopo
— (não registrado na spec original)
Mudanças
apps/habits/services.py — HabitDayHistoryService (nova classe)
Tabela de histórico com uma linha por (hábito, data), para evitar recalcular streak/consistência varrendo todas as execuções.
sync_day(habit, date)
- Agrega as execuções do dia (total, completed, pending)
- Remove a entrada anterior com hard_delete (necessário porque BaseModel usa soft-delete — .delete() apenas seta deleted_at, deixando a linha no banco e violando a constraint única)
- Recria com o status correto: in_progress, completed, missed ou partial
sync_habit_range(habit, start_date, end_date)
- Versão em lote de sync_day — usada após operações de bulk_create/update
- Usa .order_by() antes do .values('scheduled_date').annotate() para evitar que o ordering padrão do model (scheduled_time) entre no GROUP BY e gere múltiplas linhas por data
apps/habits/services.py — ExecutionService
get_streak(habit)
- Lê HabitDayHistory ordenado por data DESC
- Pula dias in_progress (dia ainda em andamento: não quebra nem conta)
- Conta dias completed consecutivos
- Para no primeiro dia missed ou partial
get_consistency_level(habit)
- total_days = total de linhas em HabitDayHistory até hoje
- completed_days = linhas com status='completed'
- porcentagem = completed_days / total_days * 100
- Retorna: "starting" (0–20%), "developing" (21–50%), "consistent" (51–80%), "consolidated" (81–100%)
apps/habits/signals.py (novo arquivo)
Signal post_save em HabitExecution → chama HabitDayHistoryService.sync_day automaticamente a cada save individual (ex.: usuário marca execução como concluída).
apps/habits/views.py — ExecutionListByHabitView
Adiciona streak e consistency no response, junto com habit_id e habit_name:
json
{
"count": 30,
"next": null,
"previous": null,
"results": [],
"habit_id": 1,
"habit_name": "Meditação",
"streak": 5,
"consistency": "developing"
}
apps/habits/serializers/execution.py — ExecutionSerializer
Adiciona streak e consistency (usado pelo endpoint por data). A view pré-computa os valores por hábito e passa via contexto, para evitar N+1.
apps/habits/views.py — ExecutionListByDateView
Override do list() para envolver o response com os totais diários:
json
{
"total": 5,
"completed": 3,
"results": []
}
total= quantidade de hábitos distintos com execuções naquela datacompleted= quantidade de hábitos em que TODAS as execuções do dia estãocompleted- Cada item em
resultscarregastreakeconsistencydo seu hábito - Mudança que quebra contrato — confirmado pelo time de frontend que não há problema
Como verificar
- Criar hábito com 3 horários por dia
- Completar os 3 no dia 1 e no dia 2, falhar no dia 3 →
streakdeve ser 0, não 2 - Completar 6 de 30 dias esperados →
consistency="starting"(20%) - Completar 16 de 30 →
consistency="consistent"(53%) - Chamar
GET /habits/executions/<date>com 5 hábitos no dia, 3 totalmente concluídos →total=5, completed=3 - Testes cobrem happy path, reset de streak, cada limiar de consistência e os totais diários
Documentação
Atualizar a documentação de hábitos com as regras de streak e consistência.