# Project export: ClassPulse

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: An AI-powered assistant for professors to understand student performance in real-time—through live lecture engagement and homework insights—empowering smarter teaching decisions
- Devpost: https://devpost.com/software/classpulse
- GitHub: https://github.com/speedstorm1/treehacks25
- Video: https://www.youtube.com/embed/OtibH1TkmZw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Collin Qian (34 commits), speedstorm1 (21 commits), spencerho777 (18 commits), Ethan Yuan (13 commits)

## Devpost submission (written by the team)

### Inspiration

Education is evolving, but professors often struggle to gauge real-time student understanding during lectures or identify key learning gaps from homework. Inspired by the idea of using AI to bridge this gap, we set out to build a solution that provides professors with deeper, actionable insights—making classrooms more interactive and helping students succeed.

### What it does

ClassPulse is an AI-powered assistant for professors that helps them better understand their students through two key approaches: Real-time Classroom Insights: ClassPulse listens to live lectures and generates questions for the class. It collects student responses and provides immediate insights on how well students are grasping the content. Homework Analysis: ClassPulse grades homework automatically and performs detailed question-level analysis to surface common mistakes and trends. This helps professors pinpoint where students struggle the most and adjust their teaching accordingly.

### How we built it

Backend: Node.js for building APIs, PostgreSQL for data storage, and OpenAI/Gemini APIs for natural language processing. Frontend: Next for the user interface, offering a dashboard for real-time insights and homework analysis. AI Models: Used OpenAI and Google for grading, text analysis, summarization, and question generation. Architecture: Designed a scalable system, integrating real-time data processing and background jobs for homework insights.

### Challenges we ran into

Real-Time Performance - one of our biggest technical challenges was optimizing the real-time performance of our system. When a professor creates a learning check in the middle of lecture, we needed to process audio, video, and slides both quickly and reliably. Complexity of AI Workflows - managing multiple AI workflows, from generating contextual questions to grading responses and extracting insights, required careful development to ensure both quality and reliability across different types of lecture content and student responses.

### Accomplishments we're proud of

Successfully built an AI system that integrates presentation slides, professor audio, and the video recording to generate questions and provide insights during lecture in real-time. Developed advanced NLP pipeline that grades questions, classifies errors, and identifies gaps in student knowledge, allowing professors to make data-driven decisions. Created a full platform experience, including a user-friendly interface that makes complex insights easy to understand and visualize

### What we learned

Multi-modal data processing - techniques for summarizing large amounts of diverse educational data into concise, actionable insights AI system architecture - designing scalable systems that orchestrate multiple AI models while maintaining reliability Full-Stack Integration - bridging modern frontend frameworks and visualizations with AI-powered backends to create a seamless, responsive classroom platform

### What's next

Enhanced Student Insights: Expand analysis to include long-term trends in student performance across multiple assignments and lectures. Student Engagement Tools: Introduce interactive features for students, such as personalized feedback and progress tracking. Broader AI Model Support: Integrate new AI models like Deepseek and Perplexity for even more accurate question generation and grading.

## README (from the GitHub repository)

# 🚀 ClassPulse

<div align="center">
  <h3>AI-Powered Teaching Assistant</h3>
  <p>Bridging the gap between professors and students with real-time insights</p>
</div>

Demo: https://www.youtube.com/watch?v=OtibH1TkmZw

## 💡 Inspiration

Education is evolving, but professors often struggle to gauge real-time student understanding during lectures or identify key learning gaps from homework. Inspired by the idea of using AI to bridge this gap, we set out to build a solution that provides professors with deeper, actionable insights—making classrooms more interactive and helping students succeed.

## 🎯 What it does

ClassPulse is an AI-powered assistant for professors that helps them better understand their students through two key approaches:

### 🎓 Real-time Classroom Insights
- Listens to live lectures and generates targeted questions
- Collects and analyzes student responses in real-time
- Provides immediate feedback on student comprehension
- Helps professors adjust their teaching on the fly

### 📚 Smart Homework Analysis
- Automated grading with detailed question-level analysis
- Identifies common mistakes and learning patterns
- Surfaces actionable insights for targeted intervention
- Saves professors countless hours of manual grading

## 🛠 How we built it

### Backend Architecture
- FastAPI for robust API development
- PostgreSQL via Supabase for scalable data storage
- OpenAI/Gemini APIs for advanced NLP capabilities
- Background jobs for homework analysis

### Frontend Stack
```
Next.js 15 | React 19 | TypeScript | Tailwind CSS | shadcn/ui | Recharts
```

### AI Integration
```
OpenAI API | Google Gemini Pro | Natural Language Processing | Pattern Recognition
```

## 🎯 Challenges we ran into

- **Summarization Accuracy**: Fine-tuning AI prompts to generate meaningful insights without overwhelming users
- **Real-time Processing**: Optimizing the system for immediate feedback during live sessions
- **Data Integration**: Seamlessly combining insights from multiple sources

## 🏆 Accomplishments that we're proud of

- Built a real-time AI system that enhances classroom engagement
- Automated homework analysis with actionable insights
- Created an intuitive interface for complex data visualization
- Integrated multiple AI models for enhanced accuracy

