pennmarc 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +2 -2
- 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 +8 -3
- 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/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000`
|
3
|
-
# on 2024-01-
|
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
|
@@ -60,7 +60,7 @@ Metrics/CyclomaticComplexity:
|
|
60
60
|
- 'lib/pennmarc/helpers/title.rb'
|
61
61
|
- 'lib/pennmarc/util.rb'
|
62
62
|
|
63
|
-
# Offense count:
|
63
|
+
# Offense count: 26
|
64
64
|
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
65
65
|
Metrics/MethodLength:
|
66
66
|
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
|
@@ -3,10 +3,12 @@
|
|
3
3
|
module PennMARC
|
4
4
|
# Methods that return Library and Location values from Alma enhanced MARC fields
|
5
5
|
class Location < Helper
|
6
|
+
ONLINE_LIBRARY = 'Online library'
|
7
|
+
|
6
8
|
class << self
|
7
9
|
# Retrieves library location from enriched marc 'itm' or 'hld' fields, giving priority to the item location over
|
8
10
|
# the holdings location. Returns item's location if available. Otherwise, returns holding's location.
|
9
|
-
# {PennMARC::
|
11
|
+
# {PennMARC::Enriched} maps enriched marc fields and subfields created during Alma publishing.
|
10
12
|
# @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/
|
11
13
|
# Alma documentation for these added fields
|
12
14
|
# @param [MARC::Record] record
|
@@ -19,7 +21,7 @@ module PennMARC
|
|
19
21
|
# Retrieves the specific location from enriched marc 'itm' or 'hld' fields, giving priority to the item location
|
20
22
|
# over the holdings location. Returns item library location if available. Otherwise, returns holdings library
|
21
23
|
# location.
|
22
|
-
# {PennMARC::
|
24
|
+
# {PennMARC::Enriched} maps enriched marc fields and subfields created during Alma publishing.
|
23
25
|
# @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/
|
24
26
|
# Alma documentation for these added fields
|
25
27
|
# @param [MARC::Record] record
|
@@ -31,7 +33,7 @@ module PennMARC
|
|
31
33
|
|
32
34
|
# Base method to retrieve location data from enriched marc 'itm' or 'hld' fields, giving priority to the item
|
33
35
|
# location over the holdings location. Returns item location if available. Otherwise, returns holdings location.
|
34
|
-
# {PennMARC::
|
36
|
+
# {PennMARC::Enriched} maps enriched marc fields and subfields created during Alma publishing.
|
35
37
|
# @see https://developers.exlibrisgroup.com/alma/apis/docs/bibs/R0VUIC9hbG1hd3MvdjEvYmlicy97bW1zX2lkfQ==/
|
36
38
|
# Alma documentation for these added fields
|
37
39
|
# @param [MARC::Record] record
|
@@ -61,7 +63,9 @@ module PennMARC
|
|
61
63
|
location_map[subfield.value.to_sym][display_value.to_sym]
|
62
64
|
}.flatten.compact_blank
|
63
65
|
}.uniq
|
64
|
-
|
66
|
+
if record.tags.intersect?([Enriched::Pub::ELEC_INVENTORY_TAG, Enriched::Api::ELEC_INVENTORY_TAG])
|
67
|
+
locations << ONLINE_LIBRARY
|
68
|
+
end
|
65
69
|
locations
|
66
70
|
end
|
67
71
|
|
@@ -82,17 +86,18 @@ module PennMARC
|
|
82
86
|
# Since item records may reflect locations more accurately, we use them if they exist;
|
83
87
|
# if not, we use the holdings.
|
84
88
|
|
85
|
-
tag = PennMARC::EnrichedMarc::TAG_HOLDING
|
86
|
-
subfield_code = PennMARC::EnrichedMarc::SUB_HOLDING_SHELVING_LOCATION
|
87
|
-
|
88
89
|
# if the record has an enriched item field present, use it
|
89
|
-
if field_defined?(record,
|
90
|
-
tag =
|
91
|
-
subfield_code =
|
92
|
-
#
|
93
|
-
elsif field_defined?(record,
|
94
|
-
tag =
|
95
|
-
subfield_code =
|
90
|
+
if field_defined?(record, Enriched::Pub::ITEM_TAG)
|
91
|
+
tag = Enriched::Pub::ITEM_TAG
|
92
|
+
subfield_code = Enriched::Pub::ITEM_CURRENT_LOCATION
|
93
|
+
# if the record has API inventory tags, use them
|
94
|
+
elsif field_defined?(record, Enriched::Api::PHYS_INVENTORY_TAG)
|
95
|
+
tag = Enriched::Api::PHYS_INVENTORY_TAG
|
96
|
+
subfield_code = Enriched::Api::PHYS_LOCATION_CODE
|
97
|
+
# otherwise use Pub holding tags
|
98
|
+
else
|
99
|
+
tag = Enriched::Pub::PHYS_INVENTORY_TAG
|
100
|
+
subfield_code = Enriched::Pub::PHYS_LOCATION_CODE
|
96
101
|
end
|
97
102
|
|
98
103
|
{ tag: tag, subfield_code: subfield_code }
|