Merge branch 'develop' of https://kamco.git.gs.dabeeo.com/MVPTeam/kamco-cd-api into feat/infer_dev_260211

# Conflicts:
#	src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceSendDto.java
#	src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceRunService.java
This commit is contained in:
2026-02-26 09:53:11 +09:00
4 changed files with 269 additions and 15 deletions

View File

@@ -14,6 +14,9 @@ import lombok.ToString;
@ToString
public class InferenceSendDto {
private static final String DEV_PATH_PREFIX = "kamco-nfs";
private static final String PROD_PATH_PREFIX = "data";
private pred_requests_areas pred_requests_areas;
private String model_version;
private String cd_model_path;
@@ -23,12 +26,17 @@ public class InferenceSendDto {
private String cd_model_type;
private Double priority;
public String getCd_model_path() {
return cd_model_path == null ? null : cd_model_path.replace("kamcd-nfs", "data");
// 프로덕션은 경로가 바뀜
public void changeValForProd() {
if (pred_requests_areas != null) {
pred_requests_areas.changeValForProd();
}
if (this.cd_model_path != null) {
this.cd_model_path = this.cd_model_path.replace(DEV_PATH_PREFIX, PROD_PATH_PREFIX);
}
if (this.cls_model_path != null) {
this.cls_model_path = this.cls_model_path.replace(DEV_PATH_PREFIX, PROD_PATH_PREFIX);
}
public String getCls_model_path() {
return cls_model_path == null ? null : cls_model_path.replace("kamcd-nfs", "data");
}
@Getter
@@ -43,13 +51,18 @@ public class InferenceSendDto {
private String input1_scene_path;
private String input2_scene_path;
public String getInput1_scene_path() {
return input1_scene_path == null ? null : input1_scene_path.replace("kamcd-nfs", "data");
public void changeValForProd() {
// ai팀에서 kamco-nfs를 data로 세팅했음
if (this.input1_scene_path != null) {
this.input1_scene_path =
this.input1_scene_path.replace(
InferenceSendDto.DEV_PATH_PREFIX, InferenceSendDto.PROD_PATH_PREFIX);
}
if (this.input2_scene_path != null) {
this.input2_scene_path =
this.input2_scene_path.replace(
InferenceSendDto.DEV_PATH_PREFIX, InferenceSendDto.PROD_PATH_PREFIX);
}
public String getInput2_scene_path() {
return input2_scene_path == null ? null : input2_scene_path.replace("kamcd-nfs", "data");
}
}
}

View File

@@ -713,6 +713,14 @@ public class InferenceResultService {
sendDto.setCls_model_version(modelInfo.getModelVer());
sendDto.setCd_model_type(modelType);
sendDto.setPriority(5d);
// 운영환경일때 경로수정 dean 260226
if (profile != null && profile.equals("prod")) {
log.info("profile = {} change inforence req", profile);
sendDto.changeValForProd();
}
log.info("[Inference Send]SendDto={}", sendDto);
return sendDto;
}

View File

@@ -0,0 +1,229 @@
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;
public void run(Integer compareYear, Integer targetYear, UUID modelUuid) {
List<String> compareList = mapSheetMngCoreService.getMapSheetMngHst(compareYear);
List<String> targetList = mapSheetMngCoreService.getMapSheetMngHst(targetYear);
log.info(
"hst list count compareList = {}, targetList = {}", compareList.size(), targetList.size());
Set<String> compareSet = new HashSet<>(compareList);
Set<String> 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<String> 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<String> 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.debug("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<String> 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<Map<String, Object>> 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);
// 운영환경일때 경로수정 dean 260226
if (profile != null && profile.equals("prod")) {
log.info("profile = {} change inforence req", profile);
sendDto.changeValForProd();
}
return sendDto;
}
}

View File

@@ -355,8 +355,12 @@ public class MapSheetInferenceJobService {
m.setCls_model_version(progressDto.getClsModelVersion());
m.setCd_model_type(type);
m.setPriority(5d);
// log.info("InferenceSendDto={}", m);
// 운영환경일때 경로수정 dean 260226
if (profile != null && profile.equals("prod")) {
log.info("profile = {} [M]change inforence req", profile);
m.changeValForProd();
}
log.info("[Inference]SendDto={}", m);
// 추론 실행 api 호출
Long batchId = ensureAccepted(m);