h-adminsim 1.0.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.
- h_adminsim/__init__.py +5 -0
- h_adminsim/admin_staff.py +280 -0
- h_adminsim/assets/configs/data4primary.yaml +47 -0
- h_adminsim/assets/configs/data4secondary.yaml +47 -0
- h_adminsim/assets/configs/data4tertiary.yaml +47 -0
- h_adminsim/assets/country/address.json +141859 -0
- h_adminsim/assets/country/country_code.json +244 -0
- h_adminsim/assets/departments/department.json +85 -0
- h_adminsim/assets/departments/symptom.json +4530 -0
- h_adminsim/assets/fhir.schema.json +75253 -0
- h_adminsim/assets/names/firstname.txt +1219 -0
- h_adminsim/assets/names/lastname.txt +88799 -0
- h_adminsim/assets/prompts/cancel_patient_system.txt +38 -0
- h_adminsim/assets/prompts/intake_staff_task_user.txt +16 -0
- h_adminsim/assets/prompts/intake_supervisor_system.txt +8 -0
- h_adminsim/assets/prompts/intake_supervisor_user.txt +31 -0
- h_adminsim/assets/prompts/reschedule_patient_system.txt +38 -0
- h_adminsim/assets/prompts/schedule_patient_rejected_system.txt +42 -0
- h_adminsim/assets/prompts/schedule_patient_system.txt +36 -0
- h_adminsim/assets/prompts/schedule_staff_reasoning.txt +57 -0
- h_adminsim/assets/prompts/schedule_staff_sc_tool_calling.txt +13 -0
- h_adminsim/assets/prompts/schedule_staff_system.txt +10 -0
- h_adminsim/assets/prompts/schedule_staff_tool_calling.txt +41 -0
- h_adminsim/client/__init__.py +3 -0
- h_adminsim/client/google_client.py +209 -0
- h_adminsim/client/openai_client.py +199 -0
- h_adminsim/client/vllm_client.py +160 -0
- h_adminsim/environment/__init__.py +1 -0
- h_adminsim/environment/hospital.py +462 -0
- h_adminsim/environment/op_scheduling_simulation.py +1126 -0
- h_adminsim/pipeline/__init__.py +3 -0
- h_adminsim/pipeline/data_generator.py +192 -0
- h_adminsim/pipeline/evaluator.py +33 -0
- h_adminsim/pipeline/simulation.py +231 -0
- h_adminsim/registry/__init__.py +5 -0
- h_adminsim/registry/errors.py +89 -0
- h_adminsim/registry/models.py +126 -0
- h_adminsim/registry/phrases.py +10 -0
- h_adminsim/registry/pydantic_models.py +21 -0
- h_adminsim/registry/variables.py +9 -0
- h_adminsim/supervisor.py +182 -0
- h_adminsim/task/agent_task.py +900 -0
- h_adminsim/task/fhir_manager.py +222 -0
- h_adminsim/task/schedule_assign.py +151 -0
- h_adminsim/tools/__init__.py +5 -0
- h_adminsim/tools/agent_data_builder.py +124 -0
- h_adminsim/tools/data_converter.py +536 -0
- h_adminsim/tools/data_synthesizer.py +365 -0
- h_adminsim/tools/evaluator.py +258 -0
- h_adminsim/tools/sanity_checker.py +216 -0
- h_adminsim/tools/scheduling_rule.py +420 -0
- h_adminsim/utils/__init__.py +136 -0
- h_adminsim/utils/common_utils.py +698 -0
- h_adminsim/utils/fhir_utils.py +190 -0
- h_adminsim/utils/filesys_utils.py +135 -0
- h_adminsim/utils/image_preprocess_utils.py +188 -0
- h_adminsim/utils/random_utils.py +358 -0
- h_adminsim/version.txt +1 -0
- h_adminsim-1.0.0.dist-info/LICENSE +30 -0
- h_adminsim-1.0.0.dist-info/METADATA +494 -0
- h_adminsim-1.0.0.dist-info/RECORD +62 -0
- h_adminsim-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from tqdm import tqdm
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from h_adminsim.utils import Information, log
|
|
6
|
+
from h_adminsim.utils.fhir_utils import *
|
|
7
|
+
from h_adminsim.utils.filesys_utils import json_load, json_save_fast, get_files
|
|
8
|
+
from h_adminsim.utils.common_utils import (
|
|
9
|
+
get_iso_time,
|
|
10
|
+
get_utc_offset,
|
|
11
|
+
convert_time_to_segment,
|
|
12
|
+
convert_segment_to_time,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DataConverter:
|
|
18
|
+
def __init__(self, config):
|
|
19
|
+
# Initialize configuration
|
|
20
|
+
self.fhir_url = config.fhir_url
|
|
21
|
+
data_dir = os.path.join(config.project, config.data_name, 'data')
|
|
22
|
+
self.data_files = get_files(data_dir, ext='json')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def data_to_practitioner(data: dict, output_dir: Optional[str] = None, sanity_check: bool = False) -> list[dict]:
|
|
27
|
+
"""
|
|
28
|
+
Convert synthetic hospital data into `Practitioner` FHIR resources.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
data (dict): Synthetic hospital data containing doctor information.
|
|
32
|
+
output_dir (Optional[str], optional): Directory path to save the converted Practitioner resources
|
|
33
|
+
as `.fhir.json` files. If None, the resources are not saved to disk.
|
|
34
|
+
Defaults to None.
|
|
35
|
+
sanity_check (bool, optional): If True, performs a sanity check to ensure the uniqueness of the generated FHIR data.
|
|
36
|
+
This only applies when output_dir is specified. Defaults to False.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
list[dict]: A list of converted FHIR Practitioner resource objects.
|
|
40
|
+
"""
|
|
41
|
+
save_dir = None
|
|
42
|
+
if output_dir:
|
|
43
|
+
os.makedirs(os.path.join(output_dir, 'practitioner'), exist_ok=True)
|
|
44
|
+
save_dir = os.path.join(output_dir, 'practitioner')
|
|
45
|
+
|
|
46
|
+
hospital_name = data.get('metadata')['hospital_name']
|
|
47
|
+
department_data = data.get('department')
|
|
48
|
+
practitioners = list()
|
|
49
|
+
|
|
50
|
+
for doctor_name, doctor_values in data['doctor'].items():
|
|
51
|
+
practitioner_id = get_individual_id(
|
|
52
|
+
hospital_name,
|
|
53
|
+
department_data[doctor_values['department']]['code'].lower(),
|
|
54
|
+
doctor_name
|
|
55
|
+
)
|
|
56
|
+
names = doctor_name.split()
|
|
57
|
+
practitioner_obj = {
|
|
58
|
+
'resourceType': 'Practitioner',
|
|
59
|
+
'id': practitioner_id,
|
|
60
|
+
'active': True,
|
|
61
|
+
'name': [
|
|
62
|
+
{
|
|
63
|
+
'family': names[-1],
|
|
64
|
+
'given': [' '.join(names[1:-1])],
|
|
65
|
+
'prefix': [names[0]]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
'gender': doctor_values['gender'],
|
|
69
|
+
'telecom': doctor_values['telecom'],
|
|
70
|
+
'birthDate': doctor_values['birthDate']
|
|
71
|
+
}
|
|
72
|
+
practitioners.append(practitioner_obj)
|
|
73
|
+
|
|
74
|
+
if save_dir:
|
|
75
|
+
save_path = os.path.join(save_dir, f'{practitioner_id}.fhir.json')
|
|
76
|
+
if sanity_check:
|
|
77
|
+
assert not os.path.exists(save_path), log(f"Same file exists: {save_path}", "error")
|
|
78
|
+
json_save_fast(
|
|
79
|
+
save_path,
|
|
80
|
+
practitioner_obj
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return practitioners
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def data_to_practitionerrole(data: dict, output_dir: Optional[str] = None, sanity_check: bool = False) -> list[dict]:
|
|
88
|
+
"""
|
|
89
|
+
Convert synthetic hospital data into `PractitionerRole` FHIR resources.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
data (dict): Synthetic hospital data containing doctor information.
|
|
93
|
+
output_dir (Optional[str], optional): Directory path to save the converted PractitionerRole resources
|
|
94
|
+
as `.fhir.json` files. If None, the resources are not saved to disk.
|
|
95
|
+
Defaults to None.
|
|
96
|
+
sanity_check (bool, optional): If True, performs a sanity check to ensure the uniqueness of the generated FHIR data.
|
|
97
|
+
This only applies when output_dir is specified. Defaults to False.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
list[dict]: A list of converted FHIR PractitionerRole resource objects.
|
|
101
|
+
"""
|
|
102
|
+
save_dir = None
|
|
103
|
+
if output_dir:
|
|
104
|
+
os.makedirs(os.path.join(output_dir, 'practitionerrole'), exist_ok=True)
|
|
105
|
+
save_dir = os.path.join(output_dir, 'practitionerrole')
|
|
106
|
+
|
|
107
|
+
hospital_name = data.get('metadata')['hospital_name']
|
|
108
|
+
department_data = data.get('department')
|
|
109
|
+
practitionerroles = list()
|
|
110
|
+
|
|
111
|
+
for doctor_name, doctor_values in data['doctor'].items():
|
|
112
|
+
practitioner_id = get_individual_id(
|
|
113
|
+
hospital_name,
|
|
114
|
+
department_data[doctor_values['department']]['code'].lower(),
|
|
115
|
+
doctor_name
|
|
116
|
+
)
|
|
117
|
+
practitionerrole_id = get_practitionerrole_id(practitioner_id)
|
|
118
|
+
practitionerrole_obj = {
|
|
119
|
+
'resourceType': 'PractitionerRole',
|
|
120
|
+
'id': practitionerrole_id,
|
|
121
|
+
'active': True,
|
|
122
|
+
'specialty': [
|
|
123
|
+
{
|
|
124
|
+
'coding': [{
|
|
125
|
+
'code': doctor_values['specialty']['code'],
|
|
126
|
+
'display': doctor_values['specialty']['name']
|
|
127
|
+
}],
|
|
128
|
+
'text': doctor_values['department']
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
'characteristic': [
|
|
132
|
+
{
|
|
133
|
+
'coding': [{
|
|
134
|
+
'code': 'capacity_per_hour',
|
|
135
|
+
'display': str(doctor_values['capacity_per_hour'])
|
|
136
|
+
}],
|
|
137
|
+
'text': 'capacity_per_hour'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
'coding': [{
|
|
141
|
+
'code': 'capacity',
|
|
142
|
+
'display': str(doctor_values['capacity'])
|
|
143
|
+
}],
|
|
144
|
+
'text': 'capacity'
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
'practitioner': {'reference': f'Practitioner/{practitioner_id}'}
|
|
148
|
+
}
|
|
149
|
+
practitionerroles.append(practitionerrole_obj)
|
|
150
|
+
|
|
151
|
+
if save_dir:
|
|
152
|
+
save_path = os.path.join(save_dir, f'{practitionerrole_id}.fhir.json')
|
|
153
|
+
if sanity_check:
|
|
154
|
+
assert not os.path.exists(save_path), log(f"Same file exists: {save_path}", "error")
|
|
155
|
+
json_save_fast(
|
|
156
|
+
save_path,
|
|
157
|
+
practitionerrole_obj
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return practitionerroles
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def data_to_patient(data: dict, output_dir: Optional[str] = None, sanity_check: bool = False) -> list[dict]:
|
|
165
|
+
"""
|
|
166
|
+
Convert synthetic hospital data into `Patient` FHIR resources.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
data (dict): Synthetic hospital data containing doctor information.
|
|
170
|
+
output_dir (Optional[str], optional): Directory path to save the converted Patient resources
|
|
171
|
+
as `.fhir.json` files. If None, the resources are not saved to disk.
|
|
172
|
+
Defaults to None.
|
|
173
|
+
sanity_check (bool, optional): If True, performs a sanity check to ensure the uniqueness of the generated FHIR data.
|
|
174
|
+
This only applies when output_dir is specified. Defaults to False.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
list[dict]: A list of converted FHIR Patient resource objects.
|
|
178
|
+
"""
|
|
179
|
+
save_dir = None
|
|
180
|
+
if output_dir:
|
|
181
|
+
os.makedirs(os.path.join(output_dir, 'patient'), exist_ok=True)
|
|
182
|
+
save_dir = os.path.join(output_dir, 'patient')
|
|
183
|
+
|
|
184
|
+
hospital_name = data.get('metadata')['hospital_name']
|
|
185
|
+
department_data = data.get('department')
|
|
186
|
+
patients = list()
|
|
187
|
+
|
|
188
|
+
for patient_name, patient_values in data['patient'].items():
|
|
189
|
+
patient_id = get_individual_id(
|
|
190
|
+
hospital_name,
|
|
191
|
+
department_data[patient_values['department']]['code'].lower(),
|
|
192
|
+
patient_name
|
|
193
|
+
)
|
|
194
|
+
names = patient_name.split()
|
|
195
|
+
patient_obj = {
|
|
196
|
+
'resourceType': 'Patient',
|
|
197
|
+
'id': patient_id,
|
|
198
|
+
'active': True,
|
|
199
|
+
'name': [
|
|
200
|
+
{
|
|
201
|
+
'family': names[-1],
|
|
202
|
+
'given': [' '.join(names[:-1])],
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
'gender': patient_values['gender'],
|
|
206
|
+
'telecom': patient_values['telecom'],
|
|
207
|
+
'birthDate': patient_values['birthDate'],
|
|
208
|
+
'identifier': patient_values['identifier'],
|
|
209
|
+
'address': patient_values['address']
|
|
210
|
+
}
|
|
211
|
+
patients.append(patient_obj)
|
|
212
|
+
|
|
213
|
+
if save_dir:
|
|
214
|
+
save_path = os.path.join(save_dir, f'{patient_id}.fhir.json')
|
|
215
|
+
if sanity_check:
|
|
216
|
+
assert not os.path.exists(save_path), log(f"Same file exists: {save_path}", "error")
|
|
217
|
+
json_save_fast(
|
|
218
|
+
save_path,
|
|
219
|
+
patient_obj
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return patients
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def data_to_schedule(data: dict, output_dir: Optional[str] = None, sanity_check: bool = False) -> list[dict]:
|
|
227
|
+
"""
|
|
228
|
+
Convert synthetic hospital data into `Schedule` FHIR resources.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
data (dict): Synthetic hospital data containing doctor information.
|
|
232
|
+
output_dir (Optional[str], optional): Directory path to save the converted Schedule resources
|
|
233
|
+
as `.fhir.json` files. If None, the resources are not saved to disk.
|
|
234
|
+
Defaults to None.
|
|
235
|
+
sanity_check (bool, optional): If True, performs a sanity check to ensure the uniqueness of the generated FHIR data.
|
|
236
|
+
This only applies when output_dir is specified. Defaults to False.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
list[dict]: A list of converted FHIR Schedule resource objects.
|
|
240
|
+
"""
|
|
241
|
+
save_dir = None
|
|
242
|
+
if output_dir:
|
|
243
|
+
os.makedirs(os.path.join(output_dir, 'schedule'), exist_ok=True)
|
|
244
|
+
save_dir = os.path.join(output_dir, 'schedule')
|
|
245
|
+
|
|
246
|
+
hospital_name = data.get('metadata')['hospital_name']
|
|
247
|
+
department_data = data.get('department')
|
|
248
|
+
country_code = data.get('metadata').get('country_code', 'KR')
|
|
249
|
+
time_zone = data.get('metadata').get('timezone', None)
|
|
250
|
+
start_date = data.get('metadata').get('start_date', None)
|
|
251
|
+
end_date = data.get('metadata').get('end_date', start_date)
|
|
252
|
+
start = get_iso_time(data.get('metadata')['time']['start_hour'], start_date, get_utc_offset(country_code, time_zone))
|
|
253
|
+
end = get_iso_time(data.get('metadata')['time']['end_hour'], end_date, get_utc_offset(country_code, time_zone))
|
|
254
|
+
schedules = list()
|
|
255
|
+
|
|
256
|
+
for doctor_name, doctor_values in data['doctor'].items():
|
|
257
|
+
practitioner_id = get_individual_id(
|
|
258
|
+
hospital_name,
|
|
259
|
+
department_data[doctor_values['department']]['code'].lower(),
|
|
260
|
+
doctor_name
|
|
261
|
+
)
|
|
262
|
+
schedule_id = get_schedule_id(practitioner_id)
|
|
263
|
+
schedule_obj = {
|
|
264
|
+
'resourceType': 'Schedule',
|
|
265
|
+
'id': schedule_id,
|
|
266
|
+
'active': True,
|
|
267
|
+
'actor': [{'reference': f'Practitioner/{practitioner_id}'}],
|
|
268
|
+
'planningHorizon': {'start': start, 'end': end}
|
|
269
|
+
}
|
|
270
|
+
schedules.append(schedule_obj)
|
|
271
|
+
|
|
272
|
+
if save_dir:
|
|
273
|
+
save_path = os.path.join(save_dir, f'{schedule_id}.fhir.json')
|
|
274
|
+
if sanity_check:
|
|
275
|
+
assert not os.path.exists(save_path), log(f"Same file exists: {save_path}", "error")
|
|
276
|
+
json_save_fast(
|
|
277
|
+
save_path,
|
|
278
|
+
schedule_obj
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return schedules
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
def data_to_slot(data: dict, output_dir: Optional[str] = None, sanity_check: bool = False) -> list[dict]:
|
|
286
|
+
"""
|
|
287
|
+
Convert synthetic hospital data into `Slot` FHIR resources.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
data (dict): Synthetic hospital data containing doctor information.
|
|
291
|
+
output_dir (Optional[str], optional): Directory path to save the converted Slot resources
|
|
292
|
+
as `.fhir.json` files. If None, the resources are not saved to disk.
|
|
293
|
+
Defaults to None.
|
|
294
|
+
sanity_check (bool, optional): If True, performs a sanity check to ensure the uniqueness of the generated FHIR data.
|
|
295
|
+
This only applies when output_dir is specified. Defaults to False.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
list[dict]: A list of converted FHIR Slot resource objects.
|
|
299
|
+
"""
|
|
300
|
+
save_dir = None
|
|
301
|
+
if output_dir:
|
|
302
|
+
os.makedirs(os.path.join(output_dir, 'slot'), exist_ok=True)
|
|
303
|
+
save_dir = os.path.join(output_dir, 'slot')
|
|
304
|
+
|
|
305
|
+
hospital_name = data.get('metadata')['hospital_name']
|
|
306
|
+
department_data = data.get('department')
|
|
307
|
+
country_code = data.get('metadata').get('country_code', 'KR')
|
|
308
|
+
time_zone = data.get('metadata').get('timezone', None)
|
|
309
|
+
utc_offset = get_utc_offset(country_code, time_zone)
|
|
310
|
+
start_hour = data.get('metadata')['time']['start_hour']
|
|
311
|
+
end_hour = data.get('metadata')['time']['end_hour']
|
|
312
|
+
interval_hour = data.get('metadata')['time']['interval_hour']
|
|
313
|
+
entire_segments = convert_time_to_segment(start_hour, end_hour, interval_hour)
|
|
314
|
+
slots = list()
|
|
315
|
+
|
|
316
|
+
for doctor_name, doctor_values in data['doctor'].items():
|
|
317
|
+
practitioner_id = get_individual_id(
|
|
318
|
+
hospital_name,
|
|
319
|
+
department_data[doctor_values['department']]['code'].lower(),
|
|
320
|
+
doctor_name
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
for date, schedules in doctor_values['schedule'].items():
|
|
324
|
+
# Filtering fixed schedule
|
|
325
|
+
fixed_schedule = []
|
|
326
|
+
for schedule in schedules:
|
|
327
|
+
fixed_schedule += convert_time_to_segment(start_hour, end_hour, interval_hour, schedule)
|
|
328
|
+
|
|
329
|
+
# Appointment available time segments
|
|
330
|
+
free_schedule = sorted(list(set(entire_segments) - set(fixed_schedule)))
|
|
331
|
+
|
|
332
|
+
# Add slot as a `busy` status
|
|
333
|
+
for seg in fixed_schedule:
|
|
334
|
+
st, tr = convert_segment_to_time(start_hour, end_hour, interval_hour, [seg])
|
|
335
|
+
slot_id = get_slot_id(practitioner_id, date, seg)
|
|
336
|
+
slot_obj = {
|
|
337
|
+
'resourceType': 'Slot',
|
|
338
|
+
'id': slot_id,
|
|
339
|
+
'schedule': {'reference': f'Schedule/{get_schedule_id(practitioner_id)}'},
|
|
340
|
+
'status': 'busy',
|
|
341
|
+
'start': get_iso_time(st, date, utc_offset),
|
|
342
|
+
'end': get_iso_time(tr, date, utc_offset),
|
|
343
|
+
}
|
|
344
|
+
slots.append(slot_obj)
|
|
345
|
+
|
|
346
|
+
if save_dir:
|
|
347
|
+
save_path = os.path.join(save_dir, f'{slot_id}.fhir.json')
|
|
348
|
+
if sanity_check:
|
|
349
|
+
assert not os.path.exists(save_path), log(f"Same file exists: {save_path}", "error")
|
|
350
|
+
json_save_fast(
|
|
351
|
+
save_path,
|
|
352
|
+
slot_obj
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Add slot as a `free` status
|
|
356
|
+
for seg in free_schedule:
|
|
357
|
+
slot_id = get_slot_id(practitioner_id, date, seg)
|
|
358
|
+
st, tr = convert_segment_to_time(start_hour, end_hour, interval_hour, [seg])
|
|
359
|
+
slot_obj = {
|
|
360
|
+
'resourceType': 'Slot',
|
|
361
|
+
'id': slot_id,
|
|
362
|
+
'schedule': {'reference': f'Schedule/{get_schedule_id(practitioner_id)}'},
|
|
363
|
+
'status': 'free',
|
|
364
|
+
'start': get_iso_time(st, date, utc_offset),
|
|
365
|
+
'end': get_iso_time(tr, date, utc_offset),
|
|
366
|
+
}
|
|
367
|
+
slots.append(slot_obj)
|
|
368
|
+
|
|
369
|
+
if save_dir:
|
|
370
|
+
save_path = os.path.join(save_dir, f'{slot_id}.fhir.json')
|
|
371
|
+
if sanity_check:
|
|
372
|
+
assert not os.path.exists(save_path), log(f"Same file exists: {save_path}", "error")
|
|
373
|
+
json_save_fast(
|
|
374
|
+
save_path,
|
|
375
|
+
slot_obj
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
return slots
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@staticmethod
|
|
382
|
+
def data_to_appointment(data: dict, output_dir: Optional[str] = None, sanity_check: bool = False) -> list[dict]:
|
|
383
|
+
"""
|
|
384
|
+
Convert synthetic hospital data into `Appointment` FHIR resources.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
data (dict): Synthetic hospital data containing doctor information.
|
|
388
|
+
output_dir (Optional[str], optional): Directory path to save the converted Appointment resources
|
|
389
|
+
as `.fhir.json` files. If None, the resources are not saved to disk.
|
|
390
|
+
Defaults to None.
|
|
391
|
+
sanity_check (bool, optional): If True, performs a sanity check to ensure the uniqueness of the generated FHIR data.
|
|
392
|
+
This only applies when output_dir is specified. Defaults to False.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
list[dict]: A list of converted FHIR Appointment resource objects.
|
|
396
|
+
"""
|
|
397
|
+
save_dir = None
|
|
398
|
+
if output_dir:
|
|
399
|
+
os.makedirs(os.path.join(output_dir, 'appointment'), exist_ok=True)
|
|
400
|
+
save_dir = os.path.join(output_dir, 'appointment')
|
|
401
|
+
|
|
402
|
+
hospital_name = data.get('metadata')['hospital_name']
|
|
403
|
+
department_data = data.get('department')
|
|
404
|
+
country_code = data.get('metadata').get('country_code', 'KR')
|
|
405
|
+
time_zone = data.get('metadata').get('timezone', None)
|
|
406
|
+
utc_offset = get_utc_offset(country_code, time_zone)
|
|
407
|
+
start_hour = data.get('metadata')['time']['start_hour']
|
|
408
|
+
end_hour = data.get('metadata')['time']['end_hour']
|
|
409
|
+
interval_hour = data.get('metadata')['time']['interval_hour']
|
|
410
|
+
appointments = list()
|
|
411
|
+
|
|
412
|
+
for patient_name, patient_values in data['patient'].items():
|
|
413
|
+
doctor_name = patient_values['attending_physician']
|
|
414
|
+
practitioner_id = get_individual_id(
|
|
415
|
+
hospital_name,
|
|
416
|
+
department_data[patient_values['department']]['code'].lower(),
|
|
417
|
+
doctor_name
|
|
418
|
+
)
|
|
419
|
+
patient_id = get_individual_id(
|
|
420
|
+
hospital_name,
|
|
421
|
+
department_data[patient_values['department']]['code'].lower(),
|
|
422
|
+
patient_name
|
|
423
|
+
)
|
|
424
|
+
participant = [
|
|
425
|
+
{"actor": {"reference": f"Practitioner/{practitioner_id}", "display": doctor_name}, "status": "accepted"},
|
|
426
|
+
{"actor": {"reference": f"Patient/{patient_id}", "display": patient_name}, "status": "accepted"}
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
# Filtering fixed schedule
|
|
430
|
+
date = patient_values['date']
|
|
431
|
+
schedule_time_range = patient_values['schedule']
|
|
432
|
+
schedule_segments = convert_time_to_segment(start_hour, end_hour, interval_hour, schedule_time_range)
|
|
433
|
+
appointment_id = get_appointment_id(practitioner_id, date, schedule_segments[0], schedule_segments[-1])
|
|
434
|
+
appointment_obj = {
|
|
435
|
+
'resourceType': 'Appointment',
|
|
436
|
+
'id': appointment_id,
|
|
437
|
+
'status': 'booked',
|
|
438
|
+
'start': get_iso_time(schedule_time_range[0], date, utc_offset),
|
|
439
|
+
'end': get_iso_time(schedule_time_range[-1], date, utc_offset),
|
|
440
|
+
'slot': [{'reference': f'Slot/{get_slot_id(practitioner_id, date, seg)}'} for seg in schedule_segments],
|
|
441
|
+
'participant': participant
|
|
442
|
+
}
|
|
443
|
+
appointments.append(appointment_obj)
|
|
444
|
+
|
|
445
|
+
if save_dir:
|
|
446
|
+
save_path = os.path.join(save_dir, f'{appointment_id}.fhir.json')
|
|
447
|
+
if sanity_check:
|
|
448
|
+
assert not os.path.exists(save_path), log(f"Same file exists: {save_path}", "error")
|
|
449
|
+
json_save_fast(
|
|
450
|
+
save_path,
|
|
451
|
+
appointment_obj
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return appointments
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@staticmethod
|
|
458
|
+
def get_fhir_appointment(gt_resource_path: Optional[str] = None,
|
|
459
|
+
data: Optional[dict] = None) -> dict:
|
|
460
|
+
"""
|
|
461
|
+
Load a FHIR Appointment resource from a file path if available, or generate it dynamically from the provided data.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
gt_resource_path (Optional[str], optional):
|
|
465
|
+
Path to the ground-truth FHIR Appointment resource file.
|
|
466
|
+
If the file exists, it will be loaded and returned.
|
|
467
|
+
If not, a resource will be generated from the `data` argument.
|
|
468
|
+
data (Optional[dict], optional):
|
|
469
|
+
Dictionary containing the metadata and patient information
|
|
470
|
+
needed to generate the Appointment resource.
|
|
471
|
+
Expected to include 'metadata' and 'information' keys.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
dict: A FHIR Appointment resource in dictionary form.
|
|
475
|
+
"""
|
|
476
|
+
try:
|
|
477
|
+
return json_load(gt_resource_path)
|
|
478
|
+
except:
|
|
479
|
+
metadata, info, department = data.get('metadata'), data.get('information'), data.get('department')
|
|
480
|
+
schedule = info.get('schedule')
|
|
481
|
+
if 'time' in schedule:
|
|
482
|
+
schedule = schedule.get('time')
|
|
483
|
+
|
|
484
|
+
gt_resource = DataConverter.data_to_appointment(
|
|
485
|
+
{
|
|
486
|
+
'metadata': metadata,
|
|
487
|
+
'department': department,
|
|
488
|
+
'patient': {
|
|
489
|
+
info.get('patient'): {
|
|
490
|
+
'department': info.get('department'),
|
|
491
|
+
'attending_physician': info.get('attending_physician'),
|
|
492
|
+
'date': info.get('date'),
|
|
493
|
+
'schedule': schedule
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
)[0]
|
|
498
|
+
return gt_resource
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def __call__(self, output_dir: Optional[str] = None, sanity_check: bool = False) -> list[Information]:
|
|
502
|
+
"""
|
|
503
|
+
Convert synthetic hospital data files into FHIR resources and optionally save them to disk.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
output_dir (Optional[str], optional): Directory to save the converted FHIR resources as `.fhir.json` files.
|
|
507
|
+
If None, the resources will not be saved. Defaults to None.
|
|
508
|
+
sanity_check (bool, optional): If True, performs a sanity check to ensure the uniqueness of the generated FHIR data.
|
|
509
|
+
This only applies when output_dir is specified. Defaults to False.
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
list[Information]: An object containing the converted FHIR resources, including practitioners, schedules, slots, patients, and appointments.
|
|
513
|
+
"""
|
|
514
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
515
|
+
all_resources = list()
|
|
516
|
+
|
|
517
|
+
for data_file in tqdm(self.data_files, desc='Converting to FHIR data..'):
|
|
518
|
+
data = json_load(data_file)
|
|
519
|
+
practitioners = DataConverter.data_to_practitioner(data, output_dir, sanity_check)
|
|
520
|
+
practitionerroles = DataConverter.data_to_practitionerrole(data, output_dir, sanity_check)
|
|
521
|
+
schedules = DataConverter.data_to_schedule(data, output_dir, sanity_check)
|
|
522
|
+
slots = DataConverter.data_to_slot(data, output_dir, sanity_check)
|
|
523
|
+
patients = DataConverter.data_to_patient(data, output_dir, sanity_check)
|
|
524
|
+
appointments = DataConverter.data_to_appointment(data, output_dir, sanity_check)
|
|
525
|
+
|
|
526
|
+
information = Information(
|
|
527
|
+
practitioners=practitioners,
|
|
528
|
+
practitionerrole=practitionerroles,
|
|
529
|
+
schedules=schedules,
|
|
530
|
+
slots=slots,
|
|
531
|
+
patients=patients,
|
|
532
|
+
appointments=appointments
|
|
533
|
+
)
|
|
534
|
+
all_resources.append(information)
|
|
535
|
+
|
|
536
|
+
return all_resources
|