Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app/jobs/pds_lookup_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

class PDSLookupJob < ApplicationJob
include GoodJob::ActiveJobExtensions::Concurrency

queue_as :pds

# NHS API imposes a limit of 5 requests per second
good_job_control_concurrency_with perform_limit: 5,
perform_throttle: [5, 1.second],
key: -> { queue_name }

# Because the NHS API imposes a limit of 5 requests per second, we're almost
# certain to hit throttling and the default exponential backoff strategy
# appears to trigger more race conditions in the job performing code, meaning
# there’s more instances where more than 5 requests are attempted.
retry_on GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError,
attempts: :unlimited,
wait: ->(_) { rand(0.5..5) }

def perform(**args)
NHS::PDS::Patient.find_by(**args)
end
end
23 changes: 23 additions & 0 deletions app/lib/nhs/pds/patient.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
# frozen_string_literal: true

module NHS::PDS::Patient
SEARCH_FIELDS = %w[
_fuzzy-match
_exact-match
_history
_max-results
family
given
gender
birthdate
death-date
email
phone
address-postcode
general-practitioner
].freeze
class << self
def find(nhs_number)
NHS::PDS.connection.get("Patient/#{nhs_number}")
end

def find_by(**attributes)
if (missing_attrs = (attributes.keys.map(&:to_s) - SEARCH_FIELDS)).any?
raise "Unrecognised attributes: #{missing_attrs.join(", ")}"
end

NHS::PDS.connection.get("Patient", attributes)
end
end
end
12 changes: 2 additions & 10 deletions app/models/pds/patient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,8 @@ class PDS::Patient

class << self
def find(nhs_number)
response =
JSON.parse(
Net::HTTP.get(
URI(
"https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient/#{nhs_number}"
),
"X-Request-ID": SecureRandom.uuid
)
)
from_pds_fhir_response(response)
response = NHS::PDS::Patient.find(nhs_number)
from_pds_fhir_response(JSON.parse(response.body))
end

private
Expand Down
31 changes: 31 additions & 0 deletions lib/tasks/pds.rake
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,36 @@ namespace :pds do
puts response.body
end
end

desc "Find patient using patient info"
task find_by: :environment do |_, _args|
query = {
"_fuzzy-match" => ENV["_fuzzy_match"],
"_exact-match" => ENV["_exact_match"],
"_history" => ENV["_history"],
"_max-results" => ENV["_max_results"],
"given" => ENV["given"],
"family" => ENV["family"],
"gender" => ENV["gender"],
"birthdate" => ENV["birthdate"],
"death-date" => ENV["death_date"],
"email" => ENV["email"],
"phone" => ENV["phone"],
"address-postcode" => ENV["address_postcode"],
"general-practitioner" => ENV["general_practitioner"]
}.compact
response = NHS::PDS::Patient.find_by(**query)

$stdout.puts response.status unless response.status == 200
if $stdout.tty?
puts response.env.url
puts ""
puts response.headers.map { "#{_1}: #{_2}" }
puts ""
puts JSON.pretty_generate(JSON.parse(response.body))
else
puts response.body
end
end
end
end
125 changes: 125 additions & 0 deletions spec/fixtures/patient_record.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
{
"entry": [
{
"fullUrl": "https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient/9449306168",
"resource": {
"address": [
{
"extension": [
{
"extension": [
{
"url": "type",
"valueCoding": {
"code": "PAF",
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-AddressKeyType"
}
},
{
"url": "value",
"valueString": "07352931"
}
],
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-AddressKey"
}
],
"id": "gon1",
"line": ["103 PRESTON LANE", "TADWORTH", "SURREY"],
"period": {
"start": "2011-06-23"
},
"postalCode": "KT20 5HJ",
"use": "home"
}
],
"birthDate": "1939-01-09",
"gender": "female",
"generalPractitioner": [
{
"id": "dp7s",
"identifier": {
"period": {
"start": "1951-08-09"
},
"system": "https://fhir.nhs.uk/Id/ods-organization-code",
"value": "H81109"
},
"type": "Organization"
}
],
"id": "9449306168",
"identifier": [
{
"extension": [
{
"url": "https://fhir.hl7.org.uk/StructureDefinition/Extension-UKCore-NHSNumberVerificationStatus",
"valueCodeableConcept": {
"coding": [
{
"code": "01",
"display": "Number present and verified",
"system": "https://fhir.hl7.org.uk/CodeSystem/UKCore-NHSNumberVerificationStatus",
"version": "1.0.0"
}
]
}
}
],
"system": "https://fhir.nhs.uk/Id/nhs-number",
"value": "9449306168"
}
],
"meta": {
"security": [
{
"code": "U",
"display": "unrestricted",
"system": "http://terminology.hl7.org/CodeSystem/v3-Confidentiality"
}
],
"versionId": "6"
},
"name": [
{
"family": "LAWMAN",
"given": ["ELDREDA"],
"id": "frs8",
"period": {
"start": "1978-06-14"
},
"prefix": ["MS"],
"use": "usual"
}
],
"resourceType": "Patient",
"telecom": [
{
"id": "6FF70BFB",
"period": {
"start": "2021-12-15"
},
"system": "email",
"use": "home",
"value": "jagdish.khunti1@nhs.net"
},
{
"id": "4B88618D",
"period": {
"start": "2021-12-15"
},
"system": "phone",
"use": "mobile",
"value": "07463657671"
}
]
},
"search": {
"score": 1
}
}
],
"resourceType": "Bundle",
"timestamp": "2024-08-31T10:40:34+00:00",
"total": 1,
"type": "searchset"
}
11 changes: 11 additions & 0 deletions spec/jobs/pds_lookup_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

