TeddyCare: Dual-Role Healthcare Platform
A HackMIT 2023 project: one app that a patient and their doctor both log into, showing each of them a different thing, with a medical chat model that has the patient's actual wearable data in context. It won the $1,000 Tune AI sponsor award.
The premise was that a health assistant is close to useless without your numbers in front of it. "Why is my resting heart rate up" is unanswerable in the abstract and easy to answer if you know the patient's baseline is 58 and they just doubled their weekly mileage.
The two roles
Patients connect a wearable through Terra (Fitbit, Apple Watch, Garmin, Oura), see their vitals and trends, and chat with the model. Doctors get a patient list, full records covering vitals, medications, and visits, appointment management, and the same chat with access to the patient data.
Roles come from Clerk organizations rather than a role column: membership in the doctor org routes you to the doctor dashboard, membership in patient routes you to yours, and middleware enforces it before any page renders. Using the auth provider's own org primitive turned out to be much less code than route-level guards, and much harder to get wrong at 3am.
Wearable data
graph LR
A[Patient] -->|Connects Device| B[Terra Widget]
B -->|OAuth Flow| C[Terra API]
C -->|Webhook| D[TeddyCare API]
D -->|Store| E[Database]
E -->|Retrieve| F[AI Chat]
F -->|Personalized Advice| A
Terra normalizes across device vendors, which matters more than it sounds like it should, because every vendor reports sleep and HRV slightly differently. Webhook reliability still varied noticeably by device, Apple Watch being the most consistent of the devices tested, and the free tier rate limits hard enough that requests need batching.
The schema is deliberately small:
model User {
clerkId String @unique
role Role
vitals Vital[]
medications Medication[]
}
model Vital {
timestamp DateTime
heartRate Int?
hrv Float?
sleep Sleep?
}
The chat
A fine-tuned GPT-4o (rohan/tune-gpt-4o) served through Tune Studio, trained on medical Q&A, clinical notes, and patient-doctor dialogue. The system prompt adapts to the role: technical terminology and differential diagnosis for a doctor, plain language and a push toward seeing an actual physician for a patient.
Recent vitals get injected before the user's message:
System: Patient has:
- Resting HR: 68 bpm (usually 58, +17%)
- Sleep: 6.5h last night (below usual 7.5h)
- Activity: Marathon training (20 to 40 mi/week)
User: Why is my resting heart rate higher?
The context injection did more for answer quality than the fine-tune did. The fine-tune bought reliability of register, mostly: it stopped hedging into uselessness when asked a clinical question.
Stack
Next.js 14 with the App Router and server components, Clerk for auth and org-based roles, Prisma over PostgreSQL, Terra for health data, shadcn/ui and Radix on Tailwind, deployed to Vercel.
The design lesson that stuck: users wanted trends, not readings. A resting heart rate of 68 means nothing on its own and everything against a personal baseline, so alert thresholds have to be personalized or they are noise.