# ThreadAuto 뉴스 크롤링 → 인사이트 콘텐츠 파이프라인 설계서
**작업 ID**: task-1189.1 | **버전**: 1.0 | **작성일**: 2026-03-28
**작성자**: 헤르메스 (개발1팀장) | **검토**: 에이전트 미팅 3사이클 합의

---

## 1. 개요

### 목적
ThreadAuto의 콘텐츠 소스를 evergreen_topics.json 기반 상시 토픽에서 **실시간 보험 뉴스 기반 인사이트 콘텐츠**로 확장한다.

### 범위
크롤링 → 필터링 → 저장 → 토픽 선택 → 인사이트 생성 → 검증 → 발행 전체 파이프라인 설계.

### 뉴스 소스
1. **매일경제 보험 섹션**: https://www.mk.co.kr/news/financial/insurance (HTML 스크래핑)
2. **보험저널 보험산업 섹션**: https://www.insjournal.co.kr/news/articleList.html?sc_multi_code=S2&view_type=sm (HTML 스크래핑, 기존 하위 섹션 4개와 병행)

---

## 2. E2E 파이프라인 아키텍처

```
[크롤러 스케줄러 — 6시간 주기 (00/06/12/18 KST)]
    │
    ├─ news_crawler.py (requests+BS4)
    │   ├─ MKInsuranceFetcher (매일경제)
    │   ├─ InsJournalFetcher (보험저널 S2)
    │   └─ [Scrapling fallback — WAF 차단 시]
    │
    ▼ raw_articles (제목, URL, 발행일, 리드문 2문장)
    │
[필터링 파이프라인]
    │
    ├─ 1단: dedup_checker.py
    │   ├─ URL 기반 중복 제거
    │   └─ 제목 Jaccard 유사도 ≥ 0.85 중복 제거
    │
    ├─ 2단: rule_filter.py
    │   ├─ 기존 keyword_filter.py 확장 (보험 키워드 점수)
    │   └─ 특정 회사 페널티 키워드 (인사발령, 순이익, 영업이익 등)
    │
    ├─ 3단: llm_filter.py (보조)
    │   ├─ "업계 전반 vs 특정 회사" AI 분류
    │   └─ ai_category_hint 필드 저장 (참고용, 최종 판정권 없음)
    │
    ▼ filtered_articles (news_cache.json schema v2)
    │
[토픽 선택기 — topic_selector.py]
    │
    ├─ DAILY_MIX_V3 "업계동향" 6개 슬롯
    │   ├─ 뉴스 기반: 최대 4개 (가용 시)
    │   └─ evergreen: 최소 2개 (항상 보장)
    ├─ 도메인 다양성 점수 ≥ 0.5 (eTLD+1 기준)
    │
    ▼ selected_topics
    │
[인사이트 생성 — FiveStagePipeline (mode="news_insight")]
    │
    ├─ 0단: news_context_injector.py (전처리)
    │   ├─ 제목+리드문+출처URL → 프롬프트 컨텍스트 구성
    │   ├─ locked_fact 변수 격리 (수치 보호)
    │   └─ visual_hint 필드 포함
    │
    ├─ 1~5단: angle → structure → writing → hooking → review
    │   └─ mode에 따라 뉴스 인사이트 오버레이 프롬프트 적용
    │
    ├─ 검증: locked_fact 정규식 정합성 (수치 원형 보존 확인)
    ├─ 재시도: 최대 3회 → 실패 시 human review 큐
    │   └─ 30분 타임아웃 → evergreen 콘텐츠 대체
    │
    ▼ generated_content
    │
[검증 레이어]
    │
    ├─ compliance_filter.py
    │   ├─ Layer 1: 블랙리스트 키워드
    │   ├─ Layer 2: 수치 교차검증 (fact_db + 출처 URL)
    │   ├─ Layer 3: 스코어링
    │   └─ NEW: entity_role 분류 (SUBJECT → 드랍, CONTEXT → 익명화)
    │
    ├─ content_qa.py: 원문 유사도 ≤ 0.6 (difflib)
    │
    ▼ verified_content
    │
[발행]
    ├─ run_text_post.py (content_type="text_news_insight")
    ├─ run_card_post.py (content_type="cardnews" + 출처 슬라이드)
    └─ expires_at = published_at + 48h
```

---

## 3. 핵심 설계 결정

### 3.1 크롤링

| 항목 | 결정 | 근거 |
|------|------|------|
| 스택 | requests + BeautifulSoup4 | 기존 rss_fetcher.py와 동일 패턴, 최소 의존성 |
| Fallback | Scrapling (anti-bot 경량 라이브러리) | WAF 차단 시 단계적 전환 |
| 주기 | 6시간 (KST 00/06/12/18) | freshness와 IP 차단 방지 균형 |
| 수집 범위 | 제목 + URL + 발행일 + 리드문 2문장 | 저작권 리스크 최소화 |

### 3.2 필터링

**규칙 기반 1차 게이트 (결정론적, 감사 추적 가능)**
- 기존 keyword_filter.py의 3-tier 키워드 점수 체계 유지
- **추가: 특정 회사 페널티 키워드**
  - 인사발령, 순이익, 영업이익, 대표이사 취임, 실적 발표
  - 페널티 키워드 매칭 시 점수 -5점
  - 최종 점수 < 0 → 드랍 (특정 회사 전용 기사 판정)

**LLM 보조 2차 (비결정적, 참고용)**
- AI가 "업계 전반" vs "특정 회사 전용" 분류
- ai_category_hint 필드로 JSON 저장 (최종 pass/fail 권한 없음)
- 1차 게이트 경계값 기사에 한해 인간 검수자 참고 자료로 활용

**중복 제거 (필터링 전단)**
- URL 기반 + 제목 Jaccard 유사도 ≥ 0.85
- 보험저널 S2 전체 섹션과 기존 4개 하위 섹션 간 중복 방지

### 3.3 entity_role 분류 (compliance_filter 확장)

```
판단 규칙 (우선순위 순):
1. 기사 제목에 기업명 포함 → SUBJECT (기사의 주체)
2. 리드문 첫 문장 주어에 기업명 → SUBJECT
3. 기업명 언급 1회 이하 → CONTEXT (부수적 언급)
4. "~에 따르면", "~와 비교" 패턴 → CONTEXT
5. 기업 블랙리스트 매칭 → 무조건 SUBJECT (보수적 처리)
6. 나머지 → LLM 판단 (신뢰도 ≥ 0.85만 적용, 미달 → CONTEXT)

처리:
- SUBJECT → 기사 드랍 (특정 회사 전용)
- CONTEXT → 기업명 익명화 후 사용 가능
```

### 3.4 인사이트 생성

**파이프라인 구조**: FiveStagePipeline 단일 유지, mode="news_insight" 분기
- news_context_injector가 전처리 스텝으로 프롬프트 컨텍스트 구성
- locked_fact 변수로 뉴스 수치 격리 (LLM 수정 방지)
- writing 단계 후 정규식으로 locked_fact 보존 검증

**프롬프트 원칙** (아테나 설계):
1. 관련성 앵커링: "이것이 보험 소비자에게 의미하는 것은" 브리지 필수
2. 수치 맥락화: 단순 수치 인용 금지, 비교 기준 제시
3. 행동 유발 마무리: "지금 확인해야 할 것은" CTA
4. 원문 인용 금지, 금융 조언 표현 금지

**재생성 정책**: 3회 재시도 → human review 큐 → 30분 타임아웃 → evergreen 대체

### 3.5 저작권 보호

| 보호 수단 | 설명 |
|-----------|------|
| 수집 범위 제한 | 제목+URL+리드문 2문장만 (본문 전체 수집 금지) |
| 원문 유사도 체크 | difflib SequenceMatcher 0.6 이하 필수 |
| 출처 명시 | 카드뉴스 마지막 슬라이드에 출처 표기 |
| 파라프레이징 강제 | 프롬프트에 "원문 문장 직접 인용 금지" 명시 |

### 3.6 news_cache.json Schema v2

```json
{
  "schema_version": 2,
  "articles": [
    {
      "id": "news-mk-20260328-001",
      "url": "https://...",
      "title": "보험업계 디지털 전환 가속",
      "lead_text": "보험업계가... 최근 2문장",
      "published_at": "2026-03-28T09:00:00+09:00",
      "source_id": "mk_insurance",
      "source_domain": "mk.co.kr",
      "crawled_at": "2026-03-28T12:00:00+09:00",
      "keyword_score": 5,
      "matched_keywords": {"primary": [...], "secondary": [...], "tertiary": [...]},
      "company_penalty": -5,
      "final_score": 0,
      "entity_role": "CONTEXT",
      "ai_category_hint": "industry_wide",
      "filtered_reason": null,
      "expires_at": "2026-03-30T12:00:00+09:00"
    }
  ]
}
```