describe PDSLookupJob, type: :job do
it "calls the NHS::PDS::Patient.find_by" do
allow(NHS::PDS::Patient).to receive(:find_by)

described_class.perform_now(given: "name")

expect(NHS::PDS::Patient).to have_received(:find_by).with(given: "name")
end
end
64 changes: 64 additions & 0 deletions spec/lib/nhs/pds/patient_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

describe NHS::PDS::Patient do
before do
allow(NHS::API).to receive(:connection).and_return(
Faraday.new do |builder|
stubbed_requests.each do |request, response|
builder.adapter :test do |stub|
stub.get(request) { response }
end
end
end
)
end

describe ".find" do
let(:stubbed_requests) do
[
[
"https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient/9449306168",
[200, {}, "patient record as json"]
]
]
end

it "sends a GET request to retrieve a patient by their NHS number" do
response = described_class.find("9449306168").body

expect(response).to eq "patient record as json"
end
end

describe ".find_by" do
let(:stubbed_requests) do
[
[
"https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient",
[200, {}, "patient record as json"]
]
]
end

it "sends a GET request to with the provided attributes" do
response =
described_class.find_by(
family: "Lawman",
gender: "female",
birthdate: "eq1939-01-09"
)

expect(response.body).to eq "patient record as json"
end

it "raises an error if an unrecognised attribute is provided" do
expect {
described_class.find_by(
given: "Eldreda",
family_name: "Lawman",
date_of_birth: "1939-01-09"
)
}.to raise_error("Unrecognised attributes: family_name, date_of_birth")
end
end
end
22 changes: 1 addition & 21 deletions spec/lib/nhs/pds_spec.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
# frozen_string_literal: true

describe NHS::PDS do
before do
allow(NHS::API).to receive(:connection).and_return(
Faraday.new do |builder|
builder.adapter :test do |stub|
stub.get(
"https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient/9000000009"
) { [200, {}, {}.to_json] }
end
end
)
end
before { allow(NHS::API).to receive(:connection).and_return(Faraday.new) }

describe ".connection" do
it "sets the url" do
Expand All @@ -20,14 +10,4 @@
).to eq "https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4"
end
end

describe NHS::PDS::Patient do
describe ".find_patient" do
it "sends a GET request to retrieve a patient by their NHS number" do
response = described_class.find("9000000009").body

expect(response).to eq "{}"
end
end
end
end
21 changes: 8 additions & 13 deletions spec/models/pds/patient_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,20 @@
File.read("spec/support/pds-get-patient-response.json")
end
let(:request_id) { "123e4567-e89b-12d3-a456-426614174000" }
let(:patient_json) do
File.read(Rails.root.join("spec/fixtures/patient_record.json"))
end

before do
allow(SecureRandom).to receive(:uuid).and_return(request_id)
stub_request(
:get,
"https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient/9000000009"
).with(headers: { "X-Request-ID" => request_id }).to_return(
status: 200,
body: json_response
allow(NHS::PDS::Patient).to receive(:find).and_return(
instance_double(Faraday::Response, status: 200, body: json_response)
)
end

it "returns a patient with the correct attributes" do
patient = described_class.find("9000000009")
it "calls find_patient on PDS library" do
described_class.find("9449306168")

expect(patient.nhs_number).to eq("9000000009")
expect(patient.given_name).to eq("Jane")
expect(patient.family_name).to eq("Smith")
expect(patient.date_of_birth).to eq("2010-10-22")
expect(NHS::PDS::Patient).to have_received(:find).with("9449306168")
end
end
end