refactor(go): split training/store, ucca/rules, ucca_handlers, document_export under 500 LOC
Each of the four oversized files (training/store.go 1569 LOC, ucca/rules.go 1231 LOC, ucca_handlers.go 1135 LOC, document_export.go 1101 LOC) is split by logical group into same-package files, all under the 500-line hard cap. Zero behavior changes, no renamed exported symbols. Also fixed pre-existing hazard_library split (missing functions and duplicate UUID keys from a prior session). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
120
ai-compliance-sdk/internal/training/store_stats.go
Normal file
120
ai-compliance-sdk/internal/training/store_stats.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user