POSA_Copyrighter/docs/operations/copyrighter-operation-worklist.md
유창욱 3f7b3a9cf2 chore: initial commit of copyrighter (rights_filter)
Image rights / copyright detection system: SQLite store, HTTP app,
search integrations (Naver, Google Custom Search, Google Cloud Vision
web detection), image analysis (fingerprints, face/person detection,
evidence enrichment, risk scoring), an admin/review layer, governance
and retention policies, batch jobs, and a browser-based operator GUI.

This baseline incorporates a full code-review remediation pass
(46 fixes; 358 tests passing). Highlights:

CRITICAL
- Prevent evidence cascade-delete during the schema-constraint
  migration by disabling FK enforcement around the table rebuild.

Security
- Sandbox served media (neutralize stored XSS from uploaded/collected
  SVGs) via CSP + nosniff on the untrusted media routes.
- Strip embedded EXIF/GPS from external image derivatives before they
  are sent to third-party APIs.
- Return a clean 404 (not an uncaught StopIteration) for PATCH on an
  unknown provider.

Correctness
- LLM-summary failures no longer add +30 to the risk score.
- Decode only explicit JS escapes so Korean image URLs are not mangled.
- Consume search quota only after a successful request.
- Naver/Google adapters map responses inside the failure boundary, so a
  malformed response degrades to evidence instead of crashing enrichment.
- Domain-aware provider attribution; face-box IoU de-duplication; count
  searches (not result items); per-box crop isolation; clamp evidence
  confidence and Google CSE num; real submittedEpoch; and more.

Robustness
- Offline LLM connect fast-fails (short connect timeout) so seed/reload
  requests are not stalled; full read timeout preserved for generation.
- Malformed numeric env vars fall back to defaults instead of crashing
  startup.

Performance
- Per-submission evidence reads (no full-table scan per rescore),
  audit-log LIMIT, lazy active-store lookup, hoisted timestamps.

Tests
- ~24 regression tests added pinning the above fixes.

Runtime data (data/, outputs/, *.sqlite3, *.log), secrets (.env), and
node_modules are gitignored.
2026-06-09 09:50:31 +09:00

15 KiB

Copyrighter 운영 연결 상태

기준 포트는 9500이다.

이번에 연결된 항목

  • 9500 API 서버 골격 생성 완료
  • 정적 운영자 콘솔을 9500 서버에서 함께 제공
  • web/operator-gui/app.js/api/bootstrap API 응답으로 초기 데이터를 갱신하도록 변경
  • SQLite 저장소 연결 완료
  • 로컬 제출 이미지 폴더 연결 완료
  • 기본 헬스체크 추가
  • .env 또는 프로세스 환경변수 기반 외부 API/로컬 LLM 설정 로딩
  • Naver 이미지 검색 API 연결: 수동 검색에서 텍스트 쿼리만 전송
  • Google Cloud Vision Web Detection 연결: 신규 로컬 제출 분석 시 파생 이미지 전송
  • 내부 Ollama 연결: 재분석 시 기존 근거 기반 LLM 요약 생성
  • 증거 상태 기록: 판단에 사용, 무관, 오탐, 보류를 증거별로 저장
  • 보류/반려 판정 기반 주의 후보 자동 생성
  • 주의 후보 이미지 유사도 매칭, 확정 DB 편입, 오탐 제외 흐름 추가

실행 방법

.env.example을 참고해 루트에 .env를 만들고 필요한 키만 채운다. 키가 없는 외부 API provider는 자동으로 disabled 상태가 된다. Ollama LLM은 로컬 기본값으로 켜진다.

NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
GOOGLE_CLOUD_VISION_API_KEY=
COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH=false
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=qwen2.5:0.5b-instruct
cd C:\Users\USER\Desktop\complete\copyrighter
python run_copyrighter_server.py

브라우저:

http://127.0.0.1:9500/

헬스체크:

http://127.0.0.1:9500/health
http://127.0.0.1:9500/api/providers/health

기본 저장 위치:

data/copyrighter.sqlite3
data/submissions/submissions.json
data/submissions/images/

env 키와 연결 동작

