lib/sul_orcid_client/work_mapper.rb
# frozen_string_literal: true
class SulOrcidClient
# Maps a Cocina Description to an Orcid Work.
# Note that this mapping is currently based on a description generated
# from an H2 work. However, it could be extended to more completely map descriptions.
# rubocop:disable Metrics/ClassLength
# rubocop:disable Metrics/MethodLength
class WorkMapper
# Error raised by WorkMapper
class WorkMapperError < StandardError; end
def self.map(description:, doi: nil)
new(description:, doi:).map
end
# @param [Cocina::Models::Description] description to map
# @param [String] doi from identification.doi
def initialize(description:, doi: nil)
@description = description
@doi = doi
end
def map
{
title: map_title,
'short-description': map_short_description,
citation: map_citation,
type: map_type,
'publication-date': map_publication_date,
'external-ids': map_external_ids,
url: description.purl,
contributors: map_contributors,
'language-code': 'en',
country: {
value: 'US'
}
}.compact
end
private
attr_reader :description, :doi
def map_title
title = description.title.first&.value
raise WorkMapperError, 'Title not mapped' unless title
{
title: {
value: title.truncate(500) # ORCID has a max length for this field
}
}
end
def map_short_description
description.note.find { |note| note.type == 'abstract' }&.value
end
def map_citation
citation = description.note.find { |note| note.type == 'preferred citation' }&.value
return unless citation
{
'citation-type': 'formatted-unspecified',
'citation-value': citation
}
end
def map_external_ids
{ 'external-id':
[
map_external_id('uri', description.purl, description.purl)
].tap do |ids|
ids << map_external_id('doi', doi, "https://doi.org/#{doi}") if doi
end }
end
def map_external_id(type, value, url)
{
'external-id-type': type,
'external-id-value': value,
'external-id-url': {
value: url
},
'external-id-relationship': 'self'
}
end
def map_publication_date
date = event_value('publication') || event_value('deposit')
return unless date
year, month, day = parse_date(date)
return unless year
{
year: {
value: year
}
}.tap do |publication_date|
publication_date[:month] = { value: month } if month
publication_date[:day] = { value: day } if month && day
end
end
def parse_date(date)
matcher = date.match(/(\d{4})-?(\d{2})?-?(\d{2})?/)
return [nil, nil, nil] unless matcher
matcher[1..3]
end
def event_value(type)
description&.event&.find { |event| event.type == type }&.date&.first&.value
end
H2_TERM_MAP = {
'Data' => 'data-set',
'Software/Code' => 'software',
'Article' => 'journal-article',
'Book' => 'book',
'Book chapter' => 'book-chapter',
'Code' => 'software',
'Conference session' => 'lecture-speech',
'Course/instructional materials' => 'manual',
'Database' => 'data-set',
'Dramatic performance' => 'artistic-performance',
'Geospatial data' => 'data-set',
'Journal/periodical issue' => 'journal-issue',
'Performance' => 'artistic-performance',
'Poetry reading' => 'artistic-performance',
'Poster' => 'conference-poster',
'Preprint' => 'preprint',
'Presentation recording' => 'lecture-speech',
'Questionnaire' => 'research-technique',
'Report' => 'report',
'Software' => 'software',
'Speech' => 'lecture-speech',
'Statistical model' => 'research-technique',
'Syllabus' => 'manual',
'Tabular data' => 'data-set',
'Technical report' => 'report',
'Text corpus' => 'data-set',
'Thesis' => 'dissertation-thesis',
'Working paper' => 'working-paper'
}.freeze
def map_type
# See https://info.orcid.org/ufaqs/what-work-types-does-orcid-support/
# For now, only mapping H2 terms; if there is not an H2 term, using "other".
h2_form = description.form.find { |form| form.source&.value == 'Stanford self-deposit resource types' }
return 'other' unless h2_form
map_h2_resource_types(h2_form)
end
def map_h2_resource_types(form)
# Try to match subtypes first, then type
subtype_terms = form.structuredValue.select { |term| term.type == 'subtype' }.map(&:value)
type_term = form.structuredValue.find { |term| term.type == 'type' }&.value
matching_term = (subtype_terms + [type_term]).find { |term| H2_TERM_MAP.key?(term) }
return 'other' unless matching_term
H2_TERM_MAP[matching_term]
end
def map_contributors
contributors = description.contributor.filter_map do |contributor|
ContributorMapper.map(contributor:)
end.presence
return unless contributors
{
contributor: contributors
}
end
end
# rubocop:enable Metrics/ClassLength
# rubocop:enable Metrics/MethodLength
end