his_emr_api_lab 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|