# task-146.1 보고서: 약관 PDF 업로드 아키텍처 미팅 결과

**작성일:** 2026-03-03
**팀장:** 오딘 (개발2팀장)
**팀원:** 토르(백엔드), 프레이야(프론트엔드), 미미르(UX/UI), 헤임달(테스터)
**작업 유형:** 아키텍처 분석 미팅 (코딩 없음)

---

## 1. 작업 개요

약관 PDF 업로드 파이프라인의 현재 아키텍처를 팀원 전원이 백엔드/프론트엔드/UX/QA 4개 관점에서 다각도로 분석하고, 개선 방안을 도출하는 아키텍처 미팅을 수행함.

**분석 대상 핵심 파일:**
- `nextapp/src/app/api/admin/drive-upload/route.ts` — 업로드 API
- `functions/src/pdfIndexing.ts` — PDF 인덱싱 Cloud Function
- `nextapp/src/app/admin/upload/page.tsx` — 업로드 UI
- `nextapp/src/app/admin/terms/client.tsx` — 약관 관리 UI
- `firestore.rules` — 보안 규칙
- `nextapp/src/lib/googleDrive.ts` — Google Drive API 래퍼

---

## 2. 발견 사항 요약

### 총 발견 항목: 43건

- **Critical (즉시 수정):** 6건
- **High (1~2주 내):** 12건
- **Medium (1~2개월 내):** 16건
- **Low (장기):** 9건

---

## 3. Critical 발견 사항 (즉시 수정 필요)

### C-1. insurance_metadata docId 이중 prefix 버그
- **발견자:** 토르(백엔드) + 헤임달(테스터)
- **위치:** `pdfIndexing.ts` L277 vs `route.ts` L128
- **문제:** `route.ts`는 `docId = productId` (= `companyId_상품명_날짜`), `pdfIndexing.ts`는 `metaDocId = companyId_productId` (= `companyId_companyId_상품명_날짜`). 두 코드가 서로 다른 문서를 업데이트하여 **metadata가 2개 생성되는 심각 버그**.
- **영향:** 인덱싱 결과와 업로드 메타데이터가 분리되어 데이터 정합성 파괴.

### C-2. 파일 검증 완전 부재 (Magic Bytes / Content-Type / 크기)
- **발견자:** 헤임달(테스터)
- **위치:** `route.ts` L90~L121
- **문제:** PDF Magic Bytes 검증, Content-Type 재확인, 파일 크기 제한이 모두 없음. `.exe`나 악성 파일을 `.pdf`로 위장해 업로드 가능.

### C-3. 동시 업로드 Race Condition → 중복 Jobs/Chunks 생성
- **발견자:** 토르(백엔드) + 헤임달(테스터)
- **위치:** `route.ts` L107~L159, `pdfIndexing.ts` L220~L273
- **문제:** 동일 파일 동시 업로드 시 두 jobs가 생성되어 두 Cloud Function이 동시 실행, chunks 중복/혼재.

### C-4. 재인덱싱 시 초과 청크 미삭제 → 인덱스 오염
- **발견자:** 토르(백엔드) + 헤임달(테스터)
- **위치:** `pdfIndexing.ts` L220~L273
- **문제:** 재업로드 시 이전 청크 수 > 새 청크 수이면 고아 청크가 잔류. RAG 검색 결과에 구버전 내용이 섞임.

### C-5. 카테고리 자동 분류 키워드 누락 ("손해" 미포함)
- **발견자:** 토르(백엔드) + 미미르(UX)
- **위치:** `route.ts` L14 `getCategoryFromCompany()`
- **문제:** `NON_LIFE_KEYWORDS`에 '손해' 키워드 없음. `KB손해보험`, `롯데손해보험` 등이 생명보험으로 잘못 분류됨. 변액보험 카테고리도 지원 불가.

### C-6. 업로드 후 인덱싱 상태 추적 UI 완전 부재
- **발견자:** 프레이야(프론트엔드) + 미미르(UX)
- **위치:** `upload/page.tsx`
- **문제:** API가 `jobId`를 반환하지만 프론트엔드가 완전히 무시. 관리자가 인덱싱 성공/실패 여부를 알 방법이 없음. `/admin/terms`도 사이드바 네비게이션에 없어 접근이 어려움.

