hgzero/claude/data-flow-diagram.md

561 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Meeting Service 데이터 플로우 다이어그램
## 1. 전체 시스템 플로우
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ 회의 생명주기 데이터 플로우 │
└──────────────────────────────────────────────────────────────────────────────┘
Phase 1: 회의 준비 단계
════════════════════════════════════════════════════════════════════════════════
사용자가 회의 생성
┌─────────────────────────────────────────────────────────────┐
│ 1-1. CreateMeeting API │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO meetings ( │
│ meeting_id, title, purpose, scheduled_at, │
│ organizer_id, status, created_at │
│ ) │
│ VALUES (...) │
│ │
│ + INSERT INTO meeting_participants [V2] │
│ (meeting_id, user_id, invitation_status) │
│ FOR EACH participant │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ meetings: SCHEDULED status
✓ meeting_participants: PENDING status
Phase 2: 회의 진행 중
════════════════════════════════════════════════════════════════════════════════
회의 시작 (start_meeting API)
┌─────────────────────────────────────────────────────────────┐
│ 2-1. StartMeeting UseCase │
│ ────────────────────────────────────────────────────────────│
│ UPDATE meetings SET │
│ status = 'IN_PROGRESS', │
│ started_at = NOW() │
│ WHERE meeting_id = ? │
└─────────────────────────────────────────────────────────────┘
회의 중 회의록 작성
┌─────────────────────────────────────────────────────────────┐
│ 2-2. CreateMinutes API (회의 시작 후) │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO minutes ( │
│ id, meeting_id, user_id, title, status, │
│ created_by, version, created_at │
│ ) VALUES ( │
│ 'consolidated-minutes-1', 'meeting-001', │
│ NULL, [V3] ← AI 통합 회의록 표시 │
│ '2025년 1월 10일 회의', 'DRAFT', ... │
│ ) │
│ │
│ + 각 참석자별 회의록도 동시 생성: │
│ INSERT INTO minutes ( │
│ id, meeting_id, user_id, ... │
│ ) VALUES ( │
│ 'user-minutes-user1', 'meeting-001', │
│ 'user1@example.com', [V3] ← 참석자 구분 │
│ ... │
│ ) │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ meetings: IN_PROGRESS
✓ minutes (multiple records):
- 1개의 통합 회의록 (user_id=NULL)
- N개의 참석자별 회의록 (user_id=참석자ID)
✓ minutes_sections: 초기 섹션 생성
Phase 3: 회의록 작성 중
════════════════════════════════════════════════════════════════════════════════
사용자가 회의록 섹션 작성
┌─────────────────────────────────────────────────────────────┐
│ 3-1. UpdateMinutes API (여러 번) │
│ ────────────────────────────────────────────────────────────│
│ │
│ 각 섹션별로: │
│ INSERT INTO minutes_sections ( │
│ id, minutes_id, type, title, content, order │
│ ) VALUES ( │
│ 'section-1', 'consolidated-minutes-1', │
│ 'DISCUSSION', '신제품 기획 방향', │
│ '신제품의 주요 타겟은 20-30대 직장인으로 설정...', │
│ 1 │
│ ) │
│ │
│ UPDATE minutes_sections SET │
│ content = '...', │
│ updated_at = NOW() │
│ WHERE id = 'section-1' │
│ │
│ ★ 중요: content 컬럼에 실제 회의록 내용 저장! │
│ minutes 테이블에는 content가 없음 │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ minutes: status='DRAFT'
✓ minutes_sections: 사용자가 작성한 내용 축적
✓ 각 참석자가 자신의 회의록을 독립적으로 작성
Phase 4: 회의 종료
════════════════════════════════════════════════════════════════════════════════
회의 종료 (end_meeting API)
┌─────────────────────────────────────────────────────────────┐
│ 4-1. EndMeeting UseCase [V3 추가] │
│ ────────────────────────────────────────────────────────────│
│ UPDATE meetings SET │
│ status = 'COMPLETED', │
│ ended_at = NOW() [V3] ← 종료 시간 기록 │
│ WHERE meeting_id = ? │
│ │
│ ★ 중요: 회의 종료와 동시에 회의록 준비 시작 │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ meetings: status='COMPLETED', ended_at=현재시간
✓ minutes: 계속 DRAFT (사용자 추가 편집 가능)
Phase 5: 회의록 최종화
════════════════════════════════════════════════════════════════════════════════
사용자가 회의록 최종화 요청
┌─────────────────────────────────────────────────────────────┐
│ 5-1. FinalizeMinutes API │
│ ────────────────────────────────────────────────────────────│
│ UPDATE minutes SET │
│ status = 'FINALIZED', │
│ finalized_by = ?, │
│ finalized_at = NOW(), │
│ version = version + 1 │
│ WHERE id = 'consolidated-minutes-1' │
│ │
│ UPDATE minutes_sections SET │
│ locked = TRUE, │
│ locked_by = ?, │
│ verified = TRUE │
│ WHERE minutes_id = 'consolidated-minutes-1' │
│ │
│ ★ 중요: minutes_id를 통해 관련된 모든 섹션 잠금 │
└─────────────────────────────────────────────────────────────┘
Event 발생: MinutesAnalysisRequestEvent (Async)
DB State:
✓ minutes: status='FINALIZED'
✓ minutes_sections: locked=TRUE, verified=TRUE
✓ 모든 섹션이 수정 불가능
Phase 6: AI 분석 처리 (비동기 - MinutesAnalysisEventConsumer)
════════════════════════════════════════════════════════════════════════════════
이벤트 수신: MinutesAnalysisRequestEvent
┌─────────────────────────────────────────────────────────────┐
│ 6-1. 통합 회의록 조회 (user_id=NULL) │
│ ────────────────────────────────────────────────────────────│
│ SELECT m.*, GROUP_CONCAT(ms.content) AS full_content │
│ FROM minutes m │
│ LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id │
│ WHERE m.meeting_id = ? AND m.user_id IS NULL │
│ ORDER BY ms.order │
│ │
│ ★ 참석자별 회의록은 AI 분석 대상이 아님 │
│ user_id IS NOT NULL인 것들은 개인 기록용 │
└─────────────────────────────────────────────────────────────┘
AI Service 호출 (Claude API)
AI가 회의록 분석
- 안건별로 분리
- 요약 생성
- 결정사항 추출
- 보류사항 추출
- Todo 추출
┌─────────────────────────────────────────────────────────────┐
│ 6-2. agenda_sections 생성 [V3] │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO agenda_sections ( │
│ id, minutes_id, meeting_id, agenda_number, │
│ agenda_title, ai_summary_short, discussions, │
│ decisions, pending_items, opinions, todos [V4] │
│ ) VALUES ( │
│ 'uuid-1', 'consolidated-minutes-1', 'meeting-001', │
│ 1, '신제품 기획 방향성', │
│ '타겟 고객을 20-30대로 설정...', │
│ '신제품의 주요 타겟 고객층을 20-30대...', │
│ ["타겟 고객: 20-30대 직장인", "UI 개선 최우선"], │
│ [], │
│ [{"speaker": "김민준", "opinion": "..."}], │
│ [ │
│ {"title": "시장 조사", "assignee": "김민준", │
│ "dueDate": "2025-02-15", "priority": "HIGH"} │
│ ] [V4] │
│ ) │
│ │
│ FOR EACH agenda detected by AI │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 6-3. ai_summaries 저장 [V3] │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO ai_summaries ( │
│ id, meeting_id, summary_type, │
│ source_minutes_ids, result, processing_time_ms, │
│ model_version, keywords, statistics, created_at │
│ ) VALUES ( │
│ 'summary-uuid-1', 'meeting-001', 'CONSOLIDATED', │
│ ["consolidated-minutes-1"], │
│ {AI 응답 전체 JSON}, │
│ 2500, │
│ 'claude-3.5-sonnet', │
│ ["신제품", "타겟층", "UI개선"], │
│ {"participants": 5, "agendas": 3, "todos": 8}, │
│ NOW() │
│ ) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 6-4. todos 저장 [V3 확장] │
│ ────────────────────────────────────────────────────────────│
│ INSERT INTO todos ( │
│ todo_id, meeting_id, minutes_id, title, │
│ assignee_id, due_date, status, priority, │
│ extracted_by, section_reference, │
│ extraction_confidence, created_at │
│ ) VALUES ( │
│ 'todo-uuid-1', 'meeting-001', │
│ 'consolidated-minutes-1', '시장 조사 보고서 작성', │
│ 'user1@example.com', '2025-02-15', 'PENDING', 'HIGH', │
│ 'AI', '안건 1: 신제품 기획', [V3] │
│ 0.95, [V3] 신뢰도 │
│ NOW() │
│ ) │
│ │
│ ★ 주의: agenda_sections.todos (JSON)에도 동시 저장 │
│ 개별 관리 필요시만 todos 테이블에 저장 │
└─────────────────────────────────────────────────────────────┘
DB State:
✓ agenda_sections: AI 요약 결과 저장됨 (안건별)
✓ ai_summaries: AI 처리 결과 캐시
✓ todos: AI 추출 Todo (extracted_by='AI')
Phase 7: 회의록 및 분석 결과 조회
════════════════════════════════════════════════════════════════════════════════
Case 1: 통합 회의록 조회
─────────────────────────────────────────────────────────────
SELECT m.*, ms.*, ag.*, ai.* FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
LEFT JOIN agenda_sections ag ON m.id = ag.minutes_id
LEFT JOIN ai_summaries ai ON m.meeting_id = ai.meeting_id
WHERE m.meeting_id = 'meeting-001'
AND m.user_id IS NULL [V3]
ORDER BY ms.order
Case 2: 특정 사용자의 개인 회의록 조회
─────────────────────────────────────────────────────────────
SELECT m.*, ms.* FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
WHERE m.meeting_id = 'meeting-001'
AND m.user_id = 'user1@example.com' [V3]
ORDER BY ms.order
→ 개인이 작성한 회의록만 조회
→ AI 분석 결과(agenda_sections) 미포함
Case 3: AI 분석 결과만 조회
─────────────────────────────────────────────────────────────
SELECT ag.* FROM agenda_sections ag
WHERE ag.meeting_id = 'meeting-001'
ORDER BY ag.agenda_number
→ 안건별 AI 요약
→ todos JSON 필드 포함 (V4)
Case 4: 추출된 Todo 조회
─────────────────────────────────────────────────────────────
SELECT * FROM todos
WHERE meeting_id = 'meeting-001'
AND extracted_by = 'AI' [V3]
ORDER BY priority DESC, due_date ASC
또는 agenda_sections의 JSON todos 필드 사용
└──────────────────────────────────────────────────────────────────────────────┘
```
---
## 2. 상태 전이 다이어그램 (State Transition)
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ meetings 테이블 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
[생성]
├─────────────────────────┐
▼ │
SCHEDULED │ (시간 경과)
(scheduled_at 설정) │
│ │
│ start_meeting API │
▼ │
IN_PROGRESS │
(started_at 설정) │
│ │
│ end_meeting API [V3] │
▼ │
COMPLETED │
(ended_at 설정) [V3 추가] ├─────────────────────────┐
│ │ │
└─────────────────────────┘ │
│ 회의록 최종화 │
│ (finalize_minutes API) │
▼ │
minutes: FINALIZED │
(status='FINALIZED') │
│ │
│ (비동기 이벤트) │
▼ │
AI 분석 완료 │
agenda_sections 생성 │
ai_summaries 생성 │
todos 추출 │
│ │
└─────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ minutes 테이블 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
CREATE DRAFT
(minutes 생성) ───────────► (사용자 작성 중)
update_minutes API
(섹션 추가/수정)
finalize_minutes API
FINALIZED
(AI 분석 대기 중)
(비동기 처리 완료)
분석 완료 (상태 유지)
agenda_sections 생성됨
ai_summaries 생성됨
┌─────────────────────────────────────────────────────────────────────────────┐
│ minutes_sections 잠금 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
편집 가능
(locked=FALSE)
│ finalize_minutes
잠금됨
(locked=TRUE, locked_by=user_id)
└─────► 수정 불가
verified=TRUE
┌─────────────────────────────────────────────────────────────────────────────┐
│ todos 완료 상태 │
└─────────────────────────────────────────────────────────────────────────────┘
PENDING
(생성됨)
│ todo 완료 API
COMPLETED
(completed_at 설정)
```
---
## 3. 사용자별 회의록 데이터 구조
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ 1개 회의 (meetings: meeting-001)
│ ├─ 참석자: user1, user2, user3
└──────────────────────────────────────────────────────────────────────────────┘
회의 종료 → minutes 테이블에 여러 레코드 생성
┌─────────────────────────────────────────────────────────────────┐
│ minutes 테이블 (3개 레코드 생성) │
├─────────────────────────────────────────────────────────────────┤
│ id │ meeting_id │ user_id │ status
├─────────────────────┼─────────────┼──────────────────────┼────────
│ consol-minutes-001 │ meeting-001 │ NULL [V3] │ DRAFT
│ user1-minutes-001 │ meeting-001 │ user1@example.com │ DRAFT
│ user2-minutes-001 │ meeting-001 │ user2@example.com │ DRAFT
│ user3-minutes-001 │ meeting-001 │ user3@example.com │ DRAFT
└─────────────────────┴─────────────┴──────────────────────┴────────
↓ (각각 minutes_sections 참조)
┌─────────────────────────────────────────────────────────────────┐
│ minutes_sections 테이블 (4그룹 × N개 섹션) │
├─────────────────────────────────────────────────────────────────┤
│ id │ minutes_id │ type │ title │ content
├────────┼────────────────────┼─────────────┼──────────┼─────────
│ sec-1 │ consol-minutes-001 │ DISCUSSION │ 안건1 │ "AI가..."
│ sec-2 │ consol-minutes-001 │ DECISION │ 결정1 │ "..."
│ │ │ │ │
│ sec-3 │ user1-minutes-001 │ DISCUSSION │ 안건1 │ "사용자1..."
│ sec-4 │ user1-minutes-001 │ DISCUSSION │ 안건2 │ "..."
│ │ │ │ │
│ sec-5 │ user2-minutes-001 │ DISCUSSION │ 안건1 │ "사용자2..."
│ sec-6 │ user2-minutes-001 │ DECISION │ 결정1 │ "..."
│ │ │ │ │
│ sec-7 │ user3-minutes-001 │ DISCUSSION │ 안건1 │ "사용자3..."
└────────┴────────────────────┴─────────────┴──────────┴─────────
각 사용자가 독립적으로 작성:
- User1: consol-minutes-001의 sec-3, sec-4 편집
- User2: user2-minutes-001의 sec-5, sec-6 편집
- User3: user3-minutes-001의 sec-7 편집
AI 분석 (user_id=NULL인 것만):
┌─────────────────────────────────────────────────────────────────┐
│ agenda_sections 테이블 │
├─────────────────────────────────────────────────────────────────┤
│ id │ minutes_id │ meeting_id │ agenda_number
├────────┼────────────────────┼─────────────┼──────────────────
│ ag-1 │ consol-minutes-001 │ meeting-001 │ 1
│ ag-2 │ consol-minutes-001 │ meeting-001 │ 2
└────────┴────────────────────┴─────────────┴──────────────────
→ minutes_id를 통해 통합 회의록만 참조
→ user_id='user1@example.com'인 회의록은 참조하지 않음
```
---
## 4. 인덱스 활용 쿼리 예시
```sql
-- 쿼리 1: 특정 회의의 통합 회의록 조회 (V3 인덱스 활용)
SELECT * FROM minutes
WHERE meeting_id = 'meeting-001' AND user_id IS NULL
ORDER BY created_at DESC;
└─► 인덱스: idx_minutes_meeting_user (meeting_id, user_id)
-- 쿼리 2: 특정 사용자의 회의록 조회 (복합 인덱스 활용)
SELECT * FROM minutes
WHERE meeting_id = 'meeting-001' AND user_id = 'user1@example.com'
ORDER BY created_at DESC;
└─► 인덱스: idx_minutes_meeting_user (meeting_id, user_id)
-- 쿼리 3: 안건별 AI 요약 조회 (V3 인덱스 활용)
SELECT * FROM agenda_sections
WHERE meeting_id = 'meeting-001'
ORDER BY agenda_number ASC;
└─► 인덱스: idx_sections_meeting (meeting_id)
-- 쿼리 4: 특정 안건의 세부 요약 (복합 인덱스 활용)
SELECT * FROM agenda_sections
WHERE meeting_id = 'meeting-001' AND agenda_number = 1;
└─► 인덱스: idx_sections_agenda (meeting_id, agenda_number)
-- 쿼리 5: AI 추출 Todo 조회 (V3 인덱스 활용)
SELECT * FROM todos
WHERE meeting_id = 'meeting-001' AND extracted_by = 'AI'
ORDER BY priority DESC, due_date ASC;
└─► 인덱스: idx_todos_extracted (extracted_by)
└─► 인덱스: idx_todos_meeting (meeting_id)
-- 쿼리 6: 특정 회의의 모든 데이터 조회 (JOIN)
SELECT
m.*,
ms.content,
ag.ai_summary_short,
ag.todos,
ai.keywords
FROM minutes m
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
LEFT JOIN agenda_sections ag ON m.id = ag.minutes_id
LEFT JOIN ai_summaries ai ON m.meeting_id = ai.meeting_id
WHERE m.meeting_id = 'meeting-001' AND m.user_id IS NULL
ORDER BY ms.order ASC, ag.agenda_number ASC;
└─► 인덱스: idx_minutes_meeting_user (meeting_id, user_id)
└─► 인덱스: idx_sections_minutes (minutes_id)
```
---
## 5. 데이터 저장 크기 예상
```
1개 회의 (참석자 5명) 데이터 크기:
├─ meetings: ~500 bytes
├─ meeting_participants (5명): ~5 × 150 = 750 bytes
├─ minutes (6개: 1 통합 + 5 개인): ~6 × 400 = 2.4 KB
├─ minutes_sections (30개 섹션): ~30 × 2 KB = 60 KB
├─ agenda_sections (5개 안건): ~5 × 4 KB = 20 KB
├─ ai_summaries: ~10 KB
└─ todos (8개): ~8 × 800 bytes = 6.4 KB
Total: ~100 KB/회의
1년 (250개 회의) 예상:
└─► 25 MB + 인덱스 ~5 MB = ~30 MB
JSON 필드 데이터 크기:
├─ agenda_sections.decisions: ~200 bytes/건
├─ agenda_sections.opinions: ~300 bytes/건
├─ agenda_sections.todos: ~500 bytes/건 [V4]
├─ ai_summaries.result: ~5-10 KB/건
└─ ai_summaries.statistics: ~200 bytes/건
```