dyadd commited on
Commit
124b7ba
·
verified ·
1 Parent(s): 453c738

Upload folder using huggingface_hub

Browse files
nbs/00_learning_context.ipynb CHANGED
@@ -1,367 +1,355 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": null,
6
- "metadata": {},
7
- "outputs": [],
8
- "source": [
9
- "#| default_exp learning_context"
10
- ]
11
- },
12
- {
13
- "cell_type": "markdown",
14
- "metadata": {},
15
- "source": [
16
- "# Learning Context\n",
17
- "\n",
18
- "> Core module for managing learning context (memory -> LOs, prior cases, knowledge gaps, feedback preferences)"
19
- ]
20
- },
21
- {
22
- "cell_type": "markdown",
23
- "metadata": {},
24
- "source": [
25
- "## Setup"
26
- ]
27
- },
28
- {
29
- "cell_type": "code",
30
- "execution_count": null,
31
- "metadata": {},
32
- "outputs": [],
33
- "source": [
34
- "#| hide\n",
35
- "from nbdev.showdoc import *"
36
- ]
37
- },
38
- {
39
- "cell_type": "code",
40
- "execution_count": null,
41
- "metadata": {},
42
- "outputs": [
43
- {
44
- "name": "stderr",
45
- "output_type": "stream",
46
- "text": [
47
- "C:\\Users\\deepa\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
48
- " from .autonotebook import tqdm as notebook_tqdm\n"
49
- ]
50
- }
51
- ],
52
- "source": [
53
- "#| export\n",
54
- "from typing import Dict, List, Optional, Any, Tuple\n",
55
- "from datetime import datetime\n",
56
- "import json\n",
57
- "from pathlib import Path\n",
58
- "from wardbuddy.utils import setup_logger, load_context_safely, save_context_safely"
59
- ]
60
- },
61
- {
62
- "cell_type": "code",
63
- "execution_count": null,
64
- "metadata": {},
65
- "outputs": [],
66
- "source": [
67
- "#| export\n",
68
- "logger = setup_logger(__name__)"
69
- ]
70
- },
71
- {
72
- "cell_type": "markdown",
73
- "metadata": {},
74
- "source": [
75
- "## Learning Context Management\n",
76
- "\n",
77
- "> Core module for managing student learning context and history"
78
- ]
79
- },
80
- {
81
- "cell_type": "markdown",
82
- "metadata": {},
83
- "source": [
84
- "The system needs to track and handle student information for personalised education. \n",
85
- "\n",
86
- "This module handles:\n",
87
- "* Tracking learning objectives\n",
88
- "* Managing case history\n",
89
- "* Monitoring knowledge gaps\n",
90
- "* Customizing feedback preferences\n",
91
- "\n",
92
- "and finally:\n",
93
- "* Context persistence\n"
94
- ]
95
- },
96
- {
97
- "cell_type": "code",
98
- "execution_count": null,
99
- "metadata": {},
100
- "outputs": [],
101
- "source": [
102
- "#| export\n",
103
- "class LearningContext:\n",
104
- " \"\"\"\n",
105
- " Manages the dynamic learning context for each student.\n",
106
- " \n",
107
- " Tracks:\n",
108
- " - Current rotation details\n",
109
- " - Learning objectives and progress\n",
110
- " - Knowledge gaps and strengths\n",
111
- " - Custom feedback preferences\n",
112
- " \"\"\"\n",
113
- " \n",
114
- " def __init__(self, context_file: Optional[Path] = None):\n",
115
- " \"\"\"\n",
116
- " Initialize learning context, optionally loading from file.\n",
117
- " \n",
118
- " Args:\n",
119
- " context_file: Optional path to saved context\n",
120
- " \"\"\"\n",
121
- " # Initialize current rotation\n",
122
- " self.current_rotation = {\n",
123
- " \"specialty\": \"\",\n",
124
- " \"start_date\": None,\n",
125
- " \"end_date\": None,\n",
126
- " \"key_focus_areas\": []\n",
127
- " }\n",
128
- " \n",
129
- " # Initialize learning objectives list\n",
130
- " self.learning_objectives = [] # [{\"objective\": str, \"status\": \"active\"|\"completed\", \"added\": datetime}]\n",
131
- " \n",
132
- " # Initialize knowledge profile\n",
133
- " self.knowledge_profile = {\n",
134
- " \"gaps\": {}, # topic -> confidence level\n",
135
- " \"strengths\": [],\n",
136
- " \"recent_progress\": [] # [{topic, improvement, date}]\n",
137
- " }\n",
138
- " \n",
139
- " # Initialize feedback preferences\n",
140
- " self.feedback_preferences = [] # [{\"focus\": str, \"active\": bool}]\n",
141
- " \n",
142
- " if context_file and context_file.exists():\n",
143
- " self.load_context(context_file)\n",
144
- " \n",
145
- " logger.info(\"Learning context initialized\")\n",
146
- " \n",
147
- " def update_rotation(self, rotation_details: Dict[str, Any]) -> None:\n",
148
- " \"\"\"Update current rotation details\"\"\"\n",
149
- " self.current_rotation.update(rotation_details)\n",
150
- " \n",
151
- " def add_learning_objective(self, objective: str) -> None:\n",
152
- " \"\"\"Add new learning objective\"\"\"\n",
153
- " self.learning_objectives.append({\n",
154
- " \"objective\": objective,\n",
155
- " \"status\": \"active\",\n",
156
- " \"added\": datetime.now().isoformat()\n",
157
- " })\n",
158
- " \n",
159
- " def complete_objective(self, objective: str) -> None:\n",
160
- " \"\"\"Mark learning objective as completed\"\"\"\n",
161
- " for obj in self.learning_objectives:\n",
162
- " if obj[\"objective\"] == objective and obj[\"status\"] == \"active\":\n",
163
- " obj[\"status\"] = \"completed\"\n",
164
- " obj[\"completed\"] = datetime.now().isoformat()\n",
165
- " break\n",
166
- " \n",
167
- " def update_knowledge_gap(self, topic: str, confidence: float) -> None:\n",
168
- " \"\"\"Update knowledge gap confidence level\"\"\"\n",
169
- " old_confidence = self.knowledge_profile[\"gaps\"].get(topic)\n",
170
- " self.knowledge_profile[\"gaps\"][topic] = confidence\n",
171
- " \n",
172
- " # Track progress if confidence improved\n",
173
- " if old_confidence and confidence > old_confidence:\n",
174
- " self.knowledge_profile[\"recent_progress\"].append({\n",
175
- " \"topic\": topic,\n",
176
- " \"improvement\": confidence - old_confidence,\n",
177
- " \"date\": datetime.now().isoformat()\n",
178
- " })\n",
179
- " \n",
180
- " # Keep only recent progress\n",
181
- " self.knowledge_profile[\"recent_progress\"] = \\\n",
182
- " self.knowledge_profile[\"recent_progress\"][-5:]\n",
183
- " \n",
184
- " def add_strength(self, topic: str) -> None:\n",
185
- " \"\"\"Add identified strength\"\"\"\n",
186
- " if topic not in self.knowledge_profile[\"strengths\"]:\n",
187
- " self.knowledge_profile[\"strengths\"].append(topic)\n",
188
- " \n",
189
- " def toggle_feedback_focus(self, focus: str, active: bool) -> None:\n",
190
- " \"\"\"Toggle feedback focus area\"\"\"\n",
191
- " # Update if exists\n",
192
- " for pref in self.feedback_preferences:\n",
193
- " if pref[\"focus\"] == focus:\n",
194
- " pref[\"active\"] = active\n",
195
- " return\n",
196
- " \n",
197
- " # Add if new\n",
198
- " self.feedback_preferences.append({\n",
199
- " \"focus\": focus,\n",
200
- " \"active\": active\n",
201
- " })\n",
202
- " \n",
203
- " def save_context(self, file_path: Path) -> None:\n",
204
- " \"\"\"Save context to file\"\"\"\n",
205
- " context_data = {\n",
206
- " \"current_rotation\": self.current_rotation,\n",
207
- " \"learning_objectives\": self.learning_objectives,\n",
208
- " \"knowledge_profile\": self.knowledge_profile,\n",
209
- " \"feedback_preferences\": self.feedback_preferences\n",
210
- " }\n",
211
- " save_context_safely(context_data, file_path)\n",
212
- " \n",
213
- " def load_context(self, file_path: Path) -> None:\n",
214
- " \"\"\"Load context from file\"\"\"\n",
215
- " try:\n",
216
- " context_data = load_context_safely(file_path)\n",
217
- " \n",
218
- " # Use .get() with default values to handle missing keys\n",
219
- " self.current_rotation = context_data.get(\"current_rotation\", {\n",
220
- " \"specialty\": \"\",\n",
221
- " \"start_date\": None,\n",
222
- " \"end_date\": None,\n",
223
- " \"key_focus_areas\": []\n",
224
- " })\n",
225
- " \n",
226
- " # Ensure required keys exist\n",
227
- " for key in [\"specialty\", \"start_date\", \"end_date\", \"key_focus_areas\"]:\n",
228
- " if key not in self.current_rotation:\n",
229
- " self.current_rotation[key] = None if key != \"key_focus_areas\" else []\n",
230
- " \n",
231
- " self.learning_objectives = context_data.get(\"learning_objectives\", [])\n",
232
- " self.knowledge_profile = context_data.get(\"knowledge_profile\", {\n",
233
- " \"gaps\": {},\n",
234
- " \"strengths\": [],\n",
235
- " \"recent_progress\": []\n",
236
- " })\n",
237
- " self.feedback_preferences = context_data.get(\"feedback_preferences\", [])\n",
238
- " \n",
239
- " logger.info(f\"Context loaded successfully from {file_path}\")\n",
240
- " \n",
241
- " except Exception as e:\n",
242
- " logger.error(f\"Error loading context: {str(e)}\")\n",
243
- " # Initialize with defaults on error\n",
244
- " self.__init__()"
245
- ]
246
- },
247
- {
248
- "cell_type": "markdown",
249
- "metadata": {},
250
- "source": [
251
- "## Tests"
252
- ]
253
- },
254
- {
255
- "cell_type": "code",
256
- "execution_count": null,
257
- "metadata": {},
258
- "outputs": [
259
- {
260
- "name": "stderr",
261
- "output_type": "stream",
262
- "text": [
263
- "2025-01-18 23:31:55,450 - __main__ - INFO - Learning context initialized\n",
264
- "2025-01-18 23:31:55,457 - __main__ - INFO - Learning context initialized\n"
265
- ]
266
- },
267
- {
268
- "name": "stdout",
269
- "output_type": "stream",
270
- "text": [
271
- "All learning context tests passed!\n"
272
- ]
273
- }
274
- ],
275
- "source": [
276
- "def test_learning_context():\n",
277
- " \"\"\"Test LearningContext functionality\"\"\"\n",
278
- " # Create a temporary directory for tests\n",
279
- " import tempfile\n",
280
- " temp_dir = tempfile.mkdtemp()\n",
281
- " temp_path = Path(temp_dir) / \"test_context.json\"\n",
282
- " \n",
283
- " try:\n",
284
- " # Initialize context\n",
285
- " context = LearningContext()\n",
286
- " \n",
287
- " # Test rotation updates\n",
288
- " rotation_details = {\n",
289
- " \"specialty\": \"Emergency Medicine\",\n",
290
- " \"start_date\": \"2025-01-01\",\n",
291
- " \"end_date\": \"2025-03-31\",\n",
292
- " \"key_focus_areas\": [\"Resuscitation\", \"Procedures\"]\n",
293
- " }\n",
294
- " context.update_rotation(rotation_details)\n",
295
- " assert context.current_rotation[\"specialty\"] == \"Emergency Medicine\"\n",
296
- " \n",
297
- " # Test learning objectives\n",
298
- " context.add_learning_objective(\"Improve chest pain assessment\")\n",
299
- " assert len(context.learning_objectives) == 1\n",
300
- " assert context.learning_objectives[0][\"status\"] == \"active\"\n",
301
- " \n",
302
- " # Test completing objectives\n",
303
- " context.complete_objective(\"Improve chest pain assessment\")\n",
304
- " assert context.learning_objectives[0][\"status\"] == \"completed\"\n",
305
- " \n",
306
- " # Test knowledge gaps\n",
307
- " context.update_knowledge_gap(\"ECG interpretation\", 0.6)\n",
308
- " assert context.knowledge_profile[\"gaps\"][\"ECG interpretation\"] == 0.6\n",
309
- " \n",
310
- " # Test progress tracking\n",
311
- " context.update_knowledge_gap(\"ECG interpretation\", 0.8)\n",
312
- " assert len(context.knowledge_profile[\"recent_progress\"]) == 1\n",
313
- " \n",
314
- " # Test strengths\n",
315
- " context.add_strength(\"History taking\")\n",
316
- " assert \"History taking\" in context.knowledge_profile[\"strengths\"]\n",
317
- " \n",
318
- " # Test feedback preferences\n",
319
- " context.toggle_feedback_focus(\"Include more ddx\", True)\n",
320
- " assert len(context.feedback_preferences) == 1\n",
321
- " assert context.feedback_preferences[0][\"active\"] == True\n",
322
- " \n",
323
- " # Test persistence\n",
324
- " context.save_context(temp_path)\n",
325
- " assert temp_path.exists()\n",
326
- " \n",
327
- " # Test loading in new context\n",
328
- " new_context = LearningContext(temp_path)\n",
329
- " assert new_context.current_rotation[\"specialty\"] == \"Emergency Medicine\"\n",
330
- " assert len(new_context.learning_objectives) == 1\n",
331
- " assert \"ECG interpretation\" in new_context.knowledge_profile[\"gaps\"]\n",
332
- " \n",
333
- " finally:\n",
334
- " # Cleanup\n",
335
- " import shutil\n",
336
- " shutil.rmtree(temp_dir)\n",
337
- " \n",
338
- " print(\"All learning context tests passed!\")\n",
339
- "\n",
340
- "# Run tests\n",
341
- "if __name__ == \"__main__\":\n",
342
- " test_learning_context()"
343
- ]
344
- }
345
- ],
346
- "metadata": {
347
- "kernelspec": {
348
- "display_name": "Python 3 (ipykernel)",
349
- "language": "python",
350
- "name": "python3"
351
- },
352
- "language_info": {
353
- "codemirror_mode": {
354
- "name": "ipython",
355
- "version": 3
356
- },
357
- "file_extension": ".py",
358
- "mimetype": "text/x-python",
359
- "name": "python",
360
- "nbconvert_exporter": "python",
361
- "pygments_lexer": "ipython3",
362
- "version": "3.12.7"
363
- }
364
- },
365
- "nbformat": 4,
366
- "nbformat_minor": 4
367
- }
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "#| default_exp learning_context"
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "markdown",
14
+ "metadata": {},
15
+ "source": [
16
+ "# Learning Context\n",
17
+ "\n",
18
+ "> Core module for managing learning context (memory -> LOs, prior cases, knowledge gaps, feedback preferences)"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "markdown",
23
+ "metadata": {},
24
+ "source": [
25
+ "## Setup"
26
+ ]
27
+ },
28
+ {
29
+ "cell_type": "code",
30
+ "execution_count": null,
31
+ "metadata": {},
32
+ "outputs": [],
33
+ "source": [
34
+ "#| hide\n",
35
+ "from nbdev.showdoc import *"
36
+ ]
37
+ },
38
+ {
39
+ "cell_type": "code",
40
+ "execution_count": null,
41
+ "metadata": {},
42
+ "outputs": [
43
+ {
44
+ "name": "stderr",
45
+ "output_type": "stream",
46
+ "text": [
47
+ "C:\\Users\\deepa\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
48
+ " from .autonotebook import tqdm as notebook_tqdm\n"
49
+ ]
50
+ }
51
+ ],
52
+ "source": [
53
+ "#| export\n",
54
+ "from typing import Dict, List, Optional, Any, Tuple\n",
55
+ "from datetime import datetime\n",
56
+ "import json\n",
57
+ "from pathlib import Path\n",
58
+ "from wardbuddy.utils import setup_logger, load_context_safely, save_context_safely"
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": null,
64
+ "metadata": {},
65
+ "outputs": [],
66
+ "source": [
67
+ "#| export\n",
68
+ "logger = setup_logger(__name__)"
69
+ ]
70
+ },
71
+ {
72
+ "cell_type": "markdown",
73
+ "metadata": {},
74
+ "source": [
75
+ "## Learning Context Management\n",
76
+ "\n",
77
+ "> Core module for managing student learning context and history"
78
+ ]
79
+ },
80
+ {
81
+ "cell_type": "markdown",
82
+ "metadata": {},
83
+ "source": [
84
+ "The system needs to track and handle student information for personalised education. \n",
85
+ "\n",
86
+ "This module handles:\n",
87
+ "* Tracking learning objectives\n",
88
+ "* Managing case history\n",
89
+ "* Monitoring knowledge gaps\n",
90
+ "* Customizing feedback preferences\n",
91
+ "\n",
92
+ "and finally:\n",
93
+ "* Context persistence\n"
94
+ ]
95
+ },
96
+ {
97
+ "cell_type": "code",
98
+ "execution_count": null,
99
+ "metadata": {},
100
+ "outputs": [],
101
+ "source": [
102
+ "#| export\n",
103
+ "class LearningContext:\n",
104
+ " \"\"\"\n",
105
+ " Manages the dynamic learning context for each student.\n",
106
+ " \n",
107
+ " Tracks:\n",
108
+ " - Current rotation details\n",
109
+ " - Learning objectives and progress\n",
110
+ " - Knowledge gaps and strengths\n",
111
+ " - Custom feedback preferences\n",
112
+ " \"\"\"\n",
113
+ " \n",
114
+ " def __init__(self, context_file: Optional[Path] = None):\n",
115
+ " \"\"\"\n",
116
+ " Initialize learning context, optionally loading from file.\n",
117
+ " \n",
118
+ " Args:\n",
119
+ " context_file: Optional path to saved context\n",
120
+ " \"\"\"\n",
121
+ " # Initialize current rotation\n",
122
+ " self.current_rotation = {\n",
123
+ " \"specialty\": \"\",\n",
124
+ " \"start_date\": None,\n",
125
+ " \"end_date\": None,\n",
126
+ " \"key_focus_areas\": []\n",
127
+ " }\n",
128
+ " \n",
129
+ " # Initialize learning objectives list\n",
130
+ " self.learning_objectives = [] # [{\"objective\": str, \"status\": \"active\"|\"completed\", \"added\": datetime}]\n",
131
+ " \n",
132
+ " # Initialize knowledge profile\n",
133
+ " self.knowledge_profile = {\n",
134
+ " \"gaps\": {}, # topic -> confidence level\n",
135
+ " \"strengths\": [],\n",
136
+ " \"recent_progress\": [] # [{topic, improvement, date}]\n",
137
+ " }\n",
138
+ " \n",
139
+ " # Initialize feedback preferences\n",
140
+ " self.feedback_preferences = [] # [{\"focus\": str, \"active\": bool}]\n",
141
+ " \n",
142
+ " if context_file and context_file.exists():\n",
143
+ " self.load_context(context_file)\n",
144
+ " \n",
145
+ " logger.info(\"Learning context initialized\")\n",
146
+ " \n",
147
+ " def update_rotation(self, rotation_details: Dict[str, Any]) -> None:\n",
148
+ " \"\"\"Update current rotation details\"\"\"\n",
149
+ " self.current_rotation.update(rotation_details)\n",
150
+ " \n",
151
+ " def add_learning_objective(self, objective: str) -> None:\n",
152
+ " \"\"\"Add new learning objective\"\"\"\n",
153
+ " self.learning_objectives.append({\n",
154
+ " \"objective\": objective,\n",
155
+ " \"status\": \"active\",\n",
156
+ " \"added\": datetime.now().isoformat()\n",
157
+ " })\n",
158
+ " \n",
159
+ " def complete_objective(self, objective: str) -> None:\n",
160
+ " \"\"\"Mark learning objective as completed\"\"\"\n",
161
+ " for obj in self.learning_objectives:\n",
162
+ " if obj[\"objective\"] == objective and obj[\"status\"] == \"active\":\n",
163
+ " obj[\"status\"] = \"completed\"\n",
164
+ " obj[\"completed\"] = datetime.now().isoformat()\n",
165
+ " break\n",
166
+ " \n",
167
+ " def update_knowledge_gap(self, topic: str, confidence: float) -> None:\n",
168
+ " \"\"\"Update knowledge gap confidence level\"\"\"\n",
169
+ " old_confidence = self.knowledge_profile[\"gaps\"].get(topic)\n",
170
+ " self.knowledge_profile[\"gaps\"][topic] = confidence\n",
171
+ " \n",
172
+ " # Track progress if confidence improved\n",
173
+ " if old_confidence and confidence > old_confidence:\n",
174
+ " self.knowledge_profile[\"recent_progress\"].append({\n",
175
+ " \"topic\": topic,\n",
176
+ " \"improvement\": confidence - old_confidence,\n",
177
+ " \"date\": datetime.now().isoformat()\n",
178
+ " })\n",
179
+ " \n",
180
+ " # Keep only recent progress\n",
181
+ " self.knowledge_profile[\"recent_progress\"] = \\\n",
182
+ " self.knowledge_profile[\"recent_progress\"][-5:]\n",
183
+ " \n",
184
+ " def add_strength(self, topic: str) -> None:\n",
185
+ " \"\"\"Add identified strength\"\"\"\n",
186
+ " if topic not in self.knowledge_profile[\"strengths\"]:\n",
187
+ " self.knowledge_profile[\"strengths\"].append(topic)\n",
188
+ " \n",
189
+ " def toggle_feedback_focus(self, focus: str, active: bool) -> None:\n",
190
+ " \"\"\"Toggle feedback focus area\"\"\"\n",
191
+ " # Update if exists\n",
192
+ " for pref in self.feedback_preferences:\n",
193
+ " if pref[\"focus\"] == focus:\n",
194
+ " pref[\"active\"] = active\n",
195
+ " return\n",
196
+ " \n",
197
+ " # Add if new\n",
198
+ " self.feedback_preferences.append({\n",
199
+ " \"focus\": focus,\n",
200
+ " \"active\": active\n",
201
+ " })\n",
202
+ " \n",
203
+ " def save_context(self, file_path: Path) -> None:\n",
204
+ " \"\"\"Save context to file\"\"\"\n",
205
+ " context_data = {\n",
206
+ " \"current_rotation\": self.current_rotation,\n",
207
+ " \"learning_objectives\": self.learning_objectives,\n",
208
+ " \"knowledge_profile\": self.knowledge_profile,\n",
209
+ " \"feedback_preferences\": self.feedback_preferences\n",
210
+ " }\n",
211
+ " save_context_safely(context_data, file_path)\n",
212
+ " \n",
213
+ " def load_context(self, file_path: Path) -> None:\n",
214
+ " \"\"\"Load context from file\"\"\"\n",
215
+ " try:\n",
216
+ " context_data = load_context_safely(file_path)\n",
217
+ " \n",
218
+ " # Use .get() with default values to handle missing keys\n",
219
+ " self.current_rotation = context_data.get(\"current_rotation\", {\n",
220
+ " \"specialty\": \"\",\n",
221
+ " \"start_date\": None,\n",
222
+ " \"end_date\": None,\n",
223
+ " \"key_focus_areas\": []\n",
224
+ " })\n",
225
+ " \n",
226
+ " # Ensure required keys exist\n",
227
+ " for key in [\"specialty\", \"start_date\", \"end_date\", \"key_focus_areas\"]:\n",
228
+ " if key not in self.current_rotation:\n",
229
+ " self.current_rotation[key] = None if key != \"key_focus_areas\" else []\n",
230
+ " \n",
231
+ " self.learning_objectives = context_data.get(\"learning_objectives\", [])\n",
232
+ " self.knowledge_profile = context_data.get(\"knowledge_profile\", {\n",
233
+ " \"gaps\": {},\n",
234
+ " \"strengths\": [],\n",
235
+ " \"recent_progress\": []\n",
236
+ " })\n",
237
+ " self.feedback_preferences = context_data.get(\"feedback_preferences\", [])\n",
238
+ " \n",
239
+ " logger.info(f\"Context loaded successfully from {file_path}\")\n",
240
+ " \n",
241
+ " except Exception as e:\n",
242
+ " logger.error(f\"Error loading context: {str(e)}\")\n",
243
+ " # Initialize with defaults on error\n",
244
+ " self.__init__()"
245
+ ]
246
+ },
247
+ {
248
+ "cell_type": "markdown",
249
+ "metadata": {},
250
+ "source": [
251
+ "## Tests"
252
+ ]
253
+ },
254
+ {
255
+ "cell_type": "code",
256
+ "execution_count": null,
257
+ "metadata": {},
258
+ "outputs": [
259
+ {
260
+ "name": "stderr",
261
+ "output_type": "stream",
262
+ "text": [
263
+ "2025-01-18 23:31:55,450 - __main__ - INFO - Learning context initialized\n",
264
+ "2025-01-18 23:31:55,457 - __main__ - INFO - Learning context initialized\n"
265
+ ]
266
+ },
267
+ {
268
+ "name": "stdout",
269
+ "output_type": "stream",
270
+ "text": [
271
+ "All learning context tests passed!\n"
272
+ ]
273
+ }
274
+ ],
275
+ "source": [
276
+ "def test_learning_context():\n",
277
+ " \"\"\"Test LearningContext functionality\"\"\"\n",
278
+ " # Create a temporary directory for tests\n",
279
+ " import tempfile\n",
280
+ " temp_dir = tempfile.mkdtemp()\n",
281
+ " temp_path = Path(temp_dir) / \"test_context.json\"\n",
282
+ " \n",
283
+ " try:\n",
284
+ " # Initialize context\n",
285
+ " context = LearningContext()\n",
286
+ " \n",
287
+ " # Test rotation updates\n",
288
+ " rotation_details = {\n",
289
+ " \"specialty\": \"Emergency Medicine\",\n",
290
+ " \"start_date\": \"2025-01-01\",\n",
291
+ " \"end_date\": \"2025-03-31\",\n",
292
+ " \"key_focus_areas\": [\"Resuscitation\", \"Procedures\"]\n",
293
+ " }\n",
294
+ " context.update_rotation(rotation_details)\n",
295
+ " assert context.current_rotation[\"specialty\"] == \"Emergency Medicine\"\n",
296
+ " \n",
297
+ " # Test learning objectives\n",
298
+ " context.add_learning_objective(\"Improve chest pain assessment\")\n",
299
+ " assert len(context.learning_objectives) == 1\n",
300
+ " assert context.learning_objectives[0][\"status\"] == \"active\"\n",
301
+ " \n",
302
+ " # Test completing objectives\n",
303
+ " context.complete_objective(\"Improve chest pain assessment\")\n",
304
+ " assert context.learning_objectives[0][\"status\"] == \"completed\"\n",
305
+ " \n",
306
+ " # Test knowledge gaps\n",
307
+ " context.update_knowledge_gap(\"ECG interpretation\", 0.6)\n",
308
+ " assert context.knowledge_profile[\"gaps\"][\"ECG interpretation\"] == 0.6\n",
309
+ " \n",
310
+ " # Test progress tracking\n",
311
+ " context.update_knowledge_gap(\"ECG interpretation\", 0.8)\n",
312
+ " assert len(context.knowledge_profile[\"recent_progress\"]) == 1\n",
313
+ " \n",
314
+ " # Test strengths\n",
315
+ " context.add_strength(\"History taking\")\n",
316
+ " assert \"History taking\" in context.knowledge_profile[\"strengths\"]\n",
317
+ " \n",
318
+ " # Test feedback preferences\n",
319
+ " context.toggle_feedback_focus(\"Include more ddx\", True)\n",
320
+ " assert len(context.feedback_preferences) == 1\n",
321
+ " assert context.feedback_preferences[0][\"active\"] == True\n",
322
+ " \n",
323
+ " # Test persistence\n",
324
+ " context.save_context(temp_path)\n",
325
+ " assert temp_path.exists()\n",
326
+ " \n",
327
+ " # Test loading in new context\n",
328
+ " new_context = LearningContext(temp_path)\n",
329
+ " assert new_context.current_rotation[\"specialty\"] == \"Emergency Medicine\"\n",
330
+ " assert len(new_context.learning_objectives) == 1\n",
331
+ " assert \"ECG interpretation\" in new_context.knowledge_profile[\"gaps\"]\n",
332
+ " \n",
333
+ " finally:\n",
334
+ " # Cleanup\n",
335
+ " import shutil\n",
336
+ " shutil.rmtree(temp_dir)\n",
337
+ " \n",
338
+ " print(\"All learning context tests passed!\")\n",
339
+ "\n",
340
+ "# Run tests\n",
341
+ "if __name__ == \"__main__\":\n",
342
+ " test_learning_context()"
343
+ ]
344
+ }
345
+ ],
346
+ "metadata": {
347
+ "kernelspec": {
348
+ "display_name": "python3",
349
+ "language": "python",
350
+ "name": "python3"
351
+ }
352
+ },
353
+ "nbformat": 4,
354
+ "nbformat_minor": 4
355
+ }
 
 
 
 
 
 
 
 
 
 
 
 
nbs/01_clinical_tutor.ipynb CHANGED
@@ -218,52 +218,41 @@
218
  " # Could add exponential backoff here if needed\n",
