BREATHE / SETUP.md
tannuiscoding's picture
added app.py
5a264f5
# BREATHE β€” Project Setup Guide
## Overview
**BREATHE** is a full-stack stress intelligence web application. After logging in, users land on the **Breathe hub** β€” a full-screen activity centre with cards for:
- Stress assessments (psychometric + free-text, ML-powered)
- Gratitude journaling (saved to account)
- Daily to-do list (saved in browser only)
- Guided breathing and relaxation exercises
All stress data is stored in a database and shown on a live dashboard with charts and history.
---
## Project Structure
```
breathe-app/
β”œβ”€β”€ app.py # WSGI entry-point
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ .env.example # copy β†’ .env and fill in values
β”œβ”€β”€ .gitignore
β”œβ”€β”€ backend/
β”‚ β”œβ”€β”€ __init__.py # Flask app factory + DB init
β”‚ β”œβ”€β”€ models/
β”‚ β”‚ β”œβ”€β”€ user.py # User ORM (profile fields: avatar, gender, dob, bio, is_anonymous)
β”‚ β”‚ β”œβ”€β”€ assessment.py # Assessment ORM
β”‚ β”‚ └── gratitude.py # GratitudeEntry ORM
β”‚ β”œβ”€β”€ routes/
β”‚ β”‚ β”œβ”€β”€ auth.py # /api/auth/*
β”‚ β”‚ β”œβ”€β”€ assessments.py # /api/assessments/*
β”‚ β”‚ β”œβ”€β”€ profile.py # /api/profile/*
β”‚ β”‚ └── gratitude.py # /api/gratitude/*
β”‚ └── ml/
β”‚ └── ml_engine.py # Psychometric + RoBERTa inference (demo fallback)
β”œβ”€β”€ frontend/ # React 18 + Vite 5 SPA
β”‚ β”œβ”€β”€ index.html
β”‚ β”œβ”€β”€ vite.config.js # Vite config (proxy /api β†’ Flask :5000)
β”‚ β”œβ”€β”€ package.json
β”‚ └── src/
β”‚ β”œβ”€β”€ main.jsx
β”‚ β”œβ”€β”€ App.jsx # Router + auth guards
β”‚ β”œβ”€β”€ index.css # Global design system
β”‚ β”œβ”€β”€ utils.js # Shared helpers + colour maps
β”‚ β”œβ”€β”€ api/client.js # Fetch wrapper (get/post/put/del)
β”‚ β”œβ”€β”€ context/
β”‚ β”‚ └── AuthContext.jsx
β”‚ β”œβ”€β”€ components/
β”‚ β”‚ └── Layout.jsx # Sidebar shell
β”‚ └── pages/
β”‚ β”œβ”€β”€ LandingPage.jsx
β”‚ β”œβ”€β”€ AuthPage.jsx
β”‚ β”œβ”€β”€ BreathePage.jsx # Activity hub (post-login home)
β”‚ β”œβ”€β”€ DashboardPage.jsx
β”‚ β”œβ”€β”€ AssessPage.jsx
β”‚ β”œβ”€β”€ HistoryPage.jsx
β”‚ β”œβ”€β”€ ProfilePage.jsx
β”‚ β”œβ”€β”€ GratitudePage.jsx
β”‚ └── TodoPage.jsx
β”œβ”€β”€ models/
β”‚ β”œβ”€β”€ psychometric/ # .pkl files from notebook
β”‚ └── text/ # roberta-model.pt
└── instance/
└── breathe.db # SQLite DB (auto-created)
```
---
## Prerequisites
| Requirement | Version |
|-------------|---------|
| Python | β‰₯ 3.10 |
| Node.js | β‰₯ 18 |
| npm | β‰₯ 9 |
| pip | β‰₯ 23 |
| (Optional) NVIDIA GPU + CUDA 12.x | for full RoBERTa inference |
---
## Step-by-Step Setup
### 1 β€” Open the project folder
```bash
cd breathe-app
```
### 2 β€” Create a Python virtual environment
```bash
python -m venv venv
# macOS / Linux
source venv/bin/activate
# Windows PowerShell
venv\Scripts\Activate.ps1
```
### 3 β€” Install Python dependencies
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
> **GPU note:** For GPU-accelerated RoBERTa inference, replace the `torch` line in
> `requirements.txt` with:
> ```
> torch==2.2.2+cu121 --index-url https://download.pytorch.org/whl/cu121
> ```
### 4 β€” Configure environment variables
```bash
cp .env.example .env
```
Open `.env` and fill in:
| Variable | What to set |
|----------|-------------|
| `SECRET_KEY` | Long random string |
| `DATABASE_URL` | `sqlite:///breathe.db` (default) or a PostgreSQL URI |
| `PSYCHO_MODEL_DIR` | Path to folder with trained psychometric `.pkl` files |
| `ROBERTA_CKPT` | Path to `roberta-model.pt` |
> **Demo mode:** If model paths are missing or `torch` is not installed, the app runs with
> rule-based heuristic predictions. All features of the UI still work.
#### Psychometric model artefacts
Run `psychometric notebook.ipynb` to produce these files, then set `PSYCHO_MODEL_DIR`:
```
base_scaler.pkl final_scaler.pkl le_dict.pkl le_target.pkl
selected_cols.pkl poly.pkl top_num.pkl *_best_model.pkl
```
#### RoBERTa checkpoint
```
ROBERTA_CKPT=/path/to/breathe/text-sentiment/roberta-model.pt
```
### 5 β€” Install frontend dependencies
```bash
cd frontend
npm install
cd ..
```
> **asdf users:** run `asdf set nodejs 20.18.1` inside the `frontend/` directory first.
### 6 β€” Run the development servers
Open **two terminal tabs**.
**Terminal 1 β€” Flask API (port 5000)**
```bash
# from breathe-app/
source venv/bin/activate
python app.py
```
**Terminal 2 β€” React dev server (port 5173)**
```bash
# from breathe-app/frontend/
npm run dev
```
Open **http://localhost:5173** in your browser.
- After login you land on the **Breathe hub** (`/app/breathe`)
- The React dev server proxies all `/api/*` requests to Flask on port 5000 automatically
- The SQLite database (`instance/breathe.db`) is created automatically on first run
### 7 β€” (Optional) PostgreSQL
```bash
pip install psycopg2-binary
createdb breathe
# Set in .env:
# DATABASE_URL=postgresql://postgres:password@localhost:5432/breathe
```
---
## Database Migrations
The app uses `db.create_all()` on startup to create new tables. If you add columns to an
existing table (e.g. upgrading from an older version), run the migration script manually:
```bash
source venv/bin/activate
python3 - <<'EOF'
import sqlite3, os
conn = sqlite3.connect('instance/breathe.db')
cur = conn.cursor()
# Example: add profile columns if missing
cur.execute("PRAGMA table_info(users)")
existing = {row[1] for row in cur.fetchall()}
new_cols = {
"avatar": "TEXT", "gender": "VARCHAR(32)",
"working_status": "VARCHAR(64)", "dob": "VARCHAR(10)",
"bio": "VARCHAR(500)", "is_anonymous": "BOOLEAN DEFAULT 0",
}
for col, t in new_cols.items():
if col not in existing:
cur.execute(f"ALTER TABLE users ADD COLUMN {col} {t}")
conn.commit(); conn.close(); print('done')
EOF
```
---
## Running in Production
### 1 β€” Build the React frontend
```bash
cd frontend
npm run build
cd ..
```
Outputs a static bundle to `frontend/dist/`. Flask auto-detects and serves it.
### 2 β€” Start the Flask server
```bash
source venv/bin/activate
gunicorn "app:app" \
--workers 2 \
--bind 0.0.0.0:5000 \
--timeout 120
```
Set `FLASK_DEBUG=false` and a strong `SECRET_KEY` in `.env` before deploying.
---
## API Reference
### Auth
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/auth/signup` | Create account (`username`, `email`, `password`) |
| POST | `/api/auth/login` | Log in (`email` or `username`, `password`) |
| POST | `/api/auth/logout` | Log out |
| GET | `/api/auth/me` | Current user |
### Assessments
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/assessments` | Submit assessment |
| GET | `/api/assessments` | List (paginated `?page=1&per_page=20`) |
| GET | `/api/assessments/<id>` | Single assessment |
| GET | `/api/assessments/summary` | Dashboard summary + timeline |
### Profile
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/profile` | Get current profile |
| PUT | `/api/profile` | Update bio, gender, working_status, dob, is_anonymous |
| POST | `/api/profile/avatar` | Upload/remove avatar (base64 data-url, max 2 MB) |
### Gratitude Journal
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/gratitude` | Save entry (`items[]`, `content`, `mood`) |
| GET | `/api/gratitude` | List entries (`?page=1`) |
| DELETE | `/api/gratitude/<id>` | Delete entry |
---
## Page Routes
| URL | Page | Auth |
|-----|------|------|
| `/` | Landing page | Public |
| `/auth` | Login / Signup | Public |
| `/app/breathe` | Breathe hub (post-login home) | Protected |
| `/app/dashboard` | Stress dashboard | Protected |
| `/app/assess` | New assessment | Protected |
| `/app/history` | Assessment history | Protected |
| `/app/profile` | User profile | Protected |
| `/app/gratitude` | Gratitude journal | Protected |
| `/app/todo` | Daily to-do list | Protected |
---
## Stress Level Mapping
| Level | Score Range | Colour |
|-------|-------------|--------|
| Minimal | 0.00 – 0.20 | 🟒 Green |
| Mild | 0.20 – 0.40 | 🟑 Light green |
| Moderate | 0.40 – 0.60 | 🟠 Yellow |
| Severe | 0.60 – 0.80 | πŸ”΄ Orange |
| Critical | 0.80 – 1.00 | 🚨 Red |
---
## Troubleshooting
| Problem | Fix |
|---------|-----|
| `No version is set for nodejs` | Run `asdf set nodejs 20.18.1` in `frontend/` |
| Blank page in browser | Make sure Flask is running on port 5000 |
| CORS errors | Add `http://localhost:5173` to `CORS_ORIGINS` in `.env` |
| `ModuleNotFoundError: torch` | `pip install torch` or use demo mode |
| `FileNotFoundError: base_scaler.pkl` | Set `PSYCHO_MODEL_DIR` in `.env` |
| HTTP 500 on login after upgrade | Run the DB migration script above to add new columns |
| Port 5000 in use | Set `PORT=5001` in `.env` and update `vite.config.js` proxy target |
| Database locked (SQLite) | Use PostgreSQL for multi-worker deployments |
---
## Project Structure
```
breathe-app/
β”œβ”€β”€ app.py # WSGI entry-point
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ .env.example # copy β†’ .env and fill in values
β”œβ”€β”€ .gitignore
β”œβ”€β”€ backend/
β”‚ β”œβ”€β”€ __init__.py # Flask app factory + DB init
β”‚ β”œβ”€β”€ models/
β”‚ β”‚ β”œβ”€β”€ user.py # User ORM model
β”‚ β”‚ └── assessment.py # Assessment ORM model
β”‚ β”œβ”€β”€ routes/
β”‚ β”‚ β”œβ”€β”€ auth.py # /api/auth/* endpoints
β”‚ β”‚ └── assessments.py # /api/assessments/* endpoints
β”‚ └── ml/
β”‚ └── ml_engine.py # Psychometric + RoBERTa inference
β”œβ”€β”€ frontend/ # React + Vite SPA
β”‚ β”œβ”€β”€ index.html # Vite HTML shell
β”‚ β”œβ”€β”€ vite.config.js # Vite config (proxy /api β†’ Flask)
β”‚ β”œβ”€β”€ package.json
β”‚ └── src/
β”‚ β”œβ”€β”€ main.jsx
β”‚ β”œβ”€β”€ App.jsx # Router + auth guards
β”‚ β”œβ”€β”€ index.css # Global design system
β”‚ β”œβ”€β”€ utils.js # Shared helpers + colour maps
β”‚ β”œβ”€β”€ api/client.js # Fetch wrapper
β”‚ β”œβ”€β”€ context/
β”‚ β”‚ └── AuthContext.jsx
β”‚ β”œβ”€β”€ components/
β”‚ β”‚ └── Layout.jsx # Sidebar shell
β”‚ └── pages/
β”‚ β”œβ”€β”€ AuthPage.jsx # Login / Signup
β”‚ β”œβ”€β”€ DashboardPage.jsx # Charts + stats
β”‚ β”œβ”€β”€ AssessPage.jsx # Sliders + gauge
β”‚ └── HistoryPage.jsx # Paginated history
β”œβ”€β”€ models/
β”‚ β”œβ”€β”€ psychometric/ # .pkl files from notebook
β”‚ └── text/ # roberta-model.pt
└── instance/
└── breathe.db # SQLite DB (auto-created)
```
---
## Prerequisites
| Requirement | Version |
|-------------|---------|
| Python | β‰₯ 3.10 || Node.js | β‰₯ 18 |
| npm | β‰₯ 9 || pip | β‰₯ 23 |
| Git | any |
| (Optional) NVIDIA GPU + CUDA 12.x | for full RoBERTa inference |
---
## Step-by-Step Setup
### 1 β€” Clone / open the project
```bash
cd breathe-app
```
### 2 β€” Create a virtual environment
```bash
python -m venv venv
# macOS / Linux
source venv/bin/activate
# Windows PowerShell
venv\Scripts\Activate.ps1
```
### 3 β€” Install Python dependencies
```bash
pip install --upgrade pip
pip install -r requirements.txt
```
> **GPU note:** For GPU-accelerated RoBERTa inference, replace the `torch` line in
> `requirements.txt` with:
> ```
> torch==2.2.2+cu121 --index-url https://download.pytorch.org/whl/cu121
> ```
### 4 β€” Configure environment variables
```bash
cp .env.example .env
```
Open `.env` in any editor and fill in:
| Variable | What to set |
|----------|------------|
| `SECRET_KEY` | Long random string (see comment in file) |
| `DATABASE_URL` | SQLite default works out of the box. For Postgres: `postgresql://user:pass@host/db` |
| `PSYCHO_MODEL_DIR` | Absolute path to the directory containing your trained psychometric model `.pkl` files (output of `psychometric notebook.ipynb`) |
| `ROBERTA_CKPT` | Absolute path to `roberta-model.pt` (found in `text-sentiment/`) |
#### Generating the psychometric model artefacts
The psychometric notebook saves these files into `SAVE_DIR` (`/kaggle/working/saved_models` by default):
```
base_scaler.pkl
final_scaler.pkl
le_dict.pkl
le_target.pkl
selected_cols.pkl
poly.pkl
top_num.pkl
<model>_best_model.pkl (e.g. lightgbm_best_model.pkl)
```
Download them from Kaggle β†’ paste into any local folder β†’ set `PSYCHO_MODEL_DIR` to that folder.
#### Using the RoBERTa checkpoint
The file `text-sentiment/roberta-model.pt` is already in the workspace.
Set `ROBERTA_CKPT` to its full path:
```
ROBERTA_CKPT=/Users/you/Downloads/breathe/text-sentiment/roberta-model.pt
```
> **Demo mode:** If you omit or leave either path blank, the app runs in
> **Demo mode** β€” a rule-based heuristic engine. All features of the UI work;
> only the ML predictions are approximate.
### 5 β€” Install frontend dependencies
```bash
cd frontend
npm install
cd ..
```
> If you use **asdf** to manage Node versions, first run:
> ```bash
> asdf set nodejs 20.18.1 # or whichever version is installed
> ```
### 6 β€” Run the development servers
The app requires **two processes** running at the same time β€” open two terminal tabs.
**Terminal 1 β€” Flask API (port 5000)**
```bash
# from breathe-app/
source venv/bin/activate # Windows: venv\Scripts\Activate.ps1
python app.py
```
**Terminal 2 β€” React dev server (port 5173)**
```bash
# from breathe-app/frontend/
npm run dev
```
Then open **http://localhost:5173** in your browser.
> The React dev server proxies all `/api/*` requests to Flask on port 5000 automatically β€” no extra config needed.
The database (`instance/breathe.db`) is created automatically by Flask on first run.
### 7 β€” (Optional) PostgreSQL setup
```bash
# Install psycopg2
pip install psycopg2-binary
# Create database
createdb breathe
# Set in .env:
DATABASE_URL=postgresql://postgres:password@localhost:5432/breathe
```
---
## Running in Production
### 1 β€” Build the React frontend
```bash
cd frontend
npm run build
cd ..
```
This outputs a fully static bundle to `frontend/dist/`. Flask automatically detects and serves it β€” no separate Node process needed in production.
### 2 β€” Start the Flask server
```bash
source venv/bin/activate
gunicorn "app:app" \
--workers 2 \
--bind 0.0.0.0:5000 \
--timeout 120
```
Set `FLASK_DEBUG=false` and a strong `SECRET_KEY` in `.env` before deploying.
For HTTPS, put Nginx or Caddy in front as a reverse proxy.
---
## API Reference
All endpoints return JSON.
### Auth
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/auth/signup` | Create account (`username`, `email`, `password`) |
| POST | `/api/auth/login` | Log in (`email` or `username`, `password`) |
| POST | `/api/auth/logout` | Log out |
| GET | `/api/auth/me` | Get current user |
### Assessments
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/assessments` | Submit assessment (psychometric JSON + text note) |
| GET | `/api/assessments` | List assessments (paginated: `?page=1&per_page=20`) |
| GET | `/api/assessments/<id>` | Get single assessment |
| GET | `/api/assessments/summary` | Dashboard summary + timeline |
#### Example POST body
```json
{
"psychometric": {
"Sleep_Duration": 6.5,
"Sleep_Quality": 2,
"Work_Hours": 11,
"Physical_Activity": 0.5,
"Screen_Time": 8,
"Travel_Time": 1.5,
"Social_Interactions": 2,
"Caffeine_Intake": 4,
"Alcohol_Intake": 0,
"Blood_Pressure": 125,
"Cholesterol_Level": 200,
"Blood_Sugar_Level": 95,
"Gender": "Female",
"Occupation": "Working Professional",
"Smoking_Status": "Non-Smoker",
"Diet_Quality": "Average"
},
"text_note": "I've been feeling overwhelmed with deadlines lately and can't stop overthinking at night."
}
```
#### Example response
```json
{
"message": "Assessment saved",
"prediction": {
"psycho_label": "High",
"psycho_score": 0.8241,
"text_label": "Stress",
"text_score": 0.4512,
"fused_label": "Severe",
"fused_score": 0.6376,
"modality_used": "both"
},
"assessment": { "id": 7, "created_at": "2026-05-02T14:32:00", ... }
}
```
---
## Stress Level Mapping
| Level | Score Range | Colour |
|-------|-------------|--------|
| Minimal | 0.00 – 0.20 | 🟒 Green |
| Mild | 0.20 – 0.40 | 🟑 Light green |
| Moderate | 0.40 – 0.60 | 🟠 Yellow |
| Severe | 0.60 – 0.80 | πŸ”΄ Orange |
| Critical | 0.80 – 1.00 | 🚨 Red |
---
## Troubleshooting
| Problem | Fix |
|---------|-----|
| `No version is set for nodejs` | Run `asdf set nodejs 20.18.1` in the `frontend/` directory |
| React dev server shows blank page | Make sure Flask is running on port 5000 (the Vite proxy needs it) |
| `CORS` errors in browser console | Ensure `CORS_ORIGINS` in `.env` includes `http://localhost:5173` |
| `ModuleNotFoundError: torch` | Run `pip install torch` or install the CUDA wheel |
| `FileNotFoundError: base_scaler.pkl` | Set `PSYCHO_MODEL_DIR` correctly in `.env` |
| `RoBERTa weights not found` | App falls back to demo mode β€” set `ROBERTA_CKPT` |
| `Port 5000 in use` | Change `PORT=5001` in `.env` and update `vite.config.js` proxy target |
| Database locked (SQLite) | Only one worker at a time with SQLite; use Postgres for multi-worker |
---
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Backend | Python 3.10+, Flask 3, SQLAlchemy |
| Database | SQLite (dev) / PostgreSQL (prod) |
| Auth | Server-side sessions + Werkzeug password hashing |
| ML β€” Tabular | LightGBM / CatBoost / XGBoost / Stacking ensemble |
| ML β€” Text | RoBERTa-base fine-tuned on mental-health dataset |
| ML β€” Fusion | Weighted probability fusion β†’ 5-class output |
| Frontend | React 18, Vite 5, React Router v6, Recharts 2 |