env 용도 연결되는 흐름
NAVER_CLIENT_ID Naver Open API 클라이언트 ID POST /api/search/manual에서 provider가 naver일 때 사용
NAVER_CLIENT_SECRET Naver Open API 클라이언트 Secret Naver 요청 헤더에 사용
NAVER_SEARCH_DISPLAY 검색 결과 개수, 기본 10 Naver 이미지 검색 쿼리
NAVER_SEARCH_PAGES 이미지 검색 페이지 수, 기본 1, 최대 10 display 단위로 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다.
NAVER_SEARCH_SORT 정렬, 기본 sim Naver 이미지 검색 쿼리
NAVER_BLOG_SEARCH_DISPLAY 블로그 검색 결과 개수, 기본 3 이미지 검색이 직접 매칭을 만들지 못할 때 원문 페이지 대표 이미지를 찾는 보조 검색
NAVER_BLOG_SEARCH_PAGES 블로그 검색 페이지 수, 기본 1, 최대 10 블로그 보조 검색의 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다.
NAVER_BLOG_SEARCH_SORT 블로그 정렬, 기본 sim Naver 블로그 검색 쿼리
NAVER_WEB_SEARCH_DISPLAY 웹문서 검색 결과 개수, 기본 3 이미지/블로그 검색이 매칭 이미지를 만들지 못할 때 일반 웹문서 페이지 대표 이미지를 찾는 보조 검색
NAVER_WEB_SEARCH_PAGES 웹문서 검색 페이지 수, 기본 1, 최대 10 웹문서 보조 검색의 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다.
GOOGLE_CLOUD_VISION_API_KEY Cloud Vision REST API 키 신규 제출 seed 분석에서 Web Detection 사용
GOOGLE_CLOUD_VISION_PARENT 선택 project/location parent Cloud Vision 요청 body에 선택적으로 포함
COPYRIGHTER_GOOGLE_FACE_CROP_SEARCH 얼굴 영역 Google Web Detection 사용 여부, 기본 false 재분석 시 감지된 얼굴 영역 crop만 별도 파생 이미지로 보내 웹 근거를 수집한다. 동일인 판정이나 얼굴 인식 점수로 쓰지 않는다.
GOOGLE_CUSTOM_SEARCH_IMAGE_RESULTS Google 이미지 검색 1페이지 결과 개수, 기본 3 Google Custom Search 이미지 검색 쿼리
GOOGLE_CUSTOM_SEARCH_IMAGE_PAGES Google 이미지 검색 페이지 수, 기본 1, 최대 10 num 단위로 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다.
GOOGLE_CUSTOM_SEARCH_WEB_RESULTS Google 웹 검색 1페이지 결과 개수, 기본 3 이미지 검색이 직접 매칭을 만들지 못할 때 웹 검색 결과 페이지의 대표 이미지를 찾는다.
GOOGLE_CUSTOM_SEARCH_WEB_PAGES Google 웹 검색 페이지 수, 기본 1, 최대 10 웹 검색의 다음 결과 페이지까지 가져온다. 페이지 수만큼 API 호출량이 늘어난다.
COPYRIGHTER_AUTO_NAVER_QUERY_LIMIT Google 근거에서 자동 생성해 실행할 Naver 쿼리 수, 기본 3, 최대 10 Google 페이지 제목과 엔티티를 우선순위로 정렬해 여러 텍스트 이미지 검색을 자동 실행한다.
COPYRIGHTER_AUTO_NAVER_BLOG_QUERY_LIMIT 이미지 검색 매칭이 없을 때 추가 실행할 Naver 블로그 쿼리 수, 기본 1, 최대 10 블로그 검색 결과 페이지의 대표 이미지를 추출해 제출 이미지와 지문 비교한다.
COPYRIGHTER_AUTO_NAVER_WEB_QUERY_LIMIT 이미지/블로그 검색 매칭이 없을 때 추가 실행할 Naver 웹문서 쿼리 수, 기본 1, 최대 10 웹문서 검색 결과 페이지의 대표 이미지를 추출해 제출 이미지와 지문 비교한다.
COPYRIGHTER_SEARCH_RESULT_COMPARE_LIMIT 검색 결과 이미지 URL을 내려받아 제출 이미지와 지문 비교할 건수, 기본 3, 최대 20 Naver/Google 결과 이미지를 로컬 저장소에 저장한 뒤 pHash로 제출 이미지와 직접 비교한다.
COPYRIGHTER_SEARCH_RESULT_PAGE_IMAGE_LIMIT 검색 결과 원문 페이지에서 추출할 대표 이미지 수, 기본 3, 최대 10 결과 자체에 이미지 URL이 없으면 og:image, twitter:image, img 후보를 제한된 수만 내려받아 제출 이미지와 비교한다.
COPYRIGHTER_SEARCH_RESULT_SIMILARITY_THRESHOLD 검색 결과 이미지 유사도 필터 임계치, 기본 0.9, 범위 0.0~1.0 Naver/Google 결과 후보 이미지의 pHash 유사도 기반 매칭 임계치를 조절한다.
OLLAMA_BASE_URL Ollama 로컬 서버 URL, 기본 http://localhost:11434 POST /api/submissions/{id}/rerun-enrichment에서 LLM 요약 사용
OLLAMA_MODEL Ollama 모델, 기본 qwen2.5:0.5b-instruct Ollama /api/generate payload의 model
COPYRIGHTER_NAVER_DAILY_LIMIT Naver 일일 호출 제한 로컬 policy gate
COPYRIGHTER_GOOGLE_DAILY_LIMIT Google 일일 호출 제한 로컬 policy gate
COPYRIGHTER_LLM_DAILY_LIMIT LLM 일일 호출 제한 provider 상태 표시용

