fhir-sheets 1.1.0__py3-none-any.whl → 1.2.0__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/cli/{fhirsheets.py → main.py} +5 -6
- fhir_sheets/core/conversion.py +46 -42
- fhir_sheets/core/model/cohort_data_entity.py +97 -0
- fhir_sheets/core/model/resource_definition_entity.py +20 -0
- fhir_sheets/core/model/resource_link_entity.py +22 -0
- fhir_sheets/core/read_input.py +22 -14
- {fhir_sheets-1.1.0.dist-info → fhir_sheets-1.2.0.dist-info}/METADATA +5 -3
- fhir_sheets-1.2.0.dist-info/RECORD +16 -0
- {fhir_sheets-1.1.0.dist-info → fhir_sheets-1.2.0.dist-info}/WHEEL +1 -1
- fhir_sheets-1.1.0.dist-info/RECORD +0 -13
- {fhir_sheets-1.1.0.dist-info → fhir_sheets-1.2.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -5,6 +5,7 @@ import argparse
|
|
|
5
5
|
import orjson
|
|
6
6
|
import json
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from pprint import pprint
|
|
8
9
|
|
|
9
10
|
def find_sets(d, path=""):
|
|
10
11
|
if isinstance(d, dict):
|
|
@@ -27,16 +28,14 @@ def main(input_file, output_folder):
|
|
|
27
28
|
output_folder_path = Path().cwd() / Path(output_folder)
|
|
28
29
|
if not output_folder_path.exists():
|
|
29
30
|
output_folder_path.mkdir(parents=True, exist_ok=True) # Create the folder if it doesn't exist
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
resource_definition_entities, resource_link_entities, cohort_data = read_input.read_xlsx_and_process(input_file)
|
|
32
|
+
pprint(cohort_data)
|
|
33
33
|
#For each index of patients
|
|
34
|
-
for i in range(0,
|
|
34
|
+
for i in range(0,cohort_data.num_entries):
|
|
35
35
|
# Construct the file path for each JSON file
|
|
36
36
|
file_path = output_folder_path / f"{i}.json"
|
|
37
37
|
#Create a bundle
|
|
38
|
-
fhir_bundle = conversion.create_transaction_bundle(
|
|
39
|
-
data['resource_link_entities'], data['patient_data_entities'], i)
|
|
38
|
+
fhir_bundle = conversion.create_transaction_bundle(resource_definition_entities, resource_link_entities, cohort_data, i)
|
|
40
39
|
# Step 3: Write the processed data to the output file
|
|
41
40
|
find_sets(fhir_bundle)
|
|
42
41
|
json_string = orjson.dumps(fhir_bundle)
|
fhir_sheets/core/conversion.py
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
1
2
|
import uuid
|
|
2
3
|
from jsonpath_ng.jsonpath import Fields, Slice, Where
|
|
3
4
|
from jsonpath_ng.ext import parse as parse_ext
|
|
5
|
+
|
|
6
|
+
from .model.cohort_data_entity import CohortData, FieldEntry
|
|
7
|
+
from .model.resource_definition_entity import ResourceDefinition
|
|
8
|
+
from .model.resource_link_entity import ResourceLink
|
|
4
9
|
from . import fhir_formatting
|
|
5
10
|
from . import special_values
|
|
6
11
|
|
|
7
12
|
#Main top level function
|
|
8
13
|
#Creates a full transaction bundle for a patient at index
|
|
9
|
-
def create_transaction_bundle(resource_definition_entities, resource_link_entities,
|
|
14
|
+
def create_transaction_bundle(resource_definition_entities: List[ResourceDefinition], resource_link_entities: List[ResourceLink], cohort_data: CohortData, index = 0):
|
|
10
15
|
root_bundle = initialize_bundle()
|
|
11
16
|
created_resources = {}
|
|
12
17
|
for resource_definition in resource_definition_entities:
|
|
13
|
-
entity_name = resource_definition
|
|
18
|
+
entity_name = resource_definition.entity_name
|
|
14
19
|
#Create and collect fhir resources
|
|
15
|
-
fhir_resource = create_fhir_resource(resource_definition,
|
|
20
|
+
fhir_resource = create_fhir_resource(resource_definition, cohort_data, index)
|
|
16
21
|
created_resources[entity_name] = fhir_resource
|
|
17
22
|
#Link resources after creation
|
|
18
23
|
add_default_resource_links(created_resources, resource_link_entities)
|
|
@@ -31,36 +36,35 @@ def initialize_bundle():
|
|
|
31
36
|
root_bundle['entry'] = []
|
|
32
37
|
return root_bundle
|
|
33
38
|
|
|
39
|
+
#Initialize a resource from a resource definition. Adding basic information all resources need
|
|
40
|
+
def initialize_resource(resource_definition):
|
|
41
|
+
initial_resource = {}
|
|
42
|
+
initial_resource['resourceType'] = resource_definition.resource_type.strip()
|
|
43
|
+
initial_resource['id'] = str(uuid.uuid4()).strip()
|
|
44
|
+
if resource_definition.profiles:
|
|
45
|
+
initial_resource['meta'] = {
|
|
46
|
+
'profile': resource_definition.profiles
|
|
47
|
+
}
|
|
48
|
+
return initial_resource
|
|
49
|
+
|
|
34
50
|
# Creates a fhir-json structure from a resource definition entity and the patient_data_sheet
|
|
35
|
-
def create_fhir_resource(resource_definition,
|
|
51
|
+
def create_fhir_resource(resource_definition: ResourceDefinition, cohort_data: CohortData, index = 0):
|
|
36
52
|
resource_dict = initialize_resource(resource_definition)
|
|
37
53
|
#Get field entries for this entitiy
|
|
38
54
|
try:
|
|
39
|
-
all_field_entries =
|
|
55
|
+
all_field_entries = cohort_data.entities[resource_definition.entity_name].fields
|
|
40
56
|
except KeyError:
|
|
41
|
-
print(f"WARNING: Patient index {index} - Create Fhir Resource Error - {resource_definition
|
|
57
|
+
print(f"WARNING: Patient index {index} - Create Fhir Resource Error - {resource_definition.entity_name} - No columns for entity '{resource_definition.entity_name}' found for resource in 'PatientData' sheet")
|
|
42
58
|
return resource_dict
|
|
43
59
|
#For each field within the entity
|
|
44
|
-
for field_entry in all_field_entries.
|
|
60
|
+
for field_entry_key, field_entry in all_field_entries.items():
|
|
45
61
|
#Create a jsonpath from each provided json path and value for this resource
|
|
46
|
-
if field_entry
|
|
47
|
-
create_structure_from_jsonpath(resource_dict, field_entry
|
|
62
|
+
if field_entry.values and len(field_entry.values) > index:
|
|
63
|
+
create_structure_from_jsonpath(resource_dict, field_entry.jsonpath, resource_definition, field_entry, field_entry.value_type, field_entry.values[index])
|
|
48
64
|
return resource_dict
|
|
49
|
-
|
|
50
|
-
#Initialize a resource from a resource definition. Adding basic
|
|
51
|
-
def initialize_resource(resource_definition):
|
|
52
|
-
initial_resource = {}
|
|
53
|
-
initial_resource['resourceType'] = resource_definition['ResourceType'].strip()
|
|
54
|
-
resource_definition['id'] = str(uuid.uuid4())
|
|
55
|
-
initial_resource['id'] = resource_definition['id'].strip()
|
|
56
|
-
if resource_definition['Profile(s)']:
|
|
57
|
-
initial_resource['meta'] = {
|
|
58
|
-
'profile': resource_definition['Profile(s)']
|
|
59
|
-
}
|
|
60
|
-
return initial_resource
|
|
61
65
|
|
|
62
66
|
#Create a resource_link for default references in the cases where only 1 resourceType of the source and destination exist
|
|
63
|
-
def add_default_resource_links(created_resources, resource_link_entities):
|
|
67
|
+
def add_default_resource_links(created_resources: dict, resource_link_entities: List[ResourceLink]):
|
|
64
68
|
default_references = [
|
|
65
69
|
('allergyintolerance', 'patient', 'patient'),
|
|
66
70
|
('allergyintolerance', 'practitioner', 'asserter'),
|
|
@@ -115,11 +119,11 @@ def add_default_resource_links(created_resources, resource_link_entities):
|
|
|
115
119
|
originResourceEntityName = resource_counts[sourceType]['singletonEntityName']
|
|
116
120
|
destinationResourceEntityName = resource_counts[destinationType]['singletonEntityName']
|
|
117
121
|
resource_link_entities.append(
|
|
118
|
-
{
|
|
122
|
+
ResourceLink({
|
|
119
123
|
"OriginResource": originResourceEntityName,
|
|
120
124
|
"DestinationResource": destinationResourceEntityName,
|
|
121
125
|
"ReferencePath": fieldName
|
|
122
|
-
}
|
|
126
|
+
})
|
|
123
127
|
)
|
|
124
128
|
return
|
|
125
129
|
|
|
@@ -143,29 +147,29 @@ def create_resource_links(created_resources, resource_link_entites):
|
|
|
143
147
|
print("Building resource links")
|
|
144
148
|
for resource_link_entity in resource_link_entites:
|
|
145
149
|
try:
|
|
146
|
-
origin_resource = created_resources[resource_link_entity
|
|
150
|
+
origin_resource = created_resources[resource_link_entity.origin_resource]
|
|
147
151
|
except KeyError:
|
|
148
|
-
print(f"WARNING: In ResourceLinks tab, found a Origin Resource of : {resource_link_entity
|
|
152
|
+
print(f"WARNING: In ResourceLinks tab, found a Origin Resource of : {resource_link_entity.origin_resource} but no such entity found in PatientData")
|
|
149
153
|
continue
|
|
150
154
|
try:
|
|
151
|
-
destination_resource = created_resources[resource_link_entity
|
|
155
|
+
destination_resource = created_resources[resource_link_entity.destination_resource]
|
|
152
156
|
except KeyError:
|
|
153
|
-
print(f"WARNING: In ResourceLinks tab, found a Desitnation Resource of : {resource_link_entity
|
|
157
|
+
print(f"WARNING: In ResourceLinks tab, found a Desitnation Resource of : {resource_link_entity.destination_resource} but no such entity found in PatientData")
|
|
154
158
|
continue
|
|
155
|
-
destination_resource_type = created_resources[resource_link_entity
|
|
156
|
-
destination_resource_id = created_resources[resource_link_entity
|
|
157
|
-
link_tuple = (created_resources[resource_link_entity
|
|
158
|
-
created_resources[resource_link_entity
|
|
159
|
-
resource_link_entity
|
|
159
|
+
destination_resource_type = created_resources[resource_link_entity.destination_resource]['resourceType']
|
|
160
|
+
destination_resource_id = created_resources[resource_link_entity.destination_resource]['id']
|
|
161
|
+
link_tuple = (created_resources[resource_link_entity.origin_resource]['resourceType'].strip().lower(),
|
|
162
|
+
created_resources[resource_link_entity.destination_resource]['resourceType'].strip().lower(),
|
|
163
|
+
resource_link_entity.reference_path.strip().lower())
|
|
160
164
|
if link_tuple in arrayType_references:
|
|
161
|
-
if resource_link_entity
|
|
162
|
-
origin_resource[resource_link_entity
|
|
165
|
+
if resource_link_entity.reference_path.strip().lower() not in origin_resource:
|
|
166
|
+
origin_resource[resource_link_entity.reference_path.strip().lower()] = []
|
|
163
167
|
new_reference = reference_json_block.copy()
|
|
164
168
|
new_reference['reference'] = destination_resource_type + "/" + destination_resource_id
|
|
165
|
-
origin_resource[resource_link_entity
|
|
169
|
+
origin_resource[resource_link_entity.reference_path.strip().lower()].append(new_reference)
|
|
166
170
|
else:
|
|
167
|
-
origin_resource[resource_link_entity
|
|
168
|
-
origin_resource[resource_link_entity
|
|
171
|
+
origin_resource[resource_link_entity.reference_path.strip().lower()] = reference_json_block.copy()
|
|
172
|
+
origin_resource[resource_link_entity.reference_path.strip().lower()]["reference"] = destination_resource_type + "/" + destination_resource_id
|
|
169
173
|
return
|
|
170
174
|
|
|
171
175
|
def add_resource_to_transaction_bundle(root_bundle, fhir_resource):
|
|
@@ -188,7 +192,7 @@ def add_resource_to_transaction_bundle(root_bundle, fhir_resource):
|
|
|
188
192
|
# resource_definition: resource description model from import
|
|
189
193
|
# entity_definition: specific field entry information for this function
|
|
190
194
|
# value: Actual value to assign
|
|
191
|
-
def create_structure_from_jsonpath(root_struct, json_path, resource_definition,
|
|
195
|
+
def create_structure_from_jsonpath(root_struct: Dict, json_path: str, resource_definition: ResourceDefinition, field_entry: FieldEntry, dataType: str, value: Any):
|
|
192
196
|
#Get all dot notation components as seperate
|
|
193
197
|
if dataType is not None and dataType.strip().lower() == 'string':
|
|
194
198
|
value = str(value)
|
|
@@ -198,7 +202,7 @@ def create_structure_from_jsonpath(root_struct, json_path, resource_definition,
|
|
|
198
202
|
return root_struct
|
|
199
203
|
#Start of top-level function which calls the enclosed recursive function
|
|
200
204
|
parts = json_path.split('.')
|
|
201
|
-
return build_structure(root_struct, json_path, resource_definition,
|
|
205
|
+
return build_structure(root_struct, json_path, resource_definition, field_entry, parts, value, [])
|
|
202
206
|
|
|
203
207
|
# main recursive function to drill into the json structure, assign paths, and create structure where needed
|
|
204
208
|
def build_structure(current_struct, json_path, resource_definition, entity_definition, parts, value, previous_parts):
|
|
@@ -211,7 +215,7 @@ def build_structure(current_struct, json_path, resource_definition, entity_defin
|
|
|
211
215
|
if matching_handler is not None:
|
|
212
216
|
return special_values.custom_handlers[matching_handler].assign_value(json_path, resource_definition, entity_definition, current_struct, parts[-1], value)
|
|
213
217
|
#Ignore dollar sign ($) and drill farther down
|
|
214
|
-
if part == '$' or part == resource_definition
|
|
218
|
+
if part == '$' or part == resource_definition.resource_type.strip():
|
|
215
219
|
#Ignore the dollar sign and the resourcetype
|
|
216
220
|
return build_structure_recurse(current_struct, json_path, resource_definition, entity_definition, parts, value, previous_parts, part)
|
|
217
221
|
|
|
@@ -236,7 +240,7 @@ def build_structure(current_struct, json_path, resource_definition, entity_defin
|
|
|
236
240
|
if part + 1 > len(current_struct):
|
|
237
241
|
current_struct.extend({} for x in range (part + 1 - len(current_struct)))
|
|
238
242
|
#Actual assigning to the path
|
|
239
|
-
fhir_formatting.assign_value(current_struct, part, value, entity_definition
|
|
243
|
+
fhir_formatting.assign_value(current_struct, part, value, entity_definition.value_type)
|
|
240
244
|
return current_struct
|
|
241
245
|
|
|
242
246
|
# If there is a simple qualifier with '['and ']'
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import pprint
|
|
2
|
+
from typing import Dict, Any, List, Optional
|
|
3
|
+
|
|
4
|
+
class FieldEntry:
|
|
5
|
+
def __init__(self, data: Dict[str, Any]):
|
|
6
|
+
self.jsonpath: Optional[str] = data.get('jsonpath')
|
|
7
|
+
self.value_type: Optional[str] = data.get('valueType')
|
|
8
|
+
self.valuesets: Optional[str] = data.get('valuesets')
|
|
9
|
+
self.values: Optional[List[str]] = data.get('values')
|
|
10
|
+
|
|
11
|
+
def __repr__(self) -> str:
|
|
12
|
+
return (f"FieldEntry(\n\tjsonpath='{self.jsonpath}',\n\tvalue_type='{self.value_type}', "
|
|
13
|
+
f"\n\tvaluesets='{self.valuesets}', \n\tvalues={self.values})")
|
|
14
|
+
|
|
15
|
+
class EntityData:
|
|
16
|
+
def __init__(self, data: Dict[str, Dict[str, Any]]):
|
|
17
|
+
"""
|
|
18
|
+
Initializes the EntityData object. Accepts either a dictionary of raw data
|
|
19
|
+
or a list of FieldEntry objects.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
data (Dict[str, Dict[str, Any]]): A dictionary mapping names to raw field data.
|
|
23
|
+
entries (List[FieldEntry]): A list of pre-created FieldEntry objects.
|
|
24
|
+
"""
|
|
25
|
+
self.fields: Dict[str, FieldEntry] = {}
|
|
26
|
+
for name, field_data in data.items():
|
|
27
|
+
self.fields[name] = FieldEntry(field_data)
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return f"EntityData(fields=\n{pprint.pformat(self.fields, indent=4)})"
|
|
31
|
+
|
|
32
|
+
def insert(self, name: str, entry: FieldEntry):
|
|
33
|
+
"""
|
|
34
|
+
Inserts a new FieldEntry into the collection.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name (str): The referential name for the field.
|
|
38
|
+
entry (FieldEntry): The FieldEntry object to insert.
|
|
39
|
+
"""
|
|
40
|
+
self.fields[name] = entry
|
|
41
|
+
|
|
42
|
+
def remove(self, name: str) -> bool:
|
|
43
|
+
"""
|
|
44
|
+
Removes a FieldEntry by its referential name.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
name (str): The referential name of the field to remove.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
bool: True if the field was removed, False otherwise.
|
|
51
|
+
"""
|
|
52
|
+
if name in self.fields:
|
|
53
|
+
del self.fields[name]
|
|
54
|
+
return True
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
class CohortData:
|
|
58
|
+
def __init__(self, data: Dict[str, EntityData] = None):
|
|
59
|
+
"""
|
|
60
|
+
Initializes the CohortData object.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
data (Dict[str, EntityData]): A dictionary where keys are entity names
|
|
64
|
+
and values are EntityData objects.
|
|
65
|
+
"""
|
|
66
|
+
self.entities: Dict[str, EntityData] = {}
|
|
67
|
+
self.num_entries = 0
|
|
68
|
+
if data:
|
|
69
|
+
self.entities.update(data)
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str:
|
|
72
|
+
return f"CohortData(entities={self.entities})"
|
|
73
|
+
|
|
74
|
+
def insert_entity(self, name: str, entity_data: EntityData):
|
|
75
|
+
"""
|
|
76
|
+
Inserts a new EntityData object into the cohort.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
name (str): The name of the entity (e.g., 'PrimaryPatient').
|
|
80
|
+
entity_data (EntityData): The EntityData object to insert.
|
|
81
|
+
"""
|
|
82
|
+
self.entities[name] = entity_data
|
|
83
|
+
|
|
84
|
+
def remove_entity(self, name: str) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Removes an EntityData object by its name.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
name (str): The name of the entity to remove.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
bool: True if the entity was removed, False otherwise.
|
|
93
|
+
"""
|
|
94
|
+
if name in self.entities:
|
|
95
|
+
del self.entities[name]
|
|
96
|
+
return True
|
|
97
|
+
return False
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ResourceDefinition:
|
|
5
|
+
"""
|
|
6
|
+
A class to represent a Resource Definition for FHIR initialization.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, entity_data: Dict[str, Any]):
|
|
9
|
+
"""
|
|
10
|
+
Initializes the ResourceLink object from a dictionary.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
data: A dictionary containing 'Entity name', 'ResourceType', and 'Profile(s)'.
|
|
14
|
+
"""
|
|
15
|
+
self.entity_name = entity_data.get('Entity Name')
|
|
16
|
+
self.resource_type = entity_data.get('ResourceType')
|
|
17
|
+
self.profiles = entity_data.get('Profile(s)')
|
|
18
|
+
|
|
19
|
+
def __repr__(self) -> str:
|
|
20
|
+
return f"FhirEntity(entity_name='{self.entity_name}', resource_type='{self.resource_type}', profiles={self.profiles})"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ResourceLink:
|
|
5
|
+
"""
|
|
6
|
+
A class to represent a Fhir Reference between two resources.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, data: Dict[str, Any]):
|
|
9
|
+
"""
|
|
10
|
+
Initializes the ResourceLink object from a dictionary.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
data: A dictionary containing 'OriginResource', 'ReferencePath', and 'DestinationResource'.
|
|
14
|
+
"""
|
|
15
|
+
self.origin_resource = data.get('OriginResource')
|
|
16
|
+
self.reference_path = data.get('ReferencePath')
|
|
17
|
+
self.destination_resource = data.get('DestinationResource')
|
|
18
|
+
|
|
19
|
+
def __repr__(self) -> str:
|
|
20
|
+
return (f"ResourceLink(origin_resource='{self.origin_resource}', "
|
|
21
|
+
f"reference_path='{self.reference_path}', "
|
|
22
|
+
f"destination_resource='{self.destination_resource}')")
|
fhir_sheets/core/read_input.py
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import openpyxl
|
|
2
2
|
|
|
3
|
+
from .model.cohort_data_entity import CohortData, EntityData, FieldEntry
|
|
4
|
+
|
|
5
|
+
from .model.resource_definition_entity import ResourceDefinition
|
|
6
|
+
from .model.resource_link_entity import ResourceLink
|
|
7
|
+
|
|
3
8
|
# Function to read the xlsx file and access specific sheets
|
|
4
9
|
def read_xlsx_and_process(file_path):
|
|
5
10
|
# Load the workbook
|
|
@@ -16,19 +21,15 @@ def read_xlsx_and_process(file_path):
|
|
|
16
21
|
|
|
17
22
|
if 'PatientData' in workbook.sheetnames:
|
|
18
23
|
sheet = workbook['PatientData']
|
|
19
|
-
|
|
24
|
+
cohort_data = process_sheet_patient_data(sheet, resource_definition_entities)
|
|
20
25
|
|
|
21
|
-
return
|
|
22
|
-
"resource_definition_entities": resource_definition_entities,
|
|
23
|
-
"resource_link_entities": resource_link_entities,
|
|
24
|
-
"patient_data_entities": patient_data_entities,
|
|
25
|
-
"num_entries": num_entries
|
|
26
|
-
}
|
|
26
|
+
return resource_definition_entities, resource_link_entities, cohort_data
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
# Function to process the specific sheet with 'Entity Name', 'ResourceType', and 'Profile(s)'
|
|
30
30
|
def process_sheet_resource_definitions(sheet):
|
|
31
31
|
resource_definitions = []
|
|
32
|
+
resource_definition_entities = []
|
|
32
33
|
headers = [cell.value for cell in next(sheet.iter_rows(min_row=1, max_row=1))] # Get headers
|
|
33
34
|
|
|
34
35
|
for row in sheet.iter_rows(min_row=3, values_only=True):
|
|
@@ -38,27 +39,30 @@ def process_sheet_resource_definitions(sheet):
|
|
|
38
39
|
# Split 'Profile(s)' column into a list of URLs
|
|
39
40
|
if row_data.get("Profile(s)"):
|
|
40
41
|
row_data["Profile(s)"] = [url.strip() for url in row_data["Profile(s)"].split(",")]
|
|
41
|
-
|
|
42
|
+
resource_definition_entities.append(ResourceDefinition(row_data))
|
|
42
43
|
resource_definitions.append(row_data)
|
|
43
44
|
|
|
44
|
-
return
|
|
45
|
+
return resource_definition_entities
|
|
45
46
|
|
|
46
47
|
# Function to process the specific sheet with 'OriginResource', 'ReferencePath', and 'DestinationResource'
|
|
47
48
|
def process_sheet_resource_links(sheet):
|
|
48
49
|
resource_links = []
|
|
50
|
+
resource_link_entities = []
|
|
49
51
|
headers = [cell.value for cell in next(sheet.iter_rows(min_row=1, max_row=1))] # Get headers
|
|
50
52
|
for row in sheet.iter_rows(min_row=3, values_only=True):
|
|
51
53
|
row_data = dict(zip(headers, row)) # Create a dictionary for each row
|
|
52
54
|
if all(cell is None or cell == "" for cell in row_data):
|
|
53
55
|
continue
|
|
54
56
|
resource_links.append(row_data)
|
|
57
|
+
resource_link_entities.append(ResourceLink(row_data))
|
|
55
58
|
|
|
56
|
-
return
|
|
59
|
+
return resource_link_entities
|
|
57
60
|
|
|
58
61
|
# Function to process the "PatientData" sheet
|
|
59
62
|
def process_sheet_patient_data(sheet, resource_definition_entities):
|
|
60
63
|
# Initialize the dictionary to store the processed data
|
|
61
64
|
patient_data = {}
|
|
65
|
+
cohort_data = CohortData()
|
|
62
66
|
# Extract the data from the first 6 rows (Entity To Query, JsonPath, etc.)
|
|
63
67
|
for col in sheet.iter_cols(min_row=1, max_row=6, min_col=3, values_only=True): # Start from 3rd column
|
|
64
68
|
if all(entry is None for entry in col):
|
|
@@ -68,20 +72,23 @@ def process_sheet_patient_data(sheet, resource_definition_entities):
|
|
|
68
72
|
if (entity_name is None or entity_name == "") and (field_name is not None and field_name != ""):
|
|
69
73
|
print(f"WARNING: - Reading Patient Data Issue - {field_name} - 'Entity To Query' cell missing for column labelled '{field_name}', please provide entity name from the ResourceDefinitions tab.")
|
|
70
74
|
|
|
71
|
-
if entity_name not in [entry
|
|
75
|
+
if entity_name not in [entry.entity_name for entry in resource_definition_entities]:
|
|
72
76
|
print(f"WARNING: - Reading Patient Data Issue - {field_name} - 'Entity To Query' cell has entity named '{entity_name}', however, the ResourceDefinition tab has no matching resource. Please provide a corresponding entry in the ResourceDefinition tab.")
|
|
73
77
|
# Create structure for this entity if not already present
|
|
74
78
|
if entity_name not in patient_data:
|
|
75
79
|
patient_data[entity_name] = {}
|
|
80
|
+
cohort_data.insert_entity(entity_name, EntityData({}))
|
|
76
81
|
|
|
77
82
|
# Add jsonpath, valuesets, and initialize an empty list for 'values'
|
|
78
83
|
if field_name not in patient_data[entity_name]:
|
|
79
|
-
|
|
84
|
+
field_data = {
|
|
80
85
|
"jsonpath": col[1], # JsonPath from the second row
|
|
81
86
|
"valueType": col[2], # Value Type from the third row
|
|
82
87
|
"valuesets": col[3], # Value Set from the fourth row
|
|
83
88
|
"values": [] # Initialize empty list for actual values
|
|
84
89
|
}
|
|
90
|
+
patient_data[entity_name][field_name] = field_data
|
|
91
|
+
cohort_data.entities[entity_name].insert(field_name, FieldEntry(field_data))
|
|
85
92
|
|
|
86
93
|
# Now process the rows starting from the 6th row (the actual data entries)
|
|
87
94
|
num_entries = 0
|
|
@@ -95,5 +102,6 @@ def process_sheet_patient_data(sheet, resource_definition_entities):
|
|
|
95
102
|
field_name = sheet.cell(row=6, column=i + 2).value # Get the Data Element for this column
|
|
96
103
|
if entity_name in patient_data and field_name in patient_data[entity_name]:
|
|
97
104
|
# Append the actual data values to the 'values' array
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
cohort_data.entities[entity_name].fields[field_name].values.append(value)
|
|
106
|
+
cohort_data.num_entries = num_entries
|
|
107
|
+
return cohort_data
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: fhir-sheets
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: FhirSheets is a command-line tool that reads an Excel file in FHIR cohort format and generates FHIR bundle JSON files from it. Each row in the template Excel file is used to create an individual JSON file, outputting them to a specified folder.
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Author: Michael Riley
|
|
6
7
|
Author-email: Michael.Riley@gtri.gatech.edu
|
|
7
8
|
Requires-Python: >=3.13
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
10
12
|
Requires-Dist: et-xmlfile (==1.1.0)
|
|
11
13
|
Requires-Dist: jsonpath-ng (==1.6.1)
|
|
12
14
|
Requires-Dist: openpyxl (==3.1.5)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
fhir_sheets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
fhir_sheets/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
fhir_sheets/cli/main.py,sha256=UlQn3i_aaZEpCk7DaOQwiRRQoL7-lup_HepbR7x-Qkc,2668
|
|
4
|
+
fhir_sheets/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
fhir_sheets/core/conversion.py,sha256=AkhAlQceI3U1Ho8711TPTmP-msY65XjpgbTYNq91mj0,18482
|
|
6
|
+
fhir_sheets/core/fhir_formatting.py,sha256=kGnXZe3RusafxuCz_tp4bBZbdIAFWXJ4ZRrnG2iQYfo,13011
|
|
7
|
+
fhir_sheets/core/model/cohort_data_entity.py,sha256=rcPwn_FecsYkX0wgi7DaZMBXJ43S8a5tg4Lxnp6PCyQ,3308
|
|
8
|
+
fhir_sheets/core/model/resource_definition_entity.py,sha256=NYOBjGz2LyfjI9LVl7j34fcAp-qEmzrBTg9ZLpFfDxA,738
|
|
9
|
+
fhir_sheets/core/model/resource_link_entity.py,sha256=-iCzGaKJt5ZISTSwBVmEuLIKzgRtbL_I7JRpoEc89h0,812
|
|
10
|
+
fhir_sheets/core/read_input.py,sha256=08MFs_dxv-QidrloSS1w40CH-4__1M5Loq_rufb8aBc,5810
|
|
11
|
+
fhir_sheets/core/special_values.py,sha256=KwpJLggH3-BSH8NrGtRTtVBz0fl-iH7swxPwBb3iMx4,15513
|
|
12
|
+
fhir_sheets/core/util.py,sha256=gudEPi_xTDC9YHqUlGEkn_tA26HbhNjuqGgjtuOEINk,9
|
|
13
|
+
fhir_sheets-1.2.0.dist-info/METADATA,sha256=LwFMslfkuJmVev_hcUFRgz00lZIV7jP4XDaS6hykIkw,739
|
|
14
|
+
fhir_sheets-1.2.0.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
|
|
15
|
+
fhir_sheets-1.2.0.dist-info/licenses/LICENSE,sha256=Qv2ilebwoUtMJnRsZwRy729xS5JZQzLauJ0tQzkAkTA,1088
|
|
16
|
+
fhir_sheets-1.2.0.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
fhir_sheets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
fhir_sheets/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
fhir_sheets/cli/fhirsheets.py,sha256=EOC42o4nJ49G8QDGxb3PeNMqB0F05OV061LifC1dPq4,2721
|
|
4
|
-
fhir_sheets/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
fhir_sheets/core/conversion.py,sha256=_N5Zo5Qb_vzUuoi-cMX1P-ELAOcDQQMaNo1BJBLu05Q,18170
|
|
6
|
-
fhir_sheets/core/fhir_formatting.py,sha256=kGnXZe3RusafxuCz_tp4bBZbdIAFWXJ4ZRrnG2iQYfo,13011
|
|
7
|
-
fhir_sheets/core/read_input.py,sha256=9zD7B61se_r9rkFYmKBRoWgFqYo9YJ8eIYFsm7Jn9VM,5312
|
|
8
|
-
fhir_sheets/core/special_values.py,sha256=KwpJLggH3-BSH8NrGtRTtVBz0fl-iH7swxPwBb3iMx4,15513
|
|
9
|
-
fhir_sheets/core/util.py,sha256=gudEPi_xTDC9YHqUlGEkn_tA26HbhNjuqGgjtuOEINk,9
|
|
10
|
-
fhir_sheets-1.1.0.dist-info/LICENSE,sha256=Qv2ilebwoUtMJnRsZwRy729xS5JZQzLauJ0tQzkAkTA,1088
|
|
11
|
-
fhir_sheets-1.1.0.dist-info/METADATA,sha256=0Cl4h-ONolHWJOEdxmATNtq8Oe0l557wwrzjNRdn8ws,666
|
|
12
|
-
fhir_sheets-1.1.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
13
|
-
fhir_sheets-1.1.0.dist-info/RECORD,,
|
|
File without changes
|