# CLAUDE.md — web-backend 프로젝트 가이드

> Claude Code가 이 프로젝트를 작업할 때 참조하는 **안정적 카탈로그**.
> 포트·nginx 라우팅·서비스별 API 엔드포인트 목록·공통 규칙만 담는다.
> **DB 스키마 세부·스케줄러 잡·환경변수 세부·최근 기능 히스토리는 서비스별 메모리(`service_<name>.md`)가 authoritative** — 9번 섹션 각 서비스 끝의 메모리 포인터 참조.

---

## 0. 메모리 구조 규칙 (하네스 엔지니어링)

이 모노레포는 **서비스당 1개 메모리 파일**(`memory/service_<name>.md`)로 운영 상태를 관리한다.

- **CLAUDE.md (이 파일, 항상 로딩)** = 변하지 않는 지도: 포트, nginx 라우팅, 서비스 한 줄 역할, API 엔드포인트 목록, cross-cutting 규칙.
- **`service_<name>.md` (관련 시 recall)** = 휘발성 상세: DB 테이블+컬럼, 스케줄러 cron, 환경변수, provider/큐 흐름, 비자명한 함정, 최근 기능 작업 히스토리.

**작업 시작 전**: 해당 서비스의 `service_<name>.md`를 먼저 읽어 최신 운영 상태·함정을 확인할 것. 14개 서비스 전부 메모리 파일이 있다(`MEMORY.md` 인덱스 참조).
**변경 후**: DB 스키마/스케줄러/운영 흐름이 바뀌면 CLAUDE.md가 아니라 해당 서비스 메모리를 갱신할 것.

---

## 1. 프로젝트 개요

Synology NAS 기반의 개인 웹 플랫폼 백엔드 모노레포.
- **서비스 15개**: lotto, stock, music-lab, video-lab, image-lab, insta-lab, realestate-lab, agent-office, tarot-lab, saju-lab, personal, packs-lab, travel-proxy, co-gahusb, deployer
- **공유 인프라**: `_shared/access_log` 모듈 (5개 서비스 공유), `redis` (music/video/image/insta-lab 큐 공유)
- **렌더/생성 위임**: music/video/image/insta의 무거운 생성·렌더는 **Windows AI 워커**(`web-ai` 별도 레포)가 담당. NAS 서비스는 Redis 큐 push + 결과 webhook 수신만 한다.
- **프론트엔드**: 별도 레포 (React + Vite SPA), 빌드 산출물만 NAS에 배포
- **인프라**: Docker Compose (16+ 컨테이너) + Nginx(리버스 프록시) + Gitea Webhook 자동 배포

---

## 2. NAS 환경

| 항목 | 값 |
|------|----|
| 장비 | Synology NAS |
| CPU | Intel Celeron J4025 (2 Core, 2.0 GHz) |
| 메모리 | 18 GB |
| Docker | Synology Container Manager |
| Git 서버 | Gitea (self-hosted, NAS 내부) |
| AI 서버 | Windows PC (192.168.45.59) — NVIDIA RTX 5070 Ti (16GB VRAM) + Ollama. 상세 → `infra_windows_ai.md` 메모리 |

---

## 3. NAS 디렉토리 구조

```
/volume1
├── docker/webpage/          # 운영 런타임 (Docker Compose 실행 위치)
│   ├── <service>/           # 각 서비스 소스 (rsync 동기화)
│   ├── nginx/default.conf   # Nginx 설정
│   ├── scripts/deploy.sh    # Webhook 트리거 배포 스크립트
│   ├── docker-compose.yml
│   ├── .env                 # 운영 환경변수
│   └── data/                # SQLite DB + 생성 미디어 (*.db, music/, video/, image/, insta_cards/ 등)
│
├── workspace/web-page-backend/  # Git 레포 클론 위치 (REPO_PATH)
│
└── web/images/webPage/travel/   # 원본 여행 사진 (RO 마운트)
```

배포 흐름·런타임 함정 상세 → `service_deployer.md`, `feedback_nas_deploy_runtime.md` 메모리.

---

## 4. Docker 서비스 & 포트

| 컨테이너 | 포트 | 역할 |
|---------|------|------|
| `lotto` | 18000 | 로또 데이터 수집·분석·추천 API |
| `stock` | 18500 | 주식 뉴스·AI 분석·KIS API 연동 + 보유종목 인텔리전스 |
| `music-lab` | 18600 | AI 음악 생성 게이트웨이 (Suno/MusicGen 호출은 Windows 워커, NAS는 Redis push) |
| `video-lab` | 18801 | 동영상 생성 게이트웨이 (sora/veo/kling/seedance, Redis 큐) |
| `image-lab` | 18802 | 이미지 생성 게이트웨이 (gpt_image/nano_banana/flux, Redis 큐) |
| `insta-lab` | 18700 | 인스타 카드 피드 자동 생성 (렌더는 Windows insta-render 워커) |
| `realestate-lab` | 18800 | 부동산 청약 자동 수집·매칭 API |
| `agent-office` | 18900 | AI 에이전트 오피스 (실시간 WebSocket + 텔레그램 연동) |
| `tarot-lab` | 18250 | 타로 카드 해석 (Claude Sonnet 3-card, agent-office에서 분리) |
| `saju-lab` | 18300 | 사주 분석 + 궁합 (Claude Sonnet, TS→Python 포팅, lunar↔solar 내장) |
| `packs-lab` | 18950 | NAS 자료 다운로드 자동화 (DSM 공유 링크 + 5GB 업로드, Vercel SaaS와 HMAC 통신) |
| `personal` | 18850 | 개인 서비스 (포트폴리오·블로그·투두 통합) |
| `travel-proxy` | 19000 | 여행 사진 API + 썸네일 생성 |
| `co-gahusb` | 18920 | 세션 간 협업 팀 버스 (FastMCP streamable-http + Redis, Bearer `CO_BUS_KEY`, DNS-rebinding 보호 off) |
| `redis` | 6379 | 비동기 큐 (music/video/image/insta-lab + co-gahusb 공유) |
| `frontend` (nginx) | 8080 | 정적 SPA 서빙 + API 리버스 프록시 |
| `webpage-deployer` | 19010 | Gitea Webhook 수신 → 자동 배포 |

