stock 실시간 매매알람(watchlist/trade_alert_state/history·webai 계약·1분 Windows 워커), agent-office 매매알람 notify+/watch 봇·분산워커 관측, 주의사항에 팀규칙·tzdata, DB 테이블 목록 최신화. (기존 하네스 엔지니어링 섹션도 함께 커밋)
444 lines
24 KiB
Markdown
444 lines
24 KiB
Markdown
# web-backend
|
||
|
||
Synology NAS 기반 개인 웹 플랫폼 백엔드 모노레포.
|
||
로또 분석, 주식 포트폴리오, AI 음악 생성, 인스타 카드 피드, 부동산 청약, AI 에이전트 오피스, 여행 앨범, 개인 서비스(포트폴리오·블로그·투두), NAS 자료 다운로드 자동화를 하나의 Docker Compose 스택으로 운영한다.
|
||
|
||
---
|
||
|
||
## 서비스 구성
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ frontend (Nginx:8080) │
|
||
│ ├── 정적 SPA 서빙 (React + Vite) │
|
||
│ └── API 리버스 프록시 │
|
||
│ ├── /api/ → lotto:8000 (로또) │
|
||
│ ├── /api/stock/, /trade/ → stock:8000 │
|
||
│ ├── /api/portfolio → stock:8000 │
|
||
│ ├── /api/music/ → music-lab:8000 │
|
||
│ ├── /api/insta/ → insta-lab:8000 │
|
||
│ ├── /api/realestate/ → realestate-lab:8000 │
|
||
│ ├── /api/agent-office/ → agent-office:8000 (+ WebSocket) │
|
||
│ ├── /api/profile/, /todos, /blog/ → personal:8000 │
|
||
│ ├── /api/packs/ → packs-lab:8000 (HMAC + 5GB upload) │
|
||
│ ├── /api/travel/ → travel-proxy:8000 │
|
||
│ ├── /media/music/, /media/videos/ (nginx 직접 서빙, 미디어) │
|
||
│ ├── /media/travel/… (nginx 직접 서빙, 사진/썸네일) │
|
||
│ └── /webhook → deployer:9000 │
|
||
└──────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
| 컨테이너 | 포트 | 역할 |
|
||
|---------|------|------|
|
||
| `lotto` | 18000 | 로또 데이터 수집·분석·추천 API |
|
||
| `stock` | 18500 | 주식 뉴스·AI 요약·KIS 실계좌·포트폴리오·자산 추적 |
|
||
| `music-lab` | 18600 | AI 음악 생성 (Suno + 로컬 MusicGen 듀얼 프로바이더) + YouTube 수익화 |
|
||
| `insta-lab` | 18700 | 인스타 카드 피드 자동 생성 (뉴스→키워드→10페이지 카드, Playwright) |
|
||
| `realestate-lab` | 18800 | 청약 공고 자동 수집·5티어 매칭·신규 매칭 push |
|
||
| `agent-office` | 18900 | AI 에이전트 가상 오피스 (WebSocket + 텔레그램 봇) |
|
||
| `personal` | 18850 | 개인 서비스 — 포트폴리오·블로그·투두 통합 |
|
||
| `packs-lab` | 18950 | NAS 자료 다운로드 자동화 (DSM 공유 링크 + 5GB 청크 업로드) |
|
||
| `travel-proxy` | 19000 | 여행 사진 API + 온디맨드 썸네일 |
|
||
| `frontend` | 8080 | SPA 서빙 + 리버스 프록시 |
|
||
| `webpage-deployer` | 19010 | Gitea Webhook → 자동 배포 |
|
||
|
||
---
|
||
|
||
## 디렉토리 구조
|
||
|
||
```
|
||
web-backend/
|
||
├── lotto/ # 로또 추천·통계·시뮬레이션
|
||
├── stock/ # 주식·포트폴리오·KIS 연동
|
||
├── music-lab/ # AI 음악 생성 + YouTube 수익화
|
||
├── insta-lab/ # 인스타 카드 피드 자동 생성 (Playwright)
|
||
├── realestate-lab/ # 청약 자동 수집·5티어 매칭
|
||
├── agent-office/ # AI 에이전트 오피스 (WS + 텔레그램)
|
||
├── personal/ # 포트폴리오·블로그·투두 통합
|
||
├── packs-lab/ # NAS 자료 다운로드 자동화 (HMAC + Supabase)
|
||
├── 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 작업용 상세 컨텍스트
|
||
```
|
||
|
||
---
|
||
|
||
## 빠른 시작 (로컬 개발)
|
||
|
||
```bash
|
||
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 | http://localhost:18000 |
|
||
| stock | http://localhost:18500 |
|
||
| music-lab | http://localhost:18600 |
|
||
| insta-lab | http://localhost:18700 |
|
||
| realestate-lab | http://localhost:18800 |
|
||
| personal | http://localhost:18850 |
|
||
| agent-office | http://localhost:18900 |
|
||
| packs-lab | http://localhost:18950 |
|
||
| travel-proxy | http://localhost:19000 |
|
||
|
||
---
|
||
|
||
## 서비스별 기능
|
||
|
||
### 1. lotto-backend (`/api/`)
|
||
|
||
로또 당첨번호 수집·통계 분석·몬테카를로 시뮬레이션 기반 추천 + 투두·블로그 CRUD.
|
||
|
||
- **로또**: 당첨번호 조회, 5종 통계 분석, 시뮬레이션 최적 번호(`best_picks` 20쌍), 통계/히트맵/스마트/배치 추천, 전략 가중치(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 (`/api/stock/`, `/api/trade/`, `/api/portfolio`)
|
||
|
||
주식 뉴스 스크래핑 + LLM 요약 + KIS 실계좌 연동 + 포트폴리오·자산 스냅샷.
|
||
|
||
- **뉴스**: 네이버 증권 + 해외 사이트 크롤링, LLM 기반 한국어 요약
|
||
- **실계좌**: Windows AI 서버(192.168.45.59:8000) 프록시 → KIS Open API (잔고/주문)
|
||
- **포트폴리오**: 종목·예수금·매도 히스토리 관리, 현재가 자동 조회
|
||
- **자산 스냅샷**: 평일 15:40 자동 저장 (KRX 공휴일 판별, `holidays.json` 매년 갱신)
|
||
- **실시간 매매 알람** (2026-07-02): 장중(+시간외) 1분 폴링으로 매수(watchlist ∪ 스크리너 후보, TA 시그널)·매도(보유종목, exit 룰 + 트레일링 스톱) 조건 충족 시 텔레그램(본인+아내) 알람. **TA 계산은 Windows `trade-monitor` WSL2 docker 워커**, NAS는 감시대상 조립 + edge 중복판정(영속) + 발송 담당. 관심종목은 `/api/stock/watchlist` CRUD 또는 텔레그램 `/watch` 봇 명령. webai 계약: `GET /api/webai/trade-alert/monitor-set` · `POST /report`. 워커/프론트 탭은 web-ai/web-ui repo (설계: `docs/superpowers/specs/2026-07-02-realtime-trade-alerts-design.md`)
|
||
|
||
**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. insta-lab (`/api/insta/`)
|
||
|
||
인스타그램 카드 피드 자동 생성 — 뉴스 모니터링 → 키워드 추출 → 10페이지 카드 카피·PNG 렌더 → 텔레그램 푸시 → 사용자 수동 업로드.
|
||
|
||
```
|
||
NAVER 뉴스 + YouTube 인기 (외부 트렌드)
|
||
→ 카테고리별 빈도 + Claude Haiku 정제 → 트렌딩 키워드
|
||
→ 사용자가 키워드 선택
|
||
→ Claude Sonnet으로 10페이지 카피 추론 (커버 1 + 본문 8 + CTA 1)
|
||
→ Jinja2 + Playwright 1080×1350 PNG 10장 렌더
|
||
→ 텔레그램 미디어 그룹 + 추천 캡션·해시태그
|
||
```
|
||
|
||
- **AI 엔진**: Claude Sonnet (카피) + Claude Haiku (키워드 분류)
|
||
- **데이터 소스**: NAVER 뉴스 검색 + YouTube Data API v3 mostPopular(KR)
|
||
- **카테고리 가중치**: 사용자가 economy/psychology/celebrity 등 카테고리별 가중치 설정 → 자동 추출 비율에 반영
|
||
- **카드 디자인**: `insta-lab/app/templates/default/card.html.j2` — 사용자가 자유 수정 (Tailwind 등)
|
||
- **프롬프트 템플릿**: 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 / music-lab / insta-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` 기준 라우팅
|
||
- **실시간 매매 알람 수신**: `POST /api/agent-office/stock/trade-alert` (stock이 edge 판정한 알람 push) → 텔레그램 본인+아내 발송. 봇 명령 `/watch`·`/unwatch`·`/watchlist`로 관심종목 관리
|
||
- **분산 워커 관측**: `GET /api/agent-office/nodes`가 `worker:<name>:heartbeat`를 집계 → web-ui `/infra` 시각화 + 다운/복구/dead-letter 텔레그램 경보. WSL docker 워커는 `node_monitor.WORKER_REGISTRY` 등재 필수(위 주의사항 팀 규칙)
|
||
|
||
#### 에이전트 구성
|
||
|
||
| 에이전트 | 스케줄 | 승인 | 주요 기능 |
|
||
|---------|--------|-----|----------|
|
||
| 📈 **주식 트레이더** (`stock`) | 08:00 매일 | — | 뉴스 요약 (LLM) → 텔레그램 아침 브리핑, 종목 알람 등록 |
|
||
| 🎵 **음악 프로듀서** (`music`) | 수동 트리거 | ✅ 작곡 | 프롬프트 수신 → 승인 → Suno API 작곡 → 트랙 푸시 |
|
||
| 🎴 **인스타 큐레이터** (`insta`) | 09:00 / 09:30 매일 | — | 09:00 외부 트렌드(NAVER + YouTube) 수집 → 09:30 가중치 기반 키워드 추출 → 텔레그램 후보 5개씩 카테고리당 인라인 버튼 푸시 → 사용자 선택 시 카드 10장 미디어 그룹 |
|
||
| 🏢 **청약 애널리스트** (`realestate`) | realestate-lab push trigger | — | realestate-lab이 신규 매칭 발견 시 push → 인라인 [북마크] 버튼 포함 텔레그램 알림 |
|
||
| 🎬 **YouTube 리서처** (`youtube`) | 09:00 매일 | — | 한국 YouTube 트렌딩 + Google Trends + Billboard → music-lab market_trends push |
|
||
|
||
#### 에이전트별 명령
|
||
|
||
**Stock** — `fetch_news`, `list_alerts`, `add_alert`, `test_telegram`
|
||
**Music** — `compose` (승인 필요), `credits`
|
||
**Insta** — `extract`, `render <keyword_id>`, `collect_trends`
|
||
**Realestate** — `fetch_matches`, `dashboard`
|
||
**YouTube** — `research {countries: [...]}`
|
||
|
||
#### 스케줄러 잡
|
||
|
||
- 07:00 월요일 — Lotto: AI 큐레이터 브리핑 (5세트 + 내러티브)
|
||
- 07:30 — Stock: 뉴스 요약
|
||
- 08:00 평일 — Stock: AI 뉴스 sentiment 분석
|
||
- 09:00 — YouTube: 한국 트렌딩 수집
|
||
- 09:00 — Insta: 외부 트렌드 수집 (NAVER 인기 + YouTube mostPopular)
|
||
- 09:30 — Insta: 키워드 추출 (가중치 적용) + 텔레그램 후보 푸시
|
||
- 15:40 평일 — Stock: 총 자산 스냅샷
|
||
- 16:30 평일 — Stock: 스크리너 실행
|
||
- 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)
|
||
|
||
`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` (구 `OllamaError` alias 유지)
|
||
|
||
### 총 자산 스냅샷 (stock)
|
||
|
||
평일 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 | draws, recommendations, simulation_runs/candidates, best_picks, purchase_history, strategy_performance/weights, weekly_reports, lotto_briefings |
|
||
| `stock.db` | stock | articles, portfolio, broker_cash, asset_snapshots, sell_history, holdings_signals, news_sentiment, **watchlist, trade_alert_state, trade_alert_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), video_projects, revenue_records, market_trends, trend_reports |
|
||
| `insta.db` | insta-lab | news_articles, trending_keywords (source 컬럼), card_slates, card_assets, generation_tasks, prompt_templates, account_preferences |
|
||
| `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, youtube_research_jobs, lotto_signals/baselines, notified_failed_pipelines (파이프라인 실패 알림 dedup) |
|
||
| `personal.db` | personal | profile, careers, projects, skills, introductions, todos, blog_posts |
|
||
| `travel.db` | travel-proxy | photos (album, filename, mtime, has_thumb), album_covers |
|
||
| `pack_files` (외부 Supabase) | packs-lab | filename, host_path, mime, byte_size, sha256, deleted_at |
|
||
|
||
---
|
||
|
||
## 환경변수
|
||
|
||
```env
|
||
# 경로
|
||
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, insta-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
|
||
|
||
# stock admin protection (CODE_REVIEW F2)
|
||
ADMIN_API_KEY=
|
||
ALLOW_UNAUTHENTICATED_ADMIN=false
|
||
|
||
# music-lab
|
||
SUNO_API_KEY=
|
||
MUSIC_AI_SERVER_URL=
|
||
MUSIC_MEDIA_BASE=/media/music
|
||
|
||
# insta-lab + agent-office (NAVER 검색 + YouTube Data API 공유)
|
||
NAVER_CLIENT_ID=
|
||
NAVER_CLIENT_SECRET=
|
||
YOUTUBE_DATA_API_KEY=
|
||
|
||
# realestate-lab
|
||
DATA_GO_KR_API_KEY=
|
||
|
||
# packs-lab (DSM + Supabase)
|
||
DSM_HOST=
|
||
DSM_USER=
|
||
DSM_PASS=
|
||
BACKEND_HMAC_SECRET=
|
||
SUPABASE_URL=
|
||
SUPABASE_SERVICE_KEY=
|
||
PACK_HOST_DIR=/docker/webpage/media/packs # shared folder 시점 (CLAUDE.md F5)
|
||
|
||
# agent-office
|
||
TELEGRAM_BOT_TOKEN=
|
||
TELEGRAM_CHAT_ID=
|
||
TELEGRAM_WEBHOOK_URL=
|
||
STOCK_URL=http://stock:8000
|
||
MUSIC_LAB_URL=http://music-lab:8000
|
||
INSTA_LAB_URL=http://insta-lab:8000
|
||
REALESTATE_LAB_URL=http://realestate-lab:8000
|
||
|
||
# personal (포트폴리오 편집 인증)
|
||
PORTFOLIO_EDIT_PASSWORD=
|
||
```
|
||
|
||
---
|
||
|
||
## 인프라
|
||
|
||
| 항목 | 값 |
|
||
|------|----|
|
||
| 장비 | 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/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`
|
||
- **시뮬레이션 교체 방식** — `best_picks`는 교체형 (`is_active=0` 비활성화 후 신규 입력)
|
||
- **[팀 규칙] 모든 WSL docker 워커는 `/infra` 관측 필수** — 새 워커는 ① `worker:<name>:heartbeat`(EX45) 발신 ② BE가 `agent-office/app/node_monitor.py`의 `WORKER_REGISTRY`에 등재 ③ → `/api/agent-office/nodes`·web-ui `/infra` 노출 + 다운/복구/dead-letter 경보. 미준수 = 사일런트 사망 재발(insta-render 2주 사고). 워커 PR 머지 게이트
|
||
- **Alpine + tzdata 함정** — stock 컨테이너는 `python:3.12-alpine` + tzdata 미설치라 `TZ=Asia/Seoul`이 무효 → `date.today()`가 UTC. KST 날짜는 `_today_kst()`(=`utcnow()+9h`) 명시 변환 필수 (아침 스케줄 리포트 하루 밀림 방지)
|
||
|
||
---
|
||
|
||
## 하네스 엔지니어링 (Claude Code 제어)
|
||
|
||
이 레포는 Claude Code 세션의 동작을 `.claude/` 설정으로 **제어(harness engineering)** 한다. 모든 산출물은 git 추적되어 이 체크아웃의 모든 세션(co-gahusb 팀버스의 BE 역할 포함)에 공유된다.
|
||
|
||
### 제어 표면 (무엇을 통제하는가)
|
||
|
||
| 레이어 | 메커니즘 | 위치 | 역할 |
|
||
|--------|---------|------|------|
|
||
| 컨텍스트 주입 | CLAUDE.md 계층 + 서비스 메모리 | `CLAUDE.md`, `memory/service_*.md` | 항상 로딩되는 카탈로그(불변) ↔ 관련 시 recall(가변) 2계층 |
|
||
| 권한 가드 | permissions allow/deny/ask | `.claude/settings.json` | 읽기전용 명령 무프롬프트 / 시크릿·DB 차단 / push·reset 확인 |
|
||
| 행동 강제 | PreToolUse·PostToolUse·SessionStart hook | `.claude/hooks/` | CLAUDE.md 주석 규칙을 하네스가 실제 차단·환기 |
|
||
| 반복 워크플로우 | slash commands | `.claude/commands/` | `/co-inbox`, `/svc`, `/harness-audit` |
|
||
| 전문 역할 | subagents | `.claude/agents/` | `be-developer`, `evaluator` |
|
||
| 협업 버스 | MCP 서버 | `.mcp.json` | co-gahusb 팀버스(세션 간 메시지·작업·락) |
|
||
|
||
### 적용된 가드 (hook)
|
||
|
||
| hook | 이벤트 / matcher | 동작 | 근거 |
|
||
|------|-----------------|------|------|
|
||
| `pretooluse-guard.sh` | PreToolUse · `Bash\|PowerShell` | **차단** 로컬 docker 변경(`up/down/build/restart/exec…`; ps·logs·config·images는 허용) | `feedback_docker_nas` |
|
||
| 〃 | 〃 | **차단** `git commit --amend` · `git push --force`(`--force-with-lease`는 허용) | `feedback_concurrent_session_git_collision` |
|
||
| 〃 | 〃 | **차단** PowerShell `>`/`>>` 파일 리다이렉트(UTF-16 BOM; `2>$null`·`> $null`은 허용) | `feedback_powershell_redirect_encoding` |
|
||
| `posttooluse-memory.sh` | PostToolUse · `Edit\|Write` | 서비스 `db.py`/`models.py`/스케줄러/`.sql` 편집 시 `service_<name>.md` 갱신 환기(비차단) | 메모리 디스플린 |
|
||
| `session-start.sh` | SessionStart · `startup\|resume` | BE 역할 + 수신함/락 넛지 주입 | 협업 버스 프로토콜 |
|
||
|
||
차단 판단 로직은 `.claude/hooks/_guard.py`(Python). 래퍼는 파서 부재 시 **fail-open**(통과)하고, 출력은 UTF-8로 고정한다.
|
||
|
||
### slash commands
|
||
|
||
| 커맨드 | 용도 |
|
||
|--------|------|
|
||
| `/co-inbox` | co-gahusb 팀버스 BE 수신함(inbox + tasks + locks) 일괄 확인 |
|
||
| `/svc <name>` | 해당 `service_<name>.md` 메모리 + 핵심 파일 위치를 즉시 로드 |
|
||
| `/harness-audit` | 서브에이전트 fan-out으로 CLAUDE.md 카탈로그 ↔ 실제 코드 드리프트 감사 |
|
||
|
||
### 확장 / 유지보수
|
||
|
||
- **hook 경로는 이 머신 기준 절대경로**(`/c/Users/jaeoh/Desktop/workspace/web-backend/.claude/hooks/…`)다. 레포를 다른 경로로 클론하면 `settings.json`의 3개 hook command 경로를 갱신해야 한다.
|
||
- 가드 패턴 추가/수정은 `_guard.py`만 고치면 된다(설정 변경 불필요).
|
||
- hook은 새 세션에서 자동 로드된다. 진행 중 세션에 즉시 반영하려면 `/hooks` 메뉴를 열거나 재시작한다.
|
||
- 메모리 디스플린: 코드 구조가 바뀌면 **CLAUDE.md(불변 카탈로그)** 가 아니라 **`service_*.md`(가변 상세)** 를 갱신한다.
|
||
|
||
---
|
||
|
||
## 참고 문서
|
||
|
||
- `CLAUDE.md` — Claude Code 작업용 상세 컨텍스트 (API 전체 목록, 테이블 스키마 등)
|
||
- `docs/` — 서비스별 기획·설계 문서
|
||
- `.claude/` — 하네스 설정(settings·hooks·commands·agents). 위 "하네스 엔지니어링" 섹션 참조
|