Skip to content

02 System Architecture

Anubha Parashar edited this page Oct 10, 2026 · 1 revision

HealthBook AI (internal code name: BabyCare) is a three-tier system: two thin clients, one FastAPI application that owns every rule, and two private stores (a relational database and an encrypted object store). This page shows how the parts connect, which external services the code actually calls, and how a request travels through the API. The same code base also runs fully on one machine with SQLite and local encrypted files.

Responsibilities

  • Keep all clinical and authorization logic on the server; clients only render and submit.
  • Keep originals and clinical text encrypted at the application layer before they reach any store.
  • Call external services only for transport (email, push, optional vision) and never send clinical detail through them unless the guardian consents per document.

Overall System Architecture

flowchart LR
    subgraph clients["Clients"]
        WEB["Web app<br/>vanilla JS, served by Firebase Hosting"]
        DOC["doctor.html<br/>clinician share viewer"]
        AND["Android app<br/>Expo and React Native"]
    end
    subgraph render["Render free web service"]
        API["FastAPI API<br/>uvicorn, Docker"]
        OCR["Local Tesseract OCR<br/>optional, in the container"]
    end
    subgraph supa["Supabase"]
        PG[("PostgreSQL<br/>session pooler")]
        BUCKET[("Private storage bucket<br/>Fernet-encrypted objects")]
        CRON["pg_cron + pg_net<br/>scheduler wake-up"]
    end
    subgraph ext["External services"]
        BREVO["Brevo HTTPS email API"]
        EXPO["Expo push service"]
        WPUSH["Browser push services<br/>Web Push VAPID"]
        VISION["Opt-in vision provider<br/>OpenAI-compatible HTTPS"]
    end
    subgraph home["Home network"]
        CAMW["Local camera worker<br/>python -m api.camera_worker"]
        CAM["IP camera<br/>RTSP or HTTP"]
    end
    WEB -->|"HTTPS JSON, cookie session"| API
    DOC -->|"HTTPS, share token"| API
    AND -->|"HTTPS JSON, bearer token"| API
    API -->|"SQL over TLS"| PG
    API -->|"service-role HTTPS"| BUCKET
    API --> OCR
    CRON -->|"POST scheduler tick<br/>X-Scheduler-Key"| API
    API -->|"password reset mail"| BREVO
    API -->|"generic push text"| EXPO
    API -->|"generic push text"| WPUSH
    API -.->|"only with per-document consent"| VISION
    CAM -->|"local stream"| CAMW
    CAMW -->|"derived events only<br/>X-Camera-Worker-Key"| API
Loading

The dashed edge is disabled by default: render.yaml sets BABYCARE_VISION_URL to an empty string, and api/providers.py refuses to send anything unless the URL, model and key are configured and the request carries explicit consent naming the configured host.

Component responsibilities

