uc3-dmp-id 0.1.6 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed142333899fd7e57c9dc8c5052b51205b13494bb513b2199583397eef7162c7
4
- data.tar.gz: 01cd7806f0900434cec2a2c5e43488fd9da112e1521e44914a81a51c2a5d7742
3
+ metadata.gz: 5d3c26185a12403a3c3dfa1b9ab0dec6b798b1b127905a22d04f6f929d8cbf73
4
+ data.tar.gz: de6b7a8f6c3395516ed10adc6eb55d0d84a9f52ae785dc36418f408f097c5900
5
5
  SHA512:
6
- metadata.gz: 102d4e2cafbf9594f6a69d195e9c2659f34e19505bded960979ce24901d144d07d0851b92a6d94a979b87e081a984d3c4999e0933cb667014b38c95fdd781495
7
- data.tar.gz: 5301292abe35dcaf79c1ae053d5035a057986a671442f4e0d06087441373b607256de5c951603be764f857ef273643bbd17ee6fa42179d9f7b6ef6ea49033755
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
@@ -19,7 +18,6 @@ module Uc3DmpId
19
18
 
20
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 = {}
@@ -28,7 +26,6 @@ module Uc3DmpId
28
26
  _extract_dmp_details(dmp:)
29
27
  raise ComparatorError, MSG_MISSING_DMP if @details_hash.empty?
30
28
  end
31
- # rubocop:enable Metrics/AbcSize
32
29
 
33
30
  # Compare the incoming hash with the DMP details that were gathered during initialization.
34
31
  #
@@ -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.6'
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.6
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-27 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