# 판정 사유(Explanation) API
「왜 이렇게 분류됐나요?」에 답하는 공통 응답 필드입니다. **모든 코어핀 필터**(개인정보·문서보안·유해발화·정신건강, 그리고 이를 묶어 쓰는 문서·URL·크롤)가 같은 모양의 `explanation` 객체를 응답에 실어 줍니다.
파트너·고객은 이 객체를 그대로 저장해 자기 서비스 화면에 **「필터링 기록·사유」**로 보여줄 수 있습니다.
- **스키마 버전**: `corepin.explanation/v1`
- **기본값**: 켜져 있음. 응답 크기를 줄이려면 요청에 `"explain": false` (분류·탐지 API)
- **변경 영향**: 기존 필드는 그대로이고 `explanation` 만 추가됩니다. 모르는 필드는 무시하면 됩니다.
## 1. 어디에서 받나요
| 필터 | 엔드포인트 | 응답 위치 |
|---|---|---|
| 개인정보 | `POST /v1/pii/detect` · `/redact` · `/batch` | `explanation` (배치는 `results[].explanation`) |
| 문서보안 | `POST /v1/dlp/classify` · `/batch` | `explanation` |
| 유해발화 | `POST /v1/moderation/classify` · `/batch` | `explanation` |
| 정신건강 | `POST /v1/mental_health/classify` · `/batch` | `explanation` |
| 문서 파서 | `POST /v1/doc/parse` · `/batch` · `/url` · 비동기 | `pii_applied.explanation` · `dlp_classification.explanation` · `moderation.explanation` · `mental_health.explanation` |
| 웹 크롤러 | `POST /v1/crawl/scrape` · `/crawl` | 페이지·첨부별 위와 동일 |
## 2. 객체 구조
```json
{
"schema_version": "corepin.explanation/v1",
"filter": "dlp",
"decision": "flagged",
"summary_ko": "문서 보안 표식 때문에 등급이 ‘기밀’(으)로 올라갔습니다 (유형: 전략·기획).",
"reasons": [
{
"code": "CONFIDENTIAL", "label_ko": "기밀", "kind": "grade",
"basis": "marking_rule", "decisive": true, "tier": "rule",
"description_ko": "문서에 ‘대외비’ 표식이 있어 모델 판정(내부)을 ‘기밀’(으)로 올렸습니다.",
"evidence": [{"text": "대외비", "start": 22, "end": 25, "rule_id": "marking.confidential"}]
},
{
"code": "STRATEGY", "label_ko": "전략·기획", "kind": "type",
"basis": "model", "decisive": true, "tier": "t2_1",
"score": 0.41, "threshold": 0.33,
"description_ko": "‘전략·기획’ 유형 점수 0.41 가 반응 기준 0.33 이상입니다…"
}
],
"dismissed": [],
"inputs": {"tier_used": "t2_1", "confidence": 0.74, "confidence_band": "medium"},
"notes_ko": ["신뢰도가 낮은 판정이면 자동 차단 대신 관리자 검토 대상으로 두세요."]
}
```
| 필드 | 설명 |
|---|---|
| `filter` | `pii` · `dlp` · `moderation` · `mental_health` |
| `decision` | `flagged`(검출됨) · `clear`(검출 없음) · `skipped`(검사 생략) |
| `summary_ko` | **한 문장 요약.** 관리자 화면에 그대로 써도 되는 문장 |
| `reasons[]` | 판정 이유. 등급·유형·라벨·축·카테고리 하나당 1건 |
| `reasons[].basis` | `rule`·`marking_rule`(고정 규칙이 결정) · `model`·`t3_model`(모델 판단) · `policy`(조직 정책) |
| `reasons[].decisive` | `false` 면 결과를 바꾸지 않은 **참고 정보** |
| `reasons[].score` · `threshold` | 모델 점수와 반응 기준값. 규칙 판정이면 비어 있음 |
| `reasons[].evidence[]` | 근거 위치. `text`(80자 발췌)·`start`·`end`·`segment`(문서를 문장 단위로 쪼갠 번호)·`rule_id` |
| `dismissed[]` | **후보였으나 최종 결과에서 제외된 항목과 사유** (`why_code`/`why_ko`). 「왜 안 걸렸나」 추적용 |
| `inputs` | 판정에 쓰인 입력 정보(길이·단계·신뢰 구간) |
| `notes_ko` | 오탐 가능성·한계 안내 |
| `policy` | `apply_policy=true` 일 때만 — 차단 결정과 맞은 등급·유형 |
### `dismissed[].why_code`
| 코드 | 의미 |
|---|---|
| `not_target_grade` · `not_target_type` · `not_target_label` | 호출에서 지정한 대상 등급·유형·라벨이 아니라 응답에서 뺌 |
| `injection_defense_text` | 인젝션을 「막기 위한」 지시문으로 판단해 M10 제외 |
| `no_deterministic_evidence` | 문서 검사에서 규칙 근거 없는 M10(모델 판정) 제외 |
| `document_context_no_request` | 문서 문장에 AI 에게 시키는 요청·의도 표현이 없고 용어를 서술적으로 언급한 것으로 판단해 제외 (보안·준법·인사 문서의 표준 용어 오탐 방지) |
## 3. 필터별 읽는 법
### 문서보안(DLP)
- 등급은 **문서 앞부분(약 512토큰, 한글 기준 천 자 안팎)** 을 분류 모델이 읽고 판단합니다. 신뢰도가 낮으면 심층 검증 모델이 한 번 더 봅니다 (`tier: t3`).
- 문서에 `대외비`·`영업비밀`·`극비` 같은 **보안 표식**이 있으면 해당 등급 이상으로 올립니다(규칙). 이때 `basis=marking_rule` 과 표식 위치가 `evidence` 로 옵니다. 모델 판정은 `decisive:false` 참고 항목으로 남습니다.
- 유형마다 **반응 기준(threshold)** 이 다릅니다(현행 모델: 인수·합병 0.33 · 계약·합의 0.35 · 인사·평가 0.39 · 전략·기획 0.48 · 구매·조달 0.49 · 재무·실적 0.51 · 고객 정보 0.58 · 보안·인증 0.59 · 법무·소송 0.70 · R&D·지식재산 0.72). `score` 가 `threshold` 를 막 넘은 값(0.10 이내)이면 경계선 판정입니다 — 파트너 쪽에서 점수 기준을 더해 차단 대상을 좁힐 수 있습니다.
- `inputs.confidence_band`: `low`(<0.60) · `medium` · `high`(≥0.85). `low` 는 자동 차단 대신 검토를 권장합니다.
### 유해발화(Moderation)
- 라벨마다 `basis` 가 **규칙**(표현 사전·패턴이 걸린 문구와 위치가 `evidence`)인지 **모델**(문장 의미 판단, 규칙 근거 없음)인지 나옵니다. 모델 판단은 오탐 가능성이 더 높으므로 검토 대상으로 분류하기 좋습니다.
- 문서는 줄·문장 단위로 쪼개 검사합니다. `evidence[].segment` 는 그 문장의 순서, `start/end` 는 문서 본문 안 위치입니다.
### 정신건강(Mental health)
- 감지된 축(`reasons[kind=axis]`)과 수준(`level`), 모델이 근거로 지목한 표현(`kind=indicator`)이 옵니다.
- 이 필터는 **차단이 아니라 알림·안내** 용도입니다. 문서에서는 상담 기록·민원처럼 사람이 1인칭·구어체로 서술한 문장만 추려 검사하며, 해당 문장이 없으면 `decision: skipped` 로 건너뜁니다(과금 없음).
### 개인정보(PII)
- 카테고리별 건수와 위치만 옵니다. **원문 텍스트는 싣지 않습니다**(`detected_spans[].text` 에 이미 있음).
## 4. 기록 조회 API
처리 시점 응답의 `explanation` 을 저장하는 것이 가장 풍부합니다. 저장하지 못했다면 아래로 코드 수준의 기록을 다시 볼 수 있습니다. 서버는 **본문·문장 발췌를 저장하지 않으므로** 기록에는 코드·점수·규칙 묶음만 있습니다.
| 엔드포인트 | 설명 |
|---|---|
| `GET /v1/decisions/{request_id}` | 한 요청의 필터 기록 전체. 같은 조직의 다른 키로 처리한 요청도 조회 |
| `GET /v1/doc/history` | 문서 처리 기록 — `summary.filters.files[]` 에 등급·라벨·위험 수준과 사유 코드(`why`) |
| `GET /v1/{pii,dlp,moderation,mental_health}/history` | 필터별 기록. `summary.why` 에 사유 코드 |
| `GET /v1/explain/catalog` | 모든 라벨의 정의·판단 기준·예시·판정 방식 (인증 불필요) |
`summary.why` 는 압축 형식입니다.
```json
{"d": "flagged",
"r": [{"c": "CONFIDENTIAL", "b": "marking_rule"},
{"c": "STRATEGY", "b": "model", "s": 0.41, "t": 0.33}],
"x": [{"c": "M_AND_A", "w": "not_target_type"}]}
```
`d`=decision · `r[]`=이유(`c` 코드, `b` basis, `s` score, `t` threshold, `l` level, `n` count, `x:0` 참고항목) · `x[]`=제외(`w` why_code).
## 5. 파트너 연동 권장 방식
1. 필터 호출 시 `explain` 을 끄지 말고, 응답의 **`explanation` 전체 JSON 과 `meta.request_id` 를 함께 저장**합니다.
2. 관리자 화면의 차단 목록에는 `summary_ko` 를 한 줄로, 상세 펼침에는 `reasons[].description_ko` 와 `evidence` 를 보여줍니다.
3. 오탐 문의가 들어오면 `basis`(규칙/모델)·`score`·`dismissed` 로 1차 판단하고, 저장된 `request_id` 로 코어핀에 문의합니다.
4. `evidence.text` 에는 고객 문서의 일부가 들어 있을 수 있습니다. 저장·열람 권한은 원문과 같은 수준으로 관리하세요.
## 6. 자주 묻는 질문
**마스킹까지 했는데 왜 업로드가 차단되나요?**
개인정보 마스킹과 문서보안 분류는 별개입니다. 문서보안은 문서의 *성격*(계약·재무·전략·고객 정보 등)과 *보안 표식*으로 등급·유형을 판단하므로, 개인정보를 가려도 같은 성격의 문서는 같은 등급·유형이 나옵니다. 마스킹 이후 본문을 분류한 경우 `notes_ko` 에도 같은 안내가 옵니다. 차단 사유는 `reasons` 에서 어떤 등급·유형이 몇 점으로 걸렸는지 확인하세요.
**과하게 차단되는 느낌입니다.**
① `reasons[].score` 가 기준을 막 넘은 경계선 유형은 차단 대상에서 빼거나 검토로 돌리세요 ② 등급 신뢰도 `low` 는 자동 차단 대신 검토로 ③ 차단 유형을 업무상 꼭 필요한 것만 남기세요. 자세한 설정은 「필터 설정 가이드」를 보세요.
**유해발화 라벨이 잡혔는데 어떤 문장인가요?**
`reasons[].evidence[]` 의 `segment`·`start`·`end` 와 `text` 를 보세요. 규칙 판정이면 걸린 표현이, 모델 판정이면 그 문장의 발췌가 옵니다.