# Project export: Recall

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: Proactive and pre-emptive action, care, and follow ups
- Devpost: https://devpost.com/software/recall-tpfq6l
- GitHub: https://github.com/anishko/recall
- Demo: https://recall.pics/
- Video: https://www.youtube.com/embed/Klm92dWi0Jk?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 5 GitHub contributor(s) — vedantlbhatt (22 commits), Anish Konduri (16 commits), Claude Opus 4.7 (1M context) (7 commits), yaro (4 commits), Cursor (2 commits)

## Devpost submission (written by the team)

### Inspiration

Every day, radiology reports flag findings that need a follow-up scan, a specialist referral, or a repeat in six months. And every day, a huge number of those follow-ups never happen. The patient leaves the imaging center, the recommendation sits in a PDF, and nobody closes the loop. These missed follow-ups are one of the most preventable failure points in healthcare, and they fall hardest on patients who don't speak English well or don't know how to navigate the system. We wanted to build the thing that actually makes the follow-up happen proactively, without taking the doctor out of the decision.

### What it does

Recall closes the loop on radiology follow-ups: An imaging center's radiology report comes in as a PDF. Claude parses the report and classifies the finding against published clinical guidelines (Fleischner, BI-RADS, and others) to decide whether a follow-up is needed and how urgent it is. It drafts a patient-facing call script, then routes the case to a radiologist for sign-off. No patient is ever contacted without explicit human approval. Once the radiologist approves, a multilingual voice agent calls the patient, explains the follow-up, and books the appointment, in the patient's preferred language. The whole pipeline is decision-support, not autonomous diagnosis. The human sign-off gate is the floor: the system physically cannot dial a patient on an unapproved case.

### How we built it

The backend is a FastAPI service that orchestrates the pipeline. Claude handles the report parsing, guideline classification, urgency triage, and script drafting. The voice agent is built on a Twilio + Deepgram bridge, with Claude as the reasoning layer driving the conversation, and it streams audio in real time over a websocket for the length of the call. Radiologist sign-off happens by email or directly from the doctor dashboard; both paths converge on the same approval function, which flips the case to approved and triggers the outbound call. The frontend is a Next.js app on Vercel with a landing page, a patient portal, and a doctor dashboard. Case data, audit logs, and call outcomes live in Supabase. The multilingual support is real: the voice agent swaps its speech and transcription models per language and localizes the greeting, so a French-speaking patient hears a fluent French call, not English with an accent.

### Challenges we ran into

Holding a live phone call open. A patient call lasts minutes, and the websocket to the voice provider would silently drop during conversational pauses. We had to add a keepalive heartbeat to hold the connection open while the patient was thinking. Multilingual speech recognition. Getting a non-English call to actually transcribe correctly meant pinning the recognition language explicitly, because the model would otherwise default to English and turn French replies into word-salad. Keeping the safety gate airtight. We wanted the approval-to-call flow to be impossible to bypass, so the call function re-checks the approval status from the database at dial time rather than trusting the caller. Integrating four people's work under a deadline. Voice, orchestrator, and frontend were built in parallel and had to converge cleanly.

### What we learned

How much of a "working AI product" is actually the unglamorous plumbing, the keepalives, the language pinning, the idempotency, the human-in-the-loop gate. The Claude reasoning was the easy part; making it safe and reliable enough to put on a real phone call to a real person was the hard part.

### What's next

Real clinical validation, proper patient consent flows, HIPAA-grade infrastructure, and retry cadences for patients who miss the first call. We deliberately scoped the human sign-off as mandatory and would keep it that way in production.

## README (from the GitHub repository)

# Recall

Voice agent that closes the radiology follow-up gap. Decision support only; a
radiologist signs off before any patient is contacted.

Read `CLAUDE.md` first. It is the source of truth for architecture, the safety
invariants, the 5 Claude tools, and lane ownership.

## Lanes
- `api/orchestrator` — Vedant (FastAPI + Claude tools)
- `api/voice` — Anish (Deepgram Voice Agent + Twilio + SMS sign-off)
- `supabase/` — Anish (schema, realtime, storage)
- `web/` — Aditya (Next.js dashboard)

## Setup
1. `cp .env.example .env` and fill keys.
2. Apply `supabase/schema.sql` to the Supabase Postgres instance.
3. Each lane runs its own service. Deploy api -> Render, web -> Vercel.

