LiamKhoaLe commited on
Commit
f05b79e
·
1 Parent(s): c6c9f51

Solve updatePatientDisplay func with undefined handler

Browse files
src/data/repositories/account.py DELETED
@@ -1,220 +0,0 @@
1
- # src/data/repositories/account.py
2
- """
3
- User account management operations for MongoDB.
4
- Each account represents a doctor.
5
-
6
- ## Fields
7
- _id: index
8
- name: The name attached to the account
9
- role: What type of account this is
10
- specialty: Any extra information about the account
11
- created_at: The timestamp when the account was created
12
- updated_at: The timestamp when the account data was last modified
13
- last_seen: The last time this account was retrieved
14
- """
15
-
16
- import re
17
- from datetime import datetime, timezone
18
- from typing import Any
19
-
20
- from bson import ObjectId
21
- from bson.errors import InvalidId
22
- from pandas import DataFrame
23
- from pymongo import ASCENDING
24
- from pymongo.errors import ConnectionFailure, PyMongoError, WriteError
25
-
26
- from src.data.connection import (ActionFailed, Collections, get_collection,
27
- setup_collection)
28
- from src.models.account import Account
29
- from src.utils.logger import logger
30
-
31
- VALID_ROLES = [
32
- "Doctor",
33
- "Healthcare Prof",
34
- "Nurse",
35
- "Caregiver",
36
- "Physician",
37
- "Medical Student",
38
- "Other"
39
- ]
40
-
41
- def init(
42
- *,
43
- collection_name: str = Collections.ACCOUNT,
44
- validator_path: str = "schemas/account_validator.json",
45
- drop: bool = False
46
- ):
47
- """Initializes the collection, applying schema validation."""
48
- try:
49
- if drop:
50
- get_collection(collection_name).drop()
51
- setup_collection(collection_name, validator_path)
52
- except (ConnectionFailure, PyMongoError) as e:
53
- logger().error(f"Failed to initialize collection '{collection_name}': {e}")
54
- raise ActionFailed(f"Database operation failed during initialization: {e}") from e
55
-
56
- def get_account_frame(
57
- *,
58
- collection_name: str = Collections.ACCOUNT
59
- ) -> DataFrame:
60
- """Get accounts as a pandas DataFrame, raising ActionFailed on error."""
61
- try:
62
- collection = get_collection(collection_name)
63
- return DataFrame(collection.find())
64
- except (ConnectionFailure, PyMongoError) as e:
65
- logger().error(f"Failed to retrieve account frame: {e}")
66
- raise ActionFailed(f"Could not retrieve accounts for DataFrame: {e}") from e
67
-
68
- def create_account(
69
- name: str,
70
- role: str,
71
- specialty: str | None = None,
72
- *,
73
- collection_name: str = Collections.ACCOUNT
74
- ) -> str:
75
- """Creates a new user account, raising ActionFailed on error."""
76
- now = datetime.now(timezone.utc)
77
- user_data: dict[str, Any] = {
78
- "name": name,
79
- "role" : role,
80
- "created_at": now,
81
- "updated_at": now,
82
- "last_seen": now
83
- }
84
- if specialty:
85
- user_data["specialty"] = specialty
86
-
87
- try:
88
- collection = get_collection(collection_name)
89
- result = collection.insert_one(user_data)
90
- logger().info(f"Created new account: {result.inserted_id}")
91
- return str(result.inserted_id)
92
- except WriteError as e:
93
- logger().error(f"Failed to create account due to data conflict: {e}")
94
- raise ActionFailed(f"Account could not be created. Data is conflicting or invalid.") from e
95
- except (ConnectionFailure, PyMongoError) as e:
96
- logger().error(f"Database error while creating account: {e}")
97
- raise ActionFailed(f"A database error occurred while creating the account.") from e
98
-
99
- def update_account(
100
- user_id: str,
101
- updates: dict[str, Any],
102
- *,
103
- collection_name: str = Collections.ACCOUNT
104
- ) -> bool:
105
- """Updates an existing user account, raising ActionFailed on error."""
106
- try:
107
- obj_user_id = ObjectId(user_id)
108
- collection = get_collection(collection_name)
109
-
110
- if "created_at" in updates:
111
- logger().warning("Attempting to modify the 'created_at' attribute of an account. This is not allowed.")
112
- updates.pop("created_at")
113
- updates["updated_at"] = datetime.now(timezone.utc)
114
-
115
- result = collection.update_one(
116
- {"_id": obj_user_id},
117
- {"$set": updates}
118
- )
119
- return result.modified_count > 0
120
- except InvalidId as e:
121
- logger().error(f"Invalid user_id format for update: '{user_id}'")
122
- raise ActionFailed(f"The provided user ID '{user_id}' is not a valid format.") from e
123
- except (ConnectionFailure, PyMongoError) as e:
124
- logger().error(f"Database error while updating account '{user_id}': {e}")
125
- raise ActionFailed(f"A database error occurred while updating the account.") from e
126
-
127
- def get_account(
128
- user_id: str,
129
- *,
130
- collection_name: str = Collections.ACCOUNT
131
- ) -> Account | None:
132
- """Retrieves an account by ID. Returns a Pydantic Account object or None."""
133
- try:
134
- obj_user_id = ObjectId(user_id)
135
- collection = get_collection(collection_name)
136
- now = datetime.now(timezone.utc)
137
-
138
- account_dict = collection.find_one_and_update(
139
- {"_id": obj_user_id},
140
- {"$set": {"last_seen": now}},
141
- return_document=True
142
- )
143
-
144
- if account_dict:
145
- return Account.model_validate(account_dict)
146
- return None
147
- except InvalidId as e:
148
- logger().error(f"Invalid user_id format for get: '{user_id}'")
149
- raise ActionFailed(f"The provided user ID '{user_id}' is not a valid format.") from e
150
- except (ConnectionFailure, PyMongoError) as e:
151
- logger().error(f"Database error while getting account '{user_id}': {e}")
152
- raise ActionFailed(f"A database error occurred while retrieving the account.") from e
153
-
154
- def get_account_by_name(
155
- name: str,
156
- *,
157
- collection_name: str = Collections.ACCOUNT
158
- ) -> Account | None:
159
- """
160
- Gets an account by name. Returns a Pydantic Account object or None.
161
- @deprecated Use search_accounts instead for more flexibility.
162
- """
163
- logger().info(f"Trying to retrieve account: {name}")
164
- try:
165
- collection = get_collection(collection_name)
166
- now = datetime.now(timezone.utc)
167
- account_dict = collection.find_one_and_update(
168
- {"name": name},
169
- {"$set": {"last_seen": now}},
170
- return_document=True
171
- )
172
- if account_dict:
173
- return Account.model_validate(account_dict)
174
- return None
175
- except (ConnectionFailure, PyMongoError) as e:
176
- logger().error(f"Database error while getting account by name '{name}': {e}")
177
- raise ActionFailed(f"A database error occurred while retrieving the account by name.") from e
178
-
179
- def search_accounts(
180
- query: str,
181
- limit: int = 10,
182
- *,
183
- collection_name: str = Collections.ACCOUNT
184
- ) -> list[Account]:
185
- """Searches accounts by name, returning a list of Pydantic Account objects."""
186
- if not query:
187
- return []
188
-
189
- logger().info(f"Searching accounts with query: '{query}', limit: {limit}")
190
- pattern = re.compile(re.escape(query), re.IGNORECASE)
191
-
192
- try:
193
- collection = get_collection(collection_name)
194
- cursor = collection.find({
195
- "name": {"$regex": pattern}
196
- }).sort("name", ASCENDING).limit(limit)
197
-
198
- accounts = [Account.model_validate(doc) for doc in cursor]
199
- logger().info(f"Found {len(accounts)} accounts matching query")
200
- return accounts
201
- except (ConnectionFailure, PyMongoError) as e:
202
- logger().error(f"Database error during account search for query '{query}': {e}")
203
- raise ActionFailed(f"A database error occurred during the account search.") from e
204
-
205
- def get_all_accounts(
206
- limit: int = 50,
207
- *,
208
- collection_name: str = Collections.ACCOUNT
209
- ) -> list[Account]:
210
- """Gets all accounts, returning a list of Pydantic Account objects."""
211
- try:
212
- collection = get_collection(collection_name)
213
- cursor = collection.find().sort("name", ASCENDING).limit(limit)
214
-
215
- accounts = [Account.model_validate(doc) for doc in cursor]
216
- logger().info(f"Retrieved {len(accounts)} accounts")
217
- return accounts
218
- except (ConnectionFailure, PyMongoError) as e:
219
- logger().error(f"Database error while getting all accounts: {e}")
220
- raise ActionFailed(f"A database error occurred while retrieving all accounts.") from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/data/repositories/emr.py DELETED
@@ -1,285 +0,0 @@
1
- # src/data/repositories/emr.py
2
-
3
- import json
4
- from datetime import datetime, timezone
5
- from typing import Any, Dict, List, Optional
6
-
7
- from bson import ObjectId
8
- from pymongo import ASCENDING, DESCENDING
9
- from pymongo.errors import ConnectionFailure, OperationFailure, PyMongoError
10
-
11
- from src.data.connection import ActionFailed, get_collection, setup_collection
12
- from src.models.emr import EMRCreateRequest, EMRResponse, ExtractedData
13
- from src.utils.logger import logger
14
-
15
- EMR_COLLECTION = "emr"
16
-
17
-
18
- def init():
19
- """Create the EMR collection with validation schema."""
20
- try:
21
- setup_collection(EMR_COLLECTION, "schemas/emr_validator.json")
22
- # Create indexes for better performance
23
- collection = get_collection(EMR_COLLECTION)
24
- collection.create_index("patient_id")
25
- collection.create_index("doctor_id")
26
- collection.create_index("session_id")
27
- collection.create_index("message_id")
28
- collection.create_index("created_at")
29
- collection.create_index([("patient_id", ASCENDING), ("created_at", DESCENDING)])
30
- collection.create_index([("patient_id", ASCENDING), ("doctor_id", ASCENDING)])
31
- collection.create_index([("session_id", ASCENDING), ("created_at", DESCENDING)])
32
- collection.create_index("confidence_score")
33
- logger().info("EMR collection created successfully with indexes")
34
- except Exception as e:
35
- logger().error(f"Error creating EMR collection: {e}")
36
- raise
37
-
38
-
39
- def create_emr_entry(emr_data: EMRCreateRequest, embeddings: List[float]) -> str:
40
- """Create a new EMR entry in the database."""
41
- try:
42
- collection = get_collection(EMR_COLLECTION)
43
-
44
- # Check if EMR entry already exists for this message
45
- existing = collection.find_one({"message_id": emr_data.message_id})
46
- if existing:
47
- logger().warning(f"EMR entry already exists for message {emr_data.message_id}")
48
- return str(existing["_id"])
49
-
50
- now = datetime.now(timezone.utc)
51
-
52
- doc = {
53
- "patient_id": emr_data.patient_id,
54
- "doctor_id": emr_data.doctor_id,
55
- "message_id": emr_data.message_id,
56
- "session_id": emr_data.session_id,
57
- "original_message": emr_data.original_message,
58
- "extracted_data": emr_data.extracted_data.model_dump(),
59
- "embeddings": embeddings,
60
- "confidence_score": emr_data.confidence_score,
61
- "created_at": now,
62
- "updated_at": now
63
- }
64
-
65
- result = collection.insert_one(doc)
66
- logger().info(f"Created EMR entry for patient {emr_data.patient_id}, message {emr_data.message_id}")
67
- return str(result.inserted_id)
68
-
69
- except Exception as e:
70
- logger().error(f"Error creating EMR entry: {e}")
71
- raise
72
-
73
-
74
- def get_emr_by_id(emr_id: str) -> Optional[Dict[str, Any]]:
75
- """Get an EMR entry by its ID."""
76
- try:
77
- collection = get_collection(EMR_COLLECTION)
78
- result = collection.find_one({"_id": ObjectId(emr_id)})
79
- if result:
80
- result["_id"] = str(result["_id"])
81
- return result
82
- except Exception as e:
83
- logger().error(f"Error getting EMR by ID {emr_id}: {e}")
84
- return None
85
-
86
-
87
- def get_patient_emr_entries(
88
- patient_id: str,
89
- limit: int = 20,
90
- offset: int = 0
91
- ) -> List[Dict[str, Any]]:
92
- """Get EMR entries for a specific patient, ordered by creation date."""
93
- try:
94
- collection = get_collection(EMR_COLLECTION)
95
- cursor = collection.find(
96
- {"patient_id": patient_id}
97
- ).sort("created_at", DESCENDING).skip(offset).limit(limit)
98
-
99
- results = []
100
- for doc in cursor:
101
- doc["_id"] = str(doc["_id"])
102
- results.append(doc)
103
-
104
- logger().info(f"Retrieved {len(results)} EMR entries for patient {patient_id}")
105
- return results
106
-
107
- except Exception as e:
108
- logger().error(f"Error getting patient EMR entries: {e}")
109
- return []
110
-
111
-
112
- def search_emr_by_semantic_similarity(
113
- patient_id: str,
114
- query_embeddings: List[float],
115
- limit: int = 10,
116
- threshold: float = 0.7
117
- ) -> List[Dict[str, Any]]:
118
- """Search EMR entries using semantic similarity with embeddings."""
119
- try:
120
- collection = get_collection(EMR_COLLECTION)
121
-
122
- # Use MongoDB's vector search capabilities if available
123
- # For now, we'll implement a simple cosine similarity search
124
- pipeline = [
125
- {"$match": {"patient_id": patient_id}},
126
- {
127
- "$addFields": {
128
- "similarity": {
129
- "$let": {
130
- "vars": {
131
- "dotProduct": {
132
- "$reduce": {
133
- "input": {"$range": [0, {"$size": "$embeddings"}]},
134
- "initialValue": 0,
135
- "in": {
136
- "$add": [
137
- "$$value",
138
- {
139
- "$multiply": [
140
- {"$arrayElemAt": ["$embeddings", "$$this"]},
141
- {"$arrayElemAt": [query_embeddings, "$$this"]}
142
- ]
143
- }
144
- ]
145
- }
146
- }
147
- },
148
- "magnitudeA": {
149
- "$sqrt": {
150
- "$reduce": {
151
- "input": "$embeddings",
152
- "initialValue": 0,
153
- "in": {"$add": ["$$value", {"$multiply": ["$$this", "$$this"]}]}
154
- }
155
- }
156
- },
157
- "magnitudeB": {
158
- "$sqrt": {
159
- "$reduce": {
160
- "input": query_embeddings,
161
- "initialValue": 0,
162
- "in": {"$add": ["$$value", {"$multiply": ["$$this", "$$this"]}]}
163
- }
164
- }
165
- }
166
- },
167
- "in": {
168
- "$divide": [
169
- "$$dotProduct",
170
- {"$multiply": ["$$magnitudeA", "$$magnitudeB"]}
171
- ]
172
- }
173
- }
174
- }
175
- }
176
- },
177
- {"$match": {"similarity": {"$gte": threshold}}},
178
- {"$sort": {"similarity": DESCENDING}},
179
- {"$limit": limit}
180
- ]
181
-
182
- results = list(collection.aggregate(pipeline))
183
- for doc in results:
184
- doc["_id"] = str(doc["_id"])
185
-
186
- logger().info(f"Found {len(results)} similar EMR entries for patient {patient_id}")
187
- return results
188
-
189
- except Exception as e:
190
- logger().error(f"Error searching EMR by similarity: {e}")
191
- return []
192
-
193
-
194
- def update_emr_entry(emr_id: str, updates: Dict[str, Any]) -> bool:
195
- """Update an EMR entry."""
196
- try:
197
- collection = get_collection(EMR_COLLECTION)
198
- updates["updated_at"] = datetime.now(timezone.utc)
199
-
200
- result = collection.update_one(
201
- {"_id": ObjectId(emr_id)},
202
- {"$set": updates}
203
- )
204
-
205
- success = result.modified_count > 0
206
- if success:
207
- logger().info(f"Updated EMR entry {emr_id}")
208
- else:
209
- logger().warning(f"No EMR entry found with ID {emr_id}")
210
-
211
- return success
212
-
213
- except Exception as e:
214
- logger().error(f"Error updating EMR entry {emr_id}: {e}")
215
- return False
216
-
217
-
218
- def delete_emr_entry(emr_id: str) -> bool:
219
- """Delete an EMR entry."""
220
- try:
221
- collection = get_collection(EMR_COLLECTION)
222
- result = collection.delete_one({"_id": ObjectId(emr_id)})
223
-
224
- success = result.deleted_count > 0
225
- if success:
226
- logger().info(f"Deleted EMR entry {emr_id}")
227
- else:
228
- logger().warning(f"No EMR entry found with ID {emr_id}")
229
-
230
- return success
231
-
232
- except Exception as e:
233
- logger().error(f"Error deleting EMR entry {emr_id}: {e}")
234
- return False
235
-
236
-
237
- def check_emr_exists(message_id: str) -> bool:
238
- """Check if an EMR entry already exists for a message."""
239
- try:
240
- collection = get_collection(EMR_COLLECTION)
241
- existing = collection.find_one({"message_id": message_id})
242
- return existing is not None
243
- except Exception as e:
244
- logger().error(f"Error checking EMR existence: {e}")
245
- return False
246
-
247
-
248
- def get_emr_statistics(patient_id: str) -> Dict[str, Any]:
249
- """Get EMR statistics for a patient."""
250
- try:
251
- collection = get_collection(EMR_COLLECTION)
252
-
253
- pipeline = [
254
- {"$match": {"patient_id": patient_id}},
255
- {
256
- "$group": {
257
- "_id": None,
258
- "total_entries": {"$sum": 1},
259
- "avg_confidence": {"$avg": "$confidence_score"},
260
- "latest_entry": {"$max": "$created_at"},
261
- "diagnosis_count": {
262
- "$sum": {"$size": "$extracted_data.diagnosis"}
263
- },
264
- "medication_count": {
265
- "$sum": {"$size": "$extracted_data.medications"}
266
- }
267
- }
268
- }
269
- ]
270
-
271
- result = list(collection.aggregate(pipeline))
272
- if result:
273
- return result[0]
274
- else:
275
- return {
276
- "total_entries": 0,
277
- "avg_confidence": 0,
278
- "latest_entry": None,
279
- "diagnosis_count": 0,
280
- "medication_count": 0
281
- }
282
-
283
- except Exception as e:
284
- logger().error(f"Error getting EMR statistics: {e}")
285
- return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/data/repositories/medical_memory.py DELETED
@@ -1,148 +0,0 @@
1
- # src/data/repositories/medical_memory.py
2
- """
3
- Medical memory management operations for MongoDB.
4
- Medical memories are unstructured summaries, often with vector embeddings for semantic search.
5
-
6
- ## Fields
7
- _id: index
8
- patient_id: The patient this memory relates to
9
- doctor_id: The doctor involved in the context of this memory
10
- session_id: The chat session this memory was derived from (optional)
11
- summary: The unstructured text summary of the medical context
12
- embedding: The vector embedding of the summary for semantic search (optional)
13
- created_at: The timestamp when the memory was created
14
- """
15
- from datetime import datetime, timezone
16
-
17
- import numpy as np
18
- from bson import ObjectId
19
- from bson.errors import InvalidId
20
- from pymongo import DESCENDING
21
- from pymongo.errors import ConnectionFailure, PyMongoError, WriteError
22
-
23
- from src.data.connection import (ActionFailed, Collections, get_collection,
24
- setup_collection)
25
- from src.models.medical import MedicalMemory, SemanticSearchResult
26
- from src.utils.logger import logger
27
-
28
-
29
- def init(
30
- *,
31
- collection_name: str = Collections.MEDICAL_MEMORY,
32
- validator_path: str = "schemas/medical_memory_validator.json",
33
- drop: bool = False
34
- ):
35
- """Initializes the medical_memory collection, applying schema validation."""
36
- try:
37
- if drop:
38
- get_collection(collection_name).drop()
39
- setup_collection(collection_name, validator_path)
40
- except (ConnectionFailure, PyMongoError) as e:
41
- logger().error(f"Failed to initialize collection '{collection_name}': {e}")
42
- raise ActionFailed(f"Database operation failed during initialization: {e}") from e
43
-
44
- def create_memory(
45
- patient_id: str,
46
- doctor_id: str,
47
- summary: str,
48
- session_id: str | None = None,
49
- embedding: list[float] | None = None,
50
- *,
51
- collection_name: str = Collections.MEDICAL_MEMORY
52
- ) -> str:
53
- """Saves a new medical memory summary, raising ActionFailed on error."""
54
- try:
55
- collection = get_collection(collection_name)
56
- doc = {
57
- "patient_id": ObjectId(patient_id),
58
- "doctor_id": ObjectId(doctor_id),
59
- "summary": summary,
60
- "created_at": datetime.now(timezone.utc)
61
- }
62
- if session_id:
63
- doc["session_id"] = ObjectId(session_id)
64
- if embedding:
65
- doc["embedding"] = embedding
66
-
67
- result = collection.insert_one(doc)
68
- return str(result.inserted_id)
69
- except InvalidId as e:
70
- logger().error(f"Invalid ObjectId format provided for medical memory: {e}")
71
- raise ActionFailed("Patient, Doctor, or Session ID is not a valid format.") from e
72
- except (WriteError, ConnectionFailure, PyMongoError) as e:
73
- logger().error(f"Failed to create medical memory: {e}")
74
- raise ActionFailed("A database error occurred while creating the medical memory.") from e
75
-
76
- def get_recent_memories(
77
- patient_id: str,
78
- limit: int = 20,
79
- *,
80
- collection_name: str = Collections.MEDICAL_MEMORY
81
- ) -> list[MedicalMemory]:
82
- """Retrieves the most recent memory summaries for a patient."""
83
- try:
84
- obj_patient_id = ObjectId(patient_id)
85
- collection = get_collection(collection_name)
86
- cursor = collection.find(
87
- {"patient_id": obj_patient_id}
88
- ).sort("created_at", DESCENDING).limit(limit)
89
-
90
- return [MedicalMemory.model_validate(doc) for doc in cursor]
91
- except InvalidId as e:
92
- logger().error(f"Invalid patient_id format for get_recent_memories: '{patient_id}'")
93
- raise ActionFailed("The provided patient ID is not a valid format.") from e
94
- except (ConnectionFailure, PyMongoError) as e:
95
- logger().error(f"Database error retrieving recent memories for patient '{patient_id}': {e}")
96
- raise ActionFailed("A database error occurred while retrieving recent memories.") from e
97
-
98
- def search_memories_semantic(
99
- patient_id: str,
100
- query_embedding: list[float],
101
- limit: int = 5,
102
- *,
103
- collection_name: str = Collections.MEDICAL_MEMORY
104
- ) -> list[SemanticSearchResult]:
105
- """Searches memory summaries using semantic similarity with embeddings."""
106
- try:
107
- obj_patient_id = ObjectId(patient_id)
108
- collection = get_collection(collection_name)
109
-
110
- # In a real-world scenario, this would be an Atlas Vector Search query.
111
- # This implementation fetches all docs and calculates similarity in the client.
112
- docs = list(collection.find({
113
- "patient_id": obj_patient_id,
114
- "embedding": {"$exists": True}
115
- }))
116
-
117
- if not docs:
118
- return []
119
-
120
- query_vec = np.array(query_embedding, dtype="float32")
121
- results = []
122
- for doc in docs:
123
- doc_vec = np.array(doc["embedding"], dtype="float32")
124
-
125
- # Calculate cosine similarity
126
- dot_product = np.dot(query_vec, doc_vec)
127
- norm_query = np.linalg.norm(query_vec)
128
- norm_doc = np.linalg.norm(doc_vec)
129
-
130
- if norm_query > 0 and norm_doc > 0:
131
- similarity = float(dot_product / (norm_query * norm_doc))
132
- result_data = {
133
- "summary": doc["summary"],
134
- "similarity_score": similarity,
135
- "created_at": doc["created_at"],
136
- "session_id": doc.get("session_id")
137
- }
138
- results.append(SemanticSearchResult.model_validate(result_data))
139
-
140
- # Sort by similarity (highest first) and return top results
141
- results.sort(key=lambda x: x.similarity_score, reverse=True)
142
- return results[:limit]
143
- except InvalidId as e:
144
- logger().error(f"Invalid patient_id format for semantic search: '{patient_id}'")
145
- raise ActionFailed("The provided patient ID is not a valid format.") from e
146
- except (ConnectionFailure, PyMongoError) as e:
147
- logger().error(f"Database error during semantic search for patient '{patient_id}': {e}")
148
- raise ActionFailed("A database error occurred during the semantic search.") from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/data/repositories/medical_record.py DELETED
@@ -1,93 +0,0 @@
1
- # src/data/repositories/medical_record.py
2
- """
3
- Medical record management operations for MongoDB.
4
- Medical records are structured, factual pieces of information about a patient.
5
-
6
- ## Fields
7
- _id: index
8
- patient_id: The patient this record belongs to
9
- doctor_id: The doctor who created or is associated with this record
10
- record_type: The category of the record (e.g., 'Consultation', 'LabResult')
11
- details: An object containing the specific, structured data for the record
12
- created_at: The timestamp when the record was created
13
- updated_at: The timestamp when the record was last modified
14
- """
15
-
16
- from datetime import datetime, timezone
17
- from typing import Any
18
-
19
- from bson import ObjectId
20
- from bson.errors import InvalidId
21
- from pymongo import ASCENDING
22
- from pymongo.errors import ConnectionFailure, PyMongoError, WriteError
23
-
24
- from src.data.connection import (ActionFailed, Collections, get_collection,
25
- setup_collection)
26
- from src.models.medical import MedicalRecord
27
- from src.utils.logger import logger
28
-
29
-
30
- def init(
31
- *,
32
- collection_name: str = Collections.MEDICAL_RECORDS,
33
- validator_path: str = "schemas/medical_record_validator.json",
34
- drop: bool = False
35
- ):
36
- """Initializes the medical_records collection, applying schema validation."""
37
- try:
38
- if drop:
39
- get_collection(collection_name).drop()
40
- setup_collection(collection_name, validator_path)
41
- except (ConnectionFailure, PyMongoError) as e:
42
- logger().error(f"Failed to initialize collection '{collection_name}': {e}")
43
- raise ActionFailed(f"Database operation failed during initialization: {e}") from e
44
-
45
- def create_medical_record(
46
- patient_id: str,
47
- doctor_id: str,
48
- record_type: str,
49
- details: dict[str, Any],
50
- *,
51
- collection_name: str = Collections.MEDICAL_RECORDS
52
- ) -> str:
53
- """Creates a new medical record, raising ActionFailed on error."""
54
- now = datetime.now(timezone.utc)
55
- try:
56
- collection = get_collection(collection_name)
57
- record_data = {
58
- "patient_id": ObjectId(patient_id),
59
- "doctor_id": ObjectId(doctor_id),
60
- "record_type": record_type,
61
- "details": details,
62
- "created_at": now,
63
- "updated_at": now
64
- }
65
- result = collection.insert_one(record_data)
66
- return str(result.inserted_id)
67
- except InvalidId as e:
68
- logger().error(f"Invalid ObjectId format provided for medical record: {e}")
69
- raise ActionFailed("Patient ID or Doctor ID is not a valid format.") from e
70
- except (WriteError, ConnectionFailure, PyMongoError) as e:
71
- logger().error(f"Failed to create medical record: {e}")
72
- raise ActionFailed("A database error occurred while creating the medical record.") from e
73
-
74
- def get_records_for_patient(
75
- patient_id: str,
76
- *,
77
- collection_name: str = Collections.MEDICAL_RECORDS
78
- ) -> list[MedicalRecord]:
79
- """Retrieves all medical records for a patient, sorted by creation date."""
80
- try:
81
- obj_patient_id = ObjectId(patient_id)
82
- collection = get_collection(collection_name)
83
- cursor = collection.find(
84
- {"patient_id": obj_patient_id}
85
- ).sort("created_at", ASCENDING)
86
-
87
- return [MedicalRecord.model_validate(doc) for doc in cursor]
88
- except InvalidId as e:
89
- logger().error(f"Invalid patient_id format for get_records_for_patient: '{patient_id}'")
90
- raise ActionFailed("The provided patient ID is not a valid format.") from e
91
- except (ConnectionFailure, PyMongoError) as e:
92
- logger().error(f"Database error retrieving records for patient '{patient_id}': {e}")
93
- raise ActionFailed("A database error occurred while retrieving medical records.") from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/data/repositories/patient.py DELETED
@@ -1,176 +0,0 @@
1
- # src/data/repositories/patient.py
2
- """
3
- Patient management operations for MongoDB.
4
- A patient is a person who has been assigned to a doctor for treatment.
5
-
6
- ## Fields
7
- _id: index
8
- name: The name of the patient
9
- age: How old the patient is
10
- sex: Male or female
11
- ethnicity: Geneological information
12
- address: Where they live
13
- phone: What their phone number is
14
- email: What their email address it
15
- medications: Any medications they are currently taking
16
- past_assessment_summary: Summarisation of past assessments
17
- assigned_doctor_id: The id of the account assigned to this patient
18
- created_at: The timestamp when the patient was created
19
- updated_at: The timestamp when the patient data was last modified
20
- """
21
-
22
- import re
23
- from datetime import datetime, timezone
24
- from typing import Any
25
-
26
- from bson import ObjectId
27
- from bson.errors import InvalidId
28
- from pymongo import ASCENDING
29
- from pymongo.errors import ConnectionFailure, PyMongoError, WriteError
30
-
31
- from src.data.connection import (ActionFailed, Collections, get_collection,
32
- setup_collection)
33
- from src.models.patient import Patient
34
- from src.utils.logger import logger
35
-
36
-
37
- def init(
38
- *,
39
- collection_name: str = Collections.PATIENT,
40
- validator_path: str = "schemas/patient_validator.json",
41
- drop: bool = False
42
- ):
43
- """Initializes the collection, applying schema and indexes."""
44
- try:
45
- if drop:
46
- get_collection(collection_name).drop()
47
- setup_collection(collection_name, validator_path)
48
- get_collection(collection_name).create_index("assigned_doctor_id")
49
- logger("Init").info(f"Created index on assigned_doctor_id in '{collection_name}'")
50
- except (ConnectionFailure, PyMongoError) as e:
51
- logger().error(f"Failed to initialize collection '{collection_name}': {e}")
52
- raise ActionFailed(f"Database operation failed during initialization: {e}") from e
53
-
54
- def create_patient(
55
- name: str,
56
- age: int,
57
- sex: str,
58
- ethnicity: str | None = None,
59
- address: str | None = None,
60
- phone: str | None = None,
61
- email: str | None = None,
62
- medications: list[str] | None = None,
63
- past_assessment_summary: str | None = None,
64
- assigned_doctor_id: str | None = None,
65
- *,
66
- collection_name: str = Collections.PATIENT
67
- ) -> str:
68
- """Creates a new patient record, raising ActionFailed on error."""
69
- now = datetime.now(timezone.utc)
70
- patient_data = {
71
- "name": name,
72
- "age": age,
73
- "sex": sex,
74
- "created_at": now,
75
- "updated_at": now
76
- }
77
- # Add optional fields to the dictionary
78
- if ethnicity: patient_data["ethnicity"] = ethnicity
79
- if address: patient_data["address"] = address
80
- if phone: patient_data["phone"] = phone
81
- if email: patient_data["email"] = email
82
- if medications: patient_data["medications"] = medications
83
- if past_assessment_summary: patient_data["past_assessment_summary"] = past_assessment_summary
84
-
85
- try:
86
- collection = get_collection(collection_name)
87
- if assigned_doctor_id:
88
- patient_data["assigned_doctor_id"] = ObjectId(assigned_doctor_id)
89
-
90
- result = collection.insert_one(patient_data)
91
- return str(result.inserted_id)
92
- except InvalidId as e:
93
- logger().error(f"Invalid assigned_doctor_id format: '{assigned_doctor_id}'")
94
- raise ActionFailed(f"The assigned doctor ID is not a valid format.") from e
95
- except WriteError as e:
96
- logger().error(f"Failed to create patient due to validation or write error: {e}")
97
- raise ActionFailed(f"Patient could not be created due to invalid data.") from e
98
- except (ConnectionFailure, PyMongoError) as e:
99
- logger().error(f"Database error while creating patient: {e}")
100
- raise ActionFailed(f"A database error occurred while creating the patient.") from e
101
-
102
- def get_patient_by_id(
103
- patient_id: str,
104
- *,
105
- collection_name: str = Collections.PATIENT
106
- ) -> Patient | None:
107
- """Gets a patient by ID. Returns a Pydantic Patient object or None."""
108
- logger().info(f"Searching for patient with id '{patient_id}'")
109
- try:
110
- obj_patient_id = ObjectId(patient_id)
111
- collection = get_collection(collection_name)
112
- patient_dict = collection.find_one({"_id": obj_patient_id})
113
-
114
- if patient_dict:
115
- return Patient.model_validate(patient_dict)
116
- return None
117
- except InvalidId as e:
118
- logger().error(f"Invalid patient_id format for get: '{patient_id}'")
119
- raise ActionFailed(f"The provided patient ID '{patient_id}' is not a valid format.") from e
120
- except (ConnectionFailure, PyMongoError) as e:
121
- logger().error(f"Database error in get_patient_by_id for ID '{patient_id}': {e}")
122
- raise ActionFailed(f"A database error occurred while retrieving the patient.") from e
123
-
124
- # TODO Make this more rigidly typed, maybe merge with create_patient?
125
- def update_patient_profile(
126
- patient_id: str,
127
- updates: dict[str, Any],
128
- *,
129
- collection_name: str = Collections.PATIENT
130
- ) -> int:
131
- """Updates a patient's profile, raising ActionFailed on error."""
132
- try:
133
- obj_patient_id = ObjectId(patient_id)
134
- collection = get_collection(collection_name)
135
- updates["updated_at"] = datetime.now(timezone.utc)
136
-
137
- result = collection.update_one(
138
- {"_id": obj_patient_id},
139
- {"$set": updates}
140
- )
141
- return result.modified_count
142
- except InvalidId as e:
143
- logger().error(f"Invalid patient_id format for update: '{patient_id}'")
144
- raise ActionFailed(f"The provided patient ID '{patient_id}' is not a valid format.") from e
145
- except WriteError as e:
146
- logger().error(f"Failed to update patient '{patient_id}' due to validation or write error: {e}")
147
- raise ActionFailed(f"Patient profile could not be updated due to invalid data.") from e
148
- except (ConnectionFailure, PyMongoError) as e:
149
- logger().error(f"Database error in update_patient_profile for ID '{patient_id}': {e}")
150
- raise ActionFailed(f"A database error occurred while updating the patient profile.") from e
151
-
152
- def search_patients(
153
- query: str,
154
- limit: int = 10,
155
- *,
156
- collection_name: str = Collections.PATIENT
157
- ) -> list[Patient]:
158
- """Searches patients by name, returning a list of Pydantic Patient objects."""
159
- if not query:
160
- return []
161
-
162
- logger().info(f"Searching patients with query: '{query}', limit: {limit}")
163
- pattern = re.compile(re.escape(query), re.IGNORECASE)
164
-
165
- try:
166
- collection = get_collection(collection_name)
167
- cursor = collection.find({
168
- "name": {"$regex": pattern}
169
- }).sort("name", ASCENDING).limit(limit)
170
-
171
- patients = [Patient.model_validate(doc) for doc in cursor]
172
- logger().info(f"Found {len(patients)} patients matching query")
173
- return patients
174
- except (ConnectionFailure, PyMongoError) as e:
175
- logger().error(f"Database error during patient search for query '{query}': {e}")
176
- raise ActionFailed(f"A database error occurred during the patient search.") from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/data/repositories/session.py DELETED
@@ -1,289 +0,0 @@
1
- # src/data/repositories/session.py
2
- """
3
- Chat session management operations for MongoDB.
4
- A session is owned by an account and is related to a patient.
5
- A session contains many messages.
6
-
7
- ## Fields
8
- _id: index
9
- account_id: The user account who owns this session
10
- patient_id: The patient being discussed in this session
11
- title: The title of this session
12
- created_at: When this session was created
13
- updated_at: When this session was updated, new message or title
14
- messages: An array of messages sent in this session
15
- messages._id: index, the order the messages were sent
16
- messages.sent_by_user: Whether or not this was sent by the user or the ai
17
- messages.content: The actual contents of the message
18
- messages.timestamp: When the message was sent
19
- """
20
-
21
- from datetime import datetime, timedelta, timezone
22
- from typing import Any
23
-
24
- from bson import ObjectId
25
- from bson.errors import InvalidId
26
- from pymongo import DESCENDING
27
- from pymongo.errors import ConnectionFailure, PyMongoError, WriteError
28
-
29
- from src.data.connection import (ActionFailed, Collections, get_collection,
30
- setup_collection)
31
- from src.models.session import Message, Session
32
- from src.utils.logger import logger
33
-
34
-
35
- def init(
36
- *,
37
- collection_name: str = Collections.SESSION,
38
- validator_path: str = "schemas/session_validator.json",
39
- drop: bool = False
40
- ):
41
- """Initializes the collection, applying schema and indexes."""
42
- try:
43
- if drop:
44
- get_collection(collection_name).drop()
45
- setup_collection(collection_name, validator_path)
46
- get_collection(collection_name).create_index("messages._id")
47
- logger("Init").info(f"Created index on messages._id in '{collection_name}'")
48
- except (ConnectionFailure, PyMongoError) as e:
49
- logger().error(f"Failed to initialize collection '{collection_name}': {e}")
50
- raise ActionFailed(f"Database operation failed during initialization: {e}") from e
51
-
52
- def create_session(
53
- account_id: str,
54
- patient_id: str,
55
- title: str,
56
- *,
57
- collection_name: str = Collections.SESSION
58
- ) -> Session:
59
- """Creates a new chat session, returning a Pydantic Session object."""
60
- now = datetime.now(timezone.utc)
61
- try:
62
- collection = get_collection(collection_name)
63
- session_data: dict[str, Any] = {
64
- "account_id": ObjectId(account_id),
65
- "patient_id": ObjectId(patient_id),
66
- "title": title,
67
- "created_at": now,
68
- "updated_at": now,
69
- "messages": []
70
- }
71
- result = collection.insert_one(session_data)
72
-
73
- # Retrieve the newly created document to ensure all data is consistent
74
- created_doc = collection.find_one({"_id": result.inserted_id})
75
- if not created_doc:
76
- raise ActionFailed("Failed to retrieve session immediately after creation.")
77
-
78
- return Session.model_validate(created_doc)
79
- except InvalidId as e:
80
- logger().error(f"Invalid ObjectId format provided for session creation: {e}")
81
- raise ActionFailed("Account ID or Patient ID is not a valid format.") from e
82
- except (WriteError, ConnectionFailure, PyMongoError) as e:
83
- logger().error(f"Failed to create chat session: {e}")
84
- raise ActionFailed("A database error occurred while creating the session.") from e
85
-
86
- def get_user_sessions(
87
- account_id: str,
88
- limit: int = 20,
89
- *,
90
- collection_name: str = Collections.SESSION
91
- ) -> list[Session]:
92
- """Retrieves sessions for a user, returning a list of Pydantic Session objects."""
93
- try:
94
- obj_account_id = ObjectId(account_id)
95
- collection = get_collection(collection_name)
96
- cursor = collection.find(
97
- {"account_id": obj_account_id}
98
- ).sort("updated_at", DESCENDING).limit(limit)
99
-
100
- return [Session.model_validate(doc) for doc in cursor]
101
- except InvalidId as e:
102
- logger().error(f"Invalid account_id format for get_user_sessions: '{account_id}'")
103
- raise ActionFailed("The provided account ID is not a valid format.") from e
104
- except (ConnectionFailure, PyMongoError) as e:
105
- logger().error(f"Database error listing sessions for account '{account_id}': {e}")
106
- raise ActionFailed("A database error occurred while retrieving user sessions.") from e
107
-
108
- def list_patient_sessions(
109
- patient_id: str,
110
- limit: int = 20,
111
- *,
112
- collection_name: str = Collections.SESSION
113
- ) -> list[Session]:
114
- """Retrieves sessions for a patient, returning a list of Pydantic Session objects."""
115
- try:
116
- obj_patient_id = ObjectId(patient_id)
117
- collection = get_collection(collection_name)
118
- cursor = collection.find(
119
- {"patient_id": obj_patient_id}
120
- ).sort("updated_at", DESCENDING).limit(limit)
121
-
122
- return [Session.model_validate(doc) for doc in cursor]
123
- except InvalidId as e:
124
- logger().error(f"Invalid patient_id format for list_patient_sessions: '{patient_id}'")
125
- raise ActionFailed("The provided patient ID is not a valid format.") from e
126
- except (ConnectionFailure, PyMongoError) as e:
127
- logger().error(f"Database error listing sessions for patient '{patient_id}': {e}")
128
- raise ActionFailed("A database error occurred while retrieving patient sessions.") from e
129
-
130
- def get_session(
131
- session_id: str,
132
- *,
133
- collection_name: str = Collections.SESSION
134
- ) -> Session | None:
135
- """Retrieves a session. Returns a Pydantic Session object or None."""
136
- try:
137
- obj_session_id = ObjectId(session_id)
138
- collection = get_collection(collection_name)
139
- session_dict = collection.find_one({"_id": obj_session_id})
140
-
141
- if session_dict:
142
- return Session.model_validate(session_dict)
143
- return None
144
- except InvalidId as e:
145
- logger().error(f"Invalid session_id format for get_session: '{session_id}'")
146
- raise ActionFailed("The provided session ID is not a valid format.") from e
147
- except (ConnectionFailure, PyMongoError) as e:
148
- logger().error(f"Database error retrieving session '{session_id}': {e}")
149
- raise ActionFailed("A database error occurred while retrieving the session.") from e
150
-
151
- def get_session_messages(
152
- session_id: str,
153
- limit: int | None = None,
154
- *,
155
- collection_name: str = Collections.SESSION
156
- ) -> list[Message]:
157
- """Gets messages from a session, returning a list of Pydantic Message objects."""
158
- try:
159
- obj_session_id = ObjectId(session_id)
160
- collection = get_collection(collection_name)
161
- pipeline = [
162
- {"$match": {"_id": obj_session_id}},
163
- {"$unwind": "$messages"},
164
- {"$sort": {"messages.timestamp": -1}},
165
- {"$replaceRoot": {"newRoot": "$messages"}}
166
- ]
167
- if limit:
168
- pipeline.append({"$limit": limit})
169
-
170
- message_dicts = list(collection.aggregate(pipeline))
171
- return [Message.model_validate(doc) for doc in message_dicts]
172
- except InvalidId as e:
173
- logger().error(f"Invalid session_id format for get_session_messages: '{session_id}'")
174
- raise ActionFailed("The provided session ID is not a valid format.") from e
175
- except (ConnectionFailure, PyMongoError) as e:
176
- logger().error(f"Database error retrieving messages for session '{session_id}': {e}")
177
- raise ActionFailed("A database error occurred while retrieving messages.") from e
178
-
179
- def update_session_title(
180
- session_id: str,
181
- title: str,
182
- *,
183
- collection_name: str = Collections.SESSION
184
- ) -> bool:
185
- """Updates a session's title, raising ActionFailed on error."""
186
- try:
187
- obj_session_id = ObjectId(session_id)
188
- collection = get_collection(collection_name)
189
- result = collection.update_one(
190
- {"_id": obj_session_id},
191
- {
192
- "$set": {
193
- "title": title,
194
- "updated_at": datetime.now(timezone.utc)
195
- }
196
- }
197
- )
198
- return result.modified_count > 0
199
- except InvalidId as e:
200
- logger().error(f"Invalid session_id format for update_session_title: '{session_id}'")
201
- raise ActionFailed("The provided session ID is not a valid format.") from e
202
- except (WriteError, ConnectionFailure, PyMongoError) as e:
203
- logger().error(f"Database error updating title for session '{session_id}': {e}")
204
- raise ActionFailed("A database error occurred while updating the session title.") from e
205
-
206
- def delete_session(
207
- session_id: str,
208
- *,
209
- collection_name: str = Collections.SESSION
210
- ) -> bool:
211
- """Deletes a session, raising ActionFailed on error."""
212
- try:
213
- obj_session_id = ObjectId(session_id)
214
- collection = get_collection(collection_name)
215
- result = collection.delete_one({"_id": obj_session_id})
216
- return result.deleted_count > 0
217
- except InvalidId as e:
218
- logger().error(f"Invalid session_id format for delete_session: '{session_id}'")
219
- raise ActionFailed("The provided session ID is not a valid format.") from e
220
- except (ConnectionFailure, PyMongoError) as e:
221
- logger().error(f"Database error deleting session '{session_id}': {e}")
222
- raise ActionFailed("A database error occurred while deleting the session.") from e
223
-
224
- def prune_old_sessions(
225
- days: int = 30,
226
- *,
227
- collection_name: str = Collections.SESSION
228
- ) -> int:
229
- """Deletes old sessions, raising ActionFailed on error."""
230
- try:
231
- collection = get_collection(collection_name)
232
- cutoff = datetime.now(timezone.utc) - timedelta(days=days)
233
- result = collection.delete_many({"updated_at": {"$lt": cutoff}})
234
- if result.deleted_count > 0:
235
- logger().info(f"Deleted {result.deleted_count} old sessions (>{days} days)")
236
- return result.deleted_count
237
- except (ConnectionFailure, PyMongoError) as e:
238
- logger().error(f"Database error during session prune: {e}")
239
- raise ActionFailed("A database error occurred while pruning old sessions.") from e
240
-
241
- def add_message(
242
- session_id: str,
243
- content: str,
244
- sent_by_user: bool,
245
- *,
246
- collection_name: str = Collections.SESSION
247
- ):
248
- """Adds a message to a session, raising ActionFailed on error."""
249
- try:
250
- obj_session_id = ObjectId(session_id)
251
- collection = get_collection(collection_name)
252
-
253
- session = collection.find_one(
254
- {"_id": obj_session_id},
255
- {"messages": {"$slice": -1}}
256
- )
257
- if not session:
258
- raise ActionFailed(f"Chat session not found: {session_id}")
259
-
260
- messages = session.get("messages", [])
261
- next_id = messages[0]["_id"] + 1 if messages else 0
262
- now = datetime.now(timezone.utc)
263
- message_data: dict[str, Any] = {
264
- "_id": next_id,
265
- "sent_by_user": sent_by_user,
266
- "content": content,
267
- "timestamp": now
268
- }
269
-
270
- result = collection.update_one(
271
- {"_id": obj_session_id},
272
- {
273
- "$push": {"messages": message_data},
274
- "$set": {"updated_at": now}
275
- }
276
- )
277
-
278
- if result.modified_count == 0:
279
- raise ActionFailed(f"Failed to add message to session, no documents modified: {session_id}")
280
-
281
- except InvalidId as e:
282
- logger().error(f"Invalid session_id format for add_message: '{session_id}'")
283
- raise ActionFailed("The provided session ID is not a valid format.") from e
284
- except ActionFailed as e:
285
- logger().warning(str(e))
286
- raise
287
- except (WriteError, ConnectionFailure, PyMongoError) as e:
288
- logger().error(f"Database error adding message to session '{session_id}': {e}")
289
- raise ActionFailed("A database error occurred while adding the message.") from e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/js/app.js CHANGED
@@ -1290,6 +1290,12 @@ How can I assist you today?`;
1290
  }
1291
 
1292
  updatePatientDisplay(patientId, patientName = null) {
 
 
 
 
 
 
1293
  const status = document.getElementById('patientStatus');
1294
  const actions = document.getElementById('patientActions');
1295
  const emrLink = document.getElementById('emrLink');
 
1290
  }
1291
 
1292
  updatePatientDisplay(patientId, patientName = null) {
1293
+ // Safety check: don't update display if patientId is undefined or null
1294
+ if (!patientId || patientId === 'undefined' || patientId === 'null') {
1295
+ console.warn('updatePatientDisplay called with invalid patientId:', patientId);
1296
+ return;
1297
+ }
1298
+
1299
  const status = document.getElementById('patientStatus');
1300
  const actions = document.getElementById('patientActions');
1301
  const emrLink = document.getElementById('emrLink');
static/js/emr.js CHANGED
@@ -101,15 +101,19 @@ class EMRPage {
101
  const urlParams = new URLSearchParams(window.location.search);
102
  const patientId = urlParams.get('patient_id');
103
 
104
- if (patientId) {
 
105
  this.currentPatientId = patientId;
106
  await this.loadPatientInfo();
107
  } else {
108
  // Try to get from localStorage
109
  const savedPatientId = localStorage.getItem('medicalChatbotPatientId');
110
- if (savedPatientId) {
111
  this.currentPatientId = savedPatientId;
112
  await this.loadPatientInfo();
 
 
 
113
  }
114
  }
115
  }
 
101
  const urlParams = new URLSearchParams(window.location.search);
102
  const patientId = urlParams.get('patient_id');
103
 
104
+ // Check if patientId is valid (not undefined, null, or empty)
105
+ if (patientId && patientId !== 'undefined' && patientId !== 'null' && patientId.trim() !== '') {
106
  this.currentPatientId = patientId;
107
  await this.loadPatientInfo();
108
  } else {
109
  // Try to get from localStorage
110
  const savedPatientId = localStorage.getItem('medicalChatbotPatientId');
111
+ if (savedPatientId && savedPatientId !== 'undefined' && savedPatientId !== 'null' && savedPatientId.trim() !== '') {
112
  this.currentPatientId = savedPatientId;
113
  await this.loadPatientInfo();
114
+ } else {
115
+ console.warn('No valid patient ID found in URL or localStorage');
116
+ this.showEmptyState();
117
  }
118
  }
119
  }