219
  " \n",
220
  " def _build_discussion_prompt(self) -> str:\n",
221
- " \"\"\"\n",
222
- " Build context-aware prompt for case discussion.\n",
223
- " \n",
224
- " Incorporates:\n",
225
- " - Current rotation details\n",
226
- " - Active feedback preferences\n",
227
- " - Recent learning points\n",
228
- " - Knowledge gaps needing attention\n",
229
- " \n",
230
- " Returns:\n",
231
- " str: Contextualized system prompt\n",
232
- " \"\"\"\n",
233
  " rotation = self.learning_context.current_rotation\n",
234
  " active_preferences = [\n",
235
  " p[\"focus\"] for p in self.learning_context.feedback_preferences \n",
236
  " if p[\"active\"]\n",
237
  " ]\n",
238
  " \n",
239
- " # Get relevant knowledge gaps\n",
240
  " significant_gaps = {\n",
241
  " topic: score for topic, score \n",
242
  " in self.learning_context.knowledge_profile[\"gaps\"].items()\n",
243
  " if score < 0.7 # Only include significant gaps\n",
244
  " }\n",
245
  " \n",
246
- " prompt = f\"\"\"You are an experienced clinical supervisor in {rotation['specialty']} \n",
247
- " providing teaching and feedback. You aim to:\n",
248
- "\n",
249
- " 1. Help students develop strong clinical reasoning\n",
250
- " 2. Connect theory to practical applications\n",
251
- " 3. Build diagnostic confidence\n",
252
- " 4. Improve presentation skills\n",
253
  "\n",
 
 
 
 
 
 
 
