# task-3037 — 네이버 상위노출 분석 회장 피드백 4건 반영

**레벨**: Lv.2 · **팀**: dev3-team · **회장 승인 완료** (2026-08-27)
**저장소**: `Jeon-Jonghyuk/InsuRo` (`/home/jay/projects/InsuRo`, 기본 브랜치 **main**)

## 배경
task-3025 로 네이버 블로그·카페 상위노출 분석을 배포했다(main `2f6f48e`, 라이브 반영 완료).
**회장이 직접 테스트하고 피드백 4건을 줬다.** 이 태스크는 그 4건을 반영한다.

기존 구현 위치:
```
server/naver_benchmark/collector.py   검색결과 수집(순위·광고판별)
server/naver_benchmark/analyzer.py    본문 분석(글자수·이미지수·키워드수)
server/naver_benchmark/constants.py
server/routes/naver_benchmark_v1.py   POST /api/v1/naver-benchmark/analyze
src/components/keyword/NaverBenchmarkPanel.tsx   화면
```

---

## ① 키워드 카운팅을 **제목 / 본문 분리**해서 제시

> 회장: *"키워드 카운팅은 제목과 본문 나눠서 각각 알려줘야 함"*

**현재**: `_keyword_count(text, keyword)` 가 **본문만** 센다. 제목은 세지 않는다.

**바꿀 것**: 제목 키워드 수 · 본문 키워드 수를 **각각** 산출해 화면에 따로 보여준다.
- 합계만 보여주지 마라. 회장이 요구한 것은 **분리**다
- 제목은 이미 `PostItem.title` 로 수집돼 있으니 본문 크롤링 없이 셀 수 있다

---

## ② 이미지 개수를 **검색결과 리스트의 배지 숫자**와 일치시킨다 ★ 가장 중요

> 회장: *"이미지 갯수는 본문에 있는 이미지 갯수여야 해. 참고로, 블로그 글 내부에 들어가지
> 않더라도 **블로그 탭 리스트에 나타나는 글들의 이미지 제일 오른쪽에 있는 숫자**가 이미지
> 갯수야. 이 숫자랑 같게 카운팅이 되어야 함"*

**회장 제공 화면 근거**: 네이버 블로그탭 검색결과에서 각 글의 썸네일 묶음 **맨 오른쪽 이미지
우하단에 원형 배지 숫자**가 붙는다. 회장 스크린샷에서 실제로 관측된 값: **5 · 8 · 3 · 4**.

**현재**: `analyzer._count_images_in_scope()` 가 **본문 HTML 의 `<img>` + SE3 `data-linkdata`**
를 파싱해 센다. 이 값이 배지 숫자와 **일치하지 않는다**.

**바꿀 것**:
- **검색결과 리스트에서 배지 숫자를 직접 파싱**해 이미지 개수로 쓴다
- ★ 이렇게 하면 **본문을 크롤링하지 않아도 되어 훨씬 빠르다**(현재 10건 본문 수집에 2~3분 소요)
- ★ **먼저 실제 네이버 HTML 을 받아 배지 숫자가 어느 요소에 있는지 확인하라.** 추측 금지
- 배지가 없는 글(썸네일 1장 이하 등)의 처리 규칙을 정하고 근거를 남겨라
- 본문 파싱 방식과 배지 방식의 값을 **몇 건 대조해 표로** 제시하라. 왜 다른지 설명이 필요하다
- 배지 파싱이 실패하면 **조용히 0 으로 두지 말고** 그 사실이 드러나게 하라

---

## ③ 근거 글 목록 = 광고 제외 Top 10 인지 더블체크 + **순위 표기 정정**

> 회장: *"근거 글 목록에는 광고를 제외한 Top 10 이 맞는지 조건 한번만 더블체크 해줘"*

**ANU 선확인**: 광고 제외는 **Top10 산정 전**에 이뤄지고 있다(`_is_ad_blog_item` → `continue`
→ 비광고만 `items` 에 append → `len(items) >= top_n` 에서 break). 이 부분은 정상이다.

**그런데 순위 표기에 문제가 있다**:
```python
for pos, item in enumerate(raw_items, start=1):
    if _is_ad_blog_item(item): ad_excluded += 1; continue
    items.append(PostItem(rank=pos, ...))   # ← pos 는 광고 포함 원본 위치
```
`rank` 가 **광고를 포함한 원본 위치**라, 회장 화면에서 1위 글이 `rank=4` 로 보였다.
- **광고 제외 후 실제 순위**(1,2,3…)를 제공하라
- 원본 위치도 보존하고 싶으면 **필드를 나눠라**(예: `rank` = 광고 제외 순위, `raw_position` = 원본)
- 화면에는 **광고 제외 순위**를 쓴다. 1위 글이 1위로 보여야 한다
- 카페탭도 동일하게 점검하라

---

## ④ 평균·중앙값 제거 → **Top 3 기준 산출**로 변경

> 회장: *"10건의 글자수, 이미지수, 키워드 개수 목표의 평균 or 중간값은 알 필요 없음.
> 왜냐면 우리는 **1위 글만 벤치마킹** 할 거기 때문이야. 만약 좀 더 의미있는 data 분석을
> 하기 원한다면 지금처럼 **리스트는 top10 개 보여주지만 숫자는 top3 에 해당하는 것만**
> 계산하면 좋겠음 (**Top3 라고 언급도 되어야겠지**)"*