## 🎓 What we learned

- Advanced techniques for educational data summarization
- Integration strategies for multiple AI models
- Real-time data processing and visualization
- Building scalable education technology

## 🚀 Quick Start

### Backend Setup

1. Clone and setup environment:
```bash
git clone https://github.com/speedstorm1/treehacks25.git
cd treehacks25/treehacks-backend
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate
pip install -r requirements.txt
```

2. Configure environment variables in `.env`:
```env
SUPABASE_URL=your_supabase_url
SUPABASE_KEY=your_supabase_anon_key
GOOGLE_API_KEY=your_google_ai_api_key
OPENAI_API_KEY=your_openai_api_key
```

3. Start the server:
```bash
uvicorn main:app --reload --port 8000
```

### Frontend Setup

1. Install dependencies:
```bash
cd ../treehacks-frontend
npm install
```

2. Configure environment variables in `.env.local`:
```env
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
```

3. Start development server:
```bash
npm run dev
```

Visit `http://localhost:3000` to see the app in action!

## 📈 What's next for ClassPulse

- **Enhanced Student Insights**: Long-term performance tracking across assignments
- **Student Engagement Tools**: Interactive features for personalized feedback
- **Broader AI Integration**: Support for new models and improved accuracy
- **Analytics Dashboard**: Advanced visualization of learning patterns
- **Mobile Support**: Real-time insights on the go

## 🔌 API Documentation

### Sessions
```
GET    /api/sessions            # List all sessions
POST   /api/sessions            # Create new session
GET    /api/sessions/{id}       # Get session details
GET    /api/sessions/{id}/stats # Get session statistics
```

### Assignments
```
GET    /assignment              # List assignments
POST   /assignment              # Create assignment
GET    /assignment/{id}         # Get assignment details
GET    /assignment/{id}/insights # Get assignment insights
```

### Topics
```
GET    /api/topics             # List topics
POST   /api/topics             # Create topic
PUT    /api/topics/{id}        # Update topic
DELETE /api/topics/{id}        # Delete topic
```

## 🤝 Contributing

We welcome contributions! Please follow these steps:

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request

## 📝 License

This project is licensed under the MIT License.

## 🙏 Acknowledgments

