8개 lab의 _conn() 함수에 표준 동시성 패턴 통일:
- timeout=120.0 (connection 획득)
- PRAGMA journal_mode=WAL (reader/writer 분리)
- PRAGMA busy_timeout=120000 (트랜잭션 충돌 시 120초 대기)
stock-lab/screener/router.py 의 검증된 패턴(d9b6122) 을 lotto, stock-lab(메인),
music-lab, blog-lab, realestate-lab, agent-office, personal, travel-proxy 로 확산.
기존 'database is locked' 오류 윈도우를 흡수.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
web-backend
Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포. 로또 분석, 주식 포트폴리오, AI 음악 생성, 블로그 마케팅, 부동산 청약, AI 에이전트 오피스, 여행 앨범을 하나의 Docker Compose 스택으로 운영한다.
서비스 구성
┌──────────────────────────────────────────────────────────────────────┐
│ lotto-frontend (Nginx:8080) │
│ ├── 정적 SPA 서빙 (React + Vite) │
│ └── API 리버스 프록시 │
│ ├── /api/ → lotto-backend:8000 (로또·블로그·투두)│
│ ├── /api/stock/, /trade/ → stock-lab:8000 │
│ ├── /api/portfolio → stock-lab:8000 │
│ ├── /api/music/ → music-lab:8000 │
│ ├── /api/blog-marketing/ → blog-lab:8000 │
│ ├── /api/realestate/ → realestate-lab:8000 │
│ ├── /api/agent-office/ → agent-office:8000 (+ WebSocket) │
│ ├── /api/travel/ → travel-proxy:8000 │
│ ├── /media/music/… (nginx 직접 서빙, 생성 오디오) │
│ ├── /media/travel/… (nginx 직접 서빙, 사진/썸네일) │
│ └── /webhook → deployer:9000 │
└──────────────────────────────────────────────────────────────────────┘
| 컨테이너 | 포트 | 역할 |
|---|---|---|
lotto-backend |
18000 | 로또 데이터 수집·분석·추천 + 블로그·투두 API |
stock-lab |
18500 | 주식 뉴스·AI 요약·KIS 실계좌·포트폴리오·자산 추적 |
music-lab |
18600 | AI 음악 생성 (Suno + 로컬 MusicGen 듀얼 프로바이더) |
blog-lab |
18700 | 블로그 마케팅 수익화 (키워드→글 생성→리뷰→발행) |
realestate-lab |
18800 | 청약 공고 자동 수집·프로필 매칭 |
agent-office |
18900 | AI 에이전트 가상 오피스 (WebSocket + 텔레그램 봇) |
travel-proxy |
19000 | 여행 사진 API + 온디맨드 썸네일 |
lotto-frontend |
8080 | SPA 서빙 + 리버스 프록시 |
webpage-deployer |
19010 | Gitea Webhook → 자동 배포 |
디렉토리 구조
web-backend/
├── backend/ # lotto-backend (로또·블로그·투두)
├── stock-lab/ # 주식·포트폴리오
├── music-lab/ # AI 음악 생성
├── blog-lab/ # 블로그 마케팅 파이프라인
├── realestate-lab/ # 청약 자동 수집·매칭
├── agent-office/ # AI 에이전트 오피스 (WS + 텔레그램)
├── travel-proxy/ # 여행 사진 + 썸네일
├── deployer/ # Gitea Webhook 수신 → 자동 배포
├── nginx/default.conf # 리버스 프록시 + SPA + 캐시
├── scripts/ # deploy.sh, deploy-nas.sh, healthcheck.sh
├── docker-compose.yml
├── .env.example
└── CLAUDE.md # Claude Code 작업용 상세 컨텍스트
빠른 시작 (로컬 개발)
cp .env.example .env
docker compose up -d
curl http://localhost:18000/health
curl http://localhost:18500/health
| 서비스 | 로컬 URL |
|---|---|
| Frontend + API | http://localhost:8080 |
| lotto-backend | http://localhost:18000 |
| stock-lab | http://localhost:18500 |
| music-lab | http://localhost:18600 |
| blog-lab | http://localhost:18700 |
| realestate-lab | http://localhost:18800 |
| agent-office | http://localhost:18900 |
| travel-proxy | http://localhost:19000 |
서비스별 기능
1. lotto-backend (/api/)
로또 당첨번호 수집·통계 분석·몬테카를로 시뮬레이션 기반 추천 + 투두·블로그 CRUD.
- 로또: 당첨번호 조회, 5종 통계 분석, 시뮬레이션 최적 번호(
best_picks20쌍), 통계/히트맵/스마트/배치 추천, 전략 가중치(EMA+Softmax), 구매 이력 관리 - 추천 이력: 즐겨찾기·태그·메모 관리
- 투두리스트: UUID PK, 상태(todo/in_progress/done)
- 블로그: 일기형 포스트 (tags JSON 배열, date DESC)
스케줄러
- 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)
주식 뉴스 스크래핑 + LLM 요약 + KIS 실계좌 연동 + 포트폴리오·자산 스냅샷.
- 뉴스: 네이버 증권 + 해외 사이트 크롤링, LLM 기반 한국어 요약
- 실계좌: Windows AI 서버(192.168.45.59:8000) 프록시 → KIS Open API (잔고/주문)
- 포트폴리오: 종목·예수금·매도 히스토리 관리, 현재가 자동 조회
- 자산 스냅샷: 평일 15:40 자동 저장 (KRX 공휴일 판별,
holidays.json매년 갱신)
LLM provider 전환 — LLM_PROVIDER 환경변수
claude(기본): Anthropic Messages API (claude-haiku-4-5)ollama: Windows AI 서버 Ollama (qwen3:14b)
현재가 조회: 네이버 모바일 API → HTML 파싱 폴백, 3분 TTL 메모리 캐시
3. music-lab (/api/music/)
듀얼 프로바이더 AI 음악 생성.
- Suno (
suno): REST API 연동, 보컬·가사·인스트루멘탈. 1회 요청 시 2개 variation 생성, 곡 연장, 보컬 분리, WAV 변환, 12스템 분리, 뮤직비디오, AI Cover 등 풀 스위트 지원 - 로컬 MusicGen (
local): Windows AI PC(RTX 5070 Ti, 16GB VRAM) 인스트루멘탈 전용 - 라이브러리: 생성 파일은
/app/data/music/에 저장되고 Nginx가/media/music/으로 직접 서빙 - 가사 도구: 저장·편집·타임스탬프 기반 가라오케 동기
4. blog-lab (/api/blog-marketing/)
블로그 마케팅 수익화 4단계 파이프라인 (draft → marketed → reviewed → published).
리서치(Naver Search + 상위 블로그 본문 크롤링)
→ 작가(AI 초안 생성)
→ 마케터(전환율 강화 + 브랜드 링크 삽입)
→ 평가자(6기준×10점, 42/60 통과 시 published)
- AI 엔진: Claude API (
claude-sonnet-4-20250514) - 키워드 분석: 네이버 검색(블로그+쇼핑) API + 경쟁도/기회 점수
- 수익 추적: 포스트별 월간 클릭/구매/수익 기록
- 프롬프트 템플릿: DB에 저장 → 코드 배포 없이 수정 가능
5. realestate-lab (/api/realestate/)
공공데이터포털 청약홈 API 연동 + 프로필 기반 자동 매칭.
- 공고 수집: 09:00 매일 자동 (
DATA_GO_KR_API_KEY필요) - 상태 갱신 + 재매칭: 00:00 매일 자동
- 프로필 매칭: 지역·주택형·소득·부양가족 등으로 점수화, 신규 매칭 알림
- 대시보드: 진행 중 공고수, 신규 매칭수, 다가오는 일정 요약
6. agent-office (/api/agent-office/)
AI 에이전트 가상 오피스 — 2D 픽셀아트 사무실에서 4명의 에이전트가 실제 작업을 수행한다.
- 아키텍처: stock-lab / 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) - 텔레그램 연동: 양방향 알림 + 인라인 키보드 승인
- 봇이 작업 결과를 텔레그램으로 푸시, 명령은 텔레그램에서 바로 에이전트에 전달
- Webhook 검증 후
chat.id기준 라우팅
에이전트 구성
| 에이전트 | 스케줄 | 승인 | 주요 기능 |
|---|---|---|---|
📈 주식 트레이더 (stock) |
08:00 매일 | — | 뉴스 요약 (LLM) → 텔레그램 아침 브리핑, 종목 알람 등록 |
🎵 음악 프로듀서 (music) |
수동 트리거 | ✅ 작곡 | 프롬프트 수신 → 승인 → Suno API 작곡 → 트랙 푸시 |
✍️ 블로그 마케터 (blog) |
10:00 매일 | ✅ 발행 | 트렌드 키워드 1개 선택 → 리서치→작가→마케터→평가 자동 실행 → 점수·본문을 텔레그램 승인 요청 → 승인 시 published 전환, 거절 시 재생성 |
🏢 청약 애널리스트 (realestate) |
09:15 매일 | — | realestate-lab 수집 트리거 → 신규 매칭 상위 5건 + 대시보드 요약을 텔레그램 리포트 (읽음 처리 자동) |
에이전트별 명령
Stock — fetch_news, list_alerts, add_alert, test_telegram
Music — compose (승인 필요), credits
Blog — research {keyword}, add_trend_keyword, list_trend_keywords
Realestate — fetch_matches, dashboard
스케줄러 잡
- 07:00 월요일 — Lotto: AI 큐레이터 브리핑 (5세트 + 내러티브)
- 07:30 — Stock: 뉴스 요약
- 09:15 — Realestate: 매칭 리포트
- 10:00 — Blog: 자동 파이프라인 (리서치→생성→리뷰→승인 대기)
- 60초 interval — 유휴 에이전트 휴식 체크
7. travel-proxy (/api/travel/)
여행 사진 API + SQLite 인덱스 + 온디맨드 썸네일 + 지역 관리.
- 원본:
/data/travel/(RO 마운트) - 썸네일: 480×480 Pillow 리사이징,
/data/thumbs/영구 캐시 (tmp → rename 원자성 보장) - DB:
/data/thumbs/travel.db(photos, album_covers 테이블) - 메타:
region_map.json(RO) +region_map_extra.json(RW 오버라이드) +regions.geojson - 지역 관리: 앨범 지역 변경, 커스텀 지역 생성, 지도 핀 좌표 지정
- 데이터 흐름: 수동 sync → 폴더 스캔 → SQLite 인덱싱 + 썸네일 일괄 생성
8. deployer (/webhook)
Gitea Webhook 수신 → NAS 자동 배포.
- HMAC SHA256 서명 검증 (
compare_digest,WEBHOOK_SECRET) - 수신 즉시 200 응답 후 BackgroundTask로 배포
- 배포 스크립트:
git pull→.releases/백업 →rsync→docker compose up -d --build→chown PUID:PGID - 타임아웃 10분
핵심 로직
몬테카를로 시뮬레이션 (lotto-backend)
역대 당첨번호 분석 → 번호별 가중치 산출
→ 가중 확률 샘플링으로 후보 20,000개 생성
→ 5가지 기법으로 각 조합 점수화
→ 상위 100개 DB 저장 → best_picks 20개 교체
| 기법 | 가중치 | 내용 |
|---|---|---|
| 빈도 Z-score | 25% | 번호 출현 빈도의 표준편차 |
| 조합 지문 | 30% | 합계 정규분포 + 홀짝 비율 + 구간분포 |
| 갭 분석 | 20% | 마지막 출현 이후 경과 회차 |
| 공동 출현 | 15% | 번호 쌍 동시 출현 빈도 |
| 다양성 | 10% | 연속번호·범위·구간 커버리지 |
LLM 요약 provider 추상화 (stock-lab)
ai_summarizer.py는 provider 분리 구조. summarize_news(articles) 시그니처는 provider와 무관하게 고정.
_summarize_with_claude: Anthropic Messages API 직접 호출 (httpx, SDK 의존성 없음)_summarize_with_ollama: Ollama/api/generate(타임아웃 180s, qwen3:14b 첫 로드 대응)- 실패 시
LLMError(구OllamaErroralias 유지)
총 자산 스냅샷 (stock-lab)
평일 15:40 자동 실행 → holidays.json으로 공휴일 스킵 → 포트폴리오 현재가 조회 + 예수금 합계 → asset_snapshots upsert (date UNIQUE).
에이전트 FSM + WS 동기화 (agent-office)
DB에 저장된 에이전트 상태가 바뀔 때마다 websocket_manager가 전체 클라이언트에 브로드캐스트. 텔레그램 봇은 waiting 상태 작업에 인라인 키보드를 붙여 승인 요청. 승인/거부 결과가 DB → WS → 프론트로 전파.
자동 배포
git push → Gitea → X-Gitea-Signature (HMAC SHA256)
→ deployer:9000/webhook (서명 검증, compare_digest)
→ BackgroundTask: scripts/deploy.sh (10분 타임아웃)
1. git pull
2. .releases/{timestamp}/ 백업
3. rsync (repo → runtime)
4. docker compose up -d --build
5. chown PUID:PGID
프론트엔드는 자동 배포 안 됨 — 로컬 빌드 후 NAS에 수동 업로드 (
scripts/deploy.bat --frontend)
데이터베이스
각 서비스는 독립 SQLite DB를 /app/data/ 볼륨에 저장.
| 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 |
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 |
agent_office.db |
agent-office | agent_config, agent_tasks, agent_logs, telegram_state, conversation_messages |
travel.db |
travel-proxy | photos (album, filename, mtime, has_thumb), album_covers |
환경변수
# 경로
RUNTIME_PATH=.
REPO_PATH=.
FRONTEND_PATH=./frontend/dist
PHOTO_PATH=./mock_data/photos
# NAS 파일 권한
PUID=1000
PGID=1000
# 외부 서비스
WINDOWS_AI_SERVER_URL=http://192.168.45.59:8000
WEBHOOK_SECRET=your_secret_here
# LLM (stock-lab, blog-lab, agent-office 공통)
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
LLM_PROVIDER=claude # claude | ollama
OLLAMA_URL=http://192.168.45.59:11435
OLLAMA_MODEL=qwen3:14b
# music-lab
SUNO_API_KEY=
MUSIC_AI_SERVER_URL=
MUSIC_MEDIA_BASE=/media/music
# blog-lab
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
# realestate-lab
DATA_GO_KR_API_KEY=
# agent-office
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=
TELEGRAM_WEBHOOK_URL=
STOCK_LAB_URL=http://stock-lab:8000
MUSIC_LAB_URL=http://music-lab:8000
BLOG_LAB_URL=http://blog-lab:8000
REALESTATE_LAB_URL=http://realestate-lab:8000
인프라
| 항목 | 값 |
|---|---|
| 장비 | Synology NAS (Intel Celeron J4025, 18GB RAM) |
| Docker | Synology Container Manager |
| Git 서버 | Gitea (NAS 내부 self-hosted, gahusb.synology.me) |
| AI 서버 | Windows PC (192.168.45.59) — RTX 5070 Ti (16GB VRAM) + Ollama + MusicGen |
| Python | 3.12 (slim 기반 이미지) |
| DB | SQLite (볼륨 마운트로 영속 저장) |
주의사항
.env파일 — 절대 커밋 금지..env.example만 레포에 포함- Nginx trailing slash —
/api/portfolio는 두 location 블록으로 처리 (trailing slash 유무 모두 매칭) - 라우트 순서 —
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 기준) - Windows AI 서버 IP —
192.168.45.59공유기 DHCP 고정 예약. Synology Tailscale은 userspace 모드라 TCP 불가 → 로컬 IP 사용 - Suno CDN —
cdn1.suno.aiURL은 임시 만료 → 생성 즉시 로컬 다운로드 필수 - LLM provider 롤백 — Claude API 장애 시
.env의LLM_PROVIDER=ollama로 전환 후docker compose up -d - 시뮬레이션 교체 방식 —
best_picks는 교체형 (is_active=0비활성화 후 신규 입력)
참고 문서
CLAUDE.md— Claude Code 작업용 상세 컨텍스트 (API 전체 목록, 테이블 스키마 등)docs/— 서비스별 기획·설계 문서