nehal2006 commited on
Commit
68f9b9e
·
1 Parent(s): 7984e74

initial commit

Browse files
Files changed (45) hide show
  1. .dockerignore +23 -0
  2. .gitignore +27 -0
  3. Dockerfile +45 -0
  4. add_column.py +17 -0
  5. backend/app/__init__.py +1 -0
  6. backend/app/data/uploads/emotions/50ebcab1-5059-4dde-8dec-55a7e7b1c61e.avif +0 -0
  7. backend/app/data/uploads/emotions/a4fdb987-a0ec-4753-a92e-2658dfc5a2b0.avif +0 -0
  8. backend/app/database.py +33 -0
  9. backend/app/main.py +81 -0
  10. backend/app/models.py +74 -0
  11. backend/app/models/custom_ai/custom_weights.pth +0 -0
  12. backend/app/models/custom_ai/labels.json +1 -0
  13. backend/app/routers/__init__.py +0 -0
  14. backend/app/routers/activities.py +33 -0
  15. backend/app/routers/auth.py +67 -0
  16. backend/app/routers/dashboard.py +74 -0
  17. backend/app/routers/diary.py +43 -0
  18. backend/app/routers/emotion.py +234 -0
  19. backend/app/utils/auth_utils.py +25 -0
  20. backend/app/utils/storage.py +76 -0
  21. backend/requirements.txt +21 -0
  22. check_data.py +17 -0
  23. check_db.py +15 -0
  24. check_ids.py +23 -0
  25. check_logs.py +15 -0
  26. check_root_db.py +23 -0
  27. frontend/static/css/style.css +340 -0
  28. frontend/static/js/script.js +0 -0
  29. frontend/templates/activities.html +290 -0
  30. frontend/templates/children.html +91 -0
  31. frontend/templates/dashboard.html +77 -0
  32. frontend/templates/diary.html +51 -0
  33. frontend/templates/emotion.html +257 -0
  34. frontend/templates/game.html +123 -0
  35. frontend/templates/index.html +47 -0
  36. frontend/templates/login.html +39 -0
  37. frontend/templates/quiz.html +139 -0
  38. link_child.py +48 -0
  39. run.py +34 -0
  40. seed_db.py +114 -0
  41. test_cloud_db.py +33 -0
  42. test_fer.py +8 -0
  43. test_fer_simple.py +23 -0
  44. train_custom_model.py +126 -0
  45. update_db_schema.py +26 -0
.dockerignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .env
6
+ .venv
7
+ venv/
8
+ ENV/
9
+
10
+ # Databases
11
+ *.db
12
+ *.sqlite3
13
+
14
+ # Local Data
15
+ backend/data/uploads/
16
+ backend/data/reports/
17
+ data/
18
+
19
+ # OS/IDE
20
+ .vscode/
21
+ .idea/
22
+ .DS_Store
23
+ Thumbs.db
.gitignore ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .venv
6
+ venv/
7
+ ENV/
8
+
9
+ # Environment / Secrets (CRITICAL)
10
+ .env
11
+ backend/.env
12
+
13
+ # Databases
14
+ *.db
15
+ *.sqlite3
16
+ backend/data/*.db
17
+
18
+ # Local Uploads (S3 will handle this in cloud)
19
+ backend/data/uploads/*
20
+ !backend/data/uploads/.gitkeep
21
+ backend/data/reports/*
22
+ !backend/data/reports/.gitkeep
23
+
24
+ # OS/IDE
25
+ .vscode/
26
+ .idea/
27
+ .DS_Store
Dockerfile ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.11-slim
3
+
4
+ # Set environment variables
5
+ ENV PYTHONUNBUFFERED=1
6
+ ENV PYTHONDONTWRITEBYTECODE=1
7
+ ENV PYTHONPATH=/app/backend
8
+
9
+ # Set the working directory in the container
10
+ WORKDIR /app
11
+
12
+ # Install system dependencies for OpenCV and other libraries
13
+ RUN apt-get update && apt-get install -y --no-install-recommends \
14
+ libgl1-mesa-glx \
15
+ libglib2.0-0 \
16
+ libsm6 \
17
+ libxext6 \
18
+ libxrender-dev \
19
+ gcc \
20
+ python3-dev \
21
+ && rm -rf /var/lib/apt/lists/*
22
+
23
+ # Copy only the requirements first to leverage Docker cache
24
+ COPY backend/requirements.txt .
25
+
26
+ # Install dependencies
27
+ # Note: This will install CPU versions of torch to keep the image size manageable
28
+ # since most standard cloud servers don't have GPUs.
29
+ RUN pip install --no-cache-dir -r requirements.txt
30
+
31
+ # Copy the backend and frontend directories into the container
32
+ COPY backend /app/backend
33
+ COPY frontend /app/frontend
34
+
35
+ # Create necessary directories for runtime data (even if we move to S3 later)
36
+ RUN mkdir -p /app/backend/data/uploads/emotions \
37
+ /app/backend/data/uploads/diary \
38
+ /app/backend/data/reports
39
+
40
+ # Expose the port the app runs on
41
+ EXPOSE 8000
42
+
43
+ # Run the application using uvicorn
44
+ # We use 0.0.0.0 to allow external connections from the cloud
45
+ CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
add_column.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+
4
+ db_path = os.path.join("backend", "data", "neurosense.db")
5
+ if os.path.exists(db_path):
6
+ try:
7
+ conn = sqlite3.connect(db_path)
8
+ cursor = conn.cursor()
9
+ cursor.execute("ALTER TABLE emotion_logs ADD COLUMN image_hash VARCHAR;")
10
+ conn.commit()
11
+ print("Success! Added image_hash column to emotion_logs table.")
12
+ except Exception as e:
13
+ print(f"Error (maybe column already exists): {e}")
14
+ finally:
15
+ conn.close()
16
+ else:
17
+ print(f"Database not found at {db_path}")
backend/app/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Mark app as a package
backend/app/data/uploads/emotions/50ebcab1-5059-4dde-8dec-55a7e7b1c61e.avif ADDED
backend/app/data/uploads/emotions/a4fdb987-a0ec-4753-a92e-2658dfc5a2b0.avif ADDED
backend/app/database.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from sqlalchemy import create_engine
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+ from sqlalchemy.orm import sessionmaker
5
+
6
+ # Get the absolute path to the directory where this file is located (backend/app)
7
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
8
+ # Default local SQLite path
9
+ DEFAULT_DB_PATH = os.path.join(os.path.dirname(BASE_DIR), "data", "neurosense.db")
10
+
11
+ # Use DATABASE_URL from environment variable (for Cloud), or fallback to local SQLite
12
+ SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL", f"sqlite:///{DEFAULT_DB_PATH}")
13
+
14
+ # Fix for some cloud providers (like Heroku) that use 'postgres://' instead of 'postgresql://'
15
+ if SQLALCHEMY_DATABASE_URL.startswith("postgres://"):
16
+ SQLALCHEMY_DATABASE_URL = SQLALCHEMY_DATABASE_URL.replace("postgres://", "postgresql://", 1)
17
+
18
+ # SQLite requires 'check_same_thread: False', PostgreSQL does not
19
+ engine_args = {}
20
+ if SQLALCHEMY_DATABASE_URL.startswith("sqlite"):
21
+ engine_args["connect_args"] = {"check_same_thread": False}
22
+
23
+ engine = create_engine(SQLALCHEMY_DATABASE_URL, **engine_args)
24
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
25
+
26
+ Base = declarative_base()
27
+
28
+ def get_db():
29
+ db = SessionLocal()
30
+ try:
31
+ yield db
32
+ finally:
33
+ db.close()
backend/app/main.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.responses import RedirectResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.templating import Jinja2Templates
5
+ from app.database import engine, Base
6
+ from app.routers import auth, dashboard, emotion, activities, diary
7
+ import os
8
+
9
+ # Create Database Tables
10
+ Base.metadata.create_all(bind=engine)
11
+
12
+ app = FastAPI(title="NeuroSense")
13
+
14
+ # Get base directory (backend root)
15
+ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16
+ FRONTEND_DIR = os.path.join(os.path.dirname(BASE_DIR), "frontend")
17
+
18
+ # Ensure necessary directories exist in backend/data
19
+ os.makedirs(os.path.join(BASE_DIR, "data/uploads/emotions"), exist_ok=True)
20
+ os.makedirs(os.path.join(BASE_DIR, "data/uploads/diary"), exist_ok=True)
21
+ os.makedirs(os.path.join(BASE_DIR, "data/reports"), exist_ok=True)
22
+
23
+ # Mount Static Files (CSS, JS) from frontend folder
24
+ app.mount("/static", StaticFiles(directory=os.path.join(FRONTEND_DIR, "static")), name="static")
25
+
26
+ # Mount Data Folders for viewing (from backend/data)
27
+ app.mount("/uploads", StaticFiles(directory=os.path.join(BASE_DIR, "data/uploads")), name="uploads")
28
+ app.mount("/reports", StaticFiles(directory=os.path.join(BASE_DIR, "data/reports")), name="reports")
29
+
30
+ # Templates from frontend/templates
31
+ templates = Jinja2Templates(directory=os.path.join(FRONTEND_DIR, "templates"))
32
+
33
+ # Include Routers
34
+ app.include_router(auth.router)
35
+ app.include_router(dashboard.router)
36
+ app.include_router(emotion.router)
37
+ app.include_router(activities.router)
38
+ app.include_router(diary.router)
39
+
40
+ # Page Routes
41
+
42
+ @app.get("/")
43
+ def home_page(request: Request):
44
+ # Root is Dashboard
45
+ return templates.TemplateResponse("dashboard.html", {"request": request})
46
+
47
+ @app.get("/login")
48
+ def login_page(request: Request):
49
+ return templates.TemplateResponse("login.html", {"request": request})
50
+
51
+ @app.get("/children")
52
+ def children_page(request: Request):
53
+ return templates.TemplateResponse("children.html", {"request": request})
54
+
55
+ @app.get("/emotion-learning")
56
+ def emotion_page(request: Request):
57
+ return templates.TemplateResponse("emotion.html", {"request": request})
58
+
59
+ @app.get("/activities")
60
+ def activities_page(request: Request):
61
+ return templates.TemplateResponse("activities.html", {"request": request})
62
+
63
+ @app.get("/game/{game_name}")
64
+ def game_page(request: Request, game_name: str):
65
+ return templates.TemplateResponse("game.html", {"request": request, "game_name": game_name})
66
+
67
+ @app.get("/dashboard")
68
+ def dashboard_redirect(request: Request):
69
+ return RedirectResponse(url="/")
70
+
71
+ @app.get("/diary")
72
+ def diary_page(request: Request):
73
+ return templates.TemplateResponse("diary.html", {"request": request})
74
+
75
+ @app.get("/quiz")
76
+ def quiz_page(request: Request):
77
+ return templates.TemplateResponse("quiz.html", {"request": request})
78
+
79
+ if __name__ == "__main__":
80
+ import uvicorn
81
+ uvicorn.run(app, host="127.0.0.1", port=8000)
backend/app/models.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Float
2
+ from sqlalchemy.orm import relationship
3
+ from datetime import datetime
4
+ from app.database import Base
5
+
6
+ class User(Base):
7
+ __tablename__ = "users"
8
+ id = Column(Integer, primary_key=True, index=True)
9
+ username = Column(String, unique=True, index=True)
10
+ hashed_password = Column(String)
11
+ children = relationship("Child", back_populates="parent")
12
+ diary_entries = relationship("DiaryEntry", back_populates="parent")
13
+
14
+ class Child(Base):
15
+ __tablename__ = "children"
16
+ id = Column(Integer, primary_key=True, index=True)
17
+ name = Column(String)
18
+ age = Column(Integer)
19
+ parent_id = Column(Integer, ForeignKey("users.id"))
20
+ autism_inheritance = Column(String, default="") # Family history/traits
21
+ sensory_level = Column(String, default="standard") # 'low', 'standard', 'high'
22
+ parent = relationship("User", back_populates="children")
23
+
24
+ emotion_logs = relationship("EmotionLog", back_populates="child")
25
+ activity_logs = relationship("ActivityLog", back_populates="child")
26
+ quiz_results = relationship("QuizResult", back_populates="child")
27
+
28
+ class EmotionLog(Base):
29
+ __tablename__ = "emotion_logs"
30
+ id = Column(Integer, primary_key=True, index=True)
31
+ child_id = Column(Integer, ForeignKey("children.id"))
32
+ image_path = Column(String)
33
+ image_hash = Column(String, index=True) # New column
34
+ predicted_emotion = Column(String)
35
+ corrected_emotion = Column(String, nullable=True)
36
+ confirmed = Column(Boolean, default=False)
37
+ timestamp = Column(DateTime, default=datetime.utcnow)
38
+
39
+ child = relationship("Child", back_populates="emotion_logs")
40
+
41
+ class ActivityLog(Base):
42
+ __tablename__ = "activity_logs"
43
+ id = Column(Integer, primary_key=True, index=True)
44
+ child_id = Column(Integer, ForeignKey("children.id"))
45
+ activity_name = Column(String) # e.g., "Emotion Match", "Color Learn"
46
+ score = Column(Integer) # e.g., 100
47
+ duration_seconds = Column(Integer)
48
+ timestamp = Column(DateTime, default=datetime.utcnow)
49
+
50
+ child = relationship("Child", back_populates="activity_logs")
51
+
52
+ class QuizResult(Base):
53
+ __tablename__ = "quiz_results"
54
+ id = Column(Integer, primary_key=True, index=True)
55
+ child_id = Column(Integer, ForeignKey("children.id"))
56
+ quiz_name = Column(String)
57
+ score = Column(Integer)
58
+ total_questions = Column(Integer)
59
+ timestamp = Column(DateTime, default=datetime.utcnow)
60
+
61
+ child = relationship("Child", back_populates="quiz_results")
62
+
63
+ class DiaryEntry(Base):
64
+ __tablename__ = "diary_entries"
65
+ id = Column(Integer, primary_key=True, index=True)
66
+ parent_id = Column(Integer, ForeignKey("users.id"))
67
+ child_name = Column(String) # Or link directly to child, but keeping simple for now
68
+ title = Column(String)
69
+ message = Column(String)
70
+ image_path = Column(String, nullable=True)
71
+ video_path = Column(String, nullable=True)
72
+ timestamp = Column(DateTime, default=datetime.utcnow)
73
+
74
+ parent = relationship("User", back_populates="diary_entries")
backend/app/models/custom_ai/custom_weights.pth ADDED
Binary file (7.14 kB). View file
 
backend/app/models/custom_ai/labels.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"0": "angry", "1": "disgust", "2": "fear", "3": "happy", "4": "love", "5": "sad", "6": "shock", "7": "silly", "8": "surprise", "9": "tired"}
backend/app/routers/__init__.py ADDED
File without changes
backend/app/routers/activities.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends
2
+ from sqlalchemy.orm import Session
3
+ from app.database import get_db
4
+ from app.models import ActivityLog, QuizResult
5
+ from pydantic import BaseModel
6
+
7
+ router = APIRouter(prefix="/activities", tags=["activities"])
8
+
9
+ class ActivityCreate(BaseModel):
10
+ child_id: int
11
+ activity_name: str
12
+ score: int
13
+ duration_seconds: int
14
+
15
+ class QuizCreate(BaseModel):
16
+ child_id: int
17
+ quiz_name: str
18
+ score: int
19
+ total_questions: int
20
+
21
+ @router.post("/log-activity")
22
+ def log_activity(act: ActivityCreate, db: Session = Depends(get_db)):
23
+ new_log = ActivityLog(**act.dict())
24
+ db.add(new_log)
25
+ db.commit()
26
+ return {"message": "Activity logged"}
27
+
28
+ @router.post("/log-quiz")
29
+ def log_quiz(quiz: QuizCreate, db: Session = Depends(get_db)):
30
+ new_log = QuizResult(**quiz.dict())
31
+ db.add(new_log)
32
+ db.commit()
33
+ return {"message": "Quiz logged"}
backend/app/routers/auth.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, status, Response
2
+ from sqlalchemy.orm import Session
3
+ from app.database import get_db
4
+ from app.models import User, Child
5
+ from app.utils.auth_utils import get_password_hash, verify_password, create_access_token
6
+ from pydantic import BaseModel
7
+ from typing import List
8
+
9
+ router = APIRouter(prefix="/auth", tags=["auth"])
10
+
11
+ class UserCreate(BaseModel):
12
+ username: str
13
+ password: str
14
+
15
+ class ChildCreate(BaseModel):
16
+ name: str
17
+ age: int
18
+ parent_id: int
19
+ autism_inheritance: str = ""
20
+ sensory_level: str = "standard"
21
+
22
+ @router.post("/register")
23
+ def register(user: UserCreate, db: Session = Depends(get_db)):
24
+ db_user = db.query(User).filter(User.username == user.username).first()
25
+ if db_user:
26
+ raise HTTPException(status_code=400, detail="Username already registered")
27
+ hashed_pwd = get_password_hash(user.password)
28
+ new_user = User(username=user.username, hashed_password=hashed_pwd)
29
+ db.add(new_user)
30
+ db.commit()
31
+ db.refresh(new_user)
32
+ return {"message": "User created successfully"}
33
+
34
+ @router.post("/login")
35
+ def login(user: UserCreate, db: Session = Depends(get_db)):
36
+ db_user = db.query(User).filter(User.username == user.username).first()
37
+ if not db_user or not verify_password(user.password, db_user.hashed_password):
38
+ raise HTTPException(status_code=401, detail="Invalid credentials")
39
+ access_token = create_access_token(data={"sub": db_user.username, "id": db_user.id})
40
+ return {"access_token": access_token, "token_type": "bearer", "user_id": db_user.id}
41
+
42
+ @router.post("/add-child")
43
+ def add_child(child: ChildCreate, db: Session = Depends(get_db)):
44
+ new_child = Child(
45
+ name=child.name,
46
+ age=child.age,
47
+ parent_id=child.parent_id,
48
+ autism_inheritance=child.autism_inheritance,
49
+ sensory_level=child.sensory_level
50
+ )
51
+ db.add(new_child)
52
+ db.commit()
53
+ db.refresh(new_child)
54
+ return {"message": "Child added", "child_id": new_child.id}
55
+
56
+ @router.get("/children/{parent_id}")
57
+ def get_children(parent_id: int, db: Session = Depends(get_db)):
58
+ return db.query(Child).filter(Child.parent_id == parent_id).all()
59
+
60
+ @router.delete("/delete-child/{child_id}")
61
+ def delete_child(child_id: int, db: Session = Depends(get_db)):
62
+ db_child = db.query(Child).filter(Child.id == child_id).first()
63
+ if not db_child:
64
+ raise HTTPException(status_code=404, detail="Child not found")
65
+ db.delete(db_child)
66
+ db.commit()
67
+ return {"message": "Child deleted successfully"}
backend/app/routers/dashboard.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException
2
+ from sqlalchemy.orm import Session
3
+ from sqlalchemy import func
4
+ from app.database import get_db
5
+ from app.models import Child, EmotionLog, ActivityLog, QuizResult
6
+ from datetime import datetime, timedelta
7
+ from reportlab.lib.pagesizes import letter
8
+ from reportlab.pdfgen import canvas
9
+ import os
10
+
11
+ router = APIRouter(prefix="/dashboard", tags=["dashboard"])
12
+
13
+ @router.get("/progress/{child_id}")
14
+ def get_progress(child_id: int, db: Session = Depends(get_db)):
15
+ # Activity Stats
16
+ activity_stats = db.query(
17
+ ActivityLog.activity_name,
18
+ func.avg(ActivityLog.score).label('avg_score'),
19
+ func.count(ActivityLog.id).label('sessions')
20
+ ).filter(ActivityLog.child_id == child_id).group_by(ActivityLog.activity_name).all()
21
+
22
+ # Quiz Stats
23
+ quiz_stats = db.query(
24
+ QuizResult.quiz_name,
25
+ func.avg(QuizResult.score).label('avg_score'),
26
+ func.count(QuizResult.id).label('attempts')
27
+ ).filter(QuizResult.child_id == child_id).group_by(QuizResult.quiz_name).all()
28
+
29
+ # Emotion Stats (Last 30 days)
30
+ thirty_days_ago = datetime.utcnow() - timedelta(days=30)
31
+ emotion_stats = db.query(
32
+ EmotionLog.predicted_emotion,
33
+ func.count(EmotionLog.id).label('count')
34
+ ).filter(
35
+ EmotionLog.child_id == child_id,
36
+ EmotionLog.timestamp >= thirty_days_ago
37
+ ).group_by(EmotionLog.predicted_emotion).all()
38
+
39
+ return {
40
+ "activities": [{"name": s[0], "avg_score": s[1], "sessions": s[2]} for s in activity_stats],
41
+ "quizzes": [{"name": s[0], "avg_score": s[1], "attempts": s[2]} for s in quiz_stats],
42
+ "emotions": [{"emotion": s[0], "count": s[1]} for s in emotion_stats]
43
+ }
44
+
45
+ @router.get("/generate-report/{child_id}")
46
+ def generate_report(child_id: int, db: Session = Depends(get_db)):
47
+ child = db.query(Child).filter(Child.id == child_id).first()
48
+ if not child:
49
+ raise HTTPException(status_code=404, detail="Child not found")
50
+
51
+ report_dir = "data/reports"
52
+ os.makedirs(report_dir, exist_ok=True)
53
+ filename = f"report_{child_id}_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
54
+ file_path = os.path.join(report_dir, filename)
55
+
56
+ # Simple PDF generation with ReportLab
57
+ c = canvas.Canvas(file_path, pagesize=letter)
58
+ c.drawString(100, 750, f"NeuroSense - 15 Day Progress Report")
59
+ c.drawString(100, 730, f"Child Name: {child.name}")
60
+ c.drawString(100, 710, f"Age: {child.age}")
61
+ c.drawString(100, 690, f"Date: {datetime.now().strftime('%Y-%m-%d')}")
62
+
63
+ # Add activity summary
64
+ y = 650
65
+ c.drawString(100, y, "Activity Participation:")
66
+ y -= 20
67
+ activities = db.query(ActivityLog).filter(ActivityLog.child_id == child_id).all()
68
+ for act in activities[-5:]: # Last 5
69
+ c.drawString(120, y, f"- {act.activity_name}: Score {act.score} ({act.timestamp.strftime('%Y-%m-%d')})")
70
+ y -= 20
71
+
72
+ c.save()
73
+
74
+ return {"report_url": f"/reports/{filename}"}
backend/app/routers/diary.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, UploadFile, File, Form
2
+ from sqlalchemy.orm import Session
3
+ from app.database import get_db
4
+ from app.models import DiaryEntry
5
+ import os
6
+ import shutil
7
+ import uuid
8
+
9
+ router = APIRouter(prefix="/diary", tags=["diary"])
10
+
11
+ UPLOAD_DIR = "data/uploads/diary"
12
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
13
+
14
+ from app.utils.storage import save_file
15
+
16
+ @router.post("/add")
17
+ async def add_diary(
18
+ parent_id: int = Form(...),
19
+ child_name: str = Form(...),
20
+ title: str = Form(...),
21
+ message: str = Form(...),
22
+ file: UploadFile = File(None),
23
+ db: Session = Depends(get_db)
24
+ ):
25
+ image_path_db = None
26
+ if file:
27
+ image_path_db = save_file(file, "diary")
28
+
29
+ entry = DiaryEntry(
30
+ parent_id=parent_id,
31
+ child_name=child_name,
32
+ title=title,
33
+ message=message,
34
+ image_path=image_path_db
35
+ )
36
+ db.add(entry)
37
+ db.commit()
38
+ db.refresh(entry)
39
+ return {"message": "Diary entry added", "id": entry.id}
40
+
41
+ @router.get("/{parent_id}")
42
+ def get_diary(parent_id: int, db: Session = Depends(get_db)):
43
+ return db.query(DiaryEntry).filter(DiaryEntry.parent_id == parent_id).order_by(DiaryEntry.timestamp.desc()).all()
backend/app/routers/emotion.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ # Ensure Keras backend is torch if not already set (important for Python 3.14)
3
+ if "KERAS_BACKEND" not in os.environ:
4
+ os.environ["KERAS_BACKEND"] = "torch"
5
+
6
+ from fastapi import APIRouter, Depends, UploadFile, File, Form, HTTPException
7
+ from sqlalchemy.orm import Session
8
+ from app.database import get_db
9
+ from app.models import EmotionLog
10
+ import uuid
11
+ import shutil
12
+ from fer import FER
13
+ import cv2
14
+ import hashlib
15
+ from PIL import Image
16
+ import numpy as np
17
+ import torch
18
+ import torch.nn as nn
19
+ import json
20
+
21
+ router = APIRouter(prefix="/emotion", tags=["emotion"])
22
+ detector = FER(mtcnn=False)
23
+
24
+ # --- Custom Model Support ---
25
+ class EmotionClassifier(nn.Module):
26
+ def __init__(self, input_size, num_classes):
27
+ super(EmotionClassifier, self).__init__()
28
+ self.fc1 = nn.Linear(input_size, 64)
29
+ self.relu = nn.ReLU()
30
+ self.fc2 = nn.Linear(64, num_classes)
31
+ def forward(self, x):
32
+ return self.fc2(self.relu(self.fc1(x)))
33
+
34
+ # Get backend root directory
35
+ ROUTER_DIR = os.path.dirname(os.path.abspath(__file__))
36
+ APP_DIR = os.path.dirname(ROUTER_DIR)
37
+ BASE_DIR = os.path.dirname(APP_DIR)
38
+ UPLOAD_DIR = os.path.join(BASE_DIR, "data/uploads/emotions")
39
+ MODEL_PATH = os.path.join(APP_DIR, "models/custom_ai")
40
+ os.makedirs(UPLOAD_DIR, exist_ok=True)
41
+
42
+ def get_custom_emotion(base_emotions_dict):
43
+ """If a custom model exists, use it to refine the FER output."""
44
+ weights_path = os.path.join(MODEL_PATH, "custom_weights.pth")
45
+ labels_path = os.path.join(MODEL_PATH, "labels.json")
46
+
47
+ # Get base FER's best guess
48
+ top_emo = max(base_emotions_dict, key=base_emotions_dict.get)
49
+ top_score = base_emotions_dict[top_emo]
50
+
51
+ # If standard FER is VERY confident (> 0.95), trust it.
52
+ if top_score > 0.95:
53
+ return top_emo
54
+
55
+ # 1. Fallback to standard FER logic if no custom model exists
56
+ if not os.path.exists(weights_path) or not os.path.exists(labels_path):
57
+ if top_score < 0.35:
58
+ return "neutral"
59
+
60
+ if top_emo == "happy":
61
+ neutral_score = base_emotions_dict.get("neutral", 0)
62
+ if neutral_score > (top_score * 0.5):
63
+ return "neutral"
64
+
65
+ return top_emo
66
+
67
+ try:
68
+ with open(labels_path, "r") as f:
69
+ idx_to_emotion = json.load(f)
70
+
71
+ num_classes = len(idx_to_emotion)
72
+ model = EmotionClassifier(input_size=7, num_classes=num_classes)
73
+ model.load_state_dict(torch.load(weights_path, weights_only=True))
74
+ model.eval()
75
+
76
+ # Convert FER dict to tensor features (standard 7 emotions)
77
+ # Sort keys to ensure consistent feature order matching training
78
+ features_list = [base_emotions_dict[k] for k in sorted(base_emotions_dict.keys())]
79
+ features = torch.tensor(features_list, dtype=torch.float32).unsqueeze(0)
80
+
81
+ with torch.no_grad():
82
+ outputs = model(features)
83
+ probs = torch.softmax(outputs, dim=1)
84
+ conf, predicted = torch.max(probs, 1)
85
+
86
+ custom_emo = idx_to_emotion[str(predicted.item())]
87
+
88
+ # Use custom model if it has reasonable confidence (> 0.4)
89
+ # or if the base FER is not very confident (< 0.6)
90
+ if conf.item() > 0.4 or top_score < 0.6:
91
+ return custom_emo
92
+
93
+ return top_emo # Stick with base FER
94
+ except Exception as e:
95
+ print(f"DEBUG: Custom model inference error: {e}")
96
+ return top_emo
97
+
98
+ from app.utils.storage import save_file
99
+ import hashlib
100
+ from PIL import Image
101
+ import io
102
+
103
+ @router.post("/upload")
104
+ async def upload_emotion(
105
+ child_id: int = Form(...),
106
+ file: UploadFile = File(...),
107
+ db: Session = Depends(get_db)
108
+ ):
109
+ content = await file.read()
110
+ image_hash = hashlib.md5(content).hexdigest()
111
+
112
+ existing_entry = db.query(EmotionLog).filter(
113
+ EmotionLog.image_hash == image_hash,
114
+ EmotionLog.corrected_emotion != None
115
+ ).order_by(EmotionLog.timestamp.desc()).first()
116
+
117
+ # Reset file pointer for save_file
118
+ file.file.seek(0)
119
+ # Save file using utility (returns S3 URL or local path)
120
+ saved_path = save_file(file, "emotions")
121
+
122
+ if existing_entry:
123
+ predicted_emotion = existing_entry.corrected_emotion
124
+ else:
125
+ try:
126
+ # For FER detection, we still need a local copy or numpy array
127
+ # Using io.BytesIO to avoid saving to disk twice
128
+ pil_img = Image.open(io.BytesIO(content)).convert("RGB")
129
+ img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
130
+ if img is None:
131
+ predicted_emotion = "unknown"
132
+ else:
133
+ emotions = detector.detect_emotions(img)
134
+ if emotions:
135
+ predicted_emotion = get_custom_emotion(emotions[0]["emotions"])
136
+ else:
137
+ predicted_emotion = "neutral"
138
+ except Exception as e:
139
+ predicted_emotion = "error"
140
+ print(f"DEBUG: Image processing error: {e}")
141
+
142
+ log_entry = EmotionLog(
143
+ child_id=child_id,
144
+ image_path=saved_path,
145
+ image_hash=image_hash,
146
+ predicted_emotion=predicted_emotion,
147
+ confirmed=False
148
+ )
149
+ db.add(log_entry)
150
+ db.commit()
151
+ db.refresh(log_entry)
152
+
153
+ return {
154
+ "id": log_entry.id,
155
+ "predicted_emotion": predicted_emotion,
156
+ "image_url": saved_path
157
+ }
158
+
159
+ @router.post("/process-frame")
160
+ async def process_frame(
161
+ child_id: int = Form(...),
162
+ frame_data: str = Form(...),
163
+ db: Session = Depends(get_db)
164
+ ):
165
+ import base64
166
+ try:
167
+ header, encoded = frame_data.split(",", 1)
168
+ data = base64.b64decode(encoded)
169
+ nparr = np.frombuffer(data, np.uint8)
170
+ img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
171
+ if img is None:
172
+ return {"emotion": "unknown", "error": "Failed to decode image"}
173
+
174
+ emotions = detector.detect_emotions(img)
175
+ if emotions:
176
+ scores = emotions[0]["emotions"]
177
+ # Use custom model logic for real-time camera too!
178
+ predicted_emotion = get_custom_emotion(scores)
179
+ else:
180
+ predicted_emotion = "neutral"
181
+
182
+ return {"emotion": predicted_emotion}
183
+ except Exception as e:
184
+ return {"emotion": "unknown", "error": str(e)}
185
+
186
+ @router.post("/confirm")
187
+ def confirm_emotion(
188
+ log_id: int = Form(...),
189
+ corrected_emotion: str = Form(None),
190
+ confirmed: bool = Form(...),
191
+ db: Session = Depends(get_db)
192
+ ):
193
+ log_entry = db.query(EmotionLog).filter(EmotionLog.id == log_id).first()
194
+ if not log_entry:
195
+ raise HTTPException(status_code=404, detail="Log entry not found")
196
+
197
+ log_entry.confirmed = confirmed
198
+ if corrected_emotion:
199
+ log_entry.corrected_emotion = corrected_emotion
200
+ db.query(EmotionLog).filter(EmotionLog.image_hash == log_entry.image_hash).update({
201
+ "corrected_emotion": corrected_emotion,
202
+ "confirmed": True
203
+ })
204
+
205
+ db.commit()
206
+ return {"message": "Log updated successfully"}
207
+
208
+ @router.get("/unique-emotions")
209
+ def get_unique_emotions(db: Session = Depends(get_db)):
210
+ standard = {"happy", "sad", "angry", "fear", "surprise", "neutral"}
211
+ custom = db.query(EmotionLog.corrected_emotion).filter(EmotionLog.corrected_emotion != None).distinct().all()
212
+ custom_set = {c[0] for c in custom if c[0]}
213
+ return sorted(list(standard.union(custom_set)))
214
+
215
+ @router.post("/train")
216
+ async def train_model():
217
+ import subprocess
218
+ import sys
219
+ # Run the train_custom_model.py script as a subprocess
220
+ try:
221
+ # Need to find the absolute path to train_custom_model.py
222
+ # It is in the root directory (one level up from backend)
223
+ root_dir = os.path.dirname(BASE_DIR)
224
+ train_script = os.path.join(root_dir, "train_custom_model.py")
225
+
226
+ result = subprocess.run([sys.executable, train_script], capture_output=True, text=True)
227
+ if result.returncode == 0:
228
+ return {"message": "Success! AI brain retrained.", "output": result.stdout}
229
+ else:
230
+ # Combine stdout and stderr to catch the "FAILED:" message
231
+ error_msg = result.stdout + "\n" + result.stderr
232
+ return {"message": "Training failed", "error": error_msg.strip()}
233
+ except Exception as e:
234
+ return {"message": "Error starting training", "error": str(e)}
backend/app/utils/auth_utils.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from passlib.context import CryptContext
2
+ from datetime import datetime, timedelta
3
+ from jose import jwt
4
+
5
+ SECRET_KEY = "supersecretkey" # In production, use environment variable
6
+ ALGORITHM = "HS256"
7
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30
8
+
9
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
10
+
11
+ def verify_password(plain_password, hashed_password):
12
+ return pwd_context.verify(plain_password, hashed_password)
13
+
14
+ def get_password_hash(password):
15
+ return pwd_context.hash(password)
16
+
17
+ def create_access_token(data: dict, expires_delta: timedelta | None = None):
18
+ to_encode = data.copy()
19
+ if expires_delta:
20
+ expire = datetime.utcnow() + expires_delta
21
+ else:
22
+ expire = datetime.utcnow() + timedelta(minutes=15)
23
+ to_encode.update({"exp": expire})
24
+ encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
25
+ return encoded_jwt
backend/app/utils/storage.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import boto3
3
+ from botocore.exceptions import NoCredentialsError
4
+ from fastapi import UploadFile
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ # S3 Configuration from Environment Variables
9
+ S3_BUCKET = os.getenv("S3_BUCKET_NAME")
10
+ AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY_ID")
11
+ AWS_SECRET_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
12
+ AWS_REGION = os.getenv("AWS_REGION", "us-east-1")
13
+
14
+ # Initialize S3 Client
15
+ s3_client = None
16
+ if S3_BUCKET and AWS_ACCESS_KEY and AWS_SECRET_KEY:
17
+ s3_client = boto3.client(
18
+ "s3",
19
+ aws_access_key_id=AWS_ACCESS_KEY,
20
+ aws_secret_access_key=AWS_SECRET_KEY,
21
+ region_name=AWS_REGION
22
+ )
23
+
24
+ def save_file(file: UploadFile, folder: str) -> str:
25
+ """
26
+ Saves a file to S3 if configured, otherwise saves locally.
27
+ Returns the public URL (for S3) or the local relative path.
28
+ """
29
+ file_name = f"{os.urandom(8).hex()}_{file.filename}"
30
+
31
+ # --- Option A: Upload to S3 ---
32
+ if s3_client:
33
+ try:
34
+ s3_client.upload_fileobj(
35
+ file.file,
36
+ S3_BUCKET,
37
+ f"{folder}/{file_name}",
38
+ ExtraArgs={"ACL": "public-read"} # Make publicly accessible
39
+ )
40
+ # Return the S3 URL
41
+ return f"https://{S3_BUCKET}.s3.{AWS_REGION}.amazonaws.com/{folder}/{file_name}"
42
+ except NoCredentialsError:
43
+ print("AWS credentials not found. Falling back to local storage.")
44
+ except Exception as e:
45
+ print(f"S3 Upload failed: {e}. Falling back to local storage.")
46
+
47
+ # --- Option B: Local Fallback ---
48
+ # Determine base directory (backend root)
49
+ BASE_DIR = Path(__file__).resolve().parent.parent.parent
50
+ local_path = BASE_DIR / "data" / "uploads" / folder
51
+ local_path.mkdir(parents=True, exist_ok=True)
52
+
53
+ full_path = local_path / file_name
54
+ with open(full_path, "wb") as buffer:
55
+ shutil.copyfileobj(file.file, buffer)
56
+
57
+ # Return local relative path (as currently used in your DB)
58
+ return f"/uploads/{folder}/{file_name}"
59
+
60
+ def delete_file(file_path: str):
61
+ """
62
+ Optional: Delete a file from S3 or local disk.
63
+ """
64
+ if s3_client and "amazonaws.com" in file_path:
65
+ # Extract key from URL
66
+ key = file_path.split(".com/")[-1]
67
+ try:
68
+ s3_client.delete_object(Bucket=S3_BUCKET, Key=key)
69
+ except Exception as e:
70
+ print(f"S3 Delete failed: {e}")
71
+ elif file_path.startswith("/uploads/"):
72
+ # Local delete
73
+ BASE_DIR = Path(__file__).resolve().parent.parent.parent
74
+ full_path = BASE_DIR / "data" / file_path.lstrip("/")
75
+ if os.path.exists(full_path):
76
+ os.remove(full_path)
backend/requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ sqlalchemy
4
+ python-multipart
5
+ jinja2
6
+ python-jose[cryptography]
7
+ passlib[bcrypt]
8
+ bcrypt==4.0.1
9
+ fer
10
+ opencv-python
11
+ reportlab
12
+ python-dotenv
13
+ aiofiles
14
+ Pillow
15
+ torch
16
+ torchvision
17
+ torchaudio
18
+ numpy
19
+ moviepy<2.0.0
20
+ psycopg2-binary
21
+ boto3
check_data.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+
4
+ db_path = os.path.join("neurosense", "data", "neurosense.db")
5
+ if os.path.exists(db_path):
6
+ conn = sqlite3.connect(db_path)
7
+ cursor = conn.cursor()
8
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
9
+ tables = cursor.fetchall()
10
+ for table in tables:
11
+ name = table[0]
12
+ cursor.execute(f"SELECT count(*) FROM {name};")
13
+ count = cursor.fetchone()[0]
14
+ print(f"Table {name}: {count} rows")
15
+ conn.close()
16
+ else:
17
+ print(f"Database file not found at: {db_path}")
check_db.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+
4
+ db_path = os.path.join("neurosense", "data", "neurosense.db")
5
+ if os.path.exists(db_path):
6
+ conn = sqlite3.connect(db_path)
7
+ cursor = conn.cursor()
8
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
9
+ tables = cursor.fetchall()
10
+ print("Tables in database:")
11
+ for table in tables:
12
+ print(f"- {table[0]}")
13
+ conn.close()
14
+ else:
15
+ print(f"Database file not found at: {db_path}")
check_ids.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+
4
+ db_path = os.path.join("neurosense", "data", "neurosense.db")
5
+ if os.path.exists(db_path):
6
+ conn = sqlite3.connect(db_path)
7
+ cursor = conn.cursor()
8
+
9
+ print("--- Users ---")
10
+ cursor.execute("SELECT id, username FROM users")
11
+ users = cursor.fetchall()
12
+ for u in users:
13
+ print(f"ID: {u[0]}, Username: {u[1]}")
14
+
15
+ print("\n--- Children ---")
16
+ cursor.execute("SELECT id, name, parent_id FROM children")
17
+ children = cursor.fetchall()
18
+ for c in children:
19
+ print(f"ID: {c[0]}, Name: {c[1]}, Parent ID: {c[2]}")
20
+
21
+ conn.close()
22
+ else:
23
+ print(f"Database file not found at: {db_path}")
check_logs.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+
4
+ db_path = os.path.join("backend", "data", "neurosense.db")
5
+ if os.path.exists(db_path):
6
+ conn = sqlite3.connect(db_path)
7
+ cursor = conn.cursor()
8
+ cursor.execute("SELECT id, predicted_emotion, corrected_emotion, image_hash FROM emotion_logs ORDER BY id DESC LIMIT 5")
9
+ rows = cursor.fetchall()
10
+ print("Recent Emotion Logs:")
11
+ for r in rows:
12
+ print(f"ID: {r[0]}, Predicted: {r[1]}, Corrected: {r[2]}, Hash: {r[3]}")
13
+ conn.close()
14
+ else:
15
+ print("DB not found")
check_root_db.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+
4
+ db_path = os.path.join("data", "neurosense.db")
5
+ if os.path.exists(db_path):
6
+ conn = sqlite3.connect(db_path)
7
+ cursor = conn.cursor()
8
+
9
+ print("--- Users ---")
10
+ cursor.execute("SELECT id, username FROM users")
11
+ users = cursor.fetchall()
12
+ for u in users:
13
+ print(f"ID: {u[0]}, Username: {u[1]}")
14
+
15
+ print("\n--- Children ---")
16
+ cursor.execute("SELECT id, name, parent_id FROM children")
17
+ children = cursor.fetchall()
18
+ for c in children:
19
+ print(f"ID: {c[0]}, Name: {c[1]}, Parent ID: {c[2]}")
20
+
21
+ conn.close()
22
+ else:
23
+ print(f"Database file not found at: {db_path}")
frontend/static/css/style.css ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap');
2
+
3
+ :root {
4
+ --primary: #8d6e63;
5
+ --secondary: #d6c6a2;
6
+ --success: #6d8764;
7
+ --bg: #f4eee0;
8
+ --text: #4e342e;
9
+
10
+ --earth-tan: #eaddcf;
11
+ --earth-sand: #d6c6a2;
12
+ --earth-clay: #d7ccc8;
13
+ --earth-cream: #f5f5f5;
14
+ }
15
+
16
+ body {
17
+ font-family: 'Nunito', sans-serif;
18
+ background-color: var(--bg);
19
+ color: var(--text);
20
+ margin: 0;
21
+ line-height: 1.6;
22
+ }
23
+
24
+ header {
25
+ background: var(--secondary);
26
+ color: var(--text);
27
+ padding: 3.5rem 2rem;
28
+ text-align: center;
29
+ border-bottom: 8px solid #bdae8a;
30
+ box-shadow: 0 5px 20px rgba(0,0,0,0.05);
31
+ }
32
+
33
+ header h1 {
34
+ font-size: 3.5rem;
35
+ margin: 0;
36
+ font-weight: 800;
37
+ letter-spacing: -1px;
38
+ text-shadow: 2px 2px 0px rgba(255,255,255,0.3);
39
+ }
40
+
41
+ header p {
42
+ font-size: 1.3rem;
43
+ opacity: 0.8;
44
+ font-weight: 700;
45
+ }
46
+
47
+ nav {
48
+ display: flex;
49
+ justify-content: center;
50
+ gap: 10px;
51
+ padding: 12px;
52
+ background: white;
53
+ width: fit-content;
54
+ margin: -25px auto 40px;
55
+ border-radius: 15px;
56
+ box-shadow: 0 10px 25px rgba(78,52,46,0.1);
57
+ border: 3px solid var(--secondary);
58
+ }
59
+
60
+ nav a {
61
+ text-decoration: none;
62
+ color: var(--text);
63
+ font-weight: 800;
64
+ padding: 12px 24px;
65
+ border-radius: 10px;
66
+ transition: all 0.2s;
67
+ text-transform: uppercase;
68
+ font-size: 0.9rem;
69
+ }
70
+
71
+ nav a:hover {
72
+ background: var(--secondary);
73
+ }
74
+
75
+ .container {
76
+ max-width: 1100px;
77
+ margin: 0 auto 5rem;
78
+ padding: 0 1.5rem;
79
+ }
80
+
81
+ .grid {
82
+ display: grid;
83
+ grid-template-columns: repeat(auto-fit, minmax(300px,1fr));
84
+ gap: 2.5rem;
85
+ }
86
+
87
+ .card {
88
+ background: white;
89
+ padding: 2.5rem;
90
+ border-radius: 20px;
91
+ box-shadow: 0 10px 30px rgba(78,52,46,0.05);
92
+ border: 2px solid #e0d7c6;
93
+ transition: transform 0.3s;
94
+ }
95
+
96
+ .card:hover {
97
+ transform: translateY(-8px);
98
+ }
99
+
100
+ .card.theme-blue { background-color: #efebe9; }
101
+ .card.theme-green { background-color: #f5f5f5; }
102
+ .card.theme-purple { background-color: var(--earth-tan); }
103
+ .card.theme-orange { background-color: var(--earth-sand); }
104
+ .card.theme-pink { background-color: var(--earth-clay); }
105
+
106
+ button {
107
+ background: var(--text);
108
+ color: white;
109
+ border: none;
110
+ padding: 16px 32px;
111
+ border-radius: 12px;
112
+ font-weight: 800;
113
+ cursor: pointer;
114
+ box-shadow: 0 6px 0 #2d1b18;
115
+ transition: all 0.1s;
116
+ }
117
+
118
+ button:active {
119
+ transform: translateY(4px);
120
+ box-shadow: 0 2px 0 #2d1b18;
121
+ }
122
+
123
+ button:hover {
124
+ filter: brightness(1.2);
125
+ }
126
+
127
+ .btn-play {
128
+ background: white !important;
129
+ color: var(--text) !important;
130
+ box-shadow: 0 6px 0 #d6c6a2 !important;
131
+ border: 2px solid #d6c6a2 !important;
132
+ }
133
+
134
+ input, select, textarea {
135
+ width: 100%;
136
+ padding: 16px;
137
+ margin: 10px 0;
138
+ border: 2px solid #d6c6a2;
139
+ border-radius: 12px;
140
+ background: white;
141
+ font-family: inherit;
142
+ font-weight: 700;
143
+ color: var(--text);
144
+ }
145
+
146
+ .hidden {
147
+ display: none !important;
148
+ }
149
+
150
+ /* Sensory Themes - Re-unified to Beige/Brown */
151
+ body.calm-theme {
152
+ --primary: #a1887f;
153
+ --secondary: #d7ccc8;
154
+ --bg: #efebe9;
155
+ --text: #3e2723;
156
+ }
157
+ body.calm-theme .card, body.calm-theme .kids-card {
158
+ background: #fdfbf9;
159
+ border-color: #d7ccc8;
160
+ }
161
+ body.calm-theme header {
162
+ background: #d7ccc8;
163
+ border-bottom-color: #bcaaa4;
164
+ }
165
+
166
+ /* ========================= */
167
+ /* GAME UI IMPROVEMENTS */
168
+ /* ========================= */
169
+
170
+ .control-box {
171
+ display: flex;
172
+ justify-content: center;
173
+ gap: 20px;
174
+ margin-top: 30px;
175
+ flex-wrap: wrap;
176
+ }
177
+
178
+ .cute-btn {
179
+ padding: 12px 24px;
180
+ border-radius: 50px;
181
+ font-size: 1rem;
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 8px;
185
+ border: 3px solid white;
186
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
187
+ }
188
+
189
+ /* All buttons now shades of brown/beige */
190
+ .btn-pink, .btn-blue, .btn-orange, .btn-green {
191
+ background: var(--primary);
192
+ color: white;
193
+ }
194
+
195
+ /* ========================= */
196
+ /* BIG EMOJI GAME BUTTONS */
197
+ /* ========================= */
198
+
199
+ .emotion-options {
200
+ display:flex;
201
+ justify-content:center;
202
+ gap:40px;
203
+ flex-wrap:wrap;
204
+ margin-top:30px;
205
+ }
206
+
207
+ /* UPDATED SIZE FOR VISIBILITY */
208
+ .game-shape {
209
+ font-size: 150px;
210
+ width: 250px;
211
+ height: 250px;
212
+ cursor: pointer;
213
+ transition: transform 0.2s ease, background-color 0.2s;
214
+ background: white;
215
+ border-radius: 40px;
216
+ border: 6px solid var(--secondary);
217
+ box-shadow: 0 12px 0 var(--secondary);
218
+ display: flex;
219
+ justify-content: center;
220
+ align-items: center;
221
+ line-height: 1;
222
+ margin: 10px;
223
+ }
224
+
225
+ .game-shape:hover {
226
+ transform: scale(1.05) translateY(-5px);
227
+ background-color: #fffdf5;
228
+ }
229
+
230
+ .game-shape:active {
231
+ transform: translateY(6px);
232
+ box-shadow: 0 4px 0 var(--secondary);
233
+ }
234
+
235
+ /* color circles */
236
+
237
+ .color-swatch {
238
+ border-radius:50%;
239
+ cursor:pointer;
240
+ transition: transform 0.2s;
241
+ }
242
+
243
+ .color-swatch:hover {
244
+ transform: scale(1.15);
245
+ }
246
+
247
+ /* memory game */
248
+
249
+ .memory-card .content.hidden {
250
+ visibility:hidden;
251
+ }
252
+
253
+ /* ========================= */
254
+ /* KIDS THEME CARDS & PANELS */
255
+ /* ========================= */
256
+
257
+ .kids-card {
258
+ background: #fffdf5;
259
+ border: 4px solid #4e342e;
260
+ border-radius: 30px;
261
+ overflow: hidden;
262
+ box-shadow: 10px 10px 0px rgba(78, 52, 46, 0.1);
263
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
264
+ cursor: pointer;
265
+ text-align: center;
266
+ padding: 20px;
267
+ display: flex;
268
+ flex-direction: column;
269
+ align-items: center;
270
+ justify-content: space-between;
271
+ min-height: 280px;
272
+ }
273
+
274
+ .kids-card:hover {
275
+ transform: translateY(-8px) scale(1.02);
276
+ box-shadow: 12px 12px 0px rgba(78, 52, 46, 0.15);
277
+ }
278
+
279
+ .kids-card .icon {
280
+ font-size: 60px;
281
+ margin-bottom: 15px;
282
+ display: block;
283
+ filter: drop-shadow(0 5px 0 rgba(0,0,0,0.05));
284
+ }
285
+
286
+ .kids-card h3 {
287
+ margin: 8px 0;
288
+ font-size: 1.4rem;
289
+ color: #4e342e;
290
+ font-weight: 800;
291
+ }
292
+
293
+ .kids-card p {
294
+ font-size: 0.9rem;
295
+ color: #6d4c41;
296
+ font-weight: 700;
297
+ margin-bottom: 15px;
298
+ line-height: 1.3;
299
+ }
300
+
301
+ .btn-kids {
302
+ background: white !important;
303
+ color: #4e342e !important;
304
+ border: 3px solid #d6c6a2 !important;
305
+ box-shadow: 0 5px 0 #d6c6a2 !important;
306
+ padding: 10px 20px;
307
+ border-radius: 12px;
308
+ font-weight: 800;
309
+ text-transform: uppercase;
310
+ font-size: 0.8rem;
311
+ width: 100%;
312
+ transition: all 0.1s;
313
+ }
314
+
315
+ .kids-card:hover .btn-kids {
316
+ background: var(--secondary) !important;
317
+ border-color: #4e342e !important;
318
+ box-shadow: 0 5px 0 #4e342e !important;
319
+ color: #4e342e !important;
320
+ }
321
+
322
+ .grid-kids {
323
+ display: grid;
324
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
325
+ gap: 30px;
326
+ margin-top: 20px;
327
+ }
328
+
329
+ @media (min-width: 1100px) {
330
+ .grid-kids {
331
+ grid-template-columns: repeat(3, 1fr);
332
+ }
333
+ }
334
+
335
+ /* Reverting all theme classes to beige/brown variations */
336
+ .card.theme-blue, .kids-card.theme-blue { background-color: #efebe9; }
337
+ .card.theme-green, .kids-card.theme-green { background-color: #f5f5f5; }
338
+ .card.theme-purple, .kids-card.theme-purple { background-color: var(--earth-tan); }
339
+ .card.theme-orange, .kids-card.theme-orange { background-color: var(--earth-sand); }
340
+ .card.theme-pink, .kids-card.theme-pink { background-color: var(--earth-clay); }
frontend/static/js/script.js ADDED
The diff for this file is too large to render. See raw diff
 
frontend/templates/activities.html ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Activities - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.2">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1 id="page-title">Loading Activities...</h1>
12
+ <p id="age-level-indicator" style="font-weight: bold; color: var(--primary); font-size: 1.4rem; margin-top: -10px;"></p>
13
+ <p id="level-description"></p>
14
+ </header>
15
+ <nav>
16
+ <a href="/">Dashboard</a>
17
+ <a href="/emotion-learning">Emotion Learning</a>
18
+ <a href="/activities">Activities</a>
19
+ <a href="/quiz">Quiz</a>
20
+ <a href="/diary">Memory Diary</a>
21
+ <a href="/children">Child</a>
22
+ <a href="/login" id="nav-login">Login</a>
23
+ <a href="#" id="nav-logout" class="hidden" onclick="logout()">Logout</a>
24
+ </nav>
25
+
26
+ <div class="container" style="max-width: 1200px;">
27
+ <!-- The container starts empty to prevent clashing -->
28
+ <div id="game-selection-container" class="grid-kids">
29
+ </div>
30
+ </div>
31
+
32
+ <script src="/static/js/script.js?v=1.7"></script>
33
+ <script>
34
+ const GAME_TEMPLATES = {
35
+ 'color-match': `
36
+ <div class="kids-card theme-orange" onclick="goToGame('Color Match')">
37
+ <span class="icon">🎨</span>
38
+ <h3>Learn Colors</h3>
39
+ <p>Basic color concepts and matching!</p>
40
+ <button class="btn-kids">Play Now</button>
41
+ </div>`,
42
+ 'coloring-book': `
43
+ <div class="kids-card theme-pink" onclick="goToGame('Coloring Book')">
44
+ <span class="icon">🖌️</span>
45
+ <h3>Online Coloring</h3>
46
+ <p>Creativity and motor skills!</p>
47
+ <button class="btn-kids">Play Now</button>
48
+ </div>`,
49
+ 'animal-coloring': `
50
+ <div class="kids-card theme-green" onclick="goToGame('Animal Coloring')">
51
+ <span class="icon">🐶</span>
52
+ <h3>Animal Coloring</h3>
53
+ <p>Fine motor and color recognition!</p>
54
+ <button class="btn-kids">Play Now</button>
55
+ </div>`,
56
+ 'memory-game': `
57
+ <div class="kids-card theme-blue" onclick="goToGame('Memory Game')">
58
+ <span class="icon">🧠</span>
59
+ <h3>Memory Game</h3>
60
+ <p>Find the pairs and the magic star!</p>
61
+ <button class="btn-kids">Play Now</button>
62
+ </div>`,
63
+ 'pattern-match': `
64
+ <div class="kids-card theme-blue" onclick="goToGame('Pattern Match')">
65
+ <span class="icon">🧩</span>
66
+ <h3>Pattern Match</h3>
67
+ <p>Logical pattern challenges!</p>
68
+ <button class="btn-kids">Play Now</button>
69
+ </div>`,
70
+ 'mood-matcher': `
71
+ <div class="kids-card theme-purple" onclick="goToGame('Mood Matcher')">
72
+ <span class="icon">🎭</span>
73
+ <h3>Mood Matcher</h3>
74
+ <p>Advanced emotion recognition!</p>
75
+ <button class="btn-kids">Play Now</button>
76
+ </div>`,
77
+ 'emotion-basic': `
78
+ <div class="kids-card theme-purple" onclick="goToGame('Learn Emotions')">
79
+ <span class="icon">😊</span>
80
+ <h3>Learn Emotions</h3>
81
+ <p>Happy, sad, angry, and silly!</p>
82
+ <button class="btn-kids">Play Now</button>
83
+ </div>`,
84
+ 'shape-match': `
85
+ <div class="kids-card theme-orange" onclick="goToGame('Learn Shapes')">
86
+ <span class="icon">📐</span>
87
+ <h3>Learn Shapes</h3>
88
+ <p>Circle, square, triangle, and star!</p>
89
+ <button class="btn-kids">Play Now</button>
90
+ </div>`,
91
+ 'alphabet-trace': `
92
+ <div class="kids-card theme-blue" onclick="goToGame('Alphabet Trace')">
93
+ <span class="icon">📝</span>
94
+ <h3>Alphabet Writing</h3>
95
+ <p>Trace letters A to Z!</p>
96
+ <button class="btn-kids">Play Now</button>
97
+ </div>`,
98
+ 'number-write': `
99
+ <div class="kids-card theme-green" onclick="goToGame('Number Write')">
100
+ <span class="icon">🔢</span>
101
+ <h3>Number Writing</h3>
102
+ <p>Write numbers 1 to 10!</p>
103
+ <button class="btn-kids">Play Now</button>
104
+ </div>`,
105
+ 'alphabet-memory': `
106
+ <div class="kids-card theme-orange" onclick="goToGame('Alphabet Memory')">
107
+ <span class="icon">🔤</span>
108
+ <h3>Alphabet Match</h3>
109
+ <p>Match Upper and Lower case!</p>
110
+ <button class="btn-kids">Play Now</button>
111
+ </div>`,
112
+ 'word-match': `
113
+ <div class="kids-card theme-purple" onclick="goToGame('Word Match')">
114
+ <span class="icon">🔊</span>
115
+ <h3>Word Matching</h3>
116
+ <p>Audio flashcard memory!</p>
117
+ <button class="btn-kids">Play Now</button>
118
+ </div>`,
119
+ 'halves-match': `
120
+ <div class="kids-card theme-pink" onclick="goToGame('Halves Match')">
121
+ <span class="icon">🌓</span>
122
+ <h3>Joining Halves</h3>
123
+ <p>Complete the pictures!</p>
124
+ <button class="btn-kids">Play Now</button>
125
+ </div>`,
126
+ 'abc-sort': `
127
+ <div class="kids-card theme-blue" onclick="goToGame('Alphabet Sort')">
128
+ <span class="icon">🔠</span>
129
+ <h3>Alphabet Sort</h3>
130
+ <p>Upper and lower case sorting!</p>
131
+ <button class="btn-kids">Play Now</button>
132
+ </div>`,
133
+ 'alphabet-order': `
134
+ <div class="kids-card theme-orange" onclick="goToGame('Alphabet Order')">
135
+ <span class="icon">🔡</span>
136
+ <h3>Alphabet Order</h3>
137
+ <p>Put letters in A-Z order!</p>
138
+ <button class="btn-kids">Play Now</button>
139
+ </div>`,
140
+ 'missing-letter': `
141
+ <div class="kids-card theme-green" onclick="goToGame('Missing Letter')">
142
+ <span class="icon">❓</span>
143
+ <h3>Missing Letter</h3>
144
+ <p>Spelling and phonics challenge!</p>
145
+ <button class="btn-kids">Play Now</button>
146
+ </div>`,
147
+ 'word-picture': `
148
+ <div class="kids-card theme-orange" onclick="goToGame('Word Picture Match')">
149
+ <span class="icon">🖼️</span>
150
+ <h3>Words & Pictures</h3>
151
+ <p>Match complex words to icons!</p>
152
+ <button class="btn-kids">Play Now</button>
153
+ </div>`,
154
+ 'object-search': `
155
+ <div class="kids-card theme-purple" onclick="goToGame('Object Search')">
156
+ <span class="icon">🔍</span>
157
+ <h3>Object Search</h3>
158
+ <p>Find colors and objects!</p>
159
+ <button class="btn-kids">Play Now</button>
160
+ </div>`,
161
+ 'jigsaw-puzzle': `
162
+ <div class="kids-card theme-pink" onclick="goToGame('Jigsaw Puzzle')">
163
+ <span class="icon">🧩</span>
164
+ <h3>Jigsaw Puzzle</h3>
165
+ <p>Complete the picture pieces!</p>
166
+ <button class="btn-kids">Play Now</button>
167
+ </div>`,
168
+ 'spatial-puzzle': `
169
+ <div class="kids-card theme-pink" onclick="goToGame('Spatial Puzzle')">
170
+ <span class="icon">📐</span>
171
+ <h3>Spatial Reasoning</h3>
172
+ <p>Match the missing pieces!</p>
173
+ <button class="btn-kids">Play Now</button>
174
+ </div>`,
175
+ 'advanced-patterns': `
176
+ <div class="kids-card theme-blue" onclick="goToGame('Advanced Patterns')">
177
+ <span class="icon">📈</span>
178
+ <h3>Pattern Logic</h3>
179
+ <p>Complex pattern completion!</p>
180
+ <button class="btn-kids">Play Now</button>
181
+ </div>`
182
+ };
183
+
184
+ document.addEventListener('DOMContentLoaded', async () => {
185
+ console.log("Activities page loaded");
186
+
187
+ // Check if token exists (global from script.js)
188
+ const activeToken = localStorage.getItem('token');
189
+ if (!activeToken) {
190
+ console.log("No token found, redirecting to login");
191
+ window.location.href = '/login';
192
+ return;
193
+ }
194
+
195
+ // Try to sync child data, but don't block forever
196
+ try {
197
+ if (typeof syncChildAge === 'function') {
198
+ await syncChildAge();
199
+ } else {
200
+ console.warn("syncChildAge function not found");
201
+ }
202
+ } catch (e) {
203
+ console.error("Error syncing child age:", e);
204
+ }
205
+
206
+ renderLevelSpecificContent();
207
+ });
208
+
209
+ function renderLevelSpecificContent() {
210
+ console.log("Rendering activities...");
211
+ const ageStr = localStorage.getItem('selectedChildAge');
212
+ const age = (ageStr && ageStr !== 'undefined') ? parseInt(ageStr) : 0;
213
+ const container = document.getElementById('game-selection-container');
214
+ const title = document.getElementById('page-title');
215
+ const indicator = document.getElementById('age-level-indicator');
216
+ const desc = document.getElementById('level-description');
217
+
218
+ if (!container) {
219
+ console.error("Game selection container not found!");
220
+ return;
221
+ }
222
+
223
+ // Clear container
224
+ container.innerHTML = '';
225
+
226
+ // Fallback for missing level info function
227
+ let levelInfo = { name: "All Levels", desc: "Fun activities for everyone!", level: 1 };
228
+ if (typeof getLevelInfo === 'function') {
229
+ levelInfo = getLevelInfo(age);
230
+ }
231
+
232
+ title.innerText = "Your Activities";
233
+
234
+ if (age === 0) {
235
+ indicator.innerText = "All Activities";
236
+ desc.innerText = "Try any game below to start learning!";
237
+
238
+ // Show ALL games
239
+ Object.values(GAME_TEMPLATES).forEach(template => {
240
+ container.innerHTML += template;
241
+ });
242
+ return;
243
+ }
244
+
245
+ indicator.innerText = levelInfo.name;
246
+ desc.innerText = levelInfo.desc;
247
+
248
+ // Show recommended + other games based on age
249
+ if (age >= 3 && age <= 6) {
250
+ container.innerHTML += GAME_TEMPLATES['animal-coloring'];
251
+ container.innerHTML += GAME_TEMPLATES['color-match'];
252
+ container.innerHTML += GAME_TEMPLATES['shape-match'];
253
+ container.innerHTML += GAME_TEMPLATES['emotion-basic'];
254
+ container.innerHTML += GAME_TEMPLATES['coloring-book'];
255
+ }
256
+ else if (age >= 7 && age <= 9) {
257
+ container.innerHTML += GAME_TEMPLATES['alphabet-trace'];
258
+ container.innerHTML += GAME_TEMPLATES['number-write'];
259
+ container.innerHTML += GAME_TEMPLATES['alphabet-memory'];
260
+ container.innerHTML += GAME_TEMPLATES['word-match'];
261
+ container.innerHTML += GAME_TEMPLATES['halves-match'];
262
+ container.innerHTML += GAME_TEMPLATES['pattern-match'];
263
+ container.innerHTML += GAME_TEMPLATES['memory-game'];
264
+ }
265
+ else {
266
+ container.innerHTML += GAME_TEMPLATES['abc-sort'];
267
+ container.innerHTML += GAME_TEMPLATES['alphabet-order'];
268
+ container.innerHTML += GAME_TEMPLATES['missing-letter'];
269
+ container.innerHTML += GAME_TEMPLATES['word-picture'];
270
+ container.innerHTML += GAME_TEMPLATES['object-search'];
271
+ container.innerHTML += GAME_TEMPLATES['jigsaw-puzzle'];
272
+ container.innerHTML += GAME_TEMPLATES['spatial-puzzle'];
273
+ container.innerHTML += GAME_TEMPLATES['advanced-patterns'];
274
+ container.innerHTML += GAME_TEMPLATES['mood-matcher'];
275
+ }
276
+
277
+ if (container.innerHTML === '') {
278
+ console.warn("No activities rendered for age:", age);
279
+ container.innerHTML = "<h3>No specific activities found for this age. Try the games above!</h3>";
280
+ Object.values(GAME_TEMPLATES).forEach(template => {
281
+ container.innerHTML += template;
282
+ });
283
+ }
284
+ }
285
+ function goToGame(name) {
286
+ window.location.href = `/game/${encodeURIComponent(name)}`;
287
+ }
288
+ </script>
289
+ </body>
290
+ </html>
frontend/templates/children.html ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Manage Child - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.1">
8
+ <style>
9
+ .child-card {
10
+ display: flex;
11
+ justify-content: space-between;
12
+ align-items: center;
13
+ padding: 1.5rem;
14
+ margin-bottom: 1rem;
15
+ }
16
+ .child-info h3 { margin: 0; color: var(--primary); }
17
+ .child-actions { display: flex; gap: 10px; }
18
+ .btn-delete {
19
+ background: #e74c3c;
20
+ box-shadow: 0 4px 0 #c0392b;
21
+ }
22
+ .btn-select {
23
+ background: #2ecc71;
24
+ box-shadow: 0 4px 0 #27ae60;
25
+ }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <header>
30
+ <h1>Manage Children</h1>
31
+ <p>Add, select, or remove child profiles</p>
32
+ </header>
33
+ <nav>
34
+ <a href="/">Dashboard</a>
35
+ <a href="/emotion-learning">Emotion Learning</a>
36
+ <a href="/activities">Activities</a>
37
+ <a href="/quiz">Quiz</a>
38
+ <a href="/diary">Memory Diary</a>
39
+ <a href="/children">Child</a>
40
+ <a href="/login" id="nav-login">Login</a>
41
+ <a href="#" id="nav-logout" class="hidden" onclick="logout()">Logout</a>
42
+ </nav>
43
+
44
+ <div class="container">
45
+ <div class="grid">
46
+ <div class="card theme-orange">
47
+ <h3>➕ Add New Child</h3>
48
+ <input type="text" id="child-name" placeholder="Child's Name" style="background: white;">
49
+ <input type="number" id="child-age" placeholder="Child's Age" style="background: white;">
50
+
51
+ <div style="margin-top: 15px; text-align: left;">
52
+ <label style="font-weight: bold; font-size: 0.9rem;">Autism Traits/History in Family?</label>
53
+ <select id="child-autism-inheritance" style="background: white; margin-top: 5px;">
54
+ <option value="none">No known history</option>
55
+ <option value="immediate">Immediate family (parents/siblings)</option>
56
+ <option value="extended">Extended family (cousins/grandparents)</option>
57
+ <option value="suspected">Suspected traits (not diagnosed)</option>
58
+ </select>
59
+ </div>
60
+
61
+ <div style="margin-top: 15px; text-align: left;">
62
+ <label style="font-weight: bold; font-size: 0.9rem;">Sensory Sensitivity Level?</label>
63
+ <select id="child-sensory-level" style="background: white; margin-top: 5px;">
64
+ <option value="low">Low (Needs more stimulation)</option>
65
+ <option value="standard" selected>Standard (Balanced)</option>
66
+ <option value="high">High (Needs calm environment)</option>
67
+ </select>
68
+ </div>
69
+
70
+ <button onclick="addChild()" style="width:100%; margin-top: 20px;">Add Profile</button>
71
+ </div>
72
+
73
+ <div class="card theme-blue">
74
+ <h3>👶 Your Children</h3>
75
+ <div id="child-list">
76
+ <!-- Loaded via JS -->
77
+ <p>Loading children...</p>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <script src="/static/js/script.js"></script>
84
+ <script>
85
+ document.addEventListener('DOMContentLoaded', () => {
86
+ if (!token) window.location.href = '/login';
87
+ else loadChildren();
88
+ });
89
+ </script>
90
+ </body>
91
+ </html>
frontend/templates/dashboard.html ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Dashboard - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.1">
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ </head>
10
+ <body>
11
+ <header>
12
+ <h1>Dashboard</h1>
13
+ <p>Track progress and celebrate achievements! 🏆</p>
14
+ </header>
15
+ <nav>
16
+ <a href="/">Dashboard</a>
17
+ <a href="/emotion-learning">Emotion Learning</a>
18
+ <a href="/activities">Activities</a>
19
+ <a href="/quiz">Quiz</a>
20
+ <a href="/diary">Memory Diary</a>
21
+ <a href="/children">Child</a>
22
+ <a href="/login" id="nav-login">Login</a>
23
+ <a href="#" id="nav-logout" class="hidden" onclick="logout()">Logout</a>
24
+ </nav>
25
+
26
+ <div class="container">
27
+ <div class="card theme-orange" style="margin-bottom: 40px;">
28
+ <div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px;">
29
+ <div style="flex: 1; min-width: 300px;">
30
+ <h2 style="margin-bottom: 10px;">👋 Welcome Back!</h2>
31
+ <p style="font-weight: 700;">Select a child to view their amazing progress.</p>
32
+ <div style="display: flex; gap: 10px; margin-top: 15px;">
33
+ <select id="dashboard-child-select" onchange="loadProgress()" style="max-width: 250px; margin: 0; background: white;">
34
+ <option value="">Choose a Profile</option>
35
+ </select>
36
+ <button onclick="generateReport()" class="btn-play" style="width: auto; margin: 0;">PDF Report</button>
37
+ </div>
38
+ </div>
39
+ <div>
40
+ <a href="/children" class="button" style="text-decoration: none; background: white; color: var(--primary); padding: 14px 28px; border-radius: 20px; font-weight: 800; box-shadow: 0 6px 0 #ddd;">Manage Profiles</a>
41
+ </div>
42
+ </div>
43
+ <div id="report-link" style="margin-top: 15px;"></div>
44
+ </div>
45
+
46
+ <!-- New: Learning Level Display -->
47
+ <div id="dashboard-level-display"></div>
48
+
49
+ <div class="grid">
50
+ <div class="card" style="background: white;">
51
+ <h3>📊 Activity Performance</h3>
52
+ <p style="font-size: 0.9rem; color: #888; margin-bottom: 15px;">Scores across different learning games</p>
53
+ <canvas id="activitiesChart"></canvas>
54
+ </div>
55
+ <div class="card" style="background: white;">
56
+ <h3>🌈 Emotion Distribution</h3>
57
+ <p style="font-size: 0.9rem; color: #888; margin-bottom: 15px;">Emotions detected during learning sessions</p>
58
+ <canvas id="emotionChart"></canvas>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="card" style="margin-top: 40px;">
63
+ <h3>✨ Recent Achievements</h3>
64
+ <div id="progress-highlights" style="margin-top: 20px;">
65
+ <p style="color: #999; font-style: italic;">Select a child to see highlights...</p>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ <script src="/static/js/script.js"></script>
70
+ <script>
71
+ document.addEventListener('DOMContentLoaded', () => {
72
+ if (!token) window.location.href = '/login';
73
+ loadChildrenForDashboard();
74
+ });
75
+ </script>
76
+ </body>
77
+ </html>
frontend/templates/diary.html ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Memory Diary - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.1">
8
+ </head>
9
+ <body>
10
+ <header><h1>Memory Diary</h1></header>
11
+ <nav>
12
+ <a href="/">Dashboard</a>
13
+ <a href="/emotion-learning">Emotion Learning</a>
14
+ <a href="/activities">Activities</a>
15
+ <a href="/quiz">Quiz</a>
16
+ <a href="/diary">Memory Diary</a>
17
+ <a href="/children">Child</a>
18
+ <a href="/login" id="nav-login">Login</a>
19
+ <a href="#" id="nav-logout" class="hidden" onclick="logout()">Logout</a>
20
+ </nav>
21
+
22
+ <div class="container" style="max-width: 800px;">
23
+ <div class="kids-card theme-pink" style="cursor: default; text-align: left; align-items: flex-start;">
24
+ <div style="display: flex; align-items: center; gap: 15px; margin-bottom: 20px;">
25
+ <span class="icon" style="font-size: 50px; margin: 0;">📖</span>
26
+ <h2 style="margin: 0; font-size: 2rem;">New Memory</h2>
27
+ </div>
28
+ <p style="text-align: left; margin-bottom: 20px;">Write down a happy memory or something fun you did today!</p>
29
+ <input type="text" id="diary-title" placeholder="Memory Title (e.g., Fun at the Park!)" style="margin-bottom: 15px;">
30
+ <textarea id="diary-message" placeholder="What happened today? Tell your story..." style="min-height: 120px; margin-bottom: 15px;"></textarea>
31
+ <div style="width: 100%; margin-bottom: 20px;">
32
+ <label style="font-weight: 800; display: block; margin-bottom: 5px; color: #4e342e;">Add a Photo (optional):</label>
33
+ <input type="file" id="diary-upload" accept="image/*" style="padding: 10px; border-style: dashed;">
34
+ </div>
35
+ <button onclick="saveDiary()" class="btn-kids">✨ Save to My Timeline ✨</button>
36
+ </div>
37
+ <div style="margin: 50px 0 30px; text-align: center;">
38
+ <h2 style="font-size: 2.5rem; color: #4e342e; text-shadow: 2px 2px 0 white;">🌈 My Timeline</h2>
39
+ <div style="width: 100px; height: 6px; background: var(--secondary); margin: 10px auto; border-radius: 3px;"></div>
40
+ </div>
41
+ <div id="diary-timeline"></div>
42
+ </div>
43
+ <script src="/static/js/script.js"></script>
44
+ <script>
45
+ document.addEventListener('DOMContentLoaded', () => {
46
+ if (!token) window.location.href = '/login';
47
+ loadDiary();
48
+ });
49
+ </script>
50
+ </body>
51
+ </html>
frontend/templates/emotion.html ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Emotion Learning - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.1">
8
+ <style>
9
+ .learning-grid {
10
+ display: grid;
11
+ grid-template-columns: 1fr 1fr;
12
+ gap: 30px;
13
+ align-items: start;
14
+ }
15
+
16
+ .feature-panel {
17
+ background: #fffdf5;
18
+ border: 4px solid #4e342e;
19
+ border-radius: 30px;
20
+ overflow: hidden;
21
+ box-shadow: 10px 10px 0px rgba(78, 52, 46, 0.1);
22
+ transition: transform 0.2s;
23
+ }
24
+
25
+ .panel-header {
26
+ background: var(--secondary);
27
+ padding: 20px;
28
+ border-bottom: 4px solid #4e342e;
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 15px;
32
+ }
33
+
34
+ .panel-header h2 {
35
+ margin: 0;
36
+ font-size: 1.8rem;
37
+ color: #4e342e;
38
+ }
39
+
40
+ .panel-content {
41
+ padding: 25px;
42
+ }
43
+
44
+ .instruction-tag {
45
+ display: inline-block;
46
+ background: #fdfae6;
47
+ color: #8d6e63;
48
+ padding: 5px 15px;
49
+ border-radius: 10px;
50
+ font-weight: 800;
51
+ font-size: 0.8rem;
52
+ margin-bottom: 15px;
53
+ border: 2px solid #eaddcf;
54
+ }
55
+
56
+ .camera-container-v2 {
57
+ background: #fdfaf0;
58
+ border-radius: 20px;
59
+ overflow: hidden;
60
+ position: relative;
61
+ border: 4px solid #d6c6a2;
62
+ min-height: 300px;
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ }
67
+
68
+ .camera-overlay {
69
+ font-family: 'Nunito', sans-serif;
70
+ font-weight: 900;
71
+ padding: 10px 25px;
72
+ border: 3px solid #d6c6a2 !important;
73
+ border-radius: 40px;
74
+ background: white;
75
+ color: #4e342e;
76
+ font-size: 1.2rem;
77
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
78
+ }
79
+
80
+ .camera-controls-v3 {
81
+ display: flex;
82
+ gap: 12px;
83
+ justify-content: center;
84
+ flex-wrap: wrap;
85
+ margin-top: 25px;
86
+ }
87
+
88
+ .btn-light-action {
89
+ background: white !important;
90
+ color: #4e342e !important;
91
+ border: 3px solid #d6c6a2 !important;
92
+ box-shadow: 0 5px 0 #d6c6a2 !important;
93
+ min-width: 130px;
94
+ padding: 12px 20px;
95
+ font-size: 0.9rem;
96
+ }
97
+
98
+ .btn-light-action:hover {
99
+ background: #fdfaf0 !important;
100
+ }
101
+
102
+ .btn-light-action:active {
103
+ transform: translateY(3px);
104
+ box-shadow: 0 2px 0 #d6c6a2 !important;
105
+ }
106
+
107
+ .upload-zone {
108
+ border: 3px dashed #d6c6a2;
109
+ padding: 30px;
110
+ text-align: center;
111
+ border-radius: 20px;
112
+ background: #fffcf5;
113
+ cursor: pointer;
114
+ transition: all 0.2s;
115
+ }
116
+
117
+ .upload-zone:hover {
118
+ background: #fdf8eb;
119
+ border-color: #8d6e63;
120
+ }
121
+
122
+ .training-section {
123
+ grid-column: span 2;
124
+ margin-top: 20px;
125
+ }
126
+
127
+ @media (max-width: 950px) {
128
+ .learning-grid { grid-template-columns: 1fr; }
129
+ .training-section { grid-column: span 1; }
130
+ }
131
+ </style>
132
+ </head>
133
+ <body>
134
+ <header>
135
+ <h1>Emotion Learning</h1>
136
+ <p>Explore and recognize emotions with AI assistance! 🌟</p>
137
+ </header>
138
+ <nav>
139
+ <a href="/">Dashboard</a>
140
+ <a href="/emotion-learning">Emotion Learning</a>
141
+ <a href="/activities">Activities</a>
142
+ <a href="/quiz">Quiz</a>
143
+ <a href="/diary">Memory Diary</a>
144
+ <a href="/children">Child</a>
145
+ <a href="/login" id="nav-login">Login</a>
146
+ <a href="#" id="nav-logout" class="hidden" onclick="logout()">Logout</a>
147
+ </nav>
148
+
149
+ <div class="container" style="max-width: 1200px;">
150
+ <div class="learning-grid">
151
+
152
+ <!-- Left Panel: Live Scan -->
153
+ <div class="feature-panel">
154
+ <div class="panel-header">
155
+ <span style="font-size: 2.5rem;">📸</span>
156
+ <h2>Live Detection</h2>
157
+ </div>
158
+ <div class="panel-content">
159
+ <span class="instruction-tag">STEP 1: SEE EMOTIONS LIVE</span>
160
+ <p style="font-size: 0.95rem; margin-bottom: 20px; font-weight: 600;">Look into the lens! The AI will identify your emotions in real-time.</p>
161
+
162
+ <div class="camera-container-v2">
163
+ <video id="webcam" autoplay playsinline style="width: 100%; display: block;"></video>
164
+ <div id="live-overlay" class="camera-overlay" style="position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); width: 80%; text-align: center;">Ready...</div>
165
+ </div>
166
+
167
+ <div class="camera-controls-v3">
168
+ <button onclick="startCamera()" class="btn-light-action">Activate Lens</button>
169
+ <button onclick="capturePhoto()" class="btn-play" style="width: auto; margin: 0; min-width: 130px;">Snapshot</button>
170
+ <button onclick="stopCamera()" class="btn-light-action">Turn Off</button>
171
+ </div>
172
+ <p id="live-result" style="text-align: center; font-weight: 800; margin-top: 15px; color: var(--text);"></p>
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Right Panel: Photo Analysis -->
177
+ <div class="feature-panel">
178
+ <div class="panel-header">
179
+ <span style="font-size: 2.5rem;">📂</span>
180
+ <h2>Photo Library</h2>
181
+ </div>
182
+ <div class="panel-content">
183
+ <span class="instruction-tag">STEP 2: ANALYZE SAVED PHOTOS</span>
184
+ <p style="font-size: 0.95rem; margin-bottom: 20px; font-weight: 600;">Choose a file from your device to perform a deep emotion scan.</p>
185
+
186
+ <div class="upload-zone" onclick="document.getElementById('emotion-upload').click()">
187
+ <div style="font-size: 3rem; margin-bottom: 10px;">☁️</div>
188
+ <p style="margin: 0; font-weight: 800;">Click to Upload Image</p>
189
+ <input type="file" id="emotion-upload" accept="image/*" style="display: none;" onchange="handleFileSelected(this)">
190
+ <p id="file-name-display" style="font-size: 0.8rem; color: var(--primary); margin-top: 10px;"></p>
191
+ </div>
192
+ <button onclick="uploadEmotion()" style="width: 100%; margin-top: 15px;" class="btn-play">Run Analysis</button>
193
+
194
+ <div id="emotion-result" class="ai-feedback-card hidden" style="margin-top: 25px; border: 3px solid var(--secondary); background: #fffcf5;">
195
+ <h3 style="margin-bottom: 15px; font-size: 1.4rem;">Scan Result: <span id="predicted-emotion" style="color: var(--primary);"></span></h3>
196
+ <img id="result-img" style="max-width: 100%; border-radius: 15px; border: 4px solid white; box-shadow: 0 5px 15px rgba(0,0,0,0.1);">
197
+
198
+ <div style="margin-top: 10px; padding-top: 15px; border-top: 2px dashed #d6c6a2; position: relative; top: -10px;">
199
+ <p style="font-weight: 800; margin-bottom: 10px;">Verify Result:</p>
200
+ <div style="display: flex; gap: 15px; justify-content: center; align-items: center; width: 100%;">
201
+ <button onclick="confirmEmotion(true)" class="btn-select" style="flex: 1; min-width: 120px; padding: 12px 10px; font-size: 0.9rem; margin: 0;">Correct ✅</button>
202
+ <button onclick="confirmEmotion(false)" class="btn-delete" style="flex: 1; min-width: 120px; padding: 12px 10px; font-size: 0.9rem; margin: 0;">Wrong ❌</button>
203
+ </div>
204
+
205
+ <div id="correction-area" class="hidden" style="margin-top: 20px; text-align: left;">
206
+ <label style="font-weight: 800; font-size: 0.9rem;">Select Correct Emotion:</label>
207
+ <select id="corrected-emotion" onchange="toggleCustomEmotion()" style="margin-top: 5px; border-width: 3px;">
208
+ <option value="happy">Happy</option>
209
+ <option value="sad">Sad</option>
210
+ <option value="angry">Angry</option>
211
+ <option value="fear">Fear</option>
212
+ <option value="surprise">Surprise</option>
213
+ <option value="neutral">Neutral</option>
214
+ <option value="other">Other (Type new...)</option>
215
+ </select>
216
+ <input type="text" id="custom-emotion-name" class="hidden" placeholder="Enter emotion name">
217
+ <button onclick="saveCorrection()" class="btn-play" style="margin-top: 10px; width: 100%;">Update AI Knowledge</button>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ </div>
223
+
224
+ <!-- Bottom Section: Training Hub -->
225
+ <div class="training-section kids-card theme-orange">
226
+ <div style="display: flex; align-items: center; gap: 15px; justify-content: center;">
227
+ <span class="icon">🧠</span>
228
+ <h2 style="margin: 0; font-size: 2.2rem;">AI Brain Center</h2>
229
+ </div>
230
+ <p style="text-align: center; max-width: 700px; margin: 20px auto; font-weight: 700; font-size: 1.1rem; color: #4e342e;">
231
+ Finalize your session! Click below to permanently integrate your corrections into the AI's memory.
232
+ </p>
233
+ <div style="text-align: center; margin-bottom: 10px; width: 100%;">
234
+ <button onclick="trainAI()" class="btn-kids" style="width: auto; padding: 25px 60px; font-size: 1.4rem; background: #fff !important;">✨ SYNC AI BRAIN ✨</button>
235
+ <p id="train-status" style="font-size: 1rem; margin-top: 20px; color: #4e342e; font-weight: 800;"></p>
236
+ </div>
237
+ </div>
238
+
239
+ </div>
240
+ </div>
241
+
242
+ <script src="/static/js/script.js?v=1.8"></script>
243
+ <script>
244
+ document.addEventListener('DOMContentLoaded', () => {
245
+ if (!token) window.location.href = '/login';
246
+ loadCustomEmotions();
247
+ });
248
+
249
+ function handleFileSelected(input) {
250
+ const display = document.getElementById('file-name-display');
251
+ if (input.files && input.files[0]) {
252
+ display.innerText = "Selected: " + input.files[0].name;
253
+ }
254
+ }
255
+ </script>
256
+ </body>
257
+ </html>
frontend/templates/game.html ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ game_name }} - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.2">
8
+ <style>
9
+ .memory-card {
10
+ aspect-ratio: 1/1;
11
+ min-width: 100px;
12
+ min-height: 100px;
13
+ border-radius: 25px !important;
14
+ box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
15
+ font-size: 50px !important;
16
+ }
17
+
18
+ #game-area {
19
+ border-top: 15px solid var(--primary);
20
+ text-align: center;
21
+ }
22
+
23
+ #current-game-title {
24
+ font-size: 2.5rem;
25
+ margin-bottom: 30px;
26
+ text-shadow: 2px 2px 0px rgba(0,0,0,0.05);
27
+ }
28
+ </style>
29
+ </head>
30
+ <body>
31
+ <header>
32
+ <h1 class="bounce">{{ game_name }}</h1>
33
+ <p>You're doing great! Keep going! 🌟</p>
34
+ </header>
35
+ <nav>
36
+ <a href="/">Dashboard</a>
37
+ <a href="/emotion-learning">Emotion Learning</a>
38
+ <a href="/activities">Activities</a>
39
+ <a href="/quiz">Quiz</a>
40
+ <a href="/diary">Memory Diary</a>
41
+ <a href="/children">Child</a>
42
+ <a href="/login" id="nav-login">Login</a>
43
+ <a href="#" id="nav-logout" class="hidden" onclick="logout()">Logout</a>
44
+ </nav>
45
+
46
+ <div class="container">
47
+ <div id="game-area" class="card">
48
+ <h2 id="current-game-title">{{ game_name }}</h2>
49
+ <div id="game-container" style="min-height: 450px; padding: 20px; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;">
50
+ <div id="loading-fallback" style="text-align:center;">
51
+ <p>Loading your fun game...</p>
52
+ <button onclick="forceStart()" class="btn-blue" style="margin-top:10px;">Click here if it stays blank</button>
53
+ </div>
54
+ </div>
55
+ <div id="game-controls" class="hidden" style="margin-top: 30px;">
56
+ <div style="background: var(--bg); padding: 15px 30px; border-radius: 40px; display: inline-block; margin-bottom: 20px; border: 3px solid white; box-shadow: 0 5px 15px rgba(0,0,0,0.05);">
57
+ <span style="font-size: 1.5rem; font-weight: 800;">Score: <span id="game-score" style="color: var(--primary);">0</span></span>
58
+ </div>
59
+ <br>
60
+ <button onclick="restartGame()" class="btn-blue">Restart</button>
61
+ <button onclick="finishGame()" class="btn-green">Finish & Save</button>
62
+ </div>
63
+ <div style="text-align: center; margin-top: 30px;">
64
+ <button onclick="closeGame()" class="cute-btn btn-pink" id="close-game-btn" style="display:inline-flex; margin: 0 auto; cursor:pointer;"><span>🔙</span> Back to Activities</button>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ <script src="/static/js/script.js?v=1.7"></script>
69
+ <script>
70
+ const RAW_GAME_NAME = "{{ game_name }}";
71
+
72
+ document.addEventListener('DOMContentLoaded', () => {
73
+ if (!token) {
74
+ window.location.href = '/login';
75
+ return;
76
+ }
77
+ if (!selectedChildId) {
78
+ alert("Please select a child profile first!");
79
+ window.location.href = '/children';
80
+ return;
81
+ }
82
+
83
+ forceStart();
84
+ });
85
+
86
+ function forceStart() {
87
+ const decoded = decodeURIComponent(RAW_GAME_NAME);
88
+ console.log("Forcing start for:", decoded);
89
+ if (typeof startGame === 'function') {
90
+ startGame(decoded);
91
+ } else {
92
+ console.error("startGame function not found!");
93
+ }
94
+ }
95
+
96
+ window.closeGame = function() {
97
+ window.location.href = '/activities';
98
+ };
99
+
100
+ // Standardized checkShape for the new UI
101
+ window.checkShape = function(selected, target) {
102
+ const feedback = document.getElementById('feedback');
103
+ if (selected === target) {
104
+ if (feedback) {
105
+ feedback.innerText = "Fantastic! 🌟";
106
+ feedback.style.color = "var(--success)";
107
+ }
108
+ gameScore += 10;
109
+ document.getElementById('game-score').innerText = gameScore;
110
+ setTimeout(() => {
111
+ const container = document.getElementById('game-container');
112
+ if (container) startShapeMatch(container);
113
+ }, 1000);
114
+ } else {
115
+ if (feedback) {
116
+ feedback.innerText = "Not that one. Try again! 🤔";
117
+ feedback.style.color = "var(--danger)";
118
+ }
119
+ }
120
+ };
121
+ </script>
122
+ </body>
123
+ </html>
frontend/templates/index.html ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NeuroSense - AI Assisted Learning</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.1">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1>NeuroSense</h1>
12
+ <p>AI Assisted Learning Platform for Children with Autism</p>
13
+ </header>
14
+ <nav>
15
+ <a href="/">Home</a>
16
+ <a href="/login" id="nav-login">Login/Register</a>
17
+ <a href="/emotion-learning">Emotion Learning</a>
18
+ <a href="/activities">Activities</a>
19
+ <a href="/quiz">Quiz</a>
20
+ <a href="/dashboard">Dashboard</a>
21
+ <a href="/diary">Memory Diary</a>
22
+ <a href="/children">Switch Child</a>
23
+ <a href="#" onclick="logout()" id="nav-logout" class="hidden">Logout</a>
24
+ </nav>
25
+ <div class="container">
26
+ <div class="card">
27
+ <h2>Welcome to NeuroSense</h2>
28
+ <p>Our platform helps children with ASD improve their emotional and cognitive skills through interactive games and AI-powered learning.</p>
29
+ <div class="grid">
30
+ <div class="card">
31
+ <h3>😊 Emotion Learning</h3>
32
+ <p>Learn to recognize and express emotions using AI.</p>
33
+ </div>
34
+ <div class="card">
35
+ <h3>🎮 Fun Activities</h3>
36
+ <p>Color matching, memory games, and more.</p>
37
+ </div>
38
+ <div class="card">
39
+ <h3>📊 Parent Dashboard</h3>
40
+ <p>Track progress and download reports.</p>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ <script src="/static/js/script.js"></script>
46
+ </body>
47
+ </html>
frontend/templates/login.html ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.1">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1>NeuroSense</h1>
12
+ <p>Your child's learning journey starts here! ✨</p>
13
+ </header>
14
+
15
+ <div class="container" style="max-width: 500px;">
16
+ <div id="login-section" class="card">
17
+ <h2 style="text-align: center; margin-bottom: 30px;">Welcome Back! 👋</h2>
18
+ <input type="text" id="login-username" placeholder="Username">
19
+ <input type="password" id="login-password" placeholder="Password">
20
+ <button onclick="login()" style="width: 100%; margin-top: 20px;">Login to Dashboard</button>
21
+ <p style="text-align: center; margin-top: 20px; font-weight: 600;">
22
+ New here? <a href="#" onclick="toggleAuth(true)" style="color: var(--primary);">Create an account</a>
23
+ </p>
24
+ </div>
25
+
26
+ <div id="register-section" class="card hidden">
27
+ <h2 style="text-align: center; margin-bottom: 30px;">Create Account 🚀</h2>
28
+ <input type="text" id="reg-username" placeholder="Choose Username">
29
+ <input type="password" id="reg-password" placeholder="Choose Password">
30
+ <button onclick="register()" style="width: 100%; margin-top: 20px;">Join NeuroSense</button>
31
+ <p style="text-align: center; margin-top: 20px; font-weight: 600;">
32
+ Already have an account? <a href="#" onclick="toggleAuth(false)" style="color: var(--primary);">Login here</a>
33
+ </p>
34
+ </div>
35
+ </div>
36
+
37
+ <script src="/static/js/script.js"></script>
38
+ </body>
39
+ </html>
frontend/templates/quiz.html ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Quiz - NeuroSense</title>
7
+ <link rel="stylesheet" href="/static/css/style.css?v=1.1">
8
+ <style>
9
+ .quiz-option {
10
+ font-size: 1.2rem;
11
+ padding: 20px;
12
+ cursor: pointer;
13
+ transition: all 0.2s;
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ gap: 10px;
18
+ }
19
+ .quiz-option:hover {
20
+ transform: scale(1.05);
21
+ background: #f8f9fa;
22
+ }
23
+ .quiz-option span {
24
+ font-size: 50px;
25
+ }
26
+ .progress-bar {
27
+ width: 100%;
28
+ height: 10px;
29
+ background: #eee;
30
+ border-radius: 5px;
31
+ margin-bottom: 20px;
32
+ overflow: hidden;
33
+ }
34
+ #progress-fill {
35
+ height: 100%;
36
+ background: var(--success);
37
+ width: 0%;
38
+ transition: width 0.3s;
39
+ }
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <header>
44
+ <h1>Quiz Time!</h1>
45
+ <p id="age-level-indicator" style="font-weight: bold; color: var(--primary); text-align: center; margin-top: -10px; font-size: 1.2rem;"></p>
46
+ </header>
47
+ <nav>
48
+ <a href="/">Dashboard</a>
49
+ <a href="/emotion-learning">Emotion Learning</a>
50
+ <a href="/activities">Activities</a>
51
+ <a href="/quiz">Quiz</a>
52
+ <a href="/diary">Memory Diary</a>
53
+ <a href="/children">Child</a>
54
+ <a href="/login" id="nav-login">Login</a>
55
+ <a href="#" id="nav-logout" class="hidden" onclick="logout()">Logout</a>
56
+ </nav>
57
+ <div class="container" style="max-width: 1200px;">
58
+ <div id="quiz-selection" class="grid-kids">
59
+ <!-- Level 1 Quizzes (Visible to all) -->
60
+ <div class="kids-card theme-green age-level-1 age-level-2 age-level-3" onclick="startNewQuiz('Daily Routine')">
61
+ <span class="icon">☀️</span>
62
+ <h3>Daily Routine</h3>
63
+ <p>What comes next in our daily schedule?</p>
64
+ <button class="btn-kids">Start Quiz</button>
65
+ </div>
66
+
67
+ <!-- Level 2 Quizzes (Visible to Level 2 & 3) -->
68
+ <div class="kids-card theme-blue age-level-2 age-level-3" onclick="startNewQuiz('Social Skills')">
69
+ <span class="icon">🤝</span>
70
+ <h3>Social Skills</h3>
71
+ <p>What should we do in different social situations?</p>
72
+ <button class="btn-kids">Start Quiz</button>
73
+ </div>
74
+
75
+ <div class="kids-card theme-orange age-level-2 age-level-3" onclick="startNewQuiz('Safety & Help')">
76
+ <span class="icon">🆘</span>
77
+ <h3>Safety & Help</h3>
78
+ <p>Learn who to ask for help and stay safe.</p>
79
+ <button class="btn-kids">Start Quiz</button>
80
+ </div>
81
+
82
+ <!-- Level 3 Quizzes (Visible to Level 3) -->
83
+ <div class="kids-card theme-purple age-level-3" onclick="startNewQuiz('Advanced Social Skills')">
84
+ <span class="icon">🧠</span>
85
+ <h3>Advanced Logic</h3>
86
+ <p>Understand complex emotions and social cues.</p>
87
+ <button class="btn-kids">Start Quiz</button>
88
+ </div>
89
+ </div>
90
+
91
+ <div id="quiz-area" class="card hidden">
92
+ <div style="display: flex; justify-content: space-between; align-items: center;">
93
+ <h2 id="current-quiz-name"></h2>
94
+ <span id="question-counter" style="font-weight: bold; color: var(--primary);"></span>
95
+ </div>
96
+
97
+ <div class="progress-bar"><div id="progress-fill"></div></div>
98
+
99
+ <div id="quiz-content">
100
+ <p id="quiz-question-text" style="font-size: 24px; text-align: center; margin-bottom: 30px; font-weight: 500;"></p>
101
+ <div id="quiz-options-grid" class="grid" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));">
102
+ <!-- Options injected via JS -->
103
+ </div>
104
+ </div>
105
+
106
+ <div id="quiz-results" class="hidden" style="text-align: center; padding: 20px;">
107
+ <h2 style="font-size: 40px;">Great Job! 🎉</h2>
108
+ <p id="final-score-text" style="font-size: 24px; margin: 20px 0;"></p>
109
+ <button onclick="location.reload()" class="btn-blue">Back to Quizzes</button>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ <script src="/static/js/script.js"></script>
114
+ <script>
115
+ document.addEventListener('DOMContentLoaded', async () => {
116
+ if (!token) window.location.href = '/login';
117
+ await syncChildAge();
118
+ filterQuizzesByAge();
119
+ });
120
+
121
+ function filterQuizzesByAge() {
122
+ const ageStr = localStorage.getItem('selectedChildAge');
123
+ const age = ageStr ? parseInt(ageStr) : 0;
124
+ const indicator = document.getElementById('age-level-indicator');
125
+
126
+ const levelInfo = getLevelInfo(age);
127
+ if (indicator) indicator.innerText = levelInfo.name;
128
+
129
+ document.querySelectorAll('.kids-card').forEach(card => {
130
+ if (card.classList.contains(`age-level-${levelInfo.level}`)) {
131
+ card.style.display = 'flex';
132
+ } else {
133
+ card.style.display = 'none';
134
+ }
135
+ });
136
+ }
137
+ </script>
138
+ </body>
139
+ </html>
link_child.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+
4
+ # Add backend to sys.path
5
+ base_dir = os.path.dirname(os.path.abspath(__file__))
6
+ backend_dir = os.path.join(base_dir, "backend")
7
+ sys.path.insert(0, backend_dir)
8
+
9
+ from app.database import SessionLocal
10
+ from app.models import User, Child, DiaryEntry
11
+
12
+ def link_to_nehal():
13
+ db = SessionLocal()
14
+ try:
15
+ # Find nehal user
16
+ nehal = db.query(User).filter(User.username == "nehal").first()
17
+ if not nehal:
18
+ print("User 'nehal' not found. Please register it in the app first!")
19
+ return
20
+
21
+ # Find Alex child
22
+ alex = db.query(Child).filter(Child.name == "Alex").first()
23
+ if not alex:
24
+ print("Child 'Alex' not found. Seeding new data for nehal...")
25
+ from seed_db import seed
26
+ seed() # This will ensure Alex exists
27
+ alex = db.query(Child).filter(Child.name == "Alex").first()
28
+
29
+ # Update Alex's parent_id to nehal's ID
30
+ alex.parent_id = nehal.id
31
+
32
+ # Also update diary entries
33
+ diary_entries = db.query(DiaryEntry).all()
34
+ for entry in diary_entries:
35
+ entry.parent_id = nehal.id
36
+
37
+ db.commit()
38
+ print(f"Success! Alex (and diary entries) are now linked to user '{nehal.username}' (ID: {nehal.id}).")
39
+ print("You can now login as 'nehal' and you will see Alex.")
40
+
41
+ except Exception as e:
42
+ print(f"Error: {e}")
43
+ db.rollback()
44
+ finally:
45
+ db.close()
46
+
47
+ if __name__ == "__main__":
48
+ link_to_nehal()
run.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uvicorn
2
+ import os
3
+ import sys
4
+
5
+ # Set Keras backend to torch for Python 3.14 compatibility
6
+ os.environ["KERAS_BACKEND"] = "torch"
7
+
8
+ if __name__ == "__main__":
9
+ # Get absolute path to the backend directory
10
+ base_dir = os.path.dirname(os.path.abspath(__file__))
11
+ backend_dir = os.path.join(base_dir, "backend")
12
+
13
+ # Add backend_dir to sys.path so 'app' can be found
14
+ sys.path.insert(0, backend_dir)
15
+
16
+ # Move into the backend directory so relative paths for DB/Uploads work correctly
17
+ os.chdir(backend_dir)
18
+
19
+ # Ensure data directories exist inside backend/data
20
+ dirs = [
21
+ "data/uploads/emotions",
22
+ "data/uploads/diary",
23
+ "data/reports"
24
+ ]
25
+
26
+ for d in dirs:
27
+ os.makedirs(d, exist_ok=True)
28
+
29
+ print(f"Starting NeuroSense Backend from: {backend_dir}")
30
+ print("Frontend is being served from: ../frontend")
31
+ print("Open http://127.0.0.1:8000 in your browser.")
32
+
33
+ # Run the app
34
+ uvicorn.run("app.main:app", host="127.0.0.1", port=8000, reload=True)
seed_db.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from datetime import datetime, timedelta
4
+ from passlib.context import CryptContext
5
+
6
+ # Add backend to sys.path
7
+ base_dir = os.path.dirname(os.path.abspath(__file__))
8
+ backend_dir = os.path.join(base_dir, "backend")
9
+ sys.path.insert(0, backend_dir)
10
+
11
+ from app.database import SessionLocal, engine, Base
12
+ from app.models import User, Child, EmotionLog, DiaryEntry, ActivityLog, QuizResult
13
+
14
+ # Password hashing
15
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
16
+
17
+ def seed():
18
+ db = SessionLocal()
19
+ try:
20
+ # Create tables if they don't exist
21
+ Base.metadata.create_all(bind=engine)
22
+
23
+ # 1. Create User
24
+ user = db.query(User).filter(User.username == "parent1").first()
25
+ if not user:
26
+ user = User(
27
+ username="parent1",
28
+ hashed_password=pwd_context.hash("password123")
29
+ )
30
+ db.add(user)
31
+ db.commit()
32
+ db.refresh(user)
33
+ print("User 'parent1' created.")
34
+ else:
35
+ print("User 'parent1' already exists.")
36
+
37
+ # 2. Create Child
38
+ child = db.query(Child).filter(Child.parent_id == user.id).first()
39
+ if not child:
40
+ child = Child(
41
+ name="Alex",
42
+ age=6,
43
+ parent_id=user.id
44
+ )
45
+ db.add(child)
46
+ db.commit()
47
+ db.refresh(child)
48
+ print("Child 'Alex' created.")
49
+ else:
50
+ print("Child 'Alex' already exists.")
51
+
52
+ # 3. Add Emotion Logs (Past 3 days)
53
+ if db.query(EmotionLog).count() == 0:
54
+ emotions = ["happy", "sad", "angry", "surprise", "neutral", "happy", "fear"]
55
+ for i, emotion in enumerate(emotions):
56
+ log = EmotionLog(
57
+ child_id=child.id,
58
+ predicted_emotion=emotion,
59
+ image_path=f"uploads/emotions/sample_{i}.jpg",
60
+ timestamp=datetime.utcnow() - timedelta(days=i/2),
61
+ confirmed=True if i % 2 == 0 else False
62
+ )
63
+ db.add(log)
64
+ print(f"Added {len(emotions)} emotion logs.")
65
+
66
+ # 4. Add Diary Entries
67
+ if db.query(DiaryEntry).count() == 0:
68
+ entries = [
69
+ {"title": "Great Progress!", "message": "Alex was very happy during the emotion matching game today."},
70
+ {"title": "Rough Morning", "message": "Had a bit of trouble focusing this morning, but improved by noon."}
71
+ ]
72
+ for entry_data in entries:
73
+ entry = DiaryEntry(
74
+ parent_id=user.id,
75
+ child_name="Alex",
76
+ title=entry_data["title"],
77
+ message=entry_data["message"],
78
+ timestamp=datetime.utcnow() - timedelta(days=1)
79
+ )
80
+ db.add(entry)
81
+ print(f"Added {len(entries)} diary entries.")
82
+
83
+ # 5. Add Activity Logs
84
+ if db.query(ActivityLog).count() == 0:
85
+ activities = [
86
+ {"name": "Emotion Match", "score": 90, "duration": 120},
87
+ {"name": "Color Learn", "score": 100, "duration": 80}
88
+ ]
89
+ for act in activities:
90
+ log = ActivityLog(
91
+ child_id=child.id,
92
+ activity_name=act["name"],
93
+ score=act["score"],
94
+ duration_seconds=act["duration"]
95
+ )
96
+ db.add(log)
97
+ print(f"Added {len(activities)} activity logs.")
98
+
99
+ db.commit()
100
+ print("\nDatabase seeded successfully!")
101
+ print("-" * 30)
102
+ print("Login Credentials:")
103
+ print("Username: parent1")
104
+ print("Password: password123")
105
+ print("-" * 30)
106
+
107
+ except Exception as e:
108
+ print(f"Error seeding database: {e}")
109
+ db.rollback()
110
+ finally:
111
+ db.close()
112
+
113
+ if __name__ == "__main__":
114
+ seed()
test_cloud_db.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ # Load .env first
6
+ load_dotenv(os.path.join(os.getcwd(), "backend", ".env"))
7
+
8
+ # Add the backend directory to the Python path
9
+ sys.path.append(os.path.join(os.getcwd(), "backend"))
10
+
11
+ from app.database import engine, Base
12
+ from sqlalchemy import text
13
+
14
+ def test_connection():
15
+ print("Testing connection to Neon Cloud PostgreSQL...")
16
+ try:
17
+ # Try to connect
18
+ with engine.connect() as connection:
19
+ result = connection.execute(text("SELECT version();"))
20
+ version = result.fetchone()
21
+ print(f"✅ Success! Connected to: {version[0]}")
22
+
23
+ # Try to create tables
24
+ print("Creating tables in cloud database...")
25
+ from app.models import User, Child, EmotionLog, ActivityLog, QuizResult, DiaryEntry
26
+ Base.metadata.create_all(bind=engine)
27
+ print("✅ Success! All tables created.")
28
+
29
+ except Exception as e:
30
+ print(f"❌ Connection failed: {e}")
31
+
32
+ if __name__ == "__main__":
33
+ test_connection()
test_fer.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ["KERAS_BACKEND"] = "torch"
3
+ try:
4
+ from fer import FER
5
+ detector = FER()
6
+ print("FER imported and detector created with torch backend.")
7
+ except Exception as e:
8
+ print(f"Error: {e}")
test_fer_simple.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.environ["KERAS_BACKEND"] = "torch"
3
+ from fer import FER
4
+ import cv2
5
+ import numpy as np
6
+
7
+ def test_fer():
8
+ try:
9
+ detector = FER(mtcnn=False)
10
+ # Create a blank image
11
+ img = np.zeros((100, 100, 3), dtype=np.uint8)
12
+ # Add a white circle (to represent a face-ish thing, though FER might not find a face)
13
+ cv2.circle(img, (50, 50), 30, (255, 255, 255), -1)
14
+
15
+ print("Testing FER detector...")
16
+ emotions = detector.detect_emotions(img)
17
+ print(f"Result: {emotions}")
18
+ print("FER is working (even if no face found in blank image)")
19
+ except Exception as e:
20
+ print(f"FER error: {e}")
21
+
22
+ if __name__ == "__main__":
23
+ test_fer()
train_custom_model.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+
4
+ # Set Keras backend to torch for Python 3.14 compatibility
5
+ os.environ["KERAS_BACKEND"] = "torch"
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.optim as optim
10
+ from torch.utils.data import Dataset, DataLoader
11
+ import cv2
12
+ import numpy as np
13
+ from PIL import Image
14
+
15
+ # Add backend to sys.path
16
+ base_dir = os.path.dirname(os.path.abspath(__file__))
17
+ backend_dir = os.path.join(base_dir, "backend")
18
+ sys.path.insert(0, backend_dir)
19
+
20
+ from app.database import SessionLocal
21
+ from app.models import EmotionLog
22
+ from fer import FER
23
+
24
+ # 1. Simple Neural Network to "learn" new emotions
25
+ class EmotionClassifier(nn.Module):
26
+ def __init__(self, input_size, num_classes):
27
+ super(EmotionClassifier, self).__init__()
28
+ self.fc1 = nn.Linear(input_size, 64)
29
+ self.relu = nn.ReLU()
30
+ self.fc2 = nn.Linear(64, num_classes)
31
+
32
+ def forward(self, x):
33
+ return self.fc2(self.relu(self.fc1(x)))
34
+
35
+ def train():
36
+ db = SessionLocal()
37
+ detector = FER(mtcnn=False)
38
+
39
+ # Get all logs that have a corrected emotion
40
+ logs = db.query(EmotionLog).filter(EmotionLog.corrected_emotion != None).all()
41
+
42
+ # Check unique emotions count
43
+ all_emotions = sorted(list(set([log.corrected_emotion for log in logs])))
44
+
45
+ if len(all_emotions) < 2:
46
+ print(f"FAILED: Not enough variety! You only have one corrected emotion: {all_emotions}. Correct at least two different images with different emotions to train the custom brain.")
47
+ sys.exit(1)
48
+
49
+ if len(logs) < 3:
50
+ print(f"FAILED: Not enough data! You only have {len(logs)} corrections. Please correct at least 3 images (from different photos) to help the AI learn.")
51
+ sys.exit(1)
52
+
53
+ # Prepare labels and mappings
54
+ emotion_to_idx = {emo: i for i, emo in enumerate(all_emotions)}
55
+ idx_to_emotion = {i: emo for emo, i in emotion_to_idx.items()}
56
+
57
+ X = []
58
+ y = []
59
+
60
+ print(f"Preparing data for emotions: {all_emotions}")
61
+
62
+ for log in logs:
63
+ # Load the image
64
+ img_path = os.path.join(backend_dir, "data", log.image_path)
65
+ if not os.path.exists(img_path):
66
+ print(f"Skipping missing image: {img_path}")
67
+ continue
68
+
69
+ try:
70
+ img = cv2.imread(img_path)
71
+ if img is None:
72
+ print(f"Skipping unreadable image: {img_path}")
73
+ continue
74
+ # Use FER to detect face and get the emotion probabilities (features)
75
+ results = detector.detect_emotions(img)
76
+ if results:
77
+ # We take the 7 base emotion probabilities as features
78
+ # Sort keys to ensure consistent feature order matching inference
79
+ emo_dict = results[0]["emotions"]
80
+ features = [emo_dict[k] for k in sorted(emo_dict.keys())]
81
+ X.append(features)
82
+ y.append(emotion_to_idx[log.corrected_emotion])
83
+ else:
84
+ # If FER fails on this specific image, we use neutral features
85
+ print(f"Warning: FER could not find face in {img_path}, skipping.")
86
+ except Exception as e:
87
+ print(f"Error processing {img_path}: {e}")
88
+ continue
89
+
90
+ if not X:
91
+ print("FAILED: Could not extract features from any of your corrected images.")
92
+ sys.exit(1)
93
+
94
+ X = torch.tensor(X, dtype=torch.float32)
95
+ y = torch.tensor(y, dtype=torch.long)
96
+
97
+ # 2. Train the model
98
+ model = EmotionClassifier(input_size=7, num_classes=len(all_emotions))
99
+ criterion = nn.CrossEntropyLoss()
100
+ optimizer = optim.Adam(model.parameters(), lr=0.01)
101
+
102
+ print("Training the custom brain...")
103
+ # Increase epochs for small dataset to ensure convergence
104
+ for epoch in range(200):
105
+ optimizer.zero_grad()
106
+ outputs = model.forward(X)
107
+ loss = criterion(outputs, y)
108
+ loss.backward()
109
+ optimizer.step()
110
+ if (epoch+1) % 50 == 0:
111
+ print(f"Epoch [{epoch+1}/200], Loss: {loss.item():.4f}")
112
+
113
+ # 3. Save the custom model and the label mapping
114
+ save_path = os.path.join(backend_dir, "app/models/custom_ai")
115
+ os.makedirs(save_path, exist_ok=True)
116
+
117
+ torch.save(model.state_dict(), os.path.join(save_path, "custom_weights.pth"))
118
+
119
+ import json
120
+ with open(os.path.join(save_path, "labels.json"), "w") as f:
121
+ json.dump(idx_to_emotion, f)
122
+
123
+ print(f"SUCCESS: Custom brain trained and saved. It now knows: {all_emotions}")
124
+
125
+ if __name__ == "__main__":
126
+ train()
update_db_schema.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import os
3
+
4
+ db_path = 'backend/data/neurosense.db'
5
+
6
+ if not os.path.exists(db_path):
7
+ print(f"Error: Database not found at {db_path}")
8
+ else:
9
+ conn = sqlite3.connect(db_path)
10
+ cursor = conn.cursor()
11
+
12
+ # Check current columns
13
+ cursor.execute('PRAGMA table_info(children)')
14
+ cols = [c[1] for c in cursor.fetchall()]
15
+
16
+ if 'autism_inheritance' not in cols:
17
+ print("Adding autism_inheritance...")
18
+ cursor.execute("ALTER TABLE children ADD COLUMN autism_inheritance VARCHAR DEFAULT ''")
19
+
20
+ if 'sensory_level' not in cols:
21
+ print("Adding sensory_level...")
22
+ cursor.execute("ALTER TABLE children ADD COLUMN sensory_level VARCHAR DEFAULT 'standard'")
23
+
24
+ conn.commit()
25
+ conn.close()
26
+ print("Database updated successfully.")