mirror of
https://github.com/hwanny1128/HGZero.git
synced 2026-06-13 07:09:09 +00:00
작업 중: Meeting AI 통합 개발 진행 상황 저장
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
# Meeting Service 데이터베이스 스키마 분석 문서
|
||||
|
||||
## 생성된 문서 목록
|
||||
|
||||
본 분석은 Meeting Service의 데이터베이스 스키마를 전방위적으로 분석한 결과입니다.
|
||||
|
||||
### 1. SCHEMA-REPORT-SUMMARY.md (메인 보고서)
|
||||
**파일**: `/Users/jominseo/HGZero/claude/SCHEMA-REPORT-SUMMARY.md`
|
||||
**내용**:
|
||||
- Executive Summary (핵심 발견사항)
|
||||
- 데이터베이스 구조 개요
|
||||
- 테이블별 상세 분석 (1.1~1.8)
|
||||
- 회의록 작성 플로우
|
||||
- 사용자별 회의록 구조
|
||||
- 마이그레이션 변경사항 (V2, V3, V4)
|
||||
- 성능 최적화 포인트
|
||||
- 핵심 질문 답변
|
||||
- 개발 시 주의사항
|
||||
|
||||
**빠르게 읽기**: Executive Summary부터 시작하세요.
|
||||
|
||||
---
|
||||
|
||||
### 2. database-schema-analysis.md (상세 분석)
|
||||
**파일**: `/Users/jominseo/HGZero/claude/database-schema-analysis.md`
|
||||
**내용**:
|
||||
- 마이그레이션 파일 현황 (V1~V4)
|
||||
- 각 테이블의 상세 구조
|
||||
- minutes vs agenda_sections 비교 분석
|
||||
- 회의록 작성 플로우에서의 테이블 사용
|
||||
- 사용자별 회의록 저장 구조
|
||||
- SQL 쿼리 패턴
|
||||
- 데이터 정규화 현황
|
||||
- 인덱스 최적화 방안
|
||||
- 데이터 저장 크기 예상
|
||||
|
||||
**상세 분석 필요시**: 이 문서를 참고하세요.
|
||||
|
||||
---
|
||||
|
||||
### 3. data-flow-diagram.md (흐름도)
|
||||
**파일**: `/Users/jominseo/HGZero/claude/data-flow-diagram.md`
|
||||
**내용**:
|
||||
- 전체 시스템 플로우 (7 Phase)
|
||||
- 상태 전이 다이어그램
|
||||
- 사용자별 회의록 데이터 구조
|
||||
- 인덱스 활용 쿼리 예시
|
||||
- 데이터 저장 크기 예상
|
||||
|
||||
**시각적 이해 필요시**: 이 문서를 참고하세요.
|
||||
|
||||
---
|
||||
|
||||
### 4. database-diagram.puml (ER 다이어그램)
|
||||
**파일**: `/Users/jominseo/HGZero/claude/database-diagram.puml`
|
||||
**포맷**: PlantUML (UML 형식)
|
||||
**내용**:
|
||||
- 모든 테이블과 관계
|
||||
- V2, V3, V4 마이그레이션 표시
|
||||
- 주요 필드 강조
|
||||
|
||||
**다이어그램 생성**:
|
||||
```bash
|
||||
# PlantUML로 PNG 생성
|
||||
plantuml database-diagram.puml -o database-diagram.png
|
||||
|
||||
# 또는 온라인 에디터
|
||||
https://www.plantuml.com/plantuml/uml/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 핵심 발견사항 한눈에 보기
|
||||
|
||||
### 1. Minutes 테이블 구조
|
||||
```
|
||||
잘못된 이해: minutes.content ← 회의록 내용
|
||||
올바른 구조: minutes_sections.content ← 회의록 내용
|
||||
minutes ← 메타데이터만 (title, status, version)
|
||||
```
|
||||
|
||||
### 2. 사용자별 회의록 (V3)
|
||||
```
|
||||
minutes.user_id = NULL → AI 통합 회의록
|
||||
minutes.user_id = 'user@.com' → 개인 회의록
|
||||
인덱스: idx_minutes_meeting_user(meeting_id, user_id)
|
||||
```
|
||||
|
||||
### 3. AI 분석 결과 저장 (V3, V4)
|
||||
```
|
||||
agenda_sections → 안건별 구조화된 요약
|
||||
└─ todos (JSON) → 추출된 Todo [V4]
|
||||
ai_summaries → 전체 AI 처리 결과 캐시
|
||||
todos 테이블 → 상세 관리 필요시만
|
||||
```
|
||||
|
||||
### 4. 정규화 (V2)
|
||||
```
|
||||
이전: meetings.participants = "user1,user2,user3"
|
||||
현재: meeting_participants (테이블, 복합PK)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 빠른 참조표
|
||||
|
||||
### 회의록 작성 플로우
|
||||
| 단계 | API | 데이터베이스 변화 |
|
||||
|------|-----|-----------------|
|
||||
| 1 | CreateMeeting | meetings INSERT |
|
||||
| 2 | StartMeeting | meetings.status = IN_PROGRESS |
|
||||
| 3 | CreateMinutes | minutes INSERT (통합 + 개인) |
|
||||
| 4 | UpdateMinutes | minutes_sections.content UPDATE |
|
||||
| 5 | EndMeeting | meetings.status = COMPLETED, ended_at [V3] |
|
||||
| 6 | FinalizeMinutes | minutes.status = FINALIZED, sections locked |
|
||||
| 7 | AI 분석 | agenda_sections, ai_summaries, todos INSERT |
|
||||
|
||||
### 테이블별 핵심 필드
|
||||
```
|
||||
meetings : meeting_id, status, ended_at [V3]
|
||||
minutes : id, meeting_id, user_id [V3], status
|
||||
minutes_sections : id, minutes_id, content ★
|
||||
agenda_sections : id, minutes_id, agenda_number, todos [V4]
|
||||
ai_summaries : id, meeting_id, result (JSON)
|
||||
todos : todo_id, extracted_by [V3], extraction_confidence [V3]
|
||||
```
|
||||
|
||||
### 인덱스
|
||||
```
|
||||
PRIMARY:
|
||||
idx_minutes_meeting_user (meeting_id, user_id) [V3]
|
||||
idx_sections_meeting (meeting_id) [V3]
|
||||
idx_sections_agenda (meeting_id, agenda_number) [V3]
|
||||
|
||||
SECONDARY:
|
||||
idx_todos_extracted (extracted_by) [V3]
|
||||
idx_todos_meeting (meeting_id) [V3]
|
||||
idx_summaries_type (meeting_id, summary_type) [V3]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 마이그레이션 타임라인
|
||||
|
||||
```
|
||||
V1 (초기)
|
||||
├─ meetings, minutes, minutes_sections
|
||||
├─ todos, meeting_analysis
|
||||
└─ JPA Hibernate로 자동 생성
|
||||
|
||||
V2 (2025-10-27)
|
||||
├─ meeting_participants 테이블 생성
|
||||
├─ meetings.participants (CSV) 마이그레이션
|
||||
└─ 정규화 완료
|
||||
|
||||
V3 (2025-10-28) ★ 주요 변경
|
||||
├─ minutes.user_id 추가 (사용자별 회의록)
|
||||
├─ agenda_sections 테이블 신규 (AI 요약)
|
||||
├─ ai_summaries 테이블 신규 (AI 결과 캐시)
|
||||
└─ todos 테이블 확장 (extracted_by, extraction_confidence)
|
||||
|
||||
V4 (2025-10-28)
|
||||
└─ agenda_sections.todos JSON 필드 추가
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 자주 묻는 질문
|
||||
|
||||
### Q: minutes 테이블에 content 필드가 있나요?
|
||||
**A**: 없습니다. 실제 회의록 내용은 `minutes_sections.content`에 저장됩니다.
|
||||
`minutes` 테이블은 메타데이터만 보유합니다 (title, status, version 등).
|
||||
|
||||
### Q: 사용자별 회의록은 어떻게 구분되나요?
|
||||
**A**: `minutes.user_id` 컬럼으로 구분됩니다.
|
||||
- NULL: AI 통합 회의록
|
||||
- NOT NULL: 개인별 회의록 (각 참석자마다 생성)
|
||||
|
||||
### Q: AI 분석은 모든 회의록을 처리하나요?
|
||||
**A**: 아니요. 통합 회의록(`user_id=NULL`)만 분석합니다.
|
||||
개인별 회의록(`user_id NOT NULL`)은 개인 기록용이며 AI 분석 대상이 아닙니다.
|
||||
|
||||
### Q: agenda_sections와 minutes_sections의 차이는?
|
||||
**A**:
|
||||
- `minutes_sections`: 사용자가 작성한 순차적 회의록 섹션
|
||||
- `agenda_sections`: AI가 분석한 안건별 구조화된 요약
|
||||
|
||||
### Q: Todo는 어디에 저장되나요?
|
||||
**A**: 두 곳에 저장 가능합니다.
|
||||
1. `agenda_sections.todos` (JSON): 안건별 요약의 일부
|
||||
2. `todos` 테이블: 상세 관리 필요시만
|
||||
|
||||
---
|
||||
|
||||
## 성능 최적화 팁
|
||||
|
||||
### 복합 인덱스 활용
|
||||
```sql
|
||||
-- 가장 중요한 쿼리 (V3)
|
||||
SELECT * FROM minutes
|
||||
WHERE meeting_id = ? AND user_id = ?;
|
||||
└─ 인덱스: idx_minutes_meeting_user (meeting_id, user_id)
|
||||
```
|
||||
|
||||
### 추천 추가 인덱스
|
||||
```sql
|
||||
CREATE INDEX idx_minutes_status_created
|
||||
ON minutes(status, created_at DESC);
|
||||
|
||||
CREATE INDEX idx_agenda_meeting_created
|
||||
ON agenda_sections(meeting_id, created_at DESC);
|
||||
```
|
||||
|
||||
### 쿼리 패턴
|
||||
```sql
|
||||
-- 통합 회의록 조회 (가장 흔함)
|
||||
SELECT m.*, ms.* FROM minutes m
|
||||
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
|
||||
WHERE m.meeting_id = ? AND m.user_id IS NULL
|
||||
|
||||
-- 개인 회의록 조회
|
||||
SELECT m.*, ms.* FROM minutes m
|
||||
LEFT JOIN minutes_sections ms ON m.id = ms.minutes_id
|
||||
WHERE m.meeting_id = ? AND m.user_id = ?
|
||||
|
||||
-- AI 분석 결과 조회
|
||||
SELECT * FROM agenda_sections
|
||||
WHERE meeting_id = ? ORDER BY agenda_number
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 문서 읽기 순서 추천
|
||||
|
||||
### 1단계: 빠른 이해 (5분)
|
||||
→ `SCHEMA-REPORT-SUMMARY.md`의 Executive Summary만 읽기
|
||||
|
||||
### 2단계: 구조 이해 (15분)
|
||||
→ `database-diagram.puml` (다이어그램 확인)
|
||||
→ `data-flow-diagram.md`의 Phase 1~7 읽기
|
||||
|
||||
### 3단계: 상세 이해 (30분)
|
||||
→ `SCHEMA-REPORT-SUMMARY.md` 전체 읽기
|
||||
→ `database-schema-analysis.md`의 핵심 섹션 읽기
|
||||
|
||||
### 4단계: 개발 참고 (필요시)
|
||||
→ `database-schema-analysis.md`의 쿼리 예시
|
||||
→ `data-flow-diagram.md`의 인덱스 활용 섹션
|
||||
|
||||
---
|
||||
|
||||
## 개발 체크리스트
|
||||
|
||||
회의록 작성 기능 개발시:
|
||||
|
||||
### 데이터 저장
|
||||
- [ ] 회의록 내용은 `minutes_sections.content`에 저장
|
||||
- [ ] `minutes` 테이블에는 메타데이터만 저장 (title, status)
|
||||
- [ ] 회의 종료시 `minutes.user_id` 값 확인 (NULL vs 사용자ID)
|
||||
|
||||
### AI 분석
|
||||
- [ ] 통합 회의록(`user_id=NULL`)만 AI 분석 대상으로 처리
|
||||
- [ ] `agenda_sections`은 통합 회의록에만 생성
|
||||
- [ ] `ai_summaries`에 전체 결과 캐싱
|
||||
|
||||
### 쿼리 성능
|
||||
- [ ] 복합 인덱스 활용: `idx_minutes_meeting_user`
|
||||
- [ ] 조회시 `WHERE meeting_id AND user_id` 조건 사용
|
||||
- [ ] 기존 인덱스 모두 생성 확인
|
||||
|
||||
### 데이터 무결성
|
||||
- [ ] 회의 종료시 `ended_at` 기록 (V3)
|
||||
- [ ] 최종화시 `minutes_sections` locked 처리
|
||||
- [ ] AI 추출 Todo의 `extraction_confidence` 값 확인
|
||||
|
||||
---
|
||||
|
||||
## 관련 파일 위치
|
||||
|
||||
**마이그레이션**:
|
||||
```
|
||||
/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/
|
||||
├─ V2__create_meeting_participants_table.sql
|
||||
├─ V3__add_meeting_end_support.sql
|
||||
└─ V4__add_todos_to_agenda_sections.sql
|
||||
```
|
||||
|
||||
**엔티티**:
|
||||
```
|
||||
/Users/jominseo/HGZero/meeting/src/main/java/.../entity/
|
||||
├─ MeetingEntity.java
|
||||
├─ MinutesEntity.java
|
||||
├─ MinutesSectionEntity.java
|
||||
├─ AgendaSectionEntity.java [V3]
|
||||
├─ TodoEntity.java
|
||||
└─ MeetingParticipantEntity.java [V2]
|
||||
```
|
||||
|
||||
**서비스**:
|
||||
```
|
||||
/Users/jominseo/HGZero/meeting/src/main/java/.../service/
|
||||
├─ MinutesService.java
|
||||
├─ MinutesSectionService.java
|
||||
└─ MinutesAnalysisEventConsumer.java (비동기 AI 분석)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 지원
|
||||
|
||||
이 문서에 대한 추가 질문이나 불명확한 부분이 있으면:
|
||||
|
||||
1. `SCHEMA-REPORT-SUMMARY.md`의 "핵심 질문 답변" 섹션 확인
|
||||
2. `database-schema-analysis.md`에서 상세 내용 검색
|
||||
3. `data-flow-diagram.md`에서 흐름도 재확인
|
||||
|
||||
---
|
||||
|
||||
**문서 작성일**: 2025-10-28
|
||||
**분석 대상**: Meeting Service (feat/meeting-ai 브랜치)
|
||||
**마이그레이션 버전**: V1~V4
|
||||
**상태**: 완료 및 검증됨
|
||||
@@ -0,0 +1,607 @@
|
||||
# Meeting Service 데이터베이스 스키마 분석 최종 보고서
|
||||
|
||||
**작성일**: 2025-10-28
|
||||
**분석 대상**: Meeting Service (feat/meeting-ai 브랜치)
|
||||
**분석 범위**: 마이그레이션 V1~V4, 엔티티 구조, 데이터 플로우
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
### 핵심 발견사항
|
||||
|
||||
1. **minutes 테이블에 content 필드가 없음**
|
||||
- 실제 회의록 내용은 `minutes_sections.content`에 저장
|
||||
- minutes 테이블은 메타데이터만 보유 (title, status, version 등)
|
||||
|
||||
2. **사용자별 회의록 완벽하게 지원 (V3)**
|
||||
- `minutes.user_id = NULL`: AI 통합 회의록
|
||||
- `minutes.user_id = 참석자ID`: 개인별 회의록
|
||||
- 인덱스: `idx_minutes_meeting_user` (meeting_id, user_id)
|
||||
|
||||
3. **AI 분석 결과 구조화 저장 (V3, V4)**
|
||||
- `agenda_sections`: 안건별 구조화된 요약
|
||||
- `ai_summaries`: AI 처리 결과 캐싱
|
||||
- `todos` (V4): 각 안건의 JSON으로 저장
|
||||
|
||||
4. **정규화 완료 (V2)**
|
||||
- `meetings.participants` (CSV) → `meeting_participants` (테이블)
|
||||
- 복합 PK: (meeting_id, user_id)
|
||||
|
||||
---
|
||||
|
||||
## 데이터베이스 구조 개요
|
||||
|
||||
### 테이블 분류
|
||||
|
||||
**핵심 테이블** (V1):
|
||||
- `meetings`: 회의 기본 정보
|
||||
- `minutes`: 회의록 메타데이터
|
||||
- `minutes_sections`: 회의록 섹션 (실제 내용)
|
||||
|
||||
**참석자 관리** (V2):
|
||||
- `meeting_participants`: 회의 참석자 정보
|
||||
|
||||
**AI 분석** (V3):
|
||||
- `agenda_sections`: 안건별 AI 요약
|
||||
- `ai_summaries`: AI 처리 결과 캐시
|
||||
- `todos`: Todo 아이템 (expanded)
|
||||
|
||||
---
|
||||
|
||||
## 1. 핵심 테이블별 상세 분석
|
||||
|
||||
### 1.1 meetings (회의 기본 정보)
|
||||
|
||||
**구성**:
|
||||
- PK: meeting_id (VARCHAR(50))
|
||||
- 주요 필드: title, purpose, description
|
||||
- 상태: SCHEDULED → IN_PROGRESS → COMPLETED
|
||||
- 시간: scheduled_at, started_at, ended_at (V3)
|
||||
|
||||
**중요 변경**:
|
||||
- V3에서 `ended_at` 추가
|
||||
- 회의 정확한 종료 시간 기록
|
||||
|
||||
```sql
|
||||
-- 조회 예시
|
||||
SELECT * FROM meetings
|
||||
WHERE status = 'COMPLETED'
|
||||
AND ended_at >= NOW() - INTERVAL '7 days'
|
||||
ORDER BY ended_at DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.2 minutes (회의록 메타데이터)
|
||||
|
||||
**구성**:
|
||||
```
|
||||
minutes_id (PK)
|
||||
├─ meeting_id (FK)
|
||||
├─ user_id (V3) ← NULL: AI 통합 회의록 / NOT NULL: 개인 회의록
|
||||
├─ title
|
||||
├─ status (DRAFT, FINALIZED)
|
||||
├─ version
|
||||
├─ created_by, finalized_by
|
||||
└─ created_at, finalized_at
|
||||
```
|
||||
|
||||
**중요**:
|
||||
- **content 필드 없음** → minutes_sections에 저장
|
||||
- 메타데이터만 관리 (생성자, 확정자, 버전 등)
|
||||
|
||||
**쿼리 패턴**:
|
||||
```sql
|
||||
-- AI 통합 회의록
|
||||
SELECT * FROM minutes
|
||||
WHERE meeting_id = ? AND user_id IS NULL;
|
||||
|
||||
-- 특정 사용자의 회의록
|
||||
SELECT * FROM minutes
|
||||
WHERE meeting_id = ? AND user_id = ?;
|
||||
|
||||
-- 복합 인덱스 활용: idx_minutes_meeting_user
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 minutes_sections (회의록 섹션 - 실제 내용)
|
||||
|
||||
**구성**:
|
||||
```
|
||||
section_id (PK)
|
||||
├─ minutes_id (FK) ← 어느 회의록에 속하는가
|
||||
├─ type (AGENDA, DISCUSSION, DECISION, ACTION_ITEM)
|
||||
├─ title
|
||||
├─ content ← ★ 실제 회의록 내용
|
||||
├─ order
|
||||
├─ verified (검증 완료)
|
||||
├─ locked (수정 불가)
|
||||
└─ locked_by
|
||||
```
|
||||
|
||||
**핵심 특성**:
|
||||
- **content**: 사용자가 작성한 실제 내용
|
||||
- **locked**: finalize_minutes 호출시 잠금
|
||||
- **verified**: 확정시 TRUE로 설정
|
||||
|
||||
**데이터 흐름**:
|
||||
```
|
||||
1. CreateMinutes → minutes_sections 초기 생성
|
||||
2. UpdateMinutes → content 저장 (여러 번)
|
||||
3. FinalizeMinutes → locked=TRUE, verified=TRUE
|
||||
4. (locked 상태에서 수정 불가)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.4 agenda_sections (AI 요약 - V3)
|
||||
|
||||
**구성**:
|
||||
```
|
||||
id (PK, UUID)
|
||||
├─ minutes_id (FK) ← 통합 회의록만 (user_id=NULL)
|
||||
├─ meeting_id (FK)
|
||||
├─ agenda_number (1, 2, 3...)
|
||||
├─ agenda_title
|
||||
├─ ai_summary_short (1줄 요약)
|
||||
├─ discussions (3-5문장 논의)
|
||||
├─ decisions (JSON 배열)
|
||||
├─ pending_items (JSON 배열)
|
||||
├─ opinions (JSON 배열: {speaker, opinion})
|
||||
└─ todos (JSON 배열 [V4])
|
||||
```
|
||||
|
||||
**V4 추가 사항**:
|
||||
- `todos` JSON 필드 추가
|
||||
- 안건별 추출된 Todo 저장
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "시장 조사 보고서 작성",
|
||||
"assignee": "김민준",
|
||||
"dueDate": "2025-02-15",
|
||||
"description": "20-30대 타겟 시장 조사",
|
||||
"priority": "HIGH"
|
||||
}
|
||||
```
|
||||
|
||||
**중요**:
|
||||
- **통합 회의록만 분석** (user_id=NULL인 것)
|
||||
- 참석자별 회의록(user_id NOT NULL)은 AI 분석 대상 아님
|
||||
- minutes_id로 통합 회의록 참조
|
||||
|
||||
---
|
||||
|
||||
### 1.5 minutes_sections vs agenda_sections
|
||||
|
||||
| 항목 | minutes_sections | agenda_sections |
|
||||
|------|-----------------|-----------------|
|
||||
| **용도** | 사용자 작성 | AI 요약 |
|
||||
| **모든 회의록** | ✓ 통합 + 개인 | ✗ 통합만 |
|
||||
| **구조** | 순차적 섹션 | 안건별 구조화 |
|
||||
| **내용 저장** | content (TEXT) | JSON 필드들 |
|
||||
| **관계** | 1:N (minutes과) | N:1 (minutes과) |
|
||||
| **목적** | 기록 | 분석/요약 |
|
||||
|
||||
**생성 흐름**:
|
||||
```
|
||||
회의 시작
|
||||
↓
|
||||
minutes 생성 (통합 + 개인)
|
||||
↓
|
||||
minutes_sections 생성 (4개 그룹)
|
||||
↓
|
||||
사용자 작성 중...
|
||||
↓
|
||||
회의 종료 → FinalizeMinutes
|
||||
↓
|
||||
minutes_sections locked
|
||||
↓
|
||||
AI 분석 (비동기)
|
||||
↓
|
||||
agenda_sections 생성 (통합 회의록 기반)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.6 ai_summaries (AI 처리 결과 - V3)
|
||||
|
||||
**구성**:
|
||||
```
|
||||
id (PK, UUID)
|
||||
├─ meeting_id (FK)
|
||||
├─ summary_type (CONSOLIDATED, TODO_EXTRACTION)
|
||||
├─ source_minutes_ids (JSON: 사용된 회의록 ID 배열)
|
||||
├─ result (JSON: AI 응답 전체)
|
||||
├─ processing_time_ms (처리 시간)
|
||||
├─ model_version (claude-3.5-sonnet)
|
||||
├─ keywords (JSON: 키워드 배열)
|
||||
└─ statistics (JSON: {participants, agendas, todos})
|
||||
```
|
||||
|
||||
**용도**:
|
||||
- AI 처리 결과 캐싱
|
||||
- 재처리 필요시 참조
|
||||
- 성능 통계 기록
|
||||
|
||||
---
|
||||
|
||||
### 1.7 todos (Todo 아이템)
|
||||
|
||||
**기본 구조**:
|
||||
```
|
||||
todo_id (PK)
|
||||
├─ meeting_id (FK)
|
||||
├─ minutes_id (FK)
|
||||
├─ title
|
||||
├─ description
|
||||
├─ assignee_id
|
||||
├─ due_date
|
||||
├─ status (PENDING, COMPLETED)
|
||||
├─ priority (HIGH, MEDIUM, LOW)
|
||||
└─ completed_at
|
||||
```
|
||||
|
||||
**V3 추가 필드**:
|
||||
```
|
||||
├─ extracted_by (AI / MANUAL) ← AI 자동 추출 vs 수동
|
||||
├─ section_reference (안건 참조)
|
||||
└─ extraction_confidence (0.00~1.00) ← AI 신뢰도
|
||||
```
|
||||
|
||||
**저장 전략**:
|
||||
1. `agenda_sections.todos` (JSON): 간단한 Todo, 기본 저장 위치
|
||||
2. `todos` 테이블: 상세 관리 필요시만 추가 저장
|
||||
|
||||
---
|
||||
|
||||
### 1.8 meeting_participants (참석자 관리 - V2)
|
||||
|
||||
**구성**:
|
||||
```
|
||||
PK: (meeting_id, user_id)
|
||||
├─ invitation_status (PENDING, ACCEPTED, DECLINED)
|
||||
├─ attended (BOOLEAN)
|
||||
└─ created_at, updated_at
|
||||
```
|
||||
|
||||
**V2 개선**:
|
||||
- 이전: meetings.participants (CSV 문자열)
|
||||
- 현재: 별도 테이블 (정규화)
|
||||
- 복합 PK로 중복 방지
|
||||
|
||||
---
|
||||
|
||||
## 2. 회의록 작성 플로우 (전체)
|
||||
|
||||
### 단계별 데이터 변화
|
||||
|
||||
```
|
||||
PHASE 1: 회의 준비
|
||||
═════════════════════════════════════════════
|
||||
1. CreateMeeting
|
||||
→ INSERT meetings (status='SCHEDULED')
|
||||
→ INSERT meeting_participants (5명)
|
||||
|
||||
PHASE 2: 회의 진행
|
||||
═════════════════════════════════════════════
|
||||
2. StartMeeting
|
||||
→ UPDATE meetings SET status='IN_PROGRESS'
|
||||
|
||||
3. CreateMinutes (회의 중)
|
||||
→ INSERT minutes (user_id=NULL) × 1 (통합)
|
||||
→ INSERT minutes (user_id=user_id) × 5 (개인)
|
||||
→ INSERT minutes_sections (초기 생성)
|
||||
|
||||
4. UpdateMinutes (여러 번)
|
||||
→ UPDATE minutes_sections SET content='...'
|
||||
|
||||
PHASE 3: 회의 종료
|
||||
═════════════════════════════════════════════
|
||||
5. EndMeeting
|
||||
→ UPDATE meetings SET
|
||||
status='COMPLETED',
|
||||
ended_at=NOW() [V3]
|
||||
|
||||
PHASE 4: 회의록 최종화
|
||||
═════════════════════════════════════════════
|
||||
6. FinalizeMinutes
|
||||
→ UPDATE minutes SET
|
||||
status='FINALIZED'
|
||||
→ UPDATE minutes_sections SET
|
||||
locked=TRUE,
|
||||
verified=TRUE
|
||||
|
||||
PHASE 5: AI 분석 (비동기)
|
||||
═════════════════════════════════════════════
|
||||
7. MinutesAnalysisEventConsumer
|
||||
→ Read minutes (user_id=NULL)
|
||||
→ Read minutes_sections
|
||||
→ Call AI Service
|
||||
→ INSERT agenda_sections [V3]
|
||||
→ INSERT ai_summaries [V3]
|
||||
→ INSERT todos [V3 확장]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 사용자별 회의록 구조
|
||||
|
||||
### 데이터 분리 방식
|
||||
|
||||
**1개 회의 (참석자 5명)**:
|
||||
|
||||
```
|
||||
meetings: 1개
|
||||
├─ meeting_id = 'meeting-001'
|
||||
└─ status = COMPLETED
|
||||
|
||||
meeting_participants: 5개
|
||||
├─ (meeting-001, user1@example.com)
|
||||
├─ (meeting-001, user2@example.com)
|
||||
├─ (meeting-001, user3@example.com)
|
||||
├─ (meeting-001, user4@example.com)
|
||||
└─ (meeting-001, user5@example.com)
|
||||
|
||||
minutes: 6개 [V3]
|
||||
├─ (id=consol-1, meeting_id=meeting-001, user_id=NULL)
|
||||
│ → 통합 회의록 (AI 분석 대상)
|
||||
├─ (id=user1-min, meeting_id=meeting-001, user_id=user1@example.com)
|
||||
│ → 사용자1 개인 회의록
|
||||
├─ (id=user2-min, meeting_id=meeting-001, user_id=user2@example.com)
|
||||
│ → 사용자2 개인 회의록
|
||||
├─ ... (user3, user4, user5)
|
||||
└─
|
||||
|
||||
minutes_sections: 수십 개 (6개 회의록 × N개 섹션)
|
||||
├─ Group 1: consol-1의 섹션들 (AI 작성)
|
||||
├─ Group 2: user1-min의 섹션들 (사용자1 작성)
|
||||
├─ Group 3: user2-min의 섹션들 (사용자2 작성)
|
||||
└─ ... (user3, user4, user5)
|
||||
|
||||
agenda_sections: 5개 [V3]
|
||||
├─ (id=ag-1, minutes_id=consol-1, agenda_number=1)
|
||||
├─ (id=ag-2, minutes_id=consol-1, agenda_number=2)
|
||||
└─ ... (3, 4, 5)
|
||||
```
|
||||
|
||||
**핵심**:
|
||||
- 참석자별 회의록은 minutes.user_id로 구분
|
||||
- 인덱스 활용: `idx_minutes_meeting_user`
|
||||
- AI 분석: 통합 회의록만 (user_id=NULL)
|
||||
|
||||
---
|
||||
|
||||
## 4. 마이그레이션 변경사항 요약
|
||||
|
||||
### V2 (2025-10-27)
|
||||
|
||||
```sql
|
||||
-- meeting_participants 테이블 생성
|
||||
CREATE TABLE meeting_participants (
|
||||
meeting_id, user_id (복합 PK),
|
||||
invitation_status, attended
|
||||
)
|
||||
|
||||
-- 데이터 마이그레이션
|
||||
SELECT TRIM(participant) FROM meetings.participants (CSV)
|
||||
→ INSERT INTO meeting_participants
|
||||
|
||||
-- meetings.participants 컬럼 삭제
|
||||
ALTER TABLE meetings DROP COLUMN participants
|
||||
```
|
||||
|
||||
**영향**: 정규화 완료, 중복 데이터 제거
|
||||
|
||||
---
|
||||
|
||||
### V3 (2025-10-28)
|
||||
|
||||
#### 3-1. minutes 테이블 확장
|
||||
```sql
|
||||
ALTER TABLE minutes ADD COLUMN user_id VARCHAR(100);
|
||||
CREATE INDEX idx_minutes_meeting_user ON minutes(meeting_id, user_id);
|
||||
```
|
||||
|
||||
**의미**: 사용자별 회의록 지원
|
||||
|
||||
---
|
||||
|
||||
#### 3-2. agenda_sections 테이블 신규
|
||||
```sql
|
||||
CREATE TABLE agenda_sections (
|
||||
id, minutes_id, meeting_id,
|
||||
agenda_number, agenda_title,
|
||||
ai_summary_short, discussions,
|
||||
decisions (JSON),
|
||||
pending_items (JSON),
|
||||
opinions (JSON)
|
||||
)
|
||||
```
|
||||
|
||||
**의미**: AI 요약을 구조화된 형식으로 저장
|
||||
|
||||
---
|
||||
|
||||
#### 3-3. ai_summaries 테이블 신규
|
||||
```sql
|
||||
CREATE TABLE ai_summaries (
|
||||
id, meeting_id, summary_type,
|
||||
source_minutes_ids (JSON),
|
||||
result (JSON),
|
||||
processing_time_ms,
|
||||
model_version,
|
||||
keywords (JSON),
|
||||
statistics (JSON)
|
||||
)
|
||||
```
|
||||
|
||||
**의미**: AI 처리 결과 캐싱
|
||||
|
||||
---
|
||||
|
||||
#### 3-4. todos 테이블 확장
|
||||
```sql
|
||||
ALTER TABLE todos ADD COLUMN extracted_by VARCHAR(50) DEFAULT 'AI';
|
||||
ALTER TABLE todos ADD COLUMN section_reference VARCHAR(200);
|
||||
ALTER TABLE todos ADD COLUMN extraction_confidence DECIMAL(3,2);
|
||||
```
|
||||
|
||||
**의미**: AI 자동 추출 추적
|
||||
|
||||
---
|
||||
|
||||
### V4 (2025-10-28)
|
||||
|
||||
```sql
|
||||
ALTER TABLE agenda_sections ADD COLUMN todos JSON;
|
||||
```
|
||||
|
||||
**의미**: 안건별 Todo를 JSON으로 저장
|
||||
|
||||
---
|
||||
|
||||
## 5. 성능 최적화
|
||||
|
||||
### 현재 인덱스
|
||||
|
||||
```
|
||||
meetings:
|
||||
├─ PK: meeting_id
|
||||
|
||||
minutes:
|
||||
├─ PK: id
|
||||
└─ idx_minutes_meeting_user (meeting_id, user_id) [V3]
|
||||
|
||||
minutes_sections:
|
||||
├─ PK: id
|
||||
└─ (minutes_id로 FK 지원)
|
||||
|
||||
agenda_sections: [V3]
|
||||
├─ PK: id
|
||||
├─ idx_sections_meeting (meeting_id)
|
||||
├─ idx_sections_agenda (meeting_id, agenda_number)
|
||||
└─ idx_sections_minutes (minutes_id)
|
||||
|
||||
ai_summaries: [V3]
|
||||
├─ PK: id
|
||||
├─ idx_summaries_meeting (meeting_id)
|
||||
├─ idx_summaries_type (meeting_id, summary_type)
|
||||
└─ idx_summaries_created (created_at)
|
||||
|
||||
todos:
|
||||
├─ PK: todo_id
|
||||
├─ idx_todos_extracted (extracted_by) [V3]
|
||||
└─ idx_todos_meeting (meeting_id) [V3]
|
||||
|
||||
meeting_participants: [V2]
|
||||
├─ PK: (meeting_id, user_id)
|
||||
├─ idx_user_id (user_id)
|
||||
└─ idx_invitation_status (invitation_status)
|
||||
```
|
||||
|
||||
### 추천 추가 인덱스
|
||||
|
||||
```sql
|
||||
-- 자주 조회하는 패턴
|
||||
CREATE INDEX idx_minutes_status_created
|
||||
ON minutes(status, created_at DESC);
|
||||
|
||||
CREATE INDEX idx_agenda_meeting_created
|
||||
ON agenda_sections(meeting_id, created_at DESC);
|
||||
|
||||
CREATE INDEX idx_todos_meeting_assignee
|
||||
ON todos(meeting_id, assignee_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 핵심 질문 답변
|
||||
|
||||
### Q1: minutes 테이블에 content 필드가 있는가?
|
||||
**A**: **없음**
|
||||
- minutes: 메타데이터만 (title, status, version 등)
|
||||
- 실제 내용: minutes_sections.content
|
||||
|
||||
### Q2: minutes_section과 agenda_sections의 차이점?
|
||||
**A**:
|
||||
| 항목 | minutes_sections | agenda_sections |
|
||||
|------|-----------------|-----------------|
|
||||
| 목적 | 사용자 작성 | AI 요약 |
|
||||
| 모든 회의록 | O | X (통합만) |
|
||||
| 내용 저장 | content (TEXT) | JSON |
|
||||
|
||||
### Q3: 사용자별 회의록 저장 방식?
|
||||
**A**:
|
||||
- minutes.user_id로 구분
|
||||
- NULL: AI 통합회의록
|
||||
- NOT NULL: 개인별 회의록
|
||||
- 인덱스: idx_minutes_meeting_user
|
||||
|
||||
### Q4: V3, V4 주요 변경?
|
||||
**A**:
|
||||
- V3: user_id, agenda_sections, ai_summaries, todos 확장
|
||||
- V4: agenda_sections.todos JSON 추가
|
||||
|
||||
---
|
||||
|
||||
## 7. 개발 시 주의사항
|
||||
|
||||
### Do's ✓
|
||||
- minutes_sections.content에 실제 내용 저장
|
||||
- AI 분석시 user_id=NULL인 minutes만 처리
|
||||
- agenda_sections.todos와 todos 테이블 동시 저장 (필요시)
|
||||
- 복합 인덱스 활용 (meeting_id, user_id)
|
||||
|
||||
### Don'ts ✗
|
||||
- minutes 테이블에 content 저장 (없음)
|
||||
- 참석자별 회의록(user_id NOT NULL)을 AI 분석 (통합만)
|
||||
- agenda_sections를 모든 minutes에 생성 (통합만)
|
||||
- 인덱스 무시한 풀 스캔
|
||||
|
||||
---
|
||||
|
||||
## 8. 파일 위치 및 참조
|
||||
|
||||
**마이그레이션 파일**:
|
||||
- `/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/V2__*.sql`
|
||||
- `/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/V3__*.sql`
|
||||
- `/Users/jominseo/HGZero/meeting/src/main/resources/db/migration/V4__*.sql`
|
||||
|
||||
**엔티티**:
|
||||
- `MeetingEntity`, `MinutesEntity`, `MinutesSectionEntity`
|
||||
- `AgendaSectionEntity`, `TodoEntity`, `MeetingParticipantEntity`
|
||||
|
||||
**서비스**:
|
||||
- `MinutesService`, `MinutesSectionService`
|
||||
- `MinutesAnalysisEventConsumer` (비동기)
|
||||
|
||||
---
|
||||
|
||||
## 9. 결론
|
||||
|
||||
### 핵심 설계 원칙
|
||||
1. **메타데이터 vs 내용 분리**: minutes (메타) vs minutes_sections (내용)
|
||||
2. **사용자별 격리**: user_id 컬럼으로 개인 회의록 관리
|
||||
3. **AI 결과 구조화**: JSON으로 유연성과 성능 확보
|
||||
4. **정규화 완료**: 참석자 정보 테이블화
|
||||
|
||||
### 검증 사항
|
||||
- V3, V4 마이그레이션 정상 적용
|
||||
- 모든 인덱스 생성됨
|
||||
- 관계 설정 정상 (FK, 1:N)
|
||||
|
||||
### 다음 단계
|
||||
- 성능 모니터링 (쿼리 실행 계획)
|
||||
- 추가 인덱스 검토
|
||||
- AI 분석 결과 검증
|
||||
- 참석자별 회의록 사용성 테스트
|
||||
|
||||
---
|
||||
|
||||
**문서 정보**:
|
||||
- 작성자: Database Architecture Analysis
|
||||
- 대상 서비스: Meeting Service (AI 통합 회의록)
|
||||
- 최종 버전: 2025-10-28
|
||||
@@ -0,0 +1,560 @@
|
||||
# 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/건
|
||||
```
|
||||
@@ -0,0 +1,130 @@
|
||||
@startuml Meeting Service Database Schema
|
||||
!theme mono
|
||||
|
||||
'=== Core Tables ===
|
||||
entity "meetings" {
|
||||
* **meeting_id : VARCHAR(50)
|
||||
--
|
||||
title : VARCHAR(200) NOT NULL
|
||||
purpose : VARCHAR(500)
|
||||
description : TEXT
|
||||
scheduled_at : TIMESTAMP NOT NULL
|
||||
started_at : TIMESTAMP
|
||||
ended_at : TIMESTAMP [V3]
|
||||
status : VARCHAR(20) NOT NULL
|
||||
organizer_id : VARCHAR(50) NOT NULL
|
||||
created_at : TIMESTAMP
|
||||
updated_at : TIMESTAMP
|
||||
template_id : VARCHAR(50)
|
||||
}
|
||||
|
||||
entity "meeting_participants" {
|
||||
* **meeting_id : VARCHAR(50) [FK]
|
||||
* **user_id : VARCHAR(100)
|
||||
--
|
||||
invitation_status : VARCHAR(20)
|
||||
attended : BOOLEAN
|
||||
created_at : TIMESTAMP
|
||||
updated_at : TIMESTAMP
|
||||
}
|
||||
|
||||
entity "minutes" {
|
||||
* **id : VARCHAR(50)
|
||||
--
|
||||
meeting_id : VARCHAR(50) [FK] NOT NULL
|
||||
user_id : VARCHAR(100) [V3]
|
||||
title : VARCHAR(200) NOT NULL
|
||||
status : VARCHAR(20) NOT NULL
|
||||
version : INT NOT NULL
|
||||
created_by : VARCHAR(50) NOT NULL
|
||||
finalized_by : VARCHAR(50)
|
||||
finalized_at : TIMESTAMP
|
||||
created_at : TIMESTAMP
|
||||
updated_at : TIMESTAMP
|
||||
}
|
||||
|
||||
entity "minutes_sections" {
|
||||
* **id : VARCHAR(50)
|
||||
--
|
||||
minutes_id : VARCHAR(50) [FK] NOT NULL
|
||||
type : VARCHAR(50) NOT NULL
|
||||
title : VARCHAR(200) NOT NULL
|
||||
**content : TEXT
|
||||
order : INT
|
||||
verified : BOOLEAN
|
||||
locked : BOOLEAN
|
||||
locked_by : VARCHAR(50)
|
||||
created_at : TIMESTAMP
|
||||
updated_at : TIMESTAMP
|
||||
}
|
||||
|
||||
'=== V3 New Tables ===
|
||||
entity "agenda_sections" {
|
||||
* **id : VARCHAR(36)
|
||||
--
|
||||
minutes_id : VARCHAR(36) [FK] NOT NULL
|
||||
meeting_id : VARCHAR(50) [FK] NOT NULL
|
||||
agenda_number : INT NOT NULL
|
||||
agenda_title : VARCHAR(200) NOT NULL
|
||||
ai_summary_short : TEXT
|
||||
discussions : TEXT
|
||||
decisions : JSON
|
||||
pending_items : JSON
|
||||
opinions : JSON
|
||||
**todos : JSON [V4]
|
||||
created_at : TIMESTAMP
|
||||
updated_at : TIMESTAMP
|
||||
}
|
||||
|
||||
entity "ai_summaries" {
|
||||
* **id : VARCHAR(36)
|
||||
--
|
||||
meeting_id : VARCHAR(50) [FK] NOT NULL
|
||||
summary_type : VARCHAR(50) NOT NULL
|
||||
source_minutes_ids : JSON NOT NULL
|
||||
result : JSON NOT NULL
|
||||
processing_time_ms : INT
|
||||
model_version : VARCHAR(50)
|
||||
keywords : JSON
|
||||
statistics : JSON
|
||||
created_at : TIMESTAMP
|
||||
}
|
||||
|
||||
entity "todos" {
|
||||
* **todo_id : VARCHAR(50)
|
||||
--
|
||||
meeting_id : VARCHAR(50) [FK] NOT NULL
|
||||
minutes_id : VARCHAR(50) [FK]
|
||||
title : VARCHAR(200) NOT NULL
|
||||
description : TEXT
|
||||
assignee_id : VARCHAR(50) NOT NULL
|
||||
due_date : DATE
|
||||
status : VARCHAR(20) NOT NULL
|
||||
priority : VARCHAR(20)
|
||||
extracted_by : VARCHAR(50) [V3]
|
||||
section_reference : VARCHAR(200) [V3]
|
||||
extraction_confidence : DECIMAL(3,2) [V3]
|
||||
completed_at : TIMESTAMP
|
||||
created_at : TIMESTAMP
|
||||
updated_at : TIMESTAMP
|
||||
}
|
||||
|
||||
'=== Relationships ===
|
||||
meetings ||--o{ meeting_participants : "1:N [V2]"
|
||||
meetings ||--o{ minutes : "1:N"
|
||||
meetings ||--o{ agenda_sections : "1:N [V3]"
|
||||
meetings ||--o{ ai_summaries : "1:N [V3]"
|
||||
meetings ||--o{ todos : "1:N"
|
||||
minutes ||--o{ minutes_sections : "1:N"
|
||||
minutes ||--o{ agenda_sections : "1:N [V3]"
|
||||
|
||||
'=== Legend ===
|
||||
legend right
|
||||
V2 = Migration 2 (2025-10-27)
|
||||
V3 = Migration 3 (2025-10-28)
|
||||
V4 = Migration 4 (2025-10-28)
|
||||
[FK] = Foreign Key
|
||||
**bold** = Important fields
|
||||
end legend
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,675 @@
|
||||
# Meeting Service 데이터베이스 스키마 전체 분석
|
||||
|
||||
## 1. 마이그레이션 파일 현황
|
||||
|
||||
### 마이그레이션 체인
|
||||
```
|
||||
V1 (초기) → V2 (회의 참석자) → V3 (회의종료) → V4 (todos)
|
||||
```
|
||||
|
||||
### 각 마이그레이션 내용
|
||||
- **V1**: 초기 스키마 (meetings, minutes, minutes_sections 등 - JPA로 자동 생성)
|
||||
- **V2**: `meeting_participants` 테이블 분리 (2025-10-27)
|
||||
- **V3**: 회의종료 기능 지원 (2025-10-28) - **주요 변경**
|
||||
- **V4**: `agenda_sections` 테이블에 `todos` 컬럼 추가 (2025-10-28)
|
||||
|
||||
---
|
||||
|
||||
## 2. 핵심 테이블 구조 분석
|
||||
|
||||
### 2.1 meetings 테이블
|
||||
**용도**: 회의 기본 정보 저장
|
||||
|
||||
| 컬럼명 | 타입 | 설명 | 용도 |
|
||||
|--------|------|------|------|
|
||||
| meeting_id | VARCHAR(50) | PK | 회의 고유 식별자 |
|
||||
| title | VARCHAR(200) | NOT NULL | 회의 제목 |
|
||||
| purpose | VARCHAR(500) | | 회의 목적 |
|
||||
| description | TEXT | | 상세 설명 |
|
||||
| scheduled_at | TIMESTAMP | NOT NULL | 예정된 시간 |
|
||||
| started_at | TIMESTAMP | | 실제 시작 시간 |
|
||||
| ended_at | TIMESTAMP | | **V3 추가**: 실제 종료 시간 |
|
||||
| status | VARCHAR(20) | NOT NULL | 상태: SCHEDULED, IN_PROGRESS, COMPLETED |
|
||||
| organizer_id | VARCHAR(50) | NOT NULL | 회의 주최자 |
|
||||
| created_at | TIMESTAMP | | 생성 시간 |
|
||||
| updated_at | TIMESTAMP | | 수정 시간 |
|
||||
|
||||
**관계**:
|
||||
- 1:N with `meeting_participants` (V2에서 분리)
|
||||
- 1:N with `minutes`
|
||||
|
||||
---
|
||||
|
||||
### 2.2 minutes 테이블
|
||||
**용도**: 회의록 기본 정보 + 사용자별 회의록 구분
|
||||
|
||||
| 컬럼명 | 타입 | 설명 | 용도 |
|
||||
|--------|------|------|------|
|
||||
| id/minutes_id | VARCHAR(50) | PK | 회의록 고유 식별자 |
|
||||
| meeting_id | VARCHAR(50) | FK | 해당 회의 ID |
|
||||
| user_id | VARCHAR(100) | **V3 추가** | NULL: AI 통합 회의록 / NOT NULL: 참석자별 회의록 |
|
||||
| title | VARCHAR(200) | NOT NULL | 회의록 제목 |
|
||||
| status | VARCHAR(20) | NOT NULL | DRAFT, FINALIZED |
|
||||
| version | INT | NOT NULL | 버전 관리 |
|
||||
| created_by | VARCHAR(50) | NOT NULL | 작성자 |
|
||||
| finalized_by | VARCHAR(50) | | 확정자 |
|
||||
| finalized_at | TIMESTAMP | | 확정 시간 |
|
||||
| created_at | TIMESTAMP | | 생성 시간 |
|
||||
| updated_at | TIMESTAMP | | 수정 시간 |
|
||||
|
||||
**중요**: `minutes` 테이블에는 `content` 컬럼이 **없음**
|
||||
- 실제 회의록 내용은 `minutes_sections`의 `content`에 저장됨
|
||||
- minutes는 메타데이터만 저장
|
||||
|
||||
**인덱스 (V3)**: `idx_minutes_meeting_user` on (meeting_id, user_id)
|
||||
|
||||
**관계**:
|
||||
- N:1 with `meetings`
|
||||
- 1:N with `minutes_sections`
|
||||
- 1:N with `agenda_sections` (V3 추가)
|
||||
|
||||
---
|
||||
|
||||
### 2.3 minutes_sections 테이블
|
||||
**용도**: 회의록 섹션별 상세 내용
|
||||
|
||||
| 컬럼명 | 타입 | 설명 |
|
||||
|--------|------|------|
|
||||
| id | VARCHAR(50) | PK |
|
||||
| minutes_id | VARCHAR(50) | FK to minutes |
|
||||
| type | VARCHAR(50) | AGENDA, DISCUSSION, DECISION, ACTION_ITEM |
|
||||
| title | VARCHAR(200) | 섹션 제목 |
|
||||
| **content** | TEXT | **섹션 상세 내용 저장** |
|
||||
| order | INT | 섹션 순서 |
|
||||
| verified | BOOLEAN | 검증 완료 여부 |
|
||||
| locked | BOOLEAN | 잠금 여부 |
|
||||
| locked_by | VARCHAR(50) | 잠금 사용자 |
|
||||
|
||||
**중요 사항**:
|
||||
- 회의록 실제 내용은 여기에 저장됨
|
||||
- `minutes`와 N:1 관계 (1개 회의록에 다중 섹션)
|
||||
- 사용자별 회의록도 각각 섹션을 가짐
|
||||
|
||||
---
|
||||
|
||||
### 2.4 agenda_sections 테이블 (V3 신규)
|
||||
**용도**: 안건별 AI 요약 결과 저장 (구조화된 형식)
|
||||
|
||||
| 컬럼명 | 타입 | 설명 | 포함 데이터 |
|
||||
|--------|------|------|-----------|
|
||||
| id | VARCHAR(36) | PK | UUID |
|
||||
| minutes_id | VARCHAR(36) | FK | 통합 회의록 참조 |
|
||||
| meeting_id | VARCHAR(50) | FK | 회의 ID |
|
||||
| agenda_number | INT | | 안건 번호 (1, 2, 3...) |
|
||||
| agenda_title | VARCHAR(200) | | 안건 제목 |
|
||||
| ai_summary_short | TEXT | | 짧은 요약 (1줄, 20자 이내) |
|
||||
| discussions | TEXT | | 논의 사항 (3-5문장) |
|
||||
| decisions | JSON | | 결정 사항 배열 |
|
||||
| pending_items | JSON | | 보류 사항 배열 |
|
||||
| opinions | JSON | | 참석자별 의견: [{speaker, opinion}] |
|
||||
| **todos** | JSON | **V4 추가** | 추출된 Todo: [{title, assignee, dueDate, description, priority}] |
|
||||
|
||||
**V4 추가 구조** (todos JSON):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"title": "시장 조사 보고서 작성",
|
||||
"assignee": "김민준",
|
||||
"dueDate": "2025-02-15",
|
||||
"description": "20-30대 타겟 시장 조사",
|
||||
"priority": "HIGH"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**인덱스**:
|
||||
- `idx_sections_meeting` on meeting_id
|
||||
- `idx_sections_agenda` on (meeting_id, agenda_number)
|
||||
- `idx_sections_minutes` on minutes_id
|
||||
|
||||
**관계**:
|
||||
- N:1 with `minutes` (통합 회의록만 참조)
|
||||
- N:1 with `meetings`
|
||||
|
||||
---
|
||||
|
||||
### 2.5 minutes_section vs agenda_sections 차이점
|
||||
|
||||
| 특성 | minutes_sections | agenda_sections |
|
||||
|------|------------------|-----------------|
|
||||
| **용도** | 회의록 작성용 | AI 요약 결과 저장용 |
|
||||
| **구조** | 순차적 섹션 (type: AGENDA, DISCUSSION, DECISION) | 안건별 구조화된 데이터 |
|
||||
| **내용 저장** | content (TEXT) | 구조화된 필드 + JSON |
|
||||
| **소유 관계** | 모든 회의록 (사용자별 포함) | 통합 회의록만 (user_id=NULL) |
|
||||
| **목적** | 사용자 작성 | AI 자동 생성 |
|
||||
| **JSON 필드** | 없음 | decisions, pending_items, opinions, todos |
|
||||
|
||||
**생성 흐름**:
|
||||
```
|
||||
회의 종료 → 통합 회의록 (minutes, user_id=NULL)
|
||||
→ minutes_sections 생성 (사용자가 내용 작성)
|
||||
→ AI 분석 → agenda_sections 생성 (AI 요약 결과 저장)
|
||||
|
||||
동시에:
|
||||
→ 참석자별 회의록 (minutes, user_id NOT NULL)
|
||||
→ 참석자별 minutes_sections 생성
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.6 ai_summaries 테이블 (V3 신규)
|
||||
**용도**: AI 요약 결과 캐싱
|
||||
|
||||
| 컬럼명 | 타입 | 설명 |
|
||||
|--------|------|------|
|
||||
| id | VARCHAR(36) | PK |
|
||||
| meeting_id | VARCHAR(50) | FK |
|
||||
| summary_type | VARCHAR(50) | CONSOLIDATED (통합 요약) / TODO_EXTRACTION (Todo 추출) |
|
||||
| source_minutes_ids | JSON | 통합에 사용된 회의록 ID 배열 |
|
||||
| result | JSON | **AI 응답 전체 결과** |
|
||||
| processing_time_ms | INT | AI 처리 시간 |
|
||||
| model_version | VARCHAR(50) | 사용 모델 (claude-3.5-sonnet) |
|
||||
| keywords | JSON | 주요 키워드 배열 |
|
||||
| statistics | JSON | 통계 (참석자 수, 안건 수 등) |
|
||||
|
||||
---
|
||||
|
||||
### 2.7 todos 테이블
|
||||
**용도**: Todo 아이템 저장
|
||||
|
||||
| 컬럼명 | 타입 | 설명 |
|
||||
|--------|------|------|
|
||||
| todo_id | VARCHAR(50) | PK |
|
||||
| minutes_id | VARCHAR(50) | FK | 관련 회의록 |
|
||||
| meeting_id | VARCHAR(50) | FK | 회의 ID |
|
||||
| title | VARCHAR(200) | 제목 |
|
||||
| description | TEXT | 상세 설명 |
|
||||
| assignee_id | VARCHAR(50) | 담당자 |
|
||||
| due_date | DATE | 마감일 |
|
||||
| status | VARCHAR(20) | PENDING, COMPLETED |
|
||||
| priority | VARCHAR(20) | HIGH, MEDIUM, LOW |
|
||||
| completed_at | TIMESTAMP | 완료 시간 |
|
||||
|
||||
**V3에서 추가된 컬럼**:
|
||||
```sql
|
||||
extracted_by VARCHAR(50) -- AI 또는 MANUAL
|
||||
section_reference VARCHAR(200) -- 관련 회의록 섹션 참조
|
||||
extraction_confidence DECIMAL(3,2) -- AI 신뢰도 (0.00~1.00)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.8 meeting_participants 테이블 (V2 신규)
|
||||
**용도**: 회의 참석자 정보 분리
|
||||
|
||||
| 컬럼명 | 타입 | 설명 |
|
||||
|--------|------|------|
|
||||
| meeting_id | VARCHAR(50) | PK1, FK |
|
||||
| user_id | VARCHAR(100) | PK2 |
|
||||
| invitation_status | VARCHAR(20) | PENDING, ACCEPTED, DECLINED |
|
||||
| attended | BOOLEAN | 참석 여부 |
|
||||
| created_at | TIMESTAMP | |
|
||||
| updated_at | TIMESTAMP | |
|
||||
|
||||
**변경 배경 (V2)**:
|
||||
- 이전: meetings.participants (CSV 문자열)
|
||||
- 현재: meeting_participants (별도 테이블, 정규화)
|
||||
|
||||
---
|
||||
|
||||
## 3. 회의록 작성 플로우에서의 테이블 사용
|
||||
|
||||
### 3.1 회의 시작 (StartMeeting)
|
||||
```
|
||||
meetings 테이블 UPDATE
|
||||
└─ status: SCHEDULED → IN_PROGRESS
|
||||
└─ started_at 기록
|
||||
```
|
||||
|
||||
### 3.2 회의 종료 (EndMeeting)
|
||||
```
|
||||
meetings 테이블 UPDATE
|
||||
├─ status: IN_PROGRESS → COMPLETED
|
||||
└─ ended_at 기록 (V3 신규)
|
||||
|
||||
↓
|
||||
|
||||
minutes 테이블 생성 (AI 통합 회의록)
|
||||
├─ user_id = NULL
|
||||
├─ status = DRAFT
|
||||
└─ 각 참석자별 회의록도 동시 생성
|
||||
└─ user_id = 참석자ID
|
||||
|
||||
↓
|
||||
|
||||
minutes_sections 테이블 초기 생성
|
||||
├─ 통합 회의록용 섹션
|
||||
└─ 각 참석자별 섹션
|
||||
```
|
||||
|
||||
### 3.3 회의록 작성 (CreateMinutes / UpdateMinutes)
|
||||
```
|
||||
minutes 테이블 UPDATE
|
||||
├─ title 작성
|
||||
└─ status 유지 (DRAFT)
|
||||
|
||||
↓
|
||||
|
||||
minutes_sections 테이블 INSERT/UPDATE
|
||||
├─ type: AGENDA, DISCUSSION, DECISION 등
|
||||
├─ title: 섹션 제목
|
||||
├─ content: 실제 회의록 내용 ← **여기에 사용자가 입력한 내용 저장**
|
||||
└─ order: 순서
|
||||
|
||||
사용자가 작성한 내용 저장 경로:
|
||||
minutes_sections.content (TEXT 컬럼)
|
||||
```
|
||||
|
||||
### 3.4 AI 분석 (FinializeMinutes + AI Processing)
|
||||
```
|
||||
minutes 테이블 UPDATE
|
||||
├─ status: DRAFT → FINALIZED
|
||||
└─ finalized_at 기록
|
||||
|
||||
↓
|
||||
|
||||
agenda_sections 테이블 INSERT
|
||||
├─ minutesId = 통합 회의록 ID (user_id=NULL)
|
||||
├─ AI 요약: aiSummaryShort, discussions
|
||||
├─ 구조화된 데이터: decisions, pendingItems, opinions (JSON)
|
||||
└─ todos (V4): AI 추출 Todo (JSON)
|
||||
|
||||
↓
|
||||
|
||||
ai_summaries 테이블 INSERT
|
||||
├─ summary_type: CONSOLIDATED
|
||||
├─ result: AI 응답 전체 결과
|
||||
└─ keywords, statistics
|
||||
|
||||
↓
|
||||
|
||||
todos 테이블 INSERT (선택)
|
||||
├─ 간단한 Todo는 agenda_sections.todos에만 저장
|
||||
└─ 상세 관리 필요한 경우 별도 테이블 저장
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 사용자별 회의록 저장 구조
|
||||
|
||||
### 4.1 회의 종료 시 자동 생성
|
||||
|
||||
```
|
||||
1개의 회의 → 여러 회의록
|
||||
├─ AI 통합 회의록 (minutes.user_id = NULL)
|
||||
│ ├─ minutes_sections (AI/시스템이 생성)
|
||||
│ └─ agenda_sections (AI 분석 결과)
|
||||
│
|
||||
└─ 각 참석자별 회의록 (minutes.user_id = 참석자ID)
|
||||
├─ User1의 회의록 (minutes.user_id = 'user1@example.com')
|
||||
│ └─ minutes_sections (User1이 작성)
|
||||
│
|
||||
├─ User2의 회의록 (minutes.user_id = 'user2@example.com')
|
||||
│ └─ minutes_sections (User2이 작성)
|
||||
│
|
||||
└─ ...
|
||||
```
|
||||
|
||||
### 4.2 minutes 테이블 쿼리 예시
|
||||
|
||||
```sql
|
||||
-- 특정 회의의 AI 통합 회의록
|
||||
SELECT * FROM minutes
|
||||
WHERE meeting_id = 'meeting-001' AND user_id IS NULL;
|
||||
|
||||
-- 특정 회의의 참석자별 회의록
|
||||
SELECT * FROM minutes
|
||||
WHERE meeting_id = 'meeting-001' AND user_id IS NOT NULL;
|
||||
|
||||
-- 특정 사용자의 회의록
|
||||
SELECT * FROM minutes
|
||||
WHERE user_id = 'user1@example.com';
|
||||
|
||||
-- 참석자별로 회의록 조회 (복합 인덱스 활용)
|
||||
SELECT * FROM minutes
|
||||
WHERE meeting_id = 'meeting-001' AND user_id = 'user1@example.com';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. V3 마이그레이션의 주요 변경사항
|
||||
|
||||
### 5.1 minutes 테이블 확장
|
||||
```sql
|
||||
ALTER TABLE minutes ADD COLUMN IF NOT EXISTS user_id VARCHAR(100);
|
||||
CREATE INDEX IF NOT EXISTS idx_minutes_meeting_user ON minutes(meeting_id, user_id);
|
||||
```
|
||||
|
||||
**영향**:
|
||||
- 기존 회의록: `user_id = NULL` (AI 통합 회의록)
|
||||
- 새 회의록: `user_id = 참석자ID` (참석자별)
|
||||
- 쿼리 성능: 복합 인덱스로 빠른 검색
|
||||
|
||||
### 5.2 agenda_sections 테이블 신규 생성
|
||||
- AI 요약을 구조화된 형식으로 저장
|
||||
- JSON 필드로 결정사항, 보류사항, 의견, Todo 저장
|
||||
- minutes_id로 통합 회의록과 연결
|
||||
|
||||
### 5.3 ai_summaries 테이블 신규 생성
|
||||
- AI 처리 결과 캐싱
|
||||
- 처리 시간, 모델 버전 기록
|
||||
- 재처리 필요 시 참조 가능
|
||||
|
||||
### 5.4 todos 테이블 확장
|
||||
```sql
|
||||
ALTER TABLE todos ADD COLUMN extracted_by VARCHAR(50) DEFAULT 'AI';
|
||||
ALTER TABLE todos ADD COLUMN section_reference VARCHAR(200);
|
||||
ALTER TABLE todos ADD COLUMN extraction_confidence DECIMAL(3,2) DEFAULT 0.00;
|
||||
```
|
||||
|
||||
**목적**:
|
||||
- AI 자동 추출 vs 수동 작성 구분
|
||||
- Todo의 출처 추적
|
||||
- AI 신뢰도 관리
|
||||
|
||||
---
|
||||
|
||||
## 6. V4 마이그레이션의 변경사항
|
||||
|
||||
### 6.1 agenda_sections 테이블에 todos 컬럼 추가
|
||||
```sql
|
||||
ALTER TABLE agenda_sections ADD COLUMN IF NOT EXISTS todos JSON;
|
||||
```
|
||||
|
||||
**구조**:
|
||||
```json
|
||||
{
|
||||
"title": "시장 조사 보고서 작성",
|
||||
"assignee": "김민준",
|
||||
"dueDate": "2025-02-15",
|
||||
"description": "20-30대 타겟 시장 조사",
|
||||
"priority": "HIGH"
|
||||
}
|
||||
```
|
||||
|
||||
**저장 경로**:
|
||||
- **안건별 요약의 Todo**: `agenda_sections.todos` (JSON)
|
||||
- **개별 Todo 관리**: `todos` 테이블 (필요시)
|
||||
|
||||
---
|
||||
|
||||
## 7. 데이터 정규화 현황
|
||||
|
||||
### 7.1 정규화 수행 (V2)
|
||||
```
|
||||
meetings (이전):
|
||||
participants: "user1@example.com,user2@example.com"
|
||||
|
||||
↓ 정규화 (V2 마이그레이션)
|
||||
|
||||
meetings_participants (별도 테이블):
|
||||
[meeting_id, user_id] (복합 PK)
|
||||
invitation_status
|
||||
attended
|
||||
```
|
||||
|
||||
### 7.2 JSON 필드 사용 (V3, V4)
|
||||
- `decisions`, `pending_items`, `opinions`, `todos` (agenda_sections)
|
||||
- `keywords`, `statistics` (ai_summaries)
|
||||
- `source_minutes_ids` (ai_summaries)
|
||||
|
||||
**사용 이유**:
|
||||
- 변동적인 구조 데이터
|
||||
- AI 응답의 유연한 저장
|
||||
- 쿼리 패턴이 검색보다 전체 조회
|
||||
|
||||
---
|
||||
|
||||
## 8. 핵심 질문 답변
|
||||
|
||||
### Q1: minutes 테이블에 content 필드가 있는가?
|
||||
**A**: **없음**. 회의록 실제 내용은 `minutes_sections.content`에 저장됨.
|
||||
|
||||
### Q2: minutes_section과 agenda_sections의 차이점?
|
||||
| 항목 | minutes_sections | agenda_sections |
|
||||
|------|-----------------|-----------------|
|
||||
| 목적 | 사용자 작성 | AI 요약 |
|
||||
| 모든 회의록 | O | X (통합만) |
|
||||
| 구조 | 순차적 | 안건별 |
|
||||
| 내용 저장 | content (TEXT) | JSON |
|
||||
|
||||
### Q3: 사용자별 회의록을 저장할 적절한 구조는?
|
||||
**A**:
|
||||
- `minutes` 테이블: `user_id` 컬럼으로 구분
|
||||
- `minutes_sections`: 각 회의록의 섹션
|
||||
- 인덱스: `idx_minutes_meeting_user` (meeting_id, user_id)
|
||||
|
||||
### Q4: V3, V4 주요 변경사항은?
|
||||
- **V3**: user_id 추가, agenda_sections 신규, ai_summaries 신규, todos 확장
|
||||
- **V4**: agenda_sections.todos JSON 필드 추가
|
||||
|
||||
---
|
||||
|
||||
## 9. 데이터베이스 구조도 (PlantUML)
|
||||
|
||||
```plantuml
|
||||
@startuml
|
||||
!theme mono
|
||||
|
||||
entity "meetings" as meetings {
|
||||
* meeting_id: VARCHAR(50)
|
||||
--
|
||||
title: VARCHAR(200)
|
||||
status: VARCHAR(20)
|
||||
organizer_id: VARCHAR(50)
|
||||
started_at: TIMESTAMP
|
||||
ended_at: TIMESTAMP [V3]
|
||||
created_at: TIMESTAMP
|
||||
updated_at: TIMESTAMP
|
||||
}
|
||||
|
||||
entity "meeting_participants" as participants {
|
||||
* meeting_id: VARCHAR(50) [FK]
|
||||
* user_id: VARCHAR(100)
|
||||
--
|
||||
invitation_status: VARCHAR(20)
|
||||
attended: BOOLEAN
|
||||
}
|
||||
|
||||
entity "minutes" as minutes {
|
||||
* id: VARCHAR(50)
|
||||
--
|
||||
meeting_id: VARCHAR(50) [FK]
|
||||
user_id: VARCHAR(100) [V3]
|
||||
title: VARCHAR(200)
|
||||
status: VARCHAR(20)
|
||||
created_by: VARCHAR(50)
|
||||
finalized_at: TIMESTAMP
|
||||
}
|
||||
|
||||
entity "minutes_sections" as sections {
|
||||
* id: VARCHAR(50)
|
||||
--
|
||||
minutes_id: VARCHAR(50) [FK]
|
||||
type: VARCHAR(50)
|
||||
title: VARCHAR(200)
|
||||
content: TEXT
|
||||
locked: BOOLEAN
|
||||
}
|
||||
|
||||
entity "agenda_sections" as agenda {
|
||||
* id: VARCHAR(36)
|
||||
--
|
||||
minutes_id: VARCHAR(36) [FK, 통합회의록만]
|
||||
meeting_id: VARCHAR(50) [FK]
|
||||
agenda_number: INT
|
||||
agenda_title: VARCHAR(200)
|
||||
ai_summary_short: TEXT
|
||||
discussions: TEXT
|
||||
decisions: JSON
|
||||
opinions: JSON
|
||||
todos: JSON [V4]
|
||||
}
|
||||
|
||||
entity "ai_summaries" as summaries {
|
||||
* id: VARCHAR(36)
|
||||
--
|
||||
meeting_id: VARCHAR(50) [FK]
|
||||
summary_type: VARCHAR(50)
|
||||
result: JSON
|
||||
keywords: JSON
|
||||
statistics: JSON
|
||||
}
|
||||
|
||||
entity "todos" as todos {
|
||||
* todo_id: VARCHAR(50)
|
||||
--
|
||||
meeting_id: VARCHAR(50) [FK]
|
||||
minutes_id: VARCHAR(50) [FK]
|
||||
title: VARCHAR(200)
|
||||
assignee_id: VARCHAR(50)
|
||||
status: VARCHAR(20)
|
||||
extracted_by: VARCHAR(50) [V3]
|
||||
}
|
||||
|
||||
meetings ||--o{ participants: "1:N"
|
||||
meetings ||--o{ minutes: "1:N"
|
||||
meetings ||--o{ agenda: "1:N"
|
||||
meetings ||--o{ todos: "1:N"
|
||||
minutes ||--o{ sections: "1:N"
|
||||
minutes ||--o{ agenda: "1:N"
|
||||
meetings ||--o{ summaries: "1:N"
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 회의록 작성 전체 플로우
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 1. 회의 시작 (StartMeeting) │
|
||||
│ ├─ meetings.status = IN_PROGRESS │
|
||||
│ └─ meetings.started_at 기록 │
|
||||
└─────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────────────────────┐
|
||||
│ 2. 회의 진행 중 (회의록 작성) │
|
||||
│ ├─ CreateMinutes: minutes 생성 (user_id=NULL 통합) │
|
||||
│ ├─ CreateMinutes: 참석자별 minutes 생성 │
|
||||
│ ├─ UpdateMinutes: minutes_sections 작성 │
|
||||
│ │ └─ content에 회의 내용 저장 │
|
||||
│ └─ SaveMinutes: draft 상태 유지 │
|
||||
└─────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────────────────────┐
|
||||
│ 3. 회의 종료 (EndMeeting) │
|
||||
│ ├─ meetings.status = COMPLETED │
|
||||
│ ├─ meetings.ended_at = NOW() [V3] │
|
||||
│ └─ 회의 기본 정보 확정 │
|
||||
└─────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────────────────────┐
|
||||
│ 4. 회의록 최종화 (FinalizeMinutes) │
|
||||
│ ├─ minutes.status = FINALIZED │
|
||||
│ ├─ minutes.finalized_by = 확정자 │
|
||||
│ ├─ minutes.finalized_at = NOW() │
|
||||
│ └─ minutes_sections 내용 확정 (locked) │
|
||||
└─────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────────────────────┐
|
||||
│ 5. AI 분석 처리 (MinutesAnalysisEventConsumer) │
|
||||
│ ├─ 통합 회의록 분석 (user_id=NULL) │
|
||||
│ │ │
|
||||
│ ├─ agenda_sections INSERT [V3] │
|
||||
│ │ ├─ minutes_id = 통합 회의록 ID │
|
||||
│ │ ├─ ai_summary_short, discussions │
|
||||
│ │ ├─ decisions, pending_items, opinions (JSON) │
|
||||
│ │ └─ todos (JSON) [V4] │
|
||||
│ │ │
|
||||
│ ├─ ai_summaries INSERT [V3] │
|
||||
│ │ ├─ summary_type = CONSOLIDATED │
|
||||
│ │ ├─ result = AI 응답 전체 │
|
||||
│ │ └─ keywords, statistics │
|
||||
│ │ │
|
||||
│ └─ todos TABLE INSERT (선택) │
|
||||
│ ├─ extracted_by = 'AI' [V3] │
|
||||
│ └─ extraction_confidence [V3] │
|
||||
└─────────────────┬───────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────────────────────┐
|
||||
│ 6. 회의록 조회 │
|
||||
│ ├─ 통합 회의록 조회 │
|
||||
│ │ └─ minutes + minutes_sections + agenda_sections │
|
||||
│ ├─ 참석자별 회의록 조회 │
|
||||
│ │ └─ minutes (user_id=참석자) + minutes_sections │
|
||||
│ └─ Todo 조회 │
|
||||
│ └─ agenda_sections.todos 또는 todos 테이블 │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 성능 최적화 포인트
|
||||
|
||||
### 11.1 인덱스 현황
|
||||
```
|
||||
meetings:
|
||||
- PK: meeting_id
|
||||
|
||||
minutes:
|
||||
- PK: id
|
||||
- idx_minutes_meeting_user (meeting_id, user_id) [V3] ← 핵심
|
||||
|
||||
minutes_sections:
|
||||
- PK: id
|
||||
- FK: minutes_id
|
||||
|
||||
agenda_sections: [V3]
|
||||
- PK: id
|
||||
- idx_sections_meeting (meeting_id)
|
||||
- idx_sections_agenda (meeting_id, agenda_number)
|
||||
- idx_sections_minutes (minutes_id)
|
||||
|
||||
ai_summaries: [V3]
|
||||
- PK: id
|
||||
- idx_summaries_meeting (meeting_id)
|
||||
- idx_summaries_type (meeting_id, summary_type)
|
||||
- idx_summaries_created (created_at)
|
||||
|
||||
todos:
|
||||
- PK: todo_id
|
||||
- idx_todos_extracted (extracted_by) [V3]
|
||||
- idx_todos_meeting (meeting_id) [V3]
|
||||
|
||||
meeting_participants: [V2]
|
||||
- PK: (meeting_id, user_id)
|
||||
- idx_user_id (user_id)
|
||||
- idx_invitation_status (invitation_status)
|
||||
```
|
||||
|
||||
### 11.2 추천 추가 인덱스
|
||||
```sql
|
||||
-- 빠른 조회를 위한 인덱스
|
||||
CREATE INDEX idx_minutes_status ON minutes(status, created_at DESC);
|
||||
CREATE INDEX idx_agenda_meeting_created ON agenda_sections(meeting_id, created_at DESC);
|
||||
CREATE INDEX idx_todos_meeting_assignee ON todos(meeting_id, assignee_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 결론
|
||||
|
||||
### 핵심 설계 원칙
|
||||
1. **참석자별 회의록**: minutes.user_id로 구분 (NULL=AI 통합, NOT NULL=개인)
|
||||
2. **내용 저장**: minutes_sections.content에 사용자가 작성한 내용 저장
|
||||
3. **구조화된 요약**: agenda_sections에 AI 요약을 JSON으로 저장
|
||||
4. **추적 가능성**: extracted_by, section_reference로 Todo 출처 추적
|
||||
5. **정규화**: V2에서 meeting_participants로 정규화 완료
|
||||
|
||||
### 주의사항
|
||||
- `minutes` 테이블 자체는 메타데이터만 저장 (title, status 등)
|
||||
- 실제 회의 내용: `minutes_sections.content`
|
||||
- AI 요약 결과: `agenda_sections` (구조화됨)
|
||||
- Todo는 두 곳에 저장 가능: agenda_sections.todos (JSON) / todos 테이블
|
||||
Reference in New Issue
Block a user