---

## 5. Nginx 라우팅 규칙

| 경로 | 프록시 대상 | 비고 |
|------|------------|------|
| `/api/` | `lotto:8000` | lotto API (catch-all fallback) |
| `/api/travel/` | `travel-proxy:8000` | travel API (proxy_read_timeout 600s) |
| `/api/stock/` | `stock:8000` | stock API |
| `/api/trade/` | `stock:8000` | KIS 실계좌 API |
| `/api/portfolio` | `stock:8000` | trailing slash 유무 모두 매칭 |
| `/api/music/` | `music-lab:8000` | AI 음악 생성·라이브러리 API (660s) |
| `/api/video/` | `video-lab:8000` | 동영상 생성 게이트웨이 (120s) |
| `/api/image/` | `image-lab:8000` | 이미지 생성 게이트웨이 (120s) |
| `/api/insta/` | `insta-lab:8000` | 인스타 카드 자동 생성 API (300s) |
| `/api/realestate/` | `realestate-lab:8000` | 부동산 청약 API |
| `/api/tarot/` | `tarot-lab:8000` | 타로 해석 (proxy_read/send_timeout **600s**, Claude 3-card 응답) |
| `/api/saju/` | `saju-lab:8000` | 사주 분석 (300s) |
| `/api/todos` | `personal:8000` | 투두 API |
| `/api/blog/` | `personal:8000` | 블로그 API |
| `/api/profile/` | `personal:8000` | 포트폴리오 API |
| `/api/agent-office/` | `agent-office:8000` | AI 에이전트 오피스 API + WebSocket (86400s) |
| `/api/co/` | `co-gahusb:8000/` | MCP 팀 버스 (trailing-slash strip → `/mcp`, `Authorization` forward, `proxy_buffering off`, 3600s) |
| `/api/packs/upload` | `packs-lab:8000` | 5GB multipart 업로드 (`client_max_body_size 5G`, `proxy_request_buffering off`, **1800s** timeout) |
| `/api/packs/` | `packs-lab:8000` | 다운로드/list |
| `/api/internal/insta/` | `insta-lab:8000` | Windows 워커 webhook (nginx IP 화이트리스트 + 앱 `X-Internal-Key`) |
| `/api/internal/music/` | `music-lab:8000` | Windows 워커 webhook (IP 화이트리스트 + `X-Internal-Key`) |
| `/api/internal/video/` | `video-lab:8000` | Windows 워커 webhook (IP 화이트리스트 + `X-Internal-Key`) |
| `/api/internal/image/` | `image-lab:8000` | Windows 워커 webhook (IP 화이트리스트 + `X-Internal-Key`) |
| `/api/webai/` | `stock:8000` | Windows AI 서버 프록시 (rate-limited 60r/m) |
| `/webhook`, `/webhook/` | `deployer:9000` | Gitea Webhook |
| `/ext/feargreed` | CNN API | 공포탐욕지수 외부 프록시 |
| `/ext/vix`, `/ext/treasury`, `/ext/wti`, `/ext/brent` | Yahoo Finance | 시장 지표 외부 프록시 |
| `/media/music/` | `/data/music/` (파일 직접 서빙) | 생성된 오디오 파일 (30d cache) |
| `/media/video/` | `/data/video/` (파일 직접 서빙) | video-lab 생성 영상 (1d cache). **단수 `video`** |
| `/media/videos/` | `/data/videos/` (파일 직접 서빙) | music-lab 뮤직비디오 (1d). **복수 `videos`** |
| `/media/image/` | `/data/image/` (파일 직접 서빙) | image-lab 생성 이미지 (1d cache) |
| `/media/insta/` | `/data/insta_cards/` (파일 직접 서빙) | 카드 PNG (1h cache) |
| `/media/travel/.thumb/` | `/data/thumbs/` (파일 직접 서빙) | 썸네일 캐시 (30d). nginx miss 시 앱 라우트 폴백 생성 |
| `/media/travel/` | `/data/travel/` (파일 직접 서빙) | 원본 사진 (7d) |
| `/assets/` | 정적 파일 (장기 캐시) | Vite 해시 파일 (1y immutable) |
| `/` | SPA fallback (`try_files → index.html`) | `index.html` no-cache |

---

## 6. 기술 스택

