# Project export: SleepSense

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 2026
- Tagline: We help you clear the hurdles that don't let you sleep comfortably, be it bruxism or stress. We help you sleep better through our hardware-based end-to-end design that provides you right support.
- Devpost: https://devpost.com/software/sleepsense-h52ma3
- GitHub: https://github.com/raj-chinagundi/treehacks-26
- Video: https://www.youtube.com/embed/acdo23eFIlc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — raj-chinagundi (15 commits), Aabhas Senapati (2 commits), Andrea (1 commits)

## Devpost submission (written by the team)

### Inspiration

Tuna, one of our Team members, is someone who suffers from chronic bruxism, a condition of continuous teeth clenching during the day and night, that causes extreme jaw pain, and sleep inconveniences. This firsthand experience of a real problem formed the basis of our project SleepSense, to solve this broader pain point of lack of good sleep, which is especially common amongst people from all groups these days. We found the sentiment of lack of good sleep was repeatedly echoed throughout the hackathon, be it amongst the engineers, or the founder and the VCs, all of them emphasized the less sleep all these people get. So we were driven by this mission to build a project that caters to these amazing people, and make their every hour of sleep count, by helping them have a restful sleep.

### What it does

Our project is an end-to-end solution that uses real-time sensing on patients to obtain their vital health parameters, such as cardiac and breathing, along with specific health data, like muscular activity, acquired using our custom-developed device that clips easily onto people's night masks and helps them improve their sleep. The device is not only able to acquire high-frequency data and transmit it securely to the cloud, but it is also of a very small form factor, which is ergonomically convenient for a user to put on during their sleep and monitor any form of abnormalities that disrupt their sleep. This multimodal data is then used by our model, which uses existing clinical data to accurately diagnose the root cause of their sleep disruptions, like bruxism, chronic stress, genetic factors like hormone imbalance, etc. Based on the preliminary diagnosis, our agentic platform can then generate reports about their sleep patterns, acute and chronic conditions, and then recommend specific suggestions about the nearest healthcare specialists relating to the problem, and also provide them with detailed reports of the patient's health data to consult and advise them proper plan of action.

### How we built it

Hardware: We designed a compact clip-on module built around an ESP32 microcontroller and an EMG muscle sensor that attaches to a standard sleep mask. The ESP32 reads raw 12-bit ADC values from the EMG sensor at high frequency, capturing jaw muscle activity in real time. For heart rate, we use an off-the-shelf wearable monitor that communicates over Wi-Fi. The ESP32 handles data capture and streams readings wirelessly to the cloud. We 3D-printed a custom enclosure (designed in CAD) to keep the electronics small and comfortable enough to wear during sleep. We also initially faced hiccups in sourcing the hardware and sensors we needed. While we were limited by our access to hardware, we tried to make the best use of what we had available to design this. Software: Data Pipeline: The wearable POSTs heart rate to a Flask server, while the ESP32 writes EMG data to Google Sheets (easiest way to bridge the hardware to our backend). Flask polls both sources, combines them at 10 Hz, and streams the result to the frontend over Server-Sent Events. Dashboard: A Next.js 14 app connects to that SSE stream and renders live Recharts visualizations, heart rate, jaw activity level, and raw EMG, updating at 5 Hz. NextAuth handles login with Google OAuth or a zero-config demo mode. Report Engine: reportLogic.ts classifies jaw activity from EMG thresholds, detects clenching events, and checks whether each one was preceded by a heart-rate spike. If yes → arousal-linked (stress response). If no → isolated (habitual bruxism). These feed into a sleep quality score. AI Chatbot: GPT-4o gets the full sensor dump and event log as context, so it can reason over the actual session data. We gave it two function-calling tools: search_clinics (Google Places API) and confirm_booking, so users can go from data analysis to booking a specialist without leaving the chat.

### Challenges we ran into

Hardware: Getting reliable EMG readings from a sensor mounted on a sleep mask was tricky; small shifts in placement caused big swings in signal quality. We also ran into power and heat issues with the ESP32 running continuous high-frequency ADC reads over Wi-Fi, and had to tune the sampling rate to balance data quality with battery life, latency, and stability. Software: Syncing two async data sources: Heart rate arrives via HTTP POST and EMG comes from polling Google Sheets, two completely different timing models. Getting them aligned into a single 10 Hz stream without drift or stale readings took a lot of trial and error with Flask's threading. SSE connection drops: The browser's EventSource would silently disconnect after a few minutes of inactivity or on network hiccups. We had to add reconnection logic and buffer management on the frontend to avoid gaps in the chart data.

