Skip to content

Commit 70dbd40

Browse files
authored
Merge pull request #748 from NHSDigital/feature/eema1-NRL-1075-multiCategorySearch
NRL-1075 multi category search
2 parents 1d0756d + eea1b39 commit 70dbd40

13 files changed

+563
-22
lines changed

api/consumer/searchDocumentReference/search_document_reference.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ def handler(
5858
expression="type",
5959
)
6060

61-
if not validate_category(params.category):
61+
categories = params.category.root.split(",") if params.category else []
62+
if not validate_category(categories):
6263
logger.log(
6364
LogReference.CONSEARCH002b,
6465
category=params.category,
@@ -102,7 +103,7 @@ def handler(
102103
nhs_number=params.nhs_number,
103104
custodian=custodian_id,
104105
pointer_types=pointer_types,
105-
categories=[params.category.root] if params.category else [],
106+
categories=categories,
106107
):
107108
try:
108109
document_reference = DocumentReference.model_validate_json(result.document)

api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
from moto import mock_aws
44

55
from api.consumer.searchDocumentReference.search_document_reference import handler
6+
from nrlf.core.constants import (
7+
CATEGORY_ATTRIBUTES,
8+
TYPE_ATTRIBUTES,
9+
Categories,
10+
PointerTypes,
11+
)
612
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
713
from nrlf.tests.data import load_document_reference
814
from nrlf.tests.dynamodb import mock_repository
@@ -144,6 +150,19 @@ def test_search_document_reference_happy_path_with_category(
144150
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
145151
repository.create(doc_pointer)
146152

153+
# Second pointer different category
154+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
155+
doc_ref2.id = "Y05868-736253002-Valid2"
156+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
157+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
158+
PointerTypes.NEWS2_CHART.value
159+
).get("display")
160+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
161+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
162+
Categories.OBSERVATIONS.value
163+
).get("display")
164+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
165+
147166
event = create_test_api_gateway_event(
148167
headers=create_headers(),
149168
query_string_parameters={
@@ -160,7 +179,6 @@ def test_search_document_reference_happy_path_with_category(
160179
"headers": default_response_headers(),
161180
"isBase64Encoded": False,
162181
}
163-
164182
parsed_body = json.loads(body)
165183
assert parsed_body == {
166184
"resourceType": "Bundle",
@@ -176,6 +194,63 @@ def test_search_document_reference_happy_path_with_category(
176194
}
177195

178196

197+
@mock_aws
198+
@mock_repository
199+
def test_search_document_reference_happy_path_with_multiple_categories(
200+
repository: DocumentPointerRepository,
201+
):
202+
doc_ref = load_document_reference("Y05868-736253002-Valid")
203+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
204+
repository.create(doc_pointer)
205+
206+
# Second pointer different category
207+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
208+
doc_ref2.id = "Y05868-736253002-Valid2"
209+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
210+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
211+
PointerTypes.NEWS2_CHART.value
212+
).get("display")
213+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
214+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
215+
Categories.OBSERVATIONS.value
216+
).get("display")
217+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
218+
219+
event = create_test_api_gateway_event(
220+
headers=create_headers(),
221+
query_string_parameters={
222+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
223+
"category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108",
224+
},
225+
)
226+
227+
result = handler(event, create_mock_context())
228+
body = result.pop("body")
229+
230+
assert result == {
231+
"statusCode": "200",
232+
"headers": default_response_headers(),
233+
"isBase64Encoded": False,
234+
}
235+
236+
parsed_body = json.loads(body)
237+
assert parsed_body == {
238+
"resourceType": "Bundle",
239+
"type": "searchset",
240+
"link": [
241+
{
242+
"relation": "self",
243+
"url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191&category=http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108",
244+
}
245+
],
246+
"total": 2,
247+
"entry": [
248+
{"resource": doc_ref2.model_dump(exclude_none=True)},
249+
{"resource": doc_ref.model_dump(exclude_none=True)},
250+
],
251+
}
252+
253+
179254
@mock_aws
180255
@mock_repository
181256
def test_search_document_reference_happy_path_with_nicip_type(

api/consumer/searchPostDocumentReference/search_post_document_reference.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ def handler(
6161
expression="type",
6262
)
6363

64-
if not validate_category(body.category):
64+
categories = body.category.root.split(",") if body.category else []
65+
if not validate_category(categories):
6566
logger.log(
6667
LogReference.CONPOSTSEARCH002b,
67-
type=body.category,
68+
category=body.category,
6869
) # TODO - Should update error message once permissioning by category is implemented
6970
return SpineErrorResponse.INVALID_CODE_SYSTEM(
7071
diagnostics="The provided category is not valid",
@@ -105,7 +106,7 @@ def handler(
105106
nhs_number=body.nhs_number,
106107
custodian=custodian_id,
107108
pointer_types=pointer_types,
108-
categories=[body.category.root] if body.category else [],
109+
categories=categories,
109110
):
110111
try:
111112
document_reference = DocumentReference.model_validate_json(result.document)

api/consumer/searchPostDocumentReference/tests/test_search_post_document_reference_consumer.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
from api.consumer.searchPostDocumentReference.search_post_document_reference import (
66
handler,
77
)
8+
from nrlf.core.constants import (
9+
CATEGORY_ATTRIBUTES,
10+
TYPE_ATTRIBUTES,
11+
Categories,
12+
PointerTypes,
13+
)
814
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
915
from nrlf.tests.data import load_document_reference
1016
from nrlf.tests.dynamodb import mock_repository
@@ -153,6 +159,19 @@ def test_search_post_document_reference_happy_path_with_category(
153159
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
154160
repository.create(doc_pointer)
155161

162+
# Second pointer different category
163+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
164+
doc_ref2.id = "Y05868-736253002-Valid2"
165+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
166+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
167+
PointerTypes.NEWS2_CHART.value
168+
).get("display")
169+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
170+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
171+
Categories.OBSERVATIONS.value
172+
).get("display")
173+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
174+
156175
event = create_test_api_gateway_event(
157176
headers=create_headers(),
158177
body=json.dumps(
@@ -187,6 +206,65 @@ def test_search_post_document_reference_happy_path_with_category(
187206
}
188207

189208

209+
@mock_aws
210+
@mock_repository
211+
def test_search_post_document_reference_happy_path_with_multiple_categories(
212+
repository: DocumentPointerRepository,
213+
):
214+
doc_ref = load_document_reference("Y05868-736253002-Valid")
215+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
216+
repository.create(doc_pointer)
217+
218+
# Second pointer different category
219+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
220+
doc_ref2.id = "Y05868-736253002-Valid2"
221+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
222+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
223+
PointerTypes.NEWS2_CHART.value
224+
).get("display")
225+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
226+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
227+
Categories.OBSERVATIONS.value
228+
).get("display")
229+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
230+
231+
event = create_test_api_gateway_event(
232+
headers=create_headers(),
233+
body=json.dumps(
234+
{
235+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
236+
"category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108",
237+
}
238+
),
239+
)
240+
241+
result = handler(event, create_mock_context())
242+
body = result.pop("body")
243+
244+
assert result == {
245+
"statusCode": "200",
246+
"headers": default_response_headers(),
247+
"isBase64Encoded": False,
248+
}
249+
250+
parsed_body = json.loads(body)
251+
assert parsed_body == {
252+
"resourceType": "Bundle",
253+
"type": "searchset",
254+
"link": [
255+
{
256+
"relation": "self",
257+
"url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191&category=http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108",
258+
}
259+
],
260+
"total": 2,
261+
"entry": [
262+
{"resource": doc_ref2.model_dump(exclude_none=True)},
263+
{"resource": doc_ref.model_dump(exclude_none=True)},
264+
],
265+
}
266+
267+
190268
@mock_aws
191269
@mock_repository
192270
def test_search_document_reference_no_results(repository: DocumentPointerRepository):

api/producer/searchDocumentReference/search_document_reference.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ def handler(
5959
expression="type",
6060
)
6161

62-
if not validate_category(params.category):
62+
categories = params.category.root.split(",") if params.category else []
63+
if not validate_category(categories):
6364
logger.log(
6465
LogReference.PROSEARCH002b,
65-
type=params.category,
66+
category=params.category,
6667
) # TODO - Should update error message once permissioning by category is implemented
6768
return SpineErrorResponse.INVALID_CODE_SYSTEM(
6869
diagnostics="Invalid query parameter (The provided category is not valid)",
@@ -78,15 +79,15 @@ def handler(
7879
custodian_suffix=metadata.ods_code_extension,
7980
nhs_number=params.nhs_number,
8081
pointer_types=pointer_types,
81-
categories=[params.category.root] if params.category else [],
82+
categories=params.category.root.split(",") if params.category else [],
8283
)
8384

8485
for result in repository.search(
8586
custodian=metadata.ods_code,
8687
custodian_suffix=metadata.ods_code_extension,
8788
nhs_number=params.nhs_number,
8889
pointer_types=pointer_types,
89-
categories=[params.category.root] if params.category else [],
90+
categories=params.category.root.split(",") if params.category else [],
9091
):
9192
try:
9293
document_reference = DocumentReference.model_validate_json(result.document)

api/producer/searchDocumentReference/tests/test_search_document_reference_producer.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
from moto import mock_aws
44

55
from api.producer.searchDocumentReference.search_document_reference import handler
6-
from nrlf.core.constants import Categories, PointerTypes
6+
from nrlf.core.constants import (
7+
CATEGORY_ATTRIBUTES,
8+
TYPE_ATTRIBUTES,
9+
Categories,
10+
PointerTypes,
11+
)
712
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
813
from nrlf.tests.data import load_document_reference
914
from nrlf.tests.dynamodb import mock_repository
@@ -326,6 +331,19 @@ def test_search_document_reference_filters_by_category(
326331
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
327332
repository.create(doc_pointer)
328333

334+
# Second pointer different category
335+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
336+
doc_ref2.id = "Y05868-736253002-Valid2"
337+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
338+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
339+
PointerTypes.NEWS2_CHART.value
340+
).get("display")
341+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
342+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
343+
Categories.OBSERVATIONS.value
344+
).get("display")
345+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
346+
329347
event = create_test_api_gateway_event(
330348
headers=create_headers(),
331349
query_string_parameters={
@@ -352,6 +370,57 @@ def test_search_document_reference_filters_by_category(
352370
}
353371

354372

373+
@mock_aws
374+
@mock_repository
375+
def test_search_document_reference_filters_with_multiple_categories(
376+
repository: DocumentPointerRepository,
377+
):
378+
doc_ref = load_document_reference("Y05868-736253002-Valid")
379+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
380+
repository.create(doc_pointer)
381+
382+
# Second pointer different category
383+
doc_ref2 = load_document_reference("Y05868-736253002-Valid")
384+
doc_ref2.id = "Y05868-736253002-Valid2"
385+
doc_ref2.type.coding[0].code = PointerTypes.NEWS2_CHART.coding_value()
386+
doc_ref2.type.coding[0].display = TYPE_ATTRIBUTES.get(
387+
PointerTypes.NEWS2_CHART.value
388+
).get("display")
389+
doc_ref2.category[0].coding[0].code = Categories.OBSERVATIONS.coding_value()
390+
doc_ref2.category[0].coding[0].display = CATEGORY_ATTRIBUTES.get(
391+
Categories.OBSERVATIONS.value
392+
).get("display")
393+
repository.create(DocumentPointer.from_document_reference(doc_ref2))
394+
395+
event = create_test_api_gateway_event(
396+
headers=create_headers(),
397+
query_string_parameters={
398+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
399+
"category": "http://snomed.info/sct|734163000,http://snomed.info/sct|1102421000000108",
400+
},
401+
)
402+
403+
result = handler(event, create_mock_context())
404+
body = result.pop("body")
405+
406+
assert result == {
407+
"statusCode": "200",
408+
"headers": default_response_headers(),
409+
"isBase64Encoded": False,
410+
}
411+
412+
parsed_body = json.loads(body)
413+
assert parsed_body == {
414+
"resourceType": "Bundle",
415+
"type": "searchset",
416+
"total": 2,
417+
"entry": [
418+
{"resource": doc_ref2.model_dump(exclude_none=True)},
419+
{"resource": doc_ref.model_dump(exclude_none=True)},
420+
],
421+
}
422+
423+
355424
@mock_aws
356425
@mock_repository
357426
def test_search_document_reference_filters_by_pointer_types(

0 commit comments

Comments
 (0)