| 레이어 | 기술 |
|--------|------|
| Backend 언어 | Python 3.12 |
| API 프레임워크 | FastAPI |
| DB | SQLite (`/app/data/*.db`) |
| 스케줄러 | APScheduler |
| 컨테이너 | Docker (`python:3.12-slim` 기반) |
| AI 연동 | Claude API (Anthropic) + Ollama (Llama 3.1, Windows PC 192.168.45.59) |
| 주식 API | KIS (한국투자증권) Open API |
| 생성 워커 | Windows `web-ai` 레포 (music/video/image/insta 렌더·생성) |

---

## 7. 자동 배포 흐름

```
개발자 git push → Gitea → Webhook (HMAC SHA256 검증)
  → deployer 컨테이너 → scripts/deploy.sh (오케스트레이터)
  → deploy-nas.sh (rsync REPO→RUNTIME) → docker compose up -d --build
```

- 배포 스크립트 동기화 함정(6개 hardcoded 위치) → `feedback_deploy_script_sync.md` 메모리
- Webhook 검증·동시배포 락·헬스체크 게이트·`.releases` 백업 상세 → `service_deployer.md` 메모리
- 머지 후 첫 webhook이 깨지는 패턴 → `feedback_nas_deploy_runtime.md` 메모리
- **프론트엔드는 자동 배포 안 됨**: 로컬 Vite 빌드 후 NAS에 수동 업로드

---

## 8. 로컬 개발 환경

```bash
# .env 기본값으로 즉시 실행 가능 (RUNTIME_PATH=., PHOTO_PATH=./mock_data/photos)
docker compose up -d
```

⚠️ **Docker는 NAS에서만 구동** — 로컬에서 docker 명령어 실행 금지 (`feedback_docker_nas.md`).

| 서비스 | 로컬 URL | | 서비스 | 로컬 URL |
|--------|----------|--|--------|----------|
| Frontend + API | http://localhost:8080 | | Tarot Lab | http://localhost:18250 |
| Lotto | http://localhost:18000 | | Saju Lab | http://localhost:18300 |
| Stock | http://localhost:18500 | | Video Lab | http://localhost:18801 |
| Music Lab | http://localhost:18600 | | Image Lab | http://localhost:18802 |
| Insta Lab | http://localhost:18700 | | Personal | http://localhost:18850 |
| Realestate Lab | http://localhost:18800 | | Agent Office | http://localhost:18900 |
| Packs Lab | http://localhost:18950 | | Travel API | http://localhost:19000 |
| Redis | redis://localhost:6379 | | | |

---

## 9. 서비스별 API 엔드포인트

> 각 서비스의 DB 스키마·스케줄러·환경변수·운영 함정·최근 작업은 **해당 메모리 파일**을 읽을 것.

### lotto (lotto/)
로또 데이터 수집·분석·추천 + 자율 학습(능동 시그널·가중치 진화·자가학습 백테스트).
- 핵심 파일: `main.py`, `db.py`, `recommender.py`, `collector.py`, `checker.py`, `generator.py`, `analyzer.py`, `purchase_manager.py`, `strategy_evolver.py`, `backtest.py`, `routers/`(curator/briefing/review/backtest)
- 📌 상세(DB 15테이블·스케줄러·v1~v3 자율학습): **`service_lotto.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/lotto/latest` | 최신 당첨번호 |
| GET | `/api/lotto/{drw_no}` | 특정 회차 |
| GET | `/api/lotto/stats` | 번호 빈도 통계 |
| GET | `/api/lotto/analysis` | 5가지 통계 분석 리포트 |
| GET | `/api/lotto/best` | 시뮬레이션 최적 번호 (기본 20쌍) |
| GET | `/api/lotto/simulation` | 시뮬레이션 상세 결과 |
| GET | `/api/lotto/recommend` | 통계 기반 추천 |
| GET | `/api/lotto/recommend/heatmap` | 히트맵 기반 추천 |
| GET/POST | `/api/lotto/recommend/batch` | 배치 추천 (조회/저장) |
| GET | `/api/lotto/recommend/smart` | 전략 진화 기반 메타 추천 |
| GET/POST | `/api/lotto/purchase` | 구매 이력 조회/등록 |
| PUT/DELETE | `/api/lotto/purchase/{id}` | 구매 이력 수정/삭제 |
| GET | `/api/lotto/purchase/stats` | 구매 통계 (전체/실제/가상 + 전략별) |
| GET | `/api/lotto/strategy/weights` | 전략별 가중치 + 성과 + trend |
| GET | `/api/lotto/strategy/performance` | 전략별 회차 성과 이력 (차트용) |
| POST | `/api/lotto/strategy/evolve` | 수동 가중치 재계산 |
| POST | `/api/admin/simulate`, `/api/admin/sync_latest` | 시뮬레이션·동기화 수동 실행 |
| GET | `/api/history` | 추천 이력 (limit, offset, favorite, tag, sort) |
| PATCH/DELETE | `/api/history/{id}` | 즐겨찾기·메모·태그 수정 / 삭제 |
| GET | `/api/lotto/curator/candidates`, `/curator/context`, `/curator/usage` | 큐레이터 후보·맥락·토큰비용 |
| POST | `/api/lotto/briefing` | AI 브리핑 저장 |
| GET | `/api/lotto/briefing/latest`, `/briefing/{draw_no}`, `/briefing` | 브리핑 조회/이력 |
| GET | `/api/lotto/evolver/status`, `/evolver/history`, `/evolver/trials/{week_start}` | 가중치 진화 상태/이력/주별 trial |
| POST | `/api/lotto/evolver/generate-now`, `/evolver/evaluate-now` | 진화 수동 트리거 |
| GET | `/api/lotto/backtest/track-record` | forward 가상구매 성적 |
| GET | `/api/lotto/backtest/calibration` | winner 캘리브레이션 percentile |
| GET | `/api/lotto/backtest/review/{draw_no}` | 특정 회차 백테스트 리뷰 |
| POST | `/api/lotto/backtest/run-forward` | forward 가상구매 수동 실행 |
| POST | `/api/lotto/backtest/backfill` | 역대 캘리브레이션 백필 (`batch`, `sample_m`) |