- Built with ❤️ during TreeHacks 2024
- UI components from [shadcn/ui](https://ui.shadcn.com/)
- AI features powered by OpenAI and Google's Gemini Pro

---

<div align="center">
  Made with 🌟 by Team ClassPulse
</div>


## Detected evidence (automated analysis)

Indexed codebase: 79 recognized source files, 306 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (89 of 89)

```
.DS_Store
LICENSE
README.md
treehacks-backend/.gitignore
treehacks-backend/drive_utils.py
treehacks-backend/llm_utils.py
treehacks-backend/main.py
treehacks-backend/models.py
treehacks-backend/question_gen.py
treehacks-backend/requirements.txt
treehacks-backend/services/answerInsightService.py
treehacks-backend/services/assignmentGradingService.py
treehacks-backend/services/homeworkService.py
treehacks-backend/services/questionGradingService.py
treehacks-backend/services/questionParsingService.py
treehacks-backend/services/sessionGradingService.py
treehacks-backend/services/sessionService.py
treehacks-backend/services/submissionParsingService.py
treehacks-backend/slide_utils.py
treehacks-backend/speech_to_text.py
treehacks-backend/test_assignment_grading_service.py
treehacks-backend/test_parsing_service.py
treehacks-backend/test_question_grading_service.py
treehacks-backend/topic_utils.py
treehacks-backend/video_utils.py
treehacks-frontend/.gitignore
treehacks-frontend/components.json
treehacks-frontend/eslint.config.mjs
treehacks-frontend/next.config.ts
treehacks-frontend/package.json
treehacks-frontend/postcss.config.mjs
treehacks-frontend/README.md
treehacks-frontend/src/app/announcements/page.tsx
treehacks-frontend/src/app/assignments/[id]/page.tsx
treehacks-frontend/src/app/assignments/page.tsx
treehacks-frontend/src/app/context/ClassContext.tsx
treehacks-frontend/src/app/globals.css
treehacks-frontend/src/app/home/page.tsx
treehacks-frontend/src/app/layout.tsx
treehacks-frontend/src/app/lectures/[id]/add-session/page.tsx
treehacks-frontend/src/app/lectures/[id]/error.tsx
treehacks-frontend/src/app/lectures/[id]/lecture-content.tsx
treehacks-frontend/src/app/lectures/[id]/loading.tsx
treehacks-frontend/src/app/lectures/[id]/page.tsx
treehacks-frontend/src/app/lectures/add/page.tsx
treehacks-frontend/src/app/lectures/page.tsx
treehacks-frontend/src/app/page.tsx
treehacks-frontend/src/app/professor-setup/layout.tsx
treehacks-frontend/src/app/professor-setup/page.tsx
treehacks-frontend/src/app/profile/page.tsx
treehacks-frontend/src/app/sessions/[id]/page.tsx
treehacks-frontend/src/app/sessions/page.tsx
treehacks-frontend/src/app/student/[id]/layout.tsx
treehacks-frontend/src/app/student/[id]/page.tsx
treehacks-frontend/src/app/student/hw-upload/page.tsx
treehacks-frontend/src/app/topics/[id]/page.tsx
treehacks-frontend/src/components/breadcrumb.tsx
treehacks-frontend/src/components/class-guard.tsx
treehacks-frontend/src/components/close-session-button.tsx
treehacks-frontend/src/components/line-graph.tsx
treehacks-frontend/src/components/pdf-viewer.tsx
treehacks-frontend/src/components/pie-chart.tsx
treehacks-frontend/src/components/progress-bar.tsx
treehacks-frontend/src/components/sidebar.tsx
treehacks-frontend/src/components/ui/badge.tsx
treehacks-frontend/src/components/ui/button.tsx
treehacks-frontend/src/components/ui/card.tsx
treehacks-frontend/src/components/ui/dialog.tsx
treehacks-frontend/src/components/ui/form.tsx
treehacks-frontend/src/components/ui/input.tsx
treehacks-frontend/src/components/ui/label.tsx
treehacks-frontend/src/components/ui/progress.tsx
treehacks-frontend/src/components/ui/separator.tsx
treehacks-frontend/src/components/ui/sheet.tsx
treehacks-frontend/src/components/ui/sidebar.tsx
treehacks-frontend/src/components/ui/skeleton.tsx
treehacks-frontend/src/components/ui/slider.tsx
treehacks-frontend/src/components/ui/table.tsx
treehacks-frontend/src/components/ui/tabs.tsx
treehacks-frontend/src/components/ui/textarea.tsx
treehacks-frontend/src/components/ui/toast.tsx
treehacks-frontend/src/components/ui/toaster.tsx
treehacks-frontend/src/components/ui/tooltip.tsx
treehacks-frontend/src/components/video-player.tsx
treehacks-frontend/src/hooks/use-mobile.tsx
treehacks-frontend/src/hooks/use-toast.ts
treehacks-frontend/src/lib/utils.ts
treehacks-frontend/tailwind.config.ts
treehacks-frontend/tsconfig.json
```

### Dependencies

- treehacks-backend/requirements.txt: aiohappyeyeballs@==2.4.6, aiohttp@==3.11.12, aiosignal@==1.3.2, annotated-types@==0.7.0, anyio@==4.8.0, attrs@==25.1.0, beautifulsoup4@==4.13.3, bs4@==0.0.2, cachetools@==5.5.1, certifi@==2025.1.31, charset-normalizer@==3.4.1, click@==8.1.8, deprecation@==2.1.0, distro@==1.9.0, fastapi@==0.115.8, filelock@==3.17.0, frozenlist@==1.5.0, gdown@==5.1.0, google-ai-generativelanguage@==0.6.15, google-api-core@==2.24.1, google-api-python-client@==2.161.0, google-auth@==2.38.0, google-auth-httplib2@==0.2.0, google-generativeai@==0.8.4, googleapis-common-protos@==1.67.0, gotrue@==2.11.3, grpcio@==1.70.0, grpcio-status@==1.70.0, h11@==0.14.0, h2@==4.2.0, hpack@==4.1.0, httpcore@==1.0.7, httplib2@==0.22.0, httpx@==0.27.0, hyperframe@==6.1.0, idna@==3.10, jiter@==0.8.2, multidict@==6.1.0, numpy@==1.26.3, openai@==1.63.0, opencv-python@==4.9.0.80, packaging@==24.2, pdf2image@==1.17.0, pillow@==11.1.0, postgrest@==0.19.3, propcache@==0.2.1, proto-plus@==1.26.0, protobuf@==5.29.3, pyasn1@==0.6.1, pyasn1-modules@==0.4.1, pydantic@==2.10.6, pydantic-core@==2.27.2, pyparsing@==3.2.1, PyPDF2@==3.0.1, pytesseract@==0.3.13, python-dateutil@==2.9.0.post0, python-dotenv@==1.0.1, python-multipart@==0.0.6, realtime@==2.3.0, requests@==2.31.0, rsa@==4.9, six@==1.17.0, sniffio@==1.3.1, soupsieve@==2.6, starlette@==0.45.3, storage3@==0.11.3, strenum@==0.4.15, supabase@==2.13.0, supafunc@==0.9.3, tqdm@==4.67.1, typing-extensions@==4.12.2, uritemplate@==4.1.1, urllib3@==2.3.0, uvicorn@==0.34.0, websockets@==14.2, yarl@==1.18.3
- treehacks-frontend/package.json: @eslint/eslintrc@^3, @hookform/resolvers@^4.1.0, @radix-ui/react-dialog@^1.1.6, @radix-ui/react-icons@^1.3.2, @radix-ui/react-label@^2.1.2, @radix-ui/react-progress@^1.1.2, @radix-ui/react-separator@^1.1.2, @radix-ui/react-slider@^1.2.3, @radix-ui/react-slot@^1.1.2, @radix-ui/react-tabs@^1.1.3, @radix-ui/react-toast@^1.2.6, @radix-ui/react-tooltip@^1.1.8, @supabase/supabase-js@^2.48.1, @types/node@^20, @types/react@^19, @types/react-dom@^19, class-variance-authority@^0.7.1, clsx@^2.1.1, date-fns@^4.1.0, eslint@^9, eslint-config-next@15.1.7, lucide-react@^0.475.0, next@15.1.7, postcss@^8, react@^19.0.0, react-dom@^19.0.0, react-hook-form@^7.54.2, react-markdown@^9.0.3, react-pdf@^9.2.1, recharts@^2.15.1, shadcn-ui@^0.9.4, tailwind-merge@^3.0.1, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, typescript@^5, zod@^3.24.2

### Recent commits (newest first)

- Update README.md
- RUN NLP
- quick fix
- fix
- quick fixes
- total count based off session responses
- session data updates without refresh
- make prompt not produce snippets
- /topics in session id as string
- remove markdown from prompt, tried markdown block, changed project name
- fixed the warning signal, added pie charts for the comprehension checks
- basic pie chart conneceted to database
- updating session summary
- README and License
- fix table name
- update variable again
- update variable
- add hopefully working session scoring and insights
- syllabus upload
- Merge branch 'cq-topic-mastery'

## Key source files (fetched from GitHub, selected and truncated for size)

### treehacks-backend/requirements.txt

```
aiohappyeyeballs==2.4.6
aiohttp==3.11.12
aiosignal==1.3.2
annotated-types==0.7.0
anyio==4.8.0
attrs==25.1.0
beautifulsoup4==4.13.3
bs4==0.0.2
cachetools==5.5.1
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
deprecation==2.1.0
distro==1.9.0
fastapi==0.115.8
filelock==3.17.0
frozenlist==1.5.0
gdown==5.1.0
google-ai-generativelanguage==0.6.15
google-api-core==2.24.1
google-api-python-client==2.161.0
google-auth==2.38.0
google-auth-httplib2==0.2.0
google-generativeai==0.8.4
googleapis-common-protos==1.67.0
gotrue==2.11.3
grpcio==1.70.0
grpcio-status==1.70.0
h11==0.14.0
h2==4.2.0
hpack==4.1.0
httpcore==1.0.7
httplib2==0.22.0
httpx==0.27.0
hyperframe==6.1.0
idna==3.10
jiter==0.8.2
multidict==6.1.0
numpy==1.26.3
openai==1.63.0
opencv-python==4.9.0.80
packaging==24.2
pdf2image==1.17.0
pillow==11.1.0
postgrest==0.19.3
propcache==0.2.1
proto-plus==1.26.0
protobuf==5.29.3
pyasn1==0.6.1
pyasn1-modules==0.4.1
pydantic==2.10.6
pydantic-core==2.27.2
pyparsing==3.2.1
pytesseract==0.3.13
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-multipart==0.0.6
PyPDF2==3.0.1
realtime==2.3.0
requests==2.31.0
rsa==4.9
six==1.17.0
sniffio==1.3.1
soupsieve==2.6
starlette==0.45.3
storage3==0.11.3
strenum==0.4.15
supabase==2.13.0
supafunc==0.9.3
tqdm==4.67.1
typing-extensions==4.12.2
uritemplate==4.1.1
urllib3==2.3.0
uvicorn==0.34.0
websockets==14.2
yarl==1.18.3

```

### treehacks-frontend/package.json

```
{
  "name": "treehacks-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@hookform/resolvers": "^4.1.0",
    "@radix-ui/react-dialog": "^1.1.6",
    "@radix-ui/react-icons": "^1.3.2",
    "@radix-ui/react-label": "^2.1.2",
    "@radix-ui/react-progress": "^1.1.2",
    "@radix-ui/react-separator": "^1.1.2",
    "@radix-ui/react-slider": "^1.2.3",
    "@radix-ui/react-slot": "^1.1.2",
    "@radix-ui/react-tabs": "^1.1.3",
    "@radix-ui/react-toast": "^1.2.6",
    "@radix-ui/react-tooltip": "^1.1.8",
    "@supabase/supabase-js": "^2.48.1",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "date-fns": "^4.1.0",
    "lucide-react": "^0.475.0",
    "next": "15.1.7",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-hook-form": "^7.54.2",
    "react-markdown": "^9.0.3",
    "react-pdf": "^9.2.1",
    "recharts": "^2.15.1",
    "shadcn-ui": "^0.9.4",
    "tailwind-merge": "^3.0.1",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.1.7",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

```

### treehacks-backend/main.py

```python
from fastapi import FastAPI, HTTPException, File, UploadFile, Form
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
from pydantic import BaseModel
import random
import string
import json
from models import *
from drive_utils import download_lecture_and_slides
import os
from supabase import create_client, Client
from video_utils import extract_audio
from speech_to_text import transcribe_with_timestamps
from slide_utils import map_slides_to_video
from question_gen import generate_questions, save_questions
from topic_utils import get_all_topics, get_topic_by_id
from services.homeworkService import publish_question_extracted_insight, publish_homework_summary
from llm_utils import extract_topics_from_syllabus
import requests
import io
from PyPDF2 import PdfReader
from datetime import datetime
from fastapi import File, UploadFile
from services.questionParsingService import parse_and_store_questions
from services.sessionService import publish_session_question_extracted_insight, publish_session_summary
from services.sessionGradingService import grade_session

load_dotenv()
url: str = os.environ.get("SUPABASE_URL")
key: str = os.environ.get("SUPABASE_KEY")
supabase: Client = create_client(url, key)

app = FastAPI(
    title="Treehacks API",
    description="An API to interact with Treehacks",
    version="1.0.0"
)

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "http://localhost:*"],  # Allow specific port and any localhost
    allow_credentials=True,
    allow_methods=["*"],  # Allows all HTTP methods (POST, GET, etc.)
    allow_headers=["*"],  # Allows all headers
)

def generate_short_id(length: int = 5) -> str:
    # Use only uppercase letters and numbers, excluding confusing characters
    characters = string.ascii_uppercase.replace('O', '') + string.digits.replace('0', '').replace('1', '')
    while True:
        # Generate a random ID
        short_id = ''.join(random.choices(characters, k=length))
        
        # Check if it already exists in the database
        exists = supabase.table('sessions').select('short_id').eq('short_id', short_id).execute()
        
        if not exists.data:
            return short_id

@app.post("/test", response_model=TestResponse)
def test(request: TestRequest):
    return TestResponse(response=f"Your request was: {request.text}")

@app.post("/setup")
def setup():
    download_lecture_and_slides()
    return {"message": "Setup complete"}

#given title, num questions, and lecture id, create a session
@app.post("/api/sessions")
async def create_session(session: SessionCreate):
    short_id = generate_short_id()
    
    try:
        result = supabase.table('sessions').insert({
            'title': session.title,
            'short_id': short_id,
            'active': True,
            'num_questions': session.num_questions,
            'lecture_id': session.lecture_id,
            'timestamp': session.timestamp,
            'class_id': session.class_id
        }).execute()
        
        return result.data[0]
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

#list all all sessions
@app.get("/api/sessions")
async def list_sessions(class_id: str):
    if class_id is None:
        raise HTTPException(status_code=400, detail="Class id is required")
    result = supabase.table('sessions').select('*').eq('class_id', class_id).order('created_at', desc=True).execute()
    return result.data or []

#given a session short id, get the session data
@app.get("/api/sessions/{short_id}")
async def get_session(short_id: str):
    # Convert to uppercase to make it case-insensitive
    short_id = short_id.upper()
    
    result = supabase.table('sessions').select('*').eq('short_id', short_id).execute()
    
    if not result.data:
        raise HTTPException(status_code=404, detail="Session not found")
    
    return result.data[0]

#get progress given the session id (number of submissions)
@app.get("/api/sessions/{short_id}/progress")
async def get_progress(short_id: str):
    try:
        short_id = short_id.upper()
        
        # First get the session
        session_result = (supabase.table('sessions')
            .select('id')
            .eq('short_id', short_id)
            .execute())
        
        if not session_result.data:
            raise HTTPException(status_code=404, detail="Session not found")
            
        session_id = session_result.data[0]['id']
        
        # Get the first question's ID
        question_result = (supabase.table('session_questions')
            .select('id')
            .eq('question_number', 0)
            .eq('session_id', session_id)
            .execute())
        
        if not question_result.data:
            # If no questions exist yet, return 0 responses
            return {'response_count': 0}
            
        question_id = question_result.data[0]['id']
        
        # Get response count for the first question
        count_result = (supabase.table('session_question_response_count')
            .select('response_count')
            .eq('session_question_id', question_id)
            .execute())
        
        # If no responses yet, return 0
        if not count_result.data:
            return {'response_count': 0}
            
        return count_result.data[0]
        
    except Exception as e:
        if isinstance(e, HTTPException):
            raise e
        print(f"Error getting progress: {str(e)}")
        return {'response_count': 0}

#get all questions for a session given the session id
@app.get("/api/sessions/questions/{short_id}")
async def get_questions(short_id: str):
    # Convert to uppercase to make it case-insensitive
    short_id = short_id.upper()
    
    result = supabase.table('sessions').select('*').eq('short_id', short_id).execute()
    
    if not result.data:
        raise HTTPException(status_code=404, detail="Session not found")

    session_id
[truncated — 22092 more characters]
```

### treehacks-frontend/src/app/layout.tsx

```typescript
import type React from "react"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"
import { SidebarProvider } from "@/components/ui/sidebar"
import { Sidebar } from "@/components/sidebar"
import { ClassProvider } from "./context/ClassContext"
import { ClassGuard } from "@/components/class-guard"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "ClassPulse",
  description: "Analytics dashboard for classroom performance",
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" className="h-full">
      <body className={`${inter.className} h-full`}>
        <SidebarProvider>
          <ClassProvider>
            <div className="flex h-full w-full">
              <Sidebar />
              <div className="flex-1 min-w-0 overflow-x-hidden">
                <main className="h-full w-full">
                  <ClassGuard>
                    {children}
                  </ClassGuard>
                </main>
              </div>
            </div>
          </ClassProvider>
        </SidebarProvider>
      </body>
    </html>
  )
}

```

### treehacks-frontend/src/app/page.tsx

```typescript
"use client"

import { useRouter } from "next/navigation"
import { useClass } from "./context/ClassContext"
import { useEffect, useState } from "react"
import { createClient } from "@supabase/supabase-js"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Plus } from "lucide-react"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)

interface Class {
  id: string
  title: string
  created_at: string
  syllabus: string | null
  code: string | null
}

export default function Home() {
  const router = useRouter()
  const { setClass} = useClass()
  const [classes, setClasses] = useState<Class[]>([])
  const [isLoading, setIsLoading] = useState(true)
  const [isDialogOpen, setIsDialogOpen] = useState(false)
  const [newClassName, setNewClassName] = useState("")
  const [newClassCode, setNewClassCode] = useState("")

  useEffect(() => {
    fetchClasses()
  }, [])

  const fetchClasses = async () => {
    try {
      const { data } = await supabase.from("class").select("*")
      setClasses(data || [])
    } catch (error) {
      console.error("Error fetching classes:", error)
    } finally {
      setIsLoading(false)
    }
  }

  const handleAddClass = async () => {
    if (!newClassName.trim() || !newClassCode.trim()) return

    try {
      const { data, error } = await supabase
        .from("class")
        .insert([{ 
          title: newClassName.trim(),
          class_code: newClassCode.trim()
        }])
        .select()

      if (error) throw error

      setClasses([...(data || []), ...classes])
      setNewClassName("")
      setNewClassCode("")
      setIsDialogOpen(false)
    } catch (error) {
      console.error("Error adding class:", error)
    }
  }

  const handleSelectClass = async (classId: string) => {
    const result = await supabase.from('class').select('*').eq('id', classId)
    const classCode = await result.data[0]['class_code']
    setClass(classId, classCode)
    router.push("/home")
  }

  return (
    <div className="min-h-screen bg-background">
      <div className="container mx-auto py-16 px-4 sm:px-6 lg:px-8">
        <div className="max-w-3xl mx-auto space-y-8">
          <div className="text-center space-y-4">
            <h1 className="text-4xl font-bold tracking-tight">Welcome to ClassPulse</h1>
            <p className="text-lg text-muted-foreground">
              Select a class to get started or create a new one
            </p>
          </div>

          <div className="flex items-center justify-between">
            <h2 className="text-2xl font-semibold">Your Classes</h2>
            <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
              <DialogTrigger asChild>
                <Button>
                  <Plus className="mr-2 h-5 w-5" />
                  New Class
                </Button>
              </DialogTrigger>
              <DialogContent>
                <DialogHeader>
                  <DialogTitle>Create New Class</DialogTitle>
                  <DialogDescription>
                    Add a new class to manage lectures and sessions
                  </DialogDescription>
                </DialogHeader>
                <div className="space-y-4 py-4">
                  <div className="space-y-2">
                    <Label htmlFor="name">Class Name</Label>
                    <Input
                      id="name"
                      value={newClassName}
                      onChange={(e) => setNewClassName(e.target.value)}
                      placeholder="Enter class name"
                    />
                  </div>
                  <div className="space-y-2">
                    <Label htmlFor="name">Class Code</Label>
                    <Input
                      id="code"
                      value={newClassCode}
                      onChange={(e) => setNewClassCode(e.target.value)}
                      placeholder="Enter class code"
                    />
                  </div>
                </div>
                <DialogFooter>
                  <Button
                    onClick={handleAddClass}
                    disabled={!(newClassName.trim() && newClassCode.trim())}
                  >
                    Create Class
                  </Button>
                </DialogFooter>
              </DialogContent>
            </Dialog>
          </div>

          <div className="grid gap-4">
            {classes.map((classItem) => (
              <Card
                key={classItem.id}
                className="cursor-pointer hover:bg-muted/50 transition-colors"
                onClick={() => handleSelectClass(classItem.id)}
              >
                <CardHeader>
                  <CardTitle>{classItem.title}</CardTitle>
                  <CardDescription>
                    Created on {new Date(classItem.created_at).toLocaleDateString()}
                  </CardDescription>
                </CardHeader>
              </Card>
            ))}
            {!isLoading && classes.length === 0 && (
              <Card>
                <CardContent className="pt-6">
                  <p className="text-center text-muted-foreground">
                    No classes yet. Create your first class to get started!
                  </p>
                </CardContent>
              </Card>
            )}
          </div>
        </div>
      </div>
    </div>
  )
}

```

### treehacks-frontend/src/app/professor-setup/layout.tsx

```typescript
import type React from "react"
export default function ProfessorSetupLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="absolute inset-0 flex items-center justify-center p-4">
      {children}
    </div>
  )
}

```

### treehacks-frontend/src/app/profile/page.tsx

```typescript
"use client"

import type React from "react"

import { useState } from "react"
import { Breadcrumb } from "@/components/breadcrumb"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { useClass } from "@/app/context/ClassContext"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"

export default function Profile() {
  const { classCode } = useClass()
  const [name, setName] = useState("John Doe")
  const [email, setEmail] = useState("john.doe@example.com")

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    // Handle profile update logic here
    console.log("Profile updated", { name, email })
  }

  return (
    <div className="min-h-full p-8">
      <div className="max-w-[2000px] mx-auto space-y-8">
        <Breadcrumb
          items={[
            { label: classCode || "Home", href: "/home" },
            { label: "Profile", href: "/profile" },
          ]}
        />

        <h1 className="text-3xl font-bold">Profile Settings</h1>

        <div className="grid grid-cols-1 gap-6">
          <Card>
            <CardHeader className="p-8">
              <CardTitle className="text-2xl">Personal Information</CardTitle>
              <CardDescription className="text-base">Update your profile information</CardDescription>
            </CardHeader>
            <CardContent className="p-8 pt-0">
              <form onSubmit={handleSubmit} className="space-y-6">
                <div className="space-y-2">
                  <Label htmlFor="name" className="text-base">Full Name</Label>
                  <Input
                    id="name"
                    value={name}
                    onChange={(e) => setName(e.target.value)}
                    className="h-12 text-lg"
                  />
                </div>
                <div className="space-y-2">
                  <Label htmlFor="email" className="text-base">Email Address</Label>
                  <Input
                    id="email"
                    type="email"
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                    className="h-12 text-lg"
                  />
                </div>
                <Button type="submit" size="lg">Save Changes</Button>
              </form>
            </CardContent>
          </Card>
        </div>
      </div>
    </div>
  )
}

```

### treehacks-frontend/src/app/lectures/page.tsx

```typescript
'use client'

import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { PlusCircle, Video, FileText } from "lucide-react"
import Link from "next/link"
import { useState, useEffect } from "react"
import { Breadcrumb } from "@/components/breadcrumb"
import { useClass } from "../context/ClassContext"

interface Lecture {
  id: string
  name: string
  created_at: string
  slides?: string
  lecture_video?: string
  class_id: string
}

export default function Lectures() {
  const [lectures, setLectures] = useState<Lecture[]>([])
  const { classId, classCode } = useClass()

  useEffect(() => {
    const fetchLectures = async () => {
      if (!classId) return
      
      try {
        const response = await fetch(`http://localhost:8000/api/lectures?class_id=${classId}`)
        if (!response.ok) {
          throw new Error('Failed to fetch lectures')
        }
        const data = await response.json()
        setLectures(data)
      } catch (error) {
        console.error('Error fetching lectures:', error)
      }
    }

    fetchLectures()
  }, [classId])

  return (
    <div className="min-h-full p-8">
      <div className="max-w-[2000px] mx-auto space-y-8">
        <Breadcrumb
          items={[
            { label: classCode || "Home", href: "/home" },
            { label: "Lectures", href: "/lectures" },
          ]}
        />

        <div className="flex justify-between items-center">
          <div>
            <h1 className="text-3xl font-bold">Lectures</h1>
            <p className="text-muted-foreground mt-1">
              {lectures.length} lecture{lectures.length === 1 ? '' : 's'}
            </p>
          </div>
          <Button size="lg" asChild>
            <Link href="/lectures/add">
              <PlusCircle className="mr-2 h-5 w-5" />
              Add Lecture
            </Link>
          </Button>
        </div>

        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
          {lectures.map((lecture) => (
            <Link key={lecture.id} href={`/lectures/${lecture.id}`}>
              <Card className="hover:bg-accent transition-colors">
                <CardHeader className="p-8">
                  <div className="flex justify-between items-start">
                    <div>
                      <CardTitle className="text-xl">{lecture.name}</CardTitle>
                      <CardDescription className="text-base">
                        Created on {new Date(lecture.created_at).toLocaleDateString()}
                      </CardDescription>
                    </div>
                    <div className="flex gap-2 text-muted-foreground">
                      {lecture.slides && (
                        <FileText className="h-5 w-5" />
                      )}
                      {lecture.lecture_video && (
                        <Video className="h-5 w-5" />
                      )}
                    </div>
                  </div>
                </CardHeader>
              </Card>
            </Link>
          ))}
          {lectures.length === 0 && (
            <Card>
              <CardContent className="p-8">
                <p className="text-center text-muted-foreground text-lg">
                  {classId ? 'No lectures yet. Create one to get started!' : 'Please select a class to view lectures.'}
                </p>
              </CardContent>
            </Card>
          )}
        </div>
      </div>
    </div>
  )
}