## Branches
`main` (protected, PR + 1 review until code freeze), `vedant/backend`,
`anish/voice`, `aditya/dashboard`.


## Detected evidence (automated analysis)

Indexed codebase: 157 recognized source files, 563 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
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel (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: Codex — evidence: config files committed to the repository
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 179)

```
.env.example
.gitignore
api/__init__.py
api/db/__init__.py
api/db/.gitkeep
api/db/audit.py
api/db/cases.py
api/db/client.py
api/main.py
api/orchestrator/__init__.py
api/orchestrator/.gitkeep
api/orchestrator/analyze.py
api/orchestrator/cadence.py
api/orchestrator/email.py
api/orchestrator/guidelines.py
api/orchestrator/router.py
api/orchestrator/signoff.py
api/README.md
api/requirements.txt
api/scripts/test_call.py
api/tests/__init__.py
api/tests/test_agent_config.py
api/tests/test_bridge.py
api/tests/test_outbound_dial.py
api/tests/test_persist_outcome.py
api/tests/test_signoff_gate.py
api/tests/test_ws_handler.py
api/voice/__init__.py
api/voice/.gitkeep
api/voice/agent_config.py
api/voice/bridge.py
api/voice/call.py
api/voice/calls.py
api/voice/client.py
api/voice/config.py
api/voice/demo_context.py
api/voice/README.md
api/voice/router.py
api/voice/slots.py
api/voice/twilio_client.py
CLAUDE.md
docs/TEAM_HANDOFF.md
frontend-migration-prd.txt
new-web/.gitignore
new-web/next.config.ts
new-web/package.json
new-web/pnpm-workspace.yaml
new-web/postcss.config.mjs
new-web/README.md
new-web/scripts/test-endpoints.sh
new-web/src/app/api/analyze/route.ts
new-web/src/app/api/audit/route.ts
new-web/src/app/api/cases/[id]/approve/route.ts
new-web/src/app/api/cases/[id]/flag/route.ts
new-web/src/app/api/cases/[id]/route.ts
new-web/src/app/api/cases/route.ts
new-web/src/app/api/eval/route.ts
new-web/src/app/api/patient/[token]/route.ts
new-web/src/app/api/patient/[token]/schedule/route.ts
new-web/src/app/api/patient/[token]/send-to-family/route.ts
new-web/src/app/api/review/[id]/route.ts
new-web/src/app/api/signoff/approve/route.ts
new-web/src/app/api/signoff/reject/route.ts
new-web/src/app/cases/[id]/page.tsx
new-web/src/app/dashboard/audit/page.tsx
new-web/src/app/dashboard/case/[id]/page.tsx
new-web/src/app/dashboard/eval/page.tsx
new-web/src/app/dashboard/layout.tsx
new-web/src/app/dashboard/page.tsx
new-web/src/app/globals.css
new-web/src/app/layout.tsx
new-web/src/app/p/[token]/family/page.tsx
new-web/src/app/p/[token]/page.tsx
new-web/src/app/p/[token]/scheduling/page.tsx
new-web/src/app/p/[token]/send-to-family/page.tsx
new-web/src/app/page.tsx
new-web/src/app/review/[id]/page.tsx
new-web/src/app/signoff/rejected/page.tsx
new-web/src/app/upload/page.tsx
new-web/src/components/ApprovePanel.tsx
new-web/src/components/CaseRow.tsx
new-web/src/components/ClinicalEvaluationDashboard.tsx
new-web/src/components/ConfidenceBar.tsx
new-web/src/components/DashboardShell.tsx
new-web/src/components/FamilyViewBanner.tsx
new-web/src/components/FindingCard.tsx
new-web/src/components/LangSwitcher.tsx
new-web/src/components/LanguageFlag.tsx
new-web/src/components/LiveCallStrip.tsx
new-web/src/components/Logo.tsx
new-web/src/components/MeaningCard.tsx
new-web/src/components/MockBadge.tsx
new-web/src/components/NextStepsTimeline.tsx
new-web/src/components/PatientHero.tsx
new-web/src/components/ReportPanel.tsx
new-web/src/components/SchedulingGrid.tsx
new-web/src/components/SectionVisibilityToggle.tsx
new-web/src/components/SendToFamilyForm.tsx
new-web/src/components/SliceCarousel.tsx
new-web/src/components/ThemeToggle.tsx
new-web/src/components/UrgencyBadge.tsx
new-web/src/hooks/useSectionVisibility.ts
new-web/src/hooks/useTheme.ts
new-web/src/hooks/useTranslation.ts
new-web/src/i18n/ar-TN.json
new-web/src/i18n/en.json
new-web/src/i18n/fr.json
new-web/src/i18n/zh.json
new-web/src/lib/api-proxy.ts
new-web/src/lib/caseAdapter.ts
new-web/src/lib/cases.ts
new-web/src/lib/clinicalEvaluation.ts
new-web/src/lib/cn.ts
new-web/src/lib/deidentify.ts
new-web/src/lib/mockCases.ts
new-web/src/lib/supabaseTypes.ts
new-web/src/lib/types.ts
new-web/src/store/useDashboard.ts
new-web/src/store/usePatient.ts
new-web/tsconfig.json
[59 more files omitted for size]
```