### Accomplishments we're proud of

We had a lot of fun in the process of making this, and we are really proud of all the friends we made along the way. We were also really happy that we were able to consult real patients through our interactions with other hackers, mentors, judges, and involved parties, like dentists, for understanding the needs of the market space, and we are shocked to realize that this was a far more common issue than we initially thought it to be. Finally, we built something that is a genuinely hard engineering problem, to acquire critical data, and then leverage agentic AI tools that can use existing and new clinical data, and improve people's sleep, and that's what makes us really proud and happy.

### What we learned

Working across hardware and software taught us how messy real-time sensor data really is - what looks clean on a breadboard behaves very differently when someone's actually wearing it. We learned how to stitch together multiple async data sources into a reliable streaming pipeline, and how much SSE simplifies things when you only need one-way data flow. On the AI side, function calling turned out to be the unlock - it's what took our chatbot from "here's some generic advice" to actually finding clinics and booking appointments. Talking to dentists and fellow hackers also opened our eyes to how widespread and underserved sleep disruption problems really are.

### What's next

One of the key improvements that we would like to incorporate into SleepSense is adding real-time biofeedback to the users, which can help patients suffering from bruxism, which is one of the major sleep disruptors for many people today. We would like to incorporate other important health parameters using our device and leverage on exisitng unpublished clinical data through partnerships to help both doctors, healthcare specialists, and patients. We would like to possibly explore how this project could be taken beyond the hackathon to build a genuinely impactful product.

## README (from the GitHub repository)

# SleepSense

> Real-time bruxism and jaw-clenching monitoring dashboard - built at TreeHacks 2026.

SleepSense connects a smartwatch heart-rate sensor and an muscle sensor to a live analytics dashboard. Sensor data streams through a Flask data hub into a Next.js frontend that visualizes jaw activity, detects clenching events, classifies them by their cardiac–muscular relationship, and generates clinical-grade reports. An embedded GPT-4o chatbot can reason over the patient's data and book a specialist through Google Places.

