Skip to content

Commit

Permalink
New: [AEA-3751] - Modify the Spine Client to expose an interface to c…
Browse files Browse the repository at this point in the history
…all the 'Search' Spine interaction (#199)

## Summary

🎫 [AEA-3751](https://nhsd-jira.digital.nhs.uk/browse/AEA-3751) Modify
the Spine Client to expose an interface to call the 'Search' Spine
interaction

- ✨ New Feature
- ⚠️ Potential issues that might be caused by this change

### Details

Development implementation detail
- Implement this in the Spine Client package
- Copy the ebxml stuff from FHIR Facade
- Call clinical content view on spine (PRESCRIPTIONSEARCH_SM01) and
return the result
- Use the same certificate (from the current Spine client) and the ASID
which PfP uses (back end calls)
- We want to use the same re-try as the current getPrescriptions
interface

---------

Co-authored-by: jonathanwelch1-nhs <[email protected]>
  • Loading branch information
kris-szlapa and jonathanwelch1-nhs authored Sep 9, 2024
1 parent 10f1652 commit 8436248
Show file tree
Hide file tree
Showing 7 changed files with 536 additions and 23 deletions.
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"jest.jestCommandLine": "/workspaces/nhs-eps-spine-client/node_modules/.bin/jest --no-cache",
"jest.nodeEnv": {
"POWERTOOLS_DEV": true,
"NODE_OPTIONS": "--experimental-vm-modules"
}
}
143 changes: 122 additions & 21 deletions src/live-spine-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,84 @@ import axiosRetry from "axios-retry"
import {handleCallError, handleErrorResponse} from "./utils"
import Mustache from "mustache"
import CLINICAL_CONTENT_VIEW_TEMPLATE from "./resources/clinical_content_view"
import PRESCRIPTION_SEARCH_TEMPLATE from "./resources/prescription_search"

// timeout in ms to wait for response from spine to avoid lambda timeout
const SPINE_TIMEOUT = 45000

// Clinical Content View Globals
const CLINICAL_VIEW_REQUEST_PATH = "syncservice-pds/pds"

// Prescription Search Globals
const PRESCRIPTION_SEARCH_REQUEST_PATH = "syncservice-pds/pds"

export interface ClinicalViewParams {
requestId: string,
prescriptionId: string,
organizationId: string,
repeatNumber?: string,
sdsRoleProfileId: string,
sdsId: string,
jobRoleCode: string
requestId: string,
prescriptionId: string,
organizationId: string,
repeatNumber?: string,
sdsRoleProfileId: string,
sdsId: string,
jobRoleCode: string
}

interface ClinicalContentViewPartials {
messageGUID: string,
toASID: string,
fromASID: string
creationTime: string,
agentPersonSDSRoleProfileId: string,
agentPersonSDSId: string,
agentPersonJobRoleCode: string,
organizationId: string,
prescriptionId: string,
repeatNumber: string
messageGUID: string,
toASID: string,
fromASID: string
creationTime: string,
agentPersonSDSRoleProfileId: string,
agentPersonSDSId: string,
agentPersonJobRoleCode: string,
organizationId: string,
prescriptionId: string,
repeatNumber: string
}

export interface PrescriptionSearchParams {
requestId: string,
prescriptionId: string,
organizationId: string,
sdsRoleProfileId: string,
sdsId: string,
jobRoleCode: string,
nhsNumber?: string
dispenserOrg?: string
prescriberOrg?: string
releaseVersion?: string
prescriptionStatus?: string
prescriptionStatus1?: string
prescriptionStatus2?: string
prescriptionStatus3?: string
creationDateRange?: {
lowDate?: string
highDate?: string
}
mySiteOrganisation?: string
}

export interface PrescriptionSearchPartials {
messageGUID: string,
toASID: string,
fromASID: string
creationTime: string,
agentPersonSDSRoleProfileId: string,
agentPersonSDSId: string,
agentPersonJobRoleCode: string,
prescriptionId?: string
nhsNumber?: string
dispenserOrg?: string
prescriberOrg?: string
releaseVersion?: string
prescriptionStatus?: string
prescriptionStatus1?: string
prescriptionStatus2?: string
prescriptionStatus3?: string
creationDateRange?: {
lowDate?: string
highDate?: string
}
mySiteOrganisation?: string
}

export class LiveSpineClient implements SpineClient {
Expand Down Expand Up @@ -118,10 +168,9 @@ export class LiveSpineClient implements SpineClient {
timeout: SPINE_TIMEOUT
})

// This can be removed when https://nhsd-jira.digital.nhs.uk/browse/AEA-3448 is complete
handleErrorResponse(this.logger, response)
return response
} catch (error) {
} catch(error) {
handleCallError(this.logger, error)
}
}
Expand All @@ -131,14 +180,14 @@ export class LiveSpineClient implements SpineClient {
}

