diff --git a/design/backend/sequence/inner/meeting-회의종료.puml b/design/backend/sequence/inner/meeting-회의종료.puml index 0fb73c4..0805d74 100644 --- a/design/backend/sequence/inner/meeting-회의종료.puml +++ b/design/backend/sequence/inner/meeting-회의종료.puml @@ -1,13 +1,17 @@ -@startuml meeting-회의종료 +@startuml meeting-회의종료_new !theme mono -title Meeting Service - 회의종료 내부 시퀀스 +title Meeting Service - 회의종료 내부 시퀀스 (AI 제안 데이터 반영 포함) participant "MeetingController" as Controller participant "MeetingService" as Service participant "SessionService" as SessionService +participant "AISuggestionService" as AISuggestion +participant "MinutesService" as MinutesService participant "MeetingRepository" as MeetingRepo participant "SessionRepository" as SessionRepo +participant "AISuggestionRepository" as SuggestionRepo +participant "MinutesRepository" as MinutesRepo participant "StatisticsService" as StatService database "Meeting DB<>" as DB database "Redis Cache<>" as Cache @@ -106,15 +110,136 @@ else 종료 가능 StatService --> Service: Statistics deactivate StatService - ' 회의록 상태 업데이트 - Service -> MeetingRepo: updateMinutesStatus(meetingId, "DRAFT") - activate MeetingRepo - MeetingRepo -> DB: UPDATE minutes\nSET status = 'DRAFT',\n endedAt = NOW()\nWHERE meetingId = ?\nAND status = 'IN_PROGRESS' + == AI 제안 데이터를 회의록에 최종 반영 == + + note over Service + 핵심 추가 로직: + 회의 진행 중 사용자가 "적용"한 AI 제안들을 + 회의록(minutes)에 최종 반영하여 완성도 향상 + end note + + ' 1. APPLIED 상태의 AI 제안 조회 + Service -> AISuggestion: getAppliedSuggestions(meetingId) + activate AISuggestion + + AISuggestion -> SuggestionRepo: findAppliedSuggestions(meetingId) + activate SuggestionRepo + SuggestionRepo -> DB: SELECT *\nFROM ai_suggestions\nWHERE meeting_id = ?\nAND status = 'APPLIED'\nORDER BY suggestion_type, applied_at activate DB - DB --> MeetingRepo: 업데이트 완료 + note right + 조회 대상: + - suggestion_type: DISCUSSION, DECISION, TODO + - status: APPLIED (사용자가 적용한 것만) + - content: 제안 내용 + - priority, confidence_score 등 + end note + DB --> SuggestionRepo: AI 제안 목록 deactivate DB - MeetingRepo --> Service: 업데이트 성공 - deactivate MeetingRepo + SuggestionRepo --> AISuggestion: List + deactivate SuggestionRepo + + AISuggestion --> Service: appliedSuggestions + deactivate AISuggestion + + ' 2. 회의록 조회 + Service -> MinutesRepo: findByMeetingId(meetingId) + activate MinutesRepo + MinutesRepo -> DB: SELECT * FROM minutes\nWHERE meeting_id = ? + activate DB + DB --> MinutesRepo: 회의록 정보 + deactivate DB + MinutesRepo --> Service: Minutes + deactivate MinutesRepo + + ' 3. AI 제안을 회의록 섹션별로 반영 + Service -> MinutesService: mergeAISuggestionsToMinutes(minutesId, appliedSuggestions) + activate MinutesService + + note over MinutesService + 처리 단계: + 1. suggestion_type별로 그룹화 + - DISCUSSION → 논의사항 섹션 + - DECISION → 결정사항 섹션 + - TODO → 액션아이템 섹션 + 2. 각 섹션별로 기존 내용과 병합 + 3. 중복 제거 및 정렬 + 4. minutes 테이블에 최종 반영 + end note + + loop 각 suggestion_type별 (DISCUSSION, DECISION, TODO) + + MinutesService -> MinutesService: 해당 타입의 제안 필터링 + + MinutesService -> MinutesRepo: getSectionContent(minutesId, sectionType) + activate MinutesRepo + MinutesRepo -> DB: SELECT section_content\nFROM minutes_sections\nWHERE minutes_id = ?\nAND section_type = ? + activate DB + DB --> MinutesRepo: 기존 섹션 내용 + deactivate DB + MinutesRepo --> MinutesService: sectionContent + deactivate MinutesRepo + + MinutesService -> MinutesService: 기존 내용과 AI 제안 병합 + note right + 병합 규칙: + - 중복 항목 제거 (유사도 90% 이상) + - 우선순위/신뢰도 높은 순 정렬 + - 사용자 수동 작성 내용 우선 + - AI 제안은 추가/보완 역할 + - 타임스탬프 기준 정렬 + end note + + MinutesService -> MinutesRepo: updateSectionContent(minutesId, sectionType, mergedContent) + activate MinutesRepo + MinutesRepo -> DB: UPDATE minutes_sections\nSET section_content = ?,\n updated_at = NOW(),\n ai_enhanced = true\nWHERE minutes_id = ?\nAND section_type = ? + activate DB + note right + 저장 내용: + - section_content: 병합된 최종 내용 + - ai_enhanced: AI가 보강했음을 표시 + - updated_at: 최종 업데이트 시간 + end note + DB --> MinutesRepo: 업데이트 완료 + deactivate DB + MinutesRepo --> MinutesService: 성공 + deactivate MinutesRepo + + end + + MinutesService --> Service: 병합 완료 + deactivate MinutesService + + ' 4. AI 제안 상태 업데이트 (APPLIED → MERGED) + Service -> SuggestionRepo: updateSuggestionsStatus(appliedSuggestions, "MERGED") + activate SuggestionRepo + SuggestionRepo -> DB: UPDATE ai_suggestions\nSET status = 'MERGED',\n merged_at = NOW()\nWHERE id IN (...) + activate DB + note right + 상태 변경: + - APPLIED → MERGED + - 회의록에 최종 반영 완료 표시 + - merged_at: 반영 시간 기록 + end note + DB --> SuggestionRepo: 업데이트 완료 + deactivate DB + SuggestionRepo --> Service: 성공 + deactivate SuggestionRepo + + ' 회의록 상태 업데이트 + Service -> MinutesRepo: updateMinutesStatus(meetingId, "DRAFT") + activate MinutesRepo + MinutesRepo -> DB: UPDATE minutes\nSET status = 'DRAFT',\n endedAt = NOW(),\n ai_suggestions_merged = true,\n merged_count = ?\nWHERE meetingId = ?\nAND status = 'IN_PROGRESS' + activate DB + note right + 회의록 완성도 표시: + - ai_suggestions_merged: AI 제안 반영됨 + - merged_count: 반영된 제안 수 + - 사용자가 최종 검토 가능한 DRAFT 상태 + end note + DB --> MinutesRepo: 업데이트 완료 + deactivate DB + MinutesRepo --> Service: 업데이트 성공 + deactivate MinutesRepo ' 캐시 무효화 Service -> Cache: DELETE meeting:info:{meetingId} @@ -122,6 +247,11 @@ else 종료 가능 Cache --> Service: 삭제 완료 deactivate Cache + Service -> Cache: DELETE minutes:content:{minutesId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + note over Service 비동기 이벤트 발행: - STT 서비스에 녹음 중지 요청 @@ -130,7 +260,7 @@ else 종료 가능 end note ' 이벤트 발행 - Service -> EventHub: publish(MeetingEnded)\n{\n meetingId, sessionId,\n endedAt, statistics,\n minutesId\n} + Service -> EventHub: publish(MeetingEnded)\n{\n meetingId, sessionId,\n endedAt, statistics,\n minutesId,\n aiSuggestionsMerged: true,\n mergedCount: N\n} activate EventHub EventHub --> Service: 발행 완료 deactivate EventHub @@ -150,7 +280,16 @@ else 종료 가능 "participantCount": 5, "utteranceCount": 120 }, - "minutesId": "uuid" + "minutesId": "uuid", + "aiEnhancement": { + "merged": true, + "suggestionsApplied": 8, + "sections": { + "discussion": 3, + "decision": 2, + "todo": 3 + } + } } end note @@ -159,4 +298,28 @@ end deactivate Controller +note over Controller, EventHub +처리 시간: +- 회의/세션 종료: 200-300ms +- 통계 생성: 500-800ms +- AI 제안 조회: 200-300ms +- 회의록 병합: 1-2초 (제안 수에 따라) +- 상태 업데이트: 300-500ms +- 캐시 무효화: 100-200ms +총 처리 시간: 약 3-5초 + +AI 제안 병합 규칙: +- APPLIED 상태의 제안만 반영 +- 중복 제거 (유사도 90% 이상) +- 우선순위/신뢰도 높은 순 +- 사용자 수동 작성 내용 우선 +- 섹션별 최대 제안 수 제한 없음 +- 모든 제안은 추적 가능 (merged_at) + +트랜잭션 관리: +- 회의 종료, AI 제안 병합, 상태 업데이트는 단일 트랜잭션 +- 실패 시 전체 롤백 +- 이벤트 발행은 트랜잭션 성공 후 +end note + @enduml diff --git a/design/backend/sequence/inner/meeting-회의종료_bk.puml b/design/backend/sequence/inner/meeting-회의종료_bk.puml new file mode 100644 index 0000000..0fb73c4 --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의종료_bk.puml @@ -0,0 +1,162 @@ +@startuml meeting-회의종료 +!theme mono + +title Meeting Service - 회의종료 내부 시퀀스 + +participant "MeetingController" as Controller +participant "MeetingService" as Service +participant "SessionService" as SessionService +participant "MeetingRepository" as MeetingRepo +participant "SessionRepository" as SessionRepo +participant "StatisticsService" as StatService +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +queue "Azure Event Hubs<>" as EventHub + +[-> Controller: POST /meetings/{meetingId}/end +activate Controller + +note over Controller +경로 변수: meetingId +사용자 정보: userId, userName, email +end note + +Controller -> Controller: meetingId 유효성 검증 + +Controller -> Service: endMeeting(meetingId, userId) +activate Service + +' 회의 정보 조회 +Service -> MeetingRepo: findById(meetingId) +activate MeetingRepo +MeetingRepo -> DB: SELECT * FROM meetings WHERE id = ? +activate DB +DB --> MeetingRepo: 회의 정보 +deactivate DB +MeetingRepo --> Service: Meeting +deactivate MeetingRepo + +note over Service +비즈니스 규칙 검증: +- 회의가 존재하는지 확인 +- 회의 종료 권한 확인 (생성자만) +- 회의 상태 확인 (IN_PROGRESS만 종료 가능) +end note + +Service -> Service: 권한 검증\n(생성자만 종료 가능) + +Service -> Service: 회의 상태 확인 + +alt 회의가 진행 중이 아님 + Service --> Controller: 409 Conflict\n진행 중인 회의가 아님 + return 409 Conflict +else 종료 가능 + ' 세션 정보 조회 + Service -> SessionRepo: findActiveSession(meetingId) + activate SessionRepo + SessionRepo -> DB: SELECT * FROM meeting_sessions\nWHERE meetingId = ?\nAND status = 'ACTIVE' + activate DB + DB --> SessionRepo: 세션 정보 + deactivate DB + SessionRepo --> Service: Session + deactivate SessionRepo + + ' 세션 종료 + Service -> SessionRepo: endSession(sessionId) + activate SessionRepo + SessionRepo -> DB: UPDATE meeting_sessions\nSET status = 'ENDED',\n endedAt = NOW()\nWHERE id = ? + activate DB + DB --> SessionRepo: 업데이트 완료 + deactivate DB + SessionRepo --> Service: 종료 성공 + deactivate SessionRepo + + ' 회의 상태 업데이트 + Service -> MeetingRepo: updateStatus(meetingId, "ENDED") + activate MeetingRepo + MeetingRepo -> DB: UPDATE meetings\nSET status = 'ENDED',\n actualEndTime = NOW()\nWHERE id = ? + activate DB + DB --> MeetingRepo: 업데이트 완료 + deactivate DB + MeetingRepo --> Service: 업데이트 성공 + deactivate MeetingRepo + + note over Service + 회의 통계 생성: + - 회의 총 시간 + - 참석자 수 + - 발언 횟수 (STT 데이터 기반) + - 주요 키워드 + end note + + ' 회의 통계 생성 + Service -> StatService: generateMeetingStatistics(sessionId) + activate StatService + + StatService -> DB: SELECT\n COUNT(DISTINCT speakerId) as speakerCount,\n COUNT(*) as utteranceCount,\n TIMESTAMPDIFF(MINUTE, startedAt, endedAt) as duration\nFROM transcripts WHERE sessionId = ? + activate DB + DB --> StatService: 통계 데이터 + deactivate DB + + StatService -> DB: INSERT INTO meeting_statistics\n(meetingId, sessionId, duration,\nparticipantCount, utteranceCount, createdAt) + activate DB + DB --> StatService: 통계 저장 완료 + deactivate DB + + StatService --> Service: Statistics + deactivate StatService + + ' 회의록 상태 업데이트 + Service -> MeetingRepo: updateMinutesStatus(meetingId, "DRAFT") + activate MeetingRepo + MeetingRepo -> DB: UPDATE minutes\nSET status = 'DRAFT',\n endedAt = NOW()\nWHERE meetingId = ?\nAND status = 'IN_PROGRESS' + activate DB + DB --> MeetingRepo: 업데이트 완료 + deactivate DB + MeetingRepo --> Service: 업데이트 성공 + deactivate MeetingRepo + + ' 캐시 무효화 + Service -> Cache: DELETE meeting:info:{meetingId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + note over Service + 비동기 이벤트 발행: + - STT 서비스에 녹음 중지 요청 + - AI 서비스에 최종 회의록 생성 요청 + - 참석자에게 회의 종료 알림 + end note + + ' 이벤트 발행 + Service -> EventHub: publish(MeetingEnded)\n{\n meetingId, sessionId,\n endedAt, statistics,\n minutesId\n} + activate EventHub + EventHub --> Service: 발행 완료 + deactivate EventHub + + Service --> Controller: MeetingEndResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "meetingId": "uuid", + "sessionId": "uuid", + "status": "ENDED", + "endedAt": "2025-01-23T15:00:00", + "statistics": { + "duration": 60, + "participantCount": 5, + "utteranceCount": 120 + }, + "minutesId": "uuid" + } + end note + + return 200 OK\nMeetingEndResponse +end + +deactivate Controller + +@enduml