tricc-oo 1.5.22__py3-none-any.whl → 1.5.24__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 +17 -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 +45 -54
- 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 +145 -89
- 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 +59 -71
- tricc_oo/strategies/output/base_output_strategy.py +142 -67
- tricc_oo/strategies/output/fhir_form.py +377 -0
- tricc_oo/strategies/output/html_form.py +224 -0
- tricc_oo/strategies/output/openmrs_form.py +647 -0
- tricc_oo/strategies/output/spice.py +106 -127
- tricc_oo/strategies/output/xls_form.py +263 -222
- tricc_oo/strategies/output/xlsform_cdss.py +623 -142
- tricc_oo/strategies/output/xlsform_cht.py +108 -115
- tricc_oo/strategies/output/xlsform_cht_hf.py +13 -24
- tricc_oo/visitors/tricc.py +1297 -1016
- tricc_oo/visitors/utils.py +16 -16
- tricc_oo/visitors/xform_pd.py +91 -89
- {tricc_oo-1.5.22.dist-info → tricc_oo-1.5.24.dist-info}/METADATA +127 -84
- tricc_oo-1.5.24.dist-info/RECORD +50 -0
- tricc_oo-1.5.24.dist-info/licenses/LICENSE +373 -0
- tricc_oo-1.5.22.dist-info/RECORD +0 -46
- {tricc_oo-1.5.22.dist-info → tricc_oo-1.5.24.dist-info}/WHEEL +0 -0
- {tricc_oo-1.5.22.dist-info → tricc_oo-1.5.24.dist-info}/top_level.txt +0 -0
tests/build.py
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
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.output.openmrs_form import OpenMRSStrategy
|
|
7
|
+
from tricc_oo.strategies.output.fhir_form import FHIRStrategy
|
|
8
|
+
from tricc_oo.strategies.output.html_form import HTMLStrategy
|
|
9
|
+
|
|
10
|
+
from tricc_oo.strategies.input.drawio import DrawioStrategy
|
|
1
11
|
import getopt
|
|
2
12
|
import gettext
|
|
3
13
|
import logging
|
|
@@ -23,15 +33,9 @@ langs = SingletonLangClass()
|
|
|
23
33
|
# langs.add_trad('fr', fr)
|
|
24
34
|
# langs.add_trad('en', en)
|
|
25
35
|
|
|
26
|
-
from tricc_oo.strategies.input.drawio import DrawioStrategy
|
|
27
36
|
|
|
28
37
|
# from tricc_oo.serializers.medalcreator import execute
|
|
29
38
|
|
|
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
39
|
|
|
36
40
|
def setup_logger(
|
|
37
41
|
logger_name,
|
|
@@ -94,9 +98,7 @@ LEVELS = {
|
|
|
94
98
|
|
|
95
99
|
|
|
96
100
|
def print_help():
|
|
97
|
-
print(
|
|
98
|
-
"-i / --input draw.io filepath (MANDATORY) or directory containing drawio files"
|
|
99
|
-
)
|
|
101
|
+
print("-i / --input draw.io filepath (MANDATORY) or directory containing drawio files")
|
|
100
102
|
print("-o / --output xls file ")
|
|
101
103
|
print("-d form_id ")
|
|
102
104
|
print("-s L4 system/strategy (odk, cht, cc)")
|
|
@@ -116,9 +118,7 @@ if __name__ == "__main__":
|
|
|
116
118
|
input_strategy = "DrawioStrategy"
|
|
117
119
|
output_strategy = "XLSFormStrategy"
|
|
118
120
|
try:
|
|
119
|
-
opts, args = getopt.getopt(
|
|
120
|
-
sys.argv[1:], "hti:o:s:I:O:l:d:D:", ["input=", "output=", "help", "trads"]
|
|
121
|
-
)
|
|
121
|
+
opts, args = getopt.getopt(sys.argv[1:], "hti:o:s:I:O:l:d:D:", ["input=", "output=", "help", "trads"])
|
|
122
122
|
except getopt.GetoptError:
|
|
123
123
|
print_help()
|
|
124
124
|
sys.exit(1)
|
|
@@ -145,7 +145,7 @@ if __name__ == "__main__":
|
|
|
145
145
|
if in_filepath is None:
|
|
146
146
|
print_help()
|
|
147
147
|
sys.exit(2)
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
if not download_dir:
|
|
150
150
|
download_dir = out_path
|
|
151
151
|
debug_path = os.fspath(out_path + "/debug.log")
|
|
@@ -165,7 +165,7 @@ if __name__ == "__main__":
|
|
|
165
165
|
setup_logger("default", debug_file_path, logging.INFO)
|
|
166
166
|
file_content = []
|
|
167
167
|
files = []
|
|
168
|
-
in_filepath_list = in_filepath.split(
|
|
168
|
+
in_filepath_list = in_filepath.split(",")
|
|
169
169
|
for in_filepath in in_filepath_list:
|
|
170
170
|
pre, ext = os.path.splitext(in_filepath)
|
|
171
171
|
|
|
@@ -173,18 +173,13 @@ if __name__ == "__main__":
|
|
|
173
173
|
# if output file path not specified, just chagne the extension
|
|
174
174
|
out_path = os.path.dirname(pre)
|
|
175
175
|
|
|
176
|
-
|
|
177
176
|
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
|
-
]
|
|
177
|
+
files = [os.path.join(in_filepath, f) for f in os.listdir(in_filepath) if f.endswith(".drawio")]
|
|
183
178
|
elif os.path.isfile(in_filepath) and in_filepath.endswith(".drawio"):
|
|
184
179
|
files = [in_filepath]
|
|
185
|
-
|
|
180
|
+
|
|
186
181
|
for f in files:
|
|
187
|
-
with open(f,
|
|
182
|
+
with open(f, "r") as s:
|
|
188
183
|
content = s.read()
|
|
189
184
|
# present issue with some drawio file that miss the XML header
|
|
190
185
|
|
|
@@ -193,7 +188,6 @@ if __name__ == "__main__":
|
|
|
193
188
|
logger.critical(f"{in_filepath} is neither a drawio file nor a directory containing drawio files")
|
|
194
189
|
exit(1)
|
|
195
190
|
|
|
196
|
-
|
|
197
191
|
strategy = globals()[input_strategy](files)
|
|
198
192
|
logger.info(f"build the graph from strategy {input_strategy}")
|
|
199
193
|
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)
|