ArogyaSync Platform
Clinical data sync from bedside vitals to an audit trail.
API Uptime
API Latency (p95)
Integrated Services
Tech Stack
The problem
Clinics in rural India were recording patient health data on paper. When patients moved between clinics or lost their records, doctors had no history to work from. The system had to work with unreliable internet and keep insurance claims tied to tamper-proof records.
The challenge
Rural and semi-urban clinics were generating patient vitals data on paper, which led to transcription errors, sync delays of 12 to 24 hours, and data loss during network outages. The platform had to handle concurrent writes from hundreds of field devices and deliver them while offline. Insurance verification also had to compare live patient data with insurer CSV registries and keep an immutable audit trail.
Architecture & System Design

Multi-service cloud deployment: REST API handles clinical data management with real-time streaming to dashboards. Blockchain service anchors medical records for immutable audit trails. Mobile app uses offline-first local storage with automatic sync when reconnected. Separate service for insurance eligibility verification against CSV registries.
The platform is built as a set of independently deployable services running on AWS EC2 (Mumbai, ap-south-1). The Go/Gin REST API handles clinical CRUD, real-time WebSocket streaming, and authentication. A Python Flask blockchain service anchors record hashes to Polygon Amoy testnet for tamper-proof audit trails. A separate Python Flask insurance service processes insurer CSV files and performs eligibility cross-checks with scheduled APScheduler jobs. The Flutter mobile app uses an offline-first sync queue (WorkManager equivalent + SQLite) to survive connectivity gaps. Three React dashboards (hospital, device management, insurance portal) provide real-time visualisation via WebSocket and REST polling.
Code Walkthrough
3-step walk-through of the production implementation. File paths and intent appear above each block.
- 01
Step 1 of 3
Real-time vitals fan-out
clinical-api/internal/handlers/ws.goDashboards used to poll the DB every few seconds. Now each one holds a single WebSocket subscription to its sensor, the vitals handler publishes once, and fan-out happens in-memory - zero extra DB reads. The subtle part is concurrency: gorilla/websocket forbids overlapping writes to the same connection, and the per-sensor client map has to tolerate concurrent subscribe/broadcast/unsubscribe without racing.
govar ( sensorClients = make(map[uint]map[*websocket.Conn]bool) sensorClientsMu sync.RWMutex // gorilla/websocket requires writes to a single conn be serialised. connWriteMu sync.Map // *websocket.Conn → *sync.Mutex ) // VitalsBroadcast is the wire format for live vitals. // PatientID and MACAddress are deliberately excluded so the channel // cannot be used to re-identify patients - PHI stays server-side. type VitalsBroadcast struct { HeartRate *int `json:"heartRate"` SpO2 *float64 `json:"spo2"` Temperature *float64 `json:"temperature"` RecordTime time.Time `json:"recordedTimeStamp"` DeviceID string `json:"deviceId"` } func BroadcastToSensorClients(sensorID uint, payload []byte) { // Snapshot the inner map under the read lock, then release it before // writing. Without this copy, a slow client could block new subscribers // and a concurrent delete would race the iteration. sensorClientsMu.RLock() inner, ok := sensorClients[sensorID] if !ok { sensorClientsMu.RUnlock() return } snapshot := make([]*websocket.Conn, 0, len(inner)) for c := range inner { snapshot = append(snapshot, c) } sensorClientsMu.RUnlock() for _, conn := range snapshot { if err := writeToConn(conn, websocket.TextMessage, payload); err != nil { evictClient(sensorID, conn) } } } // writeToConn acquires a per-connection mutex before writing. Without this, // a concurrent pinger + broadcast can corrupt the frame stream. func writeToConn(conn *websocket.Conn, msgType int, data []byte) error { mu, _ := connWriteMu.LoadOrStore(conn, &sync.Mutex{}) mu.(*sync.Mutex).Lock() defer mu.(*sync.Mutex).Unlock() conn.SetWriteDeadline(time.Now().Add(writeWait)) return conn.WriteMessage(msgType, data) }TakeawayFan-out is two snapshots - one of the client set, one of each connection's write lock - so the read path never blocks new subscribers and no write ever overlaps with the pinger.
- 02
Step 2 of 3
Offline-first sync queue with tempId rewiring
clinical_mobile_app/lib/data/services/offline_sync_service.dartField health workers operate in areas with unreliable network. Writes have to feel instant, so they go into a local cache first and the UI never waits. The subtlety is what happens when a worker creates a patient offline and then adds vitals for that patient - both actions sit in the queue, the patient has no real ID yet, and when connectivity returns we replay in order, rewriting the downstream references as the server hands out real IDs.
dartclass OfflineSyncService { final CacheService _cache; final http.Client _client; final LocalStorageRepository _localStorage; StreamSubscription<List<ConnectivityResult>>? _sub; void start() { _sub = Connectivity().onConnectivityChanged.listen((results) { if (results.isNotEmpty && !results.contains(ConnectivityResult.none)) { syncPendingActions(); } }); } Future<void> syncPendingActions() async { final pending = _cache.getPendingActions(); if (pending.isEmpty) return; final remaining = <Map<String, dynamic>>[]; final idMapping = <String, String>{}; for (final action in pending) { try { // Rewrite any tempId references with previously-mapped real IDs. final resolved = _applyIdMappings(action, idMapping); final result = await _processAction(resolved); if (result.clientError is AuthFailure) { // Token invalid - clear the queue and surface upstream. await _cache.updatePendingActions(const []); throw result.clientError!; } if (result.success && result.newId != null && action['tempId'] != null) { idMapping[action['tempId']] = result.newId!; } else if (!result.success) { remaining.add(action); } } catch (_) { remaining.add(action); // transient - retry next cycle } } await _cache.updatePendingActions(remaining); } Future<SyncResult> _processAction(Map<String, dynamic> action) async { // Never replay with the token captured at enqueue time - it may be expired. // Re-fetch a fresh token for every action, every replay. final freshToken = await _localStorage.getToken(); if (freshToken == null) return SyncResult(success: false); final headers = Map<String, String>.from(action['headers'] as Map? ?? {}); headers['Authorization'] = 'Bearer $freshToken'; return _dispatch(action, headers); } }TakeawayOffline queues are subtle - you have to rewrite tempIds across queued actions, always re-fetch auth tokens at replay time, and treat 401/403 as a terminal 'clear the queue' event rather than a retryable error.
- 03
Step 3 of 3
Hashing evidence and anchoring on Polygon
blockchain_anchor_service/blockchain_client.pyFor the insurance audit trail we needed immutable proof that the day's vital records existed and hadn't been altered, without putting any patient data on-chain. Each hourly CSV gets SHA-256'd, and only the 32-byte digest is posted to anchorBatch(timestamp, hash) on Polygon Amoy - so storage costs stay flat and the raw records stay in Postgres.
pythondef hash_file(file_path: str) -> str: """Streaming SHA-256 so large evidence files don't blow up RAM.""" sha256 = hashlib.sha256() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): sha256.update(chunk) return sha256.hexdigest() def anchor_to_contract(timestamp: str, file_hash: str) -> str: """Post a single hash to the on-chain anchor contract. Returns tx hash.""" w3 = get_w3() account = Account.from_key(get_private_key()) batch_bytes32 = Web3.to_bytes(hexstr=file_hash.removeprefix("0x")) if len(batch_bytes32) != 32: raise ValueError("hash must be exactly 32 bytes") # Nonce management: read under a lock using the 'pending' tx count so # two concurrent anchor jobs don't both grab the same nonce and collide. with nonce_lock: nonce = w3.eth.get_transaction_count(account.address, "pending") tx = get_contract().functions.anchorBatch(timestamp, batch_bytes32).build_transaction({ "chainId": POLYGON_CHAIN_ID, "from": account.address, "nonce": nonce, "maxFeePerGas": w3.to_wei(MAX_FEE_GWEI, "gwei"), "maxPriorityFeePerGas": w3.to_wei(MAX_PRIORITY_FEE_GWEI, "gwei"), "gas": 200_000, }) signed = w3.eth.account.sign_transaction(tx, get_private_key()) tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction) # Polygon Amoy sometimes delays the receipt - retry up to 3 times. for attempt in range(3): try: receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=60) break except Exception: if attempt == 2: raise time.sleep(5 * (attempt + 1)) if receipt.status != 1: raise RuntimeError(f"anchor tx reverted: {tx_hash.hex()}") return tx_hash.hex()TakeawayThree things that are easy to miss with web3.py - read the nonce from 'pending' (not 'latest') under a lock, handle receipt-fetch timeouts explicitly, and always check receipt.status != 1 to catch silent reverts.
Results
ArogyaSync is live across a pilot network of field health workers. The API sustains 99.9% uptime with p95 latency under 150ms. The blockchain anchor service has recorded thousands of immutable medical events on Polygon. The insurance portal handles bulk CSV verification jobs, cutting manual eligibility checks from days to minutes. The hospital dashboard provides real-time multi-patient vitals monitoring with historical chart exports.
Explore the product system
This case study covers the main product surfaces. Use the buttons to move between the dashboard, app, website, and brand work.
Product surfaces
A guided capture of the connected clinical platform.
Gallery & Demos
ArogyaSync IoT Module - Bedside Installation
The ArogyaSync hardware module physically mounted onto a bedside patient monitor. It captures vitals data directly from the monitor and transmits it wirelessly to the cloud platform.
ArogyaSync IoT Module - Live Vitals Capture
The IoT device clipped onto a running patient monitor displaying SpO₂ and heart rate. The module reads and streams real-time vitals to the hospital dashboard without manual data entry.
Flutter Mobile App - Offline-First Vitals Entry
Field health workers capture patient vitals offline; app syncs automatically when connectivity returns. Demonstrates WorkManager + SQLite queue rewiring tempIds with server-issued IDs.
Hospital Dashboard - WebSocket Real-Time Updates
Live patient vital streams updating without polling. Dashboard subscribes to sensor channels; vitals broker fan-outs in-memory with per-connection write serialization.
Click any image or video to expand · ← → keys navigate
Live Demos
More from ArogyaSync Technologies Pvt. Ltd.
Interested in this work?
I can walk through the architecture and code during an interview.

