public
Last active

MetaCPAN Mortality Scoring

  • Download Gist
MetaCPAN-mortality.pl
Perl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
use v5.10;
use MetaCPAN::API;
use Data::Dumper;
use List::AllUtils qw(min max);
use DateTime;
use DateTime::Format::ISO8601;
 
my @score_label = qw(
Abysmal
Poor
Average
Good
Great
);
 
# Example modules:
# Very stable: DBI, DBIx::Class, perl
# Newer but stable: Pod::Coverage::TrustPod
# Older but stable: subs::auto, Devel::Carp, AnyData
# Average: Net::SSH, Mail::IMAPClient
# Broken: XML::Smart, Proc::ProcessTable
# Abandoned: Net::IRC
# DEPRECATED: Dancer::Plugin::DebugDump
# Bad all around: Net::MacMap, Net::ARP, pop
 
my @modules = qw(
DBI DBIx::Class perl
Pod::Coverage::TrustPod
subs::auto Devel::Carp AnyData
Net::SSH Mail::IMAPClient
XML::Smart
Proc::ProcessTable
Net::IRC
Net::MacMap
Net::ARP
pop
);
 
my $dp = DateTime::Format::ISO8601->new();
my $mcpan = MetaCPAN::API->new();
foreach my $module (@modules) {
(my $dist_name = $module) =~ s/::/-/g;
 
my $latest_release = $mcpan->release( distribution => $dist_name );
my $global_testing = $mcpan->release(
search => {
size => 0,
query => { filtered => {
query => {match_all => {}},
filter => { term => { distribution => $dist_name } },
}},
facets => {
pass => { statistical => { field => 'tests.pass' } },
fail => { statistical => { field => 'tests.fail' } },
},
}
)->{facets};
my $score_vars = {
maturity => {
code_size => $latest_release->{stat}{size},
num_releases => $mcpan->release( search => { q => "distribution:$dist_name", size => 0, } )->{hits}{total},
last_release => $latest_release->{date},
first_release => $mcpan->release(
search => {
q => "distribution:$dist_name",
fields => 'date',
sort => 'date:asc',
size => 1,
}
)->{hits}{hits}[0]{fields}{date},
test_pass_global => $global_testing->{pass}{total},
test_fail_global => $global_testing->{fail}{total},
test_ttl_global => $global_testing->{pass}{total} + $global_testing->{fail}{total},
# Number of resolved RT/GH tickets === needs another module
},
stability => {
last_release => $latest_release->{date},
test_pass_latest => $latest_release->{tests}{pass},
test_fail_latest => $latest_release->{tests}{fail},
test_ttl_latest => $latest_release->{tests}{pass} + $latest_release->{tests}{fail},
avg_cpan_rating => $mcpan->rating(
search => {
size => 0,
query => { filtered => {
query => {match_all => {}},
filter => { term => { distribution => $dist_name } },
}},
facets => {
rating => { statistical => { field => 'rating' } },
},
}
)->{facets}{rating}{mean} || 3,
# Kwalitee/CPANTS scoring === not sure where this is
},
sustainability => {
reverse_depend => $mcpan->release( search => { q => "release.dependency.module:\"$module\"", size => 0, } )->{hits}{total},
author_last_release => $mcpan->release(
search => {
q => "author:".$latest_release->{author},
fields => 'date',
sort => 'date:desc',
size => 1,
}
)->{hits}{hits}[0]{fields}{date},
# Number of open RT/GH tickets === needs another module
# Oldest open RT/GH ticket date (non-wistlist) === needs another module
},
};
# Score calculating
# Maturity (Past)
# 45% = Number of releases
# 25% = Life span (last minus first release date)
# 20% = Size of code
# 10% = Test Pass percentage (global)
# TODO: Number of resolved RT/GH tickets
my $lvl = $score_vars->{maturity};
$lvl->{life_span} = $dp->parse_datetime($lvl->{last_release}) - $dp->parse_datetime($lvl->{first_release});
$lvl->{pass_per} = $lvl->{test_pass_global} / ($lvl->{test_ttl_global} || 1);
$lvl->{_score} =
# Number of releases = standard scale (max 15)
min($lvl->{num_releases}, 15) / 15 * 45 +
# Life span = standard scale (max 60 months)
min($lvl->{life_span}->in_units('months'), 60) / 60 * 25 +
# Size of code = SQRT scale (max 200K)
sqrt(min($lvl->{code_size}, 200000)) / 447.22 * 20 +
# Test Pass % = square scale (vs. fractions = downward slope)
$lvl->{pass_per} ** 2 * 10
;
 
# Stability (Present)
# 45% = Last release span (now minus last release date)
# 35% = Test Pass percentage (this version)
# 20% = Average CPAN rating
# TODO: Kwalitee/CPANTS scoring
$lvl = $score_vars->{stability};
$lvl->{last_span} = DateTime->now - $dp->parse_datetime($lvl->{last_release});
$lvl->{pass_per} = $lvl->{test_ttl_latest} ? $lvl->{test_pass_latest} / $lvl->{test_ttl_latest} : $score_vars->{maturity}{pass_per};
$lvl->{_score} =
# Last release span = inverse square scale (max 10 years)
max(1315 - $lvl->{last_span}->in_units('months') ** 1.5, 0) / 1315 * 45 +
# Test Pass % = square scale (vs. fractions = downward slope)
$lvl->{pass_per} ** 2 * 35 +
# Average CPAN rating = standard scale (max 5)
$lvl->{avg_cpan_rating} / 5 * 20
;
 
# Sustainability (Future)
# 40% = Author's last release date
# 35% = Stability score
# 25% = Number of modules that depend on this
# TODO: Number of open RT/GH tickets
# TODO: Oldest open RT/GH ticket date (non-wistlist)
$lvl = $score_vars->{sustainability};
$lvl->{last_span} = DateTime->now - $dp->parse_datetime($lvl->{author_last_release});
$lvl->{_score} =
# Author's last release date = inverse square scale (pivot at 5 years; could go into negative)
(465 - $lvl->{last_span}->in_units('months') ** 1.5) / 465 * 30 +
# Stability score
$score_vars->{stability}{_score} / 100 * 35 +
# Reverse dependencies = inverse reciprocal scale
($lvl->{reverse_depend} ?
(1 - 1/$lvl->{reverse_depend}) * 25 :
0
)
;
# Print score results
say "$module:";
foreach my $type (qw(maturity stability sustainability)) {
printf " %14s: %2u%% (%s)\n", ucfirst $type, $score_vars->{$type}{_score}, $score_label[int( $score_vars->{$type}{_score} / 20 )];
}
say;
 
#say Data::Dumper->new([$score_vars], [$module])->Indent(1)->Pad(' ')->Sortkeys(1)->Dump();
}
gistfile1.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
# Maturity (Past)
# 45% = Number of releases
# 25% = Life span (last minus first release date)
# 20% = Size of code
# 10% = Test Pass percentage (global)
# TODO: Number of resolved RT/GH tickets
 
