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.
Files changed (62) hide show
  1. h_adminsim/__init__.py +5 -0
  2. h_adminsim/admin_staff.py +280 -0
  3. h_adminsim/assets/configs/data4primary.yaml +47 -0
  4. h_adminsim/assets/configs/data4secondary.yaml +47 -0
  5. h_adminsim/assets/configs/data4tertiary.yaml +47 -0
  6. h_adminsim/assets/country/address.json +141859 -0
  7. h_adminsim/assets/country/country_code.json +244 -0
  8. h_adminsim/assets/departments/department.json +85 -0
  9. h_adminsim/assets/departments/symptom.json +4530 -0
  10. h_adminsim/assets/fhir.schema.json +75253 -0
  11. h_adminsim/assets/names/firstname.txt +1219 -0
  12. h_adminsim/assets/names/lastname.txt +88799 -0
  13. h_adminsim/assets/prompts/cancel_patient_system.txt +38 -0
  14. h_adminsim/assets/prompts/intake_staff_task_user.txt +16 -0
  15. h_adminsim/assets/prompts/intake_supervisor_system.txt +8 -0
  16. h_adminsim/assets/prompts/intake_supervisor_user.txt +31 -0
  17. h_adminsim/assets/prompts/reschedule_patient_system.txt +38 -0
  18. h_adminsim/assets/prompts/schedule_patient_rejected_system.txt +42 -0
  19. h_adminsim/assets/prompts/schedule_patient_system.txt +36 -0
  20. h_adminsim/assets/prompts/schedule_staff_reasoning.txt +57 -0
  21. h_adminsim/assets/prompts/schedule_staff_sc_tool_calling.txt +13 -0
  22. h_adminsim/assets/prompts/schedule_staff_system.txt +10 -0
  23. h_adminsim/assets/prompts/schedule_staff_tool_calling.txt +41 -0
  24. h_adminsim/client/__init__.py +3 -0
  25. h_adminsim/client/google_client.py +209 -0
  26. h_adminsim/client/openai_client.py +199 -0
  27. h_adminsim/client/vllm_client.py +160 -0
  28. h_adminsim/environment/__init__.py +1 -0
  29. h_adminsim/environment/hospital.py +462 -0
  30. h_adminsim/environment/op_scheduling_simulation.py +1126 -0
  31. h_adminsim/pipeline/__init__.py +3 -0
  32. h_adminsim/pipeline/data_generator.py +192 -0
  33. h_adminsim/pipeline/evaluator.py +33 -0
  34. h_adminsim/pipeline/simulation.py +231 -0
  35. h_adminsim/registry/__init__.py +5 -0
  36. h_adminsim/registry/errors.py +89 -0
  37. h_adminsim/registry/models.py +126 -0
  38. h_adminsim/registry/phrases.py +10 -0
  39. h_adminsim/registry/pydantic_models.py +21 -0
  40. h_adminsim/registry/variables.py +9 -0
  41. h_adminsim/supervisor.py +182 -0
  42. h_adminsim/task/agent_task.py +900 -0
  43. h_adminsim/task/fhir_manager.py +222 -0
  44. h_adminsim/task/schedule_assign.py +151 -0
  45. h_adminsim/tools/__init__.py +5 -0
  46. h_adminsim/tools/agent_data_builder.py +124 -0
  47. h_adminsim/tools/data_converter.py +536 -0
  48. h_adminsim/tools/data_synthesizer.py +365 -0
  49. h_adminsim/tools/evaluator.py +258 -0
  50. h_adminsim/tools/sanity_checker.py +216 -0
  51. h_adminsim/tools/scheduling_rule.py +420 -0
  52. h_adminsim/utils/__init__.py +136 -0
  53. h_adminsim/utils/common_utils.py +698 -0
  54. h_adminsim/utils/fhir_utils.py +190 -0
  55. h_adminsim/utils/filesys_utils.py +135 -0
  56. h_adminsim/utils/image_preprocess_utils.py +188 -0
  57. h_adminsim/utils/random_utils.py +358 -0
  58. h_adminsim/version.txt +1 -0
  59. h_adminsim-1.0.0.dist-info/LICENSE +30 -0
  60. h_adminsim-1.0.0.dist-info/METADATA +494 -0
  61. h_adminsim-1.0.0.dist-info/RECORD +62 -0
  62. h_adminsim-1.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,462 @@
1
+ import time
2
+ import random
3
+ from copy import deepcopy
4
+ from decimal import getcontext
5
+ from datetime import timedelta
6
+ from typing import Union, Tuple, Optional
7
+
8
+ from h_adminsim.task.fhir_manager import FHIRManager
9
+ from h_adminsim.utils import log, colorstr
10
+ from h_adminsim.utils.fhir_utils import get_all_doctor_info
11
+ from h_adminsim.utils.common_utils import (
12
+ iso_to_date,
13
+ iso_to_hour,
14
+ get_iso_time,
15
+ sort_schedule,
16
+ get_utc_offset,
17
+ str_to_datetime,
18
+ datetime_to_str,
19
+ compare_iso_time,
20
+ exponential_backoff,
21
+ generate_random_iso_time_between,
22
+ convert_time_list_to_merged_time,
23
+ )
24
+
25
+
26
+
27
+ class HospitalEnvironment:
28
+ def __init__(self,
29
+ agent_test_data: dict,
30
+ fhir_url: Optional[str] = None,
31
+ fhir_max_connection_retries: int = 5,
32
+ start_day_before: float = 3):
33
+
34
+ # FHIR manager
35
+ self.fhir_manager = FHIRManager(fhir_url) if fhir_url else None
36
+
37
+ # Basic
38
+ getcontext().prec = 10
39
+ self._epsilon = 1e-6
40
+ self.max_retries = fhir_max_connection_retries
41
+ self._days_before = start_day_before
42
+ self.HOSPITAL_NAME = agent_test_data.get('metadata').get('hospital_name')
43
+ self._START_DATE = agent_test_data.get('metadata').get('start_date')
44
+ self._END_DATE = agent_test_data.get('metadata').get('end_date')
45
+ self._START_HOUR = agent_test_data.get('metadata').get('time').get('start_hour')
46
+ self._END_HOUR = agent_test_data.get('metadata').get('time').get('end_hour')
47
+ self._TIME_UNIT = agent_test_data.get('metadata').get('time').get('interval_hour')
48
+ self._PATIENT_NUM = len(agent_test_data.get('agent_data'))
49
+ _country_code = agent_test_data.get('metadata').get('country_code', 'KR')
50
+ self.booking_num = {k: 0 for k in agent_test_data.get('doctor')}
51
+
52
+ # Time setting
53
+ self._utc_offset = get_utc_offset(_country_code)
54
+ self.current_time = get_iso_time(
55
+ time_hour=random.uniform(max(0, self._START_HOUR - 6), max(0, self._START_HOUR - self._epsilon)),
56
+ date=datetime_to_str(str_to_datetime(self._START_DATE) - timedelta(days=self._days_before), "%Y-%m-%d"),
57
+ utc_offset=self._utc_offset
58
+ )
59
+ self.avg_gap = self.__calculate_max_time_increment()
60
+
61
+ # Misc.
62
+ self.patient_schedules = list()
63
+ self.waiting_list = list()
64
+ self.first_verbose_flag = True
65
+
66
+ # Cache variables
67
+ self._fhir_practitioner_cache = None
68
+ self._fhir_practitionerrole_cache = None
69
+ self._fhir_schedule_cache = None
70
+ self._fhir_slot_cache = None
71
+
72
+
73
+ def __calculate_max_time_increment(self) -> float:
74
+ """
75
+ Calculate the maximum average time increment (gap) between patient booking within the defined scheduling period.
76
+
77
+ Returns:
78
+ float: The average time gap (in hours) between patients.
79
+ """
80
+ st = str_to_datetime(get_iso_time(self._START_HOUR, self._START_DATE, self._utc_offset))
81
+ tr = str_to_datetime(get_iso_time(self._END_HOUR, self._END_DATE, self._utc_offset))
82
+ total_hours = (tr - st).total_seconds() / 3600
83
+ avg_gap = total_hours / self._PATIENT_NUM
84
+ return avg_gap
85
+
86
+
87
+ def get_general_doctor_info_from_fhir(self, use_cache: bool = True) -> dict:
88
+ """
89
+ Build a doctor information dictionary from FHIR resources for simulation.
90
+
91
+ Args:
92
+ use_cache (bool): If True, reuse cached FHIR resources if available. Defaults to True.
93
+
94
+ Returns:
95
+ dict: doctor_information (dict): Dictionary of doctor data including their existing schedules.
96
+ Each key is a doctor's name, and each value includes a 'schedule' field.
97
+ """
98
+ if self.first_verbose_flag:
99
+ log('Build doctor information from the FHIR resources..')
100
+ self.first_verbose_flag = False
101
+
102
+ hospital_id = self.HOSPITAL_NAME.replace('_', '')
103
+ cache_ready = all([
104
+ self._fhir_practitioner_cache,
105
+ self._fhir_practitionerrole_cache,
106
+ self._fhir_schedule_cache,
107
+ self._fhir_slot_cache,
108
+ ])
109
+
110
+ if not use_cache or not cache_ready:
111
+ self._fhir_practitioner_cache = [
112
+ x for x in self.fhir_manager.read_all('Practitioner', verbose=False)
113
+ if hospital_id in x['resource']['id']
114
+ ]
115
+ self._fhir_practitionerrole_cache = [
116
+ x for x in self.fhir_manager.read_all('PractitionerRole', verbose=False)
117
+ if hospital_id in x['resource']['id']
118
+ ]
119
+ self._fhir_schedule_cache = [
120
+ x for x in self.fhir_manager.read_all('Schedule', verbose=False)
121
+ if hospital_id in x['resource']['id']
122
+ ]
123
+ self._fhir_slot_cache = [
124
+ x for x in self.fhir_manager.read_all('Slot', verbose=False)
125
+ if hospital_id in x['resource']['id']
126
+ ]
127
+
128
+ # Get Appointment resources from the FHIR server
129
+ # NOTE: Sometimes, a FHIR resource is accessed before it gets updated, so the operation is performed with a retry flag
130
+ retry_count = 0
131
+ while 1:
132
+ try:
133
+ self.fhir_appointment = [
134
+ x for x in self.fhir_manager.read_all('Appointment', verbose=False)
135
+ if hospital_id in x['resource']['id']
136
+ ]
137
+ valid_len = len(list(filter(lambda x: x['status'] != 'cancelled', self.patient_schedules)))
138
+ assert len(self.fhir_appointment) == valid_len, f"Mismatch in appointment count: expected {valid_len}, got {len(self.fhir_appointment)}"
139
+ break
140
+ except AssertionError as e:
141
+ if retry_count >= self.max_retries:
142
+ log(f"\nMax retries reached. Last error: {e}", level='error')
143
+ raise e
144
+ wait_time = exponential_backoff(retry_count)
145
+ log(f"[{retry_count + 1}/{self.max_retries}] {type(e).__name__}: {e}. Retrying in {wait_time:.1f} seconds...", level='warning')
146
+ time.sleep(wait_time)
147
+ retry_count += 1
148
+ continue
149
+
150
+ # Convert resources regardless of whether they came from cache or fresh read
151
+ doctor_information = get_all_doctor_info(
152
+ self._fhir_practitioner_cache,
153
+ self._fhir_practitionerrole_cache,
154
+ self._fhir_schedule_cache,
155
+ self._fhir_slot_cache,
156
+ self.fhir_appointment,
157
+ **{'start': self._START_HOUR, 'end': self._END_HOUR, 'interval': self._TIME_UNIT}
158
+ )
159
+ return doctor_information
160
+
161
+
162
+ def get_doctor_schedule(self,
163
+ doctor_information: Optional[dict] = None,
164
+ *,
165
+ department: Optional[str] = None,
166
+ fhir_integration: bool = False,
167
+ express_detail: bool = False) -> dict:
168
+ """
169
+ Build doctor schedules for a given department.
170
+
171
+ Args:
172
+ doctor_information (Optional[dict], optional): Simulation doctor data (used when fhir_integration is False). Defaults to None.
173
+ department (Optional[str], optional): Target department name. Defaults to None.
174
+ fhir_integration (bool, optional): If True, build schedules from FHIR resources. Defaults to False.
175
+ express_detail (bool, optional): If True, express schedules with explicit start/end fields. Defualtsto False.
176
+
177
+ Returns:
178
+ dict: Filtered doctor scheduling information.
179
+ """
180
+ def __build_single_doctor_schedule(practitioner_role: dict) -> Tuple[str, dict]:
181
+ """
182
+ Build scheduling information for a single doctor.
183
+
184
+ Args:
185
+ practitioner_role (dict): A FHIR PractitionerRole resource for a doctor, already filtered by department.
186
+
187
+ Returns:
188
+ Tuple[str, dict]: Doctor name and his (or her) information dictionary containing the constructed scheduling information.
189
+ """
190
+ schedule, appointments = dict(), list()
191
+ practitioner_id = practitioner_role['practitioner']['reference']
192
+ practitioner = self.fhir_manager.read('Practitioner', practitioner_id.split('/')[-1], verbose=False).json()
193
+ practitioner_schedule_id = self.fhir_manager.read_all('Schedule', params={'actor': practitioner_id}, verbose=False)[0]['resource']['id']
194
+ fixed_slots = [slot['resource'] for slot in self.fhir_manager.read_all('Slot', params={'schedule': f'Schedule/{practitioner_schedule_id}'}, verbose=False)]
195
+
196
+ # Append fixed schedules of a doctor
197
+ for slot in fixed_slots:
198
+ date = iso_to_date(slot['start'])
199
+ schedule.setdefault(date, [])
200
+ if slot['status'] != 'free':
201
+ schedule[date].append([iso_to_hour(slot['start']), iso_to_hour(slot['end'])])
202
+
203
+ # Get all appointments related to this slot
204
+ appointment_resources = self.fhir_manager.read_all('Appointment', params={'slot': f'Slot/{slot["id"]}'}, verbose=False)
205
+ if len(appointment_resources) > 0:
206
+ appointments.append(appointment_resources[0]['resource'])
207
+
208
+ # Merge fixed schedule times
209
+ for date, time_list in schedule.items():
210
+ schedule[date] = convert_time_list_to_merged_time(
211
+ time_list=sort_schedule(time_list),
212
+ start=self._START_HOUR,
213
+ end=self._END_HOUR,
214
+ interval=self._TIME_UNIT
215
+ )
216
+
217
+ # Append patient appointments of a doctor
218
+ for appointment in appointments:
219
+ date = iso_to_date(appointment['start'])
220
+ schedule.setdefault(date, [])
221
+ schedule[date].append([iso_to_hour(appointment['start']), iso_to_hour(appointment['end'])])
222
+
223
+ # Collect doctor's information
224
+ name = f"{practitioner['name'][0]['prefix'][0]} {practitioner['name'][0]['given'][0]} {practitioner['name'][0]['family']}"
225
+ department = practitioner_role['specialty'][0]['text']
226
+ specialty = {
227
+ 'name': practitioner_role['specialty'][0]['coding'][0]['display'],
228
+ 'code': practitioner_role['specialty'][0]['coding'][0]['code']
229
+ }
230
+ capacity_attributes = {attr['text']: attr['coding'][0]['display'] for attr in practitioner_role['characteristic']}
231
+ workload = f"{round(self.booking_num[name] / int(capacity_attributes['capacity']) * 100, 2)}%"
232
+ outpatient_duration = 1 / int(capacity_attributes['capacity_per_hour'])
233
+ information = {
234
+ 'department': department,
235
+ 'specialty': specialty,
236
+ 'schedule': sort_schedule(schedule),
237
+ 'workload': workload,
238
+ 'outpatient_duration': outpatient_duration
239
+ }
240
+ return name, information
241
+
242
+ filtered_doctor_information = {'doctor': {}}
243
+
244
+ # Get filtered doctor information directly from FHIR
245
+ if fhir_integration:
246
+ if self.first_verbose_flag:
247
+ log('Build doctor information from the FHIR resources..')
248
+ self.first_verbose_flag = False
249
+
250
+ # Get doctors belonging to the department
251
+ params={"specialty:text": department}
252
+ practitioner_roles = [resource['resource'] for resource in self.fhir_manager.read_all('PractitionerRole', params=params, verbose=False)]
253
+
254
+ for practitioner_role in practitioner_roles:
255
+ doctor_name, doctor_schedule = __build_single_doctor_schedule(practitioner_role)
256
+ filtered_doctor_information['doctor'][doctor_name] = doctor_schedule
257
+
258
+ # Get filtered doctor information from the simulation data
259
+ else:
260
+ for k, v in doctor_information.items():
261
+ if v['department'] == department:
262
+ tmp_schedule = deepcopy(v)
263
+ del tmp_schedule['capacity_per_hour'], tmp_schedule['capacity'], tmp_schedule['gender'], tmp_schedule['telecom'], tmp_schedule['birthDate']
264
+ tmp_schedule['workload'] = f"{round(self.booking_num[k] / v['capacity'] * 100, 2)}%"
265
+ tmp_schedule['outpatient_duration'] = 1 / v['capacity_per_hour']
266
+ filtered_doctor_information['doctor'][k] = tmp_schedule
267
+
268
+
269
+ # Whether express more details in the built schedules
270
+ if express_detail:
271
+ for _, info in filtered_doctor_information['doctor'].items():
272
+ info['schedule'] = {
273
+ date: [{'start': s[0], 'end': s[1]} for s in schedule]
274
+ for date, schedule in info['schedule'].items()
275
+ }
276
+
277
+ return filtered_doctor_information
278
+
279
+
280
+ def resume(self, agent_results: dict):
281
+ """
282
+ Resume the hospital environment from previously saved agent results.
283
+
284
+ Args:
285
+ agent_test_data (dict): Input data containing static information
286
+ about doctors, patients, and other hospital resources.
287
+ agent_results (dict): Previously saved results from the agent's simulation.
288
+ """
289
+ if 'schedule' in agent_results:
290
+ statuses = [x for y in agent_results['schedule']['status'] for x in (y if isinstance(y, list) or isinstance(y, tuple) else [y])]
291
+ preds = [x for y in agent_results['schedule']['pred'] for x in (y if isinstance(y, list) or isinstance(y, tuple) else [y])]
292
+ for status, pred in zip(statuses, preds):
293
+ if isinstance(status, bool) and status:
294
+ if 'patient' in pred:
295
+ self.patient_schedules.append(pred)
296
+ self.current_time = pred['last_updated_time']
297
+
298
+ if 'status' in pred and not pred['status'] == 'cancelled':
299
+ self.booking_num[pred['attending_physician']] += 1
300
+
301
+ self.waiting_list = sorted([(i, s) for i, s in enumerate(self.patient_schedules) if s['waiting_order'] >= 0], key=lambda x: x[1]['waiting_order'])
302
+
303
+ log(f"Resumed hospital time set to {self.current_time}.")
304
+ log(f"Resumed hospital environment with {len(self.patient_schedules)} patient schedules.")
305
+ log(f"Resumed waiting list with {len(self.waiting_list)} patient schedules.")
306
+ log(f"Current booking numbers per doctor: {self.booking_num}")
307
+
308
+ self.update_current_time()
309
+ self.update_patient_status()
310
+
311
+
312
+ def schedule_cancel_event(self, idx: int, verbose: bool = False):
313
+ """
314
+ Cancel a scheduled event for the patient.
315
+ This method updates the status of a scheduled event at the given index
316
+ to 'cancelled'. Index values greater than or equal to 0 are allowed.
317
+
318
+ Args:
319
+ idx (int): The index of the schedule to cancel. Must be 0 or a positive integer.
320
+ verbose (bool, optional): Whether logging the each result or not. Defaults to False.
321
+ """
322
+ if idx >= 0:
323
+ for turn, (i, _) in enumerate(self.waiting_list):
324
+ if i == idx:
325
+ self.pop_waiting_list(turn, verbose)
326
+ break
327
+ self.patient_schedules[idx]['status'] = 'cancelled'
328
+ self.patient_schedules[idx]['last_updated_time'] = self.current_time
329
+ self.booking_num[self.patient_schedules[idx]['attending_physician']] -= 1
330
+ if verbose:
331
+ log(f'{colorstr("[CANCELLED]")}: {self.patient_schedules[idx]} schedule is cancelled.')
332
+
333
+
334
+ def add_waiting_list(self, idx: int, verbose: bool = False):
335
+ """
336
+ Add a schedule to the waiting list for an earlier appointment if needed.
337
+
338
+ Args:
339
+ idx (int): The index of the schedule to add to the waiting list. Must be 0 or a positive integer.
340
+ verbose (bool, optional): Whether logging the each result or not. Defaults to False.
341
+ """
342
+ if idx >= 0:
343
+ requested_schedule = self.patient_schedules[idx]
344
+ if all(requested_schedule != s[1] for s in self.waiting_list):
345
+ requested_schedule['waiting_order'] = len(self.waiting_list)
346
+ self.waiting_list.append((idx, requested_schedule))
347
+ if verbose:
348
+ log(f'{colorstr("[WAITING LIST ADDED]")}: {requested_schedule} schedule is appended to the waiting list.')
349
+
350
+
351
+ def pop_waiting_list(self, idx: Union[list[int], int], verbose: bool = False):
352
+ """
353
+ Pop a schedule to the waiting list.
354
+
355
+ Args:
356
+ idx (Union[list[int], int]): The index list (or index) of the schedule to pop from the waiting list.
357
+ verbose (bool, optional): Whether logging the each result or not. Defaults to False.
358
+ """
359
+ if isinstance(idx, int) and idx >= 0:
360
+ idx = [idx]
361
+
362
+ if len(idx):
363
+ idx = sorted(idx, reverse=True)
364
+ for _id in idx:
365
+ schedule = self.waiting_list.pop(_id)
366
+ schedule[1]['waiting_order'] = -1
367
+ if verbose:
368
+ log(f'{colorstr("[WAITING LIST POPPED]")}: {schedule[1]} schedule is popped from the waiting list.')
369
+
370
+ for i, (_, schedule) in enumerate(self.waiting_list):
371
+ schedule['waiting_order'] = i
372
+
373
+
374
+ def update_fhir(self, fhir_resources: dict):
375
+ """
376
+ Update resources on the FHIR server.
377
+
378
+ fhir_resources (dict): Dictionary where each key is a FHIR resource type (e.g., 'Appointment', 'Slot'),
379
+ and each value is the corresponding FHIR resource data to be updated.
380
+ """
381
+ # Update new FHIR resources
382
+ for resource_type, resource in fhir_resources.items():
383
+ if resource and resource_type.lower() in ['patient', 'appointment']:
384
+ self.fhir_manager.create(resource_type, resource, verbose=False)
385
+
386
+
387
+ def delete_fhir(self, fhir_resources: dict):
388
+ """
389
+ Delete resources on the FHIR server.
390
+
391
+ Args:
392
+ fhir_resources (dict): Dictionary where each key is a FHIR resource type (e.g., 'Appointment', 'Slot'),
393
+ and each value is the corresponding FHIR resource data to be updated.
394
+ """
395
+ # Delete the existing FHIR resources
396
+ for resource_type, resource in fhir_resources.items():
397
+ if resource and resource_type.lower() in ['patient', 'appointment']:
398
+ self.fhir_manager.delete(resource_type, resource['id'], verbose=False)
399
+
400
+
401
+ def update_current_time(self):
402
+ """
403
+ Update the current hospital time.
404
+ """
405
+ min_iso_time = self.current_time
406
+ max_iso_time = (str_to_datetime(self.current_time) + timedelta(hours=self.avg_gap)).isoformat(timespec='seconds')
407
+ self.current_time = generate_random_iso_time_between(min_iso_time, max_iso_time)
408
+
409
+
410
+ def update_patient_status(self):
411
+ """
412
+ Update the status of each patient based on the current hospital time.
413
+ """
414
+ for schedule in self.patient_schedules:
415
+ if schedule.get('waiting_order', -1) < 0:
416
+ schedule['waiting_order'] = -1
417
+
418
+ if schedule.get('status') == 'cancelled':
419
+ continue
420
+
421
+ tmp_st_iso_time = get_iso_time(schedule['schedule'][0], date=schedule['date'], utc_offset=self._utc_offset)
422
+ tmp_tr_iso_time = get_iso_time(schedule['schedule'][-1], date=schedule['date'], utc_offset=self._utc_offset)
423
+
424
+ if compare_iso_time(self.current_time, tmp_tr_iso_time):
425
+ status = 'completed'
426
+ elif compare_iso_time(tmp_st_iso_time, self.current_time):
427
+ status = 'scheduled'
428
+ else:
429
+ status = 'in_progress'
430
+
431
+ schedule['status'] = status
432
+
433
+
434
+ def reset_variable(self):
435
+ """
436
+ Reset variables.
437
+ """
438
+ pass
439
+
440
+
441
+ def update_env(self,
442
+ status: bool,
443
+ patient_schedule: Union[dict, str],
444
+ fhir_resources: dict):
445
+ """
446
+ Update the hospital environment after successfully assigning an appointment.
447
+
448
+ Args:
449
+ status (bool): Whether the appointment was successfully assigned.
450
+ patient_schedule (Union[dict, str]): The patient's new schedule to add. Should contain a 'schedule' key with start and end time.
451
+ fhir_resources (dict): Dictionary where each key is a FHIR resource type (e.g., 'Appointment', 'Slot'),
452
+ and each value is the corresponding FHIR resource data to be updated.
453
+ """
454
+ if status:
455
+ self.update_fhir(fhir_resources)
456
+ self.update_current_time()
457
+ self.patient_schedules.append(patient_schedule)
458
+ self.update_patient_status()
459
+ self.booking_num[patient_schedule['attending_physician']] += 1
460
+
461
+ self.reset_variable()
462
+