diff --git a/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java index efc54ae8..ec9a9ec3 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/SecurityConfig.java @@ -105,7 +105,8 @@ public class SecurityConfig { "/api/layer/map/**", "/api/layer/tile-url", "/api/layer/tile-url-year", - "/api/common-code/clazz") + "/api/common-code/clazz", + "/api/inference/download/*") .permitAll() // 로그인한 사용자만 가능 IAM .requestMatchers( diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceRunController.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceRunController.java deleted file mode 100644 index 7dd6801e..00000000 --- a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceRunController.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.kamco.cd.kamcoback.inference; - -import com.kamco.cd.kamcoback.config.api.ApiResponseDto; -import com.kamco.cd.kamcoback.inference.service.InferenceRunService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -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 java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@Tag(name = "추론 실행", description = "추론 실행") -@Log4j2 -@RequestMapping("/api/inference/run") -@RequiredArgsConstructor -@RestController -public class InferenceRunController { - - private final InferenceRunService inferenceRunService; - - @Operation(summary = "추론 진행 여부 확인", description = "어드민 홈 > 추론관리 > 추론관리 > 추론관리 목록") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "검색 성공", - content = - @Content( - mediaType = "application/json", - schema = - @Schema( - description = "진행 여부 (UUID 있으면 진행중)", - type = "UUID", - example = "44709877-2e27-4fc5-bacb-8e0328c69b64"))), - @ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping - public ApiResponseDto getProcessing( - @Parameter(description = "비교년도", example = "2021") @RequestParam(required = false) - Integer compareYear, - @Parameter(description = "기준년도", example = "2022") @RequestParam(required = false) - Integer targetYear, - @Parameter(description = "모델 uuid") @RequestParam(required = false) UUID modelUuid) { - - inferenceRunService.run(compareYear, targetYear, modelUuid); - return ApiResponseDto.ok(null); - } -} diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java index 452a106b..86545422 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java @@ -246,15 +246,15 @@ public class InferenceResultDto { @NotBlank private String title; - @Schema(description = "G1", example = "b40e0f68-c1d8-49fc-93f9-a36270093861") + @Schema(description = "G1", example = "643adead-f3d2-4f10-9037-862bee919399") @NotNull private UUID model1Uuid; - @Schema(description = "G2", example = "ec92b7d2-b5a3-4915-9bdf-35fb3ca8ad27") + @Schema(description = "G2", example = "dd86b4ef-28e3-4e3d-9ee4-f60d9cb54e13") @NotNull private UUID model2Uuid; - @Schema(description = "G3", example = "37f45782-8ccf-4cf6-911c-a055a1510d39") + @Schema(description = "G3", example = "58c1153e-dec6-4424-82a1-189083a9d9dc") @NotNull private UUID model3Uuid; @@ -676,4 +676,13 @@ public class InferenceResultDto { private Long m2ModelBatchId; private Long m3ModelBatchId; } + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class MapSheetFallbackYearDto { + private String mapSheetNum; + private Integer mngYyyy; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceManualService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceManualService.java index e2128359..2035fb20 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceManualService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceManualService.java @@ -17,6 +17,8 @@ public class InferenceManualService { if (resultList.isEmpty()) {} - for (InferenceResultsTestingDto.Basic result : resultList) {} + for (InferenceResultsTestingDto.Basic result : resultList) { + System.out.println(result); + } } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java index 3fd1e243..cf7b60b9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java @@ -21,6 +21,7 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceStatusDetailDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetNumDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SaveInferenceAiDto; @@ -64,6 +65,7 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +/** 추론 관리 */ @Service @Log4j2 @RequiredArgsConstructor @@ -119,92 +121,207 @@ public class InferenceResultService { @Transactional public UUID run(InferenceResultDto.RegReq req) { + if (req.getDetectOption().equals(DetectOption.EXCL.getId())) { + return runExcl(req); + } + return runPrev(req); + } + + /** + * 변화탐지 옵션 추론제외 실행 + * + * @param req + * @return + */ + public UUID runExcl(InferenceResultDto.RegReq req) { + // target 도엽 조회 List targetDtoList = mapSheetMngCoreService.getHstMapSheetList(req); - List compareList = mapSheetMngCoreService.getMapSheetMngHst(req.getCompareYyyy()); + + // target 리스트 추출 (null 제거 + 중복 제거) List targetList = - mapSheetMngCoreService.getHstMapSheetList(req).stream() + targetDtoList.stream() .map(MngListDto::getMapSheetNum) .filter(Objects::nonNull) .distinct() .toList(); - log.info( - "hst list count compareList = {}, targetList = {}", compareList.size(), targetList.size()); + // compare 도엽번호 리스트 조회 (null 제거 + 중복 제거) + List compareList = + mapSheetMngCoreService.getMapSheetNumByHst(req.getCompareYyyy()).stream() + .filter(Objects::nonNull) + .distinct() + .toList(); + // compare 기준 Set compareSet = new HashSet<>(compareList); - Set targetSet = new HashSet<>(targetList); - long intersectionCount = - targetSet.stream() + // target 기준으로 compare에 존재하는 도엽만 필터링 + List filteredTargetList = targetList.stream().filter(compareSet::contains).toList(); + + // 도엽 비교 로그 출력 + logYearComparison(targetList, compareList, filteredTargetList); + + // compare geojson 파일 생성 + Scene compareScene = + getSceneInference( + req.getCompareYyyy().toString(), // 기준년도 + filteredTargetList, // 교집합 도엽 + req.getMapSheetScope(), // ALL / 부분 + req.getDetectOption()); // EXCL / PREV + + // target geojson 파일 생성 + Scene targetScene = + getSceneInference( + req.getTargetYyyy().toString(), // 대상년도 + filteredTargetList, // 교집합 도엽 + req.getMapSheetScope(), + req.getDetectOption()); + + // 추론 실행 + return executeInference( + req, + targetDtoList, // 전체 target 목록 + filteredTargetList, // 최종 추론 대상 + compareScene, // compare geojson + targetScene // target geojson + ); + } + + /** + * 변화탐지 옵션 이전 년도 도엽 사용 실행 + * + * @param req + * @return + */ + @Transactional + public UUID runPrev(InferenceResultDto.RegReq req) { + // target 목록 조회 + List targetDtoList = mapSheetMngCoreService.getHstMapSheetList(req); + + // target 도엽번호 리스트 추출 중복 제거 + List targetList = + targetDtoList.stream() + .map(MngListDto::getMapSheetNum) + .filter(Objects::nonNull) .distinct() - .filter(compareSet::contains) - .count(); // compare와 target에 공통으로 존재하는 도협 수 + .toList(); - long excludedTargetCount = - targetSet.stream() - .distinct() - .filter(s -> !compareSet.contains(s)) - .count(); // target 에만 존재하는 도협 수 (compare 에는 없음) + // compare 목록 조회 + List compareDtoList = + new ArrayList<>(mapSheetMngCoreService.getMapSheetNumDtoByHst(req.getCompareYyyy())); - long onlyCompareCount = - compareSet.stream() - .distinct() - .filter(s -> !targetSet.contains(s)) - .count(); // compare 에만 존재하는 도협 수 (target 에는 없음) + // compare 도엽번호 Set 구성 + Set compareSet = + compareDtoList.stream() + .map(MapSheetFallbackYearDto::getMapSheetNum) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + // target에는 있으나 compare에는 없는 도엽 추출 + List remainingTargetList = + targetList.stream().filter(s -> !compareSet.contains(s)).toList(); + + // compare에 없을때 이전 년도 사용 가능여부 조회 + List fallbackYearDtoList = + mapSheetMngCoreService.findFallbackCompareYearByMapSheets( + req.getTargetYyyy(), // 대상년도 기준 + remainingTargetList // compare에 없는 도엽들 + ); + + // 기존 compare , 사용가능 이전년도 정보 합치기 + compareDtoList.addAll(fallbackYearDtoList); + + // 중복제거하여 사용할 compare 도엽 목록 + Set availableCompareSheets = + compareDtoList.stream() + .map(MapSheetFallbackYearDto::getMapSheetNum) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + // 최종 추론 대상 도엽 + // target 기준으로 compare 에 존재하는 도엽만 추출 + List filteredTargetList = + targetList.stream().filter(availableCompareSheets::contains).toList(); + + // compareDtoList도 최종 기준으로 필터 + Set filteredTargetSet = new HashSet<>(filteredTargetList); + + List filteredCompareDtoList = + compareDtoList.stream() + .filter(d -> d.getMapSheetNum() != null) + .filter(d -> filteredTargetSet.contains(d.getMapSheetNum())) + .toList(); + + // compare only 계산 (target에는 없는 compare 도엽 수) log 용 + long compareOnlyCount = + compareDtoList.stream() + .map(MapSheetFallbackYearDto::getMapSheetNum) + .filter(s -> s != null && !targetList.contains(s)) + .count(); + + // 연도별 도엽 비교 로그 출력 log.info( """ ===== MapSheet Year Comparison ===== target Total: {} compare Total: {} Intersection: {} - target Only (Excluded from compare): {} + target Only (Excluded): {} compare Only: {} ==================================== """, - targetSet.size(), - compareSet.size(), - intersectionCount, - excludedTargetCount, - onlyCompareCount); + targetList.size(), // target count + compareDtoList.size(), // compare count + filteredTargetList.size(), // target 기준으로 compare 비교하여 최종 추론할 도엽 count + targetList.size() - filteredTargetList.size(), // compare에 존재하지 않는 target 도엽 수 + compareOnlyCount); // target 에 존재하지 않는 compare 도엽수 - List filteredTargetList = - targetSet.stream() // target 기준으로 - .filter(compareSet::contains) // compare에 있는 도협만 남김 - .toList(); + // compare 기준 geojson 생성 (년도 fallback 반영) + Scene compareScene = + getSceneInference( + filteredCompareDtoList, + req.getCompareYyyy().toString(), + req.getMapSheetScope(), + req.getDetectOption()); - Scene modelComparePath = - getSceneInference(req.getCompareYyyy().toString(), filteredTargetList, "", ""); + // target 기준 geojson 생성 + Scene targetScene = + getSceneInference( + req.getTargetYyyy().toString(), + filteredTargetList, + req.getMapSheetScope(), + req.getDetectOption()); - Scene modelTargetPath = - getSceneInference(req.getTargetYyyy().toString(), filteredTargetList, "", ""); + // AI 추론 실행 + return executeInference(req, targetDtoList, filteredTargetList, compareScene, targetScene); + } - // 작은 쪽 기준으로 탐지건수/파일생성 리스트 결정 - List imageFeatureList; - if (modelComparePath.getFeatures().size() <= modelTargetPath.getFeatures().size()) { - imageFeatureList = modelComparePath.getFeatures(); - } else { - imageFeatureList = modelTargetPath.getFeatures(); - } + /** + * learn 테이블 저장 및 AI 추론 API 호출 + * + * @param req + * @param targetDtoList + * @param filteredTargetList + * @param modelComparePath + * @param modelTargetPath + * @return + */ + private UUID executeInference( + InferenceResultDto.RegReq req, + List targetDtoList, + List filteredTargetList, + Scene modelComparePath, + Scene modelTargetPath) { + Set filteredSet = new HashSet<>(filteredTargetList); - // imageFeatureList 기준 sceneId Set - Set sceneIdSet = - imageFeatureList.stream() - .map(ImageFeature::getSceneId) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - - // targetList(List) 리턴용으로 필터링 List newTargetList = targetDtoList.stream() .filter(m -> m.getMapSheetNum() != null) - .filter(m -> sceneIdSet.contains(m.getMapSheetNum())) + .filter(m -> filteredSet.contains(m.getMapSheetNum())) .toList(); - // 목록 및 추론 대상 도엽정보 저장 UUID uuid = inferenceResultCoreService.saveInferenceInfo(req, newTargetList); - // ai 서버에 전달할 파라미터 생성 pred_requests_areas predRequestsAreas = new pred_requests_areas(); predRequestsAreas.setInput1_year(req.getCompareYyyy()); predRequestsAreas.setInput2_year(req.getTargetYyyy()); @@ -215,12 +332,9 @@ public class InferenceResultService { m1.setPred_requests_areas(predRequestsAreas); log.info("[INFERENCE] Start m1 = {}", m1); - m1.setPred_requests_areas(predRequestsAreas); - // ai 추론 실행 api 호출 Long batchId = ensureAccepted(m1); - // ai 추론 실행후 응답값 update SaveInferenceAiDto saveInferenceAiDto = new SaveInferenceAiDto(); saveInferenceAiDto.setUuid(uuid); saveInferenceAiDto.setBatchId(batchId); @@ -230,11 +344,42 @@ public class InferenceResultService { saveInferenceAiDto.setModelComparePath(modelComparePath.getFilePath()); saveInferenceAiDto.setModelTargetPath(modelTargetPath.getFilePath()); saveInferenceAiDto.setModelStartDttm(ZonedDateTime.now()); + inferenceResultCoreService.update(saveInferenceAiDto); return uuid; } + /** + * EXCL 로그 + * + * @param targetList + * @param compareList + * @param filteredTargetList + */ + private void logYearComparison( + List targetList, List compareList, List filteredTargetList) { + Set targetSet = new HashSet<>(targetList); + + long compareOnlyCount = compareList.stream().filter(s -> !targetSet.contains(s)).count(); + + log.info( + """ + ===== MapSheet Year Comparison ===== + target Total: {} + compare Total: {} + Intersection: {} + target Only (Excluded): {} + compare Only: {} + ==================================== + """, + targetList.size(), // target count + compareList.size(), // compare count + filteredTargetList.size(), // target 기준으로 compare 비교하여 최종 추론할 도엽 count + targetList.size() - filteredTargetList.size(), // compare에 존재하지 않는 target 도엽 수 + compareOnlyCount); // target 에 존재하지 않는 compare 도엽수 + } + /** * 변화탐지 실행 정보 생성 * @@ -265,31 +410,31 @@ public class InferenceResultService { List totalNumList = new ArrayList<>(); - // if (DetectOption.EXCL.getId().equals(req.getDetectOption())) { - // // "추론제외" 일때 전년도 이전 값이 있어도 전년도 도엽이 없으면 비교 안함 - // for (MngListCompareDto dto : compareList) { - // if (Objects.equals(dto.getBeforeYear(), req.getCompareYyyy())) { - // TotalListDto totalDto = new TotalListDto(); - // totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear()); - // totalDto.setMapSheetNum(dto.getMapSheetNum()); - // totalNumList.add(totalDto); - // } - // } - // } else if (DetectOption.PREV.getId().equals(req.getDetectOption())) { - // // "이전 년도 도엽 사용" 이면 전년도 이전 도엽도 사용 - // for (MngListCompareDto dto : compareList) { - // if (dto.getBeforeYear() != 0) { - // TotalListDto totalDto = new TotalListDto(); - // totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear()); - // totalDto.setMapSheetNum(dto.getMapSheetNum()); - // totalNumList.add(totalDto); - // } - // } - // } - // - // if (totalNumList.isEmpty()) { - // throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND); - // } + if (DetectOption.EXCL.getId().equals(req.getDetectOption())) { + // "추론제외" 일때 전년도 이전 값이 있어도 전년도 도엽이 없으면 비교 안함 + for (MngListCompareDto dto : compareList) { + if (Objects.equals(dto.getBeforeYear(), req.getCompareYyyy())) { + TotalListDto totalDto = new TotalListDto(); + totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear()); + totalDto.setMapSheetNum(dto.getMapSheetNum()); + totalNumList.add(totalDto); + } + } + } else if (DetectOption.PREV.getId().equals(req.getDetectOption())) { + // "이전 년도 도엽 사용" 이면 전년도 이전 도엽도 사용 + for (MngListCompareDto dto : compareList) { + if (dto.getBeforeYear() != 0) { + TotalListDto totalDto = new TotalListDto(); + totalDto.setBeforeYear(dto.getBeforeYear() == null ? 0 : dto.getBeforeYear()); + totalDto.setMapSheetNum(dto.getMapSheetNum()); + totalNumList.add(totalDto); + } + } + } + + if (totalNumList.isEmpty()) { + throw new CustomApiException("NOT_FOUND_COMPARE_YEAR", HttpStatus.NOT_FOUND); + } // 사용할 영상파일 년도 기록 및 추론에 포함되는지 설정 for (MngListDto target : targetList) { @@ -442,7 +587,7 @@ public class InferenceResultService { } /** - * 추론 AI API 호출 + * 추론 AI API 호출 batch id를 리턴 * * @param dto */ @@ -485,6 +630,7 @@ public class InferenceResultService { headers.setContentType(MediaType.APPLICATION_JSON); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + // 추론 실행 API 호출 ExternalCallResult result = externalHttpClient.call(inferenceUrl, HttpMethod.POST, dto, headers, String.class); @@ -499,12 +645,14 @@ public class InferenceResultService { objectMapper.readValue(result.body(), new TypeReference<>() {}); if (list.isEmpty()) { - throw new IllegalStateException("Inference response is empty"); + throw new CustomApiException( + "NOT_FOUND", HttpStatus.NOT_FOUND, "Inference response is empty"); } Object batchIdObj = list.get(0).get("batch_id"); if (batchIdObj == null) { - throw new IllegalStateException("batch_id not found in response"); + throw new CustomApiException( + "NOT_FOUND", HttpStatus.NOT_FOUND, "batch_id not found in response"); } return Long.valueOf(batchIdObj.toString()); @@ -523,6 +671,7 @@ public class InferenceResultService { */ private InferenceSendDto getModelInfo(UUID uuid) { + // 모델정보 조회 Basic modelInfo = modelMngCoreService.findByModelUuid(uuid); String cdModelPath = ""; @@ -580,6 +729,23 @@ public class InferenceResultService { yyyy, mapSheetNums, mapSheetScope, detectOption); } + /** + * 년도 별로 조회하여 geojson 파일 생성 + * + * @param yearDtos + * @param year + * @param mapSheetScope + * @param detectOption + * @return + */ + private Scene getSceneInference( + List yearDtos, + String year, + String mapSheetScope, + String detectOption) { + return mapSheetMngCoreService.getSceneInference(yearDtos, year, mapSheetScope, detectOption); + } + /** * 분석결과 요약정보 * diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceRunService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceRunService.java deleted file mode 100644 index a34fe9ef..00000000 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceRunService.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.kamco.cd.kamcoback.inference.service; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.kamco.cd.kamcoback.common.exception.CustomApiException; -import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene; -import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient; -import com.kamco.cd.kamcoback.config.resttemplate.ExternalHttpClient.ExternalCallResult; -import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto; -import com.kamco.cd.kamcoback.inference.dto.InferenceSendDto.pred_requests_areas; -import com.kamco.cd.kamcoback.model.dto.ModelMngDto.Basic; -import com.kamco.cd.kamcoback.model.dto.ModelMngDto.ModelType; -import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService; -import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Service; - -@Service -@Log4j2 -@RequiredArgsConstructor -public class InferenceRunService { - private final ExternalHttpClient externalHttpClient; - private final MapSheetMngCoreService mapSheetMngCoreService; - private final ModelMngCoreService modelMngCoreService; - private final ObjectMapper objectMapper; - - @Value("${spring.profiles.active}") - private String profile; - - @Value("${inference.url}") - private String inferenceUrl; - - // TODO 이거 쓰는건가? - public void run(Integer compareYear, Integer targetYear, UUID modelUuid) { - - List compareList = mapSheetMngCoreService.getMapSheetMngHst(compareYear); - List targetList = mapSheetMngCoreService.getMapSheetMngHst(targetYear); - - log.info( - "hst list count compareList = {}, targetList = {}", compareList.size(), targetList.size()); - - Set compareSet = new HashSet<>(compareList); - Set targetSet = new HashSet<>(targetList); - - long intersectionCount = - targetSet.stream() - .distinct() - .filter(compareSet::contains) - .count(); // compare와 target에 공통으로 존재하는 도협 수 - - long excludedTargetCount = - targetSet.stream() - .distinct() - .filter(s -> !compareSet.contains(s)) - .count(); // target 에만 존재하는 도협 수 (compare 에는 없음) - - long onlyCompareCount = - compareSet.stream() - .distinct() - .filter(s -> !targetSet.contains(s)) - .count(); // compare 에만 존재하는 도협 수 (target 에는 없음) - - log.info( - """ - ===== MapSheet Year Comparison ===== - target Total: {} - compare Total: {} - Intersection: {} - target Only (Excluded from compare): {} - compare Only: {} - ==================================== - """, - targetSet.size(), - compareSet.size(), - intersectionCount, - excludedTargetCount, - onlyCompareCount); - - List filteredTargetList = - targetSet.stream() // target 기준으로 - .filter(compareSet::contains) // compare에 있는 도협만 남김 - .toList(); - - Scene modelComparePath = getSceneInference(compareYear.toString(), filteredTargetList, "", ""); - - Scene modelTargetPath = getSceneInference(targetYear.toString(), filteredTargetList, "", ""); - - // ai 서버에 전달할 파라미터 생성 - pred_requests_areas predRequestsAreas = new pred_requests_areas(); - predRequestsAreas.setInput1_year(compareYear); - predRequestsAreas.setInput2_year(targetYear); - predRequestsAreas.setInput1_scene_path(modelComparePath.getFilePath()); - predRequestsAreas.setInput2_scene_path(modelTargetPath.getFilePath()); - - InferenceSendDto m1 = this.getModelInfo(modelUuid); - m1.setPred_requests_areas(predRequestsAreas); - - // ai 추론 실행 api 호출 - Long batchId = ensureAccepted(m1); - } - - private Scene getSceneInference( - String yyyy, List mapSheetNums, String mapSheetScope, String detectOption) { - return mapSheetMngCoreService.getSceneInference( - yyyy, mapSheetNums, mapSheetScope, detectOption); - } - - /** - * 추론 AI API 호출 - * - * @param dto - */ - private Long ensureAccepted(InferenceSendDto dto) { - - if (dto == null) { - log.warn("not InferenceSendDto dto"); - throw new CustomApiException("BAD_REQUEST", HttpStatus.BAD_REQUEST); - } - - // 1) 요청 로그 - try { - log.info("Inference request dto={}", objectMapper.writeValueAsString(dto)); - } catch (JsonProcessingException e) { - log.warn("Failed to serialize inference dto", e); - } - - // 3) HTTP 호출 - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setAccept(List.of(MediaType.APPLICATION_JSON)); - - ExternalCallResult result = - externalHttpClient.call(inferenceUrl, HttpMethod.POST, dto, headers, String.class); - - if (result.statusCode() < 200 || result.statusCode() >= 300) { - log.error("Inference API failed. status={}, body={}", result.statusCode(), result.body()); - throw new CustomApiException("BAD_GATEWAY", HttpStatus.BAD_GATEWAY); - } - - // 4) 응답 파싱 - try { - List> list = - objectMapper.readValue(result.body(), new TypeReference<>() {}); - - if (list.isEmpty()) { - throw new IllegalStateException("Inference response is empty"); - } - - Object batchIdObj = list.get(0).get("batch_id"); - if (batchIdObj == null) { - throw new IllegalStateException("batch_id not found in response"); - } - - return Long.valueOf(batchIdObj.toString()); - - } catch (Exception e) { - log.error("Failed to parse inference response. body={}", result.body(), e); - throw new CustomApiException("INVALID_INFERENCE_RESPONSE", HttpStatus.BAD_GATEWAY); - } - } - - /** - * 모델정보 조회 dto 생성 후 반환 - * - * @param uuid - * @return - */ - private InferenceSendDto getModelInfo(UUID uuid) { - - Basic modelInfo = modelMngCoreService.findByModelUuid(uuid); - - String cdModelPath = ""; - String cdModelConfigPath = ""; - String cdClsModelPath = ""; - - if (modelInfo.getCdModelPath() != null && modelInfo.getCdModelFileName() != null) { - cdModelPath = - Paths.get(modelInfo.getCdModelPath(), modelInfo.getCdModelFileName()).toString(); - } - - if (modelInfo.getCdModelConfig() != null && modelInfo.getCdModelConfigFileName() != null) { - cdModelConfigPath = - Paths.get(modelInfo.getCdModelConfig(), modelInfo.getCdModelConfigFileName()).toString(); - } - - if (modelInfo.getClsModelPath() != null && modelInfo.getClsModelFileName() != null) { - cdClsModelPath = - Paths.get(modelInfo.getClsModelPath(), modelInfo.getClsModelFileName()).toString(); - } - - String modelType = ""; - - if (modelInfo.getModelType().equals(ModelType.G1.getId())) { - modelType = ModelType.G1.getId(); - } else if (modelInfo.getModelType().equals(ModelType.G2.getId())) { - modelType = ModelType.G2.getId(); - } else { - modelType = ModelType.G3.getId(); - } - - InferenceSendDto sendDto = new InferenceSendDto(); - sendDto.setModel_version(modelInfo.getModelVer()); - sendDto.setCd_model_path(cdModelPath); - sendDto.setCd_model_config(cdModelConfigPath); - sendDto.setCls_model_path(cdClsModelPath); - sendDto.setCls_model_version(modelInfo.getModelVer()); - sendDto.setCd_model_type(modelType); - sendDto.setPriority(5.0); - return sendDto; - } -} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java index b8631fa0..b0dabcb9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java @@ -369,6 +369,12 @@ public class InferenceResultCoreService { return mapSheetLearnRepository.getInferenceServerStatusList(); } + /** + * 진행중 배치 조회 + * + * @param status + * @return + */ public InferenceBatchSheet getInferenceResultByStatus(String status) { MapSheetLearnEntity entity = mapSheetLearnRepository.getInferenceResultByStatus(status).orElse(null); @@ -403,6 +409,12 @@ public class InferenceResultCoreService { return mapSheetLearnRepository.getInferenceAiResultById(id, modelUuid); } + /** + * 추론 진행 현황 상세 + * + * @param uuid + * @return + */ public InferenceStatusDetailDto getInferenceStatus(UUID uuid) { return mapSheetLearnRepository.getInferenceStatus(uuid); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java index 45d8136c..c5f16364 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapSheetMngCoreService.java @@ -7,6 +7,7 @@ import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.Scene; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto; @@ -24,13 +25,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -233,27 +235,52 @@ public class MapSheetMngCoreService { } /** - * 추론 실행에 필요한 geojson 파일 생성 + * geojson 생성 * - * @param yyyy 영상관리 파일별 년도 - * @param scenes 5k 도엽 번호 리스트 - * @param mapSheetScope EXCL : 추론제외, PREV 이전 년도 도엽 사용 + * @param yyyy + * @param scenes + * @param mapSheetScope + * @param detectOption * @return */ public Scene getSceneInference( String yyyy, List scenes, String mapSheetScope, String detectOption) { + List features = loadSceneInferenceBySheets(yyyy, scenes); + return writeSceneGeoJson(yyyy, mapSheetScope, detectOption, features); + } - Map result = new HashMap<>(); + /** + * geojson 생성 + * + * @param yearDtos + * @param yyyy + * @param mapSheetScope + * @param detectOption + * @return + */ + public Scene getSceneInference( + List yearDtos, + String yyyy, + String mapSheetScope, + String detectOption) { + List features = loadSceneInferenceByFallbackYears(yearDtos); + return writeSceneGeoJson(yyyy, mapSheetScope, detectOption, features); + } + + /** + * 파일 경로/이름 , 파일 생성 , 도엽번호 반환 + * + * @param yyyy + * @param mapSheetScope + * @param detectOption + * @param sceneInference + * @return Scene + */ + private Scene writeSceneGeoJson( + String yyyy, String mapSheetScope, String detectOption, List sceneInference) { boolean isAll = MapSheetScope.ALL.getId().equals(mapSheetScope); + String optionSuffix = buildOptionSuffix(detectOption); - String optionSuffix = ""; - if (DetectOption.EXCL.getId().equals(detectOption)) { - optionSuffix = "_EXCL"; - } else if (DetectOption.PREV.getId().equals(detectOption)) { - optionSuffix = "_PREV"; - } - - // 1) 경로/파일명 결정 String targetDir = "local".equals(activeEnv) ? System.getProperty("user.home") + "/geojson" : inferenceDir; @@ -264,24 +291,11 @@ public class MapSheetMngCoreService { Path outputPath = Paths.get(targetDir, filename); - // 2) ALL일 때만 재사용 - // if (isAll && Files.exists(outputPath)) { - // return outputPath.toString(); - // } - - // 3) 데이터 조회 - List sceneInference = mapSheetMngRepository.getSceneInference(yyyy, scenes); - if (sceneInference == null || sceneInference.isEmpty()) { - log.warn( - "NOT_FOUND_TARGET_YEAR: yyyy={}, isAll={}, scenesSize={}", - yyyy, - isAll, - scenes == null ? 0 : scenes.size()); + log.warn("NOT_FOUND_TARGET_YEAR: yyyy={}, isAll={}, featuresSize={}", yyyy, isAll, 0); throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND); } - // 4) 파일 생성 try { log.info("create Directories outputPath: {}", outputPath); log.info( @@ -291,15 +305,15 @@ public class MapSheetMngCoreService { targetDir, filename); log.info("outputPath={}, parent={}", outputPath.toAbsolutePath(), outputPath.getParent()); + Files.createDirectories(outputPath.getParent()); new GeoJsonFileWriter() .exportToFile(sceneInference, "scene_inference_" + yyyy, 5186, outputPath.toString()); - log.info("GeoJsonFileWriter: {}", "scene_inference_" + yyyy); + Scene scene = new Scene(); scene.setFeatures(sceneInference); scene.setFilePath(outputPath.toString()); - return scene; } catch (IOException e) { @@ -309,6 +323,80 @@ public class MapSheetMngCoreService { } } + /** + * geojson 파일명 Suffix + * + * @param detectOption + * @return + */ + private String buildOptionSuffix(String detectOption) { + if (DetectOption.EXCL.getId().equals(detectOption)) return "_EXCL"; + if (DetectOption.PREV.getId().equals(detectOption)) return "_PREV"; + return ""; + } + + /** + * 년도, 도엽번호로 조회 + * + * @param yyyy + * @param scenes + * @return ImageFeature + */ + private List loadSceneInferenceBySheets(String yyyy, List scenes) { + List sceneInference = mapSheetMngRepository.getSceneInference(yyyy, scenes); + + if (sceneInference == null || sceneInference.isEmpty()) { + log.warn( + "NOT_FOUND_TARGET_YEAR: yyyy={}, scenesSize={}", + yyyy, + scenes == null ? 0 : scenes.size()); + throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND); + } + return sceneInference; + } + + /** + * 년도별로 나눠 조회 + * + * @param yearDtos + * @return ImageFeature + */ + private List loadSceneInferenceByFallbackYears( + List yearDtos) { + if (yearDtos == null || yearDtos.isEmpty()) { + return List.of(); + } + + // 년도 별로 루프를 돌리기위해 년도별 정리 + Map> groupedByYear = + yearDtos.stream() + .filter(d -> d.getMngYyyy() != null && d.getMapSheetNum() != null) + .collect(Collectors.groupingBy(MapSheetFallbackYearDto::getMngYyyy)); + + List sceneInference = new ArrayList<>(); + + for (Map.Entry> entry : groupedByYear.entrySet()) { + Integer year = entry.getKey(); + + // 년도별 mapSheetNum 만들기 + List sheetNums = + entry.getValue().stream() + .map(MapSheetFallbackYearDto::getMapSheetNum) + .filter(Objects::nonNull) + .distinct() + .toList(); + + // tif파일 정보 조회 + List temp = mapSheetMngRepository.getSceneInference(year.toString(), sheetNums); + + if (temp != null && !temp.isEmpty()) { + sceneInference.addAll(temp); + } + } + + return sceneInference; + } + /** * 변화탐지 실행 가능 기준 년도 조회 * @@ -343,8 +431,30 @@ public class MapSheetMngCoreService { return mapSheetMngYearRepository.findByHstMapSheetCompareList(mngYyyy, mapId); } - public List getMapSheetMngHst(Integer year) { + public List getMapSheetNumByHst(Integer year) { List entity = mapSheetMngRepository.getMapSheetMngHst(year); return entity.stream().map(MapSheetMngHstEntity::getMapSheetNum).toList(); } + + /** + * 특정 연도의 도엽 이력 데이터를 조회 + * + * @param year + * @return + */ + public List getMapSheetNumDtoByHst(Integer year) { + List entity = mapSheetMngRepository.getMapSheetMngHst(year); + return entity.stream() + .map( + e -> + new MapSheetFallbackYearDto( + e.getMapSheetNum(), e.getMngYyyy() // 조회 기준 연도 + )) + .toList(); + } + + public List findFallbackCompareYearByMapSheets( + Integer year, List mapIds) { + return mapSheetMngRepository.findFallbackCompareYearByMapSheets(year, mapIds); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryCustom.java index 41ad1fa2..8f6c8c38 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryCustom.java @@ -18,29 +18,101 @@ import org.springframework.data.domain.Page; public interface MapSheetLearnRepositoryCustom { + /** + * 추론 관리 목록 조회 + * + * @param req 추론관리 목록 화면 조회 조건 + * @return 추론 관리 목록 + */ Page getInferenceMgnResultList(InferenceResultDto.SearchListReq req); + /** + * uuid 조건으로 추론 실행 정보 조회 + * + * @param uuid uuid + * @return 추론 실행 정보 + */ Optional getInferenceResultByUuid(UUID uuid); + /** + * 추론 실행중 서버정보 조회 cpu, gpu + * + * @return cpu, gpu 정보 + */ List getInferenceServerStatusList(); + /** + * 추론 실행 목록 진행 상태별 조회 + * + * @param status 추론 진행 상태 + * @return 추론 실행 정보 + */ Optional getInferenceResultByStatus(String status); + /** + * 등록된 추론 실행목록 및 등록된 모델 정보 조회 + * + * @param id 추론 실행 테이블 id + * @param modelUuid 모델 uuid + * @return 모델 정보 + */ InferenceProgressDto getInferenceAiResultById(Long id, UUID modelUuid); + /** + * 진행중인 추론 정보 상세 조회 + * + * @param uuid 추론진행 uuid + * @return 진행중인 추론정보 상세 정보 + */ InferenceStatusDetailDto getInferenceStatus(UUID uuid); + /** + * 진행중인 추론이 있는지 조회 + * + * @return 진행중인 추론 정보 + */ MapSheetLearnEntity getProcessing(); Integer getLearnStage(Integer compareYear, Integer targetYear); + /** + * 추론 결과 정보 조회 + * + * @param uuid 추론 uuid + * @return 추론 결과 및 사용 모델 정보 + */ AnalResultInfo getInferenceResultInfo(UUID uuid); + /** + * 추론 결과 bbox, point 조회 + * + * @param uuid 추론 uuid + * @return bbox, pont 정보 + */ BboxPointDto getBboxPoint(UUID uuid); + /** + * 분류별 탐지건수 조회 + * + * @param uuid 추론 uuid + * @return 분류별 탐지건수 정보 + */ List getInferenceClassCountList(UUID uuid); + /** + * 추론 결과 상세 geom 목록 조회 + * + * @param uuid 추론 uuid + * @param searchGeoReq 추론 결과 상세화면 geom 조회 조건 + * @return geom 목록 정보 + */ Page getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq); + /** + * 국유in연동 가능여부 확인 조회 + * + * @param uuid 추론 uuid + * @return 추론 존재여부, 부분도엽 여부, 추론 진행중 여부, 국유인 작업 진행중 여부 + */ GukYuinLinkFacts findLinkFacts(UUID uuid); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java index a03bcd4b..406b5a9a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java @@ -528,6 +528,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto @Override public GukYuinLinkFacts findLinkFacts(UUID uuid) { + // 해당 추론 있는지 확인 MapSheetLearnEntity learn = queryFactory .selectFrom(QMapSheetLearnEntity.mapSheetLearnEntity) @@ -538,12 +539,14 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto return new GukYuinLinkFacts(false, false, false, false); } + // 부분 도엽 실행인지 확인 boolean isPartScope = MapSheetScope.PART.getId().equals(learn.getMapSheetScope()); QMapSheetAnalInferenceEntity inf = QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; QMapSheetLearnEntity learn2 = new QMapSheetLearnEntity("learn2"); QMapSheetLearnEntity learnQ = QMapSheetLearnEntity.mapSheetLearnEntity; + // 실행중인 추론 있는지 확인 boolean hasRunningInference = queryFactory .selectOne() @@ -557,6 +560,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto .fetchFirst() != null; + // 국유인 작업 진행중 있는지 확인 boolean hasOtherUnfinishedGukYuin = queryFactory .selectOne() diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java index a5e14356..9cfa1af2 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryCustom.java @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.postgres.repository.mapsheet; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto; @@ -82,4 +83,7 @@ public interface MapSheetMngRepositoryCustom { void insertMapSheetMngTile(@Valid AddReq addReq); List getMapSheetMngHst(Integer year); + + List findFallbackCompareYearByMapSheets( + Integer year, List mapIds); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java index 68c181e3..e6bbc270 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java @@ -5,12 +5,14 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheet import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity.mapSheetMngFileEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngTileEntity.mapSheetMngTileEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngYearYnEntity.mapSheetMngYearYnEntity; import static com.kamco.cd.kamcoback.postgres.entity.QYearEntity.yearEntity; import static com.querydsl.core.types.dsl.Expressions.nullExpression; import com.kamco.cd.kamcoback.common.enums.CommonUseStatus; import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter.ImageFeature; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetFallbackYearDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheetScope; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.AddReq; @@ -1119,4 +1121,24 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport .and(mapSheetMngFileEntity.fileExt.eq("tif"))) .fetch(); } + + @Override + public List findFallbackCompareYearByMapSheets( + Integer year, List mapIds) { + BooleanBuilder builder = new BooleanBuilder(); + builder.and(mapSheetMngYearYnEntity.id.mapSheetNum.in(mapIds)); + builder.and(mapSheetMngYearYnEntity.id.mngYyyy.lt(year)); + builder.and(mapSheetMngYearYnEntity.yn.eq("Y")); + + return queryFactory + .select( + Projections.constructor( + MapSheetFallbackYearDto.class, + mapSheetMngYearYnEntity.id.mapSheetNum, + mapSheetMngYearYnEntity.id.mngYyyy.max())) + .from(mapSheetMngYearYnEntity) + .where(builder) + .groupBy(mapSheetMngYearYnEntity.id.mapSheetNum) + .fetch(); + } }