docs: 03_rag + 04_scheduler + 05_admin 워크플로우 현행화

03_rag:
- companyx_grounding_pipeline.md: 코드 SSOT 섹션 추가, 진입 조건 3단계(IC→마커 폴백) 정확히 기술, 환경변수 참조로 IP 하드코딩 제거
- companyx_incremental_indexing_workflow.md: frontmatter 표준 적용 (type, last_updated)
- rag_upload_indexing_pipeline.md: 코드 SSOT·재인덱싱·업로드 경로별 진입점 테이블 추가, 환경변수 참조

04_scheduler:
- scheduled_daily_briefing.md: n8n cron 전제 제거, APScheduler DB 기반 + LangGraph 워크플로우 기준 재작성
- scheduled_healthcheck_alert.md: n8n cron 전제 제거, /health 엔드포인트 + SKILL.md registry 기반 재작성
- scheduled_rag_reindex_retry.md: 현행 코드에 해당 잡 없음 → _archive 이동

05_admin:
- diary_reflection_pipeline.md: n8n 전제 제거, APScheduler + diary_generator.py 기준 재작성, /api/diary/generate(존재하지 않는 엔드포인트) 제거

Refs: DOCS#8

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
happybell80 2026-04-06 07:52:15 +09:00
parent 9c9de9ebae
commit f078b28ced
7 changed files with 240 additions and 93 deletions

View File

