# Project export: BridgeNote

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: UC Berkeley AI Hackathon 2026
- Tagline: Therapy when your therapist isn't available.
- Devpost: https://devpost.com/software/bridgenote
- GitHub: https://github.com/crystallamar/BridgeNote
- Demo: https://docs.google.com/presentation/d/1fRabp-ycOKkTfkf8wyJVmzLAG9qawL88xGScIzGVRJ0/edit?usp=sharing
- Team: 2 GitHub contributor(s) — crystallamar (16 commits), Claude Sonnet 4.6 (15 commits)

## Devpost submission (written by the team)

### Inspiration

Mental health has been a huge part of my life — and I'm not alone. 50% of Americans will face a mental health condition in their lifetime. Most therapy happens in 50-minute sessions every two weeks — leaving clients on their own during the times they need the most help. That gap is where crises escalate, progress gets lost, and people fall through the cracks. I wanted to build something for the other 13 days. The populations who suffer most from this gap — low-income individuals, LGBTQ+ youth, Black, Latino and Indigenous communities, women — are also the least likely to have the resources to fill it themselves. BridgeNote is an attempt to give them something that actually knows who they are.

### What it does

BridgeNote has two sides. For clients: a daily check-in (mood, sliders, habit buttons, journal prompt) and an AI companion they can talk to between sessions. For therapists: a dashboard showing mood trends, slider charts, journal sentiment, and AI-generated conversation summaries — all waiting before the next session starts. The key design decision was that the AI is not generic. The therapist enters clinical context once — treatment goals, triggers, strengths, diagnoses, last session notes — and every chatbot response is built on that foundation. It's not ChatGPT with a journal chat. It knows this specific person and how to respond with and encourage use of the tactics being learned in therapy — decided by the therapist, a licensed professional. How I built it React 18 + Recharts on the frontend FastAPI (Python) backend with streaming SSE for the chatbot Claude Sonnet via the Anthropic API — streaming responses, dynamic system prompt built at request time from Redis data Redis for all persistence: check-ins, therapist context, conversation history, check-in configs TextBlob for sentiment analysis on journal entries Docker Compose for Redis Challenges I ran into The human challenge: How do I make this solution resonate with the people who have the power to implement it — and the people whose lives it could change? The technical challenge: Getting the system prompt right. It has to pull therapist context, 7 days of check-in history, a safety protocol, and a persona — all within token budget and without overwhelming the model. I ended up capping therapist context at 300 words, conversation history at 10 messages, and max output at 400 tokens to keep responses concise and costs manageable. The hardest part was the safety guardrail. Getting an AI to stay warm and present while disclosing it isn't human, providing crisis resources, and not panicking or going clinical — that's a delicate tone to hold. It went through many iterations. Accomplishments that I'm proud of A working prototype of something I believe can make a real difference for people in mental health treatment. In 24 hours, I built a full-stack application where a therapist can enter clinical context once, and from that moment forward — their client has a companion that actually knows them, 24 hours a day, between every session. What I learned I learned that the most important design decisions weren't technical — they were ethical. Who controls the AI's behavior? (The therapist, not the user.) What does the AI do when someone is in crisis? (Stay present, disclose, refer, and never abandon the conversation.) What does it not do? (Diagnose, advise, or replace professional care.) On the technical side, I learned how much a well-structured system prompt matters. The difference between a generic mental health chatbot and one that says "I noticed your stress scores spiked the last three days — that pattern matches what you described after exam week" comes entirely from what you feed the model before the first message is sent. I also learned that Redis is underrated as a primary data store for this kind of application — fast, flexible, and more than sufficient for structured clinical data at this scale.

### What's next

Real authentication — secure login for both clients and therapists, with role-based access HIPAA-compliant infrastructure — encrypted storage, Business Associate Agreements, audit logs 24/7 human crisis loop — real-time escalation pathway to a live counselor when the AI safety guardrail fires EHR/FHIR integration — connect to existing electronic health record systems so therapists don't have to re-enter context they already have Insurance and reimbursement pathways — digital therapeutics reimbursement is an emerging category; BridgeNote is positioned for it B2B model — per-therapist SaaS subscription, starting with group practices and university counseling centers — the exact settings where the treatment gap is widest

## README (from the GitHub repository)

# BridgeNote

**Mental health support, between sessions.**

BridgeNote is an AI-powered continuity-of-care platform that keeps therapy working in the 10,000 minutes between sessions. Clients do daily check-ins and chat with a personalized AI companion. Therapists see mood trends, journal sentiment, and AI-generated session summaries — before the next appointment begins.

---

## What it does

| Role | Experience |
|------|-----------|
| **Client** | Daily mood + slider check-in → optional journal → chat with AI companion anytime |
| **Therapist** | Dashboard with mood trends, slider charts, check-in history, and AI conversation summaries per client |

The AI companion is not generic — it knows each client's treatment goals, triggers, strengths, diagnoses, and last session summary because the therapist enters that context once. Every chatbot response is built on that foundation.

---

## Live Demo

> **Requirements:** Redis (local or Docker) + an [Anthropic API key](https://console.anthropic.com/). The seed script pre-loads all demo data — no manual setup beyond that.

### 1. Clone & configure

```bash
git clone https://github.com/crystallamar/BridgeNote.git
cd BridgeNote
```

Copy the example env file and add your key:

```bash
cp backend/.env.example backend/.env
# then open backend/.env and set ANTHROPIC_API_KEY=your_key_here
```

### 2. Start Redis

```bash
# Docker (easiest)
docker-compose up redis -d

# Or Homebrew on Mac
brew services start redis
```

### 3. Start the backend

```bash
cd backend
pip install -r requirements.txt
python -m textblob.download_corpora   # one-time setup
uvicorn main:app --reload
# → http://localhost:8000
```

### 4. Seed demo data

```bash
# From the backend/ directory
python seed_demo.py
```

This loads 5 demo clients with 7 days of check-ins, therapist context, and personalized check-in configs. The primary demo client is **Ethan Rhodes** (client-001) — a CS junior navigating academic burnout and imposter syndrome.

### 5. Start the frontend

```bash
cd ../frontend
npm install --legacy-peer-deps
npm start
# → http://localhost:3000
```

### 6. Walk the demo

**Therapist view** (loads by default):
- Select **Ethan Rhodes** from the client dropdown
- **Overview tab** — treatment goals, triggers, strengths, last session summary entered by therapist
- **Check-ins tab** — mood entries, stress/energy sliders, journal snippets, "What got in the way?" habit tags
- **Conversations tab** — AI-generated session summaries ready before the next appointment

**Client view** (toggle in nav):
- Complete a check-in: mood rating → stress/energy sliders → habit buttons → AI-generated journal prompt
- Chat with the AI companion — it already knows Ethan's full context

---

## Demo Clients

| ID | Name | Profile |
|----|------|---------|
| client-001 | **Ethan Rhodes** ⭐ | CS junior, academic burnout, imposter syndrome |
| client-002 | Jordan Reid | Depression, GRAPES framework |
| client-003 | Sam Tran | PTSD stabilization, grounding focus |
| client-004 | Maya Krishnamurthy | College anxiety/depression, biweekly sessions |
| client-005 | Alex Morales | GAD, workplace performance anxiety |

---

## Tech Stack

| Layer | Technology |
|-------|-----------|
| Frontend | React 18, React Router v6, Recharts |
| Backend | FastAPI (Python 3.12), Uvicorn |
| AI | Claude claude-sonnet-4-6 (Anthropic) — streaming SSE |
| Sentiment analysis | TextBlob |
| Storage | Redis (async via redis.asyncio) — no SQL |
| Containerization | Docker Compose |

---

## Safety Protocol

The AI companion has a hardcoded safety guardrail that activates on any language related to suicidal ideation, self-harm, or intent to harm others — whether direct, hypothetical, or through metaphor:

1. Stays present and warm — never ends the conversation
2. Explicitly discloses it is an AI, not a human
3. Provides crisis resources immediately: **988 Lifeline** and **Crisis Text Line (HOME to 741741)**
4. Offers grounding (5-4-3-2-1 senses, box breathing)
5. Stays in conversation and asks grounding follow-up questions

This is an absolute constraint in the system prompt — it cannot be overridden by user messages.

---

## API Reference

```
POST /checkin/                    Submit a check-in (runs TextBlob sentiment)
GET  /checkin/recent/{id}         Fetch last N check-ins for a client
POST /checkin/journal-prompt      Generate AI journaling prompt from mood + context
POST /chat/start                  Create a new conversation
POST /chat/message                Streaming SSE chat response
GET  /chat/history/{conv_id}      Full conversation history
GET  /chat/summary/{conv_id}      AI-generated therapist summary
POST /therapist/context           Create or update therapist client context
GET  /therapist/context/{id}      Fetch therapist context
GET  /therapist/dashboard/{id}    Full dashboard: mood trend + check-ins + summaries
```

---

## Project Structure

```
BridgeNote/
├── backend/
│   ├── main.py                     FastAPI app entry point + CORS
│   ├── seed_demo.py                Seeds 5 demo clients into Redis
│   ├── services/
│   │   ├── claude_client.py        System prompt builder, streaming, safety protocol
│   │   └── redis_client.py         All Redis reads/writes
│   └── routers/
│       ├── chat.py                 Streaming SSE chat endpoint
│       ├── checkin.py              Check-in submission + journal prompt
│       └── therapist.py            Dashboard aggregation + context CRUD
├── frontend/
│   └── src/
│       ├── components/
│       │   ├── Chat/               Streaming chat UI
│       │   ├── CheckIn/            3-step check-in form
│       │   └── TherapistDashboard/ Recharts mood trends, summaries
│       └── hooks/
│           └── useChat.js          SSE streaming hook
└── docker-compose.yml
```

---

## Notes for Reviewers

- **No auth system** — client/therapist IDs are hardcoded (`client-001` / `therapist-001`) for hackathon scope
- **Redis is not persisted** between restarts unless configured — run `seed_demo.py` again if data disappears
- `npm install --legacy-peer-deps` is required due to an ajv@8 peer dependency conflict with react-scripts 5
- The `/chat/message` endpoint returns `text/event-stream`; the frontend proxy in `package.json` forwards to `localhost:8000`

---

*Built in 48 hours. Mental health tooling deserves better infrastructure.*


## Detected evidence (automated analysis)

Indexed codebase: 30 recognized source files, 156 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Docker (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (36 of 36)

```
.claude/launch.json
.gitignore
backend/.env.example
backend/CLAUDE.md
backend/Dockerfile
backend/main.py
backend/models/__init__.py
backend/models/schemas.py
backend/requirements.txt
backend/routers/__init__.py
backend/routers/chat.py
backend/routers/checkin.py
backend/routers/therapist.py
backend/seed_demo.py
backend/services/claude_client.py
backend/services/redis_client.py
backend/services/sentiment.py
CLAUDE.md
docker-compose.yml
frontend/CLAUDE.md
frontend/package.json
frontend/public/index.html
frontend/src/App.css
frontend/src/App.jsx
frontend/src/components/Chat/ChatWindow.css
frontend/src/components/Chat/ChatWindow.jsx
frontend/src/components/CheckIn/CheckInForm.css
frontend/src/components/CheckIn/CheckInForm.jsx
frontend/src/components/DemoGuide.css
frontend/src/components/DemoGuide.jsx
frontend/src/components/TherapistDashboard/Dashboard.css
frontend/src/components/TherapistDashboard/Dashboard.jsx
frontend/src/hooks/useChat.js
frontend/src/index.js
frontend/src/services/api.js
README.md
```

### Dependencies

- backend/requirements.txt: anthropic@==0.36.0, fastapi@==0.115.0, httpx@==0.27.2, pydantic@==2.9.2, python-dotenv@==1.0.1, python-multipart@==0.0.12, redis@==5.1.0, textblob@==0.18.0, uvicorn[standard]@==0.30.6
- frontend/package.json: ajv@^8.20.0, react@^18.3.1, react-dom@^18.3.1, react-router-dom@^6.26.2, react-scripts@5.0.1, recharts@^2.12.7

### Recent commits (newest first)

- Add full README with live demo setup instructions
- Tighten chatbot response length for better UX
- Smart slider defaults: higher_better starts at 7 (green), lower_better at 4 (yellow)
- Add 'What got in the way?' button group to Ethan's check-in
- Add Ethan Rhodes to client dropdown in navbar; update all display names
- Add rate limiting to chat endpoint (30 msg/min per client)
- Update Ethan to undergrad + diversify client names
- Slider value colors, notification dots, lazy summaries, Ethan R. client
- Add safety guardrail, per-slider charts, and updated demo guide
- Token guard, config sync, proper seed dates, API credit fix
- Fix route ordering, dashboard 500, client list, chat starters
- Round 3: date picker, prompt regen, dashboard data fix, Maya tag, chat offline
- Round 2: check-in date/regen prompt, chat clarity, therapist config, Maya
- Redesign check-in, add therapist client list, 3 demo clients
- Initial BridgeNote scaffold
- Initial commit

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

### CLAUDE.md

```markdown
# BridgeNote — Claude Code Context

## What this project is
A therapeutic continuity web app. Clients do daily mood check-ins and chat with an AI companion between therapy sessions. Therapists see a dashboard of mood trends, journal sentiment, and chatbot summaries.

## Stack
- **Frontend**: React 18, React Router v6, Recharts — lives in `frontend/`
- **Backend**: FastAPI (Python 3.12), Uvicorn — lives in `backend/`
- **AI**: Claude API (`claude-sonnet-4-6`) via `anthropic` SDK — streaming SSE responses
- **Storage**: Redis (async via `redis.asyncio`) — all persistence, no SQL DB
- **Containerization**: `docker-compose.yml` at root runs Redis + backend

## Running the project locally
```bash
# 1. Redis (required)
docker-compose up redis -d        # or: brew services start redis

# 2. Backend
cd backend
cp .env.example .env              # fill in ANTHROPIC_API_KEY
pip install -r requirements.txt
python -m textblob.download_corpora  # one-time
uvicorn main:app --reload         # http://localhost:8000

# 3. Seed demo data (optional but useful)
python seed_demo.py

# 4. Frontend
cd ../frontend
npm install --legacy-peer-deps    # ajv@^8 is required alongside react-scripts 5
npm start                         # http://localhost:3000
```

## Key files to know
| File | Purpose |
|------|---------|
| `backend/services/claude_client.py` | **Core chatbot logic** — builds dynamic system prompt from Redis data, streams responses |
| `backend/services/redis_client.py` | All Redis reads/writes — check-ins, therapist context, chat history |
| `backend/routers/chat.py` | Streaming SSE endpoint (`POST /chat/message`) |
| `backend/routers/checkin.py` | Daily check-in + AI journal prompt generation |
| `backend/routers/therapist.py` | Therapist context CRUD + dashboard aggregation |
| `frontend/src/hooks/useChat.js` | SSE streaming hook — reads `text/event-stream` chunks |
| `frontend/src/components/Chat/ChatWindow.jsx` | Chat UI |
| `frontend/src/components/CheckIn/CheckInForm.jsx` | 3-step mood check-in form |
| `frontend/src/components/TherapistDashboard/Dashboard.jsx` | Recharts mood trends + summaries |

## Redis data model
```
checkin:{client_id}:{checkin_id}     → JSON check-in record
checkins:{client_id}                 → sorted set (score = timestamp)
therapist_context:{client_id}        → JSON therapist-provided context
chat:{conversation_id}               → list of JSON messages (30-day TTL)
conversations:{client_id}            → set of conversation IDs
conv_client:{conversation_id}        → client_id string
```

## Demo IDs (hardcoded for hackathon)
- Client: `client-001`
- Therapist: `therapist-001`
These are set in `frontend/src/App.jsx` — change them to add multi-user support.

## API endpoints
```
POST /checkin/              Create check-in (runs sentiment analysis)
GET  /checkin/recent/{id}   Last N check-ins for client
POST /checkin/journal-prompt  AI-generated journaling prompt
POST /chat/start            Create new conversation
POST /chat/message 
[truncated — 1356 more characters]
```

### frontend/CLAUDE.md

```markdown
# Frontend — Claude Code Context

## Run
```bash
cd frontend
npm start   # port 3000, proxies /api calls to localhost:8000
```

## Install note
Must use `--legacy-peer-deps` due to ajv version conflict with react-scripts 5:
```bash
npm install --legacy-peer-deps
```

## Routing (React Router v6)
- `/` — CheckInForm (daily mood + journal)
- `/chat` — ChatWindow (streaming AI chat)
- `/therapist` — TherapistDashboard (mood trends, summaries)

## State management
No Redux. Local `useState` + the `useChat` custom hook for the chat stream.

## Streaming chat hook: src/hooks/useChat.js
- Calls `POST /chat/message` and reads `text/event-stream` via `response.body.getReader()`
- Accumulates partial tokens into the last message in state
- Handles `conv_id`, `text`, and `done` SSE event types
- Exposes: `{ messages, sendMessage, isStreaming, conversationId, reset }`

## API calls: src/services/api.js
All backend calls go through `api.*` helpers. The `proxy` field in `package.json` routes to `http://localhost:8000`.

## Adding a new view
1. Create component in `src/components/<Name>/`
2. Add `<Route>` in `src/App.jsx`
3. Add nav link in the `NavBar` component in `App.jsx`

## CSS approach
Plain CSS modules co-located with each component (e.g. `ChatWindow.css`). No Tailwind, no CSS-in-JS.
Color palette: primary `#6c63ff`, success `#2ecc71`, danger `#e74c3c`, bg `#f7f7fb`.

```

### docker-compose.yml

```yaml
version: "3.9"

services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    env_file: ./backend/.env
    depends_on:
      - redis
    volumes:
      - ./backend:/app
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

volumes:
  redis_data:

```

### backend/requirements.txt

```
fastapi==0.115.0
uvicorn[standard]==0.30.6
anthropic==0.36.0
redis==5.1.0
python-dotenv==1.0.1
pydantic==2.9.2
httpx==0.27.2
python-multipart==0.0.12
textblob==0.18.0

```

### backend/Dockerfile

```
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN python -m textblob.download_corpora
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

```

### frontend/package.json

```
{
  "name": "bridgenote",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "ajv": "^8.20.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.26.2",
    "react-scripts": "5.0.1",
    "recharts": "^2.12.7"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "proxy": "http://localhost:8000"
}

```

### backend/main.py

```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
import os

load_dotenv()

from routers import chat, checkin, therapist

app = FastAPI(title="BridgeNote API", version="0.1.0")

origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(chat.router)
app.include_router(checkin.router)
app.include_router(therapist.router)


@app.get("/health")
async def health():
    return {"status": "ok"}


@app.on_event("startup")
async def startup():
    from services.redis_client import get_redis
    r = await get_redis()
    await r.ping()
    print("Redis connected.")

```

### frontend/src/index.js

```javascript
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<React.StrictMode><App /></React.StrictMode>);

```

### frontend/src/App.jsx

```javascript
import { useState } from "react";
import { BrowserRouter, Routes, Route, Link, useLocation } from "react-router-dom";
import ChatWindow from "./components/Chat/ChatWindow";
import CheckInForm from "./components/CheckIn/CheckInForm";
import TherapistDashboard from "./components/TherapistDashboard/Dashboard";
import DemoGuide from "./components/DemoGuide";
import "./App.css";

// Demo clients matching seed_demo.py
const DEMO_CLIENTS = [
  { id: "client-001", name: "Alex Morales",       initials: "AM" },
  { id: "client-002", name: "Jordan Reid",        initials: "JR" },
  { id: "client-003", name: "Sam Tran",           initials: "ST" },
  { id: "client-004", name: "Maya Krishnamurthy", initials: "MK" },
  { id: "client-005", name: "Ethan Rhodes",       initials: "ER" },
];

function NavBar({ clientId, setClientId, onShowGuide }) {
  const { pathname } = useLocation();
  const isTherapist = pathname === "/therapist";
  return (
    <nav className="navbar">
      <div className="nav-brand">
        <span className="nav-logo">B</span>
        <span className="nav-name">BridgeNote</span>
      </div>
      <div className="nav-links">
        <Link to="/"          className={pathname === "/"          ? "active" : ""}>Check-in</Link>
        <Link to="/chat"      className={pathname === "/chat"      ? "active" : ""}>Chat</Link>
        <Link to="/therapist" className={pathname === "/therapist" ? "active" : ""}>Therapist View</Link>
      </div>
      {!isTherapist && (
        <div className="nav-client-select">
          <span className="nav-client-label">Client:</span>
          <select value={clientId} onChange={e => setClientId(e.target.value)}>
            {DEMO_CLIENTS.map(c => (
              <option key={c.id} value={c.id}>{c.name}</option>
            ))}
          </select>
        </div>
      )}
      <button className="nav-guide-btn" onClick={onShowGuide}>Demo Guide</button>
    </nav>
  );
}

export default function App() {
  const [clientId, setClientId] = useState("client-001");
  const [checkinKey, setCheckinKey] = useState(0);
  const [showGuide, setShowGuide] = useState(false);

  return (
    <BrowserRouter>
      <div className="app">
        <NavBar clientId={clientId} setClientId={setClientId} onShowGuide={() => setShowGuide(true)} />
        {showGuide && <DemoGuide onClose={() => setShowGuide(false)} />}
        <main className="app-main">
          <Routes>
            <Route
              path="/"
              element={
                <div className="page-center">
                  <CheckInForm
                    key={`${clientId}-${checkinKey}`}
                    clientId={clientId}
                    onComplete={() => setTimeout(() => setCheckinKey(k => k + 1), 3000)}
                  />
                </div>
              }
            />
            <Route
              path="/chat"
              element={
                <div className="page-chat">
                  <ChatWindow key={clientId} clientId={clientId} />
                </div>
              }
            />
            <Route path="/therapist" element={<TherapistDashboard />} />
          </Routes>
        </main>
      </div>
    </BrowserRouter>
  );
}

```

### frontend/public/index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>BridgeNote</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

```

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