Blood pressure tracker — extract readings from photos with vision AI, review, visualize, and export PDF reports.
MeanBloodPressure is a web app for logging blood pressure readings. It uses vision AI (via OpenRouter) to extract systolic and diastolic values directly from photos of handwritten blood pressure logs. Readings can be reviewed and corrected in an editable table, visualized as a bar chart with clinical threshold indicators, emailed as a PDF attachment, and exported as a formatted PDF report.
This project started because someone I know needed an easier way to track blood pressure, and it was a great opportunity to learn how to integrate LLMs through APIs into a real app.
Starting from a handwritten blood pressure log like this one (notes can be messy, the vision AI handles it as long as systolic/diastolic values are readable, optionally grouped by date or time of day):
The app extracts, analyzes, and visualizes your readings in 6 steps:
| Step | What happens |
|---|---|
| 1. Configure Settings | Create an OpenRouter account, get an API key, and choose from 5 vision AI models |
| 2. Set Thresholds | Choose clinical guidelines (ACC/AHA, ESC/ESH) or set custom BP thresholds |
| 3. Upload Photo | Take a photo, upload from gallery, or enter readings manually |
| 4. Review Readings | Review, edit, or add readings extracted by the AI |
| 5. View Statistics | Mean, SD, and daily breakdown with threshold color coding |
| 6. Charts & Export | Visual charts and exportable PDF or email reports |
- Photo extraction: Upload a photo of a BP monitor — a vision model extracts readings automatically
- Model selection: Choose from Gemini Flash 3.0 (default), Gemini 3.1 Flash Lite, GPT-5.4, Mistral Medium 3.1, or Claude Sonnet 4.6 via OpenRouter
- Review & edit: Editable table to correct or add readings manually
- Statistics: Mean and standard deviation per day and overall
- Threshold presets: ACC/AHA, ESC/ESH, Conservative, or custom thresholds for flagging readings
- Chart: Grouped bar chart with hypertension threshold indicators
- PDF export: Generate and download a formatted PDF report
- Email report: Send the PDF report as an email attachment via Resend
- Usage tracking: Monitor API usage and estimated cost
- PWA: Installable as a Progressive Web App with offline-capable service worker
- Node.js 18.18+
- An OpenRouter API key (entered in the app's Settings page)
- (Optional) A Resend API key for email reports (server-side env var)
git clone https://github.com/Thiebauts/MeanBloodPressure.git
cd MeanBloodPressure
npm installCreate a .env.local file in the project root (only needed for email export):
RESEND_API_KEY=re_... # optional, only needed for email export
The OpenRouter API key is entered directly in the app's Settings page and stored in your browser. Get a key at openrouter.ai/keys.
npm run dev
# Open http://localhost:3000Upload a photo of a blood pressure monitor, review the extracted readings, then view statistics or export a report.
npm run lint # Run ESLint
npm run lint:fix # Auto-fix ESLint issues
npm run format # Format with Prettier
npm run format:check # Check formatting without writing
npm test # Run unit tests
npm run test:watch # Run tests in watch mode
npm run test:e2e # Run end-to-end tests (Playwright)
npm run build # Production buildMeanBloodPressure/
├── README.md
├── package.json
├── next.config.js
├── tsconfig.json
├── eslint.config.mjs
├── vitest.config.ts
├── playwright.config.ts
├── docs/images/ # Screenshots and mockups for README
├── src/
│ ├── app/ # Next.js App Router pages & API routes
│ │ ├── page.tsx # Home page (upload & scan)
│ │ ├── review/ # Review & edit extracted readings
│ │ ├── results/ # Statistics, chart & Chart component
│ │ ├── settings/ # BP thresholds and model settings
│ │ └── api/ # Server-side API routes (extract, email)
│ ├── components/ # Shared React components (ErrorBoundary)
│ └── lib/ # Shared utilities (models, stats, store, settings, usage, pdf, types)
├── tests/ # Unit tests (Vitest)
├── e2e/ # End-to-end tests (Playwright)
└── public/ # Static assets & PWA manifest
- Framework: Next.js 15 (App Router), React 19
- Language: TypeScript 5.9
- AI: OpenRouter API — Gemini Flash 3.0 (default), Gemini 3.1 Flash Lite, GPT-5.4, Mistral Medium 3.1, Claude Sonnet 4.6
- PDF: jsPDF
- Email: Resend
- PWA: Service worker + Web App Manifest
- Linting: ESLint (typescript-eslint) + Prettier
- Testing: Vitest, Playwright
- Build: Standalone Next.js (
output: "standalone")
To find the best vision model for BP extraction, I built a dedicated benchmarking app — bp-ocr-bench — that evaluates 18 models across providers (Google, OpenAI, Anthropic, Mistral, Qwen, ByteDance, Nvidia) on accuracy, cost per request, and computation time.
Gemini 3 Flash and Gemini 3.1 Flash Lite sit on the Pareto frontier: they achieve 95–98% extraction accuracy at ~$0.001/request with the fastest response times. This is why they are the default models in the app. The other models (GPT-5.4, Mistral Medium 3.1, Claude Sonnet 4.6) remain available as alternatives.
This is a personal project. Commit messages follow Conventional Commits.
MIT