@ -1,7 +1,7 @@
---
tags: [workflow, rag, companyx, grounding, answer]
type: workflow
last_updated: 2026-03-23
last_updated: 2026-04-06
---
# Company X Grounding 파이프라인
@ -15,56 +15,68 @@ last_updated: 2026-03-23
- [헌장.md](../../../../0_VALUE/00_Foundations/헌장.md)
- [writing-principles.md](../../../../0_VALUE/20_Governance/writing-principles.md)
## 코드 SSOT
- `rb8001/app/services/companyx_grounding_service.py`
## 입력
- 사용자 질문
- 사용자 ID
- 검색된 근거 문서 목록
- 근거 문서 메타데이터
- 사용자 질문 (`message`)
- 사용자 ID (`user_id`)
- (선택) 의도 분류 결과 (`classified_intent`, `classified_confidence`)
## 출력
- 직접 답
- 근거 문서명
- 근거 문단 요약
- 필요 시 위치 정보
- 근거 부족 시 명시적 실패 응답
- `CompanyXRAGOutput` (Pydantic 모델):
- `direct_answer` — 질문에 대한 직접 답변 (근거 부족 시 빈 문자열)
- `evidence_docs` — 근거 문서 파일명 목록
- `failure_reason` — 답변 불가 사유 (문서 없음, 단정 불가 등)
## 처리 순서
1. `team_id == COMPANYX_TEAM_ID`이고 질문이 마커(intent/domain)에 매칭되면 grounding 경로로 진입한다. team_id만으로는 진입하지 않는다. 마커 미매칭 시 일반 스킬 경로(캘린더/이메일/뉴스 등)로 fallback한다. (260323 P1-5 롤백, 전원 동의)
2. 질문 유형을 분류한다 (설명형/사실확인형/수치확인형/재정리형).
3. 멀티쿼리를 생성한다 (`_build_query_candidates()`, 7~9개 변형).
4. 하이브리드 검색(벡터+키워드 RRF, search_mode=hybrid)으로 상위 결과를 수집한다.
5. 검색 결과를 LLM에 컨텍스트로 전달하고, LLM이 질문 적합도를 판단해 답변한다.
6. LLM 응답을 Pydantic(`CompanyXRAGOutput`)으로 검증한다.
7. 근거가 부족하면 LLM이 `failure_reason`을 명시하고, 추정 답변 대신 실패 응답으로 끝낸다.
## 진입 조건 (`should_handle_companyx_grounding()`)
1. `user_id`에서 `team_id`를 조회한다 (`get_user_team_id()`).
2. `team_id != COMPANYX_TEAM_ID`이면 진입하지 않는다.
3. LLM 의도 분류가 `companyx_rag` 이외의 확정 intent를 confidence >= 0.7로 반환했으면 진입하지 않는다.
4. `companyx_rag`으로 분류된 경우(confidence >= 0.5) 진입한다.
5. 위 두 경우 모두 아닐 때, 하위 호환성을 위해 마커 기반 판단(`_looks_like_companyx_grounding_question()`)으로 진입 여부를 결정한다.
- **마커**: intent 마커(근거, 내부 문서, MOU, 계약서, 투자조합, 재무제표 등) 또는 domain 마커(오늘전통, 옐로펀치, 컴퍼니엑스 등)
## 처리 순서 (`try_companyx_grounding()`)
1. 진입 조건 확인 (위 참조).
2. 질문 유형 분류 (`_classify_question_type()`): explanatory / fact_check / quantitative / recap.
3. 멀티쿼리 생성 (`_build_query_candidates()`): 원문 + 키워드 조합 + 문서 힌트 결합으로 중복 제거 후 다수 변형 생성.
4. 하이브리드 검색 (`_search_companyx_documents()`):
- 각 쿼리를 `$SKILL_RAG_FILE_URL/api/search`에 병렬 호출 (`asyncio.gather`).
- 페이로드: `team_id=COMPANYX_TEAM_ID`, `limit=5`, `threshold=0.35`, `search_mode=hybrid`.
- 결과를 `document_id:chunk_index` 키로 병합, 최고 점수만 유지.
5. 상위 결과 선택 (`_select_top_results()`): RRF 정규화 점수 기준 정렬, 문서 다양성(같은 document_id 중복 제거), 최대 5건(`MAX_EVIDENCE_CHUNKS`).
6. LLM 근거 답변 생성 (`_call_llm_companyx_grounding()`):
- 프롬프트: `prompts/rag/companyx_grounding.md` (render_prompt).
- 모델: `settings.DEFAULT_LLM_MODEL`, temperature=0.1, JSON 응답 형식.
- 응답을 `CompanyXRAGOutput`으로 Pydantic 검증.
7. 최종 응답 조립 (`_build_grounded_response()`): LLM 성공 시 direct_answer + 참고 문서, 실패 시 question_type별 실패 메시지.
## 검색 모드
- 기본 검색 모드는 `hybrid`이다 (벡터+키워드 RRF 합산).
- 기본 검색 모드: `hybrid` (벡터 + 키워드 RRF 합산).
- 벡터 검색: PGVector cosine similarity (Gemini Embedding 2, 768d).
- 키워드 검색: PostgreSQL tsvector + GIN 인덱스, prefix 매칭(`:*`).
- 점수 합산: RRF (Reciprocal Rank Fusion, k=60), 정규화하여 0~1 범위로 반환.
- Apache AGE 그래프 점수는 hook으로 가산 가능 (현재 보조적 위치).
- 점수 합산: RRF (Reciprocal Rank Fusion, k=60), 정규화 0~1.
- Apache AGE 그래프 점수: hook으로 가산 가능 (보조적 위치).
## 근거 선별 원칙
- 키워드 기반 룰로 검색 결과를 필터링하지 않는다 (룰베이스 절제 원칙 — 헌장.md §B.6).
- 하이브리드 검색이 반환한 RRF 정규화 점수 순서를 신뢰하고, LLM이 컨텍스트를 보고 적합도를 재판단한다.
- 키워드 기반 룰로 검색 결과를 필터링하지 않는다 (룰베이스 절제 원칙 — 헌장.md 참조).
- 하이브리드 검색 RRF 정규화 점수 순서를 신뢰하고, LLM이 컨텍스트를 보고 적합도를 재판단한다.
- 근거 선별 책임은 LLM에 있으며, 코드 레벨에서는 점수 상위 결과를 문서 다양성 기준으로 선택만 한다.
## 실패 분기
- 검색 결과가 0건이면 `try_companyx_grounding()``None`을 반환하고, `message_service`가 일반 의도 분류 경로로 fallback한다. (260323 fallback 복구)
- 검색 결과가 0건이면 `try_companyx_grounding()``None`을 반환 `message_service`가 일반 의도 분류 경로로 fallback.
- LLM이 컨텍스트만으로 답변 불가로 판단하면 `failure_reason`을 채우고, 성공처럼 반환하지 않는다.
- 수치형 질문에서 값이 없으면 추정하지 않는다.
- 내부 규정이나 최신 집계가 없으면 `문서 없음`, `미확인`, `불일치` 중 하나로 명시한다.
- 메타 대화로 회피하지 않는다.
## 현재 기준
- 이 흐름은 `rb8001` 답변 합성 규칙과 연결된다.
- 검색 결과를 그대로 붙이는 방식은 허용하지 않는다.
- 질문 유형 분류는 LLM 프롬프트 톤 조절용으로만 사용한다.
- `SKILL.md`의 Trigger 설명은 운영 의도 요약이고, 실제 진입 조건 판단은 현재 코드 기준을 우선합니다.
## 환경변수
- `SKILL_RAG_FILE_URL` — skill-rag-file 서비스 베이스 URL (필수).
## 검증 기준
- `오늘전통/옐로펀치` 같은 기준 질문에서 직접 답 + 근거 문서가 함께 나와야 한다.
- `투자사 수`, `휴가 규정` 같은 근거 부족 질문은 추정 없이 실패해야 한다.
- 기준 질문(오늘전통, 옐로펀치 등)에서 직접 답 + 근거 문서가 함께 나와야 한다.
- 근거 부족 질문(투자사 수, 휴가 규정 등)은 추정 없이 실패해야 한다.
- Slack 실응답과 테스트 응답이 같은 기준을 따라야 한다.
## 관련 문서

