본문으로 건너뛰기

PLT-FR-004 개인정보 처리 기록 구현 가이드

이 구현 가이드는 DTx 플랫폼의 PLT-FR-004 개인정보 처리 기록 요구사항을 GDPR Article 30에 따라 체계적으로 구현하기 위한 포괄적인 계획을 제시합니다.

📋 목차

  1. 개요
  2. GDPR Article 30 요구사항 분석
  3. 현재 상태 분석
  4. 아키텍처 설계
  5. 구현 계획
  6. 데이터 분류 및 매핑
  7. 자동화 시스템
  8. 모니터링 및 운영
  9. 배포 계획

1. 개요

1.1 목표

  • GDPR Article 30에 따른 개인정보 처리 기록(Records of Processing Activities) 완전 구현
  • 기존 감사 로그 시스템을 확장하여 GDPR 특화 분석 기능 추가
  • 규제 기관 요청에 대한 자동화된 대응 시스템 구축
  • 개인정보 처리 흐름의 실시간 추적 및 기록

1.2 규정 준수 요구사항

  • GDPR Article 30: 개인정보 처리 활동 기록 의무
  • DiGA: 독일 의료 데이터 처리 규정
  • FDA: 미국 의료 기기 소프트웨어 데이터 보호
  • K-FDA: 한국 의료 기기 개인정보 보호
  • ISO 27001: 정보보안 관리 체계

1.3 성공 지표

  • 기록 완성도: 처리 활동 100% 추적
  • 응답 시간: 규제 기관 요청 24시간 이내 대응
  • 자동화율: 수동 개입 없이 90% 이상 자동 기록
  • 정확도: 개인정보 분류 정확도 99% 이상
  • 감사 통과율: 규제 감사 100% 통과

2. GDPR Article 30 요구사항 분석

2.1 필수 기록 사항

2.1.1 컨트롤러 기록 (Article 30.1)

interface ProcessingRecord {
// (a) 컨트롤러 및 대리인 정보
controller: {
name: string; // "WELT Corp"
contact: string; // "privacy@dta-wide.com"
representative?: string; // EU 대표자 (해당 시)
dpo?: string; // 개인정보보호책임자
};

// (b) 처리 목적
purposes: string[]; // ["medical_treatment", "research", "compliance"]

// (c) 개인정보 주체의 범주
dataSubjectCategories: string[]; // ["patients", "clinicians", "researchers"]

// (d) 개인정보의 범주
personalDataCategories: string[]; // ["health_data", "identification", "contact"]

// (e) 수신자 범주
recipientCategories: string[]; // ["healthcare_providers", "regulators"]

// (f) 제3국 전송
internationalTransfers?: {
countries: string[];
adequacyDecision?: boolean;
safeguards: string[];
};

// (g) 삭제 기한
retentionPeriods: {
category: string;
period: string; // "7_years_post_treatment"
legalBasis: string;
}[];

// (h) 기술적·관리적 보호조치
securityMeasures: {
technical: string[]; // ["encryption", "access_control"]
organizational: string[]; // ["staff_training", "incident_response"]
};
}

2.1.2 프로세서 기록 (Article 30.2)

interface ProcessorRecord {
// (a) 프로세서 및 대리인 정보
processor: {
name: string;
contact: string;
representative?: string;
dpo?: string;
};

// (b) 각 컨트롤러별 처리 범주
controllerCategories: {
controllerId: string;
processingCategories: string[];
}[];

// (c) 제3국 전송
internationalTransfers?: {
countries: string[];
adequacyDecision?: boolean;
safeguards: string[];
};

// (d) 기술적·관리적 보호조치
securityMeasures: {
technical: string[];
organizational: string[];
};
}

2.2 의료 데이터 특수 요구사항

2.2.1 건강 데이터 처리 (Article 9)

interface HealthDataProcessing {
// 민감정보 처리 법적 근거
legalBasisSensitive:
| "explicit_consent" // 명시적 동의
| "medical_treatment" // 의료 목적
| "public_health" // 공중보건
| "scientific_research"; // 과학 연구

// 추가 보호조치
additionalSafeguards: {
pseudonymization: boolean;
minimization: boolean;
purposeLimitation: boolean;
accuracyMaintenance: boolean;
};

// 의료진 접근 제어
medicalAccess: {
roleBasedAccess: string[]; // 역할 기반 접근
needToKnowBasis: boolean; // 알 필요 원칙
auditLogging: boolean; // 접근 감사
};
}

3. 현재 상태 분석

3.1 기존 감사 시스템 현황 ✅

3.1.1 현재 BigQuery 감사 테이블

-- 기존 테이블 구조 분석
-- infrastructure/terraform/modules/logging/main.tf 참조

-- 일반 감사 로그
audit_logs (11개 테이블)
├── api_request_logs -- API 호출 기록
├── auth_audit_logs -- 인증 감사
├── system_event_logs -- 시스템 이벤트
├── error_logs -- 에러 추적
├── performance_logs -- 성능 메트릭
├── security_audit_logs -- 보안 감사
├── user_action_logs -- 사용자 행동
├── data_access_logs -- 데이터 접근
├── iam_audit_logs -- IAM 변경
├── compliance_logs -- 컴플라이언스
└── integration_logs -- 외부 연동

3.1.2 개인정보 처리 추적 부족 ⚠️

// 현재 누락된 GDPR 특화 추적 정보
interface MissingGDPRTracking {
// 처리 목적별 분류 부족
processingPurposeMapping: "❌ 미구현";

// 개인정보 범주별 추적 부족
personalDataClassification: "❌ 미구현";

// 법적 근거 기록 부족
legalBasisTracking: "❌ 미구현";

// 보관 기간 정책 추적 부족
retentionPolicyTracking: "❌ 미구함";

// 제3자 제공 추적 부족
thirdPartySharing: "❌ 미구현";

// 권리 행사 처리 추적 부족
dataSubjectRights: "❌ 미구현";
}

3.2 강화가 필요한 부분 ⚠️

3.2.1 GDPR 특화 메타데이터

// 필요한 메타데이터 구조
interface GDPRMetadata {
// 데이터 분류
dataClassification: {
isPersonalData: boolean;
isSensitiveData: boolean;
dataCategory: PersonalDataCategory;
sensitivityLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
};

// 처리 컨텍스트
processingContext: {
purpose: ProcessingPurpose;
legalBasis: LegalBasis;
consentId?: string;
retentionPeriod: string;
};

// 주체 정보
dataSubject: {
category: DataSubjectCategory;
userId?: string;
pseudonymizedId?: string;
};
}

