Spaces:
Sleeping
Sleeping
Update app/services/rag_service.py
Browse files- app/services/rag_service.py +71 -5
app/services/rag_service.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
from app.config import settings
|
| 2 |
from sqlalchemy.orm import Session
|
| 3 |
from app.services.search_service import SearchService
|
|
|
|
| 4 |
|
| 5 |
try:
|
| 6 |
from groq import Groq
|
|
@@ -25,17 +26,22 @@ class RAGService:
|
|
| 25 |
|
| 26 |
@staticmethod
|
| 27 |
def format_search_results(search_results: list) -> str:
|
| 28 |
-
"""Format search results as context"""
|
| 29 |
if not search_results:
|
| 30 |
return "No previous pathology records retrieved for this query.\n\n"
|
| 31 |
context = "RELEVANT MEDICAL DOCUMENTS FROM EMR DATABASE:\n\n"
|
| 32 |
for i, result in enumerate(search_results, 1):
|
| 33 |
context += f"Document #{i} (ID: {result.get('document_id', 'Unknown')})\n"
|
| 34 |
context += f" Patient Name: {result.get('patient_name', 'Unknown')}\n"
|
|
|
|
| 35 |
context += f" Test Type: {result.get('test_type', 'Unknown')}\n"
|
| 36 |
-
context += f" Diagnosis: {result.get('diagnosis', 'Unknown')}\n"
|
| 37 |
context += f" Report Summary: {result.get('summary', 'Unknown')}\n"
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
return context
|
| 40 |
|
| 41 |
def answer_question(
|
|
@@ -51,15 +57,75 @@ class RAGService:
|
|
| 51 |
"source_documents": [],
|
| 52 |
}
|
| 53 |
|
| 54 |
-
# 1. Retrieve
|
| 55 |
results_list = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
try:
|
| 57 |
search_result = SearchService.semantic_search(db, question, top_k=3)
|
| 58 |
if search_result.get("status") == "success":
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
except Exception as search_err:
|
| 61 |
print(f"[WARN] Vector search lookup skipped: {search_err}")
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
# 2. Format context from search results
|
| 64 |
context = self.format_search_results(results_list)
|
| 65 |
|
|
|
|
| 1 |
from app.config import settings
|
| 2 |
from sqlalchemy.orm import Session
|
| 3 |
from app.services.search_service import SearchService
|
| 4 |
+
from app.models.database_models import PathologyReport
|
| 5 |
|
| 6 |
try:
|
| 7 |
from groq import Groq
|
|
|
|
| 26 |
|
| 27 |
@staticmethod
|
| 28 |
def format_search_results(search_results: list) -> str:
|
| 29 |
+
"""Format search results as detailed medical context"""
|
| 30 |
if not search_results:
|
| 31 |
return "No previous pathology records retrieved for this query.\n\n"
|
| 32 |
context = "RELEVANT MEDICAL DOCUMENTS FROM EMR DATABASE:\n\n"
|
| 33 |
for i, result in enumerate(search_results, 1):
|
| 34 |
context += f"Document #{i} (ID: {result.get('document_id', 'Unknown')})\n"
|
| 35 |
context += f" Patient Name: {result.get('patient_name', 'Unknown')}\n"
|
| 36 |
+
context += f" Patient ID / MRN: {result.get('patient_id', 'Unknown')}\n"
|
| 37 |
context += f" Test Type: {result.get('test_type', 'Unknown')}\n"
|
| 38 |
+
context += f" Diagnosis / Assessment: {result.get('diagnosis', 'Unknown')}\n"
|
| 39 |
context += f" Report Summary: {result.get('summary', 'Unknown')}\n"
|
| 40 |
+
if result.get('findings'):
|
| 41 |
+
context += f" Biomarker Findings / Lab Results: {result.get('findings')}\n"
|
| 42 |
+
elif result.get('text_preview'):
|
| 43 |
+
context += f" Text Content Preview: {result.get('text_preview', '')}\n"
|
| 44 |
+
context += "\n"
|
| 45 |
return context
|
| 46 |
|
| 47 |
def answer_question(
|
|
|
|
| 57 |
"source_documents": [],
|
| 58 |
}
|
| 59 |
|
| 60 |
+
# 1. Retrieve relevant documents: Direct Patient/MRN lookup + Semantic vector search
|
| 61 |
results_list = []
|
| 62 |
+
seen_doc_ids = set()
|
| 63 |
+
|
| 64 |
+
# A. Direct query matching for patient name, MRN, document ID, and test type
|
| 65 |
+
import re
|
| 66 |
+
tokens = [t for t in re.findall(r'[a-zA-Z0-9_-]+', question) if len(t) >= 3 and t.lower() not in ['summarize', 'diagnostic', 'history', 'abnormal', 'flags', 'findings', 'what', 'with', 'from', 'explain', 'show', 'tell', 'about', 'report', 'reports', 'test', 'results']]
|
| 67 |
+
|
| 68 |
+
for token in tokens:
|
| 69 |
+
matched_reports = (
|
| 70 |
+
db.query(PathologyReport)
|
| 71 |
+
.filter(
|
| 72 |
+
(PathologyReport.patient_id.ilike(f"%{token}%")) |
|
| 73 |
+
(PathologyReport.patient_name.ilike(f"%{token}%")) |
|
| 74 |
+
(PathologyReport.document_id.ilike(f"%{token}%")) |
|
| 75 |
+
(PathologyReport.test_type.ilike(f"%{token}%"))
|
| 76 |
+
)
|
| 77 |
+
.limit(5)
|
| 78 |
+
.all()
|
| 79 |
+
)
|
| 80 |
+
for r in matched_reports:
|
| 81 |
+
if r.document_id not in seen_doc_ids:
|
| 82 |
+
seen_doc_ids.add(r.document_id)
|
| 83 |
+
results_list.append({
|
| 84 |
+
"document_id": r.document_id,
|
| 85 |
+
"patient_name": r.patient_name or "Unknown",
|
| 86 |
+
"patient_id": r.patient_id or "Unknown",
|
| 87 |
+
"test_type": r.test_type or "Pathology Report",
|
| 88 |
+
"diagnosis": r.diagnosis or "Normal",
|
| 89 |
+
"summary": r.summary or "Diagnostic report recorded in EMR",
|
| 90 |
+
"findings": r.findings or "",
|
| 91 |
+
"text_preview": r.summary or "",
|
| 92 |
+
})
|
| 93 |
+
|
| 94 |
+
# B. Vector / Semantic search fallback
|
| 95 |
try:
|
| 96 |
search_result = SearchService.semantic_search(db, question, top_k=3)
|
| 97 |
if search_result.get("status") == "success":
|
| 98 |
+
for res in search_result.get("results", []):
|
| 99 |
+
doc_id = res.get("document_id")
|
| 100 |
+
if doc_id and doc_id not in seen_doc_ids:
|
| 101 |
+
seen_doc_ids.add(doc_id)
|
| 102 |
+
results_list.append(res)
|
| 103 |
except Exception as search_err:
|
| 104 |
print(f"[WARN] Vector search lookup skipped: {search_err}")
|
| 105 |
|
| 106 |
+
# C. If still 0 documents found, check if there are existing reports in DB as contextual fallback
|
| 107 |
+
if len(results_list) == 0:
|
| 108 |
+
all_recent = db.query(PathologyReport).order_by(PathologyReport.created_at.desc()).limit(3).all()
|
| 109 |
+
for r in all_recent:
|
| 110 |
+
# If query mentions any part of patient name or diagnosis
|
| 111 |
+
pname = (r.patient_name or "").lower()
|
| 112 |
+
diag = (r.diagnosis or "").lower()
|
| 113 |
+
ttype = (r.test_type or "").lower()
|
| 114 |
+
q_lower = question.lower()
|
| 115 |
+
if any(part in q_lower for part in pname.split() if len(part) > 2) or any(part in q_lower for part in ttype.split() if len(part) > 2):
|
| 116 |
+
if r.document_id not in seen_doc_ids:
|
| 117 |
+
seen_doc_ids.add(r.document_id)
|
| 118 |
+
results_list.append({
|
| 119 |
+
"document_id": r.document_id,
|
| 120 |
+
"patient_name": r.patient_name or "Unknown",
|
| 121 |
+
"patient_id": r.patient_id or "Unknown",
|
| 122 |
+
"test_type": r.test_type or "Pathology Report",
|
| 123 |
+
"diagnosis": r.diagnosis or "Normal",
|
| 124 |
+
"summary": r.summary or "Diagnostic report recorded in EMR",
|
| 125 |
+
"findings": r.findings or "",
|
| 126 |
+
"text_preview": r.summary or "",
|
| 127 |
+
})
|
| 128 |
+
|
| 129 |
# 2. Format context from search results
|
| 130 |
context = self.format_search_results(results_list)
|
| 131 |
|