# 카테고리 체계 Document Server 의 1차 진입점. 6 활성 + 3 유보. 자세한 결정 배경은 `~/.claude/plans/luminous-sprouting-hamster.md` (v2.1) 참조. ## 6 활성 + 3 유보 | 카테고리 | 상태 | 저장 경로 (`/volume4/Document_Server/PKM/`) | 처리 파이프 | |---|---|---|---| | `document` | 활성 | `Inbox/...` | extract → classify → embed (+ preview/OCR) | | `library` | 활성 — **사용자 승인 후 전이** | 동상 + `@library/...` 태그 | 동상 | | `news` | 활성 | DB 만 | news_collector → classify | | `memo` | 활성 | DB 만 | 메모 UI → embed | | `audio` | 활성 (§3) | `Recordings/` | **stt → classify → embed** (extract 건너뜀) | | `video` | 활성 (§3, 최소판) | `Videos/` | **thumbnail 만** (classify/embed 안 함) | | `mail` | 유보 | (별도 plan) | — | | `calendar` | 유보 | (별도 plan) | — | | `plex` | 유보 | (별도 plan) | — | ## 역할 분리 원칙 (4 축) | 축 | 역할 | 주인 | 누가 변경 | |---|---|---|---| | `documents.category` enum | **1차 진입점** — UI 탭/라우트/Sidebar 분기 | document/library/news/memo/audio/video (+ 유보) | 백필(§1) + 사용자 승인(§2) + file_watcher(§3) | | `user_tags` 의 `@library/...` | 자료실 **내부 서가 경로** (카테고리가 library 일 때 위치) | 사용자 승인 태그 | 사용자 승인 + PATCH `/documents/{id}` | | `facet_doctype` | **AI 가 식별한 문서 유형** (분류 신호, 필터/검색 key) | 분류 파이프라인 산출물 | classify_worker | | `ai_suggestion` (JSONB) | **AI 가 제안했지만 미승인** 된 변경 후보 (category/path/doctype) | 승인 UI 소비 | classify_worker 작성 / `/accept-suggestion` clear | **4 축을 섞지 않는다.** 예: - AI 가 발주서라고 판단 → `facet_doctype='발주서'` 설정 (분류 신호 완료). `category` 는 **건드리지 않음**. - 동시에 `ai_suggestion = {proposed_category:'library', proposed_path, confidence, source_updated_at}` 저장. - 사용자가 `/library` 승인 UI 에서 1-click 승인 → 그제서야 `category='library'` + `user_tags += @library/...`, `ai_suggestion` clear. - 승인 전까지 문서는 `category='document'` 로 **문서함** 에 남음. **자동 library 승격은 v2.1 에서 하지 않는다.** 메트릭(`precision > 0.9` AND `reject_rate < 0.1` AND `월 제안 수 > 20` 1개월 유지) 충족 시 별도 plan 으로 검토. → `~/.claude/projects/-Users-hyungiahn/memory/project_document_server_future_integrations.md` ## 업로드 경로 | 채널 | document | library | audio | video | news | memo | |---|---|---|---|---|---|---| | 웹 업로드 (`UploadDropzone`) | ✅ | indirect (§4 의존) | ✅ | ✅ direct play 만 (mp4/webm) | ❌ | ❌ | | NAS 드롭 (`file_watcher`) | ✅ | ❌ (자동 전이 금지) | ✅ | ✅ + quarantine | ❌ | ❌ | | 외부 collector | ❌ | ❌ | ❌ | ❌ | `news_collector` | ❌ | | 내부 UI | ❌ | ❌ | ❌ | ❌ | ❌ | `/memos` | ## Video 채널별 정책 (§3 핵심 결정) `mp4 (H.264 AAC)`, `webm (VP9)` 만 브라우저 direct play 가능. 그 외 (`mov/mkv/avi`) 는 채널마다 다르게 처리: | 채널 | `.mp4` `.webm` | `.mov` `.mkv` `.avi` | |---|---|---| | **웹 업로드** | 수락 → `category='video'`, 썸네일 생성 | **거부 (400 + `error_code='unsupported_codec'`)**. `UploadDropzone` 가 배너 안내. | | **NAS 드롭 (file_watcher)** | 수락 → `category='video'`, 썸네일 생성 | **quarantine import** — DB 에 `category='video' + needs_conversion=true`, VideoPlayer 가 "재생 불가 — 변환 필요" 카드. 파일 삭제 안 함. | ## 업로드 한도 + error_code 체계 (§4) 업로드 한도는 `settings.upload.max_bytes` 가 단일 진실 공급원 (현재 100MB). 프록시 (home-caddy) 는 `max_bytes * content_length_slack_ratio` (기본 1.05) 이상 유지. 서버 응답 `detail` 은 `{error_code, message}` 객체: | `error_code` | HTTP | 의미 | |---|---|---| | `body_too_large` | 413 | Content-Length 또는 누적이 max_bytes 초과 | | `upload_timeout` | 408 | 서버 read timeout | | `network_abort` | 499 | 클라이언트 abort / 연결 끊김 | | `empty_file` | 400 | 0 바이트 | | `invalid_input` | 400 | 파일명/경로/필드 검증 실패 | | `unsupported_codec` | 400 | 웹 업로드 direct play 불가 비디오 (`.mov/.mkv/.avi`) | | `internal` | 500 | 그 외 알 수 없는 에러 | ## Orphan 임시파일 정리 업로드 중에는 `.uploading` 임시명으로 NAS Inbox 에 쓰고 완료 시 atomic rename. 정상 abort 는 endpoint 가 즉시 정리하지만 **프로세스 크래시 / 강제 종료** 잔존물은 `cleanup_orphan_uploads` APScheduler job (10분 주기) 이 수거. 최근 3회 누적 삭제 ≥ `cleanup_warn_threshold` (기본 10) 이면 WARNING 로그 — abort 가 구조적으로 많거나 대용량 업로드 실패 반복 신호. `file_watcher` 의 `SKIP_EXTENSIONS` 에 `.uploading` 포함 — 진행 중 파일을 잘못 픽업하지 않음.