From 14030583e764be6f674275ae571b5a06eeb44db5 Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Wed, 11 Feb 2026 14:18:28 +0000 Subject: [PATCH 1/8] [NDR-375] Author and custodian should be optional --- .../models/fhir/R4/fhir_document_reference.py | 93 ++++++++++++------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/lambdas/models/fhir/R4/fhir_document_reference.py b/lambdas/models/fhir/R4/fhir_document_reference.py index cf86f8d700..e02172985e 100644 --- a/lambdas/models/fhir/R4/fhir_document_reference.py +++ b/lambdas/models/fhir/R4/fhir_document_reference.py @@ -7,6 +7,7 @@ CodeableConcept, Coding, Extension, + Identifier, Meta, Period, Reference, @@ -64,7 +65,7 @@ class ContentStabilityExtensionValueCodeableConcept(CodeableConcept): """CodeableConcept for content stability.""" coding: List[ContentStabilityExtensionCoding] = Field( - default_factory=lambda: [ContentStabilityExtensionCoding()] + default_factory=lambda: [ContentStabilityExtensionCoding()], ) @@ -73,7 +74,7 @@ class ContentStabilityExtension(Extension): url: Literal[CONTENT_STABILITY_URL] = CONTENT_STABILITY_URL valueCodeableConcept: ContentStabilityExtensionValueCodeableConcept = Field( - default_factory=ContentStabilityExtensionValueCodeableConcept + default_factory=ContentStabilityExtensionValueCodeableConcept, ) @@ -167,7 +168,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]]: @@ -184,7 +185,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: @@ -204,25 +205,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 + ), + ), ), ) @@ -233,58 +236,80 @@ 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 . Returns: DocumentReference: A FHIR DocumentReference resource """ - if document.custodian in PatientOdsInactiveStatus.list(): - document.custodian = PCSE_ODS_CODE + author_ods = None + custodian_ods = None + if self.snomed_code_doc_type.code != SnomedCodes.PATIENT_DATA.value.code: + if document.custodian in PatientOdsInactiveStatus.list(): + document.custodian = PCSE_ODS_CODE + + author_ods = document.author or self.custodian + custodian_ods = document.custodian or self.custodian + else: + if document.author: + author_ods = document.author + if document.custodian: + custodian_ods = document.custodian - return DocumentReference( + fhir_doc_ref = DocumentReference( resourceType="DocumentReference", id=f"{self.snomed_code_doc_type.code}~{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 - ) - ) - ], - custodian=Reference( - **self._create_identifier( - "ods-organization-code", document.custodian or self.custodian - ) - ), meta=Meta(versionId=document.version), ) + if author_ods: + fhir_doc_ref.author = [ + Reference( + identifier=Identifier( + system=f"{FHIR_BASE_URL}/ods-organization-code", + value=author_ods, + ), + ), + ] + if custodian_ods: + fhir_doc_ref.custodian = Reference( + identifier=Identifier( + system=f"{FHIR_BASE_URL}/ods-organization-code", + value=custodian_ods, + ), + ) + + return fhir_doc_ref + 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), ) From eea3f5485e564bae22694bc519e5f1eca4c95b98 Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Wed, 11 Feb 2026 16:55:05 +0000 Subject: [PATCH 2/8] [NDR-365] Make format --- lambdas/models/fhir/R4/fhir_document_reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambdas/models/fhir/R4/fhir_document_reference.py b/lambdas/models/fhir/R4/fhir_document_reference.py index e02172985e..a64967bd16 100644 --- a/lambdas/models/fhir/R4/fhir_document_reference.py +++ b/lambdas/models/fhir/R4/fhir_document_reference.py @@ -223,7 +223,7 @@ def create_nrl_fhir_document_reference_object(self) -> DocumentReference: context=DocumentReferenceContext( practiceSetting=CodeableConcept( coding=self._create_snomed_coding( - self.snomed_code_practice_setting + self.snomed_code_practice_setting, ), ), ), From 91f365754fcaf7d25889620d5be0103acf87f46b Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Thu, 12 Feb 2026 15:37:48 +0000 Subject: [PATCH 3/8] [NDR-375] Test that FHIR is returned correctly. --- .../fhir/R4/test_fhir_document_reference.py | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py index 5eced85853..07942dd372 100644 --- a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py +++ b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py @@ -1,9 +1,15 @@ +from enums.snomed_codes import SnomedCodes, SnomedCode import pytest +from models.document_reference import DocumentReference from models.fhir.R4.base_models import Reference from models.fhir.R4.fhir_document_reference import ( DocumentReference as FhirDocumentReference, + DocumentReferenceInfo, +) +from tests.unit.helpers.data.test_documents import ( + create_valid_fhir_doc_json, + create_test_doc_refs, ) -from tests.unit.helpers.data.test_documents import create_valid_fhir_doc_json from utils.exceptions import FhirDocumentReferenceException @@ -46,3 +52,61 @@ def test_extract_nhs_number_from_fhir_returns_nhs_number( result = valid_fhir_doc_object.extract_nhs_number_from_fhir() assert result == valid_nhs_number + + +@pytest.mark.parametrize( + ["exclude", "include_author_and_custodian", "snomed"], + [ + ( + ["author", "custodian"], + False, + SnomedCodes.PATIENT_DATA.value, + ), + ( + [], + False, + SnomedCodes.PATIENT_DATA.value, + ), + ( + [], + True, + SnomedCodes.LLOYD_GEORGE.value, + ), + ], +) +def test_create_fhir_document_reference_object( + exclude, include_author_and_custodian, snomed +): + doc_refs = create_test_doc_refs() + doc_ref = doc_refs[0] + doc_ref_info = DocumentReferenceInfo( + nhs_number=doc_ref.nhs_number, + snomed_code_doc_type=snomed, + ) + for key in exclude: + if key in doc_ref: + doc_ref.pop(key) + + if include_author_and_custodian: + setattr(doc_ref, "author", "foobar") + setattr(doc_ref, "custodian", "foobar") + + doc_ref_info = DocumentReferenceInfo( + nhs_number=doc_ref.nhs_number, + snomed_code_doc_type=snomed, + custodian=doc_ref.custodian, + author=doc_ref.author, + ) + + fhir_doc = doc_ref_info.create_fhir_document_reference_object( + doc_ref, + ).model_dump_json( + exclude_none=True, + ) + + for key in exclude: + assert key not in fhir_doc + + if include_author_and_custodian: + assert "author" in fhir_doc + assert "custodian" in fhir_doc From 1eb029505ac7b74c200b0541184a385ee8cc464b Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Thu, 12 Feb 2026 15:41:42 +0000 Subject: [PATCH 4/8] [NDR-375] Add LG --- .../fhir/R4/test_fhir_document_reference.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py index 07942dd372..5eceaa5467 100644 --- a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py +++ b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py @@ -67,6 +67,21 @@ def test_extract_nhs_number_from_fhir_returns_nhs_number( False, SnomedCodes.PATIENT_DATA.value, ), + ( + [], + True, + SnomedCodes.PATIENT_DATA.value, + ), + ( + ["author", "custodian"], + False, + SnomedCodes.LLOYD_GEORGE.value, + ), + ( + [], + False, + SnomedCodes.LLOYD_GEORGE.value, + ), ( [], True, From 939679bbc2247cf62ff099316977a66e798fb61b Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Thu, 12 Feb 2026 16:19:46 +0000 Subject: [PATCH 5/8] [NDR-375] e2e tests --- ...test_retrieve_document_fhir_api_success.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py b/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py index eb083012d8..5d542c4152 100644 --- a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py +++ b/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py @@ -65,3 +65,27 @@ def test_file_retrieval(test_data, file_size): else: expected_presign_uri = f"https://{PDM_S3_BUCKET}.s3.eu-west-2.amazonaws.com/{pdm_record['nhs_number']}/{pdm_record['id']}" assert expected_presign_uri in json["content"][0]["attachment"]["url"] + + +@pytest.mark.parametrize( + "author, custodian", + [ + (None, None), + ("", ""), + ("foobar", "foobar"), + ], +) +def test_successful_retrieval_of_document_reference_with_and_without_author_and_custodian( + test_data, author, custodian +): + pdm_record = create_and_store_pdm_record( + test_data, dynamo_kwargs={"author": author, "custodian": custodian} + ) + + response = get_pdm_document_reference(record_id=pdm_record["id"]) + assert response.status_code == 200 + + response_json = response.json() + assert_returned_document_reference(pdm_record, response_json) + print(response_json) + assert response_json.get("docStatus") == doc_status From 696e938a8c309af16d20416b8cc3318c5f491b63 Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Thu, 12 Feb 2026 17:54:44 +0000 Subject: [PATCH 6/8] [NDR-375] PDM e2e --- .../test_retrieve_document_fhir_api_success.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py b/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py index 5d542c4152..6a2c0ddf99 100644 --- a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py +++ b/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py @@ -70,7 +70,6 @@ def test_file_retrieval(test_data, file_size): @pytest.mark.parametrize( "author, custodian", [ - (None, None), ("", ""), ("foobar", "foobar"), ], @@ -79,7 +78,7 @@ def test_successful_retrieval_of_document_reference_with_and_without_author_and_ test_data, author, custodian ): pdm_record = create_and_store_pdm_record( - test_data, dynamo_kwargs={"author": author, "custodian": custodian} + test_data, Author=author, Custodian=custodian ) response = get_pdm_document_reference(record_id=pdm_record["id"]) @@ -87,5 +86,13 @@ def test_successful_retrieval_of_document_reference_with_and_without_author_and_ response_json = response.json() assert_returned_document_reference(pdm_record, response_json) - print(response_json) - assert response_json.get("docStatus") == doc_status + if author: + author_res = response_json.get("author", None) + assert author_res[0]["identifier"]["value"] == author + else: + assert "author" not in response_json + if custodian: + cust_res = response_json.get("custodian", None) + assert cust_res["identifier"]["value"] == custodian + else: + assert "custodian" not in response_json From 9c66b9dbf915d0bec807369816821d4cc9df7fb2 Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Fri, 13 Feb 2026 11:59:23 +0000 Subject: [PATCH 7/8] [NDR-375] Make format --- .../models/fhir/R4/fhir_document_reference.py | 3 +-- .../test_retrieve_document_fhir_api_success.py | 17 ++++++++++++----- .../fhir/R4/test_fhir_document_reference.py | 14 ++++++++------ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/lambdas/models/fhir/R4/fhir_document_reference.py b/lambdas/models/fhir/R4/fhir_document_reference.py index a64967bd16..d18e0a3c5f 100644 --- a/lambdas/models/fhir/R4/fhir_document_reference.py +++ b/lambdas/models/fhir/R4/fhir_document_reference.py @@ -137,8 +137,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): diff --git a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py b/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py index 6a2c0ddf99..c2a92bc486 100644 --- a/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py +++ b/lambdas/tests/e2e/api/fhir/test_retrieve_document_fhir_api_success.py @@ -3,9 +3,9 @@ import pytest from tests.e2e.api.fhir.conftest import ( PDM_S3_BUCKET, + PDM_SNOMED, create_and_store_pdm_record, get_pdm_document_reference, - PDM_SNOMED, ) from tests.e2e.helpers.data_helper import PdmDataHelper @@ -34,7 +34,9 @@ def assert_returned_document_reference(pdm_record, response): ], ) def test_successful_retrieval_of_document_reference( - test_data, doc_status, response_status + test_data, + doc_status, + response_status, ): pdm_record = create_and_store_pdm_record(test_data, doc_status=doc_status) @@ -50,7 +52,8 @@ def test_successful_retrieval_of_document_reference( def test_file_retrieval(test_data, file_size): """Test retrieval for small and large files.""" pdm_record = create_and_store_pdm_record( - test_data, size=file_size if file_size else None + test_data, + size=file_size if file_size else None, ) response = get_pdm_document_reference(pdm_record["id"]) assert response.status_code == 200 @@ -75,10 +78,14 @@ def test_file_retrieval(test_data, file_size): ], ) def test_successful_retrieval_of_document_reference_with_and_without_author_and_custodian( - test_data, author, custodian + test_data, + author, + custodian, ): pdm_record = create_and_store_pdm_record( - test_data, Author=author, Custodian=custodian + test_data, + Author=author, + Custodian=custodian, ) response = get_pdm_document_reference(record_id=pdm_record["id"]) diff --git a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py index 5eceaa5467..f6ff8c6715 100644 --- a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py +++ b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py @@ -1,14 +1,13 @@ -from enums.snomed_codes import SnomedCodes, SnomedCode import pytest -from models.document_reference import DocumentReference +from enums.snomed_codes import SnomedCodes from models.fhir.R4.base_models import Reference from models.fhir.R4.fhir_document_reference import ( DocumentReference as FhirDocumentReference, - DocumentReferenceInfo, ) +from models.fhir.R4.fhir_document_reference import DocumentReferenceInfo from tests.unit.helpers.data.test_documents import ( - create_valid_fhir_doc_json, create_test_doc_refs, + create_valid_fhir_doc_json, ) from utils.exceptions import FhirDocumentReferenceException @@ -46,7 +45,8 @@ def test_extract_nhs_number_from_fhir_with_missing_identifier(valid_fhir_doc_obj def test_extract_nhs_number_from_fhir_returns_nhs_number( - valid_fhir_doc_object, valid_nhs_number + valid_fhir_doc_object, + valid_nhs_number, ): """Test that extract_nhs_number_from_fhir returns the correct nhs number""" result = valid_fhir_doc_object.extract_nhs_number_from_fhir() @@ -90,7 +90,9 @@ def test_extract_nhs_number_from_fhir_returns_nhs_number( ], ) def test_create_fhir_document_reference_object( - exclude, include_author_and_custodian, snomed + exclude, + include_author_and_custodian, + snomed, ): doc_refs = create_test_doc_refs() doc_ref = doc_refs[0] From 36adae1a7566e86d0301a7fd58c76195235638a3 Mon Sep 17 00:00:00 2001 From: jameslinnell Date: Fri, 13 Feb 2026 12:28:25 +0000 Subject: [PATCH 8/8] [NDR-375] Make format --- .../tests/unit/models/fhir/R4/test_fhir_document_reference.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py index f6ff8c6715..bcf217c39b 100644 --- a/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py +++ b/lambdas/tests/unit/models/fhir/R4/test_fhir_document_reference.py @@ -4,7 +4,9 @@ from models.fhir.R4.fhir_document_reference import ( DocumentReference as FhirDocumentReference, ) -from models.fhir.R4.fhir_document_reference import DocumentReferenceInfo +from models.fhir.R4.fhir_document_reference import ( + DocumentReferenceInfo, +) from tests.unit.helpers.data.test_documents import ( create_test_doc_refs, create_valid_fhir_doc_json,