Merge remote-tracking branch 'origin/feat/infer_dev_260107' into feat/infer_dev_260107

# Conflicts:
#	src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java
This commit is contained in:
2026-01-16 12:34:24 +09:00
11 changed files with 178 additions and 171 deletions

View File

@@ -323,9 +323,9 @@ public class InferenceResultApiController {
})
@GetMapping("/infer-result-info")
public ApiResponseDto<InferenceDetailDto.AnalResultInfo> getInferenceResultInfo(
@Parameter(description = "회차 uuid", example = "932fbd72-2e8e-4a49-b189-09046787f9d1")
@Parameter(description = "회차 uuid", example = "f30e8817-9625-4fff-ba43-c1e6ed2067c4")
@RequestParam
String uuid) {
UUID uuid) {
return ApiResponseDto.ok(inferenceResultService.getInferenceResultInfo(uuid));
}
@@ -344,9 +344,9 @@ public class InferenceResultApiController {
})
@GetMapping("/infer-class-count")
public ApiResponseDto<List<InferenceDetailDto.Dashboard>> getInferenceClassCountList(
@Parameter(description = "회차 uuid", example = "8584e8d4-53b3-4582-bde2-28a81495a626")
@Parameter(description = "회차 uuid", example = "242750c5-a627-429b-950a-dce5a87c1c01")
@RequestParam
String uuid) {
UUID uuid) {
return ApiResponseDto.ok(inferenceResultService.getInferenceClassCountList(uuid));
}
@@ -365,7 +365,7 @@ public class InferenceResultApiController {
})
@GetMapping("/geom-list")
public ApiResponseDto<Page<InferenceDetailDto.Geom>> getInferenceGeomList(
@Parameter(description = "회차 uuid", example = "8584e8d4-53b3-4582-bde2-28a81495a626")
@Parameter(description = "회차 uuid", example = "242750c5-a627-429b-950a-dce5a87c1c01")
@RequestParam(required = true)
UUID uuid,
@Parameter(description = "기준년도 분류", example = "land") @RequestParam(required = false)

View File

@@ -472,6 +472,7 @@ public class InferenceDetailDto {
? Duration.between(inferStartDttm, inferEndDttm)
: null;
if (elapsed != null) {
long seconds = elapsed.getSeconds();
long abs = Math.abs(seconds);
@@ -482,4 +483,5 @@ public class InferenceDetailDto {
this.elapsedDuration = String.format("%02d:%02d:%02d", h, m, s);
}
}
}
}

View File

