pennmarc 1.0.12 → 1.0.15.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +7 -12
  3. data/.rubocop_todo.yml +2 -2
  4. data/lib/pennmarc/enriched.rb +93 -0
  5. data/lib/pennmarc/helpers/access.rb +2 -2
  6. data/lib/pennmarc/helpers/citation.rb +4 -4
  7. data/lib/pennmarc/helpers/classification.rb +8 -8
  8. data/lib/pennmarc/helpers/creator.rb +70 -73
  9. data/lib/pennmarc/helpers/database.rb +6 -6
  10. data/lib/pennmarc/helpers/date.rb +3 -3
  11. data/lib/pennmarc/helpers/edition.rb +4 -2
  12. data/lib/pennmarc/helpers/format.rb +15 -14
  13. data/lib/pennmarc/helpers/helper.rb +1 -1
  14. data/lib/pennmarc/helpers/identifier.rb +16 -14
  15. data/lib/pennmarc/helpers/inventory.rb +92 -0
  16. data/lib/pennmarc/helpers/inventory_entry/base.rb +23 -0
  17. data/lib/pennmarc/helpers/inventory_entry/electronic.rb +20 -0
  18. data/lib/pennmarc/helpers/inventory_entry/physical.rb +38 -0
  19. data/lib/pennmarc/helpers/language.rb +3 -2
  20. data/lib/pennmarc/helpers/location.rb +19 -14
  21. data/lib/pennmarc/helpers/note.rb +10 -8
  22. data/lib/pennmarc/helpers/production.rb +9 -9
  23. data/lib/pennmarc/helpers/relation.rb +12 -9
  24. data/lib/pennmarc/helpers/series.rb +10 -8
  25. data/lib/pennmarc/helpers/subject.rb +12 -12
  26. data/lib/pennmarc/helpers/title.rb +20 -16
  27. data/lib/pennmarc/mappings/locations.yml +4 -0
  28. data/lib/pennmarc/util.rb +19 -4
  29. data/lib/pennmarc/version.rb +1 -1
  30. data/spec/lib/pennmarc/helpers/access_spec.rb +5 -5
  31. data/spec/lib/pennmarc/helpers/classification_spec.rb +6 -6
  32. data/spec/lib/pennmarc/helpers/creator_spec.rb +41 -7
  33. data/spec/lib/pennmarc/helpers/format_spec.rb +4 -4
  34. data/spec/lib/pennmarc/helpers/inventory_spec.rb +129 -0
  35. data/spec/lib/pennmarc/helpers/location_spec.rb +40 -9
  36. data/spec/lib/pennmarc/helpers/subject_spec.rb +37 -13
  37. metadata +10 -5
  38. data/lib/pennmarc/enriched_marc.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97610e7a00a2d206d84d5865ce9ebf14b5aa43e8081d9c4f559d198cc7adde23
4
- data.tar.gz: fcee05f6a1999b674cb699605ee1e16b04fdda87e6bb7e65c3cb54958ef19ba5
3
+ metadata.gz: cd846248b3c6fcde1cfd8a74312d84f46ce28f534effaff22ec112a5456afd2f
4
+ data.tar.gz: 7e5789d9ef2594767e438a5b1fd2978b1508afad975785059b3520d795fda15b
5
5
  SHA512:
6
- metadata.gz: '0099dbb3f521e56f8ba94527e13085058191acfdaa3625f30f050da3bb96d8ae224eb9c6e9fb54620ea6d4640800bc38bd71602a59d66871eedd26c7f6287b4e'
7
- data.tar.gz: 44cfade6b9c3ab3e0591553fbdf5c09f8be3467ccf71b0f003f7f0ff9dc8b08b1093d783b84c5f71ba3c98607a2d97f1e1ef62653cbe3af5361895d3cf964c1e
6
+ metadata.gz: 82a79402e68c4b48f3d38fa165042472aab3a8d811797145811af8a873d177ceb9020a2e71c9853f5c792a37d0533d7a87c7ec94506507c817e3dc09db345dcf
7
+ data.tar.gz: 87196088380e9b7f6750511434edcc49467ca9ee47e9577cb3f42ce75e595a2f7cb62605ede37d1a4b17b59876dec437a0beee131ee97eb19f1c411751c849cc
data/.gitlab-ci.yml CHANGED
@@ -1,4 +1,8 @@
1
1
  include:
2
+ - project: "devops/gitlab/ci-templates/general"
3
+ file:
4
+ - ".install_hashicorp_vault.yml"
5
+ - ".vault_jwt_auth.yml"
2
6
  - project: "devops/gitlab/ci-templates/ruby"
3
7
  ref: "sans-dind"
4
8
  file:
@@ -37,19 +41,10 @@ gem_publication:
37
41
  image: ruby:3.2.2
38
42
  variables:
39
43
  GEMSPEC_FILE: "${CI_PROJECT_NAME}.gemspec"
40
- VAULT_VERSION: "1.10.1"
44
+ VAULT_VERSION: "1.15.5"
41
45
  before_script:
42
- - |
43
- apt-get update && apt-get -y install \
44
- wget \
45
- unzip && \
46
- wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip -O /tmp/vault_${VAULT_VERSION}.zip && \
47
- unzip /tmp/vault_${VAULT_VERSION}.zip -d /usr/bin && \
48
- rm -fr /tmp/vault_${VAULT_VERSION}.zip && \
49
- apt-get remove -y wget unzip && \
50
- apt-get clean
51
- - export VAULT_ADDR=${VAULT_URL}
52
- - export VAULT_TOKEN="$(vault write -field=token auth/jwt/${CI_SERVER_HOST}/login role=${JWT_ROLE} jwt=${CI_JOB_JWT})"
46
+ - !reference [.install_hashicorp_vault, before_script]
47
+ - !reference [.vault_jwt_auth, before_script]
53
48
  - export GEM_HOST_API_KEY="$(vault kv get -field=rubygems_api_key ${VAULT_KV_ENDPOINT}${ENVIRONMENT})"
54
49
  - |
55
50
  gem -v
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-11 19:45:39 UTC using RuboCop version 1.51.0.
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: 25
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? [EnrichedMarc::TAG_ELECTRONIC_INVENTORY, EnrichedMarc::AlmaApi::TAG_ELECTRONIC_INVENTORY]
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? [EnrichedMarc::TAG_HOLDING, EnrichedMarc::AlmaApi::TAG_PHYSICAL_INVENTORY]
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:
@@ -11,9 +11,9 @@ module PennMARC
11
11
  # field 520 (Summary, Etc. Note).
12
12
  # https://www.loc.gov/marc/bibliographic/bd510.html
13
13
  # @param [MARC::Record] record
14
- # @return [Array] array of citations and any linked alternates
14
+ # @return [Array<String>] array of citations and any linked alternates
15
15
  def cited_in_show(record)
16
- datafield_and_linked_alternate(record, '510')
16
+ datafield_and_linked_alternate(record, '510').uniq
17
17
  end
18
18
 
19
19
  # Field 524 is the Preferred Citation of Described Materials Note. It is the Format for the citation of the
@@ -22,9 +22,9 @@ module PennMARC
22
22
  # introductory phrase that is generated as a display constant based on the first indicator value.
23
23
  # https://www.loc.gov/marc/bibliographic/bd524.html
24
24
  # @param [MARC::Record] record
25
- # @return [Array] array of citation of described materials note and any linked alternates
25
+ # @return [Array<String>] array of citation of described materials note and any linked alternates
26
26
  def cite_as_show(record)
27
- datafield_and_linked_alternate(record, '524')
27
+ datafield_and_linked_alternate(record, '524').uniq
28
28
  end
29
29
  end
30
30
  end
@@ -16,19 +16,19 @@ module PennMARC
16
16
  }.freeze
17
17
 
18
18
  # Enriched MARC tags that hold classification data
19
- TAGS = [EnrichedMarc::TAG_ITEM, EnrichedMarc::AlmaApi::TAG_PHYSICAL_INVENTORY].freeze
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::EnrichedMarc} and {PennMARC::EnrichedMarc::AlmaApi} for more
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
29
29
  # @return [Array<String>] array of classifications
30
30
  def facet(record)
31
- record.fields(TAGS).flat_map do |field|
31
+ record.fields(TAGS).flat_map { |field|
32
32
  call_number_type = subfield_values(field, call_number_type_sf(field))&.first
33
33
  call_numbers = subfield_values(field, call_number_sf(field))
34
34
 
@@ -39,7 +39,7 @@ module PennMARC
39
39
 
40
40
  format_facet(class_code, call_number_type, title)
41
41
  end
42
- end
42
+ }.uniq
43
43
  end
44
44
 
45
45
  private
@@ -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 EnrichedMarc::SUB_ITEM_CALL_NUMBER if field.tag == EnrichedMarc::TAG_ITEM
51
+ return Enriched::Pub::ITEM_CALL_NUMBER if field.tag == Enriched::Pub::ITEM_TAG
52
52
 
53
- EnrichedMarc::AlmaApi::SUB_PHYSICAL_CALL_NUMBER
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 EnrichedMarc::SUB_ITEM_CALL_NUMBER_TYPE if field.tag == EnrichedMarc::TAG_ITEM
60
+ return Enriched::Pub::ITEM_CALL_NUMBER_TYPE if field.tag == Enriched::Pub::ITEM_TAG
61
61
 
62
- EnrichedMarc::AlmaApi::SUB_PHYSICAL_CALL_NUMBER_TYPE
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. Some of the search fields remain incomplete and may need to be
21
- # further investigated and ported when search result relevancy is considered.
22
- # @todo this seems bad - why include relator labels? URIs? punctuation? leaving mostly as-is for now,
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
- creator_subfields = %w[a 1 4 6 8]
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); end
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
@@ -106,9 +67,9 @@ module PennMARC
106
67
  def show(record)
107
68
  fields = record.fields(TAGS)
108
69
  fields += record.fields('880').select { |field| subfield_value_in?(field, '6', TAGS) }
109
- fields.filter_map do |field|
70
+ fields.filter_map { |field|
110
71
  join_subfields(field, &subfield_not_in?(%w[0 1 4 6 8 e w]))
111
- end
72
+ }.uniq
112
73
  end
113
74
 
114
75
  # Author/Creator sort. Does not map and include any relator codes.
@@ -134,11 +95,11 @@ module PennMARC
134
95
  700 => 'abcdjq', 710 => 'abcdjq', 711 => 'abcen',
135
96
  800 => 'abcdjq', 810 => 'abcdjq', 811 => 'abcen'
136
97
  }
137
- source_map.flat_map do |field_num, subfields|
98
+ source_map.flat_map { |field_num, subfields|
138
99
  record.fields(field_num.to_s).map do |field|
139
100
  trim_punctuation(join_subfields(field, &subfield_in?(subfields.chars)))
140
101
  end
141
- end
102
+ }.uniq
142
103
  end
143
104
 
144
105
  # Conference for display, intended for results display
@@ -147,9 +108,9 @@ module PennMARC
147
108
  # @param [Hash] relator_map
148
109
  # @return [Array<String>] array of conference values
149
110
  def conference_show(record, relator_map: Mappers.relator)
150
- record.fields('111').filter_map do |field|
111
+ record.fields('111').filter_map { |field|
151
112
  name_from_main_entry field, relator_map
152
- end
113
+ }.uniq
153
114
  end
154
115
 
155
116
  # Conference detailed display, intended for record show page.
@@ -169,7 +130,7 @@ module PennMARC
169
130
  conf_extra = join_subfields field, &subfield_in?(%w[e j w])
170
131
  join_and_squish [conf, conf_extra].compact_blank
171
132
  end
172
- values + record.fields('880').filter_map do |field|
133
+ conferences = values + record.fields('880').filter_map do |field|
173
134
  next unless subfield_value_in? field, '6', %w[111 711]