254
  " Current Rotation Focus Areas:\n",
255
  " {', '.join(rotation['key_focus_areas'])}\n",
256
  "\n",
257
- " Areas Needing Attention:\n",
258
- " {', '.join(f'{topic} (confidence: {score:.1f})' for topic, score in significant_gaps.items()) if significant_gaps else 'No specific gaps identified'}\n",
259
  "\n",
260
- " Student's Requested Focus:\n",
261
- " {', '.join(active_preferences) if active_preferences else 'General clinical feedback'}\n",
 
 
 
 
262
  "\n",
263
- " Engage naturally as a supportive but challenging supervisor would during case \n",
264
- " presentations. Ask probing questions when appropriate, share relevant clinical \n",
265
- " pearls, and help the student build their clinical reasoning skills.\"\"\"\n",
266
- " \n",
267
  " return prompt\n",
268
  " \n",
269
  " def _build_analysis_prompt(self, conversation: List[Dict[str, str]]) -> str:\n",
 
218
  " # Could add exponential backoff here if needed\n",
219
  " \n",
220
  " def _build_discussion_prompt(self) -> str:\n",
221
+ " \"\"\"Build context-aware prompt for case discussion.\"\"\"\n",
 
 
 
 
 
 
 
 
 
 
 
222
  " rotation = self.learning_context.current_rotation\n",
223
  " active_preferences = [\n",
224
  " p[\"focus\"] for p in self.learning_context.feedback_preferences \n",
225
  " if p[\"active\"]\n",
226
  " ]\n",
227
  " \n",
 
228
  " significant_gaps = {\n",
229
  " topic: score for topic, score \n",
230
  " in self.learning_context.knowledge_profile[\"gaps\"].items()\n",
231
  " if score < 0.7 # Only include significant gaps\n",
232
  " }\n",
233
  " \n",
234
+ " prompt = f\"\"\"You are an experienced clinical supervisor in {rotation['specialty']}. Act as an engaging and conversational tutor who coaches towards deeper understanding through Socratic dialogue and targeted questions.\n",
 
 
 
 
 
 
235
  "\n",
236
+ " Key Principles:\n",
237
+ " 1. Assume I have strong foundational knowledge in medicine, clinical reasoning, and pre-medical sciences\n",
238
+ " 2. Focus on high-level connections and nuanced clinical decision-making\n",
239
+ " 3. Use targeted questions to explore my thought process and highlight key learning points\n",
240
+ " 4. Share relevant clinical pearls and real-world applications\n",
241
+ " 5. Be conversational and engaging, avoiding lecture-style responses\n",
242
+ " \n",
243
  " Current Rotation Focus Areas:\n",
244
  " {', '.join(rotation['key_focus_areas'])}\n",
245
  "\n",
246
+ " Areas for Deep Dive:\n",
247
+ " {', '.join(f'{topic} (confidence: {score:.1f})' for topic, score in significant_gaps.items()) if significant_gaps else 'General clinical reasoning'}\n",
248
  "\n",
249
+ " Student's Interests:\n",
250
+ " {', '.join(active_preferences) if active_preferences else 'Broad clinical discussion'}\n",
251
+ "\n",
252
+ " Engage as a supportive colleague who challenges thinking through conversation. Ask probing questions \n",
253
+ " that explore clinical reasoning and highlight important connections. I will ask for clarification \n",
254
+ " if concepts need more explanation.\"\"\"\n",
255
  "\n",
 
 
 
 
256
  " return prompt\n",
257
  " \n",
258
  " def _build_analysis_prompt(self, conversation: List[Dict[str, str]]) -> str:\n",
