469 lines
16 KiB
Perl
469 lines
16 KiB
Perl
package Koha::ERM::EUsage::COUNTER::5_1;
|
|
|
|
# Copyright 2025 Open Fifth
|
|
|
|
# This file is part of Koha.
|
|
#
|
|
# Koha 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 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Koha is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Koha; if not, see <https://www.gnu.org/licenses>.
|
|
|
|
use Modern::Perl;
|
|
|
|
use base qw(Koha::ERM::EUsage::SushiCounter);
|
|
|
|
=head1 NAME
|
|
|
|
Koha::ERM::EUsage::COUNTER::5 - Koha COUNTER 5.1 Object class
|
|
|
|
=head1 API
|
|
|
|
=head2 Class Methods
|
|
|
|
=head3 new
|
|
|
|
my $sushi_counter =
|
|
Koha::ERM::EUsage::SushiCounter->new( { response => decode_json( $response->decoded_content ) } );
|
|
|
|
=cut
|
|
|
|
sub new {
|
|
my ($class) = @_;
|
|
my $self = {};
|
|
|
|
bless( $self, $class );
|
|
}
|
|
|
|
=head3 _COUNTER_report_header
|
|
|
|
Return a COUNTER report header
|
|
https://cop5.projectcounter.org/en/5.1/04-reports/03-title-reports.html
|
|
|
|
=cut
|
|
|
|
sub _COUNTER_report_header {
|
|
my ($self) = @_;
|
|
|
|
my $header = $self->{sushi}->{header};
|
|
|
|
my $metric_types = $self->_get_SUSHI_Name_Value( $header->{Report_Filters}, "Metric_Type" );
|
|
my @metric_types_array = split( /\|/, $metric_types );
|
|
$metric_types = join( "; ", @metric_types_array ) if @metric_types_array;
|
|
|
|
my $begin_date = $self->_get_SUSHI_Name_Value( $header->{Report_Filters}, "Begin_Date" );
|
|
my $end_date = $self->_get_SUSHI_Name_Value( $header->{Report_Filters}, "End_Date" );
|
|
|
|
return (
|
|
[ Report_Name => $header->{Report_Name} || "" ],
|
|
[ Report_ID => $header->{Report_ID} || "" ],
|
|
[ Release => $header->{Release} || "" ],
|
|
[ Institution_Name => $header->{Institution_Name} || "" ],
|
|
[ Institution_ID => $self->get_mapped_SUSHI_values( 'Type', $header->{Institution_ID} ) || "" ],
|
|
[ Metric_Types => $metric_types || "" ],
|
|
[ Report_Filters => $self->get_mapped_SUSHI_values( 'Name', $header->{Report_Filters} ) || "" ],
|
|
|
|
#TODO: Report_Attributes may need parsing, test this with a SUSHI response that provides it
|
|
[ Report_Attributes => $header->{Report_Attributes} || "" ],
|
|
[
|
|
Exceptions => join(
|
|
"; ", map( $_->{Code} . ": " . $_->{Message} . " (" . $_->{Data} . ")", @{ $header->{Exceptions} } )
|
|
)
|
|
|| ""
|
|
],
|
|
[ Reporting_Period => "Begin_Date=" . $begin_date . "; End_Date=" . $end_date ],
|
|
[ Created => $header->{Created} || "" ],
|
|
[ Created_By => $header->{Created_By} || "" ],
|
|
[ Registry_Record => $header->{Registry_Record} || "" ],
|
|
[""] #empty 14th line (COUNTER 5.1)
|
|
);
|
|
}
|
|
|
|
=head3 get_counter51_value
|
|
|
|
Given a SUSHI response, get the value for the given key in the correct format for the COUNTER report
|
|
https://counter5.cambridge.org/r51/sushi-docs/
|
|
|
|
=cut
|
|
|
|
sub get_counter51_value {
|
|
my ( $self, $item, $key ) = @_;
|
|
my $counter_51_value = $item->{$key};
|
|
if ( ref($counter_51_value) eq 'ARRAY' ) {
|
|
return join( "; ", @$counter_51_value );
|
|
} else {
|
|
return $counter_51_value;
|
|
}
|
|
}
|
|
|
|
=head3 get_mapped_SUSHI_values
|
|
|
|
Given a SUSHI response, get the value for the given key in the correct format for the COUNTER report
|
|
https://counter5.cambridge.org/r51/sushi-docs/
|
|
|
|
It will return a string of the form "Name:Value;Name:Value;..."
|
|
If the value is an array with a single element, just the value will be returned.
|
|
If the value is an array with multiple elements, they will be joined with "|"
|
|
|
|
=cut
|
|
|
|
sub get_mapped_SUSHI_values {
|
|
my ( $self, $key, $sushi_type_value ) = @_;
|
|
|
|
return join(
|
|
"; ",
|
|
map {
|
|
$_ . ":"
|
|
. (
|
|
ref $sushi_type_value->{$_} eq 'ARRAY'
|
|
&& scalar( @{ $sushi_type_value->{$_} } ) == 1 ? $sushi_type_value->{$_}[0]
|
|
: ( ref $sushi_type_value->{$_} eq 'ARRAY' && scalar( @{ $sushi_type_value->{$_} } ) > 1 )
|
|
? join( "|", @{ $sushi_type_value->{$_} } )
|
|
: $sushi_type_value->{$_}
|
|
)
|
|
} keys %{$sushi_type_value}
|
|
);
|
|
}
|
|
|
|
=head3 _get_SUSHI_Name_Value
|
|
|
|
Returns "Value" of a given "Name"
|
|
|
|
=cut
|
|
|
|
sub _get_SUSHI_Name_Value {
|
|
my ( $self, $item, $name ) = @_;
|
|
|
|
return $self->get_counter51_value( $item, $name );
|
|
}
|
|
|
|
=head3 _get_SUSHI_Type_Value
|
|
|
|
Returns "Value" of a given "Type"
|
|
|
|
=cut
|
|
|
|
sub _get_SUSHI_Type_Value {
|
|
my ( $self, $item, $type ) = @_;
|
|
|
|
return $self->get_counter51_value( $item, $type );
|
|
}
|
|
|
|
=head3 _get_COUNTER_row_usages
|
|
|
|
Returns the total and monthly usages for a row
|
|
|
|
=cut
|
|
|
|
sub _get_COUNTER_row_usages {
|
|
my ( $self, $row, $metric_type, $access_type ) = @_;
|
|
|
|
my @usage_months = $self->_get_usage_months( $self->{sushi}->{header} );
|
|
|
|
my @usage_months_fields = ();
|
|
my $count_total = 0;
|
|
|
|
foreach my $usage_month (@usage_months) {
|
|
my $month_is_empty = 1;
|
|
|
|
my $usage_to_add;
|
|
if ($access_type) {
|
|
my @access_type_performances =
|
|
grep { $_->{Access_Type} eq $access_type } @{ $row->{Attribute_Performance} };
|
|
$usage_to_add = $access_type_performances[0]->{Performance}->{$metric_type}->{$usage_month};
|
|
} else {
|
|
$usage_to_add = $row->{Attribute_Performance}->[0]->{Performance}->{$metric_type}->{$usage_month};
|
|
}
|
|
if ( defined $usage_to_add ) {
|
|
$count_total += $usage_to_add;
|
|
$month_is_empty = 0;
|
|
push( @usage_months_fields, $usage_to_add );
|
|
}
|
|
|
|
if ($month_is_empty) {
|
|
push( @usage_months_fields, 0 );
|
|
}
|
|
}
|
|
|
|
return ( $count_total, @usage_months_fields );
|
|
}
|
|
|
|
=head3 get_SUSHI_metric_types
|
|
|
|
Given a SUSHI report row, return a sorted list of all the metric types
|
|
|
|
=cut
|
|
|
|
sub get_SUSHI_metric_types {
|
|
my ( $self, $report_row ) = @_;
|
|
|
|
my @metric_types = ();
|
|
@metric_types = keys %{ $report_row->{Attribute_Performance}->[0]->{Performance} };
|
|
@metric_types = sort @metric_types;
|
|
return @metric_types;
|
|
}
|
|
|
|
=head3 _COUNTER_report_body
|
|
|
|
Return the COUNTER report body as an array
|
|
|
|
=cut
|
|
|
|
sub _COUNTER_report_body {
|
|
my ($self) = @_;
|
|
|
|
my $header = $self->{sushi}->{header};
|
|
my $body = $self->{sushi}->{body};
|
|
|
|
my @report_body = ();
|
|
|
|
foreach my $report_row ( @{$body} ) {
|
|
my @metric_types = $self->get_SUSHI_metric_types($report_row);
|
|
|
|
my $specific_fields = $self->get_report_type_specific_fields( $header->{Report_ID} );
|
|
my $use_access_type = grep ( /Access_Type/, @{$specific_fields} ) ? 1 : 0;
|
|
my $use_yop = grep ( /YOP/, @{$specific_fields} ) ? 1 : 0;
|
|
my $use_data_type = grep ( /Data_Type/, @{$specific_fields} ) ? 1 : 0;
|
|
|
|
if ($use_data_type) {
|
|
my %data_types = map { $_->{Data_Type} => 1 } @{ $report_row->{Attribute_Performance} };
|
|
my @data_types = sort keys %data_types;
|
|
|
|
foreach my $data_type (@data_types) {
|
|
|
|
if ($use_yop) {
|
|
my %yops = map { $_->{YOP} => 1 } @{ $report_row->{Attribute_Performance} };
|
|
my @yops = sort { $b <=> $a } keys %yops;
|
|
foreach my $yop (@yops) {
|
|
|
|
if ($use_access_type) {
|
|
my @access_types = map { $_->{"Access_Type"} } @{ $report_row->{Attribute_Performance} };
|
|
foreach my $access_type (@access_types) {
|
|
|
|
# Add one report row for each metric_type we're working with
|
|
foreach my $metric_type (@metric_types) {
|
|
push(
|
|
@report_body,
|
|
$self->_COUNTER_report_row(
|
|
$report_row, $metric_type, $access_type, $yop,
|
|
$data_type
|
|
)
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
# Add one report row for each metric_type we're working with
|
|
foreach my $metric_type (@metric_types) {
|
|
push(
|
|
@report_body,
|
|
$self->_COUNTER_report_row( $report_row, $metric_type, undef, $yop, $data_type )
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
# Add one report row for each metric_type we're working with
|
|
foreach my $metric_type (@metric_types) {
|
|
push(
|
|
@report_body,
|
|
$self->_COUNTER_report_row( $report_row, $metric_type, undef, undef, $data_type )
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} elsif ($use_yop) {
|
|
my %yops = map { $_->{YOP} => 1 } @{ $report_row->{Attribute_Performance} };
|
|
my @yops = sort { $b <=> $a } keys %yops;
|
|
foreach my $yop (@yops) {
|
|
|
|
if ($use_access_type) {
|
|
my @access_types = map { $_->{"Access_Type"} } @{ $report_row->{Attribute_Performance} };
|
|
foreach my $access_type (@access_types) {
|
|
|
|
# Add one report row for each metric_type we're working with
|
|
foreach my $metric_type (@metric_types) {
|
|
push(
|
|
@report_body,
|
|
$self->_COUNTER_report_row( $report_row, $metric_type, $access_type, $yop )
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
|
|
# Add one report row for each metric_type we're working with
|
|
foreach my $metric_type (@metric_types) {
|
|
push( @report_body, $self->_COUNTER_report_row( $report_row, $metric_type, undef, $yop ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
} elsif ($use_access_type) {
|
|
my @access_types = map { $_->{"Access_Type"} } @{ $report_row->{Attribute_Performance} };
|
|
foreach my $access_type (@access_types) {
|
|
|
|
# Add one report row for each metric_type we're working with
|
|
foreach my $metric_type (@metric_types) {
|
|
push( @report_body, $self->_COUNTER_report_row( $report_row, $metric_type, $access_type ) );
|
|
}
|
|
}
|
|
} else {
|
|
foreach my $metric_type (@metric_types) {
|
|
push( @report_body, $self->_COUNTER_report_row( $report_row, $metric_type ) );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return @report_body;
|
|
}
|
|
|
|
=head3 _COUNTER_title_report_row
|
|
|
|
Return a COUNTER title for the COUNTER titles report body
|
|
https://cop5.countermetrics.org/en/5.1.0.1/04-reports/03-title-reports.html#column-headings-elements
|
|
|
|
=cut
|
|
|
|
sub _COUNTER_title_report_row {
|
|
my ( $self, $title_row, $metric_type, $total_usage, $monthly_usages, $access_type, $yop, $data_type ) = @_;
|
|
|
|
my $header = $self->{sushi}->{header};
|
|
my $specific_fields = $self->get_report_type_specific_fields( $header->{Report_ID} );
|
|
my $access_type_to_use = $access_type ? $access_type : ( $title_row->{Access_Type} || "" );
|
|
my $yop_to_use = $yop ? $yop : ( $title_row->{YOP} || "" );
|
|
my $data_type_to_use = $data_type ? $data_type : ( $title_row->{Data_Type} || "" );
|
|
|
|
return (
|
|
[
|
|
# Title
|
|
$title_row->{Title} || "",
|
|
|
|
# Publisher
|
|
$title_row->{Publisher} || "",
|
|
|
|
# Publisher_ID
|
|
$self->_get_SUSHI_Type_Value( $title_row->{Publisher_ID}, "ISNI" ) || "",
|
|
|
|
# Platform
|
|
$title_row->{Platform} || "",
|
|
|
|
# DOI
|
|
$self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "DOI" ) || "",
|
|
|
|
# Proprietary_ID
|
|
$self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "Proprietary" ) || "",
|
|
|
|
# ISBN
|
|
grep ( /ISBN/, @{$specific_fields} )
|
|
? ( $self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "ISBN" ) || "" )
|
|
: (),
|
|
|
|
# Print_ISSN
|
|
$self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "Print_ISSN" ) || "",
|
|
|
|
# Online_ISSN
|
|
$self->_get_SUSHI_Type_Value( $title_row->{Item_ID}, "Online_ISSN" ) || "",
|
|
|
|
# URI - FIXME: What goes in URI?
|
|
"",
|
|
|
|
# Data_Type
|
|
grep ( /Data_Type/, @{$specific_fields} ) ? $data_type_to_use : (),
|
|
|
|
# YOP
|
|
grep ( /YOP/, @{$specific_fields} ) ? $yop_to_use : (),
|
|
|
|
# Access_Type
|
|
grep ( /Access_Type/, @{$specific_fields} ) ? $access_type_to_use : (),
|
|
|
|
# Metric_Type
|
|
$metric_type,
|
|
|
|
# Report_Period_Total
|
|
$total_usage,
|
|
|
|
# Monthly usage entries
|
|
@{$monthly_usages}
|
|
]
|
|
);
|
|
}
|
|
|
|
=head3 _COUNTER_titles_report_column_headings
|
|
|
|
Return titles report column headings
|
|
|
|
=cut
|
|
|
|
sub _COUNTER_titles_report_column_headings {
|
|
my ($self) = @_;
|
|
|
|
my $header = $self->{sushi}->{header};
|
|
my @month_headings = $self->_get_usage_months( $header, 1 );
|
|
my $specific_fields = $self->get_report_type_specific_fields( $header->{Report_ID} );
|
|
|
|
return (
|
|
[
|
|
"Title",
|
|
"Publisher",
|
|
"Publisher_ID",
|
|
"Platform",
|
|
"DOI",
|
|
"Proprietary_ID",
|
|
grep ( /ISBN/, @{$specific_fields} ) ? ("ISBN") : (),
|
|
"Print_ISSN",
|
|
"Online_ISSN",
|
|
"URI",
|
|
grep ( /Data_Type/, @{$specific_fields} ) ? ("Data_Type") : (),
|
|
|
|
#"Section_Type", #TODO: Only if requested (?)
|
|
grep ( /YOP/, @{$specific_fields} ) ? ("YOP") : (),
|
|
grep ( /Access_Type/, @{$specific_fields} ) ? ("Access_Type") : (),
|
|
|
|
#"Access_Method", #TODO: Only if requested (?)
|
|
"Metric_Type",
|
|
"Reporting_Period_Total",
|
|
|
|
# @month_headings in "Mmm-yyyy" format. TODO: Show unless Exclude_Monthly_Details=true
|
|
@month_headings
|
|
]
|
|
);
|
|
}
|
|
|
|
=head3 get_report_type_specific_fields
|
|
|
|
Returns the specific fields for a given report_type
|
|
|
|
=cut
|
|
|
|
sub get_report_type_specific_fields {
|
|
my ( $self, $report_type ) = @_;
|
|
|
|
my %report_type_map = (
|
|
"TR_B1" => [ 'YOP', 'Data_Type', 'ISBN' ],
|
|
"TR_B2" => [ 'YOP', 'Data_Type', 'ISBN' ],
|
|
"TR_B3" => [ 'YOP', 'Data_Type', 'Access_Type', 'ISBN' ],
|
|
"TR_J3" => ['Access_Type'],
|
|
"TR_J4" => ['YOP'],
|
|
"IR_A1" => [
|
|
'Authors', 'Publication_Date', 'Article_Version', 'Print_ISSN', 'Online_ISSN', 'Parent_Title',
|
|
'Parent_Authors', 'Parent_Article_Version', 'Parent_DOI', 'Parent_Proprietary_ID', 'Parent_Print_ISSN',
|
|
'Parent_Online_ISSN', 'Parent_URI', 'Access_Type'
|
|
],
|
|
);
|
|
|
|
return $report_type_map{$report_type};
|
|
|
|
}
|
|
|
|
1;
|