4. 아키텍처 설계

4.1 전체 시스템 아키텍처

4.2 데이터 처리 플로우

5. 구현 계획

5.1 1단계: BigQuery 스키마 확장 (예상 1주)

5.1.1 GDPR 특화 테이블 생성

-- PLT-FR-004: GDPR 개인정보 처리 기록 테이블
CREATE TABLE `gdpr_processing_records` (
-- 기본 식별 정보
record_id STRING NOT NULL,
processing_activity_id STRING NOT NULL,
timestamp TIMESTAMP NOT NULL,

-- 컨트롤러 정보 (Article 30.1.a)
controller_name STRING NOT NULL DEFAULT 'WELT Corp',
controller_contact STRING NOT NULL DEFAULT 'privacy@dta-wide.com',
controller_representative STRING,
dpo_contact STRING DEFAULT 'dpo@dta-wide.com',

-- 처리 목적 (Article 30.1.b)
processing_purposes ARRAY<STRING> NOT NULL,

-- 개인정보 주체 범주 (Article 30.1.c)
data_subject_categories ARRAY<STRING> NOT NULL,

-- 개인정보 범주 (Article 30.1.d)
personal_data_categories ARRAY<STRING> NOT NULL,

-- 수신자 범주 (Article 30.1.e)
recipient_categories ARRAY<STRING>,

-- 제3국 전송 (Article 30.1.f)
international_transfers JSON,

-- 보관 기간 (Article 30.1.g)
retention_periods JSON NOT NULL,

-- 보안 조치 (Article 30.1.h)
security_measures JSON NOT NULL,

-- 법적 근거
legal_basis STRING NOT NULL,
legal_basis_sensitive STRING, -- Article 9 민감정보

-- 동의 정보
consent_id STRING,
consent_status STRING,
consent_timestamp TIMESTAMP,

-- 추가 메타데이터
environment STRING NOT NULL,
source_system STRING NOT NULL,
data_subject_id STRING, -- 개인 식별자 (해시)
session_id STRING,

-- 파티셔닝 및 클러스터링
_PARTITIONTIME TIMESTAMP,

PRIMARY KEY (record_id) NOT ENFORCED
)
PARTITION BY DATE(timestamp)
CLUSTER BY processing_purposes, data_subject_categories, legal_basis;

-- 개인정보 분류 테이블
CREATE TABLE `personal_data_classification` (
classification_id STRING NOT NULL,
field_name STRING NOT NULL,
data_type STRING NOT NULL,
is_personal_data BOOLEAN NOT NULL,
is_sensitive_data BOOLEAN NOT NULL,
data_category STRING NOT NULL,
sensitivity_level STRING NOT NULL,
processing_purposes ARRAY<STRING>,
legal_basis STRING,
retention_period STRING,
last_updated TIMESTAMP NOT NULL,

PRIMARY KEY (classification_id) NOT ENFORCED
)
CLUSTER BY data_category, sensitivity_level;

-- 권리 행사 추적 테이블
CREATE TABLE `data_subject_rights_tracking` (
request_id STRING NOT NULL,
data_subject_id STRING NOT NULL,
request_type STRING NOT NULL, -- access, rectification, erasure, portability
request_timestamp TIMESTAMP NOT NULL,
status STRING NOT NULL, -- received, in_progress, completed, rejected
completion_timestamp TIMESTAMP,
processing_time_hours NUMERIC,
affected_systems ARRAY<STRING>,
data_exported JSON, -- for portability requests
deletion_confirmation JSON, -- for erasure requests
rejection_reason STRING,

PRIMARY KEY (request_id) NOT ENFORCED
)
PARTITION BY DATE(request_timestamp)
CLUSTER BY data_subject_id, request_type, status;

5.1.2 기존 테이블 확장

-- 기존 audit_logs 테이블에 GDPR 메타데이터 추가
ALTER TABLE `api_request_logs`
ADD COLUMN gdpr_metadata JSON,
ADD COLUMN personal_data_detected BOOLEAN DEFAULT FALSE,
ADD COLUMN processing_purpose STRING,
ADD COLUMN legal_basis STRING,
ADD COLUMN data_subject_category STRING;

ALTER TABLE `data_access_logs`
ADD COLUMN gdpr_metadata JSON,
ADD COLUMN personal_data_fields ARRAY<STRING>,
ADD COLUMN access_justification STRING,
ADD COLUMN consent_reference STRING;

5.2 2단계: 데이터 분류 엔진 구현 (예상 2주)

5.2.1 개인정보 자동 분류기

// libs/core/gdpr-classification/src/lib/data-classifier.service.ts
@Injectable()
export class DataClassifierService {
private readonly classificationRules: ClassificationRule[] = [
// 식별 정보
{
fieldPattern: /^(email|phone|ssn|passport)$/i,
dataCategory: 'identification_data',
isPersonalData: true,
isSensitiveData: false,
sensitivityLevel: 'HIGH'
},

// 건강 정보
{
fieldPattern: /^(sleep|health|medical|symptom|medication)$/i,
dataCategory: 'health_data',
isPersonalData: true,
isSensitiveData: true,
sensitivityLevel: 'CRITICAL'
},

// 위치 정보
{
fieldPattern: /^(location|address|geo|timezone)$/i,
dataCategory: 'location_data',
isPersonalData: true,
isSensitiveData: false,
sensitivityLevel: 'MEDIUM'
}
];

async classifyData(data: any, context: ProcessingContext): Promise<ClassificationResult> {
const classifications: FieldClassification[] = [];

for (const [fieldName, value] of Object.entries(data)) {
const classification = this.classifyField(fieldName, value, context);
if (classification.isPersonalData) {
classifications.push(classification);
}
}

return {
hasPersonalData: classifications.length > 0,
hasSensitiveData: classifications.some(c => c.isSensitiveData),
fieldClassifications: classifications,
overallSensitivity: this.calculateOverallSensitivity(classifications),
suggestedLegalBasis: this.suggestLegalBasis(classifications, context),
requiredSecurityMeasures: this.getRequiredSecurityMeasures(classifications)
};
}

private classifyField(fieldName: string, value: any, context: ProcessingContext): FieldClassification {
// 필드명 기반 분류
for (const rule of this.classificationRules) {
if (rule.fieldPattern.test(fieldName)) {
return {
fieldName,
...rule,
processingPurpose: context.purpose,
detectionMethod: 'field_name_pattern'
};
}
}

// 값 기반 분류 (정규식, AI 모델 등)
const valueClassification = this.classifyByValue(fieldName, value);
if (valueClassification) {
return valueClassification;
}

// 기본값 (비개인정보)
return {
fieldName,
dataCategory: 'non_personal',
isPersonalData: false,
isSensitiveData: false,
sensitivityLevel: 'LOW',
detectionMethod: 'default'
};
}

private classifyByValue(fieldName: string, value: any): FieldClassification | null {
if (typeof value !== 'string') return null;

// 이메일 패턴 감지
if (this.isEmailPattern(value)) {
return {
fieldName,
dataCategory: 'identification_data',
isPersonalData: true,
isSensitiveData: false,
sensitivityLevel: 'HIGH',
detectionMethod: 'value_pattern_email'
};
}

// 전화번호 패턴 감지
if (this.isPhonePattern(value)) {
return {
fieldName,
dataCategory: 'identification_data',
isPersonalData: true,
isSensitiveData: false,
sensitivityLevel: 'MEDIUM',
detectionMethod: 'value_pattern_phone'
};
}

return null;
}
}

