# CRM 대화 자동 요약 시스템 심층 설계 문서

**프로젝트**: Insuro CRM — 보험 설계사-고객 채팅 AI 자동 요약  
**작성일**: 2026-04-19  
**버전**: v1.0 (Agent 미팅 Cycle 2 최종 합의 반영)

---

## 1. 개요

### 목적

보험 설계사와 고객 간 1:1 채팅의 핵심 내용을 AI가 자동으로 요약하여, 설계사가 매번 긴 대화를 처음부터 읽지 않고도 고객의 현재 상태와 다음 행동을 즉시 파악할 수 있도록 한다.

### 핵심 원칙

> **"찾기 전에 보여주고, 읽기 전에 파악하게 하라"**

설계사의 인지 부하를 최소화하는 것이 최우선이다. 요약은 과거 기록의 보관이 아니라, 현재 상태와 다음 행동의 즉시 제시가 목적이다.

### 현재 시스템

| 구성 요소 | 역할 |
|---|---|
| `conversations` | 대화방 메타데이터 |
| `conversation_messages` | 개별 메시지 저장 |
| `customer_ai_summaries` | 기존 AI 요약 (copilot/analyze) |

---

## 2. 데이터 모델

### 2.1 테이블 구조

#### `conversation_summaries` — 대화 요약 메인 테이블

```sql
CREATE TABLE conversation_summaries (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  conversation_id   UUID NOT NULL REFERENCES conversations(id),
  customer_id       UUID NOT NULL REFERENCES customers(id),
  agent_id          UUID NOT NULL REFERENCES agents(id),

  -- 버전 관리
  version           INTEGER NOT NULL DEFAULT 1,
  is_latest         BOOLEAN NOT NULL DEFAULT true,

  -- 요약 기간
  summary_period_start  TIMESTAMPTZ NOT NULL,
  summary_period_end    TIMESTAMPTZ NOT NULL,
  message_count         INTEGER NOT NULL,
  first_message_id      UUID REFERENCES conversation_messages(id),
  last_message_id       UUID REFERENCES conversation_messages(id),

  -- 요약 본문
  summary_text      TEXT NOT NULL,
  structured        JSONB NOT NULL DEFAULT '{}',  -- 구조화 데이터

  -- 임베딩 (벡터 검색)
  embedding         vector(1536),

  -- LLM 메타
  llm_model         TEXT,          -- 예: 'claude-haiku-3-5', 'claude-sonnet-4-5'
  llm_input_tokens  INTEGER,
  llm_output_tokens INTEGER,
  llm_cost_usd      NUMERIC(10, 6),
  prompt_cache_hit  BOOLEAN DEFAULT false,

  -- 에이전트 보정
  reviewed_by       UUID REFERENCES agents(id),
  reviewed_at       TIMESTAMPTZ,
  review_notes      TEXT,
  original_text     TEXT,          -- 보정 전 원본 보존

  created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at        TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

#### `summary_topics` — 주제별 분리 테이블

```sql
CREATE TABLE summary_topics (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  summary_id        UUID NOT NULL REFERENCES conversation_summaries(id) ON DELETE CASCADE,
  customer_id       UUID NOT NULL REFERENCES customers(id),

  topic_tag         TEXT NOT NULL,   -- 예: 'need_discovery', 'product_comparison', 'objection'
  topic_label       TEXT NOT NULL,   -- 예: '니즈탐색', '상품비교', '장벽/이의'
  content           TEXT NOT NULL,
  importance        SMALLINT NOT NULL DEFAULT 2 CHECK (importance BETWEEN 1 AND 5),

  contract_items    JSONB DEFAULT '[]',   -- 언급된 상품/계약 정보
  key_dates         JSONB DEFAULT '[]',   -- 중요 날짜 목록

  created_at        TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

#### `summary_jobs` — 비동기 작업 큐

```sql
CREATE TABLE summary_jobs (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  conversation_id   UUID NOT NULL REFERENCES conversations(id),
  customer_id       UUID NOT NULL REFERENCES customers(id),
  triggered_by      TEXT NOT NULL,  -- 'manual', 'timeout', 'session_resume'
  triggered_at      TIMESTAMPTZ NOT NULL DEFAULT now(),

  status            TEXT NOT NULL DEFAULT 'pending'
                    CHECK (status IN ('pending', 'running', 'done', 'failed', 'skipped')),

  -- 중복 방지
  EXCLUDE USING btree (
    conversation_id WITH =,
    status WITH =
  ) WHERE (status IN ('pending', 'running')),

  -- 재시도
  attempt_count     INTEGER NOT NULL DEFAULT 0,
  max_attempts      INTEGER NOT NULL DEFAULT 3,
  last_error        TEXT,
  next_retry_at     TIMESTAMPTZ,

  summary_id        UUID REFERENCES conversation_summaries(id),
  completed_at      TIMESTAMPTZ,
  created_at        TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 2.2 인덱스 전략

```sql
-- 고객별 최신 요약 조회
CREATE INDEX idx_summaries_customer_latest
  ON conversation_summaries(customer_id, created_at DESC)
  WHERE is_latest = true;

-- 대화별 버전 조회
CREATE INDEX idx_summaries_conversation_version
  ON conversation_summaries(conversation_id, version DESC);

-- 주제 태그 필터
CREATE INDEX idx_topics_tag
  ON summary_topics(customer_id, topic_tag);

-- 벡터 유사도 검색 (HNSW)
CREATE INDEX idx_summaries_embedding
  ON conversation_summaries
  USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 64);

