회의 종료 시 AI 응답 처리 개선

- MeetingEndDTO.TodoSummaryDTO에 assignee 필드 추가
- AI 응답의 todos를 직접 DTO로 변환하여 반환
- 안건별 todos 매핑 로직 개선

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Minseo-Jo 2025-10-31 11:35:25 +09:00
parent 5515909206
commit c4bd8064ec
2 changed files with 27 additions and 21 deletions

View File

@ -41,5 +41,6 @@ public class MeetingEndDTO {
@Builder @Builder
public static class TodoSummaryDTO { public static class TodoSummaryDTO {
private final String title; private final String title;
private final String assignee;
} }
} }

View File

@ -124,8 +124,8 @@ public class EndMeetingService implements EndMeetingUseCase {
meetingRepository.save(meeting); meetingRepository.save(meeting);
log.info("회의 상태 업데이트 완료 - status: {}", meeting.getStatus()); log.info("회의 상태 업데이트 완료 - status: {}", meeting.getStatus());
// 9. 응답 DTO 생성 // 9. 응답 DTO 생성 (AI 응답의 todos를 그대로 사용)
MeetingEndDTO result = createMeetingEndDTO(meeting, analysis, todos, participantMinutesList.size()); MeetingEndDTO result = createMeetingEndDTO(meeting, aiResponse, todos.size(), participantMinutesList.size());
log.info("회의 종료 처리 완료 - meetingId: {}, 안건 수: {}, Todo 수: {}", log.info("회의 종료 처리 완료 - meetingId: {}, 안건 수: {}, Todo 수: {}",
meetingId, analysis.getAgendaAnalyses().size(), todos.size()); meetingId, analysis.getAgendaAnalyses().size(), todos.size());
@ -308,7 +308,8 @@ public class EndMeetingService implements EndMeetingUseCase {
private List<TodoEntity> createAndSaveTodos(MeetingEntity meeting, ConsolidateResponse aiResponse, MeetingAnalysis analysis) { private List<TodoEntity> createAndSaveTodos(MeetingEntity meeting, ConsolidateResponse aiResponse, MeetingAnalysis analysis) {
List<TodoEntity> todos = aiResponse.getAgendaSummaries().stream() List<TodoEntity> todos = aiResponse.getAgendaSummaries().stream()
.<TodoEntity>flatMap(agenda -> { .<TodoEntity>flatMap(agenda -> {
// agendaId는 향후 Todo와 안건 매핑에 사용될 있음 (현재는 사용하지 않음) // 안건 번호를 description에 저장하여 나중에 필터링에 사용
Integer agendaNumber = agenda.getAgendaNumber();
List<ExtractedTodoDTO> todoList = agenda.getTodos() != null ? agenda.getTodos() : List.of(); List<ExtractedTodoDTO> todoList = agenda.getTodos() != null ? agenda.getTodos() : List.of();
return todoList.stream() return todoList.stream()
.<TodoEntity>map(todo -> TodoEntity.builder() .<TodoEntity>map(todo -> TodoEntity.builder()
@ -316,6 +317,7 @@ public class EndMeetingService implements EndMeetingUseCase {
.meetingId(meeting.getMeetingId()) .meetingId(meeting.getMeetingId())
.minutesId(meeting.getMeetingId()) // 실제로는 minutesId 필요 .minutesId(meeting.getMeetingId()) // 실제로는 minutesId 필요
.title(todo.getTitle()) .title(todo.getTitle())
.description("안건" + agendaNumber) // 안건 번호를 description에 임시 저장
.assigneeId(todo.getAssignee() != null ? todo.getAssignee() : "") // AI가 추출한 담당자 .assigneeId(todo.getAssignee() != null ? todo.getAssignee() : "") // AI가 추출한 담당자
.status("PENDING") .status("PENDING")
.build()); .build());
@ -342,33 +344,36 @@ public class EndMeetingService implements EndMeetingUseCase {
} }
/** /**
* 회의 종료 결과 DTO 생성 * 회의 종료 결과 DTO 생성 (AI 응답 직접 사용)
*/ */
private MeetingEndDTO createMeetingEndDTO(MeetingEntity meeting, MeetingAnalysis analysis, private MeetingEndDTO createMeetingEndDTO(MeetingEntity meeting, ConsolidateResponse aiResponse,
List<TodoEntity> todos, int participantCount) { int todoCount, int participantCount) {
// 회의 소요 시간 계산 // 회의 소요 시간 계산
int durationMinutes = calculateDurationMinutes(meeting.getStartedAt(), meeting.getEndedAt()); int durationMinutes = calculateDurationMinutes(meeting.getStartedAt(), meeting.getEndedAt());
// 안건별 요약 DTO 생성 // AI 응답의 안건 정보를 그대로 DTO로 변환 (todos 포함)
List<MeetingEndDTO.AgendaSummaryDTO> agendaSummaries = analysis.getAgendaAnalyses().stream() List<MeetingEndDTO.AgendaSummaryDTO> agendaSummaries = aiResponse.getAgendaSummaries().stream()
.<MeetingEndDTO.AgendaSummaryDTO>map(agenda -> { .<MeetingEndDTO.AgendaSummaryDTO>map(agenda -> {
// 해당 안건의 Todo 필터링 (agendaId가 없을 있음) // 안건별 todos 변환
List<MeetingEndDTO.TodoSummaryDTO> agendaTodos = todos.stream() List<MeetingEndDTO.TodoSummaryDTO> todoList = new ArrayList<>();
.filter(todo -> agenda.getAgendaId().equals(todo.getMinutesId())) // 임시 매핑 if (agenda.getTodos() != null) {
.<MeetingEndDTO.TodoSummaryDTO>map(todo -> MeetingEndDTO.TodoSummaryDTO.builder() for (ExtractedTodoDTO todo : agenda.getTodos()) {
todoList.add(MeetingEndDTO.TodoSummaryDTO.builder()
.title(todo.getTitle()) .title(todo.getTitle())
.build()) .assignee(todo.getAssignee())
.collect(Collectors.toList()); .build());
}
}
return MeetingEndDTO.AgendaSummaryDTO.builder() return MeetingEndDTO.AgendaSummaryDTO.builder()
.title(agenda.getTitle()) .title(agenda.getAgendaTitle())
.aiSummaryShort(agenda.getAiSummaryShort()) .aiSummaryShort(agenda.getSummaryShort())
.details(MeetingEndDTO.AgendaDetailsDTO.builder() .details(MeetingEndDTO.AgendaDetailsDTO.builder()
.discussion(agenda.getDiscussion()) .discussion(agenda.getSummary())
.decisions(agenda.getDecisions()) .decisions(agenda.getDecisions())
.pending(agenda.getPending()) .pending(agenda.getPending())
.build()) .build())
.todos(agendaTodos) .todos(todoList)
.build(); .build();
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -377,9 +382,9 @@ public class EndMeetingService implements EndMeetingUseCase {
.title(meeting.getTitle()) .title(meeting.getTitle())
.participantCount(participantCount) .participantCount(participantCount)
.durationMinutes(durationMinutes) .durationMinutes(durationMinutes)
.agendaCount(analysis.getAgendaAnalyses().size()) .agendaCount(aiResponse.getAgendaSummaries().size())
.todoCount(todos.size()) .todoCount(todoCount)
.keywords(analysis.getKeywords()) .keywords(aiResponse.getKeywords())
.agendaSummaries(agendaSummaries) .agendaSummaries(agendaSummaries)
.build(); .build();
} }