7 Commits

28 changed files with 627 additions and 1154 deletions

View File

@@ -9,9 +9,9 @@ import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ModelType implements EnumType {
M1("M1"),
M2("M2"),
M3("M3");
G1("G1"),
G2("G2"),
G3("G3");
private String desc;

View File

@@ -164,6 +164,7 @@ public class ApiResponseDto<T> {
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."),
UNPROCESSABLE_ENTITY_UPDATE("이 데이터는 수정할 수 없습니다."),
LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."),

View File

@@ -213,7 +213,7 @@ public class DatasetApiController {
@Parameter(
description = "모델 구분",
example = "",
schema = @Schema(allowableValues = {"M1", "M2", "M3"}))
schema = @Schema(allowableValues = {"G1", "G2", "G3"}))
@RequestParam
String modelType,
@Parameter(

View File

@@ -97,7 +97,7 @@ public class DatasetService {
}
public List<SelectDataSet> getDatasetSelectList(String modelType, String selectType) {
if (modelType.equals("M1")) {
if (modelType.equals("G1")) {
return datasetCoreService.getDatasetSelectM1List(modelType, selectType);
} else {
return datasetCoreService.getDatasetSelectM2M3List(modelType, selectType);

View File

@@ -5,7 +5,6 @@ import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto.List;
import com.kamco.cd.training.hyperparam.service.HyperParamService;
import com.kamco.cd.training.model.dto.ModelMngDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
@@ -66,6 +65,7 @@ public class HyperParamApiController {
mediaType = "application/json",
schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "422", description = "HPs_0001 수정 불가", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/{uuid}")
@@ -133,7 +133,7 @@ public class HyperParamApiController {
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content),
@ApiResponse(responseCode = "409", description = "HPs_0001 삭제 불가", content = @Content),
@ApiResponse(responseCode = "422", description = "HPs_0001 삭제 불가", content = @Content),
@ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content),
})
@DeleteMapping("/{uuid}")
@@ -154,7 +154,7 @@ public class HyperParamApiController {
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ModelMngDto.HyperParamInfo.class))),
schema = @Schema(implementation = HyperParamDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "하이퍼파라미터를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})

View File

@@ -0,0 +1,50 @@
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.dto.ModelTrainMngDto.Basic;
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("/detail/{uuid}")
public ApiResponseDto<Basic> findByModelByUUID(@PathVariable UUID uuid) {
return ApiResponseDto.ok(modelTrainDetailService.findByModelByUUID(uuid));
}
@Operation(summary = "모델학습 상세 요약 정보", description = "모델학습 상세 요약 정보 API")
@GetMapping("/summary/{uuid}")
public ApiResponseDto<ModelTrainDetailDto.DetailSummary> getModelDetailSummary(
@PathVariable UUID uuid) {
return ApiResponseDto.ok(modelTrainDetailService.getModelDetailSummary(uuid));
}
@Operation(summary = "모델학습 상세 > 하이퍼파라미터 요약 정보", description = "모델학습 상세 하이퍼파라미터 요약 정보 API")
@GetMapping("/hyper-summary/{uuid}")
public ApiResponseDto<ModelTrainDetailDto.HyperSummary> getByModelHyperParamSummary(
@PathVariable UUID uuid) {
return ApiResponseDto.ok(modelTrainDetailService.getByModelHyperParamSummary(uuid));
}
@Operation(summary = "모델학습 상세 > 데이터셋 정보", description = "모델학습 상세 데이터셋 정보 API")
@GetMapping("/mapp-dataset/{uuid}")
public ApiResponseDto<List<MappingDataset>> getByModelMappingDataset(@PathVariable UUID uuid) {
return ApiResponseDto.ok(modelTrainDetailService.getByModelMappingDataset(uuid));
}
}

View File

@@ -1,6 +1,7 @@
package com.kamco.cd.training.model;
import com.kamco.cd.training.config.api.ApiResponseDto;
import com.kamco.cd.training.model.dto.ModelConfigDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto.Basic;
import com.kamco.cd.training.model.service.ModelTrainMngService;
@@ -11,6 +12,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
@@ -51,9 +53,16 @@ public class ModelTrainMngApiController {
schema = @Schema(allowableValues = {"", "IN_PROGRESS", "COMPLETED"}))
@RequestParam(required = false)
String status,
@Parameter(
description = "모델",
example = "G1",
schema = @Schema(allowableValues = {"G1", "G2", "G3"}))
@RequestParam(required = false)
String modelNo,
@Parameter(description = "페이지 번호") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size) {
ModelTrainMngDto.SearchReq searchReq = new ModelTrainMngDto.SearchReq(status, page, size);
ModelTrainMngDto.SearchReq searchReq =
new ModelTrainMngDto.SearchReq(status, modelNo, page, size);
return ApiResponseDto.ok(modelTrainMngService.getModelList(searchReq));
}
@@ -79,183 +88,26 @@ public class ModelTrainMngApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping
public ApiResponseDto<String> createModelTrain(@RequestBody ModelTrainMngDto.AddReq req) {
public ApiResponseDto<String> createModelTrain(@Valid @RequestBody ModelTrainMngDto.AddReq req) {
modelTrainMngService.createModelTrain(req);
return ApiResponseDto.ok("ok");
}
//
// @Operation(summary = "학습 모델 상세 조회", description = "학습 모델의 상세 정보를 UUID로 조회합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "조회 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = ModelMngDto.Detail.class))),
// @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @GetMapping("/{uuid}")
// public ApiResponseDto<ModelMngDto.Detail> getModelDetail(
// @Parameter(description = "모델 UUID", example = "b7e99739-6736-45f9-a224-8161ecddf287")
// @PathVariable
// String uuid) {
// return ApiResponseDto.ok(modelTrainMngService.getModelDetailByUuid(uuid));
// }
//
// // ==================== 학습 모델학습관리 API (5종) ====================
//
// @Operation(summary = "학습 모델 통합 조회", description = "학습 관리 화면에서 학습 이력 리스트와 현재 상태를 조회합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "조회 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = List.class))),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @GetMapping("/train")
// public ApiResponseDto<List<ModelMngDto.TrainListRes>> getTrainModelList() {
// return ApiResponseDto.ok(modelTrainService.getTrainModelList());
// }
//
// @Operation(summary = "학습 설정 통합 조회", description = "학습 실행 팝업 구성에 필요한 모든 데이터를 한 번에 반환합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "조회 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = ModelMngDto.FormConfigRes.class))),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @GetMapping("/train/form-config")
// public ApiResponseDto<ModelMngDto.FormConfigRes> getFormConfig() {
// return ApiResponseDto.ok(modelTrainService.getFormConfig());
// }
//
// @Operation(summary = "학습 시작", description = "모든 설정(Step 1~3)을 마치고 최종적으로 학습 프로세스를 시작합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "학습 시작 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = ModelMngDto.TrainStartRes.class))),
// @ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @PostMapping("/train")
// public ApiResponseDto<ModelMngDto.TrainStartRes> startTraining(
// @Valid @RequestBody ModelMngDto.TrainStartReq trainReq) {
// return ApiResponseDto.ok(modelTrainService.startTraining(trainReq));
// }
//
// @Operation(summary = "학습 모델 삭제", description = "목록에서 특정 학습 모델을 삭제합니다")
// @ApiResponses(
// value = {
// @ApiResponse(responseCode = "200", description = "삭제 성공", content = @Content),
// @ApiResponse(responseCode = "400", description = "진행 중인 모델은 삭제 불가", content = @Content),
// @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @DeleteMapping("/train/{uuid}")
// public ApiResponseDto<Void> deleteTrainModel(
// @Parameter(description = "모델 UUID") @PathVariable String uuid) {
// modelTrainService.deleteTrainModel(uuid);
// return ApiResponseDto.ok(null);
// }
//
// // ==================== Resume Training (학습 재시작) ====================
//
// @Operation(summary = "학습 재시작 정보 조회", description = "중단된 학습의 재시작 가능 여부와 Checkpoint 정보를 조회합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "조회 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = ModelMngDto.ResumeInfo.class))),
// @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @GetMapping("/train/{uuid}/resume-info")
// public ApiResponseDto<ModelMngDto.ResumeInfo> getResumeInfo(
// @Parameter(description = "모델 UUID") @PathVariable String uuid) {
// return ApiResponseDto.ok(modelTrainService.getResumeInfo(uuid));
// }
//
// @Operation(summary = "학습 재시작", description = "중단된 지점(Checkpoint)부터 학습을 재개합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "재시작 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = ModelMngDto.ResumeResponse.class))),
// @ApiResponse(responseCode = "400", description = "재시작 불가능한 상태", content = @Content),
// @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @PostMapping("/train/{uuid}/resume")
// public ApiResponseDto<ModelMngDto.ResumeResponse> resumeTraining(
// @Parameter(description = "모델 UUID") @PathVariable String uuid,
// @Valid @RequestBody ModelMngDto.ResumeRequest resumeReq) {
// return ApiResponseDto.ok(modelTrainService.resumeTraining(uuid, resumeReq));
// }
//
// // ==================== Best Epoch Setting (Best Epoch 설정) ====================
//
// @Operation(summary = "Best Epoch 설정", description = "사용자가 직접 Best Epoch를 선택하여 설정합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "설정 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = ModelMngDto.BestEpochResponse.class))),
// @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @PostMapping("/train/{uuid}/best-epoch")
// public ApiResponseDto<ModelMngDto.BestEpochResponse> setBestEpoch(
// @Parameter(description = "모델 UUID") @PathVariable String uuid,
// @Valid @RequestBody ModelMngDto.BestEpochRequest bestEpochReq) {
// return ApiResponseDto.ok(modelTrainService.setBestEpoch(uuid, bestEpochReq));
// }
//
// @Operation(summary = "Epoch별 성능 지표 조회", description = "학습된 모델의 Epoch별 성능 지표를 조회합니다")
// @ApiResponses(
// value = {
// @ApiResponse(
// responseCode = "200",
// description = "조회 성공",
// content =
// @Content(
// mediaType = "application/json",
// schema = @Schema(implementation = List.class))),
// @ApiResponse(responseCode = "404", description = "모델을 찾을 수 없음", content = @Content),
// @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
// })
// @GetMapping("/train/{uuid}/epoch-metrics")
// public ApiResponseDto<List<ModelMngDto.EpochMetric>> getEpochMetrics(
// @Parameter(description = "모델 UUID") @PathVariable String uuid) {
// return ApiResponseDto.ok(modelTrainService.getEpochMetrics(uuid));
// }
@Operation(summary = "모델학습 config 정보 조회", description = "모델학습 config 정보 조회 API")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = ModelConfigDto.Basic.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/config/{uuid}")
public ApiResponseDto<ModelConfigDto.Basic> updateModelTrain(@PathVariable UUID uuid) {
return ApiResponseDto.ok(modelTrainMngService.getModelConfigByModelId(uuid));
}
}

View File

@@ -0,0 +1,23 @@
package com.kamco.cd.training.model.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class ModelConfigDto {
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class Basic {
private Long configId;
private Long modelId;
private Integer epochCount;
private Float trainPercent;
private Float validationPercent;
private Float testPercent;
private String memo;
}
}

View File

@@ -1,374 +0,0 @@
package com.kamco.cd.training.model.dto;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class ModelMngDto {
@Schema(name = "Detail", description = "모델 상세 정보")
@Getter
@Builder
public static class Detail {
private String uuid;
private String modelVer;
private String hyperVer;
private String epochVer;
private String processStep;
private String statusCd;
private String statusText;
@JsonFormatDttm private ZonedDateTime trainStartDttm;
private Integer epochCnt;
private String datasetRatio;
private Integer bestEpoch;
private Integer confirmedBestEpoch;
@JsonFormatDttm private ZonedDateTime step1EndDttm;
private ZonedDateTime step1Duration;
@JsonFormatDttm private ZonedDateTime step2EndDttm;
private ZonedDateTime step2Duration;
private Integer progressRate;
@JsonFormatDttm private ZonedDateTime createdDttm;
@JsonFormatDttm private ZonedDateTime updatedDttm;
private String modelPath;
private String errorMsg;
}
@Schema(name = "TrainListRes", description = "학습 모델 목록 응답")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class TrainListRes {
private String uuid;
private String modelVer;
private String status;
private String processStep;
@JsonFormatDttm private ZonedDateTime trainStartDttm;
private Integer progressRate;
private Integer epochCnt;
@JsonFormatDttm private ZonedDateTime step1EndDttm;
private ZonedDateTime step1Duration;
@JsonFormatDttm private ZonedDateTime step2EndDttm;
private ZonedDateTime step2Duration;
@JsonFormatDttm private ZonedDateTime createdDttm;
private String errorMsg;
private Boolean canResume;
private Integer lastCheckpointEpoch;
}
@Schema(name = "FormConfigRes", description = "학습 설정 통합 조회 응답")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class FormConfigRes {
private Boolean isTrainAvailable;
private List<HyperParamInfo> hyperParams;
private List<DatasetInfo> datasets;
private String runningModelUuid;
}
@Schema(name = "HyperParamInfo", description = "하이퍼파라미터 정보")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class HyperParamInfo {
@Schema(description = "하이퍼파라미터 버전", example = "V3.99.251221.120518")
private String hyperVer;
// Important
@Schema(description = "백본", example = "large")
private String backbone;
@Schema(description = "입력 사이즈", example = "256,256")
private String inputSize;
@Schema(description = "크롭 사이즈", example = "256,256")
private String cropSize;
@Schema(description = "에폭 수", example = "200")
private Integer epochCnt;
@Schema(description = "배치 사이즈", example = "16")
private Integer batchSize;
// Architecture
@Schema(description = "Drop Path Rate", example = "0.3")
private Double dropPathRate;
@Schema(description = "Frozen Stages", example = "-1")
private Integer frozenStages;
@Schema(description = "Neck Policy", example = "abs_diff")
private String neckPolicy;
@Schema(description = "Decoder Channels", example = "512,256,128,64")
private String decoderChannels;
@Schema(description = "Class Weight", example = "1,1")
private String classWeight;
@Schema(description = "레이어 수", example = "24")
private Integer numLayers;
// Optimization
@Schema(description = "Learning Rate", example = "0.00006")
private Double learningRate;
@Schema(description = "Weight Decay", example = "0.05")
private Double weightDecay;
@Schema(description = "Layer Decay Rate", example = "0.9")
private Double layerDecayRate;
@Schema(description = "DDP Unused Params 찾기", example = "true")
private Boolean ddpFindUnusedParams;
@Schema(description = "Ignore Index", example = "255")
private Integer ignoreIndex;
// Data
@Schema(description = "Train Workers", example = "16")
private Integer trainNumWorkers;
@Schema(description = "Val Workers", example = "8")
private Integer valNumWorkers;
@Schema(description = "Test Workers", example = "8")
private Integer testNumWorkers;
@Schema(description = "Train Shuffle", example = "true")
private Boolean trainShuffle;
@Schema(description = "Train Persistent", example = "true")
private Boolean trainPersistent;
@Schema(description = "Val Persistent", example = "true")
private Boolean valPersistent;
// Evaluation
@Schema(description = "Metrics", example = "mFscore,mIoU")
private String metrics;
@Schema(description = "Save Best", example = "changed_fscore")
private String saveBest;
@Schema(description = "Save Best Rule", example = "greater")
private String saveBestRule;
@Schema(description = "Val Interval", example = "10")
private Integer valInterval;
@Schema(description = "Log Interval", example = "400")
private Integer logInterval;
@Schema(description = "Vis Interval", example = "1")
private Integer visInterval;
// Hardware
@Schema(description = "GPU 수", example = "4")
private Integer gpuCnt;
@Schema(description = "GPU IDs", example = "0,1,2,3")
private String gpuIds;
@Schema(description = "Master Port", example = "1122")
private Integer masterPort;
// Augmentation
@Schema(description = "Rotation 확률", example = "0.5")
private Double rotProb;
@Schema(description = "Flip 확률", example = "0.5")
private Double flipProb;
@Schema(description = "Rotation 각도", example = "-20,20")
private String rotDegree;
@Schema(description = "Exchange 확률", example = "0.5")
private Double exchangeProb;
@Schema(description = "Brightness Delta", example = "10")
private Integer brightnessDelta;
@Schema(description = "Contrast Range", example = "0.8,1.2")
private String contrastRange;
@Schema(description = "Saturation Range", example = "0.8,1.2")
private String saturationRange;
@Schema(description = "Hue Delta", example = "10")
private Integer hueDelta;
// Legacy
private Double dropoutRatio;
private Integer cnnFilterCnt;
// Common
@Schema(description = "메모", example = "안녕하세요 캠코담당자 입니다. 하이퍼파라미터 신규등록합니다")
private String memo;
@JsonFormatDttm private ZonedDateTime createdDttm;
}
@Schema(name = "DatasetInfo", description = "데이터셋 정보")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class DatasetInfo {
private Long id;
private String title;
private String groupTitle;
private Long totalItems;
private String totalSize;
private Map<String, Integer> classCounts;
private String memo;
@JsonFormatDttm private ZonedDateTime createdDttm;
}
@Schema(name = "TrainStartReq", description = "학습 시작 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class TrainStartReq {
@NotBlank(message = "하이퍼파라미터 버전은 필수입니다")
@Schema(example = "V3.99.251221.120518")
private String hyperVer;
@NotEmpty(message = "데이터셋은 최소 1개 이상 선택해야 합니다")
private List<Long> datasetIds;
@NotNull(message = "에폭 수는 필수입니다")
@jakarta.validation.constraints.Min(value = 1, message = "에폭 수는 최소 1 이상이어야 합니다")
@jakarta.validation.constraints.Max(value = 200, message = "에폭 수는 최대 200까지 설정 가능합니다")
@Schema(example = "200")
private Integer epoch;
@Schema(example = "7:2:1", description = "데이터 분할 비율 (Training:Validation:Test)")
private String datasetRatio;
}
@Schema(name = "TrainStartRes", description = "학습 시작 응답")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class TrainStartRes {
private String uuid;
private String status;
}
@Schema(name = "ResumeInfo", description = "학습 재시작 정보")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class ResumeInfo {
private Boolean canResume;
private Integer lastEpoch;
private Integer totalEpoch;
private String checkpointPath;
@JsonFormatDttm private ZonedDateTime failedAt;
}
@Schema(name = "ResumeRequest", description = "학습 재시작 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ResumeRequest {
@NotNull(message = "재시작 Epoch는 필수입니다")
private Integer resumeFromEpoch;
private Integer newTotalEpoch;
}
@Schema(name = "ResumeResponse", description = "학습 재시작 응답")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class ResumeResponse {
private String uuid;
private String status;
private Integer resumedFromEpoch;
}
@Schema(name = "BestEpochRequest", description = "Best Epoch 설정 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class BestEpochRequest {
@NotNull(message = "Best Epoch는 필수입니다")
private Integer bestEpoch;
private String reason;
}
@Schema(name = "BestEpochResponse", description = "Best Epoch 설정 응답")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class BestEpochResponse {
private String uuid;
private Integer bestEpoch;
private Integer confirmedBestEpoch;
private Integer previousBestEpoch;
}
@Schema(name = "EpochMetric", description = "Epoch별 성능 지표")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class EpochMetric {
private Integer epoch;
private Double mIoU;
private Double mFscore;
private Double loss;
private Boolean isBest;
}
}

View File

@@ -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();
}
}
}