### Dependencies

- api/requirements.txt: anthropic@>=0.40.0, fastapi@>=0.115.0, httpx@>=0.27.0, PyJWT@>=2.9.0, pytest@>=8.0, python-dotenv@>=1.0.0, python-multipart@>=0.0.9, supabase@>=2.0, twilio@>=9.0.0, uvicorn[standard]@>=0.32.0, websockets@>=14.0
- new-web/package.json: @radix-ui/react-avatar@^1.2.0, @radix-ui/react-collapsible@^1.1.14, @radix-ui/react-dialog@^1.1.17, @radix-ui/react-progress@^1.1.10, @radix-ui/react-select@^2.3.1, @radix-ui/react-separator@^1.1.10, @radix-ui/react-tabs@^1.1.15, @radix-ui/react-tooltip@^1.2.10, @supabase/supabase-js@^2.108.2, @tailwindcss/postcss@^4, @tanstack/react-virtual@^3.14.3, @types/node@^20, @types/react@^19, @types/react-dom@^19, class-variance-authority@^0.7.1, clsx@^2.1.1, framer-motion@^12.40.0, lucide-react@^1.21.0, next@16.2.9, partysocket@^1.2.0, react@19.2.4, react-dom@19.2.4, swr@^2.4.1, tailwind-merge@^3.6.0, tailwindcss@^4, typescript@^5, zustand@^5.0.14
- web/package.json: @base-ui/react@^1.6.0, @supabase/supabase-js@^2.108.2, @tailwindcss/postcss@^4, @tanstack/react-table@^8.21.3, @types/node@^20, @types/react@^19, @types/react-dom@^19, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@16.2.9, lucide-react@^1.21.0, next@16.2.9, next-themes@^0.4.6, react@19.2.4, react-dom@19.2.4, shadcn@^4.11.0, tailwind-merge@^3.6.0, tailwindcss@^4, tw-animate-css@^1.4.0, typescript@^5

### Recent commits (newest first)

- adjust patient screen/view
- radrelay -> recall
- auto greet
- fix
- Merge branch 'main' of https://github.com/anishko/radrelay
- fix call issue
- deploy
- fix email not triggering call
- more UI
- Merge origin/main into local landing page work.
- new gif and background
- new front end style
- classic prompt engineering!!
- Merge pull request #5 from anishko/frontend
- migrated front end
- prd for migration
- Add new-web frontend scaffold for backend integration.
- voice: french STT language pin + deepgram keepalive (live booked call confirmed)
- deploy
- Merge pull request #4 from anishko/analysis-report

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

### CLAUDE.md

```markdown
# Recall — CLAUDE.md

Cal Hacks AI 2026 (UC Berkeley), 24hr build. Read this fully before writing code.

## What this is
Email a radiology report PDF to a Recall inbox. Claude parses it, applies the right
clinical follow-up guideline, drafts a patient phone script in the patient's language,
gets the radiologist to approve via email, then places a multilingual outbound call that
books the follow-up scan. Closes the "actionable finding never followed up" gap.

It is **decision support only**. A human radiologist signs off on every patient
communication before it goes out. We do not practice medicine.

## Hard invariants (never violate)
1. **No patient call may be placed until `cases.signoff_status = 'approved'`.** The
   gate is enforced server-side, not in the UI. `place_patient_call` must re-check the
   DB status before dialing.
2. **No real PHI.** All demo data is synthetic or from public/de-identified sources
   (MIMIC-CXR open subset + synthetic edge cases). Never hardcode a real patient.
3. **Booking is mocked in v1.** No real calendar. Patient is offered a synthetic slot
   and the confirmation is stored. This is stated honestly in the pitch.
4. **Confidence < 0.85 => flag for human review, never auto-contact the patient.**
5. **No EHR integration.** PDF-over-email is the only ingestion path in v1.

## Architecture
```
SendGrid Inbound Parse → webhook → Supabase Storage (PDF)
  → Claude Sonnet 4.5 orchestrator (5 tools below)
  → radiologist sign-off email (Resend) → 1-tap approve/reject
  → [approved] Deepgram Voice Agent + Twilio outbound call (multilingual)
  → Postgres (cases, audit_log, radiologists) + Arize Phoenix traces/evals
  → Next.js dashboard (Supabase realtime)
