feat(training): add Media Pipeline — TTS Audio, Presentation Video, Bulk Generation
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 48s
CI / test-python-backend-compliance (push) Successful in 35s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 20s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 48s
CI / test-python-backend-compliance (push) Successful in 35s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 20s
Phase A: 8 new IT-Security training modules (SEC-PWD, SEC-DESK, SEC-KIAI, SEC-BYOD, SEC-VIDEO, SEC-USB, SEC-INC, SEC-HOME) with CTM entries. Bulk content and quiz generation endpoints for all 28 modules. Phase B: Piper TTS service (Python/FastAPI) for local German speech synthesis. training_media table, TTSClient in Go backend, audio generation endpoints, AudioPlayer component in frontend. MinIO storage integration. Phase C: FFmpeg presentation video pipeline — LLM generates slide scripts, ImageMagick renders 1920x1080 slides, FFmpeg combines with audio to MP4. VideoPlayer and ScriptPreview components in frontend. New files: 15 created, 9 modified - compliance-tts-service/ (Dockerfile, main.py, tts_engine.py, storage.py, slide_renderer.py, video_generator.py) - migrations 014-016 (training engine, IT-security modules, media table) - training package (models, store, content_generator, media, handlers) - frontend (AudioPlayer, VideoPlayer, ScriptPreview, api, types, page) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
127
ai-compliance-sdk/internal/training/matrix.go
Normal file
127
ai-compliance-sdk/internal/training/matrix.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package training
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ComputeRequiredModules returns all required training modules for a user
|
||||
// based on their assigned roles. Deduplicates modules across roles.
|
||||
func ComputeRequiredModules(ctx context.Context, store *Store, tenantID uuid.UUID, roleCodes []string) ([]TrainingModule, error) {
|
||||
seen := make(map[uuid.UUID]bool)
|
||||
var modules []TrainingModule
|
||||
|
||||
for _, role := range roleCodes {
|
||||
entries, err := store.GetMatrixForRole(ctx, tenantID, role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if seen[entry.ModuleID] {
|
||||
continue
|
||||
}
|
||||
seen[entry.ModuleID] = true
|
||||
|
||||
module, err := store.GetModule(ctx, entry.ModuleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if module != nil && module.IsActive {
|
||||
modules = append(modules, *module)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if modules == nil {
|
||||
modules = []TrainingModule{}
|
||||
}
|
||||
|
||||
return modules, nil
|
||||
}
|
||||
|
||||
// GetComplianceGaps finds modules that are required but not completed for a user
|
||||
func GetComplianceGaps(ctx context.Context, store *Store, tenantID uuid.UUID, userID uuid.UUID, roleCodes []string) ([]ComplianceGap, error) {
|
||||
var gaps []ComplianceGap
|
||||
|
||||
for _, role := range roleCodes {
|
||||
entries, err := store.GetMatrixForRole(ctx, tenantID, role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
// Check if there's an active, completed assignment for this module
|
||||
assignments, _, err := store.ListAssignments(ctx, tenantID, &AssignmentFilters{
|
||||
ModuleID: &entry.ModuleID,
|
||||
UserID: &userID,
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gap := ComplianceGap{
|
||||
ModuleID: entry.ModuleID,
|
||||
ModuleCode: entry.ModuleCode,
|
||||
ModuleTitle: entry.ModuleTitle,
|
||||
RoleCode: role,
|
||||
IsMandatory: entry.IsMandatory,
|
||||
}
|
||||
|
||||
// Determine regulation area from module
|
||||
module, err := store.GetModule(ctx, entry.ModuleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if module != nil {
|
||||
gap.RegulationArea = module.RegulationArea
|
||||
}
|
||||
|
||||
if len(assignments) == 0 {
|
||||
gap.Status = "missing"
|
||||
gaps = append(gaps, gap)
|
||||
} else {
|
||||
a := assignments[0]
|
||||
gap.AssignmentID = &a.ID
|
||||
gap.Deadline = &a.Deadline
|
||||
|
||||
switch a.Status {
|
||||
case AssignmentStatusCompleted:
|
||||
// No gap
|
||||
continue
|
||||
case AssignmentStatusOverdue, AssignmentStatusExpired:
|
||||
gap.Status = string(a.Status)
|
||||
gaps = append(gaps, gap)
|
||||
default:
|
||||
// Check if overdue
|
||||
if a.Deadline.Before(timeNow()) {
|
||||
gap.Status = "overdue"
|
||||
gaps = append(gaps, gap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gaps == nil {
|
||||
gaps = []ComplianceGap{}
|
||||
}
|
||||
|
||||
return gaps, nil
|
||||
}
|
||||
|
||||
// BuildMatrixResponse builds the full CTM response grouped by role
|
||||
func BuildMatrixResponse(entries []TrainingMatrixEntry) *MatrixResponse {
|
||||
resp := &MatrixResponse{
|
||||
Entries: make(map[string][]TrainingMatrixEntry),
|
||||
Roles: RoleLabels,
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
resp.Entries[entry.RoleCode] = append(resp.Entries[entry.RoleCode], entry)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
Reference in New Issue
Block a user