malac-hd 0.1.1a1__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.
- malac/__init__.py +0 -0
- malac/hd/__init__.py +33 -0
- malac/hd/__main__.py +98 -0
- malac/hd/compiler/__init__.py +0 -0
- malac/hd/compiler/fhir/__init__.py +0 -0
- malac/hd/compiler/fhir/r4/__init__.py +0 -0
- malac/hd/compiler/fhir/r4/generator/__init__.py +4 -0
- malac/hd/compiler/fhir/r4/generator/conceptmap.py +348 -0
- malac/hd/compiler/fhir/r4/generator/fhirpath.py +930 -0
- malac/hd/compiler/fhir/r4/generator/resource.py +168 -0
- malac/hd/compiler/fhir/r4/generator/structuredefinition.py +12 -0
- malac/hd/compiler/fhir/r4/generator/structuremap.py +1484 -0
- malac/hd/compiler/fhir/r4/parser/__init__.py +0 -0
- malac/hd/compiler/fhir/r4/parser/fhirpath.g4 +175 -0
- malac/hd/compiler/fhir/r4/parser/fhirpath.interp +153 -0
- malac/hd/compiler/fhir/r4/parser/fhirpath.tokens +118 -0
- malac/hd/compiler/fhir/r4/parser/fhirpathLexer.interp +215 -0
- malac/hd/compiler/fhir/r4/parser/fhirpathLexer.py +314 -0
- malac/hd/compiler/fhir/r4/parser/fhirpathLexer.tokens +118 -0
- malac/hd/compiler/fhir/r4/parser/fhirpathListener.py +381 -0
- malac/hd/compiler/fhir/r4/parser/fhirpathParser.py +1916 -0
- malac/hd/compiler/fhir/utils.py +1617 -0
- malac/models/__init__.py +0 -0
- malac/models/cda/__init__.py +0 -0
- malac/models/cda/at_ext.py +67064 -0
- malac/models/fhir/__init__.py +0 -0
- malac/models/fhir/r4.py +259191 -0
- malac_hd-0.1.1a1.dist-info/LICENSE +165 -0
- malac_hd-0.1.1a1.dist-info/METADATA +301 -0
- malac_hd-0.1.1a1.dist-info/RECORD +33 -0
- malac_hd-0.1.1a1.dist-info/WHEEL +5 -0
- malac_hd-0.1.1a1.dist-info/entry_points.txt +2 -0
- malac_hd-0.1.1a1.dist-info/top_level.txt +1 -0
malac/__init__.py
ADDED
|
File without changes
|
malac/hd/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.1alpha1"
|
|
4
|
+
|
|
5
|
+
class ConvertMaster(ABC):
|
|
6
|
+
@abstractmethod
|
|
7
|
+
def convert(self, input, o_module): # TODO:sbe adapt - this is not what is implemented/needed in base classes
|
|
8
|
+
pass
|
|
9
|
+
# return mapping_as_py
|
|
10
|
+
|
|
11
|
+
# not sure if this class will be needed, it is only unsed inside the into py converted map
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TransformMaster(ABC):
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def transform(self, source_path, target_path):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
list_m_modules = { # this are modules that can be fully created for malac_hd or Subclasses extending modules that are fully generated by generateDS
|
|
20
|
+
# for StructureMapSub, in this module are additionally planned
|
|
21
|
+
# ConceptMapSub & StructureDefinitionSub (this one also uses
|
|
22
|
+
# ElementDefinitionSub with the <map> element)
|
|
23
|
+
".4.fhir.xml": "fhir.r4.generator.resource",
|
|
24
|
+
#".4.fhir.map": "fhir4fml"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
list_i_o_modules = { # this are modules that are fully generated by generateDS
|
|
28
|
+
"http://hl7.org/fhir/StructureDefinition":"fhir.r4",
|
|
29
|
+
"http://hl7.org/fhir/4.0/StructureDefinition":"fhir.r4",
|
|
30
|
+
#"http://hl7.org/fhir/StructureDefinition":"fhir5",
|
|
31
|
+
"http://hl7.org/fhir/cda/StructureDefinition":"cda.at_ext",
|
|
32
|
+
"http://hl7.org/cda/stds/core/StructureDefinition":"cda.at_ext"
|
|
33
|
+
}
|
malac/hd/__main__.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import sys
|
|
5
|
+
import importlib.util
|
|
6
|
+
# dynamic import by init_class()!
|
|
7
|
+
|
|
8
|
+
if __package__ != "malac.hd":
|
|
9
|
+
current = os.path.dirname(os.path.realpath(__file__))
|
|
10
|
+
parent = os.path.dirname(current)
|
|
11
|
+
sys.path.append(parent)
|
|
12
|
+
import malac.hd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
description_text = """You are using the MApping LAnguage Compiler for Health Data, short MaLaC-HD.
|
|
16
|
+
We differentiate between two modes, CONVERTING and TRANSFORMING.
|
|
17
|
+
The CONVERTION is done by compiling a given mapping to python code, that itself can be run with its own argument handling for TRANSFORMING input files.
|
|
18
|
+
Additionally, the TRANSFORMATION can also be be done by MaLaC-HD directly after CONVERTION, i.e. for direct testing purposes."""
|
|
19
|
+
auto_detect = "the ressource type is detected by its root node inside the xml"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def init_argparse(script) -> argparse.ArgumentParser:
|
|
23
|
+
if script:
|
|
24
|
+
prog = "malac-hd"
|
|
25
|
+
else:
|
|
26
|
+
prog = "python -m malac.hd"
|
|
27
|
+
parser = argparse.ArgumentParser(prog=prog, description=description_text, formatter_class=argparse.RawTextHelpFormatter)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"-m", "--mapping", help='the mapping file path, the conversion/mapping rule language is detected by file ending, right now only FHIR R4 (*.4.fhir.xml) StructureMaps and ConceptMaps can be given as mappings', required=True
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"-co", "--convert_output", help='the conversion python file path, if not given, saved in the working directory with the map-file name', required=False
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"-ti", "--transform_input", help='the transformation input file path, '+auto_detect, required=False
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"-to", "--transform_output", help='the transformation output file path, '+auto_detect, required=False
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"-s", "--silent", action="store_true", help='do not print the converted python mapping to console', required=False
|
|
44
|
+
)
|
|
45
|
+
return parser
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_standard_by_fileending(filename, list_classes, return_ending_not_class=False):
|
|
49
|
+
for iclass in list_classes:
|
|
50
|
+
if filename.endswith(iclass):
|
|
51
|
+
if return_ending_not_class:
|
|
52
|
+
return iclass
|
|
53
|
+
return list_classes[iclass]
|
|
54
|
+
sys.exit("The classification could not be detected from the file name. \n %s" % description_text)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main(script) -> None:
|
|
58
|
+
start = time.time()
|
|
59
|
+
print("____________________ MaLaC-HD "+malac.hd.__version__+" started ____________________")
|
|
60
|
+
|
|
61
|
+
parser = init_argparse(script)
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
|
|
64
|
+
print("Converting "+args.mapping)
|
|
65
|
+
|
|
66
|
+
module_name = get_standard_by_fileending(args.mapping, malac.hd.list_m_modules)
|
|
67
|
+
module_split = ["malac", "hd", "compiler"] + module_name.split(".")
|
|
68
|
+
m_module = getattr(__import__(".".join(module_split[:-1]), fromlist=[module_split[-1]]), module_split[-1])
|
|
69
|
+
map = m_module.parse(args.mapping, silence=True)
|
|
70
|
+
|
|
71
|
+
py_code = map.convert(silent=args.silent, source=os.path.abspath(args.mapping))
|
|
72
|
+
|
|
73
|
+
# as similar as possible to https://www.hl7.org/fhir/resource-operation-convert.html
|
|
74
|
+
if args.convert_output:
|
|
75
|
+
if os.path.isfile(args.convert_output):
|
|
76
|
+
os.remove(args.convert_output)
|
|
77
|
+
with open(args.convert_output, 'w', newline='', encoding='utf-8') as f:
|
|
78
|
+
f.write(py_code)
|
|
79
|
+
|
|
80
|
+
# as similar as possible to https://www.hl7.org/fhir/structuremap-operation-transform.html
|
|
81
|
+
# if args.output is full file path, then do the transform itself we just created
|
|
82
|
+
if args.transform_output and args.transform_input:
|
|
83
|
+
print("")
|
|
84
|
+
spec = importlib.util.spec_from_loader("mapping", loader=None)
|
|
85
|
+
module = importlib.util.module_from_spec(spec)
|
|
86
|
+
exec(py_code, module.__dict__)
|
|
87
|
+
module.transform(args.transform_input, args.transform_output)
|
|
88
|
+
print("")
|
|
89
|
+
|
|
90
|
+
print("altogether in "+str(round(time.time()-start,3))+" seconds.")
|
|
91
|
+
print("____________________ MaLaC-HD "+malac.hd.__version__+" ended ____________________")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def script_main():
|
|
95
|
+
main(script=True)
|
|
96
|
+
|
|
97
|
+
if __name__ == '__main__':
|
|
98
|
+
main(script=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
|
|
2
|
+
import sys
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import malac.hd
|
|
6
|
+
from malac.models.fhir import r4 as supermod
|
|
7
|
+
|
|
8
|
+
class PythonGenerator(supermod.ConceptMap, malac.hd.ConvertMaster):
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
con_map_6d = None
|
|
11
|
+
|
|
12
|
+
def __init__(self, id=None, meta=None, implicitRules=None, language=None, text=None, contained=None, extension=None, modifierExtension=None, url=None, identifier=None, version=None, name=None, title=None, status=None, experimental=None, date=None, publisher=None, contact=None, description=None, useContext=None, jurisdiction=None, purpose=None, copyright=None, sourceUri=None, sourceCanonical=None, targetUri=None, targetCanonical=None, group=None, **kwargs_):
|
|
13
|
+
super(PythonGenerator, self).__init__(id, meta, implicitRules, language, text, contained, extension, modifierExtension, url, identifier, version, name, title, status, experimental, date, publisher, contact, description, useContext, jurisdiction, purpose, copyright, sourceUri, sourceCanonical, targetUri, targetCanonical, group, **kwargs_)
|
|
14
|
+
|
|
15
|
+
# the source param is not beeing handled, because there are no known possbilities of importing/including conceptMaps in other conceptMaps
|
|
16
|
+
def convert(self, silent=True, return_header_and_footer_for_standalone=True, source=".", return_translate_def=True, return_dict_add=True): # TODO:sbe replace source and return_XXX with context/params, to make more generic
|
|
17
|
+
self.py_code = ""
|
|
18
|
+
if return_header_and_footer_for_standalone:
|
|
19
|
+
self._header_standalone()
|
|
20
|
+
if return_translate_def:
|
|
21
|
+
self._header_translate_def()
|
|
22
|
+
if return_dict_add:
|
|
23
|
+
self.con_map_6d = self.nested_dict(5, list)
|
|
24
|
+
for one_group in self.get_group():
|
|
25
|
+
for one_element in one_group.get_element():
|
|
26
|
+
for one_target in one_element.get_target():
|
|
27
|
+
self.con_map_6d \
|
|
28
|
+
[tmp.get_value() if (tmp := self.get_sourceUri()) or (tmp := self.get_sourceCanonical()) else "%"] \
|
|
29
|
+
[tmp.get_value() if (tmp := self.get_targetUri()) or (tmp := self.get_targetCanonical()) else "%"] \
|
|
30
|
+
[tmp.get_value() if (tmp := one_group.get_source()) else "%"] \
|
|
31
|
+
[tmp.get_value() if (tmp := one_group.get_target()) else "%"] \
|
|
32
|
+
[tmp.get_value() if (tmp := one_element.get_code()) else "%"] \
|
|
33
|
+
.append({"equivalence":one_target.get_equivalence().get_value(),
|
|
34
|
+
"concept":{
|
|
35
|
+
"system": tmp.get_value() if (tmp := one_group.get_source()) else "",
|
|
36
|
+
"version": "", # TODO version of codesystem out of url?
|
|
37
|
+
"code": tmp.get_value() if (tmp := one_target.get_code()) else "",
|
|
38
|
+
"display": tmp.get_value() if (tmp := one_target.get_display()) else "",
|
|
39
|
+
"userSelected": False},
|
|
40
|
+
"source": tmp.get_value() if (tmp := self.get_url()) or (tmp := self.get_id()) else "%"})
|
|
41
|
+
|
|
42
|
+
if unmapped := one_group.get_unmapped(): # using as many unsafe url characters here, that really should not be used as system id/url or codes of a real codesystem, like [ ] { } | \ ” % ~ # < >
|
|
43
|
+
tmp_equivalence = "inexact"
|
|
44
|
+
tmp_code = ""
|
|
45
|
+
tmp_display = ""
|
|
46
|
+
tmp_code_lvl = ""
|
|
47
|
+
if unmapped.get_mode().get_value() == "provided":
|
|
48
|
+
tmp_code_lvl = "|"
|
|
49
|
+
tmp_equivalence = "equal"
|
|
50
|
+
tmp_code = "|" + (tmp.get_value() if (tmp := unmapped.get_code()) else "")
|
|
51
|
+
elif unmapped.get_mode().get_value() == "fixed":
|
|
52
|
+
tmp_code_lvl = "~"
|
|
53
|
+
tmp_code = "~"+unmapped.get_code().get_value() # must be given if fixed
|
|
54
|
+
tmp_display = unmapped.get_display().get_value() or ""
|
|
55
|
+
elif unmapped.get_mode().get_value() == "other-map":
|
|
56
|
+
#tmp_match = "lambda: translate(url="+unmapped.get_url()+", conceptMapVersion=conceptMapVersion, code=code, system=system, version=version, source=source, coding=coding, codeableConcept=codeableConcept, target=target, targetsystem=targetsystem, reverse=reverse, silent=silent)" # cant access the varibales inside the dict...
|
|
57
|
+
tmp_code_lvl = "#"
|
|
58
|
+
tmp_code = "#"+unmapped.get_url().get_value()
|
|
59
|
+
else:
|
|
60
|
+
sys.exit(unmapped.get_mode()+" as mode for unmapped is not defined! Please use the modes from https://hl7.org/fhir/R4B/valueset-conceptmap-unmapped-mode.html .")
|
|
61
|
+
self.con_map_6d \
|
|
62
|
+
[tmp.get_value() if (tmp := self.get_sourceUri()) or (tmp := self.get_sourceCanonical()) else "%"] \
|
|
63
|
+
[tmp.get_value() if (tmp := self.get_targetUri()) or (tmp := self.get_targetCanonical()) else "%"] \
|
|
64
|
+
[tmp.get_value() if (tmp := one_group.get_source()) else "%"] \
|
|
65
|
+
[tmp.get_value() if (tmp := one_group.get_target()) else "%"] \
|
|
66
|
+
[tmp_code_lvl] \
|
|
67
|
+
.append({"equivalence":tmp_equivalence,
|
|
68
|
+
"concept":{
|
|
69
|
+
"system": "",
|
|
70
|
+
"version": "", # TODO version of codesystem out of url?
|
|
71
|
+
"code": tmp_code,
|
|
72
|
+
"display": tmp_display,
|
|
73
|
+
"userSelected": False},
|
|
74
|
+
"source": tmp.get_value() if (tmp := self.get_url()) or (tmp := self.get_id()) else "%"})
|
|
75
|
+
|
|
76
|
+
self.py_code += "\n"
|
|
77
|
+
j_dump = json.dumps(self.con_map_6d, indent=4)
|
|
78
|
+
# replace all the json lower case bools to python usual pascal case bools
|
|
79
|
+
j_dump = j_dump.replace(": false",": False").replace(": true",": True")
|
|
80
|
+
self.py_code += 'con_map_7d["'+(tmp.get_value() if (tmp := self.get_url()) or (tmp := self.get_id()) else "%")+'"] = ' + j_dump
|
|
81
|
+
|
|
82
|
+
if return_header_and_footer_for_standalone:
|
|
83
|
+
self._footer_standalone()
|
|
84
|
+
|
|
85
|
+
if not silent:
|
|
86
|
+
print("\n%s" % self.py_code)
|
|
87
|
+
|
|
88
|
+
return self.py_code
|
|
89
|
+
|
|
90
|
+
# from https://stackoverflow.com/a/39819609/6012216
|
|
91
|
+
def nested_dict(self, n, type):
|
|
92
|
+
if n == 1:
|
|
93
|
+
return self.defaultdict(type)
|
|
94
|
+
else:
|
|
95
|
+
return self.defaultdict(lambda: self.nested_dict(n-1, type))
|
|
96
|
+
|
|
97
|
+
def _header_standalone(self):
|
|
98
|
+
self.py_code += '''import argparse
|
|
99
|
+
import time
|
|
100
|
+
import malac.models.fhir.r4 as fhir4
|
|
101
|
+
import sys
|
|
102
|
+
|
|
103
|
+
description_text = "This has been compiled by the MApping LAnguage Compiler for Health Data, short MaLaC-HD. See arguments for more details."
|
|
104
|
+
|
|
105
|
+
def init_argparse() -> argparse.ArgumentParser:
|
|
106
|
+
parser = argparse.ArgumentParser(description=description_text)
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
'-c', '--code', help="""code --
|
|
109
|
+
The code that is to be translated. If a code is provided, a system must be provided""",
|
|
110
|
+
required=True
|
|
111
|
+
)
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
'-sys', '--system', help="""uri --
|
|
114
|
+
The system for the code that is to be translated""",
|
|
115
|
+
required=True
|
|
116
|
+
)
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
'-ver', '--version', help="""string --
|
|
119
|
+
The version of the system, if one was provided in the source data""",
|
|
120
|
+
required=True
|
|
121
|
+
)
|
|
122
|
+
parser.add_argument(
|
|
123
|
+
'-s', '--source', help="""uri --
|
|
124
|
+
Identifies the value set used when the concept (system/code pair) was chosen.
|
|
125
|
+
May be a logical id, or an absolute or relative location.
|
|
126
|
+
The source value set is an optional parameter because in some cases, the client cannot know what the source value set is.
|
|
127
|
+
However, without a source value set, the server may be unable to safely identify an applicable concept map,
|
|
128
|
+
and would return an error. For this reason, a source value set SHOULD always be provided.
|
|
129
|
+
Note that servers may be able to identify an appropriate concept map without a source value set
|
|
130
|
+
if there is a full mapping for the entire code system in the concept map, or by manual intervention""",
|
|
131
|
+
required=True
|
|
132
|
+
)
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
'-cg', '--coding', help="""Coding --
|
|
135
|
+
A coding to translate""",
|
|
136
|
+
required=True
|
|
137
|
+
)
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
'-cc', '--codeableConcept', help="""CodeableConcept --
|
|
140
|
+
A full codeableConcept to validate. The server can translate any of the coding values (e.g. existing translations)
|
|
141
|
+
as it chooses""",
|
|
142
|
+
required=True
|
|
143
|
+
)
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
'-t', '--target', help="""uri --
|
|
146
|
+
Identifies the value set in which a translation is sought. May be a logical id, or an absolute or relative location.
|
|
147
|
+
If there's no target specified, the server should return all known translations, along with their source""",
|
|
148
|
+
required=True
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
'-ts', '--targetsystem', help="""uri --
|
|
152
|
+
Identifies a target code system in which a mapping is sought. This parameter is an alternative to the target parameter -
|
|
153
|
+
only one is required. Searching for any translation to a target code system irrespective of the context (e.g. target valueset)
|
|
154
|
+
may lead to unsafe results, and it is at the discretion of the server to decide when to support this operation""",
|
|
155
|
+
required=True
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
'-r', '--reverse', action="store_true", help="""boolean -- True if stated, else False --
|
|
159
|
+
If this is true, then the operation should return all the codes that might be mapped to this code.
|
|
160
|
+
This parameter reverses the meaning of the source and target parameters""",
|
|
161
|
+
required=True
|
|
162
|
+
)
|
|
163
|
+
parser.add_argument(
|
|
164
|
+
'-st', '--silent', action="store_true", help="""boolean -- True if stated, else False --
|
|
165
|
+
Do not print the converted python mapping to console""",
|
|
166
|
+
required=True
|
|
167
|
+
)
|
|
168
|
+
return parser
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
def _header_translate_def(self):
|
|
172
|
+
self.py_code += '''
|
|
173
|
+
# output
|
|
174
|
+
# 1..1 result (boolean)
|
|
175
|
+
# 0..1 message with error details for human (string)
|
|
176
|
+
# 0..* match with (list)
|
|
177
|
+
# 0..1 equivalnce (string from https://hl7.org/fhir/R4B/valueset-concept-map-equivalence.html)
|
|
178
|
+
# 0..1 concept
|
|
179
|
+
# 0..1 system
|
|
180
|
+
# 0..1 version
|
|
181
|
+
# 0..1 code
|
|
182
|
+
# 0..1 display
|
|
183
|
+
# 0..1 userSelected will always be false, because this is a translation
|
|
184
|
+
# 0..1 source (conceptMap url)
|
|
185
|
+
# TODO implement reverse
|
|
186
|
+
def translate(url=None, conceptMapVersion=None, code=None, system=None, version=None, source=None, coding=None, codeableConcept=None, target=None, targetsystem=None, reverse=None, silent=False)\
|
|
187
|
+
-> dict [bool, str, list[dict[str, dict[str, str, str, str, bool], str]]]:
|
|
188
|
+
start = time.time()
|
|
189
|
+
|
|
190
|
+
# start validation and recall of translate in simple from
|
|
191
|
+
if codeableConcept:
|
|
192
|
+
if isinstance(codeableConcept, str):
|
|
193
|
+
codeableConcept = fhir4.parseString(codeableConcept, silent)
|
|
194
|
+
elif isinstance(coding, fhir4.CodeableConcept):
|
|
195
|
+
pass
|
|
196
|
+
else:
|
|
197
|
+
sys.exit("The codeableConcept parameter has to be a string or a CodeableConcept Object (called method as library)!")
|
|
198
|
+
# the first fit will be returned, else the last unfitted value will be returned
|
|
199
|
+
# TODO check translate params
|
|
200
|
+
for one_coding in codeableConcept.get_coding:
|
|
201
|
+
if (ret := translate(url=url, source=source, coding=one_coding,
|
|
202
|
+
target=target, targetsystem=targetsystem,
|
|
203
|
+
reverse=reverse, silent=True))[0]:
|
|
204
|
+
return ret
|
|
205
|
+
else: return ret
|
|
206
|
+
|
|
207
|
+
elif coding:
|
|
208
|
+
if isinstance(coding, str):
|
|
209
|
+
coding = fhir4.parseString(coding, silent)
|
|
210
|
+
elif isinstance(coding, fhir4.Coding):
|
|
211
|
+
pass
|
|
212
|
+
else:
|
|
213
|
+
sys.exit("The coding parameter has to be a string or a Coding Object (called method as library)!")
|
|
214
|
+
# TODO check translate params
|
|
215
|
+
return translate(url=url, source=source, coding=one_coding,
|
|
216
|
+
target=target, targetsystem=targetsystem,
|
|
217
|
+
reverse=reverse, silent=True)
|
|
218
|
+
|
|
219
|
+
elif code:
|
|
220
|
+
if not isinstance(code,str):
|
|
221
|
+
sys.exit("The code parameter has to be a string!")
|
|
222
|
+
|
|
223
|
+
elif target:
|
|
224
|
+
if not isinstance(code,str):
|
|
225
|
+
sys.exit("The target parameter has to be a string!")
|
|
226
|
+
|
|
227
|
+
elif targetsystem:
|
|
228
|
+
if not isinstance(code,str):
|
|
229
|
+
sys.exit("The targetsystem parameter has to be a string!")
|
|
230
|
+
|
|
231
|
+
else:
|
|
232
|
+
sys.exit("At least codeableConcept, coding, code, target or targetSystem has to be given!")
|
|
233
|
+
# end validation and recall of translate in simplier from
|
|
234
|
+
|
|
235
|
+
# look for any information from the one ore more generated conceptMaps into con_map_7d
|
|
236
|
+
match = []
|
|
237
|
+
unmapped = []
|
|
238
|
+
if url not in con_map_7d.keys():
|
|
239
|
+
print(' #ERROR# ConceptMap with URL "'+ url +'" is not loaded to this compiled conceptMap #ERROR#')
|
|
240
|
+
else:
|
|
241
|
+
for url_lvl in con_map_7d:
|
|
242
|
+
if url_lvl == "%" or url_lvl == str(url or ""):#+str(("/?version=" and conceptMapVersion) or ""):
|
|
243
|
+
for source_lvl in con_map_7d[url_lvl]:
|
|
244
|
+
if source_lvl == "%" or not source or source_lvl == source:
|
|
245
|
+
for target_lvl in con_map_7d[url_lvl][source_lvl]:
|
|
246
|
+
if target_lvl == "%" or not target or target_lvl == target:
|
|
247
|
+
for system_lvl in con_map_7d[url_lvl][source_lvl][target_lvl]:
|
|
248
|
+
if system_lvl == "%" or not system or system_lvl == system:#+str(("/?version=" and version) or ""):
|
|
249
|
+
for targetsystem_lvl in con_map_7d[url_lvl][source_lvl][target_lvl][system_lvl]:
|
|
250
|
+
if targetsystem_lvl == "%" or not targetsystem or targetsystem_lvl == targetsystem:
|
|
251
|
+
for code_lvl in con_map_7d[url_lvl][source_lvl][target_lvl][system_lvl][targetsystem_lvl]:
|
|
252
|
+
if code_lvl == "|" or code_lvl == "~" or code_lvl == "#":
|
|
253
|
+
unmapped += con_map_7d[url_lvl][source_lvl][target_lvl][system_lvl][targetsystem_lvl][code_lvl]
|
|
254
|
+
if code_lvl == "%" or not code or code_lvl == code:
|
|
255
|
+
match += con_map_7d[url_lvl][source_lvl][target_lvl][system_lvl][targetsystem_lvl][code_lvl]
|
|
256
|
+
|
|
257
|
+
if not match:
|
|
258
|
+
for one_unmapped in unmapped:
|
|
259
|
+
tmp_system = ""
|
|
260
|
+
tmp_version = ""
|
|
261
|
+
tmp_code = ""
|
|
262
|
+
tmp_display = ""
|
|
263
|
+
# replace all "|" values with to translated code (provided from https://hl7.org/fhir/R4B/conceptmap-definitions.html#ConceptMap.group.unmapped.mode)
|
|
264
|
+
if one_unmapped["concept"]["code"].startswith("|"):
|
|
265
|
+
tmp_system = system
|
|
266
|
+
tmp_version = version
|
|
267
|
+
tmp_code = one_unmapped["concept"]["code"][1:] + code
|
|
268
|
+
# replace all "~" values with fixed code (provided from https://hl7.org/fhir/R4B/conceptmap-definitions.html#ConceptMap.group.unmapped.mode)
|
|
269
|
+
elif one_unmapped["concept"]["code"].startswith("~"):
|
|
270
|
+
tmp_code = one_unmapped["concept"]["code"][1:]
|
|
271
|
+
tmp_display = one_unmapped["concept"]["display"]
|
|
272
|
+
elif one_unmapped["concept"]["code"].startswith("#"):
|
|
273
|
+
# TODO detect recursion like conceptMapA -> conceptMapB -> ConceptMapA -> ...
|
|
274
|
+
return translate(one_unmapped["concept"]["code"][1:], None, code, system, version, source,
|
|
275
|
+
coding, codeableConcept, target, targetsystem, reverse, silent)
|
|
276
|
+
match.append({"equivalence": one_unmapped["equivalence"],
|
|
277
|
+
"concept":{
|
|
278
|
+
"system": tmp_system,
|
|
279
|
+
"version": tmp_version, # TODO version of codesystem out of url?
|
|
280
|
+
"code": tmp_code,
|
|
281
|
+
"display": tmp_display,
|
|
282
|
+
"userSelected": False},
|
|
283
|
+
"source": one_unmapped["source"]})
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# see if any match is not "unmatched" or "disjoint"
|
|
287
|
+
result = False
|
|
288
|
+
message = ""
|
|
289
|
+
for one_match in match:
|
|
290
|
+
if one_match["equivalence"] != "unmatched" and one_match["equivalence"] != "disjoint":
|
|
291
|
+
result = True
|
|
292
|
+
|
|
293
|
+
if not silent:
|
|
294
|
+
print('Translation in '+str(round(time.time()-start,3))+' seconds for code "'+code+'" with ConceptMap "'+url+'"')
|
|
295
|
+
return {"result": result, "message": message, "match": match}
|
|
296
|
+
|
|
297
|
+
# The con_map_7d is a seven dimensional dictionary, for quickly finding the fitting translation
|
|
298
|
+
# All dimensions except the last are optional, so a explicit NONE value will be used as key and
|
|
299
|
+
# interpreted as the default key, that always will be fitting, no matter what other keys are fitting.
|
|
300
|
+
# If a version is included (purely optional), than the version will be added with a blank before to the key
|
|
301
|
+
#
|
|
302
|
+
# The 0th dimension is mandatory and stating the ConceptMap with its url (including the version).
|
|
303
|
+
#
|
|
304
|
+
# The 1st dimension is optional and stating the SOURCE valueset (including the version), as one conceptMap can only
|
|
305
|
+
# have a maximum of one SOURCE, this is reserved for MaLaC-HD ability to process multiple ConceptMaps in one output.
|
|
306
|
+
#
|
|
307
|
+
# The 2nd dimension is optional and stating the TARGET valueset (including the version), as one conceptMap can only
|
|
308
|
+
# have a maximum of one TARGET, this is reserved for MaLaC-HD ability to process multiple ConceptMaps in one output.
|
|
309
|
+
#
|
|
310
|
+
# The 3th dimension is optional and stating the SYSTEM (including the version) from the source valueset code, as one
|
|
311
|
+
# code could be used in multiple SYSTEMs from the source valueset to translate.
|
|
312
|
+
# Not stating a SYSTEM with a code, is not FHIR compliant and not a whole concept, but still a valid conceptmap.
|
|
313
|
+
# As many conceptMaps exists that are worngly using this SYSTEM element as stating the valueset, that should be
|
|
314
|
+
# stated in source, this case will still be supported by MaLaC-HD. Having a conceptMap with a source valueset
|
|
315
|
+
# and a different SYSTEM valueset will result in an impossible match and an error will not be recognized by MaLaC-HD.
|
|
316
|
+
#
|
|
317
|
+
# The 4th dimension is optional and stating the TARGET SYSTEM (including the version) from the target valueset code, as one
|
|
318
|
+
# code could be used in multiple SYSTEMs from the target valueset to translate.
|
|
319
|
+
# Not stating a TARGET SYSTEM with a code, is not FHIR compliant and not a whole concept, but still a valid conceptmap.
|
|
320
|
+
# As many conceptMaps exists that are worngly using this TARGET SYSTEM element as stating the target valueset, that should be
|
|
321
|
+
# stated in target, this case will still be supported by MaLaC-HD. Having a conceptMap with a target valueset
|
|
322
|
+
# and a different TARGET SYSTEM valueset will result in an impossible match and an error will not be recognized by MaLaC-HD.
|
|
323
|
+
#
|
|
324
|
+
# The 5th dimension is optional and stating the CODE from the source valueset, as one conceptMap can have none or
|
|
325
|
+
# multiple CODEs from the source to translate.
|
|
326
|
+
#
|
|
327
|
+
# The 6th dimension is NOT optional and stating the TARGET CODE from the target valueset. As one source code could be translated
|
|
328
|
+
# in multiple TARGET CODEs, the whole set have to be returend.
|
|
329
|
+
# For a translation with explicitly no TARGET CODE, because of an quivalence of unmatched or disjoint, NONE will be returned.
|
|
330
|
+
#
|
|
331
|
+
# a minimal example, translating "hi" to "servus":
|
|
332
|
+
# con_map_7d = {"myConMap": {None: {None: {"hi": {None: {None: ["equivalent", "<coding><code>servus</code></coding>", "https://my.concept.map/conceptMap/my"]}}}}}
|
|
333
|
+
#
|
|
334
|
+
# TODO add a dimension for a specific dependsOn property
|
|
335
|
+
# TODO add a solution for the unmapped element
|
|
336
|
+
con_map_7d = {}
|
|
337
|
+
'''
|
|
338
|
+
|
|
339
|
+
def _footer_standalone(self):
|
|
340
|
+
self.py_code += '''
|
|
341
|
+
|
|
342
|
+
if __name__ == "__main__":
|
|
343
|
+
parser = init_argparse()
|
|
344
|
+
args = parser.parse_args()
|
|
345
|
+
ret = translate(args.code, args.system, args.version, args.source, args.coding,
|
|
346
|
+
args.codeableConcept, args.target, args.targetsystem, args.reverse, args.silent)'''
|
|
347
|
+
|
|
348
|
+
supermod.ConceptMap.subclass = PythonGenerator
|