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,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
module Lims
|
5
|
+
##
|
6
|
+
# Serializes a LabOrder into a LIMS OrderDTO.
|
7
|
+
module OrderSerializer
|
8
|
+
class << self
|
9
|
+
include Utils
|
10
|
+
|
11
|
+
def serialize_order(order)
|
12
|
+
serialized_order = structify(Lab::LabOrderSerializer.serialize_order(order))
|
13
|
+
|
14
|
+
OrderDTO.new(
|
15
|
+
tracking_number: serialized_order.accession_number,
|
16
|
+
sending_facility: current_facility_name,
|
17
|
+
receiving_facility: serialized_order.target_lab,
|
18
|
+
tests: serialized_order.tests.collect(&:name),
|
19
|
+
patient: format_patient(serialized_order.patient_id),
|
20
|
+
order_location: format_order_location(serialized_order.encounter_id),
|
21
|
+
sample_type: format_sample_type(serialized_order.specimen.name),
|
22
|
+
sample_status: format_sample_status(serialized_order.specimen.name),
|
23
|
+
districy: current_district, # yes districy [sic]...
|
24
|
+
priority: serialized_order.reason_for_test.name,
|
25
|
+
date_created: serialized_order.order_date,
|
26
|
+
test_results: format_test_results(serialized_order),
|
27
|
+
type: 'Order'
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def format_order_location(encounter_id)
|
34
|
+
location_id = Encounter.select(:location_id).where(encounter_id: encounter_id)
|
35
|
+
location = Location.select(:name)
|
36
|
+
.where(location_id: location_id)
|
37
|
+
.first
|
38
|
+
|
39
|
+
location&.name
|
40
|
+
end
|
41
|
+
|
42
|
+
# Format patient into a structure that LIMS expects
|
43
|
+
def format_patient(patient_id)
|
44
|
+
person = Person.find(patient_id)
|
45
|
+
name = PersonName.find_by_person_id(patient_id)
|
46
|
+
national_id = PatientIdentifier.joins(:type)
|
47
|
+
.merge(PatientIdentifierType.where(name: 'National ID'))
|
48
|
+
.where(patient_id: patient_id)
|
49
|
+
.first
|
50
|
+
phone_number = PersonAttribute.joins(:type)
|
51
|
+
.merge(PersonAttributeType.where(name: 'Cell phone Number'))
|
52
|
+
.where(person_id: patient_id)
|
53
|
+
.first
|
54
|
+
|
55
|
+
{
|
56
|
+
first_name: name&.given_name,
|
57
|
+
last_name: name&.family_name,
|
58
|
+
id: national_id&.identifier,
|
59
|
+
phone_number: phone_number.value,
|
60
|
+
gender: person.gender,
|
61
|
+
email: nil
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def format_sample_type(name)
|
66
|
+
name.casecmp?('Unknown') ? 'not_specified' : name
|
67
|
+
end
|
68
|
+
|
69
|
+
def format_sample_status(name)
|
70
|
+
name.casecmp?('Unknown') ? 'specimen_not_collected' : 'specimen_collected'
|
71
|
+
end
|
72
|
+
|
73
|
+
def format_test_results(order)
|
74
|
+
order.tests.each_with_object({}) do |test, results|
|
75
|
+
results[test.name] = {
|
76
|
+
results: test.result.each_with_object({}) do |measure, measures|
|
77
|
+
measures[measure.indicator.name] = { result_value: "#{measure.value_modifier}#{measure.value}" }
|
78
|
+
end,
|
79
|
+
result_date: test.result.first&.date,
|
80
|
+
result_entered_by: {}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def current_health_center
|
86
|
+
health_center = Location.current_health_center
|
87
|
+
raise 'Current health center not set' unless health_center
|
88
|
+
|
89
|
+
health_center
|
90
|
+
end
|
91
|
+
|
92
|
+
def current_district
|
93
|
+
district = current_health_center.city_village\
|
94
|
+
|| current_health_center.parent&.name\
|
95
|
+
|| GlobalProperty.find_by_property('current_health_center_district')&.property_value
|
96
|
+
|
97
|
+
return district if district
|
98
|
+
|
99
|
+
GlobalProperty.create(property: 'current_health_center_district',
|
100
|
+
property_value: Config.application['district'],
|
101
|
+
uuid: SecureRandom.uuid)
|
102
|
+
|
103
|
+
Config.application['district']
|
104
|
+
end
|
105
|
+
|
106
|
+
def current_facility_name
|
107
|
+
current_health_center.name
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
module Lims
|
5
|
+
##
|
6
|
+
# Various helper methods for modules in the Lims namespaces...
|
7
|
+
module Utils
|
8
|
+
def logger
|
9
|
+
Rails.logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def structify(object)
|
13
|
+
if object.is_a?(Hash)
|
14
|
+
object.each_with_object(OpenStruct.new) do |kv_pair, struct|
|
15
|
+
key, value = kv_pair
|
16
|
+
|
17
|
+
struct[key] = structify(value)
|
18
|
+
end
|
19
|
+
elsif object.respond_to?(:map)
|
20
|
+
object.map { |item| structify(item) }
|
21
|
+
else
|
22
|
+
object
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './utils'
|
4
|
+
|
5
|
+
module Lab
|
6
|
+
module Lims
|
7
|
+
##
|
8
|
+
# Pull/Push orders from/to the LIMS queue (Oops meant CouchDB).
|
9
|
+
class Worker
|
10
|
+
def initialize(lims_api)
|
11
|
+
@lims_api = lims_api
|
12
|
+
end
|
13
|
+
|
14
|
+
include Utils
|
15
|
+
|
16
|
+
attr_reader :lims_api
|
17
|
+
|
18
|
+
def push_orders(batch_size: 100)
|
19
|
+
loop do
|
20
|
+
logger.info('Fetching new orders...')
|
21
|
+
orders = LabOrder.where.not(order_id: LimsOrderMapping.all.select(:order_id))
|
22
|
+
.limit(batch_size)
|
23
|
+
|
24
|
+
if orders.empty?
|
25
|
+
logger.info('No new orders available; exiting...')
|
26
|
+
break
|
27
|
+
end
|
28
|
+
|
29
|
+
orders.each { |order| push_order(order) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def push_order_by_id(order_id)
|
34
|
+
order = LabOrder.find(order_id)
|
35
|
+
push_order(order)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Pushes given order to LIMS queue
|
40
|
+
def push_order(order)
|
41
|
+
logger.info("Pushing order ##{order.order_id}")
|
42
|
+
|
43
|
+
order_dto = OrderDTO.from_order(order)
|
44
|
+
mapping = LimsOrderMapping.find_by(order_id: order.order_id)
|
45
|
+
|
46
|
+
if mapping
|
47
|
+
lims_api.update_order(mapping.lims_id, order_dto)
|
48
|
+
mapping.update(pushed_at: Time.now)
|
49
|
+
else
|
50
|
+
order_dto = lims_api.create_order(order_dto)
|
51
|
+
LimsOrderMapping.create!(order: order, lims_id: order_dto['_id'], pushed_at: Time.now)
|
52
|
+
end
|
53
|
+
|
54
|
+
order_dto
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Pulls orders from the LIMS queue and writes them to the local database
|
59
|
+
def pull_orders
|
60
|
+
lims_api.consume_orders(from: last_seq) do |order_dto, context|
|
61
|
+
logger.debug(`Retrieved order ##{order[:tracking_number]}`)
|
62
|
+
|
63
|
+
patient = find_patient_by_nhid(order_dto[:patient][:id])
|
64
|
+
|
65
|
+
unless patient
|
66
|
+
logger.debug(`Discarding order: Non local patient ##{order_dto[:patient][:id]} on order ##{order[:tracking_number]}`)
|
67
|
+
break
|
68
|
+
end
|
69
|
+
|
70
|
+
save_order(patient, order_dto)
|
71
|
+
update_last_seq(context.last_seq)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def find_patient_by_nhid(nhid)
|
78
|
+
national_id_type = PatientIdentifierType.where(name: 'National id')
|
79
|
+
identifier = PatientIdentifier.where(type: national_id_type, identifier: nhid)
|
80
|
+
patients = Patient.joins(:identifiers).merge(identifier).group(:patient_id).all
|
81
|
+
|
82
|
+
raise "Duplicate National Health ID: #{nhid}" if patients.size > 1
|
83
|
+
|
84
|
+
patients.first
|
85
|
+
end
|
86
|
+
|
87
|
+
def save_order(patient, order_dto)
|
88
|
+
mapping = LimsOrderMapping.find_by(couch_id: order_dto[:_id])
|
89
|
+
|
90
|
+
if mapping
|
91
|
+
update_order(patient, mapping.order_id, order_dto)
|
92
|
+
mapping.update(pulled_at: Time.now)
|
93
|
+
else
|
94
|
+
order = create_order(patient, order_dto)
|
95
|
+
LimsOrderMapping.create!(lims_id: order_dto[:_id], order: order, pulled_at: Time.now)
|
96
|
+
end
|
97
|
+
|
98
|
+
order
|
99
|
+
end
|
100
|
+
|
101
|
+
def create_order(patient, order_dto)
|
102
|
+
order = OrdersService.order_test(patient, order_dto.to_order_service_params)
|
103
|
+
update_results(order, order_dto.test_results)
|
104
|
+
|
105
|
+
order
|
106
|
+
end
|
107
|
+
|
108
|
+
def update_order(_patient, order_id, order_dto)
|
109
|
+
order = OrdersService.update_order(order_id, order_dto.to_order_service_params)
|
110
|
+
update_results(order, order_dto.test_results)
|
111
|
+
|
112
|
+
order
|
113
|
+
end
|
114
|
+
|
115
|
+
def update_results(_order, _lims_results)
|
116
|
+
# TODO: Implement me
|
117
|
+
raise 'Not implemented error'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
module Metadata
|
5
|
+
# Concepts
|
6
|
+
REASON_FOR_TEST_CONCEPT_NAME = 'Reason for test'
|
7
|
+
REQUESTING_CLINICIAN_CONCEPT_NAME = 'Person making request'
|
8
|
+
SPECIMEN_TYPE_CONCEPT_NAME = 'Specimen type'
|
9
|
+
TARGET_LAB_CONCEPT_NAME = 'Lab'
|
10
|
+
TEST_RESULT_CONCEPT_NAME = 'Lab test result'
|
11
|
+
TEST_RESULT_INDICATOR_CONCEPT_NAME = 'Lab test result indicator'
|
12
|
+
TEST_TYPE_CONCEPT_NAME = 'Test type'
|
13
|
+
|
14
|
+
# Encounter
|
15
|
+
ENCOUNTER_TYPE_NAME = 'Lab'
|
16
|
+
|
17
|
+
# Order types
|
18
|
+
ORDER_TYPE_NAME = 'Lab'
|
19
|
+
|
20
|
+
# Programs
|
21
|
+
LAB_PROGRAM_NAME = 'Lab Program'
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
# Search Lab orders.
|
5
|
+
module OrdersSearchService
|
6
|
+
class << self
|
7
|
+
def find_orders(filters)
|
8
|
+
date = filters.delete(:date)
|
9
|
+
status = filters.delete(:status)
|
10
|
+
|
11
|
+
orders = Lab::LabOrder.prefetch_relationships
|
12
|
+
.where(filters)
|
13
|
+
.order(start_date: :desc)
|
14
|
+
|
15
|
+
orders = filter_orders_by_date(orders, date) if date
|
16
|
+
orders = filter_orders_by_status(orders, status) if status
|
17
|
+
|
18
|
+
orders.map { |order| Lab::LabOrderSerializer.serialize_order(order) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def filter_orders_by_date(orders, date)
|
22
|
+
orders.where('start_date < DATE(?)', date.to_date + 1.day)
|
23
|
+
end
|
24
|
+
|
25
|
+
def filter_orders_by_status(orders, status)
|
26
|
+
case status.downcase
|
27
|
+
when 'ordered' then orders.where(concept_id: unknown_concept_id)
|
28
|
+
when 'drawn' then orders.where.not(concept_id: unknown_concept_id)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def unknown_concept_id
|
33
|
+
ConceptName.find_by_name!('Unknown').concept_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def filter_orders_by_status(orders, status)
|
37
|
+
case status.downcase
|
38
|
+
when 'ordered' then orders.where(concept_id: unknown_concept_id)
|
39
|
+
when 'drawn' then orders.where.not(concept_id: unknown_concept_id)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def unknown_concept_id
|
44
|
+
ConceptName.find_by_name!('Unknown').concept_id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lab
|
4
|
+
##
|
5
|
+
# Manage lab orders.
|
6
|
+
#
|
7
|
+
# Lab orders are just ordinary openmrs orders with extra metadata that
|
8
|
+
# separates them from other orders. Lab orders have an order type of 'Lab'
|
9
|
+
# with the order's test type as the order's concept. The order's start
|
10
|
+
# date is the day the order is made. Additional information pertaining to
|
11
|
+
# the order is stored as observations that point to the order. The
|
12
|
+
# specimen types, requesting clinician, target lab, and reason for test
|
13
|
+
# are saved as observations to the order. Refer to method #order_test for
|
14
|
+
# more information.
|
15
|
+
module OrdersService
|
16
|
+
class << self
|
17
|
+
##
|
18
|
+
# Create a lab order.
|
19
|
+
#
|
20
|
+
# Parameters schema:
|
21
|
+
#
|
22
|
+
# {
|
23
|
+
# encounter_id: {
|
24
|
+
# type: :integer,
|
25
|
+
# required: :false,
|
26
|
+
# description: 'Attach order to this if program_id and patient_id are not provided'
|
27
|
+
# },
|
28
|
+
# program_id: { type: :integer, required: false },
|
29
|
+
# patient_id: { type: :integer, required: false }
|
30
|
+
# specimen_type_id: { type: :object, properties: { concept_id: :integer }, required: %i[concept_id] },
|
31
|
+
# test_type_ids: {
|
32
|
+
# type: :array,
|
33
|
+
# items: {
|
34
|
+
# type: :object,
|
35
|
+
# properties: { concept_id: :integer },
|
36
|
+
# required: %i[concept_id]
|
37
|
+
# }
|
38
|
+
# },
|
39
|
+
# start_date: { type: :datetime }
|
40
|
+
# accession_number: { type: :string }
|
41
|
+
# target_lab: { type: :string },
|
42
|
+
# reason_for_test_id: { type: :integer },
|
43
|
+
# requesting_clinician: { type: :string }
|
44
|
+
# }
|
45
|
+
#
|
46
|
+
# encounter_id: is an ID of the encounter the lab order is to be created under
|
47
|
+
# test_type_id: is a concept_id of the name of test being ordered
|
48
|
+
# specimen_type_id: is a list of IDs for the specimens to be tested (can be ommited)
|
49
|
+
# target_lab: is the name of the lab where test will be carried out
|
50
|
+
# reason_for_test_id: is a concept_id for a (standard) reason of why the test is being carried out
|
51
|
+
# requesting_clinician: Name of the clinician requesting the test (defaults to current user)
|
52
|
+
def order_test(order_params)
|
53
|
+
Order.transaction do
|
54
|
+
encounter = find_encounter(order_params)
|
55
|
+
order = create_order(encounter, order_params)
|
56
|
+
|
57
|
+
Lab::TestsService.create_tests(order, order_params[:date], order_params[:tests])
|
58
|
+
|
59
|
+
Lab::LabOrderSerializer.serialize_order(
|
60
|
+
order, requesting_clinician: add_requesting_clinician(order, order_params),
|
61
|
+
reason_for_test: add_reason_for_test(order, order_params),
|
62
|
+
target_lab: add_target_lab(order, order_params)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def update_order(order_id, params)
|
68
|
+
specimen_id = params.dig(:specimen, :concept_id)
|
69
|
+
unless specimen_id
|
70
|
+
raise ::InvalidParameterError, 'Specimen concept_id is required'
|
71
|
+
end
|
72
|
+
|
73
|
+
order = Lab::LabOrder.find(order_id)
|
74
|
+
unless order.concept_id == unknown_concept_id
|
75
|
+
raise ::UnprocessableEntityError
|
76
|
+
end
|
77
|
+
|
78
|
+
order.update!(concept_id: specimen_id)
|
79
|
+
Lab::LabOrderSerializer.serialize_order(order)
|
80
|
+
end
|
81
|
+
|
82
|
+
def void_order(order_id, reason)
|
83
|
+
order = Lab::LabOrder.includes(%i[requesting_clinician reason_for_test target_lab], tests: [:result])
|
84
|
+
.find(order_id)
|
85
|
+
|
86
|
+
order.requesting_clinician&.void(reason)
|
87
|
+
order.reason_for_test&.void(reason)
|
88
|
+
order.target_lab&.void(reason)
|
89
|
+
|
90
|
+
order.tests.each do |test|
|
91
|
+
test.result&.void(reason)
|
92
|
+
test.void(reason)
|
93
|
+
end
|
94
|
+
|
95
|
+
order.void(reason)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
##
|
101
|
+
# Extract an encounter from the given parameters.
|
102
|
+
#
|
103
|
+
# Uses an encounter_id to retrieve an encounter if provided else
|
104
|
+
# a 'Lab' encounter is created using the provided program_id and
|
105
|
+
# patient_id.
|
106
|
+
def find_encounter(order_params)
|
107
|
+
if order_params[:encounter_id]
|
108
|
+
return Encounter.find(order_params[:encounter_id])
|
109
|
+
end
|
110
|
+
|
111
|
+
unless order_params[:patient_id]
|
112
|
+
raise InvalidParameterError, 'encounter_id or patient_id required'
|
113
|
+
end
|
114
|
+
|
115
|
+
program_id = order_params[:program_id] || Program.find_by_name!(Lab::Metadata::LAB_PROGRAM_NAME).program_id
|
116
|
+
|
117
|
+
Encounter.create!(
|
118
|
+
patient_id: order_params[:patient_id],
|
119
|
+
program_id: program_id,
|
120
|
+
type: EncounterType.find_by_name!(Lab::Metadata::ENCOUNTER_TYPE_NAME),
|
121
|
+
encounter_datetime: order_params[:date] || Date.today,
|
122
|
+
provider_id: order_params[:provider_id] || User.current&.user_id
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
def create_order(encounter, params)
|
127
|
+
Lab::LabOrder.create!(
|
128
|
+
order_type: OrderType.find_by_name!(Lab::Metadata::ORDER_TYPE_NAME),
|
129
|
+
concept_id: params.dig(:specimen, :concept_id) || unknown_concept_id,
|
130
|
+
encounter_id: encounter.encounter_id,
|
131
|
+
patient_id: encounter.patient_id,
|
132
|
+
start_date: params[:date]&.to_date || Date.today,
|
133
|
+
auto_expire_date: params[:end_date],
|
134
|
+
accession_number: params[:accession_number] || next_accession_number,
|
135
|
+
orderer: User.current&.user_id
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Attach the requesting clinician to an order
|
141
|
+
def add_requesting_clinician(order, params)
|
142
|
+
create_order_observation(
|
143
|
+
order,
|
144
|
+
Lab::Metadata::REQUESTING_CLINICIAN_CONCEPT_NAME,
|
145
|
+
params[:date],
|
146
|
+
value_text: params['requesting_clinician']
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Attach a reason for the order/test
|
152
|
+
#
|
153
|
+
# Examples of reasons include: Routine, Targeted, Confirmatory, Repeat, or Stat.
|
154
|
+
def add_reason_for_test(order, params)
|
155
|
+
create_order_observation(
|
156
|
+
order,
|
157
|
+
Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME,
|
158
|
+
params[:date],
|
159
|
+
value_coded: params['reason_for_test_id']
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Attach the lab where the test is going to get carried out.
|
165
|
+
def add_target_lab(order, params)
|
166
|
+
create_order_observation(
|
167
|
+
order,
|
168
|
+
Lab::Metadata::TARGET_LAB_CONCEPT_NAME,
|
169
|
+
params[:date],
|
170
|
+
value_text: params['target_lab']
|
171
|
+
)
|
172
|
+
end
|
173
|
+
|
174
|
+
def create_order_observation(order, concept_name, date, **values)
|
175
|
+
Observation.create!(
|
176
|
+
order: order,
|
177
|
+
encounter_id: order.encounter_id,
|
178
|
+
person_id: order.patient_id,
|
179
|
+
concept_id: ConceptName.find_by_name!(concept_name).concept_id,
|
180
|
+
obs_datetime: date&.to_time || Time.now,
|
181
|
+
**values
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
185
|
+
def next_accession_number(date = nil)
|
186
|
+
Lab::AccessionNumberService.next_accession_number(date)
|
187
|
+
end
|
188
|
+
|
189
|
+
def unknown_concept_id
|
190
|
+
ConceptName.find_by_name!('Unknown').concept_id
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|