### stock (stock/)
주식 뉴스·AI 분석·KIS 연동 + **보유종목 인텔리전스**(advisory 브리핑) + 스크리너.
- 핵심 파일: `main.py`, `db.py`, `scraper.py`, `price_fetcher.py`, `holdings_intel.py`, `screener/`, `holidays.json`
- Windows AI 서버 연동: `WINDOWS_AI_SERVER_URL=http://192.168.45.59:8000`
- 📌 상세(DB 테이블·스케줄러·보유종목 인텔 아키텍처): **`service_stock.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/stock/news` | 뉴스 조회 (`limit`, `category`) |
| GET | `/api/stock/indices` | 주요 지표 실시간 조회 |
| GET | `/api/stock/holidays` | KRX 공휴일 목록 |
| POST | `/api/stock/scrap` | 수동 뉴스 스크랩 트리거 |
| GET | `/api/stock/holdings/intel` | 보유종목 advisory (+ `/history`, `/run`) |
| GET | `/api/trade/balance` | 실계좌 잔고 (Windows AI 프록시) |
| POST | `/api/trade/order` | 주식 주문 (Windows AI 프록시) |
| GET/POST | `/api/portfolio` | 포트폴리오 조회/추가 |
| PUT/DELETE | `/api/portfolio/{id}` | 종목 수정/삭제 |
| GET/PUT | `/api/portfolio/cash` | 예수금 조회/upsert |
| DELETE | `/api/portfolio/cash/{broker}` | 예수금 삭제 |
| POST | `/api/portfolio/snapshot` | 총 자산 스냅샷 수동 저장 |
| GET | `/api/portfolio/snapshot/history` | 스냅샷 이력 (`days`) |
| GET/POST | `/api/portfolio/sell-history` | 매도 내역 조회/저장 |
| PUT/DELETE | `/api/portfolio/sell-history/{id}` | 매도 기록 수정/삭제 |
| GET/POST/DELETE | `/api/stock/watchlist` (+ `/{ticker}`) | 실시간 매수 알람 관심종목 CRUD |
| GET | `/api/stock/trade-alerts` | 매매 알람 이력 (`days`) |
| GET | `/api/webai/trade-alert/monitor-set` | (워커) 감시대상 조립 = watchlist∪screener∪보유 + session/params (X-WebAI-Key) |
| POST | `/api/webai/trade-alert/report` | (워커) 발화집합 수신 → edge diff → 신규만 텔레그램 push (X-WebAI-Key) |

### music-lab (music-lab/)
듀얼 프로바이더 음악 생성(Suno + MusicGen) + YouTube 영상 자동화 파이프라인 + 시장 트렌드.
- ⚠️ **NAS는 게이트웨이** — Suno/MusicGen 호출은 Windows `music-render` 워커가 담당. NAS는 `queue:music-render` Redis push + `/api/internal/music/update` webhook 수신. `suno_provider.py`/`local_provider.py`는 DEPRECATED stub.
- 핵심 파일: `main.py`, `db.py`, `batch_generator.py`, `compiler.py`, `internal_router.py`, `market.py`, `pipeline/`(orchestrator/cover/video/thumb/metadata/review/youtube/state_machine 등)
- 📌 상세(DB 14테이블·env·pipeline state machine·YouTube OAuth): **`service_music.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/music/providers`, `/models`, `/credits`, `/genres` | 프로바이더·모델·크레딧·장르 |
| POST | `/api/music/generate` | 음악 생성 (provider/model/vocal 등) |
| GET | `/api/music/status/{task_id}` | 생성 상태 폴링 |
| POST | `/api/music/lyrics`, `/style-boost` | 가사·스타일 프롬프트 생성 |
| GET/POST/DELETE | `/api/music/library` | 라이브러리 조회/추가/삭제 |
| POST | `/api/music/extend`, `/vocal-removal`, `/wav`, `/stem-split`, `/cover-image` | 곡 후처리 |
| POST | `/api/music/upload-cover`, `/upload-extend`, `/add-vocals`, `/add-instrumental` | 외부 음원 가공 |
| GET | `/api/music/timestamped-lyrics` | 타임스탬프 가사 |
| GET/POST/PUT/DELETE | `/api/music/lyrics/library` | 가사 라이브러리 CRUD |
| POST/GET | `/api/music/generate-batch` | 배치 생성 |
| POST/GET | `/api/music/compile` (+ `/compiles/{id}/export`) | 컴파일 |
| POST/GET/DELETE | `/api/music/video-project` (+ `/{id}/render`, `/export`) | 영상 프로젝트 |
| ALL | `/api/music/pipeline` (생성/start/feedback/cancel/publish/retry/telegram-msg/lookup) | YouTube 자동화 파이프라인. `POST /{id}/retry`=실패 step 재개(publish+업로드완료 시 409) |
| DELETE | `/api/music/pipeline/{id}` | 파이프라인 행 하드 삭제(자식 jobs/feedback 포함, 전체 목록에서 제거). 없으면 404 |
| GET/PUT | `/api/music/setup` | 파이프라인 설정 |
| GET | `/api/music/youtube/auth-url`, `/callback`, `/status`; POST `/disconnect` | YouTube OAuth |
| GET/POST/PUT/DELETE | `/api/music/revenue` (+ `/dashboard`) | 수익 기록 |
| POST | `/api/music/market/ingest` | 트렌드 수신 + 리포트 |
| GET | `/api/music/market/trends`, `/report/latest`, `/report`, `/suggest` | 트렌드 조회·추천 |
| POST | `/api/internal/music/update` | Windows 워커 결과 webhook |