@@ -62,6 +62,7 @@ public class InferenceResultService {
private final ModelMngCoreService modelMngCoreService;
private final ExternalHttpClient externalHttpClient;
private final ObjectMapper objectMapper;
private final UserUtil userUtil;
@Value("${inference.url}")
private String inferenceUrl;
@@ -72,8 +73,6 @@ public class InferenceResultService {
@Value("${spring.profiles.active}")
private String profile;
private final UserUtil userUtil;
/**
* 추론관리 목록
*
@@ -494,11 +493,11 @@ public class InferenceResultService {
return dto;
}
public AnalResultInfo getInferenceResultInfo(String uuid) {
public AnalResultInfo getInferenceResultInfo(UUID uuid) {
return inferenceResultCoreService.getInferenceResultInfo(uuid);
}
public List<Dashboard> getInferenceClassCountList(String uuid) {
public List<Dashboard> getInferenceClassCountList(UUID uuid) {
return inferenceResultCoreService.getInferenceClassCountList(uuid);
}

View File

@@ -256,35 +256,23 @@ public class LabelAllocateApiController {
mediaType = "application/json",
schema = @Schema(implementation = UpdateClosedRequest.class),
examples = {
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "라벨링 종료",
value =
"""
{"closedType": "LABELING", "closedYn": "Y"}
"""),
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "검수 종료",
value =
"""
{"closedType": "INSPECTION", "closedYn": "Y"}
"""),
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "라벨링 재개",
value =
"""
{"closedType": "LABELING", "closedYn": "N"}
"""),
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "검수 재개",
value =
"""
{"closedType": "INSPECTION", "closedYn": "N"}
"""),
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "특정 프로젝트 라벨링 전체 종료",
value =
"""
{"uuid": "f97dc186-e6d3-4645-9737-3173dde8dc64", "closedType": "LABELING", "closedYn": "Y"}
"""),
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "특정 프로젝트 검수 전체 종료",
value =
"""
{"uuid": "f97dc186-e6d3-4645-9737-3173dde8dc64", "closedType": "INSPECTION", "closedYn": "Y"}
"""),
@io.swagger.v3.oas.annotations.media.ExampleObject(
name = "특정 프로젝트 라벨링+검수 동시 종료",
value =
"""
{"uuid": "f97dc186-e6d3-4645-9737-3173dde8dc64", "closedType": "BOTH", "closedYn": "Y"}
""")
}))
@RequestBody
@@ -294,7 +282,17 @@ public class LabelAllocateApiController {
labelAllocateService.updateClosedYn(
request.getUuid(), request.getClosedType(), request.getClosedYn());
String typeLabel = "LABELING".equals(request.getClosedType()) ? "라벨링" : "검수";
String typeLabel;
if ("LABELING".equals(request.getClosedType())) {
typeLabel = "라벨링";
} else if ("INSPECTION".equals(request.getClosedType())) {
typeLabel = "검수";
} else if ("BOTH".equals(request.getClosedType())) {
typeLabel = "라벨링 및 검수";
} else {
typeLabel = "작업";
}
String statusMessage =
"Y".equals(request.getClosedYn())
? typeLabel + "이(가) 종료되었습니다."

View File

@@ -57,13 +57,14 @@ public class WorkerStatsDto {
example = "f97dc186-e6d3-4645-9737-3173dde8dc64")
private String uuid;
@NotBlank(message = "종료 유형은 필수입니다.")
@Pattern(regexp = "^(LABELING|INSPECTION)$", message = "종료 유형은 LABELING 또는 INSPECTION이어야 합니다.")
@Pattern(
regexp = "^(LABELING|INSPECTION|BOTH)$",
message = "종료 유형은 LABELING, INSPECTION 또는 BOTH 이어야 합니다.")
@Schema(
description = "종료 유형 (LABELING: 라벨링, INSPECTION: 검수)",
description = "종료 유형 (LABELING: 라벨링, INSPECTION: 검수만, BOTH: 라벨링+검수 동시)",
example = "LABELING",
allowableValues = {"LABELING", "INSPECTION"},
requiredMode = Schema.RequiredMode.REQUIRED)
allowableValues = {"LABELING", "INSPECTION", "BOTH"},
requiredMode = Schema.RequiredMode.NOT_REQUIRED)
private String closedType;
@NotBlank(message = "종료 여부는 필수입니다.")

View File

@@ -234,13 +234,19 @@ public class LabelAllocateService {
* 프로젝트 종료 여부 업데이트
*
* @param uuid 프로젝트 UUID (선택, 미입력 시 최신 프로젝트 대상)
* @param closedType 종료 유형 (LABELING/INSPECTION)
* @param closedType 종료 유형 (LABELING/INSPECTION/BOTH)
* @param closedYn 종료 여부 (Y/N)
*/
@Transactional
public void updateClosedYn(String uuid, String closedType, String closedYn) {
String targetUuid = uuid;
// closedType 유효성 검증
if (closedType == null || closedType.isBlank()) {
throw new IllegalArgumentException(
"종료 유형(closedType)은 필수입니다. (LABELING, INSPECTION, BOTH 중 하나)");
}
// uuid가 없으면 최신 프로젝트 uuid 조회
if (targetUuid == null || targetUuid.isBlank()) {
var latestProjectInfo = labelAllocateCoreService.findLatestProjectInfo();

View File

@@ -403,11 +403,11 @@ public class InferenceResultCoreService {
return dto;
}
public AnalResultInfo getInferenceResultInfo(String uuid) {
public AnalResultInfo getInferenceResultInfo(UUID uuid) {
return mapSheetLearnRepository.getInferenceResultInfo(uuid);
}
public List<Dashboard> getInferenceClassCountList(String uuid) {
public List<Dashboard> getInferenceClassCountList(UUID uuid) {
return mapSheetLearnRepository.getInferenceClassCountList(uuid);
}

View File

@@ -21,15 +21,11 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
private final QMapSheetAnalInferenceEntity inferenceEntity =
QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity;
/**
* tb_map_sheet_anal_data_inference
*/
/** tb_map_sheet_anal_data_inference */
private final QMapSheetAnalDataInferenceEntity inferenceDataEntity =
QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity;
/**
* tb_map_sheet_anal_data_inference_geom
*/
/** tb_map_sheet_anal_data_inference_geom */
private final QMapSheetAnalDataInferenceGeomEntity inferenceGeomEntity =
QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
@@ -57,7 +53,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
r.stage,
r.compare_yyyy,
r.target_yyyy,
CONCAT(r.stage, '_', r.compare_yyyy, '_', r.target_yyyy) AS anal_title,
r.title,
r.detecting_cnt,
now(),
r.m1_model_batch_id,
@@ -81,7 +77,8 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
}
/**
* inference_results 테이블을 기준으로 분석 데이터 단위(stage, compare_yyyy, target_yyyy, map_sheet_num)를 생성/갱신한다.
* inference_results 테이블을 기준으로 분석 데이터 단위(stage, compare_yyyy, target_yyyy, map_sheet_num)를
* 생성/갱신한다.
*
* <p>- 최초 생성 시 file_created_yn = false - detecting_cnt는 inference_results 건수 기준
*
@@ -187,9 +184,9 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
SELECT
r.uid AS result_uid,
msadi.stage,
r.after_p as cd_prob,
msl.compare_yyyy,
msl.target_yyyy,
r.cd_prob,
r.input1 AS compare_yyyy,
r.input2 AS target_yyyy,
CASE
WHEN r.map_id ~ '^[0-9]+$' THEN r.map_id::bigint
ELSE NULL

View File

@@ -32,9 +32,9 @@ public interface MapSheetLearnRepositoryCustom {
Integer getLearnStage(Integer compareYear, Integer targetYear);
AnalResultInfo getInferenceResultInfo(String uuid);
AnalResultInfo getInferenceResultInfo(UUID uuid);
List<Dashboard> getInferenceClassCountList(String uuid);
List<Dashboard> getInferenceClassCountList(UUID uuid);
Page<Geom> getInferenceGeomList(UUID uuid, SearchGeoReq searchGeoReq);
}

View File

@@ -19,7 +19,6 @@ 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.model.service.ModelMngService;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity;
import com.querydsl.core.BooleanBuilder;
@@ -28,7 +27,6 @@ import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityNotFoundException;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
@@ -292,7 +290,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
}
@Override
public AnalResultInfo getInferenceResultInfo(String uuid) {
public AnalResultInfo getInferenceResultInfo(UUID uuid) {
QModelMngEntity m1 = new QModelMngEntity("m1");
QModelMngEntity m2 = new QModelMngEntity("m2");
QModelMngEntity m3 = new QModelMngEntity("m3");
@@ -319,22 +317,22 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.on(mapSheetLearnEntity.m2ModelUuid.eq(m2.uuid))
.leftJoin(m3)
.on(mapSheetLearnEntity.m3ModelUuid.eq(m3.uuid))
.where(mapSheetLearnEntity.uuid.eq(UUID.fromString(uuid)))
.where(mapSheetLearnEntity.uuid.eq(uuid))
.fetchOne();
}
@Override
public List<Dashboard> getInferenceClassCountList(String uuid) {
public List<Dashboard> getInferenceClassCountList(UUID uuid) {
// analUid로 분석 정보 조회
MapSheetAnalInferenceEntity analEntity =
MapSheetLearnEntity learnEntity =
queryFactory
.selectFrom(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid)))
.selectFrom(mapSheetLearnEntity)
.where(mapSheetLearnEntity.uuid.eq(uuid))
.fetchOne();
if (Objects.isNull(analEntity)) {
throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: ");
if (Objects.isNull(learnEntity)) {
throw new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND);
}
return queryFactory
@@ -343,8 +341,10 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
Dashboard.class,
mapSheetAnalSttcEntity.id.classAfterCd,
mapSheetAnalSttcEntity.classAfterCnt.sum()))
.from(mapSheetAnalSttcEntity)
.where(mapSheetAnalSttcEntity.id.analUid.eq(analEntity.getId()))
.from(mapSheetAnalInferenceEntity)
.innerJoin(mapSheetAnalSttcEntity)
.on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalSttcEntity.id.analUid))
.where(mapSheetAnalInferenceEntity.learnId.eq(learnEntity.getId()))
.groupBy(mapSheetAnalSttcEntity.id.classAfterCd)
.orderBy(mapSheetAnalSttcEntity.id.classAfterCd.asc())
.fetch();
@@ -362,10 +362,10 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
BooleanBuilder builder = new BooleanBuilder();
// analUid로 분석 정보 조회
MapSheetAnalInferenceEntity analEntity =
MapSheetLearnEntity analEntity =
queryFactory
.selectFrom(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid))
.selectFrom(mapSheetLearnEntity)
.where(mapSheetLearnEntity.uuid.eq(uuid))
.fetchOne();
if (Objects.isNull(analEntity)) {
@@ -373,7 +373,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
}
// 추론결과 id
builder.and(mapSheetAnalInferenceEntity.id.eq(analEntity.getId()));
builder.and(mapSheetAnalInferenceEntity.learnId.eq(analEntity.getId()));
// 기준년도 분류
if (searchGeoReq.getTargetClass() != null && !searchGeoReq.getTargetClass().equals("")) {

View File

@@ -1553,6 +1553,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
updateQuery.set(mapSheetAnalInferenceEntity.labelingClosedYn, closedYn);
} else if ("INSPECTION".equals(closedType)) {
updateQuery.set(mapSheetAnalInferenceEntity.inspectionClosedYn, closedYn);
} else if ("BOTH".equals(closedType)) {
updateQuery
.set(mapSheetAnalInferenceEntity.labelingClosedYn, closedYn)
.set(mapSheetAnalInferenceEntity.inspectionClosedYn, closedYn);
}
updateQuery