diff --git a/config/betamocks/services_config.yml b/config/betamocks/services_config.yml index c9a65b09999..2bdf997c445 100644 --- a/config/betamocks/services_config.yml +++ b/config/betamocks/services_config.yml @@ -847,6 +847,22 @@ :cache_multiple_responses: :uid_location: "query" :uid_locator: "icn" + # Patient Health API + - :method: :post + :path: "/oauth2/health/system/v1/token" + :file_path: "/lighthouse/veterans_health/token" + - :method: :get + :path: "/services/fhir/v0/r4/AllergyIntolerance/*" + :file_path: "/lighthouse/veterans_health/AllergyIntolerance/show" + :cache_multiple_responses: + :uid_location: url + :uid_locator: "\/services\/fhir\/v0\/r4\/AllergyIntolerance\/(.*)" + - :method: :get + :path: "/services/fhir/v0/r4/AllergyIntolerance" + :file_path: "/lighthouse/veterans_health/AllergyIntolerance/index" + :cache_multiple_responses: + :uid_location: query + :uid_locator: patient # Lighthouse Benefits Documents API - :name: "Lighthouse Benefits Documents" diff --git a/config/features.yml b/config/features.yml index d2bf7c87455..dff261cd664 100644 --- a/config/features.yml +++ b/config/features.yml @@ -862,6 +862,10 @@ features: actor_type: user descriptiom: Enables access to MHV Account Creation API enable_in_development: true + mhv_accelerated_delivery_allergies_enabled: + actor_type: user + description: Control fetching OH allergies data + enable_in_development: false mhv_va_health_chat_enabled: actor_type: user description: Enables the VA Health Chat link at /my-health diff --git a/config/settings.yml b/config/settings.yml index 48724795f2d..729ee51450a 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -1046,6 +1046,7 @@ lighthouse: use_mocks: false veterans_health: url: https://sandbox-api.va.gov + use_mocks: false fast_tracker: client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" api_scope: diff --git a/config/settings/test.yml b/config/settings/test.yml index e3599255b89..6deb8568aad 100644 --- a/config/settings/test.yml +++ b/config/settings/test.yml @@ -228,11 +228,14 @@ lighthouse: client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" api_scope: - "launch" + - "patient/AllergyIntolerance.read" + - "patient/DiagnosticReport.read" - "patient/Patient.read" - "system/Patient.read" - "patient/Observation.read" - "patient/Practitioner.read" - "patient/MedicationRequest.read" + - "patient/Condition.read" aud_claim_url: "https://deptva-eval.okta.com/oauth2/aus8nm1q0f7VQ0a482p7/v1/token" client_id: "fake_client_id" grant_type: "client_credentials" diff --git a/lib/lighthouse/veterans_health/client.rb b/lib/lighthouse/veterans_health/client.rb index 5844a5e9416..2e7a6b3707b 100644 --- a/lib/lighthouse/veterans_health/client.rb +++ b/lib/lighthouse/veterans_health/client.rb @@ -56,6 +56,10 @@ def list_allergy_intolerances get_list(first_response) end + def get_allergy_intolerance(id) + perform_get("services/fhir/v0/r4/AllergyIntolerance/#{id}") + end + def list_bp_observations params = { category: 'vital-signs', diff --git a/lib/lighthouse/veterans_health/configuration.rb b/lib/lighthouse/veterans_health/configuration.rb index 2db70e4bd7b..1e70304330f 100644 --- a/lib/lighthouse/veterans_health/configuration.rb +++ b/lib/lighthouse/veterans_health/configuration.rb @@ -28,6 +28,8 @@ def connection # faraday.response(:logger, ::Logger.new(STDOUT), bodies: true) unless Rails.env.production? faraday.response :json + + faraday.response :betamocks if Settings.lighthouse.veterans_health.use_mocks faraday.adapter Faraday.default_adapter end end diff --git a/lib/medical_records/lighthouse_client.rb b/lib/medical_records/lighthouse_client.rb new file mode 100644 index 00000000000..2a65392a1b1 --- /dev/null +++ b/lib/medical_records/lighthouse_client.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require 'common/client/base' +require 'lighthouse/veterans_health/client' + +module MedicalRecords + ## + # Core class responsible for Medical Records API interface operations with the Lighthouse FHIR server. + # + class LighthouseClient < Common::Client::Base + ## + # Initialize the client + # + # @param icn [String] MHV patient ICN + # + def initialize(icn) + super() + + raise Common::Exceptions::ParameterMissing, 'ICN' if icn.blank? + + @icn = icn + end + + def authenticate + # FIXME: Explore doing this in a less janky way. + # This is called by the MHV Controller Concern, but is not needed for this client + # because it is handled in Lighthouse::VeteransHealth::Client::retrieve_bearer_token + end + + def lighthouse_client + @lighthouse_client ||= Lighthouse::VeteransHealth::Client.new(@icn) + end + + def list_allergies + bundle = lighthouse_client.list_allergy_intolerances + bundle = Oj.load(bundle[:body].to_json, symbol_keys: true) + sort_bundle(bundle, :recordedDate, :desc) + end + + def get_allergy(allergy_id) + bundle = lighthouse_client.get_allergy_intolerance(allergy_id) + Oj.load(bundle[:body].to_json, symbol_keys: true) + end + + protected + + def handle_api_errors(result) + if result.code.present? && result.code >= 400 + body = JSON.parse(result.body) + diagnostics = body['issue']&.first&.fetch('diagnostics', nil) + diagnostics = "Error fetching data#{": #{diagnostics}" if diagnostics}" + + # Special-case exception handling + if result.code == 500 && diagnostics.include?('HAPI-1363') + # "HAPI-1363: Either No patient or multiple patient found" + raise MedicalRecords::PatientNotFound + end + + # Default exception handling + raise Common::Exceptions::BackendServiceException.new( + "MEDICALRECORDS_#{result.code}", + status: result.code, + detail: diagnostics, + source: self.class.to_s + ) + end + end + + ## + # Merge two FHIR bundles into one, with an updated total count. + # + # @param bundle1 [FHIR:Bundle] The first FHIR bundle + # @param bundle2 [FHIR:Bundle] The second FHIR bundle + # @param page_num [FHIR:Bundle] + # + def merge_bundles(bundle1, bundle2) + unless bundle1.resourceType == 'Bundle' && bundle2.resourceType == 'Bundle' + raise 'Both inputs must be FHIR Bundles' + end + + # Clone the first bundle to avoid modifying the original + merged_bundle = bundle1.clone + + # Merge the entries from the second bundle into the merged_bundle + merged_bundle.entry ||= [] + bundle2.entry&.each do |entry| + merged_bundle.entry << entry + end + + # Update the total count in the merged bundle + merged_bundle.total = merged_bundle.entry.count + + merged_bundle + end + + ## + # Apply pagination to the entries in a FHIR::Bundle object. This assumes sorting has already taken place. + # + # @param entries a list of FHIR objects + # @param page_size [Fixnum] page size + # @param page_num [Fixnum] which page to return + # + def paginate_bundle_entries(entries, page_size, page_num) + start_index = (page_num - 1) * page_size + end_index = start_index + page_size + paginated_entries = entries[start_index...end_index] + + # Return the paginated result or an empty array if no entries + paginated_entries || [] + end + + ## + # Sort the FHIR::Bundle entries on a given field and sort order. If a field is not present, that entry + # is sorted to the end. + # + # @param bundle [FHIR::Bundle] the bundle to sort + # @param field [Symbol, String] the field to sort on (supports nested fields with dot notation) + # @param order [Symbol] the sort order, :asc (default) or :desc + # + def sort_bundle(bundle, field, order = :asc) + field = field.to_s + sort_bundle_with_criteria(bundle, order) do |resource| + fetch_nested_value(resource, field) + end + end + + ## + # Sort the FHIR::Bundle entries based on a provided block. The block should handle different resource types + # and define how to extract the sorting value from each. + # + # @param bundle [FHIR::Bundle] the bundle to sort + # @param order [Symbol] the sort order, :asc (default) or :desc + # + def sort_bundle_with_criteria(bundle, order = :asc) + sorted_entries = bundle[:entry].sort do |entry1, entry2| + value1 = yield(entry1[:resource]) + value2 = yield(entry2[:resource]) + if value2.nil? + -1 + elsif value1.nil? + 1 + else + order == :asc ? value1 <=> value2 : value2 <=> value1 + end + end + bundle[:entry] = sorted_entries + bundle + end + + ## + # Fetches the value of a potentially nested field from a given object. + # + # @param object [Object] the object to fetch the value from + # @param field_path [String] the dot-separated path to the field + # + def fetch_nested_value(object, field_path) + field_path.split('.').reduce(object) do |obj, method| + obj.respond_to?(method) ? obj.send(method) : nil + end + end + + def measure_duration(event: 'default', tags: []) + # Use time since boot to avoid clock skew issues + # https://github.com/sidekiq/sidekiq/issues/3999 + start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + result = yield + duration = (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_time).round(4) + + StatsD.measure("api.mhv.lighthouse.#{event}.duration", duration, tags:) + result + end + end +end diff --git a/modules/my_health/app/controllers/my_health/mr_controller.rb b/modules/my_health/app/controllers/my_health/mr_controller.rb index 5beb4672e47..cf2a0b1bda6 100644 --- a/modules/my_health/app/controllers/my_health/mr_controller.rb +++ b/modules/my_health/app/controllers/my_health/mr_controller.rb @@ -3,6 +3,7 @@ require 'medical_records/client' require 'medical_records/bb_internal/client' require 'medical_records/phr_mgr/client' +require 'medical_records/lighthouse_client' module MyHealth class MrController < ApplicationController @@ -19,8 +20,17 @@ class MrController < ApplicationController protected def client - @client ||= MedicalRecords::Client.new(session: { user_id: current_user.mhv_correlation_id, - icn: current_user.icn }) + use_oh_data_path = Flipper.enabled?(:mhv_accelerated_delivery_allergies_enabled, @current_user) && + params[:use_oh_data_path].to_i == 1 + if @client.nil? + @client ||= if use_oh_data_path + MedicalRecords::LighthouseClient.new(current_user.icn) + else + MedicalRecords::Client.new(session: { user_id: current_user.mhv_correlation_id, + icn: current_user.icn }) + end + end + @client end def phrmgr_client diff --git a/modules/my_health/app/controllers/my_health/v1/allergies_controller.rb b/modules/my_health/app/controllers/my_health/v1/allergies_controller.rb index 54c6e7f3eed..489dacea404 100644 --- a/modules/my_health/app/controllers/my_health/v1/allergies_controller.rb +++ b/modules/my_health/app/controllers/my_health/v1/allergies_controller.rb @@ -9,7 +9,7 @@ def index end def show - allergy_id = params[:id].try(:to_i) + allergy_id = params[:id].try(:strip) resource = client.get_allergy(allergy_id) render json: resource.to_json end diff --git a/modules/my_health/spec/requests/my_health/v1/medical_records/allergies_spec.rb b/modules/my_health/spec/requests/my_health/v1/medical_records/allergies_spec.rb index ed9f7e699a9..f01963ebe85 100644 --- a/modules/my_health/spec/requests/my_health/v1/medical_records/allergies_spec.rb +++ b/modules/my_health/spec/requests/my_health/v1/medical_records/allergies_spec.rb @@ -99,4 +99,50 @@ end end end + + context 'Premium user when use_oh_data_path is true' do + let(:mhv_account_type) { 'Premium' } + let(:current_user) { build(:user, :mhv, va_patient:, mhv_account_type:, icn: '23000219') } + + before do + sign_in_as(current_user) + + allow(Flipper).to receive(:enabled?).with(:mhv_accelerated_delivery_allergies_enabled, + instance_of(User)).and_return(true) + allow(Flipper).to receive(:enabled?).with(:mhv_medical_records_new_eligibility_check).and_return(false) + end + + it 'responds to GET #index' do + VCR.use_cassette('mr_client/get_a_list_of_allergies_oh_data_path') do + get '/my_health/v1/medical_records/allergies?use_oh_data_path=1' + end + + expect(response).to be_successful + expect(response.body).to be_a(String) + + body = JSON.parse(response.body) + expect(body['entry']).to be_an(Array) + expect(body['entry'].size).to be 2 + + item = body['entry'][1] + expect(item['resource']['resourceType']).to eq('AllergyIntolerance') + expect(item['resource']['category'][0]).to eq('food') + end + + it 'responds to GET #show' do + allergy_id = '4-6Z8D6dAzABlkPZA' + + VCR.use_cassette('mr_client/get_an_allergy_oh_data_path') do + get "/my_health/v1/medical_records/allergies/#{allergy_id}?use_oh_data_path=1" + end + + expect(response).to be_successful + expect(response.body).to be_a(String) + + body = JSON.parse(response.body) + expect(body['resourceType']).to eq('AllergyIntolerance') + expect(body['id']).to eq(allergy_id) + expect(body['category'][0]).to eq('food') + end + end end diff --git a/spec/support/vcr_cassettes/mr_client/get_a_list_of_allergies_oh_data_path.yml b/spec/support/vcr_cassettes/mr_client/get_a_list_of_allergies_oh_data_path.yml new file mode 100644 index 00000000000..96935a5514e --- /dev/null +++ b/spec/support/vcr_cassettes/mr_client/get_a_list_of_allergies_oh_data_path.yml @@ -0,0 +1,134 @@ +--- +http_interactions: +- request: + method: post + uri: "/oauth2/health/system/v1/token" + body: + encoding: US-ASCII + string: grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=&scope=launch+patient%2FAllergyIntolerance.read+patient%2FDiagnosticReport.read+patient%2FPatient.read+system%2FPatient.read+patient%2FObservation.read+patient%2FPractitioner.read+patient%2FMedicationRequest.read+patient%2FCondition.read&launch=eyJwYXRpZW50IjogIjIzMDAwMjE5In0%3D + headers: + Accept: + - application/json + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - Vets.gov Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Wed, 30 Oct 2024 21:56:28 GMT + Content-Type: + - application/json; charset=utf-8 + Connection: + - keep-alive + X-Ratelimit-Limit-Minute: + - '240' + Ratelimit-Remaining: + - '238' + Ratelimit-Reset: + - '33' + Ratelimit-Limit: + - '240' + X-Ratelimit-Remaining-Minute: + - '238' + Vary: + - Origin + Cache-Control: + - no-cache, no-store + Pragma: + - no-cache + Etag: + - W/"5d3-sJz+xGHcUyvMDK5kBZg2AckMYjY" + Access-Control-Allow-Origin: + - "*" + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"access_token":"","token_type":"Bearer","scope":"launch + patient/Patient.read patient/Practitioner.read patient/AllergyIntolerance.read + patient/Observation.read patient/Condition.read patient/MedicationRequest.read + patient/DiagnosticReport.read system/Patient.read","expires_in":300,"state":null,"patient":"23000219"}' + recorded_at: Wed, 30 Oct 2024 21:56:28 GMT +- request: + method: get + uri: "/services/fhir/v0/r4/AllergyIntolerance?_count=100&patient=23000219" + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Vets.gov Agent + Authorization: Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: '' + headers: + Date: + - Wed, 30 Oct 2024 21:56:28 GMT + Content-Type: + - application/fhir+json;charset=UTF-8 + Content-Length: + - '3017' + Connection: + - keep-alive + X-Ratelimit-Remaining-Minute: + - '399' + X-Ratelimit-Limit-Minute: + - '400' + Ratelimit-Remaining: + - '399' + Ratelimit-Reset: + - '32' + Ratelimit-Limit: + - '400' + Pragma: + - no-cache + - no-cache + Content-Language: + - en-US + Yanartas-Response-Source: + - C:2 + - R:0 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - DENY + - SAMEORIGIN + Content-Security-Policy: + - script-src 'self' + Strict-Transport-Security: + - max-age=16000000; includeSubDomains; preload; + - max-age=31536000; includeSubDomains; preload + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - no-cache, no-store + body: + encoding: UTF-8 + string: '{"resourceType":"Bundle","type":"searchset","link":[{"relation":"self","url":"/services/fhir/v0/r4/AllergyIntolerance?patient=23000219&-pageToken=1~6Z807QTZrKsurkk"}],"entry":[{"fullUrl":"/services/fhir/v0/r4/AllergyIntolerance/4-6Z8D6dAzA9QPmy8","resource":{"resourceType":"AllergyIntolerance","id":"4-6Z8D6dAzA9QPmy8","meta":{"lastUpdated":"2022-11-25T00:00:00Z"},"clinicalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical","code":"active"}]},"verificationStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/allergyintolerance-verification","code":"confirmed"}]},"type":"allergy","category":["medication"],"code":{"coding":[{"system":"http://www.nlm.nih.gov/research/umls/rxnorm","code":"25037","display":"cefdinir"}],"text":"cefdinir"},"patient":{"reference":"/services/fhir/v0/r4/Patient/23000219","display":"Mr. + Dexter530 Victor265 Schneider199"},"recordedDate":"1967-05-28T12:24:29Z","recorder":{"reference":"/services/fhir/v0/r4/Practitioner/4-Nn79MgdpowOn","display":"Dr. + Regina408 Dietrich576"},"note":[{"authorReference":{"reference":"/services/fhir/v0/r4/Practitioner/4-Nn79MgdpowOn","display":"Dr. + Regina408 Dietrich576"},"time":"1967-05-28T12:24:29Z","text":"cefdinir"}]},"search":{"mode":"match"}},{"fullUrl":"/services/fhir/v0/r4/AllergyIntolerance/4-6Z8D6dAzABlkPZA","resource":{"resourceType":"AllergyIntolerance","id":"4-6Z8D6dAzABlkPZA","meta":{"lastUpdated":"2022-11-25T00:00:00Z"},"clinicalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical","code":"active"}]},"verificationStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/allergyintolerance-verification","code":"confirmed"}]},"type":"allergy","category":["food"],"code":{"coding":[{"system":"http://snomed.info/sct","code":"44027008","display":"Seafood + (substance)"}],"text":"Seafood (substance)"},"patient":{"reference":"/services/fhir/v0/r4/Patient/23000219","display":"Mr. + Dexter530 Victor265 Schneider199"},"recordedDate":"1967-05-28T12:24:29Z","recorder":{"reference":"/services/fhir/v0/r4/Practitioner/4-Nn79Mgdyt0Mj","display":"Dr. + Marietta439 Schmeler639 MD"},"note":[{"authorReference":{"reference":"/services/fhir/v0/r4/Practitioner/4-Nn79Mgdyt0Mj","display":"Dr. + Marietta439 Schmeler639 MD"},"time":"1967-05-28T12:24:29Z","text":"Seafood + (substance)"}],"reaction":[{"substance":{"coding":[{"system":"http://snomed.info/sct","code":"44027008","display":"Seafood + (substance)"}],"text":"Seafood (substance)"},"manifestation":[{"coding":[{"system":"urn:oid:2.16.840.1.113883.6.233","code":"4637470","display":"DYSPNEA"}],"text":"DYSPNEA"},{"coding":[{"system":"urn:oid:2.16.840.1.113883.6.233","code":"4538635","display":"RASH"}],"text":"RASH"}]}]},"search":{"mode":"match"}}]}' + recorded_at: Wed, 30 Oct 2024 21:56:28 GMT +recorded_with: VCR 6.3.1 diff --git a/spec/support/vcr_cassettes/mr_client/get_an_allergy_oh_data_path.yml b/spec/support/vcr_cassettes/mr_client/get_an_allergy_oh_data_path.yml new file mode 100644 index 00000000000..e8bb1e151aa --- /dev/null +++ b/spec/support/vcr_cassettes/mr_client/get_an_allergy_oh_data_path.yml @@ -0,0 +1,130 @@ +--- +http_interactions: +- request: + method: post + uri: "/oauth2/health/system/v1/token" + body: + encoding: US-ASCII + string: grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=&scope=launch+patient%2FAllergyIntolerance.read+patient%2FDiagnosticReport.read+patient%2FPatient.read+system%2FPatient.read+patient%2FObservation.read+patient%2FPractitioner.read+patient%2FMedicationRequest.read+patient%2FCondition.read&launch=eyJwYXRpZW50IjogIjIzMDAwMjE5In0%3D + headers: + Accept: + - application/json + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - Vets.gov Agent + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 04 Nov 2024 17:40:03 GMT + Content-Type: + - application/json; charset=utf-8 + Connection: + - keep-alive + X-Ratelimit-Remaining-Minute: + - '238' + X-Ratelimit-Limit-Minute: + - '240' + Ratelimit-Remaining: + - '238' + Ratelimit-Reset: + - '58' + Ratelimit-Limit: + - '240' + Vary: + - Origin + Cache-Control: + - no-cache, no-store + Pragma: + - no-cache + Etag: + - W/"5d3-DzQogy1tvDFTBObREM24uGP3yRA" + Access-Control-Allow-Origin: + - "*" + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: '{"access_token":"","token_type":"Bearer","scope":"launch + patient/Patient.read patient/Practitioner.read patient/AllergyIntolerance.read + patient/Observation.read patient/Condition.read patient/MedicationRequest.read + patient/DiagnosticReport.read system/Patient.read","expires_in":300,"state":null,"patient":"23000219"}' + recorded_at: Mon, 04 Nov 2024 17:40:03 GMT +- request: + method: get + uri: "/services/fhir/v0/r4/AllergyIntolerance/4-6Z8D6dAzABlkPZA" + body: + encoding: US-ASCII + string: '' + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - Vets.gov Agent + Authorization: Bearer + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: '' + headers: + Date: + - Mon, 04 Nov 2024 17:40:03 GMT + Content-Type: + - application/fhir+json;charset=UTF-8 + Content-Length: + - '1476' + Connection: + - keep-alive + X-Ratelimit-Remaining-Minute: + - '399' + Ratelimit-Limit: + - '400' + Ratelimit-Remaining: + - '399' + Ratelimit-Reset: + - '57' + X-Ratelimit-Limit-Minute: + - '400' + Pragma: + - no-cache + - no-cache + Content-Language: + - en-US + Yanartas-Response-Source: + - C:1 + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + X-Frame-Options: + - DENY + - SAMEORIGIN + Content-Security-Policy: + - script-src 'self' + Strict-Transport-Security: + - max-age=16000000; includeSubDomains; preload; + - max-age=31536000; includeSubDomains; preload + Access-Control-Allow-Origin: + - "*" + Cache-Control: + - no-cache, no-store + body: + encoding: UTF-8 + string: '{"resourceType":"AllergyIntolerance","id":"4-6Z8D6dAzABlkPZA","meta":{"lastUpdated":"2022-11-25T00:00:00Z"},"clinicalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical","code":"active"}]},"verificationStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/allergyintolerance-verification","code":"confirmed"}]},"type":"allergy","category":["food"],"code":{"coding":[{"system":"http://snomed.info/sct","code":"44027008","display":"Seafood + (substance)"}],"text":"Seafood (substance)"},"patient":{"reference":"/services/fhir/v0/r4/Patient/23000219","display":"Mr. + Dexter530 Victor265 Schneider199"},"recordedDate":"1967-05-28T12:24:29Z","recorder":{"reference":"/services/fhir/v0/r4/Practitioner/4-Nn79Mgdyt0Mj","display":"Dr. + Marietta439 Schmeler639 MD"},"note":[{"authorReference":{"reference":"/services/fhir/v0/r4/Practitioner/4-Nn79Mgdyt0Mj","display":"Dr. + Marietta439 Schmeler639 MD"},"time":"1967-05-28T12:24:29Z","text":"Seafood + (substance)"}],"reaction":[{"substance":{"coding":[{"system":"http://snomed.info/sct","code":"44027008","display":"Seafood + (substance)"}],"text":"Seafood (substance)"},"manifestation":[{"coding":[{"system":"urn:oid:2.16.840.1.113883.6.233","code":"4637470","display":"DYSPNEA"}],"text":"DYSPNEA"},{"coding":[{"system":"urn:oid:2.16.840.1.113883.6.233","code":"4538635","display":"RASH"}],"text":"RASH"}]}]}' + recorded_at: Mon, 04 Nov 2024 17:40:03 GMT +recorded_with: VCR 6.3.1