NAME
Test::CPAN::Health::Report - Aggregated results and overall score for a distribution health run
SYNOPSIS
use Test::CPAN::Health::Report;
my $report = Test::CPAN::Health::Report->new(checks => \@check_objects);
$report->add_result($result);
printf "Score: %d/100\n", $report->overall_score;
for my $result (@{ $report->results }) {
printf " %s: %s\n", $result->check_id, $result->status;
}
DESCRIPTION
Holds all Test::CPAN::Health::Result objects produced by a run and computes the weighted overall score.
The scoring formula is a weighted mean of per-check scores:
score = sum(result.score * check.weight)
---------------------------------
sum(check.weight)
for all results where score is defined and status is not 'skip'
Hard caps are applied after the weighted mean: if the SecurityAdvisories check fails, the overall score is capped at 60; if CPANTesters fails, it is capped at 75. This prevents a distribution with known CVEs or a poor CI pass rate from achieving a misleadingly high headline score.
LIMITATIONS
Checks added after
overall_scorehas been called are included on the next call (the score is computed lazily and cached until invalidated byadd_result).
add_result
PURPOSE
Append a single Result to the report and invalidate the cached score.
API SPECIFICATION
INPUT
result Test::CPAN::Health::Result required
OUTPUT
Returns $self to allow chaining.
MESSAGES
Code | Severity | Message | Resolution
------+----------+------------------------------------+---------------------
RPT01 | FATAL | result must be a Result object | Pass a Result instance
FORMAL SPECIFICATION
-- Z schema (placeholder) --
AddResultOp
Report
Report'
result? : Result
-------------------------------------------------------
Report'.results = Report.results ++ [result?]
Report'.score_dirty = true
SIDE EFFECTS
Invalidates the cached overall score.
USAGE EXAMPLE
$report->add_result($result)->add_result($other_result);
overall_score
PURPOSE
Compute and return the weighted overall health score in the range 0..100. The result is cached until add_result invalidates it.
Hard caps are applied last: a failing SecurityAdvisories caps the score at 60; a failing CPANTesters caps it at 75. Both caps may apply simultaneously (the lower cap wins).
API SPECIFICATION
INPUT
None.
OUTPUT
Integer in the range 0..100.
MESSAGES
Code | Severity | Message | Resolution
------+----------+------------------------------------+---------------------
| | |
FORMAL SPECIFICATION
-- Z schema (placeholder) --
OverallScoreOp
results : seq Result
weights : check_id --> N1
score : 0..100
-------------------------------------------------------
let scorable == {r : results | r.score /= undefined /\ r.status /= skip}
score = floor(sum{r : scorable @ r.score * weights(r.check_id)}
/ sum{r : scorable @ weights(r.check_id)})
security_advisories_fail => score <= 60
cpan_testers_fail => score <= 75
SIDE EFFECTS
None.
USAGE EXAMPLE
printf "%d/100\n", $report->overall_score;
results
PURPOSE
Returns all Result objects in insertion order.
API SPECIFICATION
INPUT
None.
OUTPUT
Arrayref of Test::CPAN::Health::Result objects.
MESSAGES
Code | Severity | Message | Resolution
------+----------+------------------------------------+---------------------
| | |
FORMAL SPECIFICATION
-- Z schema (placeholder) --
results : seq Result
SIDE EFFECTS
None.
USAGE EXAMPLE
for my $r (@{ $report->results }) { ... }
by_status
PURPOSE
Group results by their status string.
API SPECIFICATION
INPUT
None.
OUTPUT
Hashref mapping status strings to arrayrefs of Result objects.
MESSAGES
Code | Severity | Message | Resolution
------+----------+------------------------------------+---------------------
| | |
FORMAL SPECIFICATION
-- Z schema (placeholder) --
by_status : status --> seq Result
-------------------------------------------------------
forall s : dom(by_status) @ forall r : by_status(s) @ r.status = s
SIDE EFFECTS
None.
USAGE EXAMPLE
my $failures = $report->by_status->{fail};
by_category
PURPOSE
Group results by the category of the originating check. Requires that each Result's data hashref carries a category key (populated by the Runner when it adds results).
API SPECIFICATION
INPUT
None.
OUTPUT
Hashref mapping category strings to arrayrefs of Result objects.
MESSAGES
Code | Severity | Message | Resolution
------+----------+------------------------------------+---------------------
| | |
FORMAL SPECIFICATION
-- Z schema (placeholder) --
by_category : category --> seq Result
SIDE EFFECTS
None.
USAGE EXAMPLE
my $security_results = $report->by_category->{security};
as_hash
PURPOSE
Serialise the entire report to a plain hashref (for JSON reporter).
API SPECIFICATION
INPUT
None.
OUTPUT
Hashref with keys: overall_score, pass, warn, fail, skip, error, results.
MESSAGES
Code | Severity | Message | Resolution
------+----------+------------------------------------+---------------------
| | |
FORMAL SPECIFICATION
-- Z schema (placeholder) --
AsHashOp
report : Report
output : Hashref
SIDE EFFECTS
None.
USAGE EXAMPLE
my $href = $report->as_hash;
AUTHOR
Nigel Horne, <njh at nigelhorne.com>
LICENSE AND COPYRIGHT
Copyright (C) 2025-2026 Nigel Horne.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.