---

## 4. High 발견 사항 (1~2주 내 수정)

### H-1. YYMM 월 유효성 검사 없음
- `route.ts` L39: `yymm=2413` 입력 시 month=13으로 잘못된 날짜 저장

### H-2. updatedAt 필드 타입 불일치
- `route.ts`는 ISO 문자열, `pdfIndexing.ts`는 Firestore Timestamp 사용 → 정렬 쿼리 불일치

### H-3. 임베딩 배치 실패 시 전체 Job 실패 (지수 백오프/retry 없음)
- `Promise.all()` 사용으로 1개 실패 시 전체 배치 실패

### H-4. Admin 판별 이중 구조 (환경변수 vs Firestore)
- API Route는 `ADMIN_EMAILS` 하드코딩, Firestore Rules는 `users.role` 기반 → 불일치 위험

### H-5. Drive 업로드 성공 + Firestore 실패 시 고아 파일 생성
- 보상 트랜잭션(rollback) 없음

### H-6. findFolder 에러 시 null 반환 → 중복 폴더 생성
- `googleDrive.ts` L84~87에서 에러를 삼키고 null 반환

### H-7. Rate Limiting 완전 부재
- 업로드 API에 요청 횟수 제한 없음

### H-8. 파일 크기 제한 없음 + 0바이트 PDF 처리 미정의

### H-9. Cloud Function 임시 파일 cleanup finally 블록 없음
- 실패 시 `/tmp`에 파일 잔류

### H-10. 스캔 PDF 감지 로직 없음
- 텍스트 추출 결과가 비어있어도 complete로 처리

### H-11. 실패 파일 개별 재시도 버튼 없음
- 일부 실패 시 전체를 다시 업로드해야 함

### H-12. 재업로드 시 진행 중 인덱싱과 충돌 방지 없음

---

## 5. 최적안 도출 — 우선 실행 로드맵

### Phase 1: 즉시 수정 (1주 내) — 데이터 정합성/보안

| # | 작업 | 담당 | 복잡도 |
|---|------|------|--------|
| 1 | metadata docId 통일 (pdfIndexing.ts 수정) | 백엔드 | Easy |
| 2 | 파일 검증 추가 (Magic Bytes + 크기 제한 50MB) | 백엔드 | Easy |
| 3 | 카테고리 키워드에 '손해' 추가 + 변액보험 지원 | 백엔드 | Easy |
| 4 | YYMM 월 유효성 검사 추가 | 백엔드 | Easy |
| 5 | updatedAt 타입 Timestamp로 통일 | 백엔드 | Easy |
| 6 | 임시 파일 cleanup finally 블록 추가 | 백엔드 | Easy |
| 7 | `/admin/terms` 사이드바 네비게이션 추가 | 프론트 | Easy |

### Phase 2: 안정성 강화 (2주 내) — 동시성/복구

| # | 작업 | 담당 | 복잡도 |
|---|------|------|--------|
| 8 | 재인덱싱 시 기존 chunks 삭제 후 재생성 | 백엔드 | Medium |
| 9 | 임베딩 지수 백오프 + 개별 retry 구현 | 백엔드 | Medium |
| 10 | 동시 업로드 방지 (productId 기반 분산 락) | 백엔드 | Medium |
| 11 | Drive 업로드 실패 시 rollback 로직 | 백엔드 | Medium |
| 12 | 업로드 후 인덱싱 Job 상태 추적 UI 추가 | 프론트 | Medium |
| 13 | 스캔 PDF 감지 + 조기 실패 처리 | 백엔드 | Easy |

### Phase 3: UX 개선 (1개월 내) — 관리자 경험

| # | 작업 | 담당 | 복잡도 |
|---|------|------|--------|
| 14 | 실시간 파싱 미리보기 (파일명 → 메타데이터) | 프론트 | Easy |
| 15 | 약관 목록 실시간 리스너 (onSnapshot) 적용 | 프론트 | Medium |
| 16 | 카테고리 수동 선택/오버라이드 UI | 프론트+UX | Medium |
| 17 | 약관 통계 대시보드 추가 | 프론트+UX | Easy |
| 18 | 인덱싱 실패 재시도 UI | 프론트 | Medium |