# Stability (Present)
# 45% = Last release span (now minus last release date)
# 35% = Test Pass percentage (this version)
# 20% = Average CPAN rating
# TODO: Kwalitee/CPANTS scoring
 
# Sustainability (Future)
# 40% = Author's last release date
# 35% = Stability score
# 25% = Number of modules that depend on this
# TODO: Number of open RT/GH tickets
# TODO: Oldest open RT/GH ticket date (non-wistlist)
 
DBI:
Maturity: 99% (Great)
Stability: 97% (Great)
Sustainability: 89% (Great)
 
DBIx::Class:
Maturity: 94% (Great)
Stability: 78% (Good)
Sustainability: 82% (Great)
 
perl:
Maturity: 94% (Great)
Stability: 78% (Good)
Sustainability: 82% (Great)
 
Pod::Coverage::TrustPod:
Maturity: 70% (Good)
Stability: 91% (Great)
Sustainability: 86% (Great)
 
subs::auto:
Maturity: 46% (Average)
Stability: 83% (Great)
Sustainability: 79% (Good)
 
Devel::Carp:
Maturity: 25% (Poor)
Stability: 46% (Average)
Sustainability: 42% (Average)
 
AnyData:
Maturity: 61% (Good)
Stability: 57% (Average)
Sustainability: 37% (Poor)
 
Net::SSH:
Maturity: 65% (Good)
Stability: 80% (Great)
Sustainability: 82% (Great)
 
Mail::IMAPClient:
Maturity: 98% (Great)
Stability: 96% (Great)
Sustainability: 88% (Great)
 
XML::Smart:
Maturity: 72% (Good)
Stability: 65% (Good)
Sustainability: 22% (Poor)
 
Proc::ProcessTable:
Maturity: 86% (Great)
Stability: 70% (Good)
Sustainability: 79% (Good)
 
Net::IRC:
Maturity: 90% (Great)
Stability: 87% (Great)
Sustainability: 60% (Good)
 
Net::MacMap:
Maturity: 25% (Poor)
Stability: 46% (Average)
Sustainability: 41% (Average)
 
Net::ARP:
Maturity: 73% (Good)
Stability: 40% (Average)
Sustainability: 27% (Poor)
 
pop:
Maturity: 34% (Poor)
Stability: 47% (Average)
Sustainability: 46% (Average)

For RT tickets, make sure to exclude "Wishlist" items in your analyses.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.