Skip to content

Commit

Permalink
Check-Ins Feature #18- backend done
Browse files Browse the repository at this point in the history
30. The user will be allowed to schedule check-ins, where the user sets aside a specific time to chat with the AI. While the user can chat with the chatbot at any time, check-ins allow users to keep track of their interactions in an organized manner.
 31. The user can schedule a maximum of 5 check-ins a day.
 32. The user can edit their check-in times at any time.
 33. The user can edit check-in frequency from daily, weekly or monthly.
 34. The user can opt in to be notified about their check-ins.
 35. The system must inform the user that if they missed a check-in upon startup, and ask if they would like to hold their check-in at that moment.
 36. If two check-ins overlap, or the user is already using the chatbot, the app should silently disregard the check-in and count it as completed.
40. System validate the proposed check-in time against other check-ins on the same day.
  • Loading branch information
dhrumilp12 committed Jun 16, 2024
1 parent 6f8448c commit 83b8556
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 13 deletions.
12 changes: 0 additions & 12 deletions client/src/Components/Home.jsx

This file was deleted.

18 changes: 17 additions & 1 deletion client/src/Components/userContext.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { createContext, useState, useCallback } from 'react';
import React, { createContext, useState, useCallback,useEffect } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
export const UserContext = createContext({ user: null });
Expand All @@ -7,7 +7,23 @@ export const UserProvider = ({ children }) => {
const [voiceEnabled, setVoiceEnabled] = useState(false);
const [user, setUser] = useState(null);
const navigate = useNavigate();
// Load user from local storage on initial load
useEffect(() => {
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
}, []);

// Persist user to local storage on changes
useEffect(() => {
if (user) {
localStorage.setItem('user', JSON.stringify(user));
} else {
localStorage.removeItem('user');
}
}, [user]);

const logout = useCallback(async () => {
try {
const token = localStorage.getItem('token');
Expand Down
8 changes: 8 additions & 0 deletions server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,28 @@
from flask_cors import CORS
from flask_jwt_extended import JWTManager

from routes.scheduler import init_scheduler
from routes.user import user_routes
from routes.ai import ai_routes
from routes.checkIn import checkIn_routes
from services.azure_mongodb import MongoDBClient

from agents.mental_health_agent import MentalHealthAIAgent

# Set up the app
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = os.environ.get("JWT_SECRET_KEY")

jwt = JWTManager(app)
CORS(app)

# Register routes
app.register_blueprint(user_routes)
app.register_blueprint(ai_routes)
app.register_blueprint(checkIn_routes)

# Initialize the scheduler
init_scheduler(app)

# DB pre-load
MentalHealthAIAgent.load_agent_facts_to_db()
Expand Down
55 changes: 55 additions & 0 deletions server/models/check_in.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
This model represents a check-in.
"""

from datetime import datetime
from pydantic import BaseModel, Field, constr
from enum import Enum

class Frequency(str, Enum):
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"

class CheckIn(BaseModel):
user_id: str
check_in_time: datetime
frequency: Frequency
status: str = "upcoming" # default status is upcoming
last_conversation: str = "" # default empty string, updated later
notify: bool = False # Default to False, updated based on user preference

def save(self, db):
# Convert model to dictionary and save to MongoDB
document = self.dict()
db.check_ins.insert_one(document)

@staticmethod
def count_user_check_ins(db, user_id, date):
start_of_day = datetime.combine(date, datetime.min.time())
end_of_day = datetime.combine(date, datetime.max.time())
count = db.check_ins.count_documents({
'user_id': user_id,
'check_in_time': {'$gte': start_of_day, '$lt': end_of_day}
})
return count

@staticmethod
def validate_check_in_time(db, user_id, proposed_time):
""" Validate the proposed check-in time against other check-ins on the same day. """
date = proposed_time.date()
start_of_day = datetime.combine(date, datetime.min.time())
end_of_day = datetime.combine(date, datetime.max.time())

# Fetch all check-ins for that user on the proposed date
existing_check_ins = db.check_ins.find({
'user_id': user_id,
'check_in_time': {'$gte': start_of_day, '$lt': end_of_day}
})

for check_in in existing_check_ins:
# Assuming check-ins should not overlap within an hour window
if abs((check_in['check_in_time'] - proposed_time).total_seconds()) < 3600:
return False # Conflict found if within an hour of another check-in

return True # No conflicts found
107 changes: 107 additions & 0 deletions server/routes/checkIn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from flask import Blueprint, request, jsonify
from datetime import datetime
from pydantic import ValidationError
from models.check_in import CheckIn, Frequency
from dotenv import load_dotenv
from services.azure_mongodb import MongoDBClient
from bson import ObjectId
from pymongo import ReturnDocument
from bson.errors import InvalidId


load_dotenv()

db_client = MongoDBClient.get_client()
db = db_client[MongoDBClient.get_db_name()]

checkIn_routes = Blueprint("checkIN", __name__)


@checkIn_routes.post('/checkIn/schedule')
def schedule_check_in():
try: # Parse and validate the request data using Pydantic model
data = request.get_json()
check_in_time=datetime.fromisoformat(data['check_in_time'])
check_in_date = datetime.fromisoformat(data['check_in_time']).date()

if CheckIn.count_user_check_ins(db,data['user_id'], check_in_date) >= 5:
return jsonify({'message': 'Limit of 5 check-ins per day exceeded'}), 403

if not CheckIn.validate_check_in_time(db, data['user_id'], check_in_time):
return jsonify({'error': 'Check-in time conflicts with an existing schedule'}), 409

check_in = CheckIn(
user_id=data['user_id'],
check_in_time=check_in_time,
frequency=Frequency(data['frequency']),
notify=data.get('notify', False)
)

# Convert Pydantic model to dictionary for MongoDB
check_in_dict = check_in.dict()
check_in_dict['check_in_time'] = check_in.check_in_time # Ensure datetime is handled correctly

# Insert the new check-in into MongoDB
result = db.check_ins.insert_one(check_in_dict)
return jsonify({'message': 'Check-in scheduled successfully', 'check_in_id': str(result.inserted_id)}), 201

except ValidationError as e:
return jsonify({'error': 'Data validation error', 'details': str(e)}), 400
except Exception as e:
return jsonify({'error': str(e)}), 500

@checkIn_routes.patch('/checkIn/update/<check_in_id>')
def update_check_in(check_in_id):
data = request.get_json()
try:
updated_data = {}
if 'check_in_time' in data:
new_check_in_time = datetime.fromisoformat(data['check_in_time'])
updated_data['check_in_time'] = new_check_in_time

if not CheckIn.validate_check_in_time(db, data['user_id'], new_check_in_time):
return jsonify({'error': 'Check-in time conflicts with an existing schedule'}), 409

if 'frequency' in data:
# Use the Frequency enum to ensure the frequency is valid
try:
updated_data['frequency'] = Frequency(data['frequency']).value
except ValueError:
return jsonify({'error': 'Invalid frequency value'}), 400

update_result = db.check_ins.find_one_and_update(
{'_id': ObjectId(check_in_id)},
{'$set': updated_data},
return_document=ReturnDocument.AFTER
)
if update_result:
return jsonify({'message': 'Check-in updated successfully'}), 200
else:
return jsonify({'message': 'No check-in found with provided ID or no update needed'}), 404

except ValidationError as e:
return jsonify({'error': 'Data validation error', 'details': str(e)}), 400
except InvalidId:
return jsonify({'error': 'Invalid check-in ID'}), 400
except Exception as e:
return jsonify({'error': str(e)}), 500

@checkIn_routes.get('/checkIn/missed')
def check_missed_check_ins():
user_id = request.args.get('user_id')
now = datetime.now()
missed_check_ins = db.check_ins.find({
'user_name': user_id,
'check_in_time': {'$lt': now},
'status': 'upcoming' # Assuming 'upcoming' means not yet checked in
})

if missed_check_ins.count() > 0:
db.check_ins.update_many(
{'user_id': user_id, 'check_in_time': {'$lt': now}, 'status': 'upcoming'},
{'$set': {'status': 'missed'}}
)
return jsonify({'message': 'You have missed check-ins, would you like to complete them now?', 'missed': list(missed_check_ins)}), 200
else:
return jsonify({'message': 'No missed check-ins'}), 200

39 changes: 39 additions & 0 deletions server/routes/scheduler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from apscheduler.schedulers.background import BackgroundScheduler
from flask import current_app as app
from datetime import datetime, timedelta
from services.azure_mongodb import MongoDBClient
import pymongo

def notify_check_ins():
db_client = MongoDBClient.get_client()
db = db_client[MongoDBClient.get_db_name()]
now = datetime.now()
upcoming_check_ins = db.check_ins.find({
'check_in_time': {'$gte': now, '$lt': now + timedelta(days=7)},
'notify': True,
'status': 'upcoming'
})

for check_in in upcoming_check_ins:
delta = check_in['check_in_time'] - now
if delta.days == 7 or delta.days == 1 or delta.total_seconds() / 3600 <= 1:
send_notification(check_in['user_id'], check_in['check_in_time'], delta)

def send_notification(user_id, check_in_time, delta):
message = ""
if delta.days == 7:
message = "Your check-in is scheduled in 1 week."
elif delta.days == 1:
message = "Your check-in is scheduled tomorrow."
elif delta.total_seconds() / 3600 <= 1:
message = "Your check-in is in less than 1 hour."

# This is where you'd integrate your actual notification logic
print(f"Notify {user_id}: {message}")

scheduler = BackgroundScheduler()
scheduler.add_job(func=notify_check_ins, trigger='interval', hours=1)
scheduler.start()

def init_scheduler(app):
app.config['scheduler'] = scheduler

0 comments on commit 83b8556

Please sign in to comment.