### Phase 4: 장기 아키텍처 (2개월+) — 확장성

| # | 작업 | 담당 | 복잡도 |
|---|------|------|--------|
| 19 | 청킹 Overlap 추가 (RAG 품질 향상) | 백엔드 | Medium |
| 20 | 회사명 정규화 테이블 (Firestore 관리) | 백엔드 | Medium |
| 21 | 대용량 PDF 분할 처리 | 백엔드 | Hard |
| 22 | OCR 파이프라인 (스캔 PDF 지원) | 백엔드 | Hard |
| 23 | Firebase Storage 마이그레이션 검토 | 백엔드 | Hard |
| 24 | 약관 버전 히스토리 관리 | 백엔드+프론트 | Hard |

---

## 6. 검토한 대안과 기각 사유

### 대안 1: Google Drive → Firebase Storage 즉시 전환
- **기각 사유:** 영향 범위가 넓음 (route.ts, pdfIndexing.ts, ragQuery.ts, 기존 Firestore 데이터 마이그레이션 필요). 현재 Drive 구조가 관리자 직접 확인 가능한 장점이 있어 즉시 전환 대비 리스크가 큼. Phase 4로 검토 유지.

### 대안 2: 파일명 규칙 폐지 → 메타데이터 폼 입력 방식 전환
- **기각 사유:** 현재 Google Drive 폴더 경로가 파일명 기반으로 자동 생성되는 아키텍처 제약. 단기적으로는 파싱 미리보기 + 카테고리 수동 선택으로 보완하고, 장기적으로 폼 기반 전환 검토.

### 대안 3: jobs 컬렉션 타입별 분리 (pdf_indexing_jobs, query_jobs 등)
- **기각 사유:** 현재 두 Function이 동일 컬렉션을 구독하는 것은 비용 미미 (약 $0.00004/건). 우선순위 낮은 최적화이므로 Phase 4로 이연.

### 대안 4: 업로드 + 약관관리 화면 통합
- **기각 사유:** 업로드는 가끔, 약관 조회는 자주 사용되므로 화면 분리 유지가 UX상 유리. 대신 네비게이션 연결 강화와 업로드 완료 후 약관관리 이동 CTA 추가로 해결.

---

## 7. 생성/수정 파일 목록

| 파일 | 작업 | 변경 사유 |
|------|------|-----------|
| `plans/plan-task-146.1.md` | 신규 생성 | 미팅 계획서 |
| `memory/reports/task-146.1.md` | 신규 생성 | 미팅 결과 보고서 |

**코드 변경 없음** — 본 작업은 아키텍처 분석/미팅으로 코딩 작업이 아님.

---

## 8. 팀장 검토 결과

### 토르 (백엔드) — 1차 검토 통과
- **평가:** 업로드 파이프라인과 인덱싱 아키텍처를 코드 라인 번호 수준으로 매우 상세하게 분석함. metadata docId 불일치 버그, 카테고리 키워드 누락, 재인덱싱 시 고아 청크 문제 등 핵심 버그를 정확히 발굴.
- **특히 좋은 점:** Cloud Function 타임아웃 대비 대용량 PDF 처리 시간 추정표가 구체적이고 실용적. 개선 제안에 구현 코드 예시와 우선순위를 함께 제시하여 즉시 실행 가능한 수준.
- **수정 사항 없음.**

### 프레이야 (프론트엔드) — 1차 검토 통과
- **평가:** 업로드 UI의 에러 핸들링, 상태 추적, 접근성 문제를 체계적으로 분석. jobId 반환값이 프론트엔드에서 완전히 무시되는 Critical 문제를 정확히 발견.
- **특히 좋은 점:** InsuranceTermsList와 admin/terms/client 컴포넌트 중복 문제, admin 상세 페이지의 Level3Search 열화 구현 문제 등 코드 품질 이슈도 잘 잡아냄. 개선안에 구현 코드 예시 포함.
- **수정 사항 없음.**