```

### treehacks-frontend/src/app/professor-setup/page.tsx

```typescript
"use client"

import type React from "react"

import { useState } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"

export default function ProfessorSetup() {
  const router = useRouter()
  const [className, setClassName] = useState("")
  const [classCode, setClassCode] = useState("")
  const [studentCount, setStudentCount] = useState("")
  const [syllabus, setSyllabus] = useState("")
  const [topics, setTopics] = useState<string[]>([])

  const handleSyllabusUpload = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const uploadedSyllabus = e.target.value
    setSyllabus(uploadedSyllabus)
    // This is a placeholder for syllabus parsing logic
    const parsedTopics = uploadedSyllabus.split("\n").filter(Boolean)
    setTopics(parsedTopics)
  }

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    // Here you would typically save the data to your backend
    console.log({ className, classCode, studentCount, syllabus, topics })
    router.push("/home")
  }

  return (
    <Card className="w-full max-w-3xl">
      <form onSubmit={handleSubmit}>
        <CardHeader className="pb-4">
          <CardTitle>Professor Setup</CardTitle>
          <CardDescription>Set up your class details and syllabus</CardDescription>
        </CardHeader>
        <CardContent className="space-y-6">
          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div className="space-y-2">
              <Label htmlFor="class-name">Class Name</Label>
              <Input id="class-name" value={className} onChange={(e) => setClassName(e.target.value)} required />
            </div>
            <div className="space-y-2">
              <Label htmlFor="class-code">Class Code</Label>
              <Input id="class-code" value={classCode} onChange={(e) => setClassCode(e.target.value)} required />
            </div>
          </div>
          <div className="space-y-2">
            <Label htmlFor="student-count">Number of Students</Label>
            <Input
              id="student-count"
              type="number"
              value={studentCount}
              onChange={(e) => setStudentCount(e.target.value)}
              required
            />
          </div>
          <div className="space-y-2">
            <Label htmlFor="syllabus">Upload Syllabus</Label>
            <Textarea
              id="syllabus"
              value={syllabus}
              onChange={handleSyllabusUpload}
              placeholder="Paste your syllabus here..."
              rows={4}
              required
            />
          </div>
          {topics.length > 0 && (
            <div className="space-y-2">
              <Label>Generated Topics</Label>
              <ul className="list-disc pl-5 max-h-40 overflow-y-auto">
                {topics.map((topic, index) => (
                  <li key={index}>{topic}</li>
                ))}
              </ul>
              <p className="text-sm text-muted-foreground mt-1">You can modify these topics after submission.</p>
            </div>
          )}
        </CardContent>
        <CardFooter className="pt-2">
          <Button type="submit">Save and Continue</Button>
        </CardFooter>
      </form>
    </Card>
  )
}

