diff --git a/src/main/java/com/whylog/server/domain/decision/controller/DecisionController.java b/src/main/java/com/whylog/server/domain/decision/controller/DecisionController.java index 2815333..088ff51 100644 --- a/src/main/java/com/whylog/server/domain/decision/controller/DecisionController.java +++ b/src/main/java/com/whylog/server/domain/decision/controller/DecisionController.java @@ -18,13 +18,6 @@ @Tag(name = "Decision", description = "결정사항 관련 API") public class DecisionController { - @GetMapping("/{decisionId}/applications") - @Operation(summary = "적용사항 목록 조회 API", description = "특정 결정사항의 적용사항 목록을 조회하는 API입니다.") - public ApiResponse> getApplications( - @PathVariable Long decisionId) { - return ApiResponse.onSuccess(null); - } - @GetMapping("/{decisionId}/reliability") @Operation(summary = "신뢰도 조회 API", description = "특정 결정사항의 신뢰도 정보를 조회하는 API입니다.") public ApiResponse getReliability( diff --git a/src/main/java/com/whylog/server/domain/decision/dto/ApplicationResponse.java b/src/main/java/com/whylog/server/domain/decision/dto/ApplicationResponse.java index 30e0d27..07b87f5 100644 --- a/src/main/java/com/whylog/server/domain/decision/dto/ApplicationResponse.java +++ b/src/main/java/com/whylog/server/domain/decision/dto/ApplicationResponse.java @@ -19,9 +19,6 @@ public class ApplicationResponse { @Schema(description = "적용사항 항목") public static class ApplicationDTO { - @Schema(description = "결정사항 ID", example = "1") - private Long decisionId; - @Schema(description = "적용사항 ID", example = "1") private Long applicationId; diff --git a/src/main/java/com/whylog/server/domain/decision/dto/DecisionResponse.java b/src/main/java/com/whylog/server/domain/decision/dto/DecisionResponse.java index bf2f046..386b671 100644 --- a/src/main/java/com/whylog/server/domain/decision/dto/DecisionResponse.java +++ b/src/main/java/com/whylog/server/domain/decision/dto/DecisionResponse.java @@ -1,6 +1,7 @@ package com.whylog.server.domain.decision.dto; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -21,8 +22,8 @@ public static class DecisionListDTO { @Schema(description = "회의 명", example = "백엔드 비상대책회의") private String name; - @Schema(description = "적용사항 개수", example = "3") - private Integer applicationCount; + @Schema(description = "적용사항 목록") + private List applications; } @Getter diff --git a/src/main/java/com/whylog/server/domain/decision/entity/Application.java b/src/main/java/com/whylog/server/domain/decision/entity/Application.java index 6e3ac0d..dde8694 100644 --- a/src/main/java/com/whylog/server/domain/decision/entity/Application.java +++ b/src/main/java/com/whylog/server/domain/decision/entity/Application.java @@ -33,6 +33,9 @@ public class Application extends BaseEntity { @JoinColumn(name = "decision_id", nullable = false) private Decision decision; + @Column(name = "name") + private String name; + // @OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true) // private final List applicationTimelines = new ArrayList<>(); // @@ -41,4 +44,11 @@ public class Application extends BaseEntity { // // @OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true) // private final List applicationBases = new ArrayList<>(); + + public static Application create(Decision decision, String name) { + Application application = new Application(); + application.decision = decision; + application.name = name; + return application; + } } diff --git a/src/main/java/com/whylog/server/domain/decision/entity/Decision.java b/src/main/java/com/whylog/server/domain/decision/entity/Decision.java index 114aafc..6e7f507 100644 --- a/src/main/java/com/whylog/server/domain/decision/entity/Decision.java +++ b/src/main/java/com/whylog/server/domain/decision/entity/Decision.java @@ -54,4 +54,11 @@ public class Decision extends BaseEntity { // // @OneToMany(mappedBy = "decision", cascade = CascadeType.ALL, orphanRemoval = true) // private final List effectRatios = new ArrayList<>(); + + public static Decision create(Meeting meeting, boolean isCreated) { + Decision decision = new Decision(); + decision.meeting = meeting; + decision.isCreated = isCreated; + return decision; + } } diff --git a/src/main/java/com/whylog/server/domain/decision/repository/DecisionRepository.java b/src/main/java/com/whylog/server/domain/decision/repository/DecisionRepository.java index 9e81ab3..e943acb 100644 --- a/src/main/java/com/whylog/server/domain/decision/repository/DecisionRepository.java +++ b/src/main/java/com/whylog/server/domain/decision/repository/DecisionRepository.java @@ -1,6 +1,7 @@ package com.whylog.server.domain.decision.repository; import com.whylog.server.domain.decision.entity.Decision; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -8,6 +9,8 @@ public interface DecisionRepository extends JpaRepository { + Optional findByMeetingId(Long meetingId); + @Modifying @Query("DELETE FROM Application a WHERE a.decision.meeting.team.id = :teamId") void deleteApplicationsByTeamId(@Param("teamId") Long teamId); diff --git a/src/main/java/com/whylog/server/domain/meeting/service/MeetingAnalysisService.java b/src/main/java/com/whylog/server/domain/meeting/service/MeetingAnalysisService.java index d93be18..0369e75 100644 --- a/src/main/java/com/whylog/server/domain/meeting/service/MeetingAnalysisService.java +++ b/src/main/java/com/whylog/server/domain/meeting/service/MeetingAnalysisService.java @@ -2,6 +2,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.whylog.server.domain.decision.entity.Application; +import com.whylog.server.domain.decision.entity.Decision; +import com.whylog.server.domain.decision.repository.ApplicationRepository; +import com.whylog.server.domain.decision.repository.DecisionRepository; import com.whylog.server.domain.meeting.dto.MeetingResponse; import com.whylog.server.domain.meeting.entity.Dialogue; import com.whylog.server.domain.meeting.entity.Meeting; @@ -51,6 +55,8 @@ public class MeetingAnalysisService { private final MeetingAudioReplayService meetingAudioReplayService; private final MeetingAudioFileService meetingAudioFileService; private final FastApiTranscribeClient fastApiTranscribeClient; + private final ApplicationRepository applicationRepository; + private final DecisionRepository decisionRepository; private final MeetingAnalysisRepository meetingAnalysisRepository; private final DialogueRepository dialogueRepository; private final TransactionTemplate transactionTemplate; @@ -179,6 +185,8 @@ private void persistMeetingAnalysis(Meeting meeting, TranscribeApplicationRunRes TranscribeApplicationRunResponse.AnalysisResultResponse analysisResult = runResult.analysisResult(); TranscribeApplicationRunResponse.OverallAnalysisResponse overallAnalysis = analysisResult != null ? analysisResult.overallAnalysis() : null; + List applications = + analysisResult != null && analysisResult.applications() != null ? analysisResult.applications() : List.of(); MeetingAnalysis.MeetingAnalysisPayload payload = buildMeetingAnalysisPayload(overallAnalysis); transactionTemplate.executeWithoutResult(status -> { @@ -195,13 +203,42 @@ private void persistMeetingAnalysis(Meeting meeting, TranscribeApplicationRunRes dialogueRepository.saveAll(dialogues); dialogues.forEach(managedMeeting::addDialogue); } + + Decision decision = createDecisionIfAbsent(managedMeeting); + replaceApplications(managedMeeting.getId(), decision, applications); }); log.info("회의 오디오 분석 저장 완료: meetingId={}, transcriptSegmentCount={}", meeting.getId(), transcriptSegments.size()); - // TODO: FastAPI의 applications 결과는 아직 저장하지 않는다. // TODO: applications 저장 후 applicationId를 발급해 /api/meeting-analysis/embeddings로 전달한다. } + // Decision이 없을 때 새로 생성한다. + private Decision createDecisionIfAbsent(Meeting meeting) { + return decisionRepository.findByMeetingId(meeting.getId()) + .orElseGet(() -> { + Decision decision = decisionRepository.save(Decision.create(meeting, true)); + log.info("결정사항 저장 완료: meetingId={}, decisionId={}", meeting.getId(), decision.getId()); + return decision; + }); + } + + // 분석 결과의 적용사항 제목 목록을 저장한다. + private void replaceApplications(Long meetingId, + Decision decision, + List applications) { + List newApplications = applications.stream() + .map(TranscribeApplicationRunResponse.ApplicationResponse::applicationTitle) + .filter(title -> title != null && !title.isBlank()) + .map(title -> Application.create(decision, title.trim())) + .toList(); + + if (!newApplications.isEmpty()) { + applicationRepository.saveAll(newApplications); + log.info("적용사항 저장 완료: meetingId={}, decisionId={}, applicationCount={}", + meetingId, decision.getId(), newApplications.size()); + } + } + // OverallAnalysis를 MeetingAnalysis 저장용 payload로 변환한다. private MeetingAnalysis.MeetingAnalysisPayload buildMeetingAnalysisPayload( diff --git a/src/main/java/com/whylog/server/domain/team/controller/TeamController.java b/src/main/java/com/whylog/server/domain/team/controller/TeamController.java index eb942b7..28d7bfd 100644 --- a/src/main/java/com/whylog/server/domain/team/controller/TeamController.java +++ b/src/main/java/com/whylog/server/domain/team/controller/TeamController.java @@ -37,7 +37,14 @@ public class TeamController { private final TeamQueryService teamQueryService; @GetMapping("/{teamId}/decisions") - @Operation(summary = "결정사항 목록 조회 API", description = "특정 팀의 결정사항 목록을 조회하는 API입니다.") + @Operation( + summary = "결정사항 목록 조회 API", + description = """ + 특정 팀의 결정사항 목록을 조회하는 API입니다. + 각 결정사항에는 해당 결정사항에 연결된 적용사항 목록도 함께 포함됩니다. + 페이징 없습니다. + """ + ) @ApiErrorCodeExamples({ @ApiErrorCodeExample(value = ErrorStatus.class, name = "_UNAUTHORIZED"), @ApiErrorCodeExample(value = ErrorStatus.class, name = "_BAD_REQUEST") diff --git a/src/main/java/com/whylog/server/domain/team/service/TeamQueryService.java b/src/main/java/com/whylog/server/domain/team/service/TeamQueryService.java index 364483a..304cabd 100644 --- a/src/main/java/com/whylog/server/domain/team/service/TeamQueryService.java +++ b/src/main/java/com/whylog/server/domain/team/service/TeamQueryService.java @@ -1,5 +1,6 @@ package com.whylog.server.domain.team.service; +import com.whylog.server.domain.decision.dto.ApplicationResponse; import com.whylog.server.domain.decision.dto.DecisionResponse; import com.whylog.server.domain.decision.entity.Decision; import com.whylog.server.domain.team.repository.TeamRepository; @@ -24,7 +25,14 @@ public List decisions( Long teamId ){ .map(d -> DecisionResponse.DecisionListDTO.builder() .decisionId(d.getId()) .name(d.getMeeting().getName()) - .applicationCount( d.getApplications().size() ) + .applications( + d.getApplications().stream() + .map(application -> ApplicationResponse.ApplicationDTO.builder() + .applicationId(application.getId()) + .name(application.getName()) + .build()) + .toList() + ) .build() ).toList(); }