pennmarc 1.0.12.pre1 → 1.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +0 -1
- data/.rubocop_todo.yml +13 -5
- data/lib/pennmarc/enriched.rb +93 -0
- data/lib/pennmarc/helpers/access.rb +2 -2
- data/lib/pennmarc/helpers/classification.rb +6 -6
- data/lib/pennmarc/helpers/creator.rb +59 -64
- data/lib/pennmarc/helpers/date.rb +3 -3
- data/lib/pennmarc/helpers/format.rb +7 -7
- data/lib/pennmarc/helpers/helper.rb +1 -1
- data/lib/pennmarc/helpers/inventory.rb +92 -0
- data/lib/pennmarc/helpers/inventory_entry/base.rb +23 -0
- data/lib/pennmarc/helpers/inventory_entry/electronic.rb +20 -0
- data/lib/pennmarc/helpers/inventory_entry/physical.rb +38 -0
- data/lib/pennmarc/helpers/location.rb +19 -14
- data/lib/pennmarc/helpers/subject.rb +6 -6
- data/lib/pennmarc/mappings/locations.yml +4 -0
- data/lib/pennmarc/util.rb +16 -1
- data/lib/pennmarc/version.rb +1 -1
- data/spec/lib/pennmarc/helpers/access_spec.rb +5 -5
- data/spec/lib/pennmarc/helpers/classification_spec.rb +6 -6
- data/spec/lib/pennmarc/helpers/creator_spec.rb +41 -7
- data/spec/lib/pennmarc/helpers/format_spec.rb +4 -4
- data/spec/lib/pennmarc/helpers/inventory_spec.rb +129 -0
- data/spec/lib/pennmarc/helpers/location_spec.rb +40 -9
- data/spec/lib/pennmarc/helpers/subject_spec.rb +37 -13
- metadata +10 -5
- data/lib/pennmarc/enriched_marc.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76a91ac26d34008866dd6b66d66b9bbebf5d952d4bda7514d34a40b87f436244
|
4
|
+
data.tar.gz: 6d6813b9dcbadcf45905a48bb30dbc48fbe47ab081d8457d4936cb2263ba1c91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a704d7a8957d042ad629446626fa8734e1d9b5c0ee65ba844343765d7c196ac0e8da3ae328213b1832556786b63a426c8ad2b4dc1e9b806795a723530d9367eb
|
7
|
+
data.tar.gz: 01f2d33cd2e7ec4a0aecb5f032bacd438a553d6d8667c5a0191e282f4722358fd23755b788a9ce21f32677d076e7224a04a73c24345f1146fd76198c42e9ebf3
|
data/.gitlab-ci.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000`
|
3
|
-
# on
|
3
|
+
# on 2024-01-17 17:00:02 UTC using RuboCop version 1.51.0.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
+
# Offense count: 1
|
10
|
+
# This cop supports safe autocorrection (--autocorrect).
|
11
|
+
# Configuration parameters: Severity, Include.
|
12
|
+
# Include: **/*.gemspec
|
13
|
+
Gemspec/RequireMFA:
|
14
|
+
Exclude:
|
15
|
+
- 'pennmarc.gemspec'
|
16
|
+
|
9
17
|
# Offense count: 23
|
10
18
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
11
19
|
Metrics/AbcSize:
|
@@ -34,7 +42,7 @@ Metrics/ClassLength:
|
|
34
42
|
- 'lib/pennmarc/helpers/subject.rb'
|
35
43
|
- 'lib/pennmarc/helpers/title.rb'
|
36
44
|
|
37
|
-
# Offense count:
|
45
|
+
# Offense count: 19
|
38
46
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
39
47
|
Metrics/CyclomaticComplexity:
|
40
48
|
Exclude:
|
@@ -52,7 +60,7 @@ Metrics/CyclomaticComplexity:
|
|
52
60
|
- 'lib/pennmarc/helpers/title.rb'
|
53
61
|
- 'lib/pennmarc/util.rb'
|
54
62
|
|
55
|
-
# Offense count:
|
63
|
+
# Offense count: 26
|
56
64
|
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
57
65
|
Metrics/MethodLength:
|
58
66
|
Exclude:
|
@@ -115,7 +123,7 @@ Naming/VariableNumber:
|
|
115
123
|
Exclude:
|
116
124
|
- 'lib/pennmarc/util.rb'
|
117
125
|
|
118
|
-
# Offense count:
|
126
|
+
# Offense count: 7
|
119
127
|
# Configuration parameters: Max, CountAsOne.
|
120
128
|
RSpec/ExampleLength:
|
121
129
|
Exclude:
|
@@ -131,7 +139,7 @@ RSpec/FilePath:
|
|
131
139
|
Exclude:
|
132
140
|
- 'spec/lib/pennmarc/parser_spec.rb'
|
133
141
|
|
134
|
-
# Offense count:
|
142
|
+
# Offense count: 12
|
135
143
|
# Configuration parameters: Max, AllowedGroups.
|
136
144
|
RSpec/NestedGroups:
|
137
145
|
Exclude:
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Constants for Alma's MARC enrichment, performed and included in the MARCXML either by the Publishing process or by
|
4
|
+
# API service
|
5
|
+
module PennMARC
|
6
|
+
module Enriched
|
7
|
+
# Enriched MARC fields added by configurable setting in the Publishing profile that generates the MARCXML
|
8
|
+
# TODO: review if we can/should modify the subfields used in the pub profile to create parity with API subfields as
|
9
|
+
# that could simplify this mapping tremendously
|
10
|
+
module Pub
|
11
|
+
# Enrichment Tag Names
|
12
|
+
PHYS_INVENTORY_TAG = 'hld'
|
13
|
+
ELEC_INVENTORY_TAG = 'prt'
|
14
|
+
ITEM_TAG = 'itm'
|
15
|
+
|
16
|
+
# Subfields for HLD tags
|
17
|
+
# Follow MARC 852 spec: https://www.loc.gov/marc/holdings/hd852.html, but names are translated into Alma parlance
|
18
|
+
PHYS_LOCATION_NAME = 'b' # e.g., Libra
|
19
|
+
PHYS_LOCATION_CODE = 'c' # e.g., stor
|
20
|
+
HOLDING_CLASSIFICATION_PART = 'h' # "classification part" first part of call num e.g., KF6450
|
21
|
+
HOLDING_ITEM_PART = 'i' # "item part?" second part of call num e.g., .C59 1989
|
22
|
+
PHYS_PUBLIC_NOTE = 'z'
|
23
|
+
PHYS_INTERNAL_NOTE = 'x'
|
24
|
+
PHYS_HOLDING_ID = '8'
|
25
|
+
|
26
|
+
# Subfields for ITM tags
|
27
|
+
ITEM_CURRENT_LOCATION = 'g'
|
28
|
+
ITEM_CALL_NUMBER_TYPE = 'h'
|
29
|
+
ITEM_CALL_NUMBER = 'i'
|
30
|
+
ITEM_DATE_CREATED = 'q'
|
31
|
+
|
32
|
+
# Subfields for PRT tags
|
33
|
+
ELEC_PORTFOLIO_ID = 'a'
|
34
|
+
ELEC_SERVICE_URL = 'b'
|
35
|
+
ELEC_COLLECTION_NAME = 'c'
|
36
|
+
ELEC_INTERFACE_NAME = 'e'
|
37
|
+
ELEC_PUBLIC_NOTE = 'f'
|
38
|
+
ELEC_COVERAGE_STMT = 'g'
|
39
|
+
|
40
|
+
# other values that could be added if we configured the Alma pub profile (and values are set on the record)
|
41
|
+
# - Authentication note
|
42
|
+
# - "static URL"
|
43
|
+
# - Electronic material type
|
44
|
+
# - Collection ID
|
45
|
+
# - create/update/activation date
|
46
|
+
# - license code
|
47
|
+
# - portfolio coverage info
|
48
|
+
# - from year, until year (month, day volume issue)
|
49
|
+
# - portfolio embargo info
|
50
|
+
# - years/months embargo'd
|
51
|
+
|
52
|
+
# TODO: evaluate this in context of changed boundwiths processing
|
53
|
+
# Franklin legacy note:
|
54
|
+
# a subfield code NOT used by the MARC 21 spec for 852 holdings records.
|
55
|
+
# we add this subfield during preprocessing to store boundwith record IDs.
|
56
|
+
# BOUND_WITH_ID = 'y'
|
57
|
+
end
|
58
|
+
|
59
|
+
# MARC enrichment originating from Alma API
|
60
|
+
# @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/ Alma docs
|
61
|
+
# We cannot modify these subfield settings
|
62
|
+
module Api
|
63
|
+
# Enrichment Tag Names
|
64
|
+
PHYS_INVENTORY_TAG = 'AVA'
|
65
|
+
ELEC_INVENTORY_TAG = 'AVE'
|
66
|
+
|
67
|
+
# Physical Holding (AVA) subfields
|
68
|
+
PHYS_CALL_NUMBER = 'd'
|
69
|
+
PHYS_CALL_NUMBER_TYPE = 'k'
|
70
|
+
PHYS_LIBRARY_CODE = 'b'
|
71
|
+
PHYS_LIBRARY_NAME = 'q'
|
72
|
+
PHYS_LOCATION_CODE = 'j'
|
73
|
+
PHYS_LOCATION_NAME = 'c'
|
74
|
+
PHYS_HOLDING_ID = '8'
|
75
|
+
PHYS_AVAILABILITY = 'e'
|
76
|
+
PHYS_TOTAL_ITEMS = 'f'
|
77
|
+
PHYS_UNAVAILABLE_ITEMS = 'g'
|
78
|
+
PHYS_SUMMARY_INFO = 'v'
|
79
|
+
PHYS_PRIORITY = 'p'
|
80
|
+
|
81
|
+
# Electronic Portfolio (AVE) subfields
|
82
|
+
ELEC_LIBRARY_CODE = 'l'
|
83
|
+
ELEC_COLLECTION_NAME = 'm'
|
84
|
+
ELEC_PUBLIC_NOTE = 'n'
|
85
|
+
ELEC_SERVICE_URL = 'u'
|
86
|
+
ELEC_COVERAGE_STMT = 's'
|
87
|
+
ELEC_INTERFACE_NAME = 't'
|
88
|
+
ELEC_PORTFOLIO_ID = '8'
|
89
|
+
ELEC_COLLECTION_ID = 'c'
|
90
|
+
ELEC_ACTIVATION_STATUS = 'e'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -33,14 +33,14 @@ module PennMARC
|
|
33
33
|
# @param [MARC::Field] field
|
34
34
|
# @return [Boolean]
|
35
35
|
def electronic_holding_tag?(field)
|
36
|
-
field.tag.in? [
|
36
|
+
field.tag.in? [Enriched::Pub::ELEC_INVENTORY_TAG, Enriched::Api::ELEC_INVENTORY_TAG]
|
37
37
|
end
|
38
38
|
|
39
39
|
# Does the record have added physical holding info?
|
40
40
|
# @param [MARC::Field] field
|
41
41
|
# @return [Boolean]
|
42
42
|
def physical_holding_tag?(field)
|
43
|
-
field.tag.in? [
|
43
|
+
field.tag.in? [Enriched::Pub::PHYS_INVENTORY_TAG, Enriched::Api::PHYS_INVENTORY_TAG]
|
44
44
|
end
|
45
45
|
|
46
46
|
# Check if a record contains an 856 entry for an online finding aid, meeting these criteria:
|
@@ -16,13 +16,13 @@ module PennMARC
|
|
16
16
|
}.freeze
|
17
17
|
|
18
18
|
# Enriched MARC tags that hold classification data
|
19
|
-
TAGS = [
|
19
|
+
TAGS = [Enriched::Pub::ITEM_TAG, Enriched::Api::PHYS_INVENTORY_TAG].freeze
|
20
20
|
|
21
21
|
class << self
|
22
22
|
# Parse classification values for faceting. We retrieve classification values from enriched MARC fields 'itm' or
|
23
23
|
# 'AVA' originating respectively from the Alma publishing process or from the Alma Api. We return the
|
24
24
|
# highest level LOC or Dewey classifications from each available call number, joining the class code with
|
25
|
-
# its title in a single string. See {PennMARC::
|
25
|
+
# its title in a single string. See {PennMARC::Enriched} and {PennMARC::Enriched::Api} for more
|
26
26
|
# information on the enriched MARC fields.
|
27
27
|
# @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/ AVA docs
|
28
28
|
# @param [MARC::Record] record
|
@@ -48,18 +48,18 @@ module PennMARC
|
|
48
48
|
# @param [MARC::DataField] field
|
49
49
|
# @return [String]
|
50
50
|
def call_number_sf(field)
|
51
|
-
return
|
51
|
+
return Enriched::Pub::ITEM_CALL_NUMBER if field.tag == Enriched::Pub::ITEM_TAG
|
52
52
|
|
53
|
-
|
53
|
+
Enriched::Api::PHYS_CALL_NUMBER
|
54
54
|
end
|
55
55
|
|
56
56
|
# Retrieve subfield code that stores call number type on enriched marc field
|
57
57
|
# @param [MARC::DataField] field
|
58
58
|
# @return [String]
|
59
59
|
def call_number_type_sf(field)
|
60
|
-
return
|
60
|
+
return Enriched::Pub::ITEM_CALL_NUMBER_TYPE if field.tag == Enriched::Pub::ITEM_TAG
|
61
61
|
|
62
|
-
|
62
|
+
Enriched::Api::PHYS_CALL_NUMBER_TYPE
|
63
63
|
end
|
64
64
|
|
65
65
|
# retrieve title of classification based on single char classification code and call number type
|
@@ -10,82 +10,43 @@ module PennMARC
|
|
10
10
|
class << self
|
11
11
|
# Main tags for Author/Creator information
|
12
12
|
TAGS = %w[100 110].freeze
|
13
|
+
|
13
14
|
# Aux tags for Author/Creator information, for use in search_aux method
|
14
15
|
AUX_TAGS = %w[100 110 111 400 410 411 700 710 711 800 810 811].freeze
|
15
16
|
|
17
|
+
CONFERENCE_SEARCH_TAGS = %w[111 711 811].freeze
|
18
|
+
|
19
|
+
# subfields NOT to join when combining raw subfield values
|
20
|
+
NAME_EXCLUDED_SUBFIELDS = %w[a 1 4 5 6 8 t].freeze
|
21
|
+
|
16
22
|
# Author/Creator search field. Includes all subfield values (even ǂ0 URIs) from
|
17
23
|
# {https://www.oclc.org/bibformats/en/1xx/100.html 100 Main Entry--Personal Name} and
|
18
24
|
# {https://www.oclc.org/bibformats/en/1xx/110.html 110 Main Entry--Corporate Name}. Maps any relator codes found
|
19
25
|
# in ǂ4. To better handle name searches, returns names as both "First Last" and "Last, First" if a comma is found
|
20
|
-
# in ǂa. Also indexes any linked values in the 880.
|
21
|
-
#
|
22
|
-
# @todo
|
23
|
-
# but this should be reexamined in the relevancy-tuning phase. URIs should def be removed. and shouldn't
|
24
|
-
# indicator1 tell us the order of the name?
|
26
|
+
# in ǂa. Also indexes any linked values in the 880.
|
27
|
+
# @todo are we including too many details here and gumming up our index? consider UIRs, relator labels, dates...
|
28
|
+
# @todo shouldn't indicator1 tell us the order of the name? do we not trust the indicator?
|
25
29
|
# @note ported from get_author_creator_1_search_values
|
26
30
|
# @param [MARC::Record] record
|
27
31
|
# @param [Hash] relator_map
|
28
32
|
# @return [Array<String>] array of author/creator values for indexing
|
29
33
|
def search(record, relator_map: Mappers.relator)
|
30
|
-
|
31
|
-
acc = record.fields(TAGS).map do |field|
|
32
|
-
pieces = field.filter_map do |sf|
|
33
|
-
if sf.code == 'a'
|
34
|
-
convert_name_order(sf.value)
|
35
|
-
elsif creator_subfields.exclude?(sf.code)
|
36
|
-
sf.value
|
37
|
-
elsif sf.code == '4'
|
38
|
-
relator = translate_relator(sf.value, relator_map)
|
39
|
-
next if relator.blank?
|
40
|
-
|
41
|
-
relator
|
42
|
-
end
|
43
|
-
end
|
44
|
-
value = join_and_squish(pieces)
|
45
|
-
if value.end_with?('.', '-')
|
46
|
-
value
|
47
|
-
else
|
48
|
-
"#{value}."
|
49
|
-
end
|
50
|
-
end
|
51
|
-
# a second iteration over the same fields produces name entries with the names not reordered
|
52
|
-
secondary_subfields = %w[4 6 8]
|
53
|
-
acc += record.fields(TAGS).map do |field|
|
54
|
-
pieces = field.filter_map do |sf|
|
55
|
-
if secondary_subfields.exclude?(sf.code)
|
56
|
-
sf.value
|
57
|
-
elsif sf.code == '4'
|
58
|
-
relator = translate_relator(sf.value, relator_map)
|
59
|
-
next if relator.blank?
|
60
|
-
|
61
|
-
relator
|
62
|
-
end
|
63
|
-
end
|
64
|
-
value = join_and_squish(pieces)
|
65
|
-
if value.end_with?('.', '-')
|
66
|
-
value
|
67
|
-
else
|
68
|
-
"#{value}."
|
69
|
-
end
|
70
|
-
end
|
71
|
-
acc += record.fields(%w[880]).filter_map do |field|
|
72
|
-
next unless field.any? { |sf| sf.code == '6' && sf.value.in?(%w[100 110]) }
|
73
|
-
|
74
|
-
suba = field.find_all(&subfield_in?(%w[a])).map { |sf|
|
75
|
-
convert_name_order(sf.value)
|
76
|
-
}.first
|
77
|
-
oth = join_and_squish(field.find_all(&subfield_not_in?(%w[6 8 a t])).map(&:value))
|
78
|
-
join_and_squish [suba, oth]
|
79
|
-
end
|
80
|
-
acc.uniq
|
34
|
+
name_search_values record: record, tags: TAGS, relator_map: relator_map
|
81
35
|
end
|
82
36
|
|
83
37
|
# Auxiliary Author/Creator search field
|
38
|
+
# This duplicates the values returned by the search method, but adds in additional MARC tags to include
|
39
|
+
# creator-adjacent entities. The added 4xx tags are mostly obsolete, but the 7xx tags are important. See:
|
40
|
+
# {https://www.loc.gov/marc/bibliographic/bd700.html MARC 700},
|
41
|
+
# {https://www.loc.gov/marc/bibliographic/bd710.html MARC 710},
|
42
|
+
# and {https://www.loc.gov/marc/bibliographic/bd711.html MARC 711}. The 800, 810 and 8111 tags are similar in
|
43
|
+
# theme to the 7xx fields but apply to serial records.
|
84
44
|
# @note ported from get_author_creator_2_search_values
|
85
|
-
# @todo port this later
|
86
45
|
# @param [MARC::Record] record
|
87
46
|
# @return [Array<String>] array of extended author/creator values for indexing
|
88
|
-
def search_aux(record)
|
47
|
+
def search_aux(record, relator_map: Mappers.relator)
|
48
|
+
name_search_values record: record, tags: AUX_TAGS, relator_map: relator_map
|
49
|
+
end
|
89
50
|
|
90
51
|
# All author/creator values for display (like #show, but multivalued?) - no 880 linkage
|
91
52
|
# @note ported from get_author_creator_values (indexed as author_creator_a) - shown on results page
|
@@ -180,9 +141,14 @@ module PennMARC
|
|
180
141
|
end
|
181
142
|
end
|
182
143
|
|
183
|
-
#
|
184
|
-
# @
|
185
|
-
|
144
|
+
# Conference name values for searching
|
145
|
+
# @param [MARC::Record] record
|
146
|
+
# @return [Array<String>]
|
147
|
+
def conference_search(record)
|
148
|
+
record.fields(CONFERENCE_SEARCH_TAGS).filter_map do |field|
|
149
|
+
join_subfields(field, &subfield_in?(%w[a c d e]))
|
150
|
+
end
|
151
|
+
end
|
186
152
|
|
187
153
|
# Retrieve contributor values for display from fields {https://www.oclc.org/bibformats/en/7xx/700.html 700}
|
188
154
|
# and {https://www.oclc.org/bibformats/en/7xx/710.html 710} and their linked alternates. Joins subfields
|
@@ -222,6 +188,32 @@ module PennMARC
|
|
222
188
|
|
223
189
|
private
|
224
190
|
|
191
|
+
# @param [MARC::Record] record
|
192
|
+
# @param [Array] tags to consider
|
193
|
+
# @param [Hash] relator_map
|
194
|
+
# @return [Array<String>] name values from given tags
|
195
|
+
def name_search_values(record:, tags:, relator_map:)
|
196
|
+
acc = record.fields(tags).filter_map do |field|
|
197
|
+
name_from_main_entry field, relator_map, should_convert_name_order: false
|
198
|
+
end
|
199
|
+
|
200
|
+
acc += record.fields(tags).filter_map do |field|
|
201
|
+
name_from_main_entry field, relator_map, should_convert_name_order: true
|
202
|
+
end
|
203
|
+
|
204
|
+
acc += record.fields(['880']).filter_map do |field|
|
205
|
+
next unless field.any? { |sf| sf.code == '6' && sf.value.in?(tags) }
|
206
|
+
|
207
|
+
suba = field.find_all(&subfield_in?(%w[a])).filter_map { |sf|
|
208
|
+
convert_name_order(sf.value)
|
209
|
+
}.first
|
210
|
+
oth = join_and_squish(field.find_all(&subfield_not_in?(%w[6 8 a t])).map(&:value))
|
211
|
+
join_and_squish [suba, oth]
|
212
|
+
end
|
213
|
+
|
214
|
+
acc.uniq
|
215
|
+
end
|
216
|
+
|
225
217
|
# Trim punctuation method extracted from Traject macro, to ensure consistent output
|
226
218
|
# @todo move to Util?
|
227
219
|
# @param [String] string
|
@@ -244,11 +236,14 @@ module PennMARC
|
|
244
236
|
|
245
237
|
# Extract the information we care about from 1xx fields, map relator codes, and use appropriate punctuation
|
246
238
|
# @param [MARC::Field] field
|
239
|
+
# @param [Hash] mapping
|
240
|
+
# @param [Boolean] should_convert_name_order
|
247
241
|
# @return [String] joined subfield values for value from field
|
248
|
-
def name_from_main_entry(field, mapping)
|
249
|
-
name_subfields = %w[0 1 4 6 8]
|
242
|
+
def name_from_main_entry(field, mapping, should_convert_name_order: false)
|
250
243
|
s = field.filter_map { |sf|
|
251
|
-
if
|
244
|
+
if sf.code == 'a'
|
245
|
+
should_convert_name_order ? convert_name_order(sf.value) : sf.value
|
246
|
+
elsif NAME_EXCLUDED_SUBFIELDS.exclude?(sf.code)
|
252
247
|
" #{sf.value}"
|
253
248
|
elsif sf.code == '4'
|
254
249
|
relator = translate_relator(sf.value, mapping)
|
@@ -20,13 +20,13 @@ module PennMARC
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Retrieve date added (subfield 'q') from enriched marc 'itm' field.
|
23
|
-
# {PennMARC::
|
23
|
+
# {PennMARC::Enriched} maps enriched marc fields and subfields created during Alma publishing. The enriched
|
24
24
|
# metadata provided by the Alma API does not include the date created value, so we can't work with that here.
|
25
25
|
# @param [MARC::Record] record
|
26
26
|
# @return [DateTime, nil] The date added, or nil if date found in record is invalid
|
27
27
|
def added(record)
|
28
|
-
record.fields(
|
29
|
-
subfield_values(field,
|
28
|
+
record.fields(Enriched::Pub::ITEM_TAG).flat_map { |field|
|
29
|
+
subfield_values(field, Enriched::Pub::ITEM_DATE_CREATED).filter_map do |date_added|
|
30
30
|
# On 2022-05-02, this field value (as exported in enriched publishing
|
31
31
|
# job from Alma) began truncating time to day-level granularity. We have
|
32
32
|
# no guarantee that this won't switch back in the future, so for the
|
@@ -165,14 +165,14 @@ module PennMARC
|
|
165
165
|
# @param [MARC::Record] record
|
166
166
|
# @return [Array]
|
167
167
|
def call_nums(record)
|
168
|
-
if field_defined?(record,
|
169
|
-
record.fields(
|
170
|
-
join_subfields(field, &subfield_in?([
|
171
|
-
|
168
|
+
if field_defined?(record, Enriched::Pub::PHYS_INVENTORY_TAG)
|
169
|
+
record.fields(Enriched::Pub::PHYS_INVENTORY_TAG).map do |field|
|
170
|
+
join_subfields(field, &subfield_in?([Enriched::Pub::HOLDING_CLASSIFICATION_PART,
|
171
|
+
Enriched::Pub::HOLDING_ITEM_PART]))
|
172
172
|
end
|
173
|
-
elsif field_defined?(record,
|
174
|
-
record.fields(
|
175
|
-
join_subfields(field, &subfield_in?([
|
173
|
+
elsif field_defined?(record, Enriched::Api::PHYS_INVENTORY_TAG)
|
174
|
+
record.fields(Enriched::Api::PHYS_INVENTORY_TAG).map do |field|
|
175
|
+
join_subfields(field, &subfield_in?([Enriched::Api::PHYS_CALL_NUMBER_TYPE]))
|
176
176
|
end
|
177
177
|
else
|
178
178
|
[]
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'inventory_entry/electronic'
|
4
|
+
require_relative 'inventory_entry/physical'
|
5
|
+
|
6
|
+
module PennMARC
|
7
|
+
# Methods for extracting holdings information ("inventory") when available
|
8
|
+
class Inventory < Helper
|
9
|
+
PHYSICAL_INVENTORY_TAGS = [
|
10
|
+
Enriched::Pub::PHYS_INVENTORY_TAG,
|
11
|
+
Enriched::Api::PHYS_INVENTORY_TAG
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
ELECTRONIC_INVENTORY_TAGS = [
|
15
|
+
Enriched::Pub::ELEC_INVENTORY_TAG,
|
16
|
+
Enriched::Api::ELEC_INVENTORY_TAG
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Hash of Physical holdings information
|
21
|
+
# @param [MARC::Record] record
|
22
|
+
# @return [Array, nil]
|
23
|
+
def physical(record)
|
24
|
+
source = enrichment_source(record)
|
25
|
+
return unless source
|
26
|
+
|
27
|
+
record.fields(PHYSICAL_INVENTORY_TAGS).map do |entry|
|
28
|
+
InventoryEntry::Physical.new(entry, source).to_h
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Hash of Electronic inventory information
|
33
|
+
# @param [MARC::Record] record
|
34
|
+
# @return [Array, nil]
|
35
|
+
def electronic(record)
|
36
|
+
source = enrichment_source(record)
|
37
|
+
return unless source
|
38
|
+
|
39
|
+
record.fields(ELECTRONIC_INVENTORY_TAGS).map do |entry|
|
40
|
+
InventoryEntry::Electronic.new(entry, source).to_h
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Count of all electronic portfolios
|
45
|
+
# @param [MARC::Record] record
|
46
|
+
# @return [Integer]
|
47
|
+
def electronic_portfolio_count(record)
|
48
|
+
record.count { |field| field.tag.in? %w[AVE prt] }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Count of all physical holdings
|
52
|
+
# @param [MARC::Record] record
|
53
|
+
# @return [Integer]
|
54
|
+
def physical_holding_count(record)
|
55
|
+
record.count { |field| field.tag.in? %w[AVA hld] }
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Determine the source of the MARC inventory enrichment
|
61
|
+
# @param [MARC::Record] record
|
62
|
+
# @return [Symbol, nil]
|
63
|
+
def enrichment_source(record)
|
64
|
+
if pub_enrichment_tags?(record)
|
65
|
+
:pub
|
66
|
+
elsif api_enrichment_tags?(record)
|
67
|
+
:api
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Does the record include tags from Publishing inventory enrichment?
|
72
|
+
# @todo move to Util?
|
73
|
+
# @param [MARC::Record] record
|
74
|
+
# @return [Boolean]
|
75
|
+
def pub_enrichment_tags?(record)
|
76
|
+
record.tags.intersect?(
|
77
|
+
[Enriched::Pub::PHYS_INVENTORY_TAG, Enriched::Pub::ELEC_INVENTORY_TAG, Enriched::Pub::ITEM_TAG]
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Does the record include tags from API inventory enrichment?
|
82
|
+
# @todo move to Util?
|
83
|
+
# @param [MARC::Record] record
|
84
|
+
# @return [Boolean]
|
85
|
+
def api_enrichment_tags?(record)
|
86
|
+
record.tags.intersect?(
|
87
|
+
[Enriched::Api::PHYS_INVENTORY_TAG, Enriched::Api::ELEC_INVENTORY_TAG]
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PennMARC
|
4
|
+
module InventoryEntry
|
5
|
+
# Base class for InventoryEntry classes, defines required interface
|
6
|
+
class Base
|
7
|
+
attr_reader :source, :field, :mapper
|
8
|
+
|
9
|
+
# @param [MARC::DataField] inventory_field
|
10
|
+
# @param [Symbol] source
|
11
|
+
def initialize(inventory_field, source)
|
12
|
+
@source = source
|
13
|
+
@field = inventory_field
|
14
|
+
@mapper = @source == :api ? Enriched::Api : Enriched::Pub
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Hash]
|
18
|
+
def to_h
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module PennMARC
|
6
|
+
module InventoryEntry
|
7
|
+
# Represent a Electronic inventory entry - simple because the subfield specification is identical across
|
8
|
+
# entries returned by the API and Alma Publishing enrichment
|
9
|
+
class Electronic < Base
|
10
|
+
# @return [Hash{Symbol->Unknown}]
|
11
|
+
def to_h
|
12
|
+
{ portfolio_id: field[mapper::ELEC_PORTFOLIO_ID],
|
13
|
+
url: field[mapper::ELEC_SERVICE_URL],
|
14
|
+
collection_name: field[mapper::ELEC_COLLECTION_NAME],
|
15
|
+
coverage: field[mapper::ELEC_COVERAGE_STMT],
|
16
|
+
note: field[mapper::ELEC_PUBLIC_NOTE] }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module PennMARC
|
6
|
+
module InventoryEntry
|
7
|
+
# Represent a Physical inventory entry
|
8
|
+
class Physical < Base
|
9
|
+
# Call number from inventory entry
|
10
|
+
# @return [String (frozen)]
|
11
|
+
def call_num
|
12
|
+
if source == :pub
|
13
|
+
"#{field[mapper::HOLDING_CLASSIFICATION_PART]}#{field[mapper::HOLDING_ITEM_PART]}"
|
14
|
+
elsif source == :api
|
15
|
+
field[mapper::PHYS_CALL_NUMBER]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Priority for inventory entry
|
20
|
+
# @note we currently don't return priority in our publishing enrichment
|
21
|
+
# @return [String, nil]
|
22
|
+
def priority
|
23
|
+
return nil if source == :pub
|
24
|
+
|
25
|
+
field[mapper::PHYS_PRIORITY]
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Hash{Symbol->Unknown}]
|
29
|
+
def to_h
|
30
|
+
{ holding_id: field[mapper::PHYS_HOLDING_ID],
|
31
|
+
location_name: field[mapper::PHYS_LOCATION_NAME],
|
32
|
+
location_code: field[mapper::PHYS_LOCATION_CODE],
|
33
|
+
call_num: call_num,
|
34
|
+
priority: priority }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|