Skip to content
Open
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
6 changes: 3 additions & 3 deletions apim/specification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ components:
- DocumentReference
id:
type: string
pattern: '16521000000101~[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}'
pattern: '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}'
description: 'The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes.'
meta:
$ref: '#/components/schemas/Meta'
Expand Down Expand Up @@ -1198,7 +1198,7 @@ components:
- div
DocumentId:
type: string
pattern: '16521000000101~[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}'
pattern: '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}'
RequestPathParams:
type: object
properties:
Expand Down Expand Up @@ -1269,7 +1269,7 @@ components:
examples:
valid:
summary: Valid ID, document held, user has correct auth (200)
value: 16521000000101~f9ed81db-f90a-42d4-b7e4-d554d8f338fd
value: f9ed81db-f90a-42d4-b7e4-d554d8f338fd
invalid_400:
summary: Invalid request, no ID present (400)
value: 400
Expand Down
65 changes: 41 additions & 24 deletions lambdas/handlers/get_fhir_document_reference_handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from enums.lambda_error import LambdaError
from enums.mtls import MtlsCommonNames
from enums.snomed_codes import SnomedCode, SnomedCodes
from oauthlib.oauth2 import WebApplicationClient
from services.base.ssm_service import SSMService
from services.dynamic_configuration_service import DynamicConfigurationService
Expand All @@ -15,6 +17,7 @@
SearchPatientException,
)
from utils.lambda_handler_utils import extract_bearer_token
from utils.lambda_header_utils import validate_common_name_in_mtls
from utils.lambda_response import ApiGatewayResponse

logger = LoggingService(__name__)
Expand All @@ -29,61 +32,69 @@
"APPCONFIG_ENVIRONMENT",
"PRESIGNED_ASSUME_ROLE",
"CLOUDFRONT_URL",
]
],
)
def lambda_handler(event, context):
try:
common_name = validate_common_name_in_mtls(event.get("requestContext"))
bearer_token = extract_bearer_token(event, context)
selected_role_id = event.get("headers", {}).get("cis2-urid", None)

document_id, snomed_code = extract_document_parameters(event)
snomed_code = _determine_document_type(common_name=common_name)

document_id = extract_document_parameters(event)

get_document_service = GetFhirDocumentReferenceService()
document_reference = get_document_service.handle_get_document_reference_request(
snomed_code, document_id
snomed_code.code,
document_id,
)

if selected_role_id and bearer_token:
verify_user_authorisation(
bearer_token, selected_role_id, document_reference.nhs_number
bearer_token,
selected_role_id,
document_reference.nhs_number,
)

document_reference_response = (
get_document_service.create_document_reference_fhir_response(
document_reference
document_reference,
)
)

logger.info(
f"Successfully retrieved document reference for document_id: {document_id}, snomed_code: {snomed_code}"
f"Successfully retrieved document reference for document_id: {document_id}, snomed_code: {snomed_code}",
)

return ApiGatewayResponse(
status_code=200, body=document_reference_response, methods="GET"
status_code=200,
body=document_reference_response,
methods="GET",
).create_api_gateway_response()

except GetFhirDocumentReferenceException as exception:
return ApiGatewayResponse(
status_code=exception.status_code,
body=exception.error.create_error_response().create_error_fhir_response(
exception.error.value.get("fhir_coding")
exception.error.value.get("fhir_coding"),
),
methods="GET",
).create_api_gateway_response()


def extract_document_parameters(event):
"""Extract document ID and SNOMED code from path parameters"""
path_params = event.get("pathParameters", {}).get("id", None)
document_id, snomed_code = get_id_and_snomed_from_path_parameters(path_params)
document_id = event.get("pathParameters", {}).get("id", None)

if not document_id or not snomed_code:
logger.error("Missing document id or snomed code in request path parameters.")
if not document_id:
logger.error("Missing document id in request path parameters.")
raise GetFhirDocumentReferenceException(
400, LambdaError.DocumentReferenceMissingParameters
400,
LambdaError.DocumentReferenceMissingParameters,
)

return document_id, snomed_code
return document_id


def verify_user_authorisation(bearer_token, selected_role_id, nhs_number):
Expand All @@ -100,27 +111,33 @@ def verify_user_authorisation(bearer_token, selected_role_id, nhs_number):

org_ods_code = oidc_service.fetch_user_org_code(userinfo, selected_role_id)
smartcard_role_code, _ = oidc_service.fetch_user_role_code(
userinfo, selected_role_id, "R"
userinfo,
selected_role_id,
"R",
)
except (OidcApiException, AuthorisationException) as e:
logger.error(f"Authorization error: {str(e)}")
raise GetFhirDocumentReferenceException(
403, LambdaError.DocumentReferenceUnauthorised
403,
LambdaError.DocumentReferenceUnauthorised,
)

try:
search_patient_service = SearchPatientDetailsService(
smartcard_role_code, org_ods_code
smartcard_role_code,
org_ods_code,
)
search_patient_service.handle_search_patient_request(nhs_number, False)
except SearchPatientException as e:
raise GetFhirDocumentReferenceException(e.status_code, e.error)


def get_id_and_snomed_from_path_parameters(path_parameters):
"""Extract document ID and SNOMED code from path parameters"""
if path_parameters:
params = path_parameters.split("~")
if len(params) == 2:
return params[1], params[0]
return None, None
def _determine_document_type(common_name: MtlsCommonNames | None) -> SnomedCode:
if not common_name:
return SnomedCodes.LLOYD_GEORGE.value

if common_name not in MtlsCommonNames:
logger.error(f"mTLS common name {common_name} - is not supported")
raise GetFhirDocumentReferenceException(400, LambdaError.DocRefInvalidType)

