uc3-dmp-id 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b097e9c732ee345e066017667d9db005ead650773c2a531dad7fe8eccb5031d
4
- data.tar.gz: 4e0134ebceda7abd03b59dcfc354faa7da70b1159e5a795d99a248288dff3f1c
3
+ metadata.gz: 5d3c26185a12403a3c3dfa1b9ab0dec6b798b1b127905a22d04f6f929d8cbf73
4
+ data.tar.gz: de6b7a8f6c3395516ed10adc6eb55d0d84a9f52ae785dc36418f408f097c5900
5
5
  SHA512:
6
- metadata.gz: 3fc6e478d5c3adf03086254de328df4f84836adbc7682827d9a1ea2df185ad626e8996685ddfc1a86130e3827a08fa2a9101256ab4dc60f9834533156202b478
7
- data.tar.gz: 9a43c72bc52d91dcd85c20bb4ec76f45518b7e7ce691ee7320f52d1b2d45dec9b21d0112aeca90db74e9dc644d521671b5ff6ae9f0dccee966745504fefe1904
6
+ metadata.gz: 1e790c4ee74028bc85b924ec1f82b4301be23014f1e0543c69103e285993002be0c00642b0a58a3e2d7418956708142f562f5306af69eeae3377609e5a3f3cb9
7
+ data.tar.gz: 06bd91810cde2da48258c1a22bc699788a131c1e4aaae3b1d12f86edbfb55ac85fd560803dcc84df4632fbafa9257650f56529d0475b03f291a4b9e0b76e829d
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bibtex'
4
+ require 'securerandom'
5
+
6
+ module Uc3DmpId
7
+ class AugmenterError < StandardError; end
8
+
9
+ # Class that adds items to the :dmphub_modifications array or directly to the
10
+ # :dmpraodmap_related_identifiers array if the confidence level was 'Absolute'
11
+ class Augmenter
12
+ attr_accessor :augmenter, :dmp, :known_mods, :known_works, :known_awards, :logger
13
+
14
+ MSG_MISSING_ANNOTATIONS = 'DMP must have its DMPHub specific annotations!'
15
+ MSG_MISSING_AUGMENTER = 'No Augmenter specified!'
16
+ MSG_MISSING_DMP = 'No DMP or the DMP did not contain enough information to use.'
17
+
18
+ # rubocop:disable Metrics/AbcSize
19
+ def initialize(**args)
20
+ @logger = args[:logger]
21
+ @augmenter = args[:augmenter]
22
+ raise AugmenterError, MSG_MISSING_AUGMENTER unless @augmenter.is_a?(Hash) && !@augmenter['PK'].nil?
23
+
24
+ @dmp = args.fetch(:dmp, {})['dmp'].nil? ? args[:dmp] : args.fetch(:dmp, {})['dmp']
25
+ raise AugmenterError, MSG_MISSING_DMP if @dmp.nil? || @dmp['dmp_id'].nil?
26
+ raise AugmenterError, MSG_MISSING_ANNOTATIONS if @dmp['PK'].nil?
27
+
28
+ _extract_known
29
+ end
30
+ # rubocop:enable Metrics/AbcSize
31
+
32
+ # rubocop:disable Metrics/AbcSize
33
+ def add_modifications(works:)
34
+ mod_hash = _generate_mod_header
35
+
36
+ works.fetch('publications', []).each do |publication|
37
+ # Skip the entry if we already know about this identifier
38
+ next if @known_works.include?(publication['id'])
39
+
40
+ work_hash = _work_to_mod_entry(type: 'publication', work: publication)
41
+ mod_hash.fetch('dmproadmap_related_identifiers', []) << work_hash unless work_hash.nil?
42
+ fundings = publication.fetch('fundingReferences', [])
43
+ next unless fundings.any?
44
+
45
+ award_hash = fundings.map { |funding| _funding_to_mod_entry(funding:) }
46
+ mod_hash.fetch('funding', []) << award_hash unless award_hash.nil?
47
+ end
48
+ return 0 unless mod_hash['dmproadmap_related_identifiers'].any? || mod_hash.fetch('funding', []).any?
49
+
50
+ # Save the DMP
51
+ mods = (@known_mods.nil? ? [] : @known_mods)
52
+ @dmp['dmphub_modifications'] = (@known_mods.nil? ? [] : @known_mods) << mod_hash
53
+
54
+ client = Uc3DmpDynamo::Client.new
55
+ resp = client.put_item(json: @dmp, logger:)
56
+ raise AugmenterError, Helper::MSG_DMP_NO_DMP_ID if resp.nil?
57
+
58
+ # Return the number of modifications added to the DMP
59
+ mod_hash.fetch('dmproadmap_related_identifiers', []).length + mod_hash.fetch('funding', []).length
60
+ end
61
+ # rubocop:enable Metrics/AbcSize
62
+
63
+ private
64
+
65
+ def _generate_mod_header
66
+ JSON.parse({
67
+ id: "#{Time.now.utc.strftime('%Y-%m-%d')}-#{SecureRandom.hex(4)}",
68
+ provenance: @augmenter['name'],
69
+ timestamp: Time.now.utc.iso8601,
70
+ dmproadmap_related_identifiers: [],
71
+ fundings: []
72
+ }.to_json)
73
+ end
74
+
75
+ # rubocop:disable Metrics/AbcSize
76
+ def _work_to_mod_entry(type:, work:)
77
+ return nil if work['id'].nil?
78
+
79
+ ret = {
80
+ type: 'doi',
81
+ identifier: work['id'],
82
+ descriptor: 'references',
83
+ status: 'pending'
84
+ }
85
+ work_type = work.fetch('type', 'Text')&.downcase&.strip
86
+ ret[:work_type] = work_type == 'text' ? type : work_type
87
+ return JSON.parse(ret.to_json) if work['bibtex'].nil?
88
+
89
+ ret[:citation] = Uc3DmpCitation::Citer.bibtex_to_citation(bibtex_as_string: work['bibtex'])
90
+ JSON.parse(ret.to_json)
91
+ end
92
+ # rubocop:enable Metrics/AbcSize
93
+
94
+ # Convert a funding entry for the dmphub_modification
95
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
96
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
97
+ def _funding_to_mod_entry(funding:)
98
+ return nil unless funding.is_a?(Hash) && (!funding['awardUri'] || !funding['awardNumber'])
99
+ return nil if @known_awards.include?(funding['awardUri']) || @known_awards.include?(funding['awardNumber'])
100
+
101
+ id = funding['awardUri'] if funding.fetch('awardUri', '').start_with?('http')
102
+ id = funding['awardNumber'] if id.nil?
103
+
104
+ ret = {
105
+ status: 'pending',
106
+ name: funding['funderName'],
107
+ funding_status: 'granted',
108
+ grant_id: {
109
+ type: if id.start_with?('http')
110
+ id.include?('doi') ? 'doi' : 'url'
111
+ else
112
+ 'other'
113
+ end,
114
+ identifier: id
115
+ }
116
+ }
117
+ funder_id = funding['funderIdentifier']
118
+ return JSON.parse(ret.to_json) if funder_id.nil?
119
+
120
+ ret[:funder_id] = {
121
+ type: if funder_id.include?('ror')
122
+ 'ror'
123
+ else
124
+ (funder_id.start_with?('http') ? 'url' : 'other')
125
+ end,
126
+ identifier: funder_id
127
+ }
128
+ JSON.parse(ret.to_json)
129
+ end
130
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
131
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
132
+
133
+ # Retrieve all of the known modifications, related identifiers and awards from the DMP
134
+ # rubocop:disable Metrics/AbcSize
135
+ def _extract_known
136
+ @known_mods = @dmp.fetch('dmphub_modifications', [])
137
+
138
+ ids = @dmp.fetch('dmproadmap_related_identifiers', []).map { |id| id['identifier'] }
139
+ ids += @known_mods.map { |m| m.fetch('dmproadmap_related_identifiers', []).map { |i| i['identifier'] } }
140
+ @known_works = ids.flatten.compact.uniq
141
+
142
+ fundings = @dmp.fetch('project', []).map { |proj| proj.fetch('funding', []) }.flatten.compact.uniq
143
+ awards = fundings.map { |fund| [fund['dmproadmap_funding_opportunity_id'], fund['grant_id']] }
144
+ .flatten.compact.uniq
145
+
146
+ awards += @known_mods.map do |mod|
147
+ mod.fetch('funding', []).map { |fund| [fund['dmproadmap_funding_opportunity_id'], fund['grant_id']] }
148
+ end
149
+ @known_awards = awards.flatten.compact.uniq.map { |award| award['identifier'] }.flatten.compact.uniq
150
+ end
151
+ # rubocop:enable Metrics/AbcSize
152
+ end
153
+ end
@@ -9,7 +9,6 @@ module Uc3DmpId
9
9
  # Class that compares incoming data from an external source to the DMP
