his_emr_api_lab 1.1.21 → 1.1.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. metadata +3 -82
  3. data/MIT-LICENSE +0 -20
  4. data/README.md +0 -71
  5. data/Rakefile +0 -32
  6. data/app/controllers/lab/application_controller.rb +0 -6
  7. data/app/controllers/lab/labels_controller.rb +0 -17
  8. data/app/controllers/lab/orders_controller.rb +0 -38
  9. data/app/controllers/lab/reasons_for_test_controller.rb +0 -9
  10. data/app/controllers/lab/results_controller.rb +0 -19
  11. data/app/controllers/lab/specimen_types_controller.rb +0 -15
  12. data/app/controllers/lab/test_result_indicators_controller.rb +0 -9
  13. data/app/controllers/lab/test_types_controller.rb +0 -15
  14. data/app/controllers/lab/tests_controller.rb +0 -26
  15. data/app/jobs/lab/application_job.rb +0 -4
  16. data/app/jobs/lab/push_order_job.rb +0 -12
  17. data/app/jobs/lab/update_patient_orders_job.rb +0 -32
  18. data/app/jobs/lab/void_order_job.rb +0 -17
  19. data/app/mailers/lab/application_mailer.rb +0 -6
  20. data/app/models/lab/application_record.rb +0 -5
  21. data/app/models/lab/lab_accession_number_counter.rb +0 -13
  22. data/app/models/lab/lab_encounter.rb +0 -7
  23. data/app/models/lab/lab_order.rb +0 -58
  24. data/app/models/lab/lab_result.rb +0 -31
  25. data/app/models/lab/lab_test.rb +0 -19
  26. data/app/models/lab/lims_failed_import.rb +0 -4
  27. data/app/models/lab/lims_order_mapping.rb +0 -10
  28. data/app/serializers/lab/lab_order_serializer.rb +0 -55
  29. data/app/serializers/lab/result_serializer.rb +0 -36
  30. data/app/serializers/lab/test_serializer.rb +0 -29
  31. data/app/services/lab/accession_number_service.rb +0 -77
  32. data/app/services/lab/concepts_service.rb +0 -82
  33. data/app/services/lab/labelling_service/order_label.rb +0 -106
  34. data/app/services/lab/lims/api/blackhole_api.rb +0 -21
  35. data/app/services/lab/lims/api/couchdb_api.rb +0 -53
  36. data/app/services/lab/lims/api/mysql_api.rb +0 -316
  37. data/app/services/lab/lims/api/rest_api.rb +0 -416
  38. data/app/services/lab/lims/api/ws_api.rb +0 -121
  39. data/app/services/lab/lims/api_factory.rb +0 -19
  40. data/app/services/lab/lims/config.rb +0 -100
  41. data/app/services/lab/lims/exceptions.rb +0 -11
  42. data/app/services/lab/lims/migrator.rb +0 -216
  43. data/app/services/lab/lims/order_dto.rb +0 -105
  44. data/app/services/lab/lims/order_serializer.rb +0 -244
  45. data/app/services/lab/lims/pull_worker.rb +0 -289
  46. data/app/services/lab/lims/push_worker.rb +0 -149
  47. data/app/services/lab/lims/utils.rb +0 -91
  48. data/app/services/lab/lims/worker.rb +0 -86
  49. data/app/services/lab/metadata.rb +0 -24
  50. data/app/services/lab/orders_search_service.rb +0 -66
  51. data/app/services/lab/orders_service.rb +0 -212
  52. data/app/services/lab/results_service.rb +0 -147
  53. data/app/services/lab/tests_service.rb +0 -93
  54. data/config/routes.rb +0 -17
  55. data/db/migrate/20210126092910_create_lab_lab_accession_number_counters.rb +0 -12
  56. data/db/migrate/20210310115457_create_lab_lims_order_mappings.rb +0 -15
  57. data/db/migrate/20210323080140_change_lims_id_to_string_in_lims_order_mapping.rb +0 -15
  58. data/db/migrate/20210326195504_add_order_revision_to_lims_order_mapping.rb +0 -5
  59. data/db/migrate/20210407071728_create_lab_lims_failed_imports.rb +0 -19
  60. data/db/migrate/20210610095024_fix_numeric_results_value_type.rb +0 -20
  61. data/db/migrate/20210807111531_add_default_to_lims_order_mapping.rb +0 -7
  62. data/lib/auto12epl.rb +0 -201
  63. data/lib/couch_bum/couch_bum.rb +0 -92
  64. data/lib/generators/lab/install/USAGE +0 -9
  65. data/lib/generators/lab/install/install_generator.rb +0 -19
  66. data/lib/generators/lab/install/templates/rswag-ui-lab.rb +0 -5
  67. data/lib/generators/lab/install/templates/start_worker.rb +0 -32
  68. data/lib/generators/lab/install/templates/swagger.yaml +0 -714
  69. data/lib/his_emr_api_lab.rb +0 -5
  70. data/lib/lab/engine.rb +0 -15
  71. data/lib/lab/version.rb +0 -5
  72. data/lib/logger_multiplexor.rb +0 -38
  73. data/lib/tasks/lab_tasks.rake +0 -25
  74. data/lib/tasks/loaders/data/reasons-for-test.csv +0 -7
  75. data/lib/tasks/loaders/data/test-measures.csv +0 -225
  76. data/lib/tasks/loaders/data/tests.csv +0 -161
  77. data/lib/tasks/loaders/loader_mixin.rb +0 -53
  78. data/lib/tasks/loaders/metadata_loader.rb +0 -26
  79. data/lib/tasks/loaders/reasons_for_test_loader.rb +0 -23
  80. data/lib/tasks/loaders/specimens_loader.rb +0 -65
  81. data/lib/tasks/loaders/test_result_indicators_loader.rb +0 -54
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lab
4
- module LabOrderSerializer
5
- def self.serialize_order(order, tests: nil, requesting_clinician: nil, reason_for_test: nil, target_lab: nil)
6
- tests ||= order.voided == 1 ? voided_tests(order) : order.tests
7
- requesting_clinician ||= order.requesting_clinician
8
- reason_for_test ||= order.reason_for_test
9
- target_lab = target_lab&.value_text || order.target_lab&.value_text || Location.current_health_center&.name
10
-
11
- ActiveSupport::HashWithIndifferentAccess.new(
12
- {
13
- id: order.order_id,
14
- order_id: order.order_id, # Deprecated: Link to :id
15
- encounter_id: order.encounter_id,
16
- order_date: order.start_date,
17
- patient_id: order.patient_id,
18
- accession_number: order.accession_number,
19
- specimen: {
20
- concept_id: order.concept_id,
21
- name: concept_name(order.concept_id)
22
- },
23
- requesting_clinician: requesting_clinician&.value_text,
24
- target_lab: target_lab,
25
- reason_for_test: {
26
- concept_id: reason_for_test&.value_coded,
27
- name: concept_name(reason_for_test&.value_coded)
28
- },
29
- tests: tests.map do |test|
30
- result_obs = test.children.first
31
-
32
- {
33
- id: test.obs_id,
34
- concept_id: test.value_coded,
35
- name: concept_name(test.value_coded),
36
- result: result_obs && ResultSerializer.serialize(result_obs)
37
- }
38
- end
39
- }
40
- )
41
- end
42
-
43
- def self.concept_name(concept_id)
44
- return concept_id unless concept_id
45
-
46
- ConceptName.select(:name).find_by_concept_id(concept_id)&.name
47
- end
48
-
49
- def self.voided_tests(order)
50
- concept = ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
51
- .select(:concept_id)
52
- LabTest.unscoped.where(concept: concept, order: order, voided: true)
53
- end
54
- end
55
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lab
4
- ##
5
- # Serialize a Lab order result
6
- module ResultSerializer
7
- def self.serialize(result)
8
- result.children.map do |measure|
9
- value, value_type = read_value(measure)
10
- concept_name = ConceptName.find_by_concept_id(measure.concept_id)
11
-
12
- {
13
- id: measure.obs_id,
14
- indicator: {
15
- concept_id: concept_name&.concept_id,
16
- name: concept_name&.name
17
- },
18
- date: measure.obs_datetime,
19
- value: value,
20
- value_type: value_type,
21
- value_modifier: measure.value_modifier
22
- }
23
- end
24
- end
25
-
26
- def self.read_value(measure)
27
- %w[value_numeric value_coded value_boolean value_text].each do |field|
28
- value = measure.send(field)
29
-
30
- return [value, field.split('_')[1]] if value
31
- end
32
-
33
- [nil, 'unknown']
34
- end
35
- end
36
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lab
4
- module TestSerializer
5
- def self.serialize(test, order: nil, result: nil)
6
- order ||= test.order
7
- result ||= test.result
8
-
9
- {
10
- id: test.obs_id,
11
- concept_id: test.value_coded,
12
- name: ConceptName.find_by_concept_id(test.value_coded)&.name,
13
- order: {
14
- id: order.order_id,
15
- concept_id: order.concept_id,
16
- name: ConceptName.find_by_concept_id(order.concept_id)&.name,
17
- accession_number: order.accession_number
18
- },
19
- result: if result
20
- {
21
- id: result.obs_id,
22
- modifier: result.value_modifier,
23
- value: result.value_text
24
- }
25
- end
26
- }
27
- end
28
- end
29
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lab
4
- # Responsible for the generation of tracking numbers
5
- module AccessionNumberService
6
- class << self
7
- # Returns the next accession number on the given date or today.
8
- #
9
- # Throws:
10
- # RangeError - If date is greater than system date
11
- def next_accession_number(date = nil)
12
- date = validate_date(date || Date.today)
13
- counter = find_counter(date)
14
-
15
- counter.with_lock do
16
- accession_number = format_accession_number(date, counter.value)
17
- counter.value += 1
18
- counter.save!
19
-
20
- return accession_number
21
- end
22
- end
23
-
24
- private
25
-
26
- def find_counter(date)
27
- counter = Lab::LabAccessionNumberCounter.find_by(date: date)
28
- return counter if counter
29
-
30
- Lab::LabAccessionNumberCounter.create(date: date, value: 1)
31
- end
32
-
33
- # Checks if date does not exceed system date
34
- def validate_date(date)
35
- return date unless date > Date.today
36
-
37
- raise RangeError, "Specified date exceeds system date: #{date} > #{Date.today}"
38
- end
39
-
40
- def format_accession_number(date, counter)
41
- year = format_year(date.year)
42
- month = format_month(date.month)
43
- day = format_day(date.day)
44
-
45
- "X#{site_code}#{year}#{month}#{day}#{counter}"
46
- end
47
-
48
- def format_year(year)
49
- (year % 100).to_s.rjust(2, '0')
50
- end
51
-
52
- # It's base 32 that uses letters for values 10+ but the letters
53
- # are ordered in a way that seems rather arbitrary
54
- # (see #get_day in https://github.com/HISMalawi/nlims_controller/blob/3c0faf1cb6572a11cb3b9bd1ea8444f457d01fd7/lib/tracking_number_service.rb#L58)
55
- DAY_NUMBERING_SYSTEM = %w[1 2 3 4 5 6 7 8 9 A B C E F G H Y J K Z M N O P Q R S T V W X].freeze
56
-
57
- def format_day(day)
58
- DAY_NUMBERING_SYSTEM[day - 1]
59
- end
60
-
61
- def format_month(month)
62
- # Months use a base 13 numbering system that's just a subset of the
63
- # numbering system used for days
64
- format_day(month)
65
- end
66
-
67
- def site_code
68
- property = GlobalProperty.find_by(property: 'site_prefix')
69
- value = property&.property_value&.strip
70
-
71
- raise "Global property 'site_prefix' not set" unless value
72
-
73
- value
74
- end
75
- end
76
- end
77
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lab
4
- # A read-only repository of sort for all lab-centric concepts.
5
- module ConceptsService
6
- def self.test_types(name: nil, specimen_type: nil)
7
- test_types = ConceptSet.find_members_by_name(Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
8
- test_types = test_types.filter_members(name: name) if name
9
-
10
- unless specimen_type
11
- return test_types.joins('INNER JOIN concept_name ON concept_set.concept_id = concept_name.concept_id')
12
- .select('concept_name.name, concept_name.concept_id')
13
- .group('concept_name.concept_id')
14
- end
15
-
16
- # Filter out only those test types that have the specified specimen
17
- # type.
18
- specimen_types = ConceptSet.find_members_by_name(Lab::Metadata::SPECIMEN_TYPE_CONCEPT_NAME)
19
- .filter_members(name: specimen_type)
20
- .select(:concept_id)
21
-
22
- concept_set = ConceptSet.where(
23
- concept_id: specimen_types,
24
- concept_set: test_types.select(:concept_id)
25
- )
26
-
27
- concept_set.joins('INNER JOIN concept_name ON concept_set.concept_set = concept_name.concept_id')
28
- .select('concept_name.concept_id, concept_name.name')
29
- .group('concept_name.concept_id')
30
- end
31
-
32
- def self.specimen_types(name: nil, test_type: nil)
33
- specimen_types = ConceptSet.find_members_by_name(Lab::Metadata::SPECIMEN_TYPE_CONCEPT_NAME)
34
- specimen_types = specimen_types.filter_members(name: name) if name
35
-
36
- unless test_type
37
- return specimen_types.select('concept_name.concept_id, concept_name.name')
38
- .joins('INNER JOIN concept_name ON concept_name.concept_id = concept_set.concept_id')
39
- .group('concept_name.concept_id')
40
- end
41
-
42
- # Retrieve only those specimen types that belong to concept
43
- # set of the selected test_type
44
- test_types = ConceptSet.find_members_by_name(Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
45
- .filter_members(name: test_type)
46
- .select(:concept_id)
47
-
48
- concept_set = ConceptSet.where(
49
- concept_id: specimen_types.select(:concept_id),
50
- concept_set: test_types
51
- )
52
-
53
- concept_set.select('concept_name.concept_id, concept_name.name')
54
- .joins('INNER JOIN concept_name ON concept_name.concept_id = concept_set.concept_id')
55
- .group('concept_name.concept_id')
56
- end
57
-
58
- def self.test_result_indicators(test_type_id)
59
- # Verify that the specified test_type is indeed a test_type
60
- test = ConceptSet.find_members_by_name(Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
61
- .where(concept_id: test_type_id)
62
- .select(:concept_id)
63
-
64
- # From the members above, filter out only those concepts that are result indicators
65
- measures = ConceptSet.find_members_by_name(Lab::Metadata::TEST_RESULT_INDICATOR_CONCEPT_NAME)
66
- .select(:concept_id)
67
-
68
- ConceptSet.where(concept_set: measures, concept_id: test)
69
- .joins('INNER JOIN concept_name AS measure ON measure.concept_id = concept_set.concept_set')
70
- .select('measure.concept_id, measure.name')
71
- .group('measure.concept_id')
72
- .map { |concept| { name: concept.name, concept_id: concept.concept_id } }
73
- end
74
-
75
- def self.reasons_for_test
76
- ConceptSet.find_members_by_name(Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME)
77
- .joins('INNER JOIN concept_name ON concept_name.concept_id = concept_set.concept_id')
78
- .select('concept_name.concept_id, concept_name.name')
79
- .map { |concept| { name: concept.name, concept_id: concept.concept_id } }
80
- end
81
- end
82
- end
@@ -1,106 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'auto12epl'
4
-
5
- module Lab
6
- module LabellingService
7
- ##
8
- # Prints an order label for order with given accession number.
9
- class OrderLabel
10
- attr_reader :order
11
-
12
- def initialize(order_id)
13
- @order = Lab::LabOrder.find(order_id)
14
- end
15
-
16
- def print
17
- # NOTE: The arguments are passed into the method below not in the order
18
- # the method expects (eg patient_id is passed to middle_name field)
19
- # to retain compatibility with labels generated by the `lab test controller`
20
- # application of the NLIMS suite.
21
- auto12epl.generate_epl(patient.given_name,
22
- patient.family_name,
23
- patient.nhid,
24
- patient.birthdate.strftime('%d/%^b/%Y'),
25
- '',
26
- patient.gender,
27
- '',
28
- drawer,
29
- '',
30
- tests,
31
- reason_for_test,
32
- order.accession_number,
33
- order.accession_number)
34
- end
35
-
36
- def reason_for_test
37
- return 'Unknown' unless order.reason_for_test
38
-
39
- short_concept_name(order.reason_for_test.value_coded) || 'Unknown'
40
- end
41
-
42
- def patient
43
- return @patient if @patient
44
-
45
- person = Person.find(order.patient_id)
46
- person_name = PersonName.find_by_person_id(order.patient_id)
47
- patient_identifier = PatientIdentifier.where(type: PatientIdentifierType.where(name: 'National id'),
48
- patient_id: order.patient_id)
49
- .first
50
-
51
- @patient = OpenStruct.new(
52
- given_name: person_name.given_name,
53
- family_name: person_name.family_name,
54
- birthdate: person.birthdate,
55
- gender: person.gender,
56
- nhid: patient_identifier&.identifier || 'Unknown'
57
- )
58
- end
59
-
60
- def drawer
61
- return 'N/A' if order.concept_id == unknown_concept.concept_id
62
-
63
- drawer_id = User.find(order.discontinued_by || order.creator).person_id
64
- draw_date = (order.discontinued_date || order.start_date).strftime('%d/%^b/%Y %H:%M:%S')
65
-
66
- name = PersonName.find_by_person_id(drawer_id)
67
- return "#{name.given_name} #{name.family_name} #{draw_date}" if name
68
-
69
- user = User.find_by_user_id(drawer_id)
70
- user ? "#{user.username} #{draw_date}" : 'N/A'
71
- end
72
-
73
- def specimen
74
- return 'N/A' if order.concept_id == unknown_concept.concept_id
75
-
76
- ConceptName.find_by_concept_id(order.concept_id)&.name || 'Unknown'
77
- end
78
-
79
- def tests
80
- tests = order.tests.map do |test|
81
- name = short_concept_name(test.value_coded) || 'Unknown'
82
-
83
- next 'VL' if name.match?(/Viral load/i)
84
-
85
- name.size > 7 ? name[0..6] : name
86
- end
87
-
88
- tests.join(', ')
89
- end
90
-
91
- def short_concept_name(concept_id)
92
- ConceptName.where(concept_id: concept_id)
93
- .min_by { |concept| concept.name.size }
94
- &.name
95
- end
96
-
97
- def unknown_concept
98
- ConceptName.find_by_name('Unknown')
99
- end
100
-
101
- def auto12epl
102
- Auto12Epl.new
103
- end
104
- end
105
- end
106
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Lab
4
- module Lims
5
- module Api
6
- ##
7
- # A LIMS Api wrappper that does nothing really.
8
- #
9
- # Primarily meant as a dummy for testing environments.
10
- class BlackholeApi
11
- def create_order(order_dto); end
12
-
13
- def update_order(order_dto); end
14
-
15
- def void_order(order_dto); end
16
-
17
- def consume_orders(&_block); end
18
- end
19
- end
20
- end
21
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'couch_bum/couch_bum'
4
-
5
- require_relative '../config'
6
-
7
- module Lab
8
- module Lims
9
- module Api
10
- ##
11
- # Talk to LIMS like a boss
12
- class CouchDbApi
13
- attr_reader :bum
14
-
15
- def initialize(config: nil)
16
- config ||= Config.couchdb
17
-
18
- @bum = CouchBum.new(protocol: config['protocol'],
19
- host: config['host'],
20
- port: config['port'],
21
- database: "#{config['prefix']}_order_#{config['suffix']}",
22
- username: config['username'],
23
- password: config['password'])
24
- end
25
-
26
- ##
27
- # Consume orders from the LIMS queue.
28
- #
29
- # Retrieves orders from the LIMS queue and passes each order to
30
- # given block until the queue is empty or connection is terminated
31
- # by calling method +choke+.
32
- def consume_orders(from: 0, limit: 30)
33
- bum.binge_changes(since: from, limit: limit, include_docs: true) do |change|
34
- next unless change['doc']['type']&.casecmp?('Order')
35
-
36
- yield OrderDTO.new(change['doc']), self
37
- end
38
- end
39
-
40
- def create_order(order)
41
- order = order.dup
42
- order.delete('_id')
43
-
44
- bum.couch_rest :post, '/', order
45
- end
46
-
47
- def update_order(id, order)
48
- bum.couch_rest :put, "/#{id}", order
49
- end
50
- end
51
- end
52
- end
53
- end