Files
web-page-backend/CLAUDE.md
gahusb ce6c8d8f7d docs(CLAUDE): 카탈로그 슬림화(966→484) + 서비스별 메모리 분담 + stale 수정
포트/nginx/API 엔드포인트 목록·cross-cutting 규칙만 CLAUDE.md에 유지. DB 스키마 세부·스케줄러·env·운영 히스토리는 service_<name>.md 메모리로 이관(§0 규칙 명시).

코드 대조로 발견한 stale 수정: insta 렌더는 Windows 워커(card_renderer.py DEPRECATED), lotto v3 backtest API 추가, music-lab 워커 위임, internal webhook X-Internal-Key 2중, /media video↔videos 구분 등.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 01:48:15 +09:00

485 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 기반의 개인 웹 플랫폼 백엔드 모노레포.
- **서비스 14개**: lotto, stock, music-lab, video-lab, image-lab, insta-lab, realestate-lab, agent-office, tarot-lab, saju-lab, personal, packs-lab, travel-proxy, 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 + 썸네일 생성 |
| `redis` | 6379 | 비동기 큐 (music/video/image/insta-lab 공유) |
| `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/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}` | 매도 기록 수정/삭제 |
### 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/telegram-msg/lookup) | YouTube 자동화 파이프라인 |
| 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/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`, `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/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) |
### 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
- **Playwright Dockerfile**: bookworm 고정 + 수동 chromium deps, `--with-deps` 금지 (`feedback_playwright_dockerfile.md`)
- **lab 네이밍**: `-lab`은 개발/연구 단계에만, 정식 서비스엔 미사용 (`feedback_lab_naming.md`)