Metadata-Version: 2.4
Name: ko-pii
Version: 1.1.0
Summary: 한국어 PII 검출 + 가역 가명화 라이브러리 (ML 없이 룰 기반)
Project-URL: Homepage, https://github.com/modak000/ko-pii
Project-URL: Repository, https://github.com/modak000/ko-pii
Project-URL: Issues, https://github.com/modak000/ko-pii/issues
Project-URL: Documentation, https://github.com/modak000/ko-pii/tree/main/docs
Project-URL: Changelog, https://github.com/modak000/ko-pii/blob/main/CHANGELOG.md
Author-email: modak000 <rlaehrud63@gmail.com>
Maintainer-email: modak000 <rlaehrud63@gmail.com>
License: Apache-2.0
License-File: LICENSE
Keywords: anonymization,government,hwp,hwpx,korean,kpipa,pii,pipa,privacy,pseudonymization,public-sector,가명화,개인정보,공공,비식별,한국어
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Legal Industry
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Natural Language :: Korean
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Linguistic
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: all
Requires-Dist: cryptography>=41.0; extra == 'all'
Requires-Dist: mcp>=1.0; extra == 'all'
Requires-Dist: olefile>=0.46; extra == 'all'
Requires-Dist: presidio-analyzer>=2.2; extra == 'all'
Requires-Dist: presidio-anonymizer>=2.2; extra == 'all'
Requires-Dist: pypdf>=3.0; extra == 'all'
Requires-Dist: torch>=2.0; extra == 'all'
Requires-Dist: transformers>=4.40; extra == 'all'
Provides-Extra: dev
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Provides-Extra: file
Requires-Dist: olefile>=0.46; extra == 'file'
Requires-Dist: pypdf>=3.0; extra == 'file'
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == 'mcp'
Provides-Extra: ml
Requires-Dist: torch>=2.0; extra == 'ml'
Requires-Dist: transformers>=4.40; extra == 'ml'
Provides-Extra: presidio
Requires-Dist: presidio-analyzer>=2.2; extra == 'presidio'
Requires-Dist: presidio-anonymizer>=2.2; extra == 'presidio'
Provides-Extra: security
Requires-Dist: cryptography>=41.0; extra == 'security'
Description-Content-Type: text/markdown

# ko-pii