### video-lab (video-lab/)
동영상 생성 게이트웨이 (Redis 비동기 큐 `queue:video-render`). provider: sora/veo/kling/seedance.
- 핵심 파일: `main.py`, `db.py`, `internal_router.py`, `auth.py`
- 흐름: POST generate → task_id + Redis push → Windows 워커 pop → `/api/internal/video/update` webhook → DB 업데이트. 출력 mp4 `/data/video/` → nginx `/media/video/` (1d)
- 📌 상세(`video_tasks` 스키마·큐 payload·`X-Internal-Key`): **`service_video.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/api/video/generate` | 영상 생성 → task_id + Redis push |
| GET | `/api/video/tasks/{id}` | 생성 상태 폴링 |
| GET | `/api/video/providers` | 지원 provider 목록 |
| POST | `/api/internal/video/update` | Windows 워커 결과 webhook |

### image-lab (image-lab/)
이미지 생성 게이트웨이 (Redis 비동기 큐 `queue:image-render`). provider: gpt_image/nano_banana/flux.
- 핵심 파일: `main.py`, `db.py`, `internal_router.py`, `auth.py`
- 흐름: video-lab과 동형. 출력 png/jpg `/data/image/` → nginx `/media/image/` (1d)
- 📌 상세(`image_tasks` 스키마·provider 모델 메타·`X-Internal-Key`): **`service_image.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/api/image/generate` | 이미지 생성 → task_id + Redis push |
| GET | `/api/image/tasks/{id}` | 생성 상태 폴링 |
| GET | `/api/image/providers` | 지원 provider 목록 |
| POST | `/api/internal/image/update` | Windows 워커 결과 webhook |

### insta-lab (insta-lab/)
인스타그램 카드 피드 자동 생성 — 뉴스→키워드→10페이지 카드 카피 → 텔레그램 푸시 → 사용자 수동 업로드.
- ⚠️ **렌더는 NAS가 안 함** — `card_renderer.py`는 DEPRECATED stub. NAS는 `queue:insta-render` Redis push만, 실제 Jinja→Playwright 렌더는 Windows `insta-render` 워커(web-ai). 워커가 `GET /slates/{id}` fetch → 렌더 → `/api/internal/insta/update` webhook.
- 핵심 파일: `app/main.py`, `config.py`, `db.py`, `news_collector.py`, `keyword_extractor.py`, `card_writer.py`, `internal_router.py`, `trend_collector.py`, `design_importer.py`, `templates/<theme>/card.html.j2`
- 카드 사이즈 1080×1350 (4:5). 디자인 import는 **로컬 Python 실행 필수**(NAS docker exec 시 소실 → `feedback_container_ephemeral_artifacts.md`)
- 📌 상세(DB 스키마·디자인 import·v2 카드뉴스·렌더 아키텍처·미해결 갭): **`service_insta.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/insta/status` | 서비스 상태 (NAVER/ANTHROPIC 키 여부) |
| POST | `/api/insta/news/collect` | 뉴스 수집 트리거 |
| GET | `/api/insta/news/articles` | 수집 기사 목록 |
| POST | `/api/insta/keywords/extract` | 키워드 추출 트리거 |
| GET | `/api/insta/keywords` | 트렌딩 키워드 목록 |
| GET/POST | `/api/insta/slates` | 슬레이트 목록/생성 |
| GET/DELETE | `/api/insta/slates/{id}` | 슬레이트 상세/삭제 |
| POST | `/api/insta/slates/{id}/render` | 카드 렌더 재시도 |
| GET | `/api/insta/slates/{id}/assets/{page}` | 카드 PNG 다운로드 (1~10) |
| GET | `/api/insta/slates/{id}/package` | zip 패키지 (10 PNG + caption.txt) |
| GET | `/api/insta/keywords/ranked` | 4신호 선별 점수 + eligible (자율 발급용) |
| POST | `/api/insta/slates/{id}/decision` | 승인/반려 (approved→published) |
| GET | `/api/insta/tasks/{task_id}` | BackgroundTask 상태 폴링 |
| GET/PUT | `/api/insta/templates/prompts/{name}` | 프롬프트 템플릿 CRUD |
| POST | `/api/internal/insta/update` | Windows 워커 결과 webhook |