### 미미르 (UX/UI) — 1차 검토 통과
- **평가:** 전체 사용자 여정을 6단계로 분해하여 각 단계의 혼란/불편 포인트를 구체적으로 식별. `/admin/terms`가 사이드바 네비게이션에 없다는 정보 아키텍처 오류를 정확히 발견.
- **특히 좋은 점:** 파일명 규칙 사용성 분석이 매우 상세함. 실제 보험사 이름 패턴을 대입하여 오분류 시나리오를 구체적으로 제시. 와이어프레임 수준의 개선안 포함.
- **수정 사항 없음.**

### 헤임달 (테스터) — 1차 검토 통과
- **평가:** 23개 결함/위험 항목을 Critical/High/Medium/Low로 체계적 분류. 파일 업로드 보안(Magic Bytes, 크기 제한), 동시성 Race Condition, 부분 실패 복구 등 실질적인 위험을 정확히 진단.
- **특히 좋은 점:** 단위/통합/E2E/부하/보안 5종 테스트 전략을 각각 구체적인 시나리오와 기대값으로 제시. 즉시 적용 가능한 수정 코드 3건 포함.
- **수정 사항 없음.**

---

## 9. 셀프 QC

### 1. 이 변경이 다른 파일에 영향을 미치는가?
본 작업은 코드 변경 없는 분석/미팅이므로 다른 파일에 직접적 영향 없음. 다만 보고서에 제시된 개선안이 향후 구현 시 영향을 미칠 파일: `route.ts`, `pdfIndexing.ts`, `googleDrive.ts`, `upload/page.tsx`, `admin/terms/client.tsx`, `admin/layout.tsx`, `firestore.rules`. 각 개선안에 영향 범위를 명시함.

### 2. 이 로직의 엣지 케이스는 무엇인가?
분석 대상인 PDF 업로드 파이프라인의 엣지 케이스를 헤임달이 12개 시나리오로 도출함: 0바이트 PDF, 비밀번호 보호 PDF, 스캔 이미지 PDF, 100MB+ 대용량 PDF, 한글 인코딩 깨짐, 동시 업로드 Race Condition, 인덱싱 중 재업로드, Drive 업로드 성공+Firestore 실패, Cloud Function 타임아웃 등.

### 3. 이 구현이 작업 지시와 정확히 일치하는가?
작업 지시: "약관 PDF 업로드 아키텍처 미팅 소집. 팀원 전원 다각도 분석 후 최적안 도출. 코딩 아님, 미팅/분석/보고만." — 4명 팀원의 병렬 분석을 수행하고 43건의 발견 사항과 24건의 개선안을 4단계 로드맵으로 도출함. 코드 변경 없음. 지시와 정확히 일치.

### 4. 에러 처리와 보안은 확인했는가?
헤임달의 보안 분석에서 CRITICAL 3건(파일 검증 부재, docId 불일치, Race Condition), HIGH 4건(Rate Limiting 부재, 크기 제한 없음, Admin 이중 구조, 에러 메시지 내부 정보 노출)을 도출. 각각에 대해 구체적 수정 방안을 제시함.

### 5. 테스트가 모든 경로를 커버하는가?
헤임달이 5종 테스트 전략(단위/통합/E2E/부하/보안)을 제시함. 단위 테스트 7개 함수, 통합 테스트 3개 흐름, E2E 3개 시나리오, 부하 테스트 3개 조건, 보안 테스트 5개 항목으로 현재 아키텍처의 주요 경로를 포괄적으로 커버.

---

## 10. 비고

- 본 미팅 결과를 기반으로 Phase 1(즉시 수정 7건)은 다음 태스크로 즉시 착수 권장
- Phase 1의 Easy 항목 7건은 1~2일 내 완료 가능 (예상)
- metadata docId 불일치(C-1)는 현재 운영 중인 데이터에도 영향을 미칠 수 있으므로, 수정 전 기존 데이터 정합성 점검 필요
- 재인덱싱 시 고아 청크 문제(C-4)는 기존 데이터에도 이미 발생했을 가능성 있음, 일괄 정리 스크립트 필요
