his_emr_api_lab 0.0.2 → 0.0.3
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 +4 -4
- data/app/controllers/lab/labels_controller.rb +15 -0
- data/app/controllers/lab/orders_controller.rb +2 -1
- data/app/models/lab/lab_order.rb +5 -1
- data/app/models/lab/lab_result.rb +10 -0
- data/app/models/lab/lab_test.rb +5 -0
- data/app/services/lab/labelling_service/order_label.rb +85 -0
- data/app/services/lab/lims/api.rb +7 -0
- data/app/services/lab/lims/exceptions.rb +11 -0
- data/app/services/lab/lims/migrator.rb +206 -0
- data/app/services/lab/lims/order_dto.rb +41 -122
- data/app/services/lab/lims/order_serializer.rb +28 -2
- data/app/services/lab/lims/utils.rb +45 -1
- data/app/services/lab/lims/worker.rb +227 -38
- data/app/services/lab/metadata.rb +1 -1
- data/app/services/lab/orders_service.rb +9 -8
- data/app/services/lab/results_service.rb +38 -6
- data/config/routes.rb +2 -0
- data/lib/auto12epl.rb +201 -0
- data/lib/couch_bum/couch_bum.rb +11 -1
- data/lib/lab/version.rb +1 -1
- data/lib/tasks/loaders/data/tests.csv +21 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 778184a052cac6932ec700f03b99615024440af3944c371d0cfa997f357c525c
|
4
|
+
data.tar.gz: 045fb73daab53abc457c08152c5148251a6dccb239f0c76e09f3172007e44b6e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95f56b7a5d1b36565074904d49e5ff69b34fb57bed690a7bec5d269749c6813e094b4e02b397517523edd90e0714710376f489338788838fad775f3c724659db
|
7
|
+
data.tar.gz: 380096a9132eb857a4a321260593640eec0d6a5b08f661e97a1ba2396619eb5984c1aca488e364930dff737ff397b07865597b9ed321c7a93101c151a8d2566f
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
class LabelsController < ApplicationController
|
5
|
+
def print_order_label
|
6
|
+
order_id = params.require(:order_id)
|
7
|
+
|
8
|
+
label = LabellingService::OrderLabel.new(order_id)
|
9
|
+
send_data(label.print, type: 'application/label; charset=utf-8',
|
10
|
+
stream: false,
|
11
|
+
filename: "#{SecureRandom.hex(24)}.lbl",
|
12
|
+
disposition: 'inline')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -14,7 +14,8 @@ module Lab
|
|
14
14
|
def update
|
15
15
|
specimen = params.require(:specimen).permit(:concept_id)
|
16
16
|
|
17
|
-
order = OrdersService.update_order(params[:id], specimen: specimen
|
17
|
+
order = OrdersService.update_order(params[:id], specimen: specimen,
|
18
|
+
force_update: params[:force_update])
|
18
19
|
|
19
20
|
render json: order
|
20
21
|
end
|
data/app/models/lab/lab_order.rb
CHANGED
@@ -35,7 +35,11 @@ module Lab
|
|
35
35
|
class_name: 'Observation',
|
36
36
|
foreign_key: :order_id
|
37
37
|
|
38
|
-
default_scope
|
38
|
+
default_scope do
|
39
|
+
joins(:order_type)
|
40
|
+
.merge(OrderType.where(name: Lab::Metadata::ORDER_TYPE_NAME))
|
41
|
+
.where.not(concept_id: ConceptName.where(name: 'Tests ordered').select(:concept_id))
|
42
|
+
end
|
39
43
|
|
40
44
|
def self.prefetch_relationships
|
41
45
|
includes(:reason_for_test,
|
@@ -17,5 +17,15 @@ module Lab
|
|
17
17
|
end),
|
18
18
|
class_name: 'Observation',
|
19
19
|
foreign_key: :obs_group_id
|
20
|
+
|
21
|
+
def void(reason)
|
22
|
+
children.each do |measure|
|
23
|
+
# Need to have a LabResultMeasure model that privately handles it's children
|
24
|
+
measure.children.each { |provider| provider.void(reason) }
|
25
|
+
measure.void(reason)
|
26
|
+
end
|
27
|
+
|
28
|
+
super(reason)
|
29
|
+
end
|
20
30
|
end
|
21
31
|
end
|
data/app/models/lab/lab_test.rb
CHANGED
@@ -0,0 +1,85 @@
|
|
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
|
+
specimen,
|
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
|
+
ConceptName.find_by_concept_id(order.reason_for_test.value_coded)&.name || '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
|
+
name = PersonName.find_by_person_id(order.creator)
|
64
|
+
return "#{name.given_name} #{name.family_name}" if name
|
65
|
+
|
66
|
+
user = User.find(order.creator)
|
67
|
+
user&.username || 'N/A'
|
68
|
+
end
|
69
|
+
|
70
|
+
def specimen
|
71
|
+
return 'N/A' if order.concept_id == unknown_concept.concept_id
|
72
|
+
|
73
|
+
ConceptName.find_by_concept_id(order.concept_id)&.name || 'Unknown'
|
74
|
+
end
|
75
|
+
|
76
|
+
def unknown_concept
|
77
|
+
ConceptName.find_by_name('Unknown')
|
78
|
+
end
|
79
|
+
|
80
|
+
def auto12epl
|
81
|
+
Auto12Epl.new
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -29,9 +29,16 @@ module Lab
|
|
29
29
|
# given block until the queue is empty or connection is terminated
|
30
30
|
# by calling method +choke+.
|
31
31
|
def consume_orders(from: 0, limit: 30)
|
32
|
+
last_seq = { value: 0 }
|
33
|
+
|
32
34
|
bum.binge_changes(since: from, limit: limit, include_docs: true) do |change|
|
35
|
+
next unless change['doc']['type']&.casecmp?('Order')
|
36
|
+
|
33
37
|
yield OrderDTO.new(change['doc']), self
|
38
|
+
last_seq[:value] = self.last_seq
|
34
39
|
end
|
40
|
+
|
41
|
+
last_seq[:value]
|
35
42
|
end
|
36
43
|
|
37
44
|
def create_order(order)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
module Lims
|
5
|
+
class LimsException < StandardError; end
|
6
|
+
class DuplicateNHID < LimsException; end
|
7
|
+
class MissingAccessionNumber < LimsException; end
|
8
|
+
class UnknownSpecimenType < LimsException; end
|
9
|
+
class UnknownTestType < LimsException; end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require 'parallel'
|
5
|
+
|
6
|
+
require 'couch_bum/couch_bum'
|
7
|
+
require 'logger_multiplexor'
|
8
|
+
|
9
|
+
require 'concept'
|
10
|
+
require 'concept_name'
|
11
|
+
require 'drug_order'
|
12
|
+
require 'encounter'
|
13
|
+
require 'encounter_type'
|
14
|
+
require 'observation'
|
15
|
+
require 'order'
|
16
|
+
require 'order_type'
|
17
|
+
require 'patient'
|
18
|
+
require 'patient_identifier'
|
19
|
+
require 'patient_identifier_type'
|
20
|
+
require 'person'
|
21
|
+
require 'person_name'
|
22
|
+
require 'program'
|
23
|
+
require 'user'
|
24
|
+
|
25
|
+
require 'lab/lab_encounter'
|
26
|
+
require 'lab/lab_order'
|
27
|
+
require 'lab/lab_result'
|
28
|
+
require 'lab/lab_test'
|
29
|
+
require 'lab/lims_order_mapping'
|
30
|
+
require 'lab/lims_failed_import'
|
31
|
+
|
32
|
+
require_relative '../orders_service'
|
33
|
+
require_relative '../results_service'
|
34
|
+
require_relative '../tests_service'
|
35
|
+
require_relative '../../../serializers/lab/lab_order_serializer'
|
36
|
+
require_relative '../../../serializers/lab/result_serializer'
|
37
|
+
require_relative '../../../serializers/lab/test_serializer'
|
38
|
+
|
39
|
+
require_relative 'order_dto'
|
40
|
+
require_relative 'utils'
|
41
|
+
|
42
|
+
module Lab
|
43
|
+
module Lims
|
44
|
+
module Migrator
|
45
|
+
class MigratorApi < Api
|
46
|
+
MAX_THREADS = 6
|
47
|
+
|
48
|
+
attr_reader :rejections
|
49
|
+
|
50
|
+
def consume_orders(from: nil, limit: 50_000)
|
51
|
+
Parallel.each(read_orders(from, limit),
|
52
|
+
in_processes: MAX_THREADS,
|
53
|
+
finish: order_pmap_post_processor(from)) do |row|
|
54
|
+
next unless row['doc']['type']&.casecmp?('Order')
|
55
|
+
|
56
|
+
User.current = Migrator.lab_user
|
57
|
+
yield OrderDTO.new(row['doc']), OpenStruct.new(last_seq: from)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def last_seq
|
62
|
+
return 0 unless File.exist?(last_seq_path)
|
63
|
+
|
64
|
+
File.open(last_seq_path, File::RDONLY) do |file|
|
65
|
+
last_seq = file.read&.strip
|
66
|
+
return last_seq.blank? ? nil : last_seq&.to_i
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def last_seq_path
|
73
|
+
Rails.root.join('log/lims/migration-last-id.dat')
|
74
|
+
end
|
75
|
+
|
76
|
+
def order_pmap_post_processor(last_seq)
|
77
|
+
lambda do |item, index, result|
|
78
|
+
save_last_seq(last_seq + index)
|
79
|
+
status, reason = result
|
80
|
+
next unless status == :rejected
|
81
|
+
|
82
|
+
(@rejections ||= []) << OpenStruct.new(order: OrderDTO.new(item['doc']), reason: reason)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def save_last_seq(last_seq)
|
87
|
+
return unless last_seq
|
88
|
+
|
89
|
+
File.open(last_seq_path, File::WRONLY | File::CREAT, 0o644) do |file|
|
90
|
+
Rails.logger.debug("Process ##{Parallel.worker_number}: Saving last seq: #{last_seq}")
|
91
|
+
file.flock(File::LOCK_EX)
|
92
|
+
file.write(last_seq.to_s)
|
93
|
+
file.flush
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def save_rejection(order_dto, reason); end
|
98
|
+
|
99
|
+
def read_orders(from, batch_size)
|
100
|
+
Enumerator.new do |enum|
|
101
|
+
loop do
|
102
|
+
start_key_param = from ? "&skip=#{from}" : ''
|
103
|
+
url = "_all_docs?include_docs=true&limit=#{batch_size}#{start_key_param}"
|
104
|
+
|
105
|
+
Rails.logger.debug("#{MigratorApi}: Pulling orders from LIMS CouchDB: #{url}")
|
106
|
+
response = bum.couch_rest :get, url
|
107
|
+
|
108
|
+
from ||= 0
|
109
|
+
|
110
|
+
break from if response['rows'].empty?
|
111
|
+
|
112
|
+
response['rows'].each do |row|
|
113
|
+
enum.yield(row)
|
114
|
+
end
|
115
|
+
|
116
|
+
from += response['rows'].size
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class MigrationWorker < Worker
|
123
|
+
protected
|
124
|
+
|
125
|
+
def last_seq
|
126
|
+
lims_api.last_seq
|
127
|
+
end
|
128
|
+
|
129
|
+
def update_last_seq(_last_seq); end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.lab_user
|
133
|
+
user = User.find_by_username('lab_daemon')
|
134
|
+
return user if user
|
135
|
+
|
136
|
+
god_user = User.first
|
137
|
+
|
138
|
+
person = Person.create!(creator: god_user.user_id)
|
139
|
+
PersonName.create!(person: person, given_name: 'Lab', family_name: 'Daemon', creator: god_user.user_id)
|
140
|
+
|
141
|
+
User.create!(username: 'lab_daemon', person: person, creator: god_user.user_id)
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.save_csv(filename, rows:, headers: nil)
|
145
|
+
CSV.open(filename, File::WRONLY | File::CREAT) do |csv|
|
146
|
+
csv << headers if headers
|
147
|
+
rows.each { |row| csv << row }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
MIGRATION_REJECTIONS_CSV_PATH = Rails.root.join('log/lims/migration-rejections.csv')
|
152
|
+
|
153
|
+
def self.export_rejections(rejections)
|
154
|
+
headers = ['Accession number', 'NHID', 'First name', 'Last name', 'Reason']
|
155
|
+
rows = (rejections || []).map do |rejection|
|
156
|
+
[
|
157
|
+
rejection.order[:tracking_number],
|
158
|
+
rejection.order[:patient][:id],
|
159
|
+
rejection.order[:patient][:first_name],
|
160
|
+
rejection.order[:patient][:last_name],
|
161
|
+
rejection.reason
|
162
|
+
]
|
163
|
+
end
|
164
|
+
|
165
|
+
save_csv(MIGRATION_REJECTIONS_CSV_PATH, headers: headers, rows: rows)
|
166
|
+
end
|
167
|
+
|
168
|
+
MIGRATION_FAILURES_CSV_PATH = Rails.root.join('log/lims/migration-failures.csv')
|
169
|
+
|
170
|
+
def self.export_failures
|
171
|
+
headers = ['Accession number', 'NHID', 'Reason', 'Difference']
|
172
|
+
rows = Lab::LimsFailedImport.all.map do |failure|
|
173
|
+
[
|
174
|
+
failure.tracking_number,
|
175
|
+
failure.patient_nhid,
|
176
|
+
failure.reason,
|
177
|
+
failure.diff
|
178
|
+
]
|
179
|
+
end
|
180
|
+
|
181
|
+
save_csv(MIGRATION_FAILURES_CSV_PATH, headers: headers, rows: rows)
|
182
|
+
end
|
183
|
+
|
184
|
+
MIGRATION_LOG_PATH = Rails.root.join('log/lims/migration.log')
|
185
|
+
|
186
|
+
def self.start_migration
|
187
|
+
log_dir = Rails.root.join('log/lims')
|
188
|
+
Dir.mkdir(log_dir) unless File.exist?(log_dir)
|
189
|
+
|
190
|
+
logger = LoggerMultiplexor.new(Logger.new($stdout), MIGRATION_LOG_PATH)
|
191
|
+
logger.level = :debug
|
192
|
+
Rails.logger = logger
|
193
|
+
ActiveRecord::Base.logger = logger
|
194
|
+
# CouchBum.logger = logger
|
195
|
+
|
196
|
+
api = MigratorApi.new
|
197
|
+
worker = MigrationWorker.new(api)
|
198
|
+
|
199
|
+
worker.pull_orders
|
200
|
+
ensure
|
201
|
+
api && export_rejections(api.rejections)
|
202
|
+
export_failures
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -1,153 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './exceptions'
|
4
|
+
|
3
5
|
module Lab
|
4
6
|
module Lims
|
5
7
|
##
|
6
8
|
# LIMS' Data Transfer Object for orders
|
7
9
|
class OrderDTO < ActiveSupport::HashWithIndifferentAccess
|
8
|
-
|
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
|
10
|
+
include Utils
|
106
11
|
|
107
12
|
##
|
108
13
|
# Unpacks a LIMS order into an object that OrdersService can handle
|
109
|
-
def to_order_service_params(
|
14
|
+
def to_order_service_params(patient_id:)
|
110
15
|
ActiveSupport::HashWithIndifferentAccess.new(
|
111
16
|
program_id: lab_program.program_id,
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
17
|
+
accession_number: self['tracking_number'],
|
18
|
+
patient_id: patient_id,
|
19
|
+
specimen: { concept_id: specimen_type_id },
|
20
|
+
tests: self['tests']&.map { |test| { concept_id: test_type_id(test) } },
|
21
|
+
requesting_clinician: requesting_clinician,
|
22
|
+
date: start_date,
|
23
|
+
target_lab: facility_name(self['receiving_facility']),
|
24
|
+
order_location: facility_name(self['sending_facility']),
|
25
|
+
reason_for_test: reason_for_test
|
120
26
|
)
|
121
27
|
end
|
122
28
|
|
123
29
|
private
|
124
30
|
|
125
31
|
# Translates a LIMS specimen name to an OpenMRS concept_id
|
126
|
-
def specimen_type_id
|
127
|
-
|
128
|
-
|
32
|
+
def specimen_type_id
|
33
|
+
lims_specimen_name = self['sample_type']
|
34
|
+
|
35
|
+
if %w[specimen_not_collected not_assigned].include?(lims_specimen_name)
|
36
|
+
return ConceptName.select(:concept_id).find_by_name!('Unknown').concept_id
|
129
37
|
end
|
130
38
|
|
131
|
-
concept =
|
39
|
+
concept = Utils.find_concept_by_name(lims_specimen_name)
|
132
40
|
return concept.concept_id if concept
|
133
41
|
|
134
|
-
raise "Unknown specimen name: #{lims_specimen_name}"
|
42
|
+
raise UnknownSpecimenType, "Unknown specimen name: #{lims_specimen_name}"
|
135
43
|
end
|
136
44
|
|
137
45
|
# Translates a LIMS test type name to an OpenMRS concept_id
|
138
46
|
def test_type_id(lims_test_name)
|
139
|
-
|
47
|
+
lims_test_name = Utils.translate_test_name(lims_test_name)
|
48
|
+
concept = Utils.find_concept_by_name(lims_test_name)
|
140
49
|
return concept.concept_id if concept
|
141
50
|
|
142
|
-
raise "Unknown test type: #{lims_test_name}"
|
51
|
+
raise UnknownTestType, "Unknown test type: #{lims_test_name}"
|
143
52
|
end
|
144
53
|
|
145
54
|
# Extract requesting clinician name from LIMS
|
146
|
-
def requesting_clinician
|
55
|
+
def requesting_clinician
|
147
56
|
# TODO: Extend requesting clinician to an obs tree having extra parameters
|
148
57
|
# like phone number and ID to closely match the lims user.
|
149
|
-
first_name =
|
150
|
-
last_name =
|
58
|
+
first_name = self['who_order_test']['first_name'] || ''
|
59
|
+
last_name = self['who_order_test']['last_name'] || ''
|
151
60
|
|
152
61
|
if first_name.blank? && last_name.blank?
|
153
62
|
logger.warn('Missing requesting clinician name')
|
@@ -157,8 +66,8 @@ module Lab
|
|
157
66
|
"#{first_name} #{last_name}"
|
158
67
|
end
|
159
68
|
|
160
|
-
def start_date
|
161
|
-
|
69
|
+
def start_date
|
70
|
+
Utils.parse_date(self['date_created'])
|
162
71
|
end
|
163
72
|
|
164
73
|
# Parses a LIMS facility name
|
@@ -168,9 +77,19 @@ module Lab
|
|
168
77
|
lims_target_lab
|
169
78
|
end
|
170
79
|
|
171
|
-
# Translates a LIMS priority to a concept_id
|
172
|
-
def reason_for_test
|
173
|
-
|
80
|
+
# Translates a LIMS sample priority to a concept_id
|
81
|
+
def reason_for_test
|
82
|
+
return unknown_concept.concept_id unless self['sample_priority']
|
83
|
+
|
84
|
+
ConceptName.find_by_name!(self['sample_priority']).concept_id
|
85
|
+
end
|
86
|
+
|
87
|
+
def lab_program
|
88
|
+
Program.find_by_name!(Lab::Metadata::LAB_PROGRAM_NAME)
|
89
|
+
end
|
90
|
+
|
91
|
+
def unknown_concept
|
92
|
+
ConceptName.find_by_name!('Unknown')
|
174
93
|
end
|
175
94
|
end
|
176
95
|
end
|