Spaces:
Runtime error
Runtime error
dylanglenister
commited on
Commit
·
56f4fd7
1
Parent(s):
976746f
Initial work on db validation
Browse files- schemas/account_validator.json +42 -0
- schemas/message_validator.json +0 -0
- schemas/patient_validator.json +59 -0
- schemas/session_validator.json +35 -0
- src/data/connection.py +24 -4
- src/data/repositories/account.py +17 -6
- src/data/repositories/medical.py +2 -0
- src/data/repositories/message.py +18 -3
- src/data/repositories/patient.py +92 -71
- src/data/repositories/session.py +16 -4
- src/main.py +28 -10
- tests/mongo_test.py +2 -0
schemas/account_validator.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$jsonSchema": {
|
| 3 |
+
"bsonType": "object",
|
| 4 |
+
"title": "Account validator",
|
| 5 |
+
"required": [
|
| 6 |
+
"name",
|
| 7 |
+
"role",
|
| 8 |
+
"created_at",
|
| 9 |
+
"updated_at"
|
| 10 |
+
],
|
| 11 |
+
"properties": {
|
| 12 |
+
"name": {
|
| 13 |
+
"bsonType": "string",
|
| 14 |
+
"description": "'name' must be a string is required."
|
| 15 |
+
},
|
| 16 |
+
"role": {
|
| 17 |
+
"enum": [
|
| 18 |
+
"Doctor",
|
| 19 |
+
"Healthcare Prof",
|
| 20 |
+
"Nurse",
|
| 21 |
+
"Caregiver",
|
| 22 |
+
"Physicion",
|
| 23 |
+
"Medical Student",
|
| 24 |
+
"Other"
|
| 25 |
+
],
|
| 26 |
+
"description": "'role' must be one of: [Doctor, Healthcare Prof, Nurse, Caregiver, Physicion, Medical Student, Other] and is required."
|
| 27 |
+
},
|
| 28 |
+
"specialty": {
|
| 29 |
+
"bsonType": "string",
|
| 30 |
+
"description": "'speciality' must be a string and is optional."
|
| 31 |
+
},
|
| 32 |
+
"created_at": {
|
| 33 |
+
"bsonType": "date",
|
| 34 |
+
"description": "'created_at' must be a date and is required."
|
| 35 |
+
},
|
| 36 |
+
"updated_at": {
|
| 37 |
+
"bsonType": "date",
|
| 38 |
+
"description": "'updated_at' must be a date and is required."
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
}
|
schemas/message_validator.json
ADDED
|
File without changes
|
schemas/patient_validator.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$jsonSchema": {
|
| 3 |
+
"bsonType": "object",
|
| 4 |
+
"title": "Patient validator",
|
| 5 |
+
"required": [
|
| 6 |
+
"name",
|
| 7 |
+
"age",
|
| 8 |
+
"sex",
|
| 9 |
+
"assigned_doctor_id",
|
| 10 |
+
"created_at",
|
| 11 |
+
"updated_at"
|
| 12 |
+
],
|
| 13 |
+
"properties": {
|
| 14 |
+
"assigned_doctor_id": {
|
| 15 |
+
"bsonType": ""
|
| 16 |
+
},
|
| 17 |
+
"name": {
|
| 18 |
+
"bsonType": "string",
|
| 19 |
+
"description": "'name' must be a string is required."
|
| 20 |
+
},
|
| 21 |
+
"age": {
|
| 22 |
+
"bsonType": "uint",
|
| 23 |
+
"description": "'description' must be an unsigned int and is required."
|
| 24 |
+
},
|
| 25 |
+
"sex": {
|
| 26 |
+
"bsonType": "string",
|
| 27 |
+
"description": "sex must be string and is required."
|
| 28 |
+
},
|
| 29 |
+
"address": {
|
| 30 |
+
"bsonType": "string",
|
| 31 |
+
"description": "'address' must be string and is optional."
|
| 32 |
+
},
|
| 33 |
+
"phone": {
|
| 34 |
+
"bsonType": "string",
|
| 35 |
+
"description": "'phone' must be a string and is optional."
|
| 36 |
+
},
|
| 37 |
+
"email": {
|
| 38 |
+
"bsonType": "string",
|
| 39 |
+
"description": "'email' must be a string and is optional."
|
| 40 |
+
},
|
| 41 |
+
"medications": {
|
| 42 |
+
"bsonType": "string",
|
| 43 |
+
"description": "'medication' must be a and is optional."
|
| 44 |
+
},
|
| 45 |
+
"past_assessment_summary": {
|
| 46 |
+
"bsonType": "string",
|
| 47 |
+
"description": "'past_assessment_summary' must be a string and is optional."
|
| 48 |
+
},
|
| 49 |
+
"created_at": {
|
| 50 |
+
"bsonType": "date",
|
| 51 |
+
"description": "'created_at' must be a date and is required."
|
| 52 |
+
},
|
| 53 |
+
"updated_at": {
|
| 54 |
+
"bsonType": "date",
|
| 55 |
+
"description": "'updated_at' must be a date and is required."
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
}
|
schemas/session_validator.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$jsonSchema": {
|
| 3 |
+
"bsonType": "object",
|
| 4 |
+
"title": "Session validator",
|
| 5 |
+
"required": [
|
| 6 |
+
"user_id",
|
| 7 |
+
"title",
|
| 8 |
+
"created_at",
|
| 9 |
+
"updated_at"
|
| 10 |
+
],
|
| 11 |
+
"properties": {
|
| 12 |
+
"user_id": {
|
| 13 |
+
"bsonType": "string",
|
| 14 |
+
"description": "'user_id' must be a string is required."
|
| 15 |
+
},
|
| 16 |
+
"title": {
|
| 17 |
+
"bsonType": "string",
|
| 18 |
+
"description": "'title' must be a string and is optional."
|
| 19 |
+
},
|
| 20 |
+
"created_at": {
|
| 21 |
+
"bsonType": "date",
|
| 22 |
+
"description": "'created_at' must be a date and is required."
|
| 23 |
+
},
|
| 24 |
+
"updated_at": {
|
| 25 |
+
"bsonType": "date",
|
| 26 |
+
"description": "'updated_at' must be a date and is required."
|
| 27 |
+
},
|
| 28 |
+
"messages": {
|
| 29 |
+
"bsonType": "array",
|
| 30 |
+
"items": {"bsonType": "string"},
|
| 31 |
+
"description": "'messages' must be a string and is optional."
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
src/data/connection.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
# data/repositories/base.py
|
| 2 |
|
|
|
|
| 3 |
import os
|
| 4 |
|
| 5 |
from pymongo import MongoClient
|
|
@@ -14,7 +15,9 @@ from src.utils.logger import logger
|
|
| 14 |
|
| 15 |
class ActionFailed(Exception):
|
| 16 |
"""Raised when a database action fails."""
|
| 17 |
-
|
|
|
|
|
|
|
| 18 |
|
| 19 |
_mongo_client: MongoClient | None = None
|
| 20 |
|
|
@@ -41,6 +44,23 @@ def close_connection():
|
|
| 41 |
_mongo_client = None
|
| 42 |
|
| 43 |
def get_collection(name: str) -> Collection:
|
| 44 |
-
"""Retrieves a MongoDB collection by name."""
|
| 45 |
-
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# data/repositories/base.py
|
| 2 |
|
| 3 |
+
import json
|
| 4 |
import os
|
| 5 |
|
| 6 |
from pymongo import MongoClient
|
|
|
|
| 15 |
|
| 16 |
class ActionFailed(Exception):
|
| 17 |
"""Raised when a database action fails."""
|
| 18 |
+
|
| 19 |
+
class EntryNotFound(Exception):
|
| 20 |
+
"""Raised when an entry cannot be found in the database."""
|
| 21 |
|
| 22 |
_mongo_client: MongoClient | None = None
|
| 23 |
|
|
|
|
| 44 |
_mongo_client = None
|
| 45 |
|
| 46 |
def get_collection(name: str) -> Collection:
|
| 47 |
+
"""Retrieves a MongoDB collection by name. Create it if it does not exist."""
|
| 48 |
+
return get_database().get_collection(name)
|
| 49 |
+
|
| 50 |
+
def does_collection_exist(name: str) -> bool:
|
| 51 |
+
return True if name in get_database().list_collection_names() else False
|
| 52 |
+
|
| 53 |
+
def create_collection(collection_name: str, validator_path: str):
|
| 54 |
+
#get_collection(collection_name).drop()
|
| 55 |
+
if does_collection_exist(collection_name):
|
| 56 |
+
raise ActionFailed("Collection already exists")
|
| 57 |
+
|
| 58 |
+
with open(validator_path, "r", encoding="utf-8") as f:
|
| 59 |
+
validator = json.load(f)
|
| 60 |
+
get_database().create_collection(
|
| 61 |
+
collection_name,
|
| 62 |
+
validator=validator,
|
| 63 |
+
validationLevel="moderate"
|
| 64 |
+
)
|
| 65 |
+
#logger().info(validator)
|
| 66 |
+
logger().info(collection_name + " database initialised")
|
src/data/repositories/account.py
CHANGED
|
@@ -1,6 +1,16 @@
|
|
| 1 |
# data/repositories/account.py
|
| 2 |
"""
|
| 3 |
User account management operations for MongoDB.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import re
|
|
@@ -12,14 +22,15 @@ from pymongo import ASCENDING
|
|
| 12 |
from pymongo.errors import (ConnectionFailure, DuplicateKeyError,
|
| 13 |
OperationFailure, PyMongoError)
|
| 14 |
|
| 15 |
-
from src.data.connection import ActionFailed,
|
|
|
|
| 16 |
from src.utils.logger import logger
|
| 17 |
|
| 18 |
ACCOUNTS_COLLECTION = "accounts"
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
|
| 24 |
def get_account_frame(
|
| 25 |
*,
|
|
@@ -120,13 +131,13 @@ def set_user_preferences(
|
|
| 120 |
}
|
| 121 |
)
|
| 122 |
if result.matched_count == 0:
|
| 123 |
-
raise
|
| 124 |
|
| 125 |
return result.modified_count > 0
|
| 126 |
except PyMongoError as e:
|
| 127 |
logger().error(f"An error occurred with the database operation: {e}")
|
| 128 |
return False
|
| 129 |
-
except
|
| 130 |
logger().error(e)
|
| 131 |
return False
|
| 132 |
|
|
|
|
| 1 |
# 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:
|
| 10 |
+
specialty:
|
| 11 |
+
medical_roles:
|
| 12 |
+
created_at: the timestamp when the account was created
|
| 13 |
+
updated_at: the timestamp when the account data was last modified
|
| 14 |
"""
|
| 15 |
|
| 16 |
import re
|
|
|
|
| 22 |
from pymongo.errors import (ConnectionFailure, DuplicateKeyError,
|
| 23 |
OperationFailure, PyMongoError)
|
| 24 |
|
| 25 |
+
from src.data.connection import (ActionFailed, EntryNotFound,
|
| 26 |
+
create_collection, get_collection)
|
| 27 |
from src.utils.logger import logger
|
| 28 |
|
| 29 |
ACCOUNTS_COLLECTION = "accounts"
|
| 30 |
|
| 31 |
+
def create():
|
| 32 |
+
#get_collection(ACCOUNTS_COLLECTION).drop()
|
| 33 |
+
create_collection(ACCOUNTS_COLLECTION, "schemas/account_validator.json")
|
| 34 |
|
| 35 |
def get_account_frame(
|
| 36 |
*,
|
|
|
|
| 131 |
}
|
| 132 |
)
|
| 133 |
if result.matched_count == 0:
|
| 134 |
+
raise EntryNotFound(f"User with ID '{user_id}' not found.")
|
| 135 |
|
| 136 |
return result.modified_count > 0
|
| 137 |
except PyMongoError as e:
|
| 138 |
logger().error(f"An error occurred with the database operation: {e}")
|
| 139 |
return False
|
| 140 |
+
except EntryNotFound as e:
|
| 141 |
logger().error(e)
|
| 142 |
return False
|
| 143 |
|
src/data/repositories/medical.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
# data/repositories/medical.py
|
| 2 |
"""
|
| 3 |
Medical records and memory management operations for MongoDB.
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
from datetime import datetime, timezone
|
|
|
|
| 1 |
# data/repositories/medical.py
|
| 2 |
"""
|
| 3 |
Medical records and memory management operations for MongoDB.
|
| 4 |
+
|
| 5 |
+
@Note Could this be split into two? One for records and one for memory.
|
| 6 |
"""
|
| 7 |
|
| 8 |
from datetime import datetime, timezone
|
src/data/repositories/message.py
CHANGED
|
@@ -1,8 +1,17 @@
|
|
| 1 |
# data/message/operations.py
|
| 2 |
"""
|
| 3 |
Message management operations for MongoDB.
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
from datetime import datetime, timezone
|
|
@@ -10,13 +19,19 @@ from typing import Any
|
|
| 10 |
|
| 11 |
from bson import ObjectId
|
| 12 |
from pymongo import ASCENDING
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
from src.data.connection import get_collection
|
| 15 |
from src.utils.logger import logger
|
| 16 |
|
| 17 |
CHAT_SESSIONS_COLLECTION = "chat_sessions"
|
| 18 |
CHAT_MESSAGES_COLLECTION = "chat_messages"
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
def add_message(
|
| 21 |
session_id: str,
|
| 22 |
message_data: dict[str, Any],
|
|
|
|
| 1 |
# data/message/operations.py
|
| 2 |
"""
|
| 3 |
Message management operations for MongoDB.
|
| 4 |
+
A message is owned by a session, and is sent by either the user or the ai.
|
| 5 |
+
|
| 6 |
+
## Fields
|
| 7 |
+
_id: index
|
| 8 |
+
session_id:
|
| 9 |
+
patient_id:
|
| 10 |
+
doctor_id:
|
| 11 |
+
role:
|
| 12 |
+
content:
|
| 13 |
+
timestamp:
|
| 14 |
+
created_at:
|
| 15 |
"""
|
| 16 |
|
| 17 |
from datetime import datetime, timezone
|
|
|
|
| 19 |
|
| 20 |
from bson import ObjectId
|
| 21 |
from pymongo import ASCENDING
|
| 22 |
+
from pymongo.errors import (ConnectionFailure, DuplicateKeyError,
|
| 23 |
+
OperationFailure, PyMongoError)
|
| 24 |
|
| 25 |
+
from src.data.connection import ActionFailed, create_collection, get_collection
|
| 26 |
from src.utils.logger import logger
|
| 27 |
|
| 28 |
CHAT_SESSIONS_COLLECTION = "chat_sessions"
|
| 29 |
CHAT_MESSAGES_COLLECTION = "chat_messages"
|
| 30 |
|
| 31 |
+
def create():
|
| 32 |
+
#get_collection(CHAT_MESSAGES_COLLECTION).drop()
|
| 33 |
+
create_collection(CHAT_MESSAGES_COLLECTION, "schemas/message_validator.json")
|
| 34 |
+
|
| 35 |
def add_message(
|
| 36 |
session_id: str,
|
| 37 |
message_data: dict[str, Any],
|
src/data/repositories/patient.py
CHANGED
|
@@ -1,6 +1,21 @@
|
|
| 1 |
# data/patient/operations.py
|
| 2 |
"""
|
| 3 |
Patient management operations for MongoDB.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import re
|
|
@@ -8,93 +23,99 @@ from datetime import datetime, timezone
|
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
from pymongo import ASCENDING
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
from src.data.connection import get_collection
|
| 13 |
from src.utils.logger import logger
|
| 14 |
|
| 15 |
PATIENTS_COLLECTION = "patients"
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
def _generate_patient_id() -> str:
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
|
| 22 |
|
| 23 |
def get_patient_by_id(patient_id: str) -> dict[str, Any] | None:
|
| 24 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
|
| 28 |
def create_patient(
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
) -> dict[str, Any]:
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
|
| 66 |
|
| 67 |
def update_patient_profile(patient_id: str, updates: dict[str, Any]) -> int:
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
|
| 73 |
|
| 74 |
def search_patients(query: str, limit: int = 10) -> list[dict[str, Any]]:
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
| 1 |
# data/patient/operations.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:
|
| 9 |
+
age:
|
| 10 |
+
sex:
|
| 11 |
+
address:
|
| 12 |
+
phone:
|
| 13 |
+
email:
|
| 14 |
+
medications:
|
| 15 |
+
past_assessment_summary:
|
| 16 |
+
assigned_doctor_id:
|
| 17 |
+
created_at:
|
| 18 |
+
updated_at:
|
| 19 |
"""
|
| 20 |
|
| 21 |
import re
|
|
|
|
| 23 |
from typing import Any
|
| 24 |
|
| 25 |
from pymongo import ASCENDING
|
| 26 |
+
from pymongo.errors import (ConnectionFailure, DuplicateKeyError,
|
| 27 |
+
OperationFailure, PyMongoError)
|
| 28 |
|
| 29 |
+
from src.data.connection import ActionFailed, create_collection, get_collection
|
| 30 |
from src.utils.logger import logger
|
| 31 |
|
| 32 |
PATIENTS_COLLECTION = "patients"
|
| 33 |
|
| 34 |
+
def create():
|
| 35 |
+
#get_collection(PATIENTS_COLLECTION).drop()
|
| 36 |
+
create_collection(PATIENTS_COLLECTION, "schemas/patient_validator.json")
|
| 37 |
+
|
| 38 |
def _generate_patient_id() -> str:
|
| 39 |
+
"""Generate zero-padded 8-digit ID"""
|
| 40 |
+
import random
|
| 41 |
+
return f"{random.randint(0, 99999999):08d}"
|
| 42 |
|
| 43 |
|
| 44 |
def get_patient_by_id(patient_id: str) -> dict[str, Any] | None:
|
| 45 |
+
collection = get_collection(PATIENTS_COLLECTION)
|
| 46 |
+
return collection.find_one({"patient_id": patient_id})
|
| 47 |
|
| 48 |
|
| 49 |
def create_patient(
|
| 50 |
+
*,
|
| 51 |
+
name: str,
|
| 52 |
+
age: int,
|
| 53 |
+
sex: str,
|
| 54 |
+
address: str | None = None,
|
| 55 |
+
phone: str | None = None,
|
| 56 |
+
email: str | None = None,
|
| 57 |
+
medications: list[str] | None = None,
|
| 58 |
+
past_assessment_summary: str | None = None,
|
| 59 |
+
assigned_doctor_id: str | None = None
|
| 60 |
) -> dict[str, Any]:
|
| 61 |
+
collection = get_collection(PATIENTS_COLLECTION)
|
| 62 |
+
now = datetime.now(timezone.utc)
|
| 63 |
+
# Ensure unique 8-digit id
|
| 64 |
+
for _ in range(10):
|
| 65 |
+
pid = _generate_patient_id()
|
| 66 |
+
if not collection.find_one({"patient_id": pid}):
|
| 67 |
+
break
|
| 68 |
+
else:
|
| 69 |
+
raise RuntimeError("Failed to generate unique patient ID")
|
| 70 |
+
doc = {
|
| 71 |
+
"patient_id": pid,
|
| 72 |
+
"name": name,
|
| 73 |
+
"age": age,
|
| 74 |
+
"sex": sex,
|
| 75 |
+
"address": address,
|
| 76 |
+
"phone": phone,
|
| 77 |
+
"email": email,
|
| 78 |
+
"medications": medications or [],
|
| 79 |
+
"past_assessment_summary": past_assessment_summary or "",
|
| 80 |
+
"assigned_doctor_id": assigned_doctor_id,
|
| 81 |
+
"created_at": now,
|
| 82 |
+
"updated_at": now
|
| 83 |
+
}
|
| 84 |
+
collection.insert_one(doc)
|
| 85 |
+
return doc
|
| 86 |
|
| 87 |
|
| 88 |
def update_patient_profile(patient_id: str, updates: dict[str, Any]) -> int:
|
| 89 |
+
collection = get_collection(PATIENTS_COLLECTION)
|
| 90 |
+
updates["updated_at"] = datetime.now(timezone.utc)
|
| 91 |
+
result = collection.update_one({"patient_id": patient_id}, {"$set": updates})
|
| 92 |
+
return result.modified_count
|
| 93 |
|
| 94 |
|
| 95 |
def search_patients(query: str, limit: int = 10) -> list[dict[str, Any]]:
|
| 96 |
+
"""Search patients by name (case-insensitive starts-with/contains) or partial patient_id."""
|
| 97 |
+
collection = get_collection(PATIENTS_COLLECTION)
|
| 98 |
+
if not query:
|
| 99 |
+
return []
|
| 100 |
+
|
| 101 |
+
logger().info(f"Searching patients with query: '{query}', limit: {limit}")
|
| 102 |
+
|
| 103 |
+
# Build a regex for name search and patient_id partial match
|
| 104 |
+
pattern = re.compile(re.escape(query), re.IGNORECASE)
|
| 105 |
+
|
| 106 |
+
try:
|
| 107 |
+
cursor = collection.find({
|
| 108 |
+
"$or": [
|
| 109 |
+
{"name": {"$regex": pattern}},
|
| 110 |
+
{"patient_id": {"$regex": pattern}}
|
| 111 |
+
]
|
| 112 |
+
}).sort("name", ASCENDING).limit(limit)
|
| 113 |
+
results = []
|
| 114 |
+
for p in cursor:
|
| 115 |
+
p["_id"] = str(p.get("_id")) if p.get("_id") else None
|
| 116 |
+
results.append(p)
|
| 117 |
+
logger().info(f"Found {len(results)} patients matching query")
|
| 118 |
+
return results
|
| 119 |
+
except Exception as e:
|
| 120 |
+
logger().error(f"Error in search_patients: {e}")
|
| 121 |
+
return []
|
src/data/repositories/session.py
CHANGED
|
@@ -1,8 +1,16 @@
|
|
| 1 |
-
# data/repositories/
|
| 2 |
"""
|
| 3 |
Chat session management operations for MongoDB.
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import uuid
|
|
@@ -13,12 +21,16 @@ from pymongo import DESCENDING
|
|
| 13 |
from pymongo.errors import (ConnectionFailure, DuplicateKeyError,
|
| 14 |
OperationFailure, PyMongoError)
|
| 15 |
|
| 16 |
-
from src.data.connection import ActionFailed, get_collection
|
| 17 |
from src.utils.logger import logger
|
| 18 |
|
| 19 |
CHAT_SESSIONS_COLLECTION = "chat_sessions"
|
| 20 |
CHAT_MESSAGES_COLLECTION = "chat_messages"
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
def create_session(
|
| 23 |
user_id: str,
|
| 24 |
title: str,
|
|
|
|
| 1 |
+
# 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 |
+
user_id:
|
| 10 |
+
title:
|
| 11 |
+
created_at:
|
| 12 |
+
updated_at:
|
| 13 |
+
messages:
|
| 14 |
"""
|
| 15 |
|
| 16 |
import uuid
|
|
|
|
| 21 |
from pymongo.errors import (ConnectionFailure, DuplicateKeyError,
|
| 22 |
OperationFailure, PyMongoError)
|
| 23 |
|
| 24 |
+
from src.data.connection import ActionFailed, create_collection, get_collection
|
| 25 |
from src.utils.logger import logger
|
| 26 |
|
| 27 |
CHAT_SESSIONS_COLLECTION = "chat_sessions"
|
| 28 |
CHAT_MESSAGES_COLLECTION = "chat_messages"
|
| 29 |
|
| 30 |
+
def create():
|
| 31 |
+
#get_collection(CHAT_SESSIONS_COLLECTION).drop()
|
| 32 |
+
create_collection(CHAT_SESSIONS_COLLECTION, "schemas/session_validator.json")
|
| 33 |
+
|
| 34 |
def create_session(
|
| 35 |
user_id: str,
|
| 36 |
title: str,
|
src/main.py
CHANGED
|
@@ -24,9 +24,20 @@ except Exception as e:
|
|
| 24 |
logger(tag="env").warning(f"Error loading .env file: {e}")
|
| 25 |
|
| 26 |
# Import project modules after trying to load environment variables
|
| 27 |
-
from src.api.routes import
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
from src.core.state import MedicalState, get_state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def startup_event(state: MedicalState):
|
|
@@ -68,6 +79,13 @@ def startup_event(state: MedicalState):
|
|
| 68 |
|
| 69 |
logger(tag="startup").info("Medical AI Assistant startup complete")
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
def shutdown_event():
|
| 72 |
"""Cleanup on shutdown"""
|
| 73 |
logger(tag="shutdown").info("Shutting down Medical AI Assistant...")
|
|
@@ -105,14 +123,14 @@ app.add_middleware(
|
|
| 105 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 106 |
|
| 107 |
# Include routers
|
| 108 |
-
app.include_router(
|
| 109 |
-
app.include_router(
|
| 110 |
-
app.include_router(
|
| 111 |
-
app.include_router(
|
| 112 |
-
app.include_router(
|
| 113 |
-
app.include_router(
|
| 114 |
-
app.include_router(
|
| 115 |
-
app.include_router(
|
| 116 |
|
| 117 |
@app.get("/api/info")
|
| 118 |
async def get_api_info():
|
|
|
|
| 24 |
logger(tag="env").warning(f"Error loading .env file: {e}")
|
| 25 |
|
| 26 |
# Import project modules after trying to load environment variables
|
| 27 |
+
from src.api.routes import audio as audio_route
|
| 28 |
+
from src.api.routes import chat as chat_route
|
| 29 |
+
from src.api.routes import doctors as doctors_route
|
| 30 |
+
from src.api.routes import patients as patients_route
|
| 31 |
+
from src.api.routes import session as session_route
|
| 32 |
+
from src.api.routes import static as static_route
|
| 33 |
+
from src.api.routes import system as system_route
|
| 34 |
+
from src.api.routes import user as user_route
|
| 35 |
from src.core.state import MedicalState, get_state
|
| 36 |
+
from src.data.repositories import account as account_repo
|
| 37 |
+
from src.data.repositories import medical as medical_repo
|
| 38 |
+
from src.data.repositories import message as message_repo
|
| 39 |
+
from src.data.repositories import patient as patient_repo
|
| 40 |
+
from src.data.repositories import session as session_repo
|
| 41 |
|
| 42 |
|
| 43 |
def startup_event(state: MedicalState):
|
|
|
|
| 79 |
|
| 80 |
logger(tag="startup").info("Medical AI Assistant startup complete")
|
| 81 |
|
| 82 |
+
# TODO On first startup, create all repositories if they don't exist
|
| 83 |
+
#account_repo.create()
|
| 84 |
+
#patient_repo.create()
|
| 85 |
+
#session_repo.create()
|
| 86 |
+
#message_repo.create()
|
| 87 |
+
#medical_repo.create()
|
| 88 |
+
|
| 89 |
def shutdown_event():
|
| 90 |
"""Cleanup on shutdown"""
|
| 91 |
logger(tag="shutdown").info("Shutting down Medical AI Assistant...")
|
|
|
|
| 123 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 124 |
|
| 125 |
# Include routers
|
| 126 |
+
app.include_router(chat_route.router)
|
| 127 |
+
app.include_router(user_route.router)
|
| 128 |
+
app.include_router(session_route.router)
|
| 129 |
+
app.include_router(patients_route.router)
|
| 130 |
+
app.include_router(doctors_route.router)
|
| 131 |
+
app.include_router(system_route.router)
|
| 132 |
+
app.include_router(static_route.router)
|
| 133 |
+
app.include_router(audio_route.router)
|
| 134 |
|
| 135 |
@app.get("/api/info")
|
| 136 |
async def get_api_info():
|
tests/mongo_test.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
| 1 |
"""MongoDB Integration Tests
|
| 2 |
|
| 3 |
To run this file, execute `python -m tests.mongo_test` from the project root directory.
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import unittest
|
|
|
|
| 1 |
"""MongoDB Integration Tests
|
| 2 |
|
| 3 |
To run this file, execute `python -m tests.mongo_test` from the project root directory.
|
| 4 |
+
|
| 5 |
+
@TODO This file is outdated and will need to be updated after the validators have been implemented.
|
| 6 |
"""
|
| 7 |
|
| 8 |
import unittest
|