# frozen_string_literal: true require_dependency "renalware/pathology" module Renalware module Pathology # Responsible for transforming an HL7 message payload into a params hash # that can be persisted by ObservationRequest. # Note: # - A message can have multiple observation_requests, each with its own observations. # - This class could be removed and a Builder class used to create the database models # directly - this would remove the extra level of indirection that this class introduces. class ObservationRequestsAttributesBuilder DEFAULT_REQUESTOR_NAME = "UNKNOWN" delegate :patient_identification, :observation_requests, to: :hl7_message delegate :internal_id, to: :patient_identification alias_attribute :requests, :observation_requests # hl7_message is an HL7Message (a decorator around an ::HL7::Message) def initialize(hl7_message, logger = Delayed::Worker.logger) @hl7_message = hl7_message @logger = logger end # Return an array of observation request attributes (with a nested array of # child observation attributes) for each OBR in the HL7 message. # The resulting array will be used to create the corresponding database records. def parse if renalware_patient? build_patient_params else logger.debug("Did not process pathology for #{internal_id}: not a renalware patient") nil end end def renalware_patient? Patient.exists?(local_patient_id: internal_id) end private attr_reader :hl7_message, :logger def build_patient_params patient = find_patient(internal_id) request_params.each do |request_param| request_param[:patient_id] = patient.id end end def request_params @request_params ||= build_observation_request_params end # rubocop:disable Metrics/MethodLength def build_observation_request_params requests.each_with_object([]) do |request, arr| request_description = find_request_description( code: request.identifier, name: request.name ) hash = { observation_request: { description_id: request_description.id, requestor_name: request.ordering_provider_name || DEFAULT_REQUESTOR_NAME, requestor_order_number: request.placer_order_number, requested_at: parse_time(request.date_time), observations_attributes: build_observations_params(request) } } arr << hash end end # rubocop:enable Metrics/MethodLength # rubocop:disable Metrics/MethodLength def build_observations_params(request) request.observations.map do |observation| observation_description = find_observation_description( code: observation.identifier, name: observation.name ) next unless validate_observation(observation, observation_description) { description_id: observation_description.id, observed_at: parse_time(observation.date_time), result: observation.value, comment: observation.comment, cancelled: observation.cancelled } end.compact end # rubocop:enable Metrics/MethodLength def find_request_description(code:, name:) RequestDescription.find_or_create_by!(code: code) do |desc| desc.name = name || code desc.lab = Lab.unknown end rescue ActiveRecord::RecordNotFound raise MissingRequestDescriptionError, code end def find_observation_description(code:, name:) ObservationDescription.find_or_create_by!(code: code) { |desc| desc.name = name || code } rescue ActiveRecord::RecordNotFound raise MissingObservationDescriptionError, code end def validate_observation(observation, observation_description) if observation.date_time.blank? logger.warn( "Skipped observation with blank `observed_at` (date_time) "\ "in OBX with code #{observation_description.code}" ) false else true end end # TODO: Support searching by other local patient ids? def find_patient(local_patient_id) Patient.find_by!(local_patient_id: local_patient_id) end # Default to using today's date and time if no date_time passed in the message def parse_time(string) return Time.zone.now.to_s if string.blank? Time.zone.parse(string).to_s end end end end