환경변수는 .env보다 우선한다. 예를 들어 PowerShell에서 이미 $env:OLLAMA_MODEL이 있으면 .env 값으로 덮어쓰지 않는다.

공식 문서 기준

현재 API

GET  /health
GET  /api/providers/health
GET  /api/bootstrap
GET  /api/review-queue
GET  /api/submissions/{submission_id}/review
POST /api/submissions/reload
POST /api/submissions/{submission_id}/rerun-enrichment
POST /api/submissions/{submission_id}/decision
POST /api/evidence/{evidence_id}/status
POST /api/knowledge/{entry_id}/promote-watchlist
POST /api/knowledge/{entry_id}/exclude-watchlist
POST /api/search/manual
GET  /api/providers
PATCH /api/providers/{provider_id}
POST /api/providers/emergency-disable
GET  /api/audit-events
GET  /media/{image_path}

로컬 이미지 저장 방식

제출 이미지는 두 방식으로 넣을 수 있다.

  • 빠른 방식: 이미지 파일을 data/submissions/images/ 아래에 복사한 뒤 화면에서 새 제출 불러오기를 누른다. 이 경우 제출 ID와 제목은 파일명 기준으로 자동 생성된다.
  • 명시 방식: data/submissions/submissions.json에 ID, 제목, 크기, 제출 시간을 직접 등록한 뒤 화면에서 새 제출 불러오기를 누른다.

예시:

[
  {
    "id": "SUB-LOCAL-001",
    "title": "로컬 얼굴 이미지 샘플",
    "file": "images/local-face.svg",
    "width": 1200,
    "height": 900,
    "submitted_at": "2026-05-26 10:00"
  }
]

이미지 파일은 data/submissions/images/ 아래에 둔다. 서버는 이 폴더 밖으로 나가는 경로를 거부한다.

서버를 재시작하지 않아도 된다. 운영 콘솔의 심사 큐 상단에서 새 제출 불러오기를 누르면 submissions.json 변경분과 폴더에 새로 복사한 이미지가 SQLite DB로 import된다.

운영자 판정 흐름

  1. 제출 이미지를 선택하고 상단의 선택 재분석 또는 행 안의 증거를 확인한다.
  2. 증거 행에서 판단에 사용, 무관, 오탐, 보류를 표시한다. 이 표시는 케이스 기록과 점수 반영 여부를 정리할 뿐, 기준 DB 후보를 만들지는 않는다.
  3. 케이스 판정을 먼저 내린다. 승인은 자동 후보를 만들지 않는다. 보류반려는 제출 이미지 지문과 선택된 근거를 묶어 주의 후보를 만든다.
  4. 이후 같은 이미지가 들어오면 주의 후보 근거 그룹에 별도로 표시되고 높은 위험 신호로 반영된다. 그래도 최종 판정은 자동 변경되지 않는다.
  5. 지식 DB 화면에서 주의 후보를 검토한 뒤 확정 DB 편입 또는 오탐 제외를 누른다. 제외된 후보는 다음 내부 유사도 분석에서 사용하지 않는다.

외부 API 연결 상태

연결 완료:

  • Naver: https://openapi.naver.com/v1/search/imageGET 요청을 보낸다. query, display, start, sort를 쿼리스트링으로 보내고 X-Naver-Client-Id, X-Naver-Client-Secret 헤더를 사용한다.
  • Google: https://vision.googleapis.com/v1/images:annotateWEB_DETECTION 요청을 보낸다. 서버는 내부 파생 이미지 bytes를 base64로 인코딩해 전송한다.
  • Provider 상태: 외부 API는 키가 있으면 enabled, 없으면 disabled와 missing env 사유를 /api/providers에 표시한다.
  • Provider Controls: 화면의 provider 활성/비활성 상태는 DB에 저장된다.

아직 운영 검증이 필요한 것:

  • 실제 운영 키로 Naver/Google 샘플 호출 품질 확인
  • 공급자별 재시도/backoff 정책
  • 일일 한도 초과 시 관리자 알림
  • API 키를 Windows 서비스/작업 스케줄러/배포 환경의 시크릿 저장소로 옮기는 운영 방식

운영 경계:

  • Naver에는 텍스트 쿼리만 보낸다.
  • Naver로 원본 이미지나 파생 이미지를 보내지 않는다.
  • Google에는 승인된 파생 이미지만 보낸다.
  • Google Cloud Vision 사용 전 계약, DPA, 데이터 보존, 메타데이터 로깅, 리전, 자격 증명 정책을 확인한다.