View File

@ -1,4 +1,8 @@
---
tags: [workflow, rag, companyx, indexing, batch, incremental]
type: workflow
last_updated: 2026-04-06
---
# Company X 단계별 증분 인덱싱 워크플로우

View File

@ -1,7 +1,7 @@
---
tags: [workflow, rag, companyx, upload, indexing]
type: workflow
last_updated: 2026-03-22
last_updated: 2026-04-06
---
# RAG 업로드·인덱싱 파이프라인
@ -15,6 +15,12 @@ last_updated: 2026-03-22
- [project-artifacts-ssot.md](../../../../0_VALUE/20_Governance/project-artifacts-ssot.md)
- [test-principles.md](../../../../0_VALUE/20_Governance/test-principles.md)
## 코드 SSOT
- `skill-rag-file` 서비스 (업로드·인덱싱·검색 담당)
- `rb8001/app/pipelines/langgraph_document.py` (문서 파이프라인에서 업로드·재인덱싱 호출)
- `rb8001/app/router/slack_handler.py` (Slack 파일 업로드 경로)
- `rb8001/app/services/naverworks_file_processor.py` (네이버웍스 첨부 업로드 경로)
## 입력
- `team_id`
- `file_url` 또는 업로드 파일 본문
@ -22,20 +28,37 @@ last_updated: 2026-03-22
- `metadata`
## 출력
- 업로드 결과
- 인덱싱 결과
- 업로드 결과 (document_id)
- 인덱싱 결과 (청크 수, 임베딩 상태)
- 저장된 문서의 메타데이터
- 실패 시 실패 원인과 재시도 가능 여부
## 처리 순서
1. 요청 페이로드를 정규화한다.
2. 원본 문서를 `skill-rag-file` 업로드 엔드포인트로 전달한다.
2. 원본 문서를 `$SKILL_RAG_FILE_URL/api/upload`로 전달한다.
3. 텍스트 추출을 수행한다. OCR 대상(이미지 PDF 등)은 OCR 폴백 경로로 처리한다.
4. 청킹한다 (기준: 1,000자 chunk, 200자 overlap).
5. 임베딩한다 (Gemini Embedding 2, 768d).
6. DB 저장 시 `tsvector` 컬럼이 트리거로 자동 생성된다 (`simple` 설정).
7. 저장 결과를 그대로 반환한다.
## 재인덱싱
- `$SKILL_RAG_FILE_URL/api/reindex` 엔드포인트를 통해 기존 문서 재인덱싱 가능.
- `langgraph_document.py` 파이프라인에서 업로드 후 필요 시 재인덱싱을 호출한다.
- 재인덱싱 후 `$SKILL_RAG_FILE_URL/api/text/{document_id}`로 텍스트 추출 결과를 검증한다.
## 업로드 경로별 진입점
| 경로 | 코드 | 설명 |
|------|------|------|
| Slack 파일 첨부 | `slack_handler.py``$SKILL_RAG_FILE_URL/api/upload` | Slack에서 파일 다운로드 후 skill-rag-file로 전송 |
| 네이버웍스 메일 첨부 | `naverworks_file_processor.py``$SKILL_RAG_FILE_URL/api/upload` | 메일 첨부 다운로드 후 업로드 |
| IR Deck 업로드 | `ir_deck.py``slack_handler.upload_files_to_rag()` | IR Deck 평가를 위한 업로드 |
| 배치 인덱싱 | `skill-rag-file/scripts/` | 대량 문서 일괄 인덱싱 |
## 환경변수
- `SKILL_RAG_FILE_URL` — skill-rag-file 서비스 베이스 URL (필수).
## 실패 분기
- 파일 누락이면 업로드 전에 실패한다.
- 텍스트 추출이 실패하면 인덱싱하지 않는다.
@ -49,7 +72,6 @@ last_updated: 2026-03-22
- 청킹 기준: 1,000자 chunk, 200자 overlap.
- 임베딩: Gemini Embedding 2, 768차원, HNSW cosine 인덱스.
- `tsvector` 컬럼은 INSERT/UPDATE 트리거로 자동 생성된다 (`simple` 설정, GIN 인덱스).
- 배치 인덱싱 스크립트: `skill-rag-file/scripts/reindex_companyx_latest_200.py` (200개 기준).
## 검증 기준
- 업로드 직후 검색 API로 최소 1건 이상 적중하는지 확인한다.