10
10
  # It determines if they are likely related and applies a confidence rating
11
11
  class Comparator
12
- MSG_MISSING_AUGMENTER = 'No Augmenter specified!'
13
12
  MSG_MISSING_DMP = 'No DMP or the DMP did not contain enough information to use.'
14
13
 
15
14
  STOP_WORDS = %w[a an and if of or the then they].freeze
@@ -17,22 +16,16 @@ module Uc3DmpId
17
16
  # See the bottom of this file for a hard-coded crosswalk between Crossref funder ids and ROR ids
18
17
  # Some APIs do not support ROR fully for funder ids, so we need to be able to reference both
19
18
 
20
- attr_accessor :augmenter, :dmp, :details_hash, :logger
19
+ attr_accessor :dmp, :details_hash, :logger
21
20
 
22
- # rubocop:disable Metrics/AbcSize
23
21
  def initialize(**args)
24
22
  @logger = args[:logger]
25
23
  @details_hash = {}
26
24
 
27
- @augmenter = args[:augmenter]
28
- raise ComparatorError, MSG_MISSING_AUGMENTER if @augmenter.nil? ||
29
- !@augmenter['PK']&.start_with?('AUGMENTERS#')
30
-
31
25
  @dmp = args.fetch(:dmp, {})['dmp'].nil? ? args[:dmp] : args.fetch(:dmp, {})['dmp']
