o2o-plagiarism-ai/docs/API_GUIDE_BAIKAL.md

294 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 저작권 탐지 API 상세 연동 가이드 (오투오 → 바이칼)
> 나누구 앱 연동용. 각 엔드포인트 사용법 · 응답 변수 의미 · **저작권 탭 화면을 그리기
> 위한 호출 순서**를 정리한다. 실시간 스키마: `https://plagiarism.o2o.kr/docs`
> 엔진: `o2o-plagiarism-2.1.0-kosimcse`
---
## 0. 공통
| 항목 | 값 |
|---|---|
| Base URL | `https://plagiarism.o2o.kr` |
| 공통 prefix | 모든 API는 `/v1` 으로 시작 |
| 요청/응답 | JSON, UTF-8, `Content-Type: application/json` |
| 인증 | 개발 단계 없음 (운영 전 토큰 추가 예정) |
| 시간 형식 | ISO 8601 UTC (예: `2026-08-04T04:37:00Z`) |
### 엔드포인트 한눈에
| # | Method | Path | 용도 | 화면 연동 |
|---|---|---|---|---|
| 1 | GET | `/v1/health` | 엔진 상태·코퍼스 크기 확인 | 앱 기동 시 1회 |
| 2 | POST | `/v1/plagiarism/detect` | **본문 1건 표절 탐지** | **저작권 탭 핵심** |
| 3 | POST | `/v1/plagiarism/batch` | 배치 탐지(≤500) 등록 | 대량 검사용 |
| 4 | GET | `/v1/plagiarism/batch/{job_id}` | 배치 결과 조회 | 대량 검사용 |
| 5 | POST | `/v1/summary` | 스토리 요약 | 요약 기능용 |
| 6 | GET | `/v1/taxonomy` | 법령 태그·케이스 정의 | 라벨 캐시용 |
| 7 | GET | `/v1/corpus` | 코퍼스 목록 | 운영/관리 |
| 8 | POST | `/v1/corpus` | 코퍼스 등록(JSON) | 운영/관리 |
| 9 | POST | `/v1/corpus/file` | 코퍼스 등록(.txt 업로드) | 운영/관리 |
| 10 | DELETE | `/v1/corpus/{doc_id}` | 코퍼스 삭제 | 운영/관리 |
---
## 1. 저작권 탭을 그리기 위한 호출 순서 ★
가장 중요한 부분. 저작권 탭 화면 하나를 채우는 데 필요한 호출 흐름이다.
```
[앱 최초 기동 / 세션 시작 시 — 1회]
① GET /v1/health → 엔진 정상·corpus_size 확인 (실패면 탭 비활성)
② GET /v1/taxonomy → 태그/케이스 한글 라벨 캐시 (표절 상세 표시용, 선택)
[사용자가 에피소드 작성 후 '저작권' 탭을 열 때 — 매 검사]
③ POST /v1/plagiarism/detect { doc_id, text: 에피소드 본문 }
└ 응답 1건으로 저작권 탭 전체를 렌더링:
• review_summary → 상단 요약(독창성/유사도/유사문장/AI 의심도)
• matches[] → 표절 의심 시 상세 목록
• matches[].evidence_spans → 본문 내 일치 구간 하이라이트
• matches[].tags / case_id → 침해 유형 배지 (②의 라벨과 결합)
• ccl_basis → 사람이 읽는 판정 근거 문장
```
**핵심: 탭 렌더링에 필요한 호출은 `detect` 단 1번**이다. `health`/`taxonomy`는
세션당 1회 캐시하면 된다. 화면 상단 숫자는 전부 `review_summary`에서 나오므로,
바이칼 쪽에서 별도 계산할 것은 없다.
### 화면 요소 ↔ 응답 필드 매핑
| 저작권 탭 화면 | 값 예시 | 응답 경로 |
|---|---|---|
| 독창성 98% | 98 | `review_summary.originality_percent` |
| 저작권 · 유사도 2% | 2 | `review_summary.similarity_percent` |
| "…3.5만 건과 대조한 결과 표절 의심 구간이 없습니다" | 35000 / false | `review_summary.compared_count`, `review_summary.has_suspicion` |
| 유사 문장 0건 | 0 | `review_summary.similar_sentence_count` |
| AI 생성 의심도 | unknown | `review_summary.ai_suspicion_level` (`low/medium/high/unknown`) |
| (표절 시) 일치 구간 하이라이트 | start~end | `matches[].evidence_spans[]` |
| (표절 시) 침해 유형 배지 | 복제권 | `matches[].tags[].label_ko` + `case_id` |
---
## 2. `POST /v1/plagiarism/detect` — 표절 탐지 (핵심)
본문 1건을 검사해 저작권 탭에 필요한 모든 데이터를 반환한다.
### 요청 본문
```json
{
"doc_id": "episode-001",
"text": "창밖으로 보이는 숲은 오늘따라 유난히 푸르게 보였다. ...",
"metadata": { "title": "에피소드 1", "author": "홍길동", "genre": "자서전" },
"options": { "return_evidence": true, "threshold": null, "top_k": 5, "autobiography_mode": null }
}
```
| 필드 | 타입 | 필수 | 의미 |
|---|---|---|---|
| `doc_id` | string | ✅ | 호출측 문서 식별자. 응답에 그대로 반환(요청-응답 짝 맞춤용) |
| `text` | string | ✅ | 검사할 본문 (1자 이상) |
| `metadata.title` | string | | 작품 제목 (선택) |
| `metadata.author` | string | | 저자 (선택) |
| `metadata.genre` | string | | 장르 (선택) |
| `metadata.publisher` | string | | 출판사 (선택) |
| `metadata.publication_year` | int | | 출판연도 (선택) |
| `options.return_evidence` | bool | | 일치 구간(`evidence_spans`) 반환 여부. 기본 `true` |
| `options.threshold` | float 0~1 \| null | | 표절 판정 임계값. `null`이면 서버 기본(0.85) |
| `options.top_k` | int | | 반환할 최대 매칭 수. 기본 5 |
| `options.autobiography_mode` | bool \| null | | 자서전 특화 전처리. `null`이면 서버 설정 |
### 응답 — 전체 필드 의미
```json
{
"doc_id": "episode-001",
"is_infringement": false,
"confidence": 0.14,
"review_summary": {
"originality_percent": 98,
"similarity_percent": 2,
"similar_sentence_count": 0,
"compared_count": 35000,
"has_suspicion": false,
"ai_suspicion_level": "low"
},
"ai_generation": { "suspicion_level": "unknown", "score": null, "available": false, "is_stub": false, "model_version": "unavailable", "note": "학습된 모델이 없어 채점하지 않음" },
"matches": [],
"extracted_elements": { "characters": [], "motifs": ["비밀의 정원"], "genre": null, "keywords": ["숲","전설"] },
"ccl_basis": null,
"autobiography_mode": true,
"candidates_before_filter": 3,
"engine_version": "o2o-plagiarism-2.1.0-kosimcse",
"analyzed_at": "2026-08-04T04:37:00Z"
}
```
**최상위 필드**
| 필드 | 타입 | 의미 | 화면 사용 |
|---|---|---|---|
| `doc_id` | string | 요청의 doc_id 그대로 | 응답 매칭 |
| `is_infringement` | bool | 후방호환 필드. 법적 침해 확정이 아니라 임계 초과 매칭 존재 여부 | — |
| `confidence` | float 0~1 | 최상위 매칭의 **결합 유사도 원값**. ⚠️ 임베딩 성분 때문에 무관한 글도 높게 나옴 → **화면 표시는 `review_summary` 사용, `confidence` 직접 표시 금지** | ✗ |
| `review_summary` | object | **저작권 탭 UI 요약** (아래 상세) | ★ |
| `ai_generation` | object | AI 생성 의심도 상세 (아래 상세) | 참고 |
| `matches` | array | 매칭된 원본별 상세. 표절 아니면 `[]` | 표절 상세 |
| `extracted_elements` | object | 본문에서 추출한 구성요소 `characters/motifs/genre/keywords` | 부가 |
| `ccl_basis` | string\|null | 사람이 읽는 판정 근거 문장. 표절 아니면 `null` | 상세 문구 |
| `autobiography_mode` | bool | 자서전 전처리 적용 여부 | — |
| `candidates_before_filter` | int\|null | LSH 1차 필터 통과 후보 수(내부 지표) | — |
| `engine_version` | string | 엔진 버전 | 로깅 |
| `analyzed_at` | datetime | 분석 시각(UTC) | 로깅 |
**`review_summary` (저작권 탭 요약 — 그대로 표시)**
| 필드 | 타입 | 의미 |
|---|---|---|
| `originality_percent` | int 0~100 | **독창성 %** = 100 유사도. 점수 변환(캘리브레이션)을 오투오가 완료해 제공 |
| `similarity_percent` | int 0~100 | **유사도 %** (무관한 글은 0~5%로 눌러 표시) |
| `similar_sentence_count` | int | **유사 문장 건수** = 임계 초과 매칭 수 |
| `compared_count` | int | **대조한 원본(코퍼스) 건수** |
| `has_suspicion` | bool | **표절 의심 구간 존재 여부** |
| `ai_suspicion_level` | `low/medium/high/unknown` | **AI 생성 의심도**. 미학습/채점 불가는 `unknown` |
**`ai_generation` (AI 생성 의심도 상세)**
| 필드 | 타입 | 의미 |
|---|---|---|
| `suspicion_level` | `low/medium/high` | 의심도 등급 |
| `score` | float 0~1 | 참고 점수 |
| `available` | bool | 학습 모델 또는 명시적으로 활성화한 baseline으로 채점했는지 |
| `is_stub` | bool | `true`면 미검증 휴리스틱 baseline. 학습 모델은 `false` |
| `model_version` | string | 학습 아티팩트/특징 버전 추적 |
| `note` | string | 안내 문구 |
> 학습 모델이 없으면 점수를 임의 생성하지 않고 `available=false`, `score=null`,
> `suspicion_level=unknown`을 반환한다. 학습 후에도 **출판 승인 게이트나 저자 제재의
> 단독 근거로 쓰지 말 것**(사람 검토 우선순위용).
**`matches[]` (표절 의심 시 채워짐)**
| 필드 | 타입 | 의미 |
|---|---|---|
| `source_doc` | string | 매칭된 원본 doc_id |
| `source_title` | string\|null | 매칭된 원본 제목 |
| `similarity` | float 0~1 | 해당 원본과의 결합 유사도 |
| `tags[]` | array | 법령 태그. `{ tag, role, label_ko }` |
| `tags[].tag` | string | 태그 코드 (예: `reproduction`) |
| `tags[].role` | `primary/secondary` | 주 침해 / 보조 |
| `tags[].label_ko` | string | 한글 표기 (예: `복제권`) — 배지에 그대로 사용 |
| `case_id` | string\|null | 39종 침해 케이스 ID (예: `A6`) |
| `case_title` | string\|null | 케이스 명칭 |
| `infringement_type` | string | 후방호환 단일 분류(`copy/transform/plot/character/unknown`) |
| `evidence_spans[]` | array | 본문 내 일치 구간 `{ start, end, matched }`**하이라이트용** |
| `score_breakdown` | object | 유사도 분해 `text_sim/lemma_sim/character_sim/motif_sim/lsh_jaccard` |
| `partial_signal` | object\|null | 군집화 부분 표절(인물만 교체 등) 분해 — 침해요소 DB 적재용 |
---
## 3. `GET /v1/health` — 엔진 상태
앱 기동 시 호출해 엔진 준비·코퍼스 규모를 확인한다.
**응답**
| 필드 | 타입 | 의미 |
|---|---|---|
| `status` | `"ok"` | 정상 |
| `engine_version` | string | 엔진 버전 |
| `corpus_size` | int | 로딩된 대조 원본 수 (`review_summary.compared_count`와 동일) |
| `taxonomy_version` | string\|null | 분류체계 버전 |
| `autobiography_mode` | bool | 자서전 모드 on/off |
---
## 4. `GET /v1/taxonomy` — 법령 태그·케이스 정의
10종 태그와 39종 케이스 정의를 반환. 세션당 1회 받아 캐시하면, `detect` 응답의
`tag`/`case_id`를 화면에 설명과 함께 표시할 수 있다.
**응답**: `meta_tags_version`, `cases_version`, `meta_tags[]`, `cases[]`
- `meta_tags[]` 각 항목: `id`, `label_ko`, `category`, `law_ref`, `scope`, `description`
- `cases[]` 각 항목: `case_id`, `title`, `subgroup`, `actor`, `primary_tags[]`, `secondary_tags[]`, `detectable_internal`, `high_risk`, `note`
---
## 5. `POST /v1/plagiarism/batch` + `GET /v1/plagiarism/batch/{job_id}` — 배치
여러 건을 한 번에 검사(비동기). 대량 점검용.
**등록 요청** `POST /v1/plagiarism/batch`
```json
{ "items": [ { "doc_id": "e1", "text": "..." }, { "doc_id": "e2", "text": "..." } ],
"options": { "threshold": null } }
```
- `items`: 1~500건. 각 `{ doc_id, text, metadata? }`
- 응답(202): `{ job_id, status, total, created_at }`
**결과 조회** `GET /v1/plagiarism/batch/{job_id}`
| 필드 | 의미 |
|---|---|
| `status` | `queued/running/completed/failed` |
| `total` / `processed` | 전체 / 처리 완료 건수 (진행률) |
| `results` | `status=completed`일 때만. `DetectResponse` 배열(§2와 동일 구조) |
| `error` | 실패 시 사유 |
> 폴링: 등록 → `job_id`로 `completed` 될 때까지 조회 → `results` 사용.
---
## 6. `POST /v1/summary` — 스토리 요약
**요청**: `{ "text": "...", "ratio": 0.3, "max_sentences": null, "use_abstractive": true }`
- `ratio`: 요약 길이 비율(입력 대비). `use_abstractive`: LLM 결합(키 없으면 추출 요약 폴백)
**응답**: `extractive`(추출 요약), `abstractive`(추상 요약\|null), `final`(최종), `mode`(`extractive/hybrid`), `num_sentences_in/out`
---
## 7. 코퍼스 관리 (운영/관리용)
대조 원본을 등록·삭제. 업로드/삭제 시 인덱스 자동 재빌드.
| API | 설명 |
|---|---|
| `GET /v1/corpus` | 목록. `{ total, docs[{doc_id,title,size_bytes,filename}] }` |
| `POST /v1/corpus` | JSON 등록 `{ doc_id?(자동), title, text }``201` |
| `POST /v1/corpus/file` | multipart `.txt` 업로드 (`title`, `doc_id?`, `file`) → `201` |
| `DELETE /v1/corpus/{doc_id}` | 삭제 → `204` |
등록 응답(`201`): `{ doc_id, title, size_bytes, corpus_size_after, rebuilt }`
---
## 8. 에러 포맷
FastAPI 표준. `{ "detail": "메시지" }`
| 코드 | 상황 |
|---|---|
| 400 | 잘못된 요청(빈 본문/비 UTF-8 파일 등) |
| 404 | 배치 job_id 없음 / 코퍼스 doc_id 없음 |
| 409 | 코퍼스 doc_id 중복 |
| 422 | 스키마 검증 실패(필드 타입·범위) |
| 503 | 분류체계 미로딩 |
---
## 9. 빠른 시작 (curl)
```bash
# 1) 엔진 상태
curl https://plagiarism.o2o.kr/v1/health
# 2) 저작권 탭용 탐지 — 이 응답 1건으로 탭 렌더링
curl -X POST https://plagiarism.o2o.kr/v1/plagiarism/detect \
-H "Content-Type: application/json" \
-d '{"doc_id":"episode-001","text":"검사할 본문 텍스트..."}'
```
문의: 오투오 양형배