### realestate-lab (realestate-lab/)
공공데이터포털 청약 분양정보 수집 + 자치구 5티어 매칭 + agent-office push 알림.
- 핵심 파일: `main.py`, `db.py`, `collector.py`, `matcher.py`, `notifier.py`, `models.py`
- 매칭 100점: 지역35 / 주택유형10 / 면적15 / 가격15 / 자격25
- 📌 상세(DB 스키마·스케줄러 4단계·매칭 모델·notifier 멱등 흐름·env): **`service_realestate.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET/POST | `/api/realestate/announcements` | 공고 목록(district/match_score 포함)/수동 등록 |
| GET/PUT/DELETE | `/api/realestate/announcements/{id}` | 공고 상세/수정/삭제 |
| PATCH | `/api/realestate/announcements/{id}/bookmark` | 북마크 토글 (텔레그램 콜백 대상) |
| DELETE | `/api/realestate/announcements/closed` | 완료 공고 일괄 삭제 |
| POST/GET | `/api/realestate/collect` (+ `/collect/status`) | 수동 수집(collect→cleanup→match→notify)/상태 |
| GET/PUT | `/api/realestate/profile` | 프로필 조회/수정 (preferred_districts, min_match_score, notify_enabled) |
| GET | `/api/realestate/matches` | 매칭 결과 (district/status 포함) |
| POST | `/api/realestate/matches/refresh` | 매칭 재계산 |
| PATCH | `/api/realestate/matches/{id}/read` | 신규 알림 읽음 |
| GET | `/api/realestate/dashboard` | 요약 (진행중·신규매칭·일정) |

### agent-office (agent-office/)
AI 에이전트 가상 오피스 — 기존 서비스 API를 프록시로 호출, 실시간 WebSocket + 텔레그램 봇.
- 핵심 파일: `main.py`, `db.py`, `config.py`, `websocket_manager.py`, `service_proxy.py`, `telegram_bot.py`, `scheduler.py`, `node_monitor.py`(분산 워커 관측 집계+경보), `agents/`(stock/music/realestate/youtube/youtube_publisher/lotto/base)
- 에이전트 7종 레지스트리. 명령 API body 필드명 → `reference_agent_office_command_api.md`
- 📌 상세(DB 9테이블·FSM·전체 cron 목록·AGENT_CONTAINER_MAP·텔레그램 캐싱·env): **`service_agent_office.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| WS | `/api/agent-office/ws` | WebSocket (init/agent_state/task_complete/command_result) |
| GET | `/api/agent-office/agents` (+ `/{id}`, `/{id}/tasks`, `/{id}/logs`) | 에이전트 목록·상세·이력·로그 |
| PUT | `/api/agent-office/agents/{id}` | 에이전트 설정 수정 |
| GET | `/api/agent-office/tasks/pending` (+ `/{id}`) | 승인 대기·작업 상세 |
| POST | `/api/agent-office/command` | 에이전트 명령 전송 |
| POST | `/api/agent-office/approve` | 작업 승인/거부 |
| POST | `/api/agent-office/telegram/webhook` | 텔레그램 Webhook (realestate_bookmark_* 콜백 포함) |
| POST | `/api/agent-office/realestate/notify` | realestate-lab 전용 push 수신 → 텔레그램 |
| GET | `/api/agent-office/states` | 전체 에이전트 상태 |
| GET | `/api/agent-office/nodes` | 분산 워커(NAS↔Windows) 관측 — heartbeat 생사+큐깊이+dead-letter 집계 (web-ui `/infra` Three.js 시각화 소비). 상세 → `infra_distributed_workers.md` |
| GET | `/api/agent-office/activity` | 전 에이전트 통합 활동 피드 (tasks+logs UNION). 필터 `agent_id`/`type`(task\|log)/`status`/`days` + `limit`/`offset` |
| GET | `/api/agent-office/conversation/stats` | 텔레그램 대화 토큰·캐시 통계 (`days`) |
| POST/GET | `/api/agent-office/youtube/research` (+ `/status`) | YouTube 트렌드 수집 트리거/상태 |
| GET | `/api/agent-office/lotto/signals`, `/lotto/baselines` | 로또 시그널 이력·baseline |
| POST | `/api/agent-office/lotto/signal-check` | 로또 시그널 평가 트리거 (light/sim/deep) |
| POST | `/api/agent-office/stock/trade-alert` | stock에서 push된 매매 알람 → 텔레그램(너+아내). 봇 명령 `/watch`·`/unwatch`·`/watchlist`로 watchlist 관리 |

### tarot-lab (tarot-lab/)
타로 카드 해석 (Claude Sonnet, agent-office에서 2026-05-25 독립).
- 핵심 파일: `app/main.py`, `pipeline.py`, `prompt.py`, `schema.py`, `models.py`, `config.py`, `db.py`
- interpret(해석만, DB 저장 X) ↔ readings(저장) 2단계 분리. nginx 600s
- 📌 상세(`tarot_readings` 스키마·max_tokens/truncation/reroll·env): **`service_tarot.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/api/tarot/interpret` | 카드 배치 → AI 해석 (저장 X) |
| POST/GET | `/api/tarot/readings` | 해석 결과 저장 / 기록 목록 (page, favorite, spread_type, category) |
| GET/PATCH/DELETE | `/api/tarot/readings/{id}` | 기록 상세 / 즐겨찾기·note 수정 / 삭제 |

