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,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
|