o2o-plagiarism-ai/README.md

205 lines
7.6 KiB
Markdown

# O2O 저작권 침해 여부 탐지 API
KOCCA 출판환경변화 과제 2단계 - 오투오 산출물. 자서전 본문을 입력하면 저작권 침해 여부를 판정해 법령 기반 메타 태그와 케이스 ID로 반환합니다.
## 기능
- **삼중 유사도 탐지** — 표면(임베딩) + 구조(형태소 lemma 교집합) + 요소(인물·모티프) 결합 판정
- **3단 캐스케이딩** — MinHash+LSH 1차 필터 → 정밀 비교 → 침해 유형 분류
- **법령 기반 10종 메타 태그** — 복제권 / 공중송신권 / 배포권 / 2차적저작물작성권 / 공표권 / 성명표시권 / 동일성유지권 / 인용 표시 누락 / 자기 창작인 양 표시 / 2차적저작물 미달 가공
- **39개 침해 케이스 자동 매핑** — 그룹 A(저자 가해) ~ X(분류체계 외)
- **자서전 특화 처리** — 공통 표현 사전 제거 + NER 마스킹으로 오탐 방지
- **검토 콘솔 웹 UI** — 브라우저에서 본문 입력·검사·결과 분석, 코퍼스 관리
- **코퍼스 직접 관리** — 사용자가 자서전을 직접 업로드/삭제, 인덱스 자동 재빌드
- **배치 처리** — 비동기 잡으로 최대 500건 일괄 검사
## 빠른 시작
### Docker
```bash
cp .env.example .env
# .env 에서 HOST/PORT/LOG_LEVEL/OPENAI_API_KEY 등 조정 (선택)
docker compose up --build
```
### 로컬 (Python 3.13 권장)
```bash
python3.13 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# .env 의 HOST/PORT 따라 실행
python -m app.main
# 또는 일회성 override
PORT=9000 LOG_LEVEL=debug python -m app.main
# 또는 uvicorn 직접
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
```
접속:
- `http://localhost:{PORT}/` — 검토 콘솔 (브라우저용)
- `http://localhost:{PORT}/docs` — API 명세 (Swagger)
`.env` 의 서버 바인딩 변수:
| 변수 | 기본 | 설명 |
|---|---|---|
| `HOST` | `0.0.0.0` | 바인딩 호스트 |
| `PORT` | `8000` | 포트 |
| `LOG_LEVEL` | `info` | debug/info/warning/error |
| `RELOAD` | `false` | 파일 변경 시 자동 재시작 (개발용) |
## 사용법
### 1. 브라우저에서 사용 (검토 콘솔)
`http://localhost:8000/` 접속.
**[탐지 검토] 탭**
1. 본문 텍스트 붙여넣기 (또는 샘플 시나리오 버튼 클릭)
2. "검사 시작" → 결과 표시
- 큰 판정 배지 (침해 여부 + 결합 유사도 %)
- 점수 분석 (text / lemma / character / motif 4개 막대)
- 매칭된 레퍼런스 + **법령 태그 chip (주/보조)** + **케이스 ID**
- 본문에 일치 구간 하이라이트
**[코퍼스 관리] 탭**
1. 좌측 폼에서 자서전 신규 등록 (제목 + 본문 또는 .txt 파일)
2. 우측 테이블에서 등록된 자서전 확인 / 삭제
3. 업로드/삭제 시 인덱스 자동 재빌드
### 2. API로 사용 (curl)
```bash
# 탐지
curl -X POST http://localhost:8000/v1/plagiarism/detect \
-H "Content-Type: application/json" \
-H "X-API-Key: combooks-key-change-me" \
-d '{
"doc_id": "test-001",
"text": "검사할 본문 텍스트…",
"metadata": {"title": "작품명", "author": "저자"}
}'
# 자서전 업로드
curl -X POST http://localhost:8000/v1/corpus \
-H "Content-Type: application/json" \
-H "X-API-Key: combooks-key-change-me" \
-d '{"title": "김OO 자서전", "text": "본문…"}'
# .txt 파일 업로드
curl -X POST http://localhost:8000/v1/corpus/file \
-H "X-API-Key: combooks-key-change-me" \
-F "title=김OO 자서전" \
-F "file=@autobiography.txt"
# 코퍼스 목록
curl -H "X-API-Key: combooks-key-change-me" http://localhost:8000/v1/corpus
# 분류체계 조회 (10종 태그 + 39 케이스)
curl http://localhost:8000/v1/taxonomy
```
## 엔드포인트 요약
| Method | Path | 용도 | 인증 |
|---|---|---|---|
| GET | `/` | 검토 콘솔 (HTML) | - |
| GET | `/docs` | Swagger UI | - |
| GET | `/v1/health` | 헬스체크 + 엔진 정보 | - |
| GET | `/v1/taxonomy` | 분류체계 조회 | - |
| POST | `/v1/plagiarism/detect` | 단건 동기 탐지 | ✅ |
| POST | `/v1/plagiarism/batch` | 배치 잡 등록 (≤500건) | ✅ |
| GET | `/v1/plagiarism/batch/{job_id}` | 배치 결과 조회 | ✅ |
| GET | `/v1/corpus` | 코퍼스 목록 | ✅ |
| POST | `/v1/corpus` | JSON 업로드 | ✅ |
| POST | `/v1/corpus/file` | .txt 파일 업로드 | ✅ |
| DELETE | `/v1/corpus/{doc_id}` | 삭제 | ✅ |
인증: `X-API-Key` 헤더에 발급된 키 (기관별 분리 발급 가능, 콤마 구분).
## 응답 스키마 (탐지 결과)
```json
{
"doc_id": "test-001",
"is_infringement": true,
"confidence": 0.85,
"extracted_elements": {
"characters": ["..."],
"motifs": ["..."],
"genre": "...",
"keywords": ["..."]
},
"matches": [{
"source_doc": "ref-0001",
"source_title": "원본 작품명",
"similarity": 0.85,
"tags": [
{"tag": "reproduction", "role": "primary", "label_ko": "복제권"},
{"tag": "citation_missing", "role": "primary", "label_ko": "인용 표시 누락"},
{"tag": "public_transmission", "role": "secondary", "label_ko": "공중송신권"}
],
"case_id": "A1",
"case_title": "시·노래 가사 본문 무단 인용",
"evidence_spans": [{"start": 21, "end": 34, "matched": "…"}],
"score_breakdown": {
"text_sim": 0.43, "lemma_sim": 0.75,
"character_sim": 0.0, "motif_sim": 0.67,
"lsh_jaccard": 0.07
}
}],
"ccl_basis": "…",
"autobiography_mode": true,
"engine_version": "o2o-plagiarism-2.0.0-pdf-v1.2"
}
```
## 환경변수 (`.env`)
| 변수 | 기본 | 설명 |
|---|---|---|
| `API_KEYS` | `combooks-key-…,baikal-key-…` | 콤마 구분 다중 키 |
| `SIMILARITY_THRESHOLD` | `0.85` | 침해 판정 임계값 (정밀도 우선) |
| `AUTOBIOGRAPHY_MODE` | `true` | 자서전 특화 전처리 (공통표현+NER) |
| `USE_LSH_FILTER` | `true` | MinHash+LSH 1차 필터 |
| `USE_LLM_EXTRACTOR` | `false` | OpenAI 기반 요소 추출 (키 필요) |
| `USE_EMBEDDING_SIMILARITY` | `false` | OpenAI 임베딩 유사도 (키 필요) |
| `OPENAI_API_KEY` | (빈값) | 비우면 룰 기반 + TF-IDF 폴백 |
| `WEIGHT_TEXT_SIM` 등 | 0.30/0.45/0.15/0.10 | 삼중 유사도 가중치 (합 1.0) |
요청 단위 override 가능 항목:
- `options.threshold` — 임계값
- `options.autobiography_mode` — 자서전 모드 ON/OFF
## 테스트
```bash
pytest tests/ -v
# 31 passed — API, 삼중 유사도, 형태소 분석, PDF 분류체계, LSH, 자서전 필터, 다중 태그 부여
```
## 디렉토리 구조
```
app/
├── main.py # FastAPI 앱
├── api/{routes,schemas}.py # 엔드포인트 + Pydantic 모델
├── core/{config,auth}.py # 설정 + API Key 인증
├── engine/
│ ├── detector.py # 탐지 파이프라인 오케스트레이션
│ ├── extractor.py # 콘텐츠 요소 추출 (룰 또는 OpenAI)
│ ├── similarity.py # 삼중 유사도 + TF-IDF/임베딩 백엔드
│ ├── structural.py # 형태소 lemma 교집합
│ ├── lsh_filter.py # MinHash+LSH 1차 필터
│ ├── autobiography_filter.py # 자서전 공통표현 제거 + NER 마스킹
│ ├── corpus.py # 코퍼스 CRUD
│ └── taxonomy.py # 10종 태그 + 39 케이스 매핑
├── jobs/store.py # 배치 잡 in-memory 스토어
└── static/index.html # 검토 콘솔 UI
data/
├── reference/ # 자서전 코퍼스 (업로드 저장 위치)
├── taxonomy/{meta_tags,cases}.json # PDF v1.2 분류체계
└── autobiography/common_patterns.txt # 자서전 빈출 표현 사전
```