# task-647.1 완료 보고서: InsuRo generate-content 플랜 기반 모델 강제 + 토큰 차감

## SCQA

**S**: InsuRo generate-content Edge Function에 구독 플랜 시스템(5티어)과 토큰 테이블(plan_token_config, feature_token_costs, token_usage_log, plan_ai_models)이 DB에 구축되어 있다.

**C**: Edge Function의 MODEL_MAP이 구(flash/pro/top) 체계이며, 사용자가 `settings.aiModel`로 상위 모델을 직접 지정하면 플랜 검증 없이 통과된다. 토큰 차감/기록 로직도 부재.

**Q**: 플랜 기반 모델 강제(haiku/sonnet/opus 티어)와 토큰 잔량 체크 + 차감을 Edge Function에 구현할 수 있는가?

**A**: TIER_MODEL_MAP 도입으로 계층적 모델 접근 제어(haiku⊂sonnet⊂opus) 구현 완료. plan_ai_models 테이블 기반 티어 조회 → 모델 검증 → 토큰 잔량 체크 → 생성 → 토큰 기록 → tokenInfo 응답 파이프라인 완성. Deno 타입 체크 에러 0건.

---

## 생성/수정 파일 목록

| 파일 | 유형 | 설명 |
| --- | --- | --- |
| `supabase/functions/generate-content/index.ts` | 수정 | 핵심 Edge Function — 모델 강제 + 토큰 시스템 |
| `supabase/migrations/20260317150000_token_usage_model_tier_and_insert_rls.sql` | 신규 | model_tier 컬럼 추가 + INSERT RLS 정책 |

경로 기준: `/home/jay/projects/InsuRo/`

---

## 구현 상세

### 1. TIER_MODEL_MAP (MODEL_MAP 교체)
- 기존 3개 키(flash/pro/top) → 3개 티어(haiku/sonnet/opus)로 재구성
- 각 티어는 하위 티어의 모든 모델을 포함하는 계층 구조
- haiku: gemini-2.0-flash-lite
- sonnet: + gemini-2.0-flash, gemini-2.5-pro
- opus: + claude-sonnet-4-6

### 2. 플랜 기반 모델 강제
- `getUserPlan()` 반환값에 `planId` 추가
- `getUserModelTier()`: plan_ai_models 테이블에서 티어 조회
- 사용자 요청 모델이 티어 허용 목록에 없으면 → `upgrade_required` (403)
- 모델 미지정 시 → 티어 기본 모델 자동 선택 (haiku→flash-lite, sonnet→flash, opus→2.5-pro)

### 3. 토큰 잔량 체크
- `checkTokenBalance()`: plan_token_config → 월간 할당량, token_usage_log → 당월 합산, feature_token_costs → 요청 비용
- quota=-1(무제한) → 체크 스킵
- 잔량 부족 → `token_exhausted` (429)

### 4. 토큰 사용 기록
- `recordTokenUsage()`: 스트리밍/비스트리밍 모두 token_usage_log에 INSERT
- model_tier, model_used, tokens_consumed 기록

### 5. API 응답 tokenInfo
- 비스트리밍 응답에 `tokenInfo: { cost, remaining, quota }` 포함
- 스트리밍 응답은 토큰 기록만 수행 (tokenInfo는 별도 조회)

---

## 발견 이슈 및 해결

### 자체 해결 (3건)

1. **Deno 타입 에러: catch절 error가 unknown** — `error instanceof Error` 체크 추가로 타입 안전하게 처리
   - `index.ts:663-666` catch절 수정

2. **getUserPlan 반환값 변경으로 인한 호출부 비호환** — 메인 핸들러에서 destructuring `{ planId, features: planFeatures }` 패턴으로 통일
   - `index.ts:343`

3. **token_usage_log에 model_tier 컬럼 부재** — 마이그레이션 SQL로 ALTER TABLE 추가
   - `20260317150000_token_usage_model_tier_and_insert_rls.sql`

---

## 검증 결과

| 시나리오 | 기대 결과 | 코드 구현 | 상태 |
| --- | --- | --- | --- |
| 무료 유저가 Pro 모델 요청 | upgrade_required 에러 | index.ts:389-397 | ✅ |
| 토큰 소진 후 요청 | token_exhausted 에러 | index.ts:448-455 | ✅ |
| 정상 요청 | 콘텐츠 + tokenInfo | index.ts:654-661 | ✅ |
| 맥스 플랜 (무제한) | 토큰 체크 스킵, 기록은 함 | index.ts:222-231, 649-651 | ✅ |
| 모델 미지정 | 기본 모델 자동 선택 | index.ts:420-438 | ✅ |
| Deno 타입 체크 | 에러 0건 | `deno check` 통과 | ✅ |

---

## 자동 검증 (QC)

```
deno check: PASS (에러 0건)
마이그레이션 SQL: 문법 검증 OK (IF NOT EXISTS/멱등성)
기존 인터페이스 호환: 유지 (topic, contentType, settings 필드 미변경)
```

---

## 비고
- 프론트엔드 수정은 1팀 소관 (tokenInfo 표시, 모델 선택 UI 제한 등)
- 마이그레이션 `20260317150000_token_usage_model_tier_and_insert_rls.sql`은 `supabase db push` 또는 배포 시 적용 필요
- Edge Function 배포: `supabase functions deploy generate-content`
