docs(claude): refresh — drop stale model/IP, inventory authoritative

stale 영역 정리:
- Qwen3.5-35B-A3B / nomic-embed-text / Qwen2.5-VL-7B → 역할별 표기 (실제 모델은 inventory)
- Mac mini Tailscale 100.76.254.116 / GPU 100.111.160.84 / NAS 100.101.79.37 → 모두 폐기 (D21 closure 2026-05-12), LAN 표기만 유지
- Mac mini nginx 앞단 프록시 → 폐기 (home-caddy 가 직접 ingress)
- "Mac mini 메인 docker compose" → GPU 가 메인 정정

추가:
- 운영 변경 정책 (inventory → config → deploy → verify)
- 머신 역할 표 / AI 파이프라인 역할 표 / 워커 스케줄 표
- 아침 브리핑 / global digest 진입점 + scheduler timezone
- asyncpg multi-statement 1 파일 1 statement 규칙 (PR-MorningBriefing-1 fix 교훈)
- 디자인 토큰 only 규칙
- 한국어 NFS 경로 NFC/NFD
This commit is contained in:
Hyungi Ahn
2026-05-12 15:07:12 +09:00
parent 1f4bbb9413
commit f6f8f3b9d8
+99 -159
View File
@@ -2,127 +2,72 @@
## Infrastructure Reference 📌 ## Infrastructure Reference 📌
**Always refer to** `~/.claude/projects/-Users-hyungiahn/memory/infra_inventory.md` for: 운영 사실 (모델명 / 엔드포인트 / IP / 컨테이너 / 포트 / drift) 의 단일 진실 소스(SSOT):
- AI model routing (primary / fallback / embedding / rerank / vision) — **the model names below may be stale**
- Machine info, Tailscale IPs, SSH targets
- Docker container topology and compose projects
- Drift log (known Desired vs Actual inconsistencies)
- Verify commands
**If this file and `infra_inventory.md` disagree, `infra_inventory.md` is authoritative.** Do not change `config.yaml` / `credentials.env` without first updating `infra_inventory.md`. **`~/.claude/projects/-Users-hyungiahn/memory/infra_inventory.md`**
**Search experiment soft lock**: During Phase 2 work (search.py refactor, QueryAnalyzer, run_eval.py execution), do **not** run `docker compose restart`, change `config.yaml`, or pull Ollama models. Violating this invalidates the experiment baseline. 이 파일과 inventory 가 충돌하면 **inventory 가 정답**. 본 CLAUDE.md 는 코딩 규칙·워크플로우·코드 구조에 집중하고 운영 값은 박지 않는다.
운영 변경 정책 (inventory → config → deploy → verify):
1. `infra_inventory.md` 먼저 갱신
2. `config.yaml` / `credentials.env` 갱신
3. deploy (commit → push → GPU pull → `docker compose up -d --build`)
4. verify (smoke endpoint, postgres count, 모니터링)
순서 어기면 drift. 발견 시 inventory `Drift Log` 등록.
**Search experiment soft lock**: Phase 2 search refactor / QueryAnalyzer / run_eval 진행 중일 때 GPU 서버의 `docker compose restart`, `config.yaml` 수정, Ollama pull 금지. flag = `~/.claude/.search-experiment-active`.
--- ---
## 프로젝트 개요 ## 프로젝트 개요
Self-hosted PKM(Personal Knowledge Management) 웹 애플리케이션. Self-hosted PKM(Personal Knowledge Management) + 다국 뉴스 비교 분석 웹 애플리케이션.
FastAPI + PostgreSQL(pgvector) + SvelteKit + Docker Compose 기반. GPU 서버가 메인 (Docker Compose / DB / 검색 / OCR / 마커), Mac mini = MLX 추론 + Whisper STT, Synology NAS = 파일 원본.
GPU 서버를 메인 서버, Mac mini를 AI 추론, Synology NAS를 파일 저장소로 사용.
## 핵심 문서 ## 핵심 문서
1. `docs/architecture.md` — 전체 시스템 아키텍처 (DB 스키마, AI 전략, 인프라, UI 설계) 1. `README.md` — 외부 소개 (기술 스택 / 주요 기능 / Quick Start)
2. `docs/deploy.md` — Docker Compose 배포 가이드 2. `docs/architecture.md` — 전체 시스템 아키텍처
3. `docs/development-stages.md` — Phase 0~5 개발 단계별 가이드 3. `docs/deploy.md` — Docker Compose 배포 가이드
4. `docs/development-stages.md` — Phase roadmap (역사적 맥락)
## 기술 스택 ## 기술 스택
| 영역 | 기술 | | 영역 | 기술 |
|------|------| |------|------|
| 백엔드 | FastAPI (Python 3.11+) | | 백엔드 | FastAPI (Python 3.11+), SQLAlchemy 2.0 async, APScheduler |
| 데이터베이스 | PostgreSQL 16 + pgvector + pg_trgm | | DB | PostgreSQL 16 + pgvector + pg_trgm (단일 `pkm` DB) |
| 프론트엔드 | SvelteKit 5 (runes mode) + Tailwind CSS 4 | | 프론트엔드 | SvelteKit 5 (runes mode) + Tailwind CSS 4 |
| 문서 파싱 | kordoc (HWP/HWPX/PDF → Markdown) + LibreOffice (오피스 → 텍스트/PDF) | | 문서 파싱 | kordoc (HWP/HWPX/PDF → MD), LibreOffice headless (오피스), marker (PDF → markdown) |
| 리버스 프록시 | Caddy (HTTP only, 앞단 프록시에서 HTTPS 처리) | | OCR | Surya OCR (docker compose `ocr-service`, GPU) |
| 인증 | JWT + TOTP 2FA | | STT | MLX Whisper (Mac mini), GPU faster-whisper 는 legacy profile |
| 리버스 프록시 | Caddy (HTTP only, 앞단 home-caddy 가 HTTPS 종료) |
| 인증 | JWT (access) + HttpOnly cookie (refresh) + TOTP 2FA |
| 컨테이너 | Docker Compose | | 컨테이너 | Docker Compose |
## 네트워크 환경 ## 머신 역할 (자세한 IP / 포트 → inventory)
``` | 머신 | 역할 |
GPU 서버 (RTX 4070 Ti Super, Ubuntu, 메인 서버): |------|------|
- Docker Compose: FastAPI(:8000), PostgreSQL(:5432), kordoc(:3100), | GPU 서버 | Docker Compose 메인: fastapi · frontend · postgres `pkm` · kordoc · ocr-service · marker-service · reranker (TEI) · caddy. Ollama (embedding / 4B 추론). home-gateway 별 compose (ingress + 나노클로 + searxng) |
Caddy(:8080 HTTP only), Ollama(127.0.0.1:11434), AI Gateway(127.0.0.1:8081), frontend(:3000) | Mac mini | MLX 26B 추론 endpoint + MLX Whisper STT. ingress 역할 0 |
- NFS 마운트: /mnt/nas/Document_Server → NAS /volume4/Document_Server | Synology NAS | 파일 원본 (`/volume4/Document_Server/PKM/` → GPU `/mnt/nas/Document_Server` NFS), Synology Office/Drive/Calendar/MailPlus |
- 외부 접근: document.hyungi.net (Mac mini nginx → Caddy) | VPS-2 (OVH) | 메일 relay (`relay.hyungi.net:587`), Gitea bare mirror, Secondary MX |
- 로컬 IP: 192.168.1.186
Mac mini M4 Pro (AI 서버 + 앞단 프록시): ## AI 파이프라인 (역할 기준 — 실제 모델 매핑은 inventory)
- MLX Server: http://100.76.254.116:8800/v1/chat/completions (Qwen3.5-35B-A3B)
- nginx: HTTPS 종료 → GPU 서버 Caddy(:8080)로 프록시
- Tailscale IP: 100.76.254.116
Synology NAS (DS1525+): | 역할 | 위치 |
- LAN IP: 192.168.1.227 |------|------|
- Tailscale IP: 100.101.79.37 | 분류/심층 요약 primary | Mac mini MLX 26B |
- 파일 원본: /volume4/Document_Server/PKM/ | Triage (1차 분류) / Fallback / Chat | GPU Ollama 4B |
- NFS export → GPU 서버 | Embedding | GPU Ollama (1024d, 다국어) |
- Synology Drive: https://link.hyungi.net (문서 편집) | Reranker | GPU TEI 컨테이너 |
- Synology Calendar: CalDAV 태스크 관리 | OCR | docker compose `ocr-service` (Surya OCR GPU) — `ai.models.vision` 미사용 |
- MailPlus: IMAP(993) + SMTP(465) | STT | Mac mini MLX Whisper large-v3 |
``` | Premium (수동 trigger) | Anthropic API (`require_explicit_trigger`, 일일 한도) |
## 인증 정보 호출 시 반드시 `app/ai/client.py``AIClient` 사용 (`call_triage` / `call_primary` / `call_fallback`). 직접 HTTP 호출 금지.
- 위치: `credentials.env` (프로젝트 루트, .gitignore에 포함)
- 템플릿: `credentials.env.example`
- 스크립트에서 python-dotenv 또는 Docker env_file로 로딩
## AI 모델 구성
```
Primary (Mac mini MLX, Tailscale 경유, 상시, 무료):
mlx-community/Qwen3.5-35B-A3B-4bit — 분류, 태그, 요약
→ http://100.76.254.116:8800/v1/chat/completions
Fallback (GPU Ollama, 같은 Docker 네트워크, MLX 장애 시):
qwen3.5:35b-a3b
→ http://ollama:11434/v1/chat/completions
Premium (Claude API, 종량제, 수동 트리거만):
claude-sonnet — 복잡한 분석, 장문 처리
→ 일일 한도 $5, require_explicit_trigger: true
Embedding (GPU Ollama, 같은 Docker 네트워크):
nomic-embed-text → 벡터 임베딩
Qwen2.5-VL-7B → 이미지/도면 OCR
bge-reranker-v2-m3 → RAG 리랭킹
```
## 프로젝트 구조
```
hyungi_Document_Server/
├── docker-compose.yml
├── Caddyfile ← HTTP only, auto_https off
├── config.yaml ← AI 엔드포인트, NAS 경로, 스케줄
├── credentials.env.example
├── app/ ← FastAPI 백엔드
│ ├── main.py ← 엔트리포인트 + APScheduler (watcher/consumer 포함)
│ ├── Dockerfile ← LibreOffice headless 포함
│ ├── core/ (config, database, auth, utils)
│ ├── models/ (document, task, queue)
│ ├── api/ (documents, search, dashboard, auth, setup)
│ ├── workers/ (file_watcher, extract, classify, embed, preview, law_monitor, mailplus, digest, queue_consumer)
│ ├── prompts/classify.txt
│ └── ai/client.py ← AIClient + parse_json_response (Qwen3.5 thinking 처리)
├── services/kordoc/ ← Node.js 마이크로서비스 (HWP/PDF 파싱)
├── gpu-server/ ← AI Gateway (deprecated, 통합됨)
├── frontend/ ← SvelteKit 5
│ └── src/
│ ├── routes/ ← 페이지 (documents, inbox, settings, login)
│ └── lib/
│ ├── components/ ← Sidebar, DocumentCard, DocumentViewer, PreviewPanel,
│ │ TagPill, FormatIcon, UploadDropzone
│ ├── stores/ ← auth, ui
│ └── api.ts ← fetch wrapper (JWT 토큰 관리)
├── migrations/ ← PostgreSQL 스키마 (schema_migrations로 추적)
├── scripts/
├── docs/
└── tests/
```
## 문서 처리 파이프라인 ## 문서 처리 파이프라인
@@ -130,82 +75,77 @@ hyungi_Document_Server/
파일 업로드 (드래그 앤 드롭 or file_watcher) 파일 업로드 (드래그 앤 드롭 or file_watcher)
extract (텍스트 추출) extract (텍스트 추출)
- kordoc: HWP, HWPX, PDF → Markdown - kordoc: HWP, HWPX, PDF → Markdown
- LibreOffice: xlsx, docx, pptx, odt 등 → txt/csv - LibreOffice: xlsx, docx, pptx 등 → txt/csv
- 직접 읽기: md, txt, csv, json, xml, html - 직접 읽기: md, txt, csv, json, xml, html
↓ ↓
classify (AI 분류) preview (PDF 미리보기 생성) classify_worker (tier triage) preview / marker
- Qwen3.5 → domain - LibreOffice → PDF 변환 - 4B Ollama → TriageOutput - LibreOffice → PDF 변환
- tags, summary - 캐시: PKM/.preview/{id}.pdf - escalate_to_26b 시 deep_summary - marker → PDF → markdown
- ai_tldr / ai_bullets / inconsistencies
embed (벡터 임베딩) embed_worker (bge-m3 1024d, doc-level)
- nomic-embed-text (768차원) chunk_worker (문서 유형별 chunking)
``` ```
**핵심 원칙:** 핵심 원칙:
- 파일은 업로드 위치에 그대로 유지 (물리적 이동 없음) - 파일은 업로드 위치에 그대로 유지 (물리적 이동 없음)
- 분류(domain/sub_group/tags)는 DB 메타데이터로만 관리 - 분류 (`ai_domain` / `ai_sub_group` / `ai_tags` / `category` / `tier`) 는 DB 메타데이터로만 관리
- preview는 classify와 병렬로 실행 (AI 결과 불필요) - preview / marker 는 classify 와 병렬
## UI 구조 ## 워커 / 스케줄러 (`app/main.py` 의 scheduler.add_job)
``` - queue_consumer (interval 1m), file_watcher (5m), upload_cleanup (10m)
┌──────────────────────────────────────────────────┐ - study_q_embed (1m), study_q_related_refresh (1m), study_queue (1m), study_session_queue (1m)
│ [☰ 사이드바] [PKM / 문서] [ℹ 정보] 버튼│ ← 상단 nav - tier_backfill (30m)
├──────────────────────────────────────────────────┤ - law_monitor (07:00 KST), mailplus_archive (07/18:00 KST)
│ [검색바] [모드] [ℹ] │ - daily_digest (20:00 KST)
│ 문서 목록 (30%) — 드래그 업로드 지원 │ ← 상단 영역 - **global_digest** (04:00 KST) — Phase 4 country×topic 7일 rolling
│ █ 문서카드 (domain 색상 바 + 포맷 아이콘) │ - **morning_briefing** (05:10 KST) — 야간 KST 0~5h 수집 뉴스 topic×country 비교
├──────────────────────────────────────────────────┤
│ 하단 뷰어/편집 (70%) — 전체 너비 │ ← 하단 영역
│ Markdown: split editor (textarea + preview) │
│ PDF: 브라우저 내장 뷰어 │
│ 오피스: PDF 변환 미리보기 + [편집] 새 탭 버튼 │
│ 이미지: img 태그 │
└──────────────────────────────────────────────────┘
사이드바: 평소 접힘, ☰로 오버레이 (domain 트리 + 스마트 그룹 + Inbox) scheduler timezone = `Asia/Seoul`.
정보 패널: ℹ 버튼 → 우측 전체 높이 drawer (메모/태그 편집/메타/처리상태/편집 URL)
```
## 데이터 계층 ## 데이터 계층
1. **원본 파일** (NAS `/volume4/Document_Server/PKM/`) — 유일한 원본, 위치 변경 없음 1. **원본 파일** NAS `/volume4/Document_Server/PKM/`. 유일한 원본, 위치 변경 없음
2. **가공 데이터** (PostgreSQL) — 텍스트 추출, AI 분류, 검색 인덱스, 메모, 태그 2. **가공 데이터** PostgreSQL `pkm` (텍스트, AI 분류, 검색 인덱스, 메모, 태그, briefing, digest, …)
3. **파생물**벡터 임베딩 (pgvector), PDF 미리보기 캐시 (`.preview/`) 3. **파생물**pgvector embedding, PDF preview 캐시 (`.preview/`), marker 결과 (markdown + extracted_images NAS 저장)
## 코딩 규칙 ## 코딩 규칙
- Python 3.11+, asyncio, type hints - Python 3.11+, asyncio, type hints
- SQLAlchemy 2.0+ async 세션 - SQLAlchemy 2.0+ async 세션
- Svelte 5 runes mode ($state, $derived, $effect — $: 사용 금지) - Svelte 5 runes mode (`$state`, `$derived`, `$effect``$:` 금지)
- 인증 정보는 credentials.env에서 로딩 (하드코딩 금지) - 인증 정보는 `credentials.env` 에서 로딩 (하드코딩 금지)
- 로그는 `logs/`에 저장 (Docker 볼륨) - 로그는 `logs/` (Docker 볼륨)
- AI 호출은 반드시 `app/ai/client.py``AIClient`를 통해 (직접 HTTP 호출 금지) - AI 호출은 반드시 `app/ai/client.py` `AIClient` 경유
- 한글 주석 사용 - 한글 주석 사용
- Migration: `migrations/*.sql`에 작성, `init_db()` 자동 실행 (schema_migrations 추적) - Migration: `migrations/NNN_*.sql`, `init_db()` 자동 실행 (`schema_migrations` 추적)
- SQL에 BEGIN/COMMIT 금지 (외부 트랜잭션 깨짐) - SQL `BEGIN/COMMIT` 금지 (외부 트랜잭션 깨짐)
- 기존 DB에서는 schema_migrations에 수동 이력 등록 필요할 수 있음 - asyncpg `prepared statement` 가 multi-statement 불허 → 1 statement 1 파일 분리
- 기존 DB 에서는 `schema_migrations` 수동 이력 등록 필요할 수 있음
- 디자인 시스템 토큰 only (`bg-surface`, `text-dim`, `border-default`, `text-accent`, …). `bg-[var(--*)]` 금지 (`lint:tokens` 차단)
- 커밋 메시지: `type(scope): summary` (`feat` / `fix` / `refactor` / `ops` / `incident` / `docs`)
## 개발/배포 워크플로우 ## 개발 / 배포 워크플로우
```bash
# 개발 (MacBook Pro)
cd ~/Documents/code/hyungi_Document_Server/
# 코드 작성 → git commit → push (Gitea)
# 배포 (GPU 서버)
ssh gpu
cd ~/Documents/code/hyungi_Document_Server/
git pull
docker compose up -d --build fastapi frontend
``` ```
MacBook Pro (개발) → Gitea push → GPU 서버에서 pull
개발: PR 머지는 Gitea UI **Rebase and merge** 기본 (선형 히스토리 + force-push 충돌 회피). 단독 작업 확증 시만 로컬 rebase+FF.
cd ~/Documents/code/hyungi_Document_Server/
# 코드 작성 → git commit & push
GPU 서버 배포 (메인):
ssh hyungi@100.111.160.84
cd ~/Documents/code/hyungi_Document_Server/
git pull
docker compose up -d --build fastapi frontend
```
## v1 코드 참조 ## v1 코드 참조
v1(DEVONthink 기반) 코드는 `v1-final` 태그로 보존: v1 (DEVONthink 기반) 코드는 `v1-final` 태그로 보존:
```bash ```bash
git show v1-final:scripts/law_monitor.py git show v1-final:scripts/law_monitor.py
git show v1-final:scripts/pkm_utils.py git show v1-final:scripts/pkm_utils.py
@@ -213,10 +153,10 @@ git show v1-final:scripts/pkm_utils.py
## 주의사항 ## 주의사항
- credentials.env는 git에 올리지 않음 (.gitignore) - `credentials.env` 는 git 에 올리지 않음 (`.gitignore`)
- NAS NFS 마운트 경로: Docker 컨테이너 내 `/documents` - NAS NFS 마운트: Docker 컨테이너 내 `/documents`. FastAPI 시작 시 `/documents/PKM` 존재 확인
- FastAPI 시작 시 `/documents/PKM` 존재 확인 (NFS 미마운트 방지) - 법령 API (LAW_OC) 는 승인 대기 중
- 법령 API (LAW_OC)는 승인 대기 중 - Ollama 는 127.0.0.1 바인딩 (외부 접근 차단)
- Ollama/AI Gateway 포트는 127.0.0.1 바인딩 (외부 접근 차단) - Caddy 는 `auto_https off` + `http://` only (HTTPS 종료는 앞단 home-caddy 가 처리)
- Caddy는 `auto_https off` + `http://` only (HTTPS는 Mac mini nginx에서 처리) - Synology Office 편집은 새 탭 열기 방식 (iframe 미사용, `edit_url` 수동 등록)
- Synology Office 편집은 새 탭 열기 방식 (iframe 미사용, edit_url 수동 등록) - 한국어 NFS 경로는 NFC↔NFD 비대칭 — 경로 수신 시 NFC→NFD→parent glob fallback 필수