mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 10:16:24 +00:00
내부 시퀀스 설계 완료
- 총 21개 PlantUML 파일 생성 (Meeting 10개, AI 6개, STT 2개, Notification 3개) - 서브 에이전트를 활용한 병렬 설계로 효율성 극대화 - 모든 시나리오는 유저스토리 및 외부 시퀀스와 1:1 매칭 - Controller → Service → Repository 계층 구조 명확히 표현 - Redis Cache, Azure Event Hubs 등 인프라 컴포넌트 표시 - 동기(→)/비동기(-->) 구분 명확 - 외부 참여자 <<E>> 표시 적용 - PlantUML 문법 검사 및 오류 수정 완료 (13개 파일 수정) - par/and 블록 문법 오류 수정 - return 형식 적용으로 참여자 없는 화살표 오류 해결 설계 특징: - 캐시 전략: Cache-Aside 패턴, TTL 관리, 즉시 무효화 - 비동기 처리: Azure Event Hubs 기반 이벤트 구독 - 실시간 협업: WebSocket 기반 동기화, 변경 델타 전송 - 데이터 일관성: 버전 관리, 양방향 연결, 트랜잭션 처리 추가 파일: - claude/sequence-inner-design.md: 내부시퀀스설계 가이드 - tools/check-plantuml.ps1: PlantUML 문법 검사 스크립트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6d84357215
commit
909025aa27
@ -36,7 +36,9 @@
|
|||||||
"Bash(mv:*)",
|
"Bash(mv:*)",
|
||||||
"Bash(cp:*)",
|
"Bash(cp:*)",
|
||||||
"mcp__sequential-thinking__sequentialthinking",
|
"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": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
76
claude/sequence-inner-design.md
Normal file
76
claude/sequence-inner-design.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# 내부시퀀스설계 가이드
|
||||||
|
|
||||||
|
[요청사항]
|
||||||
|
- <작성원칙>을 준용하여 설계
|
||||||
|
- <작성순서>에 따라 설계
|
||||||
|
- [결과파일] 안내에 따라 파일 작성
|
||||||
|
|
||||||
|
[가이드]
|
||||||
|
<작성원칙>
|
||||||
|
- **유저스토리와 매칭**되어야 함. **불필요한 추가 설계 금지**
|
||||||
|
- **외부시퀀스설계서에서 설계한 플로우와 일치**해야 함
|
||||||
|
- UI/UX설계서의 '사용자 플로우'참조하여 설계
|
||||||
|
- 마이크로서비스 내부의 처리 흐름을 표시
|
||||||
|
- **각 서비스-시나리오별로 분리하여 각각 작성**
|
||||||
|
- 각 서비스별 주요 시나리오마다 독립적인 시퀀스 설계 수행
|
||||||
|
- 프론트엔드와 백엔드 책임 분리: 프론트엔드에서 할 수 있는 것은 백엔드로 요청 안하게 함
|
||||||
|
- 표현 요소
|
||||||
|
- **API 레이어**: 해당 시나리오의 모든 관련 엔드포인트
|
||||||
|
- **비즈니스 레이어**: Controller → Service → Domain 내부 플로우
|
||||||
|
- **데이터 레이어**: Repository, Cache, External API 접근
|
||||||
|
- **인프라 레이어**: 메시지 큐, 이벤트, 로깅 등
|
||||||
|
- 다이어그램 구성
|
||||||
|
- **참여자(Actor)**: Controller, Service, Repository, Cache, External API
|
||||||
|
- **생명선(Lifeline)**: 각 참여자의 활동 구간
|
||||||
|
- **메시지(Message)**: 동기(→)/비동기(-->) 호출 구분
|
||||||
|
- **활성화 박스**: 처리 중인 시간 구간 표시
|
||||||
|
- **노트**: 중요한 비즈니스 로직이나 기술적 고려사항 설명
|
||||||
|
- 참여자가 서비스 내부가 아닌 다른 마이크로 서비스, 외부시스템, 인프라 컴포넌트면 참여자 이름 끝에 '<<E>>'를 붙임
|
||||||
|
예) database "Redis Cache<<E>>" 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
|
||||||
|
- 서비스명은 영어로 시나리오명은 한글로 작성
|
||||||
|
|
||||||
277
design/backend/sequence/inner/README.md
Normal file
277
design/backend/sequence/inner/README.md
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
# Meeting Service 내부 시퀀스 설계
|
||||||
|
|
||||||
|
Meeting Service의 내부 처리 흐름을 표현한 시퀀스 설계서 모음입니다.
|
||||||
|
|
||||||
|
## 📋 설계 개요
|
||||||
|
|
||||||
|
### 목적
|
||||||
|
- Meeting Service 내부의 Controller → Service → Repository 계층 구조 표현
|
||||||
|
- 각 시나리오별 비즈니스 로직 및 데이터 처리 흐름 상세화
|
||||||
|
- 캐시, 데이터베이스, 메시징 인프라와의 상호작용 명시
|
||||||
|
- 동기/비동기 처리 구분 명확화
|
||||||
|
|
||||||
|
### 설계 원칙
|
||||||
|
- **유저스토리 기반**: 모든 시나리오는 유저스토리와 1:1 매칭
|
||||||
|
- **외부 시퀀스 일치**: 외부 시퀀스 설계와 일관성 유지
|
||||||
|
- **계층 구조 명확화**: Controller, Service, Repository 역할 분리
|
||||||
|
- **인프라 표시**: DB, Cache, Event Hub 등 외부 참여자는 <<E>> 표시
|
||||||
|
- **비즈니스 로직 설명**: 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` 사용
|
||||||
|
- ✅ 외부 참여자 `<<E>>` 표시
|
||||||
|
- ✅ 동기 호출 `→`, 비동기 호출 `-->`
|
||||||
|
- ✅ alt/loop/note 적절히 사용
|
||||||
|
|
||||||
|
### 유저스토리 일치
|
||||||
|
- ✅ 모든 시나리오가 유저스토리와 매칭
|
||||||
|
- ✅ 외부 시퀀스 설계와 일치
|
||||||
|
- ✅ UI/UX 설계의 사용자 플로우 반영
|
||||||
|
|
||||||
|
### 아키텍처 원칙
|
||||||
|
- ✅ 계층 구조 명확 (Controller → Service → Repository)
|
||||||
|
- ✅ 캐시 우선 전략 적용
|
||||||
|
- ✅ 비동기 처리 명확히 구분
|
||||||
|
- ✅ 비즈니스 로직 상세 설명
|
||||||
|
|
||||||
|
## 📝 다음 단계
|
||||||
|
|
||||||
|
1. **PlantUML 검증**: Docker 컨테이너 설정 후 문법 검사 실행
|
||||||
|
2. **클래스 설계**: 내부 시퀀스 기반 클래스 다이어그램 작성
|
||||||
|
3. **데이터 설계**: Repository 계층 기반 테이블 스키마 설계
|
||||||
|
4. **API 명세**: Controller 기반 OpenAPI 명세 작성
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**작성일**: 2025-10-22
|
||||||
|
**작성자**: 길동 (아키텍트)
|
||||||
|
**버전**: v1.0
|
||||||
221
design/backend/sequence/inner/ai-Todo자동추출.puml
Normal file
221
design/backend/sequence/inner/ai-Todo자동추출.puml
Normal file
@ -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<<E>>" as MeetingClient
|
||||||
|
database "Azure OpenAI<<E>>" as OpenAI
|
||||||
|
database "PostgreSQL<<E>>" 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
|
||||||
244
design/backend/sequence/inner/ai-관련회의록연결.puml
Normal file
244
design/backend/sequence/inner/ai-관련회의록연결.puml
Normal file
@ -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<<E>>" as OpenAI
|
||||||
|
database "Vector DB<<E>>" as VectorDB
|
||||||
|
database "PostgreSQL<<E>>" 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
|
||||||
303
design/backend/sequence/inner/ai-맥락기반용어설명.puml
Normal file
303
design/backend/sequence/inner/ai-맥락기반용어설명.puml
Normal file
@ -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<<E>>" as OpenAI
|
||||||
|
database "Vector DB<<E>>" as VectorDB
|
||||||
|
database "Document Store<<E>>" as DocStore
|
||||||
|
database "PostgreSQL<<E>>" 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
|
||||||
242
design/backend/sequence/inner/ai-전문용어감지.puml
Normal file
242
design/backend/sequence/inner/ai-전문용어감지.puml
Normal file
@ -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<<E>>" as OpenAI
|
||||||
|
database "PostgreSQL<<E>>" 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
|
||||||
224
design/backend/sequence/inner/ai-회의록개선.puml
Normal file
224
design/backend/sequence/inner/ai-회의록개선.puml
Normal file
@ -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<<E>>" as OpenAI
|
||||||
|
database "PostgreSQL<<E>>" 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
|
||||||
185
design/backend/sequence/inner/ai-회의록자동작성.puml
Normal file
185
design/backend/sequence/inner/ai-회의록자동작성.puml
Normal file
@ -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<<E>>" as OpenAI
|
||||||
|
database "Vector DB<<E>>" as VectorDB
|
||||||
|
database "PostgreSQL<<E>>" 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
|
||||||
179
design/backend/sequence/inner/meeting-Todo완료처리.puml
Normal file
179
design/backend/sequence/inner/meeting-Todo완료처리.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
queue "Azure Event Hubs<<E>>" as EventHub
|
||||||
|
participant "WebSocket<<E>>" 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
|
||||||
158
design/backend/sequence/inner/meeting-Todo할당.puml
Normal file
158
design/backend/sequence/inner/meeting-Todo할당.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
queue "Azure Event Hubs<<E>>" 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
|
||||||
140
design/backend/sequence/inner/meeting-대시보드조회.puml
Normal file
140
design/backend/sequence/inner/meeting-대시보드조회.puml
Normal file
@ -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<<E>>" as Cache
|
||||||
|
database "Meeting DB<<E>>" 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<Meeting>
|
||||||
|
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<Todo>
|
||||||
|
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<Minutes>
|
||||||
|
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<Minutes>
|
||||||
|
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
|
||||||
153
design/backend/sequence/inner/meeting-회의록공유.puml
Normal file
153
design/backend/sequence/inner/meeting-회의록공유.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
queue "Azure Event Hubs<<E>>" 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
|
||||||
182
design/backend/sequence/inner/meeting-회의록상세조회.puml
Normal file
182
design/backend/sequence/inner/meeting-회의록상세조회.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" 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<Participant>
|
||||||
|
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<Section>
|
||||||
|
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<AISummary>
|
||||||
|
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<RelatedMinutes>
|
||||||
|
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
|
||||||
191
design/backend/sequence/inner/meeting-회의록수정.puml
Normal file
191
design/backend/sequence/inner/meeting-회의록수정.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
participant "WebSocket<<E>>" 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<Section>
|
||||||
|
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
|
||||||
148
design/backend/sequence/inner/meeting-회의록확정.puml
Normal file
148
design/backend/sequence/inner/meeting-회의록확정.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
queue "Azure Event Hubs<<E>>" 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
|
||||||
148
design/backend/sequence/inner/meeting-회의시작.puml
Normal file
148
design/backend/sequence/inner/meeting-회의시작.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
queue "Azure Event Hubs<<E>>" 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
|
||||||
123
design/backend/sequence/inner/meeting-회의예약.puml
Normal file
123
design/backend/sequence/inner/meeting-회의예약.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
queue "Azure Event Hubs<<E>>" 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<Participant>
|
||||||
|
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
|
||||||
162
design/backend/sequence/inner/meeting-회의종료.puml
Normal file
162
design/backend/sequence/inner/meeting-회의종료.puml
Normal file
@ -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<<E>>" as DB
|
||||||
|
database "Redis Cache<<E>>" as Cache
|
||||||
|
queue "Azure Event Hubs<<E>>" 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
|
||||||
147
design/backend/sequence/inner/notification-Todo알림발송.puml
Normal file
147
design/backend/sequence/inner/notification-Todo알림발송.puml
Normal file
@ -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<<E>>" as EventHub
|
||||||
|
participant "Email Service<<E>>" 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
|
||||||
158
design/backend/sequence/inner/notification-리마인더발송.puml
Normal file
158
design/backend/sequence/inner/notification-리마인더발송.puml
Normal file
@ -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<<E>>" 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
|
||||||
145
design/backend/sequence/inner/notification-초대알림발송.puml
Normal file
145
design/backend/sequence/inner/notification-초대알림발송.puml
Normal file
@ -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<<E>>" as EventHub
|
||||||
|
participant "Email Service<<E>>" 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
|
||||||
87
design/backend/sequence/inner/stt-음성녹음시작.puml
Normal file
87
design/backend/sequence/inner/stt-음성녹음시작.puml
Normal file
@ -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<<E>>" as BlobStorage
|
||||||
|
queue "Azure Event Hubs<<E>>" 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
|
||||||
115
design/backend/sequence/inner/stt-음성텍스트변환.puml
Normal file
115
design/backend/sequence/inner/stt-음성텍스트변환.puml
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
@startuml
|
||||||
|
!theme mono
|
||||||
|
|
||||||
|
title STT Service - 음성텍스트변환 내부 시퀀스
|
||||||
|
|
||||||
|
participant "Frontend<<E>>" 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<<E>>" as BlobStorage
|
||||||
|
queue "Azure Event Hubs<<E>>" 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
|
||||||
66
tools/check-plantuml.ps1
Normal file
66
tools/check-plantuml.ps1
Normal file
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user