nbs/02_learning_interface.ipynb CHANGED
@@ -1,979 +1,972 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": null,
6
- "id": "7ce0a47c-1c4f-44a4-a9d8-9ea6399a8f84",
7
- "metadata": {},
8
- "outputs": [],
9
- "source": [
10
- "#| default_exp learning_interface"
11
- ]
12
- },
13
- {
14
- "cell_type": "markdown",
15
- "id": "55331735-898e-411b-b751-5b380605be36",
16
- "metadata": {},
17
- "source": [
18
- "# Learning Interface\n",
19
- "\n",
20
- "> Gradio interface"
21
- ]
22
- },
23
- {
24
- "cell_type": "markdown",
25
- "id": "dd401991-b919-423e-9da7-961387faf11e",
26
- "metadata": {},
27
- "source": [
28
- "## Setup"
29
- ]
30
- },
31
- {
32
- "cell_type": "code",
33
- "execution_count": null,
34
- "id": "edc3fbb1-13ef-408a-b5fe-eb7a6821915b",
35
- "metadata": {},
36
- "outputs": [],
37
- "source": [
38
- "#| hide\n",
39
- "from nbdev.showdoc import *"
40
- ]
41
- },
42
- {
43
- "cell_type": "code",
44
- "execution_count": null,
45
- "id": "4be213cf-89b4-48c8-9592-f509332da485",
46
- "metadata": {},
47
- "outputs": [
48
- {
49
- "ename": "ImportError",
50
- "evalue": "cannot import name 'ClinicalTutor' from 'wardbuddy.clinical_tutor' (C:\\Users\\deepa\\OneDrive\\Documents\\StudyBuddy\\wardbuddy\\wardbuddy\\clinical_tutor.py)",
51
- "output_type": "error",
52
- "traceback": [
53
- "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
54
- "\u001b[1;31mImportError\u001b[0m Traceback (most recent call last)",
55
- "Cell \u001b[1;32mIn[4], line 7\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpathlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Path\n\u001b[0;32m 6\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01masyncio\u001b[39;00m\n\u001b[1;32m----> 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mclinical_tutor\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m ClinicalTutor\n\u001b[0;32m 8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mutils\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m format_response\n\u001b[0;32m 9\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlearning_context\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m setup_logger\n",
56
- "\u001b[1;31mImportError\u001b[0m: cannot import name 'ClinicalTutor' from 'wardbuddy.clinical_tutor' (C:\\Users\\deepa\\OneDrive\\Documents\\StudyBuddy\\wardbuddy\\wardbuddy\\clinical_tutor.py)"
57
- ]
58
- }
59
- ],
60
- "source": [
61
- "#| export\n",
62
- "from typing import Dict, List, Optional, Tuple, Any\n",
63
- "import gradio as gr\n",
64
- "from pathlib import Path\n",
65
- "import asyncio\n",
66
- "from datetime import datetime\n",
67
- "import pandas as pd\n",
68
- "from wardbuddy.clinical_tutor import ClinicalTutor\n",
69
- "from wardbuddy.learning_context import setup_logger\n",
70
- "\n",
71
- "logger = setup_logger(__name__)"
72
- ]
73
- },
74
- {
75
- "cell_type": "markdown",
76
- "id": "c39da1db-e630-4296-93f6-03b9188320cc",
77
- "metadata": {},
78
- "source": [
79
- "## Learning Interface"
80
- ]
81
- },
82
- {
83
- "cell_type": "code",
84
- "execution_count": null,
85
- "id": "aff3d321-2116-475f-b906-f74889e76d66",
86
- "metadata": {},
87
- "outputs": [],
88
- "source": [
89
- "#| export\n",
90
- "def create_dashboard_css() -> str:\n",
91
- " \"\"\"Create custom CSS for dashboard styling\"\"\"\n",
92
- " return \"\"\"\n",
93
- " .dashboard-card {\n",
94
- " border: 1px solid #e2e8f0;\n",
95
- " border-radius: 8px;\n",
96
- " padding: 16px;\n",
97
- " margin: 8px 0;\n",
98
- " background: white;\n",
99
- " }\n",
100
- " \n",
101
- " .status-active {\n",
102
- " color: #48bb78;\n",
103
- " font-weight: 500;\n",
104
- " }\n",
105
- " \n",
106
- " .status-completed {\n",
107
- " color: #718096;\n",
108
- " }\n",
109
- " \n",
110
- " .dashboard-header {\n",
111
- " font-size: 1.25rem;\n",
112
- " font-weight: 600;\n",
113
- " margin-bottom: 1rem;\n",
114
- " }\n",
115
- " \n",
116
- " .summary-modal {\n",
117
- " max-width: 800px !important;\n",
118
- " }\n",
119
- " \n",
120
- " .feedback-tag {\n",
121
- " display: inline-block;\n",
122
- " padding: 4px 8px;\n",
123
- " border-radius: 4px;\n",
124
- " margin: 4px;\n",
125
- " font-size: 0.875rem;\n",
126
- " }\n",
127
- " \n",
128
- " .feedback-active {\n",
129
- " background: #ebf8ff;\n",
130
- " color: #2b6cb0;\n",
131
- " }\n",
132
- " \n",
133
- " .feedback-inactive {\n",
134
- " background: #f7fafc;\n",
135
- " color: #718096;\n",
136
- " }\n",
137
- " \"\"\""
138
- ]
139
- },
140
- {
141
- "cell_type": "markdown",
142
- "id": "de7ace04-4841-461d-89bb-234b5f8b48e1",
143
- "metadata": {},
144
- "source": [
145
- "This module provides the user interface for the clinical learning system, including:\n",
146
- " * Case presentation and feedback\n",
147
- " * Learning preference configuration\n",
148
- " * Session management\n",
149
- " * Progress visualization"
150
- ]
151
- },
152
- {
153
- "cell_type": "code",
154
- "execution_count": null,
155
- "id": "675c20cc-43aa-4897-89ba-4fa26dd37c20",
156
- "metadata": {},
157
- "outputs": [],
158
- "source": [
159
- "#| export\n",
160
- "\n",
161
- "class LearningInterface:\n",
162
- " \"\"\"\n",
163
- " Gradio interface for clinical learning interactions.\n",
164
- " \n",
165
- " Features:\n",
166
- " - Natural case discussion chat\n",
167
- " - Dynamic learning dashboard\n",
168
- " - Post-discussion analysis\n",
169
- " - Progress tracking\n",
170
- " \"\"\"\n",
171
- " \n",
172
- " def __init__(\n",
173
- " self,\n",
174
- " context_path: Optional[Path] = None,\n",
175
- " theme: str = \"default\"\n",
176
- " ):\n",
177
- " \"\"\"Initialize learning interface.\"\"\"\n",
178
- " self.tutor = ClinicalTutor(context_path)\n",
179
- " self.theme = theme\n",
180
- " self.context_path = context_path\n",
181
- " \n",
182
- " # Track current discussion state\n",
183
- " self.current_discussion = {\n",
184
- " \"started\": None,\n",
185
- " \"case_type\": None,\n",
186
- " \"messages\": []\n",
187
- " }\n",
188
- " \n",
189
- " logger.info(\"Learning interface initialized\")\n",
190
- " \n",
191
- " async def process_chat(\n",
192
- " self,\n",
193
- " message: str,\n",
194
- " history: List[Dict[str, str]],\n",
195
- " state: Dict[str, Any]\n",
196
- " ) -> Tuple[List[Dict[str, str]], str, Dict[str, Any]]:\n",
197
- " \"\"\"\n",
198
- " Process chat messages with state management.\n",
199
- " \n",
200
- " Args:\n",
201
- " message: User input message\n",
202
- " history: Chat history\n",
203
- " state: Current interface state\n",
204
- " \n",
205
- " Returns:\n",
206
- " tuple: (updated history, cleared message, updated state)\n",
207
- " \"\"\"\n",
208
- " try:\n",
209
- " if not message.strip():\n",
210
- " return history, \"\", state\n",
211
- " \n",
212
- " # Start new discussion if none active\n",
213
- " if not state.get(\"discussion_active\"):\n",
214
- " state[\"discussion_active\"] = True\n",
215
- " state[\"discussion_start\"] = datetime.now().isoformat()\n",
216
- " \n",
217
- " # Get tutor response\n",
218
- " response = await self.tutor.discuss_case(message)\n",
219
- " \n",
220
- " # Update history and state\n",
221
- " if history is None:\n",
222
- " history = []\n",
223
- " history.extend([\n",
224
- " {\"role\": \"user\", \"content\": message},\n",
225
- " {\"role\": \"assistant\", \"content\": response}\n",
226
- " ])\n",
227
- " \n",
228
- " state[\"last_message\"] = datetime.now().isoformat()\n",
229
- " \n",
230
- " return history, \"\", state\n",
231
- " \n",
232
- " except Exception as e:\n",
233
- " logger.error(f\"Error in chat: {str(e)}\")\n",
234
- " return history or [], \"\", state\n",
235
- "\n",
236
- " async def end_discussion(\n",
237
- " self,\n",
238
- " history: List[Dict[str, str]],\n",
239
- " state: Dict[str, Any]\n",
240
- " ) -> Tuple[Dict[str, Any], Dict[str, Any]]:\n",
241
- " \"\"\"\n",
242
- " Analyze completed discussion and prepare summary.\n",
243
- " \n",
244
- " Args:\n",
245
- " history: Chat history\n",
246
- " state: Current interface state\n",
247
- " \n",
248
- " Returns:\n",
249
- " tuple: (analysis results, updated state)\n",
250
- " \"\"\"\n",
251
- " try:\n",
252
- " if not history:\n",
253
- " return {\n",
254
- " \"learning_points\": [],\n",
255
- " \"gaps\": {},\n",
256
- " \"strengths\": [],\n",
257
- " \"suggested_objectives\": []\n",
258
- " }, state\n",
259
- " \n",
260
- " # Get analysis\n",
261
- " analysis = await self.tutor.analyze_discussion(history)\n",
262
- " \n",
263
- " # Reset discussion state\n",
264
- " state[\"discussion_active\"] = False\n",
265
- " state[\"discussion_start\"] = None\n",
266
- " state[\"last_message\"] = None\n",
267
- " \n",
268
- " return analysis, state\n",
269
- " \n",
270
- " except Exception as e:\n",
271
- " logger.error(f\"Error analyzing discussion: {str(e)}\")\n",
272
- " return {\n",
273
- " \"learning_points\": [],\n",
274
- " \"gaps\": {},\n",
275
- " \"strengths\": [],\n",
276
- " \"suggested_objectives\": []\n",
277
- " }, state\n",
278
- " \n",
279
- " def update_rotation(\n",
280
- " self,\n",
281
- " specialty: str,\n",
282
- " start_date: str,\n",
283
- " end_date: str,\n",
284
- " focus_areas: str\n",
285
- " ) -> Tuple[str, str, str, str]:\n",
286
- " \"\"\"\n",
287
- " Update rotation details and return updated values.\n",
288
- " \n",
289
- " Args:\n",
290
- " specialty: Rotation specialty\n",
291
- " start_date: Start date string\n",
292
- " end_date: End date string\n",
293
- " focus_areas: Comma-separated focus areas\n",
294
- " \n",
295
- " Returns:\n",
296
- " tuple: Updated field values\n",
297
- " \"\"\"\n",
298
- " try:\n",
299
- " # Parse focus areas\n",
300
- " focus_list = [\n",
301
- " area.strip() \n",
302
- " for area in focus_areas.split(\",\") \n",
303
- " if area.strip()\n",
304
- " ]\n",
305
- " \n",
306
- " # Update context\n",
307
- " rotation = {\n",
308
- " \"specialty\": specialty,\n",
309
- " \"start_date\": start_date,\n",
310
- " \"end_date\": end_date,\n",
311
- " \"key_focus_areas\": focus_list\n",
312
- " }\n",
313
- " self.tutor.learning_context.update_rotation(rotation)\n",
314
- " \n",
315
- " # Return updated values\n",
316
- " return (\n",
317
- " specialty,\n",
318
- " start_date,\n",
319
- " end_date,\n",
320
- " \",\".join(focus_list)\n",
321
- " )\n",
322
- " \n",
323
- " except Exception as e:\n",
324
- " logger.error(f\"Error updating rotation: {str(e)}\")\n",
325
- " current = self.tutor.learning_context.current_rotation\n",
326
- " return (\n",
327
- " current[\"specialty\"],\n",
328
- " current[\"start_date\"] or \"\",\n",
329
- " current[\"end_date\"] or \"\",\n",
330
- " \",\".join(current[\"key_focus_areas\"])\n",
331
- " )\n",
332
- "\n",
333
- " def add_objective(\n",
334
- " self,\n",
335
- " objective: str,\n",
336
- " objectives_df: pd.DataFrame\n",
337
- " ) -> pd.DataFrame:\n",
338
- " \"\"\"\n",
339
- " Add new learning objective and return updated dataframe.\n",
340
- " \n",
341
- " Args:\n",
342
- " objective: New objective text\n",
343
- " objectives_df: Current objectives dataframe\n",
344
- " \n",
345
- " Returns:\n",
346
- " pd.DataFrame: Updated objectives list\n",
347
- " \"\"\"\n",
348
- " try:\n",
349
- " if not objective.strip():\n",
350
- " return objectives_df\n",
351
- " \n",
352
- " # Add to context\n",
353
- " self.tutor.learning_context.add_learning_objective(objective)\n",
354
- " \n",
355
- " # Convert to dataframe\n",
356
- " return pd.DataFrame([\n",
357
- " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n",
358
- " for obj in self.tutor.learning_context.learning_objectives\n",
359
- " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n",
360
- " \n",
361
- " except Exception as e:\n",
362
- " logger.error(f\"Error adding objective: {str(e)}\")\n",
363
- " return objectives_df\n",
364
- "\n",
365
- " def toggle_objective_status(\n",
366
- " self,\n",
367
- " evt: gr.SelectData, # Updated to use gr.SelectData\n",
368
- " objectives_df: pd.DataFrame\n",
369
- " ) -> pd.DataFrame:\n",
370
- " \"\"\"\n",
371
- " Toggle objective status between active and completed.\n",
372
- " \n",
373
- " Args:\n",
374
- " evt: Gradio select event containing row index\n",
375
- " objectives_df: Current objectives dataframe\n",
376
- " \n",
377
- " Returns:\n",
378
- " pd.DataFrame: Updated objectives list\n",
379
- " \"\"\"\n",
380
- " try:\n",
381
- " objective_idx = evt.index[0] # Get selected row index\n",
382
- " if objective_idx >= len(objectives_df):\n",
383
- " return objectives_df\n",
384
- " \n",
385
- " # Get objective\n",
386
- " objective = objectives_df.iloc[objective_idx][\"Objective\"]\n",
387
- " current_status = objectives_df.iloc[objective_idx][\"Status\"]\n",
388
- " \n",
389
- " # Toggle in context\n",
390
- " if current_status == \"active\":\n",
391
- " self.tutor.learning_context.complete_objective(objective)\n",
392
- " else:\n",
393
- " self.tutor.learning_context.add_learning_objective(objective)\n",
394
- " \n",
395
- " # Update dataframe\n",
396
- " return pd.DataFrame([\n",
397
- " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n",
398
- " for obj in self.tutor.learning_context.learning_objectives\n",
399
- " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n",
400
- " \n",
401
- " except Exception as e:\n",
402
- " logger.error(f\"Error toggling objective: {str(e)}\")\n",
403
- " return objectives_df\n",
404
- "\n",
405
- " def add_feedback_focus(\n",
406
- " self,\n",
407
- " focus: str,\n",
408
- " feedback_df: pd.DataFrame\n",
409
- " ) -> pd.DataFrame:\n",
410
- " \"\"\"Add new feedback focus area.\"\"\"\n",
411
- " try:\n",
412
- " if not focus.strip():\n",
413
- " return feedback_df\n",
414
- " \n",
415
- " # Add to context\n",
416
- " self.tutor.learning_context.toggle_feedback_focus(focus, True)\n",
417
- " \n",
418
- " # Update dataframe\n",
419
- " return pd.DataFrame([\n",
420
- " [pref[\"focus\"], pref[\"active\"]]\n",
421
- " for pref in self.tutor.learning_context.feedback_preferences\n",
422
- " ], columns=[\"Focus Area\", \"Active\"])\n",
423
- " \n",
424
- " except Exception as e:\n",
425
- " logger.error(f\"Error adding feedback focus: {str(e)}\")\n",
426
- " return feedback_df\n",
427
- "\n",
428
- " def toggle_feedback_status(\n",
429
- " self,\n",
430
- " evt: gr.SelectData, # Updated to use gr.SelectData\n",
431
- " feedback_df: pd.DataFrame\n",
432
- " ) -> pd.DataFrame:\n",
433
- " \"\"\"Toggle feedback focus active status.\"\"\"\n",
434
- " try:\n",
435
- " focus_idx = evt.index[0] # Get selected row index\n",
436
- " if focus_idx >= len(feedback_df):\n",
437
- " return feedback_df\n",
438
- " \n",
439
- " # Get focus area\n",
440
- " focus = feedback_df.iloc[focus_idx][\"Focus Area\"]\n",
441
- " current_status = feedback_df.iloc[focus_idx][\"Active\"]\n",
442
- " \n",
443
- " # Toggle in context\n",
444
- " self.tutor.learning_context.toggle_feedback_focus(\n",
445
- " focus, \n",
446
- " not current_status\n",
447
- " )\n",
448
- " \n",
449
- " # Update dataframe\n",
450
- " return pd.DataFrame([\n",
451
- " [pref[\"focus\"], pref[\"active\"]]\n",
452
- " for pref in self.tutor.learning_context.feedback_preferences\n",
453
- " ], columns=[\"Focus Area\", \"Active\"])\n",
454
- " \n",
455
- " except Exception as e:\n",
456
- " logger.error(f\"Error toggling feedback: {str(e)}\")\n",
457
- " return feedback_df\n",
458
- "\n",
459
- " def create_interface(self) -> gr.Blocks:\n",
460
- " \"\"\"Create and configure the Gradio interface\"\"\"\n",
461
- " with gr.Blocks(\n",
462
- " title=\"Clinical Learning Assistant\",\n",
463
- " theme=self.theme,\n",
464
- " css=create_dashboard_css()\n",
465
- " ) as interface:\n",
466
- " # State management\n",
467
- " state = gr.State({\n",
468
- " \"discussion_active\": False,\n",
469
- " \"discussion_start\": None,\n",
470
- " \"last_message\": None\n",
471
- " })\n",
472
- " \n",
473
- " # Header\n",
474
- " with gr.Row():\n",
475
- " gr.Markdown(\n",
476
- " \"# Clinical Learning Assistant\",\n",
477
- " elem_classes=[\"dashboard-header\"]\n",
478
- " )\n",
479
- " \n",
480
- " with gr.Row():\n",
481
- " # Left column - Chat interface\n",
482
- " with gr.Column(scale=2):\n",
483
- " # Active discussion indicator\n",
484
- " discussion_status = gr.Markdown(\n",
485
- " \"Start a new case discussion\",\n",
486
- " elem_classes=[\"dashboard-card\"]\n",
487
- " )\n",
488
- " \n",
489
- " # Chat interface\n",
490
- " chatbot = gr.Chatbot(\n",
491
- " height=500,\n",
492
- " label=\"Case Discussion\",\n",
493
- " show_label=True,\n",
494
- " elem_classes=[\"dashboard-card\"]\n",
495
- " )\n",
496
- " \n",
497
- " with gr.Row():\n",
498
- " msg = gr.Textbox(\n",
499
- " label=\"Present your case or ask questions\",\n",
500
- " placeholder=(\n",
501
- " \"Present your case as you would to your supervisor:\\n\"\n",
502
- " \"- Start with the chief complaint\\n\"\n",
503
- " \"- Include relevant history and findings\\n\"\n",
504
- " \"- Share your assessment and plan\"\n",
505
- " ),\n",
506
- " lines=5\n",
507
- " )\n",
508
- " \n",
509
- " with gr.Row():\n",
510
- " clear = gr.Button(\"Clear Discussion\")\n",
511
- " end_discussion = gr.Button(\n",
512
- " \"End Discussion & Review\",\n",
513
- " variant=\"primary\"\n",
514
- " )\n",
515
- " \n",
516
- " # Right column - Learning dashboard\n",
517
- " with gr.Column(scale=1):\n",
518
- " with gr.Tabs():\n",
519
- " # Current Rotation tab\n",
520
- " with gr.Tab(\"Current Rotation\"):\n",
521
- " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
522
- " specialty = gr.Textbox(\n",
523
- " label=\"Specialty\",\n",
524
- " value=self.tutor.learning_context.current_rotation[\"specialty\"]\n",
525
- " )\n",
526
- " start_date = gr.Textbox(\n",
527
- " label=\"Start Date (YYYY-MM-DD)\",\n",
528
- " value=self.tutor.learning_context.current_rotation[\"start_date\"]\n",
529
- " )\n",
530
- " end_date = gr.Textbox(\n",
531
- " label=\"End Date (YYYY-MM-DD)\",\n",
532
- " value=self.tutor.learning_context.current_rotation[\"end_date\"]\n",
533
- " )\n",
534
- " focus_areas = gr.Textbox(\n",
535
- " label=\"Key Focus Areas (comma-separated)\",\n",
536
- " value=\",\".join(\n",
537
- " self.tutor.learning_context.current_rotation[\"key_focus_areas\"]\n",
538
- " )\n",
539
- " )\n",
540
- " update_rotation_btn = gr.Button(\n",
541
- " \"Update Rotation\",\n",
542
- " variant=\"secondary\"\n",
543
- " )\n",
544
- " \n",
545
- " # Learning Objectives tab\n",
546
- " with gr.Tab(\"Learning Objectives\"):\n",
547
- " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
548
- " objectives_df = gr.DataFrame(\n",
549
- " headers=[\"Objective\", \"Status\", \"Date Added\"],\n",
550
- " value=[[\n",
551
- " obj[\"objective\"],\n",
552
- " obj[\"status\"],\n",
553
- " obj[\"added\"]\n",
554
- " ] for obj in self.tutor.learning_context.learning_objectives],\n",
555
- " interactive=True,\n",
556
- " wrap=True\n",
557
- " )\n",
558
- " \n",
559
- " with gr.Row():\n",
560
- " new_objective = gr.Textbox(\n",
561
- " label=\"New Learning Objective\",\n",
562
- " placeholder=\"Enter objective...\"\n",
563
- " )\n",
564
- " add_objective_btn = gr.Button(\n",
565
- " \"Add\",\n",
566
- " variant=\"secondary\"\n",
567
- " )\n",
568
- " \n",
569
- " # Feedback Preferences tab\n",
570
- " with gr.Tab(\"Feedback Focus\"):\n",
571
- " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
572
- " feedback_df = gr.DataFrame(\n",
573
- " headers=[\"Focus Area\", \"Active\"],\n",
574
- " value=[[\n",
575
- " pref[\"focus\"],\n",
576
- " pref[\"active\"]\n",
577
- " ] for pref in self.tutor.learning_context.feedback_preferences],\n",
578
- " interactive=True,\n",
579
- " wrap=True\n",
580
- " )\n",
581
- " \n",
582
- " with gr.Row():\n",
583
- " new_feedback = gr.Textbox(\n",
584
- " label=\"New Feedback Focus\",\n",
585
- " placeholder=\"Enter focus area...\"\n",
586
- " )\n",
587
- " add_feedback_btn = gr.Button(\n",
588
- " \"Add\",\n",
589
- " variant=\"secondary\"\n",
590
- " )\n",
591
- " \n",
592
- " # Knowledge Profile tab\n",
593
- " with gr.Tab(\"Knowledge Profile\"):\n",
594
- " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
595
- " # Knowledge Gaps\n",
596
- " gr.Markdown(\"### Knowledge Gaps\")\n",
597
- " gaps_display = gr.DataFrame(\n",
598
- " headers=[\"Topic\", \"Confidence\"],\n",
599
- " value=[[\n",
600
- " topic, confidence\n",
601
- " ] for topic, confidence in \n",
602
- " self.tutor.learning_context.knowledge_profile[\"gaps\"].items()\n",
603
- " ],\n",
604
- " interactive=False\n",
605
- " )\n",
606
- " \n",
607
- " # Strengths Display\n",
608
- " gr.Markdown(\"### Strengths\")\n",
609
- " strengths_display = gr.DataFrame(\n",
610
- " headers=[\"Area\"],\n",
611
- " value=[[strength] for strength in \n",
612
- " self.tutor.learning_context.knowledge_profile[\"strengths\"]\n",
613
- " ],\n",
614
- " interactive=False\n",
615
- " )\n",
616
- " \n",
617
- " # Recent Progress\n",
618
- " gr.Markdown(\"### Recent Progress\")\n",
619
- " progress_display = gr.DataFrame(\n",
620
- " headers=[\"Topic\", \"Improvement\", \"Date\"],\n",
621
- " value=[[\n",
622
- " prog[\"topic\"],\n",
623
- " f\"{prog['improvement']:.2f}\",\n",
624
- " prog[\"date\"]\n",
625
- " ] for prog in \n",
626
- " self.tutor.learning_context.knowledge_profile[\"recent_progress\"]\n",
627
- " ],\n",
628
- " interactive=False\n",
629
- " )\n",
630
- " \n",
631
- " # Discussion summary section\n",
632
- " summary_section = gr.Column(visible=False)\n",
633
- " with summary_section:\n",
634
- " gr.Markdown(\"## Discussion Summary\")\n",
635
- " \n",
636
- " # Overview section\n",
637
- " with gr.Row():\n",
638
- " with gr.Column():\n",
639
- " gr.Markdown(\"### Session Overview\")\n",
640
- " session_overview = gr.JSON(\n",
641
- " label=\"Discussion Details\",\n",
642
- " value={\n",
643
- " \"duration\": \"0 minutes\",\n",
644
- " \"messages\": 0,\n",
645
- " \"topics_covered\": []\n",
646
- " }\n",
647
- " )\n",
648
- " \n",
649
- " # Learning Points and Gaps\n",
650
- " with gr.Row():\n",
651
- " with gr.Column():\n",
652
- " gr.Markdown(\"### Key Learning Points\")\n",
653
- " learning_points = gr.JSON(label=\"Points to Remember\")\n",
654
- " \n",
655
- " with gr.Column():\n",
656
- " gr.Markdown(\"### Knowledge Profile Updates\")\n",
657
- " with gr.Row():\n",
658
- " gaps = gr.JSON(label=\"Areas for Improvement\")\n",
659
- " strengths = gr.JSON(label=\"Demonstrated Strengths\")\n",
660
- " \n",
661
- " # Future Learning section\n",
662
- " gr.Markdown(\"### Planning Ahead\")\n",
663
- " with gr.Row():\n",
664
- " with gr.Column():\n",
665
- " gr.Markdown(\"#### Suggested Learning Objectives\")\n",
666
- " objectives = gr.JSON(label=\"Consider Adding\")\n",
667
- " \n",
668
- " with gr.Column():\n",
669
- " gr.Markdown(\"#### Recommended Focus Areas\")\n",
670
- " recommendations = gr.JSON(label=\"Next Steps\")\n",
671
- " \n",
672
- " # Action buttons\n",
673
- " with gr.Row():\n",
674
- " add_selected_objectives = gr.Button(\n",
675
- " \"Add Selected Objectives\",\n",
676
- " variant=\"primary\"\n",
677
- " )\n",
678
- " close_summary = gr.Button(\"Close Summary\")\n",
679
- " \n",
680
- " # Event handlers\n",
681
- " msg.submit(\n",
682
- " self.process_chat,\n",
683
- " inputs=[msg, chatbot, state],\n",
684
- " outputs=[chatbot, msg, state]\n",
685
- " ).then(\n",
686
- " self._update_discussion_status,\n",
687
- " inputs=[state],\n",
688
- " outputs=[discussion_status]\n",
689
- " )\n",
690
- " \n",
691
- " clear.click(\n",
692
- " lambda: ([], \"\", {\n",
693
- " \"discussion_active\": False,\n",
694
- " \"discussion_start\": None,\n",
695
- " \"last_message\": None\n",
696
- " }),\n",
697
- " outputs=[chatbot, msg, state]\n",
698
- " ).then(\n",
699
- " lambda: \"Start a new case discussion\",\n",
700
- " outputs=[discussion_status]\n",
701
- " )\n",
702
- " \n",
703
- " end_discussion.click(\n",
704
- " self.end_discussion,\n",
705
- " inputs=[chatbot, state],\n",
706
- " outputs=[\n",
707
- " session_overview,\n",
708
- " learning_points,\n",
709
- " gaps,\n",
710
- " strengths,\n",
711
- " objectives,\n",
712
- " recommendations\n",
713
- " ]\n",
714
- " ).then(\n",
715
- " lambda: gr.update(visible=True),\n",
716
- " None,\n",
717
- " summary_section\n",
718
- " ).then(\n",
719
- " self._refresh_knowledge_profile,\n",
720
- " outputs=[gaps_display, strengths_display, progress_display]\n",
721
- " )\n",
722
- " \n",
723
- " close_summary.click(\n",
724
- " lambda: gr.update(visible=False),\n",
725
- " None,\n",
726
- " summary_section\n",
727
- " )\n",
728
- " \n",
729
- " # Rotation management\n",
730
- " update_rotation_btn.click(\n",
731
- " self.update_rotation,\n",
732
- " inputs=[specialty, start_date, end_date, focus_areas],\n",
733
- " outputs=[specialty, start_date, end_date, focus_areas]\n",
734
- " )\n",
735
- " \n",
736
- " # Learning objectives management\n",
737
- " add_objective_btn.click(\n",
738
- " self.add_objective,\n",
739
- " inputs=[new_objective, objectives_df],\n",
740
- " outputs=[objectives_df]\n",
741
- " ).then(\n",
742
- " lambda: \"\",\n",
743
- " None,\n",
744
- " new_objective\n",
745
- " )\n",
746
- " \n",
747
- " objectives_df.select(\n",
748
- " self.toggle_objective_status,\n",
749
- " inputs=[objectives_df],\n",
750
- " outputs=[objectives_df]\n",
751
- " )\n",
752
- " \n",
753
- " # Feedback preferences management\n",
754
- " add_feedback_btn.click(\n",
755
- " self.add_feedback_focus,\n",
756
- " inputs=[new_feedback, feedback_df],\n",
757
- " outputs=[feedback_df]\n",
758
- " ).then(\n",
759
- " lambda: \"\",\n",
760
- " None,\n",
761
- " new_feedback\n",
762
- " )\n",
763
- " \n",
764
- " feedback_df.select(\n",
765
- " self.toggle_feedback_status,\n",
766
- " inputs=[feedback_df],\n",
767
- " outputs=[feedback_df]\n",
768
- " )\n",
769
- " \n",
770
- " # Add selected objectives from summary\n",
771
- " add_selected_objectives.click(\n",
772
- " self._add_suggested_objectives,\n",
773
- " inputs=[objectives],\n",
774
- " outputs=[objectives_df]\n",
775
- " )\n",
776
- " \n",
777
- " return interface\n",
778
- " \n",
779
- " def _update_discussion_status(self, state: Dict[str, Any]) -> str:\n",
780
- " \"\"\"Update discussion status display\"\"\"\n",
781
- " try:\n",
782
- " if not state.get(\"discussion_active\"):\n",
783
- " return \"Start a new case discussion\"\n",
784
- " \n",
785
- " start = datetime.fromisoformat(state[\"discussion_start\"])\n",
786
- " duration = datetime.now() - start\n",
787
- " minutes = int(duration.total_seconds() / 60)\n",
788
- " \n",
789
- " return f\"Active discussion ({minutes} minutes)\"\n",
790
- " \n",
791
- " except Exception as e:\n",
792
- " logger.error(f\"Error updating status: {str(e)}\")\n",
793
- " return \"Discussion status unknown\"\n",
794
- " \n",
795
- " def _refresh_knowledge_profile(\n",
796
- " self\n",
797
- " ) -> Tuple[List[List[str]], List[List[str]], List[List[str]]]:\n",
798
- " \"\"\"Refresh knowledge profile displays\"\"\"\n",
799
- " try:\n",
800
- " # Gaps\n",
801
- " gaps_data = [[\n",
802
- " topic, f\"{confidence:.2f}\"\n",
803
- " ] for topic, confidence in \n",
804
- " self.tutor.learning_context.knowledge_profile[\"gaps\"].items()\n",
805
- " ]\n",
806
- " \n",
807
- " # Strengths\n",
808
- " strengths_data = [[\n",
809
- " strength\n",
810
- " ] for strength in \n",
811
- " self.tutor.learning_context.knowledge_profile[\"strengths\"]\n",
812
- " ]\n",
813
- " \n",
814
- " # Progress\n",
815
- " progress_data = [[\n",
816
- " prog[\"topic\"],\n",
817
- " f\"{prog['improvement']:.2f}\",\n",
818
- " prog[\"date\"]\n",
819
- " ] for prog in \n",
820
- " self.tutor.learning_context.knowledge_profile[\"recent_progress\"]\n",
821
- " ]\n",
822
- " \n",
823
- " return gaps_data, strengths_data, progress_data\n",
824
- " \n",
825
- " except Exception as e:\n",
826
- " logger.error(f\"Error refreshing profile: {str(e)}\")\n",
827
- " return [], [], []\n",
828
- " \n",
829
- " def _add_suggested_objectives(\n",
830
- " self,\n",
831
- " evt: gr.SelectData, # Updated to use gr.SelectData\n",
832
- " suggested_objectives: List[str]\n",
833
- " ) -> pd.DataFrame:\n",
834
- " \"\"\"Add selected suggested objectives to learning objectives\"\"\"\n",
835
- " try:\n",
836
- " selected_indices = [evt.index[0]] # Get selected row index\n",
837
- " \n",
838
- " for idx in selected_indices:\n",
839
- " if idx < len(suggested_objectives):\n",
840
- " objective = suggested_objectives[idx]\n",
841
- " self.tutor.learning_context.add_learning_objective(objective)\n",
842
- " \n",
843
- " return pd.DataFrame([\n",
844
- " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n",
845
- " for obj in self.tutor.learning_context.learning_objectives\n",
846
- " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n",
847
- " \n",
848
- " except Exception as e:\n",
849
- " logger.error(f\"Error adding objectives: {str(e)}\")\n",
850
- " return pd.DataFrame()"
851
- ]
852
- },
853
- {
854
- "cell_type": "markdown",
855
- "id": "30c0f121-5d5f-4dc0-b897-f6e2067a63b2",
856
- "metadata": {},
857
- "source": [
858
- "## Launch Function"
859
- ]
860
- },
861
- {
862
- "cell_type": "code",
863
- "execution_count": null,
864
- "id": "65f97529-b221-4a19-9856-fb20d7f7316e",
865
- "metadata": {},
866
- "outputs": [],
867
- "source": [
868
- "#| export\n",
869
- "async def launch_learning_interface(\n",
870
- " port: Optional[int] = None,\n",
871
- " context_path: Optional[Path] = None,\n",
872
- " share: bool = False,\n",
873
- " theme: str = \"default\"\n",
874
- ") -> None:\n",
875
- " \"\"\"Launch the learning interface application.\"\"\"\n",
876
- " try:\n",
877
- " interface = LearningInterface(context_path, theme)\n",
878
- " app = interface.create_interface()\n",
879
- " app.launch(\n",
880
- " server_port=port,\n",
881
- " share=share\n",
882
- " )\n",
883
- " logger.info(f\"Interface launched on port: {port}\")\n",
884
- " except Exception as e:\n",
885
- " logger.error(f\"Error launching interface: {str(e)}\")\n",
886
- " raise"
887
- ]
888
- },
889
- {
890
- "cell_type": "markdown",
891
- "id": "5c75de88-f6d5-4a5d-92b5-1ebe85895a84",
892
- "metadata": {},
893
- "source": [
894
- "## Tests"
895
- ]
896
- },
897
- {
898
- "cell_type": "code",
899
- "execution_count": null,
900
- "id": "365bc95a-d189-4ab2-aa30-022d0286b5ba",
901
- "metadata": {},
902
- "outputs": [],
903
- "source": [
904
- "async def test_learning_interface():\n",
905
- " \"\"\"Test learning interface functionality\"\"\"\n",
906
- " interface = LearningInterface()\n",
907
- " \n",
908
- " # Test chat processing\n",
909
- " history = []\n",
910
- " test_input = \"28yo M with chest pain\"\n",
911
- " \n",
912
- " new_history, msg = await interface.process_chat(test_input, history)\n",
913
- " assert isinstance(new_history, list)\n",
914
- " assert len(new_history) == 2 # User message + response\n",
915
- " assert new_history[0][\"role\"] == \"user\"\n",
916
- " assert new_history[0][\"content\"] == test_input\n",
917
- " \n",
918
- " # Test discussion analysis\n",
919
- " analysis = await interface.end_discussion(new_history)\n",
920
- " assert isinstance(analysis, dict)\n",
921
- " assert all(k in analysis for k in [\n",
922
- " 'learning_points', 'gaps', 'strengths', 'suggested_objectives'\n",
923
- " ])\n",
924
- " \n",
925
- " # Test rotation updates\n",
926
- " rotation = interface.update_rotation(\n",
927
- " \"Emergency Medicine\",\n",
928
- " \"2025-01-01\",\n",
929
- " \"2025-03-31\",\n",
930
- " [\"Resuscitation\", \"Procedures\"]\n",
931
- " )\n",
932
- " assert rotation[\"specialty\"] == \"Emergency Medicine\"\n",
933
- " assert \"Resuscitation\" in rotation[\"key_focus_areas\"]\n",
934
- " \n",
935
- " # Test objective management\n",
936
- " objectives = interface.toggle_objective(\"Improve chest pain assessment\", False)\n",
937
- " assert len(objectives) == 1\n",
938
- " assert objectives[0][\"status\"] == \"active\"\n",
939
- " \n",
940
- " objectives = interface.toggle_objective(\"Improve chest pain assessment\", True)\n",
941
- " assert objectives[0][\"status\"] == \"completed\"\n",
942
- " \n",
943
- " # Test feedback preferences\n",
944
- " preferences = interface.toggle_feedback(\"Include more ddx\", True)\n",
945
- " assert len(preferences) == 1\n",
946
- " assert preferences[0][\"active\"] == True\n",
947
- " \n",
948
- " print(\"Interface tests passed!\")\n",
949
- "\n",
950
- "# Run tests\n",
951
- "if __name__ == \"__main__\":\n",
952
- " import asyncio\n",
953
- " if not asyncio.get_event_loop().is_running():\n",
954
- " asyncio.run(test_learning_interface())"
955
- ]
956
- }
957
- ],
958
- "metadata": {
959
- "kernelspec": {
960
- "display_name": "Python 3 (ipykernel)",
961
- "language": "python",
962
- "name": "python3"
963
- },
964
- "language_info": {
965
- "codemirror_mode": {
966
- "name": "ipython",
967
- "version": 3
968
- },
969
- "file_extension": ".py",
970
- "mimetype": "text/x-python",
971
- "name": "python",
972
- "nbconvert_exporter": "python",
973
- "pygments_lexer": "ipython3",
974
- "version": "3.12.7"
975
- }
976
- },
977
- "nbformat": 4,
978
- "nbformat_minor": 5
979
- }
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "7ce0a47c-1c4f-44a4-a9d8-9ea6399a8f84",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "#| default_exp learning_interface"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "markdown",
15
+ "id": "55331735-898e-411b-b751-5b380605be36",
16
+ "metadata": {},
17
+ "source": [
18
+ "# Learning Interface\n",
19
+ "\n",
20
+ "> Gradio interface"
21
+ ]
22
+ },
23
+ {
24
+ "cell_type": "markdown",
25
+ "id": "dd401991-b919-423e-9da7-961387faf11e",
26
+ "metadata": {},
27
+ "source": [
28
+ "## Setup"
29
+ ]
30
+ },
31
+ {
32
+ "cell_type": "code",
33
+ "execution_count": null,
34
+ "id": "edc3fbb1-13ef-408a-b5fe-eb7a6821915b",
35
+ "metadata": {},
36
+ "outputs": [],
37
+ "source": [
38
+ "#| hide\n",
39
+ "from nbdev.showdoc import *"
40
+ ]
41
+ },
42
+ {
43
+ "cell_type": "code",
44
+ "execution_count": null,
45
+ "id": "4be213cf-89b4-48c8-9592-f509332da485",
46
+ "metadata": {},
47
+ "outputs": [
48
+ {
49
+ "ename": "ImportError",
50
+ "evalue": "cannot import name 'ClinicalTutor' from 'wardbuddy.clinical_tutor' (C:\\Users\\deepa\\OneDrive\\Documents\\StudyBuddy\\wardbuddy\\wardbuddy\\clinical_tutor.py)",
51
+ "output_type": "error",
52
+ "traceback": [
53
+ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
54
+ "\u001b[1;31mImportError\u001b[0m Traceback (most recent call last)",
55
+ "Cell \u001b[1;32mIn[4], line 7\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpathlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Path\n\u001b[0;32m 6\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01masyncio\u001b[39;00m\n\u001b[1;32m----> 7\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mclinical_tutor\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m ClinicalTutor\n\u001b[0;32m 8\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mutils\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m format_response\n\u001b[0;32m 9\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mwardbuddy\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mlearning_context\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m setup_logger\n",
56
+ "\u001b[1;31mImportError\u001b[0m: cannot import name 'ClinicalTutor' from 'wardbuddy.clinical_tutor' (C:\\Users\\deepa\\OneDrive\\Documents\\StudyBuddy\\wardbuddy\\wardbuddy\\clinical_tutor.py)"
57
+ ]
58
+ }
59
+ ],
60
+ "source": [
61
+ "#| export\n",
62
+ "from typing import Dict, List, Optional, Tuple, Any\n",
63
+ "import gradio as gr\n",
64
+ "from pathlib import Path\n",
65
+ "import asyncio\n",
66
+ "from datetime import datetime\n",
67
+ "import pandas as pd\n",
68
+ "from wardbuddy.clinical_tutor import ClinicalTutor\n",
69
+ "from wardbuddy.learning_context import setup_logger\n",
70
+ "\n",
71
+ "logger = setup_logger(__name__)"
72
+ ]
73
+ },
74
+ {
75
+ "cell_type": "markdown",
76
+ "id": "c39da1db-e630-4296-93f6-03b9188320cc",
77
+ "metadata": {},
78
+ "source": [
79
+ "## Learning Interface"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "code",
84
+ "execution_count": null,
85
+ "id": "aff3d321-2116-475f-b906-f74889e76d66",
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": [
89
+ "#| export\n",
90
+ "def create_dashboard_css() -> str:\n",
91
+ " \"\"\"Create custom CSS for dashboard styling\"\"\"\n",
92
+ " return \"\"\"\n",
93
+ " .dashboard-card {\n",
94
+ " border: 1px solid #e2e8f0;\n",
95
+ " border-radius: 8px;\n",
96
+ " padding: 16px;\n",
97
+ " margin: 8px 0;\n",
98
+ " background: white;\n",
99
+ " }\n",
100
+ " \n",
101
+ " .status-active {\n",
102
+ " color: #48bb78;\n",
103
+ " font-weight: 500;\n",
104
+ " }\n",
105
+ " \n",
106
+ " .status-completed {\n",
107
+ " color: #718096;\n",
108
+ " }\n",
109
+ " \n",
110
+ " .dashboard-header {\n",
111
+ " font-size: 1.25rem;\n",
112
+ " font-weight: 600;\n",
113
+ " margin-bottom: 1rem;\n",
114
+ " }\n",
115
+ " \n",
116
+ " .summary-modal {\n",
117
+ " max-width: 800px !important;\n",
118
+ " }\n",
119
+ " \n",
120
+ " .feedback-tag {\n",
121
+ " display: inline-block;\n",
122
+ " padding: 4px 8px;\n",
123
+ " border-radius: 4px;\n",
124
+ " margin: 4px;\n",
125
+ " font-size: 0.875rem;\n",
126
+ " }\n",
127
+ " \n",
128
+ " .feedback-active {\n",
129
+ " background: #ebf8ff;\n",
130
+ " color: #2b6cb0;\n",
131
+ " }\n",
132
+ " \n",
133
+ " .feedback-inactive {\n",
134
+ " background: #f7fafc;\n",
135
+ " color: #718096;\n",
136
+ " }\n",
137
+ " \"\"\""
138
+ ]
139
+ },
140
+ {
141
+ "cell_type": "markdown",
142
+ "id": "de7ace04-4841-461d-89bb-234b5f8b48e1",
143
+ "metadata": {},
144
+ "source": [
145
+ "This module provides the user interface for the clinical learning system, including:\n",
146
+ " * Case presentation and feedback\n",
147
+ " * Learning preference configuration\n",
148
+ " * Session management\n",
149
+ " * Progress visualization"
150
+ ]
151
+ },
152
+ {
153
+ "cell_type": "code",
154
+ "execution_count": null,
155
+ "id": "675c20cc-43aa-4897-89ba-4fa26dd37c20",
156
+ "metadata": {},
157
+ "outputs": [],
158
+ "source": [
159
+ "#| export\n",
160
+ "\n",
161
+ "class LearningInterface:\n",
162
+ " \"\"\"\n",
163
+ " Gradio interface for clinical learning interactions.\n",
164
+ " \n",
165
+ " Features:\n",
166
+ " - Natural case discussion chat\n",
167
+ " - Dynamic learning dashboard\n",
168
+ " - Post-discussion analysis\n",
169
+ " - Progress tracking\n",
170
+ " \"\"\"\n",
171
+ " \n",
172
+ " def __init__(\n",
173
+ " self,\n",
174
+ " context_path: Optional[Path] = None,\n",
175
+ " theme: str = \"default\"\n",
176
+ " ):\n",
177
+ " \"\"\"Initialize learning interface.\"\"\"\n",
178
+ " self.tutor = ClinicalTutor(context_path)\n",
179
+ " self.theme = theme\n",
180
+ " self.context_path = context_path\n",
181
+ " \n",
182
+ " # Track current discussion state\n",
183
+ " self.current_discussion = {\n",
184
+ " \"started\": None,\n",
185
+ " \"case_type\": None,\n",
186
+ " \"messages\": []\n",
187
+ " }\n",
188
+ " \n",
189
+ " logger.info(\"Learning interface initialized\")\n",
190
+ " \n",
191
+ " async def process_chat(\n",
192
+ " self,\n",
193
+ " message: str,\n",
194
+ " history: List[List[str]],\n",
195
+ " state: Dict[str, Any]\n",
196
+ " ) -> Tuple[List[List[str]], str, Dict[str, Any]]: \n",
197
+ " \"\"\"\n",
198
+ " Process chat messages with state management.\n",
199
+ " \n",
200
+ " Args:\n",
201
+ " message: User input message\n",
202
+ " history: Chat history\n",
203
+ " state: Current interface state\n",
204
+ " \n",
205
+ " Returns:\n",
206
+ " tuple: (updated history, cleared message, updated state)\n",
207
+ " \"\"\"\n",
208
+ " try:\n",
209
+ " if not message.strip():\n",
210
+ " return history, \"\", state\n",
211
+ " \n",
212
+ " # Start new discussion if none active\n",
213
+ " if not state.get(\"discussion_active\"):\n",
214
+ " state[\"discussion_active\"] = True\n",
215
+ " state[\"discussion_start\"] = datetime.now().isoformat()\n",
216
+ " \n",
217
+ " # Get tutor response\n",
218
+ " response = await self.tutor.discuss_case(message)\n",
219
+ " \n",
220
+ " # Update history - now using list pairs instead of dicts\n",
221
+ " if history is None:\n",
222
+ " history = []\n",
223
+ " history.append([message, response]) # Changed from dict format to list pair\n",
224
+ " \n",
225
+ " state[\"last_message\"] = datetime.now().isoformat()\n",
226
+ " \n",
227
+ " return history, \"\", state\n",
228
+ " \n",
229
+ " except Exception as e:\n",
230
+ " logger.error(f\"Error in chat: {str(e)}\")\n",
231
+ " return history or [], \"\", state\n",
232
+ "\n",
233
+ " async def end_discussion(\n",
234
+ " self,\n",
235
+ " history: List[List[str]],\n",
236
+ " state: Dict[str, Any]\n",
237
+ " ) -> Tuple[Dict[str, Any], Dict[str, Any]]:\n",
238
+ " \"\"\"\n",
239
+ " Analyze completed discussion and prepare summary.\n",
240
+ " \n",
241
+ " Args:\n",
242
+ " history: Chat history as list of [user_message, assistant_message] pairs\n",
243
+ " state: Current interface state\n",
244
+ " \n",
245
+ " Returns:\n",
246
+ " tuple: (analysis results, updated state)\n",
247
+ " \"\"\"\n",
248
+ " try:\n",
249
+ " if not history:\n",
250
+ " return {\n",
251
+ " \"learning_points\": [],\n",
252
+ " \"gaps\": {},\n",
253
+ " \"strengths\": [],\n",
254
+ " \"suggested_objectives\": []\n",
255
+ " }, state\n",
256
+ " \n",
257
+ " # Convert history format for analysis\n",
258
+ " formatted_history = []\n",
259
+ " for user_msg, assistant_msg in history:\n",
260
+ " formatted_history.extend([\n",
261
+ " {\"role\": \"user\", \"content\": user_msg},\n",
262
+ " {\"role\": \"assistant\", \"content\": assistant_msg}\n",
263
+ " ])\n",
264
+ " \n",
265
+ " # Get analysis\n",
266
+ " analysis = await self.tutor.analyze_discussion(formatted_history)\n",
267
+ " \n",
268
+ " # Reset discussion state\n",
269
+ " state[\"discussion_active\"] = False\n",
270
+ " state[\"discussion_start\"] = None\n",
271
+ " state[\"last_message\"] = None\n",
272
+ " \n",
273
+ " return analysis, state\n",
274
+ " \n",
275
+ " except Exception as e:\n",
276
+ " logger.error(f\"Error analyzing discussion: {str(e)}\")\n",
277
+ " return {\n",
278
+ " \"learning_points\": [],\n",
279
+ " \"gaps\": {},\n",
280
+ " \"strengths\": [],\n",
281
+ " \"suggested_objectives\": []\n",
282
+ " }, state \n",
283
+ " \n",
284
+ " def update_rotation(\n",
285
+ " self,\n",
286
+ " specialty: str,\n",
287
+ " start_date: str,\n",
288
+ " end_date: str,\n",
289
+ " focus_areas: str\n",
290
+ " ) -> Tuple[str, str, str, str]:\n",
291
+ " \"\"\"\n",
292
+ " Update rotation details and return updated values.\n",
293
+ " \n",
294
+ " Args:\n",
295
+ " specialty: Rotation specialty\n",
296
+ " start_date: Start date string\n",
297
+ " end_date: End date string\n",
298
+ " focus_areas: Comma-separated focus areas\n",
299
+ " \n",
300
+ " Returns:\n",
301
+ " tuple: Updated field values\n",
302
+ " \"\"\"\n",
303
+ " try:\n",
304
+ " # Parse focus areas\n",
305
+ " focus_list = [\n",
306
+ " area.strip() \n",
307
+ " for area in focus_areas.split(\",\") \n",
308
+ " if area.strip()\n",
309
+ " ]\n",
310
+ " \n",
311
+ " # Update context\n",
312
+ " rotation = {\n",
313
+ " \"specialty\": specialty,\n",
314
+ " \"start_date\": start_date,\n",
315
+ " \"end_date\": end_date,\n",
316
+ " \"key_focus_areas\": focus_list\n",
317
+ " }\n",
318
+ " self.tutor.learning_context.update_rotation(rotation)\n",
319
+ " \n",
320
+ " # Return updated values\n",
321
+ " return (\n",
322
+ " specialty,\n",
323
+ " start_date,\n",
324
+ " end_date,\n",
325
+ " \",\".join(focus_list)\n",
326
+ " )\n",
327
+ " \n",
328
+ " except Exception as e:\n",
329
+ " logger.error(f\"Error updating rotation: {str(e)}\")\n",
330
+ " current = self.tutor.learning_context.current_rotation\n",
331
+ " return (\n",
332
+ " current[\"specialty\"],\n",
333
+ " current[\"start_date\"] or \"\",\n",
334
+ " current[\"end_date\"] or \"\",\n",
335
+ " \",\".join(current[\"key_focus_areas\"])\n",
336
+ " )\n",
337
+ "\n",
338
+ " def add_objective(\n",
339
+ " self,\n",
340
+ " objective: str,\n",
341
+ " objectives_df: pd.DataFrame\n",
342
+ " ) -> pd.DataFrame:\n",
343
+ " \"\"\"\n",
344
+ " Add new learning objective and return updated dataframe.\n",
345
+ " \n",
346
+ " Args:\n",
347
+ " objective: New objective text\n",
348
+ " objectives_df: Current objectives dataframe\n",
349
+ " \n",
350
+ " Returns:\n",
351
+ " pd.DataFrame: Updated objectives list\n",
352
+ " \"\"\"\n",
353
+ " try:\n",
354
+ " if not objective.strip():\n",
355
+ " return objectives_df\n",
356
+ " \n",
357
+ " # Add to context\n",
358
+ " self.tutor.learning_context.add_learning_objective(objective)\n",
359
+ " \n",
360
+ " # Convert to dataframe\n",
361
+ " return pd.DataFrame([\n",
362
+ " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n",
363
+ " for obj in self.tutor.learning_context.learning_objectives\n",
364
+ " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n",
365
+ " \n",
366
+ " except Exception as e:\n",
367
+ " logger.error(f\"Error adding objective: {str(e)}\")\n",
368
+ " return objectives_df\n",
369
+ "\n",
370
+ " def toggle_objective_status(\n",
371
+ " self,\n",
372
+ " evt: gr.SelectData, # Updated to use gr.SelectData\n",
373
+ " objectives_df: pd.DataFrame\n",
374
+ " ) -> pd.DataFrame:\n",
375
+ " \"\"\"\n",
376
+ " Toggle objective status between active and completed.\n",
377
+ " \n",
378
+ " Args:\n",
379
+ " evt: Gradio select event containing row index\n",
380
+ " objectives_df: Current objectives dataframe\n",
381
+ " \n",
382
+ " Returns:\n",
383
+ " pd.DataFrame: Updated objectives list\n",
384
+ " \"\"\"\n",
385
+ " try:\n",
386
+ " objective_idx = evt.index[0] # Get selected row index\n",
387
+ " if objective_idx >= len(objectives_df):\n",
388
+ " return objectives_df\n",
389
+ " \n",
390
+ " # Get objective\n",
391
+ " objective = objectives_df.iloc[objective_idx][\"Objective\"]\n",
392
+ " current_status = objectives_df.iloc[objective_idx][\"Status\"]\n",
393
+ " \n",
394
+ " # Toggle in context\n",
395
+ " if current_status == \"active\":\n",
396
+ " self.tutor.learning_context.complete_objective(objective)\n",
397
+ " else:\n",
398
+ " self.tutor.learning_context.add_learning_objective(objective)\n",
399
+ " \n",
400
+ " # Update dataframe\n",
401
+ " return pd.DataFrame([\n",
402
+ " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n",
403
+ " for obj in self.tutor.learning_context.learning_objectives\n",
404
+ " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n",
405
+ " \n",
406
+ " except Exception as e:\n",
407
+ " logger.error(f\"Error toggling objective: {str(e)}\")\n",
408
+ " return objectives_df\n",
409
+ "\n",
410
+ " def add_feedback_focus(\n",
411
+ " self,\n",
412
+ " focus: str,\n",
413
+ " feedback_df: pd.DataFrame\n",
414
+ " ) -> pd.DataFrame:\n",
415
+ " \"\"\"Add new feedback focus area.\"\"\"\n",
416
+ " try:\n",
417
+ " if not focus.strip():\n",
418
+ " return feedback_df\n",
419
+ " \n",
420
+ " # Add to context\n",
421
+ " self.tutor.learning_context.toggle_feedback_focus(focus, True)\n",
422
+ " \n",
423
+ " # Update dataframe\n",
424
+ " return pd.DataFrame([\n",
425
+ " [pref[\"focus\"], pref[\"active\"]]\n",
426
+ " for pref in self.tutor.learning_context.feedback_preferences\n",
427
+ " ], columns=[\"Focus Area\", \"Active\"])\n",
428
+ " \n",
429
+ " except Exception as e:\n",
430
+ " logger.error(f\"Error adding feedback focus: {str(e)}\")\n",
431
+ " return feedback_df\n",
432
+ "\n",
433
+ " def toggle_feedback_status(\n",
434
+ " self,\n",
435
+ " evt: gr.SelectData, # Updated to use gr.SelectData\n",
436
+ " feedback_df: pd.DataFrame\n",
437
+ " ) -> pd.DataFrame:\n",
438
+ " \"\"\"Toggle feedback focus active status.\"\"\"\n",
439
+ " try:\n",
440
+ " focus_idx = evt.index[0] # Get selected row index\n",
441
+ " if focus_idx >= len(feedback_df):\n",
442
+ " return feedback_df\n",
443
+ " \n",
444
+ " # Get focus area\n",
445
+ " focus = feedback_df.iloc[focus_idx][\"Focus Area\"]\n",
446
+ " current_status = feedback_df.iloc[focus_idx][\"Active\"]\n",
447
+ " \n",
448
+ " # Toggle in context\n",
449
+ " self.tutor.learning_context.toggle_feedback_focus(\n",
450
+ " focus, \n",
451
+ " not current_status\n",
452
+ " )\n",
453
+ " \n",
454
+ " # Update dataframe\n",
455
+ " return pd.DataFrame([\n",
456
+ " [pref[\"focus\"], pref[\"active\"]]\n",
457
+ " for pref in self.tutor.learning_context.feedback_preferences\n",
458
+ " ], columns=[\"Focus Area\", \"Active\"])\n",
459
+ " \n",
460
+ " except Exception as e:\n",
461
+ " logger.error(f\"Error toggling feedback: {str(e)}\")\n",
462
+ " return feedback_df\n",
463
+ "\n",
464
+ " def create_interface(self) -> gr.Blocks:\n",
465
+ " \"\"\"Create and configure the Gradio interface\"\"\"\n",
466
+ " with gr.Blocks(\n",
467
+ " title=\"Clinical Learning Assistant\",\n",
468
+ " theme=self.theme,\n",
469
+ " css=create_dashboard_css()\n",
470
+ " ) as interface:\n",
471
+ " # State management\n",
472
+ " state = gr.State({\n",
473
+ " \"discussion_active\": False,\n",
474
+ " \"discussion_start\": None,\n",
475
+ " \"last_message\": None\n",
476
+ " })\n",
477
+ " \n",
478
+ " # Header\n",
479
+ " with gr.Row():\n",
480
+ " gr.Markdown(\n",
481
+ " \"# Clinical Learning Assistant\",\n",
482
+ " elem_classes=[\"dashboard-header\"]\n",
483
+ " )\n",
484
+ " \n",
485
+ " with gr.Row():\n",
486
+ " # Left column - Chat interface\n",
487
+ " with gr.Column(scale=2):\n",
488
+ " # Active discussion indicator\n",
489
+ " discussion_status = gr.Markdown(\n",
490
+ " \"Start a new case discussion\",\n",
491
+ " elem_classes=[\"dashboard-card\"]\n",
492
+ " )\n",
493
+ " \n",
494
+ " # Chat interface\n",
495
+ " chatbot = gr.Chatbot(\n",
496
+ " height=500,\n",
497
+ " label=\"Case Discussion\",\n",
498
+ " show_label=True,\n",
499
+ " elem_classes=[\"dashboard-card\"]\n",
500
+ " )\n",
501
+ " \n",
502
+ " with gr.Row():\n",
503
+ " msg = gr.Textbox(\n",
504
+ " label=\"Present your case or ask questions\",\n",
505
+ " placeholder=(\n",
506
+ " \"Present your case as you would to your supervisor:\\n\"\n",
507
+ " \"- Start with the chief complaint\\n\"\n",
508
+ " \"- Include relevant history and findings\\n\"\n",
509
+ " \"- Share your assessment and plan\"\n",
510
+ " ),\n",
511
+ " lines=5\n",
512
+ " )\n",
513
+ " \n",
514
+ " with gr.Row():\n",
515
+ " clear = gr.Button(\"Clear Discussion\")\n",
516
+ " end_discussion = gr.Button(\n",
517
+ " \"End Discussion & Review\",\n",
518
+ " variant=\"primary\"\n",
519
+ " )\n",
520
+ " \n",
521
+ " # Right column - Learning dashboard\n",
522
+ " with gr.Column(scale=1):\n",
523
+ " with gr.Tabs():\n",
524
+ " # Current Rotation tab\n",
525
+ " with gr.Tab(\"Current Rotation\"):\n",
526
+ " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
527
+ " specialty = gr.Textbox(\n",
528
+ " label=\"Specialty\",\n",
529
+ " value=self.tutor.learning_context.current_rotation[\"specialty\"]\n",
530
+ " )\n",
531
+ " start_date = gr.Textbox(\n",
532
+ " label=\"Start Date (YYYY-MM-DD)\",\n",
533
+ " value=self.tutor.learning_context.current_rotation[\"start_date\"]\n",
534
+ " )\n",
535
+ " end_date = gr.Textbox(\n",
536
+ " label=\"End Date (YYYY-MM-DD)\",\n",
537
+ " value=self.tutor.learning_context.current_rotation[\"end_date\"]\n",
538
+ " )\n",
539
+ " focus_areas = gr.Textbox(\n",
540
+ " label=\"Key Focus Areas (comma-separated)\",\n",
541
+ " value=\",\".join(\n",
542
+ " self.tutor.learning_context.current_rotation[\"key_focus_areas\"]\n",
543
+ " )\n",
544
+ " )\n",
545
+ " update_rotation_btn = gr.Button(\n",
546
+ " \"Update Rotation\",\n",
547
+ " variant=\"secondary\"\n",
548
+ " )\n",
549
+ " \n",
550
+ " # Learning Objectives tab\n",
551
+ " with gr.Tab(\"Learning Objectives\"):\n",
552
+ " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
553
+ " objectives_df = gr.DataFrame(\n",
554
+ " headers=[\"Objective\", \"Status\", \"Date Added\"],\n",
555
+ " value=[[\n",
556
+ " obj[\"objective\"],\n",
557
+ " obj[\"status\"],\n",
558
+ " obj[\"added\"]\n",
559
+ " ] for obj in self.tutor.learning_context.learning_objectives],\n",
560
+ " interactive=True,\n",
561
+ " wrap=True\n",
562
+ " )\n",
563
+ " \n",
564
+ " with gr.Row():\n",
565
+ " new_objective = gr.Textbox(\n",
566
+ " label=\"New Learning Objective\",\n",
567
+ " placeholder=\"Enter objective...\"\n",
568
+ " )\n",
569
+ " add_objective_btn = gr.Button(\n",
570
+ " \"Add\",\n",
571
+ " variant=\"secondary\"\n",
572
+ " )\n",
573
+ " \n",
574
+ " # Feedback Preferences tab\n",
575
+ " with gr.Tab(\"Feedback Focus\"):\n",
576
+ " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
577
+ " feedback_df = gr.DataFrame(\n",
578
+ " headers=[\"Focus Area\", \"Active\"],\n",
579
+ " value=[[\n",
580
+ " pref[\"focus\"],\n",
581
+ " pref[\"active\"]\n",
582
+ " ] for pref in self.tutor.learning_context.feedback_preferences],\n",
583
+ " interactive=True,\n",
584
+ " wrap=True\n",
585
+ " )\n",
586
+ " \n",
587
+ " with gr.Row():\n",
588
+ " new_feedback = gr.Textbox(\n",
589
+ " label=\"New Feedback Focus\",\n",
590
+ " placeholder=\"Enter focus area...\"\n",
591
+ " )\n",
592
+ " add_feedback_btn = gr.Button(\n",
593
+ " \"Add\",\n",
594
+ " variant=\"secondary\"\n",
595
+ " )\n",
596
+ " \n",
597
+ " # Knowledge Profile tab\n",
598
+ " with gr.Tab(\"Knowledge Profile\"):\n",
599
+ " with gr.Column(elem_classes=[\"dashboard-card\"]):\n",
600
+ " # Knowledge Gaps\n",
601
+ " gr.Markdown(\"### Knowledge Gaps\")\n",
602
+ " gaps_display = gr.DataFrame(\n",
603
+ " headers=[\"Topic\", \"Confidence\"],\n",
604
+ " value=[[\n",
605
+ " topic, confidence\n",
606
+ " ] for topic, confidence in \n",
607
+ " self.tutor.learning_context.knowledge_profile[\"gaps\"].items()\n",
608
+ " ],\n",
609
+ " interactive=False\n",
610
+ " )\n",
611
+ " \n",
612
+ " # Strengths Display\n",
613
+ " gr.Markdown(\"### Strengths\")\n",
614
+ " strengths_display = gr.DataFrame(\n",
615
+ " headers=[\"Area\"],\n",
616
+ " value=[[strength] for strength in \n",
617
+ " self.tutor.learning_context.knowledge_profile[\"strengths\"]\n",
618
+ " ],\n",
619
+ " interactive=False\n",
620
+ " )\n",
621
+ " \n",
622
+ " # Recent Progress\n",
623
+ " gr.Markdown(\"### Recent Progress\")\n",
624
+ " progress_display = gr.DataFrame(\n",
625
+ " headers=[\"Topic\", \"Improvement\", \"Date\"],\n",
626
+ " value=[[\n",
627
+ " prog[\"topic\"],\n",
628
+ " f\"{prog['improvement']:.2f}\",\n",
629
+ " prog[\"date\"]\n",
630
+ " ] for prog in \n",
631
+ " self.tutor.learning_context.knowledge_profile[\"recent_progress\"]\n",
632
+ " ],\n",
633
+ " interactive=False\n",
634
+ " )\n",
635
+ " \n",
636
+ " # Discussion summary section\n",
637
+ " summary_section = gr.Column(visible=False)\n",
638
+ " with summary_section:\n",
639
+ " gr.Markdown(\"## Discussion Summary\")\n",
640
+ " \n",
641
+ " # Overview section\n",
642
+ " with gr.Row():\n",
643
+ " with gr.Column():\n",
644
+ " gr.Markdown(\"### Session Overview\")\n",
645
+ " session_overview = gr.JSON(\n",
646
+ " label=\"Discussion Details\",\n",
647
+ " value={\n",
648
+ " \"duration\": \"0 minutes\",\n",
649
+ " \"messages\": 0,\n",
650
+ " \"topics_covered\": []\n",
651
+ " }\n",
652
+ " )\n",
653
+ " \n",
654
+ " # Learning Points and Gaps\n",
655
+ " with gr.Row():\n",
656
+ " with gr.Column():\n",
657
+ " gr.Markdown(\"### Key Learning Points\")\n",
658
+ " learning_points = gr.JSON(label=\"Points to Remember\")\n",
659
+ " \n",
660
+ " with gr.Column():\n",
661
+ " gr.Markdown(\"### Knowledge Profile Updates\")\n",
662
+ " with gr.Row():\n",
663
+ " gaps = gr.JSON(label=\"Areas for Improvement\")\n",
664
+ " strengths = gr.JSON(label=\"Demonstrated Strengths\")\n",
665
+ " \n",
666
+ " # Future Learning section\n",
667
+ " gr.Markdown(\"### Planning Ahead\")\n",
668
+ " with gr.Row():\n",
669
+ " with gr.Column():\n",
670
+ " gr.Markdown(\"#### Suggested Learning Objectives\")\n",
671
+ " objectives = gr.JSON(label=\"Consider Adding\")\n",
672
+ " \n",
673
+ " with gr.Column():\n",
674
+ " gr.Markdown(\"#### Recommended Focus Areas\")\n",
675
+ " recommendations = gr.JSON(label=\"Next Steps\")\n",
676
+ " \n",
677
+ " # Action buttons\n",
678
+ " with gr.Row():\n",
679
+ " add_selected_objectives = gr.Button(\n",
680
+ " \"Add Selected Objectives\",\n",
681
+ " variant=\"primary\"\n",
682
+ " )\n",
683
+ " close_summary = gr.Button(\"Close Summary\")\n",
684
+ " \n",
685
+ " # Event handlers\n",
686
+ " msg.submit(\n",
687
+ " self.process_chat,\n",
688
+ " inputs=[msg, chatbot, state],\n",
689
+ " outputs=[chatbot, msg, state]\n",
690
+ " ).then(\n",
691
+ " self._update_discussion_status,\n",
692
+ " inputs=[state],\n",
693
+ " outputs=[discussion_status]\n",
694
+ " )\n",
695
+ " \n",
696
+ " clear.click(\n",
697
+ " lambda: ([], \"\", {\n",
698
+ " \"discussion_active\": False,\n",
699
+ " \"discussion_start\": None,\n",
700
+ " \"last_message\": None\n",
701
+ " }),\n",
702
+ " outputs=[chatbot, msg, state]\n",
703
+ " ).then(\n",
704
+ " lambda: \"Start a new case discussion\",\n",
705
+ " outputs=[discussion_status]\n",
706
+ " )\n",
707
+ " \n",
708
+ " end_discussion.click(\n",
709
+ " self.end_discussion,\n",
710
+ " inputs=[chatbot, state],\n",
711
+ " outputs=[\n",
712
+ " session_overview,\n",
713
+ " learning_points,\n",
714
+ " gaps,\n",
715
+ " strengths,\n",
716
+ " objectives,\n",
717
+ " recommendations\n",
718
+ " ]\n",
719
+ " ).then(\n",
720
+ " lambda: gr.update(visible=True),\n",
721
+ " None,\n",
722
+ " summary_section\n",
723
+ " ).then(\n",
724
+ " self._refresh_knowledge_profile,\n",
725
+ " outputs=[gaps_display, strengths_display, progress_display]\n",
726
+ " )\n",
727
+ " \n",
728
+ " close_summary.click(\n",
729
+ " lambda: gr.update(visible=False),\n",
730
+ " None,\n",
731
+ " summary_section\n",
732
+ " )\n",
733
+ " \n",
734
+ " # Rotation management\n",
735
+ " update_rotation_btn.click(\n",
736
+ " self.update_rotation,\n",
737
+ " inputs=[specialty, start_date, end_date, focus_areas],\n",
738
+ " outputs=[specialty, start_date, end_date, focus_areas]\n",
739
+ " )\n",
740
+ " \n",
741
+ " # Learning objectives management\n",
742
+ " add_objective_btn.click(\n",
743
+ " self.add_objective,\n",
744
+ " inputs=[new_objective, objectives_df],\n",
745
+ " outputs=[objectives_df]\n",
746
+ " ).then(\n",
747
+ " lambda: \"\",\n",
748
+ " None,\n",
749
+ " new_objective\n",
750
+ " )\n",
751
+ " \n",
752
+ " objectives_df.select(\n",
753
+ " self.toggle_objective_status,\n",
754
+ " inputs=[objectives_df],\n",
755
+ " outputs=[objectives_df]\n",
756
+ " )\n",
757
+ " \n",
758
+ " # Feedback preferences management\n",
759
+ " add_feedback_btn.click(\n",
760
+ " self.add_feedback_focus,\n",
761
+ " inputs=[new_feedback, feedback_df],\n",
762
+ " outputs=[feedback_df]\n",
763
+ " ).then(\n",
764
+ " lambda: \"\",\n",
765
+ " None,\n",
766
+ " new_feedback\n",
767
+ " )\n",
768
+ " \n",
769
+ " feedback_df.select(\n",
770
+ " self.toggle_feedback_status,\n",
771
+ " inputs=[feedback_df],\n",
772
+ " outputs=[feedback_df]\n",
773
+ " )\n",
774
+ " \n",
775
+ " # Add selected objectives from summary\n",
776
+ " add_selected_objectives.click(\n",
777
+ " self._add_suggested_objectives,\n",
778
+ " inputs=[objectives],\n",
779
+ " outputs=[objectives_df]\n",
780
+ " )\n",
781
+ " \n",
782
+ " return interface\n",
783
+ " \n",
784
+ " def _update_discussion_status(self, state: Dict[str, Any]) -> str:\n",
785
+ " \"\"\"Update discussion status display\"\"\"\n",
786
+ " try:\n",
787
+ " if not state.get(\"discussion_active\"):\n",
788
+ " return \"Start a new case discussion\"\n",
789
+ " \n",
790
+ " start = datetime.fromisoformat(state[\"discussion_start\"])\n",
791
+ " duration = datetime.now() - start\n",
792
+ " minutes = int(duration.total_seconds() / 60)\n",
793
+ " \n",
794
+ " return f\"Active discussion ({minutes} minutes)\"\n",
795
+ " \n",
796
+ " except Exception as e:\n",
797
+ " logger.error(f\"Error updating status: {str(e)}\")\n",
798
+ " return \"Discussion status unknown\"\n",
799
+ " \n",
800
+ " def _refresh_knowledge_profile(\n",
801
+ " self\n",
802
+ " ) -> Tuple[List[List[str]], List[List[str]], List[List[str]]]:\n",
803
+ " \"\"\"Refresh knowledge profile displays\"\"\"\n",
804
+ " try:\n",
805
+ " # Gaps\n",
806
+ " gaps_data = [[\n",
807
+ " topic, f\"{confidence:.2f}\"\n",
808
+ " ] for topic, confidence in \n",
809
+ " self.tutor.learning_context.knowledge_profile[\"gaps\"].items()\n",
810
+ " ]\n",
811
+ " \n",
812
+ " # Strengths\n",
813
+ " strengths_data = [[\n",
814
+ " strength\n",
815
+ " ] for strength in \n",
816
+ " self.tutor.learning_context.knowledge_profile[\"strengths\"]\n",
817
+ " ]\n",
818
+ " \n",
819
+ " # Progress\n",
820
+ " progress_data = [[\n",
821
+ " prog[\"topic\"],\n",
822
+ " f\"{prog['improvement']:.2f}\",\n",
823
+ " prog[\"date\"]\n",
824
+ " ] for prog in \n",
825
+ " self.tutor.learning_context.knowledge_profile[\"recent_progress\"]\n",
826
+ " ]\n",
827
+ " \n",
828
+ " return gaps_data, strengths_data, progress_data\n",
829
+ " \n",
830
+ " except Exception as e:\n",
831
+ " logger.error(f\"Error refreshing profile: {str(e)}\")\n",
832
+ " return [], [], []\n",
833
+ " \n",
834
+ " def _add_suggested_objectives(\n",
835
+ " self,\n",
836
+ " evt: gr.SelectData, # Updated to use gr.SelectData\n",
837
+ " suggested_objectives: List[str]\n",
838
+ " ) -> pd.DataFrame:\n",
839
+ " \"\"\"Add selected suggested objectives to learning objectives\"\"\"\n",
840
+ " try:\n",
841
+ " selected_indices = [evt.index[0]] # Get selected row index\n",
842
+ " \n",
843
+ " for idx in selected_indices:\n",
844
+ " if idx < len(suggested_objectives):\n",
845
+ " objective = suggested_objectives[idx]\n",
846
+ " self.tutor.learning_context.add_learning_objective(objective)\n",
847
+ " \n",
848
+ " return pd.DataFrame([\n",
849
+ " [obj[\"objective\"], obj[\"status\"], obj[\"added\"]]\n",
850
+ " for obj in self.tutor.learning_context.learning_objectives\n",
851
+ " ], columns=[\"Objective\", \"Status\", \"Date Added\"])\n",
852
+ " \n",
853
+ " except Exception as e:\n",
854
+ " logger.error(f\"Error adding objectives: {str(e)}\")\n",
855
+ " return pd.DataFrame()"
856
+ ]
857
+ },
858
+ {
859
+ "cell_type": "markdown",
860
+ "id": "30c0f121-5d5f-4dc0-b897-f6e2067a63b2",
861
+ "metadata": {},
862
+ "source": [
863
+ "## Launch Function"
864
+ ]
865
+ },
866
+ {
867
+ "cell_type": "code",
868
+ "execution_count": null,
869
+ "id": "65f97529-b221-4a19-9856-fb20d7f7316e",
870
+ "metadata": {},
871
+ "outputs": [],
872
+ "source": [
873
+ "#| export\n",
874
+ "async def launch_learning_interface(\n",
875
+ " port: Optional[int] = None,\n",
876
+ " context_path: Optional[Path] = None,\n",
877
+ " share: bool = False,\n",
878
+ " theme: str = \"default\"\n",
879
+ ") -> None:\n",
880
+ " \"\"\"Launch the learning interface application.\"\"\"\n",
881
+ " try:\n",
882
+ " interface = LearningInterface(context_path, theme)\n",
883
+ " app = interface.create_interface()\n",
884
+ " app.launch(\n",
885
+ " server_port=port,\n",
886
+ " share=share\n",
887
+ " )\n",
888
+ " logger.info(f\"Interface launched on port: {port}\")\n",
889
+ " except Exception as e:\n",
890
+ " logger.error(f\"Error launching interface: {str(e)}\")\n",
891
+ " raise"
892
+ ]
893
+ },
894
+ {
895
+ "cell_type": "markdown",
896
+ "id": "5c75de88-f6d5-4a5d-92b5-1ebe85895a84",
897
+ "metadata": {},
898
+ "source": [
899
+ "## Tests"
900
+ ]
901
+ },
902
+ {
903
+ "cell_type": "code",
904
+ "execution_count": null,
905
+ "id": "365bc95a-d189-4ab2-aa30-022d0286b5ba",
906
+ "metadata": {},
907
+ "outputs": [],
908
+ "source": [
909
+ "async def test_learning_interface():\n",
910
+ " \"\"\"Test learning interface functionality\"\"\"\n",
911
+ " interface = LearningInterface()\n",
912
+ " \n",
913
+ " # Test chat processing\n",
914
+ " history = []\n",
915
+ " test_input = \"28yo M with chest pain\"\n",
916
+ " \n",
917
+ " new_history, msg = await interface.process_chat(test_input, history)\n",
918
+ " assert isinstance(new_history, list)\n",
919
+ " assert len(new_history) == 2 # User message + response\n",
920
+ " assert new_history[0][\"role\"] == \"user\"\n",
921
+ " assert new_history[0][\"content\"] == test_input\n",
922
+ " \n",
923
+ " # Test discussion analysis\n",
924
+ " analysis = await interface.end_discussion(new_history)\n",
925
+ " assert isinstance(analysis, dict)\n",
926
+ " assert all(k in analysis for k in [\n",
927
+ " 'learning_points', 'gaps', 'strengths', 'suggested_objectives'\n",
928
+ " ])\n",
929
+ " \n",
930
+ " # Test rotation updates\n",
931
+ " rotation = interface.update_rotation(\n",
932
+ " \"Emergency Medicine\",\n",
933
+ " \"2025-01-01\",\n",
934
+ " \"2025-03-31\",\n",
935
+ " [\"Resuscitation\", \"Procedures\"]\n",
936
+ " )\n",
937
+ " assert rotation[\"specialty\"] == \"Emergency Medicine\"\n",
938
+ " assert \"Resuscitation\" in rotation[\"key_focus_areas\"]\n",
939
+ " \n",
940
+ " # Test objective management\n",
941
+ " objectives = interface.toggle_objective(\"Improve chest pain assessment\", False)\n",
942
+ " assert len(objectives) == 1\n",
943
+ " assert objectives[0][\"status\"] == \"active\"\n",
944
+ " \n",
945
+ " objectives = interface.toggle_objective(\"Improve chest pain assessment\", True)\n",
946
+ " assert objectives[0][\"status\"] == \"completed\"\n",
947
+ " \n",
948
+ " # Test feedback preferences\n",
949
+ " preferences = interface.toggle_feedback(\"Include more ddx\", True)\n",
950
+ " assert len(preferences) == 1\n",
951
+ " assert preferences[0][\"active\"] == True\n",
952
+ " \n",
953
+ " print(\"Interface tests passed!\")\n",
954
+ "\n",
955
+ "# Run tests\n",
956
+ "if __name__ == \"__main__\":\n",
957
+ " import asyncio\n",
958
+ " if not asyncio.get_event_loop().is_running():\n",
959
+ " asyncio.run(test_learning_interface())"
960
+ ]
961
+ }
962
+ ],
963
+ "metadata": {
964
+ "kernelspec": {
965
+ "display_name": "python3",
966
+ "language": "python",
967
+ "name": "python3"
968
+ }
969
+ },
970
+ "nbformat": 4,
971
+ "nbformat_minor": 5
972
+ }
 
 
 
 
 
 
 