Component Responsibility Key modules / files
Web app Single-page UI built from plain script files; same-origin /api locally, apiBase on the hosted domains; service worker caches only the shell, never /api/ web/index.html, web/app.js, web/care.js, web/config.js, web/sw.js
Doctor viewer Read-only page for a share link; fetches /api/share/public/{token} web/doctor.html, web/doctor.js
Android app Native screens; token in expo-secure-store; offline queue for daily entries; Expo push registration via POST /api/devices mobile/App.tsx, mobile/DailyCareScreen.tsx, mobile/src/config.ts
FastAPI API Auth, profile isolation, ingestion, review, care journal, exports, sharing, reminders, camera ingest api/main.py plus routers in api/*_api.py, api/workflows.py, api/share.py, api/cloud.py
Security middleware Origin check on writes, rate limiting, request size cap, security headers api/guardrails.py
Database All structured records; clinical text in EncryptedText columns api/database.py, api/models.py, api/care_models.py, api/lifetime_models.py, migrations/
Object store Encrypted originals, stool photos; never decrypts, never public URLs api/storage.py (LocalStorage, SupabaseStorage)
Reminder worker Reconciles confirmed plans into notifications and delivers them; runs as a loop (python -m api.worker) or one tick per scheduler call api/worker.py, api/reminders.py, api/cloud.py
Mailer Transactional email over HTTPS (Brevo or Resend); SMTP only for local use api/mailer.py, api/password_reset.py
Push Expo push for the Android app, RFC 8030 Web Push with VAPID for the installed web app api/worker.py, api/push_web.py
OCR PyMuPDF text layer first, Tesseract for scanned pages and images, all local api/extract.py
Camera worker Runs inside the home network, reads the stream locally, posts movement/presence events only api/camera_worker.py, api/camera.py, api/camera_api.py

Request lifecycle

A typical authenticated write, for example adding a fluid entry from the web app:

  1. Client. request() in web/app.js prefixes the path with apiBase (from web/config.js) and sends X-Requested-With: BabyCare. The web client authenticates with the babycare_session cookie; the Android app sends Authorization: Bearer <token>.
  2. CORS. When BABYCARE_CORS_ORIGINS is set, CORSMiddleware allows only those origins, with credentials.
  3. Guardrails middleware (secure_headers in api/guardrails.py): rejects state-changing requests whose Origin is not the API itself or an allowed origin; applies a fixed-window rate limit per client IP (30/minute on /api/auth/*, 600/minute elsewhere; in memory on Render, in the rate_windows table otherwise); rejects bodies over 13 MB; /api/health skips the limiter so the health check never touches the database.
  4. Authentication (require_user in api/security.py): looks up the SHA-256 of the token in sessions, checks expiry (14 days), and for cookie-authenticated writes requires the X-Requested-With header as a CSRF check.
  5. Authorization (own_patient / own_resource): the profile must belong to the user or be granted through caregiver_access; read-only grants are refused on writes; unknown and foreign ids both return 404.
  6. Validation. Pydantic models bound every field (lengths, enumerations, timezone names, dates not in the future, HH:MM schedules).
  7. Persistence. SQLAlchemy writes rows; EncryptedText columns are Fernet-encrypted transparently; an audit row records the action; file bytes are encrypted before write_document().
  8. Response. JSON without password hashes, token hashes, storage names or ciphertext (as_json drops them); the middleware adds Cache-Control: no-store, a strict Content-Security-Policy, X-Frame-Options: DENY and, in production, HSTS. Storage failures map to 404/503 without object names (install_handlers in api/cloud.py).

Startup sequence

Importing api/main.py runs, in order: upgrade() (Alembic to head under a file lock; an encrypted SQLite snapshot is taken first when an existing database is behind), harden_database() (PostgreSQL only: RLS on every table and Supabase client roles revoked; production refuses to start if this fails), prepare_storage() (creates the private bucket, fails closed if it is public) and then mounts the routers and, last, the static web/ directory.

Local / self-hosted mode

The default configuration needs no cloud service at all:

Concern Hosted mode Local mode
Database Supabase PostgreSQL via DATABASE_URL SQLite at BABYCARE_DATA_DIR/babycare.sqlite3 (default when DATABASE_URL is unset)
Originals BABYCARE_STORAGE=supabase private bucket BABYCARE_STORAGE=local, files in encrypted_files/ with mode 0600
Key BABYCARE_FERNET_KEY (required in production) file_crypt.key generated once; refuses to generate a new key if encrypted data already exists
Web app Firebase Hosting, API on another origin Served by the API itself from web/, same origin
Reminders pg_cron wakes the API every 10 minutes worker service in docker-compose.yml loops every 15 seconds

Production refuses BABYCARE_STORAGE=local unless BABYCARE_ALLOW_LOCAL_STORAGE=true is set for a host with a persistent volume (api/config.py).

Security boundaries

  • The Supabase service-role key exists only on the API host; neither client ever talks to Supabase directly.
  • Database tables have RLS enabled and no grants for the anon and authenticated roles; the bucket has zero object policies.
  • Push and email bodies never contain names, diagnoses, medicines or appointment details.
  • The camera worker authenticates with a per-camera worker key and sends derived events, never video.

Clinical safety rules

  • OCR extracted ≠ clinically verified: every transcription is stored as needs_review, whichever engine produced it.
  • AI never prescribes or changes doses; the optional vision provider only transcribes, and its output is reviewed like OCR.
  • A notification/reminder is never proof a dose was given.

Implementation

api/main.py, api/guardrails.py, api/security.py, api/config.py, api/database.py, api/storage.py, api/cloud.py, api/worker.py, api/mailer.py, api/push_web.py, api/providers.py, api/camera_worker.py, web/app.js, mobile/src/config.ts.

Related pages

Deployment Architecture · Backend Architecture · Web Application Architecture · Android Application Architecture · Encryption and Private Storage · API Design

Clone this wiki locally