Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 14 additions & 52 deletions Backend/agent/agent.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import os
import sys
import logging
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from agent.context import get_relevant_context_from_vector_store
from agent.intent import classify_intent
from agent.llm import run_llm
from agent.prompt import build_prompt
from agent.cache import get_context_cache
from agent.handlers import insights

from agent.handlers.appointment import handle as handle_appointments
from agent.handlers.weight import handle as handle_weight
from agent.handlers.symptoms import handle as handle_symptoms
from agent.handlers.guidelines import handle as handle_guidelines
from agent.handlers.medicine import handle as handle_medicine
from agent.handlers.blood_pressure import handle as handle_bp

from agent.vector_store import register_vector_store_updater, update_guidelines_in_vector_store

Expand All @@ -20,84 +24,42 @@
"weight": handle_weight,
"symptoms": handle_symptoms,
"guidelines": handle_guidelines,
"medicine": handle_medicine,
"blood_pressure": handle_bp,
"get_insights": insights.handle,
}

class BabyNestAgent:
def __init__(self, db_path: str):
self.db_path = db_path
self.context_cache = get_context_cache(db_path)

# Register embedding refresh
register_vector_store_updater(update_guidelines_in_vector_store)

def get_user_context(self, user_id: str = "default"):
"""Get user context from cache."""
return self.context_cache.get_context(user_id)

def run(self, query: str, user_id: str = "default"):
if not query or not isinstance(query, str):
return "Invalid query. Please provide a valid string."
return "Invalid query."

try:
# Step 1: Get user context from cache (no DB hit if cache is valid)
user_context = self.get_user_context(user_id)
if not user_context:
return "User profile not found. Please complete your profile setup first."

# Step 2: Classify intent to see if a specialized handler should be used.
user_context = self.get_user_context(user_id) or {"current_week": 1}
intent = classify_intent(query)
logging.info("Detected Intent: %s", intent)

if intent in dispatch_intent:
# Pass user context to handlers
return dispatch_intent[intent](query, user_context)

# Step 3: Retrieve relevant context from the vector store based on the query.
context = get_relevant_context_from_vector_store(query)

# Step 4: Build the prompt with the retrieved context and user context, then run the LLM.
prompt = build_prompt(query, context, user_context)
return run_llm(prompt)

except Exception as e:
return f"Error processing query: {e}"

def update_cache(self, user_id: str = "default", data_type: str = None, operation: str = "update"):
"""
Intelligently update cache based on database changes.

Args:
user_id: User ID to update cache for
data_type: Type of data that changed ('profile', 'weight', 'medicine', 'symptoms', 'blood_pressure', 'discharge')
operation: Type of operation ('create', 'update', 'delete')
"""
self.context_cache.update_cache(user_id, data_type, operation)

def invalidate_cache(self, user_id: str = None):
"""Invalidate cache for specific user or all users."""
self.context_cache.invalidate_cache(user_id)

def refresh_cache_and_embeddings(self):
"""Manually refresh cache and regenerate embeddings after database changes."""
print("🔄 Manually refreshing cache and regenerating embeddings...")
self.context_cache.invalidate_cache()
update_guidelines_in_vector_store()

def get_cache_stats(self):
"""Get cache statistics for monitoring."""
return self.context_cache.get_cache_stats()

def cleanup_cache(self):
"""Manually trigger cache cleanup."""
self.context_cache._cleanup_old_cache_files()
self.context_cache._cleanup_memory_cache()
print("🧹 Cache cleanup completed")
logging.exception("Error processing query")
return f"Error processing query: {e!s}"

# Global agent instance
_agent_instance = None

def get_agent(db_path: str) -> BabyNestAgent:
"""Get or create the global agent instance."""
global _agent_instance
if _agent_instance is None:
_agent_instance = BabyNestAgent(db_path)
return _agent_instance

return _agent_instance
51 changes: 51 additions & 0 deletions Backend/agent/handlers/blood_pressure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import re
import sqlite3
from db.db import open_db

