mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 07:56:24 +00:00
feat: 회의종료 기능을 위한 DB 스키마 추가
## 변경 내용 - minutes 테이블에 user_id 컬럼 추가 (참석자별 회의록 지원) * user_id IS NULL: AI 통합 회의록 * user_id IS NOT NULL: 참석자별 회의록 - agenda_sections 테이블 생성 (안건별 AI 요약 저장) * agenda_number, agenda_title * ai_summary_short, discussions, decisions (JSON) * pending_items (JSON), opinions (JSON) - ai_summaries 테이블 생성 (AI 결과 캐싱) * summary_type: CONSOLIDATED, TODO_EXTRACTION * keywords, statistics (JSON) * processing_time_ms (성능 모니터링) - todos 테이블 확장 (AI 추출 정보) * extracted_by: AI, MANUAL * section_reference: 관련 안건 참조 * extraction_confidence: 0.00~1.00 ## 문서 - DB-Schema-회의종료.md: 상세 스키마 문서 - ERD-회의종료.puml: ERD 다이어그램 - 회의종료-개발계획.md: 전체 개발 계획 ## 설계 개선 - is_consolidated 컬럼 제거 (user_id로 구분 가능) - 중복 정보 제거로 데이터 일관성 향상
This commit is contained in:
parent
e09ef19d5e
commit
92e4863fc7
538
docs/DB-Schema-회의종료.md
Normal file
538
docs/DB-Schema-회의종료.md
Normal file
@ -0,0 +1,538 @@
|
|||||||
|
# 회의종료 기능 DB 스키마 설계
|
||||||
|
|
||||||
|
## 📋 개요
|
||||||
|
회의 종료 시 참석자별 회의록을 통합하고 AI 요약 및 Todo 자동 추출을 지원하기 위한 데이터베이스 스키마
|
||||||
|
|
||||||
|
**마이그레이션 버전**: V3__add_meeting_end_support.sql
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗄️ 테이블 구조
|
||||||
|
|
||||||
|
### 1. minutes (확장)
|
||||||
|
**설명**: 참석자별 회의록 및 AI 통합 회의록 저장
|
||||||
|
|
||||||
|
#### 새로 추가된 컬럼
|
||||||
|
| 컬럼명 | 타입 | 제약조건 | 설명 |
|
||||||
|
|--------|------|----------|------|
|
||||||
|
| user_id | VARCHAR(100) | NULL | 작성자 사용자 ID (참석자별 회의록인 경우) |
|
||||||
|
| is_consolidated | BOOLEAN | DEFAULT FALSE | AI 통합 회의록 여부 |
|
||||||
|
| consolidated_by | VARCHAR(255) | DEFAULT 'AI' | 통합 처리자 |
|
||||||
|
| section_type | VARCHAR(50) | DEFAULT 'PARTICIPANT' | 회의록 섹션 타입 |
|
||||||
|
|
||||||
|
#### 인덱스
|
||||||
|
- `idx_minutes_meeting_user`: (meeting_id, user_id)
|
||||||
|
- `idx_minutes_consolidated`: (is_consolidated)
|
||||||
|
- `idx_minutes_section_type`: (section_type)
|
||||||
|
|
||||||
|
#### 사용 패턴
|
||||||
|
```sql
|
||||||
|
-- 참석자별 회의록 조회
|
||||||
|
SELECT * FROM minutes
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND is_consolidated = false
|
||||||
|
AND section_type = 'PARTICIPANT';
|
||||||
|
|
||||||
|
-- AI 통합 회의록 조회
|
||||||
|
SELECT * FROM minutes
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND is_consolidated = true
|
||||||
|
AND section_type = 'CONSOLIDATED';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. agenda_sections (신규)
|
||||||
|
**설명**: 안건별 AI 요약 결과 저장
|
||||||
|
|
||||||
|
#### 컬럼 구조
|
||||||
|
| 컬럼명 | 타입 | 제약조건 | 설명 |
|
||||||
|
|--------|------|----------|------|
|
||||||
|
| id | VARCHAR(36) | PRIMARY KEY | 섹션 고유 ID |
|
||||||
|
| minutes_id | VARCHAR(36) | NOT NULL, FK | 회의록 ID (통합 회의록) |
|
||||||
|
| meeting_id | VARCHAR(50) | NOT NULL, FK | 회의 ID |
|
||||||
|
| agenda_number | INT | NOT NULL | 안건 번호 |
|
||||||
|
| agenda_title | VARCHAR(200) | NOT NULL | 안건 제목 |
|
||||||
|
| ai_summary_short | TEXT | NULL | AI 생성 짧은 요약 (1줄) |
|
||||||
|
| discussions | TEXT | NULL | 논의 사항 (3-5문장) |
|
||||||
|
| decisions | JSON | NULL | 결정 사항 배열 |
|
||||||
|
| pending_items | JSON | NULL | 보류 사항 배열 |
|
||||||
|
| opinions | JSON | NULL | 참석자별 의견 |
|
||||||
|
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 생성 시간 |
|
||||||
|
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 수정 시간 |
|
||||||
|
|
||||||
|
#### 외래키
|
||||||
|
- `fk_agenda_sections_minutes`: minutes(id) ON DELETE CASCADE
|
||||||
|
- `fk_agenda_sections_meeting`: meetings(meeting_id) ON DELETE CASCADE
|
||||||
|
|
||||||
|
#### 인덱스
|
||||||
|
- `idx_sections_meeting`: (meeting_id)
|
||||||
|
- `idx_sections_agenda`: (meeting_id, agenda_number)
|
||||||
|
- `idx_sections_minutes`: (minutes_id)
|
||||||
|
|
||||||
|
#### JSON 데이터 구조
|
||||||
|
|
||||||
|
**decisions** (결정 사항):
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
"타겟 고객: 20-30대 직장인",
|
||||||
|
"UI/UX 개선을 최우선 과제로 설정",
|
||||||
|
"예산: 5억원"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**pending_items** (보류 사항):
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
"세부 일정 확정은 다음 회의에서 논의",
|
||||||
|
"추가 예산 검토 필요"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**opinions** (참석자별 의견):
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"speaker": "김민준",
|
||||||
|
"opinion": "타겟 고객층을 명확히 설정하여 마케팅 전략 수립 필요"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"speaker": "박서연",
|
||||||
|
"opinion": "UI/UX 개선에 AI 기술 적용 검토"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 사용 패턴
|
||||||
|
```sql
|
||||||
|
-- 안건별 섹션 조회
|
||||||
|
SELECT * FROM agenda_sections
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
ORDER BY agenda_number;
|
||||||
|
|
||||||
|
-- 특정 안건 조회
|
||||||
|
SELECT * FROM agenda_sections
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND agenda_number = 1;
|
||||||
|
|
||||||
|
-- JSON 필드 쿼리 (PostgreSQL)
|
||||||
|
SELECT
|
||||||
|
agenda_title,
|
||||||
|
decisions,
|
||||||
|
jsonb_array_length(decisions::jsonb) as decision_count
|
||||||
|
FROM agenda_sections
|
||||||
|
WHERE meeting_id = 'meeting-123';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. ai_summaries (신규)
|
||||||
|
**설명**: AI 요약 결과 캐싱 및 성능 최적화
|
||||||
|
|
||||||
|
#### 컬럼 구조
|
||||||
|
| 컬럼명 | 타입 | 제약조건 | 설명 |
|
||||||
|
|--------|------|----------|------|
|
||||||
|
| id | VARCHAR(36) | PRIMARY KEY | 요약 결과 고유 ID |
|
||||||
|
| meeting_id | VARCHAR(50) | NOT NULL, FK | 회의 ID |
|
||||||
|
| summary_type | VARCHAR(50) | NOT NULL | 요약 타입 |
|
||||||
|
| source_minutes_ids | JSON | NOT NULL | 통합에 사용된 회의록 ID 배열 |
|
||||||
|
| result | JSON | NOT NULL | AI 응답 전체 결과 |
|
||||||
|
| processing_time_ms | INT | NULL | AI 처리 시간 (밀리초) |
|
||||||
|
| model_version | VARCHAR(50) | DEFAULT 'claude-3.5-sonnet' | AI 모델 버전 |
|
||||||
|
| keywords | JSON | NULL | 주요 키워드 배열 |
|
||||||
|
| statistics | JSON | NULL | 통계 정보 |
|
||||||
|
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 생성 시간 |
|
||||||
|
|
||||||
|
#### summary_type 값
|
||||||
|
- `CONSOLIDATED`: 통합 회의록 요약
|
||||||
|
- `TODO_EXTRACTION`: Todo 자동 추출
|
||||||
|
|
||||||
|
#### 외래키
|
||||||
|
- `fk_ai_summaries_meeting`: meetings(meeting_id) ON DELETE CASCADE
|
||||||
|
|
||||||
|
#### 인덱스
|
||||||
|
- `idx_summaries_meeting`: (meeting_id)
|
||||||
|
- `idx_summaries_type`: (meeting_id, summary_type)
|
||||||
|
- `idx_summaries_created`: (created_at)
|
||||||
|
|
||||||
|
#### JSON 데이터 구조
|
||||||
|
|
||||||
|
**source_minutes_ids**:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
"minutes-uuid-1",
|
||||||
|
"minutes-uuid-2",
|
||||||
|
"minutes-uuid-3"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**result** (CONSOLIDATED 타입):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"agendaSections": [
|
||||||
|
{
|
||||||
|
"agendaNumber": 1,
|
||||||
|
"aiSummaryShort": "타겟 고객을 20-30대로 설정...",
|
||||||
|
"discussions": "신제품의 주요 타겟 고객층...",
|
||||||
|
"decisions": ["타겟 고객: 20-30대", "UI/UX 개선"],
|
||||||
|
"pendingItems": [],
|
||||||
|
"opinions": [
|
||||||
|
{"speaker": "김민준", "opinion": "..."}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**result** (TODO_EXTRACTION 타입):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"todos": [
|
||||||
|
{
|
||||||
|
"content": "시장 조사 보고서 작성",
|
||||||
|
"assignee": "김민준",
|
||||||
|
"dueDate": "2025-11-01",
|
||||||
|
"priority": "HIGH",
|
||||||
|
"sectionReference": "안건 1",
|
||||||
|
"confidence": 0.92
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**keywords**:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
"신제품기획",
|
||||||
|
"예산편성",
|
||||||
|
"일정조율",
|
||||||
|
"시장조사",
|
||||||
|
"UI/UX"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**statistics**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"participantsCount": 4,
|
||||||
|
"durationMinutes": 90,
|
||||||
|
"agendasCount": 3,
|
||||||
|
"todosCount": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 사용 패턴
|
||||||
|
```sql
|
||||||
|
-- 캐시된 통합 요약 조회
|
||||||
|
SELECT * FROM ai_summaries
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND summary_type = 'CONSOLIDATED'
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- 성능 통계 조회
|
||||||
|
SELECT
|
||||||
|
summary_type,
|
||||||
|
AVG(processing_time_ms) as avg_time,
|
||||||
|
MAX(processing_time_ms) as max_time
|
||||||
|
FROM ai_summaries
|
||||||
|
GROUP BY summary_type;
|
||||||
|
|
||||||
|
-- JSON 필드 쿼리
|
||||||
|
SELECT
|
||||||
|
meeting_id,
|
||||||
|
keywords,
|
||||||
|
statistics->>'participantsCount' as participants,
|
||||||
|
statistics->>'todosCount' as todos
|
||||||
|
FROM ai_summaries
|
||||||
|
WHERE summary_type = 'CONSOLIDATED';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. todos (확장)
|
||||||
|
**설명**: AI 자동 추출 정보 추가
|
||||||
|
|
||||||
|
#### 새로 추가된 컬럼
|
||||||
|
| 컬럼명 | 타입 | 제약조건 | 설명 |
|
||||||
|
|--------|------|----------|------|
|
||||||
|
| extracted_by | VARCHAR(50) | DEFAULT 'AI' | Todo 추출 방법 |
|
||||||
|
| section_reference | VARCHAR(200) | NULL | 관련 회의록 섹션 참조 |
|
||||||
|
| extraction_confidence | DECIMAL(3,2) | DEFAULT 0.00 | AI 추출 신뢰도 점수 |
|
||||||
|
|
||||||
|
#### extracted_by 값
|
||||||
|
- `AI`: AI 자동 추출
|
||||||
|
- `MANUAL`: 사용자 수동 작성
|
||||||
|
|
||||||
|
#### 인덱스
|
||||||
|
- `idx_todos_extracted`: (extracted_by)
|
||||||
|
- `idx_todos_meeting`: (meeting_id)
|
||||||
|
|
||||||
|
#### 사용 패턴
|
||||||
|
```sql
|
||||||
|
-- AI 추출 Todo 조회
|
||||||
|
SELECT * FROM todos
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND extracted_by = 'AI'
|
||||||
|
ORDER BY extraction_confidence DESC;
|
||||||
|
|
||||||
|
-- 신뢰도 높은 Todo 조회
|
||||||
|
SELECT * FROM todos
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND extracted_by = 'AI'
|
||||||
|
AND extraction_confidence >= 0.80;
|
||||||
|
|
||||||
|
-- 안건별 Todo 조회
|
||||||
|
SELECT * FROM todos
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND section_reference = '안건 1';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 데이터 플로우
|
||||||
|
|
||||||
|
### 회의 종료 시 데이터 저장 순서
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 참석자별 회의록 저장 (minutes)
|
||||||
|
├─ user_id: 각 참석자 ID
|
||||||
|
├─ is_consolidated: false
|
||||||
|
└─ section_type: 'PARTICIPANT'
|
||||||
|
|
||||||
|
2. AI Service 호출 → 통합 요약 생성
|
||||||
|
|
||||||
|
3. 통합 회의록 저장 (minutes)
|
||||||
|
├─ is_consolidated: true
|
||||||
|
├─ section_type: 'CONSOLIDATED'
|
||||||
|
└─ consolidated_by: 'AI'
|
||||||
|
|
||||||
|
4. 안건별 섹션 저장 (agenda_sections)
|
||||||
|
├─ meeting_id: 회의 ID
|
||||||
|
├─ minutes_id: 통합 회의록 ID
|
||||||
|
└─ AI 요약 결과 (discussions, decisions, opinions 등)
|
||||||
|
|
||||||
|
5. AI 요약 결과 캐싱 (ai_summaries)
|
||||||
|
├─ summary_type: 'CONSOLIDATED'
|
||||||
|
├─ source_minutes_ids: 참석자 회의록 ID 배열
|
||||||
|
└─ result: 전체 AI 응답 (JSON)
|
||||||
|
|
||||||
|
6. Todo 자동 추출 및 저장 (todos)
|
||||||
|
├─ extracted_by: 'AI'
|
||||||
|
├─ section_reference: 관련 안건
|
||||||
|
└─ extraction_confidence: 신뢰도 점수
|
||||||
|
|
||||||
|
7. 통계 정보 캐싱 (ai_summaries)
|
||||||
|
├─ keywords: 주요 키워드 배열
|
||||||
|
└─ statistics: 참석자 수, 안건 수, Todo 수 등
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 ERD (Entity Relationship Diagram)
|
||||||
|
|
||||||
|
```
|
||||||
|
meetings
|
||||||
|
├─ 1:N → minutes (참석자별 회의록)
|
||||||
|
│ ├─ user_id (참석자)
|
||||||
|
│ └─ is_consolidated = false
|
||||||
|
│
|
||||||
|
├─ 1:1 → minutes (통합 회의록)
|
||||||
|
│ └─ is_consolidated = true
|
||||||
|
│
|
||||||
|
├─ 1:N → agenda_sections (안건별 섹션)
|
||||||
|
│ ├─ FK: minutes_id (통합 회의록)
|
||||||
|
│ └─ FK: meeting_id
|
||||||
|
│
|
||||||
|
├─ 1:N → ai_summaries (AI 요약 캐시)
|
||||||
|
│ ├─ summary_type: CONSOLIDATED | TODO_EXTRACTION
|
||||||
|
│ └─ source_minutes_ids (JSON)
|
||||||
|
│
|
||||||
|
└─ 1:N → todos (할일)
|
||||||
|
├─ extracted_by: AI | MANUAL
|
||||||
|
├─ section_reference
|
||||||
|
└─ extraction_confidence
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 주요 쿼리 예시
|
||||||
|
|
||||||
|
### 1. 회의 종료 후 전체 데이터 조회
|
||||||
|
```sql
|
||||||
|
-- 회의 기본 정보 + 통계
|
||||||
|
SELECT
|
||||||
|
m.meeting_id,
|
||||||
|
m.title,
|
||||||
|
m.status,
|
||||||
|
s.statistics->>'participantsCount' as participants,
|
||||||
|
s.statistics->>'durationMinutes' as duration,
|
||||||
|
s.statistics->>'agendasCount' as agendas,
|
||||||
|
s.statistics->>'todosCount' as todos,
|
||||||
|
s.keywords
|
||||||
|
FROM meetings m
|
||||||
|
LEFT JOIN ai_summaries s ON m.meeting_id = s.meeting_id
|
||||||
|
AND s.summary_type = 'CONSOLIDATED'
|
||||||
|
WHERE m.meeting_id = 'meeting-123';
|
||||||
|
|
||||||
|
-- 안건별 섹션 + Todo
|
||||||
|
SELECT
|
||||||
|
a.agenda_number,
|
||||||
|
a.agenda_title,
|
||||||
|
a.ai_summary_short,
|
||||||
|
a.discussions,
|
||||||
|
a.decisions,
|
||||||
|
a.pending_items,
|
||||||
|
json_agg(
|
||||||
|
json_build_object(
|
||||||
|
'content', t.content,
|
||||||
|
'assignee', t.assignee,
|
||||||
|
'dueDate', t.due_date,
|
||||||
|
'priority', t.priority,
|
||||||
|
'confidence', t.extraction_confidence
|
||||||
|
)
|
||||||
|
) as todos
|
||||||
|
FROM agenda_sections a
|
||||||
|
LEFT JOIN todos t ON a.meeting_id = t.meeting_id
|
||||||
|
AND t.section_reference = CONCAT('안건 ', a.agenda_number)
|
||||||
|
WHERE a.meeting_id = 'meeting-123'
|
||||||
|
GROUP BY a.id, a.agenda_number, a.agenda_title,
|
||||||
|
a.ai_summary_short, a.discussions, a.decisions, a.pending_items
|
||||||
|
ORDER BY a.agenda_number;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 참석자별 회의록 vs AI 통합 회의록 비교
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
'PARTICIPANT' as type,
|
||||||
|
user_id,
|
||||||
|
content,
|
||||||
|
created_at
|
||||||
|
FROM minutes
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND is_consolidated = false
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'CONSOLIDATED' as type,
|
||||||
|
'AI' as user_id,
|
||||||
|
content,
|
||||||
|
created_at
|
||||||
|
FROM minutes
|
||||||
|
WHERE meeting_id = 'meeting-123'
|
||||||
|
AND is_consolidated = true
|
||||||
|
ORDER BY type, created_at;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. AI 성능 모니터링
|
||||||
|
```sql
|
||||||
|
-- AI 처리 시간 통계
|
||||||
|
SELECT
|
||||||
|
summary_type,
|
||||||
|
COUNT(*) as total_count,
|
||||||
|
AVG(processing_time_ms) as avg_time_ms,
|
||||||
|
MIN(processing_time_ms) as min_time_ms,
|
||||||
|
MAX(processing_time_ms) as max_time_ms,
|
||||||
|
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY processing_time_ms) as p95_time_ms
|
||||||
|
FROM ai_summaries
|
||||||
|
WHERE created_at >= CURRENT_DATE - INTERVAL '7 days'
|
||||||
|
GROUP BY summary_type;
|
||||||
|
|
||||||
|
-- Todo 추출 정확도 (신뢰도 분포)
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN extraction_confidence >= 0.90 THEN 'HIGH (>=0.90)'
|
||||||
|
WHEN extraction_confidence >= 0.70 THEN 'MEDIUM (0.70-0.89)'
|
||||||
|
ELSE 'LOW (<0.70)'
|
||||||
|
END as confidence_level,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM todos
|
||||||
|
WHERE extracted_by = 'AI'
|
||||||
|
AND created_at >= CURRENT_DATE - INTERVAL '7 days'
|
||||||
|
GROUP BY confidence_level
|
||||||
|
ORDER BY confidence_level DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 마이그레이션 실행 방법
|
||||||
|
|
||||||
|
### 1. 로컬 환경
|
||||||
|
```bash
|
||||||
|
# Flyway 마이그레이션 (Spring Boot)
|
||||||
|
./gradlew flywayMigrate
|
||||||
|
|
||||||
|
# 또는 직접 SQL 실행
|
||||||
|
psql -h localhost -U postgres -d hgzero -f meeting/src/main/resources/db/migration/V3__add_meeting_end_support.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Docker Compose 환경
|
||||||
|
```bash
|
||||||
|
# 컨테이너 재시작 (자동 마이그레이션)
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d meeting-service
|
||||||
|
|
||||||
|
# 마이그레이션 로그 확인
|
||||||
|
docker-compose logs -f meeting-service | grep "Flyway"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 프로덕션 환경
|
||||||
|
```bash
|
||||||
|
# 1. 백업
|
||||||
|
pg_dump -h prod-db-host -U postgres -d hgzero > backup_$(date +%Y%m%d_%H%M%S).sql
|
||||||
|
|
||||||
|
# 2. 마이그레이션 실행
|
||||||
|
psql -h prod-db-host -U postgres -d hgzero -f V3__add_meeting_end_support.sql
|
||||||
|
|
||||||
|
# 3. 검증
|
||||||
|
psql -h prod-db-host -U postgres -d hgzero -c "\d+ agenda_sections"
|
||||||
|
psql -h prod-db-host -U postgres -d hgzero -c "\d+ ai_summaries"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 검증 체크리스트
|
||||||
|
|
||||||
|
### 마이그레이션 후 검증
|
||||||
|
- [ ] `minutes` 테이블에 새 컬럼 추가 확인
|
||||||
|
- [ ] `agenda_sections` 테이블 생성 확인
|
||||||
|
- [ ] `ai_summaries` 테이블 생성 확인
|
||||||
|
- [ ] `todos` 테이블 확장 확인
|
||||||
|
- [ ] 모든 인덱스 생성 확인
|
||||||
|
- [ ] 외래키 제약조건 확인
|
||||||
|
- [ ] 트리거 생성 확인 (updated_at)
|
||||||
|
|
||||||
|
### 성능 검증
|
||||||
|
- [ ] 참석자별 회의록 조회 성능 (인덱스 활용)
|
||||||
|
- [ ] 안건별 섹션 조회 성능
|
||||||
|
- [ ] AI 캐시 조회 성능 (<100ms)
|
||||||
|
- [ ] JSON 필드 쿼리 성능
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 참고 사항
|
||||||
|
|
||||||
|
### JSON 필드 쿼리 (PostgreSQL)
|
||||||
|
```sql
|
||||||
|
-- JSON 배열 길이
|
||||||
|
SELECT jsonb_array_length(decisions::jsonb) FROM agenda_sections;
|
||||||
|
|
||||||
|
-- JSON 필드 추출
|
||||||
|
SELECT
|
||||||
|
keywords->0 as first_keyword,
|
||||||
|
statistics->>'todosCount' as todo_count
|
||||||
|
FROM ai_summaries;
|
||||||
|
|
||||||
|
-- JSON 배열 펼치기
|
||||||
|
SELECT
|
||||||
|
agenda_title,
|
||||||
|
jsonb_array_elements_text(decisions::jsonb) as decision
|
||||||
|
FROM agenda_sections;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 성능 최적화 팁
|
||||||
|
1. **캐싱 활용**: ai_summaries 테이블을 우선 조회하여 불필요한 AI 호출 방지
|
||||||
|
2. **인덱스 활용**: meeting_id + summary_type 복합 인덱스로 빠른 조회
|
||||||
|
3. **JSON 필드**: PostgreSQL의 jsonb 타입 사용 권장 (인덱스 지원)
|
||||||
|
4. **파티셔닝**: 대용량 데이터의 경우 created_at 기준 파티셔닝 고려
|
||||||
151
docs/ERD-회의종료.puml
Normal file
151
docs/ERD-회의종료.puml
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
@startuml ERD-회의종료
|
||||||
|
!theme mono
|
||||||
|
|
||||||
|
' ========================================
|
||||||
|
' 회의종료 기능 ERD
|
||||||
|
' ========================================
|
||||||
|
|
||||||
|
!define TABLE(name,desc) class name as "desc" << (T,#FFAAAA) >>
|
||||||
|
!define PRIMARY_KEY(x) <b>PK: x</b>
|
||||||
|
!define FOREIGN_KEY(x) <i>FK: x</i>
|
||||||
|
!define NOT_NULL(x) <u>x</u>
|
||||||
|
!define UNIQUE(x) <color:blue>x</color>
|
||||||
|
|
||||||
|
' 기존 테이블
|
||||||
|
TABLE(meetings, "meetings\n회의") {
|
||||||
|
PRIMARY_KEY(meeting_id: VARCHAR(50))
|
||||||
|
--
|
||||||
|
title: VARCHAR(200)
|
||||||
|
start_time: TIMESTAMP
|
||||||
|
end_time: TIMESTAMP
|
||||||
|
status: VARCHAR(20)
|
||||||
|
created_at: TIMESTAMP
|
||||||
|
updated_at: TIMESTAMP
|
||||||
|
}
|
||||||
|
|
||||||
|
TABLE(meeting_participants, "meeting_participants\n회의 참석자") {
|
||||||
|
PRIMARY_KEY(meeting_id, user_id)
|
||||||
|
--
|
||||||
|
FOREIGN_KEY(meeting_id: VARCHAR(50))
|
||||||
|
FOREIGN_KEY(user_id: VARCHAR(100))
|
||||||
|
invitation_status: VARCHAR(20)
|
||||||
|
attended: BOOLEAN
|
||||||
|
created_at: TIMESTAMP
|
||||||
|
}
|
||||||
|
|
||||||
|
' 확장된 minutes 테이블
|
||||||
|
TABLE(minutes, "minutes\n회의록") {
|
||||||
|
PRIMARY_KEY(id: VARCHAR(36))
|
||||||
|
--
|
||||||
|
FOREIGN_KEY(meeting_id: VARCHAR(50))
|
||||||
|
content: TEXT
|
||||||
|
NOT_NULL(user_id: VARCHAR(100))
|
||||||
|
NOT_NULL(is_consolidated: BOOLEAN)
|
||||||
|
consolidated_by: VARCHAR(255)
|
||||||
|
section_type: VARCHAR(50)
|
||||||
|
created_at: TIMESTAMP
|
||||||
|
updated_at: TIMESTAMP
|
||||||
|
}
|
||||||
|
|
||||||
|
' 신규 테이블: agenda_sections
|
||||||
|
TABLE(agenda_sections, "agenda_sections\n안건별 섹션") {
|
||||||
|
PRIMARY_KEY(id: VARCHAR(36))
|
||||||
|
--
|
||||||
|
FOREIGN_KEY(minutes_id: VARCHAR(36))
|
||||||
|
FOREIGN_KEY(meeting_id: VARCHAR(50))
|
||||||
|
NOT_NULL(agenda_number: INT)
|
||||||
|
NOT_NULL(agenda_title: VARCHAR(200))
|
||||||
|
ai_summary_short: TEXT
|
||||||
|
discussions: TEXT
|
||||||
|
decisions: JSON
|
||||||
|
pending_items: JSON
|
||||||
|
opinions: JSON
|
||||||
|
created_at: TIMESTAMP
|
||||||
|
updated_at: TIMESTAMP
|
||||||
|
}
|
||||||
|
|
||||||
|
' 신규 테이블: ai_summaries
|
||||||
|
TABLE(ai_summaries, "ai_summaries\nAI 요약 캐시") {
|
||||||
|
PRIMARY_KEY(id: VARCHAR(36))
|
||||||
|
--
|
||||||
|
FOREIGN_KEY(meeting_id: VARCHAR(50))
|
||||||
|
NOT_NULL(summary_type: VARCHAR(50))
|
||||||
|
NOT_NULL(source_minutes_ids: JSON)
|
||||||
|
NOT_NULL(result: JSON)
|
||||||
|
processing_time_ms: INT
|
||||||
|
model_version: VARCHAR(50)
|
||||||
|
keywords: JSON
|
||||||
|
statistics: JSON
|
||||||
|
created_at: TIMESTAMP
|
||||||
|
}
|
||||||
|
|
||||||
|
' 확장된 todos 테이블
|
||||||
|
TABLE(todos, "todos\n할일") {
|
||||||
|
PRIMARY_KEY(id: VARCHAR(36))
|
||||||
|
--
|
||||||
|
FOREIGN_KEY(meeting_id: VARCHAR(50))
|
||||||
|
content: TEXT
|
||||||
|
assignee: VARCHAR(100)
|
||||||
|
due_date: DATE
|
||||||
|
priority: VARCHAR(20)
|
||||||
|
status: VARCHAR(20)
|
||||||
|
NOT_NULL(extracted_by: VARCHAR(50))
|
||||||
|
section_reference: VARCHAR(200)
|
||||||
|
extraction_confidence: DECIMAL(3,2)
|
||||||
|
created_at: TIMESTAMP
|
||||||
|
updated_at: TIMESTAMP
|
||||||
|
}
|
||||||
|
|
||||||
|
' 관계 정의
|
||||||
|
meetings "1" --> "N" minutes : "has"
|
||||||
|
meetings "1" --> "N" meeting_participants : "has"
|
||||||
|
meetings "1" --> "N" agenda_sections : "has"
|
||||||
|
meetings "1" --> "N" ai_summaries : "has"
|
||||||
|
meetings "1" --> "N" todos : "has"
|
||||||
|
|
||||||
|
minutes "1" --> "N" agenda_sections : "contains"
|
||||||
|
minutes "1" ..> "1" meeting_participants : "written by\n(user_id)"
|
||||||
|
|
||||||
|
' 노트 추가
|
||||||
|
note right of minutes
|
||||||
|
**참석자별 회의록**
|
||||||
|
- is_consolidated = false
|
||||||
|
- section_type = 'PARTICIPANT'
|
||||||
|
- user_id: 작성자
|
||||||
|
|
||||||
|
**AI 통합 회의록**
|
||||||
|
- is_consolidated = true
|
||||||
|
- section_type = 'CONSOLIDATED'
|
||||||
|
- user_id: NULL
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of agenda_sections
|
||||||
|
**JSON 필드 구조**
|
||||||
|
- decisions: ["결정1", "결정2"]
|
||||||
|
- pending_items: ["보류1"]
|
||||||
|
- opinions: [
|
||||||
|
{"speaker": "김민준", "opinion": "..."}
|
||||||
|
]
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of ai_summaries
|
||||||
|
**summary_type**
|
||||||
|
- CONSOLIDATED: 통합 요약
|
||||||
|
- TODO_EXTRACTION: Todo 추출
|
||||||
|
|
||||||
|
**캐싱 전략**
|
||||||
|
- 재조회 시 DB 우선 조회
|
||||||
|
- 처리 시간 <0.5초
|
||||||
|
end note
|
||||||
|
|
||||||
|
note right of todos
|
||||||
|
**extracted_by**
|
||||||
|
- AI: AI 자동 추출
|
||||||
|
- MANUAL: 사용자 수동 작성
|
||||||
|
|
||||||
|
**extraction_confidence**
|
||||||
|
- 0.00 ~ 1.00
|
||||||
|
- 0.80 이상: 신뢰도 높음
|
||||||
|
end note
|
||||||
|
|
||||||
|
@enduml
|
||||||
107
docs/setup-eventhub-policy.md
Normal file
107
docs/setup-eventhub-policy.md
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# Event Hub 정책 설정 가이드
|
||||||
|
|
||||||
|
## 📌 개요
|
||||||
|
Azure Event Hub 사용을 위한 공유 액세스 정책(SAS Policy) 설정 방법
|
||||||
|
|
||||||
|
## 🔧 설정 단계
|
||||||
|
|
||||||
|
### 1. Azure Portal 접속
|
||||||
|
- Event Hub Namespace: `hgzero-eventhub-ns`
|
||||||
|
- 리소스 그룹: `rg-digitalgarage-02`
|
||||||
|
|
||||||
|
### 2. 정책 생성
|
||||||
|
|
||||||
|
#### (1) STT 서비스용 Send 정책
|
||||||
|
```
|
||||||
|
정책 이름: stt-send-policy
|
||||||
|
권한: ✓ Send
|
||||||
|
용도: STT 서비스가 음성 인식 결과를 Event Hub에 발행
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성 방법**:
|
||||||
|
1. `hgzero-eventhub-ns` → 공유 액세스 정책
|
||||||
|
2. `+ 추가` 클릭
|
||||||
|
3. 정책 이름: `stt-send-policy`
|
||||||
|
4. **Send** 체크
|
||||||
|
5. **만들기** 클릭
|
||||||
|
6. 생성된 정책 클릭 → **연결 문자열-기본 키** 복사
|
||||||
|
|
||||||
|
#### (2) AI 서비스용 Listen 정책
|
||||||
|
```
|
||||||
|
정책 이름: ai-listen-policy
|
||||||
|
권한: ✓ Listen
|
||||||
|
용도: AI 서비스가 Event Hub에서 이벤트를 구독
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성 방법**:
|
||||||
|
1. 공유 액세스 정책 → `+ 추가`
|
||||||
|
2. 정책 이름: `ai-listen-policy`
|
||||||
|
3. **Listen** 체크
|
||||||
|
4. **만들기** → 연결 문자열 복사
|
||||||
|
|
||||||
|
#### (3) 관리용 Manage 정책
|
||||||
|
```
|
||||||
|
정책 이름: hgzero-manage-policy
|
||||||
|
권한: ✓ Manage (Send + Listen 포함)
|
||||||
|
용도: 개발, 테스트, 관리 작업
|
||||||
|
```
|
||||||
|
|
||||||
|
**생성 방법**:
|
||||||
|
1. 공유 액세스 정책 → `+ 추가`
|
||||||
|
2. 정책 이름: `hgzero-manage-policy`
|
||||||
|
3. **Manage** 체크 (자동으로 Send/Listen 포함)
|
||||||
|
4. **만들기** → 연결 문자열 복사
|
||||||
|
|
||||||
|
### 3. 연결 문자열 형식
|
||||||
|
```
|
||||||
|
Endpoint=sb://hgzero-eventhub-ns.servicebus.windows.net/;
|
||||||
|
SharedAccessKeyName=stt-send-policy;
|
||||||
|
SharedAccessKey=<YOUR_KEY>;
|
||||||
|
EntityPath=hgzero-eventhub-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. IntelliJ 실행 프로파일 설정
|
||||||
|
|
||||||
|
#### STT 서비스 (`.run/SttServiceApplication.run.xml`)
|
||||||
|
```xml
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<entry key="EVENTHUB_CONNECTION_STRING" value="Endpoint=sb://...;SharedAccessKeyName=stt-send-policy;SharedAccessKey=..."/>
|
||||||
|
<entry key="EVENTHUB_NAME" value="hgzero-eventhub-name"/>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### AI 서비스 (`.run/AiServiceApplication.run.xml`)
|
||||||
|
```xml
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<entry key="EVENTHUB_CONNECTION_STRING" value="Endpoint=sb://...;SharedAccessKeyName=ai-listen-policy;SharedAccessKey=..."/>
|
||||||
|
<entry key="EVENTHUB_NAME" value="hgzero-eventhub-name"/>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ 검증 방법
|
||||||
|
|
||||||
|
### 연결 테스트
|
||||||
|
```bash
|
||||||
|
# STT 서비스 시작 후 로그 확인
|
||||||
|
# 성공 메시지 예시:
|
||||||
|
[INFO] EventHubProducerClient - Connected to Event Hub: hgzero-eventhub-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### 권한 검증
|
||||||
|
- **Send 정책**: 이벤트 발행만 가능
|
||||||
|
- **Listen 정책**: 이벤트 구독만 가능
|
||||||
|
- **Manage 정책**: 모든 작업 가능
|
||||||
|
|
||||||
|
## 🔐 보안 권장사항
|
||||||
|
|
||||||
|
1. **최소 권한 원칙**: 각 서비스는 필요한 권한만 부여
|
||||||
|
2. **키 회전**: 정기적으로 액세스 키 재생성
|
||||||
|
3. **환경 분리**: Dev/Prod 환경별 별도 정책 사용
|
||||||
|
4. **연결 문자열 보호**: Git에 커밋하지 않음 (환경 변수 사용)
|
||||||
|
|
||||||
|
## 📚 참고 문서
|
||||||
|
- [Azure Event Hub SAS 인증](https://learn.microsoft.com/azure/event-hubs/authenticate-shared-access-signature)
|
||||||
360
docs/회의종료-개발계획.md
Normal file
360
docs/회의종료-개발계획.md
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
# 회의종료 기능 개발 계획
|
||||||
|
|
||||||
|
## 📋 개요
|
||||||
|
회의가 종료되면 모든 참석자의 회의록을 수집하여 Claude AI가 통합 요약하고 Todo를 자동 추출하는 기능
|
||||||
|
|
||||||
|
## 🎯 핵심 기능
|
||||||
|
1. **참석자별 회의록 통합**: 모든 참석자가 작성한 회의록을 DB에서 조회
|
||||||
|
2. **AI 통합 요약**: Claude AI가 안건별로 요약 및 구조화
|
||||||
|
3. **Todo 자동 추출**: AI가 회의록에서 액션 아이템 자동 추출
|
||||||
|
4. **통계 생성**: 참석자 수, 회의 시간, 안건 수, Todo 수 통계
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗄️ 데이터베이스 스키마 설계
|
||||||
|
|
||||||
|
### 1. minutes (회의록 테이블) - 확장
|
||||||
|
```sql
|
||||||
|
-- 기존 테이블 확장
|
||||||
|
ALTER TABLE minutes ADD COLUMN user_id VARCHAR(50); -- 작성자 ID
|
||||||
|
ALTER TABLE minutes ADD COLUMN is_consolidated BOOLEAN DEFAULT FALSE; -- 통합 회의록 여부
|
||||||
|
ALTER TABLE minutes ADD COLUMN consolidated_by VARCHAR(255); -- 통합 처리자 (AI)
|
||||||
|
|
||||||
|
-- 인덱스 추가
|
||||||
|
CREATE INDEX idx_minutes_meeting_user ON minutes(meeting_id, user_id);
|
||||||
|
CREATE INDEX idx_minutes_consolidated ON minutes(is_consolidated);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. agenda_sections (안건별 섹션 테이블) - 신규
|
||||||
|
```sql
|
||||||
|
CREATE TABLE agenda_sections (
|
||||||
|
id VARCHAR(36) PRIMARY KEY,
|
||||||
|
minutes_id VARCHAR(36) NOT NULL,
|
||||||
|
meeting_id VARCHAR(36) NOT NULL,
|
||||||
|
agenda_number INT NOT NULL, -- 안건 번호 (1, 2, 3...)
|
||||||
|
agenda_title VARCHAR(200) NOT NULL, -- 안건 제목
|
||||||
|
|
||||||
|
-- AI 요약 결과
|
||||||
|
ai_summary_short TEXT, -- 짧은 요약 (1줄)
|
||||||
|
discussion TEXT, -- 논의 사항
|
||||||
|
decisions JSON, -- 결정 사항 배열
|
||||||
|
pending_items JSON, -- 보류 사항 배열
|
||||||
|
opinions JSON, -- 참석자별 의견 배열
|
||||||
|
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
FOREIGN KEY (minutes_id) REFERENCES minutes(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (meeting_id) REFERENCES meetings(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
INDEX idx_sections_meeting (meeting_id),
|
||||||
|
INDEX idx_sections_agenda (meeting_id, agenda_number)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. ai_summaries (AI 요약 결과 캐시) - 신규
|
||||||
|
```sql
|
||||||
|
CREATE TABLE ai_summaries (
|
||||||
|
id VARCHAR(36) PRIMARY KEY,
|
||||||
|
meeting_id VARCHAR(36) NOT NULL,
|
||||||
|
summary_type VARCHAR(50) NOT NULL, -- 'CONSOLIDATED', 'TODO_EXTRACTION'
|
||||||
|
|
||||||
|
-- 입력 정보
|
||||||
|
source_minutes_ids JSON NOT NULL, -- 통합에 사용된 회의록 ID 배열
|
||||||
|
|
||||||
|
-- AI 처리 결과
|
||||||
|
result JSON NOT NULL, -- AI 응답 전체 결과
|
||||||
|
processing_time_ms INT, -- 처리 시간 (밀리초)
|
||||||
|
model_version VARCHAR(50), -- 사용한 AI 모델 버전
|
||||||
|
|
||||||
|
-- 통계
|
||||||
|
keywords JSON, -- 주요 키워드 배열
|
||||||
|
statistics JSON, -- 통계 정보 (참석자 수, 안건 수 등)
|
||||||
|
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
FOREIGN KEY (meeting_id) REFERENCES meetings(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
INDEX idx_summaries_meeting (meeting_id),
|
||||||
|
INDEX idx_summaries_type (meeting_id, summary_type)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. todos (Todo 테이블) - 확장
|
||||||
|
```sql
|
||||||
|
-- 기존 테이블 확장
|
||||||
|
ALTER TABLE todos ADD COLUMN extracted_by VARCHAR(50) DEFAULT 'AI'; -- 'AI' 또는 'MANUAL'
|
||||||
|
ALTER TABLE todos ADD COLUMN section_reference VARCHAR(200); -- 관련 안건 참조
|
||||||
|
ALTER TABLE todos ADD COLUMN extraction_confidence DECIMAL(3,2); -- AI 추출 신뢰도 (0.00~1.00)
|
||||||
|
|
||||||
|
-- 인덱스 추가
|
||||||
|
CREATE INDEX idx_todos_extracted ON todos(extracted_by);
|
||||||
|
CREATE INDEX idx_todos_meeting ON todos(meeting_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 API 설계
|
||||||
|
|
||||||
|
### Meeting Service API
|
||||||
|
|
||||||
|
#### 1. 참석자별 회의록 조회
|
||||||
|
```yaml
|
||||||
|
GET /meetings/{meetingId}/minutes/by-participants
|
||||||
|
Response:
|
||||||
|
participantMinutes:
|
||||||
|
- userId: "user1"
|
||||||
|
userName: "김민준"
|
||||||
|
minutesId: "uuid"
|
||||||
|
content: "회의록 내용..."
|
||||||
|
status: "DRAFT"
|
||||||
|
- userId: "user2"
|
||||||
|
userName: "박서연"
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 안건별 섹션 조회
|
||||||
|
```yaml
|
||||||
|
GET /meetings/{meetingId}/agenda-sections
|
||||||
|
Response:
|
||||||
|
sections:
|
||||||
|
- agendaNumber: 1
|
||||||
|
agendaTitle: "신제품 기획 방향성"
|
||||||
|
aiSummaryShort: "타겟 고객을 20-30대로 설정..."
|
||||||
|
discussions: "..."
|
||||||
|
decisions: [...]
|
||||||
|
todos: [...]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 회의 통계 조회
|
||||||
|
```yaml
|
||||||
|
GET /meetings/{meetingId}/statistics
|
||||||
|
Response:
|
||||||
|
participantsCount: 4
|
||||||
|
durationMinutes: 90
|
||||||
|
agendasCount: 3
|
||||||
|
todosCount: 5
|
||||||
|
keywords: ["신제품기획", "예산편성", ...]
|
||||||
|
```
|
||||||
|
|
||||||
|
### AI Service API
|
||||||
|
|
||||||
|
#### 1. 통합 회의록 요약 API (신규)
|
||||||
|
```yaml
|
||||||
|
POST /transcripts/consolidate
|
||||||
|
Request:
|
||||||
|
meetingId: "uuid"
|
||||||
|
participantMinutes:
|
||||||
|
- userId: "user1"
|
||||||
|
content: "회의록 내용..."
|
||||||
|
- userId: "user2"
|
||||||
|
content: "회의록 내용..."
|
||||||
|
agendas:
|
||||||
|
- number: 1
|
||||||
|
title: "신제품 기획 방향성"
|
||||||
|
|
||||||
|
Response:
|
||||||
|
consolidatedMinutesId: "uuid"
|
||||||
|
agendaSections:
|
||||||
|
- agendaNumber: 1
|
||||||
|
aiSummaryShort: "타겟 고객을 20-30대로 설정..."
|
||||||
|
discussions: "..."
|
||||||
|
decisions: [...]
|
||||||
|
pendingItems: [...]
|
||||||
|
keywords: ["신제품기획", "예산편성"]
|
||||||
|
processingTimeMs: 3500
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Todo 자동 추출 API (기존)
|
||||||
|
```yaml
|
||||||
|
POST /todos/extract
|
||||||
|
Request:
|
||||||
|
meetingId: "uuid"
|
||||||
|
minutesContent: "통합된 회의록 전체 내용..."
|
||||||
|
|
||||||
|
Response:
|
||||||
|
todos:
|
||||||
|
- content: "시장 조사 보고서 작성"
|
||||||
|
assignee: "김민준"
|
||||||
|
dueDate: "2025-11-01"
|
||||||
|
priority: "HIGH"
|
||||||
|
sectionReference: "안건 1"
|
||||||
|
confidence: 0.92
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤖 Claude AI 프롬프트 설계
|
||||||
|
|
||||||
|
### 1. 통합 회의록 요약 프롬프트
|
||||||
|
```
|
||||||
|
당신은 회의록 통합 전문가입니다.
|
||||||
|
|
||||||
|
입력:
|
||||||
|
- 회의 제목: {meetingTitle}
|
||||||
|
- 안건 목록: {agendas}
|
||||||
|
- 참석자별 회의록:
|
||||||
|
* {userName1}: {content1}
|
||||||
|
* {userName2}: {content2}
|
||||||
|
...
|
||||||
|
|
||||||
|
작업:
|
||||||
|
각 안건별로 다음을 생성하세요:
|
||||||
|
1. 짧은 요약 (1줄, 20자 이내)
|
||||||
|
2. 논의 사항 (핵심 내용 3-5문장)
|
||||||
|
3. 결정 사항 (배열 형태)
|
||||||
|
4. 보류 사항 (배열 형태)
|
||||||
|
5. 참석자별 의견 (speaker, opinion)
|
||||||
|
|
||||||
|
출력 형식 (JSON):
|
||||||
|
{
|
||||||
|
"agendaSections": [
|
||||||
|
{
|
||||||
|
"agendaNumber": 1,
|
||||||
|
"aiSummaryShort": "...",
|
||||||
|
"discussions": "...",
|
||||||
|
"decisions": ["결정1", "결정2"],
|
||||||
|
"pendingItems": ["보류1"],
|
||||||
|
"opinions": [
|
||||||
|
{"speaker": "김민준", "opinion": "..."}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"keywords": ["키워드1", "키워드2", ...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Todo 자동 추출 프롬프트
|
||||||
|
```
|
||||||
|
당신은 Todo 추출 전문가입니다.
|
||||||
|
|
||||||
|
입력:
|
||||||
|
- 회의록 전체 내용: {minutesContent}
|
||||||
|
- 참석자 목록: {participants}
|
||||||
|
|
||||||
|
작업:
|
||||||
|
회의록에서 액션 아이템(Todo)을 추출하세요.
|
||||||
|
- "~하기로 함", "~가 작성", "~까지 완료" 등의 패턴 탐지
|
||||||
|
- 담당자와 마감일 식별
|
||||||
|
- 우선순위 판단 (HIGH/MEDIUM/LOW)
|
||||||
|
|
||||||
|
출력 형식 (JSON):
|
||||||
|
{
|
||||||
|
"todos": [
|
||||||
|
{
|
||||||
|
"content": "시장 조사 보고서 작성",
|
||||||
|
"assignee": "김민준",
|
||||||
|
"dueDate": "2025-11-01",
|
||||||
|
"priority": "HIGH",
|
||||||
|
"sectionReference": "안건 1",
|
||||||
|
"confidence": 0.92
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 통합 플로우
|
||||||
|
|
||||||
|
### 회의 종료 처리 시퀀스
|
||||||
|
```
|
||||||
|
1. 사용자가 "회의 종료" 버튼 클릭
|
||||||
|
↓
|
||||||
|
2. Meeting Service: 회의 상태를 'ENDED'로 변경
|
||||||
|
↓
|
||||||
|
3. Meeting Service: 모든 참석자의 회의록 조회 (GET /meetings/{meetingId}/minutes/by-participants)
|
||||||
|
↓
|
||||||
|
4. AI Service 호출: 통합 요약 요청 (POST /transcripts/consolidate)
|
||||||
|
↓
|
||||||
|
5. Claude AI: 안건별 요약 및 구조화
|
||||||
|
↓
|
||||||
|
6. AI Service: agenda_sections 테이블에 저장
|
||||||
|
↓
|
||||||
|
7. AI Service 호출: Todo 자동 추출 (POST /todos/extract)
|
||||||
|
↓
|
||||||
|
8. Claude AI: Todo 추출 및 담당자 식별
|
||||||
|
↓
|
||||||
|
9. Meeting Service: todos 테이블에 저장
|
||||||
|
↓
|
||||||
|
10. Meeting Service: ai_summaries 테이블에 캐시 저장
|
||||||
|
↓
|
||||||
|
11. 프론트엔드: 07-회의종료.html 화면 렌더링
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 성능 고려사항
|
||||||
|
|
||||||
|
### 1. 처리 시간 목표
|
||||||
|
- AI 통합 요약: 3-5초 이내
|
||||||
|
- Todo 추출: 2-3초 이내
|
||||||
|
- 전체 회의 종료 처리: 10초 이내
|
||||||
|
|
||||||
|
### 2. 최적화 방안
|
||||||
|
- **병렬 처리**: 통합 요약과 Todo 추출을 병렬로 실행
|
||||||
|
- **캐싱**: ai_summaries 테이블에 결과 캐싱 (재조회 시 0.5초 이내)
|
||||||
|
- **비동기 처리**: 회의 종료 후 백그라운드에서 AI 처리
|
||||||
|
- **진행 상태 표시**: WebSocket으로 실시간 진행률 전달
|
||||||
|
|
||||||
|
### 3. 대용량 처리
|
||||||
|
- 10명 이상 참석자: 회의록을 청크 단위로 분할 처리
|
||||||
|
- 긴 회의 (2시간 이상): 안건별 병렬 처리
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 테스트 시나리오
|
||||||
|
|
||||||
|
### 1. 단위 테스트
|
||||||
|
- [ ] 참석자별 회의록 조회 API 테스트
|
||||||
|
- [ ] AI 통합 요약 API 테스트
|
||||||
|
- [ ] Todo 자동 추출 API 테스트
|
||||||
|
- [ ] 안건별 섹션 저장 및 조회 테스트
|
||||||
|
|
||||||
|
### 2. 통합 테스트
|
||||||
|
- [ ] 회의 종료 → AI 요약 → Todo 추출 전체 플로우
|
||||||
|
- [ ] 다수 참석자 (10명) 회의록 통합 테스트
|
||||||
|
- [ ] 긴 회의록 (5000자 이상) 처리 테스트
|
||||||
|
|
||||||
|
### 3. 성능 테스트
|
||||||
|
- [ ] AI 요약 응답 시간 측정 (목표: 5초 이내)
|
||||||
|
- [ ] 동시 다발적 회의 종료 처리 (10개 동시)
|
||||||
|
- [ ] 대용량 회의록 (10000자 이상) 처리 시간
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 개발 일정 (예상)
|
||||||
|
|
||||||
|
### Phase 1: DB 및 기본 API (3일)
|
||||||
|
- Day 1: DB 스키마 설계 및 마이그레이션
|
||||||
|
- Day 2: Meeting Service API 개발
|
||||||
|
- Day 3: 단위 테스트 및 API 검증
|
||||||
|
|
||||||
|
### Phase 2: AI 통합 (4일)
|
||||||
|
- Day 1-2: Claude AI 프롬프트 설계 및 테스트
|
||||||
|
- Day 3: AI Service API 개발
|
||||||
|
- Day 4: 통합 테스트
|
||||||
|
|
||||||
|
### Phase 3: 최적화 및 배포 (2일)
|
||||||
|
- Day 1: 성능 최적화 및 캐싱
|
||||||
|
- Day 2: 프론트엔드 연동 테스트 및 배포
|
||||||
|
|
||||||
|
**총 예상 기간: 9일**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 배포 체크리스트
|
||||||
|
- [ ] DB 마이그레이션 스크립트 준비
|
||||||
|
- [ ] API 명세서 업데이트
|
||||||
|
- [ ] AI 프롬프트 버전 관리
|
||||||
|
- [ ] 성능 모니터링 설정
|
||||||
|
- [ ] 에러 로깅 및 알림 설정
|
||||||
|
- [ ] 백업 및 롤백 계획 수립
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 참고 문서
|
||||||
|
- [유저스토리](../design/userstory.md)
|
||||||
|
- [AI Service API 명세](../design/backend/api/ai-service-api.yaml)
|
||||||
|
- [Meeting Service API 명세](../design/backend/api/meeting-service-api.yaml)
|
||||||
|
- [07-회의종료.html 프로토타입](../design/uiux/prototype/07-회의종료.html)
|
||||||
@ -0,0 +1,192 @@
|
|||||||
|
-- ========================================
|
||||||
|
-- V3: 회의종료 기능을 위한 스키마 확장
|
||||||
|
-- ========================================
|
||||||
|
-- 작성일: 2025-10-28
|
||||||
|
-- 설명: 참석자별 회의록, 안건별 섹션, AI 요약 결과 캐싱, Todo 자동 추출 지원
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- 1. minutes 테이블 확장
|
||||||
|
-- ========================================
|
||||||
|
-- 참석자별 회의록 지원 (user_id로 구분)
|
||||||
|
-- user_id가 NULL이면 AI 통합 회의록, NOT NULL이면 참석자별 회의록
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
-- 코멘트 추가
|
||||||
|
COMMENT ON COLUMN minutes.user_id IS '작성자 사용자 ID (NULL: AI 통합 회의록, NOT NULL: 참석자별 회의록)';
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- 2. agenda_sections 테이블 생성
|
||||||
|
-- ========================================
|
||||||
|
-- 안건별 AI 요약 결과 저장
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agenda_sections (
|
||||||
|
id VARCHAR(36) PRIMARY KEY,
|
||||||
|
minutes_id VARCHAR(36) NOT NULL,
|
||||||
|
meeting_id VARCHAR(50) NOT NULL,
|
||||||
|
|
||||||
|
-- 안건 정보
|
||||||
|
agenda_number INT NOT NULL,
|
||||||
|
agenda_title VARCHAR(200) NOT NULL,
|
||||||
|
|
||||||
|
-- AI 요약 결과
|
||||||
|
ai_summary_short TEXT,
|
||||||
|
discussions TEXT,
|
||||||
|
decisions JSON,
|
||||||
|
pending_items JSON,
|
||||||
|
opinions JSON,
|
||||||
|
|
||||||
|
-- 메타데이터
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
-- 외래키
|
||||||
|
CONSTRAINT fk_agenda_sections_minutes
|
||||||
|
FOREIGN KEY (minutes_id) REFERENCES minutes(id)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_agenda_sections_meeting
|
||||||
|
FOREIGN KEY (meeting_id) REFERENCES meetings(meeting_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스 생성
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sections_meeting ON agenda_sections(meeting_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sections_agenda ON agenda_sections(meeting_id, agenda_number);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sections_minutes ON agenda_sections(minutes_id);
|
||||||
|
|
||||||
|
-- 코멘트 추가
|
||||||
|
COMMENT ON TABLE agenda_sections IS '안건별 회의록 섹션 - AI 요약 결과 저장';
|
||||||
|
COMMENT ON COLUMN agenda_sections.id IS '섹션 고유 ID';
|
||||||
|
COMMENT ON COLUMN agenda_sections.minutes_id IS '회의록 ID (통합 회의록 참조)';
|
||||||
|
COMMENT ON COLUMN agenda_sections.meeting_id IS '회의 ID';
|
||||||
|
COMMENT ON COLUMN agenda_sections.agenda_number IS '안건 번호 (1, 2, 3...)';
|
||||||
|
COMMENT ON COLUMN agenda_sections.agenda_title IS '안건 제목';
|
||||||
|
COMMENT ON COLUMN agenda_sections.ai_summary_short IS 'AI 생성 짧은 요약 (1줄, 20자 이내)';
|
||||||
|
COMMENT ON COLUMN agenda_sections.discussions IS '논의 사항 (핵심 내용 3-5문장)';
|
||||||
|
COMMENT ON COLUMN agenda_sections.decisions IS '결정 사항 배열 (JSON)';
|
||||||
|
COMMENT ON COLUMN agenda_sections.pending_items IS '보류 사항 배열 (JSON)';
|
||||||
|
COMMENT ON COLUMN agenda_sections.opinions IS '참석자별 의견 (JSON: [{speaker, opinion}])';
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- 3. ai_summaries 테이블 생성
|
||||||
|
-- ========================================
|
||||||
|
-- AI 요약 결과 캐싱 및 성능 최적화
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ai_summaries (
|
||||||
|
id VARCHAR(36) PRIMARY KEY,
|
||||||
|
meeting_id VARCHAR(50) NOT NULL,
|
||||||
|
summary_type VARCHAR(50) NOT NULL,
|
||||||
|
|
||||||
|
-- 입력 정보
|
||||||
|
source_minutes_ids JSON NOT NULL,
|
||||||
|
|
||||||
|
-- AI 처리 결과
|
||||||
|
result JSON NOT NULL,
|
||||||
|
processing_time_ms INT,
|
||||||
|
model_version VARCHAR(50) DEFAULT 'claude-3.5-sonnet',
|
||||||
|
|
||||||
|
-- 통계 정보
|
||||||
|
keywords JSON,
|
||||||
|
statistics JSON,
|
||||||
|
|
||||||
|
-- 메타데이터
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
-- 외래키
|
||||||
|
CONSTRAINT fk_ai_summaries_meeting
|
||||||
|
FOREIGN KEY (meeting_id) REFERENCES meetings(meeting_id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 인덱스 생성
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_summaries_meeting ON ai_summaries(meeting_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_summaries_type ON ai_summaries(meeting_id, summary_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_summaries_created ON ai_summaries(created_at);
|
||||||
|
|
||||||
|
-- 코멘트 추가
|
||||||
|
COMMENT ON TABLE ai_summaries IS 'AI 요약 결과 캐시 테이블';
|
||||||
|
COMMENT ON COLUMN ai_summaries.id IS '요약 결과 고유 ID';
|
||||||
|
COMMENT ON COLUMN ai_summaries.meeting_id IS '회의 ID';
|
||||||
|
COMMENT ON COLUMN ai_summaries.summary_type IS '요약 타입 (CONSOLIDATED: 통합 요약, TODO_EXTRACTION: Todo 추출)';
|
||||||
|
COMMENT ON COLUMN ai_summaries.source_minutes_ids IS '통합에 사용된 회의록 ID 배열 (JSON)';
|
||||||
|
COMMENT ON COLUMN ai_summaries.result IS 'AI 응답 전체 결과 (JSON)';
|
||||||
|
COMMENT ON COLUMN ai_summaries.processing_time_ms IS 'AI 처리 시간 (밀리초)';
|
||||||
|
COMMENT ON COLUMN ai_summaries.model_version IS '사용한 AI 모델 버전';
|
||||||
|
COMMENT ON COLUMN ai_summaries.keywords IS '주요 키워드 배열 (JSON)';
|
||||||
|
COMMENT ON COLUMN ai_summaries.statistics IS '통계 정보 (참석자 수, 안건 수 등, JSON)';
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- 4. todos 테이블 확장
|
||||||
|
-- ========================================
|
||||||
|
-- AI 자동 추출 정보 추가
|
||||||
|
|
||||||
|
ALTER TABLE todos
|
||||||
|
ADD COLUMN IF NOT EXISTS extracted_by VARCHAR(50) DEFAULT 'AI',
|
||||||
|
ADD COLUMN IF NOT EXISTS section_reference VARCHAR(200),
|
||||||
|
ADD COLUMN IF NOT EXISTS extraction_confidence DECIMAL(3,2) DEFAULT 0.00;
|
||||||
|
|
||||||
|
-- 인덱스 추가
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_todos_extracted ON todos(extracted_by);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_todos_meeting ON todos(meeting_id);
|
||||||
|
|
||||||
|
-- 코멘트 추가
|
||||||
|
COMMENT ON COLUMN todos.extracted_by IS 'Todo 추출 방법 (AI: AI 자동 추출, MANUAL: 사용자 수동 작성)';
|
||||||
|
COMMENT ON COLUMN todos.section_reference IS '관련 회의록 섹션 참조 (예: "안건 1", "결정사항 #3")';
|
||||||
|
COMMENT ON COLUMN todos.extraction_confidence IS 'AI 추출 신뢰도 점수 (0.00~1.00)';
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- 5. 제약조건 및 트리거 추가
|
||||||
|
-- ========================================
|
||||||
|
|
||||||
|
-- updated_at 자동 업데이트 함수 (PostgreSQL)
|
||||||
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ language 'plpgsql';
|
||||||
|
|
||||||
|
-- agenda_sections 테이블에 updated_at 트리거 추가
|
||||||
|
DROP TRIGGER IF EXISTS update_agenda_sections_updated_at ON agenda_sections;
|
||||||
|
CREATE TRIGGER update_agenda_sections_updated_at
|
||||||
|
BEFORE UPDATE ON agenda_sections
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- 6. 샘플 데이터 (개발 환경용)
|
||||||
|
-- ========================================
|
||||||
|
-- 실제 운영 환경에서는 주석 처리
|
||||||
|
|
||||||
|
-- 샘플 회의록 (참석자별)
|
||||||
|
-- INSERT INTO minutes (id, meeting_id, user_id, content, created_at)
|
||||||
|
-- VALUES
|
||||||
|
-- ('sample-minutes-1', 'sample-meeting-1', 'user1@example.com', '회의록 내용 1...', CURRENT_TIMESTAMP),
|
||||||
|
-- ('sample-minutes-2', 'sample-meeting-1', 'user2@example.com', '회의록 내용 2...', CURRENT_TIMESTAMP);
|
||||||
|
|
||||||
|
-- 샘플 통합 회의록 (user_id가 NULL)
|
||||||
|
-- INSERT INTO minutes (id, meeting_id, user_id, content, created_at)
|
||||||
|
-- VALUES
|
||||||
|
-- ('sample-minutes-consolidated', 'sample-meeting-1', NULL, 'AI 통합 회의록...', CURRENT_TIMESTAMP);
|
||||||
|
|
||||||
|
-- 샘플 안건 섹션
|
||||||
|
-- INSERT INTO agenda_sections (id, minutes_id, meeting_id, agenda_number, agenda_title, ai_summary_short, discussions, decisions, pending_items, opinions)
|
||||||
|
-- VALUES
|
||||||
|
-- ('sample-section-1', 'sample-minutes-consolidated', 'sample-meeting-1', 1, '신제품 기획 방향성',
|
||||||
|
-- '타겟 고객을 20-30대로 설정...',
|
||||||
|
-- '신제품의 주요 타겟 고객층을 20-30대 직장인으로 설정하고...',
|
||||||
|
-- '["타겟 고객: 20-30대 직장인", "UI/UX 개선을 최우선 과제로 설정"]'::json,
|
||||||
|
-- '[]'::json,
|
||||||
|
-- '[{"speaker": "김민준", "opinion": "타겟 고객층을 명확히 설정하여 마케팅 전략 수립 필요"}]'::json);
|
||||||
|
|
||||||
|
-- ========================================
|
||||||
|
-- 7. 권한 설정 (필요시)
|
||||||
|
-- ========================================
|
||||||
|
-- GRANT SELECT, INSERT, UPDATE, DELETE ON agenda_sections TO meeting_service_user;
|
||||||
|
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ai_summaries TO meeting_service_user;
|
||||||
|
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ai_summaries TO ai_service_user;
|
||||||
Loading…
x
Reference in New Issue
Block a user