```

## Services / ownership
| Path | Owner | Stack |
|---|---|---|
| `api/orchestrator` | Vedant | FastAPI, Anthropic SDK, the 5 tools |
| `api/voice` | Anish | Deepgram Voice Agent + Twilio Media Streams bridge, outbound call trigger |
| `api/db` | shared (Anish owns schema) | Supabase Postgres client |
| `supabase/` | Anish | schema migration, realtime, storage bucket |
| `web/` | Aditya | Next.js 15 + Tailwind + shadcn dashboard |

Deploy: `api/` on Render, `web/` on Vercel, DB + storage on Supabase. Webhooks must hit
public deploy URLs, not localhost (venue wifi will NAT you). Use the Render URL or a
tunnel for webhook testing.

## The 5 Claude tools (single orchestrator, no multi-agent)
- `parse_report(pdf_url)` -> structured findings, demographics, language_preference
- `classify_actionability(findings, patient_age?, smoking_status?)` -> guideline_used,
  severity, recommended_followup, timeframe_days, confidence (0-1), citation.
  Guidelines embedded in system prompt: Fleischner 2017, BI-RADS, LI-RADS v2018,
  TI-RADS, Lung-RADS v2022. If confidence < 0.85, flag and stop.
- `draft_patient_script(case_summary, language[en|ar-TN|fr|zh], patient_name)` -> script text,
 6th-grade reading level, empathetic, one clear action.
- `request_radiologist_signoff(case_id, ra
[truncated — 1287 more characters]
```

### web/CLAUDE.md

```markdown
@AGENTS.md

```

### api/requirements.txt

```
fastapi>=0.115.0
uvicorn[standard]>=0.32.0
python-dotenv>=1.0.0
python-multipart>=0.0.9
twilio>=9.0.0
PyJWT>=2.9.0
httpx>=0.27.0
supabase>=2.0
anthropic>=0.40.0
websockets>=14.0
pytest>=8.0

```

### web/package.json

```
{
  "name": "web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@base-ui/react": "^1.6.0",
    "@supabase/supabase-js": "^2.108.2",
    "@tanstack/react-table": "^8.21.3",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^1.21.0",
    "next": "16.2.9",
    "next-themes": "^0.4.6",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "shadcn": "^4.11.0",
    "tailwind-merge": "^3.6.0",
    "tw-animate-css": "^1.4.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### new-web/package.json

```
{
  "name": "recall-web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "@radix-ui/react-avatar": "^1.2.0",
    "@radix-ui/react-collapsible": "^1.1.14",
    "@radix-ui/react-dialog": "^1.1.17",
    "@radix-ui/react-progress": "^1.1.10",
    "@radix-ui/react-select": "^2.3.1",
    "@radix-ui/react-separator": "^1.1.10",
    "@radix-ui/react-tabs": "^1.1.15",
    "@radix-ui/react-tooltip": "^1.2.10",
    "@supabase/supabase-js": "^2.108.2",
    "@tanstack/react-virtual": "^3.14.3",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.40.0",
    "lucide-react": "^1.21.0",
    "next": "16.2.9",
    "partysocket": "^1.2.0",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "swr": "^2.4.1",
    "tailwind-merge": "^3.6.0",
    "zustand": "^5.0.14"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### api/main.py

```python
import logging
from pathlib import Path

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

# Pick up .env so `uvicorn api.main:app` sees DEEPGRAM/SUPABASE/TWILIO keys
# and DEEPGRAM_AGENT_THINK_MODEL overrides — no shell `export` needed.
load_dotenv(Path(__file__).resolve().parents[1] / ".env")

from api.orchestrator.router import router as orchestrator_router  # noqa: E402
from api.voice.router import router as voice_router  # noqa: E402

# Uvicorn only configures its own loggers — make sure our radrelay.* logs
# also show up at INFO, with a useful format.
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logging.getLogger("radrelay").setLevel(logging.INFO)

app = FastAPI(title="RadRelay API")
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
app.include_router(voice_router)
app.include_router(orchestrator_router)


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

```

### web/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono, Lexend } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

// Lexend: designed for reading proficiency — the accessibility-first heading
// face for a clinical product (UI/UX design-system recommendation).
const lexend = Lexend({
  variable: "--font-lexend",
  subsets: ["latin"],
  weight: ["500", "600", "700"],
});

export const metadata: Metadata = {
  title: "RadRelay — Radiology follow-up dashboard",
  description:
    "AI-assisted radiology follow-up: parse, classify, draft, radiologist sign-off, multilingual outbound call.",
  icons: {
    icon: "/logo.png",
    apple: "/logo.png",
  },
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      lang="en"
      suppressHydrationWarning
      className={`${geistSans.variable} ${geistMono.variable} ${lexend.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">
        <ThemeProvider
          attribute="class"
          defaultTheme="dark"
          enableSystem={false}
          storageKey="radrelay-theme-v2"
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

```

### new-web/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono, Instrument_Serif } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin", "latin-ext"],
  weight: ["400", "500", "600", "700"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

const instrumentSerif = Instrument_Serif({
  variable: "--font-instrument-serif",
  subsets: ["latin"],
  weight: ["400"],
});

export const metadata: Metadata = {
  title: "Recall — Radiology follow-up coordination",
  description:
    "Decision support that closes the gap between radiology reports and patient follow-up.",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} ${instrumentSerif.variable}`}
    >
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `(function(){try{var s=localStorage.getItem("recall_theme");var d=window.matchMedia("(prefers-color-scheme: dark)").matches;document.documentElement.setAttribute("data-theme",(s==="dark"||(s===null&&d))?"dark":"light")}catch(e){}})()`,
          }}
        />
        <link rel="preconnect" href="https://fonts.googleapis.com" crossOrigin="anonymous" />
        <link
          href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@400;500;600;700&display=swap"
          rel="stylesheet"
        />
      </head>
      <body className="min-h-dvh antialiased font-sans">
        <a href="#main-content" className="skip-link">
          Skip to main content
        </a>
        {children}
      </body>
    </html>
  );
}