174
135
 
175
136
  next if subfield_defined? field, 'i'
@@ -178,11 +139,17 @@ module PennMARC
178
139
  conf_extra = join_subfields(field, &subfield_in?(%w[4 e j w]))
179
140
  join_and_squish [conf, conf_extra]
180
141
  end
142
+ conferences.uniq
181
143
  end
182
144
 
183
- # @todo this supports "Conference" fielded search and may not be needed
184
- # @note see get_conference_search_values
185
- def conference_search(record); end
145
+ # Conference name values for searching
146
+ # @param [MARC::Record] record
147
+ # @return [Array<String>]
148
+ def conference_search(record)
149
+ record.fields(CONFERENCE_SEARCH_TAGS).filter_map { |field|
150
+ join_subfields(field, &subfield_in?(%w[a c d e]))
151
+ }.uniq
152
+ end
186
153
 
187
154
  # Retrieve contributor values for display from fields {https://www.oclc.org/bibformats/en/7xx/700.html 700}
188
155
  # and {https://www.oclc.org/bibformats/en/7xx/710.html 710} and their linked alternates. Joins subfields
@@ -193,7 +160,7 @@ module PennMARC
193
160
  # @return [Array<String>]
194
161
  def contributor_show(record, relator_map: Mappers.relator)
195
162
  indicator_2_options = ['', ' ', '0']
196
- contributors = record.fields(%w[700 710]).filter_map do |field|
163
+ values = record.fields(%w[700 710]).filter_map do |field|
197
164
  next unless indicator_2_options.member?(field.indicator2)
198
165
  next if subfield_defined? field, 'i'
199
166
 
@@ -210,7 +177,7 @@ module PennMARC
210
177
  }.join
211
178
  "#{contributor} #{contributor_append}".squish
212
179
  end
213
- contributors + record.fields('880').filter_map do |field|
180
+ contributors = values + record.fields('880').filter_map do |field|
214
181
  next unless subfield_value_in?(field, '6', %w[700 710])
215
182
  next if subfield_defined?(field, 'i')
216
183
 
@@ -218,10 +185,37 @@ module PennMARC
218
185
  contributor_append = join_subfields(field, &subfield_in?(%w[e u 3]))
219
186
  "#{contributor} #{contributor_append}".squish
220
187
  end
188
+ contributors.uniq
221
189
  end
222
190
 
223
191
  private
224
192
 
193
+ # @param [MARC::Record] record
194
+ # @param [Array] tags to consider
195
+ # @param [Hash] relator_map
196
+ # @return [Array<String>] name values from given tags
197
+ def name_search_values(record:, tags:, relator_map:)
198
+ acc = record.fields(tags).filter_map do |field|
199
+ name_from_main_entry field, relator_map, should_convert_name_order: false
200
+ end
201
+
202
+ acc += record.fields(tags).filter_map do |field|
203
+ name_from_main_entry field, relator_map, should_convert_name_order: true
204
+ end
205
+
206
+ acc += record.fields(['880']).filter_map do |field|
207
+ next unless field.any? { |sf| sf.code == '6' && sf.value.in?(tags) }
208
+
209
+ suba = field.find_all(&subfield_in?(%w[a])).filter_map { |sf|
210
+ convert_name_order(sf.value)
211
+ }.first
212
+ oth = join_and_squish(field.find_all(&subfield_not_in?(%w[6 8 a t])).map(&:value))
213
+ join_and_squish [suba, oth]
214
+ end
215
+
216
+ acc.uniq
217
+ end
218
+
225
219
  # Trim punctuation method extracted from Traject macro, to ensure consistent output
226
220
  # @todo move to Util?
227
221
  # @param [String] string
@@ -244,11 +238,14 @@ module PennMARC
244
238
 
245
239
  # Extract the information we care about from 1xx fields, map relator codes, and use appropriate punctuation
246
240
  # @param [MARC::Field] field
241
+ # @param [Hash] mapping
242
+ # @param [Boolean] should_convert_name_order
247
243
  # @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]
