pennmarc 1.3.5 → 1.4.0
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/.pipeline.yml +4 -0
- data/.rubocop_todo.yml +15 -6
- data/Gemfile +2 -1
- data/Gemfile.lock +8 -3
- data/lib/pennmarc/helpers/date.rb +2 -2
- data/lib/pennmarc/helpers/title.rb +23 -0
- data/lib/pennmarc/mappers.rb +1 -2
- data/lib/pennmarc/services/title_suggestion_weight_service.rb +102 -0
- data/lib/pennmarc/version.rb +1 -1
- data/spec/lib/pennmarc/helpers/date_spec.rb +2 -2
- data/spec/lib/pennmarc/helpers/title_spec.rb +33 -0
- data/spec/lib/pennmarc/services/title_suggestion_weight_service_spec.rb +231 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea8b17f4fa038de9e5c44d384b82dfce5cc7a6cb909a5dc63fa1080351096e69
|
|
4
|
+
data.tar.gz: 58c48f68947d697689499018ca2abdc89d791db97e0ee076b74a12bf161d4470
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dad1e73ee44fa2635d81c6e0f1b7abc54d648b81679298121e6358c92f6872bf2f22d87f493005dad17b94402166cecb5c4751b3da33ae3a8ca35d90bc283e56
|
|
7
|
+
data.tar.gz: a23a12db492f9449632cfba5c7d1d247ab8302ede1094ac12ed0ff175f796787a2fa99dc9b17672d35ff67747523d9b4e40b31be7b602d910ae9a220d8f572a3
|
data/.pipeline.yml
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
variables:
|
|
2
|
+
FF_SCRIPT_SECTIONS: "true"
|
|
3
|
+
GIT_CLONE_PATH: "${CI_BUILDS_DIR}/${CI_PROJECT_NAME}/${CI_JOB_ID}"
|
|
4
|
+
|
|
1
5
|
include:
|
|
2
6
|
- component: gitlab.library.upenn.edu/devops/gitlab/components/general/install_hashicorp_vault@~latest
|
|
3
7
|
- component: gitlab.library.upenn.edu/devops/gitlab/components/general/vault_jwt_auth@~latest
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 10000`
|
|
3
|
-
# on 2025-
|
|
3
|
+
# on 2025-10-22 21:06:47 UTC using RuboCop version 1.79.2.
|
|
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:
|
|
9
|
+
# Offense count: 28
|
|
10
10
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
|
11
11
|
Metrics/AbcSize:
|
|
12
12
|
Exclude:
|
|
@@ -33,7 +33,7 @@ Metrics/ClassLength:
|
|
|
33
33
|
- 'lib/pennmarc/helpers/subject.rb'
|
|
34
34
|
- 'lib/pennmarc/helpers/title.rb'
|
|
35
35
|
|
|
36
|
-
# Offense count:
|
|
36
|
+
# Offense count: 22
|
|
37
37
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
38
38
|
Metrics/CyclomaticComplexity:
|
|
39
39
|
Exclude:
|
|
@@ -50,7 +50,7 @@ Metrics/CyclomaticComplexity:
|
|
|
50
50
|
- 'lib/pennmarc/helpers/title.rb'
|
|
51
51
|
- 'lib/pennmarc/util.rb'
|
|
52
52
|
|
|
53
|
-
# Offense count:
|
|
53
|
+
# Offense count: 18
|
|
54
54
|
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
|
55
55
|
Metrics/MethodLength:
|
|
56
56
|
Exclude:
|
|
@@ -70,7 +70,7 @@ Metrics/ModuleLength:
|
|
|
70
70
|
Exclude:
|
|
71
71
|
- 'lib/pennmarc/util.rb'
|
|
72
72
|
|
|
73
|
-
# Offense count:
|
|
73
|
+
# Offense count: 17
|
|
74
74
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
|
75
75
|
Metrics/PerceivedComplexity:
|
|
76
76
|
Exclude:
|
|
@@ -109,9 +109,18 @@ RSpec/ExampleLength:
|
|
|
109
109
|
Exclude:
|
|
110
110
|
- 'spec/lib/pennmarc/marc_util_spec.rb'
|
|
111
111
|
|
|
112
|
-
# Offense count:
|
|
112
|
+
# Offense count: 2
|
|
113
113
|
# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata.
|
|
114
114
|
# Include: **/*_spec.rb
|
|
115
115
|
RSpec/SpecFilePathFormat:
|
|
116
116
|
Exclude:
|
|
117
117
|
- 'spec/lib/pennmarc/parser_spec.rb'
|
|
118
|
+
- 'spec/lib/pennmarc/services/title_suggestion_weight_service_spec.rb'
|
|
119
|
+
|
|
120
|
+
# Offense count: 1
|
|
121
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
122
|
+
# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
|
|
123
|
+
# SupportedStyles: predicate, comparison
|
|
124
|
+
Style/NumericPredicate:
|
|
125
|
+
Exclude:
|
|
126
|
+
- 'lib/pennmarc/services/title_suggestion_weight_service.rb'
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -35,6 +35,7 @@ GEM
|
|
|
35
35
|
nokogiri (~> 1.0)
|
|
36
36
|
rexml
|
|
37
37
|
minitest (5.26.0)
|
|
38
|
+
nio4r (2.7.4)
|
|
38
39
|
nokogiri (1.18.9-arm64-darwin)
|
|
39
40
|
racc (~> 1.4)
|
|
40
41
|
nokogiri (1.18.9-x64-mingw-ucrt)
|
|
@@ -48,8 +49,12 @@ GEM
|
|
|
48
49
|
ast (~> 2.4.1)
|
|
49
50
|
racc
|
|
50
51
|
prism (1.4.0)
|
|
52
|
+
puma (7.1.0)
|
|
53
|
+
nio4r (~> 2.0)
|
|
51
54
|
racc (1.8.1)
|
|
52
|
-
rack (3.2.
|
|
55
|
+
rack (3.2.3)
|
|
56
|
+
rackup (2.2.1)
|
|
57
|
+
rack (>= 3)
|
|
53
58
|
rainbow (3.1.1)
|
|
54
59
|
rake (13.3.0)
|
|
55
60
|
regexp_parser (2.11.2)
|
|
@@ -121,7 +126,6 @@ GEM
|
|
|
121
126
|
rubocop-rake
|
|
122
127
|
rubocop-rspec
|
|
123
128
|
uri (1.0.4)
|
|
124
|
-
webrick (1.8.1)
|
|
125
129
|
yard (0.9.37)
|
|
126
130
|
|
|
127
131
|
PLATFORMS
|
|
@@ -137,11 +141,12 @@ DEPENDENCIES
|
|
|
137
141
|
library_stdnums (~> 1.6)
|
|
138
142
|
marc (~> 1.2)
|
|
139
143
|
nokogiri (~> 1.15)
|
|
144
|
+
puma
|
|
145
|
+
rackup
|
|
140
146
|
rake (~> 13.0)
|
|
141
147
|
rspec (~> 3.12)
|
|
142
148
|
simplecov (~> 0.22)
|
|
143
149
|
upennlib-rubocop
|
|
144
|
-
webrick (~> 1.8)
|
|
145
150
|
yard (~> 0.9)
|
|
146
151
|
|
|
147
152
|
BUNDLED WITH
|
|
@@ -36,7 +36,7 @@ module PennMARC
|
|
|
36
36
|
|
|
37
37
|
Time.strptime(date_added, format)
|
|
38
38
|
rescue StandardError => e
|
|
39
|
-
|
|
39
|
+
warn 'Error parsing date in date added subfield. ' \
|
|
40
40
|
"mmsid: #{Identifier.mmsid(record)}, value: #{date_added}, error: #{e}"
|
|
41
41
|
nil
|
|
42
42
|
end
|
|
@@ -59,7 +59,7 @@ module PennMARC
|
|
|
59
59
|
|
|
60
60
|
Time.strptime(date_time_string, '%Y%m%d%H%M%S.%N')
|
|
61
61
|
rescue StandardError => e
|
|
62
|
-
|
|
62
|
+
warn 'Error parsing last updated date. ' \
|
|
63
63
|
"mmsid: #{Identifier.mmsid(record)}, value: #{date_time_string}, error: #{e}"
|
|
64
64
|
nil
|
|
65
65
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../services/title_suggestion_weight_service'
|
|
4
|
+
|
|
3
5
|
module PennMARC
|
|
4
6
|
# This helper contains logic for parsing out Title and Title-related fields.
|
|
5
7
|
class Title < Helper
|
|
@@ -36,6 +38,27 @@ module PennMARC
|
|
|
36
38
|
NO_TITLE_PROVIDED = '[No title provided]'
|
|
37
39
|
|
|
38
40
|
class << self
|
|
41
|
+
# Values for title suggester, including only ǂa and ǂb from
|
|
42
|
+
# {https://www.loc.gov/marc/bibliographic/bd245.html 245} field. Limits the output to 20 words and strips any
|
|
43
|
+
# trailing slashes.
|
|
44
|
+
# @param record [MARC::Record]
|
|
45
|
+
# @return [Array<String>] array of all title values for suggestion
|
|
46
|
+
def suggest(record)
|
|
47
|
+
record.fields(%w[245]).filter_map do |field|
|
|
48
|
+
join_subfields(field, &subfield_in?(%w[a b]))
|
|
49
|
+
.squish
|
|
50
|
+
.truncate_words(20)
|
|
51
|
+
.sub(%r{ /$}, '')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# An integer value used for weighing title suggest values. See {PennMARC::TitleSuggestionWeightService} for logic.
|
|
56
|
+
# @param record [MARC::Record]
|
|
57
|
+
# @return [Integer]
|
|
58
|
+
def suggest_weight(record)
|
|
59
|
+
PennMARC::TitleSuggestionWeightService.weight record
|
|
60
|
+
end
|
|
61
|
+
|
|
39
62
|
# Main Title Search field. Takes from {https://www.loc.gov/marc/bibliographic/bd245.html 245} and linked 880.
|
|
40
63
|
# @note Ported from get_title_1_search_values.
|
|
41
64
|
# @param record [MARC::Record]
|
data/lib/pennmarc/mappers.rb
CHANGED
|
@@ -50,10 +50,9 @@ module PennMARC
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# @param filename [String] name of mapping file in config directory, with file extension
|
|
53
|
-
# @param symbolize_names [Boolean] whether
|
|
53
|
+
# @param symbolize_names [Boolean] whether to symbolize keys in returned hash
|
|
54
54
|
# @return [Hash, nil] mapping as hash
|
|
55
55
|
def load_map(filename, symbolize_names: true)
|
|
56
|
-
puts { "Loading #{filename}" }
|
|
57
56
|
YAML.safe_load(File.read(File.join(File.expand_path(__dir__), 'mappings', filename)),
|
|
58
57
|
symbolize_names: symbolize_names)
|
|
59
58
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PennMARC
|
|
4
|
+
# A service to calculate suggestion weights based on a variety of criteria
|
|
5
|
+
class TitleSuggestionWeightService
|
|
6
|
+
# Starting score
|
|
7
|
+
BASE_WEIGHT = 10
|
|
8
|
+
|
|
9
|
+
# Array of symbols referring to methods on this object that return a boolean and the scoring factor if the
|
|
10
|
+
# method returns true.
|
|
11
|
+
FACTORS = [
|
|
12
|
+
[:targeted_format?, 8],
|
|
13
|
+
[:published_in_last_ten_years?, 5],
|
|
14
|
+
[:electronic_holdings?, 3],
|
|
15
|
+
[:high_encoding_level?, 2],
|
|
16
|
+
[:physical_holdings?, 1],
|
|
17
|
+
[:low_encoding_level?, -2],
|
|
18
|
+
[:weird_format?, -5],
|
|
19
|
+
[:no_holdings?, -10]
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
# Score higher records with these formats
|
|
23
|
+
TARGETED_FORMATS = [Format::BOOK, Format::WEBSITE_DATABASE, Format::JOURNAL_PERIODICAL, Format::NEWSPAPER,
|
|
24
|
+
Format::SOUND_RECORDING, Format::MUSICAL_SCORE].freeze
|
|
25
|
+
# Score lower these formats
|
|
26
|
+
WEIRD_FORMATS = [Format::OTHER, Format::THREE_D_OBJECT].freeze
|
|
27
|
+
|
|
28
|
+
# See #{PennMARC::EncodingLevel} for more of the logic that determines sort values
|
|
29
|
+
# An encoding sort level of this value is considered good
|
|
30
|
+
HIGH_ENCODING_SORT_LEVEL = 0
|
|
31
|
+
# An encoding sort level higher than this is considered poor
|
|
32
|
+
LOW_ENCODING_SORT_LEVEL = 4
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
# Calculate a weight for use in sorting good title suggestions from bad
|
|
36
|
+
# @param record [MARC::Record]
|
|
37
|
+
# @return [Integer]
|
|
38
|
+
def weight(record)
|
|
39
|
+
factors.reduce(BASE_WEIGHT) do |weight, (call, score)|
|
|
40
|
+
weight + (public_send(call, record) ? score : 0)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Array[Array]]
|
|
45
|
+
def factors
|
|
46
|
+
FACTORS
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param record [MARC::Record]
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
def published_in_last_ten_years?(record)
|
|
52
|
+
return false unless Date.publication(record).present?
|
|
53
|
+
|
|
54
|
+
Date.publication(record) > 10.years.ago
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param record [MARC::Record]
|
|
58
|
+
# @return [Boolean]
|
|
59
|
+
def electronic_holdings?(record)
|
|
60
|
+
Inventory.electronic(record)&.any? || false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @param record [MARC::Record]
|
|
64
|
+
# @return [Boolean]
|
|
65
|
+
def physical_holdings?(record)
|
|
66
|
+
Inventory.physical(record)&.any? || false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @param record [MARC::Record]
|
|
70
|
+
# @return [Boolean]
|
|
71
|
+
def targeted_format?(record)
|
|
72
|
+
(Format.facet(record) & TARGETED_FORMATS).any?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param record [MARC::Record]
|
|
76
|
+
# @return [Boolean]
|
|
77
|
+
def high_encoding_level?(record)
|
|
78
|
+
Encoding.level_sort(record) == HIGH_ENCODING_SORT_LEVEL
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @param record [MARC::Record]
|
|
82
|
+
# @return [Boolean]
|
|
83
|
+
def weird_format?(record)
|
|
84
|
+
(Format.facet(record) & WEIRD_FORMATS).any?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @param record [MARC::Record]
|
|
88
|
+
# @return [Boolean]
|
|
89
|
+
def no_holdings?(record)
|
|
90
|
+
!electronic_holdings?(record) && !physical_holdings?(record)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @param record [MARC::Record]
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
def low_encoding_level?(record)
|
|
96
|
+
return false unless Encoding.level_sort(record).present?
|
|
97
|
+
|
|
98
|
+
Encoding.level_sort(record) > LOW_ENCODING_SORT_LEVEL
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/pennmarc/version.rb
CHANGED
|
@@ -90,7 +90,7 @@ describe 'PennMARC::Date' do
|
|
|
90
90
|
expect {
|
|
91
91
|
helper.added(record)
|
|
92
92
|
}.to output('Error parsing date in date added subfield. mmsid: mmsid, value: invalid date, ' \
|
|
93
|
-
"error: invalid date or strptime format - `invalid date' `%Y-%m-%d %H:%M:%S'\n").
|
|
93
|
+
"error: invalid date or strptime format - `invalid date' `%Y-%m-%d %H:%M:%S'\n").to_stderr
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -120,7 +120,7 @@ describe 'PennMARC::Date' do
|
|
|
120
120
|
expect {
|
|
121
121
|
helper.last_updated(record)
|
|
122
122
|
}.to output('Error parsing last updated date. mmsid: mmsid, value: invalid date, ' \
|
|
123
|
-
"error: invalid date or strptime format - `invalid date' `%Y%m%d%H%M%S.%N'\n").
|
|
123
|
+
"error: invalid date or strptime format - `invalid date' `%Y%m%d%H%M%S.%N'\n").to_stderr
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
end
|
|
@@ -6,6 +6,39 @@ describe 'PennMARC::Title' do
|
|
|
6
6
|
let(:fields) { [marc_field(tag: '245', subfields: subfields)] }
|
|
7
7
|
let(:record) { marc_record fields: fields, leader: leader }
|
|
8
8
|
|
|
9
|
+
describe '.suggest' do
|
|
10
|
+
context 'with slashes in the title as well as at the end of ǂb' do
|
|
11
|
+
let(:subfields) { { a: 'Title /', b: 'Subtitle /' } }
|
|
12
|
+
|
|
13
|
+
it 'removes only the trailing slash' do
|
|
14
|
+
expect(helper.suggest(record).first).to eq 'Title / Subtitle'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context 'with a long title more than twenty words' do
|
|
19
|
+
let(:subfields) do
|
|
20
|
+
{ a: 'The Book of Very Short Words With a Lot of Words',
|
|
21
|
+
b: 'Containing many words with that may or may not have many characters /' }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'truncates the title to twenty words and adds a trailing ellipsis' do
|
|
25
|
+
truncated_suggestion = helper.suggest(record).first
|
|
26
|
+
expect(truncated_suggestion).to eq(
|
|
27
|
+
'The Book of Very Short Words With a Lot of Words Containing many words with that may or may not...'
|
|
28
|
+
)
|
|
29
|
+
expect(truncated_suggestion.split(' ').count).to eq 20
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'with other subfields present' do
|
|
34
|
+
let(:subfields) { { a: 'Title', b: 'Subtitle', c: 'Author' } }
|
|
35
|
+
|
|
36
|
+
it 'returns only title fields' do
|
|
37
|
+
expect(helper.suggest(record).first).not_to include 'Author'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
9
42
|
describe '.search' do
|
|
10
43
|
let(:fields) do
|
|
11
44
|
[marc_field(tag: '245', subfields: { a: 'Title', b: 'Subtitle', c: 'Responsibility', h: 'Medium' }),
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe PennMARC::TitleSuggestionWeightService do
|
|
4
|
+
let(:record) { instance_double MARC::Record }
|
|
5
|
+
|
|
6
|
+
describe '.weight' do
|
|
7
|
+
context 'with defined factors' do
|
|
8
|
+
before do
|
|
9
|
+
allow(described_class).to receive_messages(
|
|
10
|
+
targeted_format?: true,
|
|
11
|
+
published_in_last_ten_years?: false,
|
|
12
|
+
electronic_holdings?: false,
|
|
13
|
+
high_encoding_level?: false,
|
|
14
|
+
physical_holdings?: false,
|
|
15
|
+
low_encoding_level?: false,
|
|
16
|
+
weird_format?: false,
|
|
17
|
+
no_holdings?: false
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'scores properly based on factor responce valence' do
|
|
22
|
+
expected_score = described_class::BASE_WEIGHT + described_class::FACTORS[0].second
|
|
23
|
+
expect(described_class.weight(record)).to eq expected_score
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe '.published_in_the_last_ten_years' do
|
|
29
|
+
before { allow(PennMARC::Date).to receive(:publication).with(record).and_return(record_date) }
|
|
30
|
+
|
|
31
|
+
let(:value) { described_class.published_in_last_ten_years?(record) }
|
|
32
|
+
|
|
33
|
+
context 'with no date' do
|
|
34
|
+
let(:record_date) { nil }
|
|
35
|
+
|
|
36
|
+
it 'returns true' do
|
|
37
|
+
expect(value).to be false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'with a recent date' do
|
|
42
|
+
let(:record_date) { Time.now }
|
|
43
|
+
|
|
44
|
+
it 'returns true' do
|
|
45
|
+
expect(value).to be true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context 'with an ancient date' do
|
|
50
|
+
let(:record_date) { Time.now - 400.years }
|
|
51
|
+
|
|
52
|
+
it 'returns false' do
|
|
53
|
+
expect(value).to be false
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '.targeted_format?' do
|
|
59
|
+
before { allow(PennMARC::Format).to receive(:facet).with(record).and_return([record_format]) }
|
|
60
|
+
|
|
61
|
+
let(:value) { described_class.targeted_format?(record) }
|
|
62
|
+
|
|
63
|
+
context 'with no format' do
|
|
64
|
+
let(:record_format) { nil }
|
|
65
|
+
|
|
66
|
+
it 'returns false' do
|
|
67
|
+
expect(value).to be false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context 'with a targeted format' do
|
|
72
|
+
let(:record_format) { PennMARC::TitleSuggestionWeightService::TARGETED_FORMATS.sample }
|
|
73
|
+
|
|
74
|
+
it 'returns true' do
|
|
75
|
+
expect(value).to be true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context 'with a non-targeted format' do
|
|
80
|
+
let(:record_format) { PennMARC::TitleSuggestionWeightService::WEIRD_FORMATS.sample }
|
|
81
|
+
|
|
82
|
+
it 'returns false' do
|
|
83
|
+
expect(value).to be false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe '.weird_format?' do
|
|
89
|
+
before { allow(PennMARC::Format).to receive(:facet).with(record).and_return([record_format]) }
|
|
90
|
+
|
|
91
|
+
let(:value) { described_class.weird_format?(record) }
|
|
92
|
+
|
|
93
|
+
context 'with no format' do
|
|
94
|
+
let(:record_format) { nil }
|
|
95
|
+
|
|
96
|
+
it 'returns false' do
|
|
97
|
+
expect(value).to be false
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
context 'with a weird format' do
|
|
102
|
+
let(:record_format) { PennMARC::TitleSuggestionWeightService::WEIRD_FORMATS.sample }
|
|
103
|
+
|
|
104
|
+
it 'returns true' do
|
|
105
|
+
expect(value).to be true
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'with a non-weird format' do
|
|
110
|
+
let(:record_format) { PennMARC::TitleSuggestionWeightService::TARGETED_FORMATS.sample }
|
|
111
|
+
|
|
112
|
+
it 'returns false' do
|
|
113
|
+
expect(value).to be false
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe '.low_encoding_level?' do
|
|
119
|
+
before { allow(PennMARC::Encoding).to receive(:level_sort).with(record).and_return(encoding_sort_score) }
|
|
120
|
+
|
|
121
|
+
let(:value) { described_class.low_encoding_level?(record) }
|
|
122
|
+
|
|
123
|
+
context 'with no encoding level' do
|
|
124
|
+
let(:encoding_sort_score) { nil }
|
|
125
|
+
|
|
126
|
+
it 'returns false' do
|
|
127
|
+
expect(value).to be false
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
context 'with a low encoding level' do
|
|
132
|
+
let(:encoding_sort_score) { 11 }
|
|
133
|
+
|
|
134
|
+
it 'returns true' do
|
|
135
|
+
expect(value).to be true
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
context 'with a high encoding level' do
|
|
140
|
+
let(:encoding_sort_score) { PennMARC::TitleSuggestionWeightService::HIGH_ENCODING_SORT_LEVEL }
|
|
141
|
+
|
|
142
|
+
it 'returns false' do
|
|
143
|
+
expect(value).to be false
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe '.electronic_holdings?' do
|
|
149
|
+
before do
|
|
150
|
+
allow(PennMARC::Inventory).to receive(:electronic).with(record).and_return(holdings)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
let(:value) { described_class.electronic_holdings?(record) }
|
|
154
|
+
|
|
155
|
+
context 'with electronic holdings' do
|
|
156
|
+
let(:holdings) { [PennMARC::InventoryEntry::Electronic] }
|
|
157
|
+
|
|
158
|
+
it 'returns true' do
|
|
159
|
+
expect(value).to be true
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
context 'without any holdings' do
|
|
164
|
+
let(:holdings) { [] }
|
|
165
|
+
|
|
166
|
+
it 'returns false' do
|
|
167
|
+
expect(value).to be false
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
describe '.physical_holdings?' do
|
|
173
|
+
before do
|
|
174
|
+
allow(PennMARC::Inventory).to receive(:physical).with(record).and_return(holdings)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
let(:value) { described_class.physical_holdings?(record) }
|
|
178
|
+
|
|
179
|
+
context 'with physical holdings' do
|
|
180
|
+
let(:holdings) { [PennMARC::InventoryEntry::Physical] }
|
|
181
|
+
|
|
182
|
+
it 'returns true' do
|
|
183
|
+
expect(value).to be true
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
context 'without any holdings' do
|
|
188
|
+
let(:holdings) { [] }
|
|
189
|
+
|
|
190
|
+
it 'returns false' do
|
|
191
|
+
expect(value).to be false
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
describe '.no_holdings?' do
|
|
197
|
+
before do
|
|
198
|
+
allow(PennMARC::Inventory).to receive(:physical).with(record).and_return(physical_holdings)
|
|
199
|
+
allow(PennMARC::Inventory).to receive(:electronic).with(record).and_return(electronic_holdings)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
let(:value) { described_class.no_holdings?(record) }
|
|
203
|
+
|
|
204
|
+
context 'with neither physical nor electronic holdings' do
|
|
205
|
+
let(:physical_holdings) { [] }
|
|
206
|
+
let(:electronic_holdings) { [] }
|
|
207
|
+
|
|
208
|
+
it 'returns true' do
|
|
209
|
+
expect(value).to be true
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
context 'with only electronic holdings' do
|
|
214
|
+
let(:physical_holdings) { [] }
|
|
215
|
+
let(:electronic_holdings) { [instance_double(PennMARC::InventoryEntry::Electronic)] }
|
|
216
|
+
|
|
217
|
+
it 'returns false' do
|
|
218
|
+
expect(value).to be false
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
context 'with only physical holdings' do
|
|
223
|
+
let(:physical_holdings) { [instance_double(PennMARC::InventoryEntry::Physical)] }
|
|
224
|
+
let(:electronic_holdings) { [] }
|
|
225
|
+
|
|
226
|
+
it 'returns false' do
|
|
227
|
+
expect(value).to be false
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pennmarc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Kanning
|
|
@@ -12,7 +12,7 @@ authors:
|
|
|
12
12
|
autorequire:
|
|
13
13
|
bindir: bin
|
|
14
14
|
cert_chain: []
|
|
15
|
-
date: 2025-10-
|
|
15
|
+
date: 2025-10-31 00:00:00.000000000 Z
|
|
16
16
|
dependencies:
|
|
17
17
|
- !ruby/object:Gem::Dependency
|
|
18
18
|
name: activesupport
|
|
@@ -143,6 +143,7 @@ files:
|
|
|
143
143
|
- lib/pennmarc/mappings/locations.yml
|
|
144
144
|
- lib/pennmarc/mappings/relator.yml
|
|
145
145
|
- lib/pennmarc/parser.rb
|
|
146
|
+
- lib/pennmarc/services/title_suggestion_weight_service.rb
|
|
146
147
|
- lib/pennmarc/test/marc_helpers.rb
|
|
147
148
|
- lib/pennmarc/util.rb
|
|
148
149
|
- lib/pennmarc/version.rb
|
|
@@ -172,6 +173,7 @@ files:
|
|
|
172
173
|
- spec/lib/pennmarc/helpers/title_spec.rb
|
|
173
174
|
- spec/lib/pennmarc/marc_util_spec.rb
|
|
174
175
|
- spec/lib/pennmarc/parser_spec.rb
|
|
176
|
+
- spec/lib/pennmarc/services/title_suggestion_weight_service_spec.rb
|
|
175
177
|
- spec/spec_helper.rb
|
|
176
178
|
- spec/support/fixture_helpers.rb
|
|
177
179
|
homepage: https://gitlab.library.upenn.edu/dld/catalog/pennmarc
|