tricc-oo 1.0.2__py3-none-any.whl → 1.4.16__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 +213 -0
- tests/test_cql.py +197 -0
- tests/to_ocl.py +51 -0
- {tricc → tricc_oo}/__init__.py +3 -1
- tricc_oo/converters/codesystem_to_ocl.py +169 -0
- tricc_oo/converters/cql/cqlLexer.py +822 -0
- tricc_oo/converters/cql/cqlListener.py +1632 -0
- tricc_oo/converters/cql/cqlParser.py +11204 -0
- tricc_oo/converters/cql/cqlVisitor.py +913 -0
- tricc_oo/converters/cql_to_operation.py +402 -0
- tricc_oo/converters/datadictionnary.py +115 -0
- tricc_oo/converters/drawio_type_map.py +222 -0
- tricc_oo/converters/tricc_to_xls_form.py +61 -0
- tricc_oo/converters/utils.py +65 -0
- tricc_oo/converters/xml_to_tricc.py +1003 -0
- tricc_oo/models/__init__.py +4 -0
- tricc_oo/models/base.py +732 -0
- tricc_oo/models/calculate.py +216 -0
- tricc_oo/models/ocl.py +281 -0
- tricc_oo/models/ordered_set.py +125 -0
- tricc_oo/models/tricc.py +418 -0
- tricc_oo/parsers/xml.py +138 -0
- tricc_oo/serializers/__init__.py +0 -0
- tricc_oo/serializers/xls_form.py +745 -0
- tricc_oo/strategies/__init__.py +0 -0
- tricc_oo/strategies/input/__init__.py +0 -0
- tricc_oo/strategies/input/base_input_strategy.py +111 -0
- tricc_oo/strategies/input/drawio.py +317 -0
- tricc_oo/strategies/output/base_output_strategy.py +148 -0
- tricc_oo/strategies/output/spice.py +365 -0
- tricc_oo/strategies/output/xls_form.py +697 -0
- tricc_oo/strategies/output/xlsform_cdss.py +189 -0
- tricc_oo/strategies/output/xlsform_cht.py +200 -0
- tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
- tricc_oo/visitors/__init__.py +0 -0
- tricc_oo/visitors/tricc.py +2199 -0
- tricc_oo/visitors/utils.py +17 -0
- tricc_oo/visitors/xform_pd.py +264 -0
- tricc_oo-1.4.16.dist-info/METADATA +219 -0
- tricc_oo-1.4.16.dist-info/RECORD +46 -0
- {tricc_oo-1.0.2.dist-info → tricc_oo-1.4.16.dist-info}/WHEEL +1 -1
- tricc_oo-1.4.16.dist-info/top_level.txt +2 -0
- tricc/converters/mc_to_tricc.py +0 -542
- tricc/converters/tricc_to_xls_form.py +0 -553
- tricc/converters/utils.py +0 -44
- tricc/converters/xml_to_tricc.py +0 -740
- tricc/models/tricc.py +0 -1093
- tricc/parsers/xml.py +0 -81
- tricc/serializers/xls_form.py +0 -364
- tricc/strategies/input/base_input_strategy.py +0 -80
- tricc/strategies/input/drawio.py +0 -246
- tricc/strategies/input/medalcreator.py +0 -168
- tricc/strategies/output/base_output_strategy.py +0 -92
- tricc/strategies/output/xls_form.py +0 -308
- tricc/strategies/output/xlsform_cdss.py +0 -46
- tricc/strategies/output/xlsform_cht.py +0 -106
- tricc/visitors/tricc.py +0 -375
- tricc_oo-1.0.2.dist-info/LICENSE +0 -78
- tricc_oo-1.0.2.dist-info/METADATA +0 -229
- tricc_oo-1.0.2.dist-info/RECORD +0 -26
- tricc_oo-1.0.2.dist-info/top_level.txt +0 -2
- venv/bin/vba_extract.py +0 -78
- {tricc → tricc_oo}/converters/__init__.py +0 -0
- {tricc → tricc_oo}/models/lang.py +0 -0
- {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
- {tricc → tricc_oo}/serializers/planuml.py +0 -0
tests/build.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import getopt
|
|
2
|
+
import gettext
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import gc
|
|
7
|
+
import shutil
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# set up logging to file
|
|
11
|
+
from tricc_oo.models.lang import SingletonLangClass
|
|
12
|
+
|
|
13
|
+
# gettext.bindtextdomain('tricc', './locale/')
|
|
14
|
+
# gettext.textdomain('tricc')
|
|
15
|
+
langs = SingletonLangClass()
|
|
16
|
+
|
|
17
|
+
# fr = gettext.translation('tricc', './locales' , languages=['fr'])
|
|
18
|
+
# fr.install()
|
|
19
|
+
# en = gettext.translation('tricc', './locales' , languages=['en'])
|
|
20
|
+
# en.install()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# langs.add_trad('fr', fr)
|
|
24
|
+
# langs.add_trad('en', en)
|
|
25
|
+
|
|
26
|
+
from tricc_oo.strategies.input.drawio import DrawioStrategy
|
|
27
|
+
|
|
28
|
+
# from tricc_oo.serializers.medalcreator import execute
|
|
29
|
+
|
|
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
|
+
|
|
36
|
+
def setup_logger(
|
|
37
|
+
logger_name,
|
|
38
|
+
log_file,
|
|
39
|
+
level=logging.INFO,
|
|
40
|
+
formatting="[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s",
|
|
41
|
+
):
|
|
42
|
+
l = logging.getLogger(logger_name)
|
|
43
|
+
formatter = logging.Formatter(formatting)
|
|
44
|
+
file_handler = logging.FileHandler(log_file, mode="w+")
|
|
45
|
+
file_handler.setFormatter(formatter)
|
|
46
|
+
stream_handler = logging.StreamHandler()
|
|
47
|
+
stream_handler.setFormatter(formatter)
|
|
48
|
+
|
|
49
|
+
l.setLevel(level)
|
|
50
|
+
l.addHandler(file_handler)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ColorFormatter(logging.Formatter):
|
|
54
|
+
# Define ANSI escape codes for colors
|
|
55
|
+
grey = "\x1b[38;21m"
|
|
56
|
+
yellow = "\x1b[33;21m"
|
|
57
|
+
red = "\x1b[31;21m"
|
|
58
|
+
bold_red = "\x1b[31;1m"
|
|
59
|
+
reset = "\x1b[0m"
|
|
60
|
+
format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
|
|
61
|
+
|
|
62
|
+
# Map log levels to their respective colors
|
|
63
|
+
FORMATS = {
|
|
64
|
+
logging.DEBUG: grey + format + reset,
|
|
65
|
+
logging.INFO: grey + format + reset,
|
|
66
|
+
logging.WARNING: yellow + format + reset,
|
|
67
|
+
logging.ERROR: red + format + reset,
|
|
68
|
+
logging.CRITICAL: bold_red + format + reset,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def format(self, record):
|
|
72
|
+
# Get the appropriate color format for the log level
|
|
73
|
+
log_fmt = self.FORMATS.get(record.levelno)
|
|
74
|
+
formatter = logging.Formatter(log_fmt)
|
|
75
|
+
return formatter.format(record)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
logger = logging.getLogger("default")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# set up logging to console
|
|
82
|
+
console = logging.StreamHandler()
|
|
83
|
+
console.setLevel(logging.INFO)
|
|
84
|
+
# set a format which is simpler for console use
|
|
85
|
+
console.setFormatter(ColorFormatter())
|
|
86
|
+
# add the handler to the root logger
|
|
87
|
+
logging.getLogger("default").addHandler(console)
|
|
88
|
+
|
|
89
|
+
LEVELS = {
|
|
90
|
+
"d": logging.DEBUG,
|
|
91
|
+
"w": logging.WARNING,
|
|
92
|
+
"i": logging.INFO,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def print_help():
|
|
97
|
+
print(
|
|
98
|
+
"-i / --input draw.io filepath (MANDATORY) or directory containing drawio files"
|
|
99
|
+
)
|
|
100
|
+
print("-o / --output xls file ")
|
|
101
|
+
print("-d form_id ")
|
|
102
|
+
print("-s L4 system/strategy (odk, cht, cc)")
|
|
103
|
+
print("-h / --help print that menu")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
gc.disable()
|
|
108
|
+
|
|
109
|
+
system = "odk"
|
|
110
|
+
in_filepath = None
|
|
111
|
+
out_path = None
|
|
112
|
+
form_id = None
|
|
113
|
+
debug_level = None
|
|
114
|
+
trad = False
|
|
115
|
+
download_dir = None
|
|
116
|
+
input_strategy = "DrawioStrategy"
|
|
117
|
+
output_strategy = "XLSFormStrategy"
|
|
118
|
+
try:
|
|
119
|
+
opts, args = getopt.getopt(
|
|
120
|
+
sys.argv[1:], "hti:o:s:I:O:l:d:D:", ["input=", "output=", "help", "trads"]
|
|
121
|
+
)
|
|
122
|
+
except getopt.GetoptError:
|
|
123
|
+
print_help()
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
for opt, arg in opts:
|
|
126
|
+
if opt in ("-h", "--help"):
|
|
127
|
+
print_help()
|
|
128
|
+
sys.exit()
|
|
129
|
+
elif opt in ("-i", "--input"):
|
|
130
|
+
in_filepath = arg
|
|
131
|
+
elif opt == "-o":
|
|
132
|
+
out_path = arg
|
|
133
|
+
elif opt == "-I":
|
|
134
|
+
input_strategy = arg
|
|
135
|
+
elif opt == "-O":
|
|
136
|
+
output_strategy = arg
|
|
137
|
+
elif opt == "-d":
|
|
138
|
+
form_id = arg
|
|
139
|
+
elif opt == "-l":
|
|
140
|
+
debug_level = arg
|
|
141
|
+
elif opt in ("-t", "--trads"):
|
|
142
|
+
trad = True
|
|
143
|
+
elif opt == "-D":
|
|
144
|
+
download_dir = arg
|
|
145
|
+
if in_filepath is None:
|
|
146
|
+
print_help()
|
|
147
|
+
sys.exit(2)
|
|
148
|
+
in_filepath_list = in_filepath.split(',')
|
|
149
|
+
if not download_dir:
|
|
150
|
+
download_dir = out_path
|
|
151
|
+
debug_path = os.fspath(out_path + "/debug.log")
|
|
152
|
+
debug_path = os.path.abspath(debug_path)
|
|
153
|
+
|
|
154
|
+
debug_file = Path(debug_path)
|
|
155
|
+
debug_file.parent.mkdir(exist_ok=True, parents=True)
|
|
156
|
+
logfile = open(debug_path, "w")
|
|
157
|
+
|
|
158
|
+
debug_file_path = os.path.join(out_path, "debug.log")
|
|
159
|
+
|
|
160
|
+
if debug_level is not None:
|
|
161
|
+
setup_logger("default", debug_file_path, LEVELS[debug_level])
|
|
162
|
+
elif "pydevd" in sys.modules:
|
|
163
|
+
setup_logger("default", debug_file_path, logging.DEBUG)
|
|
164
|
+
else:
|
|
165
|
+
setup_logger("default", debug_file_path, logging.INFO)
|
|
166
|
+
file_content = []
|
|
167
|
+
files = []
|
|
168
|
+
for in_filepath in in_filepath_list:
|
|
169
|
+
pre, ext = os.path.splitext(in_filepath)
|
|
170
|
+
|
|
171
|
+
if out_path is None:
|
|
172
|
+
# if output file path not specified, just chagne the extension
|
|
173
|
+
out_path = os.path.dirname(pre)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if os.path.isdir(in_filepath):
|
|
177
|
+
files = [
|
|
178
|
+
os.path.join(in_filepath, f)
|
|
179
|
+
for f in os.listdir(in_filepath)
|
|
180
|
+
if f.endswith(".drawio")
|
|
181
|
+
]
|
|
182
|
+
elif os.path.isfile(in_filepath) and in_filepath.endswith(".drawio"):
|
|
183
|
+
files = [in_filepath]
|
|
184
|
+
|
|
185
|
+
for f in files:
|
|
186
|
+
with open(f, 'r') as s:
|
|
187
|
+
content = s.read()
|
|
188
|
+
# present issue with some drawio file that miss the XML header
|
|
189
|
+
|
|
190
|
+
file_content.append(content)
|
|
191
|
+
if not file_content:
|
|
192
|
+
logger.critical(f"{in_filepath} is neither a drawio file nor a directory containing drawio files")
|
|
193
|
+
exit(1)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
strategy = globals()[input_strategy](files)
|
|
197
|
+
logger.info(f"build the graph from strategy {input_strategy}")
|
|
198
|
+
media_path = os.path.join(out_path, "media-tmp")
|
|
199
|
+
project = strategy.execute(file_content, media_path)
|
|
200
|
+
|
|
201
|
+
strategy = globals()[output_strategy](project, out_path)
|
|
202
|
+
|
|
203
|
+
logger.info("Using strategy {}".format(strategy.__class__))
|
|
204
|
+
logger.info("update the node with basic information")
|
|
205
|
+
# create constraints, clean name
|
|
206
|
+
|
|
207
|
+
output = strategy.execute()
|
|
208
|
+
|
|
209
|
+
# compress the output folder to a zip archieve and place it in the download directory
|
|
210
|
+
# shutil.make_archive(os.path.join(download_dir), "zip", os.path.join(out_path))
|
|
211
|
+
|
|
212
|
+
# if trad:
|
|
213
|
+
# langs.to_po_file("./trad.po")
|
tests/test_cql.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from tricc_oo.converters.cql_to_operation import cqlToXlsFormVisitor, transform_cql_to_operation
|
|
4
|
+
from tricc_oo.models.base import TriccOperator, TriccOperation, TriccStatic, TriccReference
|
|
5
|
+
|
|
6
|
+
class TestCql(unittest.TestCase):
|
|
7
|
+
def test_and(self):
|
|
8
|
+
if_cql = "\"p_weight\" is not null and \"p_age\" > 2"
|
|
9
|
+
dg_operation = transform_cql_to_operation(if_cql)
|
|
10
|
+
dg_expected = TriccOperation(
|
|
11
|
+
operator=TriccOperator.AND,
|
|
12
|
+
reference=[
|
|
13
|
+
TriccOperation(
|
|
14
|
+
operator=TriccOperator.NOT,
|
|
15
|
+
reference=[
|
|
16
|
+
TriccOperation(
|
|
17
|
+
operator=TriccOperator.ISNULL,
|
|
18
|
+
reference=[TriccReference("p_weight")]
|
|
19
|
+
)
|
|
20
|
+
]
|
|
21
|
+
),
|
|
22
|
+
TriccOperation(
|
|
23
|
+
operator=TriccOperator.MORE,
|
|
24
|
+
reference=[
|
|
25
|
+
TriccReference("p_age"),
|
|
26
|
+
TriccStatic(
|
|
27
|
+
value=2
|
|
28
|
+
)
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
]
|
|
32
|
+
)
|
|
33
|
+
self.assertEqual(str(dg_operation), str(dg_expected))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_durg_doage(self):
|
|
37
|
+
if_cql = "DrugDosage('paracetamol', \"p_weight\", \"p_age\")"
|
|
38
|
+
dg_operation = transform_cql_to_operation(if_cql)
|
|
39
|
+
dg_expected = TriccOperation(
|
|
40
|
+
operator=TriccOperator.DRUG_DOSAGE,
|
|
41
|
+
reference=[
|
|
42
|
+
TriccStatic(value='paracetamol'),
|
|
43
|
+
TriccReference("p_weight"),
|
|
44
|
+
TriccReference("p_age")
|
|
45
|
+
]
|
|
46
|
+
)
|
|
47
|
+
self.assertEqual(str(dg_operation), str(dg_expected))
|
|
48
|
+
|
|
49
|
+
def test_if(self):
|
|
50
|
+
if_cql = "if AgeInDays() < 60 then 'newborn' else 'child'"
|
|
51
|
+
if_operation = transform_cql_to_operation(if_cql)
|
|
52
|
+
if_expected = TriccOperation(
|
|
53
|
+
operator=TriccOperator.IF,
|
|
54
|
+
reference=[
|
|
55
|
+
TriccOperation(
|
|
56
|
+
operator=TriccOperator.LESS,
|
|
57
|
+
reference=[
|
|
58
|
+
TriccOperation(
|
|
59
|
+
operator=TriccOperator.AGE_DAY,
|
|
60
|
+
reference=[]
|
|
61
|
+
),
|
|
62
|
+
TriccStatic(
|
|
63
|
+
value=60
|
|
64
|
+
)
|
|
65
|
+
]
|
|
66
|
+
),
|
|
67
|
+
TriccStatic(value='newborn'),
|
|
68
|
+
TriccStatic(value='child'),
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
self.assertEqual(str(if_operation), str(if_expected))
|
|
72
|
+
|
|
73
|
+
def test_case(self):
|
|
74
|
+
case_cql = """
|
|
75
|
+
case AgeInMonths()
|
|
76
|
+
when 0 then 'newborn'
|
|
77
|
+
when 1 then 'newborn'
|
|
78
|
+
else 'child' end
|
|
79
|
+
"""
|
|
80
|
+
case_operation = transform_cql_to_operation(case_cql)
|
|
81
|
+
case_expected = TriccOperation(
|
|
82
|
+
operator=TriccOperator.CASE,
|
|
83
|
+
reference=[
|
|
84
|
+
TriccOperation(
|
|
85
|
+
operator=TriccOperator.AGE_MONTH,
|
|
86
|
+
reference=[]
|
|
87
|
+
),
|
|
88
|
+
[
|
|
89
|
+
TriccStatic(
|
|
90
|
+
value=0
|
|
91
|
+
),
|
|
92
|
+
TriccStatic(
|
|
93
|
+
value="newborn"
|
|
94
|
+
)
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
TriccStatic(
|
|
98
|
+
value=1
|
|
99
|
+
),
|
|
100
|
+
TriccStatic(
|
|
101
|
+
value="newborn"
|
|
102
|
+
)
|
|
103
|
+
],
|
|
104
|
+
TriccStatic(value='child'),
|
|
105
|
+
]
|
|
106
|
+
)
|
|
107
|
+
self.assertEqual(str(case_operation), str(case_expected))
|
|
108
|
+
|
|
109
|
+
def test_ifs(self):
|
|
110
|
+
case_cql = """
|
|
111
|
+
case
|
|
112
|
+
when AgeInMonths() <= 2 then true
|
|
113
|
+
when AgeInYears() > 5 then true
|
|
114
|
+
else false end
|
|
115
|
+
"""
|
|
116
|
+
case_operation = transform_cql_to_operation(case_cql)
|
|
117
|
+
case_expected = TriccOperation(
|
|
118
|
+
operator=TriccOperator.CASE,
|
|
119
|
+
reference=[
|
|
120
|
+
[
|
|
121
|
+
TriccOperation(
|
|
122
|
+
operator=TriccOperator.LESS_OR_EQUAL,
|
|
123
|
+
reference=[
|
|
124
|
+
TriccOperation(
|
|
125
|
+
operator=TriccOperator.AGE_MONTH,
|
|
126
|
+
reference=[]
|
|
127
|
+
),
|
|
128
|
+
TriccStatic(
|
|
129
|
+
value=2
|
|
130
|
+
)
|
|
131
|
+
]
|
|
132
|
+
),
|
|
133
|
+
TriccStatic(
|
|
134
|
+
value=True
|
|
135
|
+
)
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
TriccOperation(
|
|
139
|
+
operator=TriccOperator.MORE,
|
|
140
|
+
reference=[
|
|
141
|
+
TriccOperation(
|
|
142
|
+
operator=TriccOperator.AGE_YEAR,
|
|
143
|
+
reference=[]
|
|
144
|
+
),
|
|
145
|
+
TriccStatic(
|
|
146
|
+
value=5
|
|
147
|
+
)
|
|
148
|
+
]
|
|
149
|
+
),
|
|
150
|
+
TriccStatic(
|
|
151
|
+
value=True
|
|
152
|
+
)
|
|
153
|
+
],
|
|
154
|
+
TriccStatic(value=False),
|
|
155
|
+
]
|
|
156
|
+
)
|
|
157
|
+
self.assertEqual(str(case_operation), str(case_expected))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_minus(self):
|
|
161
|
+
minus_cql =""" "WFL" >= -3"""
|
|
162
|
+
# minus_cql = """
|
|
163
|
+
# ("age_in_months" < 6 and "WFL" >= -3 and "WFL" < -2) is true
|
|
164
|
+
# """
|
|
165
|
+
minus_operation = transform_cql_to_operation(minus_cql)
|
|
166
|
+
minus_expected = TriccOperation(
|
|
167
|
+
TriccOperator.MORE_OR_EQUAL,
|
|
168
|
+
[
|
|
169
|
+
TriccReference("WFL"),
|
|
170
|
+
TriccOperation(
|
|
171
|
+
TriccOperator.MINUS,
|
|
172
|
+
[TriccStatic(3)]
|
|
173
|
+
)
|
|
174
|
+
]
|
|
175
|
+
)
|
|
176
|
+
self.assertEqual(str(minus_operation), str(minus_expected))
|
|
177
|
+
|
|
178
|
+
def test_not_in(self):
|
|
179
|
+
not_in_cql = "'code' not in \"identifier\""
|
|
180
|
+
dg_operation = transform_cql_to_operation(not_in_cql)
|
|
181
|
+
dg_expected = TriccOperation(
|
|
182
|
+
operator=TriccOperator.NOT,
|
|
183
|
+
reference=[
|
|
184
|
+
TriccOperation(
|
|
185
|
+
operator=TriccOperator.SELECTED,
|
|
186
|
+
reference=[
|
|
187
|
+
TriccReference("identifier"),
|
|
188
|
+
TriccStatic(value='code'),
|
|
189
|
+
]
|
|
190
|
+
)
|
|
191
|
+
]
|
|
192
|
+
)
|
|
193
|
+
self.assertEqual(str(dg_operation), str(dg_expected))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == '__main__':
|
|
197
|
+
unittest.main()
|
tests/to_ocl.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from tricc_oo.converters.codesystem_to_ocl import transform_fhir_to_ocl
|
|
5
|
+
|
|
6
|
+
def find_and_process_codesystems(directory_path):
|
|
7
|
+
# Convert string path to Path object if not already
|
|
8
|
+
dir_path = Path(directory_path)
|
|
9
|
+
|
|
10
|
+
# Find all JSON files in the directory
|
|
11
|
+
for json_file in dir_path.glob("*.json"):
|
|
12
|
+
try:
|
|
13
|
+
# Read the JSON file
|
|
14
|
+
with open(json_file, 'r', encoding='utf-8') as f:
|
|
15
|
+
data = json.load(f)
|
|
16
|
+
|
|
17
|
+
# Check if resource_type is CodeSystem
|
|
18
|
+
if data.get("resourceType") == "CodeSystem":
|
|
19
|
+
# Get the filename without extension for output naming
|
|
20
|
+
file_key = json_file.stem
|
|
21
|
+
|
|
22
|
+
# Write the original CodeSystem JSON
|
|
23
|
+
output_cs_path = dir_path / f"{file_key}_codesystem.json"
|
|
24
|
+
with open(output_cs_path, "w", encoding='utf-8') as file:
|
|
25
|
+
file.write(json.dumps(data, indent=4))
|
|
26
|
+
|
|
27
|
+
# Transform to OCL payload
|
|
28
|
+
ocl_payload = transform_fhir_to_ocl(
|
|
29
|
+
data,
|
|
30
|
+
source_name="ALM",
|
|
31
|
+
source_owner="pdelcroix",
|
|
32
|
+
source_owner_type="User"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Save the transformed OCL payload
|
|
36
|
+
output_ocl_path = dir_path / f"{file_key}_ocl_bulk_upload.json"
|
|
37
|
+
with open(output_ocl_path, "w", encoding='utf-8') as f:
|
|
38
|
+
for item in ocl_payload:
|
|
39
|
+
json_line = json.dumps(item.dict(exclude_none=True))
|
|
40
|
+
f.write(json_line + '\n')
|
|
41
|
+
|
|
42
|
+
print(f"OCL bulk upload payload generated successfully for {file_key}!")
|
|
43
|
+
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
print(f"Error: Invalid JSON in file {json_file}")
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"Error processing {json_file}: {str(e)}")
|
|
48
|
+
|
|
49
|
+
# Example usage
|
|
50
|
+
media_path = "path/to/your/directory" # Replace with your directory path
|
|
51
|
+
find_and_process_codesystems(media_path)
|
{tricc → tricc_oo}/__init__.py
RENAMED
|
@@ -4,8 +4,10 @@ pyfhirsdc is a ython library that converts XLS to Fhir SDC ressource.
|
|
|
4
4
|
Collect easy.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "
|
|
7
|
+
__version__ = "1.0.2"
|
|
8
8
|
|
|
9
|
+
from .models.base import *
|
|
10
|
+
from .models.calculate import *
|
|
9
11
|
from .models.tricc import *
|
|
10
12
|
from .serializers import *
|
|
11
13
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
import json
|
|
3
|
+
from fhir.resources.codesystem import CodeSystem
|
|
4
|
+
from tricc_oo.models.ocl import (
|
|
5
|
+
OCLConcept,
|
|
6
|
+
OCLSource,
|
|
7
|
+
OCLDetailedName,
|
|
8
|
+
OCLDetailedDescription,
|
|
9
|
+
OclConstants,
|
|
10
|
+
get_data_type
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def extract_properties_metadata(fhir_cs: CodeSystem) -> Dict[str, Dict]:
|
|
14
|
+
"""
|
|
15
|
+
Extracts property definitions from FHIR CodeSystem and converts them to OCL attribute types
|
|
16
|
+
"""
|
|
17
|
+
property_types = {}
|
|
18
|
+
if hasattr(fhir_cs, 'property') and fhir_cs.property:
|
|
19
|
+
for prop in fhir_cs.property:
|
|
20
|
+
# Map FHIR property types to OCL datatypes
|
|
21
|
+
fhir_type = prop.type
|
|
22
|
+
ocl_type = {
|
|
23
|
+
"code": "Text",
|
|
24
|
+
"Coding": "Concept",
|
|
25
|
+
"boolean": "Boolean",
|
|
26
|
+
"decimal": "Numeric",
|
|
27
|
+
"integer": "Numeric",
|
|
28
|
+
"dateTime": "DateTime",
|
|
29
|
+
"string": "Text"
|
|
30
|
+
}.get(fhir_type, "Text")
|
|
31
|
+
|
|
32
|
+
property_types[prop.code] = {
|
|
33
|
+
"name": prop.code,
|
|
34
|
+
"datatype": ocl_type,
|
|
35
|
+
"description": prop.description if hasattr(prop, 'description') else ""
|
|
36
|
+
}
|
|
37
|
+
return property_types
|
|
38
|
+
|
|
39
|
+
def get_fhir_concept_datatype(concept):
|
|
40
|
+
|
|
41
|
+
datatype = extract_concept_properties(concept, ['datatype'])
|
|
42
|
+
if datatype:
|
|
43
|
+
return datatype['datatype']
|
|
44
|
+
else:
|
|
45
|
+
return OclConstants.DATA_TYPE_NONE
|
|
46
|
+
|
|
47
|
+
def extract_concept_properties(concept, property_types: List) -> List[Dict]:
|
|
48
|
+
"""
|
|
49
|
+
Extracts properties from a FHIR concept and converts them to OCL attributes
|
|
50
|
+
"""
|
|
51
|
+
properties = {}
|
|
52
|
+
if hasattr(concept, 'property') and concept.property:
|
|
53
|
+
for prop in concept.property:
|
|
54
|
+
if prop.code in property_types:
|
|
55
|
+
# Handle different property value types
|
|
56
|
+
if getattr(prop, 'valueCode', None):
|
|
57
|
+
value = prop.valueCode
|
|
58
|
+
elif getattr(prop, 'valueCoding', None):
|
|
59
|
+
value = prop.valueCoding.code
|
|
60
|
+
elif getattr(prop, 'valueString', None):
|
|
61
|
+
value = prop.valueString
|
|
62
|
+
elif getattr(prop, 'valueBoolean', None):
|
|
63
|
+
value = prop.valueBoolean
|
|
64
|
+
elif getattr(prop, 'valueInteger', None):
|
|
65
|
+
value = prop.valueInteger
|
|
66
|
+
elif getattr(prop, 'valueDecimal', None):
|
|
67
|
+
value = prop.valueDecimal
|
|
68
|
+
elif getattr(prop, 'valueDateTime', None):
|
|
69
|
+
value = prop.valueDateTime
|
|
70
|
+
else:
|
|
71
|
+
continue
|
|
72
|
+
if value:
|
|
73
|
+
properties[prop.code] = value
|
|
74
|
+
return properties
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_attributes_from_concept_properties(concept, property_types: Dict) -> List[Dict]:
|
|
78
|
+
attributes = []
|
|
79
|
+
properties = extract_concept_properties(concept, property_types=list(property_types))
|
|
80
|
+
for code, value in properties.items():
|
|
81
|
+
attributes.append({
|
|
82
|
+
"type": "Attribute",
|
|
83
|
+
"attribute_type": code,
|
|
84
|
+
"value": value,
|
|
85
|
+
"value_type": property_types[code]["datatype"]
|
|
86
|
+
})
|
|
87
|
+
return attributes
|
|
88
|
+
|
|
89
|
+
def transform_fhir_to_ocl(fhir_codesystem_json: Dict, source_name: str, source_owner: str, source_owner_type: str) -> List[Dict[str, Any]]:
|
|
90
|
+
"""
|
|
91
|
+
Transforms a FHIR CodeSystem resource into an OCL bulk upload JSON payload.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
fhir_codesystem_json: JSON representation of the FHIR CodeSystem resource
|
|
95
|
+
source_name: Name of the OCL Source
|
|
96
|
+
source_owner: Owner of the OCL Source (organization or user)
|
|
97
|
+
source_owner_type : User or Organization
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List of dictionaries representing OCL bulk upload format
|
|
101
|
+
"""
|
|
102
|
+
# Load the FHIR CodeSystem
|
|
103
|
+
fhir_cs = CodeSystem.parse_obj(fhir_codesystem_json)
|
|
104
|
+
|
|
105
|
+
# Extract property definitions
|
|
106
|
+
property_types = extract_properties_metadata(fhir_cs)
|
|
107
|
+
|
|
108
|
+
# Initialize OCL payload
|
|
109
|
+
ocl_payload = []
|
|
110
|
+
|
|
111
|
+
# Add source metadata
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Add property definitions to extras
|
|
115
|
+
if property_types:
|
|
116
|
+
source_extras["attribute_types"] = list(property_types.values())
|
|
117
|
+
|
|
118
|
+
ocl_payload.append(
|
|
119
|
+
OCLSource(
|
|
120
|
+
short_code=source_name,
|
|
121
|
+
id=source_name,
|
|
122
|
+
canonical_url=fhir_cs.url,
|
|
123
|
+
owner=source_owner,
|
|
124
|
+
owner_type=source_owner_type,
|
|
125
|
+
name=fhir_cs.name or "Unnamed Source",
|
|
126
|
+
full_name=fhir_cs.title if hasattr(fhir_cs, 'title') else fhir_cs.name,
|
|
127
|
+
description=fhir_cs.description or "",
|
|
128
|
+
source_type="Dictionary",
|
|
129
|
+
default_locale="en",
|
|
130
|
+
supported_locales=["en"],
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Transform concepts
|
|
135
|
+
if hasattr(fhir_cs, 'concept') and fhir_cs.concept:
|
|
136
|
+
for concept in fhir_cs.concept:
|
|
137
|
+
datatype = get_fhir_concept_datatype(concept)
|
|
138
|
+
ocl_concept = OCLConcept(
|
|
139
|
+
id=concept.code,
|
|
140
|
+
concept_class="Misc",
|
|
141
|
+
datatype=datatype,
|
|
142
|
+
owner=source_owner, # Added owner
|
|
143
|
+
owner_type=source_owner_type,
|
|
144
|
+
source=source_name, # Added source
|
|
145
|
+
names=[
|
|
146
|
+
OCLDetailedName(
|
|
147
|
+
name=concept.display,
|
|
148
|
+
locale="en",
|
|
149
|
+
name_type=OclConstants.NAME_TYPE_FULLY_SPECIFIED,
|
|
150
|
+
)
|
|
151
|
+
],
|
|
152
|
+
descriptions=[]
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Add definition if present
|
|
156
|
+
if hasattr(concept, 'definition') and concept.definition:
|
|
157
|
+
ocl_concept.descriptions.append(OCLDetailedDescription(
|
|
158
|
+
description=concept.definition,
|
|
159
|
+
locale="en",
|
|
160
|
+
))
|
|
161
|
+
|
|
162
|
+
# Extract and add properties as attributes
|
|
163
|
+
attributes = get_attributes_from_concept_properties(concept, property_types)
|
|
164
|
+
if attributes:
|
|
165
|
+
ocl_concept["attributes"] = attributes
|
|
166
|
+
|
|
167
|
+
ocl_payload.append(ocl_concept)
|
|
168
|
+
|
|
169
|
+
return ocl_payload
|