his_emr_api_lab 0.0.2
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +52 -0
- data/Rakefile +32 -0
- data/app/controllers/lab/application_controller.rb +6 -0
- data/app/controllers/lab/orders_controller.rb +34 -0
- data/app/controllers/lab/reasons_for_test_controller.rb +9 -0
- data/app/controllers/lab/results_controller.rb +19 -0
- data/app/controllers/lab/specimen_types_controller.rb +15 -0
- data/app/controllers/lab/test_result_indicators_controller.rb +9 -0
- data/app/controllers/lab/test_types_controller.rb +15 -0
- data/app/controllers/lab/tests_controller.rb +25 -0
- data/app/jobs/lab/application_job.rb +4 -0
- data/app/mailers/lab/application_mailer.rb +6 -0
- data/app/models/lab/application_record.rb +5 -0
- data/app/models/lab/lab_accession_number_counter.rb +13 -0
- data/app/models/lab/lab_encounter.rb +7 -0
- data/app/models/lab/lab_order.rb +47 -0
- data/app/models/lab/lab_result.rb +21 -0
- data/app/models/lab/lab_test.rb +14 -0
- data/app/models/lab/lims_failed_import.rb +4 -0
- data/app/models/lab/lims_order_mapping.rb +10 -0
- data/app/serializers/lab/lab_order_serializer.rb +49 -0
- data/app/serializers/lab/result_serializer.rb +36 -0
- data/app/serializers/lab/test_serializer.rb +29 -0
- data/app/services/lab/accession_number_service.rb +77 -0
- data/app/services/lab/concepts_service.rb +82 -0
- data/app/services/lab/lims/api.rb +46 -0
- data/app/services/lab/lims/config.rb +56 -0
- data/app/services/lab/lims/order_dto.rb +177 -0
- data/app/services/lab/lims/order_serializer.rb +112 -0
- data/app/services/lab/lims/utils.rb +27 -0
- data/app/services/lab/lims/worker.rb +121 -0
- data/app/services/lab/metadata.rb +23 -0
- data/app/services/lab/orders_search_service.rb +48 -0
- data/app/services/lab/orders_service.rb +194 -0
- data/app/services/lab/results_service.rb +92 -0
- data/app/services/lab/tests_service.rb +93 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20210126092910_create_lab_lab_accession_number_counters.rb +12 -0
- data/db/migrate/20210310115457_create_lab_lims_order_mappings.rb +15 -0
- data/db/migrate/20210323080140_change_lims_id_to_string_in_lims_order_mapping.rb +15 -0
- data/db/migrate/20210326195504_add_order_revision_to_lims_order_mapping.rb +5 -0
- data/db/migrate/20210407071728_create_lab_lims_failed_imports.rb +19 -0
- data/lib/couch_bum/couch_bum.rb +77 -0
- data/lib/generators/lab/install/USAGE +9 -0
- data/lib/generators/lab/install/install_generator.rb +19 -0
- data/lib/generators/lab/install/templates/rswag-ui-lab.rb +5 -0
- data/lib/generators/lab/install/templates/start_worker.rb +32 -0
- data/lib/generators/lab/install/templates/swagger.yaml +682 -0
- data/lib/his_emr_api_lab.rb +5 -0
- data/lib/lab/engine.rb +15 -0
- data/lib/lab/version.rb +5 -0
- data/lib/logger_multiplexor.rb +32 -0
- data/lib/tasks/lab_tasks.rake +25 -0
- data/lib/tasks/loaders/data/reasons-for-test.csv +6 -0
- data/lib/tasks/loaders/data/test-measures.csv +224 -0
- data/lib/tasks/loaders/data/tests.csv +142 -0
- data/lib/tasks/loaders/loader_mixin.rb +53 -0
- data/lib/tasks/loaders/metadata_loader.rb +26 -0
- data/lib/tasks/loaders/reasons_for_test_loader.rb +23 -0
- data/lib/tasks/loaders/specimens_loader.rb +65 -0
- data/lib/tasks/loaders/test_result_indicators_loader.rb +54 -0
- metadata +296 -0
@@ -0,0 +1,36 @@
|
|
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
|
@@ -0,0 +1,29 @@
|
|
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
|
@@ -0,0 +1,77 @@
|
|
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
|
@@ -0,0 +1,82 @@
|
|
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
|
@@ -0,0 +1,46 @@
|
|
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
|
+
##
|
10
|
+
# Talk to LIMS like a boss
|
11
|
+
class Api
|
12
|
+
attr_reader :bum
|
13
|
+
|
14
|
+
def initialize(config: nil)
|
15
|
+
config ||= Config.couchdb
|
16
|
+
|
17
|
+
@bum = CouchBum.new(protocol: config['protocol'],
|
18
|
+
host: config['host'],
|
19
|
+
port: config['port'],
|
20
|
+
database: "#{config['prefix']}_order_#{config['suffix']}",
|
21
|
+
username: config['username'],
|
22
|
+
password: config['password'])
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Consume orders from the LIMS queue.
|
27
|
+
#
|
28
|
+
# Retrieves orders from the LIMS queue and passes each order to
|
29
|
+
# given block until the queue is empty or connection is terminated
|
30
|
+
# by calling method +choke+.
|
31
|
+
def consume_orders(from: 0, limit: 30)
|
32
|
+
bum.binge_changes(since: from, limit: limit, include_docs: true) do |change|
|
33
|
+
yield OrderDTO.new(change['doc']), self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_order(order)
|
38
|
+
bum.couch_rest :post, '/', order
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_order(id, order)
|
42
|
+
bum.couch_rest :put, "/#{id}", order
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
module Lims
|
5
|
+
##
|
6
|
+
# Load LIMS' configuration files
|
7
|
+
module Config
|
8
|
+
class ConfigNotFound < RuntimeError; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
##
|
12
|
+
# Returns LIMS' couchdb configuration file for the current environment (Rails.env)
|
13
|
+
def couchdb
|
14
|
+
config_path = begin
|
15
|
+
find_config_path('couchdb.yml')
|
16
|
+
rescue ConfigNotFound => e
|
17
|
+
Rails.logger.error("Failed to find default LIMS couchdb config: #{e.message}")
|
18
|
+
find_config_path('couchdb-lims.yml') # This can be placed in HIS-EMR-API/config
|
19
|
+
end
|
20
|
+
|
21
|
+
Rails.logger.debug("Using LIMS couchdb config: #{config_path}")
|
22
|
+
|
23
|
+
YAML.load_file(config_path)[Rails.env]
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Returns LIMS' application.yml configuration file
|
28
|
+
def application
|
29
|
+
YAML.load_file(find_config_path('application.yml'))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
##
|
35
|
+
# Looks for a config file in various LIMS installation directories
|
36
|
+
#
|
37
|
+
# Returns: a path to a file found
|
38
|
+
def find_config_path(filename)
|
39
|
+
paths = [
|
40
|
+
"#{ENV['HOME']}/apps/nlims_controller/config/#{filename}",
|
41
|
+
"/var/www/nlims_controller/config/#{filename}",
|
42
|
+
Rails.root.parent.join("nlims_controller/config/#{filename}"),
|
43
|
+
Rails.root.join('config/lims-couch.yml')
|
44
|
+
]
|
45
|
+
|
46
|
+
paths.each do |path|
|
47
|
+
Rails.logger.debug("Looking for LIMS couchdb config at: #{path}")
|
48
|
+
return path if File.exist?(path)
|
49
|
+
end
|
50
|
+
|
51
|
+
raise ConfigNotFound, "Could not find a configuration file, checked: #{paths.join(':')}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
module Lims
|
5
|
+
##
|
6
|
+
# LIMS' Data Transfer Object for orders
|
7
|
+
class OrderDTO < ActiveSupport::HashWithIndifferentAccess
|
8
|
+
class << self
|
9
|
+
include Utils
|
10
|
+
|
11
|
+
##
|
12
|
+
# Takes a Lab::LabOrder and serializes it into a DTO
|
13
|
+
def from_order(order)
|
14
|
+
serialized_order = structify(Lab::LabOrderSerializer.serialize_order(order))
|
15
|
+
|
16
|
+
new(
|
17
|
+
tracking_number: serialized_order.accession_number,
|
18
|
+
sending_facility: current_facility_name,
|
19
|
+
receiving_facility: serialized_order.target_lab,
|
20
|
+
tests: serialized_order.tests.collect(&:name),
|
21
|
+
patient: format_patient(serialized_order.patient_id),
|
22
|
+
order_location: format_order_location(serialized_order.encounter_id),
|
23
|
+
sample_type: format_sample_type(serialized_order.specimen.name),
|
24
|
+
sample_status: format_sample_status(serialized_order.specimen.name),
|
25
|
+
districy: current_district, # yes districy [sic]...
|
26
|
+
priority: serialized_order.reason_for_test.name,
|
27
|
+
date_created: serialized_order.order_date,
|
28
|
+
test_results: format_test_results(serialized_order),
|
29
|
+
type: 'Order'
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def format_order_location(encounter_id)
|
36
|
+
location_id = Encounter.select(:location_id).where(encounter_id: encounter_id)
|
37
|
+
location = Location.select(:name)
|
38
|
+
.where(location_id: location_id)
|
39
|
+
.first
|
40
|
+
|
41
|
+
location&.name
|
42
|
+
end
|
43
|
+
|
44
|
+
# Format patient into a structure that LIMS expects
|
45
|
+
def format_patient(patient_id)
|
46
|
+
person = Person.find(patient_id)
|
47
|
+
name = PersonName.find_by_person_id(patient_id)
|
48
|
+
national_id = PatientIdentifier.joins(:type)
|
49
|
+
.merge(PatientIdentifierType.where(name: 'National ID'))
|
50
|
+
.where(patient_id: patient_id)
|
51
|
+
.first
|
52
|
+
phone_number = PersonAttribute.joins(:type)
|
53
|
+
.merge(PersonAttributeType.where(name: 'Cell phone Number'))
|
54
|
+
.where(person_id: patient_id)
|
55
|
+
.first
|
56
|
+
|
57
|
+
{
|
58
|
+
first_name: name&.given_name,
|
59
|
+
last_name: name&.family_name,
|
60
|
+
id: national_id&.value,
|
61
|
+
phone_number: phone_number,
|
62
|
+
gender: person.gender,
|
63
|
+
email: nil
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def format_sample_type(name)
|
68
|
+
name.casecmp?('Unknown') ? 'not_specified' : name
|
69
|
+
end
|
70
|
+
|
71
|
+
def format_sample_status(name)
|
72
|
+
name.casecmp?('Unknown') ? 'specimen_not_collected' : 'specimen_collected'
|
73
|
+
end
|
74
|
+
|
75
|
+
def format_test_results(order)
|
76
|
+
order.tests.each_with_object({}) do |test, results|
|
77
|
+
results[test.name] = {
|
78
|
+
results: test.result.each_with_object({}) do |measure, measures|
|
79
|
+
measures[measure.indicator.name] = { result_value: "#{measure.value_modifier}#{measure.value}" }
|
80
|
+
end,
|
81
|
+
result_date: test.result.first&.date,
|
82
|
+
result_entered_by: {}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def current_health_center
|
88
|
+
health_center = Location.current_health_center
|
89
|
+
raise 'Current health center not set' unless health_center
|
90
|
+
|
91
|
+
health_center
|
92
|
+
end
|
93
|
+
|
94
|
+
def current_district
|
95
|
+
unless current_health_center.parent
|
96
|
+
raise "Current health center ##{current_health_center.id} is not associated with any district"
|
97
|
+
end
|
98
|
+
|
99
|
+
current_health_center.city_village || current_health_center.parent.name
|
100
|
+
end
|
101
|
+
|
102
|
+
def current_facility_name
|
103
|
+
current_health_center.name
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Unpacks a LIMS order into an object that OrdersService can handle
|
109
|
+
def to_order_service_params(lims_order)
|
110
|
+
ActiveSupport::HashWithIndifferentAccess.new(
|
111
|
+
program_id: lab_program.program_id,
|
112
|
+
patient_id: patient.patient_id,
|
113
|
+
specimen_type: { concept_id: specimen_type_id(lims_order.sample_type) },
|
114
|
+
tests: lims_order.tests&.map { |test| { concept_id: test_type_id(test) } },
|
115
|
+
requesting_clinician: requesting_clinician(lims_order.who_order_test),
|
116
|
+
start_date: start_date(lims_order.date_created),
|
117
|
+
target_lab: facility_name(lims_order.receiving_facility),
|
118
|
+
order_location: facility_name(lims_order.sending_facility),
|
119
|
+
reason_for_test: reason_for_test(lims_order.sample_priority)
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# Translates a LIMS specimen name to an OpenMRS concept_id
|
126
|
+
def specimen_type_id(lims_specimen_name)
|
127
|
+
if lims_specimen_name == 'specimen_not_collected'
|
128
|
+
return ConceptName.select(:concept_id).find_by_name!('Unknown')
|
129
|
+
end
|
130
|
+
|
131
|
+
concept = ConceptName.select(:concept_id).find_by_name(lims_specimen_name)
|
132
|
+
return concept.concept_id if concept
|
133
|
+
|
134
|
+
raise "Unknown specimen name: #{lims_specimen_name}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Translates a LIMS test type name to an OpenMRS concept_id
|
138
|
+
def test_type_id(lims_test_name)
|
139
|
+
concept = ConceptName.select(:concept_id).find_by_name(lims_test_name)
|
140
|
+
return concept.concept_id if concept
|
141
|
+
|
142
|
+
raise "Unknown test type: #{lims_test_name}"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Extract requesting clinician name from LIMS
|
146
|
+
def requesting_clinician(lims_user)
|
147
|
+
# TODO: Extend requesting clinician to an obs tree having extra parameters
|
148
|
+
# like phone number and ID to closely match the lims user.
|
149
|
+
first_name = lims_user.first_name || ''
|
150
|
+
last_name = lims_user.last_name || ''
|
151
|
+
|
152
|
+
if first_name.blank? && last_name.blank?
|
153
|
+
logger.warn('Missing requesting clinician name')
|
154
|
+
return ''
|
155
|
+
end
|
156
|
+
|
157
|
+
"#{first_name} #{last_name}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def start_date(lims_order_date_created)
|
161
|
+
lims_order_date_created.to_datetime
|
162
|
+
end
|
163
|
+
|
164
|
+
# Parses a LIMS facility name
|
165
|
+
def facility_name(lims_target_lab)
|
166
|
+
return 'Unknown' if lims_target_lab == 'not_assigned'
|
167
|
+
|
168
|
+
lims_target_lab
|
169
|
+
end
|
170
|
+
|
171
|
+
# Translates a LIMS priority to a concept_id
|
172
|
+
def reason_for_test(lims_sample_priority)
|
173
|
+
ConceptName.find_by_name!(lims_sample_priority).concept_id
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|