first commit

This commit is contained in:
Tomas Dvorak
2026-02-22 10:42:17 +01:00
commit 55885a0e8f
239 changed files with 103690 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
package quality
import (
"fmt"
"time"
)
// Scorer calculates quality scores and generates scorecards
type Scorer struct {
targetScore int
}
// NewScorer creates a new scorer with the given target score
func NewScorer(targetScore int) *Scorer {
if targetScore <= 0 {
targetScore = 95 // Default target
}
return &Scorer{
targetScore: targetScore,
}
}
// CalculateScore calculates the quality score from findings
func (s *Scorer) CalculateScore(findings []Finding) (int, int) {
totalScore := 0
strictScore := 0
for _, finding := range findings {
weight := int(finding.Severity)
score := finding.Score * weight
totalScore += score
// Strict score includes open and wontfix findings
if finding.Status == StatusOpen || finding.Status == StatusWontfix {
strictScore += score
}
}
return totalScore, strictScore
}
// GenerateScorecard creates a scorecard from scan results
func (s *Scorer) GenerateScorecard(findings []Finding, lastScan time.Time) *Scorecard {
totalScore, strictScore := s.CalculateScore(findings)
// Group findings by type and tier
findingsByType := make(map[string]int)
findingsByTier := make(map[Severity]int)
statusByType := make(map[string]int)
for _, finding := range findings {
findingsByType[finding.Type]++
findingsByTier[finding.Severity]++
statusByType[string(finding.Status)]++
}
return &Scorecard{
TotalScore: totalScore,
StrictScore: strictScore,
TargetScore: s.targetScore,
FindingsByType: findingsByType,
FindingsByTier: findingsByTier,
StatusByType: statusByType,
LastScan: lastScan,
}
}
// GetHealthGrade returns a health grade based on score
func (s *Scorer) GetHealthGrade(score int) string {
percentage := s.getScorePercentage(score)
switch {
case percentage >= 90:
return "A"
case percentage >= 80:
return "B"
case percentage >= 70:
return "C"
case percentage >= 60:
return "D"
default:
return "F"
}
}
// getScorePercentage converts score to percentage (inverted - lower is better)
func (s *Scorer) getScorePercentage(score int) int {
// Invert score so lower debt = higher percentage
maxPossibleScore := 1000 // Arbitrary high value for normalization
percentage := 100 - (score * 100 / maxPossibleScore)
if percentage < 0 {
percentage = 0
}
return percentage
}
// FormatScorecard formats the scorecard for display
func (s *Scorer) FormatScorecard(card *Scorecard) string {
grade := s.GetHealthGrade(card.StrictScore)
percentage := s.getScorePercentage(card.StrictScore)
output := fmt.Sprintf(`
Code Quality Scorecard
=======================================
Overall Health: %s (%d%%)
Target Score: %d
Current Score: %d (strict: %d)
Findings by Type:
`, grade, percentage, card.TargetScore, card.TotalScore, card.StrictScore)
for ftype, count := range card.FindingsByType {
output += fmt.Sprintf(" - %s: %d\n", ftype, count)
}
output += "\nFindings by Severity:\n"
tierNames := map[Severity]string{
SeverityT1: "T1 (Auto-fixable)",
SeverityT2: "T2 (Quick manual)",
SeverityT3: "T3 (Needs judgment)",
SeverityT4: "T4 (Major refactor)",
}
for severity, count := range card.FindingsByTier {
if name, ok := tierNames[severity]; ok {
output += fmt.Sprintf(" - %s: %d\n", name, count)
}
}
output += "\nStatus Breakdown:\n"
for status, count := range card.StatusByType {
output += fmt.Sprintf(" - %s: %d\n", status, count)
}
output += fmt.Sprintf("\nLast Scan: %s\n", card.LastScan.Format("2006-01-02 15:04:05"))
return output
}
// GetNextPriority returns the next highest priority finding to fix
func (s *Scorer) GetNextPriority(findings []Finding) *Finding {
if len(findings) == 0 {
return nil
}
var highest *Finding
highestWeight := 0
for _, finding := range findings {
if finding.Status != StatusOpen {
continue
}
weight := int(finding.Severity) * finding.Score
if weight > highestWeight {
highestWeight = weight
highest = &finding
}
}
return highest
}
// GetFindingsByTier returns findings grouped by severity tier
func (s *Scorer) GetFindingsByTier(findings []Finding) map[Severity][]Finding {
result := make(map[Severity][]Finding)
for _, finding := range findings {
if finding.Status == StatusOpen {
result[finding.Severity] = append(result[finding.Severity], finding)
}
}
return result
}
// GetProgressMetrics returns progress metrics for the scan
func (s *Scorer) GetProgressMetrics(findings []Finding) map[string]interface{} {
total := len(findings)
open := 0
fixed := 0
wontfix := 0
for _, finding := range findings {
switch finding.Status {
case StatusOpen:
open++
case StatusFixed:
fixed++
case StatusWontfix:
wontfix++
}
}
return map[string]interface{}{
"total": total,
"open": open,
"fixed": fixed,
"wontfix": wontfix,
"progress": float64(fixed) / float64(total) * 100,
}
}