View File

@ -1,31 +1,62 @@
---
tags: [workflow, scheduler, news, headlines, slack]
type: workflow
last_updated: 2026-04-06
---
# scheduled_daily_briefing 워크플로우
## 목적
평일 09:10에 네이버 + 동남아 스타트업 헤드라인을 수집하여 Slack 채널에 자동 게시한다.
DB에 등록된 스케줄에 따라 네이버 + 동남아 스타트업 헤드라인을 수집하여 Slack 채널에 자동 게시한다.
## 아키텍처
- **스케줄러**: APScheduler (DB 기반). `db_loader.py``scheduler_jobs` 테이블에서 `job_type=daily_headlines` 잡을 로드하여 등록.
- **실행 래퍼**: `app/scheduler/jobs/daily_headlines.py``_run_headlines_with_logging(channel_id)`.
- **실제 워크플로우**: `app/services/workflows/headlines_workflow.py` — LangGraph StateGraph로 구성.
- **n8n 미사용**. systemd로 rb8001이 직접 실행.
## 코드 SSOT
- `rb8001/app/scheduler/jobs/daily_headlines.py` — 스케줄러 잡 래퍼
- `rb8001/app/services/skills/startup_news_skill.py``run_headlines_job()` 진입점
- `rb8001/app/services/workflows/headlines_workflow.py` — LangGraph 워크플로우 본체
- `rb8001/app/scheduler/db_loader.py` — DB 잡 로더 (`JOB_TYPE_MAP["daily_headlines"]`)
## 흐름
```
09:10 Trigger (월~금) → Build Runtime Context → [병렬] Call Naver Headlines API
Call SEA Headlines API
→ Describe Actual Path → APIs Reachable? → (true) Build Runtime Summary → Slack Delivery View
→ (false) Build Fallback Summary → Slack Delivery View
APScheduler cron trigger
→ db_loader가 등록한 guarded_job (schedule_policy 평가)
→ _run_headlines_with_logging(channel_id)
→ run_headlines_job(channel_id)
→ run_headlines_workflow(channel_id) [LangGraph]
→ fetch_naver_node (SkillCommands.fetch_naver_headlines, fmt=slack)
→ fetch_sea_node (SkillCommands.fetch_sea_headlines, fmt=json)
→ format_node (네이버 텍스트에 동남아 섹션 삽입)
→ send_node (Slack chat.postMessage)
```
## 주요 노드
| 노드 | 설명 |
|---|---|
| 09:10 Trigger | cron `10 9 * * 1-5` (평일 09:10) |
| Build Runtime Context | scheduler 메타 정보 설정 (channel, job wrapper, state store) |
| Call Naver Headlines API | `POST :8505/api/news/naver/startup-headlines` (format=slack) |
| Call SEA Headlines API | `POST :8505/api/news/sea/headlines` (format=json) |
| Describe Actual Path | 두 API 결과를 합산하여 실행 경로 요약 |
| APIs Reachable? | 양쪽 모두 200인지 확인 |
| Slack Delivery View | 최종 결과를 Slack 채널에 게시 |
## 스케줄 정책
- cron 표현식은 `scheduler_jobs` DB 테이블에서 관리한다 (예: `10 9 * * 1-5`).
- `schedule_policy` 필드에 `workday` 정책이 설정되어 있으면 공휴일/주말을 건너뛴다 (`evaluate_schedule_policy()`).
- `channel_id``config.channel_id` DB 필드에서 전달받는다.
## 엔드포인트
- 아웃바운드: `POST http://192.168.219.52:8505/api/news/naver/startup-headlines`
- 아웃바운드: `POST http://192.168.219.52:8505/api/news/sea/headlines`
- 아웃바운드: Slack `chat.postMessage` (채널: C09C98KK2TT)
## 주요 노드 (LangGraph)
| 노드 | 함수 | 설명 |
|------|------|------|
| fetch_naver | `fetch_naver_node()` | `SkillCommands.fetch_naver_headlines(fmt="slack")` 호출 |
| fetch_sea | `fetch_sea_node()` | `SkillCommands.fetch_sea_headlines(fmt="json")` 호출 |
| format | `format_node()` | 네이버 헤드라인 텍스트 끝에 동남아 섹션 삽입 |
| send | `send_node()` | Slack `chat.postMessage` 전송 |
## Slack 봇 토큰 해소
- `_get_slack_bot_token_for_channel(channel_id)` → DB에서 채널별 workspace bot_token 조회.
- 실패 시 `settings.COMPANYX_SLACK_BOT_TOKEN``settings.SLACK_BOT_TOKEN` 순서로 폴백.
## 실패 분기
- 네이버 또는 동남아 API 호출 실패 시 `errors` 리스트에 기록하고 빈 텍스트로 진행.
- 보낼 텍스트가 비어 있으면 Slack 전송을 스킵하고 `message_ts=None`으로 종료.
- Slack 전송 실패 시 `errors`에 기록.
## 관련 문서
- [skill_news_briefing_request](../02_skills/skill_news_briefing_request.md)

