dde_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []