# 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/건 ```