백엔드 없이, 변명 없이: 당신을 팔아버리지 않는 Pain Tracker 만들기
Source: Dev.to
개요
Pain Tracker는 사용자의 기기에서만 실행되는 만성 통증 관리 앱입니다.
계정도 없고, 클라우드 동기화도 없으며, 텔레메트리도 없습니다—당신의 건강 데이터는 기기에 남아 있거나 존재하지 않습니다.
일반적인 “무료” 건강 앱은 계정을 만들고 데이터를 외부 서버에 동기화하도록 요구합니다. 이는 통증 수준, 약물 복용 기록, 최악의 날 등을 보험사, 데이터 유출, 혹은 법적 소환장에 노출시킬 수 있습니다. 통증 추적이 가장 필요한 사람들—만성 질환, 장애 청구, 근로자 보상 분쟁, 의료 트라우마를 겪는 사람들—은 이러한 위험에 가장 취약합니다.
저는 법정에서 제 건강 데이터가 악용된 경험이 있어 Pain Tracker를 만들었습니다. 이 앱은 당신이 데이터에 대한 통제권을 가집니다.
아키텍처
┌─────────────────────────────────────────────────────────────┐
│ YOUR DEVICE │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ React UI │ → │ Zustand │ → │ IndexedDB │ │
│ │ │ │ │ │ (Encrypted) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ PDF/CSV │ → Your doctor. Your call. │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
핵심 포인트
- 클라우드도, 서버도, 텔레메트리도 없습니다. 데이터를 수동으로 내보내지 않는 한 절대 기기를 떠나지 않습니다.
- Zustand with Immer는 불변 업데이트와 전체 감사 추적을 제공합니다.
- IndexedDB는 로컬에 암호화된 데이터를 저장합니다.
상태 관리
import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
export const usePainTrackerStore = create()(
subscribeWithSelector(
persist(
devtools(
immer((set) => ({
entries: [],
addEntry: (entryData) =>
set((state) => {
state.entries.push({
id: crypto.randomUUID(),
timestamp: new Date().toISOString(),
version: 1,
...entryData,
});
}),
}))
),
{
name: 'pain-tracker-storage',
storage: createJSONStorage(() => localStorage),
}
)
)
);
- 모든 항목에는 버전과 타임스탬프가 포함됩니다.
- 불변 업데이트 덕분에 언제든지 데이터가 어떤 모습이었는지 증명하기 쉽습니다.
클라이언트‑사이드 암호화
// Generate a fresh IV for each encryption
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
encryptionKey,
plaintextBytes
);
- Web Crypto API를 이용한 AES‑256‑GCM
- 외부 의존성 없음
- 복호화 시 HMAC 검증
- 비밀번호 기반 키는 150,000 PBKDF2 반복을 사용합니다(무차별 공격을 실용적으로 불가능하게 만드는 최소값).
전체 암호화 구현은 Part 2에서 다룹니다.
통증 급증에 대한 적응형 인터페이스
const activateEmergencyMode = useCallback(() => {
updatePreferences({
simplifiedMode: true,
touchTargetSize: 'extra-large', // 72 px
autoSave: true,
showMemoryAids: true,
});
}, [updatePreferences]);
위기 감지는 다음을 감시합니다:
- 통증 수준 급증 (≥ 7)
- 높은 오류율 (인지 흐림)
- 불규칙한 입력 패턴 (고통)
플레어가 감지되면 UI는 자동으로:
- 간소화된 레이아웃으로 전환
- 버튼 크기 확대
- 옵션 축소
- 모든 변경 시 자동 저장 활성화
모든 데이터는 기기에 남아 있으며 언제든 삭제할 수 있습니다.
전체 훅 구현은 Part 3에서 다룹니다.
섬유근육통 평가 (ACR 2016 기준)
export function calculateFibromyalgiaScore(
painLocations: string[],
symptomScores: SymptomScores
): FibromyalgiaAssessment {
const wpi = painLocations.length; // 0‑19
const sss =
symptomScores.fatigue +
symptomScores.wakingUnrefreshed +
symptomScores.cognitiveSymptoms; // 0‑12
const meetsCriteria =
(wpi >= 7 && sss >= 5) ||
(wpi >= 4 && wpi = 9);
return {
wpiScore: wpi,
sssScore: sss,
meetsFibromyalgiaCriteria: meetsCriteria,
assessmentDate: new Date().toISOString(),
};
}
- 검증된 평가를 제공하며(진단이 아님) 임상의에게 문서화를 지원합니다.
- “모두가 아프다”와 같은 모호한 설명을 대체하도록 설계되었습니다.
근로자 보상용 내보내기 (WCB)
interface WCBExportOptions {
format: 'csv' | 'json' | 'pdf';
dateRange: { start: Date; end: Date };
includeMetadata: boolean;
wcbClaimNumber?: string;
}
export async function exportForWCB(
entries: PainEntry[],
options: WCBExportOptions
): Promise {
const filtered = entries.filter(
(e) =>
new Date(e.timestamp) >= options.dateRange.start &&
new Date(e.timestamp)
읽어볼 만한 파일
src/services/EmpathyIntelligenceEngine.ts– 휴리스틱 통증 분석 (2,076 줄)src/services/EncryptionService.ts– 클라이언트‑사이드 암호화 구현src/components/accessibility/– 트라우마‑인식 훅src/stores/pain-tracker-store.ts– 감사 추적이 포함된 상태 관리
결론
아직 주거 불안정 상태이며, 계속 배포 중입니다.
유용한 부분을 가져가고, 개선하고, 더 나은 것을 만들어 보세요.