**현재**: `Stats(median, min, max, p25, p75)` 를 10건 전체로 산출한다.

**바꿀 것**:
- **근거 글 목록은 Top 10 유지** (광고 제외)
- **수치 산출은 Top 3 만** 사용한다
- 화면에 **"Top 3 기준"임이 명시**되어야 한다. 회장이 명시적으로 요구했다
- p25/p75 같은 분포 통계는 표본 3건에서 의미가 없다. **제거하거나 단순화**하라.
  무엇을 남길지 판단하고 근거를 적어라 (1위값·Top3 범위 정도가 자연스럽다)
- ★ 이전 ANU 지시("평균 대신 median")는 **회장 지시로 대체된다.** 회장 지시가 우선이다

---

## ★ 하지 말 것
- 광고 포함 금지 (제외 유지)
- 이미지 **생성** 금지 — 개수만
- 유료 플랜 게이팅 완화 금지 (`require_plan("베이직")` 서버 강제 유지)
- `fastapi<0.137` / `beautifulsoup4` 핀 제거 금지 (CI 환경 일치용, task-3025 결과물)
- `server/main.py` 는 손대지 마라 (라우터 이미 등록됨)
- 다른 키워드 분석 탭·트렌드 파이프라인 변경 금지

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "server/naver_benchmark/**"
    - "server/routes/naver_benchmark_v1.py"
    - "server/tests/**"
    - "src/components/keyword/NaverBenchmarkPanel.tsx"
    - "src/components/keyword/__tests__/**"
    - "memory/reports/task-3037.md"
  forbidden_paths:
    - "server/main.py"
    - "server/requirements.txt"
    - "server/scripts/**"
    - "src/data/generateOptions.ts"
    - "src/pages/PolicyAnalysis.tsx"
    - ".github/workflows/**"
  commands: ["python3", "pytest", "npm", "npx", "bash", "gh"]
  merge_policy: "tiered"
  ttl_hours: 24
```

## 검증 (실측 강제)
1. **실 키워드 2건 이상** 실행 — 블로그·카페 각각. 회장 스크린샷과 같은 조건으로
   **"대장암 초기증상"** 을 포함하라(배지 5·8·3·4 가 관측된 키워드다)
2. **★ 배지 숫자 대조표** — 실제 네이버 화면의 배지 숫자와 우리가 산출한 이미지 개수를
   **글별로 나란히** 제시하라. 일치해야 한다. 불일치가 있으면 사유를 적어라
3. **제목/본문 키워드 분리** — 각 글의 제목 카운트·본문 카운트를 실제 숫자로
4. **순위 검증** — 1위 글이 `rank=1` 로 나오는지. 광고 제외 건수도 함께
5. **Top3 산출** — 수치가 Top3 기준으로만 계산되는지, 화면에 "Top 3" 표기가 있는지
6. **회귀** — `pytest` · `vitest` **base 재측정 기준선**
7. **봉인** — Top10 전체로 계산하게 되돌리는 변이, 광고를 포함시키는 변이를 만들어
   테스트가 FAIL 하는지 확인·복원. 변이가 no-op 이 아님을 `assert` 로 먼저 증명하라
8. **실 브라우저** — 결과 화면 스크린샷 (Top3 표기·제목/본문 분리·순위 확인 가능하게)

## 완료 조건 (DoD)
**이미지 개수가 네이버 검색결과 배지 숫자와 일치하고, 키워드 수가 제목·본문으로 분리 제시되며,
근거 글은 광고 제외 Top10 이 1위부터 표기되고, 수치는 Top3 기준으로만 산출되어
화면에 "Top 3" 임이 명시된다.**

## ★ 세션 조기 종료 대책
시작 즉시 브랜치 + 드래프트 PR 을 먼저 열어라. **단계마다 커밋하라.**
검증에서 오래 붙잡히지 마라 — 본문 크롤링이 느리다(②를 먼저 하면 빨라진다).
★ ANU 명세 전제가 틀렸다고 판단되면 구현하지 말고 **반증을 먼저 기록한 뒤 보고하라.**

## 운영 계약
- `origin/main`(=`2f6f48e`) 기준 `git pull --ff-only` 후 시작
- ★ **worktree 를 써라.** 메인 저장소는 cron 이 매일 06:00·08:00 에 직접 실행한다
- ★ `gh` 호출 시 `GH_TOKEN="$BOT_GITHUB_TOKEN"` 주입 필수 (회장 개인 PAT 금지 — 감사기록 오염)
- 워크플로우 `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md` · QC `/home/jay/workspace/teams/shared/QC-RULES.md`
- `WORKSPACE_ROOT=/home/jay/workspace` · `CHAT_ID=6937032012` · 수집자 key `ANU_KEY=c119085addb0f8b7`
- 완료 경로는 `finish-task.sh` 실행이 유일하다. 수동 `.done` 금지.

## 보고
**PR 생성까지가 범위다. 머지는 ANU 가 한다. 자동 머지 금지.**
`memory/reports/task-3037.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.
★ 봉투 첫 줄에 **"배지 숫자 일치 여부 + Top3 전환 완료 여부"** 를 담아라.