From b2be43a76eefa75ee539924650ffcdbb11646b4d Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Wed, 4 Feb 2026 19:46:57 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20=EC=83=81=EC=84=B8=20API?= =?UTF-8?q?=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/ModelTrainDetailApiController.java | 43 ++++++ .../model/dto/ModelTrainDetailDto.java | 144 ++++++++++++++++++ .../service/ModelTrainDetailService.java | 45 ++++++ .../core/ModelTrainDetailCoreService.java | 56 +++++++ .../model/ModelDetailRepository.java | 7 + .../model/ModelDetailRepositoryCustom.java | 20 +++ .../model/ModelDetailRepositoryImpl.java | 109 +++++++++++++ 7 files changed, 424 insertions(+) create mode 100644 src/main/java/com/kamco/cd/training/model/ModelTrainDetailApiController.java create mode 100644 src/main/java/com/kamco/cd/training/model/dto/ModelTrainDetailDto.java create mode 100644 src/main/java/com/kamco/cd/training/model/service/ModelTrainDetailService.java create mode 100644 src/main/java/com/kamco/cd/training/postgres/core/ModelTrainDetailCoreService.java create mode 100644 src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepository.java create mode 100644 src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryImpl.java diff --git a/src/main/java/com/kamco/cd/training/model/ModelTrainDetailApiController.java b/src/main/java/com/kamco/cd/training/model/ModelTrainDetailApiController.java new file mode 100644 index 0000000..6ee176f --- /dev/null +++ b/src/main/java/com/kamco/cd/training/model/ModelTrainDetailApiController.java @@ -0,0 +1,43 @@ +package com.kamco.cd.training.model; + +import com.kamco.cd.training.config.api.ApiResponseDto; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.MappingDataset; +import com.kamco.cd.training.model.service.ModelTrainDetailService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Tag(name = "모델학습 관리", description = "어드민 홈 > 모델학습관리 > 모델관리 > 목록") +@RequestMapping("/api/models") +public class ModelTrainDetailApiController { + private final ModelTrainDetailService modelTrainDetailService; + + @Operation(summary = "모델학습 상세 요약 정보", description = "모델학습 상세 요약 정보 API") + @GetMapping("/summary/{uuid}") + public ApiResponseDto getModelDetailSummary( + @PathVariable UUID uuid) { + return ApiResponseDto.ok(modelTrainDetailService.getModelDetailSummary(uuid)); + } + + @Operation(summary = "모델학습 상세 > 하이퍼파라미터 요약 정보", description = "모델학습 상세 하이퍼파라미터 요약 정보 API") + @GetMapping("/hyper-summary/{uuid}") + public ApiResponseDto getByModelHyperParamSummary( + @PathVariable UUID uuid) { + return ApiResponseDto.ok(modelTrainDetailService.getByModelHyperParamSummary(uuid)); + } + + @Operation(summary = "모델학습 상세 > 데이터셋 정보", description = "모델학습 상세 데이터셋 정보 API") + @GetMapping("/mapp-dataset/{uuid}") + public ApiResponseDto> getByModelMappingDataset(@PathVariable UUID uuid) { + return ApiResponseDto.ok(modelTrainDetailService.getByModelMappingDataset(uuid)); + } +} diff --git a/src/main/java/com/kamco/cd/training/model/dto/ModelTrainDetailDto.java b/src/main/java/com/kamco/cd/training/model/dto/ModelTrainDetailDto.java new file mode 100644 index 0000000..2f7b93a --- /dev/null +++ b/src/main/java/com/kamco/cd/training/model/dto/ModelTrainDetailDto.java @@ -0,0 +1,144 @@ +package com.kamco.cd.training.model.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.kamco.cd.training.common.enums.LearnDataType; +import com.kamco.cd.training.common.enums.TrainStatusType; +import com.kamco.cd.training.common.enums.TrainType; +import com.kamco.cd.training.common.utils.enums.Enums; +import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +public class ModelTrainDetailDto { + @Schema(name = "모델학습관리 목록", description = "모델학습관리 목록") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class DetailSummary { + + private Long modelId; + private UUID uuid; + private String modelNo; + private String modelVer; + @JsonFormatDttm private ZonedDateTime step1StrtDttm; + @JsonFormatDttm private ZonedDateTime step2EndDttm; + private String statusCd; + private String trainType; + + public String getStatusName() { + if (this.statusCd == null || this.statusCd.isBlank()) return null; + try { + return TrainStatusType.valueOf(this.statusCd).getText(); // 또는 getName() + } catch (IllegalArgumentException e) { + return this.statusCd; // 매핑 못하면 코드 그대로 반환(원하면 null 처리) + } + } + + public String getTrainTypeName() { + if (this.trainType == null || this.trainType.isBlank()) return null; + try { + return TrainType.valueOf(this.trainType).getText(); // 또는 getName() + } catch (IllegalArgumentException e) { + return this.trainType; // 매핑 못하면 코드 그대로 반환(원하면 null 처리) + } + } + + private String formatDuration(ZonedDateTime start, ZonedDateTime end) { + if (end == null) { + end = ZonedDateTime.now(); + } + + if (start == null) { + return null; + } + + long totalSeconds = Math.abs(Duration.between(start, end).getSeconds()); + + long hours = totalSeconds / 3600; + long minutes = (totalSeconds % 3600) / 60; + long seconds = totalSeconds % 60; + + return String.format("%d시간 %d분 %d초", hours, minutes, seconds); + } + + public String getStepAllDuration() { + return formatDuration(this.step1StrtDttm, this.step2EndDttm); + } + } + + @Schema(name = "모델학습관리 목록", description = "모델학습관리 목록") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class HyperSummary { + private UUID uuid; + private Long hyperParamId; + private String hyperVer; + private String backbone; + private String inputSize; + private Integer batchSize; + } + + @Schema(name = "선택한 데이터셋 목록", description = "선택한 데이터셋 목록") + @Getter + @Setter + @NoArgsConstructor + public static class MappingDataset { + private Long modelId; + private String dataType; + private Integer compareYyyy; + private Integer targetYyyy; + private Long roundNo; + private String dataTypeName; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long buildingCnt; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long containerCnt; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long wasteCnt; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Long landCoverCnt; + + public MappingDataset( + Long modelId, + String dataType, + Integer compareYyyy, + Integer targetYyyy, + Long roundNo, + Long buildingCnt, + Long containerCnt, + Long wasteCnt, + Long landCoverCnt) { + this.modelId = modelId; + this.dataType = dataType; + this.compareYyyy = compareYyyy; + this.targetYyyy = targetYyyy; + this.roundNo = roundNo; + this.buildingCnt = buildingCnt; + this.containerCnt = containerCnt; + this.wasteCnt = wasteCnt; + this.landCoverCnt = landCoverCnt; + this.dataTypeName = getDataTypeName(this.dataType); + } + + public String getDataTypeName(String groupTitleCd) { + LearnDataType type = Enums.fromId(LearnDataType.class, groupTitleCd); + return type == null ? null : type.getText(); + } + } +} diff --git a/src/main/java/com/kamco/cd/training/model/service/ModelTrainDetailService.java b/src/main/java/com/kamco/cd/training/model/service/ModelTrainDetailService.java new file mode 100644 index 0000000..d2cc297 --- /dev/null +++ b/src/main/java/com/kamco/cd/training/model/service/ModelTrainDetailService.java @@ -0,0 +1,45 @@ +package com.kamco.cd.training.model.service; + +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.DetailSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.HyperSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.MappingDataset; +import com.kamco.cd.training.postgres.core.ModelTrainDetailCoreService; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Slf4j +public class ModelTrainDetailService { + + private final ModelTrainDetailCoreService modelTrainDetailCoreService; + + /** + * 모델 상세정보 요약 + * + * @param uuid + * @return + */ + public DetailSummary getModelDetailSummary(UUID uuid) { + return modelTrainDetailCoreService.getModelDetailSummary(uuid); + } + + /** + * 모델 하이퍼파라미터 요약 + * + * @param uuid + * @return + */ + public HyperSummary getByModelHyperParamSummary(UUID uuid) { + return modelTrainDetailCoreService.getByModelHyperParamSummary(uuid); + } + + public List getByModelMappingDataset(UUID uuid) { + return modelTrainDetailCoreService.getByModelMappingDataset(uuid); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/core/ModelTrainDetailCoreService.java b/src/main/java/com/kamco/cd/training/postgres/core/ModelTrainDetailCoreService.java new file mode 100644 index 0000000..dc7fce4 --- /dev/null +++ b/src/main/java/com/kamco/cd/training/postgres/core/ModelTrainDetailCoreService.java @@ -0,0 +1,56 @@ +package com.kamco.cd.training.postgres.core; + +import com.kamco.cd.training.common.exception.BadRequestException; +import com.kamco.cd.training.common.exception.NotFoundException; +import com.kamco.cd.training.common.utils.UserUtil; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.DetailSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.HyperSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.MappingDataset; +import com.kamco.cd.training.postgres.entity.ModelMasterEntity; +import com.kamco.cd.training.postgres.repository.model.ModelDetailRepository; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ModelTrainDetailCoreService { + private final ModelDetailRepository modelDetailRepository; + private final UserUtil userUtil; + + /** + * UUID로 모델 조회 + * + * @param uuid UUID + * @return 모델 Entity + */ + public ModelMasterEntity findByUuid(String uuid) { + try { + UUID uuidObj = UUID.fromString(uuid); + return modelDetailRepository + .findByUuid(uuidObj) + .orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. UUID: " + uuid)); + } catch (IllegalArgumentException e) { + throw new BadRequestException("잘못된 UUID 형식입니다: " + uuid); + } + } + + /** + * 상세정보 페이지 > 요약정보 + * + * @param uuid + * @return + */ + public DetailSummary getModelDetailSummary(UUID uuid) { + return modelDetailRepository.getModelDetailSummary(uuid); + } + + public HyperSummary getByModelHyperParamSummary(UUID uuid) { + return modelDetailRepository.getByModelHyperParamSummary(uuid); + } + + public List getByModelMappingDataset(UUID uuid) { + return modelDetailRepository.getByModelMappingDataset(uuid); + } +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepository.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepository.java new file mode 100644 index 0000000..631c9f9 --- /dev/null +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepository.java @@ -0,0 +1,7 @@ +package com.kamco.cd.training.postgres.repository.model; + +import com.kamco.cd.training.postgres.entity.ModelMasterEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ModelDetailRepository + extends JpaRepository, ModelDetailRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryCustom.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryCustom.java new file mode 100644 index 0000000..49e43ca --- /dev/null +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryCustom.java @@ -0,0 +1,20 @@ +package com.kamco.cd.training.postgres.repository.model; + +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.DetailSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.HyperSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.MappingDataset; +import com.kamco.cd.training.postgres.entity.ModelMasterEntity; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface ModelDetailRepositoryCustom { + + Optional findByUuid(UUID uuid); + + DetailSummary getModelDetailSummary(UUID uuid); + + HyperSummary getByModelHyperParamSummary(UUID uuid); + + List getByModelMappingDataset(UUID uuid); +} diff --git a/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryImpl.java b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryImpl.java new file mode 100644 index 0000000..3e41fce --- /dev/null +++ b/src/main/java/com/kamco/cd/training/postgres/repository/model/ModelDetailRepositoryImpl.java @@ -0,0 +1,109 @@ +package com.kamco.cd.training.postgres.repository.model; + +import static com.kamco.cd.training.postgres.entity.QDatasetEntity.datasetEntity; +import static com.kamco.cd.training.postgres.entity.QModelDatasetEntity.modelDatasetEntity; +import static com.kamco.cd.training.postgres.entity.QModelDatasetMappEntity.modelDatasetMappEntity; +import static com.kamco.cd.training.postgres.entity.QModelHyperParamEntity.modelHyperParamEntity; +import static com.kamco.cd.training.postgres.entity.QModelMasterEntity.modelMasterEntity; + +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.DetailSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.HyperSummary; +import com.kamco.cd.training.model.dto.ModelTrainDetailDto.MappingDataset; +import com.kamco.cd.training.postgres.entity.ModelMasterEntity; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class ModelDetailRepositoryImpl implements ModelDetailRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + /** + * 모델 조회 + * + * @param uuid + * @return + */ + @Override + public Optional findByUuid(UUID uuid) { + return Optional.ofNullable( + queryFactory + .select(modelMasterEntity) + .from(modelMasterEntity) + .where(modelMasterEntity.uuid.eq(uuid)) + .fetchOne()); + } + + @Override + public DetailSummary getModelDetailSummary(UUID uuid) { + return queryFactory + .select( + Projections.constructor( + DetailSummary.class, + modelMasterEntity.id, + modelMasterEntity.uuid, + modelMasterEntity.modelNo, + modelMasterEntity.modelVer, + modelMasterEntity.step1StrtDttm, + modelMasterEntity.step2EndDttm, + modelMasterEntity.statusCd, + modelMasterEntity.trainType)) + .from(modelMasterEntity) + .where(modelMasterEntity.uuid.eq(uuid)) + .fetchOne(); + } + + @Override + public HyperSummary getByModelHyperParamSummary(UUID uuid) { + return queryFactory + .select( + Projections.constructor( + HyperSummary.class, + modelHyperParamEntity.uuid, + modelHyperParamEntity.id, + modelHyperParamEntity.hyperVer, + modelHyperParamEntity.backbone, + modelHyperParamEntity.inputSize, + modelHyperParamEntity.batchSize)) + .from(modelHyperParamEntity) + .where( + modelHyperParamEntity.id.eq( + JPAExpressions.select(modelMasterEntity.hyperParamId) + .from(modelMasterEntity) + .where(modelMasterEntity.uuid.eq(uuid)))) + .fetchOne(); + } + + @Override + public List getByModelMappingDataset(UUID uuid) { + return queryFactory + .select( + Projections.constructor( + MappingDataset.class, + modelMasterEntity.id, + datasetEntity.dataType, + datasetEntity.compareYyyy, + datasetEntity.targetYyyy, + datasetEntity.roundNo, + modelDatasetEntity.buildingCnt, + modelDatasetEntity.containerCnt, + modelDatasetEntity.wasteCnt, + modelDatasetEntity.landCoverCnt)) + .from(modelMasterEntity) + .innerJoin(modelDatasetEntity) + .on(modelMasterEntity.id.eq(modelDatasetEntity.model.id)) + .innerJoin(modelDatasetMappEntity) + .on(modelMasterEntity.id.eq(modelDatasetMappEntity.modelUid)) + .innerJoin(datasetEntity) + .on(modelDatasetMappEntity.datasetUid.eq(datasetEntity.id)) + .where(modelMasterEntity.uuid.eq(uuid)) + .fetch(); + } +}