mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 10:16:24 +00:00
주요 변경사항:
- 회의 종료 API 구현 (POST /api/meetings/{meetingId}/end)
- AI 회의록 통합 요약 기능 구현
- Claude API 연동 및 프롬프트 최적화
- 안건별 요약, 키워드 추출, 결정사항 자동 정리
AI Service (Python):
- Claude 모델 설정: claude-sonnet-4-5-20250929
- 회의록 통합 프롬프트 개선
- AgendaSummary 모델 summary 필드 매핑 수정
- decisions 필드 추가 및 응답 구조 정리
- 입력 데이터 로깅 추가
Meeting Service (Java):
- EndMeetingService AI 통합 로직 구현
- MeetingAnalysis 엔티티 decisions 필드 추가
- AgendaSection opinions 필드 제거
- AI Service 포트 8086으로 설정
- DB 마이그레이션 스크립트 추가 (V7)
테스트 결과:
✅ 회의 종료 API 정상 동작
✅ AI 응답 검증 (keywords, summary, decisions)
✅ 안건별 요약 및 보류사항 추출
✅ 처리 시간: ~11초, 토큰: ~2,600
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
123 lines
4.1 KiB
Python
123 lines
4.1 KiB
Python
"""Transcript Service - 회의록 통합 처리"""
|
|
import logging
|
|
from datetime import datetime
|
|
from app.models.transcript import (
|
|
ConsolidateRequest,
|
|
ConsolidateResponse,
|
|
AgendaSummary,
|
|
ExtractedTodo
|
|
)
|
|
from app.services.claude_service import claude_service
|
|
from app.prompts.consolidate_prompt import get_consolidate_prompt
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TranscriptService:
|
|
"""회의록 통합 서비스"""
|
|
|
|
async def consolidate_minutes(
|
|
self,
|
|
request: ConsolidateRequest
|
|
) -> ConsolidateResponse:
|
|
"""
|
|
참석자별 회의록을 통합하여 AI 요약 생성
|
|
"""
|
|
logger.info(f"회의록 통합 시작 - Meeting ID: {request.meeting_id}")
|
|
logger.info(f"참석자 수: {len(request.participant_minutes)}")
|
|
|
|
try:
|
|
# 1. 프롬프트 생성
|
|
participant_data = [
|
|
{
|
|
"user_name": pm.user_name,
|
|
"content": pm.content
|
|
}
|
|
for pm in request.participant_minutes
|
|
]
|
|
|
|
prompt = get_consolidate_prompt(
|
|
participant_minutes=participant_data,
|
|
agendas=request.agendas
|
|
)
|
|
|
|
# 입력 데이터 로깅
|
|
logger.info("=" * 80)
|
|
logger.info("INPUT - 참석자별 회의록:")
|
|
for pm in participant_data:
|
|
logger.info(f"\n[{pm['user_name']}]")
|
|
logger.info(f"{pm['content'][:500]}..." if len(pm['content']) > 500 else pm['content'])
|
|
logger.info("=" * 80)
|
|
|
|
# 2. Claude API 호출
|
|
start_time = datetime.utcnow()
|
|
ai_result = await claude_service.generate_completion(prompt)
|
|
processing_time = (datetime.utcnow() - start_time).total_seconds() * 1000
|
|
|
|
logger.info(f"AI 처리 완료 - {processing_time:.0f}ms")
|
|
|
|
# 3. 응답 구성
|
|
response = self._build_response(
|
|
meeting_id=request.meeting_id,
|
|
ai_result=ai_result,
|
|
participants_count=len(request.participant_minutes),
|
|
duration_minutes=request.duration_minutes
|
|
)
|
|
|
|
logger.info(f"통합 요약 완료 - 안건 수: {len(response.agenda_summaries)}, Todo 수: {response.statistics['todos_count']}")
|
|
|
|
return response
|
|
|
|
except Exception as e:
|
|
logger.error(f"회의록 통합 실패: {e}", exc_info=True)
|
|
raise
|
|
|
|
def _build_response(
|
|
self,
|
|
meeting_id: str,
|
|
ai_result: dict,
|
|
participants_count: int,
|
|
duration_minutes: int = None
|
|
) -> ConsolidateResponse:
|
|
"""AI 응답을 ConsolidateResponse로 변환"""
|
|
|
|
# 안건별 요약 변환
|
|
agenda_summaries = []
|
|
for agenda_data in ai_result.get("agenda_summaries", []):
|
|
# Todo 변환 (제목만)
|
|
todos = [
|
|
ExtractedTodo(title=todo.get("title", ""))
|
|
for todo in agenda_data.get("todos", [])
|
|
]
|
|
|
|
agenda_summaries.append(
|
|
AgendaSummary(
|
|
agenda_number=agenda_data.get("agenda_number", 0),
|
|
agenda_title=agenda_data.get("agenda_title", ""),
|
|
summary_short=agenda_data.get("summary_short", ""),
|
|
summary=agenda_data.get("summary", ""),
|
|
pending=agenda_data.get("pending", []),
|
|
todos=todos
|
|
)
|
|
)
|
|
|
|
# 통계 정보
|
|
statistics = ai_result.get("statistics", {})
|
|
statistics["participants_count"] = participants_count
|
|
if duration_minutes:
|
|
statistics["duration_minutes"] = duration_minutes
|
|
|
|
# 응답 생성
|
|
return ConsolidateResponse(
|
|
meeting_id=meeting_id,
|
|
keywords=ai_result.get("keywords", []),
|
|
statistics=statistics,
|
|
decisions=ai_result.get("decisions", ""),
|
|
agenda_summaries=agenda_summaries,
|
|
generated_at=datetime.utcnow()
|
|
)
|
|
|
|
|
|
# 싱글톤 인스턴스
|
|
transcript_service = TranscriptService()
|