leehyunhong
All projects
HAIM

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