# task-2918 보고서 — 소식지 보험사 자동감지 파일명 우선(본문 오검출 방지)

- 팀: dev1-team (헤르메스) / 담당 구현: 불칸(백엔드)
- 프로젝트: InsuRo (`/home/jay/projects/InsuRo`)
- 브랜치: `task/task-2918-dev1` (worktree `.worktrees/task-2918-dev1`, base=8a7239b=현 origin/main)
- 레벨: Lv.1 (surgical 1파일 + 테스트) / merge_policy: none → **머지·배포는 ANU**

## Situation
회장 실사용에서 소식지 업로드 시 보험사 선택이 틀리는 사례 발생. 파일명("DB생명 위드유매거진 26.08.pdf")에 발행사명이 있는데 자동감지가 다른 회사를 집음.

## Complication
`_detect_company(text)`(main.py:4959)가 **본문 텍스트만** 스캔해 `COMPANY_PATTERNS` 첫 매칭을 반환. 매거진 본문엔 여러 회사·상품명이 섞여 있어 **발행사가 아닌 회사명을 오검출**. 파일명은 감지에 전혀 안 쓰였음(회장이 "보험사명은 파일명에 있어 신뢰"라 한 것과 코드 동작의 갭).

## Question
파일명을 우선 근거로 삼아 발행사를 정확히 감지하되, 기존 감지/업로드 경로 회귀 0 + `COMPANY_PATTERNS` 내용 불변으로 구현할 수 있는가?

## Answer (구현 요약)
1. **`_detect_company` 시그니처 확장**(하위호환): `def _detect_company(text: str, filename: str = "") -> str`.
   - filename 있으면 **파일명(확장자 제거)에서 먼저 매칭 시도 → 매칭되면 그 회사 반환**.
   - 파일명에 매칭 없을 때만 기존 본문(text) 스캔으로 폴백.
   - 내부 매칭 로직을 `_match(source)` 헬퍼로 추출(중복 제거). COMPANY_PATTERNS 무변경.
2. **호출부 9곳 filename 배선**: 각 `_detect_company(X)` 호출을 바로 인접한 `_extract_title(X, F)`가 쓰던 것과 **동일한 F**(filename 또는 req.original_filename)로 전달. pdf_extract 텍스트레이어 경로(main.py:5801) 포함.
   - 줄번호(변경 후): 5452, 5477, 5510, 5532, 5554, 5675, 5801, 5898(req.original_filename), 6097.
   - filename 변수가 스코프에 없는 호출부는 없었음(전부 인접 `_extract_title`과 동일 변수 사용).
3. **유닛테스트 신규**: `server/tests/test_detect_company_filename.py` — 5 케이스(파일명우선/본문폴백/둘다없음/하위호환/빈본문).

## 생성·수정 파일
- 수정: `server/main.py` (+32 / -17, `_detect_company` 정의 + 호출부 9곳)
- 신규: `server/tests/test_detect_company_filename.py` (5 테스트)
- (비커밋·worktree-local) `.env` line44 `INSURO_EXTENSION_ORIGIN` 제거 — 아래 "발견 이슈" 참조. gitignored·일회성, 산출물 아님.

## 테스트 결과
- 신규 5 테스트: **5 passed**.
- 전체 `pytest server/tests/`: 원본 실행 시 **1269 passed, 1 failed**. 유일 실패 = `test_consultation_history_get.py::test_cors_fail_closed_when_ext_origin_unset`.
  - **회귀 아님(독립검증 완료)**: `git stash`로 우리 main.py 변경을 되돌린 base 상태에서도 동일 실패 재현. 우리 diff는 `_detect_company`만 건드리며 CORS와 무관(코드 인과상 영향 불가).
  - **근본원인 = worktree .env 오염**: worktree 복사본 `.env`에 `INSURO_EXTENSION_ORIGIN=chrome-extension://...`이 설정돼 있어, "미설정 시 fail-closed" 테스트가 실패. CI 클린 체크아웃(.env 없음)에선 통과. worktree-local .env에서 해당 라인 제거 시 → CORS 테스트 2개(`_unset`/`_when_set`) 모두 **2 passed**, 전체 suite **0 failed**로 CI parity 복원 확인.

