Module: Moult::Scoring
- Defined in:
- lib/moult/scoring.rb
Overview
Aggregates the per-method ABC and per-file churn into a ranked Report.
File complexity is the sum of its methods' ABC; the file score is complexity x churn. This raw product is dominated by outliers - acceptable for v0.1, but Scoring.combine is isolated so a normalisation strategy (log, rank, z-score) can drop in later without touching the rest of the pipeline.
Files with no methods (or only zero-scoring ones) are omitted: they cannot be a complexity hotspot. Ranking is score-descending, with complexity then path as deterministic tie-breakers (so 0-churn files - e.g. outside a repo - still order by complexity rather than arbitrarily).
Constant Summary collapse
- DEFAULT_WORST_METHODS =
3
Class Method Summary collapse
- .build_method(method_def, rel) ⇒ Object
- .build_report(root:, files:, churn:, worst_methods: DEFAULT_WORST_METHODS, git_ref: nil, generated_at: nil, churn_window: nil, churn_since: nil) ⇒ Report
-
.combine(complexity, churn) ⇒ Numeric
The v0.1 scoring rule.
-
.hotspot_for(abs, root:, churn:, worst_methods:) ⇒ Report::Hotspot?
Nil when the file has no scoring methods.
- .relative_path(abs, root) ⇒ Object
Class Method Details
.build_method(method_def, rel) ⇒ Object
69 70 71 72 73 74 75 76 |
# File 'lib/moult/scoring.rb', line 69 def build_method(method_def, rel) Report::Method.new( symbol_id: SymbolId.for(path: rel, start_line: method_def.span.start_line, fqname: method_def.name), name: method_def.name, span: method_def.span, abc: ABC.score(method_def.node) ) end |
.build_report(root:, files:, churn:, worst_methods: DEFAULT_WORST_METHODS, git_ref: nil, generated_at: nil, churn_window: nil, churn_since: nil) ⇒ Report
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/moult/scoring.rb', line 27 def build_report(root:, files:, churn:, worst_methods: DEFAULT_WORST_METHODS, git_ref: nil, generated_at: nil, churn_window: nil, churn_since: nil) hotspots = files.filter_map do |abs| hotspot_for(abs, root: root, churn: churn, worst_methods: worst_methods) end hotspots.sort_by! { |h| [-h.score, -h.complexity, h.path] } Report.new( root: root, hotspots: hotspots, git_ref: git_ref, generated_at: generated_at, churn_window: churn_window, churn_since: churn_since ) end |
.combine(complexity, churn) ⇒ Numeric
The v0.1 scoring rule. Swap-point for future normalisation.
65 66 67 |
# File 'lib/moult/scoring.rb', line 65 def combine(complexity, churn) complexity * churn end |
.hotspot_for(abs, root:, churn:, worst_methods:) ⇒ Report::Hotspot?
Returns nil when the file has no scoring methods.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/moult/scoring.rb', line 45 def hotspot_for(abs, root:, churn:, worst_methods:) rel = relative_path(abs, root) methods = Parser.parse_file(abs).map { |m| build_method(m, rel) } complexity = methods.sum(0.0, &:abc) return nil if complexity.zero? churn_count = churn[rel] kept = methods.sort_by { |m| -m.abc }.first(worst_methods) Report::Hotspot.new( path: rel, score: combine(complexity, churn_count).round(2), complexity: complexity.round(2), churn: churn_count, methods: kept ) end |
.relative_path(abs, root) ⇒ Object
78 79 80 |
# File 'lib/moult/scoring.rb', line 78 def relative_path(abs, root) SymbolId.relative_path(abs, root) end |