View File

@ -1,25 +1,70 @@
---
tags: [workflow, scheduler, healthcheck, monitoring]
type: workflow
last_updated: 2026-04-06
---
# scheduled_healthcheck_alert 워크플로우
## 목적
10분마다 rb8001의 health 엔드포인트를 확인하고, 실패 시 Slack 알림을 보낸다.
rb8001의 `/health` 엔드포인트를 통해 모든 외부 스킬 서비스와 인프라의 상태를 확인한다.
## 아키텍처
- **엔드포인트**: `GET /health` (`app/router/system_endpoint.py`)
- **라우터 인스턴스**: `RobeingRouter.get_service_status()` (`app/router/router.py`)
- **n8n 미사용**. rb8001이 FastAPI 엔드포인트로 직접 제공.
## 코드 SSOT
- `rb8001/app/router/system_endpoint.py``/health` 엔드포인트 정의
- `rb8001/app/router/router.py``get_service_status()` 구현
## 헬스체크 흐름
## 흐름
```
Schedule Trigger (*/10 * * * *) → Check rb8001 Health → Health Failed? → (true) Send Alert
→ (false) 종료
GET /health
→ system_endpoint.health_check()
→ router_instance.get_service_status()
→ registry.load_all() [SKILL.md 기반 스킬 목록]
→ 각 external_http 스킬: GET {skill_url}/health (timeout 5초)
→ GET {STATE_SERVICE_URL}/healthz (state 서비스)
→ brain 상태 확인 (decision_engine 초기화 여부)
→ 응답 조립
```
## 주요 노드
| 노드 | 설명 |
|---|---|
| Schedule Trigger | cron `*/10 * * * *` (매 10분) |
| Check rb8001 Health | `GET :8001/health` |
| Health Failed? | statusCode !== 200 분기 |
| Send Alert | Slack C_ALERTS 채널에 장애 알림 |
## 응답 형식
## 엔드포인트
- 아웃바운드: `GET http://192.168.219.52:8001/health`
- 아웃바운드: Slack `chat.postMessage` (채널: C_ALERTS)
```json
{
"status": "healthy",
"robeing_id": "{settings.ROBEING_ID}",
"services": {
"skill-news": true,
"skill-rag-file": true,
"skill-calendar": false,
"state": true,
"brain": true,
"brain_stats": { "..." : "..." }
},
"brain": "integrated",
"memory_store": "postgresql",
"memory_limit": "{settings.MAX_MEMORY_SIZE}MB"
}
```
## 스킬 상태 확인 방식
- `registry.load_all()`이 SKILL.md frontmatter에서 `runtime_kind=external_http`인 스킬 목록을 로드한다.
- 각 스킬의 `resolve_url()`로 URL을 해소한 뒤 `GET {url}/health`를 호출한다 (timeout 5초).
- HTTP 200이면 `true`, 그 외 또는 예외 시 `false`.
## 실패 분기
- `router_instance``None`이면 HTTP 503 반환.
- 개별 스킬 헬스체크 실패는 해당 스킬만 `false`로 표시하고, 전체 응답은 정상 반환.
- 전체 예외 발생 시 HTTP 503 + 상세 메시지 반환.
## 환경변수
- `STATE_SERVICE_URL` — state 서비스 URL (settings 경유).
- `ROBEING_ID` — 로빙 식별자 (settings 경유).
- `MAX_MEMORY_SIZE` — 메모리 한도 (settings 경유).
## 관련 문서
- [service_health_check](../05_admin/service_health_check.md)
- [service_health_check (레거시, _archive 이동됨)](../_archive/service_health_check.md)

