Amogh1221 commited on
Commit
edf601e
·
verified ·
1 Parent(s): d3916ed

Delete services

Browse files
services/__pycache__/auth_service.cpython-311.pyc DELETED
Binary file (3.12 kB)
 
services/__pycache__/conversation_orchestrator.cpython-311.pyc DELETED
Binary file (10.3 kB)
 
services/__pycache__/conversation_orchestrator.cpython-313.pyc DELETED
Binary file (7.81 kB)
 
services/__pycache__/email_service.cpython-311.pyc DELETED
Binary file (3.13 kB)
 
services/__pycache__/patient_service.cpython-311.pyc DELETED
Binary file (8.84 kB)
 
services/__pycache__/patient_service.cpython-313.pyc DELETED
Binary file (8.25 kB)
 
services/__pycache__/risk_assessment_service.cpython-311.pyc DELETED
Binary file (1.87 kB)
 
services/__pycache__/risk_assessment_service.cpython-313.pyc DELETED
Binary file (1.68 kB)
 
services/auth_service.py DELETED
@@ -1,43 +0,0 @@
1
- """
2
- Authentication Service
3
- Handles JWT token generation, validation, and password hashing.
4
- """
5
-
6
- import os
7
- from datetime import datetime, timedelta, timezone
8
- from jose import JWTError, jwt
9
- import bcrypt
10
- from dotenv import load_dotenv
11
-
12
- load_dotenv()
13
-
14
- SECRET_KEY = os.environ.get("JWT_SECRET_KEY")
15
- if not SECRET_KEY:
16
- raise ValueError("JWT_SECRET_KEY is not set in environment")
17
-
18
- ALGORITHM = os.environ.get("JWT_ALGORITHM")
19
- ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("JWT_ACCESS_TOKEN_EXPIRE_MINUTES"))
20
-
21
- def verify_password(plain_password: str, hashed_password: str) -> bool:
22
- return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8'))
23
-
24
- def get_password_hash(password: str) -> str:
25
- salt = bcrypt.gensalt()
26
- return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
27
-
28
- def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
29
- to_encode = data.copy()
30
- if expires_delta:
31
- expire = datetime.now(timezone.utc) + expires_delta
32
- else:
33
- expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
34
- to_encode.update({"exp": expire})
35
- encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
36
- return encoded_jwt
37
-
38
- def decode_access_token(token: str) -> dict | None:
39
- try:
40
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
41
- return payload
42
- except JWTError:
43
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/conversation_orchestrator.py DELETED
@@ -1,181 +0,0 @@
1
- """
2
- Conversation Orchestrator Service
3
- Coordinates the conversation logic, manages LLM calls, and interfaces with the risk assessment service.
4
- """
5
-
6
- import time
7
- from typing import Optional
8
- from core.ports import LLMProvider, VectorStore, SessionStore, ProfileStore, LLM1Output
9
- from core.schemas import ChatResult
10
- from services.risk_assessment_service import RiskAssessmentService
11
- from core.logger import get_logger, serinity_logger
12
-
13
- class ConversationOrchestrator:
14
- """
15
- Central coordinator for the application's core logic.
16
- Implements a 'Sync 3-Pipeline' architecture.
17
- """
18
- def __init__(
19
- self,
20
- llm_provider: LLMProvider,
21
- vector_store: VectorStore,
22
- session_store: SessionStore,
23
- profile_store: ProfileStore,
24
- risk_service: RiskAssessmentService
25
- ):
26
- self.llm_provider = llm_provider
27
- self.vector_store = vector_store
28
- self.session_store = session_store
29
- self.profile_store = profile_store
30
- self.risk_service = risk_service
31
-
32
- def _format_list(self, items: list) -> str:
33
- if not items:
34
- return "None identified yet"
35
- return "\n • " + "\n • ".join(items)
36
-
37
- def handle_message(self, session_id: str, message: str, emotion: Optional[str], default_patient_id: Optional[str]) -> ChatResult:
38
- start_time = time.time()
39
-
40
- patient_id = self.session_store.get_patient_id(session_id) or default_patient_id
41
- patient_info = self.profile_store.get_patient(patient_id) if patient_id else None
42
- user_name = patient_info.get("name", "Guest Patient") if patient_info else "Guest Patient"
43
-
44
- session_number = self.session_store.get_session_count(patient_id) if patient_id else 1
45
- log = get_logger(session_id, user_name, session_number)
46
-
47
- emotion = (emotion or "").lower().strip()
48
- user_message = message
49
- if emotion and emotion not in ("neutral", "unknown", ""):
50
- user_message = f"[vocal tone: {emotion}] {user_message}"
51
-
52
- log.user_message(message, emotion=emotion or None)
53
-
54
- self.session_store.append_message(session_id, "user", user_message)
55
-
56
- history = self.session_store.get_working_context(session_id, llm_engine=self.llm_provider)
57
-
58
- try:
59
- llm1_response = self.llm_provider.psychiatrist_response(history, patient_info)
60
- log.llm1_decision(llm1_response)
61
-
62
- base_risk = self.risk_service.assess(message, llm1_response, None)
63
-
64
- intent = llm1_response.intent
65
- if base_risk and intent != "ANALYZE":
66
- intent = "ANALYZE"
67
-
68
- final_message = llm1_response.assistant_message
69
-
70
- # Sync fast-path for user queries
71
- if intent == "QUERY" and llm1_response.search_query:
72
- retrieved_context = self.vector_store.retrieve(llm1_response.search_query, k=5)
73
- log.analyze_triggered(llm1_response.search_query, "query")
74
- log.retrieved_context(retrieved_context)
75
- final_message = self.llm_provider.psychiatrist_query_response(history, retrieved_context)
76
-
77
- # Synchronous execution of LLM2 pipeline
78
- if intent == "ANALYZE" and patient_id:
79
- llm2_message = self._run_sync_analysis(session_id, patient_id, llm1_response.clinical_summary, llm1_response.assistant_message)
80
- if llm2_message:
81
- final_message = llm2_message
82
-
83
- self.session_store.append_message(session_id, "assistant", final_message)
84
- latency_ms = int((time.time() - start_time) * 1000)
85
- log.assistant_reply(final_message, risk_injected=base_risk, latency_ms=latency_ms)
86
-
87
- chat_result = ChatResult(
88
- assistant_message=final_message,
89
- intent=intent,
90
- risk_flagged=base_risk,
91
- job_id=None,
92
- clinical_summary=llm1_response.clinical_summary
93
- )
94
-
95
- return chat_result
96
- except Exception as e:
97
- log.error("handle_message_failed", e)
98
- serinity_logger.error(f"Error in handle_message: {str(e)}")
99
- fallback_message = "I'm having a little trouble connecting my thoughts right now. Could you repeat that?"
100
- self.session_store.append_message(session_id, "assistant", fallback_message)
101
- return ChatResult(
102
- assistant_message=fallback_message,
103
- intent="UNKNOWN",
104
- risk_flagged=False,
105
- job_id=None,
106
- clinical_summary=None
107
- )
108
-
109
- def _run_sync_analysis(self, session_id: str, patient_id: str, clinical_summary: Optional[str] = None, llm1_draft: Optional[str] = None) -> Optional[str]:
110
- """
111
- Executes the heavy LLM2 pattern analysis synchronously and returns the LLM2 generated response.
112
- """
113
- session_number = self.session_store.get_session_count(patient_id) if patient_id else 1
114
- log = get_logger(session_id, "Sync Analysis Task", session_number)
115
- try:
116
- history = self.session_store.get_working_context(session_id, llm_engine=self.llm_provider)
117
- if clinical_summary and clinical_summary.strip():
118
- retrieval_query = clinical_summary
119
- query_source = "clinical_summary"
120
- else:
121
- retrieval_query = "\n".join(
122
- msg["content"]
123
- for msg in history[-10:]
124
- if msg.get("role") == "user"
125
- )
126
- query_source = "user_messages_fallback"
127
-
128
- log.analyze_triggered(retrieval_query, query_source)
129
- retrieved_context = self.vector_store.retrieve(retrieval_query)
130
- log.retrieved_context(retrieved_context)
131
-
132
- existing_profile_recap = self.profile_store.build_profile_recap(patient_id) or "No previous clinical profile exists for this patient."
133
-
134
- patient_info = self.profile_store.get_patient(patient_id) if patient_id else None
135
- demographics = ""
136
- if patient_info:
137
- demographics = (
138
- f"PATIENT DEMOGRAPHICS:\n"
139
- f"- Name: {patient_info.get('name', 'Unknown')}\n"
140
- f"- Age: {patient_info.get('age', 'Unknown')}\n"
141
- f"- Gender: {patient_info.get('gender', 'Unknown')}\n"
142
- f"- Nationality: {patient_info.get('nationality', 'Unknown')}\n"
143
- f"- Primary Concern: {patient_info.get('primary_concern', 'Unknown')}\n\n"
144
- )
145
-
146
- context_prompt = (
147
- f"{demographics}"
148
- f"Existing Clinical Profile:\n{existing_profile_recap}\n\n"
149
- )
150
- if clinical_summary and clinical_summary.strip():
151
- context_prompt += f"Current Session Clinical Summary:\n{clinical_summary}\n\n"
152
-
153
- context_prompt += (
154
- f"Retrieved Clinical Reference (Sims' Symptoms in the Mind):\n{retrieved_context}\n\n"
155
- )
156
-
157
- if llm1_draft:
158
- context_prompt += (
159
- f"Intern's Draft Response:\n{llm1_draft}\n\n"
160
- )
161
-
162
- context_prompt += (
163
- "Please perform pattern analysis across all eight domains. Evaluate how the user is behaving now (based on the current session messages and summary) compared to their existing profile, and output the updated patterns. "
164
- "You must also review the Intern's Draft Response, refine and amplify it using your deeper clinical insights, and provide the final polished conversational response."
165
- )
166
-
167
- llm2_input = history[-10:] + [{
168
- "role": "user",
169
- "content": context_prompt
170
- }]
171
- llm2_response = self.llm_provider.internal_reasoning(llm2_input)
172
- log.llm2_output(llm2_response)
173
-
174
- self.profile_store.update_long_term_memory(patient_id, llm2_response)
175
- serinity_logger.info(f"Synchronous analysis completed for patient {patient_id}.")
176
- return llm2_response.assistant_message
177
-
178
- except Exception as e:
179
- log.error("sync_analysis_failed", e)
180
- serinity_logger.error(f"Synchronous analysis failed: {e}")
181
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/email_service.py DELETED
@@ -1,65 +0,0 @@
1
- import os
2
- import requests
3
- from typing import Optional
4
- from core.logger import serinity_logger as logger
5
-
6
- def send_otp_email(to_email: str, subject: str, otp_code: str, body_text: Optional[str] = None):
7
- """
8
- Sends an OTP email either via Brevo (if CLOUD_MODE=true) or to the terminal (if CLOUD_MODE=false).
9
- """
10
- cloud_mode = os.getenv("CLOUD_MODE", "false").lower() == "true"
11
-
12
- # Format the body message
13
- if not body_text:
14
- body_text = f"Your verification code is: {otp_code}"
15
- else:
16
- body_text = body_text.replace("{otp_code}", otp_code)
17
-
18
- if not cloud_mode:
19
- # Local Mode: Print to terminal
20
- print(f"\n" + "="*40)
21
- print(f"[LOCAL EMAIL SIMULATION]")
22
- print(f"To: {to_email}")
23
- print(f"Subject: {subject}")
24
- print(f"Body:\n{body_text}")
25
- print(f"="*40 + "\n")
26
- logger.info(f"Local email simulated for {to_email} (Subject: {subject})")
27
- return True
28
-
29
- # Cloud Mode: Send via Brevo
30
- brevo_api_key = os.getenv("BREVO_API_KEY")
31
- if not brevo_api_key:
32
- logger.error("BREVO_API_KEY is not set in environment variables.")
33
- return False
34
-
35
- url = "https://api.brevo.com/v3/smtp/email"
36
- headers = {
37
- "accept": "application/json",
38
- "api-key": brevo_api_key,
39
- "content-type": "application/json"
40
- }
41
-
42
- payload = {
43
- "sender": {
44
- "name": "Serinity AI",
45
- "email": os.getenv("BREVO_SENDER_EMAIL")
46
- },
47
- "to": [
48
- {
49
- "email": to_email
50
- }
51
- ],
52
- "subject": subject,
53
- "htmlContent": f"<html><body><p>{body_text}</p></body></html>"
54
- }
55
-
56
- try:
57
- response = requests.post(url, json=payload, headers=headers, timeout=10)
58
- response.raise_for_status()
59
- logger.info(f"Email sent successfully via Brevo to {to_email}")
60
- return True
61
- except requests.exceptions.RequestException as e:
62
- logger.error(f"Failed to send email via Brevo: {str(e)}")
63
- if hasattr(e, 'response') and e.response is not None:
64
- logger.error(f"Brevo API Response: {e.response.text}")
65
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/patient_service.py DELETED
@@ -1,121 +0,0 @@
1
- """
2
- Patient Service
3
- Manages high-level lifecycle of patient sessions including creation, summarization, and abandoned session sweeping.
4
- """
5
-
6
- from typing import Optional, Tuple
7
- from core.ports import ProfileStore, SessionStore, LLMProvider
8
- from core.logger import get_logger, close_logger, serinity_logger
9
-
10
- class PatientService:
11
- """
12
- Service responsible for managing the high-level lifecycle of patient sessions.
13
- Handles session initialization, profile recap generation, and session termination/summarization.
14
- """
15
- def __init__(self, profile_store: ProfileStore, session_store: SessionStore, llm_provider: LLMProvider):
16
- self.profile_store = profile_store
17
- self.session_store = session_store
18
- self.llm_provider = llm_provider
19
-
20
- def create_new_session(self, default_patient_id: Optional[str] = None) -> Tuple[str, str, str]:
21
- session_id = self.session_store.create_session(patient_id=default_patient_id)
22
- resolved_patient_id = self.session_store.get_patient_id(session_id)
23
-
24
- recap = self.profile_store.build_profile_recap(resolved_patient_id) if resolved_patient_id else None
25
- opening_context = self.llm_provider.generate_opening_context(recap)
26
- llm1_response = self.llm_provider.psychiatrist_response(opening_context)
27
-
28
- self.session_store.append_message(session_id, "assistant", llm1_response.assistant_message)
29
-
30
- patient_info = self.profile_store.get_patient(resolved_patient_id) if resolved_patient_id else None
31
- user_name = patient_info.get("name", "Guest Patient") if patient_info else "Guest Patient"
32
- session_number = self.session_store.get_session_count(resolved_patient_id) if resolved_patient_id else 1
33
- log = get_logger(session_id, user_name, session_number)
34
- log.session_start(resolved_patient_id)
35
- log.assistant_reply(llm1_response.assistant_message, risk_injected=False)
36
-
37
- return session_id, llm1_response.assistant_message, resolved_patient_id
38
-
39
- def end_session(self, session_id: str, default_patient_id: Optional[str] = None) -> None:
40
- """
41
- Synchronously ends the session.
42
- Immediate teardown logic goes here.
43
- """
44
- self.session_store.end_session(session_id)
45
-
46
- def get_active_session(self, patient_id: str) -> Optional[str]:
47
- """Check if the patient currently has an active session."""
48
- return self.session_store.get_active_session(patient_id)
49
-
50
- def get_session_messages(self, session_id: str) -> list[dict]:
51
- """Retrieve all raw messages for a given session."""
52
- return self.session_store.get_all_messages(session_id)
53
- def reset_patient_data(self, patient_id: str) -> None:
54
- """
55
- Resets the patient's data, including sessions, messages, and profile.
56
- """
57
- self.profile_store.reset_patient_data(patient_id)
58
-
59
- def delete_patient(self, patient_id: str) -> None:
60
- """
61
- Deletes a patient and all their associated data completely.
62
- """
63
- self.profile_store.delete_patient(patient_id)
64
-
65
- def generate_session_summary(self, session_id: str, default_patient_id: Optional[str] = None) -> None:
66
- """
67
- Asynchronously generates and saves a clinical summary of the session and updates the profile via LLM3.
68
- """
69
- patient_id = self.session_store.get_patient_id(session_id) or default_patient_id
70
-
71
- history = self.session_store.get_working_context(session_id)
72
-
73
- # Check if the user actually sent any messages
74
- has_user_messages = any(msg.get("role") == "user" for msg in history)
75
-
76
- summary = "No conversation occurred."
77
- llm3_output = None
78
- if has_user_messages and patient_id:
79
- try:
80
- patient_info = self.profile_store.get_patient(patient_id)
81
- old_profile = self.profile_store.get_patient_profile(patient_id)
82
- llm3_output = self.llm_provider.generate_end_of_session_profile(old_profile, history, patient_info)
83
-
84
- if not llm3_output.update_profile:
85
- summary = "Not enough conversation to generate a meaningful summary."
86
- else:
87
- summary = llm3_output.session_summary
88
- self.profile_store.update_patient_profile(patient_id, llm3_output)
89
- self.profile_store.update_long_term_memory(patient_id, llm3_output)
90
-
91
- if llm3_output.updated_primary_concern:
92
- self.profile_store.update_primary_concern(patient_id, llm3_output.updated_primary_concern)
93
-
94
- except Exception as e:
95
- summary = f"Summary generation failed: {e}"
96
-
97
- self.session_store.save_session_summary(session_id, summary)
98
-
99
- patient_info = self.profile_store.get_patient(patient_id) if patient_id else None
100
- user_name = patient_info.get("name", "Guest Patient") if patient_info else "Guest Patient"
101
-
102
- session_number = self.session_store.get_session_count(patient_id) if patient_id else 1
103
- log = get_logger(session_id, user_name, session_number)
104
- if llm3_output:
105
- log.llm3_output(llm3_output)
106
- else:
107
- log._log_event("session_summary", {"summary": summary})
108
- close_logger(session_id)
109
-
110
- def sweep_abandoned_sessions(self, timeout_minutes: int = 30) -> None:
111
- """
112
- Finds sessions inactive for `timeout_minutes` and generates summaries for them, then closes them.
113
- """
114
- abandoned_ids = self.session_store.get_abandoned_sessions(timeout_minutes)
115
- for session_id in abandoned_ids:
116
- try:
117
- serinity_logger.info(f"Sweeping abandoned session {session_id}...")
118
- self.generate_session_summary(session_id)
119
- self.end_session(session_id)
120
- except Exception as e:
121
- serinity_logger.error(f"Failed to sweep session {session_id}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/risk_assessment_service.py DELETED
@@ -1,17 +0,0 @@
1
- from typing import List, Optional
2
- from core.ports import RiskSignal, LLM1Output, LLM2Output
3
-
4
- class RiskAssessmentService:
5
- """
6
- Evaluates patient messages and LLM responses against a suite of RiskSignals.
7
- Ensures patient safety by identifying crisis or self-harm intents in real-time.
8
- """
9
- def __init__(self, signals: List[RiskSignal]):
10
- self._signals = signals
11
-
12
- def assess(self, message: str, llm1_output: LLM1Output, llm2_output: Optional[LLM2Output] = None) -> bool:
13
- """Evaluate all risk signals and return True if any indicate a risk."""
14
- return any(
15
- signal.check(message, llm1_output, llm2_output)
16
- for signal in self._signals
17
- )