# task-1983 보고서: 인슈로 전수조사 적발 + Gemini PR 리뷰 지적 일괄 수정

**작성자**: 헤르메스 (개발1팀장)
**작성일**: 2026-04-20
**검증 레벨**: critical
**프로젝트**: InsuRo

---

## SCQA

**S**: task-1967+1 전수조사에서 적발 16건, task-1968 Gemini PR 리뷰에서 지적 7건이 보고되었다. 이 중 task-1969(13건), task-1973(1건)에서 대부분 수정 완료된 상태다.

**C**: 3건이 미해결 잔존: (1) _keyword_jobs 인메모리 관리로 서버 재시작 시 데이터 유실(HIGH), (2) 위키 랭킹 Python-side 합산으로 성능 저하 위험(MEDIUM), (3) Settings.tsx as any 4건 타입 안전성 부재(MEDIUM).

**Q**: 잔여 3건을 Supabase 인프라 활용하여 수정하고 테스트 통과를 확보할 수 있는가?

**A**: 3건 모두 수정 완료. keyword_jobs Supabase 테이블 이관, 위키 랭킹 RPC 전환, Settings.tsx 타입 교체. 158/158 테스트 PASS(기존 실패 1건 제외), tsc 0 errors, L1 스모크테스트 통과.

---

## 작업 내용

### 1. _keyword_jobs 인메모리 → Supabase keyword_jobs 테이블 이관 (B-3, HIGH)
- 인메모리 dict `_keyword_jobs: dict[str, dict] = {}` 제거
- Supabase `keyword_jobs` 테이블 migration 생성 (RLS 정책 포함)
- `_run_keyword_analysis()`, `start_keyword_analysis()`, `get_keyword_result()` 함수를 모두 Supabase CRUD로 전환
- 서버 재시작 시 진행 중인 job 정보가 유실되지 않음

### 2. 위키 랭킹 Python 합산 → Supabase RPC (B-6, MEDIUM)
- `get_wiki_rankings()` 함수에서 전체 테이블 fetch + Python `score_map` 합산 제거
- Supabase RPC `get_wiki_rankings(limit_count)` SQL 함수 생성 (GROUP BY + SUM + ORDER BY)
- `sb.rpc("get_wiki_rankings", {"limit_count": 10}).execute()` 단일 호출로 교체

### 3. Settings.tsx as any 4건 타입 수정 (C-9, MEDIUM)
- `Database` 타입 import + 5개 타입 alias 추가 (`ProfileRow`, `ApiKeyInsert/Update`, `NaverKeyInsert/Update`)
- `as any` 5건 → 정확한 Supabase 타입으로 교체
- `fcpa_settings` JSON 필드 타입 캐스팅 추가
- `error` 변수 암시적 any → `{ message: string } | null` 명시

### 4. 테스트 mock 패턴 업데이트
- wiki ranking 테스트: `sb.table().select()` → `sb.rpc()` mock 전환
- keyword analyze 테스트 2건: `_get_supabase_client` mock 추가
- keyword result 테스트: `maybe_single` 체인 mock 추가

---

## 수정 파일 목록

| 파일 | 변경 내용 | grep 검증 | 상태 |
|------|-----------|-----------|------|
| server/main.py:97 | _keyword_jobs 인메모리 dict 제거 | `grep "_keyword_jobs" → 0건` | verified |
| server/main.py:614-704 | keyword_jobs Supabase CRUD 전환 | `grep "keyword_jobs" → 다수 존재` | verified |
| server/main.py:762-778 | 위키 랭킹 RPC 전환 | `grep "rpc" → 1건 존재`, `grep "score_map" → 0건` | verified |
| src/pages/Settings.tsx:5,24-28 | Database 타입 import + alias 추가 | `grep "ProfileRow" → 존재` | verified |
| src/pages/Settings.tsx:198,212 | profileData as ProfileRow + fcpa_settings 캐스팅 | `grep "as any" → 0건` | verified |
| src/pages/Settings.tsx:271-273 | ApiKey 타입 캐스팅 + error 타입 명시 | `grep "ApiKeyUpdate" → 존재` | verified |
| src/pages/Settings.tsx:296-298 | NaverKey 타입 캐스팅 + error 타입 명시 | `grep "NaverKeyUpdate" → 존재` | verified |
| supabase/migrations/20260420000000_keyword_jobs_table.sql | 신규 migration | 파일 존재 확인 | verified |
| supabase/migrations/20260420000001_wiki_rankings_rpc.sql | 신규 migration | 파일 존재 확인 | verified |
| server/tests/test_e2e_flows.py:696-753 | wiki ranking mock → rpc 패턴 | pytest PASS | verified |
| server/tests/test_e2e_flows.py:868-901 | keyword analyze mock 추가 | pytest PASS | verified |
| server/tests/test_main.py:515-543 | keyword analyze/result mock 추가 | pytest PASS | verified |