-- 대기 중인 작업 조회
CREATE INDEX idx_jobs_pending
  ON summary_jobs(status, next_retry_at)
  WHERE status IN ('pending', 'failed');
```

### 2.3 `structured` JSONB 스키마

```json
{
  "overview": "대화 전반 1-2문장 요약",
  "sentiment_arc": {
    "start": "positive|neutral|negative",
    "end": "positive|neutral|negative",
    "turning_points": ["전환점 설명"]
  },
  "action_items": [
    {
      "owner": "agent|customer",
      "content": "할 일 내용",
      "due_date": "YYYY-MM-DD or null",
      "done": false
    }
  ],
  "contract_highlights": [
    {
      "product_name": "상품명",
      "status": "interested|comparing|rejected|signed",
      "notes": "메모"
    }
  ],
  "date_log": [
    {
      "date": "YYYY-MM-DD",
      "event": "이벤트 설명"
    }
  ]
}
```

---

## 3. API 설계

### 3.1 엔드포인트 목록

| Method | Path | 설명 |
|---|---|---|
| `POST` | `/api/v1/summaries/generate` | 요약 생성 (비동기, job_id 반환) |
| `GET` | `/api/v1/customers/{customer_id}/summaries` | 고객별 요약 히스토리 |
| `POST` | `/api/v1/summaries/search` | 하이브리드 검색 (벡터 + 키워드) |
| `PATCH` | `/api/v1/summaries/{summary_id}/review` | 에이전트 보정 |
| `GET` | `/api/v1/summary-jobs/{job_id}` | 작업 상태 조회 |

### 3.2 API 상세

#### POST /api/v1/summaries/generate

```json
// Request
{
  "conversation_id": "uuid",
  "trigger": "manual | timeout | session_resume",
  "force": false  // 중복 요약 강제 생성 여부
}

// Response 202
{
  "job_id": "uuid",
  "status": "pending",
  "estimated_seconds": 15
}
```

#### GET /api/v1/customers/{customer_id}/summaries

```
Query params:
  - page, per_page (default 20)
  - topic_tag (필터)
  - from_date, to_date
  - latest_only (boolean)

