From 77400d00c3ddefb13429a82e90e738dc0f79f819 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Mon, 4 May 2026 17:16:39 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=A0=80=EC=9E=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/decision/entity/Decision.java | 7 ++++ .../repository/DecisionRepository.java | 3 ++ .../service/MeetingAnalysisService.java | 39 ++++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) 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( From 02625dee0c4c7c639a2577fc4a48f1cfe7177857 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Mon, 4 May 2026 17:16:51 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EA=B2=B0=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=AA=A9=EB=A1=9D=EB=8F=84=20=ED=95=A8?= =?UTF-8?q?=EA=BB=98=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../decision/controller/DecisionController.java | 7 ------- .../server/domain/decision/dto/DecisionResponse.java | 5 +++-- .../server/domain/decision/entity/Application.java | 10 ++++++++++ .../server/domain/team/controller/TeamController.java | 9 ++++++++- .../server/domain/team/service/TeamQueryService.java | 11 ++++++++++- 5 files changed, 31 insertions(+), 11 deletions(-) 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/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/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..54dce8b 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,15 @@ 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() + .decisionId(d.getId()) + .applicationId(application.getId()) + .name(application.getName()) + .build()) + .toList() + ) .build() ).toList(); } From 940614bf628a706f8f108c73af41ac50d7744d3a Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Mon, 4 May 2026 17:39:23 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=EC=A0=81=EC=9A=A9=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=9D=91=EB=8B=B5=EA=B0=92=EC=9D=B4=20=EA=B2=B0?= =?UTF-8?q?=EC=A0=95=EC=82=AC=ED=95=AD=20id=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../whylog/server/domain/decision/dto/ApplicationResponse.java | 3 --- .../whylog/server/domain/team/service/TeamQueryService.java | 1 - 2 files changed, 4 deletions(-) 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/team/service/TeamQueryService.java b/src/main/java/com/whylog/server/domain/team/service/TeamQueryService.java index 54dce8b..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 @@ -28,7 +28,6 @@ public List decisions( Long teamId ){ .applications( d.getApplications().stream() .map(application -> ApplicationResponse.ApplicationDTO.builder() - .decisionId(d.getId()) .applicationId(application.getId()) .name(application.getName()) .build())