# 동남아 스타트업 뉴스 아침 브리핑

**작성일**: 2026-01-29  
**수정일**: 2026-01-30 (하이브리드 선별 보강, Phase 1~4 구현 완료)  
**작성자**: happybell80  
**관련**: `rb8001`, `skill-news`, `daily_headlines`, `560_컴퍼니엑스_개요.md`  
**이전 아이디어**: `journey/ideas/260125_동남아_스타트업_뉴스.md`  
**구현 상세**: → troubleshooting/260130_sea_headlines_implementation.md

---

## 1. UX 기대효과

- 매일 아침 뉴스 브리핑에 **「동남아 소식」** 섹션을 넣어, 컴퍼니엑스가 **동남아 기회를 탐색**하는 데 도움이 되는 뉴스를 **3개 꼭지**로 제공한다.
- 깡프로 헤드라인과 동일한 채널·동일한 아침 스케줄에 포함되며, 별도 채널·별도 job 없이 **기존 daily_headlines 메시지 안에** 섹션으로 삽입된다.

---

## 2. UI (깡프로 뉴스와 동일한 단순 형식)

- **동남아 소식** 섹션만 추가하고, 나머지 구조(오프닝, 출처, 클로징, 명언)는 깡프로 포맷 유지.
- 동남아 뉴스 3건: `01. <url|제목>`, `02. <url|제목>`, `03. <url|제목>` 한 줄씩. **제목은 한국어로 번역**하여 표기(원문 영문인 경우). **날짜 병기·출판사/신문사·부가 설명 없음**.
- 삽입 위치: **깡프로 헤드라인 목록 끝** → 빈 줄 2개 → `*동남아 소식*` → `01.` ~ `03.` → 빈 줄 2개 → `*오늘의 키워드*` (있으면) → 빈 줄 2개 → 클로징 → 명언.

**표시 예시** (24시간 이내 수집, 컴퍼니엑스 투자 분야 기준 선별):

```
*동남아 소식*

01. <url|싱가포르 Elev8.vc, AI·의료기술·로봇·첨단제조 딥테크 펀드 3천만 달러 마감>
02. <url|싱가포르 헬스테크 Mesh Bio, 동남아 확장용 시리즈 A 350만 달러 유치>
03. <url|TikTok, 태국 데이터센터에 38억 달러 투자 확정>
```

- **참고**: `skill_news/app/services/naver_startup_news_service.py` `format_startup_news_for_slack`, `rb8001/app/services/skills/startup_news_skill.py` `run_headlines_job`.

---

## 3. 수집·선별 상세

### 3.1 수집 (skill-news)

**신규 파일**: `skill_news/app/services/sea_news_collector.py`

**구현 방식**: `google_news_collector.py:55-91` 패턴 재사용
- Playwright로 Google News 검색
- URL 파라미터: `q={keyword}%20when%3A1d&hl=en&gl=SG` (24시간 필터)
- 키워드: `Singapore startup funding`, `Malaysia startup investment`, `Indonesia startup VC`, `SEA biotech healthcare`

**환경변수** (`.env`):
```
SEA_NEWS_KEYWORDS=Singapore startup funding,Malaysia startup investment,Indonesia startup VC,SEA biotech healthcare
SEA_NEWS_REGION=SG
SEA_NEWS_DAYS_BACK=1
```

**데이터 모델**: `news_models.py:6-26` `NewsArticle` 재사용

### 3.2 선별 (skill-news) — 하이브리드: 1차 키워드 → 2차 LLM 적합도

**신규 파일**: `skill_news/app/services/sea_news_filter.py` (1차 키워드 + 2차 LLM 호출), 필요 시 `sea_news_ranker.py` (LLM 전용, 311 원칙 한 파일 500줄 이하)

#### 3.2.1 1차: 키워드 필터

- **키워드 매칭**: 제목/본문에 기회·분야 키워드 **1개 이상 포함** 시 통과
  - 기회 키워드: `funding`, `investment`, `VC`, `accelerator`, `Series A/B`, `M&A`, `expansion`, `policy`
  - 분야 키워드: `biotech`, `healthcare`, `medical`, `manufacturing`, `e-commerce`
- **본문 검증**: `companyx_news_collector.py:396-413` `_remove_duplicates` 패턴 참고. 제목/본문 문자열에 키워드 리스트 `in` 검사.
- **후보 수**: 통과한 기사 중 **최대 10건** (다음 단계 LLM 입력). 10건 미만이면 전부 전달.

#### 3.2.2 2차: LLM 적합도 선정

- **입력**: 1차 통과 후보(최대 10건)의 제목·URL(또는 제목만). **컴액 컨텍스트** 문단(아래 참조)을 프롬프트에 포함.
- **컴액 컨텍스트**: `DOCS/book/500_business/560_컴퍼니엑스_개요.md` 2.1·2.2·뉴스 선별 컨텍스트 절 요약.  
  - skill_news는 DOCS를 직접 읽지 않음. **배포 시** `skill_news/app/data/companyx_context.txt` 또는 환경변수 `SEA_COMPANYX_CONTEXT`로 동기화(311: 민감 정보 하드코딩 금지, 설정은 .env/파일).
  - 요약 핵심: 시드·프리A 투자·육성·펀딩, TIPS·LIPS·오늘전통, 해외 기회 탐색 시 VC·액셀·펀딩·M&A·진출 뉴스가 도움 됨.
