fhir-sheets 2.1.6__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.
Potentially problematic release.
This version of fhir-sheets might be problematic. Click here for more details.
- fhir_sheets/__init__.py +0 -0
- fhir_sheets/__init__.pyi +0 -0
- fhir_sheets/cli/__init__.py +0 -0
- fhir_sheets/cli/__init__.pyi +0 -0
- fhir_sheets/cli/main.py +69 -0
- fhir_sheets/cli/main.pyi +6 -0
- fhir_sheets/core/__init__.py +0 -0
- fhir_sheets/core/__init__.pyi +0 -0
- fhir_sheets/core/config/FhirSheetsConfiguration.py +12 -0
- fhir_sheets/core/conversion.py +390 -0
- fhir_sheets/core/conversion.pyi +22 -0
- fhir_sheets/core/fhir_formatting.py +279 -0
- fhir_sheets/core/fhir_formatting.pyi +13 -0
- fhir_sheets/core/model/cohort_data_entity.py +45 -0
- fhir_sheets/core/model/common.py +12 -0
- fhir_sheets/core/model/resource_definition_entity.py +30 -0
- fhir_sheets/core/model/resource_link_entity.py +32 -0
- fhir_sheets/core/read_input.py +102 -0
- fhir_sheets/core/read_input.pyi +8 -0
- fhir_sheets/core/special_values.py +361 -0
- fhir_sheets/core/special_values.pyi +52 -0
- fhir_sheets/core/util.py +1 -0
- fhir_sheets/core/util.pyi +0 -0
- fhir_sheets-2.1.6.dist-info/METADATA +77 -0
- fhir_sheets-2.1.6.dist-info/RECORD +27 -0
- fhir_sheets-2.1.6.dist-info/WHEEL +4 -0
- fhir_sheets-2.1.6.dist-info/licenses/LICENSE +21 -0
fhir_sheets/__init__.py
ADDED
|
File without changes
|
fhir_sheets/__init__.pyi
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|
fhir_sheets/cli/main.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from ..core.config.FhirSheetsConfiguration import FhirSheetsConfiguration
|
|
2
|
+
from ..core import read_input
|
|
3
|
+
from ..core import conversion
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import orjson
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from pprint import pprint
|
|
10
|
+
|
|
11
|
+
def find_sets(d, path=""):
|
|
12
|
+
if isinstance(d, dict):
|
|
13
|
+
for key, value in d.items():
|
|
14
|
+
new_path = f"{path}.{key}" if path else str(key)
|
|
15
|
+
find_sets(value, new_path)
|
|
16
|
+
elif isinstance(d, list): # Handle lists of dictionaries
|
|
17
|
+
for idx, item in enumerate(d):
|
|
18
|
+
find_sets(item, f"{path}[{idx}]")
|
|
19
|
+
elif isinstance(d, set):
|
|
20
|
+
print(f"Set found at path: {path}")
|
|
21
|
+
|
|
22
|
+
def main(input_file, output_folder, config=FhirSheetsConfiguration({})):
|
|
23
|
+
# Step 1: Read the input file using read_input module
|
|
24
|
+
|
|
25
|
+
# Check if the output folder exists, and create it if not
|
|
26
|
+
|
|
27
|
+
output_folder_path = Path(output_folder)
|
|
28
|
+
if not output_folder_path.is_absolute():
|
|
29
|
+
output_folder_path = Path().cwd() / Path(output_folder)
|
|
30
|
+
if not output_folder_path.exists():
|
|
31
|
+
output_folder_path.mkdir(parents=True, exist_ok=True) # Create the folder if it doesn't exist
|
|
32
|
+
resource_definition_entities, resource_link_entities, cohort_data = read_input.read_xlsx_and_process(input_file)
|
|
33
|
+
#For each index of patients
|
|
34
|
+
for i in range(0,cohort_data.get_num_patients()):
|
|
35
|
+
# Construct the file path for each JSON file
|
|
36
|
+
file_path = output_folder_path / f"{i}.json"
|
|
37
|
+
#Create a bundle
|
|
38
|
+
fhir_bundle = conversion.create_transaction_bundle(resource_definition_entities, resource_link_entities, cohort_data, i, config)
|
|
39
|
+
# Step 3: Write the processed data to the output file
|
|
40
|
+
find_sets(fhir_bundle)
|
|
41
|
+
json_string = orjson.dumps(fhir_bundle)
|
|
42
|
+
with open(file_path, 'wb') as json_file:
|
|
43
|
+
json_file.write(json_string)
|
|
44
|
+
with open(file_path, 'r') as json_file:
|
|
45
|
+
json_string = json.load(json_file)
|
|
46
|
+
with open(file_path, 'w') as json_file:
|
|
47
|
+
json.dump(json_string, json_file, indent = 4)
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
# Create the argparse CLI
|
|
51
|
+
parser = argparse.ArgumentParser(description="Process input, convert data, and write output.")
|
|
52
|
+
|
|
53
|
+
# Define the input file argument
|
|
54
|
+
parser.add_argument('--input_file', type=str, help="Path to the input xlsx ", default="src/resources/Synthetic_Input_Baseline.xlsx")
|
|
55
|
+
|
|
56
|
+
# Define the output file argument
|
|
57
|
+
parser.add_argument('--output_folder', type=str, help="Path to save the output files", default="output/")
|
|
58
|
+
|
|
59
|
+
# Config object arguments
|
|
60
|
+
parser.add_argument('--preview_mode', type=str, help="Configuration option to generate resources as 'preview mode' references will reference the entity name. Will primarily be used to render a singular resource for preview.", default=False)
|
|
61
|
+
|
|
62
|
+
# Define the output file argument
|
|
63
|
+
parser.add_argument('--medications_as_reference', type=str, help="Configuration option to create medication references. You may still provide medicationCodeableConcept, but a post process will convert the codeableconcepts to medication resources", default=False)
|
|
64
|
+
# Parse the arguments
|
|
65
|
+
args = parser.parse_args()
|
|
66
|
+
|
|
67
|
+
# Call the main function with the provided arguments
|
|
68
|
+
config = FhirSheetsConfiguration(vars(args))
|
|
69
|
+
main(args.input_file, args.output_folder, config)
|
fhir_sheets/cli/main.pyi
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from ..core import conversion as conversion, read_input as read_input
|
|
2
|
+
from ..core.config.FhirSheetsConfiguration import FhirSheetsConfiguration as FhirSheetsConfiguration
|
|
3
|
+
from pprint import pprint as pprint
|
|
4
|
+
|
|
5
|
+
def find_sets(d, path: str = '') -> None: ...
|
|
6
|
+
def main(input_file, output_folder, config=...) -> None: ...
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FhirSheetsConfiguration():
|
|
5
|
+
def __init__(self, data: Dict[str, Any]):
|
|
6
|
+
self.preview_mode = data.get('preview_mode', False)
|
|
7
|
+
self.medications_as_reference = data.get('medications_as_reference', False)
|
|
8
|
+
|
|
9
|
+
def __repr__(self) -> str:
|
|
10
|
+
return (f"FhirSheetsConfiguration("
|
|
11
|
+
f"preview_mode={self.preview_mode}, "
|
|
12
|
+
f"medications_as_reference={self.medications_as_reference})")
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
import uuid
|
|
3
|
+
from jsonpath_ng.jsonpath import Fields, Slice, Where
|
|
4
|
+
from jsonpath_ng.ext import parse as parse_ext
|
|
5
|
+
|
|
6
|
+
from .config.FhirSheetsConfiguration import FhirSheetsConfiguration
|
|
7
|
+
|
|
8
|
+
from .model.cohort_data_entity import CohortData, CohortData
|
|
9
|
+
from .model.resource_definition_entity import ResourceDefinition
|
|
10
|
+
from .model.resource_link_entity import ResourceLink
|
|
11
|
+
from . import fhir_formatting
|
|
12
|
+
from . import special_values
|
|
13
|
+
|
|
14
|
+
#Main top level function
|
|
15
|
+
#Creates a full transaction bundle for a patient at index
|
|
16
|
+
def create_transaction_bundle(resource_definition_entities: List[ResourceDefinition], resource_link_entities: List[ResourceLink], cohort_data: CohortData, index = 0, config: FhirSheetsConfiguration = FhirSheetsConfiguration({})):
|
|
17
|
+
root_bundle = initialize_bundle()
|
|
18
|
+
created_resources = {}
|
|
19
|
+
for resource_definition in resource_definition_entities:
|
|
20
|
+
entityName = resource_definition.entityName
|
|
21
|
+
#Create and collect fhir resources
|
|
22
|
+
fhir_resource = create_fhir_resource(resource_definition, cohort_data, index, config)
|
|
23
|
+
created_resources[entityName] = fhir_resource
|
|
24
|
+
#Link resources after creation
|
|
25
|
+
add_default_resource_links(created_resources, resource_link_entities)
|
|
26
|
+
create_resource_links(created_resources, resource_link_entities, config.preview_mode)
|
|
27
|
+
#Construct into fhir bundle
|
|
28
|
+
for fhir_resource in created_resources.values():
|
|
29
|
+
add_resource_to_transaction_bundle(root_bundle, fhir_resource)
|
|
30
|
+
if config.medications_as_reference:
|
|
31
|
+
post_process_create_medication_references(root_bundle)
|
|
32
|
+
return root_bundle
|
|
33
|
+
|
|
34
|
+
def create_singular_resource(singleton_entityName: str, resource_definition_entities: List[ResourceDefinition], resource_link_entities: List[ResourceLink], cohort_data: CohortData, index = 0):
|
|
35
|
+
created_resources = {}
|
|
36
|
+
singleton_fhir_resource = {}
|
|
37
|
+
for resource_definition in resource_definition_entities:
|
|
38
|
+
entityName = resource_definition.entityName
|
|
39
|
+
#Create and collect fhir resources
|
|
40
|
+
fhir_resource = create_fhir_resource(resource_definition, cohort_data, index)
|
|
41
|
+
created_resources[entityName] = fhir_resource
|
|
42
|
+
if entityName == singleton_entityName:
|
|
43
|
+
singleton_fhir_resource = fhir_resource
|
|
44
|
+
add_default_resource_links(created_resources, resource_link_entities)
|
|
45
|
+
create_resource_links(created_resources, resource_link_entities, preview_mode=True)
|
|
46
|
+
return singleton_fhir_resource
|
|
47
|
+
|
|
48
|
+
#Initialize root bundle definition
|
|
49
|
+
def initialize_bundle():
|
|
50
|
+
root_bundle = {}
|
|
51
|
+
root_bundle['resourceType'] = 'Bundle'
|
|
52
|
+
root_bundle['id'] = str(uuid.uuid4())
|
|
53
|
+
root_bundle['meta'] = {
|
|
54
|
+
'security': [{
|
|
55
|
+
'system': 'http://terminology.hl7.org/CodeSystem/v3-ActReason',
|
|
56
|
+
'code': 'HTEST',
|
|
57
|
+
'display': 'test health data'
|
|
58
|
+
}]
|
|
59
|
+
}
|
|
60
|
+
root_bundle['type'] = 'transaction'
|
|
61
|
+
root_bundle['entry'] = []
|
|
62
|
+
return root_bundle
|
|
63
|
+
|
|
64
|
+
#Initialize a resource from a resource definition. Adding basic information all resources need
|
|
65
|
+
def initialize_resource(resource_definition):
|
|
66
|
+
initial_resource = {}
|
|
67
|
+
initial_resource['resourceType'] = resource_definition.resourceType.strip()
|
|
68
|
+
initial_resource['id'] = str(uuid.uuid4()).strip()
|
|
69
|
+
if resource_definition.profiles:
|
|
70
|
+
initial_resource['meta'] = {
|
|
71
|
+
'profile': resource_definition.profiles,
|
|
72
|
+
'security': [{
|
|
73
|
+
'system': 'http://terminology.hl7.org/CodeSystem/v3-ActReason',
|
|
74
|
+
'code': 'HTEST',
|
|
75
|
+
'display': 'test health data'
|
|
76
|
+
}]
|
|
77
|
+
}
|
|
78
|
+
return initial_resource
|
|
79
|
+
|
|
80
|
+
# Creates a fhir-json structure from a resource definition entity and the patient_data_sheet
|
|
81
|
+
def create_fhir_resource(resource_definition: ResourceDefinition, cohort_data: CohortData, index: int = 0, config: FhirSheetsConfiguration = FhirSheetsConfiguration({})) -> dict:
|
|
82
|
+
resource_dict = initialize_resource(resource_definition)
|
|
83
|
+
#Get field entries for this entity
|
|
84
|
+
header_entries_for_resourcename = [
|
|
85
|
+
headerEntry
|
|
86
|
+
for headerEntry in cohort_data.headers
|
|
87
|
+
if headerEntry.entityName == resource_definition.entityName
|
|
88
|
+
]
|
|
89
|
+
dataelements_for_resourcename = {
|
|
90
|
+
field_name: value
|
|
91
|
+
for (entityName, field_name), value in cohort_data.patients[index].entries.items()
|
|
92
|
+
if entityName == resource_definition.entityName
|
|
93
|
+
}
|
|
94
|
+
if len(dataelements_for_resourcename.keys()) == 0:
|
|
95
|
+
print(f"WARNING: Patient index {index} - Create Fhir Resource Error - {resource_definition.entityName} - No columns for entity '{resource_definition.entityName}' found for resource in 'PatientData' sheet")
|
|
96
|
+
return resource_dict
|
|
97
|
+
all_field_entries = cohort_data.entities[resource_definition.entityName].fields
|
|
98
|
+
#For each field within the entity
|
|
99
|
+
for fieldName, value in dataelements_for_resourcename.items():
|
|
100
|
+
header_element = next((header for header in header_entries_for_resourcename if header.fieldName == fieldName), None)
|
|
101
|
+
if header_element is None:
|
|
102
|
+
print(f"WARNING: Field Name {fieldName} - No Header Entry found.")
|
|
103
|
+
continue
|
|
104
|
+
jsonPath = header_element.jsonPath
|
|
105
|
+
if jsonPath is None:
|
|
106
|
+
print(f"WARNING: Field Name {fieldName} - Header Entry found, but jsonPath attribute is None. Skipping.")
|
|
107
|
+
continue
|
|
108
|
+
valueType = header_element.valueType
|
|
109
|
+
if valueType is None:
|
|
110
|
+
print(f"WARNING: Field Name {fieldName} - Header Entry found, but valueType attribute is None. Skipping.")
|
|
111
|
+
continue
|
|
112
|
+
create_structure_from_jsonpath(resource_dict, jsonPath, resource_definition, valueType, value)
|
|
113
|
+
return resource_dict
|
|
114
|
+
|
|
115
|
+
#Create a resource_link for default references in the cases where only 1 resourceType of the source and destination exist
|
|
116
|
+
def add_default_resource_links(created_resources: dict, resource_link_entities: List[ResourceLink]):
|
|
117
|
+
default_references = [
|
|
118
|
+
('allergyintolerance', 'patient', 'patient'),
|
|
119
|
+
('allergyintolerance', 'practitioner', 'asserter'),
|
|
120
|
+
('careplan', 'goal', 'goal'),
|
|
121
|
+
('careplan', 'patient', 'subject'),
|
|
122
|
+
('careplan', 'practitioner', 'performer'),
|
|
123
|
+
('diagnosticreport', 'careteam', 'performer'),
|
|
124
|
+
('diagnosticreport', 'imagingStudy', 'imagingStudy'),
|
|
125
|
+
('diagnosticreport', 'observation', 'result'),
|
|
126
|
+
('diagnosticreport', 'organization', 'performer'),
|
|
127
|
+
('diagnosticreport', 'practitioner', 'performer'),
|
|
128
|
+
('diagnosticreport', 'practitionerrole', 'performer'),
|
|
129
|
+
('diagnosticreport', 'specimen', 'specimen'),
|
|
130
|
+
('encounter', 'location', 'location'),
|
|
131
|
+
('encounter', 'organization', 'serviceProvider'),
|
|
132
|
+
('encounter', 'patient', 'subject'),
|
|
133
|
+
('encounter', 'practitioner', 'participant'),
|
|
134
|
+
('goal', 'condition', 'addresses'),
|
|
135
|
+
('goal', 'patient', 'subject'),
|
|
136
|
+
('immunization', 'patient', 'patient'),
|
|
137
|
+
('immunization', 'practitioner', 'performer'),
|
|
138
|
+
('immunization', 'organization', 'manufacturer'),
|
|
139
|
+
('medicationrequest', 'medication', 'medicationReference'),
|
|
140
|
+
('medicationrequest', 'patient', 'subject'),
|
|
141
|
+
('medicationrequest', 'practitioner', 'requester'),
|
|
142
|
+
('observation', 'device', 'device'),
|
|
143
|
+
('observation', 'patient', 'subject'),
|
|
144
|
+
('observation', 'practitioner', 'performer'),
|
|
145
|
+
('observation', 'specimen', 'specimen'),
|
|
146
|
+
('procedure', 'device', 'usedReference'),
|
|
147
|
+
('procedure', 'location', 'location'),
|
|
148
|
+
('procedure', 'patient', 'subject'),
|
|
149
|
+
('procedure', 'practitioner', 'performer'),
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
resource_counts = {}
|
|
153
|
+
for resourceName, resource in created_resources.items():
|
|
154
|
+
resourceType = resource['resourceType'].lower().strip()
|
|
155
|
+
if resourceType not in resource_counts:
|
|
156
|
+
resource_counts[resourceType]= {'count': 1, 'singletonEntityName': resourceName, 'singleResource': resource}
|
|
157
|
+
else:
|
|
158
|
+
resource_counts[resourceType]['count'] += 1
|
|
159
|
+
resource_counts[resourceType]['singletonResource'] = resource
|
|
160
|
+
resource_counts[resourceType]['singletonEntityName'] = resourceName
|
|
161
|
+
|
|
162
|
+
for default_reference in default_references:
|
|
163
|
+
sourceType = default_reference[0]
|
|
164
|
+
destinationType = default_reference[1]
|
|
165
|
+
fieldName = default_reference[2]
|
|
166
|
+
if sourceType in resource_counts and destinationType in resource_counts and \
|
|
167
|
+
resource_counts[sourceType]['count'] == 1 and resource_counts[destinationType]['count'] == 1:
|
|
168
|
+
originResourceEntityName = resource_counts[sourceType]['singletonEntityName']
|
|
169
|
+
destinationResourceEntityName = resource_counts[destinationType]['singletonEntityName']
|
|
170
|
+
resource_link_entities.append(
|
|
171
|
+
ResourceLink(originResourceEntityName,fieldName,destinationResourceEntityName)
|
|
172
|
+
)
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
#List function to create resource references/links with created entities
|
|
177
|
+
def create_resource_links(created_resources, resource_link_entites, preview_mode = False):
|
|
178
|
+
#TODO: Build resource links
|
|
179
|
+
print("Building resource links")
|
|
180
|
+
for resource_link_entity in resource_link_entites:
|
|
181
|
+
create_resource_link(created_resources, resource_link_entity, preview_mode)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
#Singular function to create a resource link.
|
|
185
|
+
def create_resource_link(created_resources, resource_link_entity, preview_mode = False):
|
|
186
|
+
# template scaffolding
|
|
187
|
+
reference_json_block = {
|
|
188
|
+
"reference" : "$value"
|
|
189
|
+
}
|
|
190
|
+
#Special reference handling blocks, in the form of (originResource, destinationResource, referencePath)
|
|
191
|
+
arrayType_references = [
|
|
192
|
+
('diagnosticreport', 'specimen', 'specimen'),
|
|
193
|
+
('diagnosticreport', 'practitioner', 'performer'),
|
|
194
|
+
('diagnosticreport', 'practitionerrole', 'performer'),
|
|
195
|
+
('diagnosticreport', 'organization', 'performer'),
|
|
196
|
+
('diagnosticreport', 'careteam', 'performer'),
|
|
197
|
+
('diagnosticreport', 'observation', 'result'),
|
|
198
|
+
('diagnosticreport', 'imagingStudy', 'imagingStudy'),
|
|
199
|
+
]
|
|
200
|
+
#Find the origin and destination resource from the link
|
|
201
|
+
try:
|
|
202
|
+
originResource = created_resources[resource_link_entity.originResource]
|
|
203
|
+
except KeyError:
|
|
204
|
+
print(f"WARNING: In ResourceLinks tab, found a Origin Resource of : {resource_link_entity.originResource} but no such entity found in PatientData")
|
|
205
|
+
return
|
|
206
|
+
try:
|
|
207
|
+
destinationResource = created_resources[resource_link_entity.destinationResource]
|
|
208
|
+
except KeyError:
|
|
209
|
+
print(f"WARNING: In ResourceLinks tab, found a Desitnation Resource of : {resource_link_entity.destinationResource} but no such entity found in PatientData")
|
|
210
|
+
return
|
|
211
|
+
#Estable the value of the refence
|
|
212
|
+
if preview_mode:
|
|
213
|
+
reference_value = destinationResource['resourceType'] + "/" + resource_link_entity.destinationResource
|
|
214
|
+
else:
|
|
215
|
+
reference_value = destinationResource['resourceType'] + "/" + destinationResource['id']
|
|
216
|
+
link_tuple = (originResource['resourceType'].strip().lower(),
|
|
217
|
+
destinationResource['resourceType'].strip().lower(),
|
|
218
|
+
resource_link_entity.referencePath.strip().lower())
|
|
219
|
+
if link_tuple in arrayType_references:
|
|
220
|
+
if resource_link_entity.referencePath.strip().lower() not in originResource:
|
|
221
|
+
originResource[resource_link_entity.referencePath.strip().lower()] = []
|
|
222
|
+
new_reference = reference_json_block.copy()
|
|
223
|
+
new_reference['reference'] = reference_value
|
|
224
|
+
originResource[resource_link_entity.referencePath.strip().lower()].append(new_reference)
|
|
225
|
+
else:
|
|
226
|
+
originResource[resource_link_entity.referencePath.strip().lower()] = reference_json_block.copy()
|
|
227
|
+
originResource[resource_link_entity.referencePath.strip().lower()]["reference"] = reference_value
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
def add_resource_to_transaction_bundle(root_bundle, fhir_resource):
|
|
231
|
+
entry = {}
|
|
232
|
+
entry['fullUrl'] = "urn:uuid:"+fhir_resource['id']
|
|
233
|
+
entry['resource'] = fhir_resource
|
|
234
|
+
entry['request'] = {
|
|
235
|
+
"method": "PUT",
|
|
236
|
+
"url": fhir_resource['resourceType'] + "/" + fhir_resource['id']
|
|
237
|
+
}
|
|
238
|
+
root_bundle['entry'].append(entry)
|
|
239
|
+
return root_bundle
|
|
240
|
+
|
|
241
|
+
#Drill down and create a structure from a json path with a simple recurisve process
|
|
242
|
+
# Supports 2 major features:
|
|
243
|
+
# 1) dot notation such as $.codeableconcept.coding[0].value = 1234
|
|
244
|
+
# 2) simple qualifiers such as $.name[use=official].family = Dickerson
|
|
245
|
+
# rootStruct: top level structure to drill into
|
|
246
|
+
# json_path: dotnotation path to follow
|
|
247
|
+
# resource_definition: resource description model from import
|
|
248
|
+
# entity_definition: specific field entry information for this function
|
|
249
|
+
# value: Actual value to assign
|
|
250
|
+
def create_structure_from_jsonpath(root_struct: Dict, json_path: str, resource_definition: ResourceDefinition, dataType: str, value: Any):
|
|
251
|
+
#Get all dot notation components as seperate
|
|
252
|
+
if dataType is not None and dataType.strip().lower() == 'string':
|
|
253
|
+
value = str(value)
|
|
254
|
+
|
|
255
|
+
if value == None:
|
|
256
|
+
print(f"WARNING: Full jsonpath: {json_path} - Expected to find a value but found None instead")
|
|
257
|
+
return root_struct
|
|
258
|
+
#Start of top-level function which calls the enclosed recursive function
|
|
259
|
+
parts = json_path.split('.')
|
|
260
|
+
return build_structure(root_struct, json_path, resource_definition, dataType, parts, value, [])
|
|
261
|
+
|
|
262
|
+
# main recursive function to drill into the json structure, assign paths, and create structure where needed
|
|
263
|
+
def build_structure(current_struct: Any, json_path: str, resource_definition: ResourceDefinition, dataType: str, parts: List[str], value: Any, previous_parts: List[str]):
|
|
264
|
+
if len(parts) == 0:
|
|
265
|
+
return current_struct
|
|
266
|
+
#Grab current part
|
|
267
|
+
part = parts[0]
|
|
268
|
+
#SPECIAL HANDLING CLAUSE
|
|
269
|
+
matching_handler = next((handler for handler in special_values.custom_handlers if (json_path.startswith(handler) or json_path == handler)), None)
|
|
270
|
+
if matching_handler is not None:
|
|
271
|
+
return special_values.custom_handlers[matching_handler].assign_value(json_path, resource_definition, dataType, current_struct, parts[-1], value)
|
|
272
|
+
#Ignore dollar sign ($) and drill farther down
|
|
273
|
+
if part == '$' or part == resource_definition.resourceType.strip():
|
|
274
|
+
#Ignore the dollar sign and the resourcetype
|
|
275
|
+
return build_structure_recurse(current_struct, json_path, resource_definition, dataType, parts, value, previous_parts, part)
|
|
276
|
+
|
|
277
|
+
# If parts length is one then this is the final key to access and pair
|
|
278
|
+
if len(parts) == 1:
|
|
279
|
+
#Check for numeic qualifier '[0]' and '[1]'
|
|
280
|
+
if '[' in part and ']' in part:
|
|
281
|
+
#Seperate the key from the qualifier
|
|
282
|
+
key_part = part[:part.index('[')]
|
|
283
|
+
qualifier = part[part.index('[')+1:part.index(']')]
|
|
284
|
+
qualifier_condition = qualifier.split('=')
|
|
285
|
+
|
|
286
|
+
#If there is no key part, aka '[0]', '[1]' etc, then it's a simple accessor
|
|
287
|
+
if key_part is None or key_part == '':
|
|
288
|
+
if not qualifier.isdigit():
|
|
289
|
+
raise TypeError(f"ERROR: Full jsonpath: {json_path} - current path - {'.'.join(previous_parts + parts[:1])} - qualifier - {qualifier} - standalone qualifier expected to be a single index numeric ([0], [1], etc)")
|
|
290
|
+
if current_struct == {}:
|
|
291
|
+
current_struct = []
|
|
292
|
+
if not isinstance(current_struct, list):
|
|
293
|
+
raise TypeError(f"ERROR: Full jsonpath: {json_path} - current path - {'.'.join(previous_parts + parts[:1])} - Expected a list, but got {type(current_struct).__name__} instead.")
|
|
294
|
+
part = int(qualifier)
|
|
295
|
+
if part + 1 > len(current_struct):
|
|
296
|
+
current_struct.extend({} for x in range (part + 1 - len(current_struct)))
|
|
297
|
+
#Actual assigning to the path
|
|
298
|
+
fhir_formatting.assign_value(current_struct, part, value, dataType)
|
|
299
|
+
return current_struct
|
|
300
|
+
|
|
301
|
+
# If there is a simple qualifier with '['and ']'
|
|
302
|
+
elif '[' in part and ']' in part:
|
|
303
|
+
#Seperate the key from the qualifier
|
|
304
|
+
key_part = part[:part.index('[')]
|
|
305
|
+
qualifier = part[part.index('[')+1:part.index(']')]
|
|
306
|
+
qualifier_condition = qualifier.split('=')
|
|
307
|
+
|
|
308
|
+
#If there is no key part, aka '[0]', '[1]' etc, then it's a simple accessor
|
|
309
|
+
if key_part is None or key_part == '':
|
|
310
|
+
if not qualifier.isdigit():
|
|
311
|
+
raise TypeError(f"ERROR: Full jsonpath: {json_path} - current path - {'.'.join(previous_parts + parts[:1])} - qualifier - {qualifier} - standalone qualifier expected to be a single index numeric ([0], [1], etc)")
|
|
312
|
+
if current_struct == {}:
|
|
313
|
+
current_struct = []
|
|
314
|
+
if not isinstance(current_struct, list):
|
|
315
|
+
raise TypeError(f"ERROR: Full jsonpath: {json_path} - current path - {'.'.join(previous_parts + parts[:1])} - Expected a list, but got {type(current_struct).__name__} instead.")
|
|
316
|
+
qualifier_as_number = int(qualifier)
|
|
317
|
+
if qualifier_as_number + 1 > len(current_struct):
|
|
318
|
+
current_struct.extend({} for x in range (qualifier_as_number + 1 - len(current_struct)))
|
|
319
|
+
inner_struct = current_struct[qualifier_as_number]
|
|
320
|
+
inner_struct = build_structure_recurse(inner_struct, json_path, resource_definition, dataType, parts, value, previous_parts, part)
|
|
321
|
+
current_struct[qualifier_as_number] = inner_struct
|
|
322
|
+
return current_struct
|
|
323
|
+
# Create the key part in the structure
|
|
324
|
+
if (not key_part in current_struct) or (isinstance(current_struct[key_part], dict)):
|
|
325
|
+
current_struct[key_part] = []
|
|
326
|
+
#If there is a key_part and the If the qualifier condition is defined
|
|
327
|
+
if len(qualifier_condition) == 2:
|
|
328
|
+
#special handling for code
|
|
329
|
+
if key_part != "coding" and (qualifier_condition[0] in ('code', 'system')):
|
|
330
|
+
#Move into the coding section if a qualifier asks for 'code' or 'system'
|
|
331
|
+
if 'coding' not in current_struct:
|
|
332
|
+
current_struct['coding'] = []
|
|
333
|
+
current_struct = current_struct['coding']
|
|
334
|
+
qualifier_key, qualifier_value = qualifier_condition
|
|
335
|
+
# Retrieve an inner structure if it exists allready that matches the criteria
|
|
336
|
+
inner_struct = next((innerElement for innerElement in current_struct[key_part] if isinstance(innerElement, dict) and innerElement.get(qualifier_key) == qualifier_value), None)
|
|
337
|
+
#If no inner structure exists, create one instead
|
|
338
|
+
if inner_struct is None:
|
|
339
|
+
inner_struct = {qualifier_key: qualifier_value}
|
|
340
|
+
current_struct[key_part].append(inner_struct)
|
|
341
|
+
#Recurse into that innerstructure where the qualifier matched to continue the part traversal
|
|
342
|
+
inner_struct = build_structure_recurse(inner_struct, json_path, resource_definition, dataType, parts, value, previous_parts, part)
|
|
343
|
+
return current_struct
|
|
344
|
+
#If there's no qualifier condition, but an index aka '[0]', '[1]' etc, then it's a simple accessor
|
|
345
|
+
elif qualifier.isdigit():
|
|
346
|
+
if not isinstance(current_struct[key_part], list):
|
|
347
|
+
raise TypeError(f"ERROR: Full jsonpath: {json_path} - current path - {'.'.join(previous_parts + [parts[0]])} - Expected a list, but got {type(current_struct).__name__} instead.")
|
|
348
|
+
qualifier_as_number = int(qualifier)
|
|
349
|
+
if qualifier_as_number > len(current_struct):
|
|
350
|
+
current_struct[key_part].extend({} for x in range (qualifier_as_number - len(current_struct)))
|
|
351
|
+
inner_struct = current_struct[key_part][qualifier_as_number]
|
|
352
|
+
inner_struct = build_structure_recurse(inner_struct, json_path, resource_definition, dataType, parts, value, previous_parts, part)
|
|
353
|
+
current_struct[key_part][qualifier_as_number] = inner_struct
|
|
354
|
+
return current_struct
|
|
355
|
+
#None qualifier accessor
|
|
356
|
+
else:
|
|
357
|
+
if(part not in current_struct):
|
|
358
|
+
current_struct[part] = {}
|
|
359
|
+
inner_struct = build_structure_recurse(current_struct[part], json_path, resource_definition, dataType, parts, value, previous_parts, part)
|
|
360
|
+
current_struct[part] = inner_struct
|
|
361
|
+
return current_struct
|
|
362
|
+
|
|
363
|
+
#Helper function to quickly recurse and return the next level of structure. Used by main recursive function
|
|
364
|
+
def build_structure_recurse(current_struct, json_path, resource_definition, dataType, parts, value, previous_parts, part):
|
|
365
|
+
previous_parts.append(part)
|
|
366
|
+
return_struct = build_structure(current_struct, json_path, resource_definition, dataType, parts[1:], value, previous_parts)
|
|
367
|
+
return return_struct
|
|
368
|
+
|
|
369
|
+
#Post-process function to add medication reference in specific references
|
|
370
|
+
def post_process_create_medication_references( root_bundle: dict):
|
|
371
|
+
medication_resources = [resource['resource'] for resource in root_bundle['entry'] if resource['resource']['resourceType'] == "Medication"]
|
|
372
|
+
medication_request_resources = [resource['resource'] for resource in root_bundle['entry'] if resource['resource']['resourceType'] == "MedicationRequest"]
|
|
373
|
+
for medication_request_resource in medication_request_resources:
|
|
374
|
+
#Get candidates
|
|
375
|
+
medication_candidates = [resource for resource in medication_resources if resource['code'] == medication_request_resource['medicationCodeableConcept']]
|
|
376
|
+
if not medication_candidates: #If no candidates, create, else get the first candidate
|
|
377
|
+
medication_target = target_medication = createMedicationResource(root_bundle, medication_request_resource['medicationCodeableConcept'])
|
|
378
|
+
medication_resources.append(target_medication)
|
|
379
|
+
else:
|
|
380
|
+
target_medication = medication_candidates[0]
|
|
381
|
+
|
|
382
|
+
del(medication_request_resource['medicationCodeableConcept'])
|
|
383
|
+
medication_request_resource['medicationReference'] = target_medication['resourceType'] + "/" + target_medication['id']
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
def createMedicationResource(root_bundle, medicationCodeableConcept):
|
|
387
|
+
target_medication = initialize_resource(ResourceDefinition.from_dict({'ResourceType': 'Medication'}))
|
|
388
|
+
target_medication['code'] = medicationCodeableConcept
|
|
389
|
+
add_resource_to_transaction_bundle(root_bundle, target_medication)
|
|
390
|
+
return target_medication
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from . import fhir_formatting as fhir_formatting, special_values as special_values
|
|
2
|
+
from .config.FhirSheetsConfiguration import FhirSheetsConfiguration as FhirSheetsConfiguration
|
|
3
|
+
from .model.cohort_data_entity import CohortData as CohortData
|
|
4
|
+
from .model.resource_definition_entity import ResourceDefinition as ResourceDefinition
|
|
5
|
+
from .model.resource_link_entity import ResourceLink as ResourceLink
|
|
6
|
+
from jsonpath_ng.jsonpath import Fields as Fields, Slice as Slice, Where as Where
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
def create_transaction_bundle(resource_definition_entities: list[ResourceDefinition], resource_link_entities: list[ResourceLink], cohort_data: CohortData, index: int = 0, config: FhirSheetsConfiguration = ...): ...
|
|
10
|
+
def create_singular_resource(singleton_entityName: str, resource_definition_entities: list[ResourceDefinition], resource_link_entities: list[ResourceLink], cohort_data: CohortData, index: int = 0): ...
|
|
11
|
+
def initialize_bundle(): ...
|
|
12
|
+
def initialize_resource(resource_definition): ...
|
|
13
|
+
def create_fhir_resource(resource_definition: ResourceDefinition, cohort_data: CohortData, index: int = 0, config: FhirSheetsConfiguration = ...) -> dict: ...
|
|
14
|
+
def add_default_resource_links(created_resources: dict, resource_link_entities: list[ResourceLink]): ...
|
|
15
|
+
def create_resource_links(created_resources, resource_link_entites, preview_mode: bool = False) -> None: ...
|
|
16
|
+
def create_resource_link(created_resources, resource_link_entity, preview_mode: bool = False) -> None: ...
|
|
17
|
+
def add_resource_to_transaction_bundle(root_bundle, fhir_resource): ...
|
|
18
|
+
def create_structure_from_jsonpath(root_struct: dict, json_path: str, resource_definition: ResourceDefinition, dataType: str, value: Any): ...
|
|
19
|
+
def build_structure(current_struct: Any, json_path: str, resource_definition: ResourceDefinition, dataType: str, parts: list[str], value: Any, previous_parts: list[str]): ...
|
|
20
|
+
def build_structure_recurse(current_struct, json_path, resource_definition, dataType, parts, value, previous_parts, part): ...
|
|
21
|
+
def post_process_create_medication_references(root_bundle: dict): ...
|
|
22
|
+
def createMedicationResource(root_bundle, medicationCodeableConcept): ...
|