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 } }