Skip to content

Commit 6ca6f39

Browse files
authored
Merge pull request #325 from ansforge/refactor/converter/cisu-conversion-pipeline
feat(converter): RC-RI conversion
2 parents ee59e27 + 4a6e062 commit 6ca6f39

37 files changed

+640
-171
lines changed

converter/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,7 @@ cython_debug/
168168
#.idea/
169169

170170
# PyPI configuration file
171-
.pypirc
171+
.pypirc
172+
173+
# Request cache
174+
*_cache.sqlite

converter/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ uv run pytest tests/test_utils.py -k test_format_object_primitive
4848
uv run pytest -rP
4949
```
5050

51+
Note : the tests download files (json samples & schemas) using the Github API. To avoid hammering the API leading to tests failure, a cache system has been added for the tests runner. To invalidate the cache and download the files again, delete the `schemas_and_samples_cache.sqlite` file at the root of the repository.
52+
5153
### Running the Service
5254

5355
Development mode:
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from converter.conversion_mixin import ConversionMixin
2+
from typing import Any, Dict
3+
4+
5+
class BaseCISUConverter(ConversionMixin):
6+
def __init__(self):
7+
raise ValueError(
8+
"BaseMessageConverter is an abstract class and cannot be instantiated directly. Use a subclass instead."
9+
)
10+
11+
@classmethod
12+
def get_rs_message_type(cls) -> str:
13+
raise NotImplementedError(
14+
"Subclasses must implement this method to return the message type in RS specifications."
15+
)
16+
17+
@classmethod
18+
def get_cisu_message_type(cls) -> str:
19+
raise NotImplementedError(
20+
"Subclasses must implement this method to return the message type in RC specifications."
21+
)
22+
23+
@classmethod
24+
def from_rs_to_cisu(cls, edxl_json):
25+
raise ValueError(
26+
f"Traduction from '{cls.get_rs_message_type()}' to '{cls.get_cisu_message_type()}' is not supported."
27+
)
28+
29+
@classmethod
30+
def from_cisu_to_rs(cls, edxl_json):
31+
raise ValueError(
32+
f"Traduction from '{cls.get_cisu_message_type()}' to '{cls.get_rs_message_type()}' is not supported."
33+
)
34+
35+
@classmethod
36+
def copy_cisu_input_content(cls, edxl_json: Dict[str, Any]) -> Dict[str, Any]:
37+
return cls._copy_input_content(edxl_json, cls.get_cisu_message_type())
38+
39+
@classmethod
40+
def copy_cisu_input_use_case_content(
41+
cls, edxl_json: Dict[str, Any]
42+
) -> Dict[str, Any]:
43+
return cls._copy_input_use_case_content(edxl_json, cls.get_cisu_message_type())
44+
45+
@classmethod
46+
def format_cisu_output_json(
47+
cls,
48+
output_json: Dict[str, Any],
49+
output_use_case_json: Dict[str, Any],
50+
) -> Dict[str, Any]:
51+
return cls._format_output_json(
52+
output_json,
53+
output_use_case_json,
54+
cls.get_cisu_message_type(),
55+
)
56+
57+
@classmethod
58+
def copy_rs_input_content(cls, edxl_json: Dict[str, Any]) -> Dict[str, Any]:
59+
return cls._copy_input_content(edxl_json, cls.get_rs_message_type())
60+
61+
@classmethod
62+
def copy_rs_input_use_case_content(
63+
cls, edxl_json: Dict[str, Any]
64+
) -> Dict[str, Any]:
65+
return cls._copy_input_use_case_content(edxl_json, cls.get_rs_message_type())
66+
67+
@classmethod
68+
def format_rs_output_json(
69+
cls,
70+
output_json: Dict[str, Any],
71+
output_use_case_json: Dict[str, Any],
72+
) -> Dict[str, Any]:
73+
return cls._format_output_json(
74+
output_json,
75+
output_use_case_json,
76+
cls.get_rs_message_type(),
77+
)

converter/converter/cisu/cisu_converter.py renamed to converter/converter/cisu/create_case/create_case_cisu_converter.py

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,20 @@
66

77
from yaml import dump
88

9-
from .utils import add_to_initial_alert_notes
10-
from ..constants import Constants
11-
from ..utils import (
9+
from converter.cisu.utils import add_to_initial_alert_notes
10+
from converter.constants import Constants
11+
from converter.utils import (
1212
delete_paths,
1313
get_field_value,
1414
get_recipient,
1515
get_sender,
1616
is_field_completed,
1717
translate_key_words,
1818
)
19+
from converter.cisu.base_cisu_converter import BaseCISUConverter
1920

2021

21-
class CISUConverterV3:
22+
class CreateCaseCISUConverter(BaseCISUConverter):
2223
"""Handles CISU format conversions"""
2324

2425
CISU_PATHS_TO_DELETE = [
@@ -70,7 +71,15 @@ class CISUConverterV3:
7071
DEFAULT_WHATS_HAPPEN = {"code": "C11.06.00", "label": "Autre nature de fait"}
7172

7273
@classmethod
73-
def from_cisu(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
74+
def get_rs_message_type(cls) -> str:
75+
return "createCaseHealth"
76+
77+
@classmethod
78+
def get_cisu_message_type(cls) -> str:
79+
return "createCase"
80+
81+
@classmethod
82+
def from_cisu_to_rs(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
7483
"""
7584
Convert from CISU to Health format
7685
@@ -161,21 +170,11 @@ def add_object_to_medical_notes(
161170
json_data["medicalNote"].append(new_note)
162171

163172
# Create independent envelope copy without usecase for output
164-
output_json = copy.deepcopy(input_json)
165-
if "createCase" not in input_json.get("content", [{}])[0].get(
166-
"jsonContent", {}
167-
).get("embeddedJsonContent", {}).get("message", {}):
168-
raise ValueError("Input JSON must contain 'createCase' key")
169-
del output_json["content"][0]["jsonContent"]["embeddedJsonContent"]["message"][
170-
"createCase"
171-
]
173+
output_json = cls.copy_cisu_input_content(input_json)
172174

173175
# Create independent use case copy for output
174-
input_use_case_json = input_json["content"][0]["jsonContent"][
175-
"embeddedJsonContent"
176-
]["message"]["createCase"]
177176
sender_id = get_sender(input_json)
178-
output_use_case_json = copy.deepcopy(input_use_case_json)
177+
output_use_case_json = cls.copy_cisu_input_use_case_content(input_json)
179178

180179
# - Updates
181180
output_use_case_json["owner"] = get_recipient(input_json)
@@ -195,10 +194,7 @@ def add_object_to_medical_notes(
195194
# - Delete paths - /!\ It must be the last step
196195
delete_paths(output_use_case_json, cls.CISU_PATHS_TO_DELETE)
197196

198-
output_json["content"][0]["jsonContent"]["embeddedJsonContent"]["message"][
199-
"createCaseHealth"
200-
] = output_use_case_json
201-
return output_json
197+
return cls.format_rs_output_json(output_json, output_use_case_json)
202198

203199
@staticmethod
204200
def count_victims(json_data: Dict[str, Any]) -> int:
@@ -219,7 +215,7 @@ def get_victim_count(cls, json_data: Dict[str, Any]):
219215
return {"count": "BEAUCOUP"}
220216

221217
@classmethod
222-
def to_cisu(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
218+
def from_rs_to_cisu(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
223219
"""
224220
Convert from Health to CISU format
225221
@@ -255,19 +251,10 @@ def add_default_external_info_type(json_data: Dict[str, Any]):
255251
info["type"] = "AUTRE"
256252

257253
# Create independent envelope copy without usecase for output
258-
output_json = copy.deepcopy(input_json)
259-
if "createCaseHealth" not in input_json.get("content", [{}])[0].get(
260-
"jsonContent", {}
261-
).get("embeddedJsonContent", {}).get("message", {}):
262-
raise ValueError("Input JSON must contain 'createCaseHealth' key")
263-
del output_json["content"][0]["jsonContent"]["embeddedJsonContent"]["message"][
264-
"createCaseHealth"
265-
]
254+
output_json = cls.copy_rs_input_content(input_json)
266255

267256
# Create independent usecase copy for output
268-
input_usecase_json = input_json["content"][0]["jsonContent"][
269-
"embeddedJsonContent"
270-
]["message"]["createCaseHealth"]
257+
input_usecase_json = cls.copy_rs_input_use_case_content(input_json)
271258
output_usecase_json = copy.deepcopy(input_usecase_json)
272259

273260
# Generate unique IDs
@@ -321,7 +308,4 @@ def add_default_external_info_type(json_data: Dict[str, Any]):
321308
get_field_value(output_usecase_json, "$.location")
322309
)
323310

324-
output_json["content"][0]["jsonContent"]["embeddedJsonContent"]["message"][
325-
"createCase"
326-
] = output_usecase_json
327-
return output_json
311+
return cls.format_cisu_output_json(output_json, output_usecase_json)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class ResourcesInfoCISUConstants:
2+
RESOURCE_PATH = "$.resource"
3+
STATE_PATH = "$.state"
4+
VEHICLE_TYPE_PATH = "$.vehicleType"
5+
6+
PATIENT_ID_KEY = "patientId"
7+
8+
VEHICLE_TYPE_SIS = "SIS"
9+
DEFAULT_CISU_STATE_VEHICLE_TYPE = "SMUR"
10+
11+
DEFAULT_CISU_STATE_STATUS = "DECISION"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import datetime
2+
3+
from converter.cisu.base_cisu_converter import BaseCISUConverter
4+
from typing import Any, Dict
5+
6+
from converter.cisu.resources_info.resources_info_cisu_constants import (
7+
ResourcesInfoCISUConstants,
8+
)
9+
from converter.utils import get_field_value, set_value, delete_paths
10+
11+
12+
class ResourcesInfoCISUConverter(BaseCISUConverter):
13+
@classmethod
14+
def get_rs_message_type(cls) -> str:
15+
return "resourcesInfo"
16+
17+
@classmethod
18+
def get_cisu_message_type(cls) -> str:
19+
return "resourcesInfoCisu"
20+
21+
@classmethod
22+
def from_cisu_to_rs(cls, edxl_json: Dict[str, Any]) -> Dict[str, Any]:
23+
output_json = cls.copy_cisu_input_content(edxl_json)
24+
output_use_case_json = cls.copy_cisu_input_use_case_content(edxl_json)
25+
resources = get_field_value(
26+
output_use_case_json, ResourcesInfoCISUConstants.RESOURCE_PATH
27+
)
28+
29+
for resource in resources:
30+
state = get_field_value(resource, ResourcesInfoCISUConstants.STATE_PATH)
31+
set_value(resource, ResourcesInfoCISUConstants.STATE_PATH, [state])
32+
33+
return cls.format_rs_output_json(output_json, output_use_case_json)
34+
35+
@classmethod
36+
def from_rs_to_cisu(cls, edxl_json: Dict[str, Any]) -> Dict[str, Any]:
37+
output_json = cls.copy_rs_input_content(edxl_json)
38+
output_use_case_json = cls.copy_rs_input_use_case_content(edxl_json)
39+
40+
resources = get_field_value(
41+
output_use_case_json, ResourcesInfoCISUConstants.RESOURCE_PATH
42+
)
43+
for resource in resources:
44+
rs_vehicle_type = get_field_value(
45+
resource, ResourcesInfoCISUConstants.VEHICLE_TYPE_PATH
46+
)
47+
cisu_vehicle_type = cls.translate_to_cisu_vehicle_type(rs_vehicle_type)
48+
set_value(
49+
resource,
50+
ResourcesInfoCISUConstants.VEHICLE_TYPE_PATH,
51+
cisu_vehicle_type,
52+
)
53+
54+
cls.keep_last_state(resource)
55+
56+
delete_paths(resource, [ResourcesInfoCISUConstants.PATIENT_ID_KEY])
57+
58+
return cls.format_cisu_output_json(output_json, output_use_case_json)
59+
60+
@classmethod
61+
def translate_to_cisu_vehicle_type(cls, rs_vehicle_type: str) -> str:
62+
if rs_vehicle_type.startswith(ResourcesInfoCISUConstants.VEHICLE_TYPE_SIS):
63+
return ResourcesInfoCISUConstants.VEHICLE_TYPE_SIS
64+
return ResourcesInfoCISUConstants.DEFAULT_CISU_STATE_VEHICLE_TYPE
65+
66+
@classmethod
67+
def keep_last_state(cls, resource: Dict[str, Any]) -> None:
68+
states = get_field_value(resource, ResourcesInfoCISUConstants.STATE_PATH)
69+
if states and len(states) > 0:
70+
latest_state = sorted(states, key=lambda x: x.get("datetime", ""))[-1]
71+
else:
72+
latest_state = {
73+
"datetime": datetime.datetime.now(datetime.timezone.utc).isoformat(
74+
timespec="seconds"
75+
),
76+
"status": ResourcesInfoCISUConstants.DEFAULT_CISU_STATE_STATUS,
77+
}
78+
79+
set_value(resource, ResourcesInfoCISUConstants.STATE_PATH, latest_state)

converter/converter/cisu/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Dict, List
22

3-
from ..utils import concatenate_values, get_field_value, is_field_completed
3+
from converter.utils import concatenate_values, get_field_value, is_field_completed
44

55

66
def add_object_to_initial_alert_notes(json_data: Dict[str, Any], note_text: str):

converter/converter/versions/conversion_mixin.py renamed to converter/converter/conversion_mixin.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import copy
22
from typing import Dict, Any
33

4-
from converter.versions.base_message_converter import BaseMessageConverter
54

6-
7-
class ConversionMixin(BaseMessageConverter):
5+
class ConversionMixin:
86
CONTENT_KEY = "content"
97
JSON_CONTENT_KEY = "jsonContent"
108
EMBEDDED_JSON_CONTENT_KEY = "embeddedJsonContent"
119
MESSAGE_KEY = "message"
1210

1311
@classmethod
14-
def copy_input_content(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
12+
def _copy_input_content(
13+
cls, input_json: Dict[str, Any], message_type: str
14+
) -> Dict[str, Any]:
1515
output_json = copy.deepcopy(input_json)
16-
message_type = cls.get_message_type()
1716

1817
message_content = (
1918
input_json.get(cls.CONTENT_KEY, [{}])[0]
@@ -30,18 +29,21 @@ def copy_input_content(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
3029
return output_json
3130

3231
@classmethod
33-
def copy_input_use_case_content(cls, input_json: Dict[str, Any]) -> Dict[str, Any]:
34-
message_type = cls.get_message_type()
32+
def _copy_input_use_case_content(
33+
cls, input_json: Dict[str, Any], message_type: str
34+
) -> Dict[str, Any]:
3535
input_use_case_json = input_json[cls.CONTENT_KEY][0][cls.JSON_CONTENT_KEY][
3636
cls.EMBEDDED_JSON_CONTENT_KEY
3737
][cls.MESSAGE_KEY][message_type]
3838
return copy.deepcopy(input_use_case_json)
3939

4040
@classmethod
41-
def format_output_json(
42-
cls, output_json: Dict[str, Any], output_use_case_json: Dict[str, Any]
41+
def _format_output_json(
42+
cls,
43+
output_json: Dict[str, Any],
44+
output_use_case_json: Dict[str, Any],
45+
message_type: str,
4346
) -> Dict[str, Any]:
44-
message_type = cls.get_message_type()
4547
output_json[cls.CONTENT_KEY][0][cls.JSON_CONTENT_KEY][
4648
cls.EMBEDDED_JSON_CONTENT_KEY
4749
][cls.MESSAGE_KEY][message_type] = output_use_case_json

0 commit comments

Comments
 (0)