
Clinical Research · Preventive Care
HAIM HAIM
Stroke-risk prediction and personalised health-management research app with a researcher console
- Client
- Kangwon National University × HB Connect
- Period
- Jul 2026 — Sep 2026 · About 6.5 weeks
- Role
- Sole developer with full ownership of app, server and admin web
- Team
- 1 engineer (me) + product owner + research team
- Platforms
- Android · Web (Admin)
- Status
- v1.0.1 Play Store build complete, distributed to study participants
~40K lines
Total code
44%
Test ratio · app
282
Commits
6.5 weeks
Timeline
18
SQL migrations
Overview
Project overview
HAIM is a research healthcare app that predicts stroke risk for study participants aged 60 and over and supports 12 weeks of personalised health management. The research team's risk algorithm (ver. 2), delivered as slides and spreadsheets, was ported to TypeScript and verified, and health-checkup FHIR files from Korea's 'My Health Record' are imported to derive risk factors.
It provides self-measured blood pressure, glucose and weight, Health Connect workout records, exercise prescriptions via the onsim server, 30 episodes of health-education card news with quizzes and goal-adherence tracking, while a researcher console manages pseudonymised datasets, statistics and access logs. App, server, admin web, deployment pipeline and backups were completed in 6.5 weeks.
My Role
Responsibilities
- 01Flutter app design and implementation (72 files, 16K lines) plus 43 test files, 7K lines (~44% of production code)
- 02NestJS server (125 files, 16K lines) — risk engine, FHIR parsers, role-based access control, field encryption
- 03React + Vite admin web — participant management, CSV export, statistics dashboard, access logs, survey publishing
- 0418 Supabase (PostgreSQL) migrations and a Firestore dual-store design
- 05Native Kotlin Health Connect channel for Android
- 06Docker → AWS ECR/ECS CD, Firebase Hosting, S3 backups (pg_dump + incremental Firestore)
Features
Key features
Stroke-risk scoring engine
Survey + raw health-checkup data → period-based (P1–P5) risk factors A–H → probability per combination. log-LR probability tables (812 KB male / 527 KB female) embedded as JSON.
FHIR health-checkup import
Users pick a 'My Health Record' FHIR JSON → parse → review per-visit charts → submit. LOINC PHR and MyHealthWay parsers.
Health records and trend charts
Blood pressure, glucose, weight and waist time series with 4- and 12-week line charts including baselines.
Health Connect workouts
Device-measured sessions read through a native channel without a plugin, de-duplicated by {source}-{start time}.
onsim-linked exercise prescription
App → HAIM server → onsim server to issue and re-issue prescriptions. The app keeps working when the external service is down.
Health education and adherence
30 episodes of card news unlocked per round with view logs and quiz responses, 84-day goal adherence, satisfaction and usability surveys, FCM push.
Researcher console
Participant list / detail / CSV, pseudonymised research dataset, statistics dashboard, access audit log, admin permission history, three roles.
Architecture
Design principles
Dual store against re-identification
Supabase holds only study codes and internal uuids (no names); names and birth dates in Firestore are encrypted server-side with AES-256-GCM (keys in AWS SSM). Only the server holds the mapping between the two.
Three-point API contract sync
App submission.dart ↔ server contract.ts ↔ DB check constraints, documented as 'change one, change all three'.
Closed-by-default role guard
A single ROLE_ACCESS table governs access; endpoints without @Roles block everyone.
Legal requirements as code
Admin permission changes are recorded by DB trigger (Korean personal-data safety standard art. 5, 3-year retention) and the server key's update/delete rights are revoked, making records tamper-proof. The privacy policy is a web link (PIPA art. 30).
Designed for users 60+
A single global text scale (kTextScale = 1.3), large touch targets and no dark mode — target-user assumptions enforced as code rules.
Challenges
Engineering challenges
A verifiable port of the research algorithm
Problem
The risk model existed only as slides and spreadsheets, with no guarantee that app and server computed the same result.
Solution
Locked in tests: all 20 worked examples match, male/female probabilities match to nine decimals, and 300 random cases cross-validate against a Python reference within 1e-12. Sample-CSV regeneration is scripted whenever the module changes.
Result · Researchers can trust the results
Protecting an unpublished research model
Problem
Even a pure calculator endpoint can leak the probability tables to anyone who observes enough responses.
Solution
Authentication is enforced on the calculator endpoint as well.
A Health Connect plugin that swallows errors
Problem
The health plugin forcibly read distance, calories and steps with every session and returned zero records silently when permissions were missing.
Solution
Replaced it with a native channel that reads only what is needed and propagates errors, removing the unnecessary BLE dependency at the same time.
Data-quality guards
Problem
Partial syncs could overwrite existing records and back-dated entries could be typed from memory.
Solution
Workouts apply only when total duration grows (ignored requests still return 200 + applied:false), daily records accept only today and yesterday, and periods without a checkup are always recorded as 'not examined' so the risk input is complete.
Firebase plugin version coupling
Problem
Adding firebase_messaging bumped core, and auth could no longer find the customAuthDomain symbol — the APK build broke in a way flutter test never catches.
Solution
Pinned a compatible version set and documented the cause and recurrence conditions in pubspec comments.
Korean IME and Android 12 splash
Problem
Name validation failed mid-composition of Hangul syllables, and splash assets stretched vertically under the Android 12+ spec.
Solution
Validation moved to the 'Next' tap, and a script generates spec-compliant splash images per size.
Tech Stack
Technology
App
- Flutter 3.44 · Dart 3.12
- InheritedWidget DI + ValueNotifier (no state library)
- Firebase Auth · Messaging
- file_selector (FHIR JSON)
- clock (time control in tests)
- Kotlin Health Connect channel
Server
- NestJS 11 · TypeScript 5.7
- Supabase (PostgreSQL) · Firestore
- AES-256-GCM field encryption (AWS SSM)
- Jest · Supertest, 32 spec files + e2e
- Docker · AWS ECR/ECS · GitHub Actions
Admin · Ops
- React 18 · Vite 6 · react-router 6
- Firebase Hosting
- S3 backups (pg_dump + 48h incremental Firestore)
- lint · build · test · e2e required before every commit
Outcomes
Outcomes
- ✓App, server, admin web, CD and backup pipeline completed in 6.5 weeks; Play Store build (1.0.1) delivered
- ✓Scoring engine cross-validated against the research algorithm to 1e-12 precision
- ✓Korean privacy law and safety standards implemented through DB triggers and permission design
Next Project
onsim 온심