From 87ebfadb7e0a8cefad92dceeecf725333eefb48d Mon Sep 17 00:00:00 2001 From: DaisukeYoda Date: Sun, 5 Oct 2025 18:04:09 +0900 Subject: [PATCH] feat: include medium-risk classes in CBO penalty calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem:** CBO analysis was only counting High Risk classes (CBO > 7) for penalty calculation, completely ignoring Medium Risk classes (3 < CBO ≤ 7). Example: Project with 364 classes, multiple classes at CBO=6, but score = 100/100 because all were "Medium Risk" (not counted). **Solution:** 1. Added `MediumCouplingClasses` field to track Medium Risk classes 2. Updated penalty calculation to use weighted ratio: - High Risk classes: weight = 1.0 - Medium Risk classes: weight = 0.5 Formula: (HighRisk × 1.0 + MediumRisk × 0.5) / TotalClasses **Impact:** Projects with many Medium Risk classes will now receive appropriate penalties: - 10% weighted ratio → -6 points (Low penalty) - 30% weighted ratio → -12 points (Medium penalty) - 60% weighted ratio → -20 points (High penalty) This makes CBO scoring more realistic and catches coupling issues that were previously ignored. **Files Changed:** - domain/analyze.go: Added MediumCouplingClasses field, updated penalty logic - app/analyze_usecase.go: Set MediumCouplingClasses from CBO analysis - domain/analyze.go: Added validation for new field 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/analyze_usecase.go | 1 + domain/analyze.go | 32 ++++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/analyze_usecase.go b/app/analyze_usecase.go index dd13bd6..9ad9bf9 100644 --- a/app/analyze_usecase.go +++ b/app/analyze_usecase.go @@ -497,6 +497,7 @@ func (uc *AnalyzeUseCase) calculateSummary(summary *domain.AnalyzeSummary, respo if response.CBO != nil { summary.CBOClasses = response.CBO.Summary.TotalClasses summary.HighCouplingClasses = response.CBO.Summary.HighRiskClasses + summary.MediumCouplingClasses = response.CBO.Summary.MediumRiskClasses summary.AverageCoupling = response.CBO.Summary.AverageCBO } diff --git a/domain/analyze.go b/domain/analyze.go index bf6dd61..3c8f3bc 100644 --- a/domain/analyze.go +++ b/domain/analyze.go @@ -112,9 +112,10 @@ type AnalyzeSummary struct { CloneGroups int `json:"clone_groups" yaml:"clone_groups"` CodeDuplication float64 `json:"code_duplication_percentage" yaml:"code_duplication_percentage"` - CBOClasses int `json:"cbo_classes" yaml:"cbo_classes"` - HighCouplingClasses int `json:"high_coupling_classes" yaml:"high_coupling_classes"` - AverageCoupling float64 `json:"average_coupling" yaml:"average_coupling"` + CBOClasses int `json:"cbo_classes" yaml:"cbo_classes"` + HighCouplingClasses int `json:"high_coupling_classes" yaml:"high_coupling_classes"` // CBO > 7 (High Risk) + MediumCouplingClasses int `json:"medium_coupling_classes" yaml:"medium_coupling_classes"` // 3 < CBO ≤ 7 (Medium Risk) + AverageCoupling float64 `json:"average_coupling" yaml:"average_coupling"` // Overall health score (0-100) HealthScore int `json:"health_score" yaml:"health_score"` @@ -160,9 +161,19 @@ func (s *AnalyzeSummary) Validate() error { } // CBO checks - if s.CBOClasses > 0 && s.HighCouplingClasses > s.CBOClasses { - return fmt.Errorf("HighCouplingClasses (%d) cannot exceed CBOClasses (%d)", - s.HighCouplingClasses, s.CBOClasses) + if s.CBOClasses > 0 { + if s.HighCouplingClasses > s.CBOClasses { + return fmt.Errorf("HighCouplingClasses (%d) cannot exceed CBOClasses (%d)", + s.HighCouplingClasses, s.CBOClasses) + } + if s.MediumCouplingClasses > s.CBOClasses { + return fmt.Errorf("MediumCouplingClasses (%d) cannot exceed CBOClasses (%d)", + s.MediumCouplingClasses, s.CBOClasses) + } + if (s.HighCouplingClasses + s.MediumCouplingClasses) > s.CBOClasses { + return fmt.Errorf("HighCouplingClasses + MediumCouplingClasses (%d) cannot exceed CBOClasses (%d)", + s.HighCouplingClasses+s.MediumCouplingClasses, s.CBOClasses) + } } return nil @@ -206,13 +217,18 @@ func (s *AnalyzeSummary) calculateDuplicationPenalty() int { } } -// calculateCouplingPenalty calculates the penalty for class coupling (max 16) +// calculateCouplingPenalty calculates the penalty for class coupling (max 20) +// Considers both High and Medium risk classes with different weights func (s *AnalyzeSummary) calculateCouplingPenalty() int { if s.CBOClasses == 0 { return 0 } - ratio := float64(s.HighCouplingClasses) / float64(s.CBOClasses) + // Calculate combined problematic classes ratio + // Weight: High Risk = 1.0, Medium Risk = 0.5 + weightedProblematicClasses := float64(s.HighCouplingClasses) + (0.5 * float64(s.MediumCouplingClasses)) + ratio := weightedProblematicClasses / float64(s.CBOClasses) + switch { case ratio > CouplingRatioHigh: return CouplingPenaltyHigh