diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4680f7e..92aeb11 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -36,7 +36,9 @@ "Bash(mv:*)", "Bash(cp:*)", "mcp__sequential-thinking__sequentialthinking", - "Bash(awk '{print $1\"\"\"\": \"\"\"\"; system(\"\"\"\"sed -n \"\"\"\" $1\"\"\"\"p design/uiux/prototype/common.css\"\"\"\")}')" + "Bash(awk '{print $1\"\"\"\": \"\"\"\"; system(\"\"\"\"sed -n \"\"\"\" $1\"\"\"\"p design/uiux/prototype/common.css\"\"\"\")}')", + "Bash(git stash:*)", + "Bash(tools/check-mermaid.sh:*)" ], "deny": [], "ask": [] diff --git a/claude/sequence-inner-design.md b/claude/sequence-inner-design.md new file mode 100644 index 0000000..586c62c --- /dev/null +++ b/claude/sequence-inner-design.md @@ -0,0 +1,76 @@ +# 내부시퀀스설계 가이드 + +[요청사항] +- <작성원칙>을 준용하여 설계 +- <작성순서>에 따라 설계 +- [결과파일] 안내에 따라 파일 작성 + +[가이드] +<작성원칙> +- **유저스토리와 매칭**되어야 함. **불필요한 추가 설계 금지** +- **외부시퀀스설계서에서 설계한 플로우와 일치**해야 함 +- UI/UX설계서의 '사용자 플로우'참조하여 설계 +- 마이크로서비스 내부의 처리 흐름을 표시 +- **각 서비스-시나리오별로 분리하여 각각 작성** +- 각 서비스별 주요 시나리오마다 독립적인 시퀀스 설계 수행 +- 프론트엔드와 백엔드 책임 분리: 프론트엔드에서 할 수 있는 것은 백엔드로 요청 안하게 함 +- 표현 요소 + - **API 레이어**: 해당 시나리오의 모든 관련 엔드포인트 + - **비즈니스 레이어**: Controller → Service → Domain 내부 플로우 + - **데이터 레이어**: Repository, Cache, External API 접근 + - **인프라 레이어**: 메시지 큐, 이벤트, 로깅 등 +- 다이어그램 구성 + - **참여자(Actor)**: Controller, Service, Repository, Cache, External API + - **생명선(Lifeline)**: 각 참여자의 활동 구간 + - **메시지(Message)**: 동기(→)/비동기(-->) 호출 구분 + - **활성화 박스**: 처리 중인 시간 구간 표시 + - **노트**: 중요한 비즈니스 로직이나 기술적 고려사항 설명 +- 참여자가 서비스 내부가 아닌 다른 마이크로 서비스, 외부시스템, 인프라 컴포넌트면 참여자 이름 끝에 '<>'를 붙임 + 예) database "Redis Cache<>" as cache + +<작성순서> +- 준비: + - 유저스토리, UI/UX설계서, 외부시퀀스설계서 분석 및 이해 + - "@analyze --play" 프로토타입이 있는 경우 웹브라우저에서 실행하여 서비스 이해 +- 실행: + - <시나리오 분류 가이드>에 따라 각 서비스별로 시나리오 분류 + - 내부시퀀스설계서 작성 + - <병렬수행>가이드에 따라 동시 수행 + - **PlantUML 스크립트 파일 생성 즉시 검사 실행**: 'PlantUML 문법 검사 가이드' 준용 +- 검토: + - <작성원칙> 준수 검토 + - 스쿼드 팀원 리뷰: 누락 및 개선 사항 검토 + - 수정 사항 선택 및 반영 + +<시나리오 분류 가이드> +- 시나리오 식별 방법 + - **유저스토리 기반**: 각 유저스토리를 기준으로 시나리오 도출 + - **비즈니스 기능 단위**: 하나의 완전한 비즈니스 기능을 수행하는 단위로 분류 +- 시나리오별 설계 원칙 + - **단일 책임**: 하나의 시나리오는 하나의 명확한 비즈니스 목적을 가짐 + - **완전성**: 해당 시나리오의 모든 API와 내부 처리를 포함 + - **독립성**: 각 시나리오는 독립적으로 이해 가능해야 함 + - **일관성**: 동일한 아키텍처 레이어 표현 방식 사용 +- 시나리오 명명 규칙 + - **케밥-케이스 사용**: entity action 형태. 한글로 작성 (예: 사용자 등록, 주문 처리) + - **동사형 액션**: 실제 수행하는 작업을 명확히 표현 + - **일관된 용어**: 프로젝트 내에서 동일한 용어 사용 + +<병렬수행> +- **서브 에이전트를 활용한 병렬 작성 필수** +- 서비스별 독립적인 에이전트가 각 내부시퀀스설계를 동시에 작업 +- 모든 설계 완료 후 전체 검증 + +[참고자료] +- 유저스토리 +- UI/UX설계서 +- 외부시퀀스설계서 +- 프로토타입 + +[예시] +- 링크: https://raw.githubusercontent.com/cna-bootcamp/clauding-guide/refs/heads/main/samples/sample-시퀀스설계서(내부).puml + +[결과파일] +- design/backend/sequence/inner/{서비스명}-{시나리오}.puml +- 서비스명은 영어로 시나리오명은 한글로 작성 + diff --git a/design/backend/sequence/inner/README.md b/design/backend/sequence/inner/README.md new file mode 100644 index 0000000..79485f9 --- /dev/null +++ b/design/backend/sequence/inner/README.md @@ -0,0 +1,277 @@ +# Meeting Service 내부 시퀀스 설계 + +Meeting Service의 내부 처리 흐름을 표현한 시퀀스 설계서 모음입니다. + +## 📋 설계 개요 + +### 목적 +- Meeting Service 내부의 Controller → Service → Repository 계층 구조 표현 +- 각 시나리오별 비즈니스 로직 및 데이터 처리 흐름 상세화 +- 캐시, 데이터베이스, 메시징 인프라와의 상호작용 명시 +- 동기/비동기 처리 구분 명확화 + +### 설계 원칙 +- **유저스토리 기반**: 모든 시나리오는 유저스토리와 1:1 매칭 +- **외부 시퀀스 일치**: 외부 시퀀스 설계와 일관성 유지 +- **계층 구조 명확화**: Controller, Service, Repository 역할 분리 +- **인프라 표시**: DB, Cache, Event Hub 등 외부 참여자는 <> 표시 +- **비즈니스 로직 설명**: note를 통한 핵심 로직 설명 + +## 📂 시나리오별 설계 파일 + +### 1. 대시보드 조회 +**파일**: `meeting-대시보드조회.puml` +**유저스토리**: AFR-USER-020 +**주요 처리**: +- 사용자별 대시보드 정보 조회 (예정된 회의, Todo, 최근 회의록, 공유받은 회의록) +- Redis 캐시 우선 조회 (Cache-Aside 패턴) +- 통계 정보 계산 (예정 회의 수, Todo 완료율) +- 응답 데이터 TTL: 5분 + +**주요 컴포넌트**: +- DashboardController +- DashboardService +- MeetingRepository, TodoRepository, MinutesRepository +- Redis Cache, Meeting DB + +--- + +### 2. 회의 예약 +**파일**: `meeting-회의예약.puml` +**유저스토리**: UFR-MEET-010 +**주요 처리**: +- 회의 정보 입력 검증 (제목, 날짜/시간, 참석자) +- 중복 회의 체크 (같은 시간대 회의 확인) +- 회의 및 참석자 정보 저장 +- 캐시 저장 (회의 정보, 참석자 목록) +- 비동기 이벤트 발행 (MeetingCreated) + +**주요 컴포넌트**: +- MeetingController +- MeetingService +- MeetingRepository, ParticipantRepository +- Meeting DB, Redis Cache +- Azure Event Hubs (비동기 알림) + +--- + +### 3. 회의 시작 +**파일**: `meeting-회의시작.puml` +**유저스토리**: UFR-MEET-030 +**주요 처리**: +- 회의 시작 권한 검증 (생성자 또는 참석자) +- 회의 상태 확인 (SCHEDULED → IN_PROGRESS) +- 회의 세션 생성 (sessionId, startedAt) +- 회의록 초안 생성 (빈 회의록) +- 비동기 이벤트 발행 (MeetingStarted → STT 녹음 시작) + +**주요 컴포넌트**: +- MeetingController +- MeetingService, SessionService +- MeetingRepository, SessionRepository +- Meeting DB, Redis Cache +- Azure Event Hubs (STT 연동) + +--- + +### 4. 회의 종료 +**파일**: `meeting-회의종료.puml` +**유저스토리**: UFR-MEET-040 +**주요 처리**: +- 회의 종료 권한 검증 (생성자만) +- 회의 세션 종료 (endedAt 기록) +- 회의 상태 변경 (IN_PROGRESS → ENDED) +- 회의 통계 생성 (총 시간, 참석자 수, 발언 횟수) +- 회의록 상태 업데이트 (DRAFT) +- 비동기 이벤트 발행 (MeetingEnded → AI 최종 회의록 생성) + +**주요 컴포넌트**: +- MeetingController +- MeetingService, SessionService, StatisticsService +- MeetingRepository, SessionRepository +- Meeting DB, Redis Cache +- Azure Event Hubs (AI 연동) + +--- + +### 5. 회의록 확정 +**파일**: `meeting-회의록확정.puml` +**유저스토리**: UFR-MEET-050 +**주요 처리**: +- 회의록 확정 권한 검증 (생성자만) +- 필수 항목 검증 (제목, 참석자, 논의 내용, 결정 사항) +- 확정 버전 생성 (v1.0) +- 회의록 스냅샷 저장 (버전 관리) +- 회의록 상태 변경 (DRAFT → FINALIZED) +- 비동기 이벤트 발행 (MinutesFinalized → AI Todo 추출) + +**주요 컴포넌트**: +- MinutesController +- MinutesService, ValidationService +- MinutesRepository +- Meeting DB, Redis Cache +- Azure Event Hubs (AI Todo 추출) + +--- + +### 6. 회의록 상세 조회 +**파일**: `meeting-회의록상세조회.puml` +**유저스토리**: UFR-MEET-047 +**주요 처리**: +- 회의록 조회 권한 검증 (생성자, 참석자, 공유받은 사용자) +- Redis 캐시 우선 조회 (Cache-Aside 패턴) +- 회의록 기본 정보 + 참석자 + 섹션 + AI 요약 조회 +- 관련 회의록 조회 (벡터 유사도 기반, 최대 3개) +- 조회 이력 기록 +- 응답 데이터 TTL: 10분 + +**주요 컴포넌트**: +- MinutesController +- MinutesService, RelatedMinutesService +- MinutesRepository, SectionRepository +- Meeting DB, Redis Cache + +--- + +### 7. 회의록 수정 +**파일**: `meeting-회의록수정.puml` +**유저스토리**: UFR-MEET-055 +**주요 처리**: +- 회의록 수정 권한 검증 (생성자만) +- 잠긴 섹션 수정 여부 확인 +- 수정 이력 저장 (버전 관리) +- 회의록 제목 및 섹션 내용 수정 +- 확정된 회의록은 DRAFT로 상태 변경 +- 실시간 동기화 (WebSocket 브로드캐스트) +- 캐시 무효화 + +**주요 컴포넌트**: +- MinutesController +- MinutesService, VersionService, CollaborationService +- MinutesRepository, SectionRepository +- Meeting DB, Redis Cache +- WebSocket (실시간 협업) + +--- + +### 8. 회의록 공유 +**파일**: `meeting-회의록공유.puml` +**유저스토리**: UFR-MEET-060 +**주요 처리**: +- 회의록 공유 권한 검증 (생성자만) +- 회의록 상태 확인 (FINALIZED만 공유 가능) +- 공유 링크 생성 (UUID 기반 토큰) +- 공유 대상 및 권한 설정 (READ_ONLY, COMMENT, EDIT) +- 다음 회의 일정 추출 및 캘린더 등록 +- 비동기 이벤트 발행 (MinutesShared → 이메일 알림) + +**주요 컴포넌트**: +- ShareController +- ShareService, CalendarService +- MinutesRepository, ShareRepository +- Meeting DB, Redis Cache +- Azure Event Hubs (알림 발송) + +--- + +### 9. Todo 할당 +**파일**: `meeting-Todo할당.puml` +**유저스토리**: UFR-TODO-010 +**주요 처리**: +- Todo 정보 입력 검증 (내용, 담당자, 회의록 ID) +- 회의록 존재 확인 +- Todo 생성 및 저장 +- 회의록 섹션과 양방향 연결 (Todo 뱃지 추가) +- 마감일이 있는 경우 캘린더 이벤트 생성 +- 비동기 이벤트 발행 (TodoAssigned → 담당자 알림) +- 캐시 무효화 (대시보드, 회의록) + +**주요 컴포넌트**: +- TodoController +- TodoService, CalendarService +- TodoRepository, MinutesRepository +- Meeting DB, Redis Cache +- Azure Event Hubs (알림 발송) + +--- + +### 10. Todo 완료 처리 +**파일**: `meeting-Todo완료처리.puml` +**유저스토리**: UFR-TODO-030 +**주요 처리**: +- Todo 완료 권한 검증 (담당자만) +- Todo 상태 변경 (IN_PROGRESS → COMPLETED) +- 회의록 섹션에 완료 상태 자동 반영 +- 모든 Todo 완료 여부 확인 +- 실시간 동기화 (WebSocket 브로드캐스트) +- 비동기 이벤트 발행 (TodoCompleted 또는 AllTodosCompleted) +- 캐시 무효화 (대시보드, 회의록) + +**주요 컴포넌트**: +- TodoController +- TodoService, CollaborationService +- TodoRepository, MinutesRepository +- Meeting DB, Redis Cache +- WebSocket (실시간 협업) +- Azure Event Hubs (알림 발송) + +--- + +## 🎯 설계 특징 + +### 1. 캐시 전략 +- **Cache-Aside 패턴**: 캐시 우선 조회 후 DB 조회 +- **TTL 설정**: 조회 데이터 5-10분, 회의 정보 10분 +- **캐시 무효화**: 데이터 변경 시 즉시 삭제 + +### 2. 비동기 처리 +- **이벤트 기반**: Azure Event Hubs를 통한 이벤트 발행 +- **서비스 분리**: STT, AI, Notification 서비스와 비동기 통신 +- **성능 최적화**: 장시간 작업은 비동기로 처리 + +### 3. 실시간 협업 +- **WebSocket**: 회의록 수정 및 Todo 완료 시 실시간 동기화 +- **변경 델타 전송**: 전체 데이터가 아닌 변경 부분만 전송 +- **충돌 방지**: 버전 관리 및 잠금 기능 + +### 4. 권한 관리 +- **역할 기반**: 생성자, 참석자, 공유받은 사용자 구분 +- **작업별 권한**: 수정, 확정, 공유 등 작업별 권한 검증 +- **섹션 잠금**: 검증 완료 섹션은 생성자만 잠금/해제 + +### 5. 데이터 일관성 +- **버전 관리**: 회의록 수정 이력 및 스냅샷 저장 +- **양방향 연결**: Todo와 회의록 섹션 간 양방향 링크 +- **트랜잭션**: 관련 데이터는 동일 트랜잭션 내 처리 + +## 🔍 검증 사항 + +### PlantUML 문법 +- ✅ `!theme mono` 사용 +- ✅ 외부 참여자 `<>` 표시 +- ✅ 동기 호출 `→`, 비동기 호출 `-->` +- ✅ alt/loop/note 적절히 사용 + +### 유저스토리 일치 +- ✅ 모든 시나리오가 유저스토리와 매칭 +- ✅ 외부 시퀀스 설계와 일치 +- ✅ UI/UX 설계의 사용자 플로우 반영 + +### 아키텍처 원칙 +- ✅ 계층 구조 명확 (Controller → Service → Repository) +- ✅ 캐시 우선 전략 적용 +- ✅ 비동기 처리 명확히 구분 +- ✅ 비즈니스 로직 상세 설명 + +## 📝 다음 단계 + +1. **PlantUML 검증**: Docker 컨테이너 설정 후 문법 검사 실행 +2. **클래스 설계**: 내부 시퀀스 기반 클래스 다이어그램 작성 +3. **데이터 설계**: Repository 계층 기반 테이블 스키마 설계 +4. **API 명세**: Controller 기반 OpenAPI 명세 작성 + +--- + +**작성일**: 2025-10-22 +**작성자**: 길동 (아키텍트) +**버전**: v1.0 diff --git a/design/backend/sequence/inner/ai-Todo자동추출.puml b/design/backend/sequence/inner/ai-Todo자동추출.puml new file mode 100644 index 0000000..1f99030 --- /dev/null +++ b/design/backend/sequence/inner/ai-Todo자동추출.puml @@ -0,0 +1,221 @@ +@startuml +!theme mono + +title AI Service 내부 시퀀스 - Todo자동추출 + +participant "TodoController" as Controller +participant "TodoExtractionService" as Service +participant "LLMClient" as LLM +participant "TodoRepository" as Repo +participant "MeetingServiceClient<>" as MeetingClient +database "Azure OpenAI<>" as OpenAI +database "PostgreSQL<>" as DB + +== MeetingEnded 이벤트 수신 == + +note over Controller + Azure Event Hubs로부터 + MeetingEnded 이벤트 수신 + (meetingId, userId, endTime) +end note + +Controller -> Service: extractTodos(meetingId) +activate Service + +== 최종 회의록 조회 == + +Service -> Repo: getFinalTranscript(meetingId) +activate Repo + +Repo -> DB: SELECT content FROM ai_transcripts\nWHERE meeting_id = {meetingId}\nORDER BY created_at DESC LIMIT 1 +activate DB + +DB --> Repo: 최종 회의록 내용 +deactivate DB + +Repo --> Service: transcriptContent +deactivate Repo + +Service -> Service: 참석자 정보 조회 준비 + +Service -> Repo: getMeetingParticipants(meetingId) +activate Repo + +Repo -> DB: SELECT participants FROM meeting_context +activate DB + +DB --> Repo: 참석자 목록 +deactivate DB + +Repo --> Service: participants +deactivate Repo + +== LLM 기반 Todo 추출 == + +Service -> Service: Todo 추출 프롬프트 생성 +note right + 시스템 프롬프트: + - 역할: Todo 추출 전문가 + - 지시사항: 액션 아이템 식별, + 담당자 및 마감일 추출 + + 사용자 프롬프트: + - 회의록 전체 내용 + - 참석자 목록 + - 추출 기준: + * "~하기로 함", "~까지 완료" + * "~담당", "제가 하겠습니다" + * 명령형 문장 +end note + +Service -> LLM: extractActionItems(prompt, transcript, participants) +activate LLM + +LLM -> OpenAI: POST /chat/completions +activate OpenAI +note right + 요청 파라미터: + - model: gpt-4o + - temperature: 0.2 + - response_format: json_object + + 응답 형식: + { + "todos": [ + { + "content": "Todo 내용", + "assignee": "담당자명", + "dueDate": "YYYY-MM-DD", + "priority": "HIGH|MEDIUM|LOW", + "section": "관련 섹션" + } + ] + } +end note + +OpenAI -> OpenAI: 회의록 분석 +note right + 1. 액션 아이템 키워드 탐지 + 2. 명령형 문장 분석 + 3. 담당자 식별 + - 발언 내용 기반 + - 직책/역할 기반 + 4. 마감일 추출 + 5. 우선순위 판단 +end note + +OpenAI --> LLM: Todo 목록 (JSON) +deactivate OpenAI + +LLM --> Service: extractedTodos +deactivate LLM + +== Todo 데이터 저장 및 검증 == + +Service -> Service: Todo 데이터 검증 +note right + 검증 항목: + - content 필수 + - assignee가 참석자 목록에 있는지 + - dueDate 형식 검증 + - priority 유효성 검증 +end note + +loop 각 Todo 항목마다 + + Service -> Repo: saveTodo(meetingId, todoData) + activate Repo + + Repo -> DB: INSERT INTO ai_extracted_todos + activate DB + note right + 저장 데이터: + - meeting_id + - content + - assignee + - due_date + - priority + - section_reference + - status: PENDING + - extracted_at + end note + + DB --> Repo: todoId + deactivate DB + + Repo --> Service: todoId + deactivate Repo + +end + +== Meeting Service에 Todo 전송 == + +Service -> Service: Todo 목록 준비 +note right + 전송 데이터 구성: + - Todo 내용 + - 담당자 정보 + - 마감일 + - 우선순위 + - 관련 회의록 섹션 링크 +end note + +Service -> MeetingClient: POST /meetings/{meetingId}/todos +activate MeetingClient +note right + 요청 바디: + { + "meetingId": "{meetingId}", + "todos": [ + { + "content": "Todo 내용", + "assignee": "담당자", + "dueDate": "2025-01-30", + "priority": "HIGH", + "sectionReference": "결정사항 #3" + } + ] + } +end note + +MeetingClient -> MeetingClient: Todo 생성 및 할당 처리 +note right + Meeting Service 내부 처리: + - Todo 테이블에 저장 + - 회의록 섹션 링크 연결 + - TodoCreated 이벤트 발행 + - 담당자에게 알림 발송 +end note + +MeetingClient --> Service: 200 OK (Todo 생성 완료) +deactivate MeetingClient + +== 처리 상태 업데이트 == + +Service -> Repo: updateExtractionStatus(meetingId, "COMPLETED") +activate Repo + +Repo -> DB: UPDATE ai_task_status\nSET status = 'COMPLETED',\ncompleted_at = NOW() +activate DB + +DB --> Repo: 업데이트 완료 +deactivate DB + +Repo --> Service: 완료 +deactivate Repo + +Service --> Controller: Todo 추출 완료 +deactivate Service + +Controller -> Controller: TodoExtractionCompleted 이벤트 발행 (내부 로깅) + +note over Controller, DB +처리 시간: +- 회의록 조회: 100-200ms +- LLM Todo 추출: 3-5초 +- 저장 처리: 200-500ms +- Meeting Service 전송: 500ms-1초 +총 처리 시간: 약 4-7초 +end note + +@enduml diff --git a/design/backend/sequence/inner/ai-관련회의록연결.puml b/design/backend/sequence/inner/ai-관련회의록연결.puml new file mode 100644 index 0000000..2b4ca14 --- /dev/null +++ b/design/backend/sequence/inner/ai-관련회의록연결.puml @@ -0,0 +1,244 @@ +@startuml +!theme mono + +title AI Service 내부 시퀀스 - 관련회의록연결 + +participant "RelationController" as Controller +participant "RelationService" as Service +participant "VectorService" as Vector +participant "LLMClient" as LLM +participant "RelationRepository" as Repo +database "Azure OpenAI<>" as OpenAI +database "Vector DB<>" as VectorDB +database "PostgreSQL<>" as DB + +== 회의록 생성 완료 이벤트 수신 == + +note over Controller + 내부 이벤트 또는 + TranscriptFinalized 이벤트 수신 + (meetingId, transcriptId) +end note + +Controller -> Service: findRelatedTranscripts(meetingId, transcriptId) +activate Service + +== 현재 회의록 정보 조회 == + +Service -> Repo: getTranscriptInfo(transcriptId) +activate Repo + +Repo -> DB: SELECT content, meeting_id, created_at\nFROM ai_transcripts\nWHERE id = {transcriptId} +activate DB + +DB --> Repo: 회의록 정보 +deactivate DB + +Repo --> Service: transcriptContent, meetingInfo +deactivate Repo + +== 주제 및 키워드 추출 == + +Service -> Service: 키워드 추출 프롬프트 생성 +note right + 프롬프트: + - 주요 주제 추출 (3-5개) + - 핵심 키워드 추출 (10-15개) + - 카테고리 분류 +end note + +Service -> LLM: extractTopicsAndKeywords(transcriptContent) +activate LLM + +LLM -> OpenAI: POST /chat/completions +activate OpenAI +note right + 요청: + - model: gpt-4o + - temperature: 0.2 + - response_format: json_object + + 응답 형식: + { + "topics": ["주제1", "주제2", "주제3"], + "keywords": ["키워드1", "키워드2", ...], + "category": "카테고리" + } +end note + +OpenAI --> LLM: 주제 및 키워드 +deactivate OpenAI + +LLM --> Service: topics, keywords, category +deactivate LLM + +== 벡터 임베딩 생성 == + +Service -> Service: 검색 쿼리 구성 +note right + 쿼리 텍스트: + - 주요 주제 + - 핵심 키워드 + - 회의 요약 + 결합하여 검색용 텍스트 생성 +end note + +Service -> Vector: generateQueryEmbedding(queryText) +activate Vector + +Vector -> OpenAI: POST /embeddings +activate OpenAI +note right + model: text-embedding-3-large + input: {queryText} +end note + +OpenAI --> Vector: 쿼리 임베딩 벡터 +deactivate OpenAI + +Vector --> Service: queryEmbedding +deactivate Vector + +== 벡터 유사도 검색 == + +Service -> Vector: searchSimilarTranscripts(queryEmbedding, meetingId, limit=20) +activate Vector + +Vector -> VectorDB: 벡터 유사도 검색 +activate VectorDB +note right + 검색 조건: + - 코사인 유사도 기준 + - 동일 폴더(프로젝트) 내 + - 현재 회의록 제외 + - 상위 20개 후보 조회 + + WHERE metadata.project_id = {projectId} + AND vector_id != {currentTranscriptId} + ORDER BY cosine_similarity DESC + LIMIT 20 +end note + +VectorDB --> Vector: 유사 회의록 목록 (벡터 유사도 점수 포함) +deactivate VectorDB + +Vector --> Service: similarTranscripts (top 20) +deactivate Vector + +== 관련도 점수 계산 및 필터링 == + +Service -> Service: 다중 기준으로 관련도 점수 재계산 +note right + 점수 계산 기준: + + 1. 벡터 유사도 (40%) + - 코사인 유사도 점수 + + 2. 키워드 일치도 (30%) + - 공통 키워드 개수 + - 가중치: 중요 키워드 우선 + + 3. 참석자 중복도 (20%) + - 동일 참석자 비율 + + 4. 시간적 연관성 (10%) + - 최근 회의 우선 + - 분기별/월별 회의 패턴 + + 최종 점수 = 가중 평균 +end note + +loop 각 후보 회의록마다 + + Service -> Repo: getTranscriptDetails(candidateId) + activate Repo + + Repo -> DB: SELECT participants, keywords, created_at + activate DB + + DB --> Repo: 상세 정보 + deactivate DB + + Repo --> Service: 참석자, 키워드, 날짜 + deactivate Repo + + Service -> Service: 관련도 점수 계산 + note right + 관련도 = + 벡터유사도 * 0.4 + + 키워드일치도 * 0.3 + + 참석자중복도 * 0.2 + + 시간연관성 * 0.1 + end note + +end + +Service -> Service: 필터링 및 정렬 +note right + 필터링 기준: + - 관련도 70% 이상만 선택 + - 관련도 점수순 정렬 + - 상위 5개 선택 +end note + +== 관련 회의록 저장 == + +loop 선택된 상위 5개 + + Service -> Repo: saveRelatedTranscript(transcriptId, relatedId, score, keywords) + activate Repo + + Repo -> DB: INSERT INTO related_transcripts + activate DB + note right + 저장 데이터: + - transcript_id + - related_transcript_id + - relevance_score + - common_keywords (JSON) + - created_at + end note + + DB --> Repo: 저장 완료 + deactivate DB + + Repo --> Service: 완료 + deactivate Repo + +end + +== 응답 데이터 구성 == + +Service -> Service: 관련 회의록 정보 구성 +note right + 각 관련 회의록별 정보: + - 제목 + - 날짜 + - 참석자 + - 관련도 점수 (%) + - 연관 키워드 + - 링크 +end note + +Service --> Controller: 관련 회의록 목록 (top 5) +deactivate Service + +Controller -> Controller: 회의록 상단에 "관련 회의록" 섹션 추가 준비 + +note over Controller, DB +처리 시간: +- 회의록 조회: 100-200ms +- LLM 주제 추출: 2-3초 +- 벡터 임베딩: 500ms-1초 +- 벡터 검색: 500ms-1초 +- 관련도 계산: 1-2초 (20개) +- 저장 처리: 200-300ms +총 처리 시간: 약 5-8초 + +정책: +- 관련도 70% 이상만 자동 연결 +- 최대 5개까지 표시 +- 동일 폴더(프로젝트) 내에서만 검색 +end note + +@enduml diff --git a/design/backend/sequence/inner/ai-맥락기반용어설명.puml b/design/backend/sequence/inner/ai-맥락기반용어설명.puml new file mode 100644 index 0000000..ba7e439 --- /dev/null +++ b/design/backend/sequence/inner/ai-맥락기반용어설명.puml @@ -0,0 +1,303 @@ +@startuml +!theme mono + +title AI Service 내부 시퀀스 - 맥락기반용어설명 + +participant "ExplanationController" as Controller +participant "TermExplanationService" as Service +participant "RAGService" as RAG +participant "VectorService" as Vector +participant "LLMClient" as LLM +participant "ExplanationRepository" as Repo +database "Azure OpenAI<>" as OpenAI +database "Vector DB<>" as VectorDB +database "Document Store<>" as DocStore +database "PostgreSQL<>" as DB + +== 용어 설명 요청 수신 == + +note over Controller + 요청 경로: + 1. 용어 감지 후 자동 트리거 (비동기) + 2. 사용자 클릭 시 API 요청 + + API: GET /api/ai/terms/{term}/explain + Query: meetingId, context +end note + +Controller -> Service: explainTerm(term, meetingId, context) +activate Service + +== 용어 정보 조회 == + +Service -> Repo: getTermInfo(term) +activate Repo + +Repo -> DB: SELECT definition, category\nFROM term_dictionary\nWHERE term = {term} +activate DB + +DB --> Repo: 기본 용어 정의 +deactivate DB + +Repo --> Service: basicDefinition, category +deactivate Repo + +== 벡터 임베딩 생성 == + +Service -> Service: 검색 쿼리 구성 +note right + 검색 쿼리: + - 용어명 + - 현재 회의 맥락 + - 카테고리 + + 예: "{term} {context} {category}" +end note + +Service -> Vector: generateQueryEmbedding(queryText) +activate Vector + +Vector -> OpenAI: POST /embeddings +activate OpenAI +note right + model: text-embedding-3-large + input: 검색 쿼리 텍스트 +end note + +OpenAI --> Vector: 쿼리 임베딩 벡터 +deactivate OpenAI + +Vector --> Service: queryEmbedding +deactivate Vector + +== RAG 검색 수행 (병렬) == + +Service -> RAG: searchRelatedDocuments(queryEmbedding, term) +activate RAG + +par "과거 회의록 검색" + RAG -> VectorDB: 벡터 유사도 검색 (회의록) + activate VectorDB + note right + 검색 조건: + - 컬렉션: meeting_transcripts + - 필터: term 포함 + - 유사도 threshold: 0.7 + - 결과: top 3 + end note + + VectorDB --> RAG: 관련 회의록 (top 3) + deactivate VectorDB + +and "사내 문서 검색" + RAG -> VectorDB: 벡터 유사도 검색 (문서) + activate VectorDB + note right + 검색 조건: + - 컬렉션: company_documents + - 문서 유형: 위키, 매뉴얼, 보고서 + - 유사도 threshold: 0.7 + - 결과: top 2 + end note + + VectorDB --> RAG: 관련 사내 문서 (top 2) + deactivate VectorDB + +and "업무 이력 검색" + RAG -> DocStore: 전체 텍스트 검색 + activate DocStore + note right + 검색 조건: + - 문서 유형: 프로젝트, 이메일 + - 키워드: {term} + - 날짜: 최근 1년 + - 결과: top 2 + end note + + DocStore --> RAG: 관련 업무 이력 (top 2) + deactivate DocStore +end + +RAG -> RAG: 검색 결과 통합 및 정렬 +note right + 통합 결과: + - 과거 회의록 3개 + - 사내 문서 2개 + - 업무 이력 2개 + + 관련도 점수순 정렬 +end note + +RAG --> Service: relatedDocuments (총 최대 7개) +deactivate RAG + +alt 관련 문서를 찾지 못한 경우 + Service -> Service: 기본 정의만 반환 준비 + note right + 응답: + - 용어 정의 (사전) + - "관련 정보를 찾을 수 없습니다" + - 전문가 요청 버튼 + end note + + Service --> Controller: 기본 정의 응답 + deactivate Service + +else 관련 문서 발견 + +== 맥락 기반 설명 생성 == + +Service -> Service: LLM 프롬프트 구성 +note right + 시스템 프롬프트: + - 역할: 용어 설명 전문가 + - 지시사항: + * 기본 정의 제공 + * 현재 회의 맥락에서의 의미 + * 관련 프로젝트/이슈 연결 + * 과거 사용 사례 요약 + * 참조 출처 링크 + + 사용자 프롬프트: + - 용어: {term} + - 기본 정의: {basicDefinition} + - 현재 회의 맥락: {context} + - 관련 문서: {relatedDocuments} + + 출력 형식: + { + "basicDefinition": "간단한 정의", + "contextualMeaning": "맥락 기반 설명", + "useCases": [사용 사례], + "relatedProjects": [프로젝트], + "pastDiscussions": [과거 논의], + "references": [출처 링크] + } +end note + +Service -> LLM: generateContextualExplanation(prompt) +activate LLM + +LLM -> OpenAI: POST /chat/completions +activate OpenAI +note right + 요청 파라미터: + - model: gpt-4o + - temperature: 0.3 + - max_tokens: 1500 + - response_format: json_object +end note + +OpenAI -> OpenAI: 맥락 기반 설명 생성 +note right + 처리 단계: + 1. 검색된 문서 분석 + 2. 용어 사용 맥락 파악 + 3. 실제 사용 사례 추출 + 4. 프로젝트/이슈 연결 + 5. 과거 논의 요약 + - 언제 논의되었는지 + - 누가 사용했는지 + - 어떻게 사용되었는지 + 6. 참조 출처 정리 +end note + +OpenAI --> LLM: 맥락 기반 설명 (JSON) +deactivate OpenAI + +LLM --> Service: explanation +deactivate LLM + +== 설명 저장 == + +Service -> Repo: saveExplanation(term, meetingId, explanation) +activate Repo + +Repo -> DB: INSERT INTO term_explanations +activate DB +note right + 저장 데이터: + - term + - meeting_id + - basic_definition + - contextual_meaning + - use_cases (JSON) + - related_projects (JSON) + - past_discussions (JSON) + - references (JSON) + - created_at +end note + +DB --> Repo: explanationId +deactivate DB + +Repo --> Service: explanationId +deactivate Repo + +== 응답 데이터 구성 == + +Service -> Service: 최종 응답 구성 +note right + 응답 구조: + { + "term": "용어명", + "basicDefinition": "간단한 정의 (1-2문장)", + "contextualMeaning": "이 회의에서의 의미", + "useCases": [ + "실제 사용 사례 1", + "실제 사용 사례 2" + ], + "relatedProjects": [ + { + "name": "프로젝트명", + "relevance": "연관성 설명" + } + ], + "pastDiscussions": [ + { + "date": "2024-12-15", + "participants": ["참석자1", "참석자2"], + "summary": "논의 요약", + "link": "회의록 링크" + } + ], + "references": [ + { + "title": "문서 제목", + "type": "위키|매뉴얼|회의록", + "link": "URL" + } + ] + } +end note + +Service --> Controller: 맥락 기반 설명 응답 +deactivate Service + +Controller --> Controller: 200 OK 응답 반환 +note right + 프론트엔드 표시: + - 툴팁 또는 사이드 패널 + - 접을 수 있는 섹션별 표시 + - 참조 링크 클릭 가능 +end note + +end + +note over Controller, DB +처리 시간: +- 용어 정보 조회: 50-100ms +- 벡터 임베딩: 500ms-1초 +- RAG 병렬 검색: 1-2초 +- LLM 설명 생성: 3-5초 +- 저장 처리: 100-200ms +총 처리 시간: 약 5-8초 + +차별화 포인트: +- 단순 용어 설명이 아닌 + 조직 내 실제 사용 맥락과 이력 제공 +- 업무 지식이 없어도 + 실질적인 도움 제공 +end note + +@enduml diff --git a/design/backend/sequence/inner/ai-전문용어감지.puml b/design/backend/sequence/inner/ai-전문용어감지.puml new file mode 100644 index 0000000..883427d --- /dev/null +++ b/design/backend/sequence/inner/ai-전문용어감지.puml @@ -0,0 +1,242 @@ +@startuml +!theme mono + +title AI Service 내부 시퀀스 - 전문용어감지 + +participant "TermController" as Controller +participant "TermDetectionService" as Service +participant "LLMClient" as LLM +participant "TermRepository" as Repo +database "Azure OpenAI<>" as OpenAI +database "PostgreSQL<>" as DB + +== 회의록 텍스트 실시간 분석 요청 == + +note over Controller + API 요청 (실시간 또는 배치): + POST /api/ai/terms/detect + Body: { + "meetingId": "{meetingId}", + "text": "회의록 텍스트" + } +end note + +Controller -> Service: detectTerms(meetingId, text) +activate Service + +== 용어 사전 조회 == + +par "조직별 용어 사전" + Service -> Repo: getOrganizationTerms(organizationId) + activate Repo + + Repo -> DB: SELECT term, definition, category\nFROM term_dictionary\nWHERE org_id = {organizationId} + activate DB + + DB --> Repo: 조직 전문용어 목록 + deactivate DB + + Repo --> Service: orgTerms + deactivate Repo + +and "산업별 표준 용어" + Service -> Repo: getIndustryTerms(industry) + activate Repo + + Repo -> DB: SELECT term, definition, category\nFROM standard_terms\nWHERE industry = {industry} + activate DB + + DB --> Repo: 산업 표준용어 목록 + deactivate DB + + Repo --> Service: industryTerms + deactivate Repo +end + +Service -> Service: 용어 사전 병합 및 준비 +note right + 용어 사전: + - 조직별 용어 (우선순위 높음) + - 산업별 표준 용어 + - 기술 용어 +end note + +== LLM 기반 전문용어 감지 == + +Service -> Service: 용어 감지 프롬프트 생성 +note right + 시스템 프롬프트: + - 역할: 전문용어 감지 전문가 + - 지시사항: + * 텍스트에서 전문용어 탐지 + * 용어 사전과 비교 + * 신뢰도 점수 계산 + * 위치 정보 추출 + + 사용자 프롬프트: + - 분석 대상 텍스트: {text} + - 용어 사전: {termDictionary} + + 응답 형식: + { + "detectedTerms": [ + { + "term": "용어명", + "position": {line, offset}, + "confidence": 0.0-1.0, + "category": "기술|업무|도메인" + } + ] + } +end note + +Service -> LLM: detectTechnicalTerms(prompt, text, termDictionary) +activate LLM + +LLM -> OpenAI: POST /chat/completions +activate OpenAI +note right + 요청 파라미터: + - model: gpt-4o + - temperature: 0.1 + - response_format: json_object +end note + +OpenAI -> OpenAI: 텍스트 분석 및 용어 감지 +note right + 처리 단계: + 1. 텍스트 토큰화 + 2. 용어 사전과 매칭 + 3. 문맥 기반 용어 식별 + 4. 신뢰도 계산 + - 정확한 매칭: 0.9-1.0 + - 변형 매칭: 0.7-0.9 + - 문맥 기반: 0.7-0.8 + 5. 위치 정보 추출 +end note + +OpenAI --> LLM: 감지된 용어 목록 (JSON) +deactivate OpenAI + +LLM --> Service: detectedTerms +deactivate LLM + +== 용어 필터링 및 검증 == + +Service -> Service: 신뢰도 기반 필터링 +note right + 필터링 기준: + - 신뢰도 70% 이상만 선택 + - 중복 용어 제거 + (첫 번째 출현만 유지) + - 카테고리별 분류 +end note + +loop 각 감지된 용어마다 + + Service -> Service: 용어 메타데이터 보강 + note right + 추가 정보: + - 용어 정의 (사전에서) + - 카테고리 + - 사용 빈도 + - 관련 문서 참조 + end note + +end + +== 감지 결과 저장 == + +Service -> Repo: saveDetectedTerms(meetingId, detectedTerms) +activate Repo + +loop 각 용어마다 + + Repo -> DB: INSERT INTO detected_terms + activate DB + note right + 저장 데이터: + - meeting_id + - term + - position (JSON) + - confidence_score + - category + - detected_at + - status: DETECTED + end note + + DB --> Repo: termId + deactivate DB + +end + +Repo --> Service: 저장 완료 +deactivate Repo + +== 하이라이트 정보 생성 == + +Service -> Service: 하이라이트 데이터 구성 +note right + 프론트엔드 전달 정보: + - 용어 위치 (줄 번호, 오프셋) + - 하이라이트 스타일 + - 툴팁 텍스트 + - 신뢰도 표시 +end note + +== 맥락 기반 설명 트리거 == + +Service -> Service: 용어 설명 생성 트리거 +note right + 비동기로 용어 설명 생성 시작 + (UFR-RAG-020 연동) + + 각 감지된 용어에 대해: + - RAG 검색 수행 + - 맥락 기반 설명 생성 +end note + +== 응답 반환 == + +Service -> Service: 응답 데이터 구성 +note right + 응답 데이터: + - detectedTerms: [ + { + "term": "용어명", + "position": {line, offset}, + "confidence": 0.85, + "category": "기술", + "highlight": true + } + ] + - totalCount: 감지된 용어 수 + - highlightInfo: 하이라이트 정보 +end note + +Service --> Controller: 감지 완료 응답 +deactivate Service + +Controller --> Controller: 200 OK 응답 반환 +note right + 프론트엔드 처리: + - 용어 하이라이트 표시 + - 툴팁 준비 + - 설명 로딩 중 표시 +end note + +note over Controller, DB +처리 시간: +- 용어 사전 조회: 100-200ms +- LLM 용어 감지: 2-4초 +- 필터링 및 검증: 100-200ms +- 저장 처리: 200-300ms +총 처리 시간: 약 3-5초 + +정책: +- 신뢰도 70% 이상만 자동 감지 +- 중복 용어는 첫 번째만 하이라이트 +- 맥락 기반 설명은 비동기 생성 +end note + +@enduml diff --git a/design/backend/sequence/inner/ai-회의록개선.puml b/design/backend/sequence/inner/ai-회의록개선.puml new file mode 100644 index 0000000..3e67364 --- /dev/null +++ b/design/backend/sequence/inner/ai-회의록개선.puml @@ -0,0 +1,224 @@ +@startuml +!theme mono + +title AI Service 내부 시퀀스 - 회의록개선 + +participant "ImproveController" as Controller +participant "ImproveService" as Service +participant "LLMClient" as LLM +participant "PromptTemplateService" as Template +participant "ImproveRepository" as Repo +database "Azure OpenAI<>" as OpenAI +database "PostgreSQL<>" as DB + +== 회의록 개선 요청 수신 == + +note over Controller + API 요청: + POST /api/ai/transcripts/{meetingId}/improve + Body: { + "promptType": "1PAGE_SUMMARY", + "customPrompt": "optional" + } +end note + +Controller -> Service: improveTranscript(meetingId, promptType, customPrompt) +activate Service + +== 원본 회의록 조회 == + +Service -> Repo: getOriginalTranscript(meetingId) +activate Repo + +Repo -> DB: SELECT content, version\nFROM ai_transcripts\nWHERE meeting_id = {meetingId}\nAND status = 'FINALIZED'\nORDER BY created_at DESC LIMIT 1 +activate DB + +DB --> Repo: 원본 회의록 내용 +deactivate DB + +Repo --> Service: originalContent, version +deactivate Repo + +== 프롬프트 템플릿 선택 == + +Service -> Template: getPromptTemplate(promptType) +activate Template + +Template -> Template: 프롬프트 유형별 템플릿 선택 +note right + 지원 프롬프트 유형: + + 1. 1PAGE_SUMMARY + - A4 1장 분량 요약 + - 핵심 내용만 압축 + + 2. CORE_SUMMARY + - 3-5개 핵심 포인트 + - 불릿 포인트 형식 + + 3. DETAILED_REPORT + - 시간순 상세 기록 + - 타임스탬프 포함 + + 4. DECISION_FOCUSED + - 의사결정 중심 + - 결정 사항과 근거 + + 5. ACTION_FOCUSED + - 액션 아이템 중심 + - Todo와 담당자 강조 + + 6. EXECUTIVE_REPORT + - 경영진 보고용 + - 간결하고 임팩트 있게 + + 7. CUSTOM + - 사용자 정의 프롬프트 +end note + +Template --> Service: promptTemplate +deactivate Template + +== 프롬프트 생성 == + +Service -> Service: 최종 프롬프트 구성 +note right + 시스템 프롬프트: + - 역할: 회의록 개선 전문가 + - 지시사항: {promptTemplate} + + 사용자 프롬프트: + - 원본 회의록: {originalContent} + - 개선 요구사항: {promptType} + - 추가 지시사항: {customPrompt} + + 출력 형식 지정: + - 구조화된 마크다운 + - 적절한 헤딩과 섹션 + - 가독성 최적화 +end note + +== LLM 기반 회의록 재구성 == + +Service -> LLM: regenerateTranscript(prompt, originalContent) +activate LLM + +LLM -> OpenAI: POST /chat/completions +activate OpenAI +note right + 요청 파라미터: + - model: gpt-4o + - temperature: 0.3 + - max_tokens: 3000 + - messages: [system, user] +end note + +OpenAI -> OpenAI: 회의록 재구성 +note right + 처리 단계: + 1. 원본 분석 + 2. 프롬프트 의도 파악 + 3. 중요도 기반 필터링 + 4. 형식에 맞춰 재배치 + 5. 불필요한 내용 제거 + 6. 스타일 조정 + - 문체 변환 + - 길이 조정 + 7. 구조화 +end note + +OpenAI --> LLM: 개선된 회의록 +deactivate OpenAI + +LLM --> Service: improvedContent +deactivate LLM + +== 개선된 회의록 저장 == + +Service -> Service: 새 버전 생성 +note right + 버전 정보: + - base_version: 원본 버전 번호 + - improvement_type: {promptType} + - version: 새 버전 번호 +end note + +Service -> Repo: saveImprovedTranscript(meetingId, improvedContent, metadata) +activate Repo + +Repo -> DB: INSERT INTO ai_transcripts +activate DB +note right + 저장 데이터: + - meeting_id + - content (개선된 내용) + - version (새 버전) + - base_version (원본 버전) + - improvement_type + - prompt_type + - custom_prompt + - created_at + - status: IMPROVED +end note + +DB --> Repo: newTranscriptId +deactivate DB + +Repo --> Service: transcriptId, version +deactivate Repo + +== 버전 연결 저장 == + +Service -> Repo: linkVersions(originalId, improvedId) +activate Repo + +Repo -> DB: INSERT INTO transcript_versions +activate DB +note right + 버전 연결 정보: + - original_transcript_id + - improved_transcript_id + - improvement_type + - created_at +end note + +DB --> Repo: 연결 완료 +deactivate DB + +Repo --> Service: 완료 +deactivate Repo + +== 응답 반환 == + +Service -> Service: 응답 데이터 구성 +note right + 응답 데이터: + - transcriptId (새 버전) + - version + - baseVersion + - improvementType + - content (개선된 내용) + - originalLink + - createdAt +end note + +Service --> Controller: 개선 완료 응답 +deactivate Service + +Controller --> Controller: 200 OK 응답 반환 + +note over Controller, DB +처리 시간: +- 원본 조회: 100-200ms +- 템플릿 선택: 10-50ms +- LLM 재구성: 4-8초 +- 저장 처리: 200-300ms +총 처리 시간: 약 5-9초 + +정책: +- 원본 회의록은 항상 보존 +- 여러 버전 동시 생성 가능 +- 버전 간 비교 기능 제공 +end note + +@enduml diff --git a/design/backend/sequence/inner/ai-회의록자동작성.puml b/design/backend/sequence/inner/ai-회의록자동작성.puml new file mode 100644 index 0000000..a7cdc88 --- /dev/null +++ b/design/backend/sequence/inner/ai-회의록자동작성.puml @@ -0,0 +1,185 @@ +@startuml +!theme mono + +title AI Service 내부 시퀀스 - 회의록자동작성 + +participant "TranscriptController" as Controller +participant "TranscriptService" as Service +participant "LLMClient" as LLM +participant "VectorService" as Vector +participant "TranscriptRepository" as Repo +database "Azure OpenAI<>" as OpenAI +database "Vector DB<>" as VectorDB +database "PostgreSQL<>" as DB + +== TranscriptReady 이벤트 수신 == + +note over Controller + Azure Event Hubs로부터 + TranscriptReady 이벤트 수신 + (meetingId, transcriptText, timestamp) +end note + +Controller -> Service: processTranscript(meetingId, transcriptText, timestamp) +activate Service + +Service -> Service: 회의 맥락 정보 조회 준비 +note right + 회의 제목, 참석자 정보, + 이전 회의록 내용 +end note + +== 병렬 처리: 맥락 정보 수집 == + +par "회의 정보 조회" + Service -> Repo: getMeetingContext(meetingId) + activate Repo + Repo -> DB: SELECT meeting_info, participants + activate DB + DB --> Repo: 회의 정보 반환 + deactivate DB + Repo --> Service: 회의 맥락 정보 + deactivate Repo +and "이전 내용 조회" + Service -> Repo: getPreviousTranscripts(meetingId) + activate Repo + Repo -> DB: SELECT previous_content + activate DB + DB --> Repo: 이전 회의록 + deactivate DB + Repo --> Service: 이전 내용 + deactivate Repo +end + +Service -> Service: 프롬프트 생성 +note right + 시스템 프롬프트: + - 역할: 회의록 작성 전문가 + - 지시사항: 구어체→문어체 변환, + 주제별 분류, 발언자별 정리 + + 사용자 프롬프트: + - 회의 제목: {title} + - 참석자: {participants} + - 이전 내용: {previous} + - 현재 발언: {transcriptText} +end note + +== LLM 기반 회의록 작성 == + +Service -> LLM: generateMinutes(prompt, meetingContext) +activate LLM + +LLM -> OpenAI: POST /chat/completions +activate OpenAI +note right + 요청 파라미터: + - model: gpt-4o + - temperature: 0.3 + - max_tokens: 2000 + - messages: [system, user] +end note + +OpenAI -> OpenAI: 텍스트 분석 및 정리 +note right + 1. 주제별 분류 + 2. 발언자별 의견 정리 + 3. 중요 키워드 추출 + 4. 구어체→문어체 변환 + 5. 문법 교정 +end note + +OpenAI --> LLM: 정리된 회의록 내용 +deactivate OpenAI + +LLM --> Service: 회의록 초안 +deactivate LLM + +== 회의록 저장 == + +Service -> Service: 회의록 데이터 구조화 +note right + 구조화 항목: + - 논의 주제 + - 발언자별 의견 + - 결정 사항 + - 보류 사항 + - 요약문 +end note + +Service -> Repo: saveTranscriptDraft(meetingId, content, timestamp) +activate Repo + +Repo -> DB: INSERT INTO ai_transcripts +activate DB +note right + 저장 데이터: + - meeting_id + - content (JSON) + - generated_at + - version + - status: DRAFT +end note + +DB --> Repo: 저장 완료 +deactivate DB + +Repo --> Service: transcriptId +deactivate Repo + +== 벡터 임베딩 생성 (비동기) == + +Service -> Vector: createEmbedding(transcriptId, content) +activate Vector + +Vector -> OpenAI: POST /embeddings +activate OpenAI +note right + model: text-embedding-3-large + input: 회의록 내용 +end note + +OpenAI --> Vector: 임베딩 벡터 +deactivate OpenAI + +Vector -> VectorDB: INSERT embedding +activate VectorDB +note right + vector_id: transcriptId + embedding: [float array] + metadata: {meetingId, timestamp} +end note + +VectorDB --> Vector: 저장 완료 +deactivate VectorDB + +Vector --> Service: 임베딩 생성 완료 +deactivate Vector + +== TranscriptSummaryCreated 이벤트 발행 == + +Service -> Controller: 회의록 생성 완료 응답 +deactivate Service + +Controller -> Controller: TranscriptSummaryCreated 이벤트 발행 +note right + 이벤트 데이터: + - meetingId + - transcriptId + - content + - generatedAt + + Partition Key: {meetingId} + Consumer: Meeting Service +end note + +note over Controller, DB +처리 시간: +- 맥락 정보 조회: 100-200ms +- LLM 생성: 3-5초 +- 저장 처리: 100-200ms +- 벡터 임베딩: 500ms-1초 (비동기) +총 처리 시간: 약 4-7초 +end note + +@enduml diff --git a/design/backend/sequence/inner/meeting-Todo완료처리.puml b/design/backend/sequence/inner/meeting-Todo완료처리.puml new file mode 100644 index 0000000..b48fff6 --- /dev/null +++ b/design/backend/sequence/inner/meeting-Todo완료처리.puml @@ -0,0 +1,179 @@ +@startuml meeting-Todo완료처리 +!theme mono + +title Meeting Service - Todo완료처리 내부 시퀀스 + +participant "TodoController" as Controller +participant "TodoService" as Service +participant "TodoRepository" as TodoRepo +participant "MinutesRepository" as MinutesRepo +participant "CollaborationService" as CollabService +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +queue "Azure Event Hubs<>" as EventHub +participant "WebSocket<>" as WebSocket + +[-> Controller: PATCH /todos/{todoId}/complete +activate Controller + +note over Controller +경로 변수: todoId +사용자 정보: userId, userName, email +end note + +Controller -> Controller: todoId 유효성 검증 + +Controller -> Service: completeTodo(todoId, userId) +activate Service + +' Todo 정보 조회 +Service -> TodoRepo: findById(todoId) +activate TodoRepo +TodoRepo -> DB: SELECT * FROM todos WHERE id = ? +activate DB +DB --> TodoRepo: Todo 정보 +deactivate DB +TodoRepo --> Service: Todo +deactivate TodoRepo + +note over Service +비즈니스 규칙 검증: +- Todo 존재 확인 +- 완료 권한 확인 (담당자만) +- 상태 확인 (이미 완료된 경우 처리) +end note + +Service -> Service: Todo 존재 확인 + +Service -> Service: 완료 권한 검증\n(담당자만 가능) + +alt 권한 없음 + Service --> Controller: 403 Forbidden\n담당자만 완료 가능 + return 403 Forbidden +else 권한 있음 + alt Todo가 이미 완료됨 + Service --> Controller: 409 Conflict\n이미 완료된 Todo + return 409 Conflict + else 완료 처리 가능 + ' 완료 확인 다이얼로그 (프론트엔드에서 처리됨) + + ' Todo 완료 처리 + Service -> TodoRepo: markAsCompleted(todoId, userId) + activate TodoRepo + TodoRepo -> DB: UPDATE todos\nSET status = 'COMPLETED',\n completedAt = NOW(),\n completedBy = ?\nWHERE id = ? + activate DB + DB --> TodoRepo: 업데이트 완료 + deactivate DB + TodoRepo --> Service: 업데이트 성공 + deactivate TodoRepo + + note over Service + 회의록 실시간 반영: + - 관련 회의록 섹션 자동 업데이트 + - 완료 표시 추가 + - 완료 시간 및 완료자 정보 기록 + end note + + ' 회의록 섹션 업데이트 + Service -> MinutesRepo: updateTodoStatus(todoId, "COMPLETED") + activate MinutesRepo + MinutesRepo -> DB: UPDATE minutes_sections\nSET todoStatuses = JSON_SET(todoStatuses,\n CONCAT('$."', ?, '"'),\n JSON_OBJECT(\n 'status', 'COMPLETED',\n 'completedAt', NOW(),\n 'completedBy', ?\n )\n)\nWHERE JSON_CONTAINS(todoIds, CAST(? AS JSON)) + activate DB + DB --> MinutesRepo: 업데이트 완료 + deactivate DB + MinutesRepo --> Service: 업데이트 성공 + deactivate MinutesRepo + + ' 회의록의 모든 Todo 완료 여부 확인 + Service -> TodoRepo: countPendingTodos(minutesId) + activate TodoRepo + TodoRepo -> DB: SELECT COUNT(*) FROM todos\nWHERE minutesId = ?\nAND status != 'COMPLETED' + activate DB + DB --> TodoRepo: 미완료 Todo 개수 + deactivate DB + TodoRepo --> Service: int pendingCount + deactivate TodoRepo + + ' 캐시 무효화 + Service -> Cache: DELETE dashboard:{assigneeId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + Service -> Cache: DELETE minutes:detail:{minutesId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + note over Service + 실시간 협업: + - WebSocket으로 회의록 업데이트 전송 + - 모든 참석자에게 완료 상태 동기화 + end note + + ' 실시간 동기화 + Service -> CollabService: broadcastTodoUpdate(minutesId, todoId, status) + activate CollabService + + note over CollabService + WebSocket 메시지 형식: + { + "type": "TODO_COMPLETED", + "todoId": "uuid", + "minutesId": "uuid", + "completedBy": { + "userId": "...", + "userName": "..." + }, + "completedAt": "...", + "timestamp": "..." + } + end note + + CollabService -> WebSocket: broadcast to room:{minutesId} + activate WebSocket + WebSocket --> CollabService: 전송 완료 + deactivate WebSocket + CollabService --> Service: 동기화 완료 + deactivate CollabService + + note over Service + 비동기 이벤트 발행: + - 완료 알림 발송 + - 모든 Todo 완료 시 전체 완료 알림 + end note + + alt 모든 Todo 완료됨 + Service -> EventHub: publish(AllTodosCompleted)\n{\n minutesId, meetingId,\n completedAt, totalTodos\n} + activate EventHub + EventHub --> Service: 발행 완료 + deactivate EventHub + else 일부 Todo만 완료 + Service -> EventHub: publish(TodoCompleted)\n{\n todoId, minutesId,\n completedBy, completedAt\n} + activate EventHub + EventHub --> Service: 발행 완료 + deactivate EventHub + end + + Service --> Controller: TodoCompleteResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "todoId": "uuid", + "status": "COMPLETED", + "completedAt": "2025-01-24T10:00:00", + "completedBy": "userId", + "minutesId": "uuid", + "allTodosCompleted": true/false + } + end note + + return 200 OK\nTodoCompleteResponse + end +end + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-Todo할당.puml b/design/backend/sequence/inner/meeting-Todo할당.puml new file mode 100644 index 0000000..2a9f0a2 --- /dev/null +++ b/design/backend/sequence/inner/meeting-Todo할당.puml @@ -0,0 +1,158 @@ +@startuml meeting-Todo할당 +!theme mono + +title Meeting Service - Todo할당 내부 시퀀스 + +participant "TodoController" as Controller +participant "TodoService" as Service +participant "TodoRepository" as TodoRepo +participant "MinutesRepository" as MinutesRepo +participant "CalendarService" as CalendarService +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +queue "Azure Event Hubs<>" as EventHub + +[-> Controller: POST /todos +activate Controller + +note over Controller +요청 데이터: +{ + "content": "Todo 내용", + "assignee": "user@example.com", + "dueDate": "2025-01-30", + "priority": "HIGH" | "MEDIUM" | "LOW", + "minutesId": "uuid", + "sectionId": "uuid" // 회의록 섹션 위치 +} +사용자 정보: userId, userName, email +end note + +Controller -> Controller: 입력 검증\n- content 필수\n- assignee 필수\n- minutesId 필수 + +Controller -> Service: createTodo(request, userId) +activate Service + +note over Service +비즈니스 규칙: +- Todo 내용 최대 500자 +- 마감일은 현재보다 미래여야 함 +- 회의록 존재 확인 +- 담당자 유효성 검증 +end note + +' 회의록 존재 확인 +Service -> MinutesRepo: findById(minutesId) +activate MinutesRepo +MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ? +activate DB +DB --> MinutesRepo: 회의록 정보 +deactivate DB +MinutesRepo --> Service: Minutes +deactivate MinutesRepo + +Service -> Service: 회의록 존재 확인 + +' Todo 생성 +Service -> Service: Todo 엔티티 생성\n- todoId (UUID)\n- 상태: IN_PROGRESS\n- 생성 정보 + +Service -> TodoRepo: save(todo) +activate TodoRepo +TodoRepo -> DB: INSERT INTO todos\n(id, content, assignee, dueDate,\npriority, status, minutesId, sectionId,\ncreatedBy, createdAt) +activate DB +DB --> TodoRepo: Todo 저장 완료 +deactivate DB +TodoRepo --> Service: Todo +deactivate TodoRepo + +note over Service +회의록 양방향 연결: +- 회의록 섹션에 Todo 뱃지 추가 +- Todo에서 회의록 섹션으로 링크 +end note + +' 회의록 섹션에 Todo 연결 +Service -> MinutesRepo: linkTodoToSection(sectionId, todoId) +activate MinutesRepo +MinutesRepo -> DB: UPDATE minutes_sections\nSET todoIds = JSON_ARRAY_APPEND(todoIds, '$', ?)\nWHERE id = ? +activate DB +DB --> MinutesRepo: 업데이트 완료 +deactivate DB +MinutesRepo --> Service: 연결 성공 +deactivate MinutesRepo + +' 마감일이 있는 경우 캘린더 연동 +alt 마감일 설정됨 + Service -> CalendarService: createTodoEvent(todo) + activate CalendarService + + note over CalendarService + 캘린더 이벤트 생성: + - 제목: Todo 내용 + - 일시: 마감일 + - 참석자: 담당자 + - 리마인더: 마감 3일 전 + end note + + CalendarService -> CalendarService: 캘린더 이벤트 생성 + CalendarService --> Service: 이벤트 ID + deactivate CalendarService + + Service -> TodoRepo: updateCalendarEventId(todoId, eventId) + activate TodoRepo + TodoRepo -> DB: UPDATE todos\nSET calendarEventId = ?\nWHERE id = ? + activate DB + DB --> TodoRepo: 업데이트 완료 + deactivate DB + TodoRepo --> Service: 업데이트 성공 + deactivate TodoRepo +end + +' 캐시 무효화 +Service -> Cache: DELETE dashboard:{assigneeId} +activate Cache +Cache --> Service: 삭제 완료 +deactivate Cache + +Service -> Cache: DELETE minutes:detail:{minutesId} +activate Cache +Cache --> Service: 삭제 완료 +deactivate Cache + +note over Service +비동기 이벤트 발행: +- 담당자에게 즉시 알림 발송 +- 회의록 실시간 업데이트 (WebSocket) +- 캘린더 초대 발송 +end note + +' 이벤트 발행 +Service -> EventHub: publish(TodoAssigned)\n{\n todoId, content, assignee,\n dueDate, minutesId, sectionId,\n assignedBy, calendarEventId\n} +activate EventHub +EventHub --> Service: 발행 완료 +deactivate EventHub + +Service --> Controller: TodoResponse +deactivate Service + +note over Controller +응답 데이터: +{ + "todoId": "uuid", + "content": "Todo 내용", + "assignee": "user@example.com", + "dueDate": "2025-01-30", + "priority": "HIGH", + "status": "IN_PROGRESS", + "minutesId": "uuid", + "sectionId": "uuid", + "calendarEventId": "...", + "createdAt": "2025-01-23T16:45:00" +} +end note + +return 201 Created\nTodoResponse + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-대시보드조회.puml b/design/backend/sequence/inner/meeting-대시보드조회.puml new file mode 100644 index 0000000..a5690ac --- /dev/null +++ b/design/backend/sequence/inner/meeting-대시보드조회.puml @@ -0,0 +1,140 @@ +@startuml meeting-대시보드조회 +!theme mono + +title Meeting Service - 대시보드조회 내부 시퀀스 + +participant "DashboardController" as Controller +participant "DashboardService" as Service +participant "MeetingRepository" as MeetingRepo +participant "TodoRepository" as TodoRepo +participant "MinutesRepository" as MinutesRepo +database "Redis Cache<>" as Cache +database "Meeting DB<>" as DB + +[-> Controller: GET /dashboard +activate Controller + +note over Controller +사용자 정보는 헤더에서 추출 +(userId, userName, email) +end note + +Controller -> Service: getDashboardData(userId) +activate Service + +' 캐시 조회 +Service -> Cache: GET dashboard:{userId} +activate Cache +Cache --> Service: 캐시 조회 결과 +deactivate Cache + +alt Cache Hit + Service --> Service: 캐시 데이터 반환 +else Cache Miss + ' 예정된 회의 조회 + Service -> MeetingRepo: findUpcomingMeetings(userId) + activate MeetingRepo + MeetingRepo -> DB: SELECT meetings\nWHERE userId = ? AND status = 'SCHEDULED'\nAND startTime > NOW()\nORDER BY startTime LIMIT 3 + activate DB + DB --> MeetingRepo: 예정된 회의 목록 + deactivate DB + MeetingRepo --> Service: List + deactivate MeetingRepo + + ' 진행 중 Todo 조회 + Service -> TodoRepo: findActiveTodos(userId) + activate TodoRepo + TodoRepo -> DB: SELECT todos\nWHERE assignee = ? AND status = 'IN_PROGRESS'\nORDER BY dueDate LIMIT 3 + activate DB + DB --> TodoRepo: 진행 중 Todo 목록 + deactivate DB + TodoRepo --> Service: List + deactivate TodoRepo + + ' 최근 회의록 조회 + Service -> MinutesRepo: findRecentMinutes(userId) + activate MinutesRepo + MinutesRepo -> DB: SELECT minutes\nWHERE creatorId = ?\nORDER BY createdAt DESC LIMIT 3 + activate DB + DB --> MinutesRepo: 최근 회의록 목록 + deactivate DB + MinutesRepo --> Service: List + deactivate MinutesRepo + + ' 공유받은 회의록 조회 + Service -> MinutesRepo: findSharedMinutes(userId) + activate MinutesRepo + MinutesRepo -> DB: SELECT minutes m JOIN shared_minutes sm\nON m.id = sm.minutesId\nWHERE sm.sharedWith = ?\nORDER BY sm.sharedAt DESC LIMIT 3 + activate DB + DB --> MinutesRepo: 공유받은 회의록 목록 + deactivate DB + MinutesRepo --> Service: List + deactivate MinutesRepo + + ' 통계 정보 조회 + Service -> MeetingRepo: countUpcomingMeetings(userId) + activate MeetingRepo + MeetingRepo -> DB: SELECT COUNT(*)\nFROM meetings\nWHERE userId = ? AND status = 'SCHEDULED' + activate DB + DB --> MeetingRepo: 예정된 회의 개수 + deactivate DB + MeetingRepo --> Service: int count + deactivate MeetingRepo + + Service -> TodoRepo: countActiveTodos(userId) + activate TodoRepo + TodoRepo -> DB: SELECT COUNT(*)\nFROM todos\nWHERE assignee = ? AND status = 'IN_PROGRESS' + activate DB + DB --> TodoRepo: 진행 중 Todo 개수 + deactivate DB + TodoRepo --> Service: int count + deactivate TodoRepo + + Service -> TodoRepo: calculateTodoCompletionRate(userId) + activate TodoRepo + TodoRepo -> DB: SELECT\n (COUNT(CASE WHEN status='COMPLETED' THEN 1 END) * 100.0 /\n COUNT(*)) as rate\nFROM todos WHERE assignee = ? + activate DB + DB --> TodoRepo: Todo 완료율 + deactivate DB + TodoRepo --> Service: double rate + deactivate TodoRepo + + note over Service + 비즈니스 로직: + - 데이터 조합 및 정제 + - DTO 변환 + - 통계 계산 + end note + + Service -> Service: 대시보드 데이터 조합 + + ' 캐시 저장 + Service -> Cache: SET dashboard:{userId}\n(TTL: 5분) + activate Cache + Cache --> Service: 캐시 저장 완료 + deactivate Cache +end + +Service --> Controller: DashboardResponse +deactivate Service + +note over Controller +응답 데이터 구조: +{ + "upcomingMeetings": [...], + "activeTodos": [...], + "recentMinutes": [...], + "sharedMinutes": [...], + "statistics": { + "upcomingMeetingsCount": n, + "activeTodosCount": n, + "todoCompletionRate": n + } +} +end note + +return 200 OK\nDashboardResponse + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-회의록공유.puml b/design/backend/sequence/inner/meeting-회의록공유.puml new file mode 100644 index 0000000..7fa7384 --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의록공유.puml @@ -0,0 +1,153 @@ +@startuml meeting-회의록공유 +!theme mono + +title Meeting Service - 회의록공유 내부 시퀀스 + +participant "ShareController" as Controller +participant "ShareService" as Service +participant "MinutesRepository" as MinutesRepo +participant "ShareRepository" as ShareRepo +participant "CalendarService" as CalendarService +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +queue "Azure Event Hubs<>" as EventHub + +[-> Controller: POST /minutes/{minutesId}/share +activate Controller + +note over Controller +경로 변수: minutesId +요청 데이터: +{ + "sharedWith": ["user1@example.com", "user2@example.com"], + "permission": "READ_ONLY" | "COMMENT" | "EDIT", + "shareMethod": "EMAIL" | "LINK", + "linkOptions": { + "expiresIn": 7, // days + "requirePassword": true, + "password": "..." + } +} +사용자 정보: userId, userName, email +end note + +Controller -> Controller: 입력 검증\n- sharedWith 최소 1명\n- permission 유효성 + +Controller -> Service: shareMinutes(minutesId, request, userId) +activate Service + +' 회의록 정보 조회 +Service -> MinutesRepo: findById(minutesId) +activate MinutesRepo +MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ? +activate DB +DB --> MinutesRepo: 회의록 정보 +deactivate DB +MinutesRepo --> Service: Minutes +deactivate MinutesRepo + +note over Service +비즈니스 규칙 검증: +- 회의록 존재 확인 +- 공유 권한 확인 (생성자만) +- 회의록 상태 확인 (FINALIZED만 공유 가능) +end note + +Service -> Service: 회의록 존재 확인 + +Service -> Service: 공유 권한 검증\n(생성자만 가능) + +Service -> Service: 회의록 상태 확인 + +alt 회의록이 확정되지 않음 + Service --> Controller: 400 Bad Request\n확정된 회의록만 공유 가능 + return 400 Bad Request +else 공유 가능 + ' 공유 링크 생성 + Service -> Service: 고유 공유 링크 생성\n(UUID 기반 토큰) + + Service -> ShareRepo: createShareLink(minutesId, token, options) + activate ShareRepo + ShareRepo -> DB: INSERT INTO share_links\n(minutesId, token, permission,\nexpiresAt, requirePassword, passwordHash,\ncreatedBy, createdAt) + activate DB + DB --> ShareRepo: 링크 생성 완료 + deactivate DB + ShareRepo --> Service: ShareLink + deactivate ShareRepo + + ' 공유 대상 저장 + loop 각 공유 대상마다 + Service -> ShareRepo: addSharedUser(minutesId, userEmail, permission) + activate ShareRepo + ShareRepo -> DB: INSERT INTO shared_minutes\n(minutesId, sharedWith, permission,\nsharedBy, sharedAt) + activate DB + DB --> ShareRepo: 공유 정보 저장 완료 + deactivate DB + ShareRepo --> Service: 저장 성공 + deactivate ShareRepo + end + + ' 회의록에서 다음 회의 일정 언급 확인 + Service -> Service: 다음 회의 일정 추출\n(회의록 내용 분석) + + alt 다음 회의 일정 언급됨 + Service -> CalendarService: createCalendarEvent(meetingInfo) + activate CalendarService + + note over CalendarService + 캘린더 이벤트 생성: + - 제목: 다음 회의 제목 + - 일시: 추출된 일시 + - 참석자: 공유 대상자 + end note + + CalendarService -> CalendarService: 캘린더 이벤트 생성 + CalendarService --> Service: 이벤트 ID + deactivate CalendarService + end + + ' 캐시 무효화 + Service -> Cache: DELETE minutes:detail:{minutesId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + note over Service + 비동기 이벤트 발행: + - 공유 대상자에게 이메일 발송 + - 공유 알림 발송 + - 캘린더 초대 발송 (일정 있는 경우) + end note + + ' 이벤트 발행 + Service -> EventHub: publish(MinutesShared)\n{\n minutesId, shareLink,\n sharedWith, permission,\n sharedBy, nextMeetingEvent\n} + activate EventHub + EventHub --> Service: 발행 완료 + deactivate EventHub + + Service --> Controller: ShareResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "minutesId": "uuid", + "shareLink": "https://.../share/{token}", + "sharedWith": [...], + "permission": "READ_ONLY", + "expiresAt": "2025-01-30T00:00:00", + "sharedAt": "2025-01-23T16:30:00", + "nextMeetingEvent": { + "eventId": "...", + "title": "...", + "startTime": "..." + } + } + end note + + return 200 OK\nShareResponse +end + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-회의록상세조회.puml b/design/backend/sequence/inner/meeting-회의록상세조회.puml new file mode 100644 index 0000000..4a0ad09 --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의록상세조회.puml @@ -0,0 +1,182 @@ +@startuml meeting-회의록상세조회 +!theme mono + +title Meeting Service - 회의록상세조회 내부 시퀀스 + +participant "MinutesController" as Controller +participant "MinutesService" as Service +participant "MinutesRepository" as MinutesRepo +participant "SectionRepository" as SectionRepo +participant "RelatedMinutesService" as RelatedService +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache + +[-> Controller: GET /minutes/{minutesId} +activate Controller + +note over Controller +경로 변수: minutesId +사용자 정보: userId, userName, email +end note + +Controller -> Controller: minutesId 유효성 검증 + +Controller -> Service: getMinutesDetail(minutesId, userId) +activate Service + +' 캐시 조회 +Service -> Cache: GET minutes:detail:{minutesId} +activate Cache +Cache --> Service: 캐시 조회 결과 +deactivate Cache + +alt Cache Hit + Service -> Service: 권한 확인\n(캐시 데이터에서) + + alt 권한 없음 + Service --> Controller: 403 Forbidden\n조회 권한 없음 + return 403 Forbidden + else 권한 있음 + Service --> Controller: MinutesDetailResponse\n(캐시 데이터) + return 200 OK + end +else Cache Miss + ' 회의록 기본 정보 조회 + Service -> MinutesRepo: findByIdWithMeeting(minutesId) + activate MinutesRepo + MinutesRepo -> DB: SELECT m.*, mt.*\nFROM minutes m\nJOIN meetings mt ON m.meetingId = mt.id\nWHERE m.id = ? + activate DB + DB --> MinutesRepo: 회의록 및 회의 정보 + deactivate DB + MinutesRepo --> Service: Minutes + Meeting + deactivate MinutesRepo + + note over Service + 비즈니스 규칙 검증: + - 회의록 존재 확인 + - 조회 권한 확인 + * 생성자 + * 참석자 + * 공유받은 사용자 + end note + + Service -> Service: 회의록 존재 확인 + + ' 권한 확인 + Service -> MinutesRepo: hasAccessPermission(minutesId, userId) + activate MinutesRepo + MinutesRepo -> DB: SELECT COUNT(*) FROM (\n SELECT 1 FROM minutes WHERE id = ? AND creatorId = ?\n UNION\n SELECT 1 FROM participants p JOIN meetings m\n ON p.meetingId = m.id JOIN minutes mi\n ON mi.meetingId = m.id\n WHERE mi.id = ? AND p.userId = ?\n UNION\n SELECT 1 FROM shared_minutes\n WHERE minutesId = ? AND sharedWith = ?\n) AS access + activate DB + DB --> MinutesRepo: 권한 확인 결과 + deactivate DB + MinutesRepo --> Service: boolean hasAccess + deactivate MinutesRepo + + alt 권한 없음 + Service --> Controller: 403 Forbidden\n조회 권한 없음 + return 403 Forbidden + else 권한 있음 + ' 참석자 목록 조회 + Service -> MinutesRepo: findParticipants(minutesId) + activate MinutesRepo + MinutesRepo -> DB: SELECT p.*, u.name, u.email\nFROM participants p\nJOIN meetings m ON p.meetingId = m.id\nJOIN minutes mi ON mi.meetingId = m.id\nJOIN users u ON p.userId = u.id\nWHERE mi.id = ? + activate DB + DB --> MinutesRepo: 참석자 목록 + deactivate DB + MinutesRepo --> Service: List + deactivate MinutesRepo + + ' 섹션별 상세 내용 조회 + Service -> SectionRepo: findSectionsByMinutesId(minutesId) + activate SectionRepo + SectionRepo -> DB: SELECT * FROM minutes_sections\nWHERE minutesId = ?\nORDER BY sectionOrder + activate DB + DB --> SectionRepo: 섹션 목록 + deactivate DB + SectionRepo --> Service: List
+ deactivate SectionRepo + + ' AI 요약 정보 조회 + Service -> SectionRepo: findAISummaries(minutesId) + activate SectionRepo + SectionRepo -> DB: SELECT * FROM ai_summaries\nWHERE minutesId = ?\nORDER BY sectionId + activate DB + DB --> SectionRepo: AI 요약 목록 + deactivate DB + SectionRepo --> Service: List + deactivate SectionRepo + + ' 관련 회의록 조회 + Service -> RelatedService: findRelatedMinutes(minutesId, limit: 3) + activate RelatedService + + note over RelatedService + 관련 회의록 검색 로직: + - 벡터 유사도 기반 검색 (AI 서비스 호출) + - 관련도 70% 이상 + - 최대 3개 + end note + + RelatedService -> DB: SELECT * FROM related_minutes\nWHERE minutesId = ?\nAND similarityScore >= 0.7\nORDER BY similarityScore DESC\nLIMIT 3 + activate DB + DB --> RelatedService: 관련 회의록 목록 + deactivate DB + RelatedService --> Service: List + deactivate RelatedService + + ' 조회 이력 기록 + Service -> MinutesRepo: recordViewHistory(minutesId, userId) + activate MinutesRepo + MinutesRepo -> DB: INSERT INTO view_history\n(minutesId, userId, viewedAt)\nVALUES (?, ?, NOW()) + activate DB + DB --> MinutesRepo: 기록 완료 + deactivate DB + MinutesRepo --> Service: 기록 성공 + deactivate MinutesRepo + + note over Service + 데이터 조합: + - 기본 정보 + - 참석자 목록 + - 섹션별 내용 + - AI 요약 + - 관련 회의록 + - 권한 정보 (수정 가능 여부) + end note + + Service -> Service: 상세 응답 DTO 구성 + + ' 캐시 저장 + Service -> Cache: SET minutes:detail:{minutesId}\n(TTL: 10분) + activate Cache + Cache --> Service: 캐싱 완료 + deactivate Cache + + Service --> Controller: MinutesDetailResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "minutesId": "uuid", + "meeting": {...}, + "participants": [...], + "sections": [...], + "aiSummaries": [...], + "relatedMinutes": [...], + "permissions": { + "canEdit": true/false, + "canShare": true/false + }, + "status": "FINALIZED", + "version": "v1.0" + } + end note + + return 200 OK\nMinutesDetailResponse + end +end + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-회의록수정.puml b/design/backend/sequence/inner/meeting-회의록수정.puml new file mode 100644 index 0000000..6660d7f --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의록수정.puml @@ -0,0 +1,191 @@ +@startuml meeting-회의록수정 +!theme mono + +title Meeting Service - 회의록수정 내부 시퀀스 + +participant "MinutesController" as Controller +participant "MinutesService" as Service +participant "MinutesRepository" as MinutesRepo +participant "SectionRepository" as SectionRepo +participant "VersionService" as VersionService +participant "CollaborationService" as CollabService +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +participant "WebSocket<>" as WebSocket + +[-> Controller: PATCH /minutes/{minutesId} +activate Controller + +note over Controller +경로 변수: minutesId +요청 데이터: +{ + "title": "수정된 제목", + "sections": [ + { + "sectionId": "uuid", + "content": "수정된 내용", + "aiSummary": "수정된 요약" + } + ] +} +사용자 정보: userId, userName, email +end note + +Controller -> Controller: 입력 검증 + +Controller -> Service: updateMinutes(minutesId, request, userId) +activate Service + +' 회의록 정보 조회 +Service -> MinutesRepo: findById(minutesId) +activate MinutesRepo +MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ? +activate DB +DB --> MinutesRepo: 회의록 정보 +deactivate DB +MinutesRepo --> Service: Minutes +deactivate MinutesRepo + +note over Service +비즈니스 규칙 검증: +- 회의록 존재 확인 +- 수정 권한 확인 (생성자만) +- 잠긴 섹션 수정 시도 검증 +- 확정된 회의록은 DRAFT로 상태 변경 +end note + +Service -> Service: 회의록 존재 확인 + +Service -> Service: 수정 권한 검증\n(생성자만 가능) + +alt 권한 없음 + Service --> Controller: 403 Forbidden\n수정 권한 없음 + return 403 Forbidden +else 권한 있음 + ' 잠긴 섹션 확인 + Service -> SectionRepo: findLockedSections(minutesId) + activate SectionRepo + SectionRepo -> DB: SELECT * FROM minutes_sections\nWHERE minutesId = ?\nAND isLocked = true + activate DB + DB --> SectionRepo: 잠긴 섹션 목록 + deactivate DB + SectionRepo --> Service: List
+ deactivate SectionRepo + + Service -> Service: 잠긴 섹션 수정 여부 검증 + + alt 잠긴 섹션 수정 시도 + Service --> Controller: 400 Bad Request\n잠긴 섹션은 수정 불가 + return 400 Bad Request + else 수정 가능 + ' 수정 이력 저장 (버전 관리) + Service -> VersionService: createModificationHistory(minutesId, userId, changes) + activate VersionService + VersionService -> DB: INSERT INTO modification_history\n(minutesId, modifiedBy, modifiedAt,\nchangeType, oldValue, newValue) + activate DB + DB --> VersionService: 이력 저장 완료 + deactivate DB + VersionService --> Service: 저장 성공 + deactivate VersionService + + ' 회의록 제목 수정 + alt 제목 변경됨 + Service -> MinutesRepo: updateTitle(minutesId, newTitle) + activate MinutesRepo + MinutesRepo -> DB: UPDATE minutes\nSET title = ?,\n updatedAt = NOW(),\n updatedBy = ?\nWHERE id = ? + activate DB + DB --> MinutesRepo: 업데이트 완료 + deactivate DB + MinutesRepo --> Service: 업데이트 성공 + deactivate MinutesRepo + end + + ' 섹션별 내용 수정 + loop 각 섹션마다 + Service -> SectionRepo: updateSection(sectionId, content, aiSummary) + activate SectionRepo + SectionRepo -> DB: UPDATE minutes_sections\nSET content = ?,\n aiSummary = ?,\n updatedAt = NOW(),\n updatedBy = ?\nWHERE id = ? + activate DB + DB --> SectionRepo: 업데이트 완료 + deactivate DB + SectionRepo --> Service: 업데이트 성공 + deactivate SectionRepo + end + + ' 회의록 상태 변경 (확정 → 작성중) + alt 확정된 회의록 수정 + Service -> MinutesRepo: updateStatus(minutesId, "DRAFT") + activate MinutesRepo + MinutesRepo -> DB: UPDATE minutes\nSET status = 'DRAFT'\nWHERE id = ?\nAND status = 'FINALIZED' + activate DB + DB --> MinutesRepo: 업데이트 완료 + deactivate DB + MinutesRepo --> Service: 상태 변경 완료 + deactivate MinutesRepo + end + + ' 캐시 무효화 + Service -> Cache: DELETE minutes:detail:{minutesId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + Service -> Cache: DELETE minutes:info:{minutesId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + note over Service + 실시간 협업: + - WebSocket을 통해 참석자에게 수정 사항 전송 + - 수정 델타만 전송 (전체 내용 아님) + - 수정자 정보 포함 + end note + + ' 실시간 동기화 + Service -> CollabService: broadcastUpdate(minutesId, changes, userId) + activate CollabService + + note over CollabService + WebSocket 메시지 형식: + { + "type": "MINUTES_UPDATED", + "minutesId": "uuid", + "changes": [...], + "modifiedBy": { + "userId": "...", + "userName": "..." + }, + "timestamp": "..." + } + end note + + CollabService -> WebSocket: broadcast to room:{minutesId} + activate WebSocket + WebSocket --> CollabService: 전송 완료 + deactivate WebSocket + CollabService --> Service: 동기화 완료 + deactivate CollabService + + Service --> Controller: MinutesUpdateResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "minutesId": "uuid", + "status": "DRAFT", + "updatedAt": "2025-01-23T16:00:00", + "updatedBy": "userId", + "version": "v1.1" + } + end note + + return 200 OK\nMinutesUpdateResponse + end +end + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-회의록확정.puml b/design/backend/sequence/inner/meeting-회의록확정.puml new file mode 100644 index 0000000..58fcc86 --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의록확정.puml @@ -0,0 +1,148 @@ +@startuml meeting-회의록확정 +!theme mono + +title Meeting Service - 회의록확정 내부 시퀀스 + +participant "MinutesController" as Controller +participant "MinutesService" as Service +participant "ValidationService" as ValidationService +participant "MinutesRepository" as MinutesRepo +participant "TodoService" as TodoService +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +queue "Azure Event Hubs<>" as EventHub + +[-> Controller: POST /minutes/{minutesId}/finalize +activate Controller + +note over Controller +경로 변수: minutesId +사용자 정보: userId, userName, email +end note + +Controller -> Controller: minutesId 유효성 검증 + +Controller -> Service: finalizeMinutes(minutesId, userId) +activate Service + +' 회의록 정보 조회 +Service -> MinutesRepo: findById(minutesId) +activate MinutesRepo +MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ? +activate DB +DB --> MinutesRepo: 회의록 정보 +deactivate DB +MinutesRepo --> Service: Minutes +deactivate MinutesRepo + +note over Service +비즈니스 규칙 검증: +- 회의록이 존재하는지 확인 +- 확정 권한 확인 (생성자만) +- 회의록 상태 확인 (DRAFT만 확정 가능) +- 필수 항목 검증 +end note + +Service -> Service: 권한 검증\n(생성자만 확정 가능) + +Service -> Service: 회의록 상태 확인 + +alt 회의록이 이미 확정됨 + Service --> Controller: 409 Conflict\n이미 확정된 회의록 + return 409 Conflict +else 확정 가능 + note over Service + 필수 항목 검증: + - 회의 제목 + - 참석자 목록 + - 주요 논의 내용 + - 결정 사항 + end note + + ' 필수 항목 검증 + Service -> ValidationService: validateRequiredFields(minutes) + activate ValidationService + + ValidationService -> ValidationService: 제목 존재 확인 + ValidationService -> ValidationService: 참석자 목록 확인 + ValidationService -> ValidationService: 논의 내용 확인 + ValidationService -> ValidationService: 결정 사항 확인 + + alt 필수 항목 누락 + ValidationService --> Service: ValidationException\n누락된 항목 목록 + Service --> Controller: 400 Bad Request\n필수 항목 누락 + return 400 Bad Request + else 검증 통과 + ValidationService --> Service: 검증 성공 + deactivate ValidationService + + ' 버전 생성 + Service -> Service: 확정 버전 번호 생성\n(v1.0) + + ' 회의록 상태 업데이트 + Service -> MinutesRepo: finalize(minutesId, version) + activate MinutesRepo + MinutesRepo -> DB: UPDATE minutes\nSET status = 'FINALIZED',\n version = ?,\n finalizedAt = NOW(),\n finalizedBy = ?\nWHERE id = ? + activate DB + DB --> MinutesRepo: 업데이트 완료 + deactivate DB + MinutesRepo --> Service: 업데이트 성공 + deactivate MinutesRepo + + ' 회의록 스냅샷 저장 (버전 관리) + Service -> MinutesRepo: saveSnapshot(minutesId, version, content) + activate MinutesRepo + MinutesRepo -> DB: INSERT INTO minutes_snapshots\n(minutesId, version, content, createdAt) + activate DB + DB --> MinutesRepo: 스냅샷 저장 완료 + deactivate DB + MinutesRepo --> Service: 저장 성공 + deactivate MinutesRepo + + ' 캐시 무효화 + Service -> Cache: DELETE minutes:info:{minutesId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + note over Service + 비동기 처리: + - AI가 Todo 항목 자동 추출 + - 회의록 공유 가능 상태로 전환 + - 참석자에게 확정 알림 + end note + + ' AI Todo 추출 이벤트 발행 + Service -> EventHub: publish(MinutesFinalized)\n{\n minutesId, meetingId,\n finalizedAt, content,\n participants\n} + activate EventHub + EventHub --> Service: 발행 완료 + deactivate EventHub + + ' Todo 자동 추출 (비동기 응답 대기 없음) + note over Service + AI 서비스가 MinutesFinalized 이벤트를 구독하여 + Todo 자동 추출 및 할당 수행 + 추출된 Todo는 별도 이벤트로 전달됨 + end note + + Service --> Controller: MinutesFinalizeResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "minutesId": "uuid", + "status": "FINALIZED", + "version": "v1.0", + "finalizedAt": "2025-01-23T15:30:00", + "finalizedBy": "userId" + } + end note + + return 200 OK\nMinutesFinalizeResponse + end +end + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-회의시작.puml b/design/backend/sequence/inner/meeting-회의시작.puml new file mode 100644 index 0000000..1ad0709 --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의시작.puml @@ -0,0 +1,148 @@ +@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 +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +queue "Azure Event Hubs<>" as EventHub + +[-> Controller: POST /meetings/{meetingId}/start +activate Controller + +note over Controller +경로 변수: meetingId +사용자 정보: userId, userName, email +end note + +Controller -> Controller: meetingId 유효성 검증 + +Controller -> Service: startMeeting(meetingId, userId) +activate Service + +' 회의 정보 조회 +Service -> Cache: GET meeting:info:{meetingId} +activate Cache +Cache --> Service: 캐시 조회 결과 +deactivate Cache + +alt Cache Miss + Service -> MeetingRepo: findById(meetingId) + activate MeetingRepo + MeetingRepo -> DB: SELECT * FROM meetings\nWHERE id = ? + activate DB + DB --> MeetingRepo: 회의 정보 + deactivate DB + MeetingRepo --> Service: Meeting + deactivate MeetingRepo + + Service -> Cache: SET meeting:info:{meetingId} + activate Cache + Cache --> Service: 캐싱 완료 + deactivate Cache +end + +note over Service +비즈니스 규칙 검증: +- 회의가 존재하는지 확인 +- 회의 시작 권한 확인 (생성자만) +- 회의 상태 확인 (SCHEDULED만 시작 가능) +- 회의 시작 시간 10분 전부터 가능 +end note + +Service -> Service: 권한 검증\n(생성자 또는 참석자) + +Service -> Service: 회의 상태 확인 + +alt 회의가 이미 진행 중 + Service --> Controller: 409 Conflict\n이미 진행 중인 회의 + return 409 Conflict +else 시작 가능 + Service -> Service: 회의 세션 생성 + + ' 세션 저장 + Service -> SessionRepo: createSession(meetingId, userId) + activate SessionRepo + + note over SessionRepo + 세션 정보: + - sessionId (UUID) + - meetingId + - startedBy (userId) + - startedAt (현재시각) + - status: ACTIVE + end note + + SessionRepo -> DB: INSERT INTO meeting_sessions\n(id, meetingId, startedBy, startedAt, status) + activate DB + DB --> SessionRepo: 세션 생성 완료 + deactivate DB + SessionRepo --> Service: Session + deactivate SessionRepo + + ' 회의 상태 업데이트 + Service -> MeetingRepo: updateStatus(meetingId, "IN_PROGRESS") + activate MeetingRepo + MeetingRepo -> DB: UPDATE meetings\nSET status = 'IN_PROGRESS',\n actualStartTime = NOW()\nWHERE id = ? + activate DB + DB --> MeetingRepo: 업데이트 완료 + deactivate DB + MeetingRepo --> Service: 업데이트 성공 + deactivate MeetingRepo + + ' 캐시 무효화 + Service -> Cache: DELETE meeting:info:{meetingId} + activate Cache + Cache --> Service: 삭제 완료 + deactivate Cache + + ' 회의록 초안 생성 (빈 회의록) + Service -> Service: 회의록 초안 생성 + + Service -> MeetingRepo: createMinutesDraft(meetingId, sessionId) + activate MeetingRepo + MeetingRepo -> DB: INSERT INTO minutes\n(id, meetingId, sessionId, status, createdAt)\nVALUES (?, ?, ?, 'DRAFT', NOW()) + activate DB + DB --> MeetingRepo: 회의록 생성 완료 + deactivate DB + MeetingRepo --> Service: Minutes + deactivate MeetingRepo + + note over Service + 비동기 이벤트 발행: + - STT 서비스에 녹음 시작 요청 + - 참석자에게 회의 시작 알림 + - 실시간 협업 WebSocket 준비 + end note + + ' 이벤트 발행 + Service -> EventHub: publish(MeetingStarted)\n{\n meetingId, sessionId,\n startedAt, participants\n} + activate EventHub + EventHub --> Service: 발행 완료 + deactivate EventHub + + Service --> Controller: SessionResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "sessionId": "uuid", + "meetingId": "uuid", + "status": "IN_PROGRESS", + "startedAt": "2025-01-23T14:00:00", + "minutesId": "uuid" + } + end note + + return 201 Created\nSessionResponse +end + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-회의예약.puml b/design/backend/sequence/inner/meeting-회의예약.puml new file mode 100644 index 0000000..e1a35d1 --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의예약.puml @@ -0,0 +1,123 @@ +@startuml meeting-회의예약 +!theme mono + +title Meeting Service - 회의예약 내부 시퀀스 + +participant "MeetingController" as Controller +participant "MeetingService" as Service +participant "MeetingRepository" as Repo +participant "ParticipantRepository" as ParticipantRepo +database "Meeting DB<>" as DB +database "Redis Cache<>" as Cache +queue "Azure Event Hubs<>" as EventHub + +[-> Controller: POST /meetings +activate Controller + +note over Controller +요청 데이터: +{ + "title": "회의 제목", + "startTime": "2025-01-23T14:00:00", + "endTime": "2025-01-23T15:00:00", + "location": "회의실 A", + "participants": ["user1@example.com", ...] +} +사용자 정보: userId, userName, email +end note + +Controller -> Controller: 입력 검증\n- 제목 필수 (최대 100자)\n- 날짜/시간 필수\n- 참석자 최소 1명 + +Controller -> Service: createMeeting(request, userId) +activate Service + +note over Service +비즈니스 규칙 검증: +- 회의 시간 유효성 (시작 < 종료) +- 중복 회의 체크 +- 참석자 유효성 검증 +end note + +Service -> Service: 회의 시간 유효성 검사 + +Service -> Repo: checkConflictingMeetings(userId, startTime, endTime) +activate Repo +Repo -> DB: SELECT COUNT(*) FROM meetings\nWHERE creatorId = ?\nAND status = 'SCHEDULED'\nAND (startTime <= ? AND endTime >= ?) +activate DB +DB --> Repo: 중복 회의 개수 +deactivate DB +Repo --> Service: int count +deactivate Repo + +alt 중복 회의 존재 + Service --> Controller: 409 Conflict\n중복된 회의 시간 + return 409 Conflict +else 중복 없음 + Service -> Service: Meeting 엔티티 생성\n- 회의 ID 생성\n- 상태: SCHEDULED\n- 생성자 정보 설정 + + ' 회의 정보 저장 + Service -> Repo: save(meeting) + activate Repo + Repo -> DB: INSERT INTO meetings\n(id, title, startTime, endTime,\nlocation, status, creatorId, createdAt) + activate DB + DB --> Repo: 회의 저장 완료 + deactivate DB + Repo --> Service: Meeting + deactivate Repo + + ' 참석자 저장 + Service -> ParticipantRepo: saveAll(participants) + activate ParticipantRepo + ParticipantRepo -> DB: INSERT INTO participants\n(meetingId, email, role, status) + activate DB + DB --> ParticipantRepo: 참석자 저장 완료 + deactivate DB + ParticipantRepo --> Service: List + deactivate ParticipantRepo + + ' 캐시 저장 + Service -> Cache: SET meeting:info:{meetingId}\n(TTL: 10분) + activate Cache + Cache --> Service: 캐싱 완료 + deactivate Cache + + Service -> Cache: SET meeting:participants:{meetingId}\n(TTL: 10분) + activate Cache + Cache --> Service: 캐싱 완료 + deactivate Cache + + note over Service + 비동기 이벤트 발행: + - 초대 이메일 발송 + - 캘린더 등록 + - 리마인더 스케줄링 + end note + + ' 이벤트 발행 + Service -> EventHub: publish(MeetingCreated)\n{\n meetingId, title, startTime,\n participants, creatorInfo\n} + activate EventHub + EventHub --> Service: 발행 완료 + deactivate EventHub + + Service --> Controller: MeetingResponse + deactivate Service + + note over Controller + 응답 데이터: + { + "meetingId": "uuid", + "title": "회의 제목", + "startTime": "...", + "endTime": "...", + "location": "...", + "participants": [...], + "status": "SCHEDULED" + } + end note + + return 201 Created\nMeetingResponse +end + +deactivate Controller + +@enduml diff --git a/design/backend/sequence/inner/meeting-회의종료.puml b/design/backend/sequence/inner/meeting-회의종료.puml new file mode 100644 index 0000000..0fb73c4 --- /dev/null +++ b/design/backend/sequence/inner/meeting-회의종료.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 diff --git a/design/backend/sequence/inner/notification-Todo알림발송.puml b/design/backend/sequence/inner/notification-Todo알림발송.puml new file mode 100644 index 0000000..766af50 --- /dev/null +++ b/design/backend/sequence/inner/notification-Todo알림발송.puml @@ -0,0 +1,147 @@ +@startuml +!theme mono + +title Notification Service - Todo알림발송 내부 시퀀스 + +participant "NotificationController" as Controller +participant "NotificationService" as Service +participant "EmailTemplateService" as TemplateService +participant "NotificationRepository" as Repository +participant "EmailClient" as EmailClient +database "Notification DB" as DB +queue "Azure Event Hubs<>" as EventHub +participant "Email Service<>" as EmailService + +== TodoAssigned 이벤트 수신 == + +EventHub -> Controller: TodoAssigned 이벤트 수신 +activate Controller +note right + 이벤트 데이터: + - todoId + - meetingId + - 담당자 (userId, userName, email) + - Todo 내용 + - 마감일 + - 우선순위 + - 회의록 링크 +end note + +Controller -> Service: sendTodoNotification(todoId, todoData) +activate Service + +== 알림 기록 생성 == + +Service -> Repository: createNotification(todoId, "TODO_ASSIGNED", assignee) +activate Repository + +Repository -> DB: INSERT INTO notifications\n(notification_id, todo_id,\ntype='TODO_ASSIGNED',\nstatus='PENDING',\nrecipients,\ncreated_at) +activate DB +DB --> Repository: notificationId 반환 +deactivate DB + +Repository --> Service: NotificationEntity 반환 +deactivate Repository + +== 이메일 템플릿 생성 == + +Service -> TemplateService: generateTodoEmail(todoData) +activate TemplateService + +TemplateService -> TemplateService: 템플릿 로드 +note right + 템플릿 정보: + - 제목: "[TODO 할당] {Todo 내용}" + - 내용: Todo 상세 + 회의록 링크 + - 우선순위 뱃지 표시 +end note + +TemplateService -> TemplateService: 데이터 바인딩 +note right + 바인딩 데이터: + - Todo 내용 + - 마감일 + - 우선순위 + - 회의 제목 + - 회의록 링크 (해당 섹션) + - Todo 관리 페이지 링크 +end note + +TemplateService --> Service: EmailContent 반환 +deactivate TemplateService + +== 이메일 발송 == + +Service -> EmailClient: sendEmail(assignee.email, emailContent) +activate EmailClient + +EmailClient -> EmailService: SMTP 이메일 발송 +activate EmailService + +EmailService --> EmailClient: 발송 결과 +deactivate EmailService + +alt 발송 성공 + EmailClient --> Service: SUCCESS + + Service -> Repository: updateNotificationStatus(notificationId, "SENT") + activate Repository + Repository -> DB: UPDATE notifications\nSET status='SENT',\nsent_at=NOW()\nWHERE notification_id='{notificationId}' + activate DB + DB --> Repository: 업데이트 완료 + deactivate DB + Repository --> Service: 완료 + deactivate Repository + +else 발송 실패 + EmailClient --> Service: FAILED (errorMessage) + + Service -> Repository: updateNotificationStatus(notificationId, "FAILED") + activate Repository + Repository -> DB: UPDATE notifications\nSET status='FAILED',\nerror_message='{errorMessage}'\nWHERE notification_id='{notificationId}' + activate DB + DB --> Repository: 업데이트 완료 + deactivate DB + Repository --> Service: 완료 + deactivate Repository + + Service -> Service: 재시도 큐에 추가 +end + +deactivate EmailClient + +Service --> Controller: NotificationResponse\n(notificationId, status) +deactivate Service + +Controller --> EventHub: TodoNotificationSent 이벤트 발행\n(todoId, notificationId, status) +deactivate Controller + +== Todo 마감일 3일 전 리마인더 (스케줄링) == + +note over Service, EmailService + 별도 스케줄링 작업: + - 마감일 3일 전 자동 리마인더 + - 실행 주기: 1일 1회 + - 대상: 미완료 Todo + - 템플릿: "[리마인더] Todo 마감 3일 전" +end note + +note over Controller, EmailService +처리 시간: +- 알림 기록 생성: ~100ms +- 템플릿 생성: ~200ms +- 이메일 발송: ~500ms +- 총 처리 시간: ~800ms + +재시도 정책: +- 최대 3회 재시도 +- 재시도 간격: 5분, 15분, 30분 + +Todo 알림 유형: +1. 할당 알림 (즉시) +2. 마감일 3일 전 리마인더 +3. 마감일 1일 전 리마인더 +4. 마감일 당일 리마인더 +end note + +@enduml diff --git a/design/backend/sequence/inner/notification-리마인더발송.puml b/design/backend/sequence/inner/notification-리마인더발송.puml new file mode 100644 index 0000000..edcd86c --- /dev/null +++ b/design/backend/sequence/inner/notification-리마인더발송.puml @@ -0,0 +1,158 @@ +@startuml +!theme mono + +title Notification Service - 리마인더발송 내부 시퀀스 + +participant "SchedulerJob" as Scheduler +participant "ReminderService" as Service +participant "EmailTemplateService" as TemplateService +participant "NotificationRepository" as Repository +participant "EmailClient" as EmailClient +database "Notification DB" as DB +participant "Email Service<>" as EmailService + +== 스케줄링된 작업 실행 (회의 시작 30분 전) == + +Scheduler -> Scheduler: 30분 전 알림 대상 회의 조회 +activate Scheduler +note right + 조회 조건: + - 회의 시작 시간 - 30분 = NOW + - 회의 상태 = 예약됨 + - 리마인더 미발송 +end note + +Scheduler -> Service: sendMeetingReminders(meetingList) +activate Service + +loop 각 회의별 + Service -> Repository: checkReminderSent(meetingId) + activate Repository + + Repository -> DB: SELECT * FROM notifications\nWHERE meeting_id='{meetingId}'\nAND type='REMINDER' + activate DB + DB --> Repository: 조회 결과 + deactivate DB + + Repository --> Service: 발송 여부 확인 + deactivate Repository + + alt 이미 발송됨 + Service -> Service: 스킵 + else 미발송 + + == 리마인더 알림 생성 == + + Service -> Repository: createNotification(meetingId, "REMINDER", participants) + activate Repository + + Repository -> DB: INSERT INTO notifications\n(notification_id, meeting_id,\ntype='REMINDER',\nstatus='PENDING',\nrecipients,\ncreated_at) + activate DB + DB --> Repository: notificationId 반환 + deactivate DB + + Repository --> Service: NotificationEntity 반환 + deactivate Repository + + == 이메일 템플릿 생성 == + + Service -> TemplateService: generateReminderEmail(meetingData) + activate TemplateService + + TemplateService -> TemplateService: 템플릿 로드 + note right + 템플릿 정보: + - 제목: "[리마인더] {회의 제목} - 30분 후 시작" + - 내용: 회의 정보 + 참여 링크 + - 긴급도: 높음 + end note + + TemplateService -> TemplateService: 데이터 바인딩 + note right + 바인딩 데이터: + - 회의 제목 + - 시작 시간 (30분 후) + - 장소 + - 회의 참여 링크 + - 준비 사항 (있는 경우) + end note + + TemplateService --> Service: EmailContent 반환 + deactivate TemplateService + + == 참석자별 이메일 발송 == + + loop 각 참석자별 + Service -> EmailClient: sendEmail(recipient, emailContent) + activate EmailClient + + EmailClient -> EmailService: SMTP 이메일 발송 + activate EmailService + + EmailService --> EmailClient: 발송 결과 + deactivate EmailService + + alt 발송 성공 + EmailClient --> Service: SUCCESS + + Service -> Repository: updateRecipientStatus(notificationId, recipient, "SENT") + activate Repository + Repository -> DB: UPDATE notification_recipients\nSET status='SENT', sent_at=NOW() + activate DB + DB --> Repository: 업데이트 완료 + deactivate DB + Repository --> Service: 완료 + deactivate Repository + + else 발송 실패 + EmailClient --> Service: FAILED + + Service -> Repository: updateRecipientStatus(notificationId, recipient, "FAILED") + activate Repository + Repository -> DB: UPDATE notification_recipients\nSET status='FAILED' + activate DB + DB --> Repository: 업데이트 완료 + deactivate DB + Repository --> Service: 완료 + deactivate Repository + + Service -> Service: 재시도 큐에 추가 + end + + deactivate EmailClient + end + + == 알림 상태 업데이트 == + + Service -> Repository: updateNotificationStatus(notificationId, finalStatus) + activate Repository + + Repository -> DB: UPDATE notifications\nSET status='{finalStatus}',\ncompleted_at=NOW(),\nsent_count={sentCount},\nfailed_count={failedCount} + activate DB + DB --> Repository: 업데이트 완료 + deactivate DB + + Repository --> Service: 완료 + deactivate Repository + end +end + +Service --> Scheduler: 전체 리마인더 발송 완료\n(총 발송 건수, 성공/실패 통계) +deactivate Service + +Scheduler -> Scheduler: 다음 스케줄 대기 +deactivate Scheduler + +note over Scheduler, EmailService +스케줄링 정책: +- 실행 주기: 1분마다 +- 대상: 30분 후 시작 회의 +- 중복 발송 방지: DB 체크 + +처리 시간: +- 대상 회의 조회: ~200ms +- 이메일 발송 (per recipient): ~500ms +- 총 처리 시간: 회의 및 참석자 수에 비례 +end note + +@enduml diff --git a/design/backend/sequence/inner/notification-초대알림발송.puml b/design/backend/sequence/inner/notification-초대알림발송.puml new file mode 100644 index 0000000..e376809 --- /dev/null +++ b/design/backend/sequence/inner/notification-초대알림발송.puml @@ -0,0 +1,145 @@ +@startuml +!theme mono + +title Notification Service - 초대알림발송 내부 시퀀스 + +participant "NotificationController" as Controller +participant "NotificationService" as Service +participant "EmailTemplateService" as TemplateService +participant "NotificationRepository" as Repository +participant "EmailClient" as EmailClient +database "Notification DB" as DB +queue "Azure Event Hubs<>" as EventHub +participant "Email Service<>" as EmailService + +== MeetingCreated 이벤트 수신 == + +EventHub -> Controller: MeetingCreated 이벤트 수신 +activate Controller +note right + 이벤트 데이터: + - meetingId + - 제목 + - 일시 + - 장소 + - 참석자 목록 (이메일) + - 생성자 정보 +end note + +Controller -> Service: sendMeetingInvitation(meetingId, meetingData) +activate Service + +== 알림 기록 생성 == + +Service -> Repository: createNotification(meetingId, "INVITATION", participants) +activate Repository + +Repository -> DB: INSERT INTO notifications\n(notification_id, meeting_id,\ntype='INVITATION',\nstatus='PENDING',\nrecipients,\ncreated_at) +activate DB +DB --> Repository: notificationId 반환 +deactivate DB + +Repository --> Service: NotificationEntity 반환 +deactivate Repository + +== 이메일 템플릿 생성 == + +Service -> TemplateService: generateInvitationEmail(meetingData) +activate TemplateService + +TemplateService -> TemplateService: 템플릿 로드 +note right + 템플릿 정보: + - 제목: "[회의 초대] {회의 제목}" + - 내용: 회의 정보 + 참여 링크 + - CTA 버튼: "회의 참석하기" +end note + +TemplateService -> TemplateService: 데이터 바인딩 +note right + 바인딩 데이터: + - 회의 제목 + - 날짜/시간 + - 장소 + - 생성자 이름 + - 회의 참여 링크 + - 캘린더 추가 링크 +end note + +TemplateService --> Service: EmailContent 반환\n(subject, htmlBody, plainTextBody) +deactivate TemplateService + +== 참석자별 이메일 발송 (병렬 처리) == + +loop 각 참석자별 + Service -> EmailClient: sendEmail(recipient, emailContent) + activate EmailClient + + EmailClient -> EmailService: SMTP 이메일 발송 + activate EmailService + + EmailService --> EmailClient: 발송 결과 + deactivate EmailService + + alt 발송 성공 + EmailClient --> Service: SUCCESS + + Service -> Repository: updateRecipientStatus(notificationId, recipient, "SENT") + activate Repository + Repository -> DB: UPDATE notification_recipients\nSET status='SENT', sent_at=NOW()\nWHERE notification_id='{notificationId}'\nAND recipient='{email}' + activate DB + DB --> Repository: 업데이트 완료 + deactivate DB + Repository --> Service: 완료 + deactivate Repository + + else 발송 실패 + EmailClient --> Service: FAILED (errorMessage) + + Service -> Repository: updateRecipientStatus(notificationId, recipient, "FAILED") + activate Repository + Repository -> DB: UPDATE notification_recipients\nSET status='FAILED',\nerror_message='{errorMessage}'\nWHERE notification_id='{notificationId}'\nAND recipient='{email}' + activate DB + DB --> Repository: 업데이트 완료 + deactivate DB + Repository --> Service: 완료 + deactivate Repository + + Service -> Service: 재시도 큐에 추가\n(최대 3회 재시도) + end + + deactivate EmailClient +end + +== 전체 알림 상태 업데이트 == + +Service -> Repository: updateNotificationStatus(notificationId, finalStatus) +activate Repository + +Repository -> DB: UPDATE notifications\nSET status='{finalStatus}',\ncompleted_at=NOW(),\nsent_count={sentCount},\nfailed_count={failedCount}\nWHERE notification_id='{notificationId}' +activate DB +DB --> Repository: 업데이트 완료 +deactivate DB + +Repository --> Service: 완료 +deactivate Repository + +Service --> Controller: NotificationResponse\n(notificationId, status, sentCount, failedCount) +deactivate Service + +Controller --> EventHub: InvitationSent 이벤트 발행\n(meetingId, notificationId, status) +deactivate Controller + +note over Controller, EmailService +처리 시간: +- 알림 기록 생성: ~100ms +- 템플릿 생성: ~200ms +- 이메일 발송 (per recipient): ~500ms +- 총 처리 시간: 참석자 수에 비례 (병렬 처리) + +재시도 정책: +- 최대 3회 재시도 +- 재시도 간격: 5분, 15분, 30분 +end note + +@enduml diff --git a/design/backend/sequence/inner/stt-음성녹음시작.puml b/design/backend/sequence/inner/stt-음성녹음시작.puml new file mode 100644 index 0000000..6f54fee --- /dev/null +++ b/design/backend/sequence/inner/stt-음성녹음시작.puml @@ -0,0 +1,87 @@ +@startuml +!theme mono + +title STT Service - 음성녹음시작 내부 시퀀스 + +participant "RecordingController" as Controller +participant "RecordingService" as Service +participant "RecordingRepository" as Repository +participant "AzureSpeechClient" as AzureClient +database "STT DB" as DB +database "Azure Blob Storage<>" as BlobStorage +queue "Azure Event Hubs<>" as EventHub + +== MeetingStarted 이벤트 수신 == + +EventHub -> Controller: MeetingStarted 이벤트 수신\n(meetingId, sessionId) +activate Controller + +Controller -> Service: prepareRecording(meetingId, sessionId) +activate Service + +Service -> Service: 녹음 세션 검증 +note right + - 중복 녹음 방지 체크 + - meetingId 유효성 검증 +end note + +Service -> Repository: createRecording(meetingId, sessionId) +activate Repository + +Repository -> DB: INSERT INTO recordings\n(recording_id, meeting_id, session_id,\nstatus='READY', created_at) +activate DB +DB --> Repository: recordingId 반환 +deactivate DB + +Repository --> Service: RecordingEntity 반환 +deactivate Repository + +== Azure Speech Service 초기화 == + +Service -> AzureClient: initializeRecognizer(recordingId, sessionId) +activate AzureClient + +AzureClient -> AzureClient: 음성 인식기 설정 +note right + - 언어: ko-KR + - 샘플레이트: 16kHz + - 화자 식별 활성화 + - 실시간 스트리밍 모드 +end note + +AzureClient -> BlobStorage: 녹음 파일 저장 경로 생성\n(path: recordings/{meetingId}/{sessionId}.wav) +activate BlobStorage +BlobStorage --> AzureClient: 저장 경로 URL 반환 +deactivate BlobStorage + +AzureClient --> Service: RecognizerConfig 반환 +deactivate AzureClient + +== 녹음 상태 업데이트 == + +Service -> Repository: updateRecordingStatus(recordingId, "RECORDING") +activate Repository + +Repository -> DB: UPDATE recordings\nSET status='RECORDING',\nstarted_at=NOW(),\nstorage_path='{blobUrl}'\nWHERE recording_id='{recordingId}' +activate DB +DB --> Repository: 업데이트 완료 +deactivate DB + +Repository --> Service: 업데이트 완료 +deactivate Repository + +Service --> Controller: RecordingResponse(recordingId, status, storagePath) +deactivate Service + +Controller --> EventHub: RecordingStarted 이벤트 발행\n(recordingId, meetingId, status) +deactivate Controller + +note over Controller, EventHub +처리 시간: +- DB 녹음 생성: ~100ms +- Azure 인식기 초기화: ~500ms +- Blob 경로 생성: ~200ms +- 총 처리 시간: ~800ms +end note + +@enduml diff --git a/design/backend/sequence/inner/stt-음성텍스트변환.puml b/design/backend/sequence/inner/stt-음성텍스트변환.puml new file mode 100644 index 0000000..b8e817f --- /dev/null +++ b/design/backend/sequence/inner/stt-음성텍스트변환.puml @@ -0,0 +1,115 @@ +@startuml +!theme mono + +title STT Service - 음성텍스트변환 내부 시퀀스 + +participant "Frontend<>" as Frontend +participant "TranscriptController" as Controller +participant "TranscriptService" as Service +participant "RecordingRepository" as RecordingRepo +participant "TranscriptRepository" as TranscriptRepo +participant "AzureSpeechClient" as AzureClient +database "STT DB" as DB +database "Azure Blob Storage<>" as BlobStorage +queue "Azure Event Hubs<>" as EventHub + +== 음성 데이터 스트리밍 수신 (5초 간격 배치) == + +Frontend -> Controller: POST /api/transcripts/stream\n(audioData, recordingId, timestamp) +activate Controller + +Controller -> Service: processAudioStream(audioData, recordingId) +activate Service + +== 음성 인식 처리 == + +Service -> AzureClient: recognizeAudio(audioData) +activate AzureClient + +AzureClient -> AzureClient: 음성 인식 수행 +note right + - 실시간 STT 처리 + - 화자 식별 (Speaker Diarization) + - 타임스탬프 자동 기록 + - 신뢰도 점수 계산 +end note + +AzureClient -> BlobStorage: 음성 파일 저장\n(chunk 단위 저장) +activate BlobStorage +BlobStorage --> AzureClient: 저장 완료 +deactivate BlobStorage + +AzureClient --> Service: RecognitionResult\n(text, speakerId, confidence, timestamp) +deactivate AzureClient + +== 정확도 검증 및 처리 == + +Service -> Service: 정확도 점수 검증 +note right + confidence >= 60%: 정상 처리 + confidence < 60%: 경고 플래그 설정 +end note + +== 변환 결과 저장 == + +Service -> TranscriptRepo: createTranscript(recordingId, text, metadata) +activate TranscriptRepo + +TranscriptRepo -> DB: INSERT INTO transcripts\n(transcript_id, recording_id, speaker_id,\ntext, confidence, timestamp, warning_flag,\ncreated_at) +activate DB +DB --> TranscriptRepo: transcriptId 반환 +deactivate DB + +TranscriptRepo --> Service: TranscriptEntity 반환 +deactivate TranscriptRepo + +== 화자 정보 업데이트 == + +Service -> RecordingRepo: updateSpeakerInfo(recordingId, speakerId) +activate RecordingRepo + +RecordingRepo -> DB: INSERT INTO speakers\n(recording_id, speaker_id, segment_count)\nON CONFLICT UPDATE segment_count +activate DB +DB --> RecordingRepo: 업데이트 완료 +deactivate DB + +RecordingRepo --> Service: 완료 +deactivate RecordingRepo + +== 이벤트 발행 == + +Service -> EventHub: TranscriptReady 이벤트 발행 +activate EventHub +note right of EventHub + 이벤트 데이터: + - transcriptId + - recordingId + - meetingId + - text + - speakerId + - timestamp + - confidence +end note +EventHub --> Service: 발행 완료 +deactivate EventHub + +Service --> Controller: TranscriptResponse\n(transcriptId, text, confidence, warningFlag) +deactivate Service + +Controller --> Frontend: 200 OK\n(transcriptId, text, speakerId, timestamp, confidence) +deactivate Controller + +note over Frontend, EventHub +처리 시간: +- Azure STT 처리: 1-3초 +- DB 저장: ~100ms +- Event 발행: ~50ms +- 총 처리 시간: 1-4초 + +정확도 경고: +- 60% 미만: 수동 수정 권장 +- 60-80%: 검토 권장 +- 80% 이상: 정상 +end note + +@enduml diff --git a/tools/check-plantuml.ps1 b/tools/check-plantuml.ps1 new file mode 100644 index 0000000..9aca9c9 --- /dev/null +++ b/tools/check-plantuml.ps1 @@ -0,0 +1,66 @@ +param( + [Parameter(Mandatory=$false)] + [string]$FilePath = "C:\home\workspace\tripgen\design\backend\system\azure-physical-architecture.txt" +) + +Write-Host "=== PlantUML Syntax Checker ===" -ForegroundColor Cyan +Write-Host "Target file: $FilePath" -ForegroundColor Yellow + +# Check if file exists +if (-not (Test-Path $FilePath)) { + Write-Host "❌ File not found: $FilePath" -ForegroundColor Red + exit 1 +} + +# Execute directly in PowerShell +$timestamp = Get-Date -Format 'yyyyMMddHHmmss' +$tempFile = "/tmp/puml_$timestamp.puml" + +# Copy file +Write-Host "`n1. Copying file..." -ForegroundColor Gray +Write-Host " Temporary file: $tempFile" +docker cp $FilePath "plantuml:$tempFile" + +if ($LASTEXITCODE -ne 0) { + Write-Host "❌ File copy failed" -ForegroundColor Red + exit 1 +} +Write-Host " ✅ Copy completed" -ForegroundColor Green + +# Find JAR file path +Write-Host "`n2. Looking for PlantUML JAR file..." -ForegroundColor Gray +$JAR_PATH = docker exec plantuml sh -c "find / -name 'plantuml*.jar' 2>/dev/null | head -1" +Write-Host " JAR path: $JAR_PATH" +Write-Host " ✅ JAR file confirmed" -ForegroundColor Green + +# Syntax check +Write-Host "`n3. Running syntax check..." -ForegroundColor Gray +$syntaxOutput = docker exec plantuml sh -c "java -jar $JAR_PATH -checkonly $tempFile 2>&1" + +if ($LASTEXITCODE -eq 0) { + Write-Host "`n✅ Syntax check passed!" -ForegroundColor Green + Write-Host " No syntax errors found in the diagram." -ForegroundColor Green +} else { + Write-Host "`n❌ Syntax errors detected!" -ForegroundColor Red + Write-Host "Error details:" -ForegroundColor Red + Write-Host $syntaxOutput -ForegroundColor Yellow + + # Detailed error check + Write-Host "`nAnalyzing detailed errors..." -ForegroundColor Yellow + $detailError = docker exec plantuml sh -c "java -jar $JAR_PATH -failfast -v $tempFile 2>&1" + $errorLines = $detailError | Select-String "Error line" + + if ($errorLines) { + Write-Host "`n📍 Error locations:" -ForegroundColor Magenta + $errorLines | ForEach-Object { + Write-Host " $($_.Line)" -ForegroundColor Red + } + } +} + +# Clean up temporary file +Write-Host "`n4. Cleaning up temporary files..." -ForegroundColor Gray +docker exec plantuml sh -c "rm -f $tempFile" 2>$null +Write-Host " ✅ Cleanup completed" -ForegroundColor Green + +Write-Host "`n=== Check completed ===" -ForegroundColor Cyan \ No newline at end of file