5.2.2 메타데이터 강화 서비스

// libs/core/gdpr-enrichment/src/lib/metadata-enricher.service.ts
@Injectable()
export class MetadataEnricherService {
constructor(
private readonly dataClassifier: DataClassifierService,
private readonly legalBasisService: LegalBasisService,
private readonly retentionPolicyService: RetentionPolicyService,
private readonly consentService: ConsentService
) {}

async enrichAuditLog(
originalLog: AuditLog,
processingContext: ProcessingContext
): Promise<EnrichedAuditLog> {
// 1. 개인정보 분류
const classification = await this.dataClassifier.classifyData(
originalLog.payload,
processingContext
);

if (!classification.hasPersonalData) {
return {
...originalLog,
gdprMetadata: { isPersonalData: false }
};
}

// 2. 법적 근거 결정
const legalBasis = await this.legalBasisService.determineLegalBasis(
classification,
processingContext
);

// 3. 보관 기간 정책 조회
const retentionPolicy = await this.retentionPolicyService.getRetentionPolicy(
classification.fieldClassifications,
processingContext.purpose
);

// 4. 동의 정보 조회 (필요 시)
let consentInfo = null;
if (legalBasis.primary === 'consent') {
consentInfo = await this.consentService.getConsentInfo(
processingContext.userId,
processingContext.purpose
);
}

// 5. GDPR 메타데이터 생성
const gdprMetadata: GDPRMetadata = {
isPersonalData: true,
hasSensitiveData: classification.hasSensitiveData,

// Article 30 요구사항
processingPurpose: processingContext.purpose,
dataSubjectCategory: this.mapUserTypeToDataSubjectCategory(processingContext.userType),
personalDataCategories: classification.fieldClassifications.map(f => f.dataCategory),
legalBasis: legalBasis.primary,
legalBasisSensitive: legalBasis.sensitive,

// 보관 및 삭제
retentionPeriod: retentionPolicy.period,
retentionBasis: retentionPolicy.legalBasis,
deletionDate: retentionPolicy.deletionDate,

// 동의 관련
consentId: consentInfo?.consentId,
consentStatus: consentInfo?.status,
consentTimestamp: consentInfo?.timestamp,

// 보안 조치
securityMeasures: classification.requiredSecurityMeasures,

// 분류 세부사항
fieldClassifications: classification.fieldClassifications,
sensitivityLevel: classification.overallSensitivity,

// 추적 정보
classificationTimestamp: new Date(),
classificationVersion: '1.0'
};

return {
...originalLog,
gdprMetadata,
personalDataDetected: true,
processingPurpose: processingContext.purpose,
legalBasis: legalBasis.primary,
dataSubjectCategory: gdprMetadata.dataSubjectCategory
};
}
}

5.3 3단계: GDPR 분석 엔진 구현 (예상 2주)

5.3.1 처리 기록 생성기

// libs/core/gdpr-analysis/src/lib/processing-record-generator.service.ts
@Injectable()
export class ProcessingRecordGeneratorService {
constructor(
private readonly bigQueryService: BigQueryService,
private readonly templateService: ProcessingRecordTemplateService
) {}

async generateDailyProcessingRecords(date: Date): Promise<ProcessingRecord[]> {
const records: ProcessingRecord[] = [];

// 1. 일별 처리 활동 집계
const processingActivities = await this.aggregateProcessingActivities(date);

for (const activity of processingActivities) {
const record = await this.generateProcessingRecord(activity);
records.push(record);
}

// 2. BigQuery에 저장
await this.storeProcessingRecords(records);

return records;
}

private async generateProcessingRecord(
activity: ProcessingActivity
): Promise<ProcessingRecord> {
return {
recordId: generateUUID(),
processingActivityId: activity.id,
timestamp: activity.timestamp,

// Article 30.1.a - 컨트롤러 정보
controllerName: 'WELT Corp',
controllerContact: 'privacy@dta-wide.com',
controllerRepresentative: this.getEURepresentative(),
dpoContact: 'dpo@dta-wide.com',

// Article 30.1.b - 처리 목적
processingPurposes: activity.purposes,

// Article 30.1.c - 개인정보 주체 범주
dataSubjectCategories: activity.dataSubjectCategories,

// Article 30.1.d - 개인정보 범주
personalDataCategories: activity.personalDataCategories,

// Article 30.1.e - 수신자 범주
recipientCategories: this.getRecipientCategories(activity),

// Article 30.1.f - 제3국 전송
internationalTransfers: this.getInternationalTransfers(activity),

// Article 30.1.g - 보관 기간
retentionPeriods: activity.retentionPolicies,

// Article 30.1.h - 보안 조치
securityMeasures: this.getSecurityMeasures(activity),

// 추가 메타데이터
legalBasis: activity.primaryLegalBasis,
legalBasisSensitive: activity.sensitiveLegalBasis,
environment: activity.environment,
sourceSystem: activity.sourceSystem
};
}

private async aggregateProcessingActivities(date: Date): Promise<ProcessingActivity[]> {
const query = `
SELECT
CONCAT(processing_purpose, '_', data_subject_category, '_', DATE(timestamp)) as activity_id,
processing_purpose,
data_subject_category,
ARRAY_AGG(DISTINCT personal_data_category IGNORE NULLS) as personal_data_categories,
ARRAY_AGG(DISTINCT legal_basis IGNORE NULLS) as legal_bases,
COUNT(*) as event_count,
MIN(timestamp) as first_event,
MAX(timestamp) as last_event
FROM \`project.dataset.api_request_logs\`
WHERE
DATE(timestamp) = @date
AND personal_data_detected = true
GROUP BY processing_purpose, data_subject_category
`;

const [rows] = await this.bigQueryService.query({
query,
params: { date: date.toISOString().split('T')[0] }
});

return rows.map(row => this.mapRowToProcessingActivity(row));
}
}