wardbuddy/learning_interface.py CHANGED
@@ -101,9 +101,9 @@ class LearningInterface:
101
  async def process_chat(
102
  self,
103
  message: str,
104
- history: List[Dict[str, str]],
105
  state: Dict[str, Any]
106
- ) -> Tuple[List[Dict[str, str]], str, Dict[str, Any]]:
107
  """
108
  Process chat messages with state management.
109
 
@@ -127,13 +127,10 @@ class LearningInterface:
127
  # Get tutor response
128
  response = await self.tutor.discuss_case(message)
129
 
130
- # Update history and state
131
  if history is None:
132
  history = []
133
- history.extend([
134
- {"role": "user", "content": message},
135
- {"role": "assistant", "content": response}
136
- ])
137
 
138
  state["last_message"] = datetime.now().isoformat()
139
 
@@ -145,14 +142,14 @@ class LearningInterface:
145
 
146
  async def end_discussion(
147
  self,
148
- history: List[Dict[str, str]],
149
  state: Dict[str, Any]
150
  ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
151
  """
152
  Analyze completed discussion and prepare summary.
153
 
154
  Args:
155
- history: Chat history
156
  state: Current interface state
157
 
158
  Returns:
@@ -167,8 +164,16 @@ class LearningInterface:
167
  "suggested_objectives": []
