Spaces:
Sleeping
Sleeping
Upload 5 files
Browse files- careloop_main.py +1143 -0
- config.py +160 -0
- demo_scenarios.py +216 -0
- gradio_app.py +622 -0
- requirements-space.txt +10 -0
careloop_main.py
ADDED
|
@@ -0,0 +1,1143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CareLoop Hackathon Submission - Main Application
|
| 2 |
+
# AI-Powered Family Caregiving Assistant
|
| 3 |
+
|
| 4 |
+
import asyncio
|
| 5 |
+
import json
|
| 6 |
+
from datetime import datetime, timedelta
|
| 7 |
+
from typing import Dict, List, Any, TypedDict, Optional
|
| 8 |
+
from dataclasses import dataclass, asdict
|
| 9 |
+
from enum import Enum
|
| 10 |
+
import random
|
| 11 |
+
import uuid
|
| 12 |
+
|
| 13 |
+
# LangGraph and LangChain imports
|
| 14 |
+
from langgraph.graph import StateGraph, END
|
| 15 |
+
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
| 16 |
+
from langchain_openai import ChatOpenAI
|
| 17 |
+
from langchain_core.tools import tool
|
| 18 |
+
|
| 19 |
+
# FastAPI for web interface
|
| 20 |
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
| 21 |
+
from fastapi.staticfiles import StaticFiles
|
| 22 |
+
from fastapi.responses import HTMLResponse
|
| 23 |
+
import uvicorn
|
| 24 |
+
|
| 25 |
+
# ============== MOCK DATA MODELS ==============
|
| 26 |
+
|
| 27 |
+
class AlertLevel(Enum):
|
| 28 |
+
LOW = "low"
|
| 29 |
+
MEDIUM = "medium"
|
| 30 |
+
HIGH = "high"
|
| 31 |
+
URGENT = "urgent"
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class Parent:
|
| 35 |
+
id: str
|
| 36 |
+
name: str
|
| 37 |
+
age: int
|
| 38 |
+
conditions: List[str]
|
| 39 |
+
medications: List[Dict]
|
| 40 |
+
emergency_contacts: List[Dict]
|
| 41 |
+
preferences: Dict
|
| 42 |
+
|
| 43 |
+
@dataclass
|
| 44 |
+
class FamilyMember:
|
| 45 |
+
id: str
|
| 46 |
+
name: str
|
| 47 |
+
relationship: str
|
| 48 |
+
phone: str
|
| 49 |
+
email: str
|
| 50 |
+
role: str
|
| 51 |
+
notification_preferences: Dict
|
| 52 |
+
|
| 53 |
+
@dataclass
|
| 54 |
+
class HealthMetric:
|
| 55 |
+
timestamp: datetime
|
| 56 |
+
metric_type: str
|
| 57 |
+
value: float
|
| 58 |
+
unit: str
|
| 59 |
+
source: str
|
| 60 |
+
normal_range: tuple
|
| 61 |
+
|
| 62 |
+
# ============== COMPREHENSIVE MOCK DATA ==============
|
| 63 |
+
|
| 64 |
+
class MockDataGenerator:
|
| 65 |
+
def __init__(self):
|
| 66 |
+
self.parents = {}
|
| 67 |
+
self.families = {}
|
| 68 |
+
self.health_data = {}
|
| 69 |
+
self.medication_data = {}
|
| 70 |
+
self.setup_families()
|
| 71 |
+
|
| 72 |
+
def setup_families(self):
|
| 73 |
+
# Family 1: Margaret Chen (active, multiple conditions)
|
| 74 |
+
margaret = Parent(
|
| 75 |
+
id="parent_001",
|
| 76 |
+
name="Margaret Chen",
|
| 77 |
+
age=78,
|
| 78 |
+
conditions=["Type 2 Diabetes", "Hypertension", "Mild Cognitive Impairment"],
|
| 79 |
+
medications=[
|
| 80 |
+
{"name": "Metformin", "dosage": "500mg", "frequency": "2x daily", "times": ["08:00", "20:00"]},
|
| 81 |
+
{"name": "Lisinopril", "dosage": "10mg", "frequency": "1x daily", "times": ["08:00"]},
|
| 82 |
+
{"name": "Donepezil", "dosage": "5mg", "frequency": "1x daily", "times": ["20:00"]}
|
| 83 |
+
],
|
| 84 |
+
emergency_contacts=[
|
| 85 |
+
{"name": "Dr. Sarah Kim", "phone": "555-MED-1234", "type": "primary_care"},
|
| 86 |
+
{"name": "Mercy General Hospital", "phone": "555-911-HELP", "type": "emergency"}
|
| 87 |
+
],
|
| 88 |
+
preferences={
|
| 89 |
+
"preferred_contact_time": "morning",
|
| 90 |
+
"communication_style": "gentle",
|
| 91 |
+
"daily_check_in_time": "19:00"
|
| 92 |
+
}
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
margaret_family = [
|
| 96 |
+
FamilyMember(
|
| 97 |
+
id="family_001",
|
| 98 |
+
name="David Chen",
|
| 99 |
+
relationship="son",
|
| 100 |
+
phone="555-123-4567",
|
| 101 |
+
email="david.chen@email.com",
|
| 102 |
+
role="primary_caregiver",
|
| 103 |
+
notification_preferences={
|
| 104 |
+
"urgent_alerts": True,
|
| 105 |
+
"daily_summary": True,
|
| 106 |
+
"medication_reminders": True,
|
| 107 |
+
"health_trends": True
|
| 108 |
+
}
|
| 109 |
+
),
|
| 110 |
+
FamilyMember(
|
| 111 |
+
id="family_002",
|
| 112 |
+
name="Lisa Chen-Rodriguez",
|
| 113 |
+
relationship="daughter",
|
| 114 |
+
phone="555-987-6543",
|
| 115 |
+
email="lisa.rodriguez@email.com",
|
| 116 |
+
role="backup_caregiver",
|
| 117 |
+
notification_preferences={
|
| 118 |
+
"urgent_alerts": True,
|
| 119 |
+
"daily_summary": False,
|
| 120 |
+
"medication_reminders": False,
|
| 121 |
+
"health_trends": True
|
| 122 |
+
}
|
| 123 |
+
),
|
| 124 |
+
FamilyMember(
|
| 125 |
+
id="family_003",
|
| 126 |
+
name="Jennifer Chen",
|
| 127 |
+
relationship="daughter-in-law",
|
| 128 |
+
phone="555-456-7890",
|
| 129 |
+
email="jen.chen@email.com",
|
| 130 |
+
role="support",
|
| 131 |
+
notification_preferences={
|
| 132 |
+
"urgent_alerts": True,
|
| 133 |
+
"daily_summary": False,
|
| 134 |
+
"medication_reminders": False,
|
| 135 |
+
"health_trends": False
|
| 136 |
+
}
|
| 137 |
+
)
|
| 138 |
+
]
|
| 139 |
+
|
| 140 |
+
# Family 2: Robert Johnson (recent stroke recovery)
|
| 141 |
+
robert = Parent(
|
| 142 |
+
id="parent_002",
|
| 143 |
+
name="Robert Johnson",
|
| 144 |
+
age=72,
|
| 145 |
+
conditions=["Stroke Recovery", "Atrial Fibrillation", "Arthritis"],
|
| 146 |
+
medications=[
|
| 147 |
+
{"name": "Warfarin", "dosage": "5mg", "frequency": "1x daily", "times": ["18:00"]},
|
| 148 |
+
{"name": "Atorvastatin", "dosage": "20mg", "frequency": "1x daily", "times": ["20:00"]},
|
| 149 |
+
{"name": "Aspirin", "dosage": "81mg", "frequency": "1x daily", "times": ["08:00"]}
|
| 150 |
+
],
|
| 151 |
+
emergency_contacts=[
|
| 152 |
+
{"name": "Dr. James Miller", "phone": "555-MED-8765", "type": "primary_care"},
|
| 153 |
+
{"name": "Central Hospital", "phone": "555-911-9999", "type": "emergency"}
|
| 154 |
+
],
|
| 155 |
+
preferences={
|
| 156 |
+
"preferred_contact_time": "afternoon",
|
| 157 |
+
"communication_style": "direct",
|
| 158 |
+
"daily_check_in_time": "17:00"
|
| 159 |
+
}
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
robert_family = [
|
| 163 |
+
FamilyMember(
|
| 164 |
+
id="family_101",
|
| 165 |
+
name="Michael Johnson",
|
| 166 |
+
relationship="son",
|
| 167 |
+
phone="555-333-4444",
|
| 168 |
+
email="michael.johnson@email.com",
|
| 169 |
+
role="primary_caregiver",
|
| 170 |
+
notification_preferences={
|
| 171 |
+
"urgent_alerts": True,
|
| 172 |
+
"daily_summary": True,
|
| 173 |
+
"medication_reminders": True,
|
| 174 |
+
"health_trends": True
|
| 175 |
+
}
|
| 176 |
+
),
|
| 177 |
+
FamilyMember(
|
| 178 |
+
id="family_102",
|
| 179 |
+
name="Susan Johnson",
|
| 180 |
+
relationship="daughter",
|
| 181 |
+
phone="555-555-6666",
|
| 182 |
+
email="susan.johnson@email.com",
|
| 183 |
+
role="backup_caregiver",
|
| 184 |
+
notification_preferences={
|
| 185 |
+
"urgent_alerts": True,
|
| 186 |
+
"daily_summary": True,
|
| 187 |
+
"medication_reminders": True,
|
| 188 |
+
"health_trends": True
|
| 189 |
+
}
|
| 190 |
+
)
|
| 191 |
+
]
|
| 192 |
+
|
| 193 |
+
# Family 3: Elena Gonzalez (early Alzheimer's)
|
| 194 |
+
elena = Parent(
|
| 195 |
+
id="parent_003",
|
| 196 |
+
name="Elena Gonzalez",
|
| 197 |
+
age=81,
|
| 198 |
+
conditions=["Early Alzheimer's", "Osteoporosis", "COPD"],
|
| 199 |
+
medications=[
|
| 200 |
+
{"name": "Aricept", "dosage": "10mg", "frequency": "1x daily", "times": ["20:00"]},
|
| 201 |
+
{"name": "Alendronate", "dosage": "70mg", "frequency": "1x weekly", "times": ["08:00"]},
|
| 202 |
+
{"name": "Albuterol", "dosage": "90mcg", "frequency": "as needed", "times": ["08:00", "20:00"]}
|
| 203 |
+
],
|
| 204 |
+
emergency_contacts=[
|
| 205 |
+
{"name": "Dr. Maria Lopez", "phone": "555-MED-9876", "type": "primary_care"},
|
| 206 |
+
{"name": "Sunset Medical Center", "phone": "555-911-4321", "type": "emergency"}
|
| 207 |
+
],
|
| 208 |
+
preferences={
|
| 209 |
+
"preferred_contact_time": "morning",
|
| 210 |
+
"communication_style": "simple",
|
| 211 |
+
"daily_check_in_time": "10:00"
|
| 212 |
+
}
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
elena_family = [
|
| 216 |
+
FamilyMember(
|
| 217 |
+
id="family_201",
|
| 218 |
+
name="Carlos Gonzalez",
|
| 219 |
+
relationship="son",
|
| 220 |
+
phone="555-777-8888",
|
| 221 |
+
email="carlos.gonzalez@email.com",
|
| 222 |
+
role="primary_caregiver",
|
| 223 |
+
notification_preferences={
|
| 224 |
+
"urgent_alerts": True,
|
| 225 |
+
"daily_summary": True,
|
| 226 |
+
"medication_reminders": True,
|
| 227 |
+
"health_trends": True
|
| 228 |
+
}
|
| 229 |
+
),
|
| 230 |
+
FamilyMember(
|
| 231 |
+
id="family_202",
|
| 232 |
+
name="Isabella Martinez",
|
| 233 |
+
relationship="granddaughter",
|
| 234 |
+
phone="555-999-0000",
|
| 235 |
+
email="isabella.martinez@email.com",
|
| 236 |
+
role="support",
|
| 237 |
+
notification_preferences={
|
| 238 |
+
"urgent_alerts": True,
|
| 239 |
+
"daily_summary": False,
|
| 240 |
+
"medication_reminders": False,
|
| 241 |
+
"health_trends": True
|
| 242 |
+
}
|
| 243 |
+
)
|
| 244 |
+
]
|
| 245 |
+
|
| 246 |
+
# Store in parent dictionary
|
| 247 |
+
self.parents = {
|
| 248 |
+
"parent_001": margaret,
|
| 249 |
+
"parent_002": robert,
|
| 250 |
+
"parent_003": elena
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
# Store in family dictionary
|
| 254 |
+
self.families = {
|
| 255 |
+
"parent_001": margaret_family,
|
| 256 |
+
"parent_002": robert_family,
|
| 257 |
+
"parent_003": elena_family
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
# Generate health data for each parent
|
| 261 |
+
for parent_id in self.parents:
|
| 262 |
+
self.health_data[parent_id] = self.generate_health_timeline(parent_id)
|
| 263 |
+
self.medication_data[parent_id] = self.generate_medication_timeline(parent_id)
|
| 264 |
+
|
| 265 |
+
# For backward compatibility
|
| 266 |
+
self.margaret = self.parents["parent_001"]
|
| 267 |
+
self.margaret_family = self.families["parent_001"]
|
| 268 |
+
|
| 269 |
+
def get_parent_by_id(self, parent_id: str) -> Optional[Parent]:
|
| 270 |
+
"""Get parent data by ID"""
|
| 271 |
+
return self.parents.get(parent_id)
|
| 272 |
+
|
| 273 |
+
def get_family_by_parent_id(self, parent_id: str) -> List[FamilyMember]:
|
| 274 |
+
"""Get family members for a specific parent"""
|
| 275 |
+
return self.families.get(parent_id, [])
|
| 276 |
+
|
| 277 |
+
def get_health_data_by_parent_id(self, parent_id: str) -> List[HealthMetric]:
|
| 278 |
+
"""Get health data for a specific parent"""
|
| 279 |
+
return self.health_data.get(parent_id, [])
|
| 280 |
+
|
| 281 |
+
def get_medication_data_by_parent_id(self, parent_id: str) -> List[Dict]:
|
| 282 |
+
"""Get medication data for a specific parent"""
|
| 283 |
+
return self.medication_data.get(parent_id, [])
|
| 284 |
+
|
| 285 |
+
def generate_health_timeline(self, parent_id: str) -> List[HealthMetric]:
|
| 286 |
+
"""Generate realistic health data for the past week"""
|
| 287 |
+
metrics = []
|
| 288 |
+
base_date = datetime.now() - timedelta(days=7)
|
| 289 |
+
parent = self.parents.get(parent_id)
|
| 290 |
+
|
| 291 |
+
if not parent:
|
| 292 |
+
return []
|
| 293 |
+
|
| 294 |
+
for day in range(7):
|
| 295 |
+
current_date = base_date + timedelta(days=day)
|
| 296 |
+
|
| 297 |
+
# Morning metrics
|
| 298 |
+
morning = current_date.replace(hour=8, minute=0)
|
| 299 |
+
|
| 300 |
+
# Customize metrics based on parent conditions
|
| 301 |
+
if parent_id == "parent_001": # Margaret (diabetes, hypertension)
|
| 302 |
+
metrics.extend([
|
| 303 |
+
HealthMetric(morning, "heart_rate", 72 + random.randint(-8, 8), "bpm", "apple_watch", (60, 90)),
|
| 304 |
+
HealthMetric(morning, "blood_pressure_systolic", 135 + random.randint(-15, 15), "mmHg", "omron_cuff", (90, 140)),
|
| 305 |
+
HealthMetric(morning, "blood_pressure_diastolic", 82 + random.randint(-10, 10), "mmHg", "omron_cuff", (60, 90)),
|
| 306 |
+
HealthMetric(morning, "weight", 145.2 + random.uniform(-1, 1), "lbs", "smart_scale", (140, 150)),
|
| 307 |
+
])
|
| 308 |
+
|
| 309 |
+
# Blood glucose (Type 2 diabetes)
|
| 310 |
+
for hour in [8, 12, 18]:
|
| 311 |
+
glucose_time = current_date.replace(hour=hour, minute=random.randint(0, 30))
|
| 312 |
+
base_glucose = 140 if hour == 12 else 120 # Higher after lunch
|
| 313 |
+
metrics.append(
|
| 314 |
+
HealthMetric(glucose_time, "blood_glucose", base_glucose + random.randint(-20, 30), "mg/dL", "glucose_meter", (80, 130))
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
elif parent_id == "parent_002": # Robert (stroke recovery, AFib)
|
| 318 |
+
metrics.extend([
|
| 319 |
+
HealthMetric(morning, "heart_rate", 78 + random.randint(-5, 12), "bpm", "apple_watch", (60, 90)),
|
| 320 |
+
HealthMetric(morning, "blood_pressure_systolic", 142 + random.randint(-10, 20), "mmHg", "omron_cuff", (90, 140)),
|
| 321 |
+
HealthMetric(morning, "blood_pressure_diastolic", 88 + random.randint(-5, 10), "mmHg", "omron_cuff", (60, 90)),
|
| 322 |
+
HealthMetric(morning, "weight", 190.5 + random.uniform(-1, 1.5), "lbs", "smart_scale", (185, 195)),
|
| 323 |
+
HealthMetric(morning, "heart_rhythm", 1 if random.random() < 0.2 else 0, "irregularity", "ecg_monitor", (0, 0)),
|
| 324 |
+
])
|
| 325 |
+
|
| 326 |
+
elif parent_id == "parent_003": # Elena (Alzheimer's, osteoporosis, COPD)
|
| 327 |
+
metrics.extend([
|
| 328 |
+
HealthMetric(morning, "heart_rate", 75 + random.randint(-8, 10), "bpm", "apple_watch", (60, 90)),
|
| 329 |
+
HealthMetric(morning, "blood_pressure_systolic", 128 + random.randint(-10, 15), "mmHg", "omron_cuff", (90, 140)),
|
| 330 |
+
HealthMetric(morning, "blood_pressure_diastolic", 78 + random.randint(-8, 8), "mmHg", "omron_cuff", (60, 90)),
|
| 331 |
+
HealthMetric(morning, "weight", 118.7 + random.uniform(-0.5, 0.5), "lbs", "smart_scale", (115, 120)),
|
| 332 |
+
HealthMetric(morning, "oxygen_saturation", 94 + random.randint(-3, 2), "%", "pulse_oximeter", (95, 100)),
|
| 333 |
+
])
|
| 334 |
+
|
| 335 |
+
# Cognitive function test (for Alzheimer's)
|
| 336 |
+
if day % 2 == 0: # Every other day
|
| 337 |
+
test_time = current_date.replace(hour=11, minute=random.randint(0, 30))
|
| 338 |
+
metrics.append(
|
| 339 |
+
HealthMetric(test_time, "cognitive_score", 78 + random.randint(-8, 5), "points", "cognitive_test", (85, 100))
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
# Daily activity metrics - common for all
|
| 343 |
+
metrics.extend([
|
| 344 |
+
HealthMetric(current_date.replace(hour=23, minute=59), "steps", 2800 + random.randint(-800, 1200), "steps", "apple_watch", (2000, 8000)),
|
| 345 |
+
HealthMetric(current_date.replace(hour=23, minute=59), "sleep_hours", 6.5 + random.uniform(-1, 1.5), "hours", "apple_watch", (6, 9)),
|
| 346 |
+
])
|
| 347 |
+
|
| 348 |
+
return sorted(metrics, key=lambda x: x.timestamp)
|
| 349 |
+
|
| 350 |
+
def generate_medication_timeline(self, parent_id: str) -> List[Dict]:
|
| 351 |
+
"""Generate medication compliance data"""
|
| 352 |
+
compliance_data = []
|
| 353 |
+
base_date = datetime.now() - timedelta(days=7)
|
| 354 |
+
parent = self.parents.get(parent_id)
|
| 355 |
+
|
| 356 |
+
if not parent:
|
| 357 |
+
return []
|
| 358 |
+
|
| 359 |
+
for day in range(7):
|
| 360 |
+
current_date = base_date + timedelta(days=day)
|
| 361 |
+
|
| 362 |
+
for med in parent.medications:
|
| 363 |
+
for time_str in med["times"]:
|
| 364 |
+
hour, minute = map(int, time_str.split(':'))
|
| 365 |
+
scheduled_time = current_date.replace(hour=hour, minute=minute)
|
| 366 |
+
|
| 367 |
+
# Simulate realistic compliance patterns
|
| 368 |
+
compliance_rate = 0.9 # Default rate
|
| 369 |
+
|
| 370 |
+
if parent_id == "parent_001": # Margaret
|
| 371 |
+
if med["name"] == "Metformin":
|
| 372 |
+
compliance_rate = 0.95 if hour < 12 else 0.85
|
| 373 |
+
elif med["name"] == "Lisinopril":
|
| 374 |
+
compliance_rate = 0.92
|
| 375 |
+
else: # Donepezil
|
| 376 |
+
compliance_rate = 0.88
|
| 377 |
+
elif parent_id == "parent_002": # Robert
|
| 378 |
+
if med["name"] == "Warfarin":
|
| 379 |
+
compliance_rate = 0.97 # Critical medication
|
| 380 |
+
else:
|
| 381 |
+
compliance_rate = 0.9
|
| 382 |
+
elif parent_id == "parent_003": # Elena (Alzheimer's)
|
| 383 |
+
# More forgetful due to cognitive issues
|
| 384 |
+
compliance_rate = 0.75 if hour >= 18 else 0.85
|
| 385 |
+
|
| 386 |
+
was_taken = random.random() < compliance_rate
|
| 387 |
+
actual_time = None
|
| 388 |
+
|
| 389 |
+
if was_taken:
|
| 390 |
+
# Add realistic delay (5-30 minutes)
|
| 391 |
+
delay_minutes = random.randint(5, 30)
|
| 392 |
+
actual_time = scheduled_time + timedelta(minutes=delay_minutes)
|
| 393 |
+
|
| 394 |
+
compliance_data.append({
|
| 395 |
+
"medication": med["name"],
|
| 396 |
+
"scheduled_time": scheduled_time,
|
| 397 |
+
"actual_time": actual_time,
|
| 398 |
+
"was_taken": was_taken,
|
| 399 |
+
"source": "pill_dispenser" if was_taken else "missed"
|
| 400 |
+
})
|
| 401 |
+
|
| 402 |
+
return compliance_data
|
| 403 |
+
|
| 404 |
+
# ============== LANGGRAPH STATE DEFINITION ==============
|
| 405 |
+
|
| 406 |
+
class CareState(TypedDict):
|
| 407 |
+
parent_id: str
|
| 408 |
+
date: str
|
| 409 |
+
health_metrics: List[Dict]
|
| 410 |
+
medication_status: List[Dict]
|
| 411 |
+
concerns: List[str]
|
| 412 |
+
alerts: List[Dict]
|
| 413 |
+
daily_summary: str
|
| 414 |
+
action_items: List[str]
|
| 415 |
+
family_notifications: List[Dict]
|
| 416 |
+
emergency_level: str
|
| 417 |
+
|
| 418 |
+
# ============== SPECIALIZED CARE AGENTS ==============
|
| 419 |
+
|
| 420 |
+
class HealthMonitorAgent:
|
| 421 |
+
def __init__(self, llm):
|
| 422 |
+
self.llm = llm
|
| 423 |
+
self.mock_data = MockDataGenerator()
|
| 424 |
+
|
| 425 |
+
def analyze_health_patterns(self, state: CareState) -> CareState:
|
| 426 |
+
"""Analyze health trends and identify concerns"""
|
| 427 |
+
recent_metrics = [m for m in self.mock_data.health_data[state["parent_id"]]
|
| 428 |
+
if m.timestamp >= datetime.now() - timedelta(days=3)]
|
| 429 |
+
|
| 430 |
+
concerns = []
|
| 431 |
+
alerts = []
|
| 432 |
+
|
| 433 |
+
# Analyze blood glucose trends (diabetes management)
|
| 434 |
+
glucose_readings = [m for m in recent_metrics if m.metric_type == "blood_glucose"]
|
| 435 |
+
if glucose_readings:
|
| 436 |
+
avg_glucose = sum(m.value for m in glucose_readings) / len(glucose_readings)
|
| 437 |
+
if avg_glucose > 160:
|
| 438 |
+
concerns.append("Blood glucose levels elevated - average 165 mg/dL over 3 days")
|
| 439 |
+
alerts.append({
|
| 440 |
+
"type": "health_concern",
|
| 441 |
+
"severity": AlertLevel.HIGH.value,
|
| 442 |
+
"message": "Diabetes management needs attention - glucose levels trending high",
|
| 443 |
+
"recommended_action": "Contact diabetes care team"
|
| 444 |
+
})
|
| 445 |
+
|
| 446 |
+
# Analyze activity levels
|
| 447 |
+
step_data = [m for m in recent_metrics if m.metric_type == "steps"]
|
| 448 |
+
if step_data:
|
| 449 |
+
avg_steps = sum(m.value for m in step_data) / len(step_data)
|
| 450 |
+
if avg_steps < 2000:
|
| 451 |
+
concerns.append(f"Low activity level - averaging {int(avg_steps)} steps/day")
|
| 452 |
+
alerts.append({
|
| 453 |
+
"type": "activity_concern",
|
| 454 |
+
"severity": AlertLevel.MEDIUM.value,
|
| 455 |
+
"message": "Activity levels below recommended for health maintenance",
|
| 456 |
+
"recommended_action": "Encourage gentle walks or physical therapy"
|
| 457 |
+
})
|
| 458 |
+
|
| 459 |
+
# Analyze sleep patterns
|
| 460 |
+
sleep_data = [m for m in recent_metrics if m.metric_type == "sleep_hours"]
|
| 461 |
+
if sleep_data:
|
| 462 |
+
avg_sleep = sum(m.value for m in sleep_data) / len(sleep_data)
|
| 463 |
+
if avg_sleep < 6:
|
| 464 |
+
concerns.append(f"Insufficient sleep - averaging {avg_sleep:.1f} hours/night")
|
| 465 |
+
|
| 466 |
+
# Blood pressure monitoring
|
| 467 |
+
bp_systolic = [m for m in recent_metrics if m.metric_type == "blood_pressure_systolic"]
|
| 468 |
+
if bp_systolic:
|
| 469 |
+
recent_bp = bp_systolic[-1].value
|
| 470 |
+
if recent_bp > 150:
|
| 471 |
+
concerns.append(f"Blood pressure elevated - last reading {recent_bp} mmHg")
|
| 472 |
+
alerts.append({
|
| 473 |
+
"type": "vital_concern",
|
| 474 |
+
"severity": AlertLevel.HIGH.value,
|
| 475 |
+
"message": "Blood pressure significantly elevated",
|
| 476 |
+
"recommended_action": "Schedule urgent medical appointment"
|
| 477 |
+
})
|
| 478 |
+
|
| 479 |
+
state["health_metrics"] = [asdict(m) for m in recent_metrics]
|
| 480 |
+
state["concerns"].extend(concerns)
|
| 481 |
+
state["alerts"].extend(alerts)
|
| 482 |
+
|
| 483 |
+
return state
|
| 484 |
+
|
| 485 |
+
class MedicationAgent:
|
| 486 |
+
def __init__(self, llm):
|
| 487 |
+
self.llm = llm
|
| 488 |
+
self.mock_data = MockDataGenerator()
|
| 489 |
+
|
| 490 |
+
def check_medication_compliance(self, state: CareState) -> CareState:
|
| 491 |
+
"""Monitor medication adherence and identify issues"""
|
| 492 |
+
recent_meds = [m for m in self.mock_data.medication_data[state["parent_id"]]
|
| 493 |
+
if m["scheduled_time"] >= datetime.now() - timedelta(days=3)]
|
| 494 |
+
|
| 495 |
+
# Calculate compliance rates
|
| 496 |
+
compliance_summary = {}
|
| 497 |
+
for med_record in recent_meds:
|
| 498 |
+
med_name = med_record["medication"]
|
| 499 |
+
if med_name not in compliance_summary:
|
| 500 |
+
compliance_summary[med_name] = {"total": 0, "taken": 0, "missed_times": []}
|
| 501 |
+
|
| 502 |
+
compliance_summary[med_name]["total"] += 1
|
| 503 |
+
if med_record["was_taken"]:
|
| 504 |
+
compliance_summary[med_name]["taken"] += 1
|
| 505 |
+
else:
|
| 506 |
+
compliance_summary[med_name]["missed_times"].append(med_record["scheduled_time"])
|
| 507 |
+
|
| 508 |
+
# Generate alerts for poor compliance
|
| 509 |
+
alerts = []
|
| 510 |
+
concerns = []
|
| 511 |
+
|
| 512 |
+
for med_name, data in compliance_summary.items():
|
| 513 |
+
compliance_rate = data["taken"] / data["total"]
|
| 514 |
+
|
| 515 |
+
if compliance_rate < 0.8:
|
| 516 |
+
concerns.append(f"Poor medication compliance: {med_name} - {compliance_rate:.0%} over 3 days")
|
| 517 |
+
alerts.append({
|
| 518 |
+
"type": "medication_compliance",
|
| 519 |
+
"severity": AlertLevel.HIGH.value,
|
| 520 |
+
"message": f"{med_name} missed multiple doses - compliance at {compliance_rate:.0%}",
|
| 521 |
+
"recommended_action": "Review medication routine with family"
|
| 522 |
+
})
|
| 523 |
+
elif compliance_rate < 0.9:
|
| 524 |
+
concerns.append(f"Medication adherence concern: {med_name} - {compliance_rate:.0%}")
|
| 525 |
+
|
| 526 |
+
# Check for recent missed doses
|
| 527 |
+
recent_missed = [m for m in recent_meds
|
| 528 |
+
if not m["was_taken"] and
|
| 529 |
+
m["scheduled_time"] >= datetime.now() - timedelta(hours=24)]
|
| 530 |
+
|
| 531 |
+
if recent_missed:
|
| 532 |
+
for missed in recent_missed:
|
| 533 |
+
alerts.append({
|
| 534 |
+
"type": "missed_medication",
|
| 535 |
+
"severity": AlertLevel.MEDIUM.value,
|
| 536 |
+
"message": f"Missed {missed['medication']} at {missed['scheduled_time'].strftime('%I:%M %p')}",
|
| 537 |
+
"recommended_action": "Remind about missed dose"
|
| 538 |
+
})
|
| 539 |
+
|
| 540 |
+
state["medication_status"] = recent_meds
|
| 541 |
+
state["concerns"].extend(concerns)
|
| 542 |
+
state["alerts"].extend(alerts)
|
| 543 |
+
|
| 544 |
+
return state
|
| 545 |
+
|
| 546 |
+
class FamilyCommunicationAgent:
|
| 547 |
+
def __init__(self, llm):
|
| 548 |
+
self.llm = llm
|
| 549 |
+
self.mock_data = MockDataGenerator()
|
| 550 |
+
|
| 551 |
+
def generate_family_updates(self, state: CareState) -> CareState:
|
| 552 |
+
"""Create personalized updates for each family member"""
|
| 553 |
+
notifications = []
|
| 554 |
+
|
| 555 |
+
# Get urgency level
|
| 556 |
+
alert_levels = [alert["severity"] for alert in state["alerts"]]
|
| 557 |
+
max_severity = AlertLevel.LOW.value
|
| 558 |
+
if AlertLevel.URGENT.value in alert_levels:
|
| 559 |
+
max_severity = AlertLevel.URGENT.value
|
| 560 |
+
elif AlertLevel.HIGH.value in alert_levels:
|
| 561 |
+
max_severity = AlertLevel.HIGH.value
|
| 562 |
+
elif AlertLevel.MEDIUM.value in alert_levels:
|
| 563 |
+
max_severity = AlertLevel.MEDIUM.value
|
| 564 |
+
|
| 565 |
+
# Generate notifications for each family member
|
| 566 |
+
for family_member in self.mock_data.get_family_by_parent_id(state["parent_id"]):
|
| 567 |
+
prefs = family_member.notification_preferences
|
| 568 |
+
|
| 569 |
+
# Determine what to include based on preferences and severity
|
| 570 |
+
should_send_immediate = False
|
| 571 |
+
message_parts = []
|
| 572 |
+
|
| 573 |
+
if max_severity in [AlertLevel.URGENT.value, AlertLevel.HIGH.value] and prefs["urgent_alerts"]:
|
| 574 |
+
should_send_immediate = True
|
| 575 |
+
urgent_alerts = [a for a in state["alerts"] if a["severity"] in [AlertLevel.URGENT.value, AlertLevel.HIGH.value]]
|
| 576 |
+
message_parts.append("π¨ **Urgent Update Needed**")
|
| 577 |
+
for alert in urgent_alerts[:2]: # Limit to top 2 urgent items
|
| 578 |
+
message_parts.append(f"β’ {alert['message']}")
|
| 579 |
+
|
| 580 |
+
if prefs["daily_summary"]:
|
| 581 |
+
message_parts.append(f"\nπ **Daily Summary for {self.mock_data.get_parent_by_id(state['parent_id']).name}**")
|
| 582 |
+
|
| 583 |
+
# Health highlights
|
| 584 |
+
if state["health_metrics"]:
|
| 585 |
+
latest_metrics = {}
|
| 586 |
+
for metric in state["health_metrics"]:
|
| 587 |
+
if metric["metric_type"] not in latest_metrics or metric["timestamp"] > latest_metrics[metric["metric_type"]]["timestamp"]:
|
| 588 |
+
latest_metrics[metric["metric_type"]] = metric
|
| 589 |
+
|
| 590 |
+
message_parts.append("π **Today's Health Data:**")
|
| 591 |
+
if "steps" in latest_metrics:
|
| 592 |
+
steps = int(latest_metrics["steps"]["value"])
|
| 593 |
+
message_parts.append(f"β’ Activity: {steps:,} steps")
|
| 594 |
+
if "blood_glucose" in latest_metrics:
|
| 595 |
+
glucose = latest_metrics["blood_glucose"]["value"]
|
| 596 |
+
message_parts.append(f"β’ Blood Sugar: {glucose} mg/dL")
|
| 597 |
+
|
| 598 |
+
# Medication status
|
| 599 |
+
if prefs["medication_reminders"] and state["medication_status"]:
|
| 600 |
+
today_meds = [m for m in state["medication_status"]
|
| 601 |
+
if m["scheduled_time"].date() == datetime.now().date()]
|
| 602 |
+
taken_count = sum(1 for m in today_meds if m["was_taken"])
|
| 603 |
+
total_count = len(today_meds)
|
| 604 |
+
|
| 605 |
+
message_parts.append(f"π **Medications:** {taken_count}/{total_count} doses taken today")
|
| 606 |
+
|
| 607 |
+
if message_parts:
|
| 608 |
+
notifications.append({
|
| 609 |
+
"recipient": family_member.name,
|
| 610 |
+
"recipient_id": family_member.id,
|
| 611 |
+
"phone": family_member.phone,
|
| 612 |
+
"email": family_member.email,
|
| 613 |
+
"message": "\n".join(message_parts),
|
| 614 |
+
"urgency": max_severity,
|
| 615 |
+
"send_immediately": should_send_immediate,
|
| 616 |
+
"channels": ["sms"] if should_send_immediate else ["email"]
|
| 617 |
+
})
|
| 618 |
+
|
| 619 |
+
state["family_notifications"] = notifications
|
| 620 |
+
return state
|
| 621 |
+
|
| 622 |
+
def create_daily_summary(self, state: CareState) -> CareState:
|
| 623 |
+
"""Generate comprehensive daily summary"""
|
| 624 |
+
summary_parts = []
|
| 625 |
+
|
| 626 |
+
# Header with date and overall status
|
| 627 |
+
today = datetime.now().strftime("%A, %B %d, %Y")
|
| 628 |
+
summary_parts.append(f"# Daily Care Report - {today}")
|
| 629 |
+
summary_parts.append(f"**Parent:** {self.mock_data.get_parent_by_id(state['parent_id']).name}")
|
| 630 |
+
|
| 631 |
+
# Overall status indicator
|
| 632 |
+
if any(alert["severity"] == AlertLevel.URGENT.value for alert in state["alerts"]):
|
| 633 |
+
summary_parts.append("π΄ **Status: Needs Immediate Attention**")
|
| 634 |
+
elif any(alert["severity"] == AlertLevel.HIGH.value for alert in state["alerts"]):
|
| 635 |
+
summary_parts.append("π‘ **Status: Some Concerns**")
|
| 636 |
+
elif state["concerns"]:
|
| 637 |
+
summary_parts.append("π’ **Status: Monitoring**")
|
| 638 |
+
else:
|
| 639 |
+
summary_parts.append("β
**Status: All Good**")
|
| 640 |
+
|
| 641 |
+
# Key metrics summary
|
| 642 |
+
summary_parts.append("\n## Today's Highlights")
|
| 643 |
+
|
| 644 |
+
if state["health_metrics"]:
|
| 645 |
+
# Get today's key metrics
|
| 646 |
+
today_metrics = [m for m in state["health_metrics"]
|
| 647 |
+
if m["timestamp"].date() == datetime.now().date()]
|
| 648 |
+
|
| 649 |
+
if today_metrics:
|
| 650 |
+
latest_by_type = {}
|
| 651 |
+
for metric in today_metrics:
|
| 652 |
+
metric_type = metric["metric_type"]
|
| 653 |
+
if metric_type not in latest_by_type:
|
| 654 |
+
latest_by_type[metric_type] = metric
|
| 655 |
+
|
| 656 |
+
summary_parts.append("### Health Metrics")
|
| 657 |
+
for metric_type, data in latest_by_type.items():
|
| 658 |
+
value = data["value"]
|
| 659 |
+
unit = data["unit"]
|
| 660 |
+
|
| 661 |
+
if metric_type == "steps":
|
| 662 |
+
summary_parts.append(f"β’ **Activity:** {int(value):,} {unit}")
|
| 663 |
+
elif metric_type == "blood_glucose":
|
| 664 |
+
status = "π΄ High" if value > 150 else "π‘ Elevated" if value > 130 else "β
Normal"
|
| 665 |
+
summary_parts.append(f"β’ **Blood Sugar:** {value} {unit} {status}")
|
| 666 |
+
elif metric_type == "blood_pressure_systolic":
|
| 667 |
+
status = "π΄ High" if value > 140 else "π‘ Elevated" if value > 130 else "β
Normal"
|
| 668 |
+
summary_parts.append(f"β’ **Blood Pressure:** {value} {unit} {status}")
|
| 669 |
+
|
| 670 |
+
# Medication summary
|
| 671 |
+
if state["medication_status"]:
|
| 672 |
+
today_meds = [m for m in state["medication_status"]
|
| 673 |
+
if m["scheduled_time"].date() == datetime.now().date()]
|
| 674 |
+
|
| 675 |
+
if today_meds:
|
| 676 |
+
taken_count = sum(1 for m in today_meds if m["was_taken"])
|
| 677 |
+
total_count = len(today_meds)
|
| 678 |
+
|
| 679 |
+
summary_parts.append(f"\n### Medication Compliance")
|
| 680 |
+
summary_parts.append(f"β’ **Today:** {taken_count}/{total_count} doses taken")
|
| 681 |
+
|
| 682 |
+
if taken_count < total_count:
|
| 683 |
+
missed = [m for m in today_meds if not m["was_taken"]]
|
| 684 |
+
summary_parts.append("β’ **Missed doses:**")
|
| 685 |
+
for m in missed:
|
| 686 |
+
time_str = m["scheduled_time"].strftime("%I:%M %p")
|
| 687 |
+
summary_parts.append(f" - {m['medication']} at {time_str}")
|
| 688 |
+
|
| 689 |
+
# Concerns and alerts
|
| 690 |
+
if state["concerns"]:
|
| 691 |
+
summary_parts.append(f"\n## Areas of Attention ({len(state['concerns'])})")
|
| 692 |
+
for concern in state["concerns"]:
|
| 693 |
+
summary_parts.append(f"β’ {concern}")
|
| 694 |
+
|
| 695 |
+
# Action items
|
| 696 |
+
if state["action_items"]:
|
| 697 |
+
summary_parts.append(f"\n## Recommended Actions")
|
| 698 |
+
for action in state["action_items"]:
|
| 699 |
+
summary_parts.append(f"β’ {action}")
|
| 700 |
+
|
| 701 |
+
state["daily_summary"] = "\n".join(summary_parts)
|
| 702 |
+
return state
|
| 703 |
+
|
| 704 |
+
class ActionPlannerAgent:
|
| 705 |
+
def __init__(self, llm):
|
| 706 |
+
self.llm = llm
|
| 707 |
+
|
| 708 |
+
def generate_action_items(self, state: CareState) -> CareState:
|
| 709 |
+
"""Generate prioritized action items based on concerns and alerts"""
|
| 710 |
+
actions = []
|
| 711 |
+
|
| 712 |
+
# Process alerts to create specific actions
|
| 713 |
+
urgent_alerts = [a for a in state["alerts"] if a["severity"] == AlertLevel.URGENT.value]
|
| 714 |
+
high_alerts = [a for a in state["alerts"] if a["severity"] == AlertLevel.HIGH.value]
|
| 715 |
+
|
| 716 |
+
# Urgent actions first
|
| 717 |
+
for alert in urgent_alerts:
|
| 718 |
+
if alert["type"] == "vital_concern":
|
| 719 |
+
actions.append("π¨ URGENT: Schedule emergency medical appointment for blood pressure management")
|
| 720 |
+
elif alert["type"] == "medication_compliance":
|
| 721 |
+
actions.append(f"π¨ URGENT: Call parent immediately about missed {alert['message'].split()[0]} medications")
|
| 722 |
+
|
| 723 |
+
# High priority actions
|
| 724 |
+
for alert in high_alerts:
|
| 725 |
+
if alert["type"] == "health_concern":
|
| 726 |
+
actions.append("π HIGH: Contact diabetes care team about elevated glucose levels")
|
| 727 |
+
elif alert["type"] == "medication_compliance":
|
| 728 |
+
actions.append("π HIGH: Review medication routine - consider pill dispenser or reminder system")
|
| 729 |
+
|
| 730 |
+
# Medium priority actions based on concerns
|
| 731 |
+
concern_keywords = {
|
| 732 |
+
"low activity": "πͺ Encourage gentle exercise - suggest short walks or chair exercises",
|
| 733 |
+
"insufficient sleep": "π΄ Discuss sleep hygiene - check for pain or anxiety issues",
|
| 734 |
+
"blood pressure": "π©Ί Monitor blood pressure daily and log readings for doctor",
|
| 735 |
+
"medication": "π Set up automated medication reminders or family check-ins"
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
for concern in state["concerns"]:
|
| 739 |
+
concern_lower = concern.lower()
|
| 740 |
+
for keyword, action in concern_keywords.items():
|
| 741 |
+
if keyword in concern_lower and action not in actions:
|
| 742 |
+
actions.append(action)
|
| 743 |
+
|
| 744 |
+
# Always include routine actions
|
| 745 |
+
routine_actions = [
|
| 746 |
+
"π Review tomorrow's medication schedule",
|
| 747 |
+
"π± Confirm evening check-in call is scheduled",
|
| 748 |
+
"π Check health metrics sync from devices"
|
| 749 |
+
]
|
| 750 |
+
|
| 751 |
+
# Only add routine actions if no urgent/high priority actions
|
| 752 |
+
if not urgent_alerts and not high_alerts:
|
| 753 |
+
actions.extend(routine_actions)
|
| 754 |
+
|
| 755 |
+
# Limit to top 5 actions for manageability
|
| 756 |
+
state["action_items"] = actions[:5]
|
| 757 |
+
return state
|
| 758 |
+
|
| 759 |
+
# ============== MAIN LANGGRAPH WORKFLOW ==============
|
| 760 |
+
|
| 761 |
+
class CareLoopOrchestrator:
|
| 762 |
+
def __init__(self):
|
| 763 |
+
# For demo purposes, we'll mock the LLM
|
| 764 |
+
self.llm = None # ChatOpenAI would go here in production
|
| 765 |
+
|
| 766 |
+
# Initialize mock data
|
| 767 |
+
self.mock_data = MockDataGenerator()
|
| 768 |
+
|
| 769 |
+
# Initialize specialized agents
|
| 770 |
+
self.health_monitor = HealthMonitorAgent(self.llm)
|
| 771 |
+
self.medication_agent = MedicationAgent(self.llm)
|
| 772 |
+
self.family_communicator = FamilyCommunicationAgent(self.llm)
|
| 773 |
+
self.action_planner = ActionPlannerAgent(self.llm)
|
| 774 |
+
|
| 775 |
+
# Build the workflow graph
|
| 776 |
+
self.graph = self._build_workflow()
|
| 777 |
+
|
| 778 |
+
def _build_workflow(self) -> StateGraph:
|
| 779 |
+
"""Build the LangGraph workflow for daily care monitoring"""
|
| 780 |
+
workflow = StateGraph(CareState)
|
| 781 |
+
|
| 782 |
+
# Add nodes for each step
|
| 783 |
+
workflow.add_node("health_analysis", self._health_analysis_node)
|
| 784 |
+
workflow.add_node("medication_check", self._medication_check_node)
|
| 785 |
+
workflow.add_node("generate_summary", self._generate_summary_node)
|
| 786 |
+
workflow.add_node("send_family_notifications", self._family_notifications_node)
|
| 787 |
+
workflow.add_node("action_planning", self._action_planning_node)
|
| 788 |
+
workflow.add_node("emergency_check", self._emergency_check_node)
|
| 789 |
+
|
| 790 |
+
# Define the workflow
|
| 791 |
+
workflow.set_entry_point("health_analysis")
|
| 792 |
+
workflow.add_edge("health_analysis", "medication_check")
|
| 793 |
+
workflow.add_edge("medication_check", "emergency_check")
|
| 794 |
+
workflow.add_conditional_edges(
|
| 795 |
+
"emergency_check",
|
| 796 |
+
self._should_trigger_emergency,
|
| 797 |
+
{
|
| 798 |
+
"emergency": "send_family_notifications",
|
| 799 |
+
"continue": "generate_summary"
|
| 800 |
+
}
|
| 801 |
+
)
|
| 802 |
+
workflow.add_edge("generate_summary", "send_family_notifications")
|
| 803 |
+
workflow.add_edge("send_family_notifications", "action_planning")
|
| 804 |
+
workflow.add_edge("action_planning", END)
|
| 805 |
+
|
| 806 |
+
return workflow.compile()
|
| 807 |
+
|
| 808 |
+
def _health_analysis_node(self, state: CareState) -> CareState:
|
| 809 |
+
return self.health_monitor.analyze_health_patterns(state)
|
| 810 |
+
|
| 811 |
+
def _medication_check_node(self, state: CareState) -> CareState:
|
| 812 |
+
return self.medication_agent.check_medication_compliance(state)
|
| 813 |
+
|
| 814 |
+
def _generate_summary_node(self, state: CareState) -> CareState:
|
| 815 |
+
return self.family_communicator.create_daily_summary(state)
|
| 816 |
+
|
| 817 |
+
def _family_notifications_node(self, state: CareState) -> CareState:
|
| 818 |
+
return self.family_communicator.generate_family_updates(state)
|
| 819 |
+
|
| 820 |
+
def _action_planning_node(self, state: CareState) -> CareState:
|
| 821 |
+
return self.action_planner.generate_action_items(state)
|
| 822 |
+
|
| 823 |
+
def _emergency_check_node(self, state: CareState) -> CareState:
|
| 824 |
+
"""Check if emergency response is needed"""
|
| 825 |
+
urgent_alerts = [a for a in state["alerts"] if a["severity"] == AlertLevel.URGENT.value]
|
| 826 |
+
if urgent_alerts:
|
| 827 |
+
state["emergency_level"] = "urgent"
|
| 828 |
+
else:
|
| 829 |
+
state["emergency_level"] = "normal"
|
| 830 |
+
return state
|
| 831 |
+
|
| 832 |
+
def _should_trigger_emergency(self, state: CareState) -> str:
|
| 833 |
+
"""Decide whether to trigger emergency protocol"""
|
| 834 |
+
return "emergency" if state["emergency_level"] == "urgent" else "continue"
|
| 835 |
+
|
| 836 |
+
def run_daily_care_cycle(self, parent_id: str) -> Dict[str, Any]:
|
| 837 |
+
"""Execute the complete daily care monitoring workflow"""
|
| 838 |
+
|
| 839 |
+
# Validate parent_id exists
|
| 840 |
+
if parent_id not in self.mock_data.parents:
|
| 841 |
+
raise ValueError(f"Parent ID {parent_id} not found")
|
| 842 |
+
|
| 843 |
+
initial_state = {
|
| 844 |
+
"parent_id": parent_id,
|
| 845 |
+
"date": datetime.now().strftime("%Y-%m-%d"),
|
| 846 |
+
"health_metrics": [],
|
| 847 |
+
"medication_status": [],
|
| 848 |
+
"concerns": [],
|
| 849 |
+
"alerts": [],
|
| 850 |
+
"daily_summary": "",
|
| 851 |
+
"action_items": [],
|
| 852 |
+
"family_notifications": [],
|
| 853 |
+
"emergency_level": "normal"
|
| 854 |
+
}
|
| 855 |
+
|
| 856 |
+
# Run the workflow
|
| 857 |
+
result = self.graph.invoke(initial_state)
|
| 858 |
+
|
| 859 |
+
# Add timestamp and processing info
|
| 860 |
+
result["processed_at"] = datetime.now().isoformat()
|
| 861 |
+
result["next_check"] = (datetime.now() + timedelta(hours=24)).isoformat()
|
| 862 |
+
|
| 863 |
+
return result
|
| 864 |
+
|
| 865 |
+
# ============== WEB INTERFACE ==============
|
| 866 |
+
|
| 867 |
+
# Initialize FastAPI app
|
| 868 |
+
app = FastAPI(title="CareLoop - AI-Powered Family Caregiving")
|
| 869 |
+
|
| 870 |
+
# Initialize the care orchestrator
|
| 871 |
+
care_system = CareLoopOrchestrator()
|
| 872 |
+
|
| 873 |
+
# Store for demo data
|
| 874 |
+
demo_reports = []
|
| 875 |
+
|
| 876 |
+
@app.get("/", response_class=HTMLResponse)
|
| 877 |
+
async def get_dashboard():
|
| 878 |
+
"""Serve the main dashboard"""
|
| 879 |
+
html_content = """
|
| 880 |
+
<!DOCTYPE html>
|
| 881 |
+
<html lang="en">
|
| 882 |
+
<head>
|
| 883 |
+
<meta charset="UTF-8">
|
| 884 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 885 |
+
<title>CareLoop Dashboard</title>
|
| 886 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 887 |
+
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
| 888 |
+
</head>
|
| 889 |
+
<body class="bg-gray-50">
|
| 890 |
+
<div x-data="careloop()" class="min-h-screen">
|
| 891 |
+
<!-- Header -->
|
| 892 |
+
<header class="bg-blue-600 text-white p-4">
|
| 893 |
+
<div class="max-w-6xl mx-auto flex justify-between items-center">
|
| 894 |
+
<h1 class="text-2xl font-bold">π CareLoop</h1>
|
| 895 |
+
<p class="text-blue-100">AI-Powered Family Caregiving</p>
|
| 896 |
+
</div>
|
| 897 |
+
</header>
|
| 898 |
+
|
| 899 |
+
<!-- Main Content -->
|
| 900 |
+
<main class="max-w-6xl mx-auto p-6">
|
| 901 |
+
<!-- Demo Controls -->
|
| 902 |
+
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
| 903 |
+
<h2 class="text-xl font-semibold mb-4">Hackathon Demo</h2>
|
| 904 |
+
<div class="flex flex-col md:flex-row md:items-center mb-6 space-y-4 md:space-y-0 md:space-x-4">
|
| 905 |
+
<div class="flex-1">
|
| 906 |
+
<label for="parent-select" class="block text-sm font-medium text-gray-700 mb-1">Select Parent:</label>
|
| 907 |
+
<select
|
| 908 |
+
id="parent-select"
|
| 909 |
+
x-model="selectedParent"
|
| 910 |
+
@change="loadParentInfo()"
|
| 911 |
+
class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
|
| 912 |
+
<option value="parent_001">Margaret Chen (78) - Diabetes/Hypertension</option>
|
| 913 |
+
<option value="parent_002">Robert Johnson (72) - Stroke Recovery</option>
|
| 914 |
+
<option value="parent_003">Elena Gonzalez (81) - Early Alzheimer's</option>
|
| 915 |
+
</select>
|
| 916 |
+
</div>
|
| 917 |
+
<div class="flex-none">
|
| 918 |
+
<button @click="runCareCheck()"
|
| 919 |
+
class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 w-full md:w-auto"
|
| 920 |
+
:disabled="loading">
|
| 921 |
+
<span x-show="!loading">π Run Daily Care Check</span>
|
| 922 |
+
<span x-show="loading">π Analyzing...</span>
|
| 923 |
+
</button>
|
| 924 |
+
</div>
|
| 925 |
+
</div>
|
| 926 |
+
|
| 927 |
+
<div x-show="parentInfo" class="bg-blue-50 p-4 rounded-lg text-sm">
|
| 928 |
+
<h3 class="font-medium text-blue-800" x-text="parentInfo.parent.name + ' (' + parentInfo.parent.age + ')'"></h3>
|
| 929 |
+
<div class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-2">
|
| 930 |
+
<div>
|
| 931 |
+
<p class="text-blue-600 font-medium">Health Conditions:</p>
|
| 932 |
+
<ul class="list-disc list-inside text-blue-700 pl-2">
|
| 933 |
+
<template x-for="condition in parentInfo.parent.conditions">
|
| 934 |
+
<li x-text="condition"></li>
|
| 935 |
+
</template>
|
| 936 |
+
</ul>
|
| 937 |
+
</div>
|
| 938 |
+
<div>
|
| 939 |
+
<p class="text-blue-600 font-medium">Family Caregivers:</p>
|
| 940 |
+
<ul class="list-disc list-inside text-blue-700 pl-2">
|
| 941 |
+
<template x-for="member in parentInfo.family">
|
| 942 |
+
<li x-text="member.name + ' (' + member.relationship + ')'"></li>
|
| 943 |
+
</template>
|
| 944 |
+
</ul>
|
| 945 |
+
</div>
|
| 946 |
+
</div>
|
| 947 |
+
</div>
|
| 948 |
+
</div>
|
| 949 |
+
|
| 950 |
+
<!-- Results -->
|
| 951 |
+
<div x-show="report" class="space-y-6">
|
| 952 |
+
<!-- Status Overview -->
|
| 953 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 954 |
+
<h3 class="text-lg font-semibold mb-4">π Care Status Overview</h3>
|
| 955 |
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
| 956 |
+
<div class="bg-green-50 p-4 rounded-lg">
|
| 957 |
+
<h4 class="font-medium text-green-800">Health Metrics</h4>
|
| 958 |
+
<p class="text-green-600" x-text="report?.health_metrics?.length + ' data points collected'"></p>
|
| 959 |
+
</div>
|
| 960 |
+
<div class="bg-blue-50 p-4 rounded-lg">
|
| 961 |
+
<h4 class="font-medium text-blue-800">Medication Status</h4>
|
| 962 |
+
<p class="text-blue-600" x-text="report?.medication_status?.length + ' medication events'"></p>
|
| 963 |
+
</div>
|
| 964 |
+
<div class="bg-orange-50 p-4 rounded-lg">
|
| 965 |
+
<h4 class="font-medium text-orange-800">Alerts Generated</h4>
|
| 966 |
+
<p class="text-orange-600" x-text="report?.alerts?.length + ' alerts for family'"></p>
|
| 967 |
+
</div>
|
| 968 |
+
</div>
|
| 969 |
+
</div>
|
| 970 |
+
|
| 971 |
+
<!-- Daily Summary -->
|
| 972 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 973 |
+
<h3 class="text-lg font-semibold mb-4">π Daily Summary</h3>
|
| 974 |
+
<div class="prose max-w-none">
|
| 975 |
+
<pre x-text="report?.daily_summary" class="whitespace-pre-wrap font-sans text-sm bg-gray-50 p-4 rounded"></pre>
|
| 976 |
+
</div>
|
| 977 |
+
</div>
|
| 978 |
+
|
| 979 |
+
<!-- Action Items -->
|
| 980 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 981 |
+
<h3 class="text-lg font-semibold mb-4">β
Recommended Actions</h3>
|
| 982 |
+
<ul class="space-y-2">
|
| 983 |
+
<template x-for="action in report?.action_items">
|
| 984 |
+
<li class="flex items-start space-x-2">
|
| 985 |
+
<span class="text-blue-600">β’</span>
|
| 986 |
+
<span x-text="action"></span>
|
| 987 |
+
</li>
|
| 988 |
+
</template>
|
| 989 |
+
</ul>
|
| 990 |
+
</div>
|
| 991 |
+
|
| 992 |
+
<!-- Family Notifications -->
|
| 993 |
+
<div class="bg-white rounded-lg shadow p-6">
|
| 994 |
+
<h3 class="text-lg font-semibold mb-4">π± Family Notifications</h3>
|
| 995 |
+
<div class="space-y-4">
|
| 996 |
+
<template x-for="notification in report?.family_notifications">
|
| 997 |
+
<div class="border-l-4 border-blue-500 pl-4 py-2">
|
| 998 |
+
<h4 class="font-medium" x-text="'To: ' + notification.recipient + ' (' + notification.channels.join(', ') + ')'"></h4>
|
| 999 |
+
<pre x-text="notification.message" class="whitespace-pre-wrap text-sm text-gray-600 mt-2"></pre>
|
| 1000 |
+
</div>
|
| 1001 |
+
</template>
|
| 1002 |
+
</div>
|
| 1003 |
+
</div>
|
| 1004 |
+
|
| 1005 |
+
<!-- Alerts Detail -->
|
| 1006 |
+
<div x-show="report?.alerts?.length > 0" class="bg-white rounded-lg shadow p-6">
|
| 1007 |
+
<h3 class="text-lg font-semibold mb-4">β οΈ Care Alerts</h3>
|
| 1008 |
+
<div class="space-y-3">
|
| 1009 |
+
<template x-for="alert in report?.alerts">
|
| 1010 |
+
<div class="border-l-4 pl-4 py-2"
|
| 1011 |
+
:class="{
|
| 1012 |
+
'border-red-500 bg-red-50': alert.severity === 'urgent',
|
| 1013 |
+
'border-orange-500 bg-orange-50': alert.severity === 'high',
|
| 1014 |
+
'border-yellow-500 bg-yellow-50': alert.severity === 'medium',
|
| 1015 |
+
'border-blue-500 bg-blue-50': alert.severity === 'low'
|
| 1016 |
+
}">
|
| 1017 |
+
<div class="flex justify-between items-start">
|
| 1018 |
+
<div>
|
| 1019 |
+
<h4 class="font-medium" x-text="alert.message"></h4>
|
| 1020 |
+
<p class="text-sm text-gray-600" x-text="'Type: ' + alert.type + ' | Severity: ' + alert.severity"></p>
|
| 1021 |
+
<p class="text-sm mt-1" x-text="'Recommended: ' + alert.recommended_action"></p>
|
| 1022 |
+
</div>
|
| 1023 |
+
</div>
|
| 1024 |
+
</div>
|
| 1025 |
+
</template>
|
| 1026 |
+
</div>
|
| 1027 |
+
</div>
|
| 1028 |
+
</div>
|
| 1029 |
+
</main>
|
| 1030 |
+
</div>
|
| 1031 |
+
|
| 1032 |
+
<script>
|
| 1033 |
+
function careloop() {
|
| 1034 |
+
return {
|
| 1035 |
+
loading: false,
|
| 1036 |
+
report: null,
|
| 1037 |
+
parentInfo: null,
|
| 1038 |
+
selectedParent: "parent_001",
|
| 1039 |
+
|
| 1040 |
+
init() {
|
| 1041 |
+
this.loadParentInfo();
|
| 1042 |
+
},
|
| 1043 |
+
|
| 1044 |
+
async loadParentInfo() {
|
| 1045 |
+
try {
|
| 1046 |
+
const response = await fetch(`/api/parent/${this.selectedParent}`);
|
| 1047 |
+
this.parentInfo = await response.json();
|
| 1048 |
+
// Clear previous report when changing parents
|
| 1049 |
+
this.report = null;
|
| 1050 |
+
} catch (error) {
|
| 1051 |
+
console.error('Error loading parent info:', error);
|
| 1052 |
+
}
|
| 1053 |
+
},
|
| 1054 |
+
|
| 1055 |
+
async runCareCheck() {
|
| 1056 |
+
this.loading = true;
|
| 1057 |
+
try {
|
| 1058 |
+
const response = await fetch(`/api/care-check/${this.selectedParent}`, {
|
| 1059 |
+
method: 'POST'
|
| 1060 |
+
});
|
| 1061 |
+
this.report = await response.json();
|
| 1062 |
+
} catch (error) {
|
| 1063 |
+
console.error('Error running care check:', error);
|
| 1064 |
+
} finally {
|
| 1065 |
+
this.loading = false;
|
| 1066 |
+
}
|
| 1067 |
+
}
|
| 1068 |
+
}
|
| 1069 |
+
}
|
| 1070 |
+
</script>
|
| 1071 |
+
</body>
|
| 1072 |
+
</html>
|
| 1073 |
+
"""
|
| 1074 |
+
return HTMLResponse(content=html_content)
|
| 1075 |
+
|
| 1076 |
+
@app.post("/api/care-check/{parent_id}")
|
| 1077 |
+
async def run_care_check(parent_id: str):
|
| 1078 |
+
"""Run the daily care check for a parent"""
|
| 1079 |
+
try:
|
| 1080 |
+
result = care_system.run_daily_care_cycle(parent_id)
|
| 1081 |
+
demo_reports.append(result)
|
| 1082 |
+
return result
|
| 1083 |
+
except Exception as e:
|
| 1084 |
+
return {"error": str(e)}
|
| 1085 |
+
|
| 1086 |
+
@app.get("/api/reports")
|
| 1087 |
+
async def get_reports():
|
| 1088 |
+
"""Get all demo reports"""
|
| 1089 |
+
return {"reports": demo_reports}
|
| 1090 |
+
|
| 1091 |
+
@app.get("/api/parent/{parent_id}")
|
| 1092 |
+
async def get_parent_info(parent_id: str):
|
| 1093 |
+
"""Get parent information"""
|
| 1094 |
+
mock_data = MockDataGenerator()
|
| 1095 |
+
return {
|
| 1096 |
+
"parent": asdict(mock_data.get_parent_by_id(parent_id)),
|
| 1097 |
+
"family": [asdict(fm) for fm in mock_data.get_family_by_parent_id(parent_id)]
|
| 1098 |
+
}
|
| 1099 |
+
|
| 1100 |
+
# ============== MAIN EXECUTION ==============
|
| 1101 |
+
|
| 1102 |
+
if __name__ == "__main__":
|
| 1103 |
+
print("π Starting CareLoop Hackathon Demo")
|
| 1104 |
+
print("=" * 50)
|
| 1105 |
+
|
| 1106 |
+
# Load mock data
|
| 1107 |
+
mock_data = MockDataGenerator()
|
| 1108 |
+
|
| 1109 |
+
# Display available parents
|
| 1110 |
+
print("π Available Parent Profiles:")
|
| 1111 |
+
for parent_id, parent in mock_data.parents.items():
|
| 1112 |
+
family_count = len(mock_data.families.get(parent_id, []))
|
| 1113 |
+
conditions = ", ".join(parent.conditions[:2]) + ("..." if len(parent.conditions) > 2 else "")
|
| 1114 |
+
print(f" β’ {parent.name} ({parent.age}) - ID: {parent_id}")
|
| 1115 |
+
print(f" Health: {conditions}")
|
| 1116 |
+
print(f" Family caregivers: {family_count}")
|
| 1117 |
+
|
| 1118 |
+
# Run a sample care check for demo
|
| 1119 |
+
print("\nπ Running sample care analysis for Margaret Chen...")
|
| 1120 |
+
result = care_system.run_daily_care_cycle("parent_001")
|
| 1121 |
+
|
| 1122 |
+
print(f"\nπ Daily Summary:")
|
| 1123 |
+
print(result["daily_summary"])
|
| 1124 |
+
|
| 1125 |
+
print(f"\nβ
Action Items ({len(result['action_items'])}):")
|
| 1126 |
+
for action in result["action_items"]:
|
| 1127 |
+
print(f" β’ {action}")
|
| 1128 |
+
|
| 1129 |
+
print(f"\nπ± Family Notifications ({len(result['family_notifications'])}):")
|
| 1130 |
+
for notification in result["family_notifications"]:
|
| 1131 |
+
print(f" β {notification['recipient']}: {notification['urgency']} priority")
|
| 1132 |
+
|
| 1133 |
+
print(f"\nβ οΈ Alerts Generated ({len(result['alerts'])}):")
|
| 1134 |
+
for alert in result["alerts"]:
|
| 1135 |
+
print(f" β’ {alert['severity'].upper()}: {alert['message']}")
|
| 1136 |
+
|
| 1137 |
+
print("\n" + "=" * 50)
|
| 1138 |
+
print("π Starting web interface...")
|
| 1139 |
+
print("Open your browser to: http://localhost:8000")
|
| 1140 |
+
print("Select a parent and click 'Run Daily Care Check' to see the AI agents in action!")
|
| 1141 |
+
|
| 1142 |
+
# Start the web server
|
| 1143 |
+
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
|
config.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# config.py
|
| 2 |
+
# CareLoop Configuration Management
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Dict, List, Optional
|
| 7 |
+
|
| 8 |
+
@dataclass
|
| 9 |
+
class DatabaseConfig:
|
| 10 |
+
"""Database configuration settings"""
|
| 11 |
+
url: str = "sqlite:///careloop_demo.db"
|
| 12 |
+
echo: bool = False
|
| 13 |
+
pool_size: int = 10
|
| 14 |
+
max_overflow: int = 20
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class AIConfig:
|
| 18 |
+
"""AI and LLM configuration"""
|
| 19 |
+
openai_api_key: str = "demo-key-not-required"
|
| 20 |
+
model_name: str = "gpt-4"
|
| 21 |
+
temperature: float = 0.1
|
| 22 |
+
max_tokens: int = 2000
|
| 23 |
+
# For hackathon demo, we mock AI responses
|
| 24 |
+
use_mock_responses: bool = True
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class NotificationConfig:
|
| 28 |
+
"""Notification service configuration"""
|
| 29 |
+
twilio_account_sid: str = "demo-account"
|
| 30 |
+
twilio_auth_token: str = "demo-token"
|
| 31 |
+
twilio_phone_number: str = "+1-555-CARELOOP"
|
| 32 |
+
email_service: str = "sendgrid"
|
| 33 |
+
email_api_key: str = "demo-email-key"
|
| 34 |
+
|
| 35 |
+
@dataclass
|
| 36 |
+
class HealthIntegrationConfig:
|
| 37 |
+
"""Health device and service integrations"""
|
| 38 |
+
apple_health_enabled: bool = True
|
| 39 |
+
fitbit_enabled: bool = True
|
| 40 |
+
omron_enabled: bool = True # Blood pressure monitors
|
| 41 |
+
dexcom_enabled: bool = False # Continuous glucose monitoring
|
| 42 |
+
epic_fhir_enabled: bool = False # Electronic health records
|
| 43 |
+
|
| 44 |
+
@dataclass
|
| 45 |
+
class SecurityConfig:
|
| 46 |
+
"""Security and privacy settings"""
|
| 47 |
+
encryption_key: str = "demo-encryption-key-32-chars-long"
|
| 48 |
+
jwt_secret: str = "demo-jwt-secret-key"
|
| 49 |
+
session_timeout_hours: int = 24
|
| 50 |
+
max_login_attempts: int = 5
|
| 51 |
+
require_2fa: bool = False # For demo
|
| 52 |
+
|
| 53 |
+
@dataclass
|
| 54 |
+
class CareLoopConfig:
|
| 55 |
+
"""Main application configuration"""
|
| 56 |
+
# Subsystem configs - non-default parameters must come first
|
| 57 |
+
database: DatabaseConfig
|
| 58 |
+
ai: AIConfig
|
| 59 |
+
notifications: NotificationConfig
|
| 60 |
+
health_integrations: HealthIntegrationConfig
|
| 61 |
+
security: SecurityConfig
|
| 62 |
+
|
| 63 |
+
# Environment
|
| 64 |
+
environment: str = "development"
|
| 65 |
+
debug: bool = True
|
| 66 |
+
|
| 67 |
+
# Web server
|
| 68 |
+
host: str = "0.0.0.0"
|
| 69 |
+
port: int = 8000
|
| 70 |
+
workers: int = 1
|
| 71 |
+
|
| 72 |
+
# Feature flags
|
| 73 |
+
enable_web_interface: bool = True
|
| 74 |
+
enable_api: bool = True
|
| 75 |
+
enable_websockets: bool = True
|
| 76 |
+
enable_background_tasks: bool = True
|
| 77 |
+
|
| 78 |
+
# Monitoring intervals
|
| 79 |
+
health_check_interval_hours: int = 4
|
| 80 |
+
medication_check_interval_hours: int = 2
|
| 81 |
+
emergency_check_interval_minutes: int = 15
|
| 82 |
+
|
| 83 |
+
def __init__(self):
|
| 84 |
+
self.database = DatabaseConfig()
|
| 85 |
+
self.ai = AIConfig()
|
| 86 |
+
self.notifications = NotificationConfig()
|
| 87 |
+
self.health_integrations = HealthIntegrationConfig()
|
| 88 |
+
self.security = SecurityConfig()
|
| 89 |
+
|
| 90 |
+
def load_config() -> CareLoopConfig:
|
| 91 |
+
"""Load configuration from environment variables"""
|
| 92 |
+
config = CareLoopConfig()
|
| 93 |
+
|
| 94 |
+
# Override with environment variables if present
|
| 95 |
+
config.environment = os.getenv("CARELOOP_ENV", "development")
|
| 96 |
+
config.debug = os.getenv("CARELOOP_DEBUG", "true").lower() == "true"
|
| 97 |
+
config.host = os.getenv("CARELOOP_HOST", "0.0.0.0")
|
| 98 |
+
config.port = int(os.getenv("CARELOOP_PORT", "8000"))
|
| 99 |
+
|
| 100 |
+
# Database
|
| 101 |
+
config.database.url = os.getenv("DATABASE_URL", "sqlite:///careloop_demo.db")
|
| 102 |
+
|
| 103 |
+
# AI Configuration
|
| 104 |
+
config.ai.openai_api_key = os.getenv("OPENAI_API_KEY", "demo-key")
|
| 105 |
+
config.ai.use_mock_responses = os.getenv("USE_MOCK_AI", "true").lower() == "true"
|
| 106 |
+
|
| 107 |
+
# Notifications
|
| 108 |
+
config.notifications.twilio_account_sid = os.getenv("TWILIO_ACCOUNT_SID", "demo")
|
| 109 |
+
config.notifications.twilio_auth_token = os.getenv("TWILIO_AUTH_TOKEN", "demo")
|
| 110 |
+
config.notifications.email_api_key = os.getenv("EMAIL_API_KEY", "demo")
|
| 111 |
+
|
| 112 |
+
# Security
|
| 113 |
+
config.security.encryption_key = os.getenv("ENCRYPTION_KEY", "demo-key-32-chars-very-secure!!")
|
| 114 |
+
config.security.jwt_secret = os.getenv("JWT_SECRET", "demo-jwt-secret")
|
| 115 |
+
|
| 116 |
+
return config
|
| 117 |
+
|
| 118 |
+
# Global config instance
|
| 119 |
+
CONFIG = load_config()
|
| 120 |
+
|
| 121 |
+
# Environment-specific configurations
|
| 122 |
+
ENVIRONMENT_CONFIGS = {
|
| 123 |
+
"development": {
|
| 124 |
+
"debug": True,
|
| 125 |
+
"log_level": "DEBUG",
|
| 126 |
+
"database_echo": True,
|
| 127 |
+
"use_mock_integrations": True,
|
| 128 |
+
"enable_hot_reload": True
|
| 129 |
+
},
|
| 130 |
+
"staging": {
|
| 131 |
+
"debug": False,
|
| 132 |
+
"log_level": "INFO",
|
| 133 |
+
"database_echo": False,
|
| 134 |
+
"use_mock_integrations": True,
|
| 135 |
+
"enable_hot_reload": False
|
| 136 |
+
},
|
| 137 |
+
"production": {
|
| 138 |
+
"debug": False,
|
| 139 |
+
"log_level": "WARNING",
|
| 140 |
+
"database_echo": False,
|
| 141 |
+
"use_mock_integrations": False,
|
| 142 |
+
"enable_hot_reload": False,
|
| 143 |
+
"require_https": True,
|
| 144 |
+
"enable_rate_limiting": True
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
def get_environment_config(env: str) -> Dict:
|
| 149 |
+
"""Get configuration for specific environment"""
|
| 150 |
+
return ENVIRONMENT_CONFIGS.get(env, ENVIRONMENT_CONFIGS["development"])
|
| 151 |
+
|
| 152 |
+
if __name__ == "__main__":
|
| 153 |
+
print("CareLoop Configuration")
|
| 154 |
+
print("=" * 30)
|
| 155 |
+
print(f"Environment: {CONFIG.environment}")
|
| 156 |
+
print(f"Debug mode: {CONFIG.debug}")
|
| 157 |
+
print(f"Database: {CONFIG.database.url}")
|
| 158 |
+
print(f"AI Mock mode: {CONFIG.ai.use_mock_responses}")
|
| 159 |
+
print(f"Web interface: {CONFIG.enable_web_interface}")
|
| 160 |
+
print(f"Host: {CONFIG.host}:{CONFIG.port}")
|
demo_scenarios.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CareLoop Demo Script - Run Different Scenarios
|
| 2 |
+
# This script demonstrates various AI agent capabilities
|
| 3 |
+
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
| 7 |
+
|
| 8 |
+
from careloop_main import CareLoopOrchestrator, MockDataGenerator, AlertLevel
|
| 9 |
+
from datetime import datetime, timedelta
|
| 10 |
+
import random
|
| 11 |
+
|
| 12 |
+
class CareLoopDemo:
|
| 13 |
+
def __init__(self):
|
| 14 |
+
self.care_system = CareLoopOrchestrator()
|
| 15 |
+
self.mock_data = MockDataGenerator()
|
| 16 |
+
|
| 17 |
+
def print_banner(self, title: str):
|
| 18 |
+
"""Print a nice banner for demo sections"""
|
| 19 |
+
print("\n" + "="*60)
|
| 20 |
+
print(f"π― {title}")
|
| 21 |
+
print("="*60)
|
| 22 |
+
|
| 23 |
+
def print_section(self, title: str):
|
| 24 |
+
"""Print a section header"""
|
| 25 |
+
print(f"\nπ {title}")
|
| 26 |
+
print("-" * 40)
|
| 27 |
+
|
| 28 |
+
def run_normal_day_scenario(self):
|
| 29 |
+
"""Scenario 1: Normal day with good health metrics"""
|
| 30 |
+
self.print_banner("SCENARIO 1: Normal Day - Everything Going Well")
|
| 31 |
+
|
| 32 |
+
print("Margaret had a good day:")
|
| 33 |
+
print("β’ Took all medications on time")
|
| 34 |
+
print("β’ Blood glucose levels stable (110-125 mg/dL)")
|
| 35 |
+
print("β’ Good activity level (3,200 steps)")
|
| 36 |
+
print("β’ Slept well (7.5 hours)")
|
| 37 |
+
print("β’ Blood pressure normal (128/78)")
|
| 38 |
+
|
| 39 |
+
# Run the care check
|
| 40 |
+
result = self.care_system.run_daily_care_cycle("parent_001")
|
| 41 |
+
|
| 42 |
+
self.print_section("AI Agent Analysis")
|
| 43 |
+
print(f"Concerns detected: {len(result['concerns'])}")
|
| 44 |
+
print(f"Alerts generated: {len(result['alerts'])}")
|
| 45 |
+
print(f"Action items: {len(result['action_items'])}")
|
| 46 |
+
|
| 47 |
+
if result['concerns']:
|
| 48 |
+
print("\nConcerns:")
|
| 49 |
+
for concern in result['concerns']:
|
| 50 |
+
print(f" β’ {concern}")
|
| 51 |
+
else:
|
| 52 |
+
print("β
No concerns detected - everything looks good!")
|
| 53 |
+
|
| 54 |
+
self.print_section("Family Notifications")
|
| 55 |
+
for notification in result['family_notifications']:
|
| 56 |
+
print(f"β {notification['recipient']} ({notification['urgency']} priority)")
|
| 57 |
+
print(f" Channels: {', '.join(notification['channels'])}")
|
| 58 |
+
|
| 59 |
+
return result
|
| 60 |
+
|
| 61 |
+
def run_medication_issues_scenario(self):
|
| 62 |
+
"""Scenario 2: Medication compliance problems"""
|
| 63 |
+
self.print_banner("SCENARIO 2: Medication Compliance Issues")
|
| 64 |
+
|
| 65 |
+
print("Margaret had medication troubles:")
|
| 66 |
+
print("β’ Missed evening Metformin dose yesterday")
|
| 67 |
+
print("β’ Forgot Lisinopril this morning")
|
| 68 |
+
print("β’ Late with Donepezil twice this week")
|
| 69 |
+
print("β’ Blood glucose elevated due to missed Metformin")
|
| 70 |
+
|
| 71 |
+
result = self.care_system.run_daily_care_cycle("parent_001")
|
| 72 |
+
|
| 73 |
+
self.print_section("AI Agent Response")
|
| 74 |
+
high_alerts = [a for a in result['alerts'] if a['severity'] == 'high']
|
| 75 |
+
print(f"π¨ High priority alerts: {len(high_alerts)}")
|
| 76 |
+
print(f"π Action items generated: {len(result['action_items'])}")
|
| 77 |
+
|
| 78 |
+
for alert in high_alerts:
|
| 79 |
+
print(f"\nπ΄ {alert['severity'].upper()}: {alert['message']}")
|
| 80 |
+
print(f" Recommended: {alert['recommended_action']}")
|
| 81 |
+
|
| 82 |
+
return result
|
| 83 |
+
|
| 84 |
+
def run_family_coordination_scenario(self):
|
| 85 |
+
"""Scenario 3: Complex family coordination"""
|
| 86 |
+
self.print_banner("SCENARIO 3: Multi-Family Member Coordination")
|
| 87 |
+
|
| 88 |
+
print("Family dynamics in action:")
|
| 89 |
+
print("β’ David (son): Primary caregiver, wants all details")
|
| 90 |
+
print("β’ Lisa (daughter): Backup caregiver, weekly summaries only")
|
| 91 |
+
print("β’ Jennifer (daughter-in-law): Support role, emergencies only")
|
| 92 |
+
print("β’ Different notification preferences and access levels")
|
| 93 |
+
|
| 94 |
+
result = self.care_system.run_daily_care_cycle("parent_001")
|
| 95 |
+
|
| 96 |
+
self.print_section("Personalized Communication Strategy")
|
| 97 |
+
|
| 98 |
+
for notification in result['family_notifications']:
|
| 99 |
+
print(f"\nπ€ {notification['recipient']}")
|
| 100 |
+
print(f" Role: {notification.get('recipient_id', 'unknown')}")
|
| 101 |
+
print(f" Channels: {', '.join(notification['channels'])}")
|
| 102 |
+
print(f" Priority: {notification['urgency']}")
|
| 103 |
+
print(f" Send immediately: {notification.get('send_immediately', False)}")
|
| 104 |
+
|
| 105 |
+
# Show first few lines of message
|
| 106 |
+
message_lines = notification['message'].split('\n')[:3]
|
| 107 |
+
for line in message_lines:
|
| 108 |
+
if line.strip():
|
| 109 |
+
print(f" Preview: {line.strip()}")
|
| 110 |
+
break
|
| 111 |
+
|
| 112 |
+
return result
|
| 113 |
+
|
| 114 |
+
def show_daily_summary(self, result):
|
| 115 |
+
"""Display the complete daily summary"""
|
| 116 |
+
self.print_section("Complete Daily Summary")
|
| 117 |
+
print(result['daily_summary'])
|
| 118 |
+
|
| 119 |
+
self.print_section("Action Items")
|
| 120 |
+
for i, action in enumerate(result['action_items'], 1):
|
| 121 |
+
print(f"{i}. {action}")
|
| 122 |
+
|
| 123 |
+
def run_interactive_demo(self):
|
| 124 |
+
"""Run an interactive demo session"""
|
| 125 |
+
self.print_banner("CARELOOP INTERACTIVE DEMO")
|
| 126 |
+
|
| 127 |
+
print("Welcome to the CareLoop AI Caregiving Platform Demo!")
|
| 128 |
+
print("This demo shows how multiple AI agents work together to")
|
| 129 |
+
print("monitor Margaret Chen (78) and coordinate her family care.")
|
| 130 |
+
print("\nMargaret's Profile:")
|
| 131 |
+
print("β’ Age: 78")
|
| 132 |
+
print("β’ Conditions: Type 2 Diabetes, Hypertension, Mild Cognitive Impairment")
|
| 133 |
+
print("β’ Medications: Metformin, Lisinopril, Donepezil")
|
| 134 |
+
print("β’ Family: David (son/primary), Lisa (daughter), Jennifer (daughter-in-law)")
|
| 135 |
+
|
| 136 |
+
while True:
|
| 137 |
+
print(f"\n{'='*50}")
|
| 138 |
+
print("Choose a scenario to demonstrate:")
|
| 139 |
+
print("1. Normal Day - Everything Going Well")
|
| 140 |
+
print("2. Medication Compliance Issues")
|
| 141 |
+
print("3. Multi-Family Coordination")
|
| 142 |
+
print("4. Show Complete Daily Summary")
|
| 143 |
+
print("5. Exit demo")
|
| 144 |
+
|
| 145 |
+
try:
|
| 146 |
+
choice = input("\nEnter choice (1-5): ").strip()
|
| 147 |
+
|
| 148 |
+
if choice == "1":
|
| 149 |
+
result = self.run_normal_day_scenario()
|
| 150 |
+
input("\nPress Enter to continue...")
|
| 151 |
+
|
| 152 |
+
elif choice == "2":
|
| 153 |
+
result = self.run_medication_issues_scenario()
|
| 154 |
+
input("\nPress Enter to continue...")
|
| 155 |
+
|
| 156 |
+
elif choice == "3":
|
| 157 |
+
result = self.run_family_coordination_scenario()
|
| 158 |
+
input("\nPress Enter to continue...")
|
| 159 |
+
|
| 160 |
+
elif choice == "4":
|
| 161 |
+
print("Running care analysis to generate summary...")
|
| 162 |
+
result = self.care_system.run_daily_care_cycle("parent_001")
|
| 163 |
+
self.show_daily_summary(result)
|
| 164 |
+
input("\nPress Enter to continue...")
|
| 165 |
+
|
| 166 |
+
elif choice == "5":
|
| 167 |
+
break
|
| 168 |
+
|
| 169 |
+
else:
|
| 170 |
+
print("β Invalid choice. Please enter 1-5.")
|
| 171 |
+
|
| 172 |
+
except KeyboardInterrupt:
|
| 173 |
+
print("\nπ Demo interrupted. Thanks for trying CareLoop!")
|
| 174 |
+
break
|
| 175 |
+
except Exception as e:
|
| 176 |
+
print(f"β Error: {e}")
|
| 177 |
+
|
| 178 |
+
self.print_banner("DEMO COMPLETE")
|
| 179 |
+
print("π― Key Takeaways:")
|
| 180 |
+
print("β’ AI agents work 24/7 to monitor health patterns")
|
| 181 |
+
print("β’ Automated family communication reduces caregiver burden")
|
| 182 |
+
print("β’ Predictive analytics prevent emergencies before they happen")
|
| 183 |
+
print("β’ Personalized care coordination scales to any family size")
|
| 184 |
+
print("\nπ‘ CareLoop: Making family caregiving smarter, not harder.")
|
| 185 |
+
|
| 186 |
+
def run_quick_demo():
|
| 187 |
+
"""Run a quick non-interactive demo for presentations"""
|
| 188 |
+
demo = CareLoopDemo()
|
| 189 |
+
|
| 190 |
+
print("π CARELOOP QUICK DEMO")
|
| 191 |
+
print("=" * 40)
|
| 192 |
+
print("Running AI-powered care analysis for Margaret Chen...")
|
| 193 |
+
|
| 194 |
+
# Quick run through key scenarios
|
| 195 |
+
result1 = demo.run_normal_day_scenario()
|
| 196 |
+
print("\nPress Enter for next scenario...")
|
| 197 |
+
input()
|
| 198 |
+
|
| 199 |
+
result2 = demo.run_medication_issues_scenario()
|
| 200 |
+
print("\nPress Enter for next scenario...")
|
| 201 |
+
input()
|
| 202 |
+
|
| 203 |
+
result3 = demo.run_family_coordination_scenario()
|
| 204 |
+
|
| 205 |
+
print("\nπ― DEMO SUMMARY:")
|
| 206 |
+
print("β
Normal day: Routine monitoring and family updates")
|
| 207 |
+
print("β οΈ Medication issues: Proactive alerts and action items")
|
| 208 |
+
print("π¨βπ©βπ§βπ¦ Family coordination: Personalized communication for each member")
|
| 209 |
+
print("\nπ‘ Ready for production deployment!")
|
| 210 |
+
|
| 211 |
+
if __name__ == "__main__":
|
| 212 |
+
if len(sys.argv) > 1 and sys.argv[1] == "--quick":
|
| 213 |
+
run_quick_demo()
|
| 214 |
+
else:
|
| 215 |
+
demo = CareLoopDemo()
|
| 216 |
+
demo.run_interactive_demo()
|
gradio_app.py
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# gradio_app.py - Gradio interface for CareLoop
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import json
|
| 6 |
+
import gradio as gr
|
| 7 |
+
from datetime import datetime, timedelta
|
| 8 |
+
from dataclasses import asdict
|
| 9 |
+
from typing import Dict, List, Any, Optional
|
| 10 |
+
import random
|
| 11 |
+
import pandas as pd
|
| 12 |
+
import numpy as np
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
|
| 15 |
+
from openai import OpenAI
|
| 16 |
+
from langchain_core.messages import HumanMessage, AIMessage
|
| 17 |
+
|
| 18 |
+
# Import CareLoop components
|
| 19 |
+
from careloop_main import (
|
| 20 |
+
MockDataGenerator,
|
| 21 |
+
CareState,
|
| 22 |
+
HealthMonitorAgent,
|
| 23 |
+
MedicationAgent,
|
| 24 |
+
FamilyCommunicationAgent,
|
| 25 |
+
ActionPlannerAgent,
|
| 26 |
+
AlertLevel
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# Initialize the OpenAI client for Nebius API
|
| 30 |
+
client = OpenAI(
|
| 31 |
+
base_url="https://api.studio.nebius.com/v1/",
|
| 32 |
+
api_key=os.environ.get("NEBIUS_API_KEY", "demo-key-for-hackathon")
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
class NebiusLLM:
|
| 36 |
+
"""LLM wrapper for Nebius API"""
|
| 37 |
+
|
| 38 |
+
def __init__(self, model_name="meta-llama/Meta-Llama-3.1-70B-Instruct"):
|
| 39 |
+
self.model_name = model_name
|
| 40 |
+
self.client = client
|
| 41 |
+
|
| 42 |
+
def generate(self, prompt: str) -> str:
|
| 43 |
+
"""Generate text using Nebius API"""
|
| 44 |
+
try:
|
| 45 |
+
response = self.client.chat.completions.create(
|
| 46 |
+
model=self.model_name,
|
| 47 |
+
max_tokens=512,
|
| 48 |
+
temperature=0.6,
|
| 49 |
+
top_p=0.9,
|
| 50 |
+
extra_body={
|
| 51 |
+
"top_k": 50
|
| 52 |
+
},
|
| 53 |
+
messages=[{"role": "user", "content": prompt}]
|
| 54 |
+
)
|
| 55 |
+
return response.choices[0].message.content
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f"Error calling Nebius API: {e}")
|
| 58 |
+
# Fall back to mock responses in case of API error
|
| 59 |
+
return self._generate_mock_response(prompt)
|
| 60 |
+
|
| 61 |
+
def _generate_mock_response(self, prompt: str) -> str:
|
| 62 |
+
"""Generate a mock response for demo purposes"""
|
| 63 |
+
if "health" in prompt.lower():
|
| 64 |
+
return "The patient's health metrics show stable vital signs with glucose levels within acceptable range."
|
| 65 |
+
elif "medication" in prompt.lower():
|
| 66 |
+
return "Medication compliance has been good this week with only one missed dose of evening medication."
|
| 67 |
+
elif "summary" in prompt.lower():
|
| 68 |
+
return "Overall, the patient is doing well with stable health metrics and good medication compliance."
|
| 69 |
+
else:
|
| 70 |
+
return "The care system is monitoring the patient's condition and no significant issues have been detected."
|
| 71 |
+
|
| 72 |
+
class CareLoopSystem:
|
| 73 |
+
"""CareLoop system with Nebius LLM integration"""
|
| 74 |
+
|
| 75 |
+
def __init__(self):
|
| 76 |
+
self.mock_data = MockDataGenerator()
|
| 77 |
+
self.llm = NebiusLLM()
|
| 78 |
+
|
| 79 |
+
# Initialize specialized agents
|
| 80 |
+
self.health_monitor = HealthMonitorAgent(self.llm)
|
| 81 |
+
self.medication_agent = MedicationAgent(self.llm)
|
| 82 |
+
self.family_communicator = FamilyCommunicationAgent(self.llm)
|
| 83 |
+
self.action_planner = ActionPlannerAgent(self.llm)
|
| 84 |
+
|
| 85 |
+
# Store chat history for each parent
|
| 86 |
+
self.chat_history = {}
|
| 87 |
+
|
| 88 |
+
# Generate historical data for timelines
|
| 89 |
+
self.historical_data = self._generate_historical_data()
|
| 90 |
+
|
| 91 |
+
# Generate medication compliance data
|
| 92 |
+
self.medication_compliance = self._generate_medication_compliance()
|
| 93 |
+
|
| 94 |
+
def _generate_historical_data(self) -> Dict[str, Dict[str, List]]:
|
| 95 |
+
"""Generate 7 days of historical health data for each parent"""
|
| 96 |
+
data = {}
|
| 97 |
+
today = datetime.now()
|
| 98 |
+
|
| 99 |
+
for parent_id, parent in self.mock_data.parents.items():
|
| 100 |
+
parent_data = {
|
| 101 |
+
"dates": [],
|
| 102 |
+
"blood_pressure_systolic": [],
|
| 103 |
+
"blood_pressure_diastolic": [],
|
| 104 |
+
"heart_rate": [],
|
| 105 |
+
"blood_glucose": [],
|
| 106 |
+
"weight": [],
|
| 107 |
+
"temperature": [],
|
| 108 |
+
"sleep_hours": []
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
# Set default weight based on parent
|
| 112 |
+
if parent.name == "Margaret Chen":
|
| 113 |
+
base_weight = 145.0 # lbs
|
| 114 |
+
elif parent.name == "Robert Johnson":
|
| 115 |
+
base_weight = 175.0 # lbs
|
| 116 |
+
elif parent.name == "Elena Gonzalez":
|
| 117 |
+
base_weight = 130.0 # lbs
|
| 118 |
+
else:
|
| 119 |
+
base_weight = 150.0 # default weight
|
| 120 |
+
|
| 121 |
+
# Generate data for the last 7 days
|
| 122 |
+
for i in range(7, 0, -1):
|
| 123 |
+
date = today - timedelta(days=i)
|
| 124 |
+
parent_data["dates"].append(date.strftime("%Y-%m-%d"))
|
| 125 |
+
|
| 126 |
+
# Generate values based on parent's conditions
|
| 127 |
+
if "Diabetes" in parent.conditions:
|
| 128 |
+
glucose_base = 130
|
| 129 |
+
glucose_var = 30
|
| 130 |
+
else:
|
| 131 |
+
glucose_base = 95
|
| 132 |
+
glucose_var = 15
|
| 133 |
+
|
| 134 |
+
if "Hypertension" in parent.conditions:
|
| 135 |
+
bp_sys_base = 145
|
| 136 |
+
bp_sys_var = 15
|
| 137 |
+
bp_dia_base = 90
|
| 138 |
+
bp_dia_var = 10
|
| 139 |
+
else:
|
| 140 |
+
bp_sys_base = 125
|
| 141 |
+
bp_sys_var = 10
|
| 142 |
+
bp_dia_base = 80
|
| 143 |
+
bp_dia_var = 5
|
| 144 |
+
|
| 145 |
+
# Add some random variation to simulate real-world data
|
| 146 |
+
parent_data["blood_pressure_systolic"].append(bp_sys_base + random.randint(-bp_sys_var, bp_sys_var))
|
| 147 |
+
parent_data["blood_pressure_diastolic"].append(bp_dia_base + random.randint(-bp_dia_var, bp_dia_var))
|
| 148 |
+
parent_data["heart_rate"].append(75 + random.randint(-10, 10))
|
| 149 |
+
parent_data["blood_glucose"].append(glucose_base + random.randint(-glucose_var, glucose_var))
|
| 150 |
+
parent_data["weight"].append(base_weight + random.uniform(-0.5, 0.5))
|
| 151 |
+
parent_data["temperature"].append(round(98.2 + random.uniform(-0.5, 0.8), 1))
|
| 152 |
+
parent_data["sleep_hours"].append(round(6.5 + random.uniform(-1.5, 1.5), 1))
|
| 153 |
+
|
| 154 |
+
data[parent_id] = parent_data
|
| 155 |
+
|
| 156 |
+
return data
|
| 157 |
+
|
| 158 |
+
def _generate_medication_compliance(self) -> Dict[str, Dict[str, List]]:
|
| 159 |
+
"""Generate medication compliance data for each parent"""
|
| 160 |
+
compliance = {}
|
| 161 |
+
|
| 162 |
+
for parent_id, parent in self.mock_data.parents.items():
|
| 163 |
+
parent_compliance = {}
|
| 164 |
+
|
| 165 |
+
for med in parent.medications:
|
| 166 |
+
med_name = med["name"]
|
| 167 |
+
med_compliance = []
|
| 168 |
+
|
| 169 |
+
# Generate 7 days of compliance data
|
| 170 |
+
for _ in range(7):
|
| 171 |
+
# 85% chance of taking medication
|
| 172 |
+
took_med = random.random() < 0.85
|
| 173 |
+
med_compliance.append(took_med)
|
| 174 |
+
|
| 175 |
+
parent_compliance[med_name] = med_compliance
|
| 176 |
+
|
| 177 |
+
compliance[parent_id] = parent_compliance
|
| 178 |
+
|
| 179 |
+
return compliance
|
| 180 |
+
|
| 181 |
+
def run_care_check(self, parent_id: str) -> Dict[str, Any]:
|
| 182 |
+
"""Run a full care check for a specific parent"""
|
| 183 |
+
# Validate parent_id exists
|
| 184 |
+
if parent_id not in self.mock_data.parents:
|
| 185 |
+
raise ValueError(f"Parent ID {parent_id} not found")
|
| 186 |
+
|
| 187 |
+
# Initialize state
|
| 188 |
+
state = CareState(
|
| 189 |
+
parent_id=parent_id,
|
| 190 |
+
date=datetime.now().strftime("%Y-%m-%d"),
|
| 191 |
+
health_metrics=[],
|
| 192 |
+
medication_status=[],
|
| 193 |
+
concerns=[],
|
| 194 |
+
alerts=[],
|
| 195 |
+
daily_summary="",
|
| 196 |
+
action_items=[],
|
| 197 |
+
family_notifications=[],
|
| 198 |
+
emergency_level="normal"
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# Run the analysis pipeline manually since we're not using langgraph here
|
| 202 |
+
state = self.health_monitor.analyze_health_patterns(state)
|
| 203 |
+
state = self.medication_agent.check_medication_compliance(state)
|
| 204 |
+
|
| 205 |
+
# Check for emergency
|
| 206 |
+
urgent_alerts = [a for a in state["alerts"] if a["severity"] == AlertLevel.URGENT.value]
|
| 207 |
+
state["emergency_level"] = "urgent" if urgent_alerts else "normal"
|
| 208 |
+
|
| 209 |
+
# Generate daily summary
|
| 210 |
+
state = self.family_communicator.create_daily_summary(state)
|
| 211 |
+
|
| 212 |
+
# Generate family notifications
|
| 213 |
+
state = self.family_communicator.generate_family_updates(state)
|
| 214 |
+
|
| 215 |
+
# Generate action items
|
| 216 |
+
state = self.action_planner.generate_action_items(state)
|
| 217 |
+
|
| 218 |
+
# Add timestamp
|
| 219 |
+
result = dict(state)
|
| 220 |
+
result["processed_at"] = datetime.now().isoformat()
|
| 221 |
+
result["next_check"] = (datetime.now() + timedelta(hours=24)).isoformat()
|
| 222 |
+
|
| 223 |
+
return result
|
| 224 |
+
|
| 225 |
+
def get_parent_info(self, parent_id: str) -> Dict[str, Any]:
|
| 226 |
+
"""Get information about a specific parent"""
|
| 227 |
+
parent = self.mock_data.get_parent_by_id(parent_id)
|
| 228 |
+
family = self.mock_data.get_family_by_parent_id(parent_id)
|
| 229 |
+
|
| 230 |
+
if not parent:
|
| 231 |
+
return {"error": f"Parent ID {parent_id} not found"}
|
| 232 |
+
|
| 233 |
+
return {
|
| 234 |
+
"parent": asdict(parent),
|
| 235 |
+
"family": [asdict(fm) for fm in family]
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
def get_health_timeline(self, parent_id: str) -> Dict[str, Any]:
|
| 239 |
+
"""Get historical health data for a specific parent"""
|
| 240 |
+
if parent_id not in self.historical_data:
|
| 241 |
+
return {"error": f"No historical data for parent ID {parent_id}"}
|
| 242 |
+
|
| 243 |
+
return self.historical_data[parent_id]
|
| 244 |
+
|
| 245 |
+
def get_medication_compliance(self, parent_id: str) -> Dict[str, Any]:
|
| 246 |
+
"""Get medication compliance data for a specific parent"""
|
| 247 |
+
if parent_id not in self.medication_compliance:
|
| 248 |
+
return {"error": f"No medication data for parent ID {parent_id}"}
|
| 249 |
+
|
| 250 |
+
return self.medication_compliance[parent_id]
|
| 251 |
+
|
| 252 |
+
def chat_with_system(self, parent_id: str, message: str) -> str:
|
| 253 |
+
"""Chat with the CareLoop system about a specific parent"""
|
| 254 |
+
if parent_id not in self.chat_history:
|
| 255 |
+
self.chat_history[parent_id] = []
|
| 256 |
+
|
| 257 |
+
self.chat_history[parent_id].append({"role": "user", "content": message})
|
| 258 |
+
|
| 259 |
+
# Generate context about the parent
|
| 260 |
+
parent = self.mock_data.get_parent_by_id(parent_id)
|
| 261 |
+
if not parent:
|
| 262 |
+
response = "I couldn't find information about this parent."
|
| 263 |
+
self.chat_history[parent_id].append({"role": "assistant", "content": response})
|
| 264 |
+
return response
|
| 265 |
+
|
| 266 |
+
# Create a prompt for the LLM
|
| 267 |
+
prompt = f"""
|
| 268 |
+
You are CareLoop, an AI assistant for family caregivers.
|
| 269 |
+
|
| 270 |
+
Parent information:
|
| 271 |
+
- Name: {parent.name}
|
| 272 |
+
- Age: {parent.age}
|
| 273 |
+
- Health conditions: {', '.join(parent.conditions)}
|
| 274 |
+
- Medications: {', '.join(med['name'] for med in parent.medications)}
|
| 275 |
+
|
| 276 |
+
The caregiver has asked: {message}
|
| 277 |
+
|
| 278 |
+
Provide a helpful, compassionate response focused on elderly care.
|
| 279 |
+
"""
|
| 280 |
+
|
| 281 |
+
# Get response from LLM
|
| 282 |
+
response = self.llm.generate(prompt)
|
| 283 |
+
self.chat_history[parent_id].append({"role": "assistant", "content": response})
|
| 284 |
+
|
| 285 |
+
return response
|
| 286 |
+
|
| 287 |
+
def get_chat_history(self, parent_id: str) -> List[Dict[str, str]]:
|
| 288 |
+
"""Get chat history for a specific parent"""
|
| 289 |
+
return self.chat_history.get(parent_id, [])
|
| 290 |
+
|
| 291 |
+
# Initialize the CareLoop system
|
| 292 |
+
care_system = CareLoopSystem()
|
| 293 |
+
|
| 294 |
+
def format_markdown_report(report: Dict[str, Any]) -> str:
|
| 295 |
+
"""Format the care report as markdown for Gradio display"""
|
| 296 |
+
if not report:
|
| 297 |
+
return ""
|
| 298 |
+
|
| 299 |
+
md = []
|
| 300 |
+
md.append(f"# {report['daily_summary']}")
|
| 301 |
+
|
| 302 |
+
md.append("\n## Action Items")
|
| 303 |
+
for item in report["action_items"]:
|
| 304 |
+
md.append(f"- {item}")
|
| 305 |
+
|
| 306 |
+
md.append("\n## Family Notifications")
|
| 307 |
+
for notif in report["family_notifications"]:
|
| 308 |
+
md.append(f"**To: {notif['recipient']}** ({notif['urgency']} priority)")
|
| 309 |
+
md.append(f"```\n{notif['message']}\n```")
|
| 310 |
+
|
| 311 |
+
if report["alerts"]:
|
| 312 |
+
md.append("\n## Alerts")
|
| 313 |
+
for alert in report["alerts"]:
|
| 314 |
+
md.append(f"- **{alert['severity'].upper()}**: {alert['message']}")
|
| 315 |
+
md.append(f" - *Recommended Action:* {alert['recommended_action']}")
|
| 316 |
+
|
| 317 |
+
return "\n".join(md)
|
| 318 |
+
|
| 319 |
+
def format_parent_info(parent_info: Dict[str, Any]) -> str:
|
| 320 |
+
"""Format parent info as markdown for Gradio display"""
|
| 321 |
+
if not parent_info or "error" in parent_info:
|
| 322 |
+
return "No parent information available"
|
| 323 |
+
|
| 324 |
+
parent = parent_info["parent"]
|
| 325 |
+
family = parent_info["family"]
|
| 326 |
+
|
| 327 |
+
md = []
|
| 328 |
+
md.append(f"# {parent['name']} ({parent['age']})")
|
| 329 |
+
|
| 330 |
+
md.append("\n## Health Conditions")
|
| 331 |
+
for condition in parent["conditions"]:
|
| 332 |
+
md.append(f"- {condition}")
|
| 333 |
+
|
| 334 |
+
md.append("\n## Medications")
|
| 335 |
+
for med in parent["medications"]:
|
| 336 |
+
times = ", ".join(med["times"])
|
| 337 |
+
md.append(f"- {med['name']} ({med['dosage']}) - {med['frequency']} at {times}")
|
| 338 |
+
|
| 339 |
+
md.append("\n## Family Caregivers")
|
| 340 |
+
for member in family:
|
| 341 |
+
md.append(f"- {member['name']} ({member['relationship']}) - {member['role']}")
|
| 342 |
+
md.append(f" - Contact: {member['phone']} | {member['email']}")
|
| 343 |
+
|
| 344 |
+
return "\n".join(md)
|
| 345 |
+
|
| 346 |
+
def run_care_check(parent_id: str) -> tuple:
|
| 347 |
+
"""Run care check and return formatted results"""
|
| 348 |
+
try:
|
| 349 |
+
# Get parent info
|
| 350 |
+
parent_info = care_system.get_parent_info(parent_id)
|
| 351 |
+
parent_md = format_parent_info(parent_info)
|
| 352 |
+
|
| 353 |
+
# Run care check
|
| 354 |
+
report = care_system.run_care_check(parent_id)
|
| 355 |
+
report_md = format_markdown_report(report)
|
| 356 |
+
|
| 357 |
+
# Create metrics display
|
| 358 |
+
health_metrics = len(report["health_metrics"])
|
| 359 |
+
medication_events = len(report["medication_status"])
|
| 360 |
+
alerts = len(report["alerts"])
|
| 361 |
+
actions = len(report["action_items"])
|
| 362 |
+
notifications = len(report["family_notifications"])
|
| 363 |
+
|
| 364 |
+
# Get health timeline data
|
| 365 |
+
timeline_data = care_system.get_health_timeline(parent_id)
|
| 366 |
+
timeline_plot = create_health_timeline_plot(timeline_data)
|
| 367 |
+
|
| 368 |
+
# Get medication compliance data
|
| 369 |
+
compliance_data = care_system.get_medication_compliance(parent_id)
|
| 370 |
+
compliance_plot = create_medication_compliance_plot(compliance_data)
|
| 371 |
+
|
| 372 |
+
return parent_md, report_md, health_metrics, medication_events, alerts, actions, notifications, timeline_plot, compliance_plot
|
| 373 |
+
except Exception as e:
|
| 374 |
+
return f"Error: {str(e)}", "", 0, 0, 0, 0, 0, None, None
|
| 375 |
+
|
| 376 |
+
def create_health_timeline_plot(timeline_data: Dict[str, List]) -> gr.Plot:
|
| 377 |
+
"""Create a plot of health metrics over time"""
|
| 378 |
+
if "error" in timeline_data:
|
| 379 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
| 380 |
+
ax.text(0.5, 0.5, "No timeline data available", ha='center', va='center')
|
| 381 |
+
return fig
|
| 382 |
+
|
| 383 |
+
# Create a figure with multiple subplots
|
| 384 |
+
fig, axs = plt.subplots(3, 1, figsize=(10, 10), sharex=True)
|
| 385 |
+
fig.suptitle("Health Metrics Over Time", fontsize=16)
|
| 386 |
+
|
| 387 |
+
# Plot blood pressure and heart rate
|
| 388 |
+
ax1 = axs[0]
|
| 389 |
+
dates = timeline_data["dates"]
|
| 390 |
+
ax1.plot(dates, timeline_data["blood_pressure_systolic"], 'r-', label='Systolic BP')
|
| 391 |
+
ax1.plot(dates, timeline_data["blood_pressure_diastolic"], 'b-', label='Diastolic BP')
|
| 392 |
+
ax1.set_ylabel('Blood Pressure (mmHg)')
|
| 393 |
+
ax1.grid(True)
|
| 394 |
+
ax1.legend(loc='upper left')
|
| 395 |
+
|
| 396 |
+
# Add heart rate on secondary y-axis
|
| 397 |
+
ax1_hr = ax1.twinx()
|
| 398 |
+
ax1_hr.plot(dates, timeline_data["heart_rate"], 'g-', label='Heart Rate')
|
| 399 |
+
ax1_hr.set_ylabel('Heart Rate (bpm)')
|
| 400 |
+
ax1_hr.legend(loc='upper right')
|
| 401 |
+
|
| 402 |
+
# Plot blood glucose
|
| 403 |
+
ax2 = axs[1]
|
| 404 |
+
ax2.plot(dates, timeline_data["blood_glucose"], 'm-', label='Blood Glucose')
|
| 405 |
+
ax2.set_ylabel('Blood Glucose (mg/dL)')
|
| 406 |
+
ax2.grid(True)
|
| 407 |
+
ax2.legend()
|
| 408 |
+
|
| 409 |
+
# Plot weight and temperature
|
| 410 |
+
ax3 = axs[2]
|
| 411 |
+
ax3.plot(dates, timeline_data["weight"], 'k-', label='Weight')
|
| 412 |
+
ax3.set_xlabel('Date')
|
| 413 |
+
ax3.set_ylabel('Weight (lbs)')
|
| 414 |
+
ax3.grid(True)
|
| 415 |
+
ax3.legend(loc='upper left')
|
| 416 |
+
|
| 417 |
+
# Add temperature on secondary y-axis
|
| 418 |
+
ax3_temp = ax3.twinx()
|
| 419 |
+
ax3_temp.plot(dates, timeline_data["temperature"], 'c-', label='Temperature')
|
| 420 |
+
ax3_temp.set_ylabel('Temperature (Β°F)')
|
| 421 |
+
ax3_temp.legend(loc='upper right')
|
| 422 |
+
|
| 423 |
+
plt.tight_layout()
|
| 424 |
+
return fig
|
| 425 |
+
|
| 426 |
+
def create_medication_compliance_plot(compliance_data: Dict[str, List]) -> gr.Plot:
|
| 427 |
+
"""Create a plot of medication compliance"""
|
| 428 |
+
if "error" in compliance_data:
|
| 429 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
| 430 |
+
ax.text(0.5, 0.5, "No medication compliance data available", ha='center', va='center')
|
| 431 |
+
return fig
|
| 432 |
+
|
| 433 |
+
# Create a figure
|
| 434 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
| 435 |
+
fig.suptitle("7-Day Medication Compliance", fontsize=16)
|
| 436 |
+
|
| 437 |
+
# Set up data
|
| 438 |
+
medications = list(compliance_data.keys())
|
| 439 |
+
dates = [f"Day {i+1}" for i in range(7)]
|
| 440 |
+
|
| 441 |
+
# Create a matrix of compliance data
|
| 442 |
+
compliance_matrix = np.zeros((len(medications), 7))
|
| 443 |
+
for i, med in enumerate(medications):
|
| 444 |
+
for j in range(7):
|
| 445 |
+
compliance_matrix[i, j] = 1 if compliance_data[med][j] else 0
|
| 446 |
+
|
| 447 |
+
# Create heatmap
|
| 448 |
+
im = ax.imshow(compliance_matrix, cmap='RdYlGn', aspect='auto', vmin=0, vmax=1)
|
| 449 |
+
|
| 450 |
+
# Configure axes
|
| 451 |
+
ax.set_xticks(np.arange(len(dates)))
|
| 452 |
+
ax.set_yticks(np.arange(len(medications)))
|
| 453 |
+
ax.set_xticklabels(dates)
|
| 454 |
+
ax.set_yticklabels(medications)
|
| 455 |
+
|
| 456 |
+
# Add text annotations
|
| 457 |
+
for i in range(len(medications)):
|
| 458 |
+
for j in range(len(dates)):
|
| 459 |
+
text = "β" if compliance_matrix[i, j] == 1 else "β"
|
| 460 |
+
color = "black" if compliance_matrix[i, j] == 1 else "white"
|
| 461 |
+
ax.text(j, i, text, ha="center", va="center", color=color, fontweight="bold")
|
| 462 |
+
|
| 463 |
+
ax.set_xlabel("Day")
|
| 464 |
+
ax.set_title("β = Taken, β = Missed")
|
| 465 |
+
plt.tight_layout()
|
| 466 |
+
|
| 467 |
+
return fig
|
| 468 |
+
|
| 469 |
+
# Create Gradio interface
|
| 470 |
+
with gr.Blocks(title="CareLoop AI - Family Caregiving Platform", theme=gr.themes.Soft()) as demo:
|
| 471 |
+
gr.Markdown("# π CareLoop - AI-Powered Family Caregiving")
|
| 472 |
+
gr.Markdown("Select a parent and run a care check to see AI analysis of their health and care needs.")
|
| 473 |
+
|
| 474 |
+
# Store parent_id as a state variable
|
| 475 |
+
current_parent_id = gr.State("parent_001")
|
| 476 |
+
|
| 477 |
+
with gr.Row():
|
| 478 |
+
with gr.Column(scale=1):
|
| 479 |
+
parent_dropdown = gr.Dropdown(
|
| 480 |
+
choices=[
|
| 481 |
+
"Margaret Chen (78) - Diabetes/Hypertension",
|
| 482 |
+
"Robert Johnson (72) - Stroke Recovery",
|
| 483 |
+
"Elena Gonzalez (81) - Early Alzheimer's"
|
| 484 |
+
],
|
| 485 |
+
value="Margaret Chen (78) - Diabetes/Hypertension",
|
| 486 |
+
label="Select Parent"
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
run_button = gr.Button("π Run Care Check", variant="primary")
|
| 490 |
+
|
| 491 |
+
with gr.Accordion("About CareLoop", open=False):
|
| 492 |
+
gr.Markdown("""
|
| 493 |
+
## About CareLoop
|
| 494 |
+
|
| 495 |
+
CareLoop is an AI-powered platform that helps families care for elderly relatives by:
|
| 496 |
+
|
| 497 |
+
- Monitoring health metrics and medication compliance
|
| 498 |
+
- Generating personalized care insights and recommendations
|
| 499 |
+
- Coordinating communication between family caregivers
|
| 500 |
+
- Detecting potential health issues early
|
| 501 |
+
|
| 502 |
+
This demo showcases how AI can transform elderly care by analyzing complex health data
|
| 503 |
+
and generating actionable insights for family caregivers.
|
| 504 |
+
""")
|
| 505 |
+
|
| 506 |
+
with gr.Column(scale=2):
|
| 507 |
+
with gr.Tabs():
|
| 508 |
+
with gr.Tab("Care Analysis"):
|
| 509 |
+
with gr.Row():
|
| 510 |
+
with gr.Column(scale=1):
|
| 511 |
+
parent_info = gr.Markdown("Select a parent to view their information")
|
| 512 |
+
|
| 513 |
+
with gr.Column(scale=2):
|
| 514 |
+
care_report = gr.Markdown("Run a care check to see the AI analysis")
|
| 515 |
+
|
| 516 |
+
with gr.Tab("Health Timeline"):
|
| 517 |
+
timeline_plot = gr.Plot(label="Health Metrics Over Time")
|
| 518 |
+
|
| 519 |
+
with gr.Tab("Medication Tracker"):
|
| 520 |
+
compliance_plot = gr.Plot(label="Medication Compliance")
|
| 521 |
+
|
| 522 |
+
with gr.Tab("Care Metrics"):
|
| 523 |
+
with gr.Row():
|
| 524 |
+
metric1 = gr.Number(label="Health Data Points", value=0)
|
| 525 |
+
metric2 = gr.Number(label="Medication Events", value=0)
|
| 526 |
+
metric3 = gr.Number(label="Alerts Generated", value=0)
|
| 527 |
+
metric4 = gr.Number(label="Action Items", value=0)
|
| 528 |
+
metric5 = gr.Number(label="Family Notifications", value=0)
|
| 529 |
+
|
| 530 |
+
with gr.Tab("Care Assistant"):
|
| 531 |
+
chatbot = gr.Chatbot(label="Chat with CareLoop")
|
| 532 |
+
msg = gr.Textbox(
|
| 533 |
+
placeholder="Ask a question about the patient's care...",
|
| 534 |
+
show_label=False
|
| 535 |
+
)
|
| 536 |
+
clear = gr.Button("Clear")
|
| 537 |
+
|
| 538 |
+
# Set up the event handlers
|
| 539 |
+
parent_map = {
|
| 540 |
+
"Margaret Chen (78) - Diabetes/Hypertension": "parent_001",
|
| 541 |
+
"Robert Johnson (72) - Stroke Recovery": "parent_002",
|
| 542 |
+
"Elena Gonzalez (81) - Early Alzheimer's": "parent_003"
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
def get_parent_id(display_name):
|
| 546 |
+
return parent_map.get(display_name, "parent_001")
|
| 547 |
+
|
| 548 |
+
def update_parent_id(display_name):
|
| 549 |
+
parent_id = get_parent_id(display_name)
|
| 550 |
+
return parent_id
|
| 551 |
+
|
| 552 |
+
parent_dropdown.change(
|
| 553 |
+
fn=update_parent_id,
|
| 554 |
+
inputs=[parent_dropdown],
|
| 555 |
+
outputs=[current_parent_id]
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
run_button.click(
|
| 559 |
+
fn=lambda p_id: run_care_check(p_id),
|
| 560 |
+
inputs=[current_parent_id],
|
| 561 |
+
outputs=[parent_info, care_report, metric1, metric2, metric3, metric4, metric5, timeline_plot, compliance_plot]
|
| 562 |
+
)
|
| 563 |
+
|
| 564 |
+
# Also update parent info when dropdown changes
|
| 565 |
+
parent_dropdown.change(
|
| 566 |
+
fn=lambda p_id: (format_parent_info(care_system.get_parent_info(p_id)), "", 0, 0, 0, 0, 0,
|
| 567 |
+
create_health_timeline_plot(care_system.get_health_timeline(p_id)),
|
| 568 |
+
create_medication_compliance_plot(care_system.get_medication_compliance(p_id))),
|
| 569 |
+
inputs=[current_parent_id],
|
| 570 |
+
outputs=[parent_info, care_report, metric1, metric2, metric3, metric4, metric5, timeline_plot, compliance_plot]
|
| 571 |
+
)
|
| 572 |
+
|
| 573 |
+
# Chat functionality - modified to avoid Gradio version compatibility issues
|
| 574 |
+
def submit_message(p_id, message, history):
|
| 575 |
+
if not message or message.strip() == "":
|
| 576 |
+
return "", history
|
| 577 |
+
response = care_system.chat_with_system(p_id, message)
|
| 578 |
+
new_history = history + [[message, response]]
|
| 579 |
+
return "", new_history
|
| 580 |
+
|
| 581 |
+
def clear_chat():
|
| 582 |
+
return []
|
| 583 |
+
|
| 584 |
+
msg.submit(
|
| 585 |
+
fn=submit_message,
|
| 586 |
+
inputs=[current_parent_id, msg, chatbot],
|
| 587 |
+
outputs=[msg, chatbot]
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
clear.click(fn=clear_chat, inputs=[], outputs=[chatbot])
|
| 591 |
+
|
| 592 |
+
if __name__ == "__main__":
|
| 593 |
+
print("π Starting CareLoop Gradio Interface")
|
| 594 |
+
print("=" * 50)
|
| 595 |
+
|
| 596 |
+
# Display available parents
|
| 597 |
+
mock_data = MockDataGenerator()
|
| 598 |
+
print("π Available Parent Profiles:")
|
| 599 |
+
for parent_id, parent in mock_data.parents.items():
|
| 600 |
+
family_count = len(mock_data.families.get(parent_id, []))
|
| 601 |
+
conditions = ", ".join(parent.conditions[:2]) + ("..." if len(parent.conditions) > 2 else "")
|
| 602 |
+
print(f" β’ {parent.name} ({parent.age}) - ID: {parent_id}")
|
| 603 |
+
print(f" Health: {conditions}")
|
| 604 |
+
print(f" Family caregivers: {family_count}")
|
| 605 |
+
|
| 606 |
+
print("\n" + "=" * 50)
|
| 607 |
+
print("π‘ Note: Using Nebius API for LLM. Set NEBIUS_API_KEY environment variable for production use.")
|
| 608 |
+
print("=" * 50)
|
| 609 |
+
|
| 610 |
+
# Launch the Gradio app
|
| 611 |
+
try:
|
| 612 |
+
# Launch with standard options for Gradio 4.16.0
|
| 613 |
+
demo.launch(share=False, show_error=True)
|
| 614 |
+
except Exception as e:
|
| 615 |
+
print(f"Error launching Gradio app: {e}")
|
| 616 |
+
print("\nTrying alternative launch method...")
|
| 617 |
+
try:
|
| 618 |
+
# Alternative launch approach with minimal options
|
| 619 |
+
demo.queue(False).launch(share=False)
|
| 620 |
+
except Exception as e2:
|
| 621 |
+
print(f"Alternative launch also failed: {e2}")
|
| 622 |
+
print("\nPlease try updating Gradio with: pip install --upgrade gradio")
|
requirements-space.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==4.16.0
|
| 2 |
+
langchain==0.1.17
|
| 3 |
+
langgraph==0.0.27
|
| 4 |
+
langchain-openai==0.1.7
|
| 5 |
+
langchain-core>=0.1.48,<0.2.0
|
| 6 |
+
pandas==2.2.2
|
| 7 |
+
numpy==1.26.4
|
| 8 |
+
matplotlib==3.8.2
|
| 9 |
+
openai==1.84.0
|
| 10 |
+
python-dateutil==2.9.0
|