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