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.
Files changed (66) hide show
  1. tests/build.py +213 -0
  2. tests/test_cql.py +197 -0
  3. tests/to_ocl.py +51 -0
  4. {tricc → tricc_oo}/__init__.py +3 -1
  5. tricc_oo/converters/codesystem_to_ocl.py +169 -0
  6. tricc_oo/converters/cql/cqlLexer.py +822 -0
  7. tricc_oo/converters/cql/cqlListener.py +1632 -0
  8. tricc_oo/converters/cql/cqlParser.py +11204 -0
  9. tricc_oo/converters/cql/cqlVisitor.py +913 -0
  10. tricc_oo/converters/cql_to_operation.py +402 -0
  11. tricc_oo/converters/datadictionnary.py +115 -0
  12. tricc_oo/converters/drawio_type_map.py +222 -0
  13. tricc_oo/converters/tricc_to_xls_form.py +61 -0
  14. tricc_oo/converters/utils.py +65 -0
  15. tricc_oo/converters/xml_to_tricc.py +1003 -0
  16. tricc_oo/models/__init__.py +4 -0
  17. tricc_oo/models/base.py +732 -0
  18. tricc_oo/models/calculate.py +216 -0
  19. tricc_oo/models/ocl.py +281 -0
  20. tricc_oo/models/ordered_set.py +125 -0
  21. tricc_oo/models/tricc.py +418 -0
  22. tricc_oo/parsers/xml.py +138 -0
  23. tricc_oo/serializers/__init__.py +0 -0
  24. tricc_oo/serializers/xls_form.py +745 -0
  25. tricc_oo/strategies/__init__.py +0 -0
  26. tricc_oo/strategies/input/__init__.py +0 -0
  27. tricc_oo/strategies/input/base_input_strategy.py +111 -0
  28. tricc_oo/strategies/input/drawio.py +317 -0
  29. tricc_oo/strategies/output/base_output_strategy.py +148 -0
  30. tricc_oo/strategies/output/spice.py +365 -0
  31. tricc_oo/strategies/output/xls_form.py +697 -0
  32. tricc_oo/strategies/output/xlsform_cdss.py +189 -0
  33. tricc_oo/strategies/output/xlsform_cht.py +200 -0
  34. tricc_oo/strategies/output/xlsform_cht_hf.py +334 -0
  35. tricc_oo/visitors/__init__.py +0 -0
  36. tricc_oo/visitors/tricc.py +2199 -0
  37. tricc_oo/visitors/utils.py +17 -0
  38. tricc_oo/visitors/xform_pd.py +264 -0
  39. tricc_oo-1.4.16.dist-info/METADATA +219 -0
  40. tricc_oo-1.4.16.dist-info/RECORD +46 -0
  41. {tricc_oo-1.0.2.dist-info → tricc_oo-1.4.16.dist-info}/WHEEL +1 -1
  42. tricc_oo-1.4.16.dist-info/top_level.txt +2 -0
  43. tricc/converters/mc_to_tricc.py +0 -542
  44. tricc/converters/tricc_to_xls_form.py +0 -553
  45. tricc/converters/utils.py +0 -44
  46. tricc/converters/xml_to_tricc.py +0 -740
  47. tricc/models/tricc.py +0 -1093
  48. tricc/parsers/xml.py +0 -81
  49. tricc/serializers/xls_form.py +0 -364
  50. tricc/strategies/input/base_input_strategy.py +0 -80
  51. tricc/strategies/input/drawio.py +0 -246
  52. tricc/strategies/input/medalcreator.py +0 -168
  53. tricc/strategies/output/base_output_strategy.py +0 -92
  54. tricc/strategies/output/xls_form.py +0 -308
  55. tricc/strategies/output/xlsform_cdss.py +0 -46
  56. tricc/strategies/output/xlsform_cht.py +0 -106
  57. tricc/visitors/tricc.py +0 -375
  58. tricc_oo-1.0.2.dist-info/LICENSE +0 -78
  59. tricc_oo-1.0.2.dist-info/METADATA +0 -229
  60. tricc_oo-1.0.2.dist-info/RECORD +0 -26
  61. tricc_oo-1.0.2.dist-info/top_level.txt +0 -2
  62. venv/bin/vba_extract.py +0 -78
  63. {tricc → tricc_oo}/converters/__init__.py +0 -0
  64. {tricc → tricc_oo}/models/lang.py +0 -0
  65. {tricc/serializers → tricc_oo/parsers}/__init__.py +0 -0
  66. {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)
@@ -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__ = "0.0.0"
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