## ★ L1 스모크테스트 결과
- 서버 재시작: 해당없음 (순수 함수 감지 로직 — HTTP 엔드포인트 아님, 실함수 직접 호출로 검증)
- API 응답 확인: 해당없음 (아래 실함수 호출로 대체)
- 실감지 검증(핵심): 실제 파일명 `"DB생명 위드유매거진 26.08.pdf"` + 본문에 다른 회사명(삼성생명/한화생명/교보생명) 섞음:
  - `_detect_company(body, filename)` → **`DB손해보험`** (파일명 우선 매칭 성공)
  - 구동작 `_detect_company(body)` (본문만) → **`한화손해보험`** ← 회장이 지적한 오검출 그대로 재현. **수정으로 해소됨.**
  - 폴백: 회사명 없는 파일명 + 본문 삼성생명 → `삼성화재` (정상 폴백)
  - 빈: 둘 다 회사명 없음 → `""` (정상)
  - 하위호환: `_detect_company(본문_삼성생명)` → `삼성화재` (기존 동작 보존)
- 스크린샷: 해당없음

## 발견 이슈 및 해결
1. **[해결] CORS 테스트 사전존재 실패** — 위 "테스트 결과" 참조. 회귀 아님을 base 재현으로 확정. worktree-local .env 오염 라인 제거로 CI parity 복원(비파괴, 산출물 무관).
2. **[범위 밖·플래그] "DB생명" → 반환값 "DB손해보험"** — `COMPANY_PATTERNS`에서 `"DB생명"`이 독립 키가 아니라 **`"DB손해보험"` 키의 패턴 값**으로 존재(main.py:4891 부근). 실제로 DB생명보험 ≠ DB손해보험(별개 회사)이라, "DB생명 위드유매거진" 소식지의 표시 회사가 `DB손해보험`으로 나옴.
   - 이번 task는 **감지 근거를 파일명 우선으로 바꾸는 것**이 목적이고 그건 정상 동작(파일명이 감지를 주도). 다만 회장의 최종 기대("보험사 선택이 맞게")를 완전히 충족하려면 **`COMPANY_PATTERNS`에 `"DB생명"` 독립 키 추가**가 필요.
   - task 제약이 "COMPANY_PATTERNS 내용 건드리지 말 것"이라 **본 task 범위 밖**. → ANU/회장 결정 필요 후속. (동일 계열: "삼성생명"도 `"삼성화재"` 키 아래 존재 — 생보/손보 계열 분리가 필요하면 별도 데이터 정비 task 권장.)

## 머지 판단
- **머지 필요**: Yes (ANU 소유 — merge_policy: none, 순서 B)
- **브랜치**: `task/task-2918-dev1` (원격 head 1efd686 == 로컬 head, push 확인 완료)
- **워크트리 경로**: `/home/jay/projects/InsuRo/.worktrees/task-2918-dev1`
- **머지 의견**: 수술적 1파일 + 테스트, 회귀 0(사전존재 CORS는 .env 오염·독립 확정), 신규 5테스트 통과. 인접 `_extract_title` 매핑과 대칭이라 충돌 위험 낮음. **머지 후 서버 재배포(systemd insuro-api) 시 회장에게 "DB생명 표시 이슈(발견 2번)" 함께 안내 권장.**

## 모델 사용 기록
- 불칸(백엔드): sonnet — main.py 수정 + 유닛테스트 (일반 로직 구현, 기본값)
- 헤르메스(팀장, Opus): 설계/매핑 지시/독립검증/L1/보고서 (직접 코딩 없음)

## 비고
- ANU 독립검증 권장: 클린 /tmp 체크아웃(.env 무오염)에서 `pytest server/tests/` 0-fail 확인 + 실 "DB생명 위드유매거진 26.08.pdf" 감지.

## 세션 통계
- 총 도구 호출: 0회

