diff --git a/CLAUDE.md b/CLAUDE.md index ca4b52e..a95c1be 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ ## 1. 프로젝트 개요 Synology NAS 기반의 개인 웹 플랫폼 백엔드 모노레포. -- **서비스**: lotto-lab, stock-lab, travel-proxy, music-lab, blog-lab, realestate-lab, agent-office, personal, packs-lab, deployer (10개) +- **서비스**: lotto-lab, stock, travel-proxy, music-lab, blog-lab, realestate-lab, agent-office, personal, packs-lab, deployer (10개) - **프론트엔드**: 별도 레포 (React + Vite SPA), 빌드 산출물만 NAS에 배포 - **인프라**: Docker Compose (10컨테이너) + Nginx(리버스 프록시) + Gitea Webhook 자동 배포 @@ -32,7 +32,7 @@ Synology NAS 기반의 개인 웹 플랫폼 백엔드 모노레포. /volume1 ├── docker/webpage/ # 운영 런타임 (Docker Compose 실행 위치) │ ├── lotto/ # lotto 소스 (rsync 동기화) -│ ├── stock-lab/ # stock-lab 소스 (rsync 동기화) +│ ├── stock/ # stock 소스 (rsync 동기화) │ ├── travel-proxy/ # travel-proxy 소스 (rsync 동기화) │ ├── deployer/ # deployer 소스 (rsync 동기화) │ ├── nginx/default.conf # Nginx 설정 @@ -54,7 +54,7 @@ Synology NAS 기반의 개인 웹 플랫폼 백엔드 모노레포. | 컨테이너 | 포트 | 역할 | |---------|------|------| | `lotto` | 18000 | 로또 데이터 수집·분석·추천 API | -| `stock-lab` | 18500 | 주식 뉴스·AI 분석·KIS API 연동 | +| `stock` | 18500 | 주식 뉴스·AI 분석·KIS API 연동 | | `music-lab` | 18600 | AI 음악 생성·라이브러리 관리 API | | `blog-lab` | 18700 | 블로그 마케팅 수익화 API | | `realestate-lab` | 18800 | 부동산 청약 자동 수집·매칭 API | @@ -73,9 +73,9 @@ Synology NAS 기반의 개인 웹 플랫폼 백엔드 모노레포. |------|------------|------| | `/api/` | `lotto:8000` | lotto API (기본) | | `/api/travel/` | `travel-proxy:8000` | travel API | -| `/api/stock/` | `stock-lab:8000` | stock API | -| `/api/trade/` | `stock-lab:8000` | KIS 실계좌 API | -| `/api/portfolio` | `stock-lab:8000` | trailing slash 유무 모두 매칭 | +| `/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 | | `/api/blog-marketing/` | `blog-lab:8000` | 블로그 마케팅 수익화 API | | `/api/realestate/` | `realestate-lab:8000` | 부동산 청약 API | @@ -205,14 +205,14 @@ docker compose up -d | GET | `/api/lotto/briefing/{draw_no}` | 특정 회차 브리핑 | | GET | `/api/lotto/briefing` | 브리핑 이력 | -### stock-lab (stock-lab/) +### stock (stock/) - Windows AI 서버 연동: `WINDOWS_AI_SERVER_URL=http://192.168.45.59:8000` - KIS API 연동으로 실계좌 잔고·거래 조회 - 뉴스 스크래핑: 네이버 증권 + 해외 사이트 - DB: `/app/data/stock.db` (articles, portfolio, broker_cash, asset_snapshots, sell_history 테이블) - 파일 구조: `main.py`, `db.py`, `scraper.py`, `price_fetcher.py`, `holidays.json` -**stock-lab API 목록** +**stock API 목록** | 메서드 | 경로 | 설명 | |--------|------|------| @@ -512,7 +512,7 @@ docker compose up -d ### agent-office (agent-office/) - AI 에이전트 가상 오피스 — 2D 픽셀아트 사무실에서 에이전트가 실제 작업 수행 -- stock-lab/music-lab/realestate-lab 기존 API를 서비스 프록시로 호출 (직접 DB 접근 없음) +- stock/music-lab/realestate-lab 기존 API를 서비스 프록시로 호출 (직접 DB 접근 없음) - 실시간 상태 동기화: WebSocket (`/api/agent-office/ws`) - 텔레그램 봇: 양방향 알림 + 승인 (인라인 키보드) - 청약 매칭 알림: realestate-lab이 신규 매칭 발견 시 push → `RealestateAgent.on_new_matches()` → 텔레그램 1통(인라인 [🔖 북마크]/[📄 공고] 또는 [전체 보기] 버튼) @@ -522,7 +522,7 @@ docker compose up -d **에이전트 FSM 상태**: idle → working → waiting (승인 대기) → reporting → break (휴식) **환경변수** -- `STOCK_LAB_URL`: stock-lab 내부 URL (기본 `http://stock-lab:8000`) +- `STOCK_URL`: stock 내부 URL (기본 `http://stock:8000`) - `MUSIC_LAB_URL`: music-lab 내부 URL (기본 `http://music-lab:8000`) - `REALESTATE_LAB_URL`: realestate-lab 내부 URL (기본 `http://realestate-lab:8000`) — 북마크 콜백 프록시 대상 - `REALESTATE_DASHBOARD_URL`: 텔레그램 [전체 보기] 버튼 URL (기본 `http://localhost:8080/realestate`) @@ -697,7 +697,7 @@ docker compose up -d - **캐시 전략**: `index.html`은 `no-store`, `assets/`는 1년 장기 캐시(immutable) - **Frontend 배포**: git push로 자동 배포되지 않음. 로컬 빌드 후 NAS에 수동 업로드 - **.env 파일**: 절대 커밋 금지. `.env.example`만 레포에 포함 -- **공휴일 목록**: `stock-lab/app/holidays.json` 매년 수동 갱신 필요 (KRX 기준) +- **공휴일 목록**: `stock/app/holidays.json` 매년 수동 갱신 필요 (KRX 기준) - **Windows AI 서버 IP**: `192.168.45.59` — 공유기 DHCP 고정 예약으로 고정. Tailscale은 Synology에서 TCP 불가(userspace 모드)라 로컬 IP 사용 - **현재가 조회**: 네이버 모바일 API → HTML 파싱 폴백, 3분 TTL 캐시 (`price_fetcher.py`) - **시뮬레이션 교체 방식**: `best_picks`는 교체형 — 새 시뮬레이션 실행 시 `is_active=0`으로 비활성화 후 신규 입력 diff --git a/README.md b/README.md index da27223..a2ba5c9 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포. │ ├── 정적 SPA 서빙 (React + Vite) │ │ └── API 리버스 프록시 │ │ ├── /api/ → lotto-backend:8000 (로또·블로그·투두)│ -│ ├── /api/stock/, /trade/ → stock-lab:8000 │ -│ ├── /api/portfolio → stock-lab:8000 │ +│ ├── /api/stock/, /trade/ → stock:8000 │ +│ ├── /api/portfolio → stock:8000 │ │ ├── /api/music/ → music-lab:8000 │ │ ├── /api/blog-marketing/ → blog-lab:8000 │ │ ├── /api/realestate/ → realestate-lab:8000 │ @@ -29,7 +29,7 @@ Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포. | 컨테이너 | 포트 | 역할 | |---------|------|------| | `lotto-backend` | 18000 | 로또 데이터 수집·분석·추천 + 블로그·투두 API | -| `stock-lab` | 18500 | 주식 뉴스·AI 요약·KIS 실계좌·포트폴리오·자산 추적 | +| `stock` | 18500 | 주식 뉴스·AI 요약·KIS 실계좌·포트폴리오·자산 추적 | | `music-lab` | 18600 | AI 음악 생성 (Suno + 로컬 MusicGen 듀얼 프로바이더) | | `blog-lab` | 18700 | 블로그 마케팅 수익화 (키워드→글 생성→리뷰→발행) | | `realestate-lab` | 18800 | 청약 공고 자동 수집·프로필 매칭 | @@ -45,7 +45,7 @@ Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포. ``` web-backend/ ├── backend/ # lotto-backend (로또·블로그·투두) -├── stock-lab/ # 주식·포트폴리오 +├── stock/ # 주식·포트폴리오 ├── music-lab/ # AI 음악 생성 ├── blog-lab/ # 블로그 마케팅 파이프라인 ├── realestate-lab/ # 청약 자동 수집·매칭 @@ -75,7 +75,7 @@ curl http://localhost:18500/health |--------|----------| | Frontend + API | http://localhost:8080 | | lotto-backend | http://localhost:18000 | -| stock-lab | http://localhost:18500 | +| stock | http://localhost:18500 | | music-lab | http://localhost:18600 | | blog-lab | http://localhost:18700 | | realestate-lab | http://localhost:18800 | @@ -99,7 +99,7 @@ curl http://localhost:18500/health - 09:10 / 21:10 — 당첨번호 동기화 + 추천 채점 - 00:05, 04:05, 08:05, 12:05, 16:05, 20:05 — 몬테카를로 시뮬레이션 (후보 20,000 → 상위 100 → best_picks 20쌍 교체) -### 2. stock-lab (`/api/stock/`, `/api/trade/`, `/api/portfolio`) +### 2. stock (`/api/stock/`, `/api/trade/`, `/api/portfolio`) 주식 뉴스 스크래핑 + LLM 요약 + KIS 실계좌 연동 + 포트폴리오·자산 스냅샷. @@ -152,7 +152,7 @@ curl http://localhost:18500/health AI 에이전트 가상 오피스 — 2D 픽셀아트 사무실에서 4명의 에이전트가 실제 작업을 수행한다. -- **아키텍처**: stock-lab / music-lab / blog-lab / realestate-lab 기존 API를 서비스 프록시로 호출 (직접 DB 접근 없음) +- **아키텍처**: stock / music-lab / blog-lab / realestate-lab 기존 API를 서비스 프록시로 호출 (직접 DB 접근 없음) - **FSM 상태**: `idle → working → waiting(승인 대기) → reporting → break` - **실시간 동기화**: WebSocket `/api/agent-office/ws` (init, agent_state, task_complete, command_result) - **텔레그램 연동**: 양방향 알림 + 인라인 키보드 승인 @@ -224,7 +224,7 @@ Gitea Webhook 수신 → NAS 자동 배포. | 공동 출현 | 15% | 번호 쌍 동시 출현 빈도 | | 다양성 | 10% | 연속번호·범위·구간 커버리지 | -### LLM 요약 provider 추상화 (stock-lab) +### LLM 요약 provider 추상화 (stock) `ai_summarizer.py`는 provider 분리 구조. `summarize_news(articles)` 시그니처는 provider와 무관하게 고정. @@ -232,7 +232,7 @@ Gitea Webhook 수신 → NAS 자동 배포. - `_summarize_with_ollama`: Ollama `/api/generate` (타임아웃 180s, qwen3:14b 첫 로드 대응) - 실패 시 `LLMError` (구 `OllamaError` alias 유지) -### 총 자산 스냅샷 (stock-lab) +### 총 자산 스냅샷 (stock) 평일 15:40 자동 실행 → `holidays.json`으로 공휴일 스킵 → 포트폴리오 현재가 조회 + 예수금 합계 → `asset_snapshots` upsert (date UNIQUE). @@ -266,7 +266,7 @@ git push → Gitea → X-Gitea-Signature (HMAC SHA256) | DB | 소유 서비스 | 주요 테이블 | |----|------------|-----------| | `lotto.db` | lotto-backend | draws, recommendations, simulation_runs/candidates, best_picks, purchase_history, strategy_performance/weights, weekly_reports, lotto_briefings, todos, blog_posts | -| `stock.db` | stock-lab | articles, portfolio, broker_cash, asset_snapshots, sell_history | +| `stock.db` | stock | articles, portfolio, broker_cash, asset_snapshots, sell_history | | `music.db` | music-lab | music_tasks, music_library (provider, lyrics, image_url, suno_id, file_hash, cover_images, wav_url, video_url, stem_urls) | | `blog_marketing.db` | blog-lab | keyword_analyses, blog_posts, brand_links, commissions, generation_tasks, prompt_templates | | `realestate.db` | realestate-lab | announcements, announcement_models, user_profile, match_results, collect_log | @@ -292,7 +292,7 @@ PGID=1000 WINDOWS_AI_SERVER_URL=http://192.168.45.59:8000 WEBHOOK_SECRET=your_secret_here -# LLM (stock-lab, blog-lab, agent-office 공통) +# LLM (stock, blog-lab, agent-office 공통) ANTHROPIC_API_KEY=sk-ant-... ANTHROPIC_MODEL=claude-haiku-4-5-20251001 LLM_PROVIDER=claude # claude | ollama @@ -315,7 +315,7 @@ DATA_GO_KR_API_KEY= TELEGRAM_BOT_TOKEN= TELEGRAM_CHAT_ID= TELEGRAM_WEBHOOK_URL= -STOCK_LAB_URL=http://stock-lab:8000 +STOCK_URL=http://stock:8000 MUSIC_LAB_URL=http://music-lab:8000 BLOG_LAB_URL=http://blog-lab:8000 REALESTATE_LAB_URL=http://realestate-lab:8000 @@ -343,7 +343,7 @@ REALESTATE_LAB_URL=http://realestate-lab:8000 - **라우트 순서** — `DELETE /api/todos/done`은 `/api/todos/{id}` 보다 먼저 등록 필수 (FastAPI prefix 매칭) - **캐시 전략** — `index.html`: no-store / `assets/`: 1년 immutable - **PUID/PGID** — travel-proxy는 NAS 파일 권한을 위해 환경변수 주입 필수 -- **공휴일 목록** — `stock-lab/app/holidays.json` 매년 수동 갱신 (KRX 기준) +- **공휴일 목록** — `stock/app/holidays.json` 매년 수동 갱신 (KRX 기준) - **Windows AI 서버 IP** — `192.168.45.59` 공유기 DHCP 고정 예약. Synology Tailscale은 userspace 모드라 TCP 불가 → 로컬 IP 사용 - **Suno CDN** — `cdn1.suno.ai` URL은 임시 만료 → 생성 즉시 로컬 다운로드 필수 - **LLM provider 롤백** — Claude API 장애 시 `.env`의 `LLM_PROVIDER=ollama`로 전환 후 `docker compose up -d` diff --git a/STATUS.md b/STATUS.md index 5a2be3f..62bbeef 100644 --- a/STATUS.md +++ b/STATUS.md @@ -12,7 +12,7 @@ | 서비스 | 포트 | 상태 | 핵심 기능 | |--------|------|------|-----------| | `lotto-backend` | 18000 | ✅ | 로또 추천·통계·리포트·구매내역 + 블로그·투두 | -| `stock-lab` | 18500 | ✅ | 주식 뉴스·지수·트레이딩·포트폴리오·자산 스냅샷 | +| `stock` | 18500 | ✅ | 주식 뉴스·지수·트레이딩·포트폴리오·자산 스냅샷 | | `music-lab` | 18600 | ✅ | Suno + MusicGen + YouTube 수익화 + 컴파일 | | `blog-lab` | 18700 | ✅ | 블로그 마케팅 수익화 파이프라인 | | `realestate-lab` | 18800 | ✅ | 청약 수집·5티어 매칭·매칭 알림 | diff --git a/agent-office/app/agents/stock.py b/agent-office/app/agents/stock.py index 01fa6ab..e79c236 100644 --- a/agent-office/app/agents/stock.py +++ b/agent-office/app/agents/stock.py @@ -51,7 +51,7 @@ class StockAgent(BaseAgent): await self.transition("working", "최신 뉴스 수집 중...", task_id) try: - # stock-lab cron(매일 8:00)이 7:30 브리핑보다 늦게 돌아 어제 뉴스가 + # stock cron(매일 8:00)이 7:30 브리핑보다 늦게 돌아 어제 뉴스가 # 요약되던 문제 방지 — 요약 직전에 동기 스크랩으로 DB를 갱신한다. try: await service_proxy.scrape_stock_news() @@ -60,7 +60,7 @@ class StockAgent(BaseAgent): await self.transition("working", "AI 뉴스 요약 생성 중...") - # AI 요약 호출 (LLM 처리는 stock-lab이 담당) + # AI 요약 호출 (LLM 처리는 stock이 담당) result = await service_proxy.summarize_stock_news(limit=15) await self.transition("reporting", "뉴스 요약 전송 중...") @@ -237,7 +237,7 @@ class StockAgent(BaseAgent): """AI 뉴스 sentiment 분석 자동 잡 (평일 08:00 KST). 흐름: - 1) stock-lab /snapshot/refresh-news-sentiment 호출 + 1) stock /snapshot/refresh-news-sentiment 호출 2) status='skipped_weekend'/'skipped_holiday' → 종료 (텔레그램 미발신) 3) updated=0 → 운영자 알림 (HTML) 4) failures > 30% → 경고 알림 후 메인 메시지 발송 @@ -304,10 +304,10 @@ class StockAgent(BaseAgent): except Exception: pass - # 정상 — Top 5 메시지 (stock-lab이 빌드해서 응답에 telegram_text 동봉) + # 정상 — Top 5 메시지 (stock이 빌드해서 응답에 telegram_text 동봉) text = result.get("telegram_text") or "" if not text: - add_log(self.agent_id, "telegram_text 누락 — stock-lab 응답 결함", "error", task_id) + add_log(self.agent_id, "telegram_text 누락 — stock 응답 결함", "error", task_id) update_task_status(task_id, "failed", {"error": "telegram_text 누락"}) await self.transition("idle", "AI 뉴스 응답 결함") return diff --git a/agent-office/app/config.py b/agent-office/app/config.py index 2606f52..890d66d 100644 --- a/agent-office/app/config.py +++ b/agent-office/app/config.py @@ -1,7 +1,7 @@ import os # Service URLs (Docker internal network) -STOCK_LAB_URL = os.getenv("STOCK_LAB_URL", "http://localhost:18500") +STOCK_URL = os.getenv("STOCK_URL", "http://localhost:18500") MUSIC_LAB_URL = os.getenv("MUSIC_LAB_URL", "http://localhost:18600") BLOG_LAB_URL = os.getenv("BLOG_LAB_URL", "http://localhost:18700") REALESTATE_LAB_URL = os.getenv("REALESTATE_LAB_URL", "http://localhost:18800") diff --git a/agent-office/app/service_proxy.py b/agent-office/app/service_proxy.py index e650aa7..891abe0 100644 --- a/agent-office/app/service_proxy.py +++ b/agent-office/app/service_proxy.py @@ -1,7 +1,7 @@ import httpx from typing import Any, Dict, List, Optional -from .config import STOCK_LAB_URL, MUSIC_LAB_URL, BLOG_LAB_URL, REALESTATE_LAB_URL +from .config import STOCK_URL, MUSIC_LAB_URL, BLOG_LAB_URL, REALESTATE_LAB_URL _client = httpx.AsyncClient(timeout=30.0) @@ -9,23 +9,23 @@ async def fetch_stock_news(limit: int = 10, category: str = None) -> List[Dict[s params = {"limit": limit} if category: params["category"] = category - resp = await _client.get(f"{STOCK_LAB_URL}/api/stock/news", params=params) + resp = await _client.get(f"{STOCK_URL}/api/stock/news", params=params) resp.raise_for_status() return resp.json() async def fetch_stock_indices() -> Dict[str, Any]: - resp = await _client.get(f"{STOCK_LAB_URL}/api/stock/indices") + resp = await _client.get(f"{STOCK_URL}/api/stock/indices") resp.raise_for_status() return resp.json() async def summarize_stock_news(limit: int = 15) -> Dict[str, Any]: - """stock-lab의 AI 요약 엔드포인트 호출. + """stock의 AI 요약 엔드포인트 호출. 반환: {"summary": str, "tokens": {...}, "model": str, "duration_ms": int, "article_count": int} """ - # stock-lab 내부 Ollama 호출이 180s까지 가능하므로 여유있게 200s + # stock 내부 Ollama 호출이 180s까지 가능하므로 여유있게 200s async with httpx.AsyncClient(timeout=200.0) as client: resp = await client.post( - f"{STOCK_LAB_URL}/api/stock/news/summarize", + f"{STOCK_URL}/api/stock/news/summarize", json={"limit": limit}, ) resp.raise_for_status() @@ -33,32 +33,32 @@ async def summarize_stock_news(limit: int = 15) -> Dict[str, Any]: async def refresh_screener_snapshot() -> Dict[str, Any]: - """stock-lab의 KRX 일봉 스냅샷 갱신 (스크리너 실행 전 호출). + """stock의 KRX 일봉 스냅샷 갱신 (스크리너 실행 전 호출). 네이버 금융 일괄 다운로드라 보통 30~120s, 여유있게 180s. """ async with httpx.AsyncClient(timeout=180.0) as client: - resp = await client.post(f"{STOCK_LAB_URL}/api/stock/screener/snapshot/refresh") + resp = await client.post(f"{STOCK_URL}/api/stock/screener/snapshot/refresh") resp.raise_for_status() return resp.json() async def refresh_ai_news_sentiment() -> Dict[str, Any]: - """stock-lab의 AI 뉴스 sentiment 분석 트리거 (08:00 cron). + """stock의 AI 뉴스 sentiment 분석 트리거 (08:00 cron). 네이버 100종목 스크래핑 + Claude Haiku 100콜 병렬 = 약 30-60초. 여유있게 240s timeout. """ async with httpx.AsyncClient(timeout=240.0) as client: resp = await client.post( - f"{STOCK_LAB_URL}/api/stock/screener/snapshot/refresh-news-sentiment" + f"{STOCK_URL}/api/stock/screener/snapshot/refresh-news-sentiment" ) resp.raise_for_status() return resp.json() async def run_stock_screener(mode: str = "auto") -> Dict[str, Any]: - """stock-lab의 스크리너 실행. + """stock의 스크리너 실행. 반환 status: - 'skipped_holiday': 공휴일/주말 — telegram_payload 없음 @@ -67,7 +67,7 @@ async def run_stock_screener(mode: str = "auto") -> Dict[str, Any]: """ async with httpx.AsyncClient(timeout=180.0) as client: resp = await client.post( - f"{STOCK_LAB_URL}/api/stock/screener/run", + f"{STOCK_URL}/api/stock/screener/run", json={"mode": mode}, ) resp.raise_for_status() @@ -75,13 +75,13 @@ async def run_stock_screener(mode: str = "auto") -> Dict[str, Any]: async def scrape_stock_news() -> Dict[str, Any]: - """stock-lab의 수동 뉴스 스크랩 트리거 — DB에 최신 뉴스 저장. + """stock의 수동 뉴스 스크랩 트리거 — DB에 최신 뉴스 저장. 아침 브리핑 직전 호출하여 어제 데이터가 아닌 오늘 새벽 뉴스를 보장한다. 네이버 금융 단일 요청이라 보통 수 초 내 완료, 여유있게 60s. """ async with httpx.AsyncClient(timeout=60.0) as client: - resp = await client.post(f"{STOCK_LAB_URL}/api/stock/scrap") + resp = await client.post(f"{STOCK_URL}/api/stock/scrap") resp.raise_for_status() return resp.json() diff --git a/agent-office/tests/test_stock_screener_job.py b/agent-office/tests/test_stock_screener_job.py index 1e33402..f2ab226 100644 --- a/agent-office/tests/test_stock_screener_job.py +++ b/agent-office/tests/test_stock_screener_job.py @@ -1,6 +1,6 @@ """StockAgent.on_screener_schedule — 평일 16:30 KST 자동 잡 단위 테스트. -stock-lab HTTP 호출은 service_proxy mock, 텔레그램은 messaging.send_raw mock. +stock HTTP 호출은 service_proxy mock, 텔레그램은 messaging.send_raw mock. """ import os import sys @@ -138,7 +138,7 @@ def test_screener_run_failure_notifies_operator(): from app.telegram import messaging fake_snap = AsyncMock(return_value={"status": "ok"}) - fake_run = AsyncMock(side_effect=RuntimeError("stock-lab 500")) + fake_run = AsyncMock(side_effect=RuntimeError("stock 500")) fake_send = AsyncMock(return_value={"ok": True, "message_id": 1}) with patch.object(service_proxy, "refresh_screener_snapshot", fake_snap), \ diff --git a/docker-compose.yml b/docker-compose.yml index 44df3b9..f41410e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,12 +22,12 @@ services: timeout: 5s retries: 3 - stock-lab: + stock: build: - context: ./stock-lab + context: ./stock args: APP_VERSION: ${APP_VERSION:-dev} - container_name: stock-lab + container_name: stock restart: unless-stopped ports: - "18500:8000" @@ -136,7 +136,7 @@ services: environment: - TZ=${TZ:-Asia/Seoul} - CORS_ALLOW_ORIGINS=${CORS_ALLOW_ORIGINS:-http://localhost:3007,http://localhost:8080} - - STOCK_LAB_URL=http://stock-lab:8000 + - STOCK_URL=http://stock:8000 - MUSIC_LAB_URL=http://music-lab:8000 - BLOG_LAB_URL=http://blog-lab:8000 - REALESTATE_LAB_URL=http://realestate-lab:8000 @@ -157,7 +157,7 @@ services: volumes: - ${RUNTIME_PATH:-.}/data/agent-office:/app/data depends_on: - - stock-lab + - stock - music-lab - blog-lab - realestate-lab @@ -242,7 +242,7 @@ services: restart: unless-stopped depends_on: - lotto - - stock-lab + - stock - music-lab - blog-lab - realestate-lab diff --git a/nginx/default.conf b/nginx/default.conf index aa85690..a6718b4 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -139,17 +139,17 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://stock-lab:8000/api/stock/; + proxy_pass http://stock:8000/api/stock/; } - # trade API (Stock Lab Proxy) + # trade API (Stock Proxy) location /api/trade/ { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://stock-lab:8000/api/trade/; + proxy_pass http://stock:8000/api/trade/; } # blog-marketing API @@ -166,14 +166,14 @@ server { proxy_pass http://$blog_backend$request_uri; } - # portfolio API (Stock Lab) — trailing slash 유무 모두 매칭 + # portfolio API (Stock) — trailing slash 유무 모두 매칭 location /api/portfolio { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass http://stock-lab:8000/api/portfolio; + proxy_pass http://stock:8000/api/portfolio; } diff --git a/scripts/deploy-nas.sh b/scripts/deploy-nas.sh index a37180e..3736389 100644 --- a/scripts/deploy-nas.sh +++ b/scripts/deploy-nas.sh @@ -2,7 +2,7 @@ set -euo pipefail # ── 서비스 목록 (한 곳에서만 관리) ── -SERVICES="lotto travel-proxy deployer stock-lab music-lab blog-lab realestate-lab agent-office personal packs-lab nginx scripts" +SERVICES="lotto travel-proxy deployer stock music-lab blog-lab realestate-lab agent-office personal packs-lab nginx scripts" # 1. 자동 감지: Docker 컨테이너 내부인가? if [ -d "/repo" ] && [ -d "/runtime" ]; then diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 84dec2a..986def0 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -7,11 +7,11 @@ flock -n 200 || { echo "Deploy already running, skipping"; exit 0; } # ── 서비스 목록 (한 곳에서만 관리) ── # docker compose 서비스명 (deployer 제외 — 자기 자신을 재빌드하면 스크립트 중단) -BUILD_TARGETS="lotto travel-proxy stock-lab music-lab blog-lab realestate-lab agent-office personal packs-lab frontend" +BUILD_TARGETS="lotto travel-proxy stock music-lab blog-lab realestate-lab agent-office personal packs-lab frontend" # 컨테이너 이름 (고아 정리용) -CONTAINER_NAMES="lotto stock-lab music-lab blog-lab realestate-lab agent-office personal packs-lab travel-proxy frontend" +CONTAINER_NAMES="lotto stock music-lab blog-lab realestate-lab agent-office personal packs-lab travel-proxy frontend" # 헬스체크 대상 -HEALTH_ENDPOINTS="lotto stock-lab travel-proxy music-lab blog-lab realestate-lab agent-office personal packs-lab" +HEALTH_ENDPOINTS="lotto stock travel-proxy music-lab blog-lab realestate-lab agent-office personal packs-lab" # data 디렉토리 (packs-lab은 별도 media/packs 사용) DATA_DIRS="music stock blog realestate agent-office personal" diff --git a/stock-lab/.dockerignore b/stock/.dockerignore similarity index 100% rename from stock-lab/.dockerignore rename to stock/.dockerignore diff --git a/stock-lab/API_SPEC.md b/stock/API_SPEC.md similarity index 100% rename from stock-lab/API_SPEC.md rename to stock/API_SPEC.md diff --git a/stock-lab/Dockerfile b/stock/Dockerfile similarity index 100% rename from stock-lab/Dockerfile rename to stock/Dockerfile diff --git a/stock-lab/app/ai_summarizer.py b/stock/app/ai_summarizer.py similarity index 99% rename from stock-lab/app/ai_summarizer.py rename to stock/app/ai_summarizer.py index edbdb17..d79906d 100644 --- a/stock-lab/app/ai_summarizer.py +++ b/stock/app/ai_summarizer.py @@ -14,7 +14,7 @@ from typing import List, Dict, Any import httpx -logger = logging.getLogger("stock-lab.ai_summarizer") +logger = logging.getLogger("stock.ai_summarizer") LLM_PROVIDER = os.getenv("LLM_PROVIDER", "claude").lower().strip() diff --git a/stock-lab/app/db.py b/stock/app/db.py similarity index 100% rename from stock-lab/app/db.py rename to stock/app/db.py diff --git a/stock-lab/app/holidays.json b/stock/app/holidays.json similarity index 100% rename from stock-lab/app/holidays.json rename to stock/app/holidays.json diff --git a/stock-lab/app/main.py b/stock/app/main.py similarity index 99% rename from stock-lab/app/main.py rename to stock/app/main.py index f66ffd6..4da8cf7 100644 --- a/stock-lab/app/main.py +++ b/stock/app/main.py @@ -11,7 +11,7 @@ from apscheduler.schedulers.background import BackgroundScheduler from pydantic import BaseModel logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s %(message)s") -logger = logging.getLogger("stock-lab") +logger = logging.getLogger("stock") from .db import ( init_db, save_articles, get_latest_articles, diff --git a/stock-lab/app/price_fetcher.py b/stock/app/price_fetcher.py similarity index 100% rename from stock-lab/app/price_fetcher.py rename to stock/app/price_fetcher.py diff --git a/stock-lab/app/scraper.py b/stock/app/scraper.py similarity index 99% rename from stock-lab/app/scraper.py rename to stock/app/scraper.py index a1f9e04..ae36a52 100644 --- a/stock-lab/app/scraper.py +++ b/stock/app/scraper.py @@ -4,7 +4,7 @@ from bs4 import BeautifulSoup from typing import List, Dict, Any import time -logger = logging.getLogger("stock-lab.scraper") +logger = logging.getLogger("stock.scraper") # 네이버 파이낸스 주요 뉴스 NAVER_FINANCE_NEWS_URL = "https://finance.naver.com/news/mainnews.naver" diff --git a/stock-lab/app/screener/__init__.py b/stock/app/screener/__init__.py similarity index 100% rename from stock-lab/app/screener/__init__.py rename to stock/app/screener/__init__.py diff --git a/stock-lab/app/screener/_test_fixtures.py b/stock/app/screener/_test_fixtures.py similarity index 100% rename from stock-lab/app/screener/_test_fixtures.py rename to stock/app/screener/_test_fixtures.py diff --git a/stock-lab/app/screener/ai_news/__init__.py b/stock/app/screener/ai_news/__init__.py similarity index 100% rename from stock-lab/app/screener/ai_news/__init__.py rename to stock/app/screener/ai_news/__init__.py diff --git a/stock-lab/app/screener/ai_news/analyzer.py b/stock/app/screener/ai_news/analyzer.py similarity index 100% rename from stock-lab/app/screener/ai_news/analyzer.py rename to stock/app/screener/ai_news/analyzer.py diff --git a/stock-lab/app/screener/ai_news/articles_source.py b/stock/app/screener/ai_news/articles_source.py similarity index 100% rename from stock-lab/app/screener/ai_news/articles_source.py rename to stock/app/screener/ai_news/articles_source.py diff --git a/stock-lab/app/screener/ai_news/pipeline.py b/stock/app/screener/ai_news/pipeline.py similarity index 100% rename from stock-lab/app/screener/ai_news/pipeline.py rename to stock/app/screener/ai_news/pipeline.py diff --git a/stock-lab/app/screener/ai_news/scraper.py b/stock/app/screener/ai_news/scraper.py similarity index 94% rename from stock-lab/app/screener/ai_news/scraper.py rename to stock/app/screener/ai_news/scraper.py index c69d471..0fc9131 100644 --- a/stock-lab/app/screener/ai_news/scraper.py +++ b/stock/app/screener/ai_news/scraper.py @@ -1,7 +1,7 @@ """[DEPRECATED] 네이버 finance 종목 뉴스 스크래핑. 본 모듈은 ai_news Phase 1 (2026-05-14) 에서 더 이상 파이프라인에서 사용되지 않음. -데이터 소스는 stock-lab 의 articles 테이블 (ai_news/articles_source.py) 로 전환됨. +데이터 소스는 stock 의 articles 테이블 (ai_news/articles_source.py) 로 전환됨. 삭제 시점: Phase 2 (DART 도입) 결정 후. IC 검증 4주 누적 후 노드 활성화 여부에 따라 본 모듈을 (a) 완전 삭제 또는 (b) ensemble fallback 으로 재활용. diff --git a/stock-lab/app/screener/ai_news/telegram.py b/stock/app/screener/ai_news/telegram.py similarity index 100% rename from stock-lab/app/screener/ai_news/telegram.py rename to stock/app/screener/ai_news/telegram.py diff --git a/stock-lab/app/screener/ai_news/validation.py b/stock/app/screener/ai_news/validation.py similarity index 100% rename from stock-lab/app/screener/ai_news/validation.py rename to stock/app/screener/ai_news/validation.py diff --git a/stock-lab/app/screener/engine.py b/stock/app/screener/engine.py similarity index 100% rename from stock-lab/app/screener/engine.py rename to stock/app/screener/engine.py diff --git a/stock-lab/app/screener/nodes/__init__.py b/stock/app/screener/nodes/__init__.py similarity index 100% rename from stock-lab/app/screener/nodes/__init__.py rename to stock/app/screener/nodes/__init__.py diff --git a/stock-lab/app/screener/nodes/ai_news.py b/stock/app/screener/nodes/ai_news.py similarity index 100% rename from stock-lab/app/screener/nodes/ai_news.py rename to stock/app/screener/nodes/ai_news.py diff --git a/stock-lab/app/screener/nodes/base.py b/stock/app/screener/nodes/base.py similarity index 100% rename from stock-lab/app/screener/nodes/base.py rename to stock/app/screener/nodes/base.py diff --git a/stock-lab/app/screener/nodes/foreign_buy.py b/stock/app/screener/nodes/foreign_buy.py similarity index 100% rename from stock-lab/app/screener/nodes/foreign_buy.py rename to stock/app/screener/nodes/foreign_buy.py diff --git a/stock-lab/app/screener/nodes/high52w.py b/stock/app/screener/nodes/high52w.py similarity index 100% rename from stock-lab/app/screener/nodes/high52w.py rename to stock/app/screener/nodes/high52w.py diff --git a/stock-lab/app/screener/nodes/hygiene.py b/stock/app/screener/nodes/hygiene.py similarity index 100% rename from stock-lab/app/screener/nodes/hygiene.py rename to stock/app/screener/nodes/hygiene.py diff --git a/stock-lab/app/screener/nodes/ma_alignment.py b/stock/app/screener/nodes/ma_alignment.py similarity index 100% rename from stock-lab/app/screener/nodes/ma_alignment.py rename to stock/app/screener/nodes/ma_alignment.py diff --git a/stock-lab/app/screener/nodes/momentum.py b/stock/app/screener/nodes/momentum.py similarity index 100% rename from stock-lab/app/screener/nodes/momentum.py rename to stock/app/screener/nodes/momentum.py diff --git a/stock-lab/app/screener/nodes/rs_rating.py b/stock/app/screener/nodes/rs_rating.py similarity index 100% rename from stock-lab/app/screener/nodes/rs_rating.py rename to stock/app/screener/nodes/rs_rating.py diff --git a/stock-lab/app/screener/nodes/vcp_lite.py b/stock/app/screener/nodes/vcp_lite.py similarity index 100% rename from stock-lab/app/screener/nodes/vcp_lite.py rename to stock/app/screener/nodes/vcp_lite.py diff --git a/stock-lab/app/screener/nodes/volume_surge.py b/stock/app/screener/nodes/volume_surge.py similarity index 100% rename from stock-lab/app/screener/nodes/volume_surge.py rename to stock/app/screener/nodes/volume_surge.py diff --git a/stock-lab/app/screener/position_sizer.py b/stock/app/screener/position_sizer.py similarity index 100% rename from stock-lab/app/screener/position_sizer.py rename to stock/app/screener/position_sizer.py diff --git a/stock-lab/app/screener/registry.py b/stock/app/screener/registry.py similarity index 100% rename from stock-lab/app/screener/registry.py rename to stock/app/screener/registry.py diff --git a/stock-lab/app/screener/router.py b/stock/app/screener/router.py similarity index 100% rename from stock-lab/app/screener/router.py rename to stock/app/screener/router.py diff --git a/stock-lab/app/screener/schema.py b/stock/app/screener/schema.py similarity index 100% rename from stock-lab/app/screener/schema.py rename to stock/app/screener/schema.py diff --git a/stock-lab/app/screener/schemas.py b/stock/app/screener/schemas.py similarity index 100% rename from stock-lab/app/screener/schemas.py rename to stock/app/screener/schemas.py diff --git a/stock-lab/app/screener/snapshot.py b/stock/app/screener/snapshot.py similarity index 100% rename from stock-lab/app/screener/snapshot.py rename to stock/app/screener/snapshot.py diff --git a/stock-lab/app/screener/telegram.py b/stock/app/screener/telegram.py similarity index 100% rename from stock-lab/app/screener/telegram.py rename to stock/app/screener/telegram.py diff --git a/stock-lab/app/test_price_fetcher.py b/stock/app/test_price_fetcher.py similarity index 99% rename from stock-lab/app/test_price_fetcher.py rename to stock/app/test_price_fetcher.py index febc040..381f855 100644 --- a/stock-lab/app/test_price_fetcher.py +++ b/stock/app/test_price_fetcher.py @@ -1,7 +1,7 @@ """price_fetcher._select_price_from_response 단위 테스트. 실행: - cd web-backend/stock-lab + cd web-backend/stock python -m unittest app.test_price_fetcher -v """ import os diff --git a/stock-lab/app/test_screener_context.py b/stock/app/test_screener_context.py similarity index 100% rename from stock-lab/app/test_screener_context.py rename to stock/app/test_screener_context.py diff --git a/stock-lab/app/test_screener_engine.py b/stock/app/test_screener_engine.py similarity index 100% rename from stock-lab/app/test_screener_engine.py rename to stock/app/test_screener_engine.py diff --git a/stock-lab/app/test_screener_nodes_base.py b/stock/app/test_screener_nodes_base.py similarity index 100% rename from stock-lab/app/test_screener_nodes_base.py rename to stock/app/test_screener_nodes_base.py diff --git a/stock-lab/app/test_screener_nodes_foreign_buy.py b/stock/app/test_screener_nodes_foreign_buy.py similarity index 100% rename from stock-lab/app/test_screener_nodes_foreign_buy.py rename to stock/app/test_screener_nodes_foreign_buy.py diff --git a/stock-lab/app/test_screener_nodes_high52w.py b/stock/app/test_screener_nodes_high52w.py similarity index 100% rename from stock-lab/app/test_screener_nodes_high52w.py rename to stock/app/test_screener_nodes_high52w.py diff --git a/stock-lab/app/test_screener_nodes_hygiene.py b/stock/app/test_screener_nodes_hygiene.py similarity index 100% rename from stock-lab/app/test_screener_nodes_hygiene.py rename to stock/app/test_screener_nodes_hygiene.py diff --git a/stock-lab/app/test_screener_nodes_ma_alignment.py b/stock/app/test_screener_nodes_ma_alignment.py similarity index 100% rename from stock-lab/app/test_screener_nodes_ma_alignment.py rename to stock/app/test_screener_nodes_ma_alignment.py diff --git a/stock-lab/app/test_screener_nodes_momentum.py b/stock/app/test_screener_nodes_momentum.py similarity index 100% rename from stock-lab/app/test_screener_nodes_momentum.py rename to stock/app/test_screener_nodes_momentum.py diff --git a/stock-lab/app/test_screener_nodes_rs_rating.py b/stock/app/test_screener_nodes_rs_rating.py similarity index 100% rename from stock-lab/app/test_screener_nodes_rs_rating.py rename to stock/app/test_screener_nodes_rs_rating.py diff --git a/stock-lab/app/test_screener_nodes_vcp_lite.py b/stock/app/test_screener_nodes_vcp_lite.py similarity index 100% rename from stock-lab/app/test_screener_nodes_vcp_lite.py rename to stock/app/test_screener_nodes_vcp_lite.py diff --git a/stock-lab/app/test_screener_nodes_volume_surge.py b/stock/app/test_screener_nodes_volume_surge.py similarity index 100% rename from stock-lab/app/test_screener_nodes_volume_surge.py rename to stock/app/test_screener_nodes_volume_surge.py diff --git a/stock-lab/app/test_screener_position_sizer.py b/stock/app/test_screener_position_sizer.py similarity index 100% rename from stock-lab/app/test_screener_position_sizer.py rename to stock/app/test_screener_position_sizer.py diff --git a/stock-lab/app/test_screener_router.py b/stock/app/test_screener_router.py similarity index 100% rename from stock-lab/app/test_screener_router.py rename to stock/app/test_screener_router.py diff --git a/stock-lab/app/test_screener_schema.py b/stock/app/test_screener_schema.py similarity index 100% rename from stock-lab/app/test_screener_schema.py rename to stock/app/test_screener_schema.py diff --git a/stock-lab/app/test_screener_snapshot.py b/stock/app/test_screener_snapshot.py similarity index 100% rename from stock-lab/app/test_screener_snapshot.py rename to stock/app/test_screener_snapshot.py diff --git a/stock-lab/app/test_screener_telegram.py b/stock/app/test_screener_telegram.py similarity index 100% rename from stock-lab/app/test_screener_telegram.py rename to stock/app/test_screener_telegram.py diff --git a/stock-lab/requirements.txt b/stock/requirements.txt similarity index 100% rename from stock-lab/requirements.txt rename to stock/requirements.txt diff --git a/stock-lab/tests/test_ai_news_analyzer.py b/stock/tests/test_ai_news_analyzer.py similarity index 100% rename from stock-lab/tests/test_ai_news_analyzer.py rename to stock/tests/test_ai_news_analyzer.py diff --git a/stock-lab/tests/test_ai_news_articles_source.py b/stock/tests/test_ai_news_articles_source.py similarity index 100% rename from stock-lab/tests/test_ai_news_articles_source.py rename to stock/tests/test_ai_news_articles_source.py diff --git a/stock-lab/tests/test_ai_news_node.py b/stock/tests/test_ai_news_node.py similarity index 100% rename from stock-lab/tests/test_ai_news_node.py rename to stock/tests/test_ai_news_node.py diff --git a/stock-lab/tests/test_ai_news_pipeline.py b/stock/tests/test_ai_news_pipeline.py similarity index 100% rename from stock-lab/tests/test_ai_news_pipeline.py rename to stock/tests/test_ai_news_pipeline.py diff --git a/stock-lab/tests/test_ai_news_router.py b/stock/tests/test_ai_news_router.py similarity index 100% rename from stock-lab/tests/test_ai_news_router.py rename to stock/tests/test_ai_news_router.py diff --git a/stock-lab/tests/test_ai_news_scraper.py b/stock/tests/test_ai_news_scraper.py similarity index 100% rename from stock-lab/tests/test_ai_news_scraper.py rename to stock/tests/test_ai_news_scraper.py diff --git a/stock-lab/tests/test_ai_news_telegram.py b/stock/tests/test_ai_news_telegram.py similarity index 100% rename from stock-lab/tests/test_ai_news_telegram.py rename to stock/tests/test_ai_news_telegram.py diff --git a/stock-lab/tests/test_ai_news_validation.py b/stock/tests/test_ai_news_validation.py similarity index 100% rename from stock-lab/tests/test_ai_news_validation.py rename to stock/tests/test_ai_news_validation.py