Add honeycomb completion heatmap and data migration framework

- Add completion_summary endpoint data to residence detail response
- Track completed_from_column on task completions (overdue/due_soon/upcoming)
- Add GetCompletionSummary repo method with monthly aggregation
- Add one-time data migration framework (data_migrations table + registry)
- Add backfill migration to classify historical completions
- Add standalone backfill script for manual/dry-run usage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-12 00:05:10 -05:00
parent 739b245ee6
commit 6803f6ec18
12 changed files with 958 additions and 21 deletions

View File

@@ -17,6 +17,7 @@ import (
"github.com/treytartt/honeydue-api/internal/dto/responses"
"github.com/treytartt/honeydue-api/internal/models"
"github.com/treytartt/honeydue-api/internal/repositories"
"github.com/treytartt/honeydue-api/internal/task/categorization"
)
// Task-related errors (DEPRECATED - kept for reference, use apperrors instead)
@@ -551,13 +552,18 @@ func (s *TaskService) CreateCompletion(req *requests.CreateTaskCompletionRequest
completedAt = *req.CompletedAt
}
// Capture the kanban column BEFORE mutating NextDueDate/InProgress,
// so we know what state the task was in when the user completed it.
completedFromColumn := categorization.DetermineKanbanColumnWithTime(task, 30, now)
completion := &models.TaskCompletion{
TaskID: req.TaskID,
CompletedByID: userID,
CompletedAt: completedAt,
Notes: req.Notes,
ActualCost: req.ActualCost,
Rating: req.Rating,
TaskID: req.TaskID,
CompletedByID: userID,
CompletedAt: completedAt,
Notes: req.Notes,
ActualCost: req.ActualCost,
Rating: req.Rating,
CompletedFromColumn: completedFromColumn,
}
// Determine interval days for NextDueDate calculation before entering the transaction.
@@ -680,11 +686,15 @@ func (s *TaskService) QuickComplete(taskID uint, userID uint) error {
completedAt := time.Now().UTC()
// Capture kanban column before state mutation
completedFromColumn := categorization.DetermineKanbanColumn(task, 30)
completion := &models.TaskCompletion{
TaskID: taskID,
CompletedByID: userID,
CompletedAt: completedAt,
Notes: "Completed from widget",
TaskID: taskID,
CompletedByID: userID,
CompletedAt: completedAt,
Notes: "Completed from widget",
CompletedFromColumn: completedFromColumn,
}
if err := s.taskRepo.CreateCompletion(completion); err != nil {