MedLens is a single React Native / Expo application with a strict separation between the navigation shell and the domain logic. Screens live under app/ (Expo Router, file-based routing); all data access, OCR, storage, and business logic live under src/. Components never issue raw SQL — every database read or write goes through a repository class.

See the Data Model for the SQLite schema and the OCR Pipeline for the capture-to-record flow.

Stack

LayerChoiceWhy
FrameworkExpo SDK 55 (managed workflow)Camera, filesystem, and SQLite without ejecting
NavigationExpo Router (file-based)React Navigation underneath, with file-based convenience
StateReact Context + useReducerNo external state libraries — most state lives in SQLite, not memory
Local DBexpo-sqliteLocal-first structured storage for all medical records
OCRML Kit (on-device)Works offline; no data leaves the device on the default tier
AI structuringOptional cloud APIHigher-accuracy extraction the user explicitly opts into
ChartsVictory NativeTime-series charts with native SVG rendering
Photo storageexpo-file-systemStructured local directory layout
Cryptoexpo-cryptoUUID generation and at-rest encryption

Because the project depends on native modules that cannot run in Expo Go, it requires a development build. See the Developer Guide.

Expo Router maps the file tree to routes. The shell is a tab bar plus a set of dynamic detail routes:

  • _layout.tsx — root layout: providers and database initialization
  • (tabs)/ — the five primary tabs:
    • Home — dashboard with latest readings, upcoming meds, recent events
    • Capture — camera viewfinder plus manual entry
    • Meds — active medications, administration log, schedule
    • Labs — lab results and trend charts
    • Vitals — vital signs and trend charts
  • review/[photoId] — the post-OCR confirm/edit screen
  • photo/[photoId] — photo detail and the records linked to it (the audit view)
  • trend/[metricName] — full drilldown for a single metric
  • patient/ — patient list, detail/edit, and add-new (the schema supports multiple patients)
  • settings — privacy tier, storage usage, export

Domain layer (src/)

  • db/ — schema and migration runner, time-series trend queries, and the repository classes (patient, medication, vital, lab, event, photo). Migrations run inside an exclusive transaction against a version table.
  • ocr/ — the extraction pipeline and its tiered backends. See OCR Pipeline.
  • storage/ — filesystem operations for photos plus the bridge that keeps the filesystem and SQLite in sync.
  • models/ — all TypeScript interfaces and shared constants.
  • utils/ — trend math (delta calculation, context-aware coloring) and date helpers.
  • context/ — the global app state, the database instance provider, and the settings provider.
  • components/ — reusable UI: delta badges, reading cards, the chart wrappers, the medication timeline, the review card, and flag badges.
  • notifications/ — local reminder scheduling and tap routing, under strict PII rules (see Privacy and Safety).

State model

State is intentionally thin because the durable record lives in SQLite, not in memory. Three contexts cover it:

  • App context — the active patient, the patient list, and a loading flag.
  • Settings context — the extraction tier, unit preferences, theme, and the lock-screen preference. The optional API key lives in the device secure store, never in SQLite.
  • Database context — the singleton database instance and a readiness flag.

Capture data flow

The core loop, from shutter to chart:

  1. Camera shutter saves the file and a photo record.
  2. The OCR pipeline runs against the captured image at the selected tier.
  3. The raw OCR text is stored back onto the photo record.
  4. The user lands on the review screen and sees the extracted records.
  5. The user confirms or edits each record.
  6. Each confirmed record is persisted through its repository.
  7. The dashboard and charts update automatically.

Design conventions

  • TypeScript strict mode; functional components with hooks only.
  • All dates are ISO 8601 strings; all IDs are UUIDs from expo-crypto.
  • Dark theme by default — the app is used in dim hospital rooms.
  • Minimum 48dp tap targets; primary actions reachable one-handed.