32
26
  _extract_dmp_details(dmp:)
33
27
  raise ComparatorError, MSG_MISSING_DMP if @details_hash.empty?
34
28
  end
35
- # rubocop:enable Metrics/AbcSize
36
29
 
37
30
  # Compare the incoming hash with the DMP details that were gathered during initialization.
38
31
  #
@@ -57,7 +50,7 @@ module Uc3DmpId
57
50
  # }
58
51
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
59
52
  def compare(hash:)
60
- response = { confidence: 'None', score: 0, notes: [], source: @augmenter['name'] }
53
+ response = { confidence: 'None', score: 0, notes: [] }
61
54
  return response unless hash.is_a?(Hash) && !hash['title'].nil?
62
55
 
63
56
  # Compare the grant ids. If we have a match return the response immediately since that is
@@ -78,10 +71,10 @@ module Uc3DmpId
78
71
  return response if response[:score] <= 2
79
72
 
80
73
  # Set the confidence level based on the score
81
- response[:confidence] = if response[:score] > 15
74
+ response[:confidence] = if response[:score] > 10
82
75
  'High'
83
76
  else
84
- (response[:score] > 10 ? 'Medium' : 'Low')
77
+ (response[:score] > 5 ? 'Medium' : 'Low')
85
78
  end
86
79
  response
87
80
  end
@@ -989,173 +989,226 @@ module Uc3DmpId
989
989
  title: 'Descriptive note',
990
990
  examples: ['data received from event data']
991
991
  },