[![Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
[![Tests](https://img.shields.io/badge/tests-699%20passed-brightgreen.svg)](#)
[![Korean PII](https://img.shields.io/badge/도메인-한국-red.svg)](#)

**한국어 문서의 개인정보를 검출하고 가역적으로 가명화하는 Python 라이브러리.** 외부 ML 의존성 없이 룰 + 사전 + 체크섬만으로 동작. 공공 문서에서 특히 강하며, 어떤 ML 파이프라인의 전처리 레이어로도 활용 가능.

```python
from k_pii import Anonymizer, ProcessingMode

result = Anonymizer(mode=ProcessingMode.STRICT, strategy="tokenize").process(
    "신청인 홍길동 (880101-1234568) 연락처 010-1234-5678"
)
print(result.text)
# 신청인 <PERSON_1> (<RRN_1>) 연락처 <PHONE_1>

print(result.vault.reveal("<RRN_1>"))            # 880101-1234568 (권한자만 복원)
print(result.combined_risk.combined_risk)        # RiskLevel.CRITICAL
```

### 가명화 전후 비교

```
원본:
  신청인 홍길동 (880101-1234568) 연락처 010-1234-5678
  주소: 서울특별시 강남구 테헤란로 152

tokenize (토큰 치환 + Vault 복원 가능):
  신청인 <PERSON_1> (<RRN_1>) 연락처 <PHONE_1>
  주소: <ADDRESS_1>

partial (일부만 가림 — 실무 양식):
  신청인 홍** (880101-1******) 연락처 010-****-5678
  주소: 서울특별시 강남구 ***

redact (카테고리명 치환):
  신청인 [성명] ([주민등록번호]) 연락처 [전화번호]
  주소: [주소]
```

> **처음이시면:** `mode=ProcessingMode.STRICT` + `strategy="tokenize"` 추천. 가장 안전한 기본 설정 (MEDIUM 위험도 이상 차단 + Vault 복원 가능).

### 이런 것도 됩니다

- **조사 붙어있어도 잡힘** — "홍길동이" "홍길동에게" "홍길동의" → 조사 자동 분리 후 PERSON 검출
- **한자 병기** — "홍길동(洪吉童)" → 한글+한자 모두 인식
- **로마자 이름** — "Hong Gildong" → 한글로 정규화 후 매칭
- **HWP/HWPX/DOCX/PDF 직접 입력** — `ko-pii report.hwp --strategy tokenize` (표·머리말·꼬리말·메타데이터 모두 처리)
- **CSV/XLSX 헤더 자동 인식** — "성명/주민번호/연락처" 헤더 → 자동으로 PERSON/RRN/PHONE 매핑
- **공문서 날짜 자동 거부** — "시행일자: 2026-05-21" "감사기간: 3월~4월" → 생일 아님 (비-생일 키워드 30+)
- **가명 표기 자동 거부** — "박씨" "김모씨" "○○○ 시민" → 이미 가명화됨 (PII 아님)
- **결합 위험도 자동 평가** — 이름만으로는 PII 아닐 수 있지만, *이름 + 주민번호 + 주소* 가 같이 나오면 → CRITICAL (「개인정보 비식별 조치 가이드라인」 의 준식별자 결합 검증)
- **감사 로그** — 누가·언제·어떤 토큰을 복원했는지 JSONL 추적 (개인정보보호법 제29조)

---

## 목차

1. [주요 특징](#주요-특징)
2. [가명화 전후 비교 + 이런 것도 됩니다](#가명화-전후-비교)
3. [설치](#설치)
4. [사용 시나리오](#사용-시나리오)
5. [평가 결과](#평가-결과)
6. [사용법](#사용법)
7. [32 PII 카테고리](#32-pii-카테고리)
8. [검출 정책 — 어떤 접두어·anchor 가 작동하는가](#검출-정책--어떤-접두어anchor-가-작동하는가)
9. [처리 모드 + 치환 전략](#처리-모드--치환-전략)
10. [부가 기능](#부가-기능)
11. [데모 / 시각 자료](#데모--시각-자료)
12. [FAQ](#faq)
13. [개발 + 문서](#개발--문서)
14. [라이선스 + 법령](#라이선스)

---

## 주요 특징

- **한국 특화** — 한국어 PII 32 카테고리 (RRN · FRN · 여권 · 사업자 · 카드 · 계좌 · 전화 · 이메일 · 주소 · 차량 · 인명 · 직책 등). 공공 문서에서 특히 강함
- **결정적 검출** — 룰 + 사전 + 체크섬. 주민등록번호·카드·사업자번호 등은 체크섬 검증으로 F1 ≈ 1.000
- **외부 의존성 없음** — Python 표준 라이브러리만 사용. 오프라인/폐쇄망 동작, GPU 불필요
- **전처리 레이어** — `DetectionResult` (label/start/end/text/confidence) 표준 객체 출력. ML 파이프라인 앞단에 끼워넣기 편함
- **가역 가명화 + Vault** — 토큰 ↔ 원본 매핑을 별도 저장소에 분리, 복원 가능
- **법적 근거 자동 부착** — 각 검출에 개인정보보호법 조항 자동 부착 (감사 추적)
- **다양한 입력** — TXT · CSV · XLSX · HWP · HWPX · DOCX · PDF (`[file]` extras)

### 도메인별 활용 가이드

| 도메인 | 권장 설정 | 비고 |
|---|---|---|
| 공공 문서 (공문서·민원·인사) | `STRICT` + `tokenize` | 기본값. 가장 잘 맞는 도메인 |
| LLM 학습 데이터 전처리 | `PARANOID` + `tokenize` 또는 `redact` | 누수 차단 우선 |
| 의약품·바이오 | `STRICT` + `exclude={"AGE","HEIGHT","WEIGHT"}` | "체중 1kg당" 같은 용법 오탐 방지 |
| 금융·보험 | `STRICT` + `tokenize` | RRN·카드·계좌 결정적 검출 |
| 일반 사무 (사내 문서) | `BALANCED` + `partial` | 읽기 편한 부분 마스킹 |

```python
# 의약품 도메인 — PERSON FP 방지 + 신체속성 오탐 방지
anon = Anonymizer(
    mode=ProcessingMode.STRICT,
    strategy="tokenize",
    exclude={"AGE", "HEIGHT", "WEIGHT"},  # "체중 1kg당" 오탐 방지
)

# PERSON FP 가 많다면 — 도메인 사전 주입
# src/k_pii/dictionaries/common_words.py 에 의약품 성분명·제조사명 추가
# 예: "이부프로펜", "한미약품", "메트포르민" → PERSON 에서 자동 제외
```

---

## 설치

```bash
pip install ko-pii
```

extras 옵션 (필요 시):

```bash
pip install "ko-pii[file]"       # HWP/HWPX/DOCX/PDF
pip install "ko-pii[security]"   # Vault AES-256-GCM
```

**Python 3.10 이상.** 코어는 표준 라이브러리만 사용. PyPI publish 는 추후 예정.

---

## 사용 시나리오

### 시나리오 1 — 결재 공문 일괄 가명화 (외부 공개·LLM 전송 전)

```python
from pathlib import Path
from k_pii import Anonymizer, ProcessingMode

anon = Anonymizer(mode=ProcessingMode.PARANOID, strategy="tokenize")

for path in Path("./공문서/").glob("*.hwp"):
    result = anon.process(path.read_text(encoding="utf-8"))
    Path(f"./가명화/{path.name}").write_text(result.text, encoding="utf-8")
    # vault.json 분리 보관 (권한 있는 사용자만 복원 가능)
    result.vault.save(f"./vault/{path.stem}.json")
```

- **PARANOID 모드** — LOW 위험도 이상 모두 차단 (LLM/외부 전송 안전)
- 가명화 결과는 외부에, Vault 는 사내 저장소에 분리 보관
- HWP/HWPX 파서: `pip install "ko-pii[file]"`

### 시나리오 2 — 민원 응대 시스템에서 사전 PII 검증

```python
from k_pii import Anonymizer, ProcessingMode, RiskLevel

anon = Anonymizer(mode=ProcessingMode.AUDIT)  # 차단 X, 검출만 보고

result = anon.process(incoming_petition_text)

# 결합 위험도가 CRITICAL 이면 담당자에게 알림
if result.combined_risk.combined_risk >= RiskLevel.CRITICAL:
    notify_admin(
        identifiers=result.combined_risk.identifiers,        # {"RRN", "PHONE"}
        quasi=result.combined_risk.quasi_identifiers,        # {"PERSON", "ADDRESS"}
    )

# 응대 직원에게는 가명화 버전 제공
masked = Anonymizer(mode=ProcessingMode.STRICT, strategy="partial").process(
    incoming_petition_text
).text
```

- **AUDIT 모드** — 차단 없이 검출만 보고 (감사·통계용)
- **결합 위험도** 자동 평가 — 「개인정보 비식별 조치 가이드라인」 의 준식별자 결합 검증
- 응대 직원에게는 `partial` 전략으로 일부만 마스킹 (`880101-1******`)

### 시나리오 3 — Python 로그에 PII 자동 가명화 (개발자용)

```python
# 코드 어디서든 logger.info("...") 호출 시 자동 가명화
import logging
from k_pii import Anonymizer, ProcessingMode

_anon = Anonymizer(mode=ProcessingMode.STRICT, strategy="redact")

class PIIFilter(logging.Filter):
    def filter(self, record):
        record.msg = _anon.process(str(record.msg)).text
        return True

logging.getLogger().addFilter(PIIFilter())
logging.info("신청인 홍길동 (880101-1234568) 처리 완료")
# → "신청인 [성명] ([주민등록번호]) 처리 완료"
```

---

## 평가 결과

### 정확도 (4 벤치마크 + 공정 비교)

| 평가 도메인 | 문서 수 | ko-pii | openai/PF | Presidio (default) | Presidio +KR |
|---|---:|---:|---:|---:|---:|
| **행정문서 + PII 주입 (메인)** | 200 | **0.885** | 0.539 | 0.134 | 0.455 |
| **Opus 외부 시각 벤치마크** ★ | 187 | **0.770** | (pending) | (pending) | (pending) |
| KDPII (한국어 일상 대화 참고) | 4,891 | **0.656** | 0.382 | 0.302 | 0.367 |
| KLUE-NER PS (신문기사 풀네임 참고) | 5,000 | **0.419** | 0.155 | 0.000 | 0.000 |
| Naver x 창원대 NER PS (뉴스 기사 참고) | 90,000 | **0.308** | — | — | — |

메인 도메인 (행정문서) F1 = **0.885**. 외부 인간 라벨 데이터 (KDPII / KLUE-NER / Naver 창원대) 에서도 측정 완료. 상세 분석: [`docs/EVALUATION_REPORT.md`](docs/EVALUATION_REPORT.md).

> **운영 전 권장:** 사용하시는 도메인의 실제 문서 30~100건을 직접 라벨링해서 검증하세요. 도메인마다 성능 차이가 있습니다.

### 알려진 한계

- **PERSON 오탐 (FP)** — 룰 기반 PERSON 검출의 가장 큰 약점. 도메인 어휘 (의약품 성분명 등) 가 사람 이름으로 잡힐 수 있음. → `common_words.py` 에 도메인 사전 주입 또는 `exclude={"PERSON"}` 으로 끄기
- **ADDRESS 비정형** — "강남 쪽에 살아" 같은 비정형 주소는 약함 (anchor 필요). 정형 주소 ("서울특별시 강남구 테헤란로 152") 는 OK
- 결정적 PII (RRN·PHONE·EMAIL·카드·사업자) 는 체크섬/형식 검증이라 오탐 거의 없음

상세 평가: [`docs/EVALUATION_REPORT.md`](docs/EVALUATION_REPORT.md).

---

## 사용법

### CLI

```bash
# 기본
ko-pii input.txt --mode STRICT --strategy tokenize \
       --vault vault.json -o output.txt --report report.html

# 배치 (디렉토리 일괄, 병렬)
ko-pii ./incoming/ --batch --workers 4 --output-dir ./anonymized/

# Vault 암호화 + 감사 로그
KPII_VAULT_PASSWORD=secret ko-pii doc.hwp \
    --vault vault.kvault --audit-log audit.jsonl
```

### Python API

```python
from k_pii import Anonymizer, ProcessingMode

anon = Anonymizer(mode=ProcessingMode.STRICT, strategy="tokenize")
result = anon.process(text)

print(result.text)                       # 가명화된 텍스트
print(result.vault.reveal("<RRN_1>"))    # 원본 복원 (권한자만)
print(result.summary["by_label"])        # {"RRN": 1, "PHONE": 1, "PERSON": 1}
```

### 결합 위험도 + k-익명성

```python
# 검출 결과의 결합 위험도 자동 평가
print(result.combined_risk.combined_risk)        # RiskLevel.CRITICAL
print(result.combined_risk.identifiers)          # {"RRN"}
print(result.combined_risk.quasi_identifiers)    # {"PERSON", "ADDRESS", "DT_BIRTH"}

# k-익명성 평가 (집단 데이터)
from k_pii.analytics import k_anonymity
report = k_anonymity(records, quasi_identifiers=["age", "city", "job"], k=5)
print(report.satisfies_k)                  # True/False
print(report.generalization_suggestions)   # ["age: 30-39", ...]
```

### CSV/XLSX 표 자동 처리

```python
from k_pii.tabular import anonymize_records
import csv

rows = list(csv.DictReader(open("employees.csv")))
# 헤더 "성명/주민번호/연락처/주소" → 자동으로 PERSON/RRN/PHONE/ADDRESS 매핑
result = anonymize_records(rows, strategy="tokenize")
print(result.rows[0])
```

### 검토 큐 워크플로우 (오탐 학습)

confidence 낮은 검출 → 검토 큐에 저장 → 사용자가 FP/OK/FN 마킹 → 누적 마킹에서 사전 추천 패치 자동 생성 (자동 반영 X, 사람 검토 후 반영).

```python
result = anon.process(text)

# 1. confidence 낮아 REVIEW 분류된 검출 (모드별 자동 분류)
for record in result.review_items():
    d = record.detection
    print(d.text, d.confidence, d.evidence)

# 2. 별도 JSONL 큐에 저장 → 사용자가 verdict 마킹
from k_pii.review.queue import ReviewQueue
q = ReviewQueue("review.jsonl")
q.enqueue_review_records(result.review_items(), document=text)

# 3. 누적 마킹 → 패치 파일 생성 (common_words 후보 / 이름 후보)
from k_pii.review.feedback import apply_feedback
apply_feedback(
    queue_path="review.jsonl",
    output_dir="feedback_patches/",
    min_repeat=2,   # 같은 토큰이 2회 이상 FP → 후보 (사전 오염 방지)
)
# → feedback_patches/common_words_additions.txt  (PERSON FP 후보)
# → feedback_patches/names_to_add.txt           (FN 표시 이름)
# → feedback_patches/summary.json
```

### 개별 검출기 호출

```python
from k_pii.patterns.rrn import detect

for r in detect("신청인 880101-1234568"):
    print(r.label, r.text, r.confidence, r.legal_basis)
# RRN 880101-1234568 1.0 개인정보보호법 제24조의2
```

---

## 32 PII 카테고리

### 결정적 검증 (체크섬·화이트리스트)

| 카테고리 | 검증 | 위험도 |
|---|---|---|
| RRN (주민등록번호) | 13자리 + 날짜 + 한국 체크섬 | CRITICAL |
| FRN (외국인등록번호) | gender 5-8 + 체크섬 | CRITICAL |
| 사업자등록번호 | 국세청 가중합 체크섬 | LOW |
| 법인등록번호 | 법인 체크섬 (RRN 우선) | MEDIUM |
| 운전면허번호 | 지방청 코드 11-28 화이트리스트 | HIGH |
| 여권번호 | prefix (M/S/PP/PD 등) + 8자리 | CRITICAL |
| 신용카드 | BIN 화이트리스트 + Luhn | CRITICAL |
| 필지고유번호 (PNU) | 19자리 + 시·도 코드 | LOW |

### 키워드 anchor (키워드 + 형식 모두 필요)

| 카테고리 | 키워드 |
|---|---|
| 건강보험증 | 건강보험 / 의료보험 / 보험증 |
| 처방번호 | 처방번호 / Rx / 교부번호 |
| 의약품 코드 | 약품코드 / KD코드 + 한국 GS1 |
| 팩스번호 | 팩스 / FAX |
| 계좌번호 | 계좌 / 은행명 60+ (3-way anchor) |
| 사번 | 사번 / 공무원번호 / 직원번호 / 임용번호 |
| 민원번호 | 민원 / 청구 / 정보공개 / 행정심판 |
| 사건번호 | 사건유형 (가합/고합/구합/헌가 등) |

### 형식 검증

| 카테고리 | 검증 |
|---|---|
| 전화번호 | 모바일 010-019 / 서울 02 / 지방 031-064 / VoIP 070 / 대표 15xx-18xx / +82 국제 |
| 이메일 | RFC 5322 |
| IP | IPv4 옥텟 + IPv6 RFC 4291 |
| URL | http(s) / ftp |
| 우편번호 | 시·도 첫자리 매핑 |
| 차량번호 | 신형 NN[가-힣]NNNN + 용도 한글 화이트리스트 |
| 공문서번호 | 부처명 + 형식 |

### 사전·휴리스틱

| 카테고리 | 사전 규모 |
|---|---|
| 인명 (PERSON) | 성씨 286 + 직책 인접 + 17 거부 룰 |
| 주소 (ADDRESS) | 광역 17 + 기초 226 + 빈출 동 150 + 국가 70 |
| 학력 (EDUCATION) | 대학 ~330 + 약칭 |
| 전공 (MAJOR) | 학과 ~400 (KEDI 분류) |
| 직책 (POSITION) | titles 250+ (정부·경찰·소방·군·검사·법관·민간) |

### 인적 속성 (준식별자 — 결합 시 식별 위험)

| 카테고리 | 검증 | 위험도 |
|---|---|---|
| 생년월일 | 날짜 + 키워드/풀네임/년생 marker | HIGH |
| 나이 | "32세 / 32살 / 환갑 / 12개월 아기 / 30대" | INFO |
| 신장 | "175cm / 1.75m" 50–250 | INFO |
| 체중 | "70kg / 70킬로" 1–300 | INFO |

> **준식별자 (Quasi-Identifier)** 단독으로는 식별 불가지만 다른 정보와 결합 시 재식별 위험. `analytics/combined_risk` 가 자동 평가.

---

## 검출 정책 — 어떤 접두어·anchor 가 작동하는가

각 PII 검출은 *단순 정규식 매칭이 아닌 multi-gate*: **접두어 라벨 / 키워드 anchor / 문맥 사전 / 형식 검증** 의 조합. 어떤 접두어가 어떤 카테고리를 트리거하는지 정리.

### PERSON 필드 라벨 (50+ 항목)

라벨 직후 1~4글자 한글 → 강한 PERSON 후보. 소스: `src/k_pii/dictionaries/field_labels.py`.

| 도메인 | 라벨 |
|---|---|
| 기본 | `성명` `이름` `성함` `이 름` |
| 민원·행정 | `신청인` `신청자` `민원인` `청구인` `보호자` `대리인` `당사자` |
| 결재 | `기안자` `결재자` `검토자` `보고자` `수신자` `발신자` `참조` |
| 회의 | `참석자` `위임자` `수임자` `출장자` |
| 사법 | `원고` `피고` `고소인` `피고소인` `증인` `감정인` |
| 경찰·소방 | `피의자` `피해자` `용의자` `참고인` `신고자` `신고인` `수사관` `조사관` `출동대장` `출동자` `구조자` |
| 인사 | `평가자` `피평가자` `면담자` `피면담자` `추천인` `피추천인` |
| 의료 | `환자` |

라벨 prefix variant 7종 모두 인식: `성명: 홍길동` / `성명 : 홍길동` / `[성명] 홍길동` / `(성명) 홍길동` / `<성명> 홍길동` / `성명 ── 홍길동` / `성명  홍길동` (2칸).

### DT_BIRTH — 생일 anchor + 비-생일 거부 키워드

**잡는 키워드** (25자 윈도우): `생년월일` `생일` `출생일` `출생` `탄생` `태어난` `DOB` `birthday` + `OO년생` marker + 풀네임 인접

**거부 키워드** (15자 윈도우 — 공문서 일반 일자가 생일로 오탐 방지): `선고일자` `심사일자` `처리일자` `회의일자` `시행일자` `공포일자` `배포일자` `발령일자` `접수일자` `회신일자` `통보일자` `처분일자` `발급일자` `유효기간` `평가기간` `계약기간` `감사기간` `출동일자` `종결일자` `작성일자` `발생일시` `발효일` `결재일자` `회계연도` `기준일` 외 30+

→ "시행일자: 2026년 5월 20일" 자동 거부, "생년월일: 1988년 1월 1일" 만 검출.

### ADDRESS — 도로명·지번 + 대화체 anchor

- **도로명·지번**: 광역·시·군·구 토큰 인접 시 anchor 없이도 검출
- **대화체 단독** (시·군·구·동 만): 25자 윈도우 내 anchor 필수 — `주소` `자택` `거주` `본적` `사세요` `사신다` `사셨` `살던` `살아` `살고` `산다` `이사` `이사하` `명함`

### ACCOUNT — 3-way anchor

셋 중 하나만 통과해도 검출:
1. **"계좌" 키워드 직전** — `계좌` `계좌번호` `계좌번`
2. **은행명 앞** — `국민` `신한` `우리` `하나` `농협` `기업` `MG` `BNK` `카뱅` `토스뱅크` 등 60+
3. **은행명 뒤** — `110-4872-153649 신한`

### PERSON 거부 룰 (17+, FP 방지)

- **단성 + 직급/지역/학교/은행**: `김부장` `김포시` `이화여대` `이수신` → 거부
- **한국어 어말 형태소 16종**: `~은데` `~는데` `~라서` `~면서` `~까지` `~라고` 끝 → 거부
- **부처·기관명**: `보건복지부` `행정안전부` → 거부
- **이미 가명화된 표기**: `박씨` `김모씨` `김 모` `정 군` `이 양` `○○○ 시민` → 거부
- **명사 suffix**: `~사건` `~수갈채` `~성스럽게` → 거부

상세: `src/k_pii/patterns/person.py::_COMMON_KOREAN_ENDINGS` + `_NOUN_SUFFIXES_FORBIDDEN`.

### 카테고리별 검출 예시

```
[PERSON]
✓ 성명: 김도윤                       (필드 라벨)
✓ [성명] 박지훈                      (대괄호 variant)
✓ <피해자> 이수정                    (꺾쇠 variant)
✓ 박지훈 과장님께                    (직책 인접)
✓ 기획재정부 김도윤 장관              (3중 매크로)
✓ 홍길동(洪吉童)                     (한자 병기)
✗ 김부장이 협조 안 함                (단성+호칭 = 거부)
✗ 보건복지부는 검토 후                (부처명)
✗ 서울에서 발표                       (지역명)

[DT_BIRTH]
✓ 생년월일: 1988년 1월 1일           (생일 키워드)
✓ 1988년생 김민수                     (년생 marker + 풀네임)
✗ 시행일자: 2026년 5월 20일          (비-생일 거부)
✗ 감사기간 2026년 3월 ~ 4월           (비-생일 거부)

[ADDRESS]
✓ 서울특별시 강남구 테헤란로 152      (도로명)
✓ 주소: 분당구 정자동                 (라벨)
✓ 살던 곳이 마포구 합정동             (대화체 anchor)
✗ 강남구 영등포구 등 25개 자치구       (anchor 없음)

[EDUCATION]
✓ 서울대학교 졸업                     (정식명)
✓ 고대 출신                          (약칭 + anchor 필수)
✗ 양재초                             (약칭 단독 = 거부)

[ACCOUNT]
✓ 계좌 110-4872-153649               (키워드 anchor)
✓ 국민은행 110-4872-153649           (은행명 앞)
✓ 110-4872-153649 신한               (은행명 뒤)
✗ 110-4872-153649                    (anchor 없음 = 거부)

[결정적 검증]
✓ 880101-1234568            (RRN — 날짜 + 체크섬)
✓ 880101-1999999            (RRN 후-2020 — 체크섬 실패도 confidence 0.7 emit)
✓ 850315-5345676            (FRN — gender 5)
✓ 120-81-47521              (사업자 — 국세청 체크섬)
✓ 191211-0006637            (법인 — 한국전력)
✓ 4242-4242-4242-4242       (카드 — Visa BIN + Luhn)
✓ M12345678                 (여권 — prefix M)
✗ 881301-1000004            (RRN — 13월 무효)
✗ 1234-1234-1234-1234       (카드 — BIN 첫자리 1 거부)
✗ A12345678                 (여권 — A prefix 거부)
✗ 020-1234-5678             (PHONE — 020 미할당)
```

전체 정책: [`docs/annotation_policy.md`](docs/annotation_policy.md) · [`docs/pattern_analysis.md`](docs/pattern_analysis.md).

---

## 처리 모드 + 치환 전략

### 처리 모드 (차단 기준)

| 모드 | 차단 기준 | 용도 |
|---|---|---|
| `PARANOID` | LOW 이상 모두 차단 | 외부 공개·LLM 전송 전 |
| `STRICT` | MEDIUM 이상 차단 | 실무 표준 (기본값) |
| `BALANCED` | HIGH 이상 차단 | 내부 협업 |
| `PERMISSIVE` | CRITICAL 만 차단 | 분석가 작업 |
| `AUDIT` | 차단 없음, 검출만 보고 | 감사·통계 |

### 치환 전략

| 전략 | `880101-1234568` → | 가역 | 설명 |
|---|---|:-:|---|
| `tokenize` | `<RRN_1>` | ✓ | 토큰 치환, Vault 에 원본 보관 |
| `redact` | `[주민등록번호]` | ✗ | 카테고리명으로 치환 |
| `asterisk` | `**************` | ✗ | 별표 마스킹 |
| `partial` | `880101-1******` | ✗ | 일부만 가림 (실무 표준) |
| `hashed` | `<RRN:abc123>` | ✗ | 해시 (같은 값 → 같은 토큰) |
| `fpe` | `771202-2345671` | ✗ | 형식 유지 암호화 (FPE) — 원본과 동일 형식 |

---

## 부가 기능

| 기능 | 설명 | 설치 |
|---|---|:---:|
| **HWP/HWPX/DOCX/PDF 파서** | 한컴오피스·MS Word·PDF 자동 파싱 (본문 + 표 + 머리말 + 메타데이터) | `[file]` |
| **Vault 암호화** | 토큰↔원본 매핑 별도 저장소. AES-256-GCM (정부·금융권 표준) + PBKDF2 480k 반복 | `[security]` |
| **감사 로그 (JSONL)** | 모든 `reveal()` 호출 기록 — 누가·언제·어떤 토큰 복원 (개인정보보호법 제29조) | 코어 |
| **배치 처리** | 디렉토리 일괄 + 병렬 워커 | 코어 |
| **검토 큐** | confidence 낮은 검출 → 사람 검토 → 누적 오탐 어휘 자동 학습 | 코어 |
| **HTML 리포트** | 정탐 초록 / 오탐 빨강 / 미탐 노랑 인터랙티브 | 코어 |
| **한자/로마자 변형** | `洪吉童` → `홍길동`, `Hong Gildong` → `홍길동` 자동 매칭 | 코어 |

---

## 데모 / 시각 자료

브라우저에서 열어보면 됩니다. (GitHub 에서는 raw 보기, clone 후에는 직접 열기)

| 데모 | 내용 | 파일 |
|---|---|---|
| **HTML 검토 리포트** | 한 문서 검출 결과 — 정탐 초록·오탐 빨강·미탐 노랑 색상 코딩 + 카테고리별 막대 + 사용자 마킹 버튼 | [`docs/synthetic_sample.html`](docs/synthetic_sample.html) |
| **Before/After 가명화 샘플** | 6 시나리오 원본 ↔ 가명화 비교 + Vault 복원표 | [`docs/sample_redaction.html`](docs/sample_redaction.html) |
| **KDPII 100 문서 시각 비교** | 일상 대화체에 ko-pii 검출 결과 — 정탐/오탐/미탐 색상 + 라벨 비교 | [`docs/kdpii_visual_compare.html`](docs/kdpii_visual_compare.html) |
| **종합 쇼케이스** | 전체 기능 데모 한 페이지 (검출 + 가명화 + 위험도 + 검토) | [`docs/showcase.html`](docs/showcase.html) |
| **대형 데모 (15 시나리오)** | 공문서·민원·인사·의료·SNS 등 15 도메인 447 검출 케이스 | [`docs/user_megademo.html`](docs/user_megademo.html) |

리포트는 `--report report.html` 옵션으로 직접 생성 가능:
```bash
ko-pii input.txt --mode STRICT --strategy tokenize --report report.html
```

---

## FAQ

**Q1. ML 없이 룰만으로 정말 잘 되나요?**
한국 공공 도메인 (행정문서 + PII 주입 200 문서) F1 **0.885**. 한국 핵심 PII (주민번호·여권·카드·사업자 등) 는 체크섬 검증으로 F1 ≈ 1.000 — 이건 ML 로 대체 불가능한 영역. PERSON 같은 맥락 의존적 PII 는 ML 이 더 나을 수 있지만, 공공 문서 (anchor 강함) 에서는 F1 0.795 로 충분히 실용적.

**Q2. 오탐(FP) 가 너무 많이 나오면 어떻게 해야 하나요?**
4가지 완화 메커니즘 (오탐 정책 섹션 참조):
1. **모드 변경** — `STRICT` → `BALANCED` 또는 `PERMISSIVE` 로 차단 기준 낮춤
2. **검토 큐 + 자동 학습** — 반복 FP 를 `apply_feedback()` 로 `common_words` 사전에 추가 (사람 검토 후)
3. **카테고리 제외** — `Anonymizer(exclude={"PERSON"})` 로 특정 카테고리 끄기
4. **거부 룰 직접 추가** — `src/k_pii/dictionaries/common_words.py` 에 어휘 추가
실측: 200 docs 메인 벤치마크에서 ADDRESS FP 3, ACCOUNT FP 0, DT_BIRTH FP 4 (PERSON 만 183).

**Q3. Vault 파일을 잃어버리면 복원 못 하나요?**
네. Vault = 토큰 → 원본 매핑이라 분실 시 복원 불가 (보안 설계상 의도). 대안:
- `[security]` extras + 비밀번호 (`KPII_VAULT_PASSWORD=...`) 로 암호화 보관
- 클라우드 KMS / 키 관리 시스템에 백업
- 가명화 전 원본 별도 백업 (감사 추적용)
복원 자체가 필요 없으면 `strategy="redact"` (`[주민등록번호]` 식으로 카테고리명 치환) 또는 `"hashed"` (같은 값 → 같은 토큰) 사용.

**Q4. HWP/HWPX 의 표·머리말·꼬리말 다 처리되나요?**
네. `[file]` extras 설치 시 한컴오피스 파서가 본문 + 표 + 머리말 + 꼬리말 + 메타데이터 (작성자/수정자) 까지 모두 추출. 다만 *출력은 텍스트만* — 원본 HWP 파일에 직접 박스 그리는 기능은 없음 (텍스트 추출 → 가명화 후 별도 파일로 저장).

**Q5. 외국인 이름 / 한자 이름도 잡히나요?**
- **한자 병기**: `홍길동(洪吉童)` → 한글+한자 모두 인식
- **외국인 한글 표기**: `스즈카`, `왕메이`, `응우옌 티 홍` 등 — 풀네임이면 검출 (성씨 사전 X 이지만 anchor + 길이로 후보 추출)
- **로마자 이름**: `Hong Gildong` → 한글로 정규화 후 매칭
정확도는 한국식 풀네임보다 낮음 (KDPII 외국 이름 카테고리 별도 평가 없음).

**Q6. 가명화한 데이터로 통계 분석 가능한가요?**
- `tokenize` 전략 — 같은 값은 같은 토큰 → 그룹화 가능 (예: 같은 RRN = 같은 사람)
- `hashed` 전략 — 결정적 해시 → 그룹화 가능 (Vault 없이도)
- `fpe` 전략 — 형식 유지 → DB 컬럼 타입 그대로 분석
- `k_anonymity()` 함수로 집단 분석 적정성 사전 검증

**Q7. 클라우드 환경 (AWS/Azure/GCP) 에서 쓸 수 있나요?**
네. 코어는 표준 라이브러리만 사용해 어떤 Python 3.10+ 환경에서도 동작. 폐쇄망/온프레미스 한국 공공 환경도 OK (외부 API 호출 없음). Docker 이미지는 추후 제공 예정.

**Q8. 다른 PII 검출 도구 (Presidio / openai/privacy-filter) 와 어떻게 다른가요?**
- **Presidio (Microsoft)** — 영어 위주. 한국 특화 PII (RRN/FRN/여권 등) 부재
- **openai/privacy-filter** — 다국어 일반 PII. 한국 핵심 PII 14 카테고리 라벨 없음
- **ko-pii** — 한국 특화, 32 카테고리, 체크섬 검증, 법적 근거 자동 부착

---

## 개발 + 문서

### 개발 환경

```bash
git clone https://github.com/modak000/k-pii
cd k-pii
python -m venv .venv
source .venv/bin/activate              # Linux/macOS
# .venv\Scripts\activate               # Windows
pip install -e ".[dev]"
pytest                                  # 699 passed
python -m k_pii.eval.benchmark -n 60    # 합성 회귀 감지
python -m k_pii.eval.kdpii kdpii.jsonl  # KDPII 평가
```

### 문서 인덱스

| 문서 | 내용 |
|---|---|
| [`EVALUATION_REPORT.md`](docs/EVALUATION_REPORT.md) | 통합 평가 보고서 (정탐/오탐/미탐 + Tier + FP 정책 + 4 벤치마크 + 부트스트랩) |
| [`eval_samples.md`](docs/eval_samples.md) | 평가 데이터 5종 실제 샘플 한눈에 |
| [`domain_fit_report.md`](docs/domain_fit_report.md) | 도메인 적합도 (KDPII vs 공공 문서 실측) |
| [`legal_mapping.md`](docs/legal_mapping.md) | 카테고리별 법조항 매핑 |
| [`annotation_policy.md`](docs/annotation_policy.md) | 검출 정책 (왜 잡고 / 왜 거부) |
| [`risk_levels.md`](docs/risk_levels.md) | 위험도 + 모드별 차단 기준 |
| [`pattern_analysis.md`](docs/pattern_analysis.md) | 패턴별 정탐/오탐 분석 |
| [`coverage.md`](docs/coverage.md) | 카테고리별 검증 매트릭스 |
| [`integration_openai_privacy_filter.md`](docs/integration_openai_privacy_filter.md) | OpenAI 비교 평가 상세 |
| [`integration_presidio.md`](docs/integration_presidio.md) | Presidio 비교 평가 상세 |
| [`integration_mcp.md`](docs/integration_mcp.md) | MCP 서버 (Claude Desktop 연동) |
| [`sample_redaction.md`](docs/sample_redaction.md) · `.html` | 가명화 샘플 Before/After |
| [`kdpii_visual_compare.html`](docs/kdpii_visual_compare.html) | KDPII 100 문서 정탐/오탐/미탐 시각 비교 |

---

## 라이선스

Apache License 2.0 — 자유 사용·상업 사용 가능. 한국 공공 부문·민간 기업 모두.

## 법령 참고

- 개인정보보호법 — 제2조 정의, 제23조 민감정보, 제24조 고유식별정보, 제24조의2 RRN, 제28조의2-5 가명정보, 제29조 안전조치의무
- 개인정보보호위원회 「가명정보 처리 가이드라인」 · 「개인정보 비식별 조치 가이드라인」
- 상법 제40조 (법인등록번호)
- 출입국관리법 제31조 (FRN)
- 국민건강보험법 제96조 (건강보험증)
- 금융실명거래 및 비밀보장에 관한 법률 (계좌)
