mirror of
https://github.com/Dvorinka/Devour.git
synced 2026-06-03 20:13:03 +00:00
first commit
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user