tricc-oo 1.5.21__py3-none-any.whl → 1.5.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tests/build.py +13 -23
- tests/test_cql.py +37 -108
- tests/to_ocl.py +15 -17
- tricc_oo/__init__.py +0 -6
- tricc_oo/converters/codesystem_to_ocl.py +51 -40
- tricc_oo/converters/cql/cqlLexer.py +1 -0
- tricc_oo/converters/cql/cqlListener.py +1 -0
- tricc_oo/converters/cql/cqlParser.py +1 -0
- tricc_oo/converters/cql/cqlVisitor.py +1 -0
- tricc_oo/converters/cql_to_operation.py +125 -123
- tricc_oo/converters/datadictionnary.py +39 -53
- tricc_oo/converters/drawio_type_map.py +143 -61
- tricc_oo/converters/tricc_to_xls_form.py +14 -24
- tricc_oo/converters/utils.py +3 -3
- tricc_oo/converters/xml_to_tricc.py +286 -231
- tricc_oo/models/__init__.py +2 -1
- tricc_oo/models/base.py +300 -308
- tricc_oo/models/calculate.py +63 -49
- tricc_oo/models/lang.py +26 -27
- tricc_oo/models/ocl.py +146 -161
- tricc_oo/models/ordered_set.py +15 -19
- tricc_oo/models/tricc.py +144 -88
- tricc_oo/parsers/xml.py +15 -30
- tricc_oo/serializers/planuml.py +4 -6
- tricc_oo/serializers/xls_form.py +81 -135
- tricc_oo/strategies/input/base_input_strategy.py +28 -32
- tricc_oo/strategies/input/drawio.py +57 -69
- tricc_oo/strategies/output/base_output_strategy.py +108 -67
- tricc_oo/strategies/output/spice.py +106 -127
- tricc_oo/strategies/output/xls_form.py +275 -200
- tricc_oo/strategies/output/xlsform_cdss.py +623 -142
- tricc_oo/strategies/output/xlsform_cht.py +114 -120
- tricc_oo/strategies/output/xlsform_cht_hf.py +13 -24
- tricc_oo/visitors/tricc.py +1191 -1021
- tricc_oo/visitors/utils.py +16 -16
- tricc_oo/visitors/xform_pd.py +91 -89
- {tricc_oo-1.5.21.dist-info → tricc_oo-1.5.23.dist-info}/METADATA +3 -1
- tricc_oo-1.5.23.dist-info/RECORD +47 -0
- tricc_oo-1.5.23.dist-info/licenses/LICENSE +373 -0
- tricc_oo-1.5.21.dist-info/RECORD +0 -46
- {tricc_oo-1.5.21.dist-info → tricc_oo-1.5.23.dist-info}/WHEEL +0 -0
- {tricc_oo-1.5.21.dist-info → tricc_oo-1.5.23.dist-info}/top_level.txt +0 -0
tests/build.py
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
from tricc_oo.strategies.output.spice import SpiceStrategy
|
|
2
|
+
from tricc_oo.strategies.output.xlsform_cht_hf import XLSFormCHTHFStrategy
|
|
3
|
+
from tricc_oo.strategies.output.xlsform_cht import XLSFormCHTStrategy
|
|
4
|
+
from tricc_oo.strategies.output.xlsform_cdss import XLSFormCDSSStrategy
|
|
5
|
+
from tricc_oo.strategies.output.xls_form import XLSFormStrategy
|
|
6
|
+
from tricc_oo.strategies.input.drawio import DrawioStrategy
|
|
1
7
|
import getopt
|
|
2
8
|
import gettext
|
|
3
9
|
import logging
|
|
@@ -23,15 +29,9 @@ langs = SingletonLangClass()
|
|
|
23
29
|
# langs.add_trad('fr', fr)
|
|
24
30
|
# langs.add_trad('en', en)
|
|
25
31
|
|
|
26
|
-
from tricc_oo.strategies.input.drawio import DrawioStrategy
|
|
27
32
|
|
|
28
33
|
# from tricc_oo.serializers.medalcreator import execute
|
|
29
34
|
|
|
30
|
-
from tricc_oo.strategies.output.xls_form import XLSFormStrategy
|
|
31
|
-
from tricc_oo.strategies.output.xlsform_cdss import XLSFormCDSSStrategy
|
|
32
|
-
from tricc_oo.strategies.output.xlsform_cht import XLSFormCHTStrategy
|
|
33
|
-
from tricc_oo.strategies.output.xlsform_cht_hf import XLSFormCHTHFStrategy
|
|
34
|
-
from tricc_oo.strategies.output.spice import SpiceStrategy
|
|
35
35
|
|
|
36
36
|
def setup_logger(
|
|
37
37
|
logger_name,
|
|
@@ -94,9 +94,7 @@ LEVELS = {
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def print_help():
|
|
97
|
-
print(
|
|
98
|
-
"-i / --input draw.io filepath (MANDATORY) or directory containing drawio files"
|
|
99
|
-
)
|
|
97
|
+
print("-i / --input draw.io filepath (MANDATORY) or directory containing drawio files")
|
|
100
98
|
print("-o / --output xls file ")
|
|
101
99
|
print("-d form_id ")
|
|
102
100
|
print("-s L4 system/strategy (odk, cht, cc)")
|
|
@@ -116,9 +114,7 @@ if __name__ == "__main__":
|
|
|
116
114
|
input_strategy = "DrawioStrategy"
|
|
117
115
|
output_strategy = "XLSFormStrategy"
|
|
118
116
|
try:
|
|
119
|
-
opts, args = getopt.getopt(
|
|
120
|
-
sys.argv[1:], "hti:o:s:I:O:l:d:D:", ["input=", "output=", "help", "trads"]
|
|
121
|
-
)
|
|
117
|
+
opts, args = getopt.getopt(sys.argv[1:], "hti:o:s:I:O:l:d:D:", ["input=", "output=", "help", "trads"])
|
|
122
118
|
except getopt.GetoptError:
|
|
123
119
|
print_help()
|
|
124
120
|
sys.exit(1)
|
|
@@ -145,7 +141,7 @@ if __name__ == "__main__":
|
|
|
145
141
|
if in_filepath is None:
|
|
146
142
|
print_help()
|
|
147
143
|
sys.exit(2)
|
|
148
|
-
|
|
144
|
+
|
|
149
145
|
if not download_dir:
|
|
150
146
|
download_dir = out_path
|
|
151
147
|
debug_path = os.fspath(out_path + "/debug.log")
|
|
@@ -165,7 +161,7 @@ if __name__ == "__main__":
|
|
|
165
161
|
setup_logger("default", debug_file_path, logging.INFO)
|
|
166
162
|
file_content = []
|
|
167
163
|
files = []
|
|
168
|
-
in_filepath_list = in_filepath.split(
|
|
164
|
+
in_filepath_list = in_filepath.split(",")
|
|
169
165
|
for in_filepath in in_filepath_list:
|
|
170
166
|
pre, ext = os.path.splitext(in_filepath)
|
|
171
167
|
|
|
@@ -173,18 +169,13 @@ if __name__ == "__main__":
|
|
|
173
169
|
# if output file path not specified, just chagne the extension
|
|
174
170
|
out_path = os.path.dirname(pre)
|
|
175
171
|
|
|
176
|
-
|
|
177
172
|
if os.path.isdir(in_filepath):
|
|
178
|
-
files = [
|
|
179
|
-
os.path.join(in_filepath, f)
|
|
180
|
-
for f in os.listdir(in_filepath)
|
|
181
|
-
if f.endswith(".drawio")
|
|
182
|
-
]
|
|
173
|
+
files = [os.path.join(in_filepath, f) for f in os.listdir(in_filepath) if f.endswith(".drawio")]
|
|
183
174
|
elif os.path.isfile(in_filepath) and in_filepath.endswith(".drawio"):
|
|
184
175
|
files = [in_filepath]
|
|
185
|
-
|
|
176
|
+
|
|
186
177
|
for f in files:
|
|
187
|
-
with open(f,
|
|
178
|
+
with open(f, "r") as s:
|
|
188
179
|
content = s.read()
|
|
189
180
|
# present issue with some drawio file that miss the XML header
|
|
190
181
|
|
|
@@ -193,7 +184,6 @@ if __name__ == "__main__":
|
|
|
193
184
|
logger.critical(f"{in_filepath} is neither a drawio file nor a directory containing drawio files")
|
|
194
185
|
exit(1)
|
|
195
186
|
|
|
196
|
-
|
|
197
187
|
strategy = globals()[input_strategy](files)
|
|
198
188
|
logger.info(f"build the graph from strategy {input_strategy}")
|
|
199
189
|
media_path = os.path.join(out_path, "media-tmp")
|
tests/test_cql.py
CHANGED
|
@@ -1,61 +1,40 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
|
-
from tricc_oo.converters.cql_to_operation import
|
|
4
|
-
from tricc_oo.models.base import
|
|
3
|
+
from tricc_oo.converters.cql_to_operation import transform_cql_to_operation
|
|
4
|
+
from tricc_oo.models.base import TriccOperator, TriccOperation, TriccStatic, TriccReference
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
class TestCql(unittest.TestCase):
|
|
7
8
|
def test_and(self):
|
|
8
|
-
if_cql = "
|
|
9
|
+
if_cql = '"p_weight" is not null and "p_age" > 2'
|
|
9
10
|
dg_operation = transform_cql_to_operation(if_cql)
|
|
10
11
|
dg_expected = TriccOperation(
|
|
11
12
|
operator=TriccOperator.AND,
|
|
12
13
|
reference=[
|
|
13
|
-
TriccOperation(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
),
|
|
17
|
-
TriccOperation(
|
|
18
|
-
operator=TriccOperator.MORE,
|
|
19
|
-
reference=[
|
|
20
|
-
TriccReference("p_age"),
|
|
21
|
-
TriccStatic(
|
|
22
|
-
value=2
|
|
23
|
-
)
|
|
24
|
-
]
|
|
25
|
-
)
|
|
26
|
-
]
|
|
14
|
+
TriccOperation(operator=TriccOperator.ISNOTNULL, reference=[TriccReference("p_weight")]),
|
|
15
|
+
TriccOperation(operator=TriccOperator.MORE, reference=[TriccReference("p_age"), TriccStatic(value=2)]),
|
|
16
|
+
],
|
|
27
17
|
)
|
|
28
18
|
self.assertEqual(str(dg_operation), str(dg_expected))
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
|
|
31
20
|
def test_durg_doage(self):
|
|
32
|
-
if_cql =
|
|
21
|
+
if_cql = 'DrugDosage(\'paracetamol\', "p_weight", "p_age")'
|
|
33
22
|
dg_operation = transform_cql_to_operation(if_cql)
|
|
34
23
|
dg_expected = TriccOperation(
|
|
35
24
|
operator=TriccOperator.DRUG_DOSAGE,
|
|
36
|
-
reference=[
|
|
37
|
-
TriccStatic(value='paracetamol'),
|
|
38
|
-
TriccReference("p_weight"),
|
|
39
|
-
TriccReference("p_age")
|
|
40
|
-
]
|
|
25
|
+
reference=[TriccStatic(value="paracetamol"), TriccReference("p_weight"), TriccReference("p_age")],
|
|
41
26
|
)
|
|
42
27
|
self.assertEqual(str(dg_operation), str(dg_expected))
|
|
43
|
-
|
|
28
|
+
|
|
44
29
|
def test_implied_concat(self):
|
|
45
30
|
if_cql = "'A' & \"B\" & 'C'"
|
|
46
31
|
cc_operation = transform_cql_to_operation(if_cql)
|
|
47
32
|
cc_expected = TriccOperation(
|
|
48
33
|
operator=TriccOperator.CONCATENATE,
|
|
49
|
-
reference=[
|
|
50
|
-
TriccStatic(value='A'),
|
|
51
|
-
TriccReference("B"),
|
|
52
|
-
TriccStatic(value='C')
|
|
53
|
-
]
|
|
34
|
+
reference=[TriccStatic(value="A"), TriccReference("B"), TriccStatic(value="C")],
|
|
54
35
|
)
|
|
55
36
|
self.assertEqual(str(cc_operation), str(cc_expected))
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
37
|
+
|
|
59
38
|
def test_if(self):
|
|
60
39
|
if_cql = "if AgeInDays() < 60 then 'newborn' else 'child'"
|
|
61
40
|
if_operation = transform_cql_to_operation(if_cql)
|
|
@@ -64,19 +43,11 @@ class TestCql(unittest.TestCase):
|
|
|
64
43
|
reference=[
|
|
65
44
|
TriccOperation(
|
|
66
45
|
operator=TriccOperator.LESS,
|
|
67
|
-
reference=[
|
|
68
|
-
TriccOperation(
|
|
69
|
-
operator=TriccOperator.AGE_DAY,
|
|
70
|
-
reference=[]
|
|
71
|
-
),
|
|
72
|
-
TriccStatic(
|
|
73
|
-
value=60
|
|
74
|
-
)
|
|
75
|
-
]
|
|
46
|
+
reference=[TriccOperation(operator=TriccOperator.AGE_DAY, reference=[]), TriccStatic(value=60)],
|
|
76
47
|
),
|
|
77
|
-
TriccStatic(value=
|
|
78
|
-
TriccStatic(value=
|
|
79
|
-
]
|
|
48
|
+
TriccStatic(value="newborn"),
|
|
49
|
+
TriccStatic(value="child"),
|
|
50
|
+
],
|
|
80
51
|
)
|
|
81
52
|
self.assertEqual(str(if_operation), str(if_expected))
|
|
82
53
|
|
|
@@ -91,28 +62,11 @@ class TestCql(unittest.TestCase):
|
|
|
91
62
|
case_expected = TriccOperation(
|
|
92
63
|
operator=TriccOperator.CASE,
|
|
93
64
|
reference=[
|
|
94
|
-
TriccOperation(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
),
|
|
98
|
-
|
|
99
|
-
TriccStatic(
|
|
100
|
-
value=0
|
|
101
|
-
),
|
|
102
|
-
TriccStatic(
|
|
103
|
-
value="newborn"
|
|
104
|
-
)
|
|
105
|
-
],
|
|
106
|
-
[
|
|
107
|
-
TriccStatic(
|
|
108
|
-
value=1
|
|
109
|
-
),
|
|
110
|
-
TriccStatic(
|
|
111
|
-
value="newborn"
|
|
112
|
-
)
|
|
113
|
-
],
|
|
114
|
-
TriccStatic(value='child'),
|
|
115
|
-
]
|
|
65
|
+
TriccOperation(operator=TriccOperator.AGE_MONTH, reference=[]),
|
|
66
|
+
[TriccStatic(value=0), TriccStatic(value="newborn")],
|
|
67
|
+
[TriccStatic(value=1), TriccStatic(value="newborn")],
|
|
68
|
+
TriccStatic(value="child"),
|
|
69
|
+
],
|
|
116
70
|
)
|
|
117
71
|
self.assertEqual(str(case_operation), str(case_expected))
|
|
118
72
|
|
|
@@ -131,57 +85,32 @@ class TestCql(unittest.TestCase):
|
|
|
131
85
|
TriccOperation(
|
|
132
86
|
operator=TriccOperator.LESS_OR_EQUAL,
|
|
133
87
|
reference=[
|
|
134
|
-
TriccOperation(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
),
|
|
138
|
-
TriccStatic(
|
|
139
|
-
value=2
|
|
140
|
-
)
|
|
141
|
-
]
|
|
88
|
+
TriccOperation(operator=TriccOperator.AGE_MONTH, reference=[]),
|
|
89
|
+
TriccStatic(value=2),
|
|
90
|
+
],
|
|
142
91
|
),
|
|
143
|
-
TriccStatic(
|
|
144
|
-
value=True
|
|
145
|
-
)
|
|
92
|
+
TriccStatic(value=True),
|
|
146
93
|
],
|
|
147
94
|
[
|
|
148
95
|
TriccOperation(
|
|
149
96
|
operator=TriccOperator.MORE,
|
|
150
|
-
reference=[
|
|
151
|
-
TriccOperation(
|
|
152
|
-
operator=TriccOperator.AGE_YEAR,
|
|
153
|
-
reference=[]
|
|
154
|
-
),
|
|
155
|
-
TriccStatic(
|
|
156
|
-
value=5
|
|
157
|
-
)
|
|
158
|
-
]
|
|
97
|
+
reference=[TriccOperation(operator=TriccOperator.AGE_YEAR, reference=[]), TriccStatic(value=5)],
|
|
159
98
|
),
|
|
160
|
-
TriccStatic(
|
|
161
|
-
value=True
|
|
162
|
-
)
|
|
99
|
+
TriccStatic(value=True),
|
|
163
100
|
],
|
|
164
101
|
TriccStatic(value=False),
|
|
165
|
-
]
|
|
102
|
+
],
|
|
166
103
|
)
|
|
167
104
|
self.assertEqual(str(case_operation), str(case_expected))
|
|
168
105
|
|
|
169
|
-
|
|
170
106
|
def test_minus(self):
|
|
171
|
-
minus_cql =""" "WFL" >= -3"""
|
|
107
|
+
minus_cql = """ "WFL" >= -3"""
|
|
172
108
|
# minus_cql = """
|
|
173
109
|
# ("age_in_months" < 6 and "WFL" >= -3 and "WFL" < -2) is true
|
|
174
110
|
# """
|
|
175
111
|
minus_operation = transform_cql_to_operation(minus_cql)
|
|
176
112
|
minus_expected = TriccOperation(
|
|
177
|
-
TriccOperator.MORE_OR_EQUAL,
|
|
178
|
-
[
|
|
179
|
-
TriccReference("WFL"),
|
|
180
|
-
TriccOperation(
|
|
181
|
-
TriccOperator.MINUS,
|
|
182
|
-
[TriccStatic(3)]
|
|
183
|
-
)
|
|
184
|
-
]
|
|
113
|
+
TriccOperator.MORE_OR_EQUAL, [TriccReference("WFL"), TriccOperation(TriccOperator.MINUS, [TriccStatic(3)])]
|
|
185
114
|
)
|
|
186
115
|
self.assertEqual(str(minus_operation), str(minus_expected))
|
|
187
116
|
|
|
@@ -195,13 +124,13 @@ class TestCql(unittest.TestCase):
|
|
|
195
124
|
operator=TriccOperator.SELECTED,
|
|
196
125
|
reference=[
|
|
197
126
|
TriccReference("identifier"),
|
|
198
|
-
TriccStatic(value=
|
|
199
|
-
]
|
|
127
|
+
TriccStatic(value="code"),
|
|
128
|
+
],
|
|
200
129
|
)
|
|
201
|
-
]
|
|
130
|
+
],
|
|
202
131
|
)
|
|
203
132
|
self.assertEqual(str(dg_operation), str(dg_expected))
|
|
204
|
-
|
|
205
133
|
|
|
206
|
-
|
|
207
|
-
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
unittest.main()
|
tests/to_ocl.py
CHANGED
|
@@ -1,51 +1,49 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import json
|
|
3
2
|
from pathlib import Path
|
|
4
3
|
from tricc_oo.converters.codesystem_to_ocl import transform_fhir_to_ocl
|
|
5
4
|
|
|
5
|
+
|
|
6
6
|
def find_and_process_codesystems(directory_path):
|
|
7
7
|
# Convert string path to Path object if not already
|
|
8
8
|
dir_path = Path(directory_path)
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
# Find all JSON files in the directory
|
|
11
11
|
for json_file in dir_path.glob("*.json"):
|
|
12
12
|
try:
|
|
13
13
|
# Read the JSON file
|
|
14
|
-
with open(json_file,
|
|
14
|
+
with open(json_file, "r", encoding="utf-8") as f:
|
|
15
15
|
data = json.load(f)
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
# Check if resource_type is CodeSystem
|
|
18
18
|
if data.get("resourceType") == "CodeSystem":
|
|
19
19
|
# Get the filename without extension for output naming
|
|
20
20
|
file_key = json_file.stem
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Write the original CodeSystem JSON
|
|
23
23
|
output_cs_path = dir_path / f"{file_key}_codesystem.json"
|
|
24
|
-
with open(output_cs_path, "w", encoding=
|
|
24
|
+
with open(output_cs_path, "w", encoding="utf-8") as file:
|
|
25
25
|
file.write(json.dumps(data, indent=4))
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# Transform to OCL payload
|
|
28
28
|
ocl_payload = transform_fhir_to_ocl(
|
|
29
|
-
data,
|
|
30
|
-
source_name="ALM",
|
|
31
|
-
source_owner="pdelcroix",
|
|
32
|
-
source_owner_type="User"
|
|
29
|
+
data, source_name="ALM", source_owner="pdelcroix", source_owner_type="User"
|
|
33
30
|
)
|
|
34
|
-
|
|
31
|
+
|
|
35
32
|
# Save the transformed OCL payload
|
|
36
33
|
output_ocl_path = dir_path / f"{file_key}_ocl_bulk_upload.json"
|
|
37
|
-
with open(output_ocl_path, "w", encoding=
|
|
34
|
+
with open(output_ocl_path, "w", encoding="utf-8") as f:
|
|
38
35
|
for item in ocl_payload:
|
|
39
36
|
json_line = json.dumps(item.dict(exclude_none=True))
|
|
40
|
-
f.write(json_line +
|
|
41
|
-
|
|
37
|
+
f.write(json_line + "\n")
|
|
38
|
+
|
|
42
39
|
print(f"OCL bulk upload payload generated successfully for {file_key}!")
|
|
43
|
-
|
|
40
|
+
|
|
44
41
|
except json.JSONDecodeError:
|
|
45
42
|
print(f"Error: Invalid JSON in file {json_file}")
|
|
46
43
|
except Exception as e:
|
|
47
44
|
print(f"Error processing {json_file}: {str(e)}")
|
|
48
45
|
|
|
46
|
+
|
|
49
47
|
# Example usage
|
|
50
48
|
media_path = "path/to/your/directory" # Replace with your directory path
|
|
51
|
-
find_and_process_codesystems(media_path)
|
|
49
|
+
find_and_process_codesystems(media_path)
|
tricc_oo/__init__.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from typing import Dict, List, Any
|
|
2
|
-
import json
|
|
3
2
|
from fhir.resources.codesystem import CodeSystem
|
|
4
3
|
from tricc_oo.models.ocl import (
|
|
5
4
|
OCLConcept,
|
|
@@ -7,15 +6,16 @@ from tricc_oo.models.ocl import (
|
|
|
7
6
|
OCLDetailedName,
|
|
8
7
|
OCLDetailedDescription,
|
|
9
8
|
OclConstants,
|
|
10
|
-
get_data_type
|
|
11
9
|
)
|
|
12
10
|
|
|
11
|
+
|
|
13
12
|
def extract_properties_metadata(fhir_cs: CodeSystem) -> Dict[str, Dict]:
|
|
14
13
|
"""
|
|
15
|
-
Extracts property definitions from FHIR CodeSystem and converts them
|
|
14
|
+
Extracts property definitions from FHIR CodeSystem and converts them
|
|
15
|
+
to OCL attribute types
|
|
16
16
|
"""
|
|
17
17
|
property_types = {}
|
|
18
|
-
if hasattr(fhir_cs,
|
|
18
|
+
if hasattr(fhir_cs, "property") and fhir_cs.property:
|
|
19
19
|
for prop in fhir_cs.property:
|
|
20
20
|
# Map FHIR property types to OCL datatypes
|
|
21
21
|
fhir_type = prop.type
|
|
@@ -26,76 +26,85 @@ def extract_properties_metadata(fhir_cs: CodeSystem) -> Dict[str, Dict]:
|
|
|
26
26
|
"decimal": "Numeric",
|
|
27
27
|
"integer": "Numeric",
|
|
28
28
|
"dateTime": "DateTime",
|
|
29
|
-
"string": "Text"
|
|
29
|
+
"string": "Text",
|
|
30
30
|
}.get(fhir_type, "Text")
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
property_types[prop.code] = {
|
|
33
33
|
"name": prop.code,
|
|
34
34
|
"datatype": ocl_type,
|
|
35
|
-
"description": prop.description if hasattr(prop,
|
|
35
|
+
"description": prop.description if hasattr(prop, "description") else "",
|
|
36
36
|
}
|
|
37
37
|
return property_types
|
|
38
38
|
|
|
39
|
+
|
|
39
40
|
def get_fhir_concept_datatype(concept):
|
|
40
|
-
|
|
41
|
-
datatype = extract_concept_properties(concept, ['datatype'])
|
|
41
|
+
datatype = extract_concept_properties(concept, ["datatype"])
|
|
42
42
|
if datatype:
|
|
43
|
-
return datatype[
|
|
43
|
+
return datatype["datatype"]
|
|
44
44
|
else:
|
|
45
45
|
return OclConstants.DATA_TYPE_NONE
|
|
46
46
|
|
|
47
|
+
|
|
47
48
|
def extract_concept_properties(concept, property_types: List) -> List[Dict]:
|
|
48
49
|
"""
|
|
49
50
|
Extracts properties from a FHIR concept and converts them to OCL attributes
|
|
50
51
|
"""
|
|
51
52
|
properties = {}
|
|
52
|
-
if hasattr(concept,
|
|
53
|
+
if hasattr(concept, "property") and concept.property:
|
|
53
54
|
for prop in concept.property:
|
|
54
55
|
if prop.code in property_types:
|
|
55
56
|
# Handle different property value types
|
|
56
|
-
if getattr(prop,
|
|
57
|
+
if getattr(prop, "valueCode", None):
|
|
57
58
|
value = prop.valueCode
|
|
58
|
-
elif getattr(prop,
|
|
59
|
+
elif getattr(prop, "valueCoding", None):
|
|
59
60
|
value = prop.valueCoding.code
|
|
60
|
-
elif getattr(prop,
|
|
61
|
+
elif getattr(prop, "valueString", None):
|
|
61
62
|
value = prop.valueString
|
|
62
|
-
elif getattr(prop,
|
|
63
|
+
elif getattr(prop, "valueBoolean", None):
|
|
63
64
|
value = prop.valueBoolean
|
|
64
|
-
elif getattr(prop,
|
|
65
|
+
elif getattr(prop, "valueInteger", None):
|
|
65
66
|
value = prop.valueInteger
|
|
66
|
-
elif getattr(prop,
|
|
67
|
+
elif getattr(prop, "valueDecimal", None):
|
|
67
68
|
value = prop.valueDecimal
|
|
68
|
-
elif getattr(prop,
|
|
69
|
+
elif getattr(prop, "valueDateTime", None):
|
|
69
70
|
value = prop.valueDateTime
|
|
70
71
|
else:
|
|
71
72
|
continue
|
|
72
73
|
if value:
|
|
73
74
|
properties[prop.code] = value
|
|
74
75
|
return properties
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
|
|
77
|
+
|
|
77
78
|
def get_attributes_from_concept_properties(concept, property_types: Dict) -> List[Dict]:
|
|
78
79
|
attributes = []
|
|
79
80
|
properties = extract_concept_properties(concept, property_types=list(property_types))
|
|
80
81
|
for code, value in properties.items():
|
|
81
|
-
attributes.append(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
attributes.append(
|
|
83
|
+
{
|
|
84
|
+
"type": "Attribute",
|
|
85
|
+
"attribute_type": code,
|
|
86
|
+
"value": value,
|
|
87
|
+
"value_type": property_types[code]["datatype"],
|
|
88
|
+
}
|
|
89
|
+
)
|
|
87
90
|
return attributes
|
|
88
91
|
|
|
89
|
-
|
|
92
|
+
|
|
93
|
+
def transform_fhir_to_ocl(
|
|
94
|
+
fhir_codesystem_json: Dict,
|
|
95
|
+
source_name: str,
|
|
96
|
+
source_owner: str,
|
|
97
|
+
source_owner_type: str,
|
|
98
|
+
) -> List[Dict[str, Any]]:
|
|
90
99
|
"""
|
|
91
100
|
Transforms a FHIR CodeSystem resource into an OCL bulk upload JSON payload.
|
|
92
|
-
|
|
101
|
+
|
|
93
102
|
Args:
|
|
94
103
|
fhir_codesystem_json: JSON representation of the FHIR CodeSystem resource
|
|
95
104
|
source_name: Name of the OCL Source
|
|
96
105
|
source_owner: Owner of the OCL Source (organization or user)
|
|
97
106
|
source_owner_type : User or Organization
|
|
98
|
-
|
|
107
|
+
|
|
99
108
|
Returns:
|
|
100
109
|
List of dictionaries representing OCL bulk upload format
|
|
101
110
|
"""
|
|
@@ -109,8 +118,7 @@ def transform_fhir_to_ocl(fhir_codesystem_json: Dict, source_name: str, source_o
|
|
|
109
118
|
ocl_payload = []
|
|
110
119
|
|
|
111
120
|
# Add source metadata
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
source_extras = {}
|
|
114
122
|
# Add property definitions to extras
|
|
115
123
|
if property_types:
|
|
116
124
|
source_extras["attribute_types"] = list(property_types.values())
|
|
@@ -123,16 +131,17 @@ def transform_fhir_to_ocl(fhir_codesystem_json: Dict, source_name: str, source_o
|
|
|
123
131
|
owner=source_owner,
|
|
124
132
|
owner_type=source_owner_type,
|
|
125
133
|
name=fhir_cs.name or "Unnamed Source",
|
|
126
|
-
full_name=fhir_cs.title if hasattr(fhir_cs,
|
|
134
|
+
full_name=fhir_cs.title if hasattr(fhir_cs, "title") else fhir_cs.name,
|
|
127
135
|
description=fhir_cs.description or "",
|
|
128
136
|
source_type="Dictionary",
|
|
129
137
|
default_locale="en",
|
|
130
138
|
supported_locales=["en"],
|
|
139
|
+
extras=source_extras,
|
|
131
140
|
)
|
|
132
|
-
|
|
141
|
+
)
|
|
133
142
|
|
|
134
143
|
# Transform concepts
|
|
135
|
-
if hasattr(fhir_cs,
|
|
144
|
+
if hasattr(fhir_cs, "concept") and fhir_cs.concept:
|
|
136
145
|
for concept in fhir_cs.concept:
|
|
137
146
|
datatype = get_fhir_concept_datatype(concept)
|
|
138
147
|
ocl_concept = OCLConcept(
|
|
@@ -149,15 +158,17 @@ def transform_fhir_to_ocl(fhir_codesystem_json: Dict, source_name: str, source_o
|
|
|
149
158
|
name_type=OclConstants.NAME_TYPE_FULLY_SPECIFIED,
|
|
150
159
|
)
|
|
151
160
|
],
|
|
152
|
-
descriptions=[]
|
|
161
|
+
descriptions=[],
|
|
153
162
|
)
|
|
154
163
|
|
|
155
164
|
# Add definition if present
|
|
156
|
-
if hasattr(concept,
|
|
157
|
-
ocl_concept.descriptions.append(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
if hasattr(concept, "definition") and concept.definition:
|
|
166
|
+
ocl_concept.descriptions.append(
|
|
167
|
+
OCLDetailedDescription(
|
|
168
|
+
description=concept.definition,
|
|
169
|
+
locale="en",
|
|
170
|
+
)
|
|
171
|
+
)
|
|
161
172
|
|
|
162
173
|
# Extract and add properties as attributes
|
|
163
174
|
attributes = get_attributes_from_concept_properties(concept, property_types)
|