package training import ( "context" "github.com/google/uuid" ) // GetTrainingStats returns aggregated training statistics for a tenant func (s *Store) GetTrainingStats(ctx context.Context, tenantID uuid.UUID) (*TrainingStats, error) { stats := &TrainingStats{} // Total active modules s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM training_modules WHERE tenant_id = $1 AND is_active = true", tenantID).Scan(&stats.TotalModules) // Total assignments s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM training_assignments WHERE tenant_id = $1", tenantID).Scan(&stats.TotalAssignments) // Status counts s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM training_assignments WHERE tenant_id = $1 AND status = 'pending'", tenantID).Scan(&stats.PendingCount) s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM training_assignments WHERE tenant_id = $1 AND status = 'in_progress'", tenantID).Scan(&stats.InProgressCount) s.pool.QueryRow(ctx, "SELECT COUNT(*) FROM training_assignments WHERE tenant_id = $1 AND status = 'completed'", tenantID).Scan(&stats.CompletedCount) // Completion rate if stats.TotalAssignments > 0 { stats.CompletionRate = float64(stats.CompletedCount) / float64(stats.TotalAssignments) * 100 } // Overdue count s.pool.QueryRow(ctx, ` SELECT COUNT(*) FROM training_assignments WHERE tenant_id = $1 AND status IN ('pending', 'in_progress') AND deadline < NOW() `, tenantID).Scan(&stats.OverdueCount) // Average quiz score s.pool.QueryRow(ctx, ` SELECT COALESCE(AVG(quiz_score), 0) FROM training_assignments WHERE tenant_id = $1 AND quiz_score IS NOT NULL `, tenantID).Scan(&stats.AvgQuizScore) // Average completion days s.pool.QueryRow(ctx, ` SELECT COALESCE(AVG(EXTRACT(EPOCH FROM (completed_at - started_at)) / 86400), 0) FROM training_assignments WHERE tenant_id = $1 AND status = 'completed' AND started_at IS NOT NULL AND completed_at IS NOT NULL `, tenantID).Scan(&stats.AvgCompletionDays) // Upcoming deadlines (within 7 days) s.pool.QueryRow(ctx, ` SELECT COUNT(*) FROM training_assignments WHERE tenant_id = $1 AND status IN ('pending', 'in_progress') AND deadline BETWEEN NOW() AND NOW() + INTERVAL '7 days' `, tenantID).Scan(&stats.UpcomingDeadlines) return stats, nil } // GetDeadlines returns upcoming deadlines for a tenant func (s *Store) GetDeadlines(ctx context.Context, tenantID uuid.UUID, limit int) ([]DeadlineInfo, error) { if limit <= 0 { limit = 20 } rows, err := s.pool.Query(ctx, ` SELECT ta.id, m.module_code, m.title, ta.user_id, ta.user_name, ta.deadline, ta.status, EXTRACT(DAY FROM (ta.deadline - NOW()))::INT AS days_left FROM training_assignments ta JOIN training_modules m ON m.id = ta.module_id WHERE ta.tenant_id = $1 AND ta.status IN ('pending', 'in_progress') ORDER BY ta.deadline ASC LIMIT $2 `, tenantID, limit) if err != nil { return nil, err } defer rows.Close() var deadlines []DeadlineInfo for rows.Next() { var d DeadlineInfo var status string err := rows.Scan( &d.AssignmentID, &d.ModuleCode, &d.ModuleTitle, &d.UserID, &d.UserName, &d.Deadline, &status, &d.DaysLeft, ) if err != nil { return nil, err } d.Status = AssignmentStatus(status) deadlines = append(deadlines, d) } if deadlines == nil { deadlines = []DeadlineInfo{} } return deadlines, nil }