# task-3024 — 신규설계 비교에 가입제안서·약관 PDF 업로드 배선

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


## ★★ 이것은 재개다 (2026-08-26 14:10 KST)
이전 세션은 **서버 OOM 으로 사망**했다(10:34 global oom-kill → cokacdir 서비스 전체 사망).
네 실수가 아니다. 아래 상태에서 **이어서** 하라.
```
브랜치  task/task-3024-dev3   (존재)
PR      #259 draft            (존재하나 main 대비 diff 0줄 — 착수 커밋만 있음)
실질 진척 0. 사실상 처음부터다.
```

### ★ 이번엔 반드시 지킬 것 — 오늘 실제로 사고가 났다
1. **worktree 를 써라.** 메인 저장소 `/home/jay/projects/InsuRo` 를 체크아웃 점유하지 마라.
   그 경로는 **cron 이 매일 06:00·08:00 에 직접 실행**한다.
   오늘 다른 팀이 메인 저장소를 작업 브랜치로 바꿔놔서 ANU 가 수동 복구했다.
2. **메모리를 아껴라.** 서버 RAM 15GB 이고 오늘 OOM 으로 봇 4팀이 전멸했다.
   대용량 파일 전체를 메모리에 올리지 말고, 검증용 PDF 도 소수로 하라.
3. **단계마다 커밋하라.** 세션이 죽어도 진척이 남아야 한다. 오늘 죽은 4팀 중 2팀은 커밋 0이었다.

## ★ 회장 지시 반복 횟수 — 이 건은 **네 번째**다
```
2026-08-20 00:33  "신규 설계는 회사별 가입제안서 pdf랑 약관을 셋트로 업로드하게 시킬거야"
2026-08-21 15:15  "신규 설계 가입제안서,약관은 어디에 업로드하게 되지??? UI 가 부족한 부분이 많구나!!!!"
2026-08-22 20:07  "신규설게 비교 입력 ui??? 어떻게 할꺼야?"
2026-08-26 09:12  "왜 JSON 으로 직접 입력만 받게 되어있지? ... pdf 로 업로드받아야지!!!"
```
엿새 동안 네 번 지시됐는데 코드가 한 줄도 붙지 않았다. **이번에는 끝내라.**
"

## ★ 회장 지적 (2026-08-26 verbatim 취지)
> "신규설계비교. 왜 JSON 으로 직접 입력만 받게 되어있지?
>  설계한 회사의 가입제안서+약관을 pdf 로 업로드받아야지!!"

**지적이 정확하다.** 현재 화면은 이렇다.
```tsx
// src/pages/NewDesignComparison.tsx
placeholder='{"analysis_table": {"columns": [...], "sections": [...]}, "policy_meta": [...]}'
existingAnalysis = JSON.parse(existingText)
newDesign       = JSON.parse(newDesignText)
```
**텍스트박스 두 개에 JSON 을 손으로 붙여넣는 화면**이다. 설계사가 쓸 수 있는 물건이 아니다.
비교 엔진은 완성돼 있는데 **입력 손잡이가 없다.** 이 태스크는 그 손잡이를 만든다.

## ★★ 새로 만들 것은 생각보다 적다 — 부품이 이미 다 있다
```
server/policy_extract/proposal_parser.py   pdfplumber 로 가입제안서 PDF 파싱 (17KB, 완성)
                                            → API 노출 0건. 어디에도 연결 안 됨 ★
server/routes/policy_terms_v1.py           약관 PDF 업로드 + job_id 폴링 (동작 중)
server/routes/new_design_comparison_v1.py  비교 API (동작 중)
증권분석 업로드 경로                        PDF·JPG·PNG (t3010, 오늘 배포)
```
**파서를 새로 짜지 마라.** `proposal_parser.py` 를 쓰고, 부족하면 그 파일을 보강하라.

### proposal_parser 출력 계약 (이미 확정돼 있다)
```python
ProposalExtract(insurer, product_name, rows[ProposalRow], total_premium,
                premium_reconciled, warnings)
ProposalRow(name, amount, premium, raw, parse_state, row_kind, parent_index)
```

## 만들 것

### ① 가입제안서 PDF 업로드 엔드포인트
`policy_terms_v1.py` 의 **업로드 + `generation_queue` 잡 + `job_id` 폴링 패턴을 그대로 따른다.**
- `UploadFile = File(...)` · 인증은 기존 FA 계정 의존성 재사용
- 파싱 결과를 **비교 API 가 받는 입력 형태로 변환**한다
- ★ 변환 지점이 이 태스크의 핵심이다. `ProposalExtract` → 비교 입력 스키마 매핑을
  **어떤 필드가 어디로 가는지 표로** 보고서에 남겨라

### ② 약관 PDF — 기존 경로 재사용 (중복 구현 금지)
약관 업로드는 `/api/insuro/policy-terms/upload` 로 **이미 동작한다.**
신규설계 비교 화면에서 그 경로를 **호출만** 하라. 새 업로드 엔드포인트를 또 만들지 마라.

