06bfbd1dca
Build + Deploy / build-admin-compliance (push) Successful in 2m46s
Build + Deploy / build-backend-compliance (push) Successful in 26s
Build + Deploy / build-ai-sdk (push) Successful in 52s
Build + Deploy / build-developer-portal (push) Successful in 22s
Build + Deploy / build-tts (push) Successful in 16s
Build + Deploy / build-document-crawler (push) Successful in 12s
Build + Deploy / build-dsms-gateway (push) Successful in 20s
Build + Deploy / build-dsms-node (push) Successful in 16s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 18s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 3m16s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 1m0s
CI / test-python-backend (push) Successful in 41s
CI / test-python-document-crawler (push) Successful in 29s
CI / test-python-dsms-gateway (push) Successful in 23s
CI / validate-canonical-controls (push) Successful in 16s
Build + Deploy / trigger-orca (push) Successful in 2m36s
Implements the Use-Case Compiler that turns Master Controls into interactive compliance audits. 5 templates (Vendor Check, SAST/DAST, DSGVO, NIS2, CRA), deterministic + LLM question generation, scoring engine with regulation/severity breakdown, and gap detection. - Backend: 9 API endpoints, 22 unit tests (all pass) - Frontend: Template selector, questionnaire, result dashboard - Migration 027: usecase_audits + usecase_answers tables Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
93 lines
1.7 KiB
Go
93 lines
1.7 KiB
Go
package usecase
|
|
|
|
// Score calculates a compliance score from answers and questions.
|
|
func Score(audit *Audit, answers []Answer) *ScoreResult {
|
|
result := &ScoreResult{
|
|
AuditID: audit.ID,
|
|
TotalQuestions: len(audit.Questions),
|
|
ByRegulation: make(map[string]RegulationScore),
|
|
BySeverity: make(map[string]SeverityScore),
|
|
}
|
|
|
|
answerMap := make(map[string]Answer)
|
|
for _, a := range answers {
|
|
answerMap[a.QuestionID] = a
|
|
}
|
|
|
|
for _, q := range audit.Questions {
|
|
a, answered := answerMap[q.ID]
|
|
if !answered {
|
|
continue
|
|
}
|
|
|
|
result.Answered++
|
|
|
|
passed := isPassed(a)
|
|
switch a.Status {
|
|
case AnswerStatusSkipped:
|
|
result.Skipped++
|
|
default:
|
|
if passed {
|
|
result.Passed++
|
|
} else {
|
|
result.Failed++
|
|
}
|
|
}
|
|
|
|
// By regulation
|
|
if q.Regulation != "" {
|
|
rs := result.ByRegulation[q.Regulation]
|
|
rs.Total++
|
|
if passed {
|
|
rs.Passed++
|
|
}
|
|
result.ByRegulation[q.Regulation] = rs
|
|
}
|
|
|
|
// By severity
|
|
sev := q.Severity
|
|
if sev == "" {
|
|
sev = "MEDIUM"
|
|
}
|
|
ss := result.BySeverity[sev]
|
|
ss.Total++
|
|
if passed {
|
|
ss.Passed++
|
|
} else {
|
|
ss.Failed++
|
|
}
|
|
result.BySeverity[sev] = ss
|
|
}
|
|
|
|
// Calculate scores
|
|
if result.Answered > 0 {
|
|
answerable := result.Answered - result.Skipped
|
|
if answerable > 0 {
|
|
result.ComplianceScore = float64(result.Passed) / float64(answerable) * 100
|
|
}
|
|
}
|
|
|
|
for reg, rs := range result.ByRegulation {
|
|
if rs.Total > 0 {
|
|
rs.Score = float64(rs.Passed) / float64(rs.Total) * 100
|
|
}
|
|
result.ByRegulation[reg] = rs
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// isPassed checks if an answer represents a pass.
|
|
func isPassed(a Answer) bool {
|
|
switch v := a.Value.(type) {
|
|
case bool:
|
|
return v
|
|
case string:
|
|
return v == "yes" || v == "true" || v == "ja"
|
|
case float64:
|
|
return v > 0
|
|
default:
|
|
return false
|
|
}
|
|
}
|