Module: Menuconform::Scorer

Defined in:
lib/menuconform/scorer.rb

Overview

The published SPEC §7 scoring formula. Pure function of the findings and the weights below — same findings, same rules version, same score.

Constant Summary collapse

WEIGHTS =
{ "error" => 5.0, "warn" => 2.0, "info" => 0.5 }.freeze
PER_RULE_CAP =
15.0
CART_CAP_RULES =
%w[CART-001 CART-002 CART-003].freeze
CART_CAP =
49
SCHEMA_CAP_RULE =
"STRUCT-001"
SCHEMA_CAP =
59

Class Method Summary collapse

Class Method Details

.score(findings) ⇒ Object

findings: [Finding] with severities set. Returns a breakdown hash.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/menuconform/scorer.rb', line 17

def score(findings)
  deductions = findings.group_by(&:rule).sort.map do |rule, group|
    severity = group.first.severity
    raw_points = group.size * WEIGHTS.fetch(severity)
    points = [raw_points, PER_RULE_CAP].min
    {
      "rule" => rule,
      "severity" => severity,
      "count" => group.size,
      "points" => points,
      "capped" => raw_points > PER_RULE_CAP
    }
  end

  raw = 100.0 - deductions.sum { |d| d["points"] }
  capped = [raw, 0.0].max

  hard_caps = []
  if findings.any? { |f| CART_CAP_RULES.include?(f.rule) } && capped > CART_CAP
    hard_caps << { "reason" => "cart-blocking findings present (CART-001/002/003)", "cap" => CART_CAP }
    capped = CART_CAP
  end
  if findings.any? { |f| f.rule == SCHEMA_CAP_RULE } && capped > SCHEMA_CAP
    hard_caps << { "reason" => "document fails schema validation (STRUCT-001)", "cap" => SCHEMA_CAP }
    capped = SCHEMA_CAP
  end

  {
    "score" => capped.round, # Ruby rounds half away from zero: half-up for our range
    "base" => 100,
    "deductions" => deductions,
    "raw_score" => raw,
    "hard_caps" => hard_caps
  }
end