### ③ 화면 — JSON 텍스트박스 제거
`src/pages/NewDesignComparison.tsx` 를 **파일 업로드 방식으로 바꾼다.**
- 기존 설계(증권) · 신규 설계(제안서) 각각 PDF 업로드
- 약관 PDF 는 선택 업로드
- 업로드 → 파싱 진행 표시 → 파싱 결과 확인 → 비교 실행
- ★ **파싱 결과를 사용자가 눈으로 확인하고 고칠 수 있어야 한다.** 파싱은 완벽하지 않다
  (`parse_state`·`warnings` 가 그래서 있다). 틀린 값을 조용히 비교에 넘기지 마라
- JSON 직접 입력은 **개발자용 폴백으로 남겨도 되나 기본 경로가 되어선 안 된다**

### ④ 실패를 실패라고 말할 것
- 파싱 실패·부분 실패(`parse_state`, `premium_reconciled=False`, `warnings`)를 **화면에 드러내라**
- ★ 2026-07~08 에 "저장됐어요" 문구를 띄우면서 실제로는 전송이 실패해 **4주간 아무도 모른** 사고가
  있었다. 성공 문구를 기본값으로 두지 마라

## ★ 하지 말 것
- 비교 엔진 로직 변경 (`policy_grouping/**` · 담보 매칭 · 실손 계산은 봉인 영역)
- 약관 업로드 엔드포인트 중복 생성
- `server/main.py` 대규모 수정 — 라우터는 **별도 파일**에 만들고 등록 1줄만 추가
- 증권분석 화면(`PolicyAnalysis.tsx`) 변경 — t3010 결과물이다
- 자격증명 값을 저장소에 커밋

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "server/routes/new_design_comparison_v1.py"
    - "server/routes/proposal_upload_v1.py"
    - "server/policy_extract/proposal_parser.py"
    - "server/policy_extract/**"
    - "server/tests/**"
    - "server/main.py"
    - "src/pages/NewDesignComparison.tsx"
    - "src/components/policy/**"
    - "src/**/__tests__/**"
    - "memory/reports/task-3024.md"
  forbidden_paths:
    - "server/routes/policy_terms_v1.py"
    - "server/policy_grouping/**"
    - "server/silson/**"
    - "src/pages/PolicyAnalysis.tsx"
    - "src/data/generateOptions.ts"
    - "extension/**"
    - ".github/workflows/**"
  commands: ["python3", "pytest", "npm", "npx", "bash", "gh"]
  merge_policy: "tiered"
  ttl_hours: 24
```
★ `server/main.py` 는 **라우터 등록 1줄만** 허용한다. 그 외 수정은 범위 위반이다.

## 검증 (실측 강제 — 실 PDF 로)
1. **실 PDF 파싱 1건 이상** — 실제 가입제안서 PDF 로 업로드→파싱→비교까지 **끝까지 통과**시켜라.
   추출된 담보 수·보험료 합계·`premium_reconciled` 값을 보고하라. 목업 금지
2. ★ **샘플 PDF 가 없으면 진행하지 말고 즉시 ANU 에 보고하라.** 회장이 보험사별 가입제안서
   샘플을 제공하기로 되어 있다. 없는 상태에서 목업으로 "완료" 선언하지 마라
3. **매핑표** — `ProposalExtract` 필드 → 비교 입력 스키마 대응을 표로
4. **실패 표면화** — 일부러 깨진 PDF(빈 파일·이미지 PDF·표 없는 PDF)를 넣어 **사용자에게 무엇이
   보이는지** 스크린샷으로 남겨라. "성공"으로 보이면 FAIL 이다
5. **회귀** — `pytest` · `vitest` **base 직접 재측정** 기준선
6. **봉인** — 파싱 실패를 성공으로 표시하는 변이를 만들어 테스트가 FAIL 하는지 확인·복원.
   변이가 no-op 이 아님을 `assert` 로 먼저 증명하라
7. **실 브라우저** — 업로드→비교 전 과정 스크린샷

## 완료 조건 (DoD)
**설계사가 가입제안서 PDF 를 올리면 담보가 파싱되어 화면에 보이고, 확인·수정 후 비교가 실행되며,
파싱 실패·불일치가 화면에 명확히 드러난다. JSON 직접 입력 없이 전 과정이 가능하다.**

## ★ 세션 조기 종료 대책
시작 즉시 브랜치 + 드래프트 PR 을 먼저 열어라. 단계마다 PR 본문에 진행을 갱신하라.
범위가 크다 — **①서버 업로드/파싱 → ②매핑 → ③화면** 순으로 하고 각 단계 끝에 커밋하라.
★ ANU 명세 전제가 틀렸다고 판단되면 구현하지 말고 **반증을 먼저 기록한 뒤 보고하라.**

## 운영 계약
- `origin/main`(=`14e9596`) 기준 `git pull --ff-only` 후 시작
- ★ `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` 금지.
- ★ 병렬 태스크 3건이 동시 진행 중이다(t3023 프론트 generate · t3025 네이버분석 · t3026 랭킹조사).
  `server/main.py` 는 라우터 등록 1줄만 건드려 충돌을 피하라

## 보고
**PR 생성까지가 범위다. 머지는 ANU 가 한다. 자동 머지 금지.**
`memory/reports/task-3024.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.
★ 봉투 첫 줄에 **"실 PDF 파싱→비교 전 과정 통과 여부 + 샘플 PDF 확보 여부"** 를 담아라.