- **LLM 요구 응답**: 각 기사에 대해 **적합도 0~100**과 **한 줄 이유**. 응답은 JSON 배열 예: `[{"index":0,"score":85,"reason":"..."}, ...]`
- **선정**: 점수 내림차순 정렬 후 **상위 3건** 선택. 동점 시 입력 순서 유지.
- **폴백**: LLM 실패·타임아웃 시 1차 키워드 통과 건을 **매칭 키워드 개수** 기준 정렬해 상위 3건 사용 (graceful degradation).

#### 3.2.3 데이터·설정 원칙

- **컴액 컨텍스트**: 코드에 문단 하드코딩 금지. `companyx_context.txt` 또는 `SEA_COMPANYX_CONTEXT`에서 로드.
- **키워드 리스트**: 환경변수 `SEA_NEWS_OPPORTUNITY_KEYWORDS`(기회), `SEA_NEWS_FIELD_KEYWORDS`(분야) 권장. 기본값은 계획서 키워드.

### 3.3 제목 번역 (LLM)

**구현 위치**: `skill_news/app/services/sea_news_filter.py` (또는 `sea_news_ranker.py`에서 2차 호출 후 `sea_news_service.py`에서 번역)

**LLM 호출**: Gemini API 사용 (311 원칙: LLM 우선 접근)
- **입력**: 2차 적합도 선정으로 확정된 **3건**의 제목만.
- 프롬프트: "Translate the following news title to Korean. Return only the translated title without explanation: {title}"
- 3건 배치 처리 (단일 호출로 통합, 311 원칙 섹션 14)

### 3.4 포맷 및 중복 제거

**신규 파일**: `skill_news/app/services/sea_news_service.py`
- `format_sea_news_for_slack()`: `naver_startup_news_service.py:333-343` 패턴 참조
- 중복 제거: `companyx_news_collector.py:396-413` 패턴 (URL + 제목 기준)
- 이력 관리: `skill_news/data/sea_news_posting.json` (날짜, URL, 제목)

---

## 4. API 연동 (rb8001 ↔ skill-news)

### 4.1 skill-news 신규 엔드포인트

**파일**: `skill_news/app/router/news_endpoints.py`
**엔드포인트**: `POST /api/news/sea/headlines` (참조: `/api/news/naver/startup-headlines`)
**응답**: `{ "success": true, "count": 3, "items": [...], "text": "*동남아 소식*\n\n01. <url|제목>..." }`

### 4.2 rb8001 수정

| 파일 | 수정 내용 |
|------|----------|
| `skill_commands.py:223` | `fetch_sea_headlines()` 신규 추가 (`fetch_naver_headlines()` 패턴 복사) |
| `startup_news_skill.py:11-93` | `run_headlines_job()`에서 동남아 뉴스 호출 + 섹션 삽입 (명언 앞, `:67-76` 패턴) |

**에러 핸들링**: 동남아 수집 실패 시 `try-except`로 생략, 깡프로만 전송

---

## 5. Phase 및 필요 작업

| Phase | 필요 작업 |
|-------|-----------|
| **Phase 1** | **skill-news**: `sea_news_collector.py` 신규 생성. `google_news_collector.py:55-91` 패턴 복사, 키워드·지역(`hl=en&gl=SG`) 변경. `.env`에 `SEA_NEWS_*` 추가. |
| **Phase 2** | **skill-news**: `sea_news_filter.py`(1차 키워드 + 2차 LLM 적합도) + `sea_news_service.py` 신규 생성. 1차: 키워드 매칭·본문 검증 → 후보 최대 10건. 2차: 컴액 컨텍스트 + Gemini로 적합도 0~100·이유 JSON → 상위 3건. 폴백: LLM 실패 시 키워드 개수 기준 상위 3건. `app/data/companyx_context.txt` 또는 `SEA_COMPANYX_CONTEXT` 로드. 3건 확정 후 LLM 제목 번역(Gemini) + Slack 포맷. |
| **Phase 3** | **skill-news**: `news_endpoints.py`에 `POST /api/news/sea/headlines` 추가. 요청 body: `{}` 또는 `{"format":"json"}` (naver/startup-headlines와 동일). |
| **Phase 4** | **rb8001**: `skill_commands.py`에 `fetch_sea_headlines()` 추가. `startup_news_skill.py` `run_headlines_job()`에서 동남아 뉴스 호출 + 섹션 삽입 (명언 앞). |
| **Phase 5** | **배포**: skill-news 먼저 배포 (`git push`) → rb8001 배포 (`git push`). 순서 중요 (의존성). |

---

## 6. 테스트 계획

**315 원칙 준수**: pytest 자동 테스트, conftest.py fixtures 사용
**TDD 접근**: 테스트 먼저 작성(Red) → 구현(Green) → 리팩터 순서 유지 (AGENTS.md TDD 원칙)

