package quality import ( "fmt" "strings" "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: ONLY includes truly unresolved issues // Excludes: fixed, false_positive, ignored, wontfix (if justified) if s.isStrictlyRelevant(finding) { // Apply strict multiplier for severity strictMultiplier := s.getStrictMultiplier(finding) strictScore += score * strictMultiplier } } 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, } } // isStrictlyRelevant determines if a finding should count in strict scoring func (s *Scorer) isStrictlyRelevant(finding Finding) bool { switch finding.Status { case StatusOpen: return true case StatusFixed: return false // Already resolved case StatusFalsePositive: return false // Not a real issue case StatusIgnored: return false // Explicitly ignored case StatusWontfix: // Only count wontfix if it's not justified with valid reasons return !s.isJustifiedWontfix(finding) default: return true } } // isJustifiedWontfix checks if wontfix has valid justification func (s *Scorer) isJustifiedWontfix(finding Finding) bool { if finding.Metadata == nil { return false } note, exists := finding.Metadata["resolution_note"] if !exists { return false } // Valid wontfix justifications validJustifications := []string{ "legacy", "deprecated", "external", "third-party", "temporary", "placeholder", "documentation", "test-only", "example", "sample", } note = strings.ToLower(note) for _, justification := range validJustifications { if strings.Contains(note, justification) { return true } } return false } // getStrictMultiplier returns severity multiplier for strict scoring func (s *Scorer) getStrictMultiplier(finding Finding) int { switch finding.Severity { case SeverityT1: return 1 // T1 issues are less critical case SeverityT2: return 2 // T2 issues are moderately important case SeverityT3: return 3 // T3 issues need attention case SeverityT4: return 5 // T4 issues are critical default: return 1 } } // 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 { // Strict percentage calculation with multiple factors if score <= 0 { return 100 } // Base calculation with stricter normalization var percentage int if score > 10000 { // Logarithmic scaling for very high scores percentage = 100 - int(float64(score-10000)/float64(score)*90) } else if score > 5000 { // Linear scaling for high scores percentage = 100 - (score * 100 / 20000) } else if score > 1000 { // Linear scaling for medium scores percentage = 100 - (score * 100 / 10000) } else { // Linear scaling for low scores percentage = 100 - (score * 100 / 2000) } if percentage < 0 { percentage = 0 } return percentage } // GetStrictHealthMetrics returns comprehensive strict health metrics func (s *Scorer) GetStrictHealthMetrics(findings []Finding) map[string]interface{} { total := len(findings) open := 0 critical := 0 high := 0 medium := 0 low := 0 resolved := 0 ignored := 0 strictScore := 0 totalScore := 0 for _, finding := range findings { totalScore += finding.Score * int(finding.Severity) switch finding.Status { case StatusOpen: open++ if s.isStrictlyRelevant(finding) { strictScore += finding.Score * int(finding.Severity) * s.getStrictMultiplier(finding) } case StatusFixed: resolved++ case StatusIgnored, StatusFalsePositive: ignored++ } switch finding.Severity { case SeverityT4: critical++ case SeverityT3: high++ case SeverityT2: medium++ case SeverityT1: low++ } } // Calculate strict percentages openPercentage := float64(open) / float64(total) * 100 criticalPercentage := float64(critical) / float64(total) * 100 resolutionRate := float64(resolved) / float64(total) * 100 // Strict health score (0-100) healthScore := 100.0 healthScore -= float64(openPercentage) * 0.5 // Penalty for open issues healthScore -= float64(criticalPercentage) * 2.0 // Higher penalty for critical healthScore -= float64(high) * 0.1 // Penalty for high severity healthScore += float64(resolutionRate) * 0.3 // Bonus for resolution rate if healthScore < 0 { healthScore = 0 } if healthScore > 100 { healthScore = 100 } return map[string]interface{}{ "total_issues": total, "open_issues": open, "critical_issues": critical, "high_issues": high, "medium_issues": medium, "low_issues": low, "resolved_issues": resolved, "ignored_issues": ignored, "open_percentage": openPercentage, "critical_percentage": criticalPercentage, "resolution_rate": resolutionRate, "strict_score": strictScore, "total_score": totalScore, "health_score": healthScore, "grade": s.GetStrictGrade(healthScore), } } // GetStrictGrade returns grade based on strict health score func (s *Scorer) GetStrictGrade(healthScore float64) string { switch { case healthScore >= 95: return "A+" case healthScore >= 90: return "A" case healthScore >= 85: return "A-" case healthScore >= 80: return "B+" case healthScore >= 75: return "B" case healthScore >= 70: return "B-" case healthScore >= 65: return "C+" case healthScore >= 60: return "C" case healthScore >= 55: return "C-" case healthScore >= 50: return "D+" case healthScore >= 45: return "D" case healthScore >= 40: return "D-" default: return "F" } } // 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(` πŸ” STRICT 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 += "\n🚨 Findings 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 { emoji := s.getSeverityEmoji(severity) output += fmt.Sprintf(" %s %s: %d\n", emoji, name, count) } } output += "\nπŸ“‹ Status Breakdown:\n" for status, count := range card.StatusByType { output += fmt.Sprintf(" - %s: %d\n", status, count) } output += fmt.Sprintf("\n⏰ Last Scan: %s\n", card.LastScan.Format("2006-01-02 15:04:05")) return output } // getSeverityEmoji returns emoji for severity level func (s *Scorer) getSeverityEmoji(severity Severity) string { switch severity { case SeverityT1: return "🟒" case SeverityT2: return "🟑" case SeverityT3: return "🟠" case SeverityT4: return "πŸ”΄" default: return "βšͺ" } } // FormatStrictScorecard formats comprehensive strict scorecard func (s *Scorer) FormatStrictScorecard(findings []Finding, lastScan time.Time) string { metrics := s.GetStrictHealthMetrics(findings) output := fmt.Sprintf(` πŸ”¬ COMPREHENSIVE STRICT ANALYSIS ======================================= 🎯 STRICT HEALTH SCORE: %.1f/100 (%s) ======================================= πŸ“Š ISSUE BREAKDOWN: Total Issues: %v πŸ”΄ Critical (T4): %v (%.1f%%) 🟠 High (T3): %v 🟑 Medium (T2): %v 🟒 Low (T1): %v πŸ“ˆ STATUS ANALYSIS: βœ… Resolved: %v (%.1f%%) πŸ”“ Open: %v (%.1f%%) ⏸️ Ignored: %v βš–οΈ SCORING: Strict Score: %v Total Score: %v Health Multiplier: %.2fx 🎯 STRICT CRITERIA: βœ“ Only unresolved issues counted βœ“ Severity-weighted scoring (T1Γ—1, T2Γ—2, T3Γ—3, T4Γ—5) βœ“ Justified wontfix excluded βœ“ False positives ignored βœ“ Resolution rate bonus applied πŸ“… Last Analysis: %s πŸ† RECOMMENDATIONS: `, metrics["health_score"], metrics["grade"], metrics["total_issues"], metrics["critical_issues"], metrics["critical_percentage"], metrics["high_issues"], metrics["medium_issues"], metrics["low_issues"], metrics["resolved_issues"], metrics["resolution_rate"], metrics["open_issues"], metrics["open_percentage"], metrics["ignored_issues"], metrics["strict_score"], metrics["total_score"], float64(metrics["strict_score"].(int))/float64(metrics["total_score"].(int)), lastScan.Format("2006-01-02 15:04:05")) // Add recommendations based on metrics if metrics["critical_percentage"].(float64) > 5 { output += " 🚨 CRITICAL: Address T4 issues immediately\n" } if metrics["open_percentage"].(float64) > 70 { output += " πŸ“ˆ HIGH DEBT: Focus on resolving open issues\n" } if metrics["resolution_rate"].(float64) < 20 { output += " ⚑ LOW RESOLUTION: Increase fix rate\n" } if healthScore, ok := metrics["health_score"].(float64); ok && healthScore < 50 { output += " ❌ POOR HEALTH: Major refactoring needed\n" } else if healthScore >= 80 { output += " βœ… GOOD HEALTH: Maintain current practices\n" } 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, } }