return SnomedCodes.PATIENT_DATA.value
61 changes: 34 additions & 27 deletions lambdas/models/fhir/R4/fhir_document_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class ContentStabilityExtensionValueCodeableConcept(CodeableConcept):
"""CodeableConcept for content stability."""

coding: List[ContentStabilityExtensionCoding] = Field(
default_factory=lambda: [ContentStabilityExtensionCoding()]
default_factory=lambda: [ContentStabilityExtensionCoding()],
)


Expand All @@ -73,7 +73,7 @@ class ContentStabilityExtension(Extension):

url: Literal[CONTENT_STABILITY_URL] = CONTENT_STABILITY_URL
valueCodeableConcept: ContentStabilityExtensionValueCodeableConcept = Field(
default_factory=ContentStabilityExtensionValueCodeableConcept
default_factory=ContentStabilityExtensionValueCodeableConcept,
)


Expand Down Expand Up @@ -136,8 +136,7 @@ def extract_nhs_number_from_fhir(self) -> str:
and self.subject.identifier.system == "https://fhir.nhs.uk/Id/nhs-number"
):
return self.subject.identifier.value
else:
raise FhirDocumentReferenceException("NHS number was not found")
raise FhirDocumentReferenceException("NHS number was not found")


class DocumentReferenceInfo(BaseModel):
Expand Down Expand Up @@ -167,7 +166,7 @@ def _create_identifier(self, system_suffix: str, value: str) -> Dict[str, Any]:
"identifier": {
"system": f"{FHIR_BASE_URL}/{system_suffix}",
"value": value,
}
},
}

def _create_snomed_coding(self, snomed_code: SnomedCode) -> List[Dict[str, str]]:
Expand All @@ -184,7 +183,7 @@ def _create_snomed_coding(self, snomed_code: SnomedCode) -> List[Dict[str, str]]
"system": SNOMED_URL,
"code": snomed_code.code,
"display": snomed_code.display_name,
}
},
]

def create_nrl_fhir_document_reference_object(self) -> DocumentReference:
Expand All @@ -204,25 +203,27 @@ def create_nrl_fhir_document_reference_object(self) -> DocumentReference:
subject=Reference(**self._create_identifier("nhs-number", self.nhs_number)),
content=[DocumentReferenceContent(attachment=self.attachment)],
custodian=Reference(
**self._create_identifier("ods-organization-code", self.custodian)
**self._create_identifier("ods-organization-code", self.custodian),
),
type=CodeableConcept(
coding=self._create_snomed_coding(self.snomed_code_doc_type)
coding=self._create_snomed_coding(self.snomed_code_doc_type),
),
category=[
CodeableConcept(
coding=self._create_snomed_coding(self.snomed_code_category)
)
coding=self._create_snomed_coding(self.snomed_code_category),
),
],
author=[
Reference(
**self._create_identifier("ods-organization-code", self.custodian)
)
**self._create_identifier("ods-organization-code", self.custodian),
),
],
context=DocumentReferenceContext(
practiceSetting=CodeableConcept(
coding=self._create_snomed_coding(self.snomed_code_practice_setting)
)
coding=self._create_snomed_coding(
self.snomed_code_practice_setting,
),
),
),
)

Expand All @@ -233,7 +234,8 @@ def create_nrl_fhir_document_reference_object(self) -> DocumentReference:
return fhir_document_ref

def create_fhir_document_reference_object(
self, document: NdrDocumentReference
self,
document: NdrDocumentReference,
) -> DocumentReference:
"""Create a FHIR DocumentReference .

Expand All @@ -245,46 +247,51 @@ def create_fhir_document_reference_object(

return DocumentReference(
resourceType="DocumentReference",
id=f"{self.snomed_code_doc_type.code}~{document.id}",
id=document.id,
docStatus=document.doc_status,
type=CodeableConcept(
coding=self._create_snomed_coding(self.snomed_code_doc_type)
coding=self._create_snomed_coding(self.snomed_code_doc_type),
),
subject=Reference(**self._create_identifier("nhs-number", self.nhs_number)),
content=[DocumentReferenceContent(attachment=self.attachment)],
date=document.created,
author=[
Reference(
**self._create_identifier(
"ods-organization-code", document.author or self.custodian
)
)
"ods-organization-code",
document.author or self.custodian,
),
),
],
custodian=Reference(
**self._create_identifier(
"ods-organization-code", document.custodian or self.custodian
)
"ods-organization-code",
document.custodian or self.custodian,
),
),
meta=Meta(versionId=document.version),
)

def create_fhir_document_reference_object_basic(
self, original_id: str, original_version
self,
original_id: str,
original_version,
) -> DocumentReference:
return DocumentReference(
resourceType="DocumentReference",
id=f"{original_id}",
type=CodeableConcept(
coding=self._create_snomed_coding(self.snomed_code_doc_type)
coding=self._create_snomed_coding(self.snomed_code_doc_type),
),
subject=Reference(**self._create_identifier("nhs-number", self.nhs_number)),
content=[DocumentReferenceContent(attachment=self.attachment)],
author=[
Reference(
**self._create_identifier(
"ods-organization-code", self.author or self.custodian
)
)
"ods-organization-code",
self.author or self.custodian,
),
),
],
meta=Meta(versionId=original_version),
)
6 changes: 1 addition & 5 deletions lambdas/services/document_reference_search_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,7 @@ def create_document_reference_fhir_response(
title=document_reference.file_name,
creation=document_reference.document_scan_creation
or document_reference.created,
url=document_retrieve_endpoint
+ "/"
+ document_reference.document_snomed_code_type
+ "~"
+ document_reference.id,
url=document_retrieve_endpoint + "/" + document_reference.id,
)
fhir_document_reference = (
DocumentReferenceInfo(
Expand Down
Loading
Loading