Response 200:
{
  "summaries": [...],
  "total": 42,
  "page": 1
}
```

#### POST /api/v1/summaries/search

```json
// Request
{
  "query": "종신보험 거절 이유",
  "customer_id": "uuid (optional)",
  "filters": {
    "topic_tags": ["objection"],
    "date_range": {"from": "2026-01-01", "to": "2026-04-19"}
  },
  "limit": 10,
  "search_mode": "hybrid"  // "vector" | "keyword" | "hybrid"
}
```

#### PATCH /api/v1/summaries/{summary_id}/review

```json
// Request
{
  "summary_text": "수정된 요약 텍스트",
  "structured": { ... },  // 부분 업데이트 가능
  "review_notes": "에이전트 메모"
}
// 원본(original_text)은 자동 보존
```

---

## 4. LLM 호출 전략

### 4.1 3단계 계층 구조

```
[메시지 원본]
     ↓
[1단계] 청크 요약 — Claude Haiku
     ↓
[2단계] 통합 요약 — Haiku (단순) / Sonnet (복잡, 조건부)
     ↓
[3단계] 임베딩 생성 — text-embedding-3-small
```

### 4.2 청크 분할 전략

우선순위 순서:

1. **주제 경계 우선**
   - 날짜 변경 (자정 경계)
   - 4시간 이상 갭
   - sender 패턴 급변 (단답 → 장문, 또는 반대)
2. **30개 메시지 폴백** (주제 경계를 찾지 못한 경우)

### 4.3 점진적 요약 (MapReduce 증분 업데이트)

- 전체 재요약 대신, 신규 메시지 청크만 처리 후 기존 요약과 병합
- 토큰 사용량 최대 **80% 절감**
- 이전 요약 + 신규 청크 → Haiku → 병합 요약

### 4.4 보험 도메인 온톨로지 기반 분류기

LLM 호출 전 규칙 분류기를 먼저 실행하여 비용 절감:

| 처리 경로 | 비율 | 처리 방식 |
|---|---|---|
| 규칙 분류기 | 80% | 온톨로지 매칭, 비용 $0 |
| Claude Haiku | 15% | 애매한 케이스 |
| Claude Sonnet | 5% | 복잡/분쟁/법적 케이스 |

보험 온톨로지 주제 태그:

| tag | label |
|---|---|
| `need_discovery` | 니즈탐색 |
| `product_comparison` | 상품비교 |
| `objection` | 장벽/이의 |
| `contract_progress` | 계약진행 |
| `maintenance` | 유지관리 |

### 4.5 Prompt Caching

- 시스템 프롬프트(보험 도메인 컨텍스트, 온톨로지 정의)를 캐시 블록으로 선언
- 캐시 히트 시 입력 토큰 비용 90% 절감
- `prompt_cache_hit` 컬럼으로 추적

### 4.6 비용 차단기

```
월 상한: $500
일일 경고: $50 초과 시 Slack/Telegram 알림
차단 동작: 상한 도달 시 요약 생성 자동 throttle
모니터링: Prometheus + Grafana 대시보드
```

---

## 5. 트리거 설계

### 5.1 트리거 유형

| 유형 | 설명 | 조건 |
|---|---|---|
| **수동 종료 버튼** | 설계사가 직접 대화 종료 버튼 클릭 | 즉시 실행 |
| **적응형 타임아웃** | 비활성 감지 후 자동 마감 | 기본 30분, 설계사 패턴 적응 |
| **세션 재개 감지** | 30분 갭 후 새 메시지 도착 시 | 이전 세션 자동 마감 후 새 세션 시작 |

### 5.2 적응형 타임아웃

- 기본값: 30분
- 설계사별 과거 대화 패턴 분석 → 타임아웃 자동 조정
- 짧은 타임아웃의 토막 요약 방지: 세션 재개 감지로 연속성 보장

### 5.3 중복 방지

1. **DB 레벨**: `summary_jobs` EXCLUDE constraint (`conversation_id + status IN pending/running`)
2. **앱 레벨**: 작업 큐 삽입 전 중복 상태 조회 (이중 검사)

### 5.4 비동기 큐

- **Celery + Redis** (Edge Functions 대신 선택)
  - 이유: 재시도 기본 지원, 장시간 작업(LLM 호출) 안정성, 모니터링 용이
- 완료 알림: **Supabase Realtime** 브로드캐스트 → 클라이언트 실시간 수신

---

## 6. UI 와이어프레임

### 6.1 CopilotSidePanel 탭 구조

```
┌─────────────────────────────────────┐
│  [실시간분석] [대화요약] [고객정보]  │
├─────────────────────────────────────┤
│                                     │
│  (선택된 탭 콘텐츠)                  │
│                                     │
└─────────────────────────────────────┘
```

### 6.2 Brief Card (4줄 스냅샷)

고객 상태 머신의 UI 표현. 항상 최상단에 고정.

```
┌─────────────────────────────────────┐
│ Brief                               │
│                                     │
│ 관심사     종신보험 (사망보장 강조) │
│ 미결       보험료 부담 해소 확인 필요│
│ 주의사항   배우자 반대 의견 있음    │
│ 다음 액션  월요일 오전 재상담 예정  │
│                                     │
│ ✓ 확인    ✎ 수정                  │
└─────────────────────────────────────┘
```

### 6.3 컴포넌트 목록

| 컴포넌트 | 설명 |
|---|---|
| `SummaryTimeline` | 월별/세션별 그룹핑된 요약 목록 |
| `SummaryByTopic` | 주제 태그별 필터 및 요약 보기 |
| `SummaryEditor` | 필드별 수정, 원본 보존, 자동저장 (5초 debounce) |
| `GlobalSummarySearch` | 전역 벡터 검색, 추천 필터 표시 |
| `SessionDivider` | 메시지 뷰 내 세션 구분선 (날짜/시간 표시) |

### 6.4 모바일 대응

- Bottom Sheet 패턴 (전체 화면 모달 대신)
- 최소 터치 타깃: 44px
- 최소 폰트: 14px
- 40-60대 설계사 접근성 고려 (고대비, 명확한 아이콘 레이블)

---

## 7. 고객 상태 머신 (하이브리드 설계)

### 7.1 설계 철학

> **요약은 상태 변경의 "근거 데이터"이고, 상태 머신은 요약의 "목적지"이다.**

설계사가 원하는 것은 "과거 기록"이 아닌 "현재 상태 + 다음 행동"이다. Brief Card가 상태 머신의 UI 표현이고, 대화 요약이 상태를 추론하는 근거가 된다.

### 7.2 CustomerState 구조

```typescript
interface CustomerState {
  decision_stage: 'exploring' | 'comparing' | 'deciding' | 'contracted' | 'maintaining';
  sentiment_trajectory: 'improving' | 'stable' | 'declining';
  confirmed_needs: string[];      // 확인된 니즈 목록
  open_objections: string[];      // 미해결 장벽 목록
  commitments: Commitment[];      // 약속/커미트먼트
  next_best_action: string;       // 시스템 추천 다음 행동
}
```

### 7.3 보험 온톨로지 기반 여정 챕터

```
[니즈탐색] → [상품비교] → [장벽/이의] → [계약진행] → [유지관리]
```

- 상태 전환 시 여정 챕터 자동 전환
- 챕터 전환은 `summary_topics`의 `topic_tag` 분포로 자동 감지

### 7.4 역방향 질의 (Phase 3)

- 현재: 설계사가 시스템에게 고객 정보를 묻는 방식
- Phase 3 목표: 시스템이 설계사에게 먼저 팔로업 알림
  - 예: "김민수 고객과 약속한 월요일 자료 전달이 오늘입니다."
  - 예: "이지연 고객이 3주째 응답이 없습니다. 재접촉 시점입니다."

---

## 8. 검색/조회 전략

### 8.1 인컨텍스트 검색 (고객 내부)

- **fuse.js** 클라이언트 사이드 퍼지 검색
- 단일 고객의 요약 내 키워드 검색
- 네트워크 요청 없이 즉시 결과

### 8.2 전역 검색 (전체 고객 대상)

- `POST /api/v1/summaries/search`
- 하이브리드 검색: 벡터 유사도 + 키워드 전문 검색
- pgvector HNSW 인덱스 활용
- 비민감 필드만 인덱싱 (주제 태그, 상품 카테고리)

### 8.3 추천 검색어

- 고빈도 주제 태그 기반으로 추천 검색어 제공
- 예: "종신보험 거절", "실손 유지", "계약 취소 의향"

### 8.4 pgvector 인덱스 유지보수

```sql
-- 분기 1회 HNSW 인덱스 재빌드 cron
-- Supabase pg_cron 또는 외부 스케줄러
SELECT cron.schedule(
  'rebuild-hnsw-index',
  '0 3 1 */3 *',  -- 분기 첫째날 새벽 3시
  $$REINDEX INDEX CONCURRENTLY idx_summaries_embedding;$$
);
```

---

## 9. 보안 & 규제

### 9.1 PII 마스킹 3단계

| 단계 | 방식 | 대상 |
|---|---|---|
| 1단계 | 정규식 (정형 데이터) | 주민번호, 전화번호, 계좌번호 |
| 2단계 | NER 모델 (비정형) | 이름, 주소, 조직명 |
| 3단계 | 도메인 사전 (보험 특화) | 상품코드, 설계사번호 등 |

**적용 범위**: AI 전송 구간에서만 마스킹. 저장 요약은 역치환으로 원본 복원.

**토큰 치환 맵**: 별도 암호화 저장. AI 응답 수신 후 즉시 역치환.

### 9.2 접근 제어

- **RLS (Row Level Security)**: Supabase 정책으로 테이블 접근 제한
- **앱 레벨 IDOR 방어**: 파라미터 기반 접근 시 소유권 재검증
- **계층적 접근 모델**: 설계사 → 팀장 → 관리자 권한 분리

### 9.3 감사 로그

```
특성: append-only (삭제/수정 불가)
무결성: 해시 체인 (각 로그 레코드가 이전 레코드 해시 포함)
보존 기간: 5년 (보험업법 준수)
```

### 9.4 암호화

| 구간 | 방식 |
|---|---|
| 전송 | TLS 1.3 |
| 저장 | TDE (Transparent Data Encryption) |
| 민감 컬럼 | AES-256-GCM 컬럼 레벨 암호화 |

### 9.5 규제 요건

| 항목 | 내용 |
|---|---|
| Anthropic ZDR | 계약 필수 (데이터가 모델 학습에 사용되지 않음 보장) |
| 개인정보보호법 | AI 요약 목적의 별도 동의 필요 |
| 보험업법 | 원본 대화 5년 보존 의무 |
| 금감원 | AI 활용 사전 협의 필요 (일정 미정, 법무팀 협조) |

### 9.6 법적 지위

> **AI 요약은 "참고 자료"로만 위치한다. 원본 대화(conversation_messages)가 법적 원본(SSOT)이다.**

---

## 10. 비용 추정

### 10.1 LLM 비용 모델

- **티어링**: Haiku 70% : Sonnet 30%
- **Prompt Caching**: 입력 토큰 90% 절감 (시스템 프롬프트)
- **증분 업데이트**: 전체 재요약 대비 토큰 80% 절감

### 10.2 설계사 100명 기준 월간 비용

| 항목 | 비용 |
|---|---|
| Claude API (티어링 + 캐싱) | ~$226-307 |
| Redis (Celery 브로커) | $0 (무료 티어) |
| Celery Worker 서버 | ~$20 |
| Grafana 모니터링 | $0 (오픈소스) |
| **합계** | **~$286-367/월** |

### 10.3 규모별 추정

| 설계사 수 | 월간 비용 |
|---|---|
| 100명 | ~$286-367 |
| 500명 | ~$950-1,300 |

### 10.4 비용 차단기

```
월 상한: $500/월 → 초과 시 요약 자동 throttle
일일 경고: $50/일 초과 시 알림 발송
모니터링: Prometheus 메트릭 → Grafana 대시보드
```

---

## 11. Phase 로드맵

### Phase 1 — MVP (2주)

**목표**: 핵심 기능만 빠르게 검증

| 항목 | 내용 |
|---|---|
| DB | 기본 테이블 3개 신설 (summaries, topics, jobs) |
| 트리거 | 수동 종료 버튼만 |
| LLM | Claude Sonnet 단일 모델 (티어링 미적용) |
| UI | Brief Card + 기본 요약 조회 |
| 보안 | PII 마스킹 기본 구현 |

**Phase 1 전 필수 완료 항목**:
- ★ **설계사 5명 인터뷰** (실제 니즈 검증 후 범위 확정)
- ★ **PII 마스킹 후 요약 품질 baseline 측정**
- ★ **비용 차단기($500/월) 포함**

### Phase 2 — 고도화 (3주)

| 항목 | 내용 |
|---|---|
| 큐 | Celery + Redis 비동기 큐 |
| 트리거 | 적응형 타임아웃 + 세션 재개 감지 |
| LLM | Haiku/Sonnet 티어링 + 온톨로지 규칙 분류기 |
| UI | SummaryTimeline + SummaryByTopic |
| 상태 | 고객 상태 머신 (CustomerState) 통합 |

### Phase 3 — 지능화 (2주)

| 항목 | 내용 |
|---|---|
| 검색 | pgvector 전역 벡터 검색 |
| 최적화 | 증분 업데이트 (MapReduce) |
| UI | SummaryEditor (에이전트 보정) |
| 알림 | 역방향 질의 (팔로업 자동 알림) |

---

## 12. 테스트 전략

### 12.1 요약 게이팅

```
조건: 메시지 5개 미만 OR 총 글자 200자 미만
처리: 요약 스킵 (status = 'skipped')
이유: 의미 없는 짧은 대화 요약 방지
```

### 12.2 동시성 처리

- **스냅샷 기반 요약**: 작업 시작 시점의 메시지 스냅샷으로 처리
- 요약 중 신규 메시지 도착해도 현재 작업 영향 없음
- 더티 플래그로 재요약 필요 여부 추적

### 12.3 3-레이어 품질 검증

| 레이어 | 검증 방식 | 도구 |
|---|---|---|
| 구조 검증 | JSON 스키마 유효성 | Zod / AJV |
| 커버리지 검증 | 원본 메시지 핵심 키워드 대조 | 자체 구현 |
| 의미 검증 | LLM Judge 샘플링 (5%) | Claude Haiku |

### 12.4 회귀 테스트

- **Golden Dataset**: 품질 기준 요약 50건 수동 작성
- **ROUGE-L 스코어**: CI 파이프라인 연동, 회귀 감지
- 목표 임계값: ROUGE-L ≥ 0.65

### 12.5 부하 테스트

```
시나리오: 200건 동시 요약 burst
목표: P99 응답 < 30초
도구: Locust 또는 k6
```

---

## 13. DA 반론 및 결정 사항 (Cycle 2)

Cycle 2에서 로키(보안)가 Devil's Advocate 역할을 맡아 제기한 6개 항목과 처리 결과:

| 항목 | DA 지적 | 처리 결과 |
|---|---|---|
| 타임아웃 | 30분 고정 → 토막 요약 오발화 | **설계 수정**: 적응형 타임아웃 + 세션 재개 감지 |
| PII 마스킹 | 마스킹 후 요약 무의미화 | **반박 수용**: AI 전송 구간에서만 적용, 저장 시 역치환 |
| 비용 | 실제 $800-1,200 가능성 | **설계 수정**: $500/월 차단기 + 자동 throttle |
| State Machine | Phase 2 이후 → 맥락 소실 | **설계 수정**: Phase 1부터 하이브리드 통합 |
| 구조화 입력 폼 | 설계사 행동 변경 요구 | **반박 수용**: 대신 AI 요약 + 👍/👎 간단 확인 도입 |
| pgvector 인덱스 | 조용한 성능 저하 위험 | **설계 수정**: 분기 1회 HNSW 재빌드 cron 추가 |

**판정**: 6개 지적 중 4개 설계 수정, 2개 반박 수용. DA 제기 문제 모두 해결.

---

## 14. 미해결 항목

- 금감원 사전 협의 일정 미정 (법무팀 협조 필요)
- 설계사 인터뷰 결과에 따라 Phase 1 범위 조정 가능
- PII 마스킹 품질 baseline 측정 결과에 따라 3단계 마스킹 세부 조정 가능
