제7장
바이오마커 결과는 검증 연구의 한 행이 되기 훨씬 전부터 식별 가능한 한 사람에 관한 임상 데이터이다. 이 장은 앞 장들에서 다룬 WIA-BIO-004 레코드가 실제 시스템 안에서 어떻게 이동하는지, 그리고 각 단계에서 표준이 어떤 프라이버시·보안 통제를 요구하는지를 설명한다.
제2장부터 제6장까지는 숫자를 올바르게 얻는 방법을 다루었다. 올바른 단위와 품질 플래그를 갖춘 측정 레코드, 정직하게 계산한 민감도와 특이도를 담은 검증 연구, 외부 검증을 통과하는 임상 상관 분석이 그것이다. 그러나 결과가 그것을 필요로 하는 임상의에게 도달하지 못하면 이 모든 작업은 쓸모가 없다. 또한 그 과정에서 데이터가 유출되거나, 참여자가 동의한 범위를 넘어 사용되거나, 익명성을 약속받은 사람에게까지 거슬러 추적될 수 있다면 받아들일 수 없다.
WIA-BIO-004는 이 문제를 두 곳에서 다룬다. 본 명세 §8.1은 적합한 시스템이 반드시 갖추어야 할 구성 요소를 열거하고, §9는 프라이버시와 보안 요구사항, 즉 HIPAA·GDPR에 따른 데이터 보호, 익명화 방법 목록, 데이터 교환용 보안 프로파일을 제시한다. 보안·프라이버시 부속서(네 번째 단계 문서로, 이 책에서는 WIA-BIO-004의 일부로 취급한다)는 여기에 바이오마커 고유의 비식별화 항목, 단계적 동의 모델, 암호화·접근 통제 규칙, 보존 기간, 프라이버시 영향평가가 필요한 조건을 더한다. 이 장은 이 요구사항들을 하나의 아키텍처로 묶고, 결과 하나를 그 아키텍처 전체에 통과시켜 본다.
§8.1은 WIA-BIO-004에 적합한 모든 시스템이 데이터 수집, 품질관리, 통계 분석, 임상 상관 분석, 보고의 다섯 구성 요소를 포함해야 한다고 규정한다. 명세는 이름만 제시할 뿐 배치 방식은 정하지 않는다. 실제로 이 구성 요소들은 하나의 데이터 경로를 따라 자연스럽게 나뉜다. 이 경로는 분석 장비에서 시작하여 두 곳에서 끝난다. 하나는 개별 결과가 진료에 쓰이는 환자 기록이고, 다른 하나는 많은 결과를 모아 바이오마커를 검증하거나 감시하는 연구 저장소이다.
| §8.1 구성 요소 | 책임 | WIA-BIO-004 산출물 | 다루는 곳 |
|---|---|---|---|
| 데이터 수집 | 단위·측정 방법·검체 종류·채취 및 측정 시각·코드화된 환자 참조와 함께 결과를 기록한다 | 측정 레코드(§7.1), SDK의 BiomarkerMeasurement | 제3장, 이 장 7.2절 |
| 품질관리 | 결과를 내보내기 전에 검체 전 단계 플래그, QC 런 상태, 단위 일관성, 시각 순서를 확인한다 | qualityFlags, 분석 프로토콜의 QC 기록 | 제5장, 이 장 7.2절 |
| 통계 분석 | 통합·비식별화된 데이터로 ROC, 민감도, 특이도, 예측도와 그 신뢰구간을 계산한다 | calculateROC, validateBiomarker, analyzeSensitivity | 제2장, 제4장 |
| 임상 상관 분석 | 동의 범위 안에서 결과를 결과 변수(병기, 생존, 치료 반응)와 연결한다 | 검증 연구 레코드(명세 §7.2), ClinicalCorrelation, PrognosticAnalysis | 제6장 |
| 보고 | 개별 결과를 EHR로 돌려보내고, 연구의 성능 보고서를 발행한다 | FHIR Observation, 성능 보고서(§5.3.1), BiomarkerReport | 제3장, 제4장 |
아래 그림은 이 장의 나머지 부분이 따라가는 데이터 경로이다. 두 가지 설계 결정을 분명히 해 둘 필요가 있다. 첫째, 식별 영역과 코드화(연구) 영역은 보안·프라이버시 부속서가 요구하는 대로 서로 다른 암호화 키를 쓰는 별도 저장소이다. 둘을 잇는 유일한 통로는 환자 식별자와 연구 코드의 대응표를 보관하는 가명처리 브리지이다. 둘째, 동의는 두 번 확인한다. 한 번은 레코드가 연구 영역으로 복사되는 브리지에서, 다른 한 번은 질의 시점에서이다. 복사가 끝난 뒤에도 동의는 철회될 수 있기 때문이다.
식별 영역 (키 K_id) 코드화·연구 영역 (키 K_coded)
------------------------------ -----------------------------------
분석 장비 --> LIS --> [1 데이터 수집] [3 통계 분석]
WIA JSON 생성 (§7.1) calculateROC / validateBiomarker
| ^
v |
[2 품질관리] 비식별화된 WIA 레코드
플래그, QC 런, 단위, 시각 ^
| |
+---------------+--------------+ +------------------------------+
v v | 가명처리 브리지 |
[5 보고: FHIR Observation] 임상 저장소 ------>| 동의 확인 #1 |
--> EHR (임상의) (환자 연결) | 식별자 제거, k-익명성 |
+------------------------------+
|
[4 임상 상관 분석]
연구 코드로 결과 변수 연결
질의 시점 동의 확인 #2
감사 기록(추가 전용, 해시 체인)은 두 영역의 모든 읽기와 쓰기를 기록한다.
표준은 어느 상자에도 특정 기술을 강제하지 않는다. 소규모 검사실은 구성 요소 1, 2, 5를 검사실 정보 시스템(LIS) 안에 구현하고 구성 요소 3을 예약 작업으로 돌릴 수 있다. 다기관 컨소시엄은 상자마다 별도 서비스를 둘 수도 있다. 표준이 고정하는 것은 각 화살표를 건너는 데이터의 모양이며, 제3장의 레코드 형식이 어떤 배치 선택보다 중요한 이유가 여기에 있다.
아키텍처를 구체적으로 이해하기 위해 C-반응성 단백(CRP) 결과 하나를 따라가 본다. 명세 §7.1의 측정 예시와 같은 종류의 결과로, 08:30 UTC에 채취한 혈청 검체를 10:45 UTC에 고감도 면역비탁법으로 측정하였고, 값은 12.5 mg/L, 참고구간은 0~10 mg/L이다. 아래 단계는 예시이며, 필드 이름은 표준과 @wia/bio-004 SDK의 것을 그대로 쓴다.
대부분의 분석 장비는 제조사 고유 형식이나 HL7 버전 2 결과 메시지로 LIS에 결과를 보낸다. 수집 구성 요소는 각 결과를 WIA-BIO-004 측정 레코드로 바꾼다. 이 단계에서 가장 자주 잃어버리는 것은 단위와 시각이다. 단위는 바이오마커마다 하나의 표준 단위로 변환하여 UCUM 코드로 기록해야 한다. 뒤따르는 통계는 숫자를 그대로 비교하기 때문이다. mg/dL로 보고된 CRP 1.25와 mg/L로 보고된 CRP 12.5는 같은 농도이지만, 둘이 섞인 검증 세트로는 의미 없는 ROC 곡선이 나온다. SDK는 단위를 대신 확인해 주지 않는다. validateBiomarker는 value와 diseaseStatus를 읽고, 단위는 경고 메시지에만 쓴다. 따라서 정규화는 바로 이 단계에서 해야 한다.
// 예시: biomarkerID별 표준 단위와 변환 계수.
// 차원을 바꾸지 않는 정확한 변환만 여기에 둔다.
const CANONICAL: Record<string, { unit: string; factors: Record<string, number> }> = {
'BIO-12345': { unit: 'mg/L', factors: { 'mg/L': 1, 'mg/dL': 10 } },
};
function normalise(rec: WiaMeasurementRecord): WiaMeasurementRecord {
const rule = CANONICAL[rec.measurement.biomarkerID];
if (!rule) throw new Error('No canonical unit declared for ' + rec.measurement.biomarkerID);
const f = rule.factors[rec.measurement.unit];
if (f === undefined) throw new Error('Unknown unit ' + rec.measurement.unit);
const m = rec.measurement;
return {
...rec,
measurement: {
...m,
value: m.value * f,
unit: rule.unit,
referenceRange: { ...m.referenceRange,
min: m.referenceRange.min * f, max: m.referenceRange.max * f, unit: rule.unit },
},
};
}
이 코드에 대해 두 가지를 짚어 둔다. 포도당을 mg/dL에서 mmol/L로 바꾸는 것처럼 차원이 바뀌는 변환은 몰질량에 의존하므로, 일반 계수표가 아니라 검토를 거친 별도 항목으로 다루어야 한다. 또한 참고구간은 값과 함께 변환해야 한다. 둘 중 하나만 변환하는 실수가 단위 버그가 시험을 빠져나가는 가장 흔한 경로이다.
결과를 내보내기 전에 품질관리 구성 요소는 제3장(3.8절)과 제5장의 규칙을 적용한다. 관문의 결과는 내보냄, 주석을 달아 내보냄, 보류 세 가지이다. 아래 표는 합리적인 규칙 집합의 예이다. 플래그가 붙은 검체에 주석을 달지 보류할지는 검사실 정책이다. 이 책이 채택한 규칙(제3장 규칙 R7)이 요구하는 것은 플래그가 붙은 검체를 제외하거나 주석을 다는 것이며, 깨끗한 검체와 조용히 섞지 않는 것이다.
| 확인 항목 | 실패 조건 | 관문 결과 | 연구 영역에 도달하는 것 |
|---|---|---|---|
| 필수 필드 | biomarkerID, value, unit, sampleType, dateCollected 중 누락 | 보류 | 수정될 때까지 없음 |
| 시각 순서 | dateMeasured가 dateCollected보다 이름 | 보류 | 수정될 때까지 없음 |
| 단위 일관성 | 정규화 후에도 값의 단위와 참고구간의 단위가 다름 | 보류 | 수정될 때까지 없음 |
| 검체 전 단계 플래그 | hemolyzed, lipemic, icteric, clotted 중 하나가 true | 주석을 달아 내보냄. 해당 검사가 영향을 받는 것으로 알려져 있으면 보류 | 플래그를 보존한 레코드. 기본적으로 주 분석에서 제외 |
| 런 QC | 분석 런이 웨스트가드 규칙(제5장)에 의해 기각됨 | 보류, 검체 재측정 | 승인된 재측정 결과만 |
| 처리 지연 | 채취부터 측정까지의 간격이 문서화된 안정성 한계를 넘음 | 주석을 달아 내보냄 | other 품질 플래그가 붙은 레코드 |
CRP 예에서는 채취부터 측정까지 2시간 15분이 걸렸고, 모든 플래그가 false이며, 런도 승인되었으므로 결과가 내보내진다. 값이 참고구간 상한을 넘는 것은 임상적 해석의 문제이지 품질 문제가 아니며, 결과는 그 해석을 담은 채로 전달된다.
보고 구성 요소는 내보내진 레코드를 HL7 FHIR Observation으로 변환한다(전체 필드 대응은 제3장에 있다). 통합에서 중요한 것은 값보다 리소스의 정체성이다. 인터페이스는 재시도를 하고, 네트워크는 응답을 잃어버린다. 두 번 전송된 결과는 차트 안에서 결과 두 개가 된다. 이에 대한 표준적인 방어는, 재시도해도 바뀌지 않는 필드로 결과마다 안정된 업무 식별자를 만들고, 그 식별자를 가진 리소스가 아직 없을 때만 서버가 생성하도록 조건부로 생성하는 것이다. FHIR는 조건부 생성(If-None-Exist 헤더)으로 이를 직접 지원한다.
POST [base]/Observation
If-None-Exist: identifier=urn:example:lab-789:results|LAB-789.BIO-12345.S-20250115-0042
{
"resourceType": "Observation",
"identifier": [{
"system": "urn:example:lab-789:results",
"value": "LAB-789.BIO-12345.S-20250115-0042"
}],
"status": "final",
"code": {
"coding": [{ "system": "http://loinc.org", "code": "(검사실 CRP 검사의 LOINC 코드)" }],
"text": "C-reactive protein, serum"
},
"subject": { "reference": "Patient/PATIENT-456" },
"effectiveDateTime": "2025-01-15T08:30:00Z",
"issued": "2025-01-15T10:45:00Z",
"valueQuantity": { "value": 12.5, "unit": "mg/L",
"system": "http://unitsofmeasure.org", "code": "mg/L" },
"interpretation": [{ "coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "H" }] }],
"referenceRange": [{
"low": { "value": 0, "unit": "mg/L", "system": "http://unitsofmeasure.org", "code": "mg/L" },
"high": { "value": 10, "unit": "mg/L", "system": "http://unitsofmeasure.org", "code": "mg/L" }
}]
}
식별자는 검사실, 바이오마커, 검체 접수 번호를 조합한 것이며, 이들은 모두 첫 시도 전에 확정된다. 시각 대응에도 주의해야 한다. effectiveDateTime에는 임상적으로 의미 있는 시각인 dateCollected를, issued에는 결과 발행 시각을 넣으며, 여기서는 dateMeasured를 썼다. LOINC 코드는 일부러 자리표시자로 두었다. 올바른 코드는 검사실이 실제로 수행하는 검사에 따라 달라지므로 책이 아니라 검사실 자체의 LOINC 매핑에서 가져와야 한다. 나중에 결과를 정정할 때는 같은 리소스의 status를 corrected로 바꾸어 갱신하며, 정정을 위해 새 리소스를 만들지 않는다.
환자의 동의가 바이오마커 발굴을 허용하면(7.4절), 브리지가 레코드를 연구 영역으로 복사한다. 브리지는 patientID를 연구 코드로 바꾸고, 7.3절의 비식별화 규칙을 적용하며, 복사할 때 적용된 동의서 버전을 기록한다. 환자 식별자와 연구 코드의 대응표는 식별 영역에 남는다. 코드화된 레코드는 통계에는 유용하지만 그 자체만으로는 사람에게 거슬러 올라갈 수 없다.
참조 표준 진단이 붙은 코드화 레코드가 충분히 모이면, 통계 분석 구성 요소가 이를 validateBiomarker의 입력으로 묶는다. 세 가지 변환이 필요하고, 각각이 알려진 오류의 원천이다. SDK의 BiomarkerValidation.measurements는 diseaseStatus를 불리언으로 기대하지만, 데이터 레코드는 문자열 열거형 diseased | healthy | at_risk | unknown을 쓰거나 알 수 없을 때 null을 쓴다. at_risk, unknown, null인 레코드는 명시적으로 제외해야 한다. 문자열을 그대로 넘기면 비어 있지 않은 모든 값이 질환군으로 계산되는데, 이것이 제4장에서 설명한 참 같은 값(truthy) 함정이다. 플래그가 붙은 검체는 기본적으로 제외한다. 그리고 SDK는 전체 측정값이 최소 100개여야 하며, 그렇지 않으면 내부 ROC 호출이 오류 B001을 던진다.
import { validateBiomarker, BIOMARKER_CONSTANTS } from '@wia/bio-004';
function toValidationInput(records: CodedRecord[]) {
const usable = records.filter((r) =>
(r.diseaseStatus === 'diseased' || r.diseaseStatus === 'healthy') &&
!hasAnyFlag(r.measurement.qualityFlags) &&
r.measurement.unit === 'mg/L'); // 표준 단위만
if (usable.length < BIOMARKER_CONSTANTS.MIN_VALIDATION_SAMPLES) {
return { ok: false as const, reason: 'B001: ' + usable.length + ' usable records' };
}
return {
ok: true as const,
input: {
biomarkerType: 'protein' as const,
measurements: usable.map((r) => ({
value: r.measurement.value,
unit: r.measurement.unit,
diseaseStatus: r.diseaseStatus === 'diseased', // 명시적 불리언
patientID: r.studyCode, // 임상 ID는 절대 쓰지 않음
})),
referenceRange: { min: 0, max: 10, unit: 'mg/L' },
prevalence: 0.10, // 예시: 의도된 사용 인구집단의 유병률
},
};
}
const prepared = toValidationInput(codedRecords);
if (prepared.ok) {
const result = validateBiomarker(prepared.input);
// result.performance 값은 백분율이며, PPV/NPV는 위의 유병률로 계산된다.
}
이 단계는 앞 장들과 두 가지 점에서 이어진다. prevalence 인자에는 연구의 환자 비율이 아니라 의도된 사용 인구집단의 유병률을 넣어야 한다. SDK는 PPV와 NPV를 개수가 아니라 이 값으로 계산하기 때문이다(제4장). 그리고 100개 확인을 SDK 호출 전에 하는 이유는, 표본이 모자란 데이터셋이 예외 대신 깔끔한 메시지를 내도록 하기 위해서이다.
아키텍처 그림의 모든 화살표는 재시도될 수 있으므로, 모든 소비자는 멱등적이어야 한다. EHR 쪽은 FHIR 식별자가 이를 처리한다. 연구 쪽에서는 코드화 레코드가 같은 업무 식별자에서 계산한 결정적 키를 가져야 한다. 예를 들어 브리지가 보관하는 키로 검체 접수 번호와 바이오마커 ID에 키 해시(HMAC-SHA256)를 적용할 수 있다. 키가 비밀이므로 접수 번호를 대입해 보는 방식으로 해시를 되돌릴 수 없고, 계산이 결정적이므로 다시 보낸 레코드는 중복되지 않고 덮어쓰인다. 검증 세트의 중복은 무해하지 않다. 같은 환자가 두 번 계산되면 표본 크기가 부풀고 모든 신뢰구간이 부당하게 좁아진다.
보안·프라이버시 부속서는 비식별화 규칙을 두 층으로 쌓는다. 첫 번째 층은 HIPAA 프라이버시 규칙의 Safe Harbor 방식(45 CFR 164.514(b)(2))이 정한 18개 식별자이다. 두 번째 층은 바이오마커 데이터에 고유한 네 항목으로, 부속서에서 19~22번으로 번호가 매겨져 있다. 두 층을 아래 표에 함께 보인다.
| 번호 | 제거하거나 변환할 식별자 | WIA-BIO-004 레코드에서 주로 나타나는 곳 |
|---|---|---|
| 1–3 | 이름, 주(州)보다 작은 지리 단위(우편번호 앞 세 자리에 대한 좁은 예외 있음), 개인과 관련된 날짜의 연도를 제외한 모든 요소 및 89세 초과 연령 | patient 블록, dateCollected, dateMeasured, patient.age |
| 4–11 | 전화번호, 팩스 번호, 전자우편 주소, 사회보장번호, 의무기록번호, 건강보험 수혜자 번호, 계좌 번호, 자격증·면허 번호 | 의무기록번호인 경우의 patientID, 자유 텍스트 필드 |
| 12–17 | 차량 식별자, 기기 식별자와 일련번호, 웹 URL, IP 주소, 생체 식별자, 얼굴 전체 사진 및 이에 준하는 영상 | metadata 안의 장비 일련번호, 영상 바이오마커, 감사 기록 |
| 18 | 그 밖의 고유한 식별 번호, 특징 또는 코드 | 검체 접수 번호, priorHistory 자유 텍스트 |
| 19 | 고유한 지문처럼 작동하는 희귀 바이오마커 값 | 소규모 코호트의 극단적 value |
| 20 | 희귀 변이의 정확한 대립유전자 빈도 | 유전 바이오마커, VCF INFO 필드 |
| 21 | 정밀한 측정값(고유하면 구간화) | 유효숫자가 많은 value |
| 22 | 개인에게 추적 가능한 배치·로트 번호 | metadata 안의 시약·검체 로트 |
바이오마커 고유 항목은 놓치기 쉬우므로 주의를 기울여야 한다. 검사값은 보통 식별자로 여겨지지 않지만, 데이터셋 안에서 단 한 사람만 가질 수 있는 값이라면 바로 식별자이다. 참여자 한 명에게서만 나타나는 대립유전자는 다른 경로로 그 사람의 유전체를 가진 누구에게나 그 참여자를 드러낸다. 검체 하나에만 쓰인 시약 로트는 연구 레코드를 검사실의 로트 대장과 연결한다. 이들 중 어느 것도 일반적인 의미의 이름이나 번호가 아니므로, 일반 임상 데이터용으로 설계된 비식별화 단계는 이를 놓친다.
명세 §9.2는 다섯 가지 방법을 열거한다. 아래 표는 이를 비교한다. 유용성 열은 우열 순위가 아니다. 방법마다 막는 연결 경로가 다르며, 실제 파이프라인은 여러 방법을 조합한다.
| 방법(§9.2) | 하는 일 | 남는 유용성 | 남는 위험과 참고 사항 |
|---|---|---|---|
| 직접 식별자 제거 | 이름, 기록 번호 등의 필드를 삭제하고 patientID를 연구 코드로 바꾼다 | 측정값의 분석 가치 전부 | 간접 식별자(나이, 날짜, 희귀 값)가 남는다 |
| 날짜 이동(±180일) | 한 사람의 모든 날짜를 같은 무작위 간격만큼 옮긴다 | 개인 안의 간격(채취→측정, 기저→추적)이 정확히 보존된다 | ±180일 범위는 WIA-BIO-004 자체의 정책이다. 이동된 전체 날짜도 여전히 날짜이므로 Safe Harbor만으로는 연도로 줄여야 하며, 날짜 이동은 보통 전문가 판정 방식으로 정당화한다. 계절 분석은 신뢰하기 어려워진다. |
| 지리적 집계 | 우편번호를 절삭하고 지역만 남긴다 | 지역 간 비교 | 작은 지역에서는 여전히 개인이 드러날 수 있다 |
| 희귀 값 억제 | 정해진 횟수보다 적게 나타나는 값을 삭제하거나 상·하한 코딩하고, 정밀한 값을 구간화한다 | 분포의 대부분 | 억제는 꼬리 통계를 편향시키며, 극단값이 임상적으로 가장 흥미로운 경우가 많다 |
| k-익명성(k ≥ 5) | 준식별자 조합마다 최소 다섯 개 레코드가 공유하도록 일반화한다 | 어떤 준식별자를 고르느냐에 달려 있다 | 다섯 레코드가 모두 같은 민감 값을 가지면 속성 노출을 막지 못한다. 이 경우를 위한 l-다양성 같은 보완 기법이 있다 |
CRP 예에서 코드화 레코드는 value 12.5 mg/L를 유지하고(일반 코호트에서는 희귀하지 않다), patientID를 연구 코드로 바꾸며, 55세를 50~59세 같은 연령대로 일반화하고, 두 시각을 같은 개인별 간격만큼 이동하여 2시간 15분의 처리 간격을 보존하며, 자유 텍스트 priorHistory는 삭제하고 분석에 필요하면 코드화된 질환으로 대체한다. 복용 약물 목록은 연구 프로토콜이 필요로 할 때만 남긴다. 드문 약물의 조합이 준식별자로 작동할 수 있기 때문이다.
유전 데이터는 농도 값과 달리 비식별화에 잘 굴복하지 않는다. 유전체는 고유하고, 유전되며, 아무것에도 동의한 적 없는 친족과 일부를 공유한다. 발표된 연구에 따르면, Y 염색체 표지자를 공개 계보 데이터베이스와 대조하여 성(姓)을 추정할 수 있는 경우가 있고(Gymrek 등, 2013), 충분히 많은 표지자가 공개되면 요약 대립유전자 빈도만으로 특정 개인이 통합 유전체 데이터셋에 포함되어 있는지를 탐지할 수 있다(Homer 등, 2008). WIA-BIO-004에는 이로부터 두 가지 실무적 결론이 나온다. 첫째, 희귀 변이의 대립유전자 빈도(20번 항목)는 한 사람의 기여가 드러나지 않을 만큼 큰 집단에서 집계한 형태로만 공개해야 한다. 둘째, 개인 수준의 유전 바이오마커 데이터는 식별자를 제거한 뒤에도 공개하지 말고, 데이터 이용 계약을 동반한 통제된 접근으로 공유해야 한다. 7.4절의 단계적 동의 모델이 공개 데이터베이스에 대한 스위치를 따로 두는 이유도 여기에 있다.
바이오마커 연구에서 동의는 한 번의 예·아니오로 끝나는 경우가 드물다. 환자는 자신의 진료를 위해 검체가 검사되는 것을 받아들이고, 그 결과가 새 바이오마커 평가에 쓰이는 데 동의하면서도, 상업적 이용이나 공개 데이터베이스 등록은 거부할 수 있다. 보안·프라이버시 부속서는 이를 단계적 동의 레코드로 모델링한다. 아래 예는 부속서의 구조를 따르되, 동의를 강제하려면 어떤 동의서 문안에 언제 동의했는지를 알아야 하므로 버전과 날짜 필드를 추가하였다.
{
"consentId": "BIOMARKER_CONSENT_001",
"consentTextVersion": "2.1",
"signedAt": "2025-01-10T09:12:00Z",
"withdrawnAt": null,
"permissions": {
"clinicalTesting": true,
"biomarkerDiscovery": true,
"dataSharing": {
"institutionalResearch": true,
"publicDatabases": false,
"commercialUse": false
},
"futureContact": {
"newFindings": true,
"additionalStudies": false
},
"sampleStorage": {
"duration": "10 years",
"futureUse": true
}
}
}
consentTextVersion, signedAt, withdrawnAt는 이 책이 추가한 필드이고, permissions 아래는 모두 부속서의 것이다. clinicalTesting은 식별 영역을 관장한다. biomarkerDiscovery와 세 개의 dataSharing 스위치는 브리지와 연구 영역을 관장한다. futureContact는 연구자가 임상팀에 참여자 재연락을 요청할 수 있는지, 예를 들어 임상적으로 조치 가능한 발견을 돌려줄 수 있는지를 정한다. sampleStorage는 물리적 검체를 관장하며, 이는 데이터 시스템 밖에 있지만 같은 동의 레코드에 연결되어야 한다.
검체 채취 시점에만 동의를 확인하는 것으로는 충분하지 않다. 동의는 철회될 수 있고, 한 목적으로 내보낸 데이터셋이 나중에 다른 목적으로 질의될 수 있다. 견고한 방법은 모든 질의에 목적을 붙이고, 질의가 실행되는 순간 레코드를 동의 레코드와 대조하여 거르는 것이다. 아래 코드는 그 개념을 보여 준다.
type Purpose = 'clinical' | 'discovery' | 'institutional' | 'public' | 'commercial';
function permits(c: Consent, purpose: Purpose, at: Date): boolean {
if (c.withdrawnAt && new Date(c.withdrawnAt) <= at) return false;
const p = c.permissions;
switch (purpose) {
case 'clinical': return p.clinicalTesting;
case 'discovery': return p.biomarkerDiscovery;
case 'institutional': return p.biomarkerDiscovery && p.dataSharing.institutionalResearch;
case 'public': return p.biomarkerDiscovery && p.dataSharing.publicDatabases;
case 'commercial': return p.biomarkerDiscovery && p.dataSharing.commercialUse;
}
}
// 모든 연구 질의는 목적을 선언한다. 동의가 그 목적을 허용하지 않는 레코드는
// 통계를 계산하기 전에 제거되며, 감사 기록에 제거된 개수와 이유가 남는다.
function consentFilter(rows: CodedRecord[], purpose: Purpose, now = new Date()) {
return rows.filter((r) => permits(consentOf(r.studyCode), purpose, now));
}
통계에는 두 가지 결과가 따른다. 1년 간격으로 두 번 실행한 검증 연구는 철회 때문에 모집단이 조금 다를 수 있으므로, 연구 레코드에 분석 날짜와 함께 동의 사유로 제외된 레코드 수를 저장해야 한다. 또한 철회는 대개 이미 집계 형태로 발표된 결과에까지 미치지 않는다. 동의서에 이를 밝혀 두어야 하며, 유럽에서는 GDPR의 연구 관련 조항(제89조)이 이러한 제한의 일반적인 법적 근거이다. 철회가 물리적 검체의 폐기까지 요구하는지는 sampleStorage와 지역 법률이 정한다.
명세 §9.3은 데이터 교환용 암호 프로파일을 짧은 JSON 객체로 제시하고, 보안·프라이버시 부속서는 저장 데이터에 대한 규칙을 더한다. 아래 프로파일은 §9.3의 값을 그대로 옮기고, 부속서가 요구하는 키 분리를 배치용 필드로 분명히 구분하여 덧붙인 것이다.
{
"encryption": "AES-256-GCM",
"keyExchange": "ECDH-P256",
"authentication": "HMAC-SHA256",
"transport": "TLS 1.3",
"dataIntegrity": "SHA-256 checksum",
"deployment (illustrative, not part of §9.3)": {
"atRest": {
"identifiableZoneKey": "kms://lab-789/k-id",
"codedZoneKey": "kms://lab-789/k-coded",
"pseudonymMappingKey": "kms://lab-789/k-map"
},
"siteToSite": "VPN in addition to TLS 1.3",
"auditLog": { "appendOnly": true, "hashChain": "SHA-256" }
}
}
각 항목은 서로 다른 일을 맡는다. 어느 항목이 어떤 일을 하는지 이해하면 흔한 두 가지 실수, 즉 한 통제가 다른 통제를 대신한다고 가정하는 실수와 불필요한 통제를 중복해서 넣는 실수를 피할 수 있다. 아래 표는 각 항목을 근거가 되는 공개 표준에 대응시킨다.
| 프로파일 항목 | 근거 표준 | 보호하는 것 | 구현 참고 |
|---|---|---|---|
| AES-256-GCM | AES(FIPS 197)의 갈루아/카운터 모드(NIST SP 800-38D) | 저장되거나 묶인 레코드의 기밀성과 무결성 | GCM은 인증 암호화이다. 같은 키로 논스를 절대 재사용해서는 안 된다 |
| ECDH-P256 | NIST P-256 곡선 위의 타원곡선 디피-헬만 | 두 당사자 사이의 공유 키 합의 | 레코드를 특정 수신자용으로 종단 간 암호화할 때 의미가 있다. 전송 구간에서는 TLS 1.3이 자체 키 교환을 수행한다 |
| HMAC-SHA256 | SHA-256을 쓰는 HMAC(RFC 2104) | 공유 비밀을 이용한 메시지 인증, 결정적 가명(7.2.6절) | 데이터가 이미 AES-GCM 안에 있으면 무결성 측면에서는 중복이지만, API 요청 서명과 키 기반 가명 생성에 유용하다 |
| TLS 1.3 | IETF RFC 8446 | 전송 중 기밀성과 무결성, 서버(선택적으로 클라이언트) 인증 | LIS와 브리지 사이 같은 시스템 간 인터페이스에는 상호 TLS를 쓴다 |
| SHA-256 체크섬 | SHA-256(FIPS 180-4) | 파일과 내보낸 데이터의 우발적 손상 탐지 | 공격자가 다시 계산할 수 있으므로 맨 체크섬은 의도적 변조를 막지 못한다. 이를 위해서는 HMAC이나 서명을 쓴다 |
부속서는 모든 바이오마커 데이터베이스에 AES-256을 적용하고, 식별 데이터와 코드화 데이터에 별도의 암호화 키를 쓰도록 요구한다. 그 목적은 피해의 봉쇄이다. 연구 저장소와 그 키가 뚫려도 공격자가 얻는 것은 코드화된 레코드뿐이며, 환자와의 대응표는 식별 영역에 있는 다른 키로 암호화되어 있다. 가명 대응표 자체에는 세 번째 키를 두는 것이 좋다. 코드화 데이터를 다시 식별 데이터로 되돌리는 것이 바로 이 표이므로, 시스템 전체에서 가장 민감한 테이블이기 때문이다. 키는 키 관리 서비스나 하드웨어 보안 모듈에 두고, 정해진 주기로 교체하며, 보호하는 데이터와 같은 곳에 저장해서는 안 된다.
§9.1은 HIPAA 관련 조치로 접근 통제와 감사 기록을 든다. 감사 기록은 완전하고, 관리자를 포함한 누구도 몰래 수정할 수 없을 때에만 쓸모가 있다. 간단한 기법은 기록을 추가 전용으로 만들고 항목을 해시로 잇는 것이다. 각 항목에 직전 항목의 SHA-256 해시를 저장하면, 항목 하나를 지우거나 바꾸는 순간 그 뒤의 모든 해시가 어긋난다. 유용한 항목에는 행위자(사용자와 역할), 접근 대상(레코드 식별자나 질의), 선언된 목적, 동의 판단과 걸러진 레코드 수, 시각, 결과가 담긴다. 감사 기록에는 식별자가 들어 있으므로 그 자체를 식별 데이터처럼 보호해야 하며, 비식별화 표에 감사 기록이 등장하는 이유도 이것이다.
보안·프라이버시 부속서는 세 종류의 데이터에 대한 접근을 역할별로 정한다. 매트릭스는 다음과 같다.
| 역할 | 바이오마커 발굴 데이터 | 임상 바이오마커 결과 | 환자 식별자 |
|---|---|---|---|
| 연구자 | 비식별화, 읽기 | 접근 불가 | 접근 불가 |
| 검사 기사 | 접근 불가 | 읽기/쓰기(코드화 ID) | 접근 불가 |
| 임상의 | 접근 불가 | 읽기(환자 연결) | 읽기 |
| 데이터 큐레이터 | 비식별화, 읽기/쓰기 | 접근 불가 | 접근 불가 |
| 관리자 | 모든 데이터 유형 | 모든 데이터 유형 | 읽기 전용 |
이 매트릭스에는 분명한 원칙이 담겨 있다. 연구 데이터를 다루는 사람은 누구도 식별자를 볼 수 없고, 식별자를 보는 사람은 누구도 연구 데이터를 다루지 않는다. 검사 기사의 경우는 눈여겨볼 만하다. 검사 기사는 임상 결과를 쓰지만 환자 이름이 아닌 코드화된 검체 ID로 본다. 이는 임상 검사실에서 흔한 검체 접수와 분석의 분리를 반영하며, 검사 기사가 환자에 대해 알 수 있는 범위를 제한한다.
매트릭스를 구현할 때 권할 만한 보완이 두 가지 있으며, 둘 다 표준의 요구사항이 아니라 이 책의 권고이다. 첫째, 관리자 행은 모든 데이터 유형에 대한 접근을 부여한다. 운영상 관리자는 결과를 읽을 필요가 아니라 시스템을 운영할 필요가 있으므로, 이 권한은 비상 접근(break-glass) 권한으로 구현하는 편이 낫다. 필요할 때 쓸 수 있되, 사유를 밝혀야 하고, 다른 사람이 검토하는 감사 항목이 반드시 남도록 하는 것이다. 둘째, 매트릭스는 가명처리 브리지를 다루지 않는다. 브리지는 사람이 아니라 서비스이므로, 식별자를 읽고 코드화 레코드를 쓸 수 있지만 어느 영역에서도 임의의 질의는 실행할 수 없는 고유한 서비스 신원을 가져야 한다.
실제 배치에서 매트릭스의 각 칸은 7.4.1절의 동의 필터와 함께 평가되는 정책 규칙이 된다. 따라서 연구자의 질의는 두 관문을 거친다. 역할 관문(이 역할이 발굴 데이터를 읽을 수 있는가)과 동의 관문(이 레코드들을 이 목적으로 써도 되는가)이다. 두 판단은 모두 기록된다.
명세 §9.1은 HIPAA와 GDPR을 명시적으로 언급한다. WIA-BIO-004는 어느 쪽도 대체하지 않으며, 이를 충족하는 데 도움이 되는 조치를 열거할 뿐이다. 아래 표는 명세의 항목을 해당 규제와 이 장에서 설명한 통제에 대응시킨다.
| §9.1 항목 | 규제 | 이 장의 통제 |
|---|---|---|
| 환자 데이터의 비식별화 | HIPAA 프라이버시 규칙, 45 CFR 164.514 | 7.3절(Safe Harbor 목록과 19~22번 항목) |
| 저장 및 전송 중 암호화 | HIPAA 보안 규칙(45 CFR Part 164, Subpart C) | 7.5절 보안 프로파일, 키 분리 |
| 접근 통제와 감사 기록 | HIPAA 보안 규칙 | 7.6절 역할 매트릭스, 7.5.2절 감사 기록 |
| 최소 필요 원칙 | HIPAA 프라이버시 규칙 | 코드화 영역, 목적 기반 질의 |
| 데이터 수집에 대한 명시적 동의 | GDPR(규정 (EU) 2016/679) 제6조·제7조·제9조. 건강 데이터와 유전 데이터는 제9조의 민감정보에 해당한다 | 7.4절 단계적 동의 |
| 열람권과 삭제권 | GDPR 제15조, 제17조 | 동의 철회, 질의 시점 필터링, 가명 대응표를 통한 개인 레코드 탐색 |
| 데이터 이동성 | GDPR 제20조 | WIA JSON 또는 FHIR 리소스로 내보내기 |
| 설계에 의한 프라이버시 | GDPR 제25조 | 영역 분리, 브리지, 플래그 및 미동의 데이터의 기본 제외 |
한 가지 미묘한 점을 짚어 둔다. GDPR에서 가명처리된 데이터는 여전히 개인정보이다. 대응표를 가진 관리자가 재식별할 수 있기 때문이다. 따라서 이 아키텍처의 코드화 영역은 Safe Harbor식 식별자의 범위 밖에 있더라도 GDPR의 적용 범위 안에 있다. 어떤 당사자도 합리적으로 재식별할 수 없는 데이터만 GDPR 밖에 놓인다. 다른 법역에도 비슷한 개념이 있다. 예를 들어 한국의 개인정보 보호법은 가명정보와 익명정보를 구분하고, 가명정보를 안전조치 아래 과학적 연구에 이용하는 것을 허용한다. 배치할 때는 데이터 흐름마다 어떤 법제가 적용되는지 확인해야 한다.
보안·프라이버시 부속서는 참여자가 500명을 넘는 새 바이오마커 발굴 연구, 다기관 데이터 공유, 상업적 바이오마커 개발에 프라이버시 영향평가(PIA)를 요구한다. 이는 표준 자체의 기준이다. GDPR에서는 처리가 높은 위험을 초래할 가능성이 있을 때마다 데이터 보호 영향평가(제35조)가 필요하며, 건강·유전 데이터 같은 민감정보를 대규모로 처리하는 경우가 여기에 포함된다. 따라서 500명 미만의 연구도 법에 따라 영향평가가 필요할 수 있다. 실무 원칙은 WIA의 기준을 하한선으로 삼는 것이다.
부속서는 임상 검증 데이터의 보존 정책을 원시 데이터는 검증 후 3년, 분석 데이터는 최소 10년, 규제 제출 자료는 영구 보존으로 정한다. 이 기간은 WIA-BIO-004의 정책이며 HIPAA나 GDPR에서 도출된 것이 아니다. 지역 법률과 다를 때는 언제나 지역 법률이 우선한다. 특정 맥락에서 더 긴 보존을 요구하는 법제도 있다. 예를 들어 EU 임상시험 규정(규정 (EU) No 536/2014)은 임상시험 마스터 파일을 최소 25년간 보관하도록 요구한다. 반대로 GDPR은 개인정보를 목적에 필요한 기간보다 오래 보관하지 않도록 요구한다. 방어 가능한 설계는 보존 기간을 레코드 부류마다 속성으로 두고, 일정에 따라 삭제하거나 익명화하며, 각 삭제를 감사 기록에 남기는 것이다.
README는 관련 WIA 표준 다섯 가지를 든다. WIA-HEALTH(의료 데이터 통합), WIA-GENOMICS(유전 바이오마커 분석), WIA-DATA(데이터 표준화와 교환), WIA-OMNI-API(범용 데이터 API), WIA-INTENT(의도 기반 질의)이다. WIA-BIO-004는 이들과의 상호작용 방식을 정의하지 않는다. 7.1절의 아키텍처에서 이들은 자연스러운 통합 지점이 된다. WIA-HEALTH는 EHR로 가는 보고 화살표에, WIA-GENOMICS는 수집 구성 요소로 들어오는 유전 바이오마커에, WIA-DATA와 WIA-OMNI-API는 연구 영역의 질의 인터페이스에, WIA-INTENT는 사용자의 질문을 목적이 명시된 질의로 바꾸는 앞단에 놓인다. 어떤 통합이든 7.6절의 두 관문은 그대로 유지해야 한다.
validateBiomarker에 집계한다.If-None-Exist 조건부 생성은 어떤 문제를 해결하는가? WIA-BIO-004 레코드의 어떤 필드가 업무 식별자로 적합한가?diseaseStatus에 diseased, healthy, at_risk, null이 섞여 있다. validateBiomarker를 호출하기 전에 각각을 어떻게 처리해야 하는지 적고, 그렇게 하지 않으면 무엇이 잘못되는지 설명하라.