```

### web/src/app/page.tsx

```typescript
import { AlertTriangle, CalendarCheck, PhoneCall, Inbox } from "lucide-react";
import { AppShell } from "@/components/app-shell";
import { CaseTable } from "@/components/case-table";
import { listCases } from "@/lib/cases";
import { needsAttention, pipelineStage } from "@/lib/case-utils";
import { cn } from "@/lib/utils";

export default async function DashboardPage() {
  const cases = await listCases();

  const attention = cases.filter(needsAttention).length;
  const calling = cases.filter((c) => pipelineStage(c) === "calling").length;
  const booked = cases.filter((c) => pipelineStage(c) === "booked").length;

  const stats = [
    {
      label: "Active cases",
      value: cases.length,
      icon: Inbox,
      chip: "bg-primary/10 text-primary",
      highlight: false,
    },
    {
      label: "Awaiting sign-off",
      value: attention,
      icon: AlertTriangle,
      chip: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
      highlight: attention > 0,
    },
    {
      label: "On call now",
      value: calling,
      icon: PhoneCall,
      chip: "bg-violet-500/10 text-violet-600 dark:text-violet-400",
      highlight: false,
    },
    {
      label: "Follow-ups booked",
      value: booked,
      icon: CalendarCheck,
      chip: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
      highlight: false,
    },
  ];

  return (
    <AppShell>
      <div className="space-y-6">
        <div>
          <h1 className="text-2xl font-semibold">Cases</h1>
          <p className="mt-1 max-w-2xl text-sm text-muted-foreground">
            Reports flow in by email. Claude parses, classifies, and drafts —
            then waits for a radiologist before any patient is called.
          </p>
        </div>

        <div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
          {stats.map((s) => (
            <div
              key={s.label}
              className={cn(
                "rounded-xl border bg-card p-4 shadow-sm transition-shadow hover:shadow-md",
                s.highlight && "border-amber-500/40 ring-1 ring-amber-500/20",
              )}
            >
              <div className="flex items-center justify-between">
                <span className="text-sm font-medium text-muted-foreground">
                  {s.label}
                </span>
                <span
                  className={cn(
                    "flex h-8 w-8 items-center justify-center rounded-lg",
                    s.chip,
                  )}
                >
                  <s.icon className="h-4 w-4" />
                </span>
              </div>
              <div className="mt-3 font-heading text-3xl font-semibold tabular-nums">
                {s.value}
              </div>
            </div>
          ))}
        </div>

        <CaseTable cases={cases} />
      </div>
    </AppShell>
  );
}