View File

@ -1,37 +1,70 @@
---
tags: [workflow, admin, diary, scheduler]
type: workflow
last_updated: 2026-04-06
---
# diary_reflection_pipeline 워크플로우
## 목적
매일 새벽 2시에 전날의 로빙 일기를 자동 생성하고 Slack에 요약을 게시한다. 수동 트리거도 지원한다.
매일 오전 2시에 전날의 로빙 일기를 자동 생성하고, DB에 저장하며, DOCS 레포에 마크다운 파일로 커밋·푸시한다.
## 아키텍처
- **스케줄러**: APScheduler (DB 기반). `db_loader.py``scheduler_jobs` 테이블에서 `job_type=diary_generator` 잡을 로드하여 등록.
- **기본 스케줄**: `settings.DIARY_GENERATOR_SCHEDULE` = `0 2 * * *` (매일 02:00).
- **활성화 플래그**: `settings.DIARY_GENERATOR_ENABLED` (기본 `True`).
- **n8n 미사용**. systemd로 rb8001이 직접 실행.
## 코드 SSOT
- `rb8001/app/scheduler/jobs/diary_generator.py` — 잡 래퍼 + 일기 생성 로직
- `rb8001/app/services/diary/aggregator.py``DiaryAggregator` (데이터 집계)
- `rb8001/app/services/diary/generator.py``DiaryGenerator` (LLM 일기 생성)
- `rb8001/app/state/diary_repository.py``save_diary()` (DB 저장)
- `rb8001/app/router/diary_endpoint.py` — 일기 조회 API
- `rb8001/app/scheduler/db_loader.py` — DB 잡 로더 (`JOB_TYPE_MAP["diary_generator"]`)
## 흐름
```
Daily at 2AM ─┐
Manual Trigger ─┤→ Set Yesterday Date → Generate Diary (rb8001) → Get Diary Content → Post to Slack
APScheduler cron trigger (0 2 * * *)
→ db_loader가 등록한 guarded_job (schedule_policy 평가)
→ _run_diary_generator_with_logging()
→ _generate_diary()
1. draft_watcher 선행 실행 (non-blocking)
2. DiaryAggregator.aggregate(target_date, robeing_id) — 전날 활동 데이터 집계
3. DiaryGenerator.generate(target_date, robeing_id, aggregated_data) — LLM 일기 생성
4. save_diary() — DB 저장 (full_content, summary, dominant_emotion, stats)
5. _save_diary_to_git() — DOCS 레포에 마크다운 저장 + git add/commit/push
6. ActivityLogger.log_scheduler_job() — 스케줄러 작업 로그 기록
```
## 주요 노드
| 노드 | 설명 |
|---|---|
| Daily at 2AM | cron `0 2 * * *` |
| Manual Trigger | `POST /admin/diary/trigger` (수동 실행용) |
| Set Yesterday Date | 전날 날짜(YYYY-MM-DD) 계산 |
| Generate Diary (rb8001) | `POST :8001/api/diary/generate` (timeout 300초) |
| Get Diary Content | `GET :8001/api/diary/{date}?robeing_id=rb8001` |
| Post to Slack | 일기 요약 + 감정 + 대시보드 링크를 Slack에 게시 |
## 일기 조회 API (현행)
## 엔드포인트
- 인바운드: `POST /admin/diary/trigger` (n8n webhook, 수동)
- 아웃바운드: `POST http://192.168.219.52:8001/api/diary/generate`
- 아웃바운드: `GET http://192.168.219.52:8001/api/diary/{date}`
- 아웃바운드: Slack `chat.postMessage`
| 메서드 | 경로 | 설명 |
|--------|------|------|
| `GET` | `/api/diary/{target_date}?robeing_id=rb8001` | 특정 날짜 일기 조회 |
| `GET` | `/api/diary/list?robeing_id=rb8001&limit=100` | 일기 목록 조회 |
## 260319 변경 영향
**주의**: `/api/diary/generate` 엔드포인트는 코드에 존재하지 않는다. 일기 생성은 APScheduler 잡이 내부적으로 `DiaryGenerator.generate()`를 직접 호출한다.
이 워크플로우는 rb8001의 `/api/diary/generate`를 호출한다. 내부적으로 `llm_service.process_request()``task_type=chat`, `context={}`로 실행되므로:
## 일기 저장 경로
- **DB**: `diary` 테이블 (`date`, `robeing_id`, `full_content`, `summary`, `dominant_emotion`, `stats`)
- **Git**: `$DOCS_REPO_PATH/book/700_for_robeing/diary/{YYYY-MM-DD}.md`
- `DOCS_REPO_PATH` 환경변수, 기본값: `$WORKSPACE_ROOT/robeing/DOCS`
- **프롬프트 DB v3 주입: 적용됨** -- DB `prompt_versions` 활성 프롬프트가 일기 생성 시 system prompt로 주입된다.
- **neutral 감정 constraints 생략: 적용됨** -- 일기 생성 시 감정이 neutral이면 감정 constraints가 생략된다.
- 이전(~260318)에는 하드코딩 프롬프트 + 모든 감정에 constraints 주입이었으므로, 일기 톤에 변화가 있을 수 있다.
## 선행 작업
- 일기 생성 전 `draft_watcher`를 실행하여 NAS 드래프트 변경사항을 activity_log에 기록한다 (실패 시 non-blocking).
## 실패 분기
- `DiaryGenerator.generate()``None`을 반환하면 `RuntimeError` 발생.
- DB 저장 실패 시 에러 로그 + ActivityLogger에 `status=error` 기록.
- Git push 실패 시 에러 로그만 남기고 계속 진행 (non-blocking).
## 환경변수
- `DIARY_GENERATOR_ENABLED` — 일기 생성 활성화 여부 (settings 경유, 기본 `True`).
- `DIARY_GENERATOR_SCHEDULE` — cron 표현식 (settings 경유, 기본 `0 2 * * *`).
- `DOCS_REPO_PATH` — DOCS 레포 경로 (환경변수, 기본 `$WORKSPACE_ROOT/robeing/DOCS`).
- `ROBEING_ID` — 로빙 식별자 (settings 경유).
## 관련 문서
- [service_health_check](./service_health_check.md)
- [service_health_check (레거시, _archive 이동됨)](../_archive/service_health_check.md)