# InfoKeyword: llm_promotional.py 파이프라인 연동

## 배경
`worker/analyzer/llm_promotional.py`에 `judge_promotional()` 함수가 완전히 구현되어 있으나,
`worker/pipeline/analyzer.py`의 `_step5_promotional()`에서 전혀 호출되지 않음.
현재는 규칙 기반(전화번호/주소/외부링크/첨부파일/이미지)만으로 홍보성 판정 중.
LLM 판정을 하이브리드로 결합하여 정확도 향상.

## 프로젝트 경로
- `/home/jay/projects/InfoKeyword/`

## 핵심 파일
1. `worker/analyzer/llm_promotional.py` — LLM 판정 함수 (기존 구현 완료, 수정 최소화)
2. `worker/pipeline/analyzer.py` — 7단계 파이프라인 오케스트레이터 (연동 대상)
3. `worker/config.py` — 설정 (새 설정 추가 필요)

## 작업 내용

### 1. config.py에 LLM 홍보성 분석 설정 추가
```python
# LLM 홍보성 분석 (하이브리드 모드)
LLM_PROMOTIONAL_ENABLED = os.getenv("LLM_PROMOTIONAL_ENABLED", "false").lower() == "true"
LLM_PROMOTIONAL_CONFIDENCE_THRESHOLD = float(os.getenv("LLM_PROMOTIONAL_CONFIDENCE_THRESHOLD", "0.7"))
```

### 2. analyzer.py의 `_analyze_single_blog()` 수정

**하이브리드 판정 로직**:
- 규칙 기반 분석을 먼저 실행 (기존 로직 유지)
- `LLM_PROMOTIONAL_ENABLED=true`일 때만 LLM 2차 검증 추가
- 규칙 기반에서 **경계선 케이스** (홍보성 시그널 1~2개만 감지)일 때 LLM이 최종 판정
- 규칙 기반에서 **명확한 케이스** (시그널 0개 또는 3개 이상)는 LLM 스킵 (비용 절감)

**경계선 판정 기준**:
```python
# 홍보성 시그널 목록
signals = []
if has_phone: signals.append("phone")
if has_address: signals.append("address")
if has_external_link: signals.append("external_link")
if has_attachment: signals.append("attachment")
if has_talktalk: signals.append("talktalk")
if has_place: signals.append("place")
if has_phone_in_image: signals.append("phone_in_image")
if has_address_in_image: signals.append("address_in_image")

# 경계선: 시그널 1~2개 → LLM 2차 검증
is_borderline = 1 <= len(signals) <= 2
```

**LLM 2차 검증 흐름**:
```python
if config.LLM_PROMOTIONAL_ENABLED and is_borderline and blog_text:
    llm_result = await asyncio.to_thread(judge_promotional, blog_text)
    if llm_result["confidence"] >= config.LLM_PROMOTIONAL_CONFIDENCE_THRESHOLD:
        is_promotional = llm_result["is_promotional"]
    # LLM 결과를 blog detail에 추가
    detail["llm_judgment"] = llm_result
```

### 3. `_step5_promotional()` 결과 dict에 LLM 필드 추가
- `llm_analyzed_count`: LLM으로 2차 검증한 블로그 수
- `llm_overridden_count`: LLM이 규칙 기반 판정을 뒤집은 수

### 4. 테스트 작성

`tests/test_llm_promotional_integration.py` 신규 생성:

- test_llm_disabled_no_call: LLM_PROMOTIONAL_ENABLED=false면 judge_promotional 호출 안 함
- test_llm_enabled_borderline_calls_llm: 경계선 케이스에서 LLM 호출 확인
- test_llm_enabled_clear_case_skips: 명확한 케이스에서 LLM 스킵 확인
- test_llm_override_result: LLM이 규칙 판정을 뒤집는 케이스
- test_llm_low_confidence_keeps_rule: LLM confidence 낮으면 규칙 결과 유지
- test_step5_result_includes_llm_fields: 결과 dict에 llm_analyzed_count 등 포함

mock 사용: `judge_promotional()` 함수는 Claude CLI를 호출하므로 반드시 mock 처리.

## QC 기준
- 기존 테스트 전체 통과 (깨지면 안 됨)
- 새 테스트 6개 이상 추가
- `LLM_PROMOTIONAL_ENABLED=false` (기본값)이면 기존 동작과 100% 동일해야 함
- pyright 타입 체크 0 errors
- config.py에 추가한 설정은 환경변수 기반 (하드코딩 금지)