```

### new-web/src/app/page.tsx

```typescript
"use client";
import { useEffect } from "react";
import Link from "next/link";
import { Logo } from "@/components/Logo";

export default function HomePage() {
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", "dark");
    localStorage.setItem("recall_theme", "dark");
  }, []);

  return (
    <main
      id="main-content"
      className="landing-page relative min-h-dvh flex flex-col bg-[var(--color-bg)]"
    >
      <div className="relative z-10 px-6 sm:px-10 py-6 sm:py-8">
        <Logo href="/" size="header" />
      </div>

      <div className="relative z-10 flex-1 flex items-center pl-6 pr-6 sm:pl-10 sm:pr-10 lg:pl-12 lg:pr-12 pb-16 w-full max-w-[1600px] mx-auto">
        <div className="grid lg:grid-cols-2 gap-10 lg:gap-5 xl:gap-6 items-center w-full">
          <div className="space-y-8 min-w-0 lg:max-w-xl lg:justify-self-start lg:ml-10 xl:ml-16">
            <div className="space-y-5">
              <p className="tag-eyebrow font-sans w-fit">
                Detection · Analysis · Pre-emptive Recall
              </p>
              <h1
                className="font-display text-5xl sm:text-6xl lg:text-[4rem] xl:text-[4.5rem] leading-[1.08] text-balance"
                style={{ color: "var(--color-text)" }}
              >
                Automated{" "}
                <span className="whitespace-nowrap">follow-ups</span> for
                patients at risk.
              </h1>
              <p
                className="font-sans text-base sm:text-lg leading-relaxed max-w-lg font-normal"
                style={{ color: "var(--color-muted-2)" }}
              >
                We use real scans to analyze, triage, and detect early stages
                of disease to proactively catch them before they progress.
              </p>
            </div>

            <div className="flex flex-wrap gap-3 pt-1 font-sans">
              <Link href="/dashboard" className="btn-accent">
                Open workspace
              </Link>
              <Link href="/upload" className="btn-primary">
                Upload report
              </Link>
            </div>

            <div
              className="font-sans flex flex-wrap gap-x-4 gap-y-1 text-xs sm:text-sm pt-1"
              style={{ color: "var(--color-muted-2)" }}
            >
              <Link
                href="/p/tok_sarah_abc123"
                className="hover:underline"
                style={{ color: "var(--color-muted)" }}
              >
                Patient portal
              </Link>
              <span aria-hidden>·</span>
              <Link
                href="/p/tok_grandma_chen_xyz789?lang=zh"
                className="hover:underline"
                style={{ color: "var(--color-muted)" }}
              >
                中文 demo
              </Link>
              <span aria-hidden>·</span>
              <Link
                href="/p/tok_alex_ar789?lang=ar-TN"
                className="hover:underline"
                style={{ color: "var(--color-muted)" }}
              >
                عربي demo
              </Link>
            </div>
          </div>

          <div className="flex justify-center lg:justify-start w-full -mt-6 sm:-mt-8 lg:-mt-12">
            <div className="overflow-hidden bg-black size-[min(92vw,480px)] sm:size-[min(85vw,560px)] md:size-[min(75vw,640px)] lg:size-[min(78dvh,740px)] xl:size-[min(84dvh,840px)]">
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src="/mrigif.gif?v=3"
                alt="Animated MRI scan slices"
                className="block size-full object-contain"
                loading="eager"
                decoding="async"
              />
            </div>
          </div>
        </div>
      </div>

      <footer
        className="font-sans relative z-10 px-6 sm:px-10 py-6 flex flex-wrap items-center justify-between gap-3 text-xs sm:text-sm border-t"
        style={{
          color: "var(--color-muted-2)",
          borderColor: "var(--color-border)",
        }}
      >
        <span>Recall · decision support only</span>
        <span>Radiologist sign-off required before patient contact</span>
      </footer>
    </main>
  );
}

```

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