dde_client 0.1.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.
@@ -0,0 +1,320 @@
1
+ # frozen_string_literal: true
2
+
3
+ # this class will basically handle rolling back patients that were merged
4
+ # rubocop:disable Metrics/ClassLength
5
+ class DdeClient::RollbackService
6
+ attr_accessor :primary_patient, :secondary_patient, :merge_type, :visit_type
7
+
8
+ Dde_CONFIG_PATH = 'config/application.yml'
9
+
10
+ # rubocop:disable Metrics/MethodLength
11
+ def rollback_merged_patient(patient_id, visit_type_id)
12
+ @visit_type = VisitType.find(visit_type_id)
13
+ tree = DdeClient::MergeAuditService.new.fetch_merge_audit(patient_id)
14
+ ActiveRecord::Base.transaction do
15
+ tree.each do |record|
16
+ @primary_patient = record['primary_id']
17
+ @secondary_patient = record['secondary_id']
18
+ @merge_type = record['merge_type']
19
+ Rails.logger.debug("Processing rollback for patients: #{primary_patient} <=> #{secondary_patient}")
20
+ process_rollback
21
+ MergeAudit.find(record['id']).void("Rolling back to #{secondary_patient}")
22
+ @common_void_reason = nil
23
+ end
24
+ end
25
+ Patient.find(patient_id)
26
+ end
27
+ # rubocop:enable Metrics/MethodLength
28
+
29
+ private
30
+
31
+ def process_rollback
32
+ rollback_dde
33
+ rollback_patient
34
+ rollback_encounter
35
+ rollback_order
36
+ rollback_observation
37
+ rollback_address
38
+ rollback_attributes
39
+ rollback_identifiers
40
+ rollback_remote_identifiers
41
+ rollback_name
42
+ end
43
+
44
+ # fetch patient doc id
45
+ def fetch_patient_doc_id(patient_id)
46
+ result = ActiveRecord::Base.connection.select_one <<~SQL
47
+ SELECT identifier
48
+ FROM patient_identifier
49
+ WHERE patient_id = #{patient_id}
50
+ AND identifier_type = #{PatientIdentifierType.find_by_name!('Dde person document ID').id}
51
+ SQL
52
+ result.blank? ? nil : result['identifier']
53
+ end
54
+
55
+ def rollback_dde
56
+ return unless merge_type.match(/remote/i)
57
+
58
+ response, status = dde_client.post('rollback_merge',
59
+ primary_person_doc_id: fetch_patient_doc_id(primary_patient),
60
+ secondary_person_doc_id: fetch_patient_doc_id(secondary_patient))
61
+
62
+ raise "Failed to rollback patients on Dde side: #{status} - #{response}" unless status == 200
63
+ end
64
+
65
+ # this is the method to rollback patient name
66
+ def rollback_name
67
+ result = ActiveRecord::Base.connection.select_all <<~SQL
68
+ SELECT * FROM person_name
69
+ WHERE voided = 1
70
+ AND person_id = #{secondary_patient}
71
+ AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'
72
+ SQL
73
+ process_name(voided_names: result)
74
+ ActiveRecord::Base.connection.execute <<~SQL
75
+ UPDATE person_name SET #{common_void_columns} #{extra_fields} WHERE person_id = #{common_void_reason}
76
+ SQL
77
+ end
78
+
79
+ def rollback_identifiers
80
+ result = ActiveRecord::Base.connection.select_all <<~SQL
81
+ SELECT * FROM patient_identifier
82
+ WHERE patient_id = #{secondary_patient} AND voided = 1
83
+ AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'
84
+ SQL
85
+ process_identifiers(voided_identifiers: result)
86
+ ActiveRecord::Base.connection.execute <<~SQL
87
+ UPDATE patient_identifier SET #{common_void_columns} WHERE patient_id = #{common_void_reason}
88
+ SQL
89
+ end
90
+
91
+ def rollback_remote_identifiers
92
+ return unless merge_type.match(/remote/i)
93
+
94
+ ActiveRecord::Base.connection.execute <<~SQL
95
+ UPDATE patient_identifier SET #{common_void_columns} WHERE patient_id = #{secondary_patient} AND void_reason = 'Assigned new id: #{fetch_patient_doc_id(primary_patient)}'
96
+ SQL
97
+ end
98
+
99
+ def rollback_attributes
100
+ result = ActiveRecord::Base.connection.select_all <<~SQL
101
+ SELECT * FROM person_attribute
102
+ WHERE person_id = #{secondary_patient} AND voided = 1
103
+ AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'
104
+ SQL
105
+ process_attributes(voided_attributes: result)
106
+ ActiveRecord::Base.connection.execute <<~SQL
107
+ UPDATE person_attribute SET #{common_void_columns} #{extra_fields} WHERE person_id = #{common_void_reason}
108
+ SQL
109
+ end
110
+
111
+ def rollback_address
112
+ result = ActiveRecord::Base.connection.select_all <<~SQL
113
+ SELECT * FROM person_address
114
+ WHERE person_id = #{secondary_patient} AND voided = 1
115
+ AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'
116
+ SQL
117
+ process_addresses(voided_addresses: result)
118
+ ActiveRecord::Base.connection.execute <<~SQL
119
+ UPDATE person_address SET #{common_void_columns} WHERE person_id = #{common_void_reason}
120
+ SQL
121
+ end
122
+
123
+ def rollback_encounter
124
+ result = ActiveRecord::Base.connection.select_all <<~SQL
125
+ SELECT * FROM encounter
126
+ WHERE patient_id = #{secondary_patient} AND voided = 1
127
+ AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'
128
+ SQL
129
+ process_encounters(voided_encounters: result)
130
+ ActiveRecord::Base.connection.execute <<~SQL
131
+ UPDATE encounter SET #{common_void_columns} #{extra_fields} WHERE patient_id = #{common_void_reason}
132
+ SQL
133
+ end
134
+
135
+ def rollback_observation
136
+ result = ActiveRecord::Base.connection.select_all <<~SQL
137
+ SELECT * FROM obs
138
+ WHERE person_id = #{secondary_patient} AND voided = 1
139
+ AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'
140
+ SQL
141
+ process_observations(voided_observations: result)
142
+ ActiveRecord::Base.connection.execute <<~SQL
143
+ UPDATE obs SET #{common_void_columns} WHERE person_id = #{common_void_reason}
144
+ SQL
145
+ end
146
+
147
+ def rollback_order
148
+ result = ActiveRecord::Base.connection.select_all <<~SQL
149
+ SELECT * FROM orders
150
+ WHERE patient_id = #{secondary_patient} AND voided = 1
151
+ AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'
152
+ SQL
153
+ process_orders(voided_orders: result)
154
+ ActiveRecord::Base.connection.execute <<~SQL
155
+ UPDATE orders SET #{common_void_columns} WHERE patient_id = #{common_void_reason}
156
+ SQL
157
+ end
158
+
159
+ def rollback_patient
160
+ ActiveRecord::Base.connection.execute <<~SQL
161
+ UPDATE patient SET #{common_void_columns} #{extra_fields} WHERE patient_id = #{common_void_reason}
162
+ SQL
163
+ rollback_person
164
+ end
165
+
166
+ def rollback_person
167
+ ActiveRecord::Base.connection.execute <<~SQL
168
+ UPDATE person SET #{common_void_columns} #{extra_fields} WHERE person_id = #{common_void_reason}
169
+ SQL
170
+ rollback_patient_visit_type_and_state
171
+ rollback_relationship
172
+ end
173
+
174
+ def rollback_patient_visit_type_and_state
175
+ ActiveRecord::Base.connection.execute <<~SQL
176
+ UPDATE patient_state ps
177
+ INNER JOIN patient_visit_type pp ON ps.patient_visit_type_id = pp.patient_visit_type_id
178
+ SET ps.date_voided = NULL, ps.void_reason = NULL, ps.voided_by = NULL, ps.voided = 0, ps.date_changed = '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', ps.changed_by = #{User.current.id},
179
+ pp.date_voided = NULL, pp.void_reason = NULL, pp.voided_by = NULL, pp.voided = 0, pp.date_changed = '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', pp.changed_by = #{User.current.id}
180
+ WHERE pp.patient_id = #{secondary_patient}
181
+ AND ps.void_reason = 'Merged into patient ##{primary_patient}:0'
182
+ AND pp.void_reason = 'Merged into patient ##{primary_patient}:0'
183
+ SQL
184
+ end
185
+
186
+ def rollback_relationship
187
+ ActiveRecord::Base.connection.execute <<~SQL
188
+ UPDATE relationship SET #{common_void_columns} WHERE person_a = #{common_void_reason}
189
+ SQL
190
+ end
191
+
192
+ def process_name(voided_names: nil)
193
+ return if voided_names.blank?
194
+
195
+ voided_names.each do |name|
196
+ patient_id, row_id = process_patient_id_and_row_id(name['void_reason'])
197
+ record = PersonName.find_by(person_name_id: row_id, person_id: patient_id)
198
+ record&.void("Rollback to patient ##{name['person_id']}:#{name['person_name_id']}")
199
+ end
200
+ end
201
+
202
+ def process_identifiers(voided_identifiers: nil)
203
+ return if voided_identifiers.blank?
204
+
205
+ voided_identifiers.each do |identifier|
206
+ patient_id, row_id = process_patient_id_and_row_id(identifier['void_reason'])
207
+ record = PatientIdentifier.find_by(patient_identifier_id: row_id, patient_id: patient_id)
208
+ record&.void("Rollback to patient ##{identifier['patient_id']}:#{identifier['patient_identifier_id']}")
209
+ end
210
+ end
211
+
212
+ def process_attributes(voided_attributes: nil)
213
+ return if voided_attributes.blank?
214
+
215
+ voided_attributes.each do |attribute|
216
+ patient_id, row_id = process_patient_id_and_row_id(attribute['void_reason'])
217
+ record = PersonAttribute.find_by(person_attribute_id: row_id, person_id: patient_id)
218
+ record&.void("Rollback to patient ##{attribute['person_id']}:#{attribute['person_attribute_id']}")
219
+ end
220
+ end
221
+
222
+ def process_addresses(voided_addresses: nil)
223
+ return if voided_addresses.blank?
224
+
225
+ voided_addresses.each do |address|
226
+ patient_id, row_id = process_patient_id_and_row_id(address['void_reason'])
227
+ record = PersonAddress.find_by(person_address_id: row_id, person_id: patient_id)
228
+ record&.void("Rollback to patient ##{address['person_id']}:#{address['person_address_id']}")
229
+ end
230
+ end
231
+
232
+ def process_encounters(voided_encounters: nil)
233
+ return if voided_encounters.blank?
234
+
235
+ voided_encounters.each do |encounter|
236
+ patient_id, row_id = process_patient_id_and_row_id(encounter['void_reason'])
237
+ record = Encounter.find_by(encounter_id: row_id, patient_id: patient_id)
238
+ record&.void("Rollback to patient ##{encounter['patient_id']}:#{encounter['encounter_id']}")
239
+ end
240
+ end
241
+
242
+ def process_observations(voided_observations: nil)
243
+ return if voided_observations.blank?
244
+
245
+ voided_observations.each do |obs|
246
+ patient_id, row_id = process_patient_id_and_row_id(obs['void_reason'])
247
+ record = Observation.find_by(obs_id: row_id, person_id: patient_id)
248
+ record&.void("Rollback to patient ##{obs['person_id']}:#{obs['obs_id']}")
249
+ end
250
+ end
251
+
252
+ def process_orders(voided_orders: nil)
253
+ return if voided_orders.blank?
254
+
255
+ voided_orders.each do |order|
256
+ patient_id, row_id = process_patient_id_and_row_id(order['void_reason'])
257
+ record = Order.find_by(order_id: row_id, patient_id: patient_id)
258
+ record&.void("Rollback to patient ##{order['patient_id']}:#{order['order_id']}")
259
+ end
260
+ end
261
+
262
+ def process_patient_id_and_row_id(reason)
263
+ reason.split('#')[1].split(':')
264
+ end
265
+
266
+ def remove_common_field(record)
267
+ record.delete('uuid')
268
+ record.delete('voided')
269
+ record.delete('voided_by')
270
+ record.delete('date_voided')
271
+ @reason = record.delete('void_reason')
272
+ record
273
+ end
274
+
275
+ def common_void_columns
276
+ @common_void_columns ||= 'date_voided = NULL, void_reason = NULL, voided_by = NULL, voided = 0'
277
+ end
278
+
279
+ def extra_fields
280
+ @extra_fields ||= ", date_changed = '#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}', changed_by = #{User.current.id}"
281
+ end
282
+
283
+ def common_void_reason
284
+ @common_void_reason ||= " #{secondary_patient} AND void_reason LIKE 'Merged into patient ##{primary_patient}:%'"
285
+ end
286
+
287
+ def dde_client
288
+ client = DdeClient.new
289
+
290
+ connection = dde_connections[visit_type.id]
291
+
292
+ dde_connections[visit_type.id] = if connection
293
+ client.restore_connection(connection)
294
+ else
295
+ client.connect(dde_config)
296
+ end
297
+
298
+ client
299
+ end
300
+
301
+ def dde_connections
302
+ @dde_connections ||= {}
303
+ end
304
+
305
+ # Loads a dde client into the dde_clients_cache for the
306
+ def dde_config
307
+ main_config = YAML.load_file(Dde_CONFIG_PATH)['dde']
308
+ raise 'No configuration for Dde found' unless main_config
309
+
310
+ visit_type_config = main_config[visit_type.name.downcase]
311
+ raise "No Dde config for visit_type #{visit_type.name} found" unless visit_type_config
312
+
313
+ {
314
+ url: main_config['url'],
315
+ username: visit_type_config['username'],
316
+ password: visit_type_config['password']
317
+ }
318
+ end
319
+ end
320
+ # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This service will handle merge audits with their tree structure
4
+ class MergeAuditService
5
+ # This method a merge audit for us
6
+ def create_merge_audit(primary_patient, secondary_patient, merge_type)
7
+ recent_merge_id = MergeAudit.where(primary_id: secondary_patient).last&.id
8
+ merge_audit = MergeAudit.create({ primary_id: primary_patient, secondary_id: secondary_patient,
9
+ creator: User.current.id, merge_type: merge_type,
10
+ secondary_previous_merge_id: recent_merge_id })
11
+ raise "Could not create audit trail due to #{merge_audit.errors.as_json}" unless merge_audit.errors.empty?
12
+ end
13
+
14
+ # this uses the patient identifier to get the audit tree
15
+ def get_patient_audit(identifier)
16
+ fetch_merge_audit(find_voided_identifier(identifier))
17
+ end
18
+
19
+ # this uses the patient id to get the audit tree and it is used by get patient_audit
20
+ def fetch_merge_audit(secondary)
21
+ first_merge = common_merge_fetch('ma.secondary_id', secondary)
22
+ raise NotFoundError, "There is no merge for #{secondary}" if first_merge.blank?
23
+
24
+ count = 0
25
+ tree = [first_merge.merge({ 'merge_number' => count += 1 })]
26
+ merge_id = MergeAudit.where(primary_id: first_merge['primary_id']).last&.id
27
+ until merge_id.blank?
28
+ parent = common_merge_fetch('ma.secondary_previous_merge_id', merge_id)
29
+ tree << parent.merge({ 'merge_number' => count += 1 }) unless parent.blank?
30
+ merge_id = parent.blank? ? nil : MergeAudit.where(primary_id: parent['primary_id']).last&.id
31
+ end
32
+ tree.reverse
33
+ end
34
+
35
+ def common_merge_fetch(field, fetch_value)
36
+ ActiveRecord::Base.connection.select_one <<~SQL
37
+ SELECT ma.id, ma.primary_id, ma.secondary_id, ma.created_at merge_date, ma.merge_type, pn.given_name primary_first_name, pn.family_name primary_surname, p.gender primary_gender, p.birthdate primary_birthdate,
38
+ spn.given_name secondary_first_name, spn.family_name secondary_surname, sp.gender secondary_gender, sp.birthdate secondary_birthdate
39
+ FROM merge_audits ma
40
+ INNER JOIN person_name pn ON pn.person_id = ma.primary_id
41
+ INNER JOIN person p ON p.person_id = ma.primary_id
42
+ INNER JOIN person_name spn ON spn.person_id = ma.secondary_id AND spn.voided = 1
43
+ INNER JOIN person sp ON sp.person_id = ma.secondary_id AND sp.voided = 1
44
+ WHERE #{field} = #{fetch_value} AND ma.voided = 0
45
+ SQL
46
+ end
47
+
48
+ def find_voided_identifier(identifier)
49
+ result = ActiveRecord::Base.connection.select_one <<~SQL
50
+ SELECT patient_id FROM patient_identifier WHERE identifier = '#{identifier}' AND identifier_type = 3 AND voided = 1 ORDER BY date_voided ASC
51
+ SQL
52
+ raise NotFoundError, "Failed to find voided identifier: #{identifier}" if result.blank?
53
+
54
+ result['patient_id']
55
+ end
56
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module contains utility methods for retrieving models
4
+ module ModelUtils
5
+ # Retrieve concept by its name
6
+ #
7
+ # Parameters:
8
+ # name - A string repr of the concept name
9
+ def concept(name)
10
+ Concept.joins(:concept_names).where('concept_name.name = ?', name).first
11
+ end
12
+
13
+ def concept_name(name)
14
+ ConceptName.find_by(name: name)
15
+ end
16
+
17
+ def concept_name_to_id(name)
18
+ return nil if name.blank?
19
+
20
+ concept_name(name)&.concept_id
21
+ end
22
+
23
+ def concept_id_to_name(id)
24
+ return nil if id.blank?
25
+
26
+ concept = Concept.find_by_concept_id(id)
27
+ concept&.fullname
28
+ end
29
+
30
+ def visit_type(name)
31
+ VisitType.find_by_name(name)
32
+ end
33
+
34
+ def encounter_type(name)
35
+ EncounterType.find_by name: name
36
+ end
37
+
38
+ def global_property(name)
39
+ GlobalProperty.find_by property: name
40
+ end
41
+
42
+ def user_property(name, user_id: nil)
43
+ user_id ||= User.current.user_id
44
+ UserProperty.find_by(user_id: user_id, property: name)
45
+ end
46
+
47
+ def order_type(name)
48
+ OrderType.find_by_name(name)
49
+ end
50
+
51
+ def report_type(name)
52
+ ReportType.find_by_name(name)
53
+ end
54
+
55
+ def patient_identifier_type(name)
56
+ PatientIdentifierType.find_by_name(name)
57
+ end
58
+
59
+ def drug(name)
60
+ Drug.find_by_name(name)
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Dde</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "dde/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,14 @@
1
+ DdeClient::Engine.routes.draw do
2
+ get '/patients/find_by_npid', to: 'api/v1/dde#find_patients_by_npid'
3
+ get '/patients/find_by_name_and_gender', to: 'api/v1/dde#find_patients_by_name_and_gender'
4
+ get '/patients/import_by_doc_id', to: 'api/v1/dde#import_patients_by_doc_id'
5
+ get '/patients/import_by_npid', to: 'api/v1/dde#import_patients_by_npid'
6
+ get '/patients/match_by_demographics', to: 'api/v1/dde#match_patients_by_demographics'
7
+ get '/patients/diff', to: 'api/v1/dde#patient_diff'
8
+ get '/patients/refresh', to: 'api/v1/dde#refresh_patient'
9
+ post '/patients/reassign_npid', to: 'api/v1/dde#reassign_patient_npid'
10
+ post '/patients/merge', to: 'api/v1/dde#merge_patients'
11
+ get '/patients/remaining_npids', to: 'api/v1/dde#remaining_npids'
12
+ get '/rollback/merge_history', to: 'rollback#merge_history'
13
+ post '/rollback/rollback_patient', to: 'rollback#rollback_patient'
14
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ClientError < StandardError; end
@@ -0,0 +1,5 @@
1
+ module DdeClient
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DdeClient
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DdeClient
4
+ VERSION = "0.1.0"
5
+ end
data/lib/dde_client.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dde_client/version"
4
+ require "dde_client/engine"
5
+
6
+ module DdeClient
7
+ class Error < StandardError; end
8
+ # Your code goes here...
9
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dde_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - bryan-mw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.8
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.8
27
+ - !ruby/object:Gem::Dependency
28
+ name: rest-client
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - brianmsyamboza@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - Rakefile
50
+ - app/assets/config/dde_client_manifest.js
51
+ - app/assets/stylesheets/dde_client/application.css
52
+ - app/controllers/dde_client/api/v1/dde_controller.rb
53
+ - app/controllers/dde_client/api/v1/rollback_controller.rb
54
+ - app/controllers/dde_client/application_controller.rb
55
+ - app/helpers/dde/application_helper.rb
56
+ - app/jobs/dde/application_job.rb
57
+ - app/mailers/dde/application_mailer.rb
58
+ - app/models/dde/application_record.rb
59
+ - app/services/dde_client/dde_client.rb
60
+ - app/services/dde_client/dde_service.rb
61
+ - app/services/dde_client/matcher.rb
62
+ - app/services/dde_client/merging_service.rb
63
+ - app/services/dde_client/rollback_service.rb
64
+ - app/services/merge_audit_service.rb
65
+ - app/utils/model_utils.rb
66
+ - app/views/layouts/dde/application.html.erb
67
+ - config/routes.rb
68
+ - lib/dde_client.rb
69
+ - lib/dde_client/client_error.rb
70
+ - lib/dde_client/engine.rb
71
+ - lib/dde_client/version.rb
72
+ homepage: https://github.com/orgs/Malawi-Ministry-of-Health/dde_client
73
+ licenses:
74
+ - MIT
75
+ metadata:
76
+ homepage_uri: https://github.com/orgs/Malawi-Ministry-of-Health/dde_client
77
+ source_code_uri: https://github.com/orgs/Malawi-Ministry-of-Health/dde_client
78
+ changelog_uri: https://github.com/orgs/Malawi-Ministry-of-Health/dde_client/CHANGELOG.md
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.7.6
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: DDE stands for Demographics Data Exchange. Its main purpose is to manage
99
+ patient IDs.
100
+ test_files: []