# Project export: Nos: AI Paramedic Copilot

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: Nos listens and watches during transport, catching, alerting, and reporting everything that get lost between the scene and the ER. Designed consciously to respect privacy.
- Devpost: https://devpost.com/software/nos-ambulance-assistant
- GitHub: https://github.com/JacobChan182/Berkeley-AI-Hackathon
- Demo: https://berkeley-ai-hackathon-production.up.railway.app/
- Video: https://www.youtube.com/embed/04NPwVVEkBY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 6 GitHub contributor(s) — Jacob Chan (28 commits), Ian Chao (13 commits), Claude Sonnet 4.6 (1M context) (6 commits), Michael Birmingham (6 commits), Cursor (4 commits), Michael Birmingham (1 commits)

## Devpost submission (written by the team)

### Inspiration

When I was FaceTiming my friend who's working as an E.M.T., he mentioned how chaotic a paramedic's job can get. When he told me this, the first thing that came to mind was not 'AI assistant in a hackathon,' but when brainstorming for this event, I quickly realized how AI could be helpful in such medical situations where every second is critical and every piece of information needs to be perfect. This is why we created Nos.

### What it does

Nos listens to the paramedic and to the patient when they can speak. It builds a live, structured picture of the case as it happens. It tracks what's been said and done, automatically identifies medications / other objects from vials or labels held up to the camera, and runs a safety check in the background that catches what would otherwise slip through: a symptom mentioned once and never followed up on, a medication about to be given that interacts with something the patient's already on. It works even when the patient is unconscious or can't speak for themselves, since it never depends on them as the only source of truth. The moment the doors open at the ER, it generates a structured handoff report from everything captured along the way.

### How we built it

We split the system into three coordinated layers connected through an event bus, so each piece could be built in parallel without blocking the others. A voice layer transcribes the paramedic-patient conversation in real time. An agent layer extracts structured medical facts, looks for relevant information on the internet, builds the timeline, and runs continuous safety checks, including flagging unfollowed-up symptoms and known medication interactions. A vision layer runs Claude's vision API against a live camera feed, using motion-and-stillness detection to capture frames automatically when something, e.g. a vial or a medical bracelet, is held steady in view, without requiring the paramedic to do anything by hand. Identified items are cross-referenced against the patient's known medications before anything is administered. Everything converges into a single handoff report at the moment of arrival. We treated privacy as a principal design constraint: raw audio and video are processed in memory and never written to disk or persistent storage. The only data retained is the structured visit record itself (timeline, medications, flags), which is what the handoff report is built from, kept to the minimum necessary for continuity of care. A database of past handoffs is kept for a first responder or medical care provider to access later, with options to delete any handoff and automatically deleting after two weeks. While we've taken steps to address privacy, we believe persistent visit data would possibly need encryption at rest and role-based access controls before real deployment in order to respect a patient's privacy as much as possible.

### Challenges we ran into

Tuning the vision pipeline's capture trigger so it fires reliably on a held-up vial without flooding the system with redundant calls on every frame. We allow the user to capture whenever they think is relevant. Keeping the safety agent anchored to real, verifiable gaps (a stated symptom with no follow-up, a known drug interaction) instead of drifting into vague or unfounded clinical judgment. Previously, Nos would alert the user if a patient stated that they were "old," which is something to keep note of but not necessarily a "concern" immediately. Figuring out how to account for unconscious or unresponsive patients. Thankfully, we designed a system where the transcription can recognize different speakers, but this doesn't affect the information that gets passed to the handoff. Building privacy into the architecture itself. We had difficulty deciding what never gets persisted and what gets shown. This is probably the challenge that we were least expecting to deal with, but arguably it's the most interesting problem for us.

### Accomplishments we're proud of

A multimodal pipeline that includes voice, vision, and structured reasoning. These all feed into one coherent report rather than three disconnected demos. We designed a safety agent that catches real, demonstrable gaps live, anchored to actual transcript and vision content rather than vague heuristics. We've created a privacy policy we can actually defend: by design, no persistent raw audio or video and a push for confidentiality when handling patient data in databases or third party apps.