View File

@@ -107,6 +107,7 @@ public class ModelTrainMngDto {
public static class SearchReq {
private String status;
private String modelNo;
// 페이징 파라미터
private int page = 0;
private int size = 20;
@@ -124,7 +125,7 @@ public class ModelTrainMngDto {
public static class AddReq {
@NotNull
@Schema(description = "모델 종류 M1, M2, M3", example = "M1")
@Schema(description = "모델 종류 G1, G2, G3", example = "G1")
private String modelNo;
@NotNull

View File

@@ -1,61 +0,0 @@
package com.kamco.cd.training.model.dto;
import com.kamco.cd.training.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime;
import lombok.Getter;
public class ModelVerDto {
@Schema(name = "modelVer Basic", description = "모델버전 엔티티 기본 정보")
@Getter
public static class Basic {
private final Long id;
private final Long modelUid;
private final String modelCate;
private final String modelVer;
private final String usedState;
private final String modelState;
private final Double qualityProb;
private final String deployState;
private final String modelPath;
@JsonFormatDttm private final ZonedDateTime createdDttm;
private final Long createdUid;
@JsonFormatDttm private final ZonedDateTime updatedDttm;
private final Long updatedUid;
public Basic(
Long id,
Long modelUid,
String modelCate,
String modelVer,
String usedState,
String modelState,
Double qualityProb,
String deployState,
String modelPath,
ZonedDateTime createdDttm,
Long createdUid,
ZonedDateTime updatedDttm,
Long updatedUid) {
this.id = id;
this.modelUid = modelUid;
this.modelCate = modelCate;
this.modelVer = modelVer;
this.usedState = usedState;
this.modelState = modelState;
this.qualityProb = qualityProb;
this.deployState = deployState;
this.modelPath = modelPath;
this.createdDttm = createdDttm;
this.createdUid = createdUid;
this.updatedDttm = updatedDttm;
this.updatedUid = updatedUid;
}
}
}

View File

@@ -0,0 +1,50 @@
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.model.dto.ModelTrainMngDto.Basic;
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<MappingDataset> getByModelMappingDataset(UUID uuid) {
return modelTrainDetailCoreService.getByModelMappingDataset(uuid);
}
public Basic findByModelByUUID(UUID uuid) {
return modelTrainDetailCoreService.findByModelByUUID(uuid);
}
}

View File

@@ -3,7 +3,7 @@ package com.kamco.cd.training.model.service;
import com.kamco.cd.training.common.dto.HyperParam;
import com.kamco.cd.training.common.enums.HyperParamSelectType;
import com.kamco.cd.training.hyperparam.dto.HyperParamDto;
import com.kamco.cd.training.model.dto.ModelMngDto;
import com.kamco.cd.training.model.dto.ModelConfigDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto.SearchReq;
import com.kamco.cd.training.postgres.core.HyperParamCoreService;
@@ -39,6 +39,7 @@ public class ModelTrainMngService {
*
* @param uuid
*/
@Transactional
public void deleteModelTrain(UUID uuid) {
modelTrainMngCoreService.deleteModel(uuid);
}
@@ -54,7 +55,7 @@ public class ModelTrainMngService {
HyperParam hyperParam = req.getHyperParam();
HyperParamDto.Basic hyper = new HyperParamDto.Basic();
/** OPTIMIZED(최적화 파라미터), EXISTING(기존 파라미터), NEW(신규 파라미터) * */
// 하이파라미터 신규저장
if (HyperParamSelectType.NEW.getId().equals(req.getHyperParamType())) {
// 하이퍼파라미터 등록
hyper = hyperParamCoreService.createHyperParam(hyperParam);
@@ -76,12 +77,12 @@ public class ModelTrainMngService {
}
/**
* 모델학습 상세 조회
* 모델학습 모델학습 uuid config정보 조회
*
* @param modelUid 모델 UID
* @return 모델 상세 정보
* @param uuid 모델학습 uuid
* @return
*/
public ModelMngDto.Detail getModelDetail(Long modelUid) {
return modelTrainMngCoreService.getModelDetail(modelUid);
public ModelConfigDto.Basic getModelConfigByModelId(UUID uuid) {
return modelTrainMngCoreService.findModelConfigByModelId(uuid);
}
}

View File

@@ -1,334 +0,0 @@
package com.kamco.cd.training.model.service;
import com.kamco.cd.training.common.exception.BadRequestException;
import com.kamco.cd.training.common.exception.NotFoundException;
import com.kamco.cd.training.model.dto.ModelMngDto;
import com.kamco.cd.training.postgres.core.DatasetCoreService;
import com.kamco.cd.training.postgres.core.ModelTrainMngCoreService;
import com.kamco.cd.training.postgres.core.SystemMetricsCoreService;
import com.kamco.cd.training.postgres.entity.ModelMasterEntity;
import java.util.List;
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 ModelTrainService {
private final ModelTrainMngCoreService modelMngCoreService;
private final DatasetCoreService datasetCoreService;
private final SystemMetricsCoreService systemMetricsCoreService;
/**
* 학습 설정 통합 조회
*
* @return 학습 설정 폼 데이터
*/
public ModelMngDto.FormConfigRes getFormConfig() {
// 1. 현재 실행 중인 모델 확인
String runningModelUuid = modelMngCoreService.findRunningModelUuid();
boolean isTrainAvailable = (runningModelUuid == null);
// 2. 저장공간 체크 (10GB 미만 시 학습 불가)
if (isTrainAvailable) {
isTrainAvailable = systemMetricsCoreService.isStorageAvailableForTraining();
long availableMB = systemMetricsCoreService.getAvailableStorageMB();
log.info("저장공간 체크 완료: {}MB 사용 가능, 학습 가능 여부: {}", availableMB, isTrainAvailable);
}
// 3. 하이퍼파라미터 목록
List<ModelMngDto.HyperParamInfo> hyperParams =
null; // hyperParamCoreService.findAllActiveHyperParams();
// 4. 데이터셋 목록
List<ModelMngDto.DatasetInfo> datasets = datasetCoreService.findAllActiveDatasetsForTraining();
return ModelMngDto.FormConfigRes.builder()
.isTrainAvailable(isTrainAvailable)
.hyperParams(hyperParams)
.datasets(datasets)
.runningModelUuid(runningModelUuid)
.build();
}
/**
* 학습 시작
*
* @param trainReq 학습 시작 요청
* @return 학습 시작 응답
*/
@Transactional
public ModelMngDto.TrainStartRes startTraining(ModelMngDto.TrainStartReq trainReq) {
// 1. 동시 실행 방지 검증
String runningModelUuid = modelMngCoreService.findRunningModelUuid();
if (runningModelUuid != null) {
throw new BadRequestException(
"이미 실행 중인 학습이 있습니다. 학습은 한 번에 한 개만 실행할 수 있습니다. (실행 중인 모델: " + runningModelUuid + ")");
}
// 2. 저장공간 체크 (10GB 미만 시 학습 불가)
if (!systemMetricsCoreService.isStorageAvailableForTraining()) {
long availableMB = systemMetricsCoreService.getAvailableStorageMB();
long requiredMB = 10 * 1024; // 10GB
throw new BadRequestException(
String.format(
"저장공간이 부족하여 학습을 시작할 수 없습니다. (필요: %dMB, 사용 가능: %dMB)", requiredMB, availableMB));
}
// 3. 데이터셋 상태 검증 (COMPLETED 상태만 학습 가능)
validateDatasetStatus(trainReq.getDatasetIds());
// 4. 데이터 분할 비율 검증 (예: "7:2:1" 형식)
if (trainReq.getDatasetRatio() != null && !trainReq.getDatasetRatio().isEmpty()) {
validateDatasetRatio(trainReq.getDatasetRatio());
}
// 5. 학습 마스터 생성
ModelMasterEntity entity = modelMngCoreService.createTrainMaster(trainReq);
// 5. 데이터셋 매핑 생성
modelMngCoreService.createDatasetMappings(entity.getId(), trainReq.getDatasetIds());
// 6. 실제 UUID 사용
String uuid = entity.getUuid().toString();
log.info(
"학습 시작: uuid={}, hyperVer={}, epoch={}, datasets={}",
uuid,
trainReq.getHyperVer(),
trainReq.getEpoch(),
trainReq.getDatasetIds());
// TODO: 비동기 GPU 학습 프로세스 트리거 로직 추가
return ModelMngDto.TrainStartRes.builder().uuid(uuid).status(entity.getStatusCd()).build();
}
/**
* 데이터셋 상태 검증
*
* @param datasetIds 데이터셋 ID 목록
*/
private void validateDatasetStatus(List<Long> datasetIds) {
for (Long datasetId : datasetIds) {
try {
var dataset = datasetCoreService.getOneById(datasetId);
// COMPLETED 상태가 아닌 데이터셋이 포함되어 있으면 예외 발생
if (dataset.getStatus() == null || !"COMPLETED".equals(dataset.getStatus())) {
throw new BadRequestException(
String.format(
"학습에 사용할 수 없는 데이터셋입니다. (ID: %d, 상태: %s). COMPLETED 상태의 데이터셋만 선택 가능합니다.",
datasetId, dataset.getStatus() != null ? dataset.getStatus() : "NULL"));
}
log.debug("데이터셋 상태 검증 통과: ID={}, Status={}", datasetId, dataset.getStatus());
} catch (NotFoundException e) {
throw new BadRequestException("존재하지 않는 데이터셋입니다. ID: " + datasetId);
}
}
log.info("모든 데이터셋 상태 검증 완료: {} 개", datasetIds.size());
}
/**
* 데이터 분할 비율 검증
*
* @param datasetRatio 데이터셋 비율 (예: "7:2:1")
*/
private void validateDatasetRatio(String datasetRatio) {
try {
String[] parts = datasetRatio.split(":");
if (parts.length != 3) {
throw new BadRequestException("데이터 분할 비율은 'Training:Validation:Test' 형식이어야 합니다 (예: 7:2:1)");
}
int train = Integer.parseInt(parts[0].trim());
int validation = Integer.parseInt(parts[1].trim());
int test = Integer.parseInt(parts[2].trim());
int sum = train + validation + test;
if (sum != 10) {
throw new BadRequestException(
String.format("데이터 분할 비율의 합계는 10이어야 합니다. (현재 합계: %d, 입력값: %s)", sum, datasetRatio));
}
if (train <= 0 || validation < 0 || test < 0) {
throw new BadRequestException("데이터 분할 비율은 모두 0 이상이어야 합니다 (Training은 1 이상)");
}
log.info(
"데이터 분할 비율 검증 완료: Training={}0%, Validation={}0%, Test={}0%", train, validation, test);
} catch (NumberFormatException e) {
throw new BadRequestException("데이터 분할 비율은 숫자로만 구성되어야 합니다: " + datasetRatio);
}
}
// ==================== Resume Training (학습 재시작) ====================
//
// /**
// * 학습 재시작 정보 조회
// *
// * @param uuid 모델 UUID
// * @return 재시작 정보
// */
// public ModelMngDto.ResumeInfo getResumeInfo(String uuid) {
// ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
//
// return ModelMngDto.ResumeInfo.builder()
// .canResume(entity.getCanResume() != null && entity.getCanResume())
// .lastEpoch(entity.getLastCheckpointEpoch())
// .totalEpoch(entity.getEpochCnt())
// .checkpointPath(entity.getCheckpointPath())
// // .failedAt(
// // entity.getStopDttm() != null
// // ? entity.getStopDttm().atZone(java.time.ZoneId.systemDefault())
// // : null)
// .build();
// }
/**
* 학습 재시작
*
* @param uuid 모델 UUID
* @param resumeReq 재시작 요청
* @return 재시작 응답
*/
@Transactional
public ModelMngDto.ResumeResponse resumeTraining(
String uuid, ModelMngDto.ResumeRequest resumeReq) {
// ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
//
// // 재시작 가능 여부 검증
// if (entity.getCanResume() == null || !entity.getCanResume()) {
// throw new IllegalStateException("학습 재시작이 불가능한 모델입니다: " + uuid);
// }
//
// if (entity.getLastCheckpointEpoch() == null) {
// throw new IllegalStateException("Checkpoint가 존재하지 않습니다: " + uuid);
// }
//
// // 상태 업데이트
// entity.setStatusCd("RUNNING");
// entity.setProgressRate(0);
//
// // 총 Epoch 수 변경 (선택사항)
// if (resumeReq.getNewTotalEpoch() != null) {
// entity.setEpochCnt(resumeReq.getNewTotalEpoch());
// }
//
// log.info(
// "학습 재시작: uuid={}, resumeFromEpoch={}, totalEpoch={}",
// uuid,
// resumeReq.getResumeFromEpoch(),
// entity.getEpochCnt());
//
// // TODO: 비동기 GPU 학습 재시작 프로세스 트리거 로직 추가
// // - Checkpoint 파일 로드
// // - 지정된 Epoch부터 학습 재개
return null;
// ModelMngDto.ResumeResponse.builder()
// .uuid(uuid)
// .status(entity.getStatusCd())
// .resumedFromEpoch(resumeReq.getResumeFromEpoch())
// .build();
}
// ==================== Best Epoch Setting (Best Epoch 설정) ====================
/**
* Best Epoch 설정
*
* @param uuid 모델 UUID
* @param bestEpochReq Best Epoch 요청
* @return Best Epoch 응답
*/
@Transactional
public ModelMngDto.BestEpochResponse setBestEpoch(
String uuid, ModelMngDto.BestEpochRequest bestEpochReq) {
// ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
//
// // 1차 학습 완료 상태 검증
// if (!"STEP1_COMPLETED".equals(entity.getStatusCd())
// && !"STEP1".equals(entity.getProcessStep())) {
// log.warn(
// "Best Epoch 설정 시도: 현재 상태={}, processStep={}",
// entity.getStatusCd(),
// entity.getProcessStep());
// }
//
// Integer previousBestEpoch = entity.getConfirmedBestEpoch();
//
// // 사용자가 확정한 Best Epoch 설정
// entity.setConfirmedBestEpoch(bestEpochReq.getBestEpoch());
//
// // 2차 학습(Test) 단계로 상태 전이
// entity.setProcessStep("STEP2");
// entity.setStatusCd("STEP2_RUNNING");
// entity.setProgressRate(0);
// entity.setUpdatedDttm(java.time.ZonedDateTime.now());
//
// log.info(
// "Best Epoch 설정 및 2차 학습 시작: uuid={}, newBestEpoch={}, previousBestEpoch={}, reason={},
// newStatus={}",
// uuid,
// bestEpochReq.getBestEpoch(),
// previousBestEpoch,
// bestEpochReq.getReason(),
// entity.getStatusCd());
// TODO: 비동기 GPU 2차 학습(Test) 프로세스 트리거 로직 추가
// - Best Epoch 모델 로드
// - Test 데이터셋으로 성능 평가 실행
// - 완료 시 STEP2_COMPLETED 상태로 전환
return null;
// ModelMngDto.BestEpochResponse.builder()
// .uuid(uuid)
// .bestEpoch(entity.getBestEpoch()) // 자동 선택된 값
// .confirmedBestEpoch(entity.getConfirmedBestEpoch()) // 사용자 확정 값
// .previousBestEpoch(previousBestEpoch)
// .build();
}
/**
* Epoch별 성능 지표 조회
*
* @param uuid 모델 UUID
* @return Epoch별 성능 지표 목록
*/
public List<ModelMngDto.EpochMetric> getEpochMetrics(String uuid) {
// ModelTrainMasterEntity entity = modelMngCoreService.findByUuid(uuid);
//
// // TODO: 실제 학습 로그 파일이나 DB에서 Epoch별 성능 지표 조회
// // 현재는 샘플 데이터 반환
// List<ModelMngDto.EpochMetric> metrics = new java.util.ArrayList<>();
//
// if (entity.getEpochCnt() != null && entity.getBestEpoch() != null) {
// // 샘플 데이터 생성 (실제로는 학습 로그 파일 파싱 또는 별도 테이블 조회)
// for (int i = 1; i <= Math.min(entity.getEpochCnt(), 10); i++) {
// int epoch = entity.getBestEpoch() - 5 + i;
// if (epoch <= 0 || epoch > entity.getEpochCnt()) {
// continue;
// }
//
// metrics.add(
// ModelMngDto.EpochMetric.builder()
// .epoch(epoch)
// .mIoU(0.80 + (Math.random() * 0.15)) // 샘플 데이터
// .mFscore(0.85 + (Math.random() * 0.10)) // 샘플 데이터
// .loss(0.3 - (Math.random() * 0.15)) // 샘플 데이터
// .isBest(entity.getBestEpoch() != null && epoch == entity.getBestEpoch())
// .build());
// }
// }
//
// log.info("Epoch별 성능 지표 조회: uuid={}, metricsCount={}", uuid, metrics.size());
return null; // metrics;
}
}

View File

@@ -1,6 +1,5 @@
package com.kamco.cd.training.postgres.core;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.training.common.enums.LearnDataRegister;
import com.kamco.cd.training.common.enums.LearnDataType;
@@ -10,15 +9,12 @@ import com.kamco.cd.training.dataset.dto.DatasetDto;
import com.kamco.cd.training.dataset.dto.DatasetDto.SelectDataSet;
import com.kamco.cd.training.dataset.dto.DatasetObjDto.Basic;
import com.kamco.cd.training.dataset.dto.DatasetObjDto.SearchReq;
import com.kamco.cd.training.model.dto.ModelMngDto;
import com.kamco.cd.training.postgres.entity.DatasetEntity;
import com.kamco.cd.training.postgres.entity.DatasetObjEntity;
import com.kamco.cd.training.postgres.repository.dataset.DatasetObjRepository;
import com.kamco.cd.training.postgres.repository.dataset.DatasetRepository;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -187,101 +183,6 @@ public class DatasetCoreService
summaryReq.getDatasetIds().size(), totalMapSheets, totalFileSize, averageMapSheets);
}
/**
* 활성 데이터셋 전체 조회 (학습 관리용)
*
* @return 데이터셋 정보 목록
*/
public List<ModelMngDto.DatasetInfo> findAllActiveDatasetsForTraining() {
List<DatasetEntity> entities = datasetRepository.findByDeletedOrderByCreatedDttmDesc(false);
return entities.stream()
.map(
entity -> {
// totalSize를 읽기 쉬운 형식으로 변환
String totalSizeStr = formatFileSize(entity.getTotalSize());
// classCounts JSON 파싱
Map<String, Integer> classCounts = entity.getClassCounts();
return ModelMngDto.DatasetInfo.builder()
.id(entity.getId())
.title(entity.getTitle())
.totalItems(entity.getTotalItems())
.totalSize(totalSizeStr)
.classCounts(classCounts)
.memo(entity.getMemo())
.createdDttm(entity.getCreatedDttm())
.build();
})
.toList();
}
/**
* JSON 문자열을 Map으로 파싱
*
* @param jsonStr JSON 문자열
* @return 클래스별 카운트 맵
*/
private Map<String, Integer> parseClassCounts(String jsonStr) {
if (jsonStr == null || jsonStr.trim().isEmpty()) {
return new HashMap<>();
}
try {
return objectMapper.readValue(jsonStr, new TypeReference<Map<String, Integer>>() {});
} catch (Exception e) {
log.warn("클래스 통계 JSON 파싱 실패: {}", jsonStr, e);
return new HashMap<>();
}
}
/**
* 데이터셋의 클래스 통계 계산 및 저장
*
* @param datasetId 데이터셋 ID
* @param classCounts 클래스별 카운트
*/
public void updateClassCounts(Long datasetId, Map<String, Integer> classCounts) {
DatasetEntity entity =
datasetRepository
.findById(datasetId)
.orElseThrow(() -> new NotFoundException("데이터셋을 찾을 수 없습니다. ID: " + datasetId));
try {
entity.setClassCounts(classCounts);
datasetRepository.save(entity);
log.info("데이터셋 클래스 통계 업데이트 완료: datasetId={}, classes={}", datasetId, classCounts.keySet());
} catch (Exception e) {
log.error("클래스 통계 JSON 변환 실패: datasetId={}", datasetId, e);
}
}
/**
* 파일 크기를 읽기 쉬운 형식으로 변환
*
* @param size 바이트 단위 크기
* @return 형식화된 문자열 (예: "1.5GB")
*/
private String formatFileSize(Long size) {
if (size == null || size == 0) {
return "0 GB";
}
double gb = size / (1024.0 * 1024.0 * 1024.0);
if (gb >= 1.0) {
return String.format("%.2f GB", gb);
}
double mb = size / (1024.0 * 1024.0);
if (mb >= 1.0) {
return String.format("%.2f MB", mb);
}
double kb = size / 1024.0;
return String.format("%.2f KB", kb);
}
public Page<Basic> searchDatasetObjectList(SearchReq searchReq) {
Page<DatasetObjEntity> entityPage = datasetObjRepository.searchDatasetObjectList(searchReq);
return entityPage.map(DatasetObjEntity::toDto);

View File

@@ -57,6 +57,9 @@ public class HyperParamCoreService {
.findHyperParamByUuid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
if (entity.getHyperVer().equals("HPs_0001")) {
throw new CustomApiException("UNPROCESSABLE_ENTITY_UPDATE", HttpStatus.UNPROCESSABLE_ENTITY);
}
applyHyperParam(entity, createReq);
// user
@@ -120,7 +123,7 @@ public class HyperParamCoreService {
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
if (entity.getHyperVer().equals("HPs_0001")) {
throw new CustomApiException("CONFLICT", HttpStatus.CONFLICT, "HPs_0001 버전은 삭제할수 없습니다.");
throw new CustomApiException("UNPROCESSABLE_ENTITY", HttpStatus.UNPROCESSABLE_ENTITY);
}
entity.setDelYn(true);

View File

@@ -0,0 +1,62 @@
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.model.dto.ModelTrainMngDto.Basic;
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<MappingDataset> getByModelMappingDataset(UUID uuid) {
return modelDetailRepository.getByModelMappingDataset(uuid);
}
public Basic findByModelByUUID(UUID uuid) {
ModelMasterEntity entity = modelDetailRepository.findByModelByUUID(uuid);
return entity.toDto();
}
}

View File

@@ -1,12 +1,12 @@
package com.kamco.cd.training.postgres.core;
import com.kamco.cd.training.common.enums.HyperParamSelectType;
import com.kamco.cd.training.common.enums.ModelType;
import com.kamco.cd.training.common.enums.TrainStatusType;
import com.kamco.cd.training.common.exception.BadRequestException;
import com.kamco.cd.training.common.exception.CustomApiException;
import com.kamco.cd.training.common.exception.NotFoundException;
import com.kamco.cd.training.common.utils.UserUtil;
import com.kamco.cd.training.model.dto.ModelMngDto;
import com.kamco.cd.training.model.dto.ModelConfigDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto;
import com.kamco.cd.training.model.dto.ModelTrainMngDto.Basic;
import com.kamco.cd.training.model.dto.ModelTrainMngDto.TrainingDataset;
@@ -72,22 +72,37 @@ public class ModelTrainMngCoreService {
*/
public Long saveModel(ModelTrainMngDto.AddReq addReq) {
ModelMasterEntity entity = new ModelMasterEntity();
ModelHyperParamEntity hyperParamEntity =
hyperParamRepository.findHyperParamByUuid(addReq.getHyperUuid()).orElse(null);
ModelHyperParamEntity hyperParamEntity = new ModelHyperParamEntity();
entity.setModelNo(addReq.getModelNo());
entity.setTrainType(addReq.getTrainType());
// 최적화 파라미터는 HPs_0001 사용
if (HyperParamSelectType.OPTIMIZED.getId().equals(addReq.getHyperParamType())) {
hyperParamEntity = hyperParamRepository.findByHyperVer("HPs_0001").orElse(null);
if (hyperParamEntity != null) {
entity.setHyperParamId(hyperParamEntity.getId());
} else {
hyperParamEntity =
hyperParamRepository.findHyperParamByUuid(addReq.getHyperUuid()).orElse(null);
}
if (hyperParamEntity == null || hyperParamEntity.getHyperVer() == null) {
throw new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND);
}
String modelVer =
String.join(
".", addReq.getModelNo(), hyperParamEntity.getHyperVer(), entity.getUuid().toString());
entity.setModelVer(modelVer);
entity.setHyperParamId(hyperParamEntity.getId());
entity.setModelNo(addReq.getModelNo());
entity.setTrainType(addReq.getTrainType()); // 일반, 전이
if (addReq.getIsStart()) {
entity.setModelStep((short) 1);
entity.setStatusCd(TrainStatusType.IN_PROGRESS.getId());
entity.setStrtDttm(ZonedDateTime.now());
entity.setStep1StrtDttm(ZonedDateTime.now());
entity.setStep1State(TrainStatusType.IN_PROGRESS.getId());
} else {
entity.setStatusCd(TrainStatusType.READY.getId());
}
entity.setCreatedUid(userUtil.getId());
@@ -109,12 +124,12 @@ public class ModelTrainMngCoreService {
modelMasterEntity.setId(modelId);
datasetEntity.setModel(modelMasterEntity);
if (addReq.getModelNo().equals(ModelType.M1.getId())) {
if (addReq.getModelNo().equals(ModelType.G1.getId())) {
datasetEntity.setBuildingCnt(dataset.getSummary().getBuildingCnt());
datasetEntity.setContainerCnt(dataset.getSummary().getContainerCnt());
} else if (addReq.getModelNo().equals(ModelType.M2.getId())) {
} else if (addReq.getModelNo().equals(ModelType.G2.getId())) {
datasetEntity.setWasteCnt(dataset.getSummary().getWasteCnt());
} else if (addReq.getModelNo().equals(ModelType.M3.getId())) {
} else if (addReq.getModelNo().equals(ModelType.G3.getId())) {
datasetEntity.setLandCoverCnt(dataset.getSummary().getLandCoverCnt());
}
@@ -147,7 +162,7 @@ public class ModelTrainMngCoreService {
* @param req 요청 파라미터
* @return
*/
public Long saveModelConfig(Long modelId, ModelTrainMngDto.ModelConfig req) {
public void saveModelConfig(Long modelId, ModelTrainMngDto.ModelConfig req) {
ModelMasterEntity modelMasterEntity = new ModelMasterEntity();
ModelConfigEntity entity = new ModelConfigEntity();
modelMasterEntity.setId(modelId);
@@ -158,79 +173,7 @@ public class ModelTrainMngCoreService {
entity.setTestPercent(req.getTestCnt());
entity.setMemo(req.getMemo());
return modelConfigRepository.save(entity).getId();
}
/**
* 모델 상세 조회
*
* @param modelUid 모델 UID
* @return 모델 상세 정보
*/
public ModelMngDto.Detail getModelDetail(Long modelUid) {
ModelMasterEntity entity =
modelMngRepository
.findById(modelUid)
.orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. ID: " + modelUid));
if (Boolean.TRUE.equals(entity.getDelYn())) {
throw new NotFoundException("삭제된 모델입니다. ID: " + modelUid);
}
return null;
// ModelMngDto.Detail.builder()
// .uuid(entity.getUuid().toString())
// .modelVer(entity.getModelVer())
// .epochVer(entity.getEpochVer())
// .processStep(entity.getProcessStep())
// .trainStartDttm(entity.getTrainStartDttm())
// .epochCnt(entity.getEpochCnt())
// .datasetRatio(entity.getDatasetRatio())
// .bestEpoch(entity.getBestEpoch())
// .confirmedBestEpoch(entity.getConfirmedBestEpoch())
// .step1EndDttm(entity.getStep1EndDttm())
// .step1Duration(entity.getStep1Duration())
// .step2EndDttm(entity.getStep2EndDttm())
// .step2Duration(entity.getStep2Duration())
// .progressRate(entity.getProgressRate())
// .createdDttm(entity.getCreatedDttm())
// .updatedDttm(entity.getUpdatedDttm())
// .modelPath(entity.getModelPath())
// .errorMsg(entity.getErrorMsg())
// .build();
}
/**
* 현재 실행 중인 모델 확인
*
* @return 실행 중인 모델 UUID (없으면 null)
*/
public String findRunningModelUuid() {
return modelMngRepository
.findFirstByStatusCdAndDelYn("RUNNING", false)
.map(entity -> entity.getUuid().toString())
.orElse(null);
}
/**
* 학습 마스터 생성
*
* @param trainReq 학습 시작 요청
* @return 생성된 모델 Entity
*/
public ModelMasterEntity createTrainMaster(ModelMngDto.TrainStartReq trainReq) {
// ModelMasterEntity entity = new ModelMasterEntity();
// entity.setModelVer(trainReq.getHyperVer());
// entity.setEpochVer(String.valueOf(trainReq.getEpoch()));
// entity.setProcessStep("STEP1");
// entity.setTrainStartDttm(ZonedDateTime.now());
// entity.setEpochCnt(trainReq.getEpoch());
// entity.setDatasetRatio(trainReq.getDatasetRatio());
// entity.setDelYn(false);
// entity.setCreatedDttm(ZonedDateTime.now());
// entity.setProgressRate(0);
return null; // modelMngRepository.save(entity);
modelConfigRepository.save(entity);
}
/**
@@ -255,14 +198,26 @@ public class ModelTrainMngCoreService {
* @param uuid UUID
* @return 모델 Entity
*/
public ModelMasterEntity findByUuid(String uuid) {
public ModelMasterEntity findByUuid(UUID uuid) {
try {
java.util.UUID uuidObj = java.util.UUID.fromString(uuid);
return modelMngRepository
.findByUuid(uuidObj)
.orElseThrow(() -> new NotFoundException("모델을 찾을 수 없습니다. UUID: " + uuid));
.findByUuid(uuid)
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
} catch (IllegalArgumentException e) {
throw new BadRequestException("잘못된 UUID 형식입니다: " + uuid);
}
}
/**
* 모델학습 아이디로 config정보 조회
*
* @param uuid
* @return
*/
public ModelConfigDto.Basic findModelConfigByModelId(UUID uuid) {
ModelMasterEntity modelEntity = findByUuid(uuid);
return modelConfigRepository
.findModelConfigByModelId(modelEntity.getId())
.orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND));
}
}

View File

@@ -32,8 +32,8 @@ public class ModelMasterEntity {
@Column(name = "model_no", length = 10)
private String modelNo;
@Size(max = 50)
@Column(name = "model_ver", length = 50)
@Size(max = 200)
@Column(name = "model_ver", length = 200)
private String modelVer;
@Column(name = "model_step")

View File

@@ -1,9 +1,12 @@
package com.kamco.cd.training.postgres.repository.hyperparam;
import com.kamco.cd.training.postgres.entity.ModelHyperParamEntity;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface HyperParamRepository
extends JpaRepository<ModelHyperParamEntity, Long>, HyperParamRepositoryCustom {}
extends JpaRepository<ModelHyperParamEntity, Long>, HyperParamRepositoryCustom {
Optional<ModelHyperParamEntity> findByHyperVer(String hyperVer);
}

View File

@@ -3,4 +3,5 @@ package com.kamco.cd.training.postgres.repository.model;
import com.kamco.cd.training.postgres.entity.ModelConfigEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ModelConfigRepository extends JpaRepository<ModelConfigEntity, Long> {}
public interface ModelConfigRepository
extends JpaRepository<ModelConfigEntity, Long>, ModelConfigRepositoryCustom {}

View File

@@ -0,0 +1,8 @@
package com.kamco.cd.training.postgres.repository.model;
import com.kamco.cd.training.model.dto.ModelConfigDto;
import java.util.Optional;
public interface ModelConfigRepositoryCustom {
Optional<ModelConfigDto.Basic> findModelConfigByModelId(Long modelId);
}

View File

@@ -0,0 +1,37 @@
package com.kamco.cd.training.postgres.repository.model;
import static com.kamco.cd.training.postgres.entity.QModelConfigEntity.modelConfigEntity;
import com.kamco.cd.training.model.dto.ModelConfigDto.Basic;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class ModelConfigRepositoryImpl implements ModelConfigRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Optional<Basic> findModelConfigByModelId(Long modelId) {
return Optional.ofNullable(
queryFactory
.select(
Projections.constructor(
Basic.class,
modelConfigEntity.id,
modelConfigEntity.model.id,
modelConfigEntity.epochCount,
modelConfigEntity.trainPercent,
modelConfigEntity.validationPercent,
modelConfigEntity.testPercent,
modelConfigEntity.memo))
.from(modelConfigEntity)
.where(modelConfigEntity.model.id.eq(modelId))
.fetchOne());
}
}

View File

@@ -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<ModelMasterEntity, Long>, ModelDetailRepositoryCustom {}

View File

@@ -0,0 +1,22 @@
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<ModelMasterEntity> findByUuid(UUID uuid);
DetailSummary getModelDetailSummary(UUID uuid);
HyperSummary getByModelHyperParamSummary(UUID uuid);
List<MappingDataset> getByModelMappingDataset(UUID uuid);
ModelMasterEntity findByModelByUUID(UUID uuid);
}

View File

@@ -0,0 +1,117 @@
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<ModelMasterEntity> 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<MappingDataset> 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();
}
@Override
public ModelMasterEntity findByModelByUUID(UUID uuid) {
return queryFactory
.selectFrom(modelMasterEntity)
.where(modelMasterEntity.uuid.eq(uuid))
.fetchOne();
}
}

View File

@@ -36,6 +36,10 @@ public class ModelMngRepositoryImpl implements ModelMngRepositoryCustom {
builder.and(modelMasterEntity.statusCd.eq(req.getStatus()));
}
if (req.getModelNo() != null && !req.getModelNo().isEmpty()) {
builder.and(modelMasterEntity.modelNo.eq(req.getModelNo()));
}
List<ModelMasterEntity> content =
queryFactory
.selectFrom(modelMasterEntity)