LLM 연결 상태

연결 완료:

  • Ollama 로컬 API에 POST /api/generate 요청을 보낸다.
  • 기본 URL은 http://localhost:11434, 기본 모델은 qwen2.5:0.5b-instruct다.
  • Ollama 요청은 API 키를 쓰지 않는다.
  • 응답 스트리밍은 끄고 stream: false로 한 번에 요약을 받는다.
  • rerun-enrichment는 이미 저장된 내부/Naver/Google 근거만 LLM 입력으로 넘긴다.
  • LLM 요약은 source_evidence_ids 또는 출처 URL을 가진 보조 근거로만 저장한다.
  • LLM 실패는 기존 근거를 낮추지 않고 failure evidence로 남긴다.

설치 명령:

ollama pull qwen2.5:0.5b-instruct

아직 운영 검증이 필요한 것:

  • 실제 운영 PC에서 ollama pull qwen2.5:0.5b-instruct 후 샘플 재분석 호출 품질 확인
  • 프롬프트/응답 로그 저장 여부와 마스킹 정책 확정
  • LLM 요약을 신청자에게 노출하지 않는 정책 재검증

LLM이 해도 되는 일:

  • 검색 쿼리 후보 생성
  • 증거 요약
  • 중복/상충 증거 정리
  • 운영자가 읽기 쉬운 근거 메모 생성

LLM이 하면 안 되는 일:

  • 최종 승인/보류/반려 결정
  • 단독 점수 산정
  • 출처 없는 유명인/작품/IP 단정
  • 신청자에게 보여줄 자동 설명 생성

아직 안 된 것: 운영용 인증과 배포

현재 9500 서버는 로컬 실행용이다. 실운영 전에 아래가 필요하다.

  • 로그인/세션 또는 사내 SSO
  • 일반 운영자와 관리자 권한 분리
  • Provider Controls 관리자 전용 접근
  • 운영자 결정/보정/지식 DB 변경 감사 로그 강화
  • 9500 서버 프로세스 관리 방식 확정
  • 백업, 복구, 모니터링, 알림

Google Custom Search 설정

env 용도 연결되는 흐름
GOOGLE_CUSTOM_SEARCH_API_KEY Google Programmable Search JSON API 키 Google Vision 텍스트 단서를 Google 이미지/웹 검색으로 확장
GOOGLE_CUSTOM_SEARCH_CX Programmable Search Engine ID Custom Search JSON API cx
GOOGLE_CUSTOM_SEARCH_IMAGE_RESULTS 이미지 검색 결과 개수, 기본 3 텍스트 쿼리 기반 이미지 결과를 내려받아 제출 이미지와 pHash 비교
GOOGLE_CUSTOM_SEARCH_WEB_RESULTS 웹 검색 결과 개수, 기본 3 이미지 검색이 비었을 때 웹 결과 페이지 대표 이미지를 비교
COPYRIGHTER_AUTO_GOOGLE_CUSTOM_QUERY_LIMIT 자동 Google Custom Search 쿼리 수, 기본 2, 최대 10 제출 이미지를 보내지 않고 텍스트 쿼리만 전송
COPYRIGHTER_GOOGLE_CUSTOM_SEARCH_DAILY_LIMIT Google Custom Search 일일 호출 제한 로컬 policy gate

자동 검색 쿼리 원천:

  • Google 페이지 제목과 엔티티가 가장 높은 우선순위다.
  • Google best guess label만 있는 경우에도 person, gentleman, portrait 같은 일반어는 버리고, IU official profile처럼 구체적인 문구만 낮은 우선순위 쿼리로 사용한다.
  • 얼굴 영역 Google Web Detection은 동일 인물 판정 근거로 쓰지 않는다. 다만 페이지 제목이나 엔티티가 구체적이면 낮은 우선순위의 텍스트 검색 쿼리로만 재사용한다.
  • 페이지 제목이 영어권 문구면 보조 쿼리에 image를 붙이고, 한국어 문구면 이미지를 붙인다. 검색엔진에 깨진 한글 템플릿을 보내지 않는다.
  • Google Custom Search와 Naver에는 제출 이미지를 보내지 않고 텍스트 쿼리만 보낸 뒤, 검색 결과 이미지나 원문 페이지 대표 이미지를 내려받아 로컬에서 pHash 비교한다.

설정 진단:

  • /api/providers/health와 공급자 화면은 requiredEnv, configuredEnv를 비밀값 없이 표시한다.
  • google_search가 disabled이면 우선 GOOGLE_CUSTOM_SEARCH_API_KEY, GOOGLE_CUSTOM_SEARCH_CX가 둘 다 .env에 있는지 확인한다.