Powered by ABDM / NHA

Welcome back

Sign in with your ABHA credentials or staff email

or sign in with

Mobile OTP Login

Enter your registered ABDM mobile number

Verify OTP

Enter the 6-digit OTP sent to your registered mobile

OTP sent successfully!
Resend OTP in 30s
🔒 Secured by ABDM · NHA India · Skip for demo
⚠ You are offline — all data is saved locally and available
Install BP Monitor
Add to home screen for offline access
VLabsDev Logo
ABDM · ABHA v3 · FHIR R4 Compliant

Select a patient to view their dashboard.

Record new vital signs reading

Readings are stored as FHIR R4 Observation resources (LOINC 85354-9 — Blood pressure panel) conforming to the ABDM WellnessRecord profile.
LOINC: 8480-6 · UCUM: mm[Hg]
LOINC: 8462-4 · UCUM: mm[Hg]
LOINC: 8867-4 · UCUM: /min
SNOMED CT code
SNOMED CT code

BP classification (AHA/ACC 2017)

Normal
<120 / <80
Elevated
120–129 / <80
High S1
130–139 / 80–89
High S2
≥140 / ≥90
Crisis
>180 / >120

Select a patient to view history.

Add new patient

ABHA number and ABHA address are required for ABDM compliance

FHIR AdministrativeGender
✓ Valid
Unique 14-digit Ayushman Bharat Health Account number
e.g. ramesh.patel@abdm

Registered patients

📄 PDF Report Export

Generate a clinical PDF report for any patient — includes readings history, statistics, trend chart and ABDM compliance status

FHIR R4 Bundle Export

Export patient health records as HL7 FHIR R4 compliant WellnessRecord bundles per ABDM Implementation Guide v6.5.0

ABDM compliance checklist

VLabsDev · Build Documentation

How BP Monitor was built

Tech stack, architecture, database-to-FHIR mapping, CI/CD pipeline, and repository structure — everything your team needs to understand, maintain, and extend this system.