### saju-lab (saju-lab/)
사주 분석 + 궁합 (Claude Sonnet 4.6 + prompt caching, saju-web TS→Python 포팅).
- 핵심 파일: `main.py`, `routers/`(saju+compat), `interpret/`(pipeline+prompt+schema), `calculator/`(core/analysis/daeun/lunar/shinsal/compatibility/fortune_scores/lucky/monthly_flow/solar_terms)
- TS 원본 버그도 동등 재현(`feedback_ts_python_reference_fixture.md`). DSM timeout 300s+
- 📌 상세(DB 스키마·계산엔진·TS 버그·UI v2 schema 매핑): **`service_saju.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/api/saju/interpret` | 사주 계산 + AI 해석 + DB 저장 (fortune_scores/lucky/monthly_flow 포함) |
| GET | `/api/saju/readings` (+ `/{id}`) | 사주 기록 목록/상세 |
| PATCH/DELETE | `/api/saju/readings/{id}` | 즐겨찾기·메모 수정 / 삭제 |
| GET | `/api/saju/current-fortune` | 저장된 사주의 현재 연도 세운 (실시간 계산) |
| POST | `/api/saju/compat/interpret` | 두 사람 궁합 계산 + AI 해석 |
| GET | `/api/saju/compat/readings` (+ `/{id}`) | 궁합 기록 목록/상세 |
| PATCH/DELETE | `/api/saju/compat/readings/{id}` | 궁합 즐겨찾기·메모 수정 / 삭제 |

### packs-lab (packs-lab/)
NAS 자료 다운로드 자동화 — DSM 공유링크 발급 + 5GB chunked 업로드. Vercel SaaS와 HMAC 통신.
- 핵심 파일: `app/main.py`, `auth.py`, `dsm_client.py`, `routes.py`, `models.py`
- 외부 DB: Supabase `pack_files`. 경로 3분리(`PACK_DATA_PATH`→`PACK_BASE_DIR`→`PACK_HOST_DIR`)
- 📌 상세(HMAC 패턴·chunked contract·운영검증 DSM env·backlog): **`service_packs.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/api/packs/sign-link` | HMAC → DSM 4시간 다운로드 URL 발급 |
| POST | `/api/packs/admin/mint-token` | HMAC → 일회성 upload 토큰 (30분 TTL) |
| POST | `/api/packs/upload` | Bearer → single-shot 5GB 저장 + Supabase INSERT |
| POST | `/api/packs/upload/init` | Bearer → chunked 세션 초기화 (jti consume) |
| PUT | `/api/packs/upload/{session_id}/chunk` | 부분파일 append (offset 불일치 시 409 + `X-Current-Offset`) |
| GET | `/api/packs/upload/{session_id}/status` | `{written, expected_size}` (재개용) |
| POST | `/api/packs/upload/{session_id}/complete` | rename + Supabase INSERT |
| DELETE | `/api/packs/upload/{session_id}` | 세션 중단 + 부분파일 정리 |
| GET | `/api/packs/list` | HMAC → 활성 pack_files |
| DELETE | `/api/packs/{file_id}` | HMAC → soft delete |

### personal (personal/)
포트폴리오 + 블로그 + 투두 통합. 편집 인증 Bearer 24h TTL (인메모리).
- 핵심 파일: `main.py`, `db.py`, `models.py`, `auth.py`
- ⚠️ `DELETE /api/todos/done`은 `DELETE /api/todos/{id}`보다 **반드시 먼저** 등록 (FastAPI prefix 매칭)
- 📌 상세(DB 7테이블·인증 흐름·라우트 함정): **`service_personal.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/profile/public` | 공개 데이터 일괄 조회 |
| POST | `/api/profile/auth` | 비밀번호 인증 → 토큰 |
| GET/PUT | `/api/profile/profile` | 프로필 조회/수정 (인증) |
| GET/POST/PUT/DELETE | `/api/profile/careers`, `/projects`, `/skills`, `/introductions` | 각 섹션 CRUD (인증) |
| PATCH | `/api/profile/introductions/{id}/main` | 메인 자기소개 지정 (인증) |
| GET/POST | `/api/todos` | 투두 목록/생성 |
| PUT/DELETE | `/api/todos/{id}` | 투두 수정/삭제 |
| DELETE | `/api/todos/done` | 완료 항목 일괄 삭제 (라우트 순서 주의) |
| GET/POST/PUT/DELETE | `/api/blog/posts` (+ `/{id}`) | 블로그 글 CRUD |

### travel-proxy (travel-proxy/)
여행 사진 API + 썸네일(480×480 Pillow) + 지역 관리.
- 핵심 파일: `main.py`, `db.py`, `indexer.py`. DB `/data/thumbs/travel.db`
- 지역 3중 파일: `region_map.json`(RO 원본) + `region_map_extra.json`(RW 오버라이드: `_regions_meta`/`_removes`/`미분류`) + `regions.geojson`. PUID/PGID 권한 주입
- 📌 상세(DB 스키마·지역 병합·sync 동작·썸네일 폴백): **`service_travel.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/travel/regions` | 지역 GeoJSON (커스텀 지역 동적 추가) |
| GET | `/api/travel/photos` | 사진 목록 (region, page, size) |
| POST | `/api/travel/sync` | 폴더 스캔 → DB 동기화 + 썸네일 생성 |
| GET | `/api/travel/albums` | 앨범 목록 + 사진 수 + 커버 + region |
| PUT | `/api/travel/albums/{album}/cover` | 앨범 커버 지정 |
| PUT | `/api/travel/albums/{album}/region` | 앨범 지역 변경 |
| PUT | `/api/travel/regions/{region_id}` | 커스텀 지역 이름/좌표 수정 |

