Clinic platform, team of six, Jul 2026

DocPilot

An outpatient clinic platform with a live queue, telehealth and an AI scribe that turns consultations into structured notes. I owned the data and security layer.

Role
Backend developer. I designed the Firestore data model and real-time listeners, wrote the security rules, set up Firebase Auth and connected Appwrite storage.
Stack
  • React 19
  • TypeScript
  • Firebase Auth
  • Cloud Firestore
  • Appwrite Storage
  • Gemini API
  • Flutter
Links

The problem

Outpatient clinics lose hours to paper queues and handwritten notes. Our team set out to digitize the whole visit: booking, the waiting-room queue, the consultation and the paperwork after it.

My part was the layer everything else stands on: where data lives, who can read it, and how changes reach every screen at once.

How it's built

The React web app talks to Firebase directly and listens for changes instead of polling. A companion Flutter app records the consultation, has Gemini turn the transcript into a structured SOAP note, then saves the note to Firestore and a PDF to Appwrite.

DocPilot architecture Web app to Firebase Auth (sign-in). Web app to Cloud Firestore (onSnapshot). Web app to Appwrite Storage (files). Voice app to Gemini API (transcript). Voice app to Cloud Firestore (note). Voice app to Appwrite Storage (PDF) Web app React 19, TypeScript Firebase Auth doctor and patient roles Cloud Firestore 190 lines of rules Appwrite Storage DICOM, JPG, PDF Gemini API transcript to SOAP note Voice app Flutter sign-in onSnapshot files transcript note PDF
Web and voice apps, and the services each one talks to.

Key decisions

Enforce access in the database, not the UI

With no server of our own, the database rules are the security boundary. Appointments, consultations and invoices can only be read by the doctor and patient named on them. A user's role is fixed at sign-up, and every write is checked against the expected fields, types and allowed values.

match /users/{userId} {
  allow create: if isOwner(userId) && isValidUser(request.resource.data);
  // the role can never change after sign-up
  allow update: if isOwner(userId) && isValidUser(request.resource.data) &&
                  request.resource.data.role == resource.data.role;
}

match /appointments/{appointmentId} {
  allow read: if isAuthenticated() &&
    (request.auth.uid == resource.data.doctorId ||
     request.auth.uid == resource.data.patientId);
}
From firestore.rules

Keep large files out of Firestore

Scans and reports (DICOM, JPG, PDF) go to Appwrite storage buckets. Firestore keeps only the metadata and a link, so queries stay small and fast while files can be as large as they need to be.

Push changes instead of polling

The queue, appointments, analytics and chat all use Firestore onSnapshot listeners. When a doctor calls the next patient, the waiting-room display and the patient's phone update from the same write.

Results

of 440 labelled data points extracted correctly by the scribe
95.5%
average wait, traditional queue vs DocPilot
45 → 18 min

Team results from our EPICS Phase II report. Scribe accuracy comes from prototype testing and ranged from 94.6% on symptoms to 96.6% on dosage and frequency. The wait-time figures are the report's comparison of a traditional clinic queue with DocPilot's dynamic queue, about 60% lower.

Notes from the scribe can't be finalized until the doctor reviews and confirms them.

Limits and next steps

  • Medical records can be read by any doctor, not only the treating one, and chat documents are open to any signed-in user. Scoping both to the people involved is the first fix.
  • The voice app calls Gemini directly with a key it carries. Moving that call behind a server function would keep the key off devices.