Skip to content

Commit 27e4c86

Browse files
committed
NRL-1053 add category parameter to search requests
1 parent 2cbeb7f commit 27e4c86

File tree

12 files changed

+205
-10
lines changed

12 files changed

+205
-10
lines changed

api/consumer/searchDocumentReference/search_document_reference.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from nrlf.core.logger import LogReference, logger
1010
from nrlf.core.model import ConnectionMetadata, ConsumerRequestParams
1111
from nrlf.core.response import Response, SpineErrorResponse
12-
from nrlf.core.validators import validate_type_system
12+
from nrlf.core.validators import validate_category, validate_type_system
1313

1414

1515
@request_handler(params=ConsumerRequestParams)
@@ -58,6 +58,16 @@ def handler(
5858
expression="type",
5959
)
6060

61+
if not validate_category(params.category):
62+
logger.log(
63+
LogReference.CONSEARCH002b,
64+
type=params.category,
65+
) # TODO - Should update error message once permissioning by category is implemented
66+
return SpineErrorResponse.INVALID_CODE_SYSTEM(
67+
diagnostics="Invalid query parameter (The provided category is not valid)",
68+
expression="category",
69+
)
70+
6171
custodian_id = (
6272
params.custodian_identifier.root.split("|", maxsplit=1)[1]
6373
if params.custodian_identifier
@@ -70,6 +80,9 @@ def handler(
7080
if params.type:
7181
self_link += f"&type={params.type.root}"
7282

83+
if params.category:
84+
self_link += f"&category={params.category.root}"
85+
7386
bundle = {
7487
"resourceType": "Bundle",
7588
"type": "searchset",
@@ -89,6 +102,7 @@ def handler(
89102
nhs_number=params.nhs_number,
90103
custodian=custodian_id,
91104
pointer_types=pointer_types,
105+
categories=[params.category.root] if params.category else [],
92106
):
93107
try:
94108
document_reference = DocumentReference.model_validate_json(result.document)

api/consumer/searchPostDocumentReference/search_post_document_reference.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from nrlf.core.logger import LogReference, logger
1010
from nrlf.core.model import ConnectionMetadata, ConsumerRequestParams
1111
from nrlf.core.response import Response, SpineErrorResponse
12-
from nrlf.core.validators import validate_type_system
12+
from nrlf.core.validators import validate_category, validate_type_system
1313

1414

1515
@request_handler(body=ConsumerRequestParams)
@@ -61,6 +61,16 @@ def handler(
6161
expression="type",
6262
)
6363

64+
if not validate_category(body.category):
65+
logger.log(
66+
LogReference.CONPOSTSEARCH002b,
67+
type=body.category,
68+
) # TODO - Should update error message once permissioning by category is implemented
69+
return SpineErrorResponse.INVALID_CODE_SYSTEM(
70+
diagnostics="Invalid query parameter (The provided category is not valid)",
71+
expression="category",
72+
)
73+
6474
custodian_id = (
6575
body.custodian_identifier.root.split("|", maxsplit=1)[1]
6676
if body.custodian_identifier
@@ -73,6 +83,9 @@ def handler(
7383
if body.type:
7484
self_link += f"&type={body.type.root}"
7585

86+
if body.category:
87+
self_link += f"&category={body.category.root}"
88+
7689
bundle = {
7790
"resourceType": "Bundle",
7891
"type": "searchset",
@@ -89,7 +102,10 @@ def handler(
89102
)
90103

91104
for result in repository.search(
92-
nhs_number=body.nhs_number, custodian=custodian_id, pointer_types=pointer_types
105+
nhs_number=body.nhs_number,
106+
custodian=custodian_id,
107+
pointer_types=pointer_types,
108+
categories=[body.category.root] if body.category else [],
93109
):
94110
try:
95111
document_reference = DocumentReference.model_validate_json(result.document)

api/consumer/swagger.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,8 @@ components:
13991399
$ref: "#/components/schemas/RequestQueryCustodian"
14001400
type:
14011401
$ref: "#/components/schemas/RequestQueryType"
1402+
category:
1403+
$ref: "#/components/schemas/RequestQueryCategory"
14021404
next-page-token:
14031405
$ref: "#/components/schemas/NextPageToken"
14041406
required:
@@ -1421,6 +1423,9 @@ components:
14211423
RequestQueryType:
14221424
type: string
14231425
example: "http://snomed.info/sct|736253002"
1426+
RequestQueryCategory:
1427+
type: string
1428+
example: "http://snomed.info/sct|103693007"
14241429
NextPageToken:
14251430
type: string
14261431
RequestHeaderOdsCode:
@@ -1531,6 +1536,33 @@ components:
15311536
invalid:
15321537
summary: Unknown
15331538
value: http://snomed.info/sct|410970009
1539+
category:
1540+
name: category
1541+
in: query
1542+
schema:
1543+
$ref: "#/components/schemas/RequestQueryCategory"
1544+
examples:
1545+
none:
1546+
summary: None
1547+
value: ""
1548+
SNOMED_CODES_CARE_PLAN:
1549+
summary: Care plan
1550+
value: http://snomed.info/sct|734163000
1551+
SNOMED_CODES_OBSERVATIONS:
1552+
summary: Observations
1553+
value: http://snomed.info/sct|1102421000000108
1554+
SNOMED_CODES_CLINICAL_NOTE:
1555+
summary: Clinical note
1556+
value: http://snomed.info/sct|823651000000106
1557+
SNOMED_CODES_DIAGNOSTIC_STUDIES_REPORT:
1558+
summary: Diagnostic studies report
1559+
value: http://snomed.info/sct|721981007
1560+
SNOMED_CODES_DIAGNOSTIC_PROCEDURE:
1561+
summary: RDiagnostic procedure
1562+
value: http://snomed.info/sct|103693007
1563+
invalid:
1564+
summary: Unknown
1565+
value: http://snomed.info/sct|410970009
15341566
nextPageToken:
15351567
name: next-page-token
15361568
description: |

api/producer/searchDocumentReference/search_document_reference.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from nrlf.core.logger import LogReference, logger
77
from nrlf.core.model import ConnectionMetadata, ProducerRequestParams
88
from nrlf.core.response import Response, SpineErrorResponse
9-
from nrlf.core.validators import validate_type_system
9+
from nrlf.core.validators import validate_category, validate_type_system
1010
from nrlf.producer.fhir.r4.model import Bundle, DocumentReference
1111

1212

@@ -59,6 +59,16 @@ def handler(
5959
expression="type",
6060
)
6161

62+
if not validate_category(params.category):
63+
logger.log(
64+
LogReference.PROSEARCH002b,
65+
type=params.category,
66+
) # TODO - Should update error message once permissioning by category is implemented
67+
return SpineErrorResponse.INVALID_CODE_SYSTEM(
68+
diagnostics="Invalid query parameter (The provided category is not valid)",
69+
expression="category",
70+
)
71+
6272
pointer_types = [params.type.root] if params.type else metadata.pointer_types
6373
bundle = {"resourceType": "Bundle", "type": "searchset", "total": 0, "entry": []}
6474

@@ -68,13 +78,15 @@ def handler(
6878
custodian_suffix=metadata.ods_code_extension,
6979
nhs_number=params.nhs_number,
7080
pointer_types=pointer_types,
81+
categories=[params.category.root],
7182
)
7283

7384
for result in repository.search(
7485
custodian=metadata.ods_code,
7586
custodian_suffix=metadata.ods_code_extension,
7687
nhs_number=params.nhs_number,
7788
pointer_types=pointer_types,
89+
categories=[params.category.root] if params.category else [],
7890
):
7991
try:
8092
document_reference = DocumentReference.model_validate_json(result.document)

api/producer/searchPostDocumentReference/search_post_document_reference.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from nrlf.core.logger import LogReference, logger
77
from nrlf.core.model import ConnectionMetadata, ProducerRequestParams
88
from nrlf.core.response import Response, SpineErrorResponse
9-
from nrlf.core.validators import validate_type_system
9+
from nrlf.core.validators import validate_category, validate_type_system
1010
from nrlf.producer.fhir.r4.model import Bundle, DocumentReference
1111

1212

@@ -53,6 +53,16 @@ def handler(
5353
expression="type",
5454
)
5555

56+
if not validate_category(body.category):
57+
logger.log(
58+
LogReference.PROPOSTSEARCH002b,
59+
type=body.category,
60+
) # TODO - Should update error message once permissioning by category is implemented
61+
return SpineErrorResponse.INVALID_CODE_SYSTEM(
62+
diagnostics="Invalid query parameter (The provided category is not valid)",
63+
expression="category",
64+
)
65+
5666
pointer_types = [body.type.root] if body.type else metadata.pointer_types
5767
bundle = {"resourceType": "Bundle", "type": "searchset", "total": 0, "entry": []}
5868

@@ -62,13 +72,15 @@ def handler(
6272
custodian_suffix=metadata.ods_code_extension,
6373
nhs_number=body.nhs_number,
6474
pointer_types=pointer_types,
75+
categories=[body.category.root] if body.category else [],
6576
)
6677

6778
for result in repository.search(
6879
custodian=metadata.ods_code,
6980
custodian_suffix=metadata.ods_code_extension,
7081
nhs_number=body.nhs_number,
7182
pointer_types=pointer_types,
83+
categories=[body.category.root] if body.category else [],
7284
):
7385
try:
7486
document_reference = DocumentReference.model_validate_json(result.document)

api/producer/swagger.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,8 @@ components:
19341934
$ref: "#/components/schemas/RequestQuerySubject"
19351935
type:
19361936
$ref: "#/components/schemas/RequestQueryType"
1937+
category:
1938+
$ref: "#/components/schemas/RequestQueryCategory"
19371939
next-page-token:
19381940
$ref: "#/components/schemas/NextPageToken"
19391941
RequestQuerySubject:
@@ -1943,6 +1945,9 @@ components:
19431945
RequestQueryType:
19441946
type: string
19451947
example: "http://snomed.info/sct|736253002"
1948+
RequestQueryCategory:
1949+
type: string
1950+
example: "http://snomed.info/sct|103693007"
19461951
NextPageToken:
19471952
type: string
19481953
RequestHeaderOdsCode:
@@ -2037,6 +2042,33 @@ components:
20372042
invalid:
20382043
summary: Unknown
20392044
value: http://snomed.info/sct|410970009
2045+
category:
2046+
name: category
2047+
in: query
2048+
schema:
2049+
$ref: "#/components/schemas/RequestQueryCategory"
2050+
examples:
2051+
none:
2052+
summary: None
2053+
value: ""
2054+
SNOMED_CODES_CARE_PLAN:
2055+
summary: Care plan
2056+
value: http://snomed.info/sct|734163000
2057+
SNOMED_CODES_OBSERVATIONS:
2058+
summary: Observations
2059+
value: http://snomed.info/sct|1102421000000108
2060+
SNOMED_CODES_CLINICAL_NOTE:
2061+
summary: Clinical note
2062+
value: http://snomed.info/sct|823651000000106
2063+
SNOMED_CODES_DIAGNOSTIC_STUDIES_REPORT:
2064+
summary: Diagnostic studies report
2065+
value: http://snomed.info/sct|721981007
2066+
SNOMED_CODES_DIAGNOSTIC_PROCEDURE:
2067+
summary: RDiagnostic procedure
2068+
value: http://snomed.info/sct|103693007
2069+
invalid:
2070+
summary: Unknown
2071+
value: http://snomed.info/sct|410970009
20402072
nextPageToken:
20412073
name: next-page-token
20422074
in: query

layer/nrlf/consumer/fhir/r4/model.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: swagger.yaml
3-
# timestamp: 2024-10-28T10:30:48+00:00
3+
# timestamp: 2024-11-10T19:12:31+00:00
44

55
from __future__ import annotations
66

@@ -397,6 +397,10 @@ class RequestQueryType(RootModel[str]):
397397
root: Annotated[str, Field(examples=["http://snomed.info/sct|736253002"])]
398398

399399

400+
class RequestQueryCategory(RootModel[str]):
401+
root: Annotated[str, Field(examples=["http://snomed.info/sct|103693007"])]
402+
403+
400404
class NextPageToken(RootModel[str]):
401405
root: str
402406

@@ -435,6 +439,7 @@ class RequestParams(BaseModel):
435439
Optional[RequestQueryCustodian], Field(alias="custodian:identifier")
436440
] = None
437441
type: Optional[RequestQueryType] = None
442+
category: Optional[RequestQueryCategory] = None
438443
next_page_token: Annotated[
439444
Optional[NextPageToken], Field(alias="next-page-token")
440445
] = None

layer/nrlf/core/dynamodb/repository.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ def _get_sk_ids_for_type(pointer_type: str) -> tuple:
3333
return category_id, type_id
3434

3535

36+
def _get_sk_id_for_category(category: str) -> tuple:
37+
category_system, category_code = category.split("|")
38+
if category_system not in SYSTEM_SHORT_IDS:
39+
raise ValueError(f"Unknown system for category: {category_system}")
40+
category_id = SYSTEM_SHORT_IDS[category_system] + "-" + category_code
41+
42+
return category_id
43+
44+
3645
class Repository(ABC, Generic[RepositoryModel]):
3746
ITEM_TYPE: Type[RepositoryModel]
3847

@@ -218,6 +227,7 @@ def search(
218227
custodian: Optional[str] = None,
219228
custodian_suffix: Optional[str] = None,
220229
pointer_types: Optional[List[str]] = [],
230+
categories: Optional[List[str]] = [],
221231
) -> Iterator[DocumentPointer]:
222232
""""""
223233
logger.log(
@@ -239,6 +249,24 @@ def search(
239249
key_conditions.append("begins_with(patient_sort, :patient_sort)")
240250
expression_values[":patient_sort"] = patient_sort
241251

252+
if len(categories) == 1:
253+
# Optimisation for single category
254+
category_id = _get_sk_id_for_category(categories[0])
255+
patient_sort = f"C#{category_id}"
256+
key_conditions.append("begins_with(patient_sort, :patient_sort)")
257+
expression_values[":patient_sort"] = patient_sort
258+
259+
if len(categories) > 1:
260+
expression_names["#category"] = "category"
261+
category_filters = [
262+
f"#category = :category_{i}" for i in range(len(categories))
263+
]
264+
caetgory_filter_values = {
265+
f":category_{i}": categories[i] for i in range(len(categories))
266+
}
267+
filter_expressions.append(f"({' OR '.join(category_filters)})")
268+
expression_values.update(caetgory_filter_values)
269+
242270
# Handle multiple categories and pointer types with filter expressions
243271
if len(pointer_types) > 1:
244272
expression_names["#pointer_type"] = "type"

0 commit comments

Comments
 (0)