5.3.2 규제 대응 자동화

// libs/core/gdpr-compliance/src/lib/regulatory-response.service.ts
@Injectable()
export class RegulatoryResponseService {
constructor(
private readonly recordGenerator: ProcessingRecordGeneratorService,
private readonly reportGenerator: ComplianceReportGeneratorService,
private readonly notificationService: NotificationService
) {}

async handleRegulatoryRequest(request: RegulatoryRequest): Promise<RegulatoryResponse> {
const startTime = Date.now();

try {
// 1. 요청 유형별 처리
let responseData: any;

switch (request.type) {
case 'PROCESSING_RECORDS':
responseData = await this.generateProcessingRecordsReport(request);
break;

case 'DATA_SUBJECT_RIGHTS':
responseData = await this.generateDataSubjectRightsReport(request);
break;

case 'BREACH_NOTIFICATION':
responseData = await this.generateBreachReport(request);
break;

case 'DPIA_DOCUMENTATION':
responseData = await this.generateDPIAReport(request);
break;

default:
throw new Error(`Unsupported request type: ${request.type}`);
}

// 2. 응답 패키지 생성
const response: RegulatoryResponse = {
requestId: request.id,
responseId: generateUUID(),
responseTimestamp: new Date(),
processingTimeMs: Date.now() - startTime,
requestType: request.type,
regulatoryAuthority: request.authority,
data: responseData,
attachments: await this.generateAttachments(request, responseData),
certificationInfo: this.getCertificationInfo()
};

// 3. 자동 알림
await this.notificationService.notifyDPO({
type: 'REGULATORY_RESPONSE_GENERATED',
request,
response,
urgency: this.calculateUrgency(request)
});

return response;

} catch (error) {
// 에러 처리 및 알림
await this.notificationService.notifyDPO({
type: 'REGULATORY_RESPONSE_FAILED',
request,
error: error.message,
urgency: 'HIGH'
});

throw error;
}
}

private async generateProcessingRecordsReport(
request: RegulatoryRequest
): Promise<ProcessingRecordsReport> {
const { dateFrom, dateTo, purposes, dataSubjects } = request.parameters;

// 기간별 처리 기록 집계
const records = await this.recordGenerator.getProcessingRecords({
dateRange: { from: dateFrom, to: dateTo },
purposes,
dataSubjectCategories: dataSubjects
});

// 통계 생성
const statistics = this.calculateProcessingStatistics(records);

// 규제 기관 형식으로 변환
return {
reportType: 'PROCESSING_ACTIVITIES',
generatedAt: new Date(),
period: { from: dateFrom, to: dateTo },
totalActivities: records.length,

// Article 30 요구사항별 섹션
controllerInfo: this.getControllerInfo(),
processingActivities: records.map(r => this.formatForRegulator(r)),
dataFlowDiagrams: await this.generateDataFlowDiagrams(records),
legalBasisSummary: statistics.legalBasisBreakdown,
retentionPolicySummary: statistics.retentionPolicyBreakdown,
securityMeasuresOverview: statistics.securityMeasuresOverview,

// 추가 컴플라이언스 정보
certifications: this.getActiveCertifications(),
auditTrail: statistics.auditMetrics,
qualityAssurance: this.getQualityMetrics(records)
};
}
}

6. 데이터 분류 및 매핑

6.1 개인정보 범주 매핑

6.1.1 의료 DTx 특화 분류

enum PersonalDataCategory {
// 기본 식별 정보
IDENTIFICATION_DATA = 'identification_data', // 이름, 이메일, 전화번호
DEMOGRAPHIC_DATA = 'demographic_data', // 나이, 성별, 지역

// 건강 관련 (Article 9 민감정보)
HEALTH_DATA = 'health_data', // 수면 데이터, 증상
MEDICAL_RECORDS = 'medical_records', // 진단, 처방
BIOMETRIC_DATA = 'biometric_data', // 생체 측정 데이터

// 행동 및 선호도
BEHAVIORAL_DATA = 'behavioral_data', // 앱 사용 패턴
PREFERENCE_DATA = 'preference_data', // 설정, 선호도

// 기술적 정보
DEVICE_DATA = 'device_data', // 기기 정보
LOCATION_DATA = 'location_data', // 위치, 시간대

// 통신 및 상호작용
COMMUNICATION_DATA = 'communication_data', // 메시지, 문의
INTERACTION_DATA = 'interaction_data', // 서비스 이용 기록

// 재정 및 거래
FINANCIAL_DATA = 'financial_data', // 결제 정보
TRANSACTION_DATA = 'transaction_data', // 거래 내역

// 법적 및 규제
CONSENT_DATA = 'consent_data', // 동의 기록
COMPLIANCE_DATA = 'compliance_data' // 규제 준수 데이터
}

enum ProcessingPurpose {
// 의료 목적 (Article 9.2.h)
MEDICAL_TREATMENT = 'medical_treatment', // 의료 처치
HEALTH_MONITORING = 'health_monitoring', // 건강 모니터링
THERAPY_DELIVERY = 'therapy_delivery', // 치료 제공

// 연구 목적 (Article 9.2.j)
SCIENTIFIC_RESEARCH = 'scientific_research', // 과학 연구
CLINICAL_TRIALS = 'clinical_trials', // 임상시험
PRODUCT_DEVELOPMENT = 'product_development', // 제품 개발

// 운영 목적
SERVICE_PROVISION = 'service_provision', // 서비스 제공
CUSTOMER_SUPPORT = 'customer_support', // 고객 지원
QUALITY_ASSURANCE = 'quality_assurance', // 품질 보증

// 규제 준수 (Article 6.1.c)
REGULATORY_COMPLIANCE = 'regulatory_compliance', // 규제 준수
SAFETY_MONITORING = 'safety_monitoring', // 안전성 모니터링
AUDIT_COMPLIANCE = 'audit_compliance', // 감사 준수

// 마케팅 및 개선
MARKETING = 'marketing', // 마케팅
SERVICE_IMPROVEMENT = 'service_improvement', // 서비스 개선
ANALYTICS = 'analytics' // 분석
}