```

### treehacks-frontend/src/app/announcements/page.tsx

```typescript
"use client"

import type React from "react"

import { useState } from "react"
import { Breadcrumb } from "@/components/breadcrumb"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Label } from "@/components/ui/label"
import { useClass } from "@/app/context/ClassContext"

interface Announcement {
  id: number
  title: string
  content: string
  date: string
}

const initialAnnouncements: Announcement[] = [
  {
    id: 1,
    title: "Welcome to the new semester",
    content: "We're excited to start this new journey with you!",
    date: "2023-09-01",
  },
  {
    id: 2,
    title: "Midterm exam schedule",
    content: "The midterm exams will be held from October 15th to October 20th.",
    date: "2023-09-15",
  },
]

export default function Announcements() {
  const [announcements, setAnnouncements] = useState(initialAnnouncements)
  const [newTitle, setNewTitle] = useState("")
  const [newContent, setNewContent] = useState("")
  const {classCode} = useClass()

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (!newTitle.trim() || !newContent.trim()) return

    const newAnnouncement: Announcement = {
      id: announcements.length + 1,
      title: newTitle,
      content: newContent,
      date: new Date().toISOString().split("T")[0],
    }

    setAnnouncements([newAnnouncement, ...announcements])
    setNewTitle("")
    setNewContent("")
  }

  return (
    <div className="min-h-full p-8">
      <div className="max-w-[2000px] mx-auto space-y-8">
        <Breadcrumb
          items={[
            { label: classCode || "Home", href: "/home" },
            { label: "Announcements", href: "/announcements" },
          ]}
        />

        <h1 className="text-3xl font-bold">Announcements</h1>

        <div className="grid grid-cols-1 gap-6">
          <Card>
            <CardHeader className="p-8">
              <CardTitle className="text-2xl">New Announcement</CardTitle>
              <CardDescription className="text-base">Create a new announcement for your class</CardDescription>
            </CardHeader>
            <CardContent className="p-8 pt-0">
              <form onSubmit={handleSubmit} className="space-y-6">
                <div className="space-y-2">
                  <Label htmlFor="title" className="text-base">Title</Label>
                  <Input
                    id="title"
                    value={newTitle}
                    onChange={(e) => setNewTitle(e.target.value)}
                    placeholder="Enter announcement title"
                    className="h-12 text-lg"
                  />
                </div>
                <div className="space-y-2">
                  <Label htmlFor="content" className="text-base">Content</Label>
                  <Textarea
                    id="content"
                    value={newContent}
                    onChange={(e) => setNewContent(e.target.value)}
                    placeholder="Enter announcement content"
                    className="min-h-[100px] text-lg"
                  />
                </div>
                <Button type="submit" size="lg">Post Announcement</Button>
              </form>
            </CardContent>
          </Card>
        </div>

        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
          {announcements.map((announcement) => (
            <Card key={announcement.id}>
              <CardHeader className="p-8">
                <CardTitle className="text-xl">{announcement.title}</CardTitle>
                <CardDescription className="text-base">Posted on {announcement.date}</CardDescription>
              </CardHeader>
              <CardContent className="p-8 pt-0">
                <p className="text-lg">{announcement.content}</p>
              </CardContent>
            </Card>
          ))}
        </div>
      </div>
    </div>
  )
}

```

[69 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]