### 3.7 DAILY_MIX 통합

```python
DAILY_MIX_V3["업계동향"] = {
    "count": 6,
    "types": [
        ("text_news_insight", 2),  # 뉴스 인사이트 텍스트 (신규)
        ("cardnews", 2),            # 카드뉴스 (뉴스 or evergreen)
        ("video", 2),               # 비디오
    ],
}

# 토픽 배정 우선순위:
# 1. news_cache에서 가용 뉴스 토픽 (최대 4개)
# 2. evergreen 풀에서 보충 (최소 2개 보장)
# 도메인 다양성 점수 ≥ 0.5 체크 후 최종 배정
```

### 3.8 장애 시나리오 Fallback

| 장애 | Fallback |
|------|----------|
| 크롤링 전체 실패 | 캐시된 최근 24시간 기사 사용, 알림 발송 |
| LLM API 타임아웃 | rule_filter만으로 발행 (인사이트 미포함 뉴스 요약) |
| locked_fact 3회 실패 | human review → 30분 후 evergreen 대체 |
| 유사도 0.6 초과 | 발행 차단, 수동 검토 큐 이동 |
| DB 연결 실패 | 파일 기반 임시 저장 → 복구 시 재처리 |
| 뉴스 0건 가용 | evergreen 100% 발행 (기존 동작 유지) |

---

## 4. 리스크 매트릭스

| ID | 리스크 | 가능성 | 영향도 | 등급 | 대응 |
|----|--------|--------|--------|------|------|
| R01 | 저작권 침해 | M | H | Critical | 유사도 0.6 체크 + 수집 범위 제한 |
| R02 | 금감원 오정보 | M | H | Critical | locked_fact + human review |
| R03 | 크롤러 차단 | H | M | High | Scrapling fallback + UA 로테이션 |
| R04 | 프롬프트 인젝션 | L | H | High | 입력 sanitization + 포맷 검증 |
| R05 | 개인정보 노출 | L | H | High | PII 정규식 + SUBJECT 분류 |
| R06 | 도메인 편중 | M | M | Medium | eTLD+1 다양성 0.5 |
| R07 | TTL 재생성 루프 | L | H | Medium | circuit breaker 5회/24h |
| R08 | QR 피싱 URL | L | M | Medium | URL 화이트리스트 |
| R09 | LLM API 중단 | M | H | Medium | rule_filter only 모드 |
| R10 | entity_role 오분류 | M | M | Medium | 블랙리스트 월 1회 |

---

## 5. 기존 코드 수정 범위

### 수정 필요 파일 (ThreadAuto 프로젝트 내)
1. `content/topic_selector.py` — DAILY_MIX_V3 업데이트, select_trend_topics() 확장
2. `content/five_stage_pipeline.py` — mode="news_insight" 분기 추가
3. `content/fact_guard.py` — 뉴스 인용 수치 출처 URL 기반 별도 검증 경로
4. `content/compliance_filter.py` — entity_role 분류 로직 추가
5. `crawler/rss_fetcher.py` — 매일경제 HTML 파서 추가
6. `crawler/keyword_filter.py` — 특정 회사 페널티 키워드 추가
7. `run_text_post.py` — text_news_insight content_type 지원
8. `run_card_post.py` — text_news_insight 지원 + 출처 슬라이드

### 신규 파일 (예상)
1. `crawler/news_crawler.py` — 통합 뉴스 크롤러
2. `crawler/scrapling_adapter.py` — Scrapling fallback 래퍼
3. `crawler/dedup_checker.py` — 제목 유사도 중복 제거
4. `content/news_context_injector.py` — FiveStagePipeline 전처리
5. `content/locked_fact_manager.py` — 수치 격리 및 정합성 검증
6. `content/domain_diversity_scorer.py` — eTLD+1 다양성 점수
7. `content/content_qa.py` — 원문 유사도 체크
8. `renderer/card_source_slide.py` — 출처 표기 슬라이드 렌더러
9. `constants.py` — ContentType Enum, EntityRole Enum
10. `prompts/news_insight_v1.txt` — 뉴스 인사이트 프롬프트
11. `data/entity_blacklist.json` — 기업 블랙리스트 (초기 50개)
12. `scheduler/news_scheduler.py` — 6시간 크롤링 + 30분 TTL 체크
13. `monitor/metrics_collector.py` — 모니터링 메트릭 수집
14. `monitor/alert_dispatcher.py` — 장애 알림 발송