6.1.2 법적 근거 매핑

enum LegalBasis {
// Article 6.1 일반 개인정보
CONSENT = 'consent', // 동의 (6.1.a)
CONTRACT = 'contract', // 계약 이행 (6.1.b)
LEGAL_OBLIGATION = 'legal_obligation', // 법적 의무 (6.1.c)
VITAL_INTERESTS = 'vital_interests', // 생명 보호 (6.1.d)
PUBLIC_TASK = 'public_task', // 공익 업무 (6.1.e)
LEGITIMATE_INTERESTS = 'legitimate_interests', // 정당한 이익 (6.1.f)
}

enum SensitiveLegalBasis {
// Article 9.2 민감정보 (건강 데이터)
EXPLICIT_CONSENT = 'explicit_consent', // 명시적 동의 (9.2.a)
MEDICAL_TREATMENT = 'medical_treatment', // 의료 목적 (9.2.h)
PUBLIC_HEALTH = 'public_health', // 공중보건 (9.2.i)
SCIENTIFIC_RESEARCH = 'scientific_research', // 과학 연구 (9.2.j)
}

// 목적별 권장 법적 근거 매핑
const PURPOSE_LEGAL_BASIS_MAPPING = {
[ProcessingPurpose.MEDICAL_TREATMENT]: {
primary: LegalBasis.VITAL_INTERESTS,
sensitive: SensitiveLegalBasis.MEDICAL_TREATMENT
},
[ProcessingPurpose.SCIENTIFIC_RESEARCH]: {
primary: LegalBasis.CONSENT,
sensitive: SensitiveLegalBasis.SCIENTIFIC_RESEARCH
},
[ProcessingPurpose.REGULATORY_COMPLIANCE]: {
primary: LegalBasis.LEGAL_OBLIGATION,
sensitive: SensitiveLegalBasis.PUBLIC_HEALTH
}
};

6.2 보관 기간 정책

6.2.1 의료 규제별 보관 기간

interface RetentionPolicy {
id: string;
dataCategory: PersonalDataCategory;
purpose: ProcessingPurpose;
region: 'EU' | 'US' | 'KR';
retentionPeriod: string;
legalBasis: string;
automaticDeletion: boolean;
exceptions?: string[];
}

const MEDICAL_RETENTION_POLICIES: RetentionPolicy[] = [
// EU/DiGA 규제
{
id: 'eu_medical_treatment',
dataCategory: PersonalDataCategory.HEALTH_DATA,
purpose: ProcessingPurpose.MEDICAL_TREATMENT,
region: 'EU',
retentionPeriod: '10_years_post_treatment',
legalBasis: 'EU Medical Device Regulation 2017/745',
automaticDeletion: true
},

// US/FDA 규제
{
id: 'us_clinical_trials',
dataCategory: PersonalDataCategory.HEALTH_DATA,
purpose: ProcessingPurpose.CLINICAL_TRIALS,
region: 'US',
retentionPeriod: '25_years_post_study',
legalBasis: '21 CFR Part 812/820',
automaticDeletion: true
},

// KR/K-FDA 규제
{
id: 'kr_medical_device',
dataCategory: PersonalDataCategory.HEALTH_DATA,
purpose: ProcessingPurpose.MEDICAL_TREATMENT,
region: 'KR',
retentionPeriod: '5_years_post_treatment',
legalBasis: '의료기기법 제6조',
automaticDeletion: true
},

// 일반 서비스 데이터
{
id: 'service_provision',
dataCategory: PersonalDataCategory.BEHAVIORAL_DATA,
purpose: ProcessingPurpose.SERVICE_PROVISION,
region: 'EU',
retentionPeriod: '2_years_post_last_activity',
legalBasis: 'GDPR Article 5.1.e',
automaticDeletion: true,
exceptions: ['user_consent_extended', 'legal_hold_active']
}
];

7. 자동화 시스템

7.1 실시간 모니터링

7.1.1 GDPR 컴플라이언스 대시보드

// apps/dta-wide-doc/docs/technical-infrastructure/gdpr-processing-records/dashboard-specs.md
interface GDPRDashboard {
// 실시간 지표
realTimeMetrics: {
dailyProcessingActivities: number;
personalDataEventsToday: number;
sensitiveDataEventsToday: number;
complianceScore: number; // 0-100

// 처리 목적별 분포
purposeDistribution: {
[key in ProcessingPurpose]: number;
};

// 법적 근거별 분포
legalBasisDistribution: {
[key in LegalBasis]: number;
};
};

// 컴플라이언스 상태
complianceStatus: {
processingRecordsComplete: boolean;
retentionPoliciesActive: boolean;
consentManagementActive: boolean;
dataSubjectRightsActive: boolean;

// 알림 및 경고
activeAlerts: ComplianceAlert[];
pendingActions: PendingAction[];
};

// 규제 기관 준비도
regulatoryReadiness: {
lastAuditDate: Date;
nextScheduledAudit: Date;
documentationCompleteness: number;
responseTimeAverage: string; // "2.3 hours"

// 최근 요청 처리
recentRequests: RegulatoryRequest[];
};
}

7.1.2 자동 알림 시스템

