Tracks audience emotion and attention during presentations using webcam data. After a session, aggregates everyone logs and shows a dashboard with engagement metrics and Claude-powered coaching tips.
Built at a hackathon, then refactored. See POSTMORTEM.md for what broke the first time and why.
Each presentation gets a session code you put on your title slide (e.g. CS4681-A). Audience members enter it in the GUI when they start recording. After you finish presenting, you pull their data and view it in the dashboard by session.
Pipeline: audience webcam -> emotion_logger_fixed.py -> sessions/{code}/emotion_{name}.jsonl -> pushed to GitHub -> server.js pulls and aggregates on demand -> React dashboard
Presenter machine
- Node.js 18+
- npm install
- ANTHROPIC_API_KEY in .env
- No Python pip packages needed, aggregate_fixed.py is pure stdlib
Audience machines
- Python 3.10+ from python.org, check Add to PATH during install
- Git from git-scm.com, default options are fine
- Everything else installed automatically by audience_setup.bat
cp .env.example .env
# add your ANTHROPIC_API_KEY
npm install
For audience distribution: open audience_setup.bat and fill in your GITHUB_TOKEN before sending it out.
Put your session code on your title slide. Keep it short like CS4681-A or honeywell-demo.
Audience steps during your intro:
- Double-click audience_setup.bat
- Enter their name and the session code
- Click Start Recording
- Click Stop and Submit when you finish
After presenting, open two terminals:
# terminal 1
node server.js
# terminal 2
npm run dev
# open http://localhost:3000
Pick your session from the dashboard, hit Pull Latest to grab the data, then Get AI Coaching Tips.
Save this as seed_test.py and run it once, then start the servers normally.
import random, time, json, os
session = "test-session"
os.makedirs(f"sessions/{session}", exist_ok=True)
emotions = ["happy", "neutral", "sad", "angry", "fear", "surprised"]
start_ms = int(time.time() * 1000)
with open(f"sessions/{session}/emotion_aditya.jsonl", "w") as f:
for i in range(100):
top = random.choice(emotions)
f.write(json.dumps({
"timestamp": start_ms + i * 2000,
"session": session, "user": "aditya",
"distracted": random.random() < 0.15,
"pitch": round(random.uniform(-25, 10), 2),
"emotion": top,
"top_emotions": [{"emotion": top, "score": round(random.uniform(0.4, 0.8), 4)}],
}) + "
")
node server.js # terminal 1
npm run dev # terminal 2
MoodMetrics/
- src/
- App.tsx
- main.tsx
- index.css
- components/
- Dashboard.tsx
- sessions/ created at runtime, gitignored
- {session_code}/
- emotion_{name}.jsonl pushed by each audience member
- summary.json generated by server on demand
- audience_setup.bat distribute this to audience
- audience_gui.py
- emotion_logger_fixed.py
- aggregate_fixed.py pure stdlib, no pip install needed
- requirements_client.txt audience machines only
- server.js
- vite.config.ts
- index.html
- package.json
- .env.example
- POSTMORTEM.md
- README.md
- Session picker, one button per presentation
- Engagement score, distraction rate, top emotion, flagged moment count
- Emotion timeline chart in 30-second buckets
- Flagged moments where confusion or distraction spiked
- Audience members tracked per session
- AI coaching tips from Claude