Skip to content

@SineSwiper /MetaCPAN-mortality.pl
Created

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
MetaCPAN Mortality Scoring
# 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)
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();
}
@dagolden

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.