def handle(query, user_context):
"""
Extracts BP readings, validates thresholds for pregnancy safety,
and logs data into the database.
"""
db = open_db()
try:
# Improved regex to capture systolic and diastolic values
match = re.search(r'(\d{1,3})\s*(?:/|over|-)\s*(\d{1,3})', query, re.IGNORECASE)
if not match:
return "Please provide BP in format like '120/80' or '120 over 80'."

systolic = int(match.group(1))
diastolic = int(match.group(2))

# Basic physiological range validation
if not (70 <= systolic <= 250 and 40 <= diastolic <= 150):
return "BP values out of valid range. Please check and try again."

week = user_context.get('week_number', 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Inconsistent user context key across handlers.

Line 24 uses week_number but the agent (Backend/agent/agent.py line 46) provides current_week in the default context, and the prompt builder (Backend/agent/prompt.py) also expects current_week. This mismatch will cause the week to always default to 1.

🔧 Proposed fix for context key consistency
-        week = user_context.get('week_number', 1)
+        week = user_context.get('current_week', 1)

Apply the same fix to Backend/agent/handlers/medicine.py line 19 and Backend/agent/handlers/insights.py line 12.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
week = user_context.get('week_number', 1)
week = user_context.get('current_week', 1)
🤖 Prompt for AI Agents
In @Backend/agent/handlers/blood_pressure.py at line 24, The handler is reading
the wrong context key ('week_number') so it always defaults to 1; update the
user_context lookups to use 'current_week' instead: change the assignment that
uses user_context.get('week_number', 1) to user_context.get('current_week', 1)
in the blood_pressure handler and apply the same change in the medicine and
insights handlers so all handlers use the same 'current_week' key as the agent
and prompt builder.


# Database persistence
db.execute(
'INSERT INTO blood_pressure_logs (week_number, systolic, diastolic, time) VALUES (?, ?, ?, datetime("now"))',
(week, systolic, diastolic)
)
db.commit()

# --- MEDICAL ALERT LOGIC ---
# Thresholds based on pregnancy-induced hypertension (PIH) guidelines
if systolic >= 140 or diastolic >= 90:
return (f"🚨 **URGENT ALERT**: Your BP is {systolic}/{diastolic}. This is classified as High Blood Pressure (Hypertension). "
"In pregnancy, this requires immediate attention. Please rest and **contact your doctor right away**.")

elif systolic <= 90 or diastolic <= 60:
return (f"⚠️ **WARNING**: Your BP is {systolic}/{diastolic}, which is low. "
"Ensure you are hydrated and notify your healthcare provider if you feel faint.")

else:
return f"✅ Recorded BP: {systolic}/{diastolic} for week {week}. Your reading is within the healthy range."
Comment on lines +35 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

pregnancy-induced hypertension PIH blood pressure thresholds guidelines ACOG 2024

💡 Result:

Short answer — ACOG (current guidance) uses 140/90 mm Hg as the treatment/initiation threshold for chronic hypertension in pregnancy (based on the CHAP trial guidance) and retains ≥160/110 mm Hg as the definition of severe (acute) maternal hypertension that requires immediate evaluation/treatment. See ACOG acute‑hypertension/preeclampsia algorithms and related practice guidance for management details. [1][2][3]

Sources

  • ACOG Practice Advisory: integration of CHAP study — treat chronic hypertension in pregnancy at threshold 140/90 mm Hg (reaffirmed). [1]
  • ACOG Acute Hypertension in Pregnancy & Postpartum algorithm — defines severe/urgent BP ≥160/110 mm Hg and gives management steps. [2]
  • ACOG Practice Bulletin (gestational hypertension/preeclampsia) — defines severe range as ≥160/110 and diagnostic criteria. [3]

🌐 Web query:

low blood pressure hypotension pregnancy threshold systolic 90 diastolic 60 guidelines

💡 Result:

Short answer: Major obstetric guidelines do not define a fixed numeric "hypotension" cutoff for pregnancy the way they do for hypertension. Clinically, values around <90/60 mmHg are commonly used as a practical threshold for low blood pressure, but trials and observational studies often use slightly different cutoffs (for example persistent SBP <100 mmHg and/or DBP <60 mmHg). Symptoms, hemodynamic instability, and fetal concerns determine evaluation and treatment rather than a single universal number. (acog.org)

Key points:

  • ACOG and major pregnancy guidelines emphasize thresholds for hypertension (e.g., ≥140/90 mmHg) and severe hypertension (≥160/110 mmHg) but do not set a formal numeric definition for maternal hypotension in routine pregnancy care. (acog.org)
  • Practical/common clinical threshold: <90/60 mmHg is widely used to label blood pressure as "low"; persistent values (study examples: SBP <100 mmHg or DBP <60 mmHg) have been studied for associations with uteroplacental blood‑flow changes. Symptoms and persistence matter. (pregnancybirthbaby.org.au)
  • Management depends on symptoms (dizziness, syncope), cause (volume depletion, vasodilation, medications, sepsis, hemorrhage), fetal status, and hemodynamic signs — not a single numeric cutoff. For any symptomatic or persistent low BP in pregnancy, contact obstetric care promptly. (pmc.ncbi.nlm.nih.gov)

If you’d like, I can pull specific guideline text or studies (PDFs) and list exact numeric cutoffs used in research or provide management steps for symptomatic hypotension.

Citations:


Clarify BP alert thresholds and align with ACOG severity definitions.

The high BP threshold (140/90 mmHg) aligns with ACOG's chronic hypertension treatment threshold but not acute hypertension. ACOG defines ≥160/110 mmHg as severe/urgent hypertension requiring immediate evaluation—yet the code labels 140/90 as "URGENT ALERT," which overstates the severity. The low BP threshold (≤90/60 mmHg) lacks formal ACOG guidance; clinically, <90/60 is used as a practical marker, but guidelines emphasize that symptoms, persistence, and clinical context should guide evaluation rather than numeric cutoffs alone.

Consider:

  • Distinguishing between chronic hypertension (≥140/90) and acute severe hypertension (≥160/110) in alerts
  • Noting that low BP evaluation depends on symptoms and persistence, not the numeric threshold alone
  • Removing the "URGENT" framing from the 140/90 alert or reserving it for ≥160/110


except sqlite3.Error as e:
return f"Database error: {str(e)}"
except Exception as e:
return f"Unexpected error: {str(e)}"
finally:
db.close()
59 changes: 59 additions & 0 deletions Backend/agent/handlers/insights.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import sqlite3
from datetime import datetime, timedelta
from db.db import open_db

def handle(query, user_context):
"""
Analyzes data from the last 7 days across all logs to provide
a comprehensive health summary.
"""
db = open_db()
try:
week = user_context.get('week_number', 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Same context key inconsistency.

This line has the same issue as the other handlers. Use current_week instead of week_number.

🤖 Prompt for AI Agents
In @Backend/agent/handlers/insights.py at line 12, The handler reads the wrong
context key; change the user_context lookup in insights.py from
user_context.get('week_number', 1) to user_context.get('current_week', 1)
(keeping the local variable name `week` or rename to `current_week` if you
prefer) so it matches the other handlers and uses the `current_week` key
consistently when extracting the week value from `user_context`.


# 1. Fetch Blood Pressure Data (Last 7 Days)
bp_rows = db.execute(
"SELECT systolic, diastolic FROM blood_pressure_logs WHERE time >= datetime('now', '-7 days')"
).fetchall()

# 2. Fetch Medicine Data (Last 7 Days)
med_rows = db.execute(
"SELECT status FROM weekly_medicine_logs WHERE time >= datetime('now', '-7 days')"
).fetchall()

# 3. Fetch Weight Data (Last 7 Days)
weight_rows = db.execute(
"SELECT weight_value FROM weight_logs WHERE time >= datetime('now', '-7 days')"
).fetchall()

# --- ANALYSIS LOGIC ---

# BP Analysis
if bp_rows:
avg_sys = sum(r[0] for r in bp_rows) // len(bp_rows)
avg_dia = sum(r[1] for r in bp_rows) // len(bp_rows)
Comment on lines +33 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use float division for precise BP averages.

Integer division (//) truncates decimal values, which could hide important variations in BP readings. For medical data, precision matters.

📊 Proposed fix for precision
-            avg_sys = sum(r[0] for r in bp_rows) // len(bp_rows)
-            avg_dia = sum(r[1] for r in bp_rows) // len(bp_rows)
+            avg_sys = round(sum(r[0] for r in bp_rows) / len(bp_rows), 1)
+            avg_dia = round(sum(r[1] for r in bp_rows) / len(bp_rows), 1)

Update the response format to handle float values:

-            f"🩺 **Blood Pressure:** Avg {avg_sys}/{avg_dia} mmHg\n"
+            f"🩺 **Blood Pressure:** Avg {avg_sys:.1f}/{avg_dia:.1f} mmHg\n"
🤖 Prompt for AI Agents
In @Backend/agent/handlers/insights.py around lines 33 - 34, The current
computation of avg_sys and avg_dia uses integer division which truncates
precision; change the calculations to use float division (replace // with /)
when computing averages from bp_rows in the insights handler (avg_sys, avg_dia
computed from bp_rows), and update any response formatting that expects integers
to preserve or round decimals (e.g., round to one or two decimal places) so the
returned BP averages maintain required medical precision.

bp_status = "Stable" if avg_sys < 140 else "High (Consult Doctor)"
else:
avg_sys, avg_dia, bp_status = "N/A", "N/A", "No data logged"

# Medicine Adherence
total_meds = len(med_rows)
taken_meds = sum(1 for r in med_rows if r[0] == 'taken')
adherence = (taken_meds / total_meds * 100) if total_meds > 0 else 0

# Response Building
response = (
f"📊 **Weekly Health Insights (Week {week})**\n\n"
f"🩺 **Blood Pressure:** Avg {avg_sys}/{avg_dia} mmHg\n"
f"💡 *Status:* {bp_status}\n\n"
f"💊 **Medicine Adherence:** {adherence:.1f}%\n"
f"💡 *Tip:* {'Great job staying consistent!' if adherence > 80 else 'Try to set reminders for your supplements.'}\n\n"
f"⚖️ **Weight Logs:** {len(weight_rows)} entries found this week."
)

return response

except Exception as e:
return f"Error generating insights: {str(e)}"
finally:
db.close()
44 changes: 44 additions & 0 deletions Backend/agent/handlers/medicine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import sqlite3
from datetime import datetime
from db.db import open_db

def handle(query, user_context):
"""
Records medicine intake and provides proactive reminders based
on standard pregnancy supplement requirements.
"""
db = open_db()
try:
# Standard supplements for pregnancy
required_meds = ["iron", "folic acid", "calcium"]
query_lower = query.lower()

# Identify which medicine the user is logging
logged_med = next((med for med in required_meds if med in query_lower), "medicine")

week = user_context.get('week_number', 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Same context key inconsistency.

This line has the same issue as Backend/agent/handlers/blood_pressure.py line 24. Use current_week instead of week_number to match the context structure provided by the agent.

🤖 Prompt for AI Agents
In @Backend/agent/handlers/medicine.py at line 19, The code reads the week from
the wrong context key: change user_context.get('week_number', 1) to
user_context.get('current_week', 1) so the handler in medicine.py uses the same
context key as the blood_pressure handler; update any dependent uses of the
local variable week if they assume the new key but no further logic changes are
required.


# Record the entry in the database
db.execute(
'INSERT INTO weekly_medicine_logs (week_number, medicine_name, status, time) VALUES (?, ?, ?, datetime("now"))',
(week, logged_med, "taken")
)
db.commit()

# --- PROACTIVE LOGIC ---
# Remind user about other important meds if they only logged one
missing_meds = [m for m in required_meds if m not in query_lower]

response = f"✅ Logged your {logged_med} intake for week {week}."

if missing_meds:
response += f"\n\n💡 **Tip**: Don't forget to also take your {', '.join(missing_meds)} today as prescribed for your pregnancy stage."

return response

except sqlite3.Error as e:
return f"Database error: {str(e)}"
except Exception as e:
return f"Unexpected error: {str(e)}"
finally:
db.close()
12 changes: 10 additions & 2 deletions Backend/agent/intent.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# agent/intent.py
def classify_intent(query: str) -> str:
if not query or not isinstance(query, str):
return "general"

query = query.lower()

if any(word in query for word in ["summary", "report", "insight", "dashboard", "track"]):
return "get_insights"

if "appointment" in query:
return "appointments"
elif "weight" in query:
return "weight"
elif "symptom" in query:
return "symptoms"
elif "medicine" in query or "took" in query or "tablet" in query or "pill" in query:
return "medicine"
Comment on lines +16 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The keyword "took" is too generic and may cause false positives.

The word "took" appears in many non-medicine contexts (e.g., "I took a walk", "I took notes"). Consider requiring a combination of keywords or using a regex pattern that looks for medicine-related context.

Suggested improvement
-    elif "medicine" in query or "took" in query or "tablet" in query or "pill" in query:
+    elif "medicine" in query or "tablet" in query or "pill" in query or ("took" in query and any(kw in query for kw in ["mg", "tablet", "pill", "capsule", "dose"])):
         return "medicine"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elif "medicine" in query or "took" in query or "tablet" in query or "pill" in query:
return "medicine"
elif "medicine" in query or "tablet" in query or "pill" in query or ("took" in query and any(kw in query for kw in ["mg", "tablet", "pill", "capsule", "dose"])):
return "medicine"
🤖 Prompt for AI Agents
In @Backend/agent/intent.py around lines 13 - 14, The branch that returns
"medicine" currently triggers on the generic token "took", which causes false
positives; update the condition in the intent detection (the elif that returns
"medicine") to either remove the standalone "took" check or require it to match
a medicine context (e.g., use a regex like r"\btook (a
)?(pill|tablet|dose|medicine|medication)\b" or require both "took" and a
medicine keyword present), so the intent only fires when "took" appears with a
medication-related word.

elif "bp" in query or "blood pressure" in query or "/" in query:
return "blood_pressure"
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Overly broad "/" pattern will cause false positives.

The "/" character alone as a trigger for blood_pressure intent will match URLs, date formats (e.g., "1/2/2026"), file paths, and fractions. This could misclassify many unrelated queries.

Additionally, the ordering creates an issue: a query like "I took my BP 120/80" would be classified as "medicine" (due to "took") rather than "blood_pressure".

Proposed fix: Use a more specific BP pattern
-    elif "bp" in query or "blood pressure" in query or "/" in query:
+    elif "bp" in query or "blood pressure" in query or re.search(r'\d{2,3}\s*/\s*\d{2,3}', query):
         return "blood_pressure"

This requires adding import re at the top of the file. The pattern specifically matches numeric BP readings like "120/80".

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @Backend/agent/intent.py around lines 15 - 16, The current condition checking
`elif "bp" in query or "blood pressure" in query or "/" in query` is too broad
and causes false positives and misordering with the `medicine` intent; import
the `re` module, replace the `"/"` check with a regex that matches numeric BP
formats (e.g., use re.search for a pattern like r'\b\d{2,3}/\d{2,3}\b' to detect
readings such as "120/80") in the `elif "bp" in query or "blood pressure" in
query ...` branch that returns "blood_pressure", and move this blood_pressure
check to run before the branch that returns "medicine" so inputs like "I took my
BP 120/80" are classified as "blood_pressure".

elif "vaccine" in query or "guideline" in query or "what tests" in query or "recommend" in query:
return "guidelines"
return "general"

return "general"
Loading