ABDM FHIR R4 Docker Swarm FastAPI + MariaDB GitLab CI/CD 99% OWASP Score
🧱 Full Technology Stack
Frontend
Vanilla JS PWA
Single-file HTML/CSS/JS. Chart.js charts. jsPDF for FHIR exports. Offline-capable.
No framework
API
FastAPI 0.111
Python 3.12. Async SQLAlchemy. Uvicorn workers. JWT auth via python-jose.
ASGI
Database
MariaDB 10.11
11 tables. InnoDB. Custom init SQL baked into Docker image for zero-touch init.
LTS
Reverse Proxy
nginx (unprivileged)
Alpine-slim. 0 critical CVEs. Serves SPA + proxies /api/* to FastAPI via rewrite.
Port 8080
Orchestration
Docker Swarm
5 services. api ×2 replicas. Rolling updates. Portainer management UI.
Production
TLS / Routing
Traefik v3
Auto Let's Encrypt via DigitalOcean DNS. inbound overlay network. myresolver.
Auto HTTPS
Monitoring
Prometheus + Grafana
Scrapes /metrics from FastAPI. Custom dashboard. Config baked into images.
Custom Images
CI/CD
GitLab CI/CD
5 stages. Builds 5 Docker images. Deploy via Portainer webhooks on main branch.
5 Images
Security
OWASP + Bandit
99% score. Grade A. HSTS, CSP, X-Frame-Options, Referrer-Policy headers.
Grade A
Standards
ABDM FHIR R4
NRCeS IG v6.5.0. WellnessRecord bundle. LOINC + SNOMED codes. ABHA identifiers.
NRCeS
Auth
JWT + bcrypt
30-min access tokens. ADMIN / CLINICIAN / PATIENT roles. Brute-force lockout.
Stateless
Alerts
Brevo (Sendinblue)
Email + SMS for crisis readings (systolic >180 mmHg). Webhook fallback. Optional.
Optional
🌐 Production URLs
App
bpmonitor.vlabsdev.in
nginx → FastAPI → MariaDB
Grafana
bpm-grafana.vlabsdev.in
FastAPI metrics dashboard
GitLab Registry
registry.gitlab.com/vlabsdev
api · nginx · prometheus · grafana · mariadb
Server
VLabsDEV-Manager-001
DigitalOcean · Docker Swarm manager
🗺 System Mind Map
BP Monitor ABDM FHIR R4 Frontend PWA Vanilla JS · Chart.js Single HTML · Offline FastAPI 0.111 Python 3.12 · Uvicorn · JWT OWASP 99% · Grade A MariaDB 10.11 11 tables · InnoDB Custom init SQL image FHIR R4 WellnessRecord Bundle NRCeS IG v6.5.0 nginx unprivileged Alpine-slim · Port 8080 SPA + /api/* proxy Traefik v3 TLS · Let's Encrypt inbound overlay Prometheus + Grafana Scrapes /metrics · Dashboard bpm-grafana.vlabsdev.in GitLab CI/CD 5 stages · 5 images Portainer webhooks
🏊 Request Swimlane — BP Reading Submission
ActorStepSystemData / Output
👤 ClinicianOpens app, logs in with email + passwordFrontend PWAPOST /api/auth/token → JWT
🌐 nginxReceives POST, exact-match location = /api/auth/tokennginx configProxies to FastAPI :8000/auth/token
⚡ FastAPIValidates credentials, checks bcrypt hash (cost=12)auth.py · passlib · python-joseReturns JWT (30 min expiry, sub=user_id)
👤 ClinicianSelects patient, enters systolic/diastolic/pulse, body site, positionFrontend — Log Reading tabForm data collected in browser
🌐 nginxReceives POST /api/readings, rewrites to /readings via rewrite directivelocation /api/ rewrite ^/api/(.*)$ /$1Proxies to http://$api_host:8000
⚡ FastAPIValidates JWT, checks role (CLINICIAN/ADMIN), validates Pydantic schemamain.py · schemas.pyBPReadingCreate schema validated
⚡ FastAPIPersists reading to database via async SQLAlchemyorm_models.py · aiomysql driverINSERT into bp_readings
🗄 MariaDBStores reading rowbp_readings table · InnoDBsystolic, diastolic, pulse, body_site_code, position_code, loinc_bp, loinc_sys, loinc_dia, loinc_hr
⚡ FastAPIChecks for crisis BP (systolic >180 mmHg)alerts.py · Brevo APISends email/SMS alert if BREVO_API_KEY configured
⚡ FastAPIWrites audit log entryaudit_logs tableuser_id, action=CREATE, resource_type=BPReading, ip_address
📊 PrometheusScrapes /metrics endpoint every 15 secondsprometheus_fastapi_instrumentatorhttp_requests_total, http_request_duration_seconds by handler+method+status
👤 ClinicianExports FHIR bundle for patientFrontend — FHIR Export tabGET /api/fhir/{patient_id}
⚡ FastAPILoads patient + all readings from DB, builds WellnessRecord bundlefhir_builder.pyBundle with 1 Patient + N Observation resources, NRCeS profiles
⚕ Database → FHIR R4 Mapping

Every BP reading maps to a FHIR R4 WellnessRecord Bundle per NRCeS ABDM IG v6.5.0. The bundle wraps one Patient resource and one Observation per reading.

Patient Resource
patients table
patients.id
Patient.id
patient-{id}
patients table
patients.name
Patient.name[0].text
use: "official"
patients table
patients.gender
Patient.gender
male | female | other | unknown
patients table
patients.dob
Patient.birthDate
ISO 8601 date
patients table
patients.abha_number
Patient.identifier
system: healthid.ndhm.gov.in
type.code: "MR" (ABHA Number)
patients table
patients.abha_address
Patient.identifier
system: phraddress.abdm.gov.in
type.code: "XX" (ABHA Address)
patients table
patients.mobile
Patient.telecom[0]
system: "phone" · use: "mobile"
Observation Resource (one per BP reading)
bp_readings table
bp_readings.systolic
Observation.component[0]
valueQuantity.value · unit: mmHg
LOINC 8480-6 (Systolic)
bp_readings table
bp_readings.diastolic
Observation.component[1]
valueQuantity.value · unit: mmHg
LOINC 8462-4 (Diastolic)
bp_readings table
bp_readings.pulse
Observation.component[2]
valueQuantity.value · unit: /min
LOINC 8867-4 (Heart rate)
bp_readings table
bp_readings.body_site_code
Observation.bodySite
SNOMED CT code
368209003=R arm · 368208006=L arm · 57850000=Wrist
bp_readings table
bp_readings.position_code
Observation.extension
observation-bodyPosition
33586001=Sitting · 10904000=Standing · 102538003=Lying
bp_readings table
bp_readings.measured_at
Observation.effectiveDateTime
ISO 8601 with timezone
Generated at export time
bundle-{patient_id}-{timestamp}
Bundle (type: document)
profile: WellnessRecord
NRCeS IG v6.5.0 · nrces.in
📁 Repository Structure
  • 📁 bp_monitor_v7/
    • 📁 api/
      • main.pyAll routes, middleware, OWASP headers, Prometheus metrics
      • auth.pyJWT generation & validation, bcrypt password hashing
      • fhir_builder.pyDB → FHIR R4 WellnessRecord bundle construction
      • orm_models.pySQLAlchemy ORM: Patient, BPReading, User, Facility, AuditLog…
      • schemas.pyPydantic request/response schemas
      • database.pyAsync SQLAlchemy engine + session factory (aiomysql driver)
      • alerts.pyBrevo email/SMS + crisis webhook alerts (systolic >180)
      • DockerfileMulti-stage: builder (gcc, pip) + runtime (non-root appuser)
    • 📁 frontend/
      • index.htmlComplete SPA — all HTML, CSS, JS in one file. PWA manifest inline.
    • 📁 nginx/conf.d/
      • bp_monitor_prod.confSecurity headers, /api/* rewrite → FastAPI, explicit /api/metrics location
    • 📁 mariadb/init/
      • 01_schema.sqlCreates all 11 tables: patients, bp_readings, users, facilities, audit_logs…
      • 02_seed.sqlReference data: body sites, positions, LOINC codes
      • 03_admin_user.sqlDefault admin user with bcrypt hash
    • 📁 monitoring/
      • prometheus.ymlScrape config: bp-api:8000/metrics every 15s
      • grafana/datasources/prometheus.ymlAuto-provisioned Prometheus datasource
      • grafana/dashboards/bp_monitor_dashboard.jsonCustom dashboard: req/sec, latency, error rate, P99
    • nginx.DockerfileBuilds nginx+frontend from nginxinc/nginx-unprivileged:alpine-slim (0 CVEs)
    • prometheus.DockerfileBakes prometheus.yml into prom/prometheus image
    • grafana.DockerfileBakes datasource + dashboards into grafana/grafana image
    • mariadb.DockerfileBakes init SQL into mariadb:10.11 — zero-touch DB initialization
    • docker-stack.ymlDocker Swarm stack: 5 services, Traefik labels, overlay networks
    • .gitlab-ci.ymlCI: lint → test → build (5 images) → security (OWASP) → deploy
⚡ CI/CD Pipeline — 5 Stages
01
Lint
flake8 · bandit static analysis
02
Test
pytest · 69/70 tests · MariaDB service container
03
Build
5 Docker images → GitLab registry
04
Security
OWASP scan · 99% · Grade A
05
Deploy
Manual trigger · Portainer webhooks
api:latest
FastAPI — Python 3.12 multi-stage
nginx:latest
Frontend SPA + nginx config baked in
prometheus:latest
prometheus.yml scrape config baked in
grafana:latest
Datasource + dashboard JSON baked in
mariadb:latest
Init SQL scripts baked in — zero-touch DB init
🚀 Build Pipeline Simulator
Simulate CI/CD Pipeline Run
In production, triggered by git push to main on GitLab. Stage 5 requires manual approval.
🌐 Network Architecture
Internet :443
Traefik TLS termination
Let's Encrypt · myresolver
inbound overlay
Traefik → nginx :8080
External Swarm network
backend overlay
nginx→api:8000
api→mariadb:3306
prometheus→api:8000