---

## 이전 task에서 이미 완료 확인된 항목 (스킵)

| 항목 | 완료 task | 확인 방법 |
|------|-----------|-----------|
| Phase A: 브랜치 머지 (CR-3,4,5) | task-1969 | `git log` 확인 — main에 반영 |
| B-1: Naver API 키 서버사이드 이동 | task-1969 | `grep "NAVER_CLIENT" main.py` → env var |
| B-2: subprocess.run → asyncio 전환 | task-1973 | `grep "asyncio.create_subprocess" anu_provider.py` |
| B-4: 맥스 플랜 잔여석 필터 | task-1969 | plan_id 필터 코드 확인 |
| B-5: AsyncClient 전역 재사용 | task-1969 | `_naver_http_client` 전역 싱글턴 |
| B-7: N+1 토픽 쿼리 | task-1969 | `.in_("summary_id")` 배치 조회 |
| C-8: Dashboard.tsx 삭제 | task-1969 | 파일 미존재 확인 |
| C-10: 채널 제한 UI | task-1969 | `isLocked` + toast + upgrade dialog |
| C-11: 무료 카운트다운 | task-1969 | `contents` 테이블 DB 조회 구현 |
| C-12: 감사 로그 (AU-1) | task-1969 | `token_usage_log` INSERT 4곳 |
| D-13: OAuth 토큰 암호화 (C6) | task-1969 | `crypto.py` Fernet 구현 |

---

## 발견 이슈 및 해결

### 이슈 1: Settings.tsx fcpa_settings 타입 불일치
- **원인**: `as any` → `as ProfileRow` 변경 시 `Json` 타입과 구체적 fcpa_settings 객체 타입 불일치
- **해결**: `(p.fcpa_settings as typeof profile.fcpa_settings)` 타입 캐스팅 추가

### 이슈 2: Settings.tsx error 변수 암시적 any
- **원인**: `let error;` 선언 시 타입 추론 불가
- **해결**: `let error: { message: string } | null = null;` 명시

### 이슈 3: 테스트 3건 실패 (keyword analyze 2건, wiki ranking 1건)
- **원인**: main.py에서 Supabase CRUD/RPC로 전환했으나 테스트 mock이 인메모리 패턴 유지
- **해결**: mock 패턴을 Supabase `table().insert/select/update` 및 `rpc()` 방식으로 전환

### 기존 이슈 (범위 외): NaverProxy 테스트 1건 실패
- **상태**: main 브랜치에서도 동일 실패 (본 작업 범위 외)
- **원인**: Naver DataLab 프록시 mock이 전역 AsyncClient 싱글턴 패턴과 불일치
- **보고**: `⚠️ 기존 테스트 실패 1건 (본 작업 범위 외): TestNaverProxyFlow::test_e2e_naver_proxy_datalab`

---

## L1 스모크테스트 결과

- 서버 재시작: 성공 (포트 8001, uvicorn main:app)
- API 응답 확인:
  - `GET /api/status` → 200 OK `{"status":"ok"}`
  - `POST /api/insuro/keywords/analyze` → 401 (인증 필수, 정상 동작)
  - `GET /api/insuro/keywords/result/test-id` → 401 (인증 필수, 정상 동작)
  - `GET /api/insuro/wiki/rankings` → 401 (인증 필수, 정상 동작)
- 스크린샷: 해당없음 (백엔드 API 작업)

---

## 테스트 결과

- **pytest**: 158 passed, 1 failed (기존 NaverProxy), 19 warnings
- **tsc --noEmit**: 0 errors
- **grep 검증**: 모든 항목 verified (0건 planned)

---

## 머지 판단

- **머지 필요**: Yes
- **브랜치**: task/task-1983-dev1
- **워크트리 경로**: /home/jay/projects/InsuRo/.worktrees/task-1983-dev1
- **PR**: https://github.com/JonghyukJeon/InsuRo/pull/4
- **Gemini PR 리뷰**: TIMEOUT (5분 초과, 아누 수동 머지 승인 필요)
- **머지 의견**: 6개 커밋, 테스트 전수 통과(기존 1건 제외), tsc 0 errors, pyright 0 errors, L1 스모크 정상. Supabase migration 2건은 프로덕션 DB에 적용 필요.

---

## 모델 사용 기록

| 팀원 | 역할 | 모델 | 작업 |
|------|------|------|------|
| 불칸 | 백엔드 | sonnet | B-3 keyword_jobs DB 이관, B-6 랭킹 RPC 전환 |
| 이리스 | 프론트엔드 | sonnet | C-9 Settings.tsx as any 타입 교체 |
| 헤르메스 | 팀장 | opus | 타입 에러 직접 수정 (fcpa_settings, error 변수) |
| 아르고스 | 테스터 | sonnet | 테스트 mock 패턴 Supabase 전환 |

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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