| 테스트 | 파일 | 내용 |
|--------|------|------|
| 수집 단위 테스트 | `skill_news/tests/test_sea_news_collector.py` | 키워드 검색, 24시간 필터, NewsArticle 반환 확인 |
| 1차 키워드 필터 단위 | `skill_news/tests/test_sea_news_filter.py` | 기회·분야 키워드 1개 이상 포함 시 통과, 후보 최대 10건 |
| 2차 LLM 적합도 단위 | `skill_news/tests/test_sea_news_filter.py` 또는 `test_sea_news_ranker.py` | Gemini 모킹, 적합도 JSON 파싱, 상위 3건 반환 확인. 폴백: LLM 실패 시 키워드 개수 기준 3건 |
| 제목 번역 단위 | `skill_news/tests/test_sea_news_filter.py` | 3건 제목 LLM 번역 모킹 |
| 포맷 단위 테스트 | `skill_news/tests/test_sea_news_service.py` | `01. <url\|제목>` 형식 확인 |
| API 통합 테스트 | `skill_news/tests/test_sea_news_endpoint.py` | `POST /api/news/sea/headlines` 응답 확인 |
| 연동 E2E 테스트 | `rb8001/tests/e2e/test_sea_news_headlines.py` | 깡프로 + 동남아 섹션 통합 Slack 메시지 확인 |

**E2E 테스트 시나리오**:
1. `run_headlines_job()` 호출 → skill-news API 호출 → 동남아 섹션 삽입 → Slack 전송 확인
2. 동남아 수집 실패 시 → 깡프로만 전송되는지 확인 (graceful degradation)

**conftest.py fixtures**:
- `mock_playwright`: Playwright 모킹
- `mock_gemini`: Gemini API 모킹 (적합도 JSON 응답, 제목 번역 응답)
- `sample_sea_articles`: 테스트용 NewsArticle 5~10건 (1차 후보)
- `sample_companyx_context`: 컴액 컨텍스트 요약 문자열 (560 기반)

---

## 7. 로깅 전략

**311 원칙 섹션 11**: INFO(시작/종료), DEBUG(중간 과정)

| 단계 | 로그 레벨 | 메시지 예시 |
|------|----------|------------|
| 수집 시작 | INFO | `동남아 뉴스 수집 시작: keywords={keywords}` |
| 수집 완료 | INFO | `동남아 뉴스 수집 완료: {count}개` |
| 1차 키워드 통과 | DEBUG | `기회 키워드 매칭: {title} → 통과, 후보 {n}건` |
| 2차 LLM 적합도 | INFO | `LLM 적합도 선정 완료: 상위 3건 (scores=...)` |
| 2차 폴백 | WARNING | `LLM 적합도 실패, 키워드 개수 기준 상위 3건 사용` |
| 번역 | INFO | `제목 번역 완료: {count}건` |
| 실패 | WARNING | `동남아 뉴스 수집 실패 (생략): {error}` |

---

## 8. 주의사항 (트러블슈팅 교훈)

| 교훈 | 출처 | 적용 |
|------|------|------|
| 검색어 부분 매칭 → 무관 기사 수집 | `250916_claude_companyx_wrong_news.md` | 본문 검증 로직 필수 |
| OOM → 리소스 경합 | `250908_headline_failure_memory_error.md` | 깡프로(09:10) 이후 동남아 수집 (동일 job 내 순차 처리) |
| 좀비 프로세스 | `250904_admin_skill-news_zombie_process_gitea_actions.md` | docker-compose.yml `init: true` 확인 |
| 워크스페이스별 토큰 분리 | `250909_slack_briefing_failure.md` | `COMPANYX_SLACK_BOT_TOKEN` 사용 확인 |
| API 엔드포인트 확인 필수 | `250914_happybell80_깡프로뉴스_용어추출_기능추가.md` | skill-news API 스펙 먼저 확인 |

---

## 9. 참고 파일

| 파일 | 참조 내용 |
|------|----------|
| `skill_news/app/services/google_news_collector.py:55-91` | 뉴스 검색 패턴 |
| `skill_news/app/services/google_news_collector.py:100` | URL 파라미터 (when:Nd) |
| `skill_news/app/services/companyx_news_collector.py:396-413` | 중복 제거 패턴 |
| `skill_news/app/services/naver_startup_news_service.py:333-343` | Slack 포맷 (`01. <url\|제목>`) |
| `skill_news/app/router/news_endpoints.py` | 기존 `/api/news/naver/*` 엔드포인트 패턴 |
| `rb8001/app/commands/skill_commands.py:223-250` | `fetch_naver_headlines()` 패턴 |
| `rb8001/app/services/skills/startup_news_skill.py:11-93` | run_headlines_job 구조 |
| `rb8001/app/services/skills/startup_news_skill.py:67-76` | 섹션 삽입 로직 (명언 앞) |
| `DOCS/book/500_business/560_컴퍼니엑스_개요.md` | 컴퍼니엑스 컨텍스트(2.1·2.2·뉴스 선별). 2차 LLM 적합도 프롬프트용 요약문 동기화 원본. |
