[KC-103] 추론 실행 조건 변경, exception 처리 추가

This commit is contained in:
2026-01-14 14:25:14 +09:00
parent 91b09a917e
commit 57438830c6
14 changed files with 359 additions and 201 deletions

View File

@@ -1,6 +1,6 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.common.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard;
@@ -10,9 +10,9 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceProgressDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
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.MapSheetNumDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.SaveInferenceAiDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
@@ -28,14 +28,17 @@ import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.dao.DataAccessException;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Log4j2
@RequiredArgsConstructor
public class InferenceResultCoreService {
@@ -63,17 +66,26 @@ public class InferenceResultCoreService {
*
* @param req
*/
public UUID saveInferenceInfo(InferenceResultDto.RegReq req) {
public UUID saveInferenceInfo(InferenceResultDto.RegReq req, List<MngListDto> targetList) {
String firstMapSheetName = null;
String mapSheetName = "";
int detectingCnt = 0;
String mapSheetName =
req.getMapSheetNum().get(0).getMapSheetName()
+ ""
+ (req.getMapSheetNum().size() - 1)
+ "";
for (MngListDto dto : targetList) {
if (Boolean.TRUE.equals(dto.getIsSuccess())) {
if (detectingCnt == 0) {
firstMapSheetName = dto.getMapSheetName();
}
detectingCnt++;
}
}
if (req.getMapSheetNum().size() == 1) {
mapSheetName =
req.getMapSheetNum().get(0).getMapSheetName() + " " + req.getMapSheetNum().size() + "";
if (detectingCnt == 0) {
mapSheetName = "";
} else if (detectingCnt == 1) {
mapSheetName = firstMapSheetName + " 1건";
} else {
mapSheetName = firstMapSheetName + "" + (detectingCnt - 1) + "";
}
MapSheetLearnEntity mapSheetLearnEntity = new MapSheetLearnEntity();
@@ -87,7 +99,9 @@ public class InferenceResultCoreService {
mapSheetLearnEntity.setDetectOption(req.getDetectOption());
mapSheetLearnEntity.setCreatedUid(userUtil.getId());
mapSheetLearnEntity.setMapSheetCnt(mapSheetName);
mapSheetLearnEntity.setDetectingCnt((long) req.getMapSheetNum().size());
mapSheetLearnEntity.setDetectingCnt((long) detectingCnt);
mapSheetLearnEntity.setStage(
mapSheetLearnRepository.getLearnStage(req.getCompareYyyy(), req.getTargetYyyy()));
// learn 테이블 저장
MapSheetLearnEntity savedLearn = mapSheetLearnRepository.save(mapSheetLearnEntity);
@@ -95,14 +109,16 @@ public class InferenceResultCoreService {
final int CHUNK = 1000;
List<MapSheetLearn5kEntity> buffer = new ArrayList<>(CHUNK);
// learn 도엽 저장
for (MapSheetNumDto mapSheetNum : req.getMapSheetNum()) {
MapSheetLearn5kEntity e = new MapSheetLearn5kEntity();
e.setLearn(savedLearn);
e.setMapSheetNum(Long.parseLong(mapSheetNum.getMapSheetNum()));
e.setCreatedUid(userUtil.getId());
buffer.add(e);
// learn 도엽 저장
for (MngListDto mngDto : targetList) {
MapSheetLearn5kEntity entity = new MapSheetLearn5kEntity();
entity.setLearn(savedLearn);
entity.setMapSheetNum(Long.parseLong(mngDto.getMapSheetNum()));
entity.setBeforeYear(Integer.valueOf(mngDto.getBeforeYear()));
entity.setIsSuccess(mngDto.getIsSuccess() != null && mngDto.getIsSuccess());
entity.setCreatedUid(userUtil.getId());
buffer.add(entity);
if (buffer.size() == CHUNK) {
flushChunk(buffer);
buffer.clear();
@@ -117,33 +133,24 @@ public class InferenceResultCoreService {
return savedLearn.getUuid();
}
/**
* 도엽별 저장
*
* @param buffer
*/
private void flushChunk(List<MapSheetLearn5kEntity> buffer) {
// 청크 번호 추출 in 조건 만들기
List<String> chunkNums =
buffer.stream().map(e -> String.valueOf(e.getMapSheetNum())).distinct().toList();
// 추론 제외
List<MapInkx5kEntity> usedEntities =
mapInkx5kRepository.findByMapSheetNumInAndUseInference(chunkNums, CommonUseStatus.USE);
// TODO 추론 제외 했으면 파일 있는지도 확인 해야함
// 조회 결과에서 번호만 Set으로
Set<String> usedSet =
usedEntities.stream()
.map(MapInkx5kEntity::getMapidcdNo)
.collect(java.util.stream.Collectors.toSet());
// 필터 후 저장
List<MapSheetLearn5kEntity> toSave =
buffer.stream().filter(e -> usedSet.contains(String.valueOf(e.getMapSheetNum()))).toList();
if (!toSave.isEmpty()) {
mapSheetLearn5kRepository.saveAll(toSave);
mapSheetLearn5kRepository.flush();
try {
if (!buffer.isEmpty()) {
mapSheetLearn5kRepository.saveAll(buffer);
mapSheetLearn5kRepository.flush();
}
} catch (DataAccessException e) {
log.error("FAIL_SAVE_MAP_SHEET(도엽 저장 중 오류가 발생했습니다.): bufferSize={}", buffer.size(), e);
throw new CustomApiException("FAIL_SAVE_MAP_SHEET", HttpStatus.INTERNAL_SERVER_ERROR);
} finally {
entityManager.clear();
}
entityManager.clear();
}
/****/
@@ -354,4 +361,13 @@ public class InferenceResultCoreService {
public UUID getProcessing() {
return mapSheetLearnRepository.getProcessing();
}
/**
* @param compareYear 비교년도
* @param targetYear 기준년도
* @return
*/
public Integer getLearnStage(Integer compareYear, Integer targetYear) {
return mapSheetLearnRepository.getLearnStage(compareYear, targetYear);
}
}

View File

@@ -1,7 +1,10 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.common.exception.CustomApiException;
import com.kamco.cd.kamcoback.common.geometry.GeoJsonFileWriter;
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.MapSheetScope;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
@@ -14,6 +17,7 @@ import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.Valid;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
@@ -23,6 +27,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -206,49 +211,82 @@ public class MapSheetMngCoreService {
}
}
public String getSceneInference(String yyyy, List<String> scenes) {
String outputPath = "";
/**
* 추론 실행에 필요한 geojson 파일 생성
*
* @param yyyy 영상관리 파일별 년도
* @param scenes 5k 도엽 번호 리스트
* @param mapSheetScope EXCL : 추론제외, PREV 이전 년도 도엽 사용
* @return
*/
public String getSceneInference(String yyyy, List<String> scenes, String mapSheetScope) {
try {
List<ImageFeature> sceneInference = mapSheetMngRepository.getSceneInference(yyyy, scenes);
boolean isAll = MapSheetScope.ALL.getId().equals(mapSheetScope);
if (sceneInference == null || sceneInference.isEmpty()) {
log.warn("No scene data found for year: {}", yyyy);
return outputPath;
}
// 1) 경로/파일명 결정
String targetDir =
"local".equals(activeEnv) ? System.getProperty("user.home") + "/geojson" : inferenceDir;
if (activeEnv.equals("local")) {
inferenceDir = System.getProperty("user.home") + "/geojson";
}
String filename = String.format("%s_%s.geojson", yyyy, activeEnv);
outputPath = Paths.get(inferenceDir, filename).toString();
String filename =
isAll
? String.format("%s_%s_ALL.geojson", yyyy, activeEnv)
: String.format("%s_%s.geojson", yyyy, activeEnv);
// 디렉토리가 없으면 생성
Files.createDirectories(Paths.get(inferenceDir));
Path outputPath = Paths.get(targetDir, filename);
// GeoJSON 파일 생성 (EPSG:5186 - Korea 2000 / Central Belt 2010)
GeoJsonFileWriter writer = new GeoJsonFileWriter();
writer.exportToFile(sceneInference, "scene_inference_" + yyyy, 5186, outputPath);
log.info("GeoJSON file created successfully: {}", outputPath);
log.info("Total features exported: {}", sceneInference.size());
} catch (IOException e) {
log.error("Failed to create GeoJSON file for year: {}", yyyy, e);
throw new RuntimeException("GeoJSON 파일 생성 실패: " + e.getMessage(), e);
// 2) ALL일 때만 재사용
if (isAll && Files.exists(outputPath)) {
return outputPath.toString();
}
return outputPath;
// 3) 데이터 조회 (파일 없을 때만)
List<ImageFeature> 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());
throw new CustomApiException("NOT_FOUND_TARGET_YEAR", HttpStatus.NOT_FOUND);
}
// 4) 파일 생성
try {
Files.createDirectories(outputPath.getParent());
new GeoJsonFileWriter()
.exportToFile(sceneInference, "scene_inference_" + yyyy, 5186, outputPath.toString());
return outputPath.toString();
} catch (IOException e) {
log.error(
"FAIL_CREATE_MAP_SHEET_FILE: yyyy={}, isAll={}, path={}", yyyy, isAll, outputPath, e);
throw new CustomApiException("FAIL_CREATE_MAP_SHEET_FILE", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
public List<MngListDto> getHstMapSheetList(int mngYyyy) {
return mapSheetMngRepository.findByHstMapSheetTargetList(mngYyyy);
/**
* 변화탐지 실행 가능 기준 년도 조회
*
* @param req
* @return
*/
public List<MngListDto> getHstMapSheetList(InferenceResultDto.RegReq req) {
return mapSheetMngRepository.findByHstMapSheetTargetList(req);
}
public List<MngListDto> getHstMapSheetList(int mngYyyy, List<String> mapIds) {
return mapSheetMngRepository.findByHstMapSheetTargetList(mngYyyy, mapIds);
}
/**
* 변화탐지 실행 가능 비교년도 조회
*
* @param mngYyyy
* @param mapId
* @return
*/
public List<MngListCompareDto> getByHstMapSheetCompareList(int mngYyyy, List<String> mapId) {
return mapSheetMngRepository.findByHstMapSheetCompareList(mngYyyy, mapId);
}

View File

@@ -48,4 +48,10 @@ public class MapSheetLearn5kEntity {
@Column(name = "created_uid")
private Long createdUid;
@Column(name = "befroe_year")
private Integer beforeYear;
@Column(name = "is_success")
private Boolean isSuccess;
}

View File

@@ -142,6 +142,9 @@ public class MapSheetLearnEntity {
@Column(name = "m3_model_end_dttm")
private ZonedDateTime m3ModelEndDttm;
@Column(name = "stage")
private Integer stage;
public InferenceResultDto.ResultList toDto() {
return new InferenceResultDto.ResultList(
this.uuid,

View File

@@ -25,4 +25,6 @@ public interface MapSheetLearnRepositoryCustom {
InferenceStatusDetailDto getInferenceStatus(UUID uuid);
UUID getProcessing();
Integer getLearnStage(Integer compareYear, Integer targetYear);
}

View File

@@ -239,4 +239,26 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.where(mapSheetLearnEntity.status.eq("IN_PROGRESS"))
.fetchOne();
}
@Override
public Integer getLearnStage(Integer compareYear, Integer targetYear) {
MapSheetLearnEntity entity = new MapSheetLearnEntity();
entity.setCompareYyyy(compareYear);
entity.setTargetYyyy(targetYear);
Integer stage =
queryFactory
.select(mapSheetLearnEntity.stage)
.from(mapSheetLearnEntity)
.where(
mapSheetLearnEntity
.compareYyyy
.eq(compareYear)
.and(mapSheetLearnEntity.targetYyyy.eq(targetYear)))
.orderBy(mapSheetLearnEntity.stage.desc())
.limit(1)
.fetchOne();
return stage == null ? 1 : stage + 1;
}
}

View File

@@ -1,6 +1,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.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
@@ -61,7 +62,7 @@ public interface MapSheetMngRepositoryCustom {
List<MapSheetMngDto.MngFilesDto> findByHstUidMapSheetFileList(Long hstUid);
List<MngListDto> findByHstMapSheetTargetList(int mngYyyy);
List<MngListDto> findByHstMapSheetTargetList(InferenceResultDto.RegReq req);
List<MngListDto> findByHstMapSheetTargetList(int mngYyyy, List<String> mapIds);

View File

@@ -5,8 +5,11 @@ 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.QYearEntity.yearEntity;
import static com.querydsl.core.types.dsl.Expressions.nullExpression;
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.MapSheetScope;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.MngListDto;
@@ -478,23 +481,57 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
return foundContent;
}
/**
* 기준년도 추론 실행 가능 도엽 조회
*
* @param req
* @return
*/
@Override
public List<MngListDto> findByHstMapSheetTargetList(int mngYyyy) {
public List<MngListDto> findByHstMapSheetTargetList(InferenceResultDto.RegReq req) {
BooleanBuilder whereBuilder = new BooleanBuilder();
whereBuilder.and(mapSheetMngHstEntity.dataState.eq("DONE"));
whereBuilder.and(
mapSheetMngHstEntity
.syncState
.eq("DONE")
.or(mapSheetMngHstEntity.syncCheckState.eq("DONE")));
whereBuilder.and(mapSheetMngHstEntity.useInference.eq("USE"));
whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(req.getTargetYyyy()));
BooleanBuilder likeBuilder = new BooleanBuilder();
if (MapSheetScope.PART.getId().equals(req.getMapSheetScope())) {
List<String> list = req.getMapSheetNum();
if (list == null || list.isEmpty()) {
return List.of();
}
for (String prefix : list) {
if (prefix == null || prefix.isBlank()) {
continue;
}
likeBuilder.or(mapSheetMngHstEntity.mapSheetNum.like(prefix.trim() + "%"));
}
}
if (likeBuilder.hasValue()) {
whereBuilder.and(likeBuilder);
}
return queryFactory
.select(
Projections.constructor(
MngListDto.class,
mapSheetMngHstEntity.mngYyyy,
mapSheetMngHstEntity.mapSheetNum,
mapSheetMngHstEntity.mapSheetName))
mapSheetMngHstEntity.mapSheetName,
nullExpression(String.class),
nullExpression(Boolean.class)))
.from(mapSheetMngHstEntity)
.where(
mapSheetMngHstEntity
.mngYyyy
.eq(mngYyyy)
.and(mapSheetMngHstEntity.dataState.eq("DONE"))
.and(mapSheetMngHstEntity.syncState.eq("DONE"))
.and(mapSheetMngHstEntity.useInference.eq("USE")))
.where(whereBuilder)
.fetch();
}
@@ -519,6 +556,13 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport
.fetch();
}
/**
* 변화탐지 실행 가능 비교년도 조회
*
* @param mngYyyy
* @param mapIds
* @return
*/
@Override
public List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapIds) {

View File

@@ -22,7 +22,4 @@ public interface MapInkx5kRepositoryCustom {
Page<MapInkx5kEntity> getSceneListByPage(
CommonUseStatus useInference, String searchVal, searchReq searchReq);
List<MapInkx5kEntity> findByMapSheetNumInAndUseInference(
List<String> mapSheetNums, CommonUseStatus use);
}

View File

@@ -129,19 +129,6 @@ public class MapInkx5kRepositoryImpl extends QuerydslRepositorySupport
return new PageImpl<>(content, pageable, count);
}
@Override
public List<MapInkx5kEntity> findByMapSheetNumInAndUseInference(
List<String> mapSheetNums, CommonUseStatus use) {
if (mapSheetNums == null || mapSheetNums.isEmpty()) {
return List.of();
}
return queryFactory
.selectFrom(mapInkx5kEntity)
.where(mapInkx5kEntity.mapidcdNo.in(mapSheetNums), mapInkx5kEntity.useInference.eq(use))
.fetch();
}
private BooleanExpression searchUseInference(CommonUseStatus useInference) {
if (Objects.isNull(useInference)) {
return null;