// libs/core/gdpr-alerts/src/lib/alert-manager.service.ts
@Injectable()
export class GDPRAlertManagerService {
constructor(
private readonly notificationService: NotificationService,
private readonly slackService: SlackService,
private readonly emailService: EmailService
) {}

// 실시간 컴플라이언스 모니터링
@Cron('*/5 * * * *') // 5분마다 실행
async monitorCompliance(): Promise<void> {
const alerts: ComplianceAlert[] = [];

// 1. 처리 기록 완성도 체크
const processingRecordGaps = await this.checkProcessingRecordGaps();
if (processingRecordGaps.length > 0) {
alerts.push({
type: 'PROCESSING_RECORD_GAP',
severity: 'HIGH',
message: `${processingRecordGaps.length} processing activities missing records`,
details: processingRecordGaps,
action: 'Review and complete processing records'
});
}

// 2. 동의 만료 체크
const expiredConsents = await this.checkExpiredConsents();
if (expiredConsents.length > 0) {
alerts.push({
type: 'CONSENT_EXPIRED',
severity: 'CRITICAL',
message: `${expiredConsents.length} consents have expired`,
details: expiredConsents,
action: 'Stop processing or obtain renewed consent'
});
}

// 3. 보관 기간 만료 체크
const retentionViolations = await this.checkRetentionViolations();
if (retentionViolations.length > 0) {
alerts.push({
type: 'RETENTION_VIOLATION',
severity: 'CRITICAL',
message: `${retentionViolations.length} data items exceed retention period`,
details: retentionViolations,
action: 'Delete or anonymize data immediately'
});
}

// 4. 권리 행사 요청 지연 체크
const delayedRightsRequests = await this.checkDelayedRightsRequests();
if (delayedRightsRequests.length > 0) {
alerts.push({
type: 'RIGHTS_REQUEST_DELAYED',
severity: 'HIGH',
message: `${delayedRightsRequests.length} data subject rights requests are delayed`,
details: delayedRightsRequests,
action: 'Complete processing within GDPR timeframes'
});
}

// 5. 알림 발송
for (const alert of alerts) {
await this.sendAlert(alert);
}
}

private async sendAlert(alert: ComplianceAlert): Promise<void> {
const channels = this.getNotificationChannels(alert.severity);

for (const channel of channels) {
switch (channel.type) {
case 'email':
await this.emailService.sendComplianceAlert(
channel.recipients,
alert
);
break;

case 'slack':
await this.slackService.sendMessage(
channel.channel,
this.formatSlackAlert(alert)
);
break;

case 'dashboard':
await this.updateDashboardAlert(alert);
break;
}
}
}
}

7.2 권리 행사 자동화

7.2.1 데이터 주체 권리 처리 엔진

// libs/core/gdpr-rights/src/lib/data-subject-rights.service.ts
@Injectable()
export class DataSubjectRightsService {
constructor(
private readonly bigQueryService: BigQueryService,
private readonly encryptionService: EncryptionService,
private readonly auditService: AuditService
) {}

// 열람권 (Right of Access) - Article 15
async processAccessRequest(request: AccessRequest): Promise<AccessResponse> {
const startTime = Date.now();

// 1. 요청 검증
await this.validateRequest(request);

// 2. 개인정보 검색
const personalData = await this.searchPersonalData(request.dataSubjectId);

// 3. 처리 정보 수집
const processingInfo = await this.getProcessingInformation(request.dataSubjectId);

// 4. 응답 데이터 구성
const responseData = {
// Article 15.1 - 처리 확인 및 정보
processingConfirmed: personalData.length > 0,
processingPurposes: processingInfo.purposes,
personalDataCategories: processingInfo.dataCategories,
recipients: processingInfo.recipients,
retentionPeriod: processingInfo.retentionPeriod,
dataSubjectRights: this.getAvailableRights(),
complaintRights: this.getComplaintRights(),

// Article 15.3 - 개인정보 사본
personalDataCopy: await this.anonymizeForExport(personalData),

// 추가 정보
dataLastUpdated: processingInfo.lastUpdated,
dataAccuracyStatus: processingInfo.accuracyStatus,
automatedDecisionMaking: processingInfo.automatedDecisions
};

// 5. 감사 로그 기록
await this.auditService.logDataSubjectRights({
type: 'ACCESS_REQUEST',
dataSubjectId: request.dataSubjectId,
requestId: request.id,
processingTimeMs: Date.now() - startTime,
responseSize: JSON.stringify(responseData).length,
timestamp: new Date()
});

return {
requestId: request.id,
responseTimestamp: new Date(),
data: responseData,
format: request.preferredFormat || 'JSON',
deliveryMethod: request.deliveryMethod || 'secure_download'
};
}

// 삭제권 (Right to Erasure) - Article 17
async processErasureRequest(request: ErasureRequest): Promise<ErasureResponse> {
const startTime = Date.now();

// 1. 삭제 가능성 검증
const erasureAssessment = await this.assessErasureRequest(request);

if (!erasureAssessment.canErase) {
return {
requestId: request.id,
status: 'REJECTED',
reason: erasureAssessment.rejectionReason,
legalBasis: erasureAssessment.legalBasis,
timestamp: new Date()
};
}

// 2. 단계별 삭제 실행
const deletionResults = [];

for (const system of erasureAssessment.affectedSystems) {
const result = await this.executeSystemDeletion(
request.dataSubjectId,
system
);
deletionResults.push(result);
}

// 3. 삭제 확인 및 검증
const verificationResult = await this.verifyDeletion(
request.dataSubjectId,
erasureAssessment.affectedSystems
);

// 4. 감사 로그 기록 (삭제 후에도 유지)
await this.auditService.logDataSubjectRights({
type: 'ERASURE_REQUEST',
dataSubjectId: request.dataSubjectId, // 해시화된 ID
requestId: request.id,
processingTimeMs: Date.now() - startTime,
affectedSystems: erasureAssessment.affectedSystems,
deletionResults,
verificationResult,
timestamp: new Date()
});

return {
requestId: request.id,
status: verificationResult.success ? 'COMPLETED' : 'PARTIALLY_COMPLETED',
deletedItems: verificationResult.deletedItems,
retainedItems: verificationResult.retainedItems,
retentionReasons: verificationResult.retentionReasons,
completionTimestamp: new Date()
};
}

// 정정권 (Right to Rectification) - Article 16
async processRectificationRequest(request: RectificationRequest): Promise<RectificationResponse> {
// 1. 현재 데이터 조회
const currentData = await this.getCurrentPersonalData(
request.dataSubjectId,
request.fields
);

// 2. 정정 요청 검증
const validationResult = await this.validateRectificationRequest(
currentData,
request.corrections
);

if (!validationResult.isValid) {
return {
requestId: request.id,
status: 'REJECTED',
reason: validationResult.rejectionReason,
timestamp: new Date()
};
}

// 3. 데이터 정정 실행
const correctionResults = [];
for (const correction of request.corrections) {
const result = await this.applyCorrectionToSystems(
request.dataSubjectId,
correction
);
correctionResults.push(result);
}

// 4. 제3자 알림 (Article 19)
await this.notifyRecipientsOfRectification(
request.dataSubjectId,
request.corrections
);

return {
requestId: request.id,
status: 'COMPLETED',
correctedFields: correctionResults.map(r => r.field),
previousValues: correctionResults.map(r => r.previousValue),
newValues: correctionResults.map(r => r.newValue),
recipientsNotified: await this.getNotifiedRecipients(request.dataSubjectId),
completionTimestamp: new Date()
};
}
}

