---
title: BeatSense
emoji: π©Ί
colorFrom: blue
colorTo: indigo
sdk: docker
app_port: 7860
pinned: false
license: mit
short_description: Cardiovascular risk workstation (FastAPI + Next.js)
---
BeatSense β Cardiovascular Risk Intelligence
Scan to open the Hugging Face Space
Β·
> **BeatSense** is a cardiovascular risk assessment platform born from a question: what would a coronary risk tool actually feel like if it were built for the nurses station of a real hospital, instead of for a notebook demo?
>
> Around a trained scikit-learn model (918 patients, twelve clinical features), it ships three coordinated surfaces: a FastAPI service that exposes the model and persists every assessment, a Next.js 16 clinical workstation with sidebar navigation, dark mode, English and Spanish, and a result rendered as a printable thermal-style ticket ready to clip to the patient record, plus a legacy Streamlit dashboard kept alive for quick demos. The model itself is a Logistic Regression with a custom 0.4 decision threshold, picked because in cardiology a false negative is far more dangerous than a false positive, an interpretable choice that recovers 94% of true positives.
>
> The repo is end-to-end: notebooks for EDA and training, a typed REST API, a production-grade clinical UI, internationalization with zero hardcoded strings, and theme tokens out of the box.
---
## Table of Contents
| | |
|---|---|
| Overview | Live Demo |
| Features | Architecture |
| Project Structure | Tech Stack |
| Dataset | Model Selection |
| Getting Started | Make Targets |
| API Reference | Frontend Highlights |
| Internationalization | Theming |
| Testing | Docker |
---
## Overview
**BeatSense** predicts the probability of coronary heart disease from clinical patient data. It ships three independent surfaces sharing the same trained model:
- A **FastAPI** REST backend that wraps the production model (models/modelo_coronario.pkl).
- A **Next.js 16** clinical workstation UI β sidebar, ticket-style report, dark mode, EN/ES β for nurses and clinicians.
- A legacy **Streamlit** dashboard (still operational) for quick demos.
The trained model is a **Logistic Regression** with a custom decision threshold of 0.4, chosen because in cardiology the cost of a false negative (missing a sick patient) far exceeds that of a false positive.
---
## Live Demo
---
## Features
| Surface | Highlights |
|---|---|
| π€ **ML pipeline** | EDA + survival analysis (KaplanβMeier, Cox), preprocessing, 4 models compared, Optuna tuning, full notebooks. |
| π **REST API** | FastAPI service with /predict, /history, /stats, /eda, /model-info, persistent CSV history, CORS enabled. |
| π©Ί **Clinical UI** | Next.js 16 + Tailwind v4 + shadcn-style components. Sidebar navigation, nurse-station header, KPI dashboard, filtered patient table, EDA charts, model insights. |
| π¨οΈ **Printable ticket** | Risk result rendered as a thermal-printer ticket: monospace font, dashed dividers, barcode, window.print(). |
| π **i18n** | English (default) + Spanish, zero hardcoded strings, JSON dictionaries, cookie + Accept-Language detection. |
| π **Dark mode** | First-class next-themes integration, dark by default, no flash on first paint. |
| π¦ **Containerized** | Docker / docker-compose for the Streamlit surface. |
| π οΈ **Dual toolchain** | uv for Python, pnpm for the Next.js workspace. One make dev boots both servers. |
---
## Architecture
```
βββββββββββββββββββββββββββ HTTPS/REST ββββββββββββββββββββββββββ
β Next.js 16 frontend β βββββββββββββββββΆ β FastAPI service β
β β’ App Router β β β’ /predict β
β β’ Server + Client β β β’ /history /stats β
β Components β β β’ /eda /model-info β
β β’ TanStack Query β βββββββββββββββββ β β’ Loads .pkl + scaler β
βββββββββββββββ¬ββββββββββββ JSON βββββββββββββ¬βββββββββββββ
β β
β static assets β joblib.load
βΌ βΌ
ββββββββββββ βββββββββββββββββββββββ
β /public β β models/ β
β logu1.pngβ β β’ modelo_coronario β
ββββββββββββ β β’ scaler_coronario β
βββββββββββββββββββββββ
β²
β persists
β
βββββββββββ΄ββββββββββββ
β docs/ β
β historial_pacientes β
ββββββββββββββββββββββββ
```
---
## Project Structure
```
BeatSense/
βββ api/ # FastAPI backend
β βββ main.py # /predict, /history, /stats, /eda, /model-info
β
βββ app.py # Legacy Streamlit app (still works)
β
βββ frontend/ # Next.js 16 clinical workstation
β βββ public/
β β βββ logu1.png # Logo (auto-cropped for tight framing)
β βββ src/
β β βββ app/
β β β βββ layout.tsx # Root layout β ThemeProvider lives here
β β β βββ globals.css # Tailwind v4 + design tokens + print CSS
β β β βββ [lang]/ # Locale-scoped routes
β β β βββ layout.tsx # CRM shell (Sidebar + Header) + Providers
β β β βββ page.tsx # Dashboard (KPIs + recent + pie chart)
β β β βββ new-evaluation/ # Clinical form β ticket result
β β β βββ patients/ # Filterable history table + CSV export
β β β βββ eda/ # Dataset analytics (Recharts)
β β β βββ model/ # Model metrics + comparison
β β β βββ settings/ # Language + theme switcher
β β βββ components/
β β β βββ ui/ # Button, Card, Input, Label, Select, Badge
β β β βββ layout/ # Sidebar, Header, PageHeader, toggles
β β β βββ forms/ # EvaluationForm (RHF + Zod)
β β β βββ ticket/ # Printable result ticket
β β β βββ charts/ # EDA charts
β β β βββ dashboard/ # Dashboard client widgets
β β β βββ patients/ # Patient table
β β β βββ model/ # Model insights
β β β βββ settings/ # Settings panel
β β β βββ providers.tsx # ThemeRoot + I18nProvider + QueryClient
β β βββ i18n/
β β β βββ config.ts # Locale list + defaultLocale
β β β βββ context.tsx # Client-side useT() hook
β β β βββ dictionaries.ts # Server-only loader
β β β βββ dictionaries/
β β β βββ en.json
β β β βββ es.json
β β βββ lib/
β β β βββ api.ts # Typed REST client (TanStack-friendly)
β β β βββ utils.ts # cn() helper
β β βββ proxy.ts # Next 16 middleware: locale routing
β βββ package.json
β βββ tsconfig.json
β
βββ data/ # Heart Disease dataset (918 records)
β βββ heart.csv # Raw
β βββ heart_procesado_g.csv # Processed by Gema
β βββ heart_procesado_i.csv # Processed by Isrodam
β βββ heart_processed_R.csv # Processed by Roberto
β
βββ models/ # Persisted scikit-learn artifacts
β βββ modelo_coronario.pkl
β βββ scaler_coronario.pkl
β βββ model_R.pkl
β
βββ notebook/ # EDA + training notebooks
β βββ eda_heart_disease_p.ipynb # EDA + survival analysis (Paloma)
β βββ eda_heart_failure_g.ipynb # EDA (Gema)
β βββ eda_heart_failure_i.ipynb # EDA (Isrodam)
β βββ eda_heart_failure_R.ipynb # EDA (Roberto)
β βββ entrenamiento_modelo_final.ipynb
β βββ entrenamiento_modelo_g.ipynb
β βββ entrenamiento_modelo_i.ipynb
β
βββ report/ # Evaluation reports (PDF)
βββ docs/historial_pacientes.csv # Patient history (written by /predict)
βββ src/img/qr_app.png # QR code to live demo
βββ assets/ # Static assets (lottie, etc.)
βββ .streamlit/ # Streamlit theming
βββ Makefile # uv + pnpm orchestration
βββ dockerfile
βββ docker-compose.yml
βββ pyproject.toml # uv-managed Python deps
βββ uv.lock
```
---
## Tech Stack
### Backend / ML
- **Python 3.11** managed with **uv**
- **FastAPI** + **Uvicorn** β REST API
- **scikit-learn** β Logistic Regression, RF, KNN; StandardScaler
- **XGBoost** β alternative model evaluated
- **Optuna** β hyperparameter search
- **pandas / numpy** β data wrangling
- **joblib** β model persistence
- **Streamlit** β legacy interactive dashboard
- **pytest** β tests
### Frontend
- **Next.js 16** (App Router, Turbopack, Server Components)
- **React 19** + **TypeScript 5**
- **Tailwind CSS v4** with custom design tokens
- **shadcn-style UI** built on **Radix UI** primitives
- **TanStack Query 5** β server-state caching
- **React Hook Form 7** + **Zod 4** β typed forms with runtime validation
- **Recharts** β analytics charts
- **next-themes** β dark/light theme
- **next-intl-style i18n** using Next.js native dictionaries
- **lucide-react** β icon set
- **pnpm 10** β workspace package manager
### DevOps
- **Make** β single command interface
- **Docker / docker-compose** β containerised Streamlit deployment
- **GitHub Actions** β CI/CD
- **Render** β production hosting (Streamlit demo)
---
## Dataset
918 records, 12 features:
| Variable | Description | Type |
|---|---|---|
| Age | Patient age | Numeric |
| Sex | Sex (M/F) | Categorical |
| ChestPainType | Type (ATA, NAP, ASY, TA) | Categorical |
| RestingBP | Resting blood pressure (mm Hg) | Numeric |
| Cholesterol | Serum cholesterol (mg/dl) | Numeric |
| FastingBS | Fasting blood sugar > 120 mg/dl (0/1) | Binary |
| RestingECG | Resting ECG result | Categorical |
| MaxHR | Max heart rate achieved | Numeric |
| ExerciseAngina | Exercise-induced angina (Y/N) | Binary |
| Oldpeak | ST depression | Numeric |
| ST_Slope | ST segment slope | Categorical |
| HeartDisease | **Target** β heart disease (0/1) | Binary |
The production model uses **12 engineered features** (Age, Sex, MaxHR, FastingBS, ExerciseAngina, one-hot of ChestPainType, one-hot of ST_Slope). Only Age and MaxHR are scaled.
---
## Model Selection
| Model | Accuracy | Recall (class 1) | ROC-AUC | Notes |
|---|---|---|---|---|
| **Logistic Regression** *(threshold 0.4)* | **88 %** | **0.94** | **0.926** | β
Selected |
| Random Forest | ~87 % | ~0.88 | ~0.91 | Higher raw accuracy, lower recall |
| K-NN | ~82 % | ~0.83 | ~0.87 | Scale-sensitive |
| XGBoost | ~86 % | ~0.87 | ~0.90 | Strong, less interpretable |
### Why Logistic Regression?
1. **Highest recall (0.94)** β at threshold 0.4 it catches 94% of truly sick patients. A false negative in cardiology is far worse than a false positive.
2. **No overfitting** β train/test gap of only 0.007. 5-fold CV: 84.74% Β± 2.91%.
3. **Clinical transparency** β linear coefficients let clinicians see exactly which features drive each prediction.
> Full analysis:
>
>
>
---
## Getting Started
### Prerequisites
- Python >=3.11, <3.13
-
-
- Node.js >=20
### Install
```bash
# Python deps
make install
# Frontend deps
make frontend-install
```
### Run everything (recommended)
```bash
make dev
```
This spawns:
### Run individually
```bash
make api # FastAPI only
make frontend # Next.js only
make streamlit # Legacy Streamlit dashboard
```
---
## Make Targets
| Target | Purpose |
|---|---|
| make install | uv sync β install Python deps |
| make frontend-install | pnpm install β install frontend deps |
| make dev | Run **both** FastAPI + Next.js in parallel |
| make api | Run FastAPI only (uvicorn :8000) |
| make frontend | Run Next.js only (pnpm dev :3000) |
| make frontend-build | Production build of Next.js |
| make streamlit | Run legacy Streamlit app |
| make docker-up / docker-down / docker-build / docker-logs | Docker Compose lifecycle |
| make jupyter | Launch JupyterLab |
| make test | Run pytest |
| make clean | Remove caches and .next/ |
---
## API Reference
Base URL
| Method | Path | Description |
|---|---|---|
| GET | /health | Liveness + model load status |
| POST | /predict | Run cardiovascular risk prediction |
| GET | /history | Return all saved patient assessments |
| GET | /stats | Aggregate KPIs (totals, high-risk %, today) |
| GET | /eda | Aggregated EDA payload for charts |
| GET | /model-info | Metrics of the selected model + alternatives |
### POST /predict payload
```json
{
"patient_id": "P-001",
"age": 55,
"sex": "M",
"chest_pain_type": "ASY",
"max_hr": 130,
"fasting_bs": 0,
"exercise_angina": "Y",
"st_slope": "Flat",
"resting_bp": 140,
"cholesterol": 240,
"resting_ecg": "Normal",
"oldpeak": 1.2
}
```
Response:
```json
{
"patient_id": "P-001",
"timestamp": "2026-06-08T10:32:41",
"probability": 94.28,
"classification": "HIGH",
"threshold": 40.0,
"probabilities": [0.057, 0.943]
}
```
---
## Frontend Highlights
- **/[lang]** Clinical dashboard β KPI cards, recent assessments, risk distribution pie.
- **/[lang]/new-evaluation** Clinical form in three sections (Identification, Vitals, Symptoms/ECG) with RHF + Zod validation. On submit, the answer renders as a printable **ticket** (window.print() switches the print stylesheet so only the ticket prints, like a thermal receipt).
- **/[lang]/patients** Searchable, filterable history table (high / low / all). CSV export client-side.
- **/[lang]/eda** Charts: age histogram, sex distribution, chest pain distribution, scatter Max HR vs Age coloured by outcome.
- **/[lang]/model** Selected model card, comparison bar chart, rationale paragraph.
- **/[lang]/settings** Language + theme switcher.
The layout is a **CRM/EHR shell** with a sticky dark sidebar (logo + navigation + institution badge), a header bar with patient search, shift indicator, language switch, theme toggle and a faux nurse-station avatar.
---
## Internationalization
- Built on **Next.js 16 native dictionaries** (app/[lang]/...).
- All UI strings live in frontend/src/i18n/dictionaries/{en,es}.json.
- Locale is resolved in this order by frontend/src/proxy.ts:
1. locale cookie (set by the language switcher),
2. Accept-Language header,
3. fallback to **English**.
- useT() exposes the dictionary to client components.
Add a new locale by creating {xx}.json, adding xx to locales in src/i18n/config.ts, and registering it in src/i18n/dictionaries.ts.
---
## Theming
- Dark mode is the **default**.
- Theme tokens are defined as CSS variables in globals.css (:root for light, .dark for dark) and bridged to Tailwind v4 via @theme inline.
- ThemeProvider is mounted at the **root layout** to avoid hydration issues with next-themes.
---
## Testing
```bash
make test # pytest
cd frontend && pnpm build # type-check + production build
```
---
## Docker
```bash
make docker-up # Build and run Streamlit container
make docker-logs # Stream logs
make docker-down # Stop
```
> Containerising the Next.js + FastAPI duo is on the roadmap.
---