async getStatus(): Promise<SpineStatus> {
if (!this.isCertificateConfigured()) {
if(!this.isCertificateConfigured()) {
return {status: "pass", message: "Spine certificate is not configured"}
}

const axiosConfig: AxiosRequestConfig = {timeout: 20000}
let endpoint: string

if (process.env.healthCheckUrl === undefined) {
if(process.env.healthCheckUrl === undefined) {
axiosConfig.httpsAgent = this.httpsAgent
endpoint = this.getSpineEndpoint("healthcheck")
} else {
Expand Down Expand Up @@ -202,6 +251,58 @@ export class LiveSpineClient implements SpineClient {
}
}

async prescriptionSearch(
inboundHeaders: APIGatewayProxyEventHeaders,
params: PrescriptionSearchParams
): Promise<AxiosResponse> {
try {
const address = this.getSpineEndpoint(PRESCRIPTION_SEARCH_REQUEST_PATH)

const outboundHeaders = {
"nhsd-correlation-id": inboundHeaders["nhsd-correlation-id"],
"nhsd-request-id": inboundHeaders["nhsd-request-id"],
"x-request-id": inboundHeaders["x-request-id"],
"x-correlation-id": inboundHeaders["x-correlation-id"],
"SOAPAction": "urn:nhs:names:services:mmquery/PRESCRIPTIONSEARCH_SM01"
}

const partials: PrescriptionSearchPartials = {
messageGUID: params.requestId,
toASID: this.spineASID ?? "",
fromASID: this.spineASID ?? "",
creationTime: new Date().toISOString(),
agentPersonSDSRoleProfileId: params.sdsRoleProfileId,
agentPersonSDSId: params.sdsId,
agentPersonJobRoleCode: params.jobRoleCode,
prescriptionId: params.prescriptionId,
nhsNumber: params.nhsNumber,
dispenserOrg: params.dispenserOrg,
prescriberOrg: params.prescriberOrg,
releaseVersion: params.releaseVersion,
prescriptionStatus: params.prescriptionStatus,
prescriptionStatus1: params.prescriptionStatus1,
prescriptionStatus2: params.prescriptionStatus2,
prescriptionStatus3: params.prescriptionStatus3,
creationDateRange: params.creationDateRange,
mySiteOrganisation: params.mySiteOrganisation
}

const requestBody = Mustache.render(PRESCRIPTION_SEARCH_TEMPLATE, partials)

this.logger.info(`Making request to ${address}`)
const response = await this.axiosInstance.post(address, requestBody, {
headers: outboundHeaders,
httpsAgent: this.httpsAgent,
timeout: SPINE_TIMEOUT
})

handleErrorResponse(this.logger, response)
return response
} catch(error) {
handleCallError(this.logger, error)
}
}

onAxiosRetry = (retryCount, error) => {
this.logger.warn(error)
this.logger.warn(`Call to spine failed - retrying. Retry count ${retryCount}`)
Expand Down
114 changes: 114 additions & 0 deletions src/resources/prescription_search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* eslint-disable max-len */
export default `<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:hl7="urn:hl7-org:v3">
<SOAP-ENV:Header>
<wsa:MessageID>uuid:{{messageGUID}}</wsa:MessageID>
<wsa:Action>urn:nhs:names:services:mmquery/PRESCRIPTIONSEARCH_SM01</wsa:Action>
<wsa:To>https://pds-sync.national.ncrs.nhs.uk/syncservice-pds/pds</wsa:To>
<wsa:From>
<wsa:Address/>
</wsa:From>
<hl7:communicationFunctionRcv>
<hl7:device>
<hl7:id root="1.2.826.0.1285.0.2.0.107" extension="{{toASID}}"/>
</hl7:device>
</hl7:communicationFunctionRcv>
<hl7:communicationFunctionSnd>
<hl7:device>
<hl7:id root="1.2.826.0.1285.0.2.0.107" extension="{{fromASID}}"/>
</hl7:device>
</hl7:communicationFunctionSnd>
<wsa:ReplyTo>
<wsa:Address/>
</wsa:ReplyTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<PRESCRIPTIONSEARCH_SM01 xmlns="urn:hl7-org:v3">
<id root="{{messageGUID}}"/>
<creationTime value="{{creationTime}}"/>
<versionCode code="V3NPfIT4.2.00"/>
<interactionId root="2.16.840.1.113883.2.1.3.2.4.12" extension="PRESCRIPTIONSEARCH_SM01"/>
<processingCode code="P"/>
<processingModeCode code="T"/>
<acceptAckCode code="NE"/>
<communicationFunctionRcv>
<device classCode="DEV" determinerCode="INSTANCE">
<id root="1.2.826.0.1285.0.2.0.107" extension="{{toASID}}"/>
</device>
</communicationFunctionRcv>
<communicationFunctionSnd>
<device classCode="DEV" determinerCode="INSTANCE">
<id root="1.2.826.0.1285.0.2.0.107" extension="{{fromASID}}"/>
</device>
</communicationFunctionSnd>
<ControlActEvent classCode="CACT" moodCode="EVN">
<author typeCode="AUT">
<AgentPersonSDS classCode="AGNT">
<id root="1.2.826.0.1285.0.2.0.67" extension="{{agentPersonSDSRoleProfileId}}"/>
<agentPersonSDS classCode="PSN" determinerCode="INSTANCE">
<id root="1.2.826.0.1285.0.2.0.65" extension="{{sdsRoleProfileId}}"/>
</agentPersonSDS>
<part typeCode="PART">
<partSDSRole classCode="ROL">
<id root="1.2.826.0.1285.0.2.1.104" extension="{{agentPersonJobRoleCode}}"/>
</partSDSRole>
</part>
</AgentPersonSDS>
</author>
<author1 typeCode="AUT">
<AgentSystemSDS classCode="AGNT">
<agentSystemSDS classCode="DEV" determinerCode="INSTANCE">
<id root="1.2.826.0.1285.0.2.0.107" extension="{{fromASID}}"/>
</agentSystemSDS>
</AgentSystemSDS>
</author1>
<query>
{{#prescriptionId}}
<prescriptionId value="{{{prescriptionId}}}" />
{{/prescriptionId}}
{{#nhsNumber}}
<patientId value="{{{nhsNumber}}}" />
{{/nhsNumber}}
{{#dispenserOrg}}
<dispenserOrganisation value="{{{dispenserOrg}}}" />
{{/dispenserOrg}}
{{#prescriberOrg}}
<prescriberOrganisation value="{{{prescriberOrg}}}"/>
{{/prescriberOrg}}
{{#releaseVersion}}
<releaseVersion value="{{{releaseVersion}}}" />
{{/releaseVersion}}
{{#prescriptionStatus}}
<prescriptionStatus value="{{{prescriptionStatus}}}" />
{{/prescriptionStatus}}
{{#prescriptionStatus1}}
<prescriptionStatus value="{{{prescriptionStatus1}}}" />
{{/prescriptionStatus1}}
{{#prescriptionStatus2}}
<prescriptionStatus value="{{{prescriptionStatus2}}}" />
{{/prescriptionStatus2}}
{{#prescriptionStatus3}}
<prescriptionStatus value="{{{prescriptionStatus3}}}" />
{{/prescriptionStatus3}}
{{#creationDateRange}}
<creationDateRange>
<value>
<window>
{{#lowDate}}
<low value = "{{{lowDate}}}" />
{{/lowDate}}
{{#highDate}}
<high value = "{{{highDate}}}" />
{{/highDate}}
</window>
</value>
</creationDateRange>
{{/creationDateRange}}
{{#mySiteOrganisation}}
<mySiteOrganisation value="{{{mySiteOrganisation}}}"/>
{{/mySiteOrganisation}}
</query>
</ControlActEvent>
</PRESCRIPTIONSEARCH_SM01>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
Loading

0 comments on commit 8436248

Please sign in to comment.