### What we learned

Specialized agents beat one big prompt. Splitting extraction, timeline-building, safety-checking, and handoff generation into separate agents made each one easier to reason about and debug, even though it meant more coordination overhead through the event bus. Multimodal inputs need to actually align with each other. The vision and transcript pipelines only became useful once we cross-referenced them. A vial identified by the camera matters because it's checked against what was said, not as a standalone fact. Privacy is a policy that has to be decided before building. Choices like never writing raw audio/video to disk only work if they're baked into the pipeline from the start. Realizing this after the architecture is set is much harder than designing for it from the first hour. The line between "assisting a first responder" and "replacing their judgment" is surprisingly thin. We had to actively rework early ideas (like flagging based on a patient's age alone) that sounded helpful but were really the system making a clinical call it had no real basis for.

### What's next

for Nos: Ambulance Assistant Our primary goal is to move more of the pipeline to fully local, on-device models so nothing leaves the vehicle at all. We experimented with this during the hackathon by hosting some of our agents locally and see it as the clear production direction, particularly for the vision component, which currently uses Claude's hosted VLM API for accuracy. For any remaining third-party model usage, production deployment would require formal data agreements, including but not limited to a signed BAA and a no-training guarantee, which is a real legal commitment we haven't pursued at hackathon scale, but is non-negotiable before Nos could be used with real patient data. We'd also want tighter integration with real EHR systems, and more rigorous validation of the safety agent's flagging accuracy against real EMS protocols rather than our own judgment and what we thought was accurate.

## README (from the GitHub repository)

# ER Copilot

Real-time AI clinical operations assistant — Berkeley AI Hackathon.

## Quick start

### Option 1 — Docker Compose (recommended)

Start all services with a single command:

```bash
cp .env.example .env               # add API keys (all optional — heuristics work offline)
docker compose up --build
```

This launches:
| Service | Port |
|---------|------|
| Next.js frontend | 3000 |
| FastAPI backend | 8000 |
| Redis | 6379 |

Open [http://localhost:3000](http://localhost:3000) → click **Demo** → watch all agents work → **Generate Handoff Report**.

### Option 2 — Manual setup

Two terminals required — Python backend + Next.js frontend.

**Terminal 1 — Python backend:**
```bash
cd backend
pip install -r requirements.txt
cp ../.env.example ../.env          # add API keys (all optional — heuristics work offline)
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
```

**Terminal 2 — Next.js frontend:**
```bash
npm install
npm run dev
```

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  Browser  (Next.js frontend — TypeScript/React, port 3000)  │
└──────────────────────┬──────────────────────────────────────┘
                       │ /api/* (proxied via next.config.ts)
┌──────────────────────▼──────────────────────────────────────┐
│  Python FastAPI backend  (port 8000)                        │
│                                                             │
│  ┌─────────────┐    ┌──────────────┐    ┌───────────────┐  │
│  │ Event Bus   │    │  6 Agents    │    │  SSE Hub      │  │
│  │ (Redis or   │───▶│  extraction  │───▶│  /api/events  │  │
│  │  in-memory) │    │  timeline    │    └───────────────┘  │
│  └─────────────┘    │  safety      │                       │
│                     │  docs        │    ┌───────────────┐  │
│  ┌─────────────┐    │  research    │    │  State Store  │  │
│  │  Claude     │───▶│  handoff     │───▶│  (Redis or    │  │
│  │  (optional) │    └──────────────┘    │   in-memory)  │  │
│  └─────────────┘                        └───────────────┘  │
└─────────────────────────────────────────────────────────────┘
```

- **Backend:** Python 3.11+ · FastAPI · asyncio
- **Event bus:** Redis pub/sub (in-memory fallback)
- **Real-time UI:** Server-Sent Events (`/api/events`)
- **6 agents:** extraction, timeline, safety, documentation, research, handoff
- **Demo Mode:** replays `scripts/demo-scenario.json`
- **Live Mode:** browser mic via Web Speech API → `/api/transcript`

## Project structure

```
backend/                  # Python FastAPI backend (replaces lib/ + app/api/)
  main.py                 # FastAPI app entry point
  events.py               # Shared event dataclasses
  bus.py                  # Event bus (Redis or in-memory)
  claude.py               # Anthropic Claude wrapper
  debounce.py             # Async debounce utility
  redis_layer/            # Redis client, keys, state persistence
  sse/                    # SSE fan-out hub
  agents/                 # 6 async agents
  prompts/                # Claude prompts + heuristic fallbacks
  demo/                   # Demo scenario replay
  routes/                 # FastAPI route handlers
  Dockerfile              # Backend container image

app/                      # Next.js frontend (UI only)
  page.tsx                # Main dashboard
  layout.tsx

components/               # React UI panels
hooks/                    # useEncounterEvents (SSE client)
scripts/
  demo-scenario.json      # Demo encounter dialogue script

Dockerfile                # Frontend container image
docker-compose.yml        # Orchestrate frontend, backend & Redis
```

## Scripts

| Command | Description |
|---------|-------------|
| `docker compose up --build` | Start all services (frontend dev, backend, Redis) |
| `docker compose -f docker-compose.prod.yml up --build` | Production images + external Redis from `.env` |
| `docker compose down` | Stop all services |
| `npm run dev` | Start Next.js frontend (port 3000) |
| `npm run typecheck` | TypeScript check |
| `uvicorn main:app --reload` | Start Python backend (run from `backend/`) |

## API keys

All optional for demo. Without keys, heuristic fallbacks produce a working demo.

| Key | Enables |
|-----|---------|
| `ANTHROPIC_API_KEY` | Claude-powered extraction, SOAP, handoff |
| `DEEPGRAM_API_KEY` | Deepgram STT (Live mode uses Web Speech API without it) |
| `REDIS_URL` | Persistent state + multi-instance pub/sub |
| `BROWSERBASE_API_KEY` | Live web research (mock citations without it) |

## Docs

- [Deployment guide](./docs/DEPLOY.md) — Railway, Docker production, env vars
- [Backend README](./backend/README.md)
- [Teammate 1 — Platform & Pipeline](./docs/TEAMMATE_1.md)
- [Teammate 2 — Agents, UI & Demo](./docs/TEAMMATE_2.md)
- [Product plan](./ER_Copilot_Hackathon_Plan.md)


## Detected evidence (automated analysis)

Indexed codebase: 84 recognized source files, 451 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (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
- Docker (technology) — claimed on Devpost, not found in the code
- Redis (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
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (98 of 98)

```
.agents/skills/railway-config/SKILL.md
.claude/settings.json
.claude/skills/frontend-design/SKILL.md
.dockerignore
.env.example
.gitignore
.railway-config-pull-66436/railway.ts
.railway/railway.ts
.railway/README.md
app/api/[...path]/route.ts
app/dashboard/page.tsx
app/globals.css
app/layout.tsx
app/logs/[encounterId]/page.tsx
app/logs/page.tsx
app/page.tsx
backend/agents/__init__.py
backend/agents/audio_events.py
backend/agents/documentation.py
backend/agents/extraction.py
backend/agents/handoff.py
backend/agents/research.py
backend/agents/runtime.py
backend/agents/safety.py
backend/agents/timeline.py
backend/agents/vision.py
backend/browserbase.py
backend/bus.py
backend/claude.py
backend/config.py
backend/debounce.py
backend/Dockerfile
backend/events.py
backend/llm_parse.py
backend/main.py
backend/nim.py
backend/prompts/__init__.py
backend/prompts/documentation.py
backend/prompts/drug_interactions.py
backend/prompts/extraction.py
backend/prompts/handoff.py
backend/prompts/research.py
backend/prompts/safety.py
backend/prompts/timeline.py
backend/prompts/vision.py
backend/README.md
backend/redis_layer/__init__.py
backend/redis_layer/client.py
backend/redis_layer/keys.py
backend/redis_layer/state.py
backend/requirements.txt
backend/routes/__init__.py
backend/routes/vision.py
backend/sse/__init__.py
backend/sse/hub.py
CLAUDE.md
components/Dashboard.tsx
components/DisclaimerBanner.tsx
components/HandoffModal.tsx
components/InsightsPanel.tsx
components/LiveMic.tsx
components/NosMark.tsx
components/SafetyAlertBanner.tsx
components/SessionLogList.tsx
components/SoapPanel.tsx
components/TelemetryBar.tsx
components/TimelinePanel.tsx
components/TranscriptPanel.tsx
components/VisionCapture.tsx
docker-compose.prod.yml
docker-compose.yml
Dockerfile
Dockerfile.dev
docs/ARCHITECTURE.md
docs/DEMO_RUNBOOK.md
docs/DEPLOY.md
docs/DEV_A.md
docs/DEV_B.md
docs/DEV_C.md
ER_Copilot_Hackathon_Plan.md
fixtures/full-encounter-state.json
hooks/useEncounterEvents.ts
instrumentation.ts
next.config.ts
package.json
PARALLEL_BUILD.md
postcss.config.mjs
Project_Context.md
railway.backend.toml
railway.frontend.toml
README.md
scripts/DEMO_SCRIPT_B.md
scripts/DEMO_SCRIPT.md
scripts/railway-deploy.sh
tailwind.config.ts
tsconfig.json
types/events.ts
types/session.ts
```

### Dependencies

- backend/requirements.txt: anthropic@>=0.40.0, fastapi@>=0.115.0, httpx@>=0.27.0, playwright@>=1.40.0, python-dotenv@>=1.0.0, redis[asyncio]@>=5.2.0, uvicorn[standard]@>=0.30.0
- package.json: @anthropic-ai/sdk@^0.39.0, @deepgram/sdk@^3.9.0, @types/node@^22.10.0, @types/react@^19.0.0, @types/react-dom@^19.0.0, @types/ws@^8.5.13, autoprefixer@^10.4.20, eslint@^9.16.0, eslint-config-next@^15.1.0, ioredis@^5.4.2, next@^15.1.0, postcss@^8.4.49, react@^19.0.0, react-dom@^19.0.0, tailwindcss@^3.4.16, tsx@^4.19.2, typescript@^5.7.2, ws@^8.18.0

### Recent commits (newest first)

- fixed gemini-claude model mix up
- updated docs for clarity
- Updated frontpage
- deploy changes
- Remove demo
- Merge remove-demo-feature into main
- Remove demo mode from backend and frontend
- deployment
- Fix Railway backend deploy: use --no-gitignore for service config.
- Fix Railway deploy: runtime API proxy and CLI deploy script.
- Add Railway deploy config with Redis Cloud support.
- Added local LLM for privacy
- collapsed warning flags
- console ui redo
- ui update
- bug fix VI
- added logs and fixed timestamps
- Merge origin/main into local safety agent work.
- latest stuff
- add recommendedActions to safety flags — closes the answer→action loop

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

### Project_Context.md

```markdown
# Project Context

This content has moved to **[ER_Copilot_Hackathon_Plan.md](./ER_Copilot_Hackathon_Plan.md)** — the single source of truth for project scope, safety rules, demo flow, and integration gates.

Dev-specific instructions (use as CLAUDE.md):

- [docs/DEV_A.md](./docs/DEV_A.md)
- [docs/DEV_B.md](./docs/DEV_B.md)
- [docs/DEV_C.md](./docs/DEV_C.md)

```

### CLAUDE.md

```markdown
# CLAUDE.md — AI Assistant Entry Point

> **Read these first** to understand what this project is for, then open your dev track file.

## Required reading

1. **[ER_Copilot_Hackathon_Plan.md](./ER_Copilot_Hackathon_Plan.md)** — Source of truth for **Nos** (Ambulance Copilot): problem framing, agents, safety rules, demo beats, dashboard layout, sponsor tracks, and pitch structure.

2. **[Project_Context.md](./Project_Context.md)** — Short pointer to the plan plus links to dev tracks.

## Then pick your track

Each `docs/DEV_*.md` is your track-specific CLAUDE.md: checklist, file ownership, and Claude agent-team launch prompts.

| Dev | Track |
|-----|-------|
| **A** — Platform & ingestion | [docs/DEV_A.md](./docs/DEV_A.md) |
| **B** — Clinical brain & safety | [docs/DEV_B.md](./docs/DEV_B.md) |
| **C** — UI, research, CV & handoff | [docs/DEV_C.md](./docs/DEV_C.md) |

Parallel build coordination: [PARALLEL_BUILD.md](./PARALLEL_BUILD.md)

## Before editing shared code

Sync with teammates before changing `types/events.ts`, `lib/events.ts`, or `backend/events.py`.

## Coding conventions (summary)

- Structured JSON from LLM agents; idempotent entity merge.
- Safety flags only on **stated facts** — never demographic proxy alone.
- No diagnosis language in flags; use "consider …" / "verify …".
- Do not commit `.env` or secrets.

```

### Dockerfile

```
FROM node:20-alpine AS deps

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm install

FROM node:20-alpine AS builder

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
COPY . .

ARG PYTHON_BACKEND_URL=http://localhost:8000
ENV PYTHON_BACKEND_URL=$PYTHON_BACKEND_URL
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

FROM node:20-alpine AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

COPY --from=builder /app/package.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next

EXPOSE 3000

CMD ["npm", "run", "start"]

```

### package.json

```
{
  "name": "nos",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "local-bus": "tsx scripts/run-local-bus.ts",
    "typecheck": "tsc --noEmit"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.39.0",
    "@deepgram/sdk": "^3.9.0",
    "ioredis": "^5.4.2",
    "next": "^15.1.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "ws": "^8.18.0"
  },
  "devDependencies": {
    "@types/node": "^22.10.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "@types/ws": "^8.5.13",
    "autoprefixer": "^10.4.20",
    "eslint": "^9.16.0",
    "eslint-config-next": "^15.1.0",
    "postcss": "^8.4.49",
    "tailwindcss": "^3.4.16",
    "tsx": "^4.19.2",
    "typescript": "^5.7.2"
  }
}

```

### docker-compose.yml

```yaml
services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  backend:
    build:
      context: .
      dockerfile: backend/Dockerfile
    ports:
      - "8000:8000"
    env_file:
      - path: .env
        required: false
    environment:
      # Always use local Redis in Compose — ignore Redis Cloud URL from .env
      - REDIS_URL=redis://redis:6379
    depends_on:
      redis:
        condition: service_healthy
    volumes:
      - ./backend:/app
      - ./scripts:/scripts:ro
      - /app/__pycache__

  frontend:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    env_file:
      - path: .env
        required: false
    environment:
      - PYTHON_BACKEND_URL=http://backend:8000
      - REDIS_URL=
    depends_on:
      - backend
    volumes:
      - .:/app
      - /app/node_modules
      - /app/.next

volumes:
  redis_data:

```

### backend/Dockerfile

```
FROM python:3.12-slim

WORKDIR /app

COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY backend/ .

EXPOSE 8000

CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}"]

```

### backend/requirements.txt

```
fastapi>=0.115.0
uvicorn[standard]>=0.30.0
anthropic>=0.40.0
redis[asyncio]>=5.2.0
httpx>=0.27.0
python-dotenv>=1.0.0
# Optional — only needed for the live Browserbase research path.
# The browser runs in Browserbase's cloud, so no `playwright install` is required,
# just the client package. Research falls back to PubMed/mock if this is absent.
playwright>=1.40.0

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Archivo, IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
import "./globals.css";

const display = Archivo({
  subsets: ["latin"],
  weight: ["500", "600", "700", "800", "900"],
  variable: "--font-display",
  display: "swap",
});

const sans = IBM_Plex_Sans({
  subsets: ["latin"],
  weight: ["400", "500", "600", "700"],
  variable: "--font-sans",
  display: "swap",
});

const mono = IBM_Plex_Mono({
  subsets: ["latin"],
  weight: ["400", "500", "600"],
  variable: "--font-mono",
  display: "swap",
});

export const metadata: Metadata = {
  title: "Nos - AI Paramedic Copilot",
  description:
    "A real-time AI teammate for paramedics. Listens to the scene, watches the patient, flags safety risks, and hands the ED a perfect report.",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={`${display.variable} ${sans.variable} ${mono.variable}`}>
      <body>{children}</body>
    </html>
  );
}

```

### backend/main.py

```python
"""
Nos — Python FastAPI backend.

Replaces the Next.js API routes (app/api/) and lib/ backend logic.
The Next.js frontend proxies all /api/* requests to this server.

Run with:
    uvicorn main:app --host 0.0.0.0 --port 8000 --reload
"""
from __future__ import annotations

import logging
import os
import sys
from contextlib import asynccontextmanager
from pathlib import Path

# Load .env from the repo root (parent of this backend/ directory)
_env_path = Path(__file__).parent.parent / ".env"
if _env_path.exists():
    from dotenv import load_dotenv
    load_dotenv(_env_path)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from agents.runtime import ensure_agents_started, stop_all_agents
from routes import router as api_router
from routes.vision import router as vision_router

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Start all agents on server boot; stop them on shutdown."""
    from bus import reset_event_bus
    from redis_layer.client import ping_redis

    if os.environ.get("REDIS_URL"):
        if not await ping_redis():
            reset_event_bus()
        else:
            from redis_layer.state import purge_expired_sessions

            expired = await purge_expired_sessions()
            if expired:
                logger.info("[main] purged %d expired session(s) from Redis", expired)

    await ensure_agents_started()
    logger.info("[main] Nos Python backend ready")
    yield
    await stop_all_agents()
    logger.info("[main] Nos Python backend shutdown")


app = FastAPI(
    title="Nos API",
    description="Real-time prehospital AI assistant backend — scene to hospital handoff",
    version="1.0.0",
    lifespan=lifespan,
)

# Allow the Next.js dev server (port 3000) and any production frontend origin.
_default_origins = ["http://localhost:3000", "http://127.0.0.1:3000"]
_extra = os.environ.get("CORS_ORIGINS", "")
_cors_origins = _default_origins + [o.strip() for o in _extra.split(",") if o.strip()]

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

app.include_router(api_router)
app.include_router(vision_router)


@app.get("/")
async def root():
    return {"service": "nos-backend", "status": "ok"}

```

### app/page.tsx

```typescript
import Link from "next/link";
import { NosMark } from "@/components/NosMark";

const AGENTS = [
  {
    n: "01",
    name: "Transcription",
    accent: "text-clinical-300",
    dot: "bg-clinical-400",
    desc: "Live speech-to-text with paramedic / patient speaker labels.",
  },
  {
    n: "02",
    name: "Extraction",
    accent: "text-clinical-300",
    dot: "bg-clinical-400",
    desc: "Pulls allergies, medications, conditions, and symptoms from the transcript.",
  },
  {
    n: "03",
    name: "Timeline",
    accent: "text-clinical-300",
    dot: "bg-clinical-400",
    desc: "Assembles a time-anchored narrative of the entire call.",
  },
  {
    n: "04",
    name: "Safety",
    accent: "text-signal-300",
    dot: "bg-signal-400",
    desc: "Flags missed follow-ups, drug interactions, and NREMT gaps.",
  },
  {
    n: "05",
    name: "Research",
    accent: "text-clinical-300",
    dot: "bg-clinical-400",
    desc: "Looks up drug interactions and clinical protocols against live sources.",
  },
  {
    n: "06",
    name: "Handoff",
    accent: "text-vitals-400",
    dot: "bg-vitals-400",
    desc: "Generates the structured ED report for the next shift.",
  },
];

const FLOW = [
  { k: "Scene arrival", v: "Mic and camera go live the moment you reach the patient." },
  { k: "Live capture", v: "Speech, vitals, telemetry, and what the camera sees merge into one picture." },
  { k: "Real-time safety", v: "Six agents cross-check every stated fact as the call unfolds." },
  { k: "Handoff", v: "Turns a messy scene into a clean report for the receiving ED." },
];

export default function Home() {
  return (
    <div className="relative min-h-screen overflow-hidden bg-ink-900 text-[var(--text)]">
      {/* Atmosphere */}
      <div className="pointer-events-none absolute inset-0 bg-aurora" />
      <div className="pointer-events-none absolute inset-0 bg-grid [mask-image:radial-gradient(80%_60%_at_50%_0%,black,transparent)]" />

      {/* ── Nav ─────────────────────────────────────────────────────── */}
      <header className="relative z-10 mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
        <Link href="/" className="flex items-center gap-2.5">
          <NosMark size={30} />
          <span className="font-display text-xl font-extrabold tracking-tight">Nos</span>
        </Link>
        <nav className="flex items-center gap-2 text-sm">
          <Link
            href="/logs"
            className="rounded-lg px-3.5 py-2 font-medium text-[var(--text-muted)] transition-colors hover:text-white"
          >
            Sessions
          </Link>
          <Link
            href="/dashboard"
            className="rounded-lg bg-signal-500 px-4 py-2 font-semibold text-white shadow-glow transition-transform hover:-translate-y-0.5"
          >
            Open console
          </Link>
        </nav>
      </header>

      {/* ── Hero ────────────────────────────────────────────────────── */}
      <section className="relative z-10 mx-auto max-w-6xl px-6 pb-10 pt-12 sm:pt-20">
        <p className="animate-rise panel-label flex items-center gap-2" style={{ animationDelay: "0ms" }}>
          <span className="inline-block h-1.5 w-1.5 animate-glow-pulse rounded-full bg-signal-500" />
          Real-time clinical copilot · EMS
        </p>

        <h1
          className="animate-rise mt-5 max-w-4xl font-display text-5xl font-extrabold leading-[0.98] tracking-tight sm:text-7xl"
          style={{ animationDelay: "80ms" }}
        >
          Nothing gets lost
          <br />
          between the{" "}
          <span className="bg-gradient-to-r from-signal-400 to-signal-600 bg-clip-text text-transparent">
            scene
          </span>{" "}
          and the{" "}
          <span className="bg-gradient-to-r from-clinical-300 to-clinical-500 bg-clip-text text-transparent">
            ED
          </span>
          .
        </h1>

        <p
          className="animate-rise mt-6 max-w-2xl text-lg leading-relaxed text-[var(--text-muted)]"
          style={{ animationDelay: "160ms" }}
        >
          Nos is a real-time AI teammate for paramedics. It listens to the scene, watches
          the patient, builds a live clinical picture, flags safety risks the instant they
          appear, and hands the emergency department a complete, structured report.
        </p>

        <div
          className="animate-rise mt-9 flex flex-wrap items-center gap-3"
          style={{ animationDelay: "240ms" }}
        >
          <Link
            href="/dashboard"
            className="group inline-flex items-center gap-2 rounded-xl bg-signal-500 px-6 py-3.5 font-semibold text-white shadow-glow transition-transform hover:-translate-y-0.5"
          >
            Open the console
            <span className="transition-transform group-hover:translate-x-0.5">→</span>
          </Link>
          <Link
            href="/logs"
            className="inline-flex items-center gap-2 rounded-xl border border-[var(--line-strong)] px-6 py-3.5 font-semibold text-[var(--text)] transition-colors hover:bg-white/5"
          >
            Replay a session
          </Link>
        </div>

        {/* ECG hero strip */}
        <div
          className="animate-rise relative mt-14 overflow-hidden rounded-2xl border border-[var(--line)] bg-ink-850/70 backdrop-blur"
          style={{ animationDelay: "320ms" }}
        >
          <div className="flex items-center justify-between border-b border-[var(--line)] px-5 py-3">
            <span className="panel-label">Encounter · live monitor</span>
            <span className="flex items-center gap-2 font-mono text-xs text-vitals-400">
              <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-vitals-400" />
              SINUS RHYTHM
            </span>
          </div>
          <div className="relative h-36 sm:h-44">
            <svg
              viewBox="0 0 1200 200"
              preserveAspectRatio="none"
              className="h-full w-
[truncated — 7776 more characters]
```

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