8. 모니터링 및 운영

8.1 주요 메트릭

8.1.1 컴플라이언스 메트릭

interface GDPRMetrics {
// 처리 기록 완성도
processingRecordMetrics: {
totalProcessingActivities: number;
recordedActivities: number;
completenessPercentage: number;
lastUpdated: Date;
};

// 권리 행사 처리 성능
dataSubjectRightsMetrics: {
totalRequests: number;
avgResponseTimeHours: number;
onTimeCompletionRate: number;
requestTypeBreakdown: {
access: number;
erasure: number;
rectification: number;
portability: number;
};
};

// 동의 관리
consentMetrics: {
activeConsents: number;
expiredConsents: number;
withdrawnConsents: number;
consentRenewalRate: number;
};

// 보안 및 위험
securityMetrics: {
personalDataBreaches: number;
unauthorizedAccess: number;
retentionViolations: number;
encryptionCoverage: number;
};
}

8.1.2 알림 정책

const GDPR_ALERT_POLICIES = {
// 즉시 알림 (CRITICAL)
IMMEDIATE_ALERTS: [
{
type: 'CONSENT_EXPIRED',
threshold: 'any',
channels: ['email', 'slack', 'sms'],
recipients: ['dpo', 'legal_team', 'on_call']
},
{
type: 'RETENTION_VIOLATION',
threshold: 'any',
channels: ['email', 'slack', 'sms'],
recipients: ['dpo', 'data_team', 'on_call']
},
{
type: 'DATA_BREACH_SUSPECTED',
threshold: 'any',
channels: ['email', 'slack', 'sms', 'phone'],
recipients: ['dpo', 'ciso', 'legal_team', 'executive_team']
}
],

// 1시간 이내 알림 (HIGH)
HOURLY_ALERTS: [
{
type: 'RIGHTS_REQUEST_OVERDUE',
threshold: '> 1 day overdue',
channels: ['email', 'slack'],
recipients: ['dpo', 'customer_support']
},
{
type: 'PROCESSING_RECORD_GAP',
threshold: '> 10 activities',
channels: ['email'],
recipients: ['data_team', 'compliance_team']
}
],

// 24시간 이내 알림 (MEDIUM)
DAILY_ALERTS: [
{
type: 'CONSENT_EXPIRING_SOON',
threshold: '< 30 days',
channels: ['email'],
recipients: ['marketing_team', 'product_team']
},
{
type: 'COMPLIANCE_SCORE_DROP',
threshold: '< 95%',
channels: ['email'],
recipients: ['dpo', 'compliance_team']
}
]
};

8.2 운영 절차

8.2.1 일상 운영 작업

-- 매일 실행: 처리 기록 생성 및 검증
-- BigQuery 스케줄 쿼리로 자동화

-- 1. 일별 처리 기록 생성
CREATE OR REPLACE PROCEDURE `project.dataset.generate_daily_processing_records`(target_date DATE)
BEGIN
-- 새로운 처리 활동 식별
CREATE OR REPLACE TEMP TABLE new_activities AS
SELECT DISTINCT
CONCAT(processing_purpose, '_', data_subject_category, '_', target_date) as activity_id,
processing_purpose,
data_subject_category,
ARRAY_AGG(DISTINCT personal_data_category IGNORE NULLS) as data_categories,
COUNT(*) as event_count,
target_date as activity_date
FROM `project.dataset.api_request_logs`
WHERE
DATE(timestamp) = target_date
AND personal_data_detected = true
AND processing_purpose IS NOT NULL
GROUP BY processing_purpose, data_subject_category;

-- 기존 기록과 비교하여 새로운 활동만 삽입
INSERT INTO `project.dataset.gdpr_processing_records`
SELECT
GENERATE_UUID() as record_id,
activity_id as processing_activity_id,
TIMESTAMP(activity_date) as timestamp,
-- ... Article 30 필수 필드들
FROM new_activities
WHERE activity_id NOT IN (
SELECT processing_activity_id
FROM `project.dataset.gdpr_processing_records`
WHERE DATE(timestamp) = target_date
);
END;

-- 2. 컴플라이언스 점검
CREATE OR REPLACE PROCEDURE `project.dataset.daily_compliance_check`(target_date DATE)
BEGIN
DECLARE compliance_issues INT64;

-- 처리 기록 완성도 체크
SET compliance_issues = (
SELECT COUNT(*)
FROM (
SELECT DISTINCT processing_purpose, data_subject_category
FROM `project.dataset.api_request_logs`
WHERE DATE(timestamp) = target_date AND personal_data_detected = true
) activities
LEFT JOIN `project.dataset.gdpr_processing_records` records
ON records.processing_activity_id = CONCAT(
activities.processing_purpose, '_',
activities.data_subject_category, '_',
target_date
)
WHERE records.record_id IS NULL
);

-- 알림 생성 (필요 시)
IF compliance_issues > 0 THEN
INSERT INTO `project.dataset.compliance_alerts`
VALUES (
GENERATE_UUID(),
'PROCESSING_RECORD_GAP',
'HIGH',
CONCAT(CAST(compliance_issues AS STRING), ' activities missing records for ', CAST(target_date AS STRING)),
target_date,
CURRENT_TIMESTAMP()
);
END IF;
END;

8.2.2 주간 보고서 자동 생성

