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,3 @@
1
+ from .data_generator import DataGenerator
2
+ from .simulation import Simulator
3
+ from .evaluator import Evaluator
@@ -0,0 +1,192 @@
1
+ import os
2
+ import random
3
+ import numpy as np
4
+ from sconf import Config
5
+ from pathlib import Path
6
+ from importlib import resources
7
+ from typing import Optional, Union
8
+
9
+ from h_adminsim.task.fhir_manager import FHIRManager
10
+ from h_adminsim.tools import DataSynthesizer, DataConverter, AgentDataBuilder
11
+ from h_adminsim.utils import Information, colorstr, log
12
+ from h_adminsim.utils.random_utils import random_uuid
13
+ from h_adminsim.utils.filesys_utils import get_files, json_load
14
+
15
+
16
+
17
+ class DataGenerator:
18
+ def __init__(self,
19
+ care_level: str = 'primary',
20
+ config: Optional[Union[str, Config]] = None):
21
+
22
+ # Initialize
23
+ self.config = self.load_config(care_level, config)
24
+ self.__env_setup(self.config)
25
+ self.fhir_url = self.config.get('fhir_url', None)
26
+ self.data_synthesizer = DataSynthesizer(self.config)
27
+ self.save_dir = self.data_synthesizer._save_dir
28
+ log(f'Data saving directory: {colorstr(self.save_dir)}')
29
+
30
+
31
+ def load_config(self, care_level: str, config: Optional[Union[str, Config]]) -> Config:
32
+ """
33
+ Load a configuration object.
34
+
35
+ If `config` is None, a default configuration is loaded based on the given
36
+ `care_level`. If `config` is a string, it is treated as a file path and
37
+ loaded as a Config object. If a Config instance is provided, it is returned
38
+ as-is.
39
+
40
+ Args:
41
+ care_level (str): Care level used to select the default config.
42
+ config (Optional[Union[str, Config]]): A file path or Config instance.
43
+
44
+ Raises:
45
+ TypeError: If `config` is not None, str, or Config.
46
+
47
+ Returns:
48
+ Config: A fully initialized Config object.
49
+ """
50
+ # Case 1: config is None -> load built-in config based on care_level
51
+ if config is None:
52
+ log(f"No config provided; using default {care_level} config.", "warning")
53
+ assert care_level in ['primary', 'secondary', 'tertiary'], \
54
+ log(f"Invalid care_level: '{care_level}'. Expected one of: primary, secondary, tertiary.", "error")
55
+ default_path = str(resources.files("h_adminsim.assets.configs").joinpath(f"data4{care_level}.yaml"))
56
+ return Config(default_path)
57
+
58
+ # Case 2: config is a string path
59
+ if isinstance(config, str):
60
+ config_inst = Config(config)
61
+ return config_inst
62
+
63
+ # Case 3: config is already a Config object
64
+ if isinstance(config, Config):
65
+ return config
66
+
67
+ # Otherwise error
68
+ raise TypeError(
69
+ log(f"Invalid config: expected None, str, or Config, got {type(config).__name__}", "error")
70
+ )
71
+
72
+
73
+ def __env_setup(self, config: Config):
74
+ """
75
+ Initialize environment-level random seeds using the given configuration.
76
+
77
+ Args:
78
+ config (Config): Configuration containing the seed value.
79
+ """
80
+ random.seed(config.seed)
81
+ np.random.seed(config.seed)
82
+
83
+
84
+ def build(self,
85
+ sanity_check: bool = True,
86
+ convert_to_fhir: bool = False,
87
+ build_agent_data: bool = True) -> Information:
88
+ """
89
+ Build the complete information bundle for the administrative simulation pipeline.
90
+
91
+ Args:
92
+ sanity_check (bool, optional): Whether to perform validation checks during synthetic data generation. Defaults to True.
93
+ convert_to_fhir (bool, optional): If True, converts synthesized data into FHIR-compliant resources and stores them
94
+ in the configured output directory. Defaults to False.
95
+ build_agent_data (bool, optional): If True, generates additional derived data required for agent-based
96
+ simulations (e.g., patient profiles, department assignments, task inputs). Defaults to True.
97
+
98
+ Raises:
99
+ Exception: Propagates any exception encountered during:
100
+ - synthetic data synthesis
101
+ - FHIR conversion
102
+ - agent data generation
103
+
104
+ Returns:
105
+ Information:
106
+ A structured container holding:
107
+ - `data`: the synthesized dataset
108
+ - `fhir_data`: list of FHIR resources (or None if disabled)
109
+ - `agent_data`: processed agent input data (or None if disabled)
110
+ """
111
+ # Data generator
112
+ try:
113
+ data, hospital_obj = self.data_synthesizer.synthesize(sanity_check=sanity_check)
114
+ log(f"Data synthesis completed successfully", color=True)
115
+ except Exception as e:
116
+ log("Data synthesis failed.", level="error")
117
+ raise e
118
+
119
+ # FHIR conversion
120
+ all_resource_list = None
121
+ if convert_to_fhir:
122
+ converter = DataConverter(self.config)
123
+ try:
124
+ all_resource_list = converter(self.save_dir / 'fhir_data', sanity_check)
125
+ log(f"Data FHIR conversion completed successfully", color=True)
126
+ except Exception as e:
127
+ log("Data FHIR conversion failed.", level='error')
128
+ raise e
129
+
130
+ # Build data for agent simulation
131
+ agent_data_list = None
132
+ if build_agent_data:
133
+ builder = AgentDataBuilder(self.config)
134
+ try:
135
+ agent_data_list = builder(self.save_dir / 'agent_data')
136
+ log(f"Agent data generation completed successfully", color=True)
137
+ except Exception as e:
138
+ log("Agent data generation failed.", level='error')
139
+ raise e
140
+
141
+ output = Information(
142
+ data=data,
143
+ fhir_data=all_resource_list,
144
+ agent_data=agent_data_list
145
+ )
146
+
147
+ return output
148
+
149
+
150
+ def upload_to_fhir(self,
151
+ fhir_data_dir: str,
152
+ fhir_url: Optional[str] = None):
153
+ """
154
+ Upload synthesized FHIR resources to the specified FHIR server.
155
+
156
+ Args:
157
+ fhir_data_dir (str): Directory containing FHIR resource JSON files (e.g., practitioner, practitionerrole, schedule, slot).
158
+ fhir_url (Optional[str], optional): Base URL of the FHIR server. If not provided, the instance's default FHIR URL is used.
159
+ """
160
+ # Initialize FHIR URL and manager
161
+ if not fhir_url:
162
+ fhir_url = self.fhir_url
163
+ assert fhir_url != None, log('')
164
+
165
+ if not fhir_url.endswith('fhir'):
166
+ fhir_url = os.path.join(fhir_url, 'fhir')
167
+
168
+ fhir_manager = FHIRManager(fhir_url)
169
+
170
+ # FHIR resources
171
+ fhir_data_dir = Path(fhir_data_dir)
172
+ fhir_resources_dirs = [fhir_data_dir / resource for resource in ['practitioner', 'practitionerrole', 'schedule', 'slot']]
173
+
174
+ # Upload resources to FHIR
175
+ for path in fhir_resources_dirs:
176
+ files = get_files(path, ext='json')
177
+ error_files = list()
178
+
179
+ for file in files:
180
+ resource_data = json_load(file)
181
+ resource_type = resource_data.get('resourceType')
182
+ if 'id' not in resource_data:
183
+ resource_data['id'] = random_uuid(False)
184
+
185
+ response = fhir_manager.create(resource_type, resource_data)
186
+ if 200 <= response.status_code < 300:
187
+ log(f"Created {resource_type} with ID {response.json().get('id')}")
188
+ else:
189
+ error_files.append(file)
190
+
191
+ if len(error_files):
192
+ log(f'Error files during creating data: {error_files}', 'warning')
@@ -0,0 +1,33 @@
1
+ from h_adminsim.tools import Evaluator as BaseEvaluator
2
+ from h_adminsim.utils import log
3
+
4
+
5
+
6
+ class Evaluator(BaseEvaluator):
7
+ def __init__(self, path: str):
8
+ super().__init__(path)
9
+
10
+
11
+ def evaluate(self, tasks: list[str]):
12
+ """
13
+ Evaluate the performance of an agent.
14
+
15
+ Args:
16
+ tasks (list[str]): A task list to evaluate.
17
+ """
18
+ if 'task' in tasks:
19
+ self.task_evaluation()
20
+ log('')
21
+
22
+ # if 'feedback' in tasks:
23
+ # self.supervisor_evaluation()
24
+ # log('')
25
+
26
+ if 'rounds' in tasks:
27
+ self.calculate_avg_rounds()
28
+ log('')
29
+
30
+ if 'department' in tasks:
31
+ self.department_evaluation()
32
+ log('')
33
+
@@ -0,0 +1,231 @@
1
+ import os
2
+ import random
3
+ import numpy as np
4
+ from typing import Optional
5
+
6
+ from h_adminsim.task.agent_task import *
7
+ from h_adminsim.task.fhir_manager import FHIRManager
8
+ from h_adminsim.environment.hospital import HospitalEnvironment
9
+ from h_adminsim.utils.filesys_utils import json_load, json_save_fast, get_files
10
+
11
+
12
+
13
+ class Simulator:
14
+ def __init__(self,
15
+ intake_task: Optional[OutpatientFirstIntake] = None,
16
+ scheduling_task: Optional[OutpatientFirstScheduling] = None,
17
+ simulation_start_day_before: float = 3,
18
+ fhir_integration: bool = False,
19
+ fhir_url: Optional[str] = None,
20
+ fhir_max_connection_retries: int = 5,
21
+ random_seed: int = 9999):
22
+
23
+ # Initialize
24
+ self.__env_setup(random_seed)
25
+ self.simulation_start_day_before = simulation_start_day_before
26
+ self.fhir_integration = fhir_integration
27
+ self.fhir_url = fhir_url if self.fhir_integration else None
28
+ self.fhir_max_connection_retries = fhir_max_connection_retries
29
+ self.task_queue, self.task_list = self._init_task(intake_task, scheduling_task)
30
+
31
+
32
+ def __env_setup(self, random_seed: int):
33
+ """
34
+ Initialize environment-level random seeds.
35
+
36
+ Args:
37
+ random_seed (int): Random seed.
38
+ """
39
+ random.seed(random_seed)
40
+ np.random.seed(random_seed)
41
+
42
+
43
+ def _init_task(self,
44
+ intake_task: Optional[OutpatientFirstIntake] = None,
45
+ scheduling_task: Optional[OutpatientFirstScheduling] = None) -> Tuple[list[FirstVisitOutpatientTask], list[str]]:
46
+ """
47
+ Initialize the task queue for first-visit outpatient workflow.
48
+
49
+ Args:
50
+ intake_task (Optional[OutpatientFirstIntake], optional): Intake task instance to include in the queue. Defaults to None.
51
+ scheduling_task (Optional[OutpatientFirstScheduling], optional): Scheduling task instance to include in the queue. Defaults to None.
52
+
53
+ Returns:
54
+ Tuple[list[FirstVisitOutpatientTask], list[str]]:
55
+ A tuple containing:
56
+ - the ordered list of task objects
57
+ - the list of task names in execution order
58
+ """
59
+ task_queue, task_list = list(), list()
60
+ assert intake_task != None or scheduling_task != None, \
61
+ log("At least one of 'intake_task' or 'scheduling_task' must be provided (both cannot be None).", level='error')
62
+
63
+ if intake_task != None:
64
+ task_queue.append(intake_task)
65
+ task_list.append(intake_task.name)
66
+ if scheduling_task != None:
67
+ task_queue.append(scheduling_task)
68
+ task_list.append(scheduling_task.name)
69
+
70
+ return task_queue, task_list
71
+
72
+
73
+ def clean_fhir(self):
74
+ """
75
+ Clear the FHIR data.
76
+ """
77
+ if self.fhir_integration:
78
+ fhir_manager = FHIRManager(self.fhir_url)
79
+ appointment_entries = fhir_manager.read_all('Appointment')
80
+ patient_entries = fhir_manager.read_all('Patient')
81
+ fhir_manager.delete_all(appointment_entries, verbose=False)
82
+ fhir_manager.delete_all(patient_entries, verbose=False)
83
+
84
+
85
+ @staticmethod
86
+ def shuffle_data(data: dict):
87
+ """
88
+ Shuffle the agent test data by the schedule start time.
89
+
90
+ Args:
91
+ data (dict): An agent test data to simulate a hospital environmnet.
92
+ """
93
+ random.shuffle(data['agent_data']) # In-place logic
94
+
95
+
96
+ @staticmethod
97
+ def resume_results(agent_simulation_data: dict, results_path: str, d_results_path: str) -> Tuple[dict, dict, dict, set]:
98
+ """
99
+ Resume a previously saved simulation by aligning agent results.
100
+
101
+ Args:
102
+ agent_simulation_data (dict): Static agent test data for a simulation.
103
+ results_path (str): Path to the JSON file containing the saved simulation results.
104
+ d_results_path (str): Path to the JSON file containing the saved dialog results.
105
+
106
+ Returns:
107
+ Tuple[dict, int]:
108
+ - dict: Schedule updated static agent test data.
109
+ - dict: Previously saved agent results.
110
+ - dict: Previously saved dialog results.
111
+ - set: A dictionary containing patients that have already been processed for each task.
112
+ """
113
+ # Load previous results
114
+ agent_results = json_load(results_path)
115
+ dialog_results = json_load(d_results_path) if os.path.exists(d_results_path) else dict()
116
+
117
+ # Get patients that have already been processed for each task
118
+ done_patients = dict()
119
+ for task_name, result in agent_results.items():
120
+ if task_name == 'intake':
121
+ done_patients[task_name] = {done['patient']['name'] for done in result['gt']}
122
+ elif task_name == 'schedule':
123
+ done_patients[task_name] = set()
124
+ for done in result['gt']:
125
+ try:
126
+ done_patients[task_name].add(done['patient'])
127
+ except (KeyError, TypeError):
128
+ continue
129
+
130
+ # Updated doctor schedules based on the resumed results
131
+ if 'schedule' in agent_results:
132
+ fixed_schedule = agent_simulation_data['doctor']
133
+ statuses = [x for y in agent_results['schedule']['status'] for x in (y if isinstance(y, list) or isinstance(y, tuple) else [y])]
134
+ preds = [x for y in agent_results['schedule']['pred'] for x in (y if isinstance(y, list) or isinstance(y, tuple) else [y])]
135
+ for status, pred in zip(statuses, preds):
136
+ if status and 'status' in pred and pred['status'] != 'cancelled':
137
+ fixed_schedule[pred['attending_physician']]['schedule'][pred['date']].append(pred['schedule'])
138
+ fixed_schedule[pred['attending_physician']]['schedule'][pred['date']].sort()
139
+
140
+ return agent_simulation_data, agent_results, dialog_results, done_patients
141
+
142
+
143
+ def run(self,
144
+ simulation_data_path: str,
145
+ output_dir: str,
146
+ resume: bool = False,
147
+ verbose: bool = False):
148
+ """
149
+ Run the agent-based hospital administrative simulation.
150
+
151
+ Args:
152
+ simulation_data_path (str): Path to a JSON file or directory containing agent simulation input data.
153
+ output_dir (str): Directory to store simulation results.
154
+ resume (bool, optional): Whether to resume a previous simulation if result files exist. Defaults to False.
155
+ verbose (bool, optional): Whether to print detailed logs during task execution. Defaults to False.
156
+
157
+ Raises:
158
+ Exception: Propagates any errors encountered during simulation or result saving.
159
+ """
160
+ # Clear FHIR data
161
+ if not resume:
162
+ self.clean_fhir()
163
+
164
+ # Load agent simulation data
165
+ is_file = os.path.isfile(simulation_data_path)
166
+ agent_simulation_data_files = [simulation_data_path] if is_file else get_files(simulation_data_path, ext='json')
167
+ all_agent_simulation_data = [json_load(path) for path in agent_simulation_data_files] # one agent simulation data per hospital
168
+
169
+ try:
170
+ os.makedirs(output_dir, exist_ok=True)
171
+
172
+ # Data per hospital
173
+ for i, agent_simulation_data in enumerate(all_agent_simulation_data):
174
+ agent_results, done_patients, dialog_results = dict(), dict(), dict()
175
+ Simulator.shuffle_data(agent_simulation_data)
176
+ environment = HospitalEnvironment(
177
+ agent_simulation_data,
178
+ self.fhir_url,
179
+ self.fhir_max_connection_retries,
180
+ self.simulation_start_day_before
181
+ )
182
+ basename = os.path.splitext(os.path.basename(agent_simulation_data_files[i]))[0]
183
+ save_path = os.path.join(output_dir, f'{basename}_result.json')
184
+ d_save_path = os.path.join(output_dir, f'{basename}_dialog.json')
185
+ log(f'{basename} simulation started..', color=True)
186
+
187
+ # Resume the results and the virtual hospital environment
188
+ if resume and os.path.exists(save_path):
189
+ agent_simulation_data, agent_results, dialog_results, done_patients = Simulator.resume_results(agent_simulation_data, save_path, d_save_path)
190
+ environment.resume(agent_results)
191
+
192
+ # Data per patient
193
+ for j, (gt, test_data) in enumerate(agent_simulation_data['agent_data']):
194
+ for task in self.task_queue:
195
+ if task.name in done_patients and gt['patient'] in done_patients[task.name]:
196
+ continue
197
+
198
+ result = task((gt, test_data), agent_simulation_data, agent_results, environment, verbose)
199
+ dialogs = result.pop('dialog')
200
+
201
+ # Append a single result
202
+ agent_results.setdefault(task.name, {'gt': [], 'pred': [], 'status': [], 'status_code': [], 'trial': [], 'dialog': []})
203
+ for k in result:
204
+ agent_results[task.name][k] += result[k]
205
+
206
+ if task.name == 'intake':
207
+ dialog_results[gt['patient']] = dialogs[0]
208
+ else:
209
+ agent_results[task.name]['dialog'] += dialogs
210
+
211
+ # Logging the results
212
+ for task_name, result in agent_results.items():
213
+ correctness = [x for y in result['status'] for x in (y if isinstance(y, list) or isinstance(y, tuple) else [y])]
214
+ status_code = [x for y in result['status_code'] for x in (y if isinstance(y, list) or isinstance(y, tuple) else [y])]
215
+ accuracy = sum(correctness) / len(correctness)
216
+ log(f'{basename} - {task_name} task results..', color=True)
217
+ log(f' - accuracy: {accuracy:.3f}, length: {len(correctness)}, status_code: {status_code}')
218
+
219
+ json_save_fast(save_path, agent_results)
220
+ if 'intake' in self.task_list:
221
+ json_save_fast(d_save_path, dialog_results)
222
+
223
+ log(f"Agent completed the tasks successfully", color=True)
224
+
225
+ except Exception as e:
226
+ if len(agent_results):
227
+ json_save_fast(save_path, agent_results)
228
+ if 'intake' in self.task_list:
229
+ json_save_fast(d_save_path, dialog_results)
230
+ log("Error occured while execute the tasks.", level='error')
231
+ raise e
@@ -0,0 +1,5 @@
1
+ from .pydantic_models import *
2
+ from .variables import *
3
+ from .models import *
4
+ from .errors import STATUS_CODES
5
+ from .phrases import *
@@ -0,0 +1,89 @@
1
+ STATUS_CODES = {
2
+ 'format': 'incorrect format',
3
+ 'department': 'incorrect department',
4
+ 'patient': 'incorrect patient information',
5
+ 'department & patient': 'incorrect department and patient information',
6
+ 'simulation': 'incomplete simulation',
7
+ 'schedule': 'invalid schedule',
8
+ 'duration': 'wrong duration',
9
+ 'conflict': {
10
+ 'physician': 'physician conflict',
11
+ 'time': 'time conflict'
12
+ },
13
+ 'preference': {
14
+ 'physician': 'mismatched physician',
15
+ 'asap': 'not earliest schedule',
16
+ 'date': 'not valid date',
17
+ },
18
+ 'cancel': {
19
+ 'identify': 'cancel: fail to identify requested schedule',
20
+ 'type': 'cancel: unexpected tool calling result'
21
+ },
22
+ 'reschedule': {
23
+ 'identify': 'reschedule: fail to identify requested schedule',
24
+ 'schedule': 'reschedule: {status_code}',
25
+ 'type': 'reschedule: unexpected tool calling result'
26
+ },
27
+ 'preceding': 'preceding task failed',
28
+ 'unexpected': "unexpected error: {e}",
29
+ 'correct': 'pass',
30
+ }
31
+
32
+ # SCHEDULING_ERROR_CAUSE = {
33
+ # 'incorrect format': [
34
+ # '* There is an issue with the output format. Please perform scheduling in the correct format.',
35
+ # ],
36
+ # 'physician conflict': [
37
+ # '* More than one doctor has been assigned. A schedule must be made with exactly one doctor.',
38
+ # ],
39
+ # 'time conflict': [
40
+ # "* The scheduling result overlaps with the doctor's existing schedule.",
41
+ # ],
42
+ # 'mismatched physician': [
43
+ # '* A different doctor was assigned even though the patient requested a specific doctor.',
44
+ # ],
45
+ # 'not earliest schedule': [
46
+ # '* The patient wants the earliest possible appointment in the department, but the assigned time is not the earliest available based on the current time.',
47
+ # '* When scheduling, it is possible to assign an earlier date or time.',
48
+ # "* The previous patient's schedule may have been cancelled. Therefore, it is necessary to carefully compare the hospital's start time with the doctor's schedule to identify available time slots.",
49
+ # ],
50
+ # 'not valid date': [
51
+ # '* The patient is available after a specific date and would like to make an appointment. Please choose the earliest possible time after that date.',
52
+ # ],
53
+ # 'invalid schedule': [
54
+ # "* The scheduling result may fall outside the hospital's operating hours.",
55
+ # "* The scheduling result may be in the past relative to the current time.",
56
+ # "* The scheduling result may not be a valid date.",
57
+ # "* The assigned doctor may not belong to the department the patient should visit.",
58
+ # ],
59
+ # 'wrong duration': [
60
+ # "* The patient's schedule does not match the consultation duration required by the doctor.",
61
+ # ],
62
+ # # 'workload balancing': [
63
+ # # "* You must schedule the appointment with a doctor who has a lower workload than the current doctor.",
64
+ # # ]
65
+ # }
66
+
67
+
68
+ class ToolCallingError(Exception):
69
+ error_code = "TOOL_CALLING_ERROR"
70
+
71
+ def __init__(self, message: str):
72
+ super().__init__(message)
73
+ self.message = message
74
+
75
+
76
+ class ScheduleNotFoundError(Exception):
77
+ error_code = "SCHEDULE_NOT_FOUND_ERROR"
78
+
79
+ def __init__(self, message: str):
80
+ super().__init__(message)
81
+ self.message = message
82
+
83
+
84
+ class SchedulingError(Exception):
85
+ error_code = "SCHEDULING_ERROR"
86
+
87
+ def __init__(self, message: str):
88
+ super().__init__(message)
89
+ self.message = message
@@ -0,0 +1,126 @@
1
+ class Hospital:
2
+ def __init__(self,
3
+ hospital_name: str,
4
+ department_num: int,
5
+ doctor_num: int,
6
+ time: dict,
7
+ **kwargs):
8
+ self.hospital_name = hospital_name
9
+ self.department_num = department_num
10
+ self.doctor_num = doctor_num
11
+ self.time = time
12
+ self.department: list[Department] = []
13
+ for key, value in kwargs.items():
14
+ setattr(self, key, value)
15
+
16
+
17
+ def add_department(self, department_name: str, **kwargs):
18
+ """
19
+ Add a department to the hospital.
20
+
21
+ Args:
22
+ department_name (str): Name of the department to add.
23
+
24
+ Returns:
25
+ Department: The newly created Department object.
26
+ """
27
+ dept = Department(department_name, **kwargs)
28
+ self.department.append(dept)
29
+ return dept
30
+
31
+
32
+ def reset_departments(self):
33
+ """
34
+ Reset the list of departments in the hospital.
35
+ """
36
+ self.department = []
37
+
38
+
39
+ def __repr__(self):
40
+ return f"Hospital(name={self.hospital_name}, departments={[d.name for d in self.department]}, time={self.time})"
41
+
42
+
43
+
44
+ class Department:
45
+ def __init__(self, name: str, **kwargs):
46
+ self.name = name
47
+ self.doctor: list[Doctor] = []
48
+ for key, value in kwargs.items():
49
+ setattr(self, key, value)
50
+
51
+
52
+ def add_doctor(self, doctor_name: str, **kwargs):
53
+ """
54
+ Add a doctor to the department.
55
+
56
+ Args:
57
+ doctor_name (str): Name of the doctor to add.
58
+
59
+ Returns:
60
+ Doctor: The newly created Doctor object.
61
+ """
62
+ doctor = Doctor(doctor_name, self, **kwargs)
63
+ self.doctor.append(doctor)
64
+ return doctor
65
+
66
+
67
+ def reset_doctors(self):
68
+ """
69
+ Reset the list of doctors in the department.
70
+ """
71
+ self.doctor = []
72
+
73
+
74
+ def __repr__(self):
75
+ return f"Department(name={self.name}, doctors={[d.name for d in self.doctor]})"
76
+
77
+
78
+
79
+ class Doctor:
80
+ def __init__(self, name: str, department: Department, **kwargs):
81
+ self.name = name
82
+ self.department = department
83
+ self.schedule = []
84
+ self.patient: list[Patient] = []
85
+ for key, value in kwargs.items():
86
+ setattr(self, key, value)
87
+
88
+
89
+ def add_patient(self, patient_name: str, **kwargs):
90
+ """
91
+ Add a patient to the doctor.
92
+
93
+ Args:
94
+ patient_name (str): Name of the patient to add.
95
+
96
+ Returns:
97
+ Paitnet: The newly created Paitent object.
98
+ """
99
+ patient = Patient(patient_name, self, **kwargs)
100
+ self.patient.append(patient)
101
+ return patient
102
+
103
+
104
+ def reset_patients(self):
105
+ """
106
+ Reset the list of patients in the doctor.
107
+ """
108
+ self.patient = []
109
+
110
+
111
+ def __repr__(self):
112
+ return f"Doctor(name={self.name}, department={self.department.name}), schedule={self.schedule})"
113
+
114
+
115
+
116
+ class Patient:
117
+ def __init__(self, name: str, attending_physician: Doctor, **kwargs):
118
+ self.name = name
119
+ self.attending_physician = attending_physician
120
+ self.schedule = []
121
+ for key, value in kwargs.items():
122
+ setattr(self, key, value)
123
+
124
+
125
+ def __repr__(self):
126
+ return f"Patient(name={self.name}, department={self.attending_physician.department.name}), attending_physician={self.attending_physician.name}), schedule={self.schedule})"