### deployer (deployer/)
Gitea Webhook 수신 → 자동 배포. HMAC SHA256 검증(`X-Gitea-Signature` 또는 `X-Hub-Signature-256`).
- 즉시 `{"ok": true}` 응답 후 BackgroundTask 배포. 동시 배포 락(threading.Lock + flock). 10분 타임아웃
- 📌 상세(검증 흐름·배포 스크립트 2단 구조·`.releases` 백업·헬스체크 게이트): **`service_deployer.md`**

| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/webhook` | Gitea Webhook 수신 → HMAC 검증 → 배포 |

---

## 10. 공유 인프라

### `_shared/access_log.py` 공용 모듈
5개 서비스(lotto, stock, music-lab, insta-lab, realestate-lab)가 공유. agent-office의 `/agents/{id}/logs`가 이를 통해 각 서비스 `/logs/recent`를 수집·병합.
- `install(app)` 단일 진입점 → middleware(요청 계측 + ring buffer maxlen=500) + `BufferLogHandler` + `GET /logs/recent?limit&since&path_prefix`
- docker-compose: `${RUNTIME_PATH}/_shared:/shared/_shared:ro` + `PYTHONPATH=/app:/shared`
- 제외: `/health` `/docs` `/logs/recent` 등 + `OPTIONS`/`HEAD`

### `redis` 컨테이너 (6379)
4개 서비스 비동기 큐 공유. 각 서비스가 `queue:<svc>-render` push → Windows AI 워커 pop → 완료 후 `/api/internal/<svc>/update` webhook → DB 업데이트.
- `queue:music-render`, `queue:video-render`, `queue:image-render`, `queue:insta-render`

---

## 11. 주의사항 (cross-cutting)

- **`.env` 절대 커밋 금지** (`.env.example`만 레포 포함)
- **커밋 경로**: web-ui / web-backend 별도 Git — 각 경로에서만 커밋 (`feedback_commit_repo.md`)
- **Docker는 NAS에서만 구동** (`feedback_docker_nas.md`)
- **Nginx trailing slash**: `/api/portfolio`는 두 location 블록으로 slash 유무 모두 매칭
- **라우트 순서**: personal `DELETE /api/todos/done`을 `/{id}`보다 먼저 등록
- **DB 마이그레이션**: 스키마 변경 시 ALTER TABLE 멱등 패턴 (각 서비스 메모리 참조)
- **공휴일 목록**: `stock/app/holidays.json` 매년 수동 갱신 (KRX 기준)
- **Windows AI 서버 IP**: `192.168.45.59` (DHCP 고정 예약). Tailscale은 Synology userspace 모드라 TCP 불가 → 로컬 IP 사용
- **렌더/생성 워커 분리**: music/video/image/insta 무거운 작업은 Windows `web-ai` 워커. NAS 코드의 `*_provider.py`/`card_renderer.py`가 DEPRECATED stub면 실 로직은 web-ai 쪽이 authoritative
- **[팀 규칙] 모든 WSL(docker) 워커는 `/infra`에서 관측 가능해야 한다**: 새 워커 추가 시 필수 3단계 — ① 워커가 `worker:<name>:heartbeat`(EX45, ~15초) 발신 ② BE가 `agent-office/app/node_monitor.py`의 `WORKER_REGISTRY`에 `{name,kind,queue}` 등재 ③ → `/api/agent-office/nodes`·web-ui `/infra` 노출 + 다운/복구/dead-letter 텔레그램 경보. 미준수 = "사일런트 사망"(insta-render 2주 무관측 사고) 재발 위험. 워커 신규/변경 PR 머지 게이트. web-ai/web-ui repo CLAUDE.md에도 동일 규칙 명시 필요. 상세는 `infra_distributed_workers.md` 메모리(관측 계약 2)
- **Playwright Dockerfile**: bookworm 고정 + 수동 chromium deps, `--with-deps` 금지 (`feedback_playwright_dockerfile.md`)
- **lab 네이밍**: `-lab`은 개발/연구 단계에만, 정식 서비스엔 미사용 (`feedback_lab_naming.md`)

---

## 협업 팀 버스 (co-gahusb) — 이 세션의 역할: **BE**

이 세션은 백엔드(BE) 역할이다. co-gahusb MCP 툴로 다른 세션(FE/AI/Producer)과 협업한다.
- **소유권**: 이 세션은 `web-backend` repo만 쓴다(FE=web-ui, AI=web-ai).
- **공유 리소스 변경 전 반드시 `acquire_lock(resource, "BE")`**: 대상 = `nas-deploy`, `stock-db-schema`, `lotto-db-schema`, `memory-mirror`, `nginx-conf`, `compose`. 점유 중이면 대기, 긴 작업은 `heartbeat_lock`, 끝나면 `release_lock`.
- **모든 툴 호출에 `role="BE"`** (또는 `from_role`/`created_by`에 BE).
- **수신**: `/loop`로 주기적으로 `read_inbox("BE", after_id=<last>)` + `list_tasks(assignee_role="BE")` 확인.
- 키 `CO_BUS_KEY`는 환경변수로 주입(커밋 금지).