// libs/core/gdpr-reporting/src/lib/weekly-report.service.ts
@Injectable()
export class WeeklyGDPRReportService {
@Cron('0 9 * * 1') // 매주 월요일 오전 9시
async generateWeeklyReport(): Promise<void> {
const weekStart = startOfWeek(new Date());
const weekEnd = endOfWeek(weekStart);

const report = await this.compileWeeklyReport(weekStart, weekEnd);

// DPO에게 이메일 발송
await this.emailService.sendWeeklyGDPRReport(
'dpo@dta-wide.com',
report
);

// Slack 채널에 요약 게시
await this.slackService.postWeeklyGDPRSummary(
'#gdpr-compliance',
report.summary
);
}

private async compileWeeklyReport(
startDate: Date,
endDate: Date
): Promise<WeeklyGDPRReport> {
return {
period: { start: startDate, end: endDate },

// 처리 활동 요약
processingActivities: {
total: await this.countProcessingActivities(startDate, endDate),
newActivities: await this.countNewActivities(startDate, endDate),
purposes: await this.getTopProcessingPurposes(startDate, endDate),
dataTypes: await this.getTopDataTypes(startDate, endDate)
},

// 권리 행사 현황
dataSubjectRights: {
newRequests: await this.countNewRightsRequests(startDate, endDate),
completedRequests: await this.countCompletedRequests(startDate, endDate),
averageResponseTime: await this.getAverageResponseTime(startDate, endDate),
overdueRequests: await this.getOverdueRequests()
},

// 컴플라이언스 지표
compliance: {
score: await this.calculateComplianceScore(),
alerts: await this.getActiveAlerts(),
risks: await this.identifyComplianceRisks(),
recommendations: await this.generateRecommendations()
},

// 향후 계획
upcomingActions: await this.getUpcomingActions(endDate)
};
}
}

9. 배포 계획

9.1 점진적 배포 전략

9.1.1 Phase 1: Dev 환경 - 인프라 구축 (1주차)

  • BigQuery 스키마 확장 및 테이블 생성
  • 데이터 분류 엔진 기본 구현
  • 메타데이터 강화 서비스 개발
  • 검증 기준: 개인정보 분류 정확도 90% 이상

9.1.2 Phase 2: Stage 환경 - 분석 시스템 (2주차)

  • GDPR 분석 엔진 구현
  • 처리 기록 생성기 개발
  • 기본 대시보드 구현
  • 검증 기준: 처리 기록 자동 생성 100% 성공

9.1.3 Phase 3: Prod 환경 - 실운영 적용 (3주차)

  • 권리 행사 자동화 시스템 구현
  • 규제 대응 자동화 구현
  • 실시간 모니터링 활성화
  • 검증 기준: 규제 기관 요청 24시간 이내 대응

9.2 성공 기준

9.2.1 기능적 요구사항

  • ✅ GDPR Article 30 요구사항 100% 충족
  • ✅ 개인정보 처리 활동 실시간 추적
  • ✅ 권리 행사 요청 자동 처리 (30일 이내)
  • ✅ 규제 기관 요청 자동 대응 (24시간 이내)

9.2.2 비기능적 요구사항

  • ✅ 시스템 가용성 99.9% 유지
  • ✅ 개인정보 분류 정확도 99% 이상
  • ✅ 처리 기록 생성 지연시간 < 1시간
  • ✅ 대시보드 응답 시간 < 3초

📊 비용 최적화

환경별 추가 비용 (월간)

Dev 환경 (기존 + GDPR 특화)

  • BigQuery (GDPR 테이블): +$15/월
  • Cloud Functions (분류 엔진): +$10/월
  • Cloud Monitoring (GDPR 메트릭): +$5/월
  • 총 추가 비용: +$30/월

Stage 환경 (기존 + GDPR 특화)

  • BigQuery (GDPR 테이블): +$50/월
  • Cloud Functions (분류 엔진): +$25/월
  • Cloud Monitoring (GDPR 메트릭): +$15/월
  • Pub/Sub (권리 행사 처리): +$10/월
  • 총 추가 비용: +$100/월

Prod 환경 (기존 + GDPR 특화)

  • BigQuery (GDPR 테이블): +$200/월
  • Cloud Functions (분류 엔진): +$100/월
  • Cloud Monitoring (GDPR 메트릭): +$50/월
  • Pub/Sub (권리 행사 처리): +$30/월
  • Cloud Scheduler (자동화): +$20/월
  • 총 추가 비용: +$400/월

💰 규제 준수 ROI

  • GDPR 위반 벌금 회피: 최대 €20M 또는 연간 매출 4%
  • 감사 비용 절감: 연간 $100,000 절약
  • 자동화를 통한 인력 비용 절감: 연간 $200,000 절약

🎯 결론

이 구현 가이드를 통해 PLT-FR-004 개인정보 처리 기록 요구사항을 GDPR Article 30에 완전히 준수하여 구현할 수 있습니다:

주요 성과

  1. 완전한 GDPR 준수 - Article 30 모든 요구사항 충족
  2. 실시간 자동화 - 수동 개입 최소화로 운영 효율성 극대화
  3. 규제 대응 준비 - 24시간 이내 자동 대응 시스템
  4. 의료 특화 설계 - DiGA, FDA, K-FDA 등 의료 규제 통합 고려
  5. 비용 효율성 - 기존 인프라 활용으로 추가 비용 최소화

🔄 기존 시스템 대비 개선사항

구분기존 시스템GDPR 특화 시스템
개인정보 추적일반 감사 로그실시간 자동 분류 및 추적
처리 기록수동 문서화자동 생성 및 유지관리
권리 행사수동 처리자동화된 워크플로우
규제 대응수작업 준비24시간 자동 대응
컴플라이언스 모니터링주기적 점검실시간 모니터링 및 알림

📋 다음 단계

  1. Phase 1 구현 - Dev 환경에서 BigQuery 스키마 확장
  2. 분류 엔진 개발 - 개인정보 자동 분류 및 메타데이터 강화
  3. 분석 시스템 구축 - GDPR 처리 기록 자동 생성
  4. 자동화 완성 - 권리 행사 및 규제 대응 자동화
  5. 실운영 배포 - Prod 환경 적용 및 모니터링 활성화

💡 핵심 인사이트

  • "완전 자동화" 접근법: 수동 개입을 최소화하여 운영 위험 제거
  • "의료 특화" 설계: 일반 GDPR을 넘어 의료 규제 특수성 반영
  • "기존 인프라 활용": BigQuery 기반 확장으로 비용 효율성 달성
  • "실시간 컴플라이언스": 사후 대응이 아닌 실시간 예방 중심

이제 이 포괄적인 구현 가이드를 바탕으로 GDPR Article 30을 완전히 준수하는 개인정보 처리 기록 시스템을 구축할 수 있습니다! 🚀

변경 이력

버전날짜작성자변경 내용
0.1.02025-08-13bok@weltcorp.com최초 작성