168
  }, state
169
 
 
 
 
 
 
 
 
 
170
  # Get analysis
171
- analysis = await self.tutor.analyze_discussion(history)
172
 
173
  # Reset discussion state
174
  state["discussion_active"] = False
@@ -184,8 +189,8 @@ class LearningInterface:
184
  "gaps": {},
185
  "strengths": [],
186
  "suggested_objectives": []
187
- }, state
188
-
189
  def update_rotation(
190
  self,
191
  specialty: str,
 
101
  async def process_chat(
102
  self,
103
  message: str,
104
+ history: List[List[str]],
105
  state: Dict[str, Any]
106
+ ) -> Tuple[List[List[str]], str, Dict[str, Any]]:
107
  """
108
  Process chat messages with state management.
109
 
 
127
  # Get tutor response
128
  response = await self.tutor.discuss_case(message)
129
 
130
+ # Update history - now using list pairs instead of dicts
131
  if history is None:
132
  history = []
133
+ history.append([message, response]) # Changed from dict format to list pair
 
 
 
134
 
135
  state["last_message"] = datetime.now().isoformat()
136
 
 
142
 
143
  async def end_discussion(
144
  self,
145
+ history: List[List[str]],
146
  state: Dict[str, Any]
147
  ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
148
  """
149
  Analyze completed discussion and prepare summary.
150
 
151
  Args:
152
+ history: Chat history as list of [user_message, assistant_message] pairs
153
  state: Current interface state
154
 
155
  Returns:
 
164
  "suggested_objectives": []
165
  }, state
166
 
167
+ # Convert history format for analysis
168
+ formatted_history = []
169
+ for user_msg, assistant_msg in history:
170
+ formatted_history.extend([
171
+ {"role": "user", "content": user_msg},
172
+ {"role": "assistant", "content": assistant_msg}
173
+ ])
174
+
175
  # Get analysis
176
+ analysis = await self.tutor.analyze_discussion(formatted_history)
177
 
178
  # Reset discussion state
179
  state["discussion_active"] = False
 
189
  "gaps": {},
190
  "strengths": [],
191
  "suggested_objectives": []
192
+ }, state
193
+
194
  def update_rotation(
195
  self,
196
  specialty: str,