992
- status: {
993
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/status',
994
- type: 'string',
995
- title: 'Modification status',
996
- enum: %w[
997
- accepted
998
- pending
999
- rejected
1000
- ]
1001
- },
1002
- dmproadmap_related_identifier: {
1003
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifier',
1004
- type: 'object',
1005
- title: 'A related identifier',
1006
- properties: {
1007
- descriptor: {
1008
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifier/properties/descriptor',
1009
- type: 'string',
1010
- enum: %w[
1011
- is_cited_by
1012
- cites
1013
- is_supplement_to
1014
- is_supplemented_by
1015
- is_described_by
1016
- describes
1017
- has_metadata
1018
- is_metadata_for
1019
- is_part_of
1020
- has_part
1021
- is_referenced_by
1022
- references
1023
- is_documented_by
1024
- documents
1025
- is_new_version_of
1026
- is_previous_version_of
1027
- ]
1028
- },
1029
- identifier: {
1030
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifier/properties/identifier',
1031
- type: 'string',
1032
- title: 'A unique identifier for the item',
1033
- description: 'Identifier for a DMP',
1034
- examples: ['https://doi.org/10.1371/journal.pcbi.1006750']
1035
- },
1036
- type: {
1037
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifier/properties/type',
1038
- type: 'string',
1039
- enum: %w[
1040
- handle
1041
- doi
1042
- ark
1043
- url
1044
- other
1045
- ]
992
+ dmproadmap_related_identifiers: {
993
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifiers',
994
+ type: 'array',
995
+ title: 'Related identifier modifications',
996
+ description: 'Identifiers discovered by an external API',
997
+ items: {
998
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifiers/items',
999
+ type: 'object',
1000
+ title: 'A related identifier',
1001
+ properties: {
1002
+ descriptor: {
1003
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifiers/items/properties/descriptor',
1004
+ type: 'string',
1005
+ enum: %w[
1006
+ is_cited_by
1007
+ cites
1008
+ is_supplement_to
1009
+ is_supplemented_by
1010
+ is_described_by
1011
+ describes
1012
+ has_metadata
1013
+ is_metadata_for
1014
+ is_part_of
1015
+ has_part
1016
+ is_referenced_by
1017
+ references
1018
+ is_documented_by
1019
+ documents
1020
+ is_new_version_of
1021
+ is_previous_version_of
1022
+ ]
1023
+ },
1024
+ identifier: {
1025
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifiers/items/properties/identifier',
1026
+ type: 'string',
1027
+ title: 'A unique identifier for the item',
1028
+ description: 'Identifier for a DMP',
1029
+ examples: ['https://doi.org/10.1371/journal.pcbi.1006750']
1030
+ },
1031
+ status: {
1032
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifiers/items/properties/status',
1033
+ type: 'string',
1034
+ title: 'Modification status',
1035
+ enum: %w[
1036
+ accepted
1037
+ pending
1038
+ rejected
1039
+ ]
1040
+ },
1041
+ type: {
1042
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifiers/items/properties/type',
1043
+ type: 'string',
1044
+ enum: %w[
1045
+ handle
1046
+ doi
1047
+ ark
1048
+ url
1049
+ other
1050
+ ]
1051
+ },
1052
+ work_type: {
1053
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifiers/items/properties/work_type',
1054
+ type: 'string'
1055
+ }
1046
1056
  },
1047
- work_type: {
1048
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/dmproadmap_related_identifier/properties/work_type',
1049
- type: 'string'
1050
- }
1051
- },
1052
- required: %w[
1053
- descriptor
1054
- identifier
1055
- type
1056
- work_type
1057
- ]
1057
+ required: %w[
1058
+ descriptor
1059
+ identifier
1060
+ type
1061
+ work_type
1062
+ ]
1063
+ }
1058
1064
  },
1059
1065
  funding: {
1060
1066
  '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding',
1061
- type: 'object',
1062
- title: 'A modification to Funding',
1063
- properties: {
1064
- dmproadmap_project_number: {
1065
- '$id': '#/properties/dmp/properties/project/items/properties/funding/properties/dmproadmap_project_number',
1066
- type: 'string',
1067
- title: "The funder's identifier for the research project",
1068
- description: "The funder's identifier used to identify the research project",
1069
- examples: ['prj-XYZ987-UCB']
1070
- },
1071
- funder_id: {
1072
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/funder_id',
1073
- type: 'object',
1074
- title: 'The Funder ID Schema',
1075
- description: 'Funder ID of the associated project',
1076
- properties: {
1077
- identifier: {
1078
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/funder_id/properties/identifier',
1079
- type: 'string',
1080
- title: 'The Funder ID Value Schema',
1081
- description: 'Funder ID, recommended to use CrossRef Funder Registry. See: https://www.crossref.org/services/funder-registry/',
1082
- examples: ['501100002428']
1067
+ type: 'array',
1068
+ title: 'Funding modifications',
1069
+ description: 'Funding information discovered by an external API',
1070
+ items: {
1071
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items',
1072
+ type: 'object',
1073
+ title: 'A funding',
1074
+ properties: {
1075
+ dmproadmap_project_number: {
1076
+ '$id': '#/properties/dmp/properties/project/items/properties/funding/items/properties/dmproadmap_project_number',
1077
+ type: 'string',
1078
+ title: "The funder's identifier for the research project",
1079
+ description: "The funder's identifier used to identify the research project",
1080
+ examples: ['prj-XYZ987-UCB']
1081
+ },
1082
+ funder_id: {
1083
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/funder_id',
1084
+ type: 'object',
1085
+ title: 'The Funder ID Schema',
1086
+ description: 'Funder ID of the associated project',
1087
+ properties: {
1088
+ identifier: {
1089
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/funder_id/properties/identifier',
1090
+ type: 'string',
1091
+ title: 'The Funder ID Value Schema',
1092
+ description: 'Funder ID, recommended to use CrossRef Funder Registry. See: https://www.crossref.org/services/funder-registry/',
1093
+ examples: ['501100002428']
1094
+ },
1095
+ type: {
1096
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/funder_id/properties/type',
1097
+ type: 'string',
1098
+ enum: %w[
1099
+ fundref
1100
+ ror
1101
+ url
1102
+ other
1103
+ ],
1104
+ title: 'The Funder ID Type Schema',
1105
+ description: 'Identifier type. Allowed values: fundref, url, other',
1106
+ examples: ['fundref']
1107
+ }
1083
1108
  },
1084
- type: {
1085
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/funder_id/properties/type',
1086
- type: 'string',
1087
- enum: %w[
1088
- fundref
1089
- ror
1090
- url
1091
- other
1092
- ],
1093
- title: 'The Funder ID Type Schema',
1094
- description: 'Identifier type. Allowed values: fundref, url, other',
1095
- examples: ['fundref']
1096
- }
1109
+ required: %w[
1110
+ identifier
1111
+ type
1112
+ ]
1097
1113
  },
1098
- required: %w[
1099
- identifier
1100
- type
1101
- ]
1102
- },
1103
- funding_status: {
1104
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/funding_status',
1105
- type: 'string',
1106
- enum: %w[
1107
- planned
1108
- applied
1109
- granted
1110
- rejected
1111
- ],
1112
- title: 'The Funding Status Schema',
1113
- description: 'To express different phases of project lifecycle. Allowed values: planned, applied, granted, rejected',
1114
- examples: ['granted']
1115
- },
1116
- grant_id: {
1117
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/grant_id',
1118
- type: 'object',
1119
- title: 'The Funding Grant ID Schema',
1120
- description: 'Grant ID of the associated project',
1121
- properties: {
1122
- identifier: {
1123
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/grant_id/properties/identifier',
1124
- type: 'string',
1125
- title: 'The Funding Grant ID Value Schema',
1126
- description: 'Grant ID',
1127
- examples: ['776242']
1114
+ funding_status: {
1115
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/funding_status',
1116
+ type: 'string',
1117
+ enum: %w[
1118
+ planned
1119
+ applied
1120
+ granted
1121
+ rejected
1122
+ ],
1123
+ title: 'The Funding Status Schema',
1124
+ description: 'To express different phases of project lifecycle. Allowed values: planned, applied, granted, rejected',
1125
+ examples: ['granted']
1126
+ },
1127
+ dmproadmap_funding_opportunity_id: {
1128
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/dmproadmap_funding_opportunity_id',
1129
+ type: 'object',
1130
+ title: 'The Funding Opportunity ID Schema',
1131
+ description: 'Opportunity ID of the associated project',
1132
+ properties: {
1133
+ identifier: {
1134
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/dmproadmap_funding_opportunity_id/properties/identifier',
1135
+ type: 'string',
1136
+ title: 'The Funding Opportunity ID Value Schema',
1137
+ description: 'Opportunity ID',
1138
+ examples: ['ABC-12345-03']
1139
+ },
1140
+ type: {
1141
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/dmproadmap_funding_opportunity_id/properties/type',
1142
+ type: 'string',
1143
+ title: 'The Funding Opportunity ID Type Schema',
1144
+ enum: %w[
1145
+ doi
1146
+ url
1147
+ other
1148
+ ],
1149
+ description: 'Identifier type. Allowed values: url, other',
1150
+ examples: ['other']
1151
+ }
1128
1152
  },
1129
- type: {
1130
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/grant_id/properties/type',
1131
- type: 'string',
1132
- title: 'The Funding Grant ID Type Schema',
1133
- enum: %w[
1134
- doi
1135
- url
1136
- other
1137
- ],
1138
- description: 'Identifier type. Allowed values: url, other',
1139
- examples: ['other']
1140
- }
1153
+ required: %w[
1154
+ identifier
1155
+ type
1156
+ ]
1141
1157
  },
1142
- required: %w[
1143
- identifier
1144
- type
1145
- ]
1158
+ grant_id: {
1159
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/grant_id',
1160
+ type: 'object',
1161
+ title: 'The Funding Grant ID Schema',
1162
+ description: 'Grant ID of the associated project',
1163
+ properties: {
1164
+ identifier: {
1165
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/grant_id/properties/identifier',
1166
+ type: 'string',
1167
+ title: 'The Funding Grant ID Value Schema',
1168
+ description: 'Grant ID',
1169
+ examples: ['776242']
1170
+ },
1171
+ type: {
1172
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/grant_id/properties/type',
1173
+ type: 'string',
1174
+ title: 'The Funding Grant ID Type Schema',
1175
+ enum: %w[
1176
+ doi
1177
+ url
1178
+ other
1179
+ ],
1180
+ description: 'Identifier type. Allowed values: url, other',
1181
+ examples: ['other']
1182
+ }
1183
+ },
1184
+ required: %w[
1185
+ identifier
1186
+ type
1187
+ ]
1188
+ },
1189
+ status: {
1190
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/status',
1191
+ type: 'string',
1192
+ title: 'Modification status',
1193
+ enum: %w[
1194
+ accepted
1195
+ pending
1196
+ rejected
1197
+ ]
1198
+ },
1199
+ name: {
1200
+ '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/items/properties/name',
1201
+ type: 'string',
1202
+ title: 'The name of the funding instituion / organization',
1203
+ description: 'Name',
1204
+ examples: ['National Science Foundation']
1205
+ }
1146
1206
  },
1147
- name: {
1148
- '$id': '#/properties/dmp/properties/dmphub_modifications/items/properties/funding/properties/name',
1149
- type: 'string',
1150
- title: 'The name of the funding instituion / organization',
1151
- description: 'Name',
1152
- examples: ['National Science Foundation']
1153
- }
1154
- },
1155
- required: %w[
1156
- funding_status
1157
- name
1158
- ]
1207
+ required: %w[
1208
+ funding_status
1209
+ name
1210
+ ]
1211
+ }
1159
1212
  },
1160
1213
  project: {
1161
1214
  '$id': '#/properties/dmp/properties/dmphub_modifications/project',
@@ -13,7 +13,7 @@ module Uc3DmpId
13
13
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
14
14
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
15
15
  # -------------------------------------------------------------------------
16
- def update(provenance:, p_key:, json: {}, note: nil, logger: nil)
16
+ def update(provenance:, p_key:, json: {}, logger: nil)
17
17
  raise UpdaterError, Helper::MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
18
18
 
19
19
  mods = Helper.parse_json(json:).fetch('dmp', {})
@@ -40,13 +40,14 @@ module Uc3DmpId
40
40
  version = Versioner.generate_version(client:, latest_version:, owner:,
41
41
  updater:, logger:)
42
42
  raise UpdaterError, Helper::MSG_DMP_UNABLE_TO_VERSION if version.nil?
43
+ # Bail if the system trying to make the update is not the creator of the DMP ID
44
+ raise UpdaterError, Helper::MSG_DMP_FORBIDDEN if owner != updater
43
45
 
44
46
  # Remove the version info because we don't want to save it on the record
45
47
  version.delete('dmphub_versions')
46
48
 
47
49
  # Splice the assertions
48
- version = _process_modifications(owner:, updater:, version:, mods:, note:,
49
- logger:)
50
+ version = _process_modifications(owner:, updater:, version:, mods:, logger:)
50
51
  # Set the :modified timestamps
51
52
  now = Time.now.utc
52
53
  version['modified'] = now.iso8601
@@ -117,42 +118,23 @@ module Uc3DmpId
117
118
  end
118
119
  # rubocop:enable Metrics/AbcSize
119
120
 
120
- # rubocop:disable Metrics/ParameterLists
121
- def _process_modifications(owner:, updater:, version:, mods:, note: nil, logger: nil)
121
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
122
+ def _process_modifications(owner:, updater:, version:, mods:, logger: nil)
122
123
  return version unless mods.is_a?(Hash) && !updater.nil?
123
124
  return mods unless version.is_a?(Hash) && !owner.nil?
124
125
 
125
- updated = if owner == updater
126
- # Splice together any assertions that may have been made while the user was editing the DMP ID
127
- Asserter.splice(latest_version: version, modified_version: mods, logger:)
128
- else
129
- # Attach the incoming changes as an assertion to the DMP ID since the updater is NOT the owner
130
- Asserter.add(updater:, latest_version: version, modified_version: mods, note:,
131
- logger:)
132
- end
133
-
134
- _merge_versions(latest_version: version, mods: updated, logger:)
135
- end
136
- # rubocop:enable Metrics/ParameterLists
137
-
138
- # We are replacing the latest version with the modifcations but want to retain the PK, SK and any dmphub_ prefixed
139
- # entries in the metadata so that we do not lose creation timestamps, provenance ids, etc.
140
- # rubocop:disable Metrics/AbcSize
141
- def _merge_versions(latest_version:, mods:, logger: nil)
142
- return mods unless latest_version.is_a?(Hash)
143
-
144
126
  logger.debug(message: 'Modifications before merge.', details: mods) if logger.respond_to?(:debug)
145
- keys_to_retain = latest_version.keys.select do |key|
127
+ keys_to_retain = version.keys.select do |key|
146
128
  (key.start_with?('dmphub_') && !%w[dmphub_modifications dmphub_versions].include?(key)) ||
147
129
  key.start_with?('PK') || key.start_with?('SK')
148
130
  end
149
131
  keys_to_retain.each do |key|
150
- mods[key] = latest_version[key]
132
+ mods[key] = version[key]
151
133
  end
152
134
  logger.debug(message: 'Modifications after merge.', details: mods) if logger.respond_to?(:debug)
153
135
  mods
154
136
  end
155
- # rubocop:enable Metrics/AbcSize
137
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
156
138
 
157
139
  # Once the DMP has been updated, we need to update it's DOI metadata
158
140
  # -------------------------------------------------------------------------
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uc3DmpId
4
- VERSION = '0.1.5'
4
+ VERSION = '0.1.7'
5
5
  end
data/lib/uc3-dmp-id.rb CHANGED
@@ -7,6 +7,7 @@ require 'json-schema'
7
7
  require 'uc3-dmp-event-bridge'
8
8
 
9
9
  require 'uc3-dmp-id/asserter'
10
+ require 'uc3-dmp-id/augmenter'
10
11
  require 'uc3-dmp-id/comparator'
11
12
  require 'uc3-dmp-id/creator'
12
13
  require 'uc3-dmp-id/deleter'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uc3-dmp-id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Riley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-26 00:00:00.000000000 Z
11
+ date: 2023-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: uc3-dmp-citation
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: uc3-dmp-dynamo
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: uc3-dmp-external-api
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.0'
83
111
  description: Helpers for working with JSON that represents a DMP ID
84
112
  email:
85
113
  - brian.riley@ucop.edu
@@ -89,7 +117,7 @@ extra_rdoc_files: []
89
117
  files:
90
118
  - README.md
91
119
  - lib/uc3-dmp-id.rb
92
- - lib/uc3-dmp-id/asserter.rb
120
+ - lib/uc3-dmp-id/augmenter.rb
93
121
  - lib/uc3-dmp-id/comparator.rb
94
122
  - lib/uc3-dmp-id/creator.rb
95
123
  - lib/uc3-dmp-id/deleter.rb
@@ -1,216 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'time'
4
-
5
- module Uc3DmpId
6
- class AsserterError < StandardError; end
7
-
8
- # Class that handles changes to a DMP ID's :dmphub_modifications section
9
- class Asserter
10
- DEFAULT_DESCRIPTOR = 'references'
11
- DEFAULT_WORK_TYPE = 'other'
12
-
13
- class << self
14
- # Add assertions to a DMP ID - this is performed by non-provenance systems
15
- # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
16
- def add(updater:, latest_version:, modified_version:, note: nil, logger: nil)
17
- return latest_version unless latest_version.is_a?(Hash)
18
-
19
- owner = latest_version['dmphub_provenance_id']&.gsub('PROVENANCE#', '')
20
- # If the updater and provenance are the same just return the :dmp as-is
21
- return latest_version if updater.nil? || !latest_version.is_a?(Hash) || !modified_version.is_a?(Hash) ||
22
- updater&.gsub('PROVENANCE#', '') == owner
23
-
24
- # contact = modified_version['contact']
25
- # contributor = modified_version.fetch('contributor', [])
26
- # project = modified_version.fetch('project', [])
27
- funding = modified_version.fetch('project', []).first&.fetch('funding', [])
28
- related_works = modified_version.fetch('dmproadmap_related_identifiers', [])
29
-
30
- if related_works.any?
31
- latest_version = _add_related_identifier(updater:, latest_version:,
32
- identifiers: related_works, note:, logger:)
33
- end
34
- return latest_version unless !funding.nil? && funding.any?
35
-
36
- _add_funding_mod(updater:, latest_version:, funding:,
37
- note:, logger:)
38
- end
39
- # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
40
-
41
- # Splice together assertions made by the owner of the DMP ID so that any :dmphub_modifications made to
42
- # the record while it was being updated are not lost
43
- # rubocop:disable Metrics/AbcSize
44
- def splice(latest_version:, modified_version:, logger: nil)
45
- # Return the modified_version if the timestamps are the same OR neither version has :dmphub_modifications
46
- return modified_version if latest_version['modified'] == modified_version['modified'] ||
47
- (latest_version.fetch('dmphub_modifications', []).empty? &&
48
- modified_version.fetch('dmphub_modifications', []).empty?)
49
-
50
- # Clone any existing :dmphub_modifications on the current DMP ID so we can retain them
51
- existing_assertions = Helper.deep_copy_dmp(obj: latest_version.fetch('dmphub_modifications', []))
52
- incoming_assertions = Helper.deep_copy_dmp(obj: modified_version.fetch('dmphub_modifications', []))
53
- if logger.respond_to?(:debug)
54
- logger.debug(message: 'Existing dmphub_modifications',
55
- details: existing_assertions)
56
- end
57
- if logger.respond_to?(:debug)
58
- logger.debug(message: 'Incoming dmphub_modifications',
59
- details: incoming_assertions)
60
- end
61
-
62
- # Keep any :dmphub_modifications and then add the incoming to the Array
63
- modified_version['dmphub_modifications'] = existing_assertions
64
- return modified_version unless incoming_assertions.any?
65
-
66
- # Add any of the assertions still on the incoming record back to the latest record
67
- incoming_assertions.each { |entry| modified_version['dmphub_modifications'] << entry }
68
- modified_version
69
- end
70
- # rubocop:enable Metrics/AbcSize
71
-
72
- private
73
-
74
- # Verify that the DMP ID record does not already have the specified identifiers and then add them
75
- # to the :latest_version in the :dmphub_modifications Array
76
- #
77
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
78
- # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
79
- def _add_related_identifier(updater:, latest_version:, identifiers:, note: '', logger: nil)
80
- return latest_version unless updater.is_a?(String) && latest_version.is_a?(Hash) && identifiers.is_a?(Array)
81
-
82
- latest_version['dmphub_modifications'] = [] if latest_version['dmphub_modifications'].nil?
83
- known_mods = latest_version['dmphub_modifications'].map do |mod|
84
- mod.fetch('dmproadmap_related_identifiers', [])
85
- end
86
- known_mods = known_mods.flatten.compact.map { |mod| mod['identifier'].downcase.strip }.compact.uniq
87
-
88
- asserted = latest_version.fetch('dmproadmap_related_identifiers', [])
89
- asserted = asserted.flatten.compact.map { |mod| mod['identifier'].downcase.strip }.compact.uniq
90
-
91
- additions = []
92
- identifiers.each do |related_identifier|
93
- # Skip if there is no :type or :identifier value
94
- if !related_identifier.is_a?(Hash) || related_identifier['type'].nil? || related_identifier['identifier'].nil?
95
- next
96
- end
97
-
98
- id = related_identifier['identifier'].downcase.strip
99
- # Skip if the :identifier is already listed in :dmphub_modifications or the
100
- # :dmproadmap_related_identifiers Arrays
101
- next if known_mods.include?(id) || asserted.include?(id)
102
-
103
- related_identifier['work_type'] = DEFAULT_WORK_TYPE if related_identifier['work_type'].nil?
104
- related_identifier['descriptor'] = DEFAULT_DESCRIPTOR if related_identifier['descriptor'].nil?
105
- additions << related_identifier
106
- end
107
-
108
- latest_version['dmproadmap_related_identifiers'] = [] if latest_version['dmproadmap_related_identifiers'].nil?
109
- assertion = _generate_assertion(updater:, note:,
110
- mods: JSON.parse({ dmproadmap_related_identifiers: additions }.to_json))
111
- if logger.respond_to?(:debug)
112
- logger.debug(message: 'Adding change to :dmphub_modifications.',
113
- details: assertion)
114
- end
115
- latest_version['dmphub_modifications'] << assertion
116
- latest_version
117
- end
118
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
119
- # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
120
-
121
- # Verify that the DMP ID record does not already have the specified funding change and then add it
122
- # to the :latest_version in the :dmphub_modifications Array
123
- #
124
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
125
- # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
126
- def _add_funding_mod(updater:, latest_version:, funding:, note: '', logger: nil)
127
- return latest_version unless updater.is_a?(String) && latest_version.is_a?(Hash) && funding.is_a?(Array)
128
-
129
- known_mods = latest_version['dmphub_modifications'].map do |mod|
130
- next if mod.nil?
131
-
132
- mod.fetch('funding', {}).fetch('grant_id', {})['identifier']&.downcase&.strip
133
- end
134
- known_mods = known_mods.flatten.compact.uniq
135
-
136
- asserted = latest_version.fetch('project', [])&.map do |project|
137
- next if project.nil?
138
-
139
- project&.fetch('funding', [])&.first&.fetch('grant_id', {})&.[]('identifier')&.downcase&.strip
140
- end
141
- asserted = asserted.flatten.compact.uniq
142
-
143
- fund = funding.reject { |entry| entry['grant_id'].nil? }.first
144
- # Skip if there is no :grant_id
145
- return latest_version if !fund.is_a?(Hash) || fund.fetch('grant_id', {})['identifier'].nil?
146
-
147
- grant_id = fund.fetch('grant_id', {})['identifier'].downcase.strip
148
- # Skip if the :grant_id is already listed as a :dmphub_modifications or project: :funding
149
- return latest_version if known_mods.include?(grant_id) || asserted.include?(grant_id)
150
-
151
- latest_version['dmphub_modifications'] = [] if latest_version['dmphub_modifications'].nil?
152
- mod = JSON.parse({ funding: fund }.to_json)
153
- mod['funding']['funding_status'] = 'granted'
154
- assertion = _generate_assertion(updater:, mods: mod, note:)
155
- if logger.respond_to?(:debug)
156
- logger.debug(message: 'Adding change to :dmphub_modifications.',
157
- details: assertion)
158
- end
159
- latest_version['dmphub_modifications'] << assertion
160
- latest_version
161
- end
162
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
163
- # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
164
-
165
- # Generate an assertion entry. For example:
166
- #
167
- # {
168
- # "id": "ABCD1234",
169
- # "provenance": "dmphub",
170
- # "timestamp": "2023-07-07T14:50:23+00:00",
171
- # "note": "Data received from OpenAlex, matched by PI names and title keywords.",
172
- # "confiedence": "Med",
173
- # "dmproadmap_related_identifiers": {
174
- # "work_type": "article",
175
- # "descriptor": "is_cited_by",
176
- # "type": "doi",
177
- # "identifier": "https://dx.doi.org/99.9876/ZYX987.V6"
178
- # }
179
- # }
180
- #
181
- # OR:
182
- #
183
- # {
184
- # "id": "ABCD1234",
185
- # "provenance": "dmphub",
186
- # "timestamp": "2023-07-07T14:50:23+00:00",
187
- # "note": "Data received from the NIH API, matched by the opportunity number.",
188
- # "confidence": "High",
189
- # "funding": {
190
- # "funding_status": "granted",
191
- # "grant_id": {
192
- # "identifier": "2019/22702-3",
193
- # "type": "other"
194
- # }
195
- # }
196
- # }
197
- def _generate_assertion(updater:, mods:, note: '')
198
- return nil if updater.nil? || !mods.is_a?(Hash)
199
-
200
- assertion = {
201
- id: SecureRandom.hex(4).upcase,
202
- provenance: updater.gsub('PROVENANCE#', ''),
203
- timestamp: Time.now.utc.iso8601,
204
- status: 'pending',
205
- note:
206
- }
207
- mods.each_pair { |key, val| assertion[key] = val }
208
- JSON.parse(assertion.to_json)
209
- end
210
- end
211
-
212
- def _score_related_work(latest_version:, work:); end
213
-
214
- def _score_funding(latest_version:, funding:); end
215
- end
216
- end