refactor(go): split portfolio, workshop, training/models, roadmap stores

portfolio/store.go (818 LOC) → store_portfolio.go, store_items.go, store_metrics.go
workshop/store.go (793 LOC) → store_sessions.go, store_participants.go, store_responses.go
training/models.go (757 LOC) → models_enums.go, models_core.go, models_api.go, models_blocks.go
roadmap/store.go (757 LOC) → store_roadmap.go, store_items.go, store_import.go

All files under 350 LOC. Zero behavior changes, same package declarations.
go vet passes on all five packages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-19 09:49:31 +02:00
parent c293d76e6b
commit 3fb5b94905
17 changed files with 3190 additions and 3125 deletions

View File

@@ -0,0 +1,276 @@
package training
import (
"time"
"github.com/google/uuid"
)
// ============================================================================
// Main Entities
// ============================================================================
// TrainingModule represents a compliance training module
type TrainingModule struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
AcademyCourseID *uuid.UUID `json:"academy_course_id,omitempty"`
ModuleCode string `json:"module_code"`
Title string `json:"title"`
Description string `json:"description,omitempty"`
RegulationArea RegulationArea `json:"regulation_area"`
NIS2Relevant bool `json:"nis2_relevant"`
ISOControls []string `json:"iso_controls"` // JSONB
FrequencyType FrequencyType `json:"frequency_type"`
ValidityDays int `json:"validity_days"`
RiskWeight float64 `json:"risk_weight"`
ContentType string `json:"content_type"`
DurationMinutes int `json:"duration_minutes"`
PassThreshold int `json:"pass_threshold"`
IsActive bool `json:"is_active"`
SortOrder int `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TrainingMatrixEntry represents a role-to-module mapping in the CTM
type TrainingMatrixEntry struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
RoleCode string `json:"role_code"`
ModuleID uuid.UUID `json:"module_id"`
IsMandatory bool `json:"is_mandatory"`
Priority int `json:"priority"`
CreatedAt time.Time `json:"created_at"`
// Joined fields (optional, populated in queries)
ModuleCode string `json:"module_code,omitempty"`
ModuleTitle string `json:"module_title,omitempty"`
}
// TrainingAssignment represents a user's training assignment
type TrainingAssignment struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
ModuleID uuid.UUID `json:"module_id"`
UserID uuid.UUID `json:"user_id"`
UserName string `json:"user_name"`
UserEmail string `json:"user_email"`
RoleCode string `json:"role_code,omitempty"`
TriggerType TriggerType `json:"trigger_type"`
TriggerEvent string `json:"trigger_event,omitempty"`
Status AssignmentStatus `json:"status"`
ProgressPercent int `json:"progress_percent"`
QuizScore *float64 `json:"quiz_score,omitempty"`
QuizPassed *bool `json:"quiz_passed,omitempty"`
QuizAttempts int `json:"quiz_attempts"`
StartedAt *time.Time `json:"started_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
Deadline time.Time `json:"deadline"`
CertificateID *uuid.UUID `json:"certificate_id,omitempty"`
EscalationLevel int `json:"escalation_level"`
LastEscalationAt *time.Time `json:"last_escalation_at,omitempty"`
EnrollmentID *uuid.UUID `json:"enrollment_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Joined fields
ModuleCode string `json:"module_code,omitempty"`
ModuleTitle string `json:"module_title,omitempty"`
}
// QuizQuestion represents a persistent quiz question for a module
type QuizQuestion struct {
ID uuid.UUID `json:"id"`
ModuleID uuid.UUID `json:"module_id"`
Question string `json:"question"`
Options []string `json:"options"` // JSONB
CorrectIndex int `json:"correct_index"`
Explanation string `json:"explanation,omitempty"`
Difficulty Difficulty `json:"difficulty"`
IsActive bool `json:"is_active"`
SortOrder int `json:"sort_order"`
CreatedAt time.Time `json:"created_at"`
}
// QuizAttempt represents a single quiz attempt by a user
type QuizAttempt struct {
ID uuid.UUID `json:"id"`
AssignmentID uuid.UUID `json:"assignment_id"`
UserID uuid.UUID `json:"user_id"`
Answers []QuizAnswer `json:"answers"` // JSONB
Score float64 `json:"score"`
Passed bool `json:"passed"`
CorrectCount int `json:"correct_count"`
TotalCount int `json:"total_count"`
DurationSeconds *int `json:"duration_seconds,omitempty"`
AttemptedAt time.Time `json:"attempted_at"`
}
// QuizAnswer represents a single answer within a quiz attempt
type QuizAnswer struct {
QuestionID uuid.UUID `json:"question_id"`
SelectedIndex int `json:"selected_index"`
Correct bool `json:"correct"`
}
// AuditLogEntry represents an entry in the training audit trail
type AuditLogEntry struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
UserID *uuid.UUID `json:"user_id,omitempty"`
Action AuditAction `json:"action"`
EntityType AuditEntityType `json:"entity_type"`
EntityID *uuid.UUID `json:"entity_id,omitempty"`
Details map[string]interface{} `json:"details"` // JSONB
IPAddress string `json:"ip_address,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// ModuleContent represents LLM-generated or manual content for a module
type ModuleContent struct {
ID uuid.UUID `json:"id"`
ModuleID uuid.UUID `json:"module_id"`
Version int `json:"version"`
ContentFormat ContentFormat `json:"content_format"`
ContentBody string `json:"content_body"`
Summary string `json:"summary,omitempty"`
GeneratedBy string `json:"generated_by,omitempty"`
LLMModel string `json:"llm_model,omitempty"`
IsPublished bool `json:"is_published"`
ReviewedBy *uuid.UUID `json:"reviewed_by,omitempty"`
ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TrainingStats contains aggregated training metrics
type TrainingStats struct {
TotalModules int `json:"total_modules"`
TotalAssignments int `json:"total_assignments"`
CompletionRate float64 `json:"completion_rate"`
OverdueCount int `json:"overdue_count"`
PendingCount int `json:"pending_count"`
InProgressCount int `json:"in_progress_count"`
CompletedCount int `json:"completed_count"`
AvgQuizScore float64 `json:"avg_quiz_score"`
AvgCompletionDays float64 `json:"avg_completion_days"`
UpcomingDeadlines int `json:"upcoming_deadlines"` // within 7 days
}
// ComplianceGap represents a missing or overdue training requirement
type ComplianceGap struct {
ModuleID uuid.UUID `json:"module_id"`
ModuleCode string `json:"module_code"`
ModuleTitle string `json:"module_title"`
RegulationArea RegulationArea `json:"regulation_area"`
RoleCode string `json:"role_code"`
IsMandatory bool `json:"is_mandatory"`
AssignmentID *uuid.UUID `json:"assignment_id,omitempty"`
Status string `json:"status"` // "missing", "overdue", "expired"
Deadline *time.Time `json:"deadline,omitempty"`
}
// EscalationResult represents the result of an escalation check
type EscalationResult struct {
AssignmentID uuid.UUID `json:"assignment_id"`
UserID uuid.UUID `json:"user_id"`
UserName string `json:"user_name"`
UserEmail string `json:"user_email"`
ModuleTitle string `json:"module_title"`
PreviousLevel int `json:"previous_level"`
NewLevel int `json:"new_level"`
DaysOverdue int `json:"days_overdue"`
EscalationLabel string `json:"escalation_label"`
}
// DeadlineInfo represents upcoming deadline information
type DeadlineInfo struct {
AssignmentID uuid.UUID `json:"assignment_id"`
ModuleCode string `json:"module_code"`
ModuleTitle string `json:"module_title"`
UserID uuid.UUID `json:"user_id"`
UserName string `json:"user_name"`
Deadline time.Time `json:"deadline"`
DaysLeft int `json:"days_left"`
Status AssignmentStatus `json:"status"`
}
// ============================================================================
// Filter Types
// ============================================================================
// ModuleFilters defines filters for listing modules
type ModuleFilters struct {
RegulationArea RegulationArea
FrequencyType FrequencyType
IsActive *bool
NIS2Relevant *bool
Search string
Limit int
Offset int
}
// AssignmentFilters defines filters for listing assignments
type AssignmentFilters struct {
ModuleID *uuid.UUID
UserID *uuid.UUID
RoleCode string
Status AssignmentStatus
Overdue *bool
Limit int
Offset int
}
// AuditLogFilters defines filters for listing audit log entries
type AuditLogFilters struct {
UserID *uuid.UUID
Action AuditAction
EntityType AuditEntityType
Limit int
Offset int
}
// CanonicalControlSummary is a lightweight view on canonical_controls for the training pipeline
type CanonicalControlSummary struct {
ControlID string `json:"control_id"`
Title string `json:"title"`
Objective string `json:"objective"`
Rationale string `json:"rationale"`
Requirements []string `json:"requirements"`
Severity string `json:"severity"`
Category string `json:"category"`
TargetAudience string `json:"target_audience"`
Tags []string `json:"tags"`
}
// CanonicalControlMeta provides aggregated metadata about canonical controls
type CanonicalControlMeta struct {
Domains []DomainCount `json:"domains"`
Categories []CategoryCount `json:"categories"`
Audiences []AudienceCount `json:"audiences"`
Total int `json:"total"`
}
// DomainCount is a domain with its control count
type DomainCount struct {
Domain string `json:"domain"`
Count int `json:"count"`
}
// CategoryCount is a category with its control count
type CategoryCount struct {
Category string `json:"category"`
Count int `json:"count"`
}
// AudienceCount is a target audience with its control count
type AudienceCount struct {
Audience string `json:"audience"`
Count int `json:"count"`
}
// BulkResult holds the result of a bulk generation operation
type BulkResult struct {
Generated int `json:"generated"`
Skipped int `json:"skipped"`
Errors []string `json:"errors"`
}