244
+ def name_from_main_entry(field, mapping, should_convert_name_order: false)
250
245
  s = field.filter_map { |sf|
251
- if name_subfields.exclude?(sf.code)
246
+ if sf.code == 'a'
247
+ should_convert_name_order ? convert_name_order(sf.value) : sf.value
248
+ elsif NAME_EXCLUDED_SUBFIELDS.exclude?(sf.code)
252
249
  " #{sf.value}"
253
250
  elsif sf.code == '4'
254
251
  relator = translate_relator(sf.value, mapping)
@@ -19,13 +19,13 @@ module PennMARC
19
19
  # @param [Marc::Record]
20
20
  # @return [Array<string>] Array of types
21
21
  def type_facet(record)
22
- record.fields('944').filter_map do |field|
22
+ record.fields('944').filter_map { |field|
23
23
  # skip unless specified database format type present
24
24
  next unless subfield_value?(field, 'a', /#{DATABASES_FACET_VALUE}/o)
25
25
 
26
26
  type = field.find { |subfield| subfield.code == 'b' }
27
27
  type&.value
28
- end
28
+ }.uniq
29
29
  end
30
30
 
31
31
  # Retrieves database subject category/communities of interest (subfield 'a') from
@@ -37,13 +37,13 @@ module PennMARC
37
37
  def category_facet(record)
38
38
  return [] unless curated_db?(record)
39
39
 
40
- record.fields('943').filter_map do |field|
40
+ record.fields('943').filter_map { |field|
41
41
  # skip unless Community of Interest code is in subfield '2'
42
42
  next unless subfield_value?(field, '2', /#{COI_CODE}/o)
43
43
 
44
44
  category = field.find { |subfield| subfield.code == 'a' }
45
45
  category&.value
46
- end
46
+ }.uniq
47
47
  end
48
48
 
49
49
  # Concatenates database subject category with database sub subject category in the format "category--subcategory"
@@ -58,7 +58,7 @@ module PennMARC
58
58
  def subcategory_facet(record)
59
59
  return [] unless curated_db?(record)
60
60
 
61
- record.fields('943').filter_map do |field|
61
+ record.fields('943').filter_map { |field|
62
62
  # skip unless Community of Interest code is in subfield '2'
63
63
  next unless subfield_value?(field, '2', /#{COI_CODE}/o)
64
64
 
@@ -73,7 +73,7 @@ module PennMARC
73
73
  next if subcategory.blank?
74
74
 
75
75
  "#{category.value}--#{subcategory.value}"
76
- end
76
+ }.uniq
77
77
  end
78
78
 
79
79
  private
@@ -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::EnrichedMarc} maps enriched marc fields and subfields created during Alma publishing. The enriched
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(EnrichedMarc::TAG_ITEM).flat_map { |field|
29
- subfield_values(field, EnrichedMarc::SUB_ITEM_DATE_CREATED).filter_map do |date_added|
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
@@ -14,9 +14,10 @@ module PennMARC
14
14
  # @param [MARC::Record] record
15
15
  # @return [Array<String>] array of editions and their alternates
16
16
  def show(record)
17
- record.fields('250').map { |field|
17
+ editions = record.fields('250').map { |field|
18
18
  join_subfields(field, &subfield_not_in?(%w[6 8]))
19
19
  } + linked_alternate_not_6_or_8(record, '250')
20
+ editions.uniq
20
21
  end
21
22
 
22
23
  # Edition values for display in search results. Just grab the first 250 field.
@@ -42,12 +43,13 @@ module PennMARC
42
43
 
43
44
  other_edition_value(field, relator_map)
44
45
  end
45
- values + record.fields('880').filter_map do |field|
46
+ editions = values + record.fields('880').filter_map do |field|
46
47
  next unless field.indicator2.blank? && subfield_value_in?(field, '6', %w[775]) &&
47
48
  subfield_defined?(field, 'i')
48
49
 
49
50
  other_edition_value(field, relator_map)
50
51
  end
52
+ editions.uniq
51
53
  end
52
54
 
53
55
  private
@@ -48,7 +48,7 @@ module PennMARC
48
48
  end
49
49
  join_subfields(f, &subfield_not_in?(subfield_to_ignore))
50
50
  end
51
- results.compact_blank
51
+ results.compact_blank.uniq
52
52
  end
53
53
 
54
54
  # Get Format values for faceting. Format values are determined using complex logic for each possible format value.
@@ -122,23 +122,24 @@ module PennMARC
122
122
  OTHER
123
123
  end
124
124
  end
125
- formats.concat(curated_format(record))
125
+ formats.concat(curated_format(record)).uniq
126
126
  end
127
127
 
128
128
  # Show "Other Format" values from {https://www.oclc.org/bibformats/en/7xx/776.html 776} and any 880 linkage.
129
129
  # @todo is 774 an error in the linked field in legacy? i changed to 776 here
130
130
  # @param [MARC::Record] record
131
- # @return [Array] other format values for display
131
+ # @return [Array<String>] other format values for display
132
132
  def other_show(record)
133
- other_formats = record.fields('776').filter_map do |field|
133
+ values = record.fields('776').filter_map do |field|
134
134
  value = join_subfields(field, &subfield_in?(%w[i a s t o]))
135
135
  next if value.blank?
136
136
 
137
137
  value
138
138
  end
139
- other_formats + linked_alternate(record, '776') do |sf|
139
+ other_formats = values + linked_alternate(record, '776') do |sf|
140
140
  sf.code.in? %w[i a s t o]
141
141
  end
142
+ other_formats.uniq
142
143
  end
143
144
 
144
145
  # Retrieve cartographic reference data for map/atlas formats for display from
@@ -146,9 +147,9 @@ module PennMARC
146
147
  # @param [MARC::Record] record
147
148
  # @return [Array<String>]
148
149
  def cartographic_show(record)
149
- record.fields(%w[255 342]).map do |field|
150
+ record.fields(%w[255 342]).map { |field|
150
151
  join_subfields(field, &subfield_not_in?(%w[6 8]))
151
- end
152
+ }.uniq
152
153
  end
153
154
 
154
155
  # Check if a set of locations has any locations that include the term 'manuscripts'
@@ -165,14 +166,14 @@ module PennMARC
165
166
  # @param [MARC::Record] record
166
167
  # @return [Array]
167
168
  def call_nums(record)
168
- if field_defined?(record, EnrichedMarc::TAG_HOLDING)
169
- record.fields(EnrichedMarc::TAG_HOLDING).map do |field|
170
- join_subfields(field, &subfield_in?([EnrichedMarc::SUB_HOLDING_CLASSIFICATION_PART,
171
- EnrichedMarc::SUB_HOLDING_ITEM_PART]))
169
+ if field_defined?(record, Enriched::Pub::PHYS_INVENTORY_TAG)
170
+ record.fields(Enriched::Pub::PHYS_INVENTORY_TAG).map do |field|
171
+ join_subfields(field, &subfield_in?([Enriched::Pub::HOLDING_CLASSIFICATION_PART,
172
+ Enriched::Pub::HOLDING_ITEM_PART]))
172
173
  end
173
- elsif field_defined?(record, EnrichedMarc::AlmaApi::TAG_PHYSICAL_INVENTORY)
174
- record.fields(EnrichedMarc::AlmaApi::TAG_PHYSICAL_INVENTORY).map do |field|
175
- join_subfields(field, &subfield_in?([EnrichedMarc::AlmaApi::SUB_PHYSICAL_CALL_NUMBER_TYPE]))
174
+ elsif field_defined?(record, Enriched::Api::PHYS_INVENTORY_TAG)
175
+ record.fields(Enriched::Api::PHYS_INVENTORY_TAG).map do |field|
176
+ join_subfields(field, &subfield_in?([Enriched::Api::PHYS_CALL_NUMBER_TYPE]))
176
177
  end
177
178
  else
178
179
  []