docsa_HD commited on
Commit
2929135
1 Parent(s): 434cfaf
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +16 -0
  2. .gitignore +64 -0
  3. environment.yml +24 -0
  4. examples/usage_examples.py +87 -0
  5. pytest.ini +6 -0
  6. requirements.txt +26 -0
  7. setup.py +47 -0
  8. src/__init__.py +4 -0
  9. src/agent.py +264 -0
  10. src/config/__init__.py +5 -0
  11. src/config/prompts.py +75 -0
  12. src/config/settings.py +70 -0
  13. src/models/__init__.py +32 -0
  14. src/models/state.py +202 -0
  15. src/nodes/__init__.py +18 -0
  16. src/nodes/input_analyzer.py +85 -0
  17. src/nodes/output_synthesizer.py +109 -0
  18. src/nodes/patient_flow.py +62 -0
  19. src/nodes/quality_monitor.py +97 -0
  20. src/nodes/resource_manager.py +78 -0
  21. src/nodes/staff_scheduler.py +76 -0
  22. src/nodes/task_router.py +45 -0
  23. src/tools/__init__.py +12 -0
  24. src/tools/patient_tools.py +171 -0
  25. src/tools/quality_tools.py +176 -0
  26. src/tools/resource_tools.py +130 -0
  27. src/tools/scheduling_tools.py +160 -0
  28. src/ui/__init__.py +3 -0
  29. src/ui/app.py +522 -0
  30. src/ui/assets/icons/.gitkeep +0 -0
  31. src/ui/assets/images/.gitkeep +0 -0
  32. src/ui/components/__init__.py +11 -0
  33. src/ui/components/chat.py +78 -0
  34. src/ui/components/header.py +73 -0
  35. src/ui/components/metrics.py +139 -0
  36. src/ui/components/sidebar.py +121 -0
  37. src/ui/styles/__init__.py +3 -0
  38. src/ui/styles/custom.css +224 -0
  39. src/ui/styles/theme.py +123 -0
  40. src/utils/__init__.py +6 -0
  41. src/utils/error_handlers.py +122 -0
  42. src/utils/logger.py +102 -0
  43. src/utils/validators.py +132 -0
  44. streamlit_app.py +10 -0
  45. test_healthcare_agent_basic.py +66 -0
  46. test_healthcare_scenarios.py +163 -0
  47. tests/__init__.py +6 -0
  48. tests/conftest.py +108 -0
  49. tests/test_agent.py +75 -0
  50. tests/test_nodes/test_input_analyzer.py +27 -0
.env.example ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OpenAI Configuration
2
+ OPENAI_API_KEY=
3
+
4
+ # Application Settings
5
+ LOG_LEVEL=INFO
6
+ MEMORY_TYPE=sqlite
7
+ MEMORY_URI=:memory:
8
+
9
+ # Hospital Configuration
10
+ HOSPITAL_NAME=Example Hospital
11
+ TOTAL_BEDS=300
12
+ DEPARTMENTS=ER,ICU,General,Surgery,Pediatrics
13
+
14
+ # Development Settings
15
+ DEBUG=False
16
+ ENVIRONMENT=production
.gitignore ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Environment
24
+ .env
25
+ .venv
26
+ env/
27
+ venv/
28
+ ENV/
29
+
30
+ # IDE
31
+ .idea/
32
+ .vscode/
33
+ *.swp
34
+ *.swo
35
+
36
+ # Logs
37
+ *.log
38
+ logs/
39
+
40
+ # Testing
41
+ .coverage
42
+ htmlcov/
43
+ .pytest_cache/
44
+
45
+ # macOS
46
+ .DS_Store
47
+ .AppleDouble
48
+ .LSOverride
49
+ Icon
50
+ ._*
51
+ .DocumentRevisions-V100
52
+ .fseventsd
53
+ .Spotlight-V100
54
+ .TemporaryItems
55
+ .Trashes
56
+ .VolumeIcon.icns
57
+ .com.apple.timemachine.donotpresent
58
+
59
+ # Directories potentially created on remote AFP share
60
+ .AppleDB
61
+ .AppleDesktop
62
+ Network Trash Folder
63
+ Temporary Items
64
+ .apdisk
environment.yml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: langgraph
2
+ channels:
3
+ - conda-forge
4
+ - defaults
5
+ dependencies:
6
+ - python=3.11
7
+ - pip
8
+ - pip:
9
+ - langgraph>=0.0.15
10
+ - langchain>=0.1.0
11
+ - openai>=1.3.0
12
+ - python-dotenv>=0.19.0
13
+ - pydantic>=2.0.0
14
+ - typing-extensions>=4.5.0
15
+ - python-json-logger>=2.0.7
16
+ - structlog>=24.1.0
17
+ - pytest>=7.0.0
18
+ - pytest-asyncio>=0.23.0
19
+ - pytest-cov>=4.1.0
20
+ - black>=23.0.0
21
+ - isort>=5.12.0
22
+ - flake8>=6.1.0
23
+ - mkdocs>=1.5.0
24
+ - mkdocs-material>=9.5.0
examples/usage_examples.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # examples/usage_examples.py
2
+ import os
3
+ from dotenv import load_dotenv
4
+ from src.agent import HealthcareAgent
5
+
6
+ # Load environment variables
7
+ load_dotenv()
8
+
9
+ def basic_usage_example():
10
+ """Basic usage example of the Healthcare Agent"""
11
+ agent = HealthcareAgent(os.getenv("OPENAI_API_KEY"))
12
+
13
+ # Single query example
14
+ response = agent.process(
15
+ "What is the current ER wait time and bed availability?"
16
+ )
17
+ print("Basic Query Response:", response)
18
+
19
+ def conversation_example():
20
+ """Example of maintaining conversation context"""
21
+ agent = HealthcareAgent()
22
+ thread_id = "example-conversation"
23
+
24
+ # Series of related queries
25
+ queries = [
26
+ "How many beds are currently available in the ER?",
27
+ "What is the current staffing level for that department?",
28
+ "Based on these metrics, what are your recommendations for optimization?"
29
+ ]
30
+
31
+ for query in queries:
32
+ print(f"\nUser: {query}")
33
+ response = agent.process(query, thread_id=thread_id)
34
+ print(f"Assistant: {response['response']}")
35
+
36
+ def department_analysis_example():
37
+ """Example of department-specific analysis"""
38
+ agent = HealthcareAgent()
39
+
40
+ # Context with department-specific metrics
41
+ context = {
42
+ "department": "ICU",
43
+ "metrics": {
44
+ "bed_capacity": 20,
45
+ "occupied_beds": 18,
46
+ "staff_count": {"doctors": 5, "nurses": 15},
47
+ "average_stay": 4.5 # days
48
+ }
49
+ }
50
+
51
+ response = agent.process(
52
+ "Analyze current ICU operations and suggest improvements",
53
+ context=context
54
+ )
55
+ print("Department Analysis:", response)
56
+
57
+ def async_streaming_example():
58
+ """Example of using async streaming responses"""
59
+ import asyncio
60
+
61
+ async def stream_response():
62
+ agent = HealthcareAgent()
63
+ query = "Provide a complete analysis of current hospital operations"
64
+
65
+ async for event in agent.graph.astream_events(
66
+ {"messages": [query]},
67
+ {"configurable": {"thread_id": "streaming-example"}}
68
+ ):
69
+ if event["event"] == "on_chat_model_stream":
70
+ content = event["data"]["chunk"].content
71
+ if content:
72
+ print(content, end="", flush=True)
73
+
74
+ asyncio.run(stream_response())
75
+
76
+ if __name__ == "__main__":
77
+ print("=== Basic Usage Example ===")
78
+ basic_usage_example()
79
+
80
+ print("\n=== Conversation Example ===")
81
+ conversation_example()
82
+
83
+ print("\n=== Department Analysis Example ===")
84
+ department_analysis_example()
85
+
86
+ print("\n=== Streaming Example ===")
87
+ async_streaming_example()# Usage examples implementation
pytest.ini ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ [pytest]
2
+ testpaths = tests
3
+ python_files = test_*.py
4
+ python_classes = Test
5
+ python_functions = test_*
6
+ addopts = -v --cov=src --cov-report=html
requirements.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core Dependencies
2
+ langgraph>=0.0.15
3
+ langchain>=0.1.0
4
+ langchain-openai>=0.0.5
5
+ openai>=1.3.0
6
+ python-dotenv>=0.19.0
7
+ typing-extensions>=4.5.0
8
+
9
+ # State Management
10
+ pydantic>=2.0.0
11
+
12
+ # Logging and Monitoring
13
+ python-json-logger>=2.0.7
14
+ structlog>=24.1.0
15
+
16
+ # Development Dependencies
17
+ pytest>=7.0.0
18
+ pytest-asyncio>=0.23.0
19
+ pytest-cov>=4.1.0
20
+ black>=23.0.0
21
+ isort>=5.12.0
22
+ flake8>=6.1.0
23
+
24
+ # Documentation
25
+ mkdocs>=1.5.0
26
+ mkdocs-material>=9.5.0
setup.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # setup.py
2
+ from setuptools import setup, find_packages
3
+
4
+ # Read requirements
5
+ with open('requirements.txt') as f:
6
+ requirements = f.read().splitlines()
7
+
8
+ # Read README for long description
9
+ with open('README.md', encoding='utf-8') as f:
10
+ long_description = f.read()
11
+
12
+ setup(
13
+ name='healthcare-ops-agent',
14
+ version='0.1.0',
15
+ description='Healthcare Operations Management Agent using LangGraph',
16
+ long_description=long_description,
17
+ long_description_content_type='text/markdown',
18
+ author='Your Name',
19
+ author_email='[email protected]',
20
+ url='https://github.com/yourusername/healthcare-ops-agent',
21
+ packages=find_packages(exclude=['tests*']),
22
+ install_requires=requirements,
23
+ classifiers=[
24
+ 'Development Status :: 3 - Alpha',
25
+ 'Intended Audience :: Healthcare Industry',
26
+ 'License :: OSI Approved :: MIT License',
27
+ 'Programming Language :: Python :: 3.9',
28
+ 'Programming Language :: Python :: 3.10',
29
+ 'Programming Language :: Python :: 3.11',
30
+ ],
31
+ python_requires='>=3.9',
32
+ include_package_data=True,
33
+ extras_require={
34
+ 'dev': [
35
+ 'pytest>=7.0.0',
36
+ 'pytest-asyncio>=0.23.0',
37
+ 'pytest-cov>=4.1.0',
38
+ 'black>=23.0.0',
39
+ 'isort>=5.12.0',
40
+ 'flake8>=6.1.0',
41
+ ],
42
+ 'docs': [
43
+ 'mkdocs>=1.5.0',
44
+ 'mkdocs-material>=9.5.0',
45
+ ],
46
+ }
47
+ )
src/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # src/__init__.py
2
+ from .agent import HealthcareAgent
3
+
4
+ __version__ = "0.1.0"
src/agent.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/agent.py
2
+ from typing import Dict, Optional, List
3
+ import uuid
4
+ from datetime import datetime
5
+ from langchain_core.messages import HumanMessage, SystemMessage, AnyMessage
6
+ from langgraph.graph import StateGraph, END
7
+ from langchain_openai import ChatOpenAI
8
+
9
+ # Remove this line as it's causing the error
10
+ # from langgraph.checkpoint import BaseCheckpointSaver
11
+
12
+ from .config.settings import Settings
13
+ from .models.state import (
14
+ HospitalState,
15
+ create_initial_state,
16
+ validate_state
17
+ )
18
+ from .nodes import (
19
+ InputAnalyzerNode,
20
+ TaskRouterNode,
21
+ PatientFlowNode,
22
+ ResourceManagerNode,
23
+ QualityMonitorNode,
24
+ StaffSchedulerNode,
25
+ OutputSynthesizerNode
26
+ )
27
+ from .tools import (
28
+ PatientTools,
29
+ ResourceTools,
30
+ QualityTools,
31
+ SchedulingTools
32
+ )
33
+
34
+ from .utils.logger import setup_logger
35
+ from .utils.error_handlers import (
36
+ ErrorHandler,
37
+ HealthcareError,
38
+ ValidationError, # Add this import
39
+ ProcessingError # Add this import
40
+ )
41
+
42
+
43
+ logger = setup_logger(__name__)
44
+
45
+ class HealthcareAgent:
46
+ def __init__(self, api_key: Optional[str] = None):
47
+ try:
48
+ # Initialize settings and validate
49
+ self.settings = Settings()
50
+ if api_key:
51
+ self.settings.OPENAI_API_KEY = api_key
52
+ self.settings.validate_settings()
53
+
54
+ # Initialize LLM
55
+ self.llm = ChatOpenAI(
56
+ model=self.settings.MODEL_NAME,
57
+ temperature=self.settings.MODEL_TEMPERATURE,
58
+ api_key=self.settings.OPENAI_API_KEY
59
+ )
60
+
61
+ # Initialize tools
62
+ self.tools = self._initialize_tools()
63
+
64
+ # Initialize nodes
65
+ self.nodes = self._initialize_nodes()
66
+
67
+ # Initialize conversation states (replacing checkpointer)
68
+ self.conversation_states = {}
69
+
70
+ # Build graph
71
+ self.graph = self._build_graph()
72
+
73
+ logger.info("Healthcare Agent initialized successfully")
74
+
75
+ except Exception as e:
76
+ logger.error(f"Error initializing Healthcare Agent: {str(e)}")
77
+ raise HealthcareError(
78
+ message="Failed to initialize Healthcare Agent",
79
+ error_code="INIT_ERROR",
80
+ details={"error": str(e)}
81
+ )
82
+
83
+ def _initialize_tools(self) -> Dict:
84
+ """Initialize all tools used by the agent"""
85
+ return {
86
+ "patient": PatientTools(),
87
+ "resource": ResourceTools(),
88
+ "quality": QualityTools(),
89
+ "scheduling": SchedulingTools()
90
+ }
91
+
92
+ def _initialize_nodes(self) -> Dict:
93
+ """Initialize all nodes in the agent workflow"""
94
+ return {
95
+ "input_analyzer": InputAnalyzerNode(self.llm),
96
+ "task_router": TaskRouterNode(),
97
+ "patient_flow": PatientFlowNode(self.llm),
98
+ "resource_manager": ResourceManagerNode(self.llm),
99
+ "quality_monitor": QualityMonitorNode(self.llm),
100
+ "staff_scheduler": StaffSchedulerNode(self.llm),
101
+ "output_synthesizer": OutputSynthesizerNode(self.llm)
102
+ }
103
+
104
+ def _build_graph(self) -> StateGraph:
105
+ """Build the workflow graph with all nodes and edges"""
106
+ try:
107
+ # Initialize graph
108
+ builder = StateGraph(HospitalState)
109
+
110
+ # Add all nodes
111
+ for name, node in self.nodes.items():
112
+ builder.add_node(name, node)
113
+
114
+ # Set entry point
115
+ builder.set_entry_point("input_analyzer")
116
+
117
+ # Add edge from input analyzer to task router
118
+ builder.add_edge("input_analyzer", "task_router")
119
+
120
+ # Define conditional routing based on task router output
121
+ def route_next(state: Dict):
122
+ return state["context"]["next_node"]
123
+
124
+ # Add conditional edges from task router
125
+ builder.add_conditional_edges(
126
+ "task_router",
127
+ route_next,
128
+ {
129
+ "patient_flow": "patient_flow",
130
+ "resource_management": "resource_manager",
131
+ "quality_monitoring": "quality_monitor",
132
+ "staff_scheduling": "staff_scheduler",
133
+ "output_synthesis": "output_synthesizer"
134
+ }
135
+ )
136
+
137
+ # Add edges from functional nodes to output synthesizer
138
+ functional_nodes = [
139
+ "patient_flow",
140
+ "resource_manager",
141
+ "quality_monitor",
142
+ "staff_scheduler"
143
+ ]
144
+
145
+ for node in functional_nodes:
146
+ builder.add_edge(node, "output_synthesizer")
147
+
148
+ # Add end condition
149
+ builder.add_edge("output_synthesizer", END)
150
+
151
+ # Compile graph
152
+ return builder.compile()
153
+
154
+ except Exception as e:
155
+ logger.error(f"Error building graph: {str(e)}")
156
+ raise HealthcareError(
157
+ message="Failed to build agent workflow graph",
158
+ error_code="GRAPH_BUILD_ERROR",
159
+ details={"error": str(e)}
160
+ )
161
+
162
+ @ErrorHandler.error_decorator
163
+ def process(
164
+ self,
165
+ input_text: str,
166
+ thread_id: Optional[str] = None,
167
+ context: Optional[Dict] = None
168
+ ) -> Dict:
169
+ """Process input through the healthcare operations workflow"""
170
+ try:
171
+ # Validate input
172
+ ErrorHandler.validate_input(input_text)
173
+
174
+ # Create or use thread ID
175
+ thread_id = thread_id or str(uuid.uuid4())
176
+
177
+ # Initialize state
178
+ initial_state = create_initial_state(thread_id)
179
+
180
+ # Add input message as HumanMessage object
181
+ initial_state["messages"].append(
182
+ HumanMessage(content=input_text)
183
+ )
184
+
185
+ # Add context if provided
186
+ if context:
187
+ initial_state["context"].update(context)
188
+
189
+ # Validate state
190
+ validate_state(initial_state)
191
+
192
+ # Store state in conversation states
193
+ self.conversation_states[thread_id] = initial_state
194
+
195
+ # Process through graph
196
+ result = self.graph.invoke(initial_state)
197
+
198
+ return self._format_response(result)
199
+
200
+ except ValidationError as ve:
201
+ logger.error(f"Validation error: {str(ve)}")
202
+ raise
203
+ except Exception as e:
204
+ logger.error(f"Error processing input: {str(e)}")
205
+ raise HealthcareError(
206
+ message="Failed to process input",
207
+ error_code="PROCESSING_ERROR",
208
+ details={"error": str(e)}
209
+ )
210
+
211
+ def _format_response(self, result: Dict) -> Dict:
212
+ """Format the final response from the graph execution"""
213
+ try:
214
+ if not result or "messages" not in result:
215
+ raise ProcessingError(
216
+ message="Invalid result format",
217
+ error_code="INVALID_RESULT",
218
+ details={"result": str(result)}
219
+ )
220
+
221
+ return {
222
+ "response": result["messages"][-1].content if result["messages"] else "",
223
+ "analysis": result.get("analysis", {}),
224
+ "metrics": result.get("metrics", {}),
225
+ "timestamp": datetime.now()
226
+ }
227
+ except Exception as e:
228
+ logger.error(f"Error formatting response: {str(e)}")
229
+ raise HealthcareError(
230
+ message="Failed to format response",
231
+ error_code="FORMAT_ERROR",
232
+ details={"error": str(e)}
233
+ )
234
+
235
+ def get_conversation_history(
236
+ self,
237
+ thread_id: str
238
+ ) -> List[Dict]:
239
+ """Retrieve conversation history for a specific thread"""
240
+ try:
241
+ return self.conversation_states.get(thread_id, {}).get("messages", [])
242
+ except Exception as e:
243
+ logger.error(f"Error retrieving conversation history: {str(e)}")
244
+ raise HealthcareError(
245
+ message="Failed to retrieve conversation history",
246
+ error_code="HISTORY_ERROR",
247
+ details={"error": str(e)}
248
+ )
249
+
250
+ def reset_conversation(
251
+ self,
252
+ thread_id: str
253
+ ) -> bool:
254
+ """Reset conversation state for a specific thread"""
255
+ try:
256
+ self.conversation_states[thread_id] = create_initial_state(thread_id)
257
+ return True
258
+ except Exception as e:
259
+ logger.error(f"Error resetting conversation: {str(e)}")
260
+ raise HealthcareError(
261
+ message="Failed to reset conversation",
262
+ error_code="RESET_ERROR",
263
+ details={"error": str(e)}
264
+ )
src/config/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # src/config/__init__.py
2
+ from .settings import Settings
3
+ from .prompts import PROMPTS
4
+
5
+ __all__ = ['Settings', 'PROMPTS']
src/config/prompts.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/config/prompts.py
2
+ PROMPTS = {
3
+ "system": """You are an expert Healthcare Operations Management Assistant.
4
+ Your role is to optimize hospital operations through:
5
+ - Patient flow management
6
+ - Resource allocation
7
+ - Quality monitoring
8
+ - Staff scheduling
9
+
10
+ Always maintain HIPAA compliance and healthcare standards in your responses.
11
+ Base your analysis on the provided metrics and department data.""",
12
+
13
+ "input_analyzer": """Analyze the following input and determine:
14
+ 1. Primary task category (patient_flow, resource_management, quality_monitoring, staff_scheduling)
15
+ 2. Required context information
16
+ 3. Priority level (1-5, where 5 is highest)
17
+ 4. Relevant department(s)
18
+
19
+ Current input: {input}""",
20
+
21
+ "patient_flow": """Analyze patient flow based on:
22
+ - Current occupancy: {occupancy}%
23
+ - Waiting times: {wait_times} minutes
24
+ - Department capacity: {department_capacity}
25
+ - Admission rate: {admission_rate} per hour
26
+
27
+ Provide specific recommendations for optimization.""",
28
+
29
+ "resource_manager": """Evaluate resource utilization:
30
+ - Equipment availability: {equipment_status}
31
+ - Supply levels: {supply_levels}
32
+ - Resource allocation: {resource_allocation}
33
+ - Budget constraints: {budget_info}
34
+
35
+ Recommend optimal resource distribution.""",
36
+
37
+ "quality_monitor": """Review quality metrics:
38
+ - Patient satisfaction: {satisfaction_score}/10
39
+ - Care outcomes: {care_outcomes}
40
+ - Compliance rates: {compliance_rates}%
41
+ - Incident reports: {incident_count}
42
+
43
+ Identify areas for improvement.""",
44
+
45
+ "staff_scheduler": """Optimize staff scheduling considering:
46
+ - Staff availability: {staff_available}
47
+ - Department needs: {department_needs}
48
+ - Skill mix requirements: {skill_requirements}
49
+ - Work hour regulations: {work_hours}
50
+
51
+ Provide scheduling recommendations.""",
52
+
53
+ "output_synthesis": """Synthesize findings and provide:
54
+ 1. Key insights
55
+ 2. Actionable recommendations
56
+ 3. Priority actions
57
+ 4. Implementation timeline
58
+
59
+ Context: {context}"""
60
+ }
61
+
62
+ # Error message templates
63
+ ERROR_MESSAGES = {
64
+ "invalid_input": "Invalid input provided. Please ensure all required information is included.",
65
+ "processing_error": "An error occurred while processing your request. Please try again.",
66
+ "data_validation": "Data validation failed. Please check the input format.",
67
+ "system_error": "System error encountered. Please contact support."
68
+ }
69
+
70
+ # Response templates
71
+ RESPONSE_TEMPLATES = {
72
+ "confirmation": "Request received and being processed. Priority level: {priority}",
73
+ "completion": "Analysis complete. {summary}",
74
+ "error": "Error: {error_message}. Error code: {error_code}"
75
+ }# System prompts implementation
src/config/settings.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/config/settings.py
2
+ import os
3
+ from typing import Dict, Any
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ class Settings:
9
+ """Configuration settings for the Healthcare Operations Management Agent"""
10
+
11
+ # OpenAI Configuration
12
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
13
+ MODEL_NAME = "gpt-4o-mini-2024-07-18"
14
+ MODEL_TEMPERATURE = 0
15
+
16
+ # LangGraph Configuration
17
+ MEMORY_TYPE = os.getenv("MEMORY_TYPE", "sqlite")
18
+ MEMORY_URI = os.getenv("MEMORY_URI", ":memory:")
19
+
20
+ # Hospital Configuration
21
+ HOSPITAL_SETTINGS = {
22
+ "total_beds": 300,
23
+ "departments": ["ER", "ICU", "General", "Surgery", "Pediatrics"],
24
+ "staff_roles": ["Doctor", "Nurse", "Specialist", "Support Staff"]
25
+ }
26
+
27
+ # Application Settings
28
+ MAX_RETRIES = int(os.getenv("MAX_RETRIES", "3"))
29
+ REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "30"))
30
+ BATCH_SIZE = int(os.getenv("BATCH_SIZE", "10"))
31
+
32
+ # Logging Configuration
33
+ LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
34
+ LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
35
+ LOG_FILE = "logs/healthcare_ops_agent.log"
36
+
37
+ # Quality Metrics Thresholds
38
+ QUALITY_THRESHOLDS = {
39
+ "min_satisfaction_score": 7.0,
40
+ "max_wait_time_minutes": 45,
41
+ "optimal_bed_utilization": 0.85,
42
+ "min_staff_ratio": {
43
+ "ICU": 0.5, # 1 nurse per 2 patients
44
+ "General": 0.25 # 1 nurse per 4 patients
45
+ }
46
+ }
47
+
48
+ @classmethod
49
+ def get_model_config(cls) -> Dict[str, Any]:
50
+ """Get model configuration"""
51
+ return {
52
+ "model": cls.MODEL_NAME,
53
+ "temperature": cls.MODEL_TEMPERATURE,
54
+ "api_key": cls.OPENAI_API_KEY
55
+ }
56
+
57
+ @classmethod
58
+ def validate_settings(cls) -> bool:
59
+ """Validate required settings"""
60
+ required_settings = [
61
+ "OPENAI_API_KEY",
62
+ "MODEL_NAME",
63
+ "MEMORY_TYPE"
64
+ ]
65
+
66
+ for setting in required_settings:
67
+ if not getattr(cls, setting):
68
+ raise ValueError(f"Missing required setting: {setting}")
69
+
70
+ return True# Configuration settings implementation
src/models/__init__.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/models/__init__.py
2
+ from .state import (
3
+ TaskType,
4
+ PriorityLevel,
5
+ Department,
6
+ HospitalState,
7
+ PatientFlowMetrics,
8
+ ResourceMetrics,
9
+ QualityMetrics,
10
+ StaffingMetrics,
11
+ HospitalMetrics,
12
+ AnalysisResult,
13
+ create_initial_state,
14
+ validate_state,
15
+ update_state_metrics
16
+ )
17
+
18
+ __all__ = [
19
+ 'TaskType',
20
+ 'PriorityLevel',
21
+ 'Department',
22
+ 'HospitalState',
23
+ 'PatientFlowMetrics',
24
+ 'ResourceMetrics',
25
+ 'QualityMetrics',
26
+ 'StaffingMetrics',
27
+ 'HospitalMetrics',
28
+ 'AnalysisResult',
29
+ 'create_initial_state',
30
+ 'validate_state',
31
+ 'update_state_metrics'
32
+ ]
src/models/state.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/models/state.py
2
+ from typing import Annotated, List, Dict, Optional
3
+ from typing_extensions import TypedDict # Changed this import
4
+ from langchain_core.messages import AnyMessage
5
+ from datetime import datetime
6
+ import operator
7
+ from enum import Enum
8
+ from langchain_core.messages import HumanMessage, SystemMessage, AnyMessage
9
+
10
+
11
+ class TaskType(str, Enum):
12
+ PATIENT_FLOW = "patient_flow"
13
+ RESOURCE_MANAGEMENT = "resource_management"
14
+ QUALITY_MONITORING = "quality_monitoring"
15
+ STAFF_SCHEDULING = "staff_scheduling"
16
+ GENERAL = "general"
17
+
18
+ class PriorityLevel(int, Enum):
19
+ LOW = 1
20
+ MEDIUM = 2
21
+ HIGH = 3
22
+ URGENT = 4
23
+ CRITICAL = 5
24
+
25
+ class Department(TypedDict):
26
+ """Department information"""
27
+ id: str
28
+ name: str
29
+ capacity: int
30
+ current_occupancy: int
31
+ staff_count: Dict[str, int]
32
+ wait_time: int
33
+
34
+ class PatientFlowMetrics(TypedDict):
35
+ """Metrics related to patient flow"""
36
+ total_beds: int
37
+ occupied_beds: int
38
+ waiting_patients: int
39
+ average_wait_time: float
40
+ admission_rate: float
41
+ discharge_rate: float
42
+ department_metrics: Dict[str, "Department"]
43
+
44
+ class ResourceMetrics(TypedDict):
45
+ """Metrics related to resource management"""
46
+ equipment_availability: Dict[str, bool]
47
+ supply_levels: Dict[str, float]
48
+ resource_utilization: float
49
+ pending_requests: int
50
+ critical_supplies: List[str]
51
+
52
+ class QualityMetrics(TypedDict):
53
+ """Metrics related to quality monitoring"""
54
+ patient_satisfaction: float
55
+ care_outcomes: Dict[str, float]
56
+ compliance_rate: float
57
+ incident_count: int
58
+ quality_scores: Dict[str, float]
59
+ last_audit_date: datetime
60
+
61
+ class StaffingMetrics(TypedDict):
62
+ """Metrics related to staff scheduling"""
63
+ total_staff: int
64
+ available_staff: Dict[str, int]
65
+ shifts_coverage: Dict[str, float]
66
+ overtime_hours: float
67
+ skill_mix_index: float
68
+ staff_satisfaction: float
69
+
70
+ class HospitalMetrics(TypedDict):
71
+ """Combined hospital metrics"""
72
+ patient_flow: PatientFlowMetrics
73
+ resources: ResourceMetrics
74
+ quality: QualityMetrics
75
+ staffing: StaffingMetrics
76
+ last_updated: datetime
77
+
78
+ class AnalysisResult(TypedDict):
79
+ """Analysis results from nodes"""
80
+ category: TaskType
81
+ priority: PriorityLevel
82
+ findings: List[str]
83
+ recommendations: List[str]
84
+ action_items: List[Dict[str, str]]
85
+ metrics_impact: Dict[str, float]
86
+
87
+
88
+ class HospitalState(TypedDict):
89
+ """Main state management for the agent"""
90
+ messages: Annotated[List[AnyMessage], operator.add]
91
+ current_task: TaskType
92
+ priority_level: PriorityLevel
93
+ department: Optional[str]
94
+ metrics: HospitalMetrics
95
+ analysis: Optional[AnalysisResult]
96
+ context: Dict[str, any] # Will include routing information
97
+ timestamp: datetime
98
+ thread_id: str
99
+
100
+
101
+ def create_initial_state(thread_id: str) -> HospitalState:
102
+ """Create initial state with default values"""
103
+ return {
104
+ "messages": [],
105
+ "current_task": TaskType.GENERAL,
106
+ "priority_level": PriorityLevel.MEDIUM,
107
+ "department": None,
108
+ "metrics": {
109
+ "patient_flow": {
110
+ "total_beds": 300,
111
+ "occupied_beds": 240,
112
+ "waiting_patients": 15,
113
+ "average_wait_time": 35.0,
114
+ "admission_rate": 4.2,
115
+ "discharge_rate": 3.8,
116
+ "department_metrics": {}
117
+ },
118
+ "resources": {
119
+ "equipment_availability": {},
120
+ "supply_levels": {},
121
+ "resource_utilization": 0.75,
122
+ "pending_requests": 5,
123
+ "critical_supplies": []
124
+ },
125
+ "quality": {
126
+ "patient_satisfaction": 8.5,
127
+ "care_outcomes": {},
128
+ "compliance_rate": 0.95,
129
+ "incident_count": 2,
130
+ "quality_scores": {},
131
+ "last_audit_date": datetime.now()
132
+ },
133
+ "staffing": {
134
+ "total_staff": 500,
135
+ "available_staff": {
136
+ "doctors": 50,
137
+ "nurses": 150,
138
+ "specialists": 30,
139
+ "support": 70
140
+ },
141
+ "shifts_coverage": {},
142
+ "overtime_hours": 120.5,
143
+ "skill_mix_index": 0.85,
144
+ "staff_satisfaction": 7.8
145
+ },
146
+ "last_updated": datetime.now()
147
+ },
148
+ "analysis": None,
149
+ "context": {
150
+ "next_node": None # Add routing context
151
+ },
152
+ "timestamp": datetime.now(),
153
+ "thread_id": thread_id
154
+ }
155
+
156
+ def validate_state(state: HospitalState) -> bool:
157
+ """Validate state structure and data types"""
158
+ try:
159
+ # Basic structure validation
160
+ required_keys = [
161
+ "messages", "current_task", "priority_level",
162
+ "metrics", "timestamp", "thread_id"
163
+ ]
164
+ for key in required_keys:
165
+ if key not in state:
166
+ raise ValueError(f"Missing required key: {key}")
167
+
168
+ # Validate messages
169
+ if not isinstance(state["messages"], list):
170
+ raise ValueError("Messages must be a list")
171
+
172
+ # Validate each message has required attributes
173
+ for msg in state["messages"]:
174
+ if not hasattr(msg, 'content'):
175
+ raise ValueError("Invalid message format - missing content")
176
+
177
+ # Validate types
178
+ if not isinstance(state["current_task"], TaskType):
179
+ raise ValueError("Invalid task type")
180
+ if not isinstance(state["priority_level"], PriorityLevel):
181
+ raise ValueError("Invalid priority level")
182
+ if not isinstance(state["timestamp"], datetime):
183
+ raise ValueError("Invalid timestamp")
184
+
185
+ return True
186
+
187
+ except Exception as e:
188
+ raise ValueError(f"State validation failed: {str(e)}")
189
+
190
+ def update_state_metrics(
191
+ state: HospitalState,
192
+ new_metrics: Dict,
193
+ category: str
194
+ ) -> HospitalState:
195
+ """Update specific category of metrics in state"""
196
+ if category not in state["metrics"]:
197
+ raise ValueError(f"Invalid metrics category: {category}")
198
+
199
+ state["metrics"][category].update(new_metrics)
200
+ state["metrics"]["last_updated"] = datetime.now()
201
+
202
+ return state
src/nodes/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/__init__.py
2
+ from .input_analyzer import InputAnalyzerNode
3
+ from .task_router import TaskRouterNode
4
+ from .patient_flow import PatientFlowNode
5
+ from .resource_manager import ResourceManagerNode
6
+ from .quality_monitor import QualityMonitorNode
7
+ from .staff_scheduler import StaffSchedulerNode
8
+ from .output_synthesizer import OutputSynthesizerNode
9
+
10
+ __all__ = [
11
+ 'InputAnalyzerNode',
12
+ 'TaskRouterNode',
13
+ 'PatientFlowNode',
14
+ 'ResourceManagerNode',
15
+ 'QualityMonitorNode',
16
+ 'StaffSchedulerNode',
17
+ 'OutputSynthesizerNode'
18
+ ]
src/nodes/input_analyzer.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/input_analyzer.py
2
+ #from typing import Dict
3
+ from typing import Dict, List, Optional, Any
4
+ from typing_extensions import TypedDict # If using TypedDict
5
+ #from langchain_core.messages import SystemMessage, HumanMessage
6
+ from ..models.state import HospitalState, TaskType, PriorityLevel
7
+ from ..config.prompts import PROMPTS
8
+ from ..utils.logger import setup_logger
9
+ from langchain_core.messages import HumanMessage, SystemMessage, AnyMessage
10
+
11
+ logger = setup_logger(__name__)
12
+
13
+ class InputAnalyzerNode:
14
+ def __init__(self, llm):
15
+ self.llm = llm
16
+ self.system_prompt = PROMPTS["input_analyzer"]
17
+
18
+ def __call__(self, state: HospitalState) -> Dict:
19
+ try:
20
+ # Get the latest message
21
+ if not state["messages"]:
22
+ raise ValueError("No messages in state")
23
+
24
+ latest_message = state["messages"][-1]
25
+
26
+ # Ensure message is a LangChain message object
27
+ if not hasattr(latest_message, 'content'):
28
+ raise ValueError("Invalid message format")
29
+
30
+ # Prepare messages for LLM
31
+ messages = [
32
+ SystemMessage(content=self.system_prompt),
33
+ latest_message if isinstance(latest_message, HumanMessage)
34
+ else HumanMessage(content=str(latest_message))
35
+ ]
36
+
37
+ # Get LLM response
38
+ response = self.llm.invoke(messages)
39
+
40
+ # Parse response to determine task type and priority
41
+ parsed_result = self._parse_llm_response(response.content)
42
+
43
+ return {
44
+ "current_task": parsed_result["task_type"],
45
+ "priority_level": parsed_result["priority"],
46
+ "department": parsed_result["department"],
47
+ "context": parsed_result["context"]
48
+ }
49
+
50
+ except Exception as e:
51
+ logger.error(f"Error in input analysis: {str(e)}")
52
+ raise
53
+
54
+ def _parse_llm_response(self, response: str) -> Dict:
55
+ """Parse LLM response to extract task type and other metadata"""
56
+ try:
57
+ # Default values
58
+ result = {
59
+ "task_type": TaskType.GENERAL,
60
+ "priority": PriorityLevel.MEDIUM,
61
+ "department": None,
62
+ "context": {}
63
+ }
64
+
65
+ # Simple parsing logic (can be made more robust)
66
+ if "patient flow" in response.lower():
67
+ result["task_type"] = TaskType.PATIENT_FLOW
68
+ elif "resource" in response.lower():
69
+ result["task_type"] = TaskType.RESOURCE_MANAGEMENT
70
+ elif "quality" in response.lower():
71
+ result["task_type"] = TaskType.QUALITY_MONITORING
72
+ elif "staff" in response.lower() or "schedule" in response.lower():
73
+ result["task_type"] = TaskType.STAFF_SCHEDULING
74
+
75
+ # Extract priority from response
76
+ if "urgent" in response.lower() or "critical" in response.lower():
77
+ result["priority"] = PriorityLevel.CRITICAL
78
+ elif "high" in response.lower():
79
+ result["priority"] = PriorityLevel.HIGH
80
+
81
+ return result
82
+
83
+ except Exception as e:
84
+ logger.error(f"Error parsing LLM response: {str(e)}")
85
+ return result
src/nodes/output_synthesizer.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/output_synthesizer.py
2
+ #from typing import Dict, List
3
+ from typing import Dict, List, Optional, Any
4
+ from typing_extensions import TypedDict # If using TypedDict
5
+ from langchain_core.messages import SystemMessage
6
+ from ..models.state import HospitalState
7
+ from ..config.prompts import PROMPTS
8
+ from ..utils.logger import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+ class OutputSynthesizerNode:
13
+ def __init__(self, llm):
14
+ self.llm = llm
15
+ self.system_prompt = PROMPTS["output_synthesis"]
16
+
17
+ def __call__(self, state: HospitalState) -> Dict:
18
+ try:
19
+ # Get analysis results from previous nodes
20
+ analysis = state.get("analysis", {})
21
+
22
+ # Format prompt with context
23
+ formatted_prompt = self.system_prompt.format(
24
+ context=self._format_context(state)
25
+ )
26
+
27
+ # Get LLM synthesis
28
+ response = self.llm.invoke([
29
+ SystemMessage(content=formatted_prompt)
30
+ ])
31
+
32
+ # Structure the final output
33
+ final_output = self._structure_final_output(
34
+ response.content,
35
+ state["current_task"],
36
+ state["priority_level"]
37
+ )
38
+
39
+ return {
40
+ "messages": [response],
41
+ "analysis": final_output
42
+ }
43
+
44
+ except Exception as e:
45
+ logger.error(f"Error in output synthesis: {str(e)}")
46
+ raise
47
+
48
+ def _format_context(self, state: HospitalState) -> str:
49
+ """Format all relevant context for synthesis"""
50
+ return f"""
51
+ Task Type: {state['current_task']}
52
+ Priority Level: {state['priority_level']}
53
+ Department: {state['department'] or 'All Departments'}
54
+ Key Metrics Summary:
55
+ - Patient Flow: {self._summarize_patient_flow(state)}
56
+ - Resources: {self._summarize_resources(state)}
57
+ - Quality: {self._summarize_quality(state)}
58
+ - Staffing: {self._summarize_staffing(state)}
59
+ """
60
+
61
+ def _structure_final_output(self, response: str, task_type: str, priority: int) -> Dict:
62
+ """Structure the final output in a standardized format"""
63
+ return {
64
+ "summary": self._extract_summary(response),
65
+ "key_findings": self._extract_key_findings(response),
66
+ "recommendations": self._extract_recommendations(response),
67
+ "action_items": self._extract_action_items(response),
68
+ "priority_level": priority,
69
+ "task_type": task_type
70
+ }
71
+
72
+ def _summarize_patient_flow(self, state: HospitalState) -> str:
73
+ metrics = state["metrics"]["patient_flow"]
74
+ return f"Occupancy {(metrics['occupied_beds']/metrics['total_beds'])*100:.1f}%"
75
+
76
+ def _summarize_resources(self, state: HospitalState) -> str:
77
+ metrics = state["metrics"]["resources"]
78
+ return f"Utilization {metrics['resource_utilization']*100:.1f}%"
79
+
80
+ def _summarize_quality(self, state: HospitalState) -> str:
81
+ metrics = state["metrics"]["quality"]
82
+ return f"Satisfaction {metrics['patient_satisfaction']:.1f}/10"
83
+
84
+ def _summarize_staffing(self, state: HospitalState) -> str:
85
+ metrics = state["metrics"]["staffing"]
86
+ return f"Staff Available: {sum(metrics['available_staff'].values())}"
87
+
88
+ def _extract_summary(self, response: str) -> str:
89
+ """Extract high-level summary from response"""
90
+ # Implementation depends on response structure
91
+ return response.split('\n')[0]
92
+
93
+ def _extract_key_findings(self, response: str) -> List[str]:
94
+ """Extract key findings from response"""
95
+ findings = []
96
+ # Implementation for parsing findings
97
+ return findings
98
+
99
+ def _extract_recommendations(self, response: str) -> List[str]:
100
+ """Extract recommendations from response"""
101
+ recommendations = []
102
+ # Implementation for parsing recommendations
103
+ return recommendations
104
+
105
+ def _extract_action_items(self, response: str) -> List[Dict]:
106
+ """Extract actionable items from response"""
107
+ action_items = []
108
+ # Implementation for parsing action items
109
+ return action_items# output_synthesizer node implementation
src/nodes/patient_flow.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/patient_flow.py
2
+ #from typing import Dict
3
+ from typing import Dict, List, Optional, Any
4
+ from typing_extensions import TypedDict # If using TypedDict
5
+ from langchain_core.messages import SystemMessage
6
+ from ..models.state import HospitalState
7
+ from ..config.prompts import PROMPTS
8
+ from ..utils.logger import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+ class PatientFlowNode:
13
+ def __init__(self, llm):
14
+ self.llm = llm
15
+ self.system_prompt = PROMPTS["patient_flow"]
16
+
17
+ def __call__(self, state: HospitalState) -> Dict:
18
+ try:
19
+ # Get current metrics
20
+ metrics = state["metrics"]["patient_flow"]
21
+
22
+ # Format prompt with current metrics
23
+ formatted_prompt = self.system_prompt.format(
24
+ occupancy=self._calculate_occupancy(metrics),
25
+ wait_times=metrics["average_wait_time"],
26
+ department_capacity=self._get_department_capacity(metrics),
27
+ admission_rate=metrics["admission_rate"]
28
+ )
29
+
30
+ # Get LLM analysis
31
+ response = self.llm.invoke([
32
+ SystemMessage(content=formatted_prompt)
33
+ ])
34
+
35
+ # Parse and structure the response
36
+ analysis = self._structure_analysis(response.content)
37
+
38
+ return {
39
+ "analysis": analysis,
40
+ "messages": [response]
41
+ }
42
+
43
+ except Exception as e:
44
+ logger.error(f"Error in patient flow analysis: {str(e)}")
45
+ raise
46
+
47
+ def _calculate_occupancy(self, metrics: Dict) -> float:
48
+ """Calculate current occupancy percentage"""
49
+ return (metrics["occupied_beds"] / metrics["total_beds"]) * 100
50
+
51
+ def _get_department_capacity(self, metrics: Dict) -> Dict:
52
+ """Get capacity details by department"""
53
+ return metrics.get("department_metrics", {})
54
+
55
+ def _structure_analysis(self, response: str) -> Dict:
56
+ """Structure the LLM response into a standardized format"""
57
+ return {
58
+ "findings": [], # Extract key findings
59
+ "recommendations": [], # Extract recommendations
60
+ "action_items": [], # Extract action items
61
+ "metrics_impact": {} # Expected impact on metrics
62
+ }# patient_flow node implementation
src/nodes/quality_monitor.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/quality_monitor.py
2
+
3
+ from typing import Dict, List, Optional, Any
4
+ from typing_extensions import TypedDict # If using TypedDict
5
+ from langchain_core.messages import SystemMessage
6
+ from ..models.state import HospitalState
7
+ from ..config.prompts import PROMPTS
8
+ from ..utils.logger import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+ class QualityMonitorNode:
13
+ def __init__(self, llm):
14
+ self.llm = llm
15
+ self.system_prompt = PROMPTS["quality_monitor"]
16
+
17
+ def __call__(self, state: HospitalState) -> Dict:
18
+ try:
19
+ # Get current quality metrics
20
+ metrics = state["metrics"]["quality"]
21
+
22
+ # Format prompt with current metrics
23
+ formatted_prompt = self.system_prompt.format(
24
+ satisfaction_score=metrics["patient_satisfaction"],
25
+ care_outcomes=self._format_care_outcomes(metrics),
26
+ compliance_rates=metrics["compliance_rate"] * 100,
27
+ incident_count=metrics["incident_count"]
28
+ )
29
+
30
+ # Get LLM analysis
31
+ response = self.llm.invoke([
32
+ SystemMessage(content=formatted_prompt)
33
+ ])
34
+
35
+ # Process quality assessment
36
+ analysis = self._analyze_quality_metrics(response.content, metrics)
37
+
38
+ return {
39
+ "analysis": analysis,
40
+ "messages": [response],
41
+ "context": {
42
+ "quality_scores": metrics["quality_scores"],
43
+ "last_audit": metrics["last_audit_date"]
44
+ }
45
+ }
46
+
47
+ except Exception as e:
48
+ logger.error(f"Error in quality monitoring analysis: {str(e)}")
49
+ raise
50
+
51
+ def _format_care_outcomes(self, metrics: Dict) -> str:
52
+ """Format care outcomes into readable text"""
53
+ outcomes = []
54
+ for metric, value in metrics["care_outcomes"].items():
55
+ outcomes.append(f"{metric}: {value:.1f}")
56
+ return ", ".join(outcomes)
57
+
58
+ def _analyze_quality_metrics(self, response: str, metrics: Dict) -> Dict:
59
+ """Analyze quality metrics and identify areas for improvement"""
60
+ return {
61
+ "satisfaction_analysis": self._analyze_satisfaction(metrics),
62
+ "compliance_analysis": self._analyze_compliance(metrics),
63
+ "incident_analysis": self._analyze_incidents(metrics),
64
+ "recommendations": self._extract_recommendations(response),
65
+ "priority_improvements": []
66
+ }
67
+
68
+ def _analyze_satisfaction(self, metrics: Dict) -> Dict:
69
+ """Analyze patient satisfaction trends"""
70
+ satisfaction = metrics["patient_satisfaction"]
71
+ return {
72
+ "current_score": satisfaction,
73
+ "status": "Good" if satisfaction >= 8.0 else "Needs Improvement",
74
+ "trend": "Unknown" # Would need historical data
75
+ }
76
+
77
+ def _analyze_compliance(self, metrics: Dict) -> Dict:
78
+ """Analyze compliance rates"""
79
+ return {
80
+ "rate": metrics["compliance_rate"],
81
+ "status": "Compliant" if metrics["compliance_rate"] >= 0.95 else "Review Required"
82
+ }
83
+
84
+ def _analyze_incidents(self, metrics: Dict) -> Dict:
85
+ """Analyze incident reports"""
86
+ return {
87
+ "count": metrics["incident_count"],
88
+ "severity": "High" if metrics["incident_count"] > 5 else "Low"
89
+ }
90
+
91
+ def _extract_recommendations(self, response: str) -> List[str]:
92
+ """Extract recommendations from LLM response"""
93
+ recommendations = []
94
+ for line in response.split('\n'):
95
+ if 'recommend' in line.lower() or 'suggest' in line.lower():
96
+ recommendations.append(line.strip())
97
+ return recommendations
src/nodes/resource_manager.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/resource_manager.py
2
+ from typing import Dict, List, Optional, Any
3
+ from typing_extensions import TypedDict # If using TypedDict
4
+ #from typing import Dict
5
+ from langchain_core.messages import SystemMessage
6
+ from ..models.state import HospitalState
7
+ from ..config.prompts import PROMPTS
8
+ from ..utils.logger import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+ class ResourceManagerNode:
13
+ def __init__(self, llm):
14
+ self.llm = llm
15
+ self.system_prompt = PROMPTS["resource_manager"]
16
+
17
+ def __call__(self, state: HospitalState) -> Dict:
18
+ try:
19
+ # Get current resource metrics
20
+ metrics = state["metrics"]["resources"]
21
+
22
+ # Format prompt with current metrics
23
+ formatted_prompt = self.system_prompt.format(
24
+ equipment_status=self._format_equipment_status(metrics),
25
+ supply_levels=self._format_supply_levels(metrics),
26
+ resource_allocation=metrics["resource_utilization"],
27
+ budget_info=self._get_budget_info(state)
28
+ )
29
+
30
+ # Get LLM analysis
31
+ response = self.llm.invoke([
32
+ SystemMessage(content=formatted_prompt)
33
+ ])
34
+
35
+ # Update state with recommendations
36
+ analysis = self._parse_recommendations(response.content)
37
+
38
+ return {
39
+ "analysis": analysis,
40
+ "messages": [response],
41
+ "context": {
42
+ "critical_supplies": metrics["critical_supplies"],
43
+ "pending_requests": metrics["pending_requests"]
44
+ }
45
+ }
46
+
47
+ except Exception as e:
48
+ logger.error(f"Error in resource management analysis: {str(e)}")
49
+ raise
50
+
51
+ def _format_equipment_status(self, metrics: Dict) -> str:
52
+ """Format equipment availability into readable text"""
53
+ status = []
54
+ for equip, available in metrics["equipment_availability"].items():
55
+ status.append(f"{equip}: {'Available' if available else 'In Use'}")
56
+ return ", ".join(status)
57
+
58
+ def _format_supply_levels(self, metrics: Dict) -> str:
59
+ """Format supply levels into readable text"""
60
+ levels = []
61
+ for item, level in metrics["supply_levels"].items():
62
+ status = "Critical" if level < 0.2 else "Low" if level < 0.4 else "Adequate"
63
+ levels.append(f"{item}: {status} ({level*100:.0f}%)")
64
+ return ", ".join(levels)
65
+
66
+ def _get_budget_info(self, state: HospitalState) -> str:
67
+ """Get budget information from context"""
68
+ return state.get("context", {}).get("budget_info", "Budget information not available")
69
+
70
+ def _parse_recommendations(self, response: str) -> Dict:
71
+ """Parse LLM recommendations into structured format"""
72
+ return {
73
+ "resource_optimization": [],
74
+ "supply_management": [],
75
+ "equipment_maintenance": [],
76
+ "budget_allocation": [],
77
+ "priority_actions": []
78
+ }# resource_manager node implementation
src/nodes/staff_scheduler.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/staff_scheduler.py
2
+ from typing import Dict, List, Optional, Any
3
+ from typing_extensions import TypedDict # If using TypedDict
4
+ from langchain_core.messages import SystemMessage
5
+ from ..models.state import HospitalState
6
+ from ..config.prompts import PROMPTS
7
+ from ..utils.logger import setup_logger
8
+
9
+ logger = setup_logger(__name__)
10
+
11
+ class StaffSchedulerNode:
12
+ def __init__(self, llm):
13
+ self.llm = llm
14
+ self.system_prompt = PROMPTS["staff_scheduler"]
15
+
16
+ def __call__(self, state: HospitalState) -> Dict:
17
+ try:
18
+ # Get current staffing metrics
19
+ metrics = state["metrics"]["staffing"]
20
+
21
+ # Format prompt with current metrics
22
+ formatted_prompt = self.system_prompt.format(
23
+ staff_available=self._format_staff_availability(metrics),
24
+ department_needs=self._get_department_needs(state),
25
+ skill_requirements=self._format_skill_requirements(metrics),
26
+ work_hours=metrics["overtime_hours"]
27
+ )
28
+
29
+ # Get LLM analysis
30
+ response = self.llm.invoke([
31
+ SystemMessage(content=formatted_prompt)
32
+ ])
33
+
34
+ # Generate scheduling recommendations
35
+ analysis = self._generate_schedule_recommendations(response.content, metrics)
36
+
37
+ return {
38
+ "analysis": analysis,
39
+ "messages": [response],
40
+ "context": {
41
+ "staff_satisfaction": metrics["staff_satisfaction"],
42
+ "skill_mix_index": metrics["skill_mix_index"]
43
+ }
44
+ }
45
+
46
+ except Exception as e:
47
+ logger.error(f"Error in staff scheduling analysis: {str(e)}")
48
+ raise
49
+
50
+ def _format_staff_availability(self, metrics: Dict) -> str:
51
+ """Format staff availability into readable text"""
52
+ return ", ".join([
53
+ f"{role}: {count} available"
54
+ for role, count in metrics["available_staff"].items()
55
+ ])
56
+
57
+ def _get_department_needs(self, state: HospitalState) -> Dict:
58
+ """Get staffing needs by department"""
59
+ return {
60
+ dept: metrics
61
+ for dept, metrics in state["metrics"]["patient_flow"]["department_metrics"].items()
62
+ }
63
+
64
+ def _format_skill_requirements(self, metrics: Dict) -> str:
65
+ """Format skill requirements into readable text"""
66
+ return f"Skill Mix Index: {metrics['skill_mix_index']:.2f}"
67
+
68
+ def _generate_schedule_recommendations(self, response: str, metrics: Dict) -> Dict:
69
+ """Generate scheduling recommendations based on LLM response"""
70
+ return {
71
+ "shift_adjustments": [],
72
+ "staff_assignments": {},
73
+ "overtime_recommendations": [],
74
+ "training_needs": [],
75
+ "efficiency_improvements": []
76
+ }# staff_scheduler node implementation
src/nodes/task_router.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/nodes/task_router.py
2
+ from typing import Literal
3
+ from typing import Dict, List, Optional, Any
4
+ from typing_extensions import TypedDict # If using TypedDict
5
+ from ..models.state import HospitalState, TaskType
6
+ from ..utils.logger import setup_logger
7
+
8
+ logger = setup_logger(__name__)
9
+
10
+ class TaskRouterNode:
11
+ def __call__(self, state: HospitalState) -> Dict:
12
+ """Route to appropriate node based on task type and return state update"""
13
+ try:
14
+ task_type = state["current_task"]
15
+
16
+ # Create base state update
17
+ state_update = {
18
+ "messages": state.get("messages", []),
19
+ "current_task": task_type,
20
+ "priority_level": state.get("priority_level"),
21
+ "context": state.get("context", {})
22
+ }
23
+
24
+ # Add routing information to context
25
+ if task_type == TaskType.PATIENT_FLOW:
26
+ state_update["context"]["next_node"] = "patient_flow"
27
+ elif task_type == TaskType.RESOURCE_MANAGEMENT:
28
+ state_update["context"]["next_node"] = "resource_management"
29
+ elif task_type == TaskType.QUALITY_MONITORING:
30
+ state_update["context"]["next_node"] = "quality_monitoring"
31
+ elif task_type == TaskType.STAFF_SCHEDULING:
32
+ state_update["context"]["next_node"] = "staff_scheduling"
33
+ else:
34
+ state_update["context"]["next_node"] = "output_synthesis"
35
+
36
+ return state_update
37
+
38
+ except Exception as e:
39
+ logger.error(f"Error in task routing: {str(e)}")
40
+ # Return default routing to output synthesis on error
41
+ return {
42
+ "messages": state.get("messages", []),
43
+ "context": {"next_node": "output_synthesis"},
44
+ "current_task": state.get("current_task")
45
+ }
src/tools/__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/tools/__init__.py
2
+ from .patient_tools import PatientTools
3
+ from .resource_tools import ResourceTools
4
+ from .quality_tools import QualityTools
5
+ from .scheduling_tools import SchedulingTools
6
+
7
+ __all__ = [
8
+ 'PatientTools',
9
+ 'ResourceTools',
10
+ 'QualityTools',
11
+ 'SchedulingTools'
12
+ ]
src/tools/patient_tools.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/tools/patient_tools.py
2
+ #from typing import Dict, List, Optional, Any
3
+ from typing_extensions import TypedDict # If using TypedDict
4
+ from typing import Dict, List, Optional
5
+ from langchain_core.tools import tool
6
+ from datetime import datetime
7
+ from ..utils.logger import setup_logger
8
+ from ..models.state import Department
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+ class PatientTools:
13
+ @tool
14
+ def calculate_wait_time(
15
+ self,
16
+ department: str,
17
+ current_queue: int,
18
+ staff_available: int
19
+ ) -> float:
20
+ """Calculate estimated wait time for a department based on queue and staff"""
21
+ try:
22
+ # Average time per patient (in minutes)
23
+ AVG_TIME_PER_PATIENT = 15
24
+
25
+ # Factor in staff availability
26
+ wait_time = (current_queue * AVG_TIME_PER_PATIENT) / max(staff_available, 1)
27
+
28
+ return round(wait_time, 1)
29
+
30
+ except Exception as e:
31
+ logger.error(f"Error calculating wait time: {str(e)}")
32
+ raise
33
+
34
+ @tool
35
+ def analyze_bed_capacity(
36
+ self,
37
+ total_beds: int,
38
+ occupied_beds: int,
39
+ pending_admissions: int
40
+ ) -> Dict:
41
+ """Analyze bed capacity and provide utilization metrics"""
42
+ try:
43
+ capacity = {
44
+ "total_beds": total_beds,
45
+ "occupied_beds": occupied_beds,
46
+ "available_beds": total_beds - occupied_beds,
47
+ "utilization_rate": (occupied_beds / total_beds) * 100,
48
+ "pending_admissions": pending_admissions,
49
+ "status": "Normal"
50
+ }
51
+
52
+ # Determine status based on utilization
53
+ if capacity["utilization_rate"] > 90:
54
+ capacity["status"] = "Critical"
55
+ elif capacity["utilization_rate"] > 80:
56
+ capacity["status"] = "High"
57
+
58
+ return capacity
59
+
60
+ except Exception as e:
61
+ logger.error(f"Error analyzing bed capacity: {str(e)}")
62
+ raise
63
+
64
+ @tool
65
+ def predict_discharge_time(
66
+ self,
67
+ admission_date: datetime,
68
+ condition_type: str,
69
+ department: str
70
+ ) -> datetime:
71
+ """Predict expected discharge time based on condition and department"""
72
+ try:
73
+ # Average length of stay (in days) by condition
74
+ LOS_BY_CONDITION = {
75
+ "routine": 3,
76
+ "acute": 5,
77
+ "critical": 7,
78
+ "emergency": 2
79
+ }
80
+
81
+ # Get base length of stay
82
+ base_los = LOS_BY_CONDITION.get(condition_type.lower(), 4)
83
+
84
+ # Adjust based on department
85
+ if department.lower() == "icu":
86
+ base_los *= 1.5
87
+
88
+ # Calculate expected discharge date
89
+ discharge_date = admission_date + timedelta(days=base_los)
90
+
91
+ return discharge_date
92
+
93
+ except Exception as e:
94
+ logger.error(f"Error predicting discharge time: {str(e)}")
95
+ raise
96
+
97
+ @tool
98
+ def optimize_patient_flow(
99
+ self,
100
+ departments: List[Department],
101
+ waiting_patients: List[Dict]
102
+ ) -> Dict:
103
+ """Optimize patient flow across departments"""
104
+ try:
105
+ optimization_result = {
106
+ "department_recommendations": {},
107
+ "patient_transfers": [],
108
+ "capacity_alerts": []
109
+ }
110
+
111
+ for dept in departments:
112
+ # Calculate department capacity
113
+ utilization = dept["current_occupancy"] / dept["capacity"]
114
+
115
+ if utilization > 0.9:
116
+ optimization_result["capacity_alerts"].append({
117
+ "department": dept["name"],
118
+ "alert": "Critical capacity",
119
+ "utilization": utilization
120
+ })
121
+
122
+ # Recommend transfers if needed
123
+ if utilization > 0.85:
124
+ optimization_result["patient_transfers"].append({
125
+ "from_dept": dept["name"],
126
+ "recommended_transfers": max(1, int((utilization - 0.8) * dept["capacity"]))
127
+ })
128
+
129
+ return optimization_result
130
+
131
+ except Exception as e:
132
+ logger.error(f"Error optimizing patient flow: {str(e)}")
133
+ raise
134
+
135
+ @tool
136
+ def assess_admission_priority(
137
+ self,
138
+ patient_condition: str,
139
+ wait_time: float,
140
+ department_load: float
141
+ ) -> Dict:
142
+ """Assess admission priority based on multiple factors"""
143
+ try:
144
+ # Base priority scores
145
+ CONDITION_SCORES = {
146
+ "critical": 10,
147
+ "urgent": 8,
148
+ "moderate": 5,
149
+ "routine": 3
150
+ }
151
+
152
+ # Calculate priority score
153
+ base_score = CONDITION_SCORES.get(patient_condition.lower(), 3)
154
+ wait_factor = min(wait_time / 30, 2) # Cap wait time factor at 2
155
+ load_penalty = department_load if department_load > 0.8 else 0
156
+
157
+ final_score = base_score + wait_factor - load_penalty
158
+
159
+ return {
160
+ "priority_score": round(final_score, 2),
161
+ "priority_level": "High" if final_score > 7 else "Medium" if final_score > 4 else "Low",
162
+ "factors": {
163
+ "condition_score": base_score,
164
+ "wait_factor": round(wait_factor, 2),
165
+ "load_penalty": round(load_penalty, 2)
166
+ }
167
+ }
168
+
169
+ except Exception as e:
170
+ logger.error(f"Error assessing admission priority: {str(e)}")
171
+ raise# patient_tools implementation
src/tools/quality_tools.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/tools/quality_tools.py
2
+ from typing import Dict, List, Optional, Any
3
+ from typing_extensions import TypedDict # If using TypedDict
4
+ from langchain_core.tools import tool
5
+ from datetime import datetime, timedelta
6
+ from ..utils.logger import setup_logger
7
+
8
+ logger = setup_logger(__name__)
9
+
10
+ class QualityTools:
11
+ @tool
12
+ def analyze_patient_satisfaction(
13
+ self,
14
+ satisfaction_scores: List[float],
15
+ feedback_comments: List[str],
16
+ department: Optional[str] = None
17
+ ) -> Dict:
18
+ """Analyze patient satisfaction scores and feedback"""
19
+ try:
20
+ analysis = {
21
+ "metrics": {
22
+ "average_score": sum(satisfaction_scores) / len(satisfaction_scores),
23
+ "total_responses": len(satisfaction_scores),
24
+ "score_distribution": {},
25
+ "trend": "stable"
26
+ },
27
+ "feedback_analysis": {
28
+ "positive_themes": [],
29
+ "negative_themes": [],
30
+ "improvement_areas": []
31
+ },
32
+ "recommendations": []
33
+ }
34
+
35
+ # Analyze score distribution
36
+ for score in satisfaction_scores:
37
+ category = int(score)
38
+ analysis["metrics"]["score_distribution"][category] = \
39
+ analysis["metrics"]["score_distribution"].get(category, 0) + 1
40
+
41
+ # Basic sentiment analysis of feedback
42
+ positive_keywords = ["great", "excellent", "good", "satisfied", "helpful"]
43
+ negative_keywords = ["poor", "bad", "slow", "unhappy", "dissatisfied"]
44
+
45
+ for comment in feedback_comments:
46
+ comment_lower = comment.lower()
47
+
48
+ # Analyze positive feedback
49
+ for keyword in positive_keywords:
50
+ if keyword in comment_lower:
51
+ analysis["feedback_analysis"]["positive_themes"].append(keyword)
52
+
53
+ # Analyze negative feedback
54
+ for keyword in negative_keywords:
55
+ if keyword in comment_lower:
56
+ analysis["feedback_analysis"]["negative_themes"].append(keyword)
57
+
58
+ # Generate recommendations
59
+ if analysis["metrics"]["average_score"] < 7.0:
60
+ analysis["recommendations"].append("Implement immediate satisfaction improvement plan")
61
+
62
+ return analysis
63
+
64
+ except Exception as e:
65
+ logger.error(f"Error analyzing patient satisfaction: {str(e)}")
66
+ raise
67
+
68
+ @tool
69
+ def monitor_clinical_outcomes(
70
+ self,
71
+ outcomes_data: List[Dict],
72
+ benchmark_metrics: Dict[str, float]
73
+ ) -> Dict:
74
+ """Monitor and analyze clinical outcomes against benchmarks"""
75
+ try:
76
+ analysis = {
77
+ "outcome_metrics": {},
78
+ "benchmark_comparison": {},
79
+ "critical_deviations": [],
80
+ "success_areas": []
81
+ }
82
+
83
+ # Analyze outcomes by category
84
+ for outcome in outcomes_data:
85
+ category = outcome["category"]
86
+ if category not in analysis["outcome_metrics"]:
87
+ analysis["outcome_metrics"][category] = {
88
+ "success_rate": 0,
89
+ "complication_rate": 0,
90
+ "readmission_rate": 0,
91
+ "total_cases": 0
92
+ }
93
+
94
+ # Update metrics
95
+ metrics = analysis["outcome_metrics"][category]
96
+ metrics["total_cases"] += 1
97
+ metrics["success_rate"] = (metrics["success_rate"] * (metrics["total_cases"] - 1) +
98
+ outcome["success"]) / metrics["total_cases"]
99
+
100
+ # Compare with benchmarks
101
+ if category in benchmark_metrics:
102
+ benchmark = benchmark_metrics[category]
103
+ deviation = metrics["success_rate"] - benchmark
104
+
105
+ if deviation < -0.1: # More than 10% below benchmark
106
+ analysis["critical_deviations"].append({
107
+ "category": category,
108
+ "deviation": deviation,
109
+ "current_rate": metrics["success_rate"],
110
+ "benchmark": benchmark
111
+ })
112
+ elif deviation > 0.05: # More than 5% above benchmark
113
+ analysis["success_areas"].append({
114
+ "category": category,
115
+ "improvement": deviation,
116
+ "current_rate": metrics["success_rate"]
117
+ })
118
+
119
+ return analysis
120
+
121
+ except Exception as e:
122
+ logger.error(f"Error monitoring clinical outcomes: {str(e)}")
123
+ raise
124
+
125
+ @tool
126
+ def track_compliance_metrics(
127
+ self,
128
+ compliance_data: List[Dict],
129
+ audit_period: str
130
+ ) -> Dict:
131
+ """Track and analyze compliance with medical standards and regulations"""
132
+ try:
133
+ analysis = {
134
+ "compliance_rate": 0,
135
+ "violations": [],
136
+ "risk_areas": [],
137
+ "audit_summary": {
138
+ "period": audit_period,
139
+ "total_checks": len(compliance_data),
140
+ "passed_checks": 0,
141
+ "failed_checks": 0
142
+ }
143
+ }
144
+
145
+ # Analyze compliance checks
146
+ for check in compliance_data:
147
+ if check["compliant"]:
148
+ analysis["audit_summary"]["passed_checks"] += 1
149
+ else:
150
+ analysis["audit_summary"]["failed_checks"] += 1
151
+ analysis["violations"].append({
152
+ "standard": check["standard"],
153
+ "severity": check["severity"],
154
+ "date": check["date"]
155
+ })
156
+
157
+ # Identify risk areas
158
+ if check["severity"] == "high" or check.get("repeat_violation", False):
159
+ analysis["risk_areas"].append({
160
+ "area": check["standard"],
161
+ "risk_level": "high",
162
+ "recommendations": ["Immediate action required",
163
+ "Staff training needed"]
164
+ })
165
+
166
+ # Calculate overall compliance rate
167
+ total_checks = analysis["audit_summary"]["total_checks"]
168
+ if total_checks > 0:
169
+ analysis["compliance_rate"] = (analysis["audit_summary"]["passed_checks"] /
170
+ total_checks * 100)
171
+
172
+ return analysis
173
+
174
+ except Exception as e:
175
+ logger.error(f"Error tracking compliance metrics: {str(e)}")
176
+ raise# quality_tools implementation
src/tools/resource_tools.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/tools/resource_tools.py
2
+ #from typing import Dict, List
3
+ from typing import Dict, List, Optional, Any
4
+ from typing_extensions import TypedDict # If using TypedDict
5
+ from langchain_core.tools import tool
6
+ from ..utils.logger import setup_logger
7
+
8
+ logger = setup_logger(__name__)
9
+
10
+ class ResourceTools:
11
+ @tool
12
+ def analyze_supply_levels(
13
+ self,
14
+ current_inventory: Dict[str, float],
15
+ consumption_rate: Dict[str, float],
16
+ reorder_thresholds: Dict[str, float]
17
+ ) -> Dict:
18
+ """Analyze supply levels and generate reorder recommendations"""
19
+ try:
20
+ analysis = {
21
+ "critical_items": [],
22
+ "reorder_needed": [],
23
+ "adequate_supplies": [],
24
+ "recommendations": []
25
+ }
26
+
27
+ for item, level in current_inventory.items():
28
+ threshold = reorder_thresholds.get(item, 0.2)
29
+ consumption = consumption_rate.get(item, 0)
30
+
31
+ # Days of supply remaining
32
+ days_remaining = level / consumption if consumption > 0 else float('inf')
33
+
34
+ if level <= threshold:
35
+ if days_remaining < 2:
36
+ analysis["critical_items"].append({
37
+ "item": item,
38
+ "current_level": level,
39
+ "days_remaining": days_remaining
40
+ })
41
+ else:
42
+ analysis["reorder_needed"].append({
43
+ "item": item,
44
+ "current_level": level,
45
+ "days_remaining": days_remaining
46
+ })
47
+ else:
48
+ analysis["adequate_supplies"].append(item)
49
+
50
+ return analysis
51
+
52
+ except Exception as e:
53
+ logger.error(f"Error analyzing supply levels: {str(e)}")
54
+ raise
55
+
56
+ @tool
57
+ def track_equipment_utilization(
58
+ self,
59
+ equipment_logs: List[Dict],
60
+ equipment_capacity: Dict[str, int]
61
+ ) -> Dict:
62
+ """Track and analyze equipment utilization rates"""
63
+ try:
64
+ utilization = {
65
+ "equipment_stats": {},
66
+ "underutilized": [],
67
+ "optimal": [],
68
+ "overutilized": []
69
+ }
70
+
71
+ for equip, capacity in equipment_capacity.items():
72
+ usage = len([log for log in equipment_logs if log["equipment"] == equip])
73
+ utilization_rate = usage / capacity
74
+
75
+ utilization["equipment_stats"][equip] = {
76
+ "usage": usage,
77
+ "capacity": capacity,
78
+ "utilization_rate": utilization_rate
79
+ }
80
+
81
+ if utilization_rate < 0.3:
82
+ utilization["underutilized"].append(equip)
83
+ elif utilization_rate > 0.8:
84
+ utilization["overutilized"].append(equip)
85
+ else:
86
+ utilization["optimal"].append(equip)
87
+
88
+ return utilization
89
+
90
+ except Exception as e:
91
+ logger.error(f"Error tracking equipment utilization: {str(e)}")
92
+ raise
93
+
94
+ @tool
95
+ def optimize_resource_allocation(
96
+ self,
97
+ department_demands: Dict[str, Dict],
98
+ available_resources: Dict[str, int]
99
+ ) -> Dict:
100
+ """Optimize resource allocation across departments"""
101
+ try:
102
+ allocation = {
103
+ "recommended_distribution": {},
104
+ "unmet_demands": [],
105
+ "resource_sharing": []
106
+ }
107
+
108
+ total_demand = sum(dept["demand"] for dept in department_demands.values())
109
+
110
+ for dept, demand in department_demands.items():
111
+ # Calculate fair share based on demand
112
+ for resource, available in available_resources.items():
113
+ dept_share = int((demand["demand"] / total_demand) * available)
114
+
115
+ allocation["recommended_distribution"][dept] = {
116
+ resource: dept_share
117
+ }
118
+
119
+ if dept_share < demand.get("minimum", 0):
120
+ allocation["unmet_demands"].append({
121
+ "department": dept,
122
+ "resource": resource,
123
+ "shortfall": demand["minimum"] - dept_share
124
+ })
125
+
126
+ return allocation
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error optimizing resource allocation: {str(e)}")
130
+ raise# resource_tools implementation
src/tools/scheduling_tools.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/tools/scheduling_tools.py
2
+ #from typing import Dict, List, Optional
3
+ from typing import Dict, List, Optional, Any
4
+ from typing_extensions import TypedDict # If using TypedDict
5
+ from langchain_core.tools import tool
6
+ from datetime import datetime, timedelta
7
+ from ..utils.logger import setup_logger
8
+
9
+ logger = setup_logger(__name__)
10
+
11
+ class SchedulingTools:
12
+ @tool
13
+ def optimize_staff_schedule(
14
+ self,
15
+ staff_availability: List[Dict],
16
+ department_needs: Dict[str, Dict],
17
+ shift_preferences: Optional[List[Dict]] = None
18
+ ) -> Dict:
19
+ """Generate optimized staff schedules based on availability and department needs"""
20
+ try:
21
+ schedule = {
22
+ "shifts": {},
23
+ "coverage_gaps": [],
24
+ "recommendations": [],
25
+ "staff_assignments": {}
26
+ }
27
+
28
+ # Process each department's needs
29
+ for dept, needs in department_needs.items():
30
+ schedule["shifts"][dept] = {
31
+ "morning": [],
32
+ "afternoon": [],
33
+ "night": []
34
+ }
35
+
36
+ required_staff = needs.get("required_staff", {})
37
+
38
+ # Match available staff to shifts
39
+ for staff in staff_availability:
40
+ if staff["department"] == dept and staff["available"]:
41
+ preferred_shift = "morning" # Default
42
+ if shift_preferences:
43
+ for pref in shift_preferences:
44
+ if pref["staff_id"] == staff["id"]:
45
+ preferred_shift = pref["preferred_shift"]
46
+
47
+ schedule["shifts"][dept][preferred_shift].append(staff["id"])
48
+
49
+ # Identify coverage gaps
50
+ for shift in ["morning", "afternoon", "night"]:
51
+ required = required_staff.get(shift, 0)
52
+ assigned = len(schedule["shifts"][dept][shift])
53
+
54
+ if assigned < required:
55
+ schedule["coverage_gaps"].append({
56
+ "department": dept,
57
+ "shift": shift,
58
+ "shortage": required - assigned
59
+ })
60
+
61
+ return schedule
62
+
63
+ except Exception as e:
64
+ logger.error(f"Error optimizing staff schedule: {str(e)}")
65
+ raise
66
+
67
+ @tool
68
+ def analyze_workforce_metrics(
69
+ self,
70
+ staff_data: List[Dict],
71
+ time_period: str
72
+ ) -> Dict:
73
+ """Analyze workforce metrics including overtime, satisfaction, and skill mix"""
74
+ try:
75
+ analysis = {
76
+ "workforce_metrics": {
77
+ "total_staff": len(staff_data),
78
+ "overtime_hours": 0,
79
+ "skill_distribution": {},
80
+ "satisfaction_score": 0,
81
+ "turnover_rate": 0
82
+ },
83
+ "recommendations": []
84
+ }
85
+
86
+ total_satisfaction = 0
87
+ total_overtime = 0
88
+
89
+ for staff in staff_data:
90
+ # Analyze overtime
91
+ total_overtime += staff.get("overtime_hours", 0)
92
+
93
+ # Track skill distribution
94
+ role = staff.get("role", "unknown")
95
+ analysis["workforce_metrics"]["skill_distribution"][role] = \
96
+ analysis["workforce_metrics"]["skill_distribution"].get(role, 0) + 1
97
+
98
+ # Track satisfaction
99
+ total_satisfaction += staff.get("satisfaction_score", 0)
100
+
101
+ # Calculate averages
102
+ if staff_data:
103
+ analysis["workforce_metrics"]["overtime_hours"] = total_overtime / len(staff_data)
104
+ analysis["workforce_metrics"]["satisfaction_score"] = \
105
+ total_satisfaction / len(staff_data)
106
+
107
+ # Generate recommendations
108
+ if analysis["workforce_metrics"]["overtime_hours"] > 10:
109
+ analysis["recommendations"].append("Reduce overtime hours through better scheduling")
110
+
111
+ if analysis["workforce_metrics"]["satisfaction_score"] < 7:
112
+ analysis["recommendations"].append("Implement staff satisfaction improvement measures")
113
+
114
+ return analysis
115
+
116
+ except Exception as e:
117
+ logger.error(f"Error analyzing workforce metrics: {str(e)}")
118
+ raise
119
+
120
+ @tool
121
+ def calculate_staffing_needs(
122
+ self,
123
+ patient_census: Dict[str, int],
124
+ acuity_levels: Dict[str, float],
125
+ staff_ratios: Dict[str, float]
126
+ ) -> Dict:
127
+ """Calculate staffing needs based on patient census and acuity"""
128
+ try:
129
+ staffing_needs = {
130
+ "required_staff": {},
131
+ "current_gaps": {},
132
+ "recommendations": []
133
+ }
134
+
135
+ for department, census in patient_census.items():
136
+ # Calculate base staffing need
137
+ acuity = acuity_levels.get(department, 1.0)
138
+ ratio = staff_ratios.get(department, 4) # default 1:4 ratio
139
+
140
+ required_staff = ceil(census * acuity / ratio)
141
+
142
+ staffing_needs["required_staff"][department] = {
143
+ "total_needed": required_staff,
144
+ "acuity_factor": acuity,
145
+ "patient_ratio": ratio
146
+ }
147
+
148
+ # Generate staffing recommendations
149
+ if required_staff > current_staff.get(department, 0):
150
+ staffing_needs["recommendations"].append({
151
+ "department": department,
152
+ "action": "increase_staff",
153
+ "amount": required_staff - current_staff.get(department, 0)
154
+ })
155
+
156
+ return staffing_needs
157
+
158
+ except Exception as e:
159
+ logger.error(f"Error calculating staffing needs: {str(e)}")
160
+ raise# scheduling_tools implementation
src/ui/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .app import HealthcareUI
2
+
3
+ __all__ = ['HealthcareUI']
src/ui/app.py ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from datetime import datetime
3
+ from typing import Optional, Dict, Any
4
+ import os
5
+
6
+ from ..agent import HealthcareAgent
7
+ from ..models.state import TaskType, PriorityLevel
8
+ from ..utils.logger import setup_logger
9
+
10
+ logger = setup_logger(__name__)
11
+
12
+ class HealthcareUI:
13
+ def __init__(self):
14
+ """Initialize the Healthcare Operations Management UI"""
15
+ try:
16
+ # Set up Streamlit page configuration
17
+ st.set_page_config(
18
+ page_title="Healthcare Operations Assistant",
19
+ page_icon="🏥",
20
+ layout="wide",
21
+ initial_sidebar_state="expanded",
22
+ menu_items={
23
+ 'About': "Healthcare Operations Management AI Assistant",
24
+ 'Report a bug': "https://github.com/yourusername/repo/issues",
25
+ 'Get Help': "https://your-docs-url"
26
+ }
27
+ )
28
+
29
+ # Apply custom theme
30
+ self.setup_theme()
31
+
32
+ # Initialize the agent
33
+ self.agent = HealthcareAgent(os.getenv("OPENAI_API_KEY"))
34
+
35
+ # Initialize session state variables only if not already set
36
+ if 'initialized' not in st.session_state:
37
+ st.session_state.initialized = True
38
+ st.session_state.messages = []
39
+ st.session_state.thread_id = datetime.now().strftime("%Y%m%d-%H%M%S")
40
+ st.session_state.current_department = "All Departments"
41
+ st.session_state.metrics_history = []
42
+ st.session_state.system_status = True
43
+
44
+ except Exception as e:
45
+ logger.error(f"Error initializing UI: {str(e)}")
46
+ st.error("Failed to initialize the application. Please refresh the page.")
47
+
48
+ def setup_theme(self):
49
+ """Configure the UI theme and styling"""
50
+ st.markdown("""
51
+ <style>
52
+ /* Main background */
53
+ .stApp {
54
+ background-color: #f0f8ff;
55
+ }
56
+
57
+ /* Headers */
58
+ h1, h2, h3 {
59
+ color: #2c3e50;
60
+ }
61
+
62
+ /* Chat messages */
63
+ .user-message {
64
+ background-color: #e3f2fd;
65
+ padding: 1rem;
66
+ border-radius: 10px;
67
+ margin: 1rem 0;
68
+ border-left: 5px solid #1976d2;
69
+ }
70
+
71
+ .assistant-message {
72
+ background-color: #fff;
73
+ padding: 1rem;
74
+ border-radius: 10px;
75
+ margin: 1rem 0;
76
+ border-left: 5px solid #4caf50;
77
+ }
78
+
79
+ /* Metrics cards */
80
+ .metric-card {
81
+ background-color: white;
82
+ padding: 1rem;
83
+ border-radius: 10px;
84
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
85
+ transition: transform 0.2s ease;
86
+ }
87
+
88
+ .metric-card:hover {
89
+ transform: translateY(-2px);
90
+ box-shadow: 0 4px 8px rgba(0,0,0,0.1);
91
+ }
92
+
93
+ /* Custom button styling */
94
+ .stButton>button {
95
+ background-color: #2196f3;
96
+ color: white;
97
+ border-radius: 20px;
98
+ padding: 0.5rem 2rem;
99
+ transition: all 0.3s ease;
100
+ }
101
+
102
+ .stButton>button:hover {
103
+ background-color: #1976d2;
104
+ transform: translateY(-1px);
105
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
106
+ }
107
+ </style>
108
+ """, unsafe_allow_html=True)
109
+
110
+ def render_header(self):
111
+ """Render the application header"""
112
+ try:
113
+ header_container = st.container()
114
+ with header_container:
115
+ col1, col2, col3 = st.columns([1, 4, 1])
116
+
117
+ with col1:
118
+ st.markdown("# 🏥")
119
+
120
+ with col2:
121
+ st.title("Healthcare Operations Assistant")
122
+ st.markdown("*Your AI-powered healthcare operations management solution* 🤖")
123
+
124
+ with col3:
125
+ # System status indicator
126
+ status = "🟢 Online" if st.session_state.system_status else "🔴 Offline"
127
+ st.markdown(f"### {status}")
128
+
129
+ except Exception as e:
130
+ logger.error(f"Error rendering header: {str(e)}")
131
+ st.error("Error loading header section")
132
+
133
+ def render_metrics(self, metrics: Optional[Dict[str, Any]] = None):
134
+ """Render the metrics dashboard"""
135
+ try:
136
+ if not metrics:
137
+ metrics = {
138
+ "patient_flow": {"occupied_beds": 75, "total_beds": 100},
139
+ "quality": {"patient_satisfaction": 8.5},
140
+ "staffing": {"available_staff": {"doctors": 20, "nurses": 50}},
141
+ "resources": {"resource_utilization": 0.75}
142
+ }
143
+
144
+ st.markdown("### 📊 Key Metrics Dashboard")
145
+ metrics_container = st.container()
146
+
147
+ with metrics_container:
148
+ # First row - Key metrics
149
+ col1, col2, col3, col4 = st.columns(4)
150
+
151
+ with col1:
152
+ occupancy = (metrics['patient_flow']['occupied_beds'] /
153
+ metrics['patient_flow']['total_beds'] * 100)
154
+ st.metric(
155
+ "Bed Occupancy 🛏️",
156
+ f"{occupancy:.1f}%",
157
+ "Normal 🟢" if occupancy < 85 else "High 🟡"
158
+ )
159
+
160
+ with col2:
161
+ satisfaction = metrics['quality']['patient_satisfaction']
162
+ st.metric(
163
+ "Patient Satisfaction 😊",
164
+ f"{satisfaction}/10",
165
+ "↗ +0.5" if satisfaction > 8 else "↘ -0.3"
166
+ )
167
+
168
+ with col3:
169
+ total_staff = sum(metrics['staffing']['available_staff'].values())
170
+ st.metric(
171
+ "Available Staff 👥",
172
+ total_staff,
173
+ "Optimal 🟢" if total_staff > 80 else "Low 🔴"
174
+ )
175
+
176
+ with col4:
177
+ utilization = metrics['resources']['resource_utilization'] * 100
178
+ st.metric(
179
+ "Resource Utilization 📦",
180
+ f"{utilization:.1f}%",
181
+ "↘ -2%"
182
+ )
183
+
184
+ # Add metrics to history
185
+ st.session_state.metrics_history.append({
186
+ 'timestamp': datetime.now(),
187
+ 'metrics': metrics
188
+ })
189
+
190
+ except Exception as e:
191
+ logger.error(f"Error rendering metrics: {str(e)}")
192
+ st.error("Error loading metrics dashboard")
193
+
194
+ def render_chat(self):
195
+ """Render the chat interface"""
196
+ try:
197
+ st.markdown("### 💬 Chat Interface")
198
+ chat_container = st.container()
199
+
200
+ with chat_container:
201
+ # Display chat messages
202
+ for message in st.session_state.messages:
203
+ role = message["role"]
204
+ content = message["content"]
205
+ timestamp = message.get("timestamp", datetime.now())
206
+
207
+ with st.chat_message(role, avatar="🤖" if role == "assistant" else "👤"):
208
+ st.markdown(content)
209
+ st.caption(f":clock2: {timestamp.strftime('%H:%M')}")
210
+
211
+ # Chat input
212
+ if prompt := st.chat_input("How can I assist you with healthcare operations today?"):
213
+ # Add user message
214
+ current_time = datetime.now()
215
+ st.session_state.messages.append({
216
+ "role": "user",
217
+ "content": prompt,
218
+ "timestamp": current_time
219
+ })
220
+
221
+ # Display user message
222
+ with st.chat_message("user", avatar="👤"):
223
+ st.markdown(prompt)
224
+ st.caption(f":clock2: {current_time.strftime('%H:%M')}")
225
+
226
+ # Display assistant response
227
+ with st.chat_message("assistant", avatar="🤖"):
228
+ with st.spinner("Processing your request... 🔄"):
229
+ try:
230
+ # Generate response based on query type
231
+ response = self._get_department_response(prompt)
232
+
233
+ # Display structured response
234
+ st.markdown("### 🔍 Key Insights")
235
+ st.markdown(response["insights"])
236
+
237
+ st.markdown("### 📋 Actionable Recommendations")
238
+ st.markdown(response["recommendations"])
239
+
240
+ st.markdown("### ⚡ Priority Actions")
241
+ st.markdown(response["priority_actions"])
242
+
243
+ st.markdown("### ⏰ Implementation Timeline")
244
+ st.markdown(response["timeline"])
245
+
246
+ # Update metrics if available
247
+ if "metrics" in response:
248
+ self.render_metrics(response["metrics"])
249
+
250
+ # Add to chat history
251
+ st.session_state.messages.append({
252
+ "role": "assistant",
253
+ "content": response["full_response"],
254
+ "timestamp": datetime.now()
255
+ })
256
+
257
+ except Exception as e:
258
+ st.error(f"Error processing request: {str(e)} ❌")
259
+ logger.error(f"Error in chat processing: {str(e)}")
260
+
261
+ except Exception as e:
262
+ logger.error(f"Error rendering chat interface: {str(e)}")
263
+ st.error("Error loading chat interface")
264
+
265
+ def _get_department_response(self, query: str) -> Dict[str, Any]:
266
+ """Generate response based on query type"""
267
+ query = query.lower()
268
+
269
+ # Waiting times response
270
+ if "waiting" in query or "wait time" in query:
271
+ return {
272
+ "insights": """
273
+ 📊 Current Department Wait Times:
274
+ - ER: 45 minutes (⚠️ Above target)
275
+ - ICU: 5 minutes (✅ Within target)
276
+ - General Ward: 25 minutes (✅ Within target)
277
+ - Surgery: 30 minutes (⚡ Approaching target)
278
+ - Pediatrics: 20 minutes (✅ Within target)
279
+ """,
280
+ "recommendations": """
281
+ 1. 👥 Deploy additional triage nurses to ER
282
+ 2. 🔄 Optimize patient handoff procedures
283
+ 3. 📱 Implement real-time wait time updates
284
+ 4. 🏥 Activate overflow protocols where needed
285
+ """,
286
+ "priority_actions": """
287
+ Immediate Actions Required:
288
+ - 🚨 Redirect non-emergency cases from ER
289
+ - 👨‍⚕️ Increase ER staffing for next 2 hours
290
+ - 📢 Update waiting patients every 15 minutes
291
+ """,
292
+ "timeline": """
293
+ Implementation Schedule:
294
+ - 🕐 0-1 hour: Staff reallocation
295
+ - 🕒 1-2 hours: Process optimization
296
+ - 🕓 2-4 hours: Situation reassessment
297
+ - 🕔 4+ hours: Long-term monitoring
298
+ """,
299
+ "metrics": {
300
+ "patient_flow": {
301
+ "occupied_beds": 85,
302
+ "total_beds": 100,
303
+ "waiting_patients": 18,
304
+ "average_wait_time": 35.0
305
+ },
306
+ "quality": {"patient_satisfaction": 7.8},
307
+ "staffing": {"available_staff": {"doctors": 22, "nurses": 55}},
308
+ "resources": {"resource_utilization": 0.82}
309
+ },
310
+ "full_response": "Based on current data, we're seeing elevated wait times in the ER department. Immediate actions have been recommended to address this situation."
311
+ }
312
+
313
+ # Bed occupancy response
314
+ elif "bed" in query or "occupancy" in query:
315
+ return {
316
+ "insights": """
317
+ 🛏️ Current Bed Occupancy Status:
318
+ - Overall Occupancy: 85%
319
+ - Critical Care: 90% (⚠️ Near capacity)
320
+ - General Wards: 82% (✅ Optimal)
321
+ - Available Emergency Beds: 5
322
+ """,
323
+ "recommendations": """
324
+ 1. 🔄 Review discharge plans
325
+ 2. 🏥 Prepare overflow areas
326
+ 3. 📋 Optimize bed turnover
327
+ 4. 👥 Adjust staff allocation
328
+ """,
329
+ "priority_actions": """
330
+ Critical Actions:
331
+ - 🚨 Expedite planned discharges
332
+ - 🏥 Activate surge capacity plan
333
+ - 📊 Hourly capacity monitoring
334
+ """,
335
+ "timeline": """
336
+ Action Timeline:
337
+ - 🕐 Immediate: Discharge reviews
338
+ - 🕑 2 hours: Capacity reassessment
339
+ - 🕒 4 hours: Staff reallocation
340
+ - 🕓 8 hours: Full situation review
341
+ """,
342
+ "metrics": {
343
+ "patient_flow": {
344
+ "occupied_beds": 90,
345
+ "total_beds": 100,
346
+ "waiting_patients": 12,
347
+ "average_wait_time": 30.0
348
+ },
349
+ "quality": {"patient_satisfaction": 8.0},
350
+ "staffing": {"available_staff": {"doctors": 25, "nurses": 58}},
351
+ "resources": {"resource_utilization": 0.88}
352
+ },
353
+ "full_response": "Current bed occupancy is at 85% with critical care areas approaching capacity. Immediate actions are being taken to optimize bed utilization."
354
+ }
355
+
356
+ # Default response for other queries
357
+ else:
358
+ return {
359
+ "insights": """
360
+ Please specify your request:
361
+ - 🏥 Department specific information
362
+ - ⏰ Wait time inquiries
363
+ - 🛏️ Bed capacity status
364
+ - 👥 Staffing information
365
+ - 📊 Resource utilization
366
+ """,
367
+ "recommendations": "To better assist you, please provide more specific details about what you'd like to know.",
368
+ "priority_actions": "No immediate actions required. Awaiting specific inquiry.",
369
+ "timeline": "Timeline will be generated based on specific requests.",
370
+ "full_response": "I'm here to help! Please specify what information you need about healthcare operations."
371
+ }
372
+
373
+ def render_sidebar(self):
374
+ """Render the sidebar with controls and filters"""
375
+ try:
376
+ with st.sidebar:
377
+ # Add custom CSS for consistent button styling
378
+ st.markdown("""
379
+ <style>
380
+ /* Container for Quick Actions */
381
+ .quick-actions-container {
382
+ display: flex;
383
+ gap: 10px;
384
+ margin: 10px 0;
385
+ }
386
+
387
+ /* Button styling */
388
+ .stButton > button {
389
+ width: 120px !important; /* Fixed width for both buttons */
390
+ height: 46px !important; /* Fixed height for both buttons */
391
+ background-color: #2196f3;
392
+ color: white;
393
+ border-radius: 20px;
394
+ border: none;
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ padding: 0 20px;
399
+ font-size: 0.9rem;
400
+ transition: all 0.3s ease;
401
+ margin: 0;
402
+ }
403
+
404
+ .stButton > button:hover {
405
+ background-color: #1976d2;
406
+ transform: translateY(-1px);
407
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
408
+ }
409
+
410
+ /* Column container fixes */
411
+ div[data-testid="column"] {
412
+ padding: 0 !important;
413
+ display: flex;
414
+ justify-content: center;
415
+ }
416
+ </style>
417
+ """, unsafe_allow_html=True)
418
+
419
+ st.markdown("### ⚙️ Settings")
420
+
421
+ # Department filter
422
+ if "department_filter" not in st.session_state:
423
+ st.session_state.department_filter = "All Departments"
424
+
425
+ st.selectbox(
426
+ "Select Department",
427
+ ["All Departments", "ER", "ICU", "General Ward", "Surgery", "Pediatrics"],
428
+ key="department_filter"
429
+ )
430
+
431
+ # Priority filter
432
+ if "priority_filter" not in st.session_state:
433
+ st.session_state.priority_filter = "Medium"
434
+
435
+ st.select_slider(
436
+ "Priority Level",
437
+ options=["Low", "Medium", "High", "Urgent", "Critical"],
438
+ key="priority_filter"
439
+ )
440
+
441
+ # Time range
442
+ if "time_range_filter" not in st.session_state:
443
+ st.session_state.time_range_filter = 8
444
+
445
+ st.slider(
446
+ "Time Range (hours)",
447
+ min_value=1,
448
+ max_value=24,
449
+ key="time_range_filter"
450
+ )
451
+
452
+ # Quick actions with consistent styling
453
+ st.markdown("### ⚡ Quick Actions")
454
+
455
+ # Create two columns for buttons
456
+ col1, col2 = st.columns(2)
457
+
458
+ with col1:
459
+ if st.button("📊 Report"):
460
+ st.info("Generating comprehensive report...")
461
+
462
+ with col2:
463
+ if st.button("🔄 Refresh"):
464
+ st.success("Data refreshed successfully!")
465
+
466
+ # Emergency Mode
467
+ st.markdown("### 🚨 Emergency Mode")
468
+
469
+ if "emergency_mode" not in st.session_state:
470
+ st.session_state.emergency_mode = False
471
+
472
+ st.toggle(
473
+ "Activate Emergency Protocol",
474
+ key="emergency_mode",
475
+ help="Enable emergency mode for critical situations"
476
+ )
477
+
478
+ if st.session_state.emergency_mode:
479
+ st.warning("Emergency Mode Active!")
480
+
481
+ # Help section
482
+ st.markdown("### ❓ Help")
483
+ with st.expander("Usage Guide"):
484
+ st.markdown("""
485
+ - 💬 Use the chat to ask questions
486
+ - 📊 Monitor real-time metrics
487
+ - ⚙️ Adjust filters as needed
488
+ - 📋 Generate reports for analysis
489
+ - 🚨 Toggle emergency mode for critical situations
490
+ """)
491
+
492
+ # Footer
493
+ st.markdown("---")
494
+ st.caption(
495
+ f"*Last updated: {datetime.now().strftime('%H:%M:%S')}*"
496
+ )
497
+
498
+ except Exception as e:
499
+ logger.error(f"Error rendering sidebar: {str(e)}")
500
+ st.error("Error loading sidebar")
501
+
502
+ def run(self):
503
+ """Run the Streamlit application"""
504
+ try:
505
+ # Main application container
506
+ main_container = st.container()
507
+
508
+ with main_container:
509
+ # Render components
510
+ self.render_header()
511
+ self.render_sidebar()
512
+
513
+ # Main content area
514
+ content_container = st.container()
515
+ with content_container:
516
+ self.render_metrics()
517
+ st.markdown("<br>", unsafe_allow_html=True) # Spacing
518
+ self.render_chat()
519
+
520
+ except Exception as e:
521
+ logger.error(f"Error running application: {str(e)}")
522
+ st.error(f"Application error: {str(e)} ❌")
src/ui/assets/icons/.gitkeep ADDED
File without changes
src/ui/assets/images/.gitkeep ADDED
File without changes
src/ui/components/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .chat import ChatComponent
2
+ from .metrics import MetricsComponent
3
+ from .sidebar import SidebarComponent
4
+ from .header import HeaderComponent
5
+
6
+ __all__ = [
7
+ 'ChatComponent',
8
+ 'MetricsComponent',
9
+ 'SidebarComponent',
10
+ 'HeaderComponent'
11
+ ]
src/ui/components/chat.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from typing import Optional, Dict, Callable
3
+ from datetime import datetime
4
+
5
+ class ChatComponent:
6
+ def __init__(self, process_message_callback: Callable):
7
+ """
8
+ Initialize the chat component
9
+
10
+ Args:
11
+ process_message_callback: Callback function to process messages
12
+ """
13
+ self.process_message = process_message_callback
14
+
15
+ # Initialize session state for messages if not exists
16
+ if 'messages' not in st.session_state:
17
+ st.session_state.messages = []
18
+
19
+ def _display_message(self, role: str, content: str, timestamp: Optional[datetime] = None):
20
+ """Display a single chat message"""
21
+ avatar = "🤖" if role == "assistant" else "👤"
22
+ with st.chat_message(role, avatar=avatar):
23
+ st.markdown(content)
24
+ if timestamp:
25
+ st.caption(f":clock2: {timestamp.strftime('%H:%M')}")
26
+
27
+ def render(self):
28
+ """Render the chat interface"""
29
+ st.markdown("### 💬 Healthcare Operations Chat")
30
+
31
+ # Display chat messages
32
+ for message in st.session_state.messages:
33
+ self._display_message(
34
+ role=message["role"],
35
+ content=message["content"],
36
+ timestamp=message.get("timestamp")
37
+ )
38
+
39
+ # Chat input
40
+ if prompt := st.chat_input(
41
+ "Ask about patient flow, resources, quality metrics, or staff scheduling..."
42
+ ):
43
+ # Add user message
44
+ current_time = datetime.now()
45
+ st.session_state.messages.append({
46
+ "role": "user",
47
+ "content": prompt,
48
+ "timestamp": current_time
49
+ })
50
+
51
+ # Display user message
52
+ self._display_message("user", prompt, current_time)
53
+
54
+ # Process message and get response
55
+ with st.spinner("Processing your request... 🔄"):
56
+ try:
57
+ response = self.process_message(prompt)
58
+
59
+ # Add and display assistant response
60
+ st.session_state.messages.append({
61
+ "role": "assistant",
62
+ "content": response["response"],
63
+ "timestamp": datetime.now()
64
+ })
65
+
66
+ self._display_message(
67
+ "assistant",
68
+ response["response"],
69
+ datetime.now()
70
+ )
71
+
72
+ except Exception as e:
73
+ st.error(f"Error processing your request: {str(e)} ❌")
74
+
75
+ def clear_chat(self):
76
+ """Clear the chat history"""
77
+ st.session_state.messages = []
78
+ st.success("Chat history cleared! 🧹")
src/ui/components/header.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from datetime import datetime
3
+
4
+ class HeaderComponent:
5
+ def __init__(self):
6
+ """Initialize the header component"""
7
+ # Initialize session state for notifications if not exists
8
+ if 'notifications' not in st.session_state:
9
+ st.session_state.notifications = []
10
+
11
+ def _add_notification(self, message: str, type: str = "info"):
12
+ """Add a notification to the session state"""
13
+ st.session_state.notifications.append({
14
+ "message": message,
15
+ "type": type,
16
+ "timestamp": datetime.now()
17
+ })
18
+
19
+ def render(self):
20
+ """Render the header"""
21
+ # Main header container
22
+ header_container = st.container()
23
+
24
+ with header_container:
25
+ # Top row with logo and title
26
+ col1, col2, col3 = st.columns([1, 4, 1])
27
+
28
+ with col1:
29
+ st.markdown("# 🏥")
30
+
31
+ with col2:
32
+ st.title("Healthcare Operations Assistant")
33
+ st.markdown("""
34
+ <div style='padding: 0.5rem 0; color: #4a4a4a;'>
35
+ *AI-Powered Healthcare Management System* 🤖
36
+ </div>
37
+ """, unsafe_allow_html=True)
38
+
39
+ with col3:
40
+ # Status indicator
41
+ status = "🟢 Online" if st.session_state.get('system_status', True) else "🔴 Offline"
42
+ st.markdown(f"### {status}")
43
+
44
+ # Notification area
45
+ if st.session_state.notifications:
46
+ with st.expander("📬 Notifications", expanded=True):
47
+ for notif in st.session_state.notifications[-3:]: # Show last 3
48
+ if notif["type"] == "info":
49
+ st.info(notif["message"])
50
+ elif notif["type"] == "warning":
51
+ st.warning(notif["message"])
52
+ elif notif["type"] == "error":
53
+ st.error(notif["message"])
54
+ elif notif["type"] == "success":
55
+ st.success(notif["message"])
56
+
57
+ # System status bar
58
+ status_cols = st.columns(4)
59
+ with status_cols[0]:
60
+ st.markdown("**System Status:** Operational ✅")
61
+ with status_cols[1]:
62
+ st.markdown("**API Status:** Connected 🔗")
63
+ with status_cols[2]:
64
+ st.markdown("**Load:** Normal 📊")
65
+ with status_cols[3]:
66
+ st.markdown(f"**Last Update:** {datetime.now().strftime('%H:%M')} 🕒")
67
+
68
+ # Divider
69
+ st.markdown("---")
70
+
71
+ def add_notification(self, message: str, type: str = "info"):
72
+ """Public method to add notifications"""
73
+ self._add_notification(message, type)
src/ui/components/metrics.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from typing import Dict, Any, Optional
3
+
4
+ class MetricsComponent:
5
+ def __init__(self):
6
+ """Initialize the metrics component"""
7
+ self.default_metrics = {
8
+ "patient_flow": {
9
+ "occupied_beds": 75,
10
+ "total_beds": 100,
11
+ "waiting_time": 15,
12
+ "discharge_rate": 8
13
+ },
14
+ "quality": {
15
+ "patient_satisfaction": 8.5,
16
+ "compliance_rate": 0.95,
17
+ "incident_count": 2
18
+ },
19
+ "staffing": {
20
+ "available_staff": {
21
+ "doctors": 20,
22
+ "nurses": 50,
23
+ "specialists": 15
24
+ },
25
+ "shift_coverage": 0.92
26
+ },
27
+ "resources": {
28
+ "resource_utilization": 0.75,
29
+ "critical_supplies": 3,
30
+ "equipment_availability": 0.88
31
+ }
32
+ }
33
+
34
+ def _render_metric_card(
35
+ self,
36
+ title: str,
37
+ value: Any,
38
+ delta: Optional[str] = None,
39
+ help_text: Optional[str] = None
40
+ ):
41
+ """Render a single metric card"""
42
+ st.metric(
43
+ label=title,
44
+ value=value,
45
+ delta=delta,
46
+ help=help_text
47
+ )
48
+
49
+ def render(self, metrics: Optional[Dict[str, Any]] = None):
50
+ """
51
+ Render the metrics dashboard
52
+
53
+ Args:
54
+ metrics: Optional metrics data to display
55
+ """
56
+ metrics = metrics or self.default_metrics
57
+
58
+ st.markdown("### 📊 Operational Metrics Dashboard")
59
+
60
+ # Create two rows of metrics
61
+ row1_cols = st.columns(4)
62
+ row2_cols = st.columns(4)
63
+
64
+ # First row - Key metrics
65
+ with row1_cols[0]:
66
+ occupancy = (metrics["patient_flow"]["occupied_beds"] /
67
+ metrics["patient_flow"]["total_beds"] * 100)
68
+ self._render_metric_card(
69
+ "Bed Occupancy 🛏️",
70
+ f"{occupancy:.1f}%",
71
+ "Normal" if occupancy < 85 else "High",
72
+ "Current bed occupancy rate across all departments"
73
+ )
74
+
75
+ with row1_cols[1]:
76
+ satisfaction = metrics["quality"]["patient_satisfaction"]
77
+ self._render_metric_card(
78
+ "Patient Satisfaction 😊",
79
+ f"{satisfaction}/10",
80
+ "↗ +0.5" if satisfaction > 8 else "↘ -0.3",
81
+ "Average patient satisfaction score"
82
+ )
83
+
84
+ with row1_cols[2]:
85
+ total_staff = sum(metrics["staffing"]["available_staff"].values())
86
+ self._render_metric_card(
87
+ "Available Staff 👥",
88
+ total_staff,
89
+ "Optimal" if total_staff > 80 else "Low",
90
+ "Total number of available staff across all roles"
91
+ )
92
+
93
+ with row1_cols[3]:
94
+ utilization = metrics["resources"]["resource_utilization"] * 100
95
+ self._render_metric_card(
96
+ "Resource Utilization 📦",
97
+ f"{utilization:.1f}%",
98
+ "Efficient" if utilization < 80 else "High",
99
+ "Current resource utilization rate"
100
+ )
101
+
102
+ # Second row - Additional metrics
103
+ with row2_cols[0]:
104
+ self._render_metric_card(
105
+ "Waiting Time ⏰",
106
+ f"{metrics['patient_flow']['waiting_time']} min",
107
+ help_text="Average patient waiting time"
108
+ )
109
+
110
+ with row2_cols[1]:
111
+ self._render_metric_card(
112
+ "Compliance Rate ✅",
113
+ f"{metrics['quality']['compliance_rate']*100:.1f}%",
114
+ help_text="Current compliance rate with protocols"
115
+ )
116
+
117
+ with row2_cols[2]:
118
+ self._render_metric_card(
119
+ "Critical Supplies ⚠️",
120
+ metrics['resources']['critical_supplies'],
121
+ "Action needed" if metrics['resources']['critical_supplies'] > 0 else "All stocked",
122
+ "Number of supplies needing immediate attention"
123
+ )
124
+
125
+ with row2_cols[3]:
126
+ self._render_metric_card(
127
+ "Shift Coverage 📅",
128
+ f"{metrics['staffing']['shift_coverage']*100:.1f}%",
129
+ help_text="Current shift coverage rate"
130
+ )
131
+
132
+ # Additional visualization if needed
133
+ with st.expander("📈 Detailed Metrics Analysis"):
134
+ st.markdown("""
135
+ ### Trend Analysis
136
+ - 📈 Patient flow is within normal range
137
+ - 📉 Resource utilization shows optimization opportunities
138
+ - 📊 Staff distribution is balanced across departments
139
+ """)
src/ui/components/sidebar.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from typing import Dict, Any, Callable
3
+ from datetime import datetime, timedelta
4
+
5
+ class SidebarComponent:
6
+ def __init__(self, on_filter_change: Optional[Callable] = None):
7
+ """
8
+ Initialize the sidebar component
9
+
10
+ Args:
11
+ on_filter_change: Optional callback for filter changes
12
+ """
13
+ self.on_filter_change = on_filter_change
14
+
15
+ # Initialize session state for filters if not exists
16
+ if 'filters' not in st.session_state:
17
+ st.session_state.filters = {
18
+ 'department': 'All Departments',
19
+ 'priority': 'Medium',
20
+ 'time_range': 8,
21
+ 'view_mode': 'Standard'
22
+ }
23
+
24
+ def render(self):
25
+ """Render the sidebar"""
26
+ with st.sidebar:
27
+ st.markdown("# ⚙️ Operations Control")
28
+
29
+ # Department Selection
30
+ st.markdown("### 🏥 Department")
31
+ department = st.selectbox(
32
+ "Select Department",
33
+ [
34
+ "All Departments",
35
+ "Emergency Room",
36
+ "ICU",
37
+ "General Ward",
38
+ "Surgery",
39
+ "Pediatrics",
40
+ "Cardiology"
41
+ ],
42
+ index=0,
43
+ help="Filter data by department"
44
+ )
45
+
46
+ # Priority Filter
47
+ st.markdown("### 🎯 Priority Level")
48
+ priority = st.select_slider(
49
+ "Set Priority",
50
+ options=["Low", "Medium", "High", "Urgent", "Critical"],
51
+ value=st.session_state.filters['priority'],
52
+ help="Filter by priority level"
53
+ )
54
+
55
+ # Time Range
56
+ st.markdown("### 🕒 Time Range")
57
+ time_range = st.slider(
58
+ "Select Time Range",
59
+ min_value=1,
60
+ max_value=24,
61
+ value=st.session_state.filters['time_range'],
62
+ help="Time range for data analysis (hours)"
63
+ )
64
+
65
+ # View Mode
66
+ st.markdown("### 👁️ View Mode")
67
+ view_mode = st.radio(
68
+ "Select View Mode",
69
+ ["Standard", "Detailed", "Compact"],
70
+ help="Change the display density"
71
+ )
72
+
73
+ # Quick Actions
74
+ st.markdown("### ⚡ Quick Actions")
75
+ col1, col2 = st.columns(2)
76
+ with col1:
77
+ if st.button("📊 Report", use_container_width=True):
78
+ st.info("Generating report...")
79
+ with col2:
80
+ if st.button("🔄 Refresh", use_container_width=True):
81
+ st.success("Data refreshed!")
82
+
83
+ # Emergency Mode Toggle
84
+ st.markdown("### 🚨 Emergency Mode")
85
+ emergency_mode = st.toggle(
86
+ "Activate Emergency Protocol",
87
+ help="Enable emergency mode for critical situations"
88
+ )
89
+ if emergency_mode:
90
+ st.warning("Emergency Mode Active!")
91
+
92
+ # Help & Documentation
93
+ with st.expander("❓ Help & Tips"):
94
+ st.markdown("""
95
+ ### Quick Guide
96
+ - 🔍 Use filters to focus on specific areas
97
+ - 📈 Monitor real-time metrics
98
+ - 🚨 Toggle emergency mode for critical situations
99
+ - 📊 Generate reports for analysis
100
+ - 💡 Access quick actions for common tasks
101
+ """)
102
+
103
+ # Update filters in session state
104
+ st.session_state.filters.update({
105
+ 'department': department,
106
+ 'priority': priority,
107
+ 'time_range': time_range,
108
+ 'view_mode': view_mode,
109
+ 'emergency_mode': emergency_mode
110
+ })
111
+
112
+ # Call filter change callback if provided
113
+ if self.on_filter_change:
114
+ self.on_filter_change(st.session_state.filters)
115
+
116
+ # Footer
117
+ st.markdown("---")
118
+ st.markdown(
119
+ f"*Last updated: {datetime.now().strftime('%H:%M:%S')}*",
120
+ help="Last data refresh timestamp"
121
+ )
src/ui/styles/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .theme import HealthcareTheme
2
+
3
+ __all__ = ['HealthcareTheme']
src/ui/styles/custom.css ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Healthcare Operations Assistant Custom Styles */
2
+
3
+ /* Layout and Structure */
4
+ .container {
5
+ max-width: 1200px;
6
+ margin: 0 auto;
7
+ padding: 1rem;
8
+ }
9
+
10
+ /* Chat Interface */
11
+ .chat-container {
12
+ background-color: #ffffff;
13
+ border-radius: 12px;
14
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
15
+ padding: 1rem;
16
+ margin: 1rem 0;
17
+ }
18
+
19
+ .user-message {
20
+ background-color: #e3f2fd;
21
+ padding: 1rem;
22
+ border-radius: 10px;
23
+ margin: 1rem 0;
24
+ border-left: 5px solid #1976d2;
25
+ }
26
+
27
+ .assistant-message {
28
+ background-color: #f5f5f5;
29
+ padding: 1rem;
30
+ border-radius: 10px;
31
+ margin: 1rem 0;
32
+ border-left: 5px solid #4caf50;
33
+ }
34
+
35
+ .message-timestamp {
36
+ font-size: 0.8rem;
37
+ color: #707070;
38
+ margin-top: 0.25rem;
39
+ }
40
+
41
+ /* Metrics Dashboard */
42
+ .metric-card {
43
+ background-color: white;
44
+ border-radius: 12px;
45
+ padding: 1.5rem;
46
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
47
+ transition: transform 0.2s;
48
+ }
49
+
50
+ .metric-card:hover {
51
+ transform: translateY(-2px);
52
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
53
+ }
54
+
55
+ .metric-title {
56
+ font-size: 1rem;
57
+ font-weight: 600;
58
+ color: #2c3e50;
59
+ margin-bottom: 0.5rem;
60
+ }
61
+
62
+ .metric-value {
63
+ font-size: 1.5rem;
64
+ font-weight: 700;
65
+ color: #1976d2;
66
+ }
67
+
68
+ .metric-trend {
69
+ font-size: 0.9rem;
70
+ color: #4caf50;
71
+ }
72
+
73
+ .metric-trend.negative {
74
+ color: #f44336;
75
+ }
76
+
77
+ /* Header Styling */
78
+ .header {
79
+ background-color: white;
80
+ padding: 1rem;
81
+ border-bottom: 1px solid #e0e0e0;
82
+ margin-bottom: 2rem;
83
+ }
84
+
85
+ .header-title {
86
+ font-size: 1.8rem;
87
+ font-weight: 700;
88
+ color: #2c3e50;
89
+ }
90
+
91
+ .header-subtitle {
92
+ font-size: 1rem;
93
+ color: #707070;
94
+ }
95
+
96
+ /* Sidebar Styling */
97
+ .sidebar {
98
+ background-color: white;
99
+ padding: 1.5rem;
100
+ border-right: 1px solid #e0e0e0;
101
+ }
102
+
103
+ .sidebar-section {
104
+ margin-bottom: 2rem;
105
+ }
106
+
107
+ .sidebar-title {
108
+ font-size: 1.1rem;
109
+ font-weight: 600;
110
+ color: #2c3e50;
111
+ margin-bottom: 1rem;
112
+ }
113
+
114
+ /* Status Indicators */
115
+ .status-indicator {
116
+ display: inline-flex;
117
+ align-items: center;
118
+ padding: 0.25rem 0.75rem;
119
+ border-radius: 9999px;
120
+ font-size: 0.875rem;
121
+ font-weight: 500;
122
+ }
123
+
124
+ .status-normal {
125
+ background-color: #4caf50;
126
+ color: white;
127
+ }
128
+
129
+ .status-warning {
130
+ background-color: #ff9800;
131
+ color: white;
132
+ }
133
+
134
+ .status-critical {
135
+ background-color: #f44336;
136
+ color: white;
137
+ }
138
+
139
+ /* Buttons and Interactive Elements */
140
+ .action-button {
141
+ background-color: #2196f3;
142
+ color: white;
143
+ border: none;
144
+ border-radius: 8px;
145
+ padding: 0.5rem 1rem;
146
+ font-weight: 500;
147
+ cursor: pointer;
148
+ transition: background-color 0.2s;
149
+ }
150
+
151
+ .action-button:hover {
152
+ background-color: #1976d2;
153
+ }
154
+
155
+ .action-button.secondary {
156
+ background-color: #f5f5f5;
157
+ color: #2c3e50;
158
+ border: 1px solid #e0e0e0;
159
+ }
160
+
161
+ .action-button.secondary:hover {
162
+ background-color: #e0e0e0;
163
+ }
164
+
165
+ /* Notifications */
166
+ .notification {
167
+ padding: 0.75rem 1rem;
168
+ border-radius: 8px;
169
+ margin-bottom: 1rem;
170
+ }
171
+
172
+ .notification.info {
173
+ background-color: #e3f2fd;
174
+ border-left: 4px solid #2196f3;
175
+ }
176
+
177
+ .notification.success {
178
+ background-color: #e8f5e9;
179
+ border-left: 4px solid #4caf50;
180
+ }
181
+
182
+ .notification.warning {
183
+ background-color: #fff3e0;
184
+ border-left: 4px solid #ff9800;
185
+ }
186
+
187
+ .notification.error {
188
+ background-color: #ffebee;
189
+ border-left: 4px solid #f44336;
190
+ }
191
+
192
+ /* Responsive Design */
193
+ @media (max-width: 768px) {
194
+ .metric-card {
195
+ margin-bottom: 1rem;
196
+ }
197
+
198
+ .header-title {
199
+ font-size: 1.5rem;
200
+ }
201
+
202
+ .sidebar {
203
+ padding: 1rem;
204
+ }
205
+ }
206
+
207
+ /* Animations */
208
+ @keyframes fadeIn {
209
+ from { opacity: 0; }
210
+ to { opacity: 1; }
211
+ }
212
+
213
+ .fade-in {
214
+ animation: fadeIn 0.3s ease-in;
215
+ }
216
+
217
+ @keyframes slideIn {
218
+ from { transform: translateY(20px); opacity: 0; }
219
+ to { transform: translateY(0); opacity: 1; }
220
+ }
221
+
222
+ .slide-in {
223
+ animation: slideIn 0.3s ease-out;
224
+ }
src/ui/styles/theme.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import Dict, Any
3
+
4
+ @dataclass
5
+ class HealthcareTheme:
6
+ """Healthcare UI theme configuration"""
7
+
8
+ # Color palette
9
+ colors = {
10
+ 'primary': '#2196f3', # Main blue
11
+ 'primary_light': '#e3f2fd', # Light blue
12
+ 'primary_dark': '#1976d2', # Dark blue
13
+ 'success': '#4caf50', # Green
14
+ 'warning': '#ff9800', # Orange
15
+ 'error': '#f44336', # Red
16
+ 'info': '#2196f3', # Blue
17
+ 'background': '#f0f8ff', # Light blue background
18
+ 'surface': '#ffffff', # White
19
+ 'text': '#2c3e50', # Dark gray
20
+ 'text_secondary': '#707070' # Medium gray
21
+ }
22
+
23
+ # Typography
24
+ fonts = {
25
+ 'primary': '"Source Sans Pro", -apple-system, BlinkMacSystemFont, sans-serif',
26
+ 'monospace': '"Roboto Mono", monospace'
27
+ }
28
+
29
+ # Spacing
30
+ spacing = {
31
+ 'xs': '0.25rem',
32
+ 'sm': '0.5rem',
33
+ 'md': '1rem',
34
+ 'lg': '1.5rem',
35
+ 'xl': '2rem'
36
+ }
37
+
38
+ # Border radius
39
+ radius = {
40
+ 'sm': '4px',
41
+ 'md': '8px',
42
+ 'lg': '12px',
43
+ 'xl': '16px',
44
+ 'pill': '9999px'
45
+ }
46
+
47
+ # Shadows
48
+ shadows = {
49
+ 'sm': '0 1px 3px rgba(0,0,0,0.12)',
50
+ 'md': '0 2px 4px rgba(0,0,0,0.1)',
51
+ 'lg': '0 4px 6px rgba(0,0,0,0.1)',
52
+ 'xl': '0 8px 12px rgba(0,0,0,0.1)'
53
+ }
54
+
55
+ @classmethod
56
+ def get_streamlit_config(cls) -> Dict[str, Any]:
57
+ """Get Streamlit theme configuration"""
58
+ return {
59
+ "theme": {
60
+ "primaryColor": cls.colors['primary'],
61
+ "backgroundColor": cls.colors['background'],
62
+ "secondaryBackgroundColor": cls.colors['surface'],
63
+ "textColor": cls.colors['text'],
64
+ "font": cls.fonts['primary']
65
+ }
66
+ }
67
+
68
+ @classmethod
69
+ def apply_theme(cls):
70
+ """Apply theme to Streamlit application"""
71
+ import streamlit as st
72
+
73
+ # Apply theme configuration
74
+ st.set_page_config(**cls.get_streamlit_config())
75
+
76
+ # Apply custom CSS
77
+ st.markdown("""
78
+ <style>
79
+ /* Import fonts */
80
+ @import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap');
81
+ @import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap');
82
+
83
+ /* Base styles */
84
+ .stApp {
85
+ font-family: var(--primary-font);
86
+ color: var(--text-color);
87
+ }
88
+
89
+ /* Custom CSS Variables */
90
+ :root {
91
+ --primary-color: """ + cls.colors['primary'] + """;
92
+ --primary-light: """ + cls.colors['primary_light'] + """;
93
+ --primary-dark: """ + cls.colors['primary_dark'] + """;
94
+ --background-color: """ + cls.colors['background'] + """;
95
+ --surface-color: """ + cls.colors['surface'] + """;
96
+ --text-color: """ + cls.colors['text'] + """;
97
+ --primary-font: """ + cls.fonts['primary'] + """;
98
+ }
99
+
100
+ /* Apply theme to Streamlit elements */
101
+ .stButton>button {
102
+ background-color: var(--primary-color);
103
+ color: white;
104
+ border-radius: """ + cls.radius['pill'] + """;
105
+ padding: """ + cls.spacing['sm'] + """ """ + cls.spacing['lg'] + """;
106
+ border: none;
107
+ box-shadow: """ + cls.shadows['sm'] + """;
108
+ }
109
+
110
+ .stButton>button:hover {
111
+ background-color: var(--primary-dark);
112
+ box-shadow: """ + cls.shadows['md'] + """;
113
+ }
114
+
115
+ .stTextInput>div>div>input {
116
+ border-radius: """ + cls.radius['md'] + """;
117
+ }
118
+
119
+ .stSelectbox>div>div>div {
120
+ border-radius: """ + cls.radius['md'] + """;
121
+ }
122
+ </style>
123
+ """, unsafe_allow_html=True)
src/utils/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # src/utils/__init__.py
2
+ from .logger import setup_logger
3
+ from .error_handlers import ErrorHandler
4
+ from .validators import Validator
5
+
6
+ __all__ = ['setup_logger', 'ErrorHandler', 'Validator']
src/utils/error_handlers.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/utils/error_handlers.py
2
+ from typing import Dict, Any, Optional, Callable
3
+ from functools import wraps
4
+ import traceback
5
+ from .logger import setup_logger
6
+
7
+ logger = setup_logger(__name__)
8
+
9
+ class HealthcareError(Exception):
10
+ """Base exception class for healthcare operations"""
11
+ def __init__(self, message: str, error_code: str, details: Optional[Dict] = None):
12
+ self.message = message
13
+ self.error_code = error_code
14
+ self.details = details or {}
15
+ super().__init__(self.message)
16
+
17
+ class ValidationError(HealthcareError):
18
+ """Raised when input validation fails"""
19
+ def __init__(self, message: str, details: Optional[Dict] = None):
20
+ super().__init__(
21
+ message=message,
22
+ error_code="INPUT_VALIDATION_ERROR",
23
+ details=details
24
+ )
25
+
26
+ class ProcessingError(HealthcareError):
27
+ """Raised when processing operations fail"""
28
+ pass
29
+
30
+ class ResourceError(HealthcareError):
31
+ """Raised when resource-related operations fail"""
32
+ pass
33
+
34
+ class ErrorHandler:
35
+ @staticmethod
36
+ def validate_input(input_text: str) -> None:
37
+ """Validate input text before processing"""
38
+ if not input_text or not input_text.strip():
39
+ raise ValidationError(
40
+ message="Input text cannot be empty",
41
+ details={"provided_input": input_text}
42
+ )
43
+
44
+ @staticmethod
45
+ def handle_error(error: Exception) -> Dict[str, Any]:
46
+ """Handle different types of errors and return appropriate response"""
47
+ if isinstance(error, ValidationError):
48
+ logger.error(f"Validation Error: {error.message}",
49
+ extra={"error_code": error.error_code, "details": error.details})
50
+ raise error # Re-raise ValidationError
51
+ elif isinstance(error, HealthcareError):
52
+ logger.error(f"Healthcare Error: {error.message}",
53
+ extra={"error_code": error.error_code, "details": error.details})
54
+ return {
55
+ "error": True,
56
+ "error_code": error.error_code,
57
+ "message": error.message,
58
+ "details": error.details
59
+ }
60
+ else:
61
+ logger.error(f"Unexpected Error: {str(error)}\n{traceback.format_exc()}")
62
+ return {
63
+ "error": True,
64
+ "error_code": "UNEXPECTED_ERROR",
65
+ "message": "An unexpected error occurred",
66
+ "details": {"error_type": type(error).__name__}
67
+ }
68
+
69
+ @staticmethod
70
+ def error_decorator(func: Callable) -> Callable:
71
+ """Decorator for handling errors in functions"""
72
+ @wraps(func)
73
+ def wrapper(*args, **kwargs):
74
+ try:
75
+ return func(*args, **kwargs)
76
+ except ValidationError:
77
+ # Let ValidationError propagate up
78
+ raise
79
+ except Exception as e:
80
+ return ErrorHandler.handle_error(e)
81
+ return wrapper
82
+
83
+ @staticmethod
84
+ def retry_operation(
85
+ operation: Callable,
86
+ max_retries: int = 3,
87
+ retry_delay: float = 1.0
88
+ ) -> Any:
89
+ """
90
+ Retry an operation with exponential backoff
91
+ """
92
+ from time import sleep
93
+
94
+ for attempt in range(max_retries):
95
+ try:
96
+ return operation()
97
+ except Exception as e:
98
+ if attempt == max_retries - 1:
99
+ raise
100
+
101
+ logger.warning(
102
+ f"Operation failed (attempt {attempt + 1}/{max_retries}): {str(e)}"
103
+ )
104
+ sleep(retry_delay * (2 ** attempt))
105
+
106
+ @staticmethod
107
+ def safe_execute(
108
+ operation: Callable,
109
+ error_code: str,
110
+ default_value: Any = None
111
+ ) -> Any:
112
+ """
113
+ Safely execute an operation with error handling
114
+ """
115
+ try:
116
+ return operation()
117
+ except Exception as e:
118
+ logger.error(f"Operation failed: {str(e)}")
119
+ raise HealthcareError(
120
+ message=f"Operation failed: {str(e)}",
121
+ error_code=error_code
122
+ )# Error handling utilities implementation
src/utils/logger.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/utils/logger.py
2
+ import logging
3
+ import sys
4
+ from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
5
+ from pathlib import Path
6
+ from datetime import datetime
7
+ from typing import Optional
8
+ from ..config.settings import Settings
9
+
10
+ class CustomFormatter(logging.Formatter):
11
+ """Custom formatter with color coding for different log levels"""
12
+
13
+ COLORS = {
14
+ 'DEBUG': '\033[0;36m', # Cyan
15
+ 'INFO': '\033[0;32m', # Green
16
+ 'WARNING': '\033[0;33m', # Yellow
17
+ 'ERROR': '\033[0;31m', # Red
18
+ 'CRITICAL': '\033[0;37;41m' # White on Red
19
+ }
20
+ RESET = '\033[0m'
21
+
22
+ def format(self, record):
23
+ # Add color to log level if on console
24
+ if hasattr(self, 'use_color') and self.use_color:
25
+ record.levelname = f"{self.COLORS.get(record.levelname, '')}{record.levelname}{self.RESET}"
26
+ return super().format(record)
27
+
28
+ def setup_logger(
29
+ name: str,
30
+ log_level: Optional[str] = None,
31
+ log_file: Optional[str] = None
32
+ ) -> logging.Logger:
33
+ """
34
+ Set up logger with both file and console handlers
35
+
36
+ Args:
37
+ name: Logger name
38
+ log_level: Optional override for log level
39
+ log_file: Optional override for log file path
40
+
41
+ Returns:
42
+ Configured logger instance
43
+ """
44
+ try:
45
+ # Create logger
46
+ logger = logging.getLogger(name)
47
+ logger.setLevel(log_level or Settings.LOG_LEVEL)
48
+
49
+ # Avoid adding handlers if they already exist
50
+ if logger.handlers:
51
+ return logger
52
+
53
+ # Create formatters
54
+ file_formatter = logging.Formatter(
55
+ '%(asctime)s - %(name)s - [%(levelname)s] - %(message)s'
56
+ )
57
+
58
+ console_formatter = CustomFormatter(
59
+ '%(asctime)s - %(name)s - [%(levelname)s] - %(message)s'
60
+ )
61
+ console_formatter.use_color = True
62
+
63
+ # Create and configure file handler
64
+ log_file = log_file or Settings.LOG_FILE
65
+ log_dir = Path(log_file).parent
66
+ log_dir.mkdir(parents=True, exist_ok=True)
67
+
68
+ # Rotating file handler (size-based)
69
+ file_handler = RotatingFileHandler(
70
+ log_file,
71
+ maxBytes=10 * 1024 * 1024, # 10MB
72
+ backupCount=5
73
+ )
74
+ file_handler.setFormatter(file_formatter)
75
+
76
+ # Time-based rotating handler for daily logs
77
+ daily_handler = TimedRotatingFileHandler(
78
+ str(log_dir / f"daily_{datetime.now():%Y-%m-%d}.log"),
79
+ when="midnight",
80
+ interval=1,
81
+ backupCount=30
82
+ )
83
+ daily_handler.setFormatter(file_formatter)
84
+
85
+ # Console handler
86
+ console_handler = logging.StreamHandler(sys.stdout)
87
+ console_handler.setFormatter(console_formatter)
88
+
89
+ # Add handlers
90
+ logger.addHandler(file_handler)
91
+ logger.addHandler(daily_handler)
92
+ logger.addHandler(console_handler)
93
+
94
+ return logger
95
+
96
+ except Exception as e:
97
+ # Fallback to basic logging if setup fails
98
+ basic_logger = logging.getLogger(name)
99
+ basic_logger.setLevel(logging.INFO)
100
+ basic_logger.addHandler(logging.StreamHandler(sys.stdout))
101
+ basic_logger.error(f"Error setting up logger: {str(e)}")
102
+ return basic_logger# Logging configuration implementation
src/utils/validators.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # src/utils/validators.py
2
+ from typing import Dict, Any, List, Optional
3
+ from datetime import datetime
4
+ from .logger import setup_logger
5
+ from .error_handlers import ValidationError
6
+
7
+ logger = setup_logger(__name__)
8
+
9
+ class Validator:
10
+ @staticmethod
11
+ def validate_state(state: Dict[str, Any]) -> bool:
12
+ """Validate the state structure and data types"""
13
+ required_keys = ["messages", "current_task", "metrics", "timestamp"]
14
+
15
+ try:
16
+ # Check required keys
17
+ for key in required_keys:
18
+ if key not in state:
19
+ raise ValidationError(
20
+ message=f"Missing required key: {key}",
21
+ error_code="INVALID_STATE_STRUCTURE"
22
+ )
23
+
24
+ # Validate timestamp
25
+ if not isinstance(state["timestamp"], datetime):
26
+ raise ValidationError(
27
+ message="Invalid timestamp format",
28
+ error_code="INVALID_TIMESTAMP"
29
+ )
30
+
31
+ return True
32
+
33
+ except Exception as e:
34
+ logger.error(f"State validation failed: {str(e)}")
35
+ raise
36
+
37
+ @staticmethod
38
+ def validate_metrics(metrics: Dict[str, Any]) -> bool:
39
+ """Validate metrics data structure and values"""
40
+ required_categories = [
41
+ "patient_flow",
42
+ "resources",
43
+ "quality",
44
+ "staffing"
45
+ ]
46
+
47
+ try:
48
+ # Check required categories
49
+ for category in required_categories:
50
+ if category not in metrics:
51
+ raise ValidationError(
52
+ message=f"Missing required metrics category: {category}",
53
+ error_code="INVALID_METRICS_STRUCTURE"
54
+ )
55
+
56
+ # Validate numeric values
57
+ Validator._validate_numeric_values(metrics)
58
+
59
+ return True
60
+
61
+ except Exception as e:
62
+ logger.error(f"Metrics validation failed: {str(e)}")
63
+ raise
64
+
65
+ @staticmethod
66
+ def validate_tool_input(
67
+ tool_name: str,
68
+ params: Dict[str, Any],
69
+ required_params: List[str]
70
+ ) -> bool:
71
+ """Validate input parameters for tools"""
72
+ try:
73
+ # Check required parameters
74
+ for param in required_params:
75
+ if param not in params:
76
+ raise ValidationError(
77
+ message=f"Missing required parameter: {param}",
78
+ error_code="MISSING_PARAMETER",
79
+ details={"tool": tool_name, "parameter": param}
80
+ )
81
+
82
+ return True
83
+
84
+ except Exception as e:
85
+ logger.error(f"Tool input validation failed: {str(e)}")
86
+ raise
87
+
88
+ @staticmethod
89
+ def validate_department_data(department_data: Dict[str, Any]) -> bool:
90
+ """Validate department-specific data"""
91
+ required_fields = [
92
+ "capacity",
93
+ "current_occupancy",
94
+ "staff_count"
95
+ ]
96
+
97
+ try:
98
+ # Check required fields
99
+ for field in required_fields:
100
+ if field not in department_data:
101
+ raise ValidationError(
102
+ message=f"Missing required field: {field}",
103
+ error_code="INVALID_DEPARTMENT_DATA"
104
+ )
105
+
106
+ # Validate capacity constraints
107
+ if department_data["current_occupancy"] > department_data["capacity"]:
108
+ raise ValidationError(
109
+ message="Current occupancy exceeds capacity",
110
+ error_code="INVALID_OCCUPANCY"
111
+ )
112
+
113
+ return True
114
+
115
+ except Exception as e:
116
+ logger.error(f"Department data validation failed: {str(e)}")
117
+ raise
118
+
119
+ @staticmethod
120
+ def _validate_numeric_values(data: Dict[str, Any], path: str = "") -> None:
121
+ """Recursively validate numeric values in nested dictionary"""
122
+ for key, value in data.items():
123
+ current_path = f"{path}.{key}" if path else key
124
+
125
+ if isinstance(value, (int, float)):
126
+ if value < 0:
127
+ raise ValidationError(
128
+ message=f"Negative value not allowed: {current_path}",
129
+ error_code="INVALID_NUMERIC_VALUE"
130
+ )
131
+ elif isinstance(value, dict):
132
+ Validator._validate_numeric_values(value, current_path)# Input validation utilities implementation
streamlit_app.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from src.ui import HealthcareUI
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ # Load environment variables
6
+ load_dotenv()
7
+
8
+ if __name__ == "__main__":
9
+ app = HealthcareUI()
10
+ app.run()
test_healthcare_agent_basic.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # test_healthcare_agent_basic.py
2
+ import os
3
+ from datetime import datetime
4
+ from src.agent import HealthcareAgent
5
+ from src.models.state import TaskType, PriorityLevel
6
+ from src.utils.error_handlers import ValidationError, HealthcareError
7
+
8
+ def main():
9
+ """Basic test of the Healthcare Operations Management Agent"""
10
+ try:
11
+ # 1. Test Agent Initialization
12
+ print("\n=== Testing Agent Initialization ===")
13
+ agent = HealthcareAgent(os.getenv("OPENAI_API_KEY"))
14
+ print("✓ Agent initialized successfully")
15
+
16
+ # 2. Test Basic Query - Patient Flow
17
+ print("\n=== Testing Patient Flow Query ===")
18
+ patient_query = "What is the current ER occupancy and wait time?"
19
+ response = agent.process(
20
+ input_text=patient_query,
21
+ thread_id="test-thread-1"
22
+ )
23
+ print(f"Query: {patient_query}")
24
+ print(f"Response: {response.get('response', 'No response')}")
25
+ print(f"Analysis: {response.get('analysis', {})}")
26
+
27
+ # 3. Test Resource Management Query
28
+ print("\n=== Testing Resource Management Query ===")
29
+ resource_query = "Check the current availability of ventilators and ICU beds"
30
+ response = agent.process(
31
+ input_text=resource_query,
32
+ thread_id="test-thread-1"
33
+ )
34
+ print(f"Query: {resource_query}")
35
+ print(f"Response: {response.get('response', 'No response')}")
36
+ print(f"Analysis: {response.get('analysis', {})}")
37
+
38
+ # 4. Test Conversation History
39
+ print("\n=== Testing Conversation History ===")
40
+ history = agent.get_conversation_history("test-thread-1")
41
+ print(f"Conversation history length: {len(history)}")
42
+
43
+ # 5. Test Reset Conversation
44
+ print("\n=== Testing Conversation Reset ===")
45
+ reset_success = agent.reset_conversation("test-thread-1")
46
+ print(f"Reset successful: {reset_success}")
47
+
48
+ # 6. Test Error Handling
49
+ print("\n=== Testing Error Handling ===")
50
+ try:
51
+ agent.process("")
52
+ print("❌ Error handling test failed - empty input accepted")
53
+ except ValidationError as ve:
54
+ print(f"✓ Error handling working correctly: Empty input rejected with validation error")
55
+ except HealthcareError as he:
56
+ print(f"✓ Error handling working correctly: {str(he)}")
57
+ except Exception as e:
58
+ print(f"❌ Unexpected error type: {type(e).__name__}: {str(e)}")
59
+
60
+ except Exception as e:
61
+ print(f"\n❌ Test failed with error: {str(e)}")
62
+
63
+ if __name__ == "__main__":
64
+ print("Starting Healthcare Agent Basic Tests...")
65
+ print(f"Test Time: {datetime.now()}")
66
+ main()
test_healthcare_scenarios.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from time import sleep
3
+ import pytest
4
+ from datetime import datetime
5
+
6
+ class HealthcareAssistantTester:
7
+ def __init__(self):
8
+ self.test_results = []
9
+
10
+ def run_test_suite(self):
11
+ """Run all test scenarios"""
12
+ print("\n=== Starting Healthcare Assistant Test Suite ===\n")
13
+
14
+ # Run all test categories
15
+ self.test_patient_flow()
16
+ self.test_resource_management()
17
+ self.test_staff_scheduling()
18
+ self.test_quality_metrics()
19
+ self.test_emergency_scenarios()
20
+ self.test_department_specific()
21
+
22
+ # Print test summary
23
+ self.print_test_summary()
24
+
25
+ def test_patient_flow(self):
26
+ """Test Patient Flow Related Queries"""
27
+ print("\n1. Testing Patient Flow Queries:")
28
+ queries = [
29
+ "Show me waiting times across all departments",
30
+ "What is the current bed occupancy in the ER?",
31
+ "How many patients are currently waiting for admission?",
32
+ "What's the average wait time in the ICU?",
33
+ "Show patient flow trends for the last 8 hours",
34
+ "Which department has the longest waiting time right now?"
35
+ ]
36
+ self._run_test_batch("Patient Flow", queries)
37
+
38
+ def test_resource_management(self):
39
+ """Test Resource Management Queries"""
40
+ print("\n2. Testing Resource Management Queries:")
41
+ queries = [
42
+ "Check medical supplies inventory status",
43
+ "What is the current ventilator availability?",
44
+ "Are there any critical supply shortages?",
45
+ "Show resource utilization across departments",
46
+ "Which supplies need immediate reordering?",
47
+ "What's the equipment maintenance status?"
48
+ ]
49
+ self._run_test_batch("Resource Management", queries)
50
+
51
+ def test_staff_scheduling(self):
52
+ """Test Staff Scheduling Queries"""
53
+ print("\n3. Testing Staff Scheduling Queries:")
54
+ queries = [
55
+ "Show current staff distribution",
56
+ "How many nurses are available in ICU?",
57
+ "What is the current shift coverage?",
58
+ "Show staff overtime hours this week",
59
+ "Is there adequate staff coverage for next shift?",
60
+ "Which departments need additional staff right now?"
61
+ ]
62
+ self._run_test_batch("Staff Scheduling", queries)
63
+
64
+ def test_quality_metrics(self):
65
+ """Test Quality Metrics Queries"""
66
+ print("\n4. Testing Quality Metrics Queries:")
67
+ queries = [
68
+ "What's our current patient satisfaction score?",
69
+ "Show me compliance rates for the last 24 hours",
70
+ "Are there any quality metrics below target?",
71
+ "What's the current incident report status?",
72
+ "Show quality trends across departments",
73
+ "Which department has the highest patient satisfaction?"
74
+ ]
75
+ self._run_test_batch("Quality Metrics", queries)
76
+
77
+ def test_emergency_scenarios(self):
78
+ """Test Emergency Scenario Queries"""
79
+ print("\n5. Testing Emergency Scenarios:")
80
+ queries = [
81
+ "Activate emergency protocol for mass casualty incident",
82
+ "Need immediate bed availability status for emergency",
83
+ "Require rapid staff mobilization plan",
84
+ "Emergency resource allocation needed",
85
+ "Critical capacity alert in ER",
86
+ "Emergency department overflow protocol status"
87
+ ]
88
+ self._run_test_batch("Emergency Scenarios", queries)
89
+
90
+ def test_department_specific(self):
91
+ """Test Department-Specific Queries"""
92
+ print("\n6. Testing Department-Specific Queries:")
93
+ queries = [
94
+ "Show complete metrics for ER department",
95
+ "What's the ICU capacity and staff status?",
96
+ "General ward patient distribution",
97
+ "Surgery department resource utilization",
98
+ "Pediatrics department waiting times",
99
+ "Cardiology unit staff coverage"
100
+ ]
101
+ self._run_test_batch("Department-Specific", queries)
102
+
103
+ def _run_test_batch(self, category: str, queries: list):
104
+ """Run a batch of test queries"""
105
+ for query in queries:
106
+ try:
107
+ print(f"\nTesting: {query}")
108
+ print("-" * 50)
109
+
110
+ # Simulate processing time
111
+ print("Processing query...")
112
+ sleep(1)
113
+
114
+ # Record test execution
115
+ self.test_results.append({
116
+ 'category': category,
117
+ 'query': query,
118
+ 'timestamp': datetime.now(),
119
+ 'status': 'Success'
120
+ })
121
+
122
+ print("✓ Test completed successfully")
123
+
124
+ except Exception as e:
125
+ print(f"✗ Test failed: {str(e)}")
126
+ self.test_results.append({
127
+ 'category': category,
128
+ 'query': query,
129
+ 'timestamp': datetime.now(),
130
+ 'status': 'Failed',
131
+ 'error': str(e)
132
+ })
133
+
134
+ def print_test_summary(self):
135
+ """Print summary of all test results"""
136
+ print("\n=== Test Execution Summary ===")
137
+ print(f"Total Tests Run: {len(self.test_results)}")
138
+
139
+ # Calculate statistics
140
+ successful_tests = len([t for t in self.test_results if t['status'] == 'Success'])
141
+ failed_tests = len([t for t in self.test_results if t['status'] == 'Failed'])
142
+
143
+ print(f"Successful Tests: {successful_tests}")
144
+ print(f"Failed Tests: {failed_tests}")
145
+
146
+ # Print results by category
147
+ print("\nResults by Category:")
148
+ categories = set([t['category'] for t in self.test_results])
149
+ for category in categories:
150
+ category_tests = [t for t in self.test_results if t['category'] == category]
151
+ category_success = len([t for t in category_tests if t['status'] == 'Success'])
152
+ print(f"{category}: {category_success}/{len(category_tests)} passed")
153
+
154
+ print("\n=== Test Suite Completed ===")
155
+
156
+ def main():
157
+ """Main test execution function"""
158
+ # Initialize and run tests
159
+ tester = HealthcareAssistantTester()
160
+ tester.run_test_suite()
161
+
162
+ if __name__ == "__main__":
163
+ main()
tests/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # tests/__init__.py
2
+ import os
3
+ import sys
4
+
5
+ # Add project root to Python path
6
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
tests/conftest.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # tests/conftest.py
2
+ import pytest
3
+ from datetime import datetime
4
+ from typing import Dict
5
+
6
+ from src.config.settings import Settings
7
+ from src.models.state import HospitalState, TaskType, PriorityLevel
8
+
9
+ @pytest.fixture
10
+ def mock_settings():
11
+ """Fixture for test settings"""
12
+ return {
13
+ "OPENAI_API_KEY": "test-api-key",
14
+ "MODEL_NAME": "gpt-4o-mini-2024-07-18",
15
+ "MODEL_TEMPERATURE": 0,
16
+ "MEMORY_TYPE": "sqlite",
17
+ "MEMORY_URI": ":memory:",
18
+ "LOG_LEVEL": "DEBUG"
19
+ }
20
+
21
+ @pytest.fixture
22
+ def mock_llm_response():
23
+ """Fixture for mock LLM responses"""
24
+ return {
25
+ "input_analysis": {
26
+ "task_type": TaskType.PATIENT_FLOW,
27
+ "priority": PriorityLevel.HIGH,
28
+ "department": "ER",
29
+ "context": {"urgent": True}
30
+ },
31
+ "patient_flow": {
32
+ "recommendations": ["Optimize bed allocation", "Increase staff in ER"],
33
+ "metrics": {"waiting_time": 25, "bed_utilization": 0.85}
34
+ },
35
+ "quality_monitoring": {
36
+ "satisfaction_score": 8.5,
37
+ "compliance_rate": 0.95,
38
+ "recommendations": ["Maintain current standards"]
39
+ }
40
+ }
41
+
42
+ @pytest.fixture
43
+ def mock_hospital_state() -> HospitalState:
44
+ """Fixture for mock hospital state"""
45
+ return {
46
+ "messages": [],
47
+ "current_task": TaskType.GENERAL,
48
+ "priority_level": PriorityLevel.MEDIUM,
49
+ "department": None,
50
+ "metrics": {
51
+ "patient_flow": {
52
+ "total_beds": 100,
53
+ "occupied_beds": 75,
54
+ "waiting_patients": 10,
55
+ "average_wait_time": 30.0
56
+ },
57
+ "resources": {
58
+ "equipment_availability": {"ventilators": True},
59
+ "supply_levels": {"masks": 0.8},
60
+ "resource_utilization": 0.75
61
+ },
62
+ "quality": {
63
+ "patient_satisfaction": 8.5,
64
+ "compliance_rate": 0.95,
65
+ "incident_count": 2
66
+ },
67
+ "staffing": {
68
+ "total_staff": 200,
69
+ "available_staff": {"doctors": 20, "nurses": 50},
70
+ "overtime_hours": 45.5
71
+ }
72
+ },
73
+ "analysis": None,
74
+ "context": {},
75
+ "timestamp": datetime.now(),
76
+ "thread_id": "test-thread-id"
77
+ }
78
+
79
+ @pytest.fixture
80
+ def mock_tools_response():
81
+ """Fixture for mock tool responses"""
82
+ return {
83
+ "patient_tools": {
84
+ "wait_time": 30.5,
85
+ "bed_capacity": {"available": 25, "total": 100},
86
+ "discharge_time": datetime.now()
87
+ },
88
+ "resource_tools": {
89
+ "supply_levels": {"critical": [], "reorder": ["masks"]},
90
+ "equipment_status": {"available": ["xray"], "in_use": ["mri"]}
91
+ }
92
+ }
93
+
94
+ @pytest.fixture
95
+ def mock_error_response():
96
+ """Fixture for mock error responses"""
97
+ return {
98
+ "validation_error": {
99
+ "code": "INVALID_INPUT",
100
+ "message": "Invalid input parameters",
101
+ "details": {"field": "department", "issue": "required"}
102
+ },
103
+ "processing_error": {
104
+ "code": "PROCESSING_FAILED",
105
+ "message": "Failed to process request",
106
+ "details": {"step": "analysis", "reason": "timeout"}
107
+ }
108
+ }# Test configuration implementation
tests/test_agent.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # tests/test_agent.py
2
+ import pytest
3
+ from src.agent import HealthcareAgent
4
+ from src.utils.error_handlers import HealthcareError
5
+
6
+ class TestHealthcareAgent:
7
+ def test_agent_initialization(self, mock_settings):
8
+ """Test agent initialization"""
9
+ agent = HealthcareAgent(api_key=mock_settings["OPENAI_API_KEY"])
10
+ assert agent is not None
11
+ assert agent.llm is not None
12
+ assert agent.tools is not None
13
+ assert agent.nodes is not None
14
+
15
+ def test_process_input(self, mock_hospital_state):
16
+ """Test processing of input through agent"""
17
+ agent = HealthcareAgent()
18
+ result = agent.process(
19
+ "What is the current ER waiting time?",
20
+ thread_id="test-thread"
21
+ )
22
+
23
+ assert "response" in result
24
+ assert "analysis" in result
25
+ assert "metrics" in result
26
+ assert "timestamp" in result
27
+
28
+ def test_conversation_history(self):
29
+ """Test conversation history retrieval"""
30
+ agent = HealthcareAgent()
31
+ thread_id = "test-thread"
32
+
33
+ # Add some messages
34
+ agent.process("Test message 1", thread_id=thread_id)
35
+ agent.process("Test message 2", thread_id=thread_id)
36
+
37
+ history = agent.get_conversation_history(thread_id)
38
+ assert len(history) >= 2
39
+
40
+ def test_error_handling(self):
41
+ """Test error handling in agent"""
42
+ agent = HealthcareAgent()
43
+
44
+ with pytest.raises(HealthcareError):
45
+ agent.process("", thread_id="test-thread")
46
+
47
+ def test_state_management(self, mock_hospital_state):
48
+ """Test state management"""
49
+ agent = HealthcareAgent()
50
+ thread_id = "test-thread"
51
+
52
+ # Process message
53
+ result = agent.process("Test message", thread_id=thread_id)
54
+ assert result is not None
55
+
56
+ # Reset conversation
57
+ reset_success = agent.reset_conversation(thread_id)
58
+ assert reset_success is True
59
+
60
+ # Verify reset
61
+ history = agent.get_conversation_history(thread_id)
62
+ assert len(history) == 0
63
+
64
+ @pytest.mark.asyncio
65
+ async def test_async_processing(self):
66
+ """Test async processing capabilities"""
67
+ agent = HealthcareAgent()
68
+ thread_id = "test-thread"
69
+
70
+ # Test streaming response
71
+ async for event in agent.graph.astream_events(
72
+ {"messages": ["Test message"]},
73
+ {"configurable": {"thread_id": thread_id}}
74
+ ):
75
+ assert event is not None# Integration tests implementation
tests/test_nodes/test_input_analyzer.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # tests/test_nodes/test_input_analyzer.py
2
+ import pytest
3
+ from src.nodes.input_analyzer import InputAnalyzerNode
4
+ from src.models.state import TaskType, PriorityLevel
5
+
6
+ def test_input_analyzer_initialization(mock_llm_response):
7
+ """Test InputAnalyzer node initialization"""
8
+ analyzer = InputAnalyzerNode(mock_llm_response)
9
+ assert analyzer is not None
10
+
11
+ def test_input_analysis(mock_hospital_state, mock_llm_response):
12
+ """Test input analysis functionality"""
13
+ analyzer = InputAnalyzerNode(mock_llm_response)
14
+ result = analyzer(mock_hospital_state)
15
+
16
+ assert "current_task" in result
17
+ assert "priority_level" in result
18
+ assert isinstance(result["current_task"], TaskType)
19
+ assert isinstance(result["priority_level"], PriorityLevel)
20
+
21
+ def test_invalid_input_handling(mock_hospital_state):
22
+ """Test handling of invalid input"""
23
+ analyzer = InputAnalyzerNode(None)
24
+ mock_hospital_state["messages"] = []
25
+
26
+ with pytest.raises(ValueError):
27
+ analyzer(mock_hospital_state)