[![Demo Video](https://img.shields.io/badge/Demo-YouTube-red?logo=youtube)](https://www.youtube.com/watch?v=acdo23eFIlc)

---

## Table of Contents

- [Project Structure](#project-structure)
- [Tech Stack](#tech-stack)
- [Architecture Overview](#architecture-overview)
- [Setup](#setup)
- [Usage](#usage)
- [Demo Video](#demo-video)

---

## Project Structure

```
sleepsense/
├── app/                            # Next.js App Router
│   ├── page.tsx                    # Landing page — sign-in (Google OAuth or demo)
│   ├── layout.tsx                  # Root layout with dark theme + SessionProvider
│   ├── providers.tsx               # NextAuth SessionProvider wrapper
│   ├── globals.css                 # Tailwind base + chatbot widget styles
│   ├── dashboard/
│   │   └── page.tsx                # Auth-protected dashboard entry point
│   └── api/
│       ├── auth/[...nextauth]/
│       │   └── route.ts            # NextAuth handler (Google + mock credentials)
│       ├── sessions/
│       │   ├── route.ts            # GET list / POST create sessions
│       │   └── [id]/route.ts       # GET / PUT individual session
│       ├── reports/
│       │   └── route.ts            # GET by sessionId / POST generate report
│       ├── bookings/
│       │   └── route.ts            # POST create booking record
│       └── places/
│           └── route.ts            # Proxy to Google Places Text Search API
│
├── components/
│   ├── Dashboard.tsx               # Main 3-section layout + session state machine
│   ├── ChatBot.tsx                 # SleepSense AI chatbot — GPT-4o with function calling
│   ├── ReportBox.tsx               # Expandable bullet-point report card
│   ├── SignInButton.tsx            # Google OAuth + demo sign-in form
│   ├── StatusBadge.tsx             # Connection status indicator
│   └── charts/
│       ├── HeartRateChart.tsx      # BPM area chart (Recharts)
│       ├── JawActivityChart.tsx    # 3-level step chart (Relaxed / Talking / Clenching)
│       ├── EMGChart.tsx            # Raw EMG waveform line chart
│       ├── HRChart.tsx             # Simple HR line chart
│       └── MainChart.tsx           # Combined 3-signal overview chart
│
├── lib/
│   ├── auth.ts                     # NextAuth config (Google provider + mock)
│   ├── storage.ts                  # JSON file read/write for sessions, reports, bookings
│   ├── mockSensor.ts               # Seeded PRNG sensor data generator
│   ├── reportLogic.ts              # Clench detection, event classification, report scoring
│   └── bruxismAgent.ts             # SleepSense AI — GPT-4o agent with search_clinics + confirm_booking tools
│
├── types/
│   └── index.ts                    # TypeScript interfaces (SensorPoint, SessionRecord, etc.)
│
├── credentials/
│   └── service-account.json        # Google service account for Sheets API (EMG polling)
│
├── data/
│   └── db.json                     # Auto-created local JSON database
├── design/CAD_enclosure            # design files for mask
├── hardware/esp32_serial_blocking  # data capture and data stream over wifi through esp32
├── test.py                         # Flask data hub (SleepSense Data Hub) — unifies HR + EMG into 10 Hz SSE stream
├── requirements.txt                # Python dependencies (flask, gspread, google-auth)
├── package.json                    # Node dependencies and scripts
├── tailwind.config.ts              # Tailwind CSS configuration
├── tsconfig.json                   # TypeScript configuration
├── next.config.mjs                 # Next.js configuration
└── postcss.config.mjs              # PostCSS plugins (Tailwind + Autoprefixer)
```

---

## Tech Stack

| Layer | Technology |
|---|---|
| **Frontend** | Next.js 14 (App Router) + React 18 + TypeScript |
| **Styling** | Tailwind CSS |
| **Charts** | Recharts (AreaChart, LineChart, ReferenceArea) |
| **Auth** | NextAuth v4 — Google OAuth + zero-config mock credentials |
| **AI Chatbot** | SleepSense AI — GPT-4o via OpenAI API with function calling (search_clinics, confirm_booking) |
| **Clinic Search** | Google Places Text Search API |
| **Data Hub** | SleepSense Data Hub — Flask (Python), combines HR + EMG into a 10 Hz SSE stream |
| **EMG Sensor** | ESP32 → Google Sheets → Flask polls via `gspread` |
| **HR Sensor** | Wearable POSTs BPM to Flask `/data` endpoint |
| **Storage** | Local JSON file (`data/db.json`) via Node `fs` in API routes |

---

## Architecture Overview

<!-- use a relative path and HTML to control size -->
<img src="./sys-architecture.png" alt="System architecture" width="700" height="500">

**Data flow:**

1. **Heart rate** — A wearable device POSTs BPM readings to Flask at `/data`.
2. **EMG** — An ESP32 writes raw 12-bit ADC values to a Google Sheet. Flask polls the sheet every second via `gspread`.
3. **Flask combiner** — A background thread reads the latest HR + EMG at 10 Hz and pushes combined JSON events over SSE (`/stream`).
4. **Next.js dashboard** — Opens an `EventSource` to Flask, buffers incoming data points, and refreshes charts at 5 Hz.
5. **Report engine** (`reportLogic.ts`) — Classifies jaw activity into Relaxed / Talking / Clenching using ADC thresholds, detects bruxating events, correlates them with heart-rate arousal, and scores sleep quality.
6. **AI chatbot** (`bruxismAgent.ts`) — Sends the full sensor data dump + event log as GPT-4o system context. The model analyzes patterns, identifies root causes, and can call `search_clinics` (Google Places) and `confirm_booking` to schedule a specialist visit.

---

## Setup

### Prerequisites

- **Node.js** ≥ 18
- **Python** ≥ 3.9 (for the Flask data hub)
- **npm**

### 1. Install Node dependencies

```bash
cd sleepsense
npm install
```

### 2. Install Python dependencies

```bash
pip install -r requirements.txt
```

### 3. Configure environment variables

Create a `.env.local` file in the project root:

```env
# NextAuth
NEXTAUTH_SECRET=your-random-secret
NEXTAUTH_URL=http://localhost:3000

# Google OAuth (optional — demo sign-in works without it)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

# Google Places API (required for clinic search in chatbot)
GOOGLE_PLACES_API_KEY=...

# OpenAI (optional — can also be entered in the chatbot UI at runtime)
NEXT_PUBLIC_OPENAI_API_KEY=...
```

> **Note:** The demo sign-in mode works with no environment variables at all. Google OAuth, Places, and OpenAI keys are only needed for their respective features.

### 4. Start the Flask data hub

```bash
python test.py
```

This starts the SleepSense Data Hub on **port 5001**. It will:
- Accept heart-rate POSTs from the wearable at `/data`
- Poll Google Sheets for ESP32 EMG data
- Stream combined data at 10 Hz via SSE at `/stream`

### 5. Start the Next.js dev server

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000).

---

## Usage

### Sign In

- **Demo mode** — Click *"Use demo account"* on the landing page. No OAuth setup needed.
- **Google OAuth** — Configure `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` in `.env.local`, add `http://localhost:3000/api/auth/callback/google` as an authorized redirect URI in Google Cloud Console.

### Monitor a Session

1. Click **Connect Device** — the dashboard opens an SSE connection to the Flask data hub and starts buffering sensor data.
2. Live charts update at 5 Hz showing **Heart Rate** (BPM area chart) and **Jaw Activity** (3-level step chart: Relaxed → Talking → Clenching).
3. The **Live Analysis** panel displays 

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 30 recognized source files, 128 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- OpenAI (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (42 of 42)

```
.env.local.example
.gitignore
app/api/auth/[...nextauth]/route.ts
app/api/bookings/route.ts
app/api/places/route.ts
app/api/reports/route.ts
app/api/sessions/[id]/route.ts
app/api/sessions/route.ts
app/dashboard/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/providers.tsx
components/charts/EMGChart.tsx
components/charts/HeartRateChart.tsx
components/charts/HRChart.tsx
components/charts/JawActivityChart.tsx
components/charts/MainChart.tsx
components/ChatBot.tsx
components/Dashboard.tsx
components/ReportBox.tsx
components/SignInButton.tsx
components/StatusBadge.tsx
data/db.json
design/CAD_enclosure/TreeHacks_V1.stl
design/CAD_enclosure/TreeHacks_V2.stl
design/CAD_enclosure/TreeHacks_V3.stl
hardware/esp32_serial_blocking/esp32_serial_blocking.ino
lib/auth.ts
lib/bruxismAgent.ts
lib/mockSensor.ts
lib/reportLogic.ts
lib/storage.ts
next.config.mjs
package.json
postcss.config.mjs
README.md
requirements.txt
tailwind.config.ts
test.py
tsconfig.json
types/index.ts
```

### Dependencies

- package.json: @types/node@^20.14.0, @types/react@^18.3.3, @types/react-dom@^18.3.0, @types/uuid@^9.0.8, autoprefixer@^10.4.19, eslint@^8.57.0, eslint-config-next@14.2.5, next@14.2.5, next-auth@^4.24.7, postcss@^8.4.38, react@^18.3.1, react-dom@^18.3.1, recharts@^2.12.7, tailwindcss@^3.4.4, typescript@^5.4.5, uuid@^9.0.1
- requirements.txt: flask@>=2.3, google-auth@>=2.29, gspread@>=5.12

### Recent commits (newest first)

- Remove sensitive credentials from esp32_serial_blocking
- Update image size in architecture overview
- Update image size in architecture overview
- Update system architecture representation in README
- Add files via upload
- Update project description in README.md
- Clarify sensor types in project description
- Add design and hardware directories to README
- Merge pull request #2 from raj-chinagundi/rajv2
- Update jawsense application code
- Merge pull request #1 from raj-chinagundi/hardware-aabhas
- Add CAD files
- Use raw ADC values instead of voltage conversion for EMG classification
- Code for hardware of SleepSense
- fix: direct ADC-to-voltage conversion, real EMG from Google Sheets, remove mock data
- Refine analytics: Jaw Pressure Index + Nervous System Activation charts with temporal event classification (Arousal-Linked / Isolated / Arousal Only)
- Add Google Places clinic search + booking flow to chatbot
- Event-based clenching charts, non-stress label, GPT-4o bruxism analyst chatbot
- Live monitoring dashboard with real-time HR integration via Flask SSE
- Initial commit: JawSense app

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

### requirements.txt

```
flask>=2.3
gspread>=5.12
google-auth>=2.29

```

### package.json

```
{
  "name": "sleepsense",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "14.2.5",
    "next-auth": "^4.24.7",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "recharts": "^2.12.7",
    "uuid": "^9.0.1"
  },
  "devDependencies": {
    "@types/node": "^20.14.0",
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@types/uuid": "^9.0.8",
    "autoprefixer": "^10.4.19",
    "eslint": "^8.57.0",
    "eslint-config-next": "14.2.5",
    "postcss": "^8.4.38",
    "tailwindcss": "^3.4.4",
    "typescript": "^5.4.5"
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata } from 'next'
import './globals.css'
import { Providers } from './providers'

export const metadata: Metadata = {
  title: 'SleepSense – Sleep & Clenching Analytics',
  description: 'Monitor and analyze sleep bruxism and stress-related jaw clenching',
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className="bg-[#0a0e1a] text-slate-100 antialiased">
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

```

### types/index.ts

```typescript
export type SessionStatus = 'recording' | 'analyzing' | 'report_ready'

export interface SensorPoint {
  t: number     // ms from session start
  emg: number   // 0–3.0 arbitrary units
  hr: number    // bpm
  temp: number  // °C
}

export interface SessionRecord {
  id: string
  userId: string
  userName: string
  startTime: string       // ISO
  endTime?: string        // ISO
  duration?: number       // seconds
  status: SessionStatus
  dataPoints?: SensorPoint[]
}

export interface ReportRecord {
  id: string
  sessionId: string
  userId: string
  duration: number          // seconds
  clenchCount: number
  stressLikelihood: number  // 0–100 %
  sleepQualityScore: number // 0–100
  avgHR: number             // bpm
  hrVariability: number     // std-dev bpm
  peakEMG: number
  avgTemp: number           // °C
  tempDrift: number         // total °C change
  createdAt: string         // ISO
}

export interface BookingRecord {
  id: string
  userId: string
  reportId: string
  providerName: string
  providerType: 'dentist' | 'psychiatrist'
  appointmentTime: string
  address: string
  city: string
  status: 'confirmed'
  createdAt: string
}

export interface StorageDB {
  sessions: SessionRecord[]
  reports: ReportRecord[]
  bookings: BookingRecord[]
}

```

### app/page.tsx

```typescript
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { redirect } from 'next/navigation'
import SignInButton from '@/components/SignInButton'

export default async function LandingPage() {
  const session = await getServerSession(authOptions)
  if (session) redirect('/dashboard')

  return (
    <div className="min-h-screen bg-[#0a0e1a] flex items-center justify-center relative overflow-hidden">

      {/* Subtle gradient orbs */}
      <div className="absolute top-[-20%] left-[-10%] w-[500px] h-[500px] bg-cyan-500/10 rounded-full blur-[120px] pointer-events-none" />
      <div className="absolute bottom-[-20%] right-[-10%] w-[400px] h-[400px] bg-violet-500/10 rounded-full blur-[120px] pointer-events-none" />

      <div className="text-center space-y-8 max-w-sm mx-auto px-6 relative z-10">

        {/* Logo */}
        <div className="flex justify-center">
          <div className="w-20 h-20 rounded-2xl bg-gradient-to-br from-cyan-500 to-cyan-600 flex items-center justify-center shadow-lg shadow-cyan-500/20">
            <svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path d="M22 6C13.16 6 6 13.16 6 22s7.16 16 16 16 16-7.16 16-16S30.84 6 22 6z"
                fill="white" fillOpacity="0.15"/>
              <path d="M10 22C10 22 13 15 22 15s12 7 12 7-3 7-12 7-12-7-12-7z"
                fill="white"/>
              <circle cx="22" cy="22" r="3.5" fill="#0a0e1a"/>
              <path d="M6 22 Q10 13 22 13 Q34 13 38 22"
                stroke="white" strokeWidth="1.8" strokeLinecap="round" fill="none"/>
            </svg>
          </div>
        </div>

        {/* Title */}
        <div>
          <h1 className="text-3xl font-bold text-white tracking-tight">SleepSense</h1>
          <p className="mt-2 text-slate-400 text-sm">Sleep &amp; Clenching Analytics</p>
        </div>

        <SignInButton />

        <p className="text-xs text-slate-500">
          By signing in you agree to our terms of service and privacy policy.
        </p>
      </div>
    </div>
  )
}

```

### app/dashboard/page.tsx

```typescript
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { redirect } from 'next/navigation'
import Dashboard from '@/components/Dashboard'

export default async function DashboardPage() {
  const session = await getServerSession(authOptions)
  if (!session?.user) redirect('/')

  const user = {
    id: (session.user as { id?: string }).id ?? session.user.email!,
    name: session.user.name ?? 'User',
    email: session.user.email!,
    image: session.user.image ?? undefined,
  }

  return <Dashboard user={user} />
}

```

### app/api/bookings/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { createBooking } from '@/lib/storage'
import { BookingRecord } from '@/types'
import { v4 as uuid } from 'uuid'

export async function POST(req: NextRequest) {
  const session = await getServerSession(authOptions)
  const userId = (session?.user as { id?: string })?.id ?? session?.user?.email ?? null
  if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

  const body = await req.json() as {
    reportId: string
    providerName: string
    providerType: 'dentist' | 'psychiatrist'
    appointmentTime: string
    address: string
    city: string
  }

  const booking: BookingRecord = {
    id: uuid(),
    userId,
    reportId: body.reportId,
    providerName: body.providerName,
    providerType: body.providerType,
    appointmentTime: body.appointmentTime,
    address: body.address,
    city: body.city,
    status: 'confirmed',
    createdAt: new Date().toISOString(),
  }

  return NextResponse.json(createBooking(booking), { status: 201 })
}

```

### app/api/sessions/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { createSession, getSessions } from '@/lib/storage'
import { SessionRecord } from '@/types'
import { v4 as uuid } from 'uuid'

type NS = { user?: { id?: string; name?: string | null; email?: string | null } } | null

function getUserId(session: NS) {
  return (session?.user as { id?: string })?.id ?? session?.user?.email ?? null
}

export async function GET() {
  const session = await getServerSession(authOptions)
  const userId = getUserId(session)
  if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  return NextResponse.json(getSessions(userId))
}

export async function POST() {
  const session = await getServerSession(authOptions)
  const userId = getUserId(session)
  if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

  const record: SessionRecord = {
    id: uuid(),
    userId,
    userName: session!.user?.name ?? session!.user?.email ?? 'User',
    startTime: new Date().toISOString(),
    status: 'recording',
  }
  return NextResponse.json(createSession(record), { status: 201 })
}

```

### app/api/places/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url)
  const query = searchParams.get('query') || 'dentist'
  const location = searchParams.get('location') || ''

  const apiKey = process.env.GOOGLE_PLACES_API_KEY
  if (!apiKey) {
    return NextResponse.json(
      { error: 'Google Places API key not configured. Set GOOGLE_PLACES_API_KEY in .env.local' },
      { status: 500 }
    )
  }

  const searchQuery = `${query} near ${location}`
  const url = `https://maps.googleapis.com/maps/api/place/textsearch/json?query=${encodeURIComponent(searchQuery)}&key=${apiKey}`

  const res = await fetch(url)
  const data = await res.json()

  if (data.status !== 'OK' && data.status !== 'ZERO_RESULTS') {
    return NextResponse.json(
      { error: `Google Places API error: ${data.status}` },
      { status: 500 }
    )
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const places = (data.results || []).slice(0, 5).map((p: any) => ({
    name: p.name,
    address: p.formatted_address,
    rating: p.rating ?? null,
    totalRatings: p.user_ratings_total ?? 0,
    openNow: p.opening_hours?.open_now ?? null,
  }))

  return NextResponse.json({ places })
}

```

### app/api/reports/route.ts

```typescript
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { getReport, getReports, createReport, updateSession } from '@/lib/storage'
import { computeReport } from '@/lib/reportLogic'
import { SensorPoint } from '@/types'

type NS = { user?: { id?: string; name?: string | null; email?: string | null } } | null

function getUserId(session: NS) {
  return (session?.user as { id?: string })?.id ?? session?.user?.email ?? null
}

export async function GET(req: NextRequest) {
  const session = await getServerSession(authOptions)
  const userId = getUserId(session)
  if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

  const sessionId = new URL(req.url).searchParams.get('sessionId')
  if (sessionId) {
    const r = getReport(sessionId)
    return r ? NextResponse.json(r) : NextResponse.json({ error: 'Not found' }, { status: 404 })
  }
  return NextResponse.json(getReports(userId))
}

export async function POST(req: NextRequest) {
  const session = await getServerSession(authOptions)
  const userId = getUserId(session)
  if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

  const { sessionId, dataPoints, durationSeconds } = (await req.json()) as {
    sessionId: string
    dataPoints: SensorPoint[]
    durationSeconds: number
  }

  const report = computeReport(sessionId, userId, dataPoints, durationSeconds)
  const saved = createReport(report)

  updateSession(sessionId, {
    status: 'report_ready',
    endTime: new Date().toISOString(),
    duration: durationSeconds,
    dataPoints: dataPoints.slice(-1000), // store last 1000 pts
  })

  return NextResponse.json(saved, { status: 201 })
}

```

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