@startuml meeting-회의종료_new !theme mono 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 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 == 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 note right 조회 대상: - suggestion_type: DISCUSSION, DECISION, TODO - status: APPLIED (사용자가 적용한 것만) - content: 제안 내용 - priority, confidence_score 등 end note DB --> SuggestionRepo: AI 제안 목록 deactivate DB 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} activate Cache Cache --> Service: 삭제 완료 deactivate Cache Service -> Cache: DELETE minutes:content:{minutesId} 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 aiSuggestionsMerged: true,\n mergedCount: N\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", "aiEnhancement": { "merged": true, "suggestionsApplied": 8, "sections": { "discussion": 3, "decision": 2, "todo": 3 } } } end note return 200 OK\nMeetingEndResponse 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