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

This commit is contained in:
Moon
2026-01-09 12:04:08 +09:00
24 changed files with 845 additions and 129 deletions

View File

View File

@@ -11,10 +11,14 @@ 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.time.LocalDate;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -59,6 +63,31 @@ public class InferenceResultApiController {
return ApiResponseDto.ok(analResList);
}
@Operation(summary = "변화탐지 실행 정보 입력", description = "어드민 홈 > 추론관리 > 추론목록 > 변화탐지 실행 정보 입력")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "변화탐지 실행 정보 생성 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(description = "저장 uuid", implementation = UUID.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/reg")
public ApiResponseDto<UUID> saveInferenceInfo(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "변화탐지 실행 정보 저장 요청 정보",
required = true)
@RequestBody
@Valid
InferenceResultDto.RegReq req) {
UUID uuid = inferenceResultService.saveInferenceInfo(req);
return ApiResponseDto.ok(uuid);
}
// @ApiResponses(
// value = {
// @ApiResponse(

View File

@@ -1,9 +1,14 @@
package com.kamco.cd.kamcoback.inference.dto;
import com.kamco.cd.kamcoback.common.utils.enums.EnumType;
import com.kamco.cd.kamcoback.common.utils.interfaces.EnumValid;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -14,6 +19,7 @@ import org.springframework.data.domain.Pageable;
public class InferenceResultDto {
/** 목록조회 dto */
@Getter
@Setter
@AllArgsConstructor
@@ -32,6 +38,7 @@ public class InferenceResultDto {
@JsonFormatDttm private ZonedDateTime applyDttm;
}
/** 목록조회 검색 조건 dto */
@Getter
@Setter
@NoArgsConstructor
@@ -53,12 +60,14 @@ public class InferenceResultDto {
}
}
/** 탐지 데이터 옵션 dto */
@Getter
@AllArgsConstructor
public enum DetectOption implements EnumType {
public enum MapSheetScope implements EnumType {
ALL("전체"),
PART("부분"),
;
private final String desc;
@Override
@@ -72,9 +81,10 @@ public class InferenceResultDto {
}
}
/** 분석대상 도엽 enum */
@Getter
@AllArgsConstructor
public enum MapSheetScope implements EnumType {
public enum DetectOption implements EnumType {
EXCL("추론제외"),
PREV("이전 년도 도엽 사용"),
;
@@ -90,4 +100,52 @@ public class InferenceResultDto {
return desc;
}
}
/** 변화탐지 실행 정보 저장 요청 정보 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class RegReq {
@Schema(description = "제목", example = "2025-2026 track changes Pororo")
@NotBlank
private String title;
@Schema(description = "M1", example = "2")
@NotNull
private Long model1Uid;
@Schema(description = "M2", example = "4")
@NotNull
private Long model2Uid;
@Schema(description = "M3", example = "7")
@NotNull
private Long model3Uid;
@Schema(description = "비교년도", example = "2003")
@NotNull
private Integer compareYyyy;
@Schema(description = "탐지년도", example = "2004")
@NotNull
private Integer targetYyyy;
@Schema(description = "분석대상 도엽 - 전체(ALL), 부분(PART)", example = "PART")
@NotBlank
@EnumValid(enumClass = MapSheetScope.class, message = "분석대상 도엽 옵션은 '전체', '부분' 만 사용 가능합니다.")
private String mapSheetScope;
@Schema(description = "탐지 데이터 옵션 - 추론제외(EXCL), 이전 년도 도엽 사용(PREV)", example = "EXCL")
@NotBlank
@EnumValid(
enumClass = DetectOption.class,
message = "탐지 데이터 옵션은 '추론제외', '이전 년도 도엽 사용' 만 사용 가능합니다.")
private String detectOption;
@Schema(description = "5k 도협 번호 목록", example = "[\"34607067\",\"35802056\"]")
@NotNull
private List<String> mapSheetNum;
}
}

View File

@@ -9,6 +9,7 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList;
import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@@ -31,6 +32,16 @@ public class InferenceResultService {
return inferenceResultCoreService.getInferenceResultList(req);
}
/**
* 변화탐지 실행 정보 생성
*
* @param req
*/
@Transactional
public UUID saveInferenceInfo(InferenceResultDto.RegReq req) {
return inferenceResultCoreService.saveInferenceInfo(req);
}
/**
* 분석결과 요약정보
*

View File

@@ -1,11 +1,11 @@
package com.kamco.cd.kamcoback.label;
import com.kamco.cd.kamcoback.common.enums.RoleType;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.UpdateClosedRequest;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse;
import com.kamco.cd.kamcoback.label.service.LabelAllocateService;
import io.swagger.v3.oas.annotations.Operation;
@@ -54,7 +54,9 @@ public class LabelAllocateApiController {
return ApiResponseDto.ok(labelAllocateService.availUserList(role));
}
@Operation(summary = "작업현황 관리 > 프로젝트 및 진행 상황 정보", description = "작업현황 관리 > 프로젝트 및 진행 상황 정보")
@Operation(
summary = "작업현황 관리 > 프로젝트 및 진행 상황 정보",
description = "작업현황 관리 > 프로젝트 및 진행 상황 정보. UUID를 입력하면 해당 프로젝트 정보를 조회하고, 미입력 시 최신 프로젝트를 조회합니다.")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@@ -63,18 +65,18 @@ public class LabelAllocateApiController {
})
@GetMapping("/projectinfo")
public ApiResponseDto<WorkerListResponse> getWorkerStatistics(
// @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false)
// Long analUid,
@Parameter(
description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회",
description = "프로젝트 UUID (선택) - 미입력 시 최신 프로젝트 조회",
example = "f97dc186-e6d3-4645-9737-3173dde8dc64")
@RequestParam(required = false)
String uuid,
@Parameter(
description = "작업자 유형 (선택) - 미입력 시 전체 조회",
example = "LABELER",
schema =
@Schema(
allowableValues = {"LABELER", "REVIEWER"},
defaultValue = "LABELER"))
schema = @Schema(allowableValues = {"LABELER", "REVIEWER"}))
@RequestParam(required = false)
String type,
@Parameter(description = "검색어 (작업자 이름 또는 사번으로 검색, 부분 일치)", example = "김라벨")
@Parameter(description = "검색어 (작업자 이름 또는 사번으로 검색, 부분 일치) - 미입력 시 전체 조회", example = "김라벨")
@RequestParam(required = false)
String search,
@Parameter(
@@ -94,11 +96,8 @@ public class LabelAllocateApiController {
@RequestParam(required = false)
String sort) {
// type이 null이면 기본값으로 LABELER 설정
String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type;
return ApiResponseDto.ok(
labelAllocateService.getWorkerStatistics(null, workerType, search, sort));
labelAllocateService.getWorkerStatisticsByUuid(uuid, type, search, sort));
}
@Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정")
@@ -236,4 +235,71 @@ public class LabelAllocateApiController {
String uuid) {
return ApiResponseDto.ok(labelAllocateService.moveAvailUserList(userId, uuid));
}
@Operation(
summary = "작업현황 관리 > 라벨링/검수 종료 여부 업데이트",
description = "라벨링/검수 종료 여부를 업데이트합니다. uuid 생략 시 최신 프로젝트 대상")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "업데이트 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping("/projectinfo/closed")
public ApiResponseDto<ApiResponseDto.ResponseObj> updateClosedYn(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "종료 여부 업데이트 요청",
required = true,
content =
@Content(
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"}
""")
}))
@RequestBody
@Valid
UpdateClosedRequest request) {
labelAllocateService.updateClosedYn(
request.getUuid(), request.getClosedType(), request.getClosedYn());
String typeLabel = "LABELING".equals(request.getClosedType()) ? "라벨링" : "검수";
String statusMessage =
"Y".equals(request.getClosedYn())
? typeLabel + "이(가) 종료되었습니다."
: typeLabel + "이(가) 재개되었습니다.";
return ApiResponseDto.okObject(
new ApiResponseDto.ResponseObj(ApiResponseDto.ApiResponseCode.OK, statusMessage));
}
}

View File

@@ -136,11 +136,13 @@ public class LabelWorkerApiController {
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
int page,
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
int size) {
int size,
@Parameter(description = "회차 UUID key", example = "f97dc186-e6d3-4645-9737-3173dde8dc64")
String uuid) {
// 대상추출(최근)
UUID lstUuid = labelWorkService.findLastLabelWorkState();
String uuid = lstUuid.toString();
// UUID lstUuid = labelWorkService.findLastLabelWorkState();
// String uuid = lstUuid.toString();
LabelWorkDto.WorkerStateSearchReq searchReq = new WorkerStateSearchReq();
searchReq.setUserRole(userRole);

View File

@@ -27,7 +27,8 @@ public class LabelAllocateDto {
LABEL_COMPLETE("라벨완료"),
INSPECT_REQ("검수요청"),
INSPECT_ING("검수진행중"),
INSPECT_COMPLETE("검수완료");
INSPECT_COMPLETE("검수완료"),
FINISH("종료");
private String desc;

View File

@@ -38,17 +38,16 @@ public class LabelWorkDto {
private Integer compareYyyy;
private Integer targetYyyy;
private Integer stage;
@JsonFormatDttm private ZonedDateTime createdDttm;
@JsonFormatDttm private ZonedDateTime gukyuinApplyDttm;
private Long detectionTotCnt;
private Long labelTotCnt;
private Long labelAssignCnt;
private Long labelStopTotCnt;
private Long labelIngTotCnt;
private Long labelSkipTotCnt;
private Long labelCompleteTotCnt;
@JsonFormatDttm private ZonedDateTime labelStartDttm;
// tb_map_sheet_anal_inference.anal_state 컬럼 값
private String analState;
// tb_map_sheet_anal_inference.anal_state 컬럼 값 -> 미사용
// private String analState;
// tb_labeling_assignment 테이블에서 stagnation_yn = 'N'인 정상 진행 건수
private Long normalProgressCnt;
@@ -56,6 +55,9 @@ public class LabelWorkDto {
// tb_labeling_assignment 테이블에서 총 배정 건수
private Long totalAssignmentCnt;
private String labelingClosedYn;
private String inspectionClosedYn;
@JsonProperty("detectYear")
public String getDetectYear() {
if (compareYyyy == null || targetYyyy == null) {
@@ -66,22 +68,24 @@ public class LabelWorkDto {
/** 라벨링 상태 반환 (tb_map_sheet_anal_inference.anal_state 기준) */
public String getLabelState() {
// anal_state 값이 있으면 해당 값 사용
if (this.analState != null && !this.analState.isEmpty()) {
return this.analState;
}
// anal_state 값이 있으면 해당 값 사용 -> 우선은 미사용
// if (this.analState != null && !this.analState.isEmpty()) {
// return this.analState;
// }
// anal_state 값이 없으면 기존 로직으로 폴백
String mngState = "PENDING";
String mngState = LabelMngState.PENDING.getId();
if (this.labelTotCnt == 0) {
mngState = "PENDING";
} else if (this.labelTotCnt > 0 && this.labelAssignCnt > 0 && this.labelIngTotCnt == 0) {
mngState = "ASSIGNED";
} else if (this.labelIngTotCnt > 0) {
mngState = "LABEL_ING";
mngState = LabelMngState.PENDING.getId();
} else if (this.labelTotCnt > 0 && this.labelAssignCnt > 0 && this.labelCompleteTotCnt == 0) {
mngState = LabelMngState.ASSIGNED.getId();
} else if (this.labelCompleteTotCnt > 0) {
mngState = LabelMngState.LABEL_ING.getId();
} else if (this.labelTotCnt <= labelCompleteTotCnt) {
mngState = "LABEL_COMPLETE";
mngState = LabelMngState.LABEL_COMPLETE.getId();
} else if (this.labelingClosedYn.equals("Y") && this.inspectionClosedYn.equals("Y")) {
mngState = LabelMngState.FINISH.getId();
}
return mngState;
@@ -109,10 +113,10 @@ public class LabelWorkDto {
if (this.totalAssignmentCnt == null || this.totalAssignmentCnt == 0) {
return 0.0;
}
if (this.normalProgressCnt == null) {
if (this.labelCompleteTotCnt == null) {
return 0.0;
}
return (double) this.normalProgressCnt / this.totalAssignmentCnt * 100.0;
return (double) this.labelCompleteTotCnt / this.totalAssignmentCnt * 100.0;
}
}

View File

@@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.label.dto;
import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import java.time.ZonedDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -35,6 +37,43 @@ public class WorkerStatsDto {
@Schema(description = "프로젝트 UUID")
private String uuid;
@Schema(description = "라벨링 종료 여부 (Y: 종료, N: 진행중)")
private String labelingClosedYn;
@Schema(description = "검수 종료 여부 (Y: 종료, N: 진행중)")
private String inspectionClosedYn;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "프로젝트 종료 여부 업데이트 요청")
public static class UpdateClosedRequest {
@Schema(
description = "프로젝트 UUID (선택) - 미입력 시 현재 진행중인 최신 프로젝트가 대상",
example = "f97dc186-e6d3-4645-9737-3173dde8dc64")
private String uuid;
@NotBlank(message = "종료 유형은 필수입니다.")
@Pattern(regexp = "^(LABELING|INSPECTION)$", message = "종료 유형은 LABELING 또는 INSPECTION이어야 합니다.")
@Schema(
description = "종료 유형 (LABELING: 라벨링, INSPECTION: 검수)",
example = "LABELING",
allowableValues = {"LABELING", "INSPECTION"},
requiredMode = Schema.RequiredMode.REQUIRED)
private String closedType;
@NotBlank(message = "종료 여부는 필수입니다.")
@Pattern(regexp = "^[YN]$", message = "종료 여부는 Y 또는 N이어야 합니다.")
@Schema(
description = "종료 여부 (Y: 종료, N: 진행중)",
example = "Y",
allowableValues = {"Y", "N"},
requiredMode = Schema.RequiredMode.REQUIRED)
private String closedYn;
}
@Getter
@@ -144,7 +183,7 @@ public class WorkerStatsDto {
@Schema(description = "검수 작업 상태 (진행중/완료)")
private String inspectionStatus;
@Schema(description = "검수 전체 대상 건수")
@Schema(description = "검수 대상 건수 (라벨링 대상과 동일)")
private Long inspectionTotalCount;
@Schema(description = "검수 완료 건수 (DONE)")
@@ -179,10 +218,6 @@ public class WorkerStatsDto {
@Deprecated
@Schema(description = "[Deprecated] inspectionRemainingCount 사용 권장")
private Long remainingInspectCount;
@Deprecated
@Schema(description = "[Deprecated] labelingStatus/inspectionStatus 사용 권장")
private String workStatus;
}
@Getter

View File

@@ -10,6 +10,8 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.MoveInfo;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.ProjectInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse;
import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService;
import java.util.List;
@@ -114,6 +116,36 @@ public class LabelAllocateService {
return WorkerListResponse.builder().projectInfo(projectInfo).progressInfo(progressInfo).build();
}
/**
* 작업자 통계 조회 (UUID 기반)
*
* @param uuid 프로젝트 UUID (선택, 미입력 시 최신 프로젝트 조회)
* @param workerType 작업자 유형 (LABELER/INSPECTOR)
* @param search 검색어 (이름 또는 사번)
* @param sortType 정렬 조건
* @return 작업자 목록 및 통계
*/
public WorkerListResponse getWorkerStatisticsByUuid(
String uuid, String workerType, String search, String sortType) {
ProjectInfo projectInfo;
WorkProgressInfo progressInfo;
if (uuid != null && !uuid.isBlank()) {
// UUID로 프로젝트 정보 조회
projectInfo = labelAllocateCoreService.findProjectInfoByUuid(uuid);
// UUID로 작업 진행 현황 조회
progressInfo = labelAllocateCoreService.findWorkProgressInfoByUuid(uuid);
} else {
// 최신 프로젝트 정보 조회
projectInfo = labelAllocateCoreService.findLatestProjectInfo();
// 최신 프로젝트 작업 진행 현황 조회
progressInfo = labelAllocateCoreService.findWorkProgressInfo(null);
}
return WorkerListResponse.builder().projectInfo(projectInfo).progressInfo(progressInfo).build();
}
public InferenceDetail findInferenceDetail(String uuid) {
return labelAllocateCoreService.findInferenceDetail(uuid);
}
@@ -195,4 +227,27 @@ public class LabelAllocateService {
public MoveInfo moveAvailUserList(String userId, String uuid) {
return labelAllocateCoreService.moveAvailUserList(userId, uuid);
}
/**
* 프로젝트 종료 여부 업데이트
*
* @param uuid 프로젝트 UUID (선택, 미입력 시 최신 프로젝트 대상)
* @param closedType 종료 유형 (LABELING/INSPECTION)
* @param closedYn 종료 여부 (Y/N)
*/
@Transactional
public void updateClosedYn(String uuid, String closedType, String closedYn) {
String targetUuid = uuid;
// uuid가 없으면 최신 프로젝트 uuid 조회
if (targetUuid == null || targetUuid.isBlank()) {
var latestProjectInfo = labelAllocateCoreService.findLatestProjectInfo();
if (latestProjectInfo == null || latestProjectInfo.getUuid() == null) {
throw new IllegalArgumentException("진행중인 프로젝트가 없습니다.");
}
targetUuid = latestProjectInfo.getUuid();
}
labelAllocateCoreService.updateClosedYnByUuid(targetUuid, closedType, closedYn);
}
}

View File

@@ -83,43 +83,43 @@ public class ModelMngApiController {
@Operation(summary = "모델삭제", description = "모델을 삭제 합니다.")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "등록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
value = {
@ApiResponse(
responseCode = "201",
description = "등록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@DeleteMapping("/{uuid}")
public ApiResponseDto<ApiResponseDto.ResponseObj> removeModel(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "모델 삭제 요청 정보",
required = true)
@PathVariable
String uuid) {
@io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "모델 삭제 요청 정보",
required = true)
@PathVariable
String uuid) {
return ApiResponseDto.ok(modelMngService.removeModel(UUID.fromString(uuid)));
}
@Operation(summary = "모델등록", description = "모델을 등록 합니다.")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "201",
description = "등록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
value = {
@ApiResponse(
responseCode = "201",
description = "등록 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Long.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping
public ApiResponseDto<ApiResponseDto.ResponseObj> ModelMgmt(
@RequestBody @Valid ModelMngDto.AddReq addReq) {

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.postgres.core;
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;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet;
@@ -8,13 +9,18 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList;
import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearn5kEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import com.kamco.cd.kamcoback.postgres.repository.Inference.MapSheetAnalDataInferenceRepository;
import com.kamco.cd.kamcoback.postgres.repository.Inference.MapSheetLearn5kRepository;
import com.kamco.cd.kamcoback.postgres.repository.Inference.MapSheetLearnRepository;
import com.kamco.cd.kamcoback.postgres.repository.scene.MapInkx5kRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@@ -27,6 +33,10 @@ public class InferenceResultCoreService {
private final MapSheetAnalDataInferenceRepository mapSheetAnalDataRepository;
private final MapSheetLearnRepository mapSheetLearnRepository;
private final MapInkx5kRepository mapInkx5kRepository;
private final MapSheetLearn5kRepository mapSheetLearn5kRepository;
private final EntityManager entityManager;
private final UserUtil userUtil;
/**
* 추론관리 목록
@@ -39,6 +49,55 @@ public class InferenceResultCoreService {
return list.map(MapSheetLearnEntity::toDto);
}
/**
* 변화탐지 실행 정보 생성
*
* @param req
*/
public UUID saveInferenceInfo(InferenceResultDto.RegReq req) {
MapSheetLearnEntity mapSheetLearnEntity = new MapSheetLearnEntity();
mapSheetLearnEntity.setTitle(req.getTitle());
mapSheetLearnEntity.setM1ModelUid(req.getModel1Uid());
mapSheetLearnEntity.setM2ModelUid(req.getModel2Uid());
mapSheetLearnEntity.setM3ModelUid(req.getModel3Uid());
mapSheetLearnEntity.setCompareYyyy(req.getCompareYyyy());
mapSheetLearnEntity.setTargetYyyy(req.getTargetYyyy());
mapSheetLearnEntity.setMapSheetScope(req.getMapSheetScope());
mapSheetLearnEntity.setDetectOption(req.getDetectOption());
mapSheetLearnEntity.setCreatedUid(userUtil.getId());
// learn 테이블 저장
MapSheetLearnEntity savedLearn = mapSheetLearnRepository.save(mapSheetLearnEntity);
final int CHUNK = 1000;
List<MapSheetLearn5kEntity> buffer = new ArrayList<>(CHUNK);
List<String> mapSheetNumList = req.getMapSheetNum();
// learn 도엽 저장
for (String mapSheetNum : mapSheetNumList) {
MapSheetLearn5kEntity e = new MapSheetLearn5kEntity();
e.setLearn(savedLearn);
e.setMapSheetNum(Long.parseLong(mapSheetNum));
e.setCreatedUid(userUtil.getId());
buffer.add(e);
if (buffer.size() == CHUNK) {
mapSheetLearn5kRepository.saveAll(buffer);
mapSheetLearn5kRepository.flush();
entityManager.clear();
buffer.clear();
}
}
if (!buffer.isEmpty()) {
mapSheetLearn5kRepository.saveAll(buffer);
mapSheetLearn5kRepository.flush();
entityManager.clear();
}
return savedLearn.getUuid();
}
/****/
/**

View File

@@ -61,6 +61,10 @@ public class LabelAllocateCoreService {
return labelAllocateRepository.findLatestProjectInfo();
}
public ProjectInfo findProjectInfoByUuid(String uuid) {
return labelAllocateRepository.findProjectInfoByUuid(uuid);
}
public UUID findLastLabelWorkState() {
return labelAllocateRepository.findLastLabelWorkState();
}
@@ -74,6 +78,10 @@ public class LabelAllocateCoreService {
return labelAllocateRepository.findWorkProgressInfo(analUid);
}
public WorkProgressInfo findWorkProgressInfoByUuid(String uuid) {
return labelAllocateRepository.findWorkProgressInfoByUuid(uuid);
}
public Long findDailyProcessedCount(
String workerId, String workerType, LocalDate date, Long analUid) {
return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid);
@@ -133,4 +141,8 @@ public class LabelAllocateCoreService {
String uuid, String userId, String paramUserId, Long assignCount) {
labelAllocateRepository.assignOwnerReAllocate(uuid, userId, paramUserId, assignCount);
}
public void updateClosedYnByUuid(String uuid, String closedType, String closedYn) {
labelAllocateRepository.updateClosedYnByUuid(uuid, closedType, closedYn);
}
}

View File

@@ -149,4 +149,14 @@ public class MapSheetAnalInferenceEntity {
@Column(name = "stage")
private Integer stage;
@Size(max = 1)
@ColumnDefault("'N'")
@Column(name = "labeling_closed_yn", length = 1)
private String labelingClosedYn = "N";
@Size(max = 1)
@ColumnDefault("'N'")
@Column(name = "inspection_closed_yn", length = 1)
private String inspectionClosedYn = "N";
}

View File

@@ -0,0 +1,51 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@Getter
@Setter
@Entity
@Table(name = "tb_map_sheet_learn_5k")
public class MapSheetLearn5kEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_sheet_learn_5k_id_gen")
@SequenceGenerator(
name = "tb_map_sheet_learn_5k_id_gen",
sequenceName = "tb_map_sheet_learn_5k_seq",
allocationSize = 1)
@Column(name = "id", nullable = false)
private Long id;
@NotNull
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "learn_id", nullable = false, referencedColumnName = "id")
private MapSheetLearnEntity learn;
@NotNull
@Column(name = "map_sheet_num", nullable = false)
private Long mapSheetNum;
@org.hibernate.annotations.CreationTimestamp
@Column(name = "created_dttm")
private ZonedDateTime createdDttm;
@Column(name = "created_uid")
private Long createdUid;
}

View File

@@ -33,7 +33,7 @@ public class MapSheetLearnEntity {
@ColumnDefault("gen_random_uuid()")
@Column(name = "uuid")
private UUID uuid;
private UUID uuid = UUID.randomUUID();
@Size(max = 200)
@NotNull
@@ -89,7 +89,7 @@ public class MapSheetLearnEntity {
@Column(name = "apply_dttm")
private ZonedDateTime applyDttm;
@ColumnDefault("now()")
@org.hibernate.annotations.CreationTimestamp
@Column(name = "created_dttm")
private ZonedDateTime createdDttm;

View File

@@ -0,0 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearn5kEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MapSheetLearn5kRepository
extends JpaRepository<MapSheetLearn5kEntity, Long>, MapSheetLearn5kRepositoryCustom {}

View File

@@ -0,0 +1,3 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference;
public interface MapSheetLearn5kRepositoryCustom {}

View File

@@ -0,0 +1,6 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference;
import org.springframework.stereotype.Repository;
@Repository
public class MapSheetLearn5kRepositoryImpl implements MapSheetLearn5kRepositoryCustom {}

View File

@@ -37,6 +37,9 @@ public interface LabelAllocateRepositoryCustom {
// 최신 프로젝트 정보 조회 (analUid 없이)
ProjectInfo findLatestProjectInfo();
// UUID로 프로젝트 정보 조회
ProjectInfo findProjectInfoByUuid(String uuid);
// 최신 작업 상태의 UUID 조회
UUID findLastLabelWorkState();
@@ -47,6 +50,9 @@ public interface LabelAllocateRepositoryCustom {
// 작업 진행 현황 조회
WorkProgressInfo findWorkProgressInfo(Long analUid);
// UUID로 작업 진행 현황 조회
WorkProgressInfo findWorkProgressInfoByUuid(String uuid);
// 작업자별 일일 처리량 조회
Long findDailyProcessedCount(String workerId, String workerType, LocalDate date, Long analUid);
@@ -77,4 +83,7 @@ public interface LabelAllocateRepositoryCustom {
void insertLabelerUser(Long analUid, String userId, int demand);
void assignOwnerReAllocate(String uuid, String userId, String paramUserId, Long assignCount);
// 프로젝트 종료 여부 업데이트 (uuid 기반)
void updateClosedYnByUuid(String uuid, String closedType, String closedYn);
}

View File

@@ -335,10 +335,50 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
@Override
public WorkProgressInfo findWorkProgressInfo(Long analUid) {
BooleanExpression analUidCondition =
analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null;
// analUid가 null이면 최신 프로젝트의 analUid 조회
Long effectiveAnalUid = analUid;
if (effectiveAnalUid == null) {
UUID latestUuid = findLastLabelWorkState();
if (latestUuid != null) {
effectiveAnalUid =
queryFactory
.select(mapSheetAnalInferenceEntity.id)
.from(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(latestUuid))
.fetchOne();
}
}
// 전체 배정 건수
BooleanExpression analUidCondition =
effectiveAnalUid != null ? labelingAssignmentEntity.analUid.eq(effectiveAnalUid) : null;
// analUid로 분석 정보(compareYyyy, targetYyyy, stage) 조회
MapSheetAnalInferenceEntity analEntity = null;
if (effectiveAnalUid != null) {
analEntity =
queryFactory
.selectFrom(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.id.eq(effectiveAnalUid))
.fetchOne();
}
// 라벨링 대상 건수: tb_map_sheet_anal_data_inference_geom에서 pnu > 0 AND pass_yn = false(부적합)인 건수
Long labelingTargetCount = 0L;
if (analEntity != null) {
labelingTargetCount =
queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid.count())
.from(mapSheetAnalDataInferenceGeomEntity)
.where(
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()),
mapSheetAnalDataInferenceGeomEntity.stage.eq(analEntity.getStage()),
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L),
mapSheetAnalDataInferenceGeomEntity.passYn.isFalse())
.fetchOne();
}
// 전체 배정 건수 (tb_labeling_assignment 기준)
Long totalAssigned =
queryFactory
.select(labelingAssignmentEntity.count())
@@ -390,47 +430,217 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.where(analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull())
.fetchOne();
// 남은 작업 건수 계산
long total = totalAssigned != null ? totalAssigned : 0L;
// 라벨링 대상 건수 (pass_yn = false인 부적합 데이터 기준)
long labelingTotal = labelingTargetCount != null ? labelingTargetCount : 0L;
long assignedTotal = totalAssigned != null ? totalAssigned : 0L;
long labelCompleted = labelingCompleted != null ? labelingCompleted : 0L;
long inspectCompleted = inspectionCompleted != null ? inspectionCompleted : 0L;
long skipped = skipCount != null ? skipCount : 0L;
long labelingRemaining = total - labelCompleted - skipped;
long inspectionRemaining = total - inspectCompleted - skipped;
// 라벨링 남은 건수: 라벨링 대상 건수 - 완료 - 스킵
long labelingRemaining = labelingTotal - labelCompleted - skipped;
if (labelingRemaining < 0) labelingRemaining = 0;
// 진행률 계산
double labelingRate = total > 0 ? (double) labelCompleted / total * 100 : 0.0;
double inspectionRate = total > 0 ? (double) inspectCompleted / total * 100 : 0.0;
// 검수 대상 건수: 라벨링 대상 건수와 동일 (기획서 기준)
long inspectionTotal = labelingTotal;
// 검수 남은 건수: 검수 대상 건수 - 검수완료(DONE) - 스킵
long inspectionRemaining = inspectionTotal - inspectCompleted - skipped;
if (inspectionRemaining < 0) inspectionRemaining = 0;
// 상태 판단
String labelingStatus = labelingRemaining > 0 ? "진행중" : "완료";
String inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료";
// 진행률 계산 (라벨링 대상 건수 기준)
double labelingRate = labelingTotal > 0 ? (double) labelCompleted / labelingTotal * 100 : 0.0;
double inspectionRate =
inspectionTotal > 0 ? (double) inspectCompleted / inspectionTotal * 100 : 0.0;
// 상태 판단 (각각의 closedYn이 "Y"이면 "종료", 아니면 진행중/완료)
String labelingStatus;
String inspectionStatus;
// 라벨링 상태 판단
if (analEntity != null && "Y".equals(analEntity.getLabelingClosedYn())) {
labelingStatus = "종료";
} else {
labelingStatus = labelingRemaining > 0 ? "진행중" : "완료";
}
// 검수 상태 판단
if (analEntity != null && "Y".equals(analEntity.getInspectionClosedYn())) {
inspectionStatus = "종료";
} else {
inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료";
}
return WorkProgressInfo.builder()
// 라벨링
// 라벨링 (pass_yn = false인 부적합 데이터 기준)
.labelingProgressRate(labelingRate)
.labelingStatus(labelingStatus)
.labelingTotalCount(total)
.labelingTotalCount(labelingTotal)
.labelingCompletedCount(labelCompleted)
.labelingSkipCount(skipped)
.labelingRemainingCount(labelingRemaining)
.labelerCount(labelerCount != null ? labelerCount : 0L)
// 검수
// 검수 (라벨링 완료 건수 기준)
.inspectionProgressRate(inspectionRate)
.inspectionStatus(inspectionStatus)
.inspectionTotalCount(total)
.inspectionTotalCount(inspectionTotal)
.inspectionCompletedCount(inspectCompleted)
.inspectionSkipCount(skipped)
.inspectionRemainingCount(inspectionRemaining)
.inspectorCount(inspectorCount != null ? inspectorCount : 0L)
// 레거시 호환 필드 (Deprecated)
.progressRate(labelingRate)
.totalAssignedCount(total)
.totalAssignedCount(labelingTotal)
.completedCount(labelCompleted)
.remainingLabelCount(labelingRemaining)
.remainingInspectCount(inspectionRemaining)
.build();
}
@Override
public WorkProgressInfo findWorkProgressInfoByUuid(String uuid) {
if (uuid == null || uuid.isBlank()) {
return findWorkProgressInfo(null);
}
UUID targetUuid = UUID.fromString(uuid);
// UUID로 analUid 조회
Long effectiveAnalUid =
queryFactory
.select(mapSheetAnalInferenceEntity.id)
.from(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(targetUuid))
.fetchOne();
if (effectiveAnalUid == null) {
return null;
}
BooleanExpression analUidCondition = labelingAssignmentEntity.analUid.eq(effectiveAnalUid);
// analUid로 분석 정보(compareYyyy, targetYyyy, stage) 조회
MapSheetAnalInferenceEntity analEntity =
queryFactory
.selectFrom(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.id.eq(effectiveAnalUid))
.fetchOne();
// 라벨링 대상 건수: tb_map_sheet_anal_data_inference_geom에서 pnu > 0 AND pass_yn = false(부적합)인 건수
Long labelingTargetCount = 0L;
if (analEntity != null) {
labelingTargetCount =
queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid.count())
.from(mapSheetAnalDataInferenceGeomEntity)
.where(
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()),
mapSheetAnalDataInferenceGeomEntity.stage.eq(analEntity.getStage()),
mapSheetAnalDataInferenceGeomEntity.pnu.gt(0L),
mapSheetAnalDataInferenceGeomEntity.passYn.isFalse())
.fetchOne();
}
// 전체 배정 건수 (tb_labeling_assignment 기준)
Long totalAssigned =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(analUidCondition)
.fetchOne();
// === 라벨링 통계 ===
Long labelingCompleted =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(
analUidCondition,
labelingAssignmentEntity.workState.in("LABEL_FIN", "TEST_ING", "DONE"))
.fetchOne();
Long skipCount =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(analUidCondition, labelingAssignmentEntity.workState.eq("SKIP"))
.fetchOne();
Long labelerCount =
queryFactory
.select(labelingAssignmentEntity.workerUid.countDistinct())
.from(labelingAssignmentEntity)
.where(analUidCondition, labelingAssignmentEntity.workerUid.isNotNull())
.fetchOne();
// === 검수 통계 ===
Long inspectionCompleted =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(analUidCondition, labelingAssignmentEntity.workState.eq("DONE"))
.fetchOne();
Long inspectorCount =
queryFactory
.select(labelingAssignmentEntity.inspectorUid.countDistinct())
.from(labelingAssignmentEntity)
.where(analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull())
.fetchOne();
// 계산
long labelingTotal = labelingTargetCount != null ? labelingTargetCount : 0L;
long labelCompleted = labelingCompleted != null ? labelingCompleted : 0L;
long inspectCompleted = inspectionCompleted != null ? inspectionCompleted : 0L;
long skipped = skipCount != null ? skipCount : 0L;
long labelingRemaining = labelingTotal - labelCompleted - skipped;
if (labelingRemaining < 0) labelingRemaining = 0;
long inspectionTotal = labelingTotal;
long inspectionRemaining = inspectionTotal - inspectCompleted - skipped;
if (inspectionRemaining < 0) inspectionRemaining = 0;
double labelingRate = labelingTotal > 0 ? (double) labelCompleted / labelingTotal * 100 : 0.0;
double inspectionRate =
inspectionTotal > 0 ? (double) inspectCompleted / inspectionTotal * 100 : 0.0;
// 상태 판단
String labelingStatus;
String inspectionStatus;
if (analEntity != null && "Y".equals(analEntity.getLabelingClosedYn())) {
labelingStatus = "종료";
} else {
labelingStatus = labelingRemaining > 0 ? "진행중" : "완료";
}
if (analEntity != null && "Y".equals(analEntity.getInspectionClosedYn())) {
inspectionStatus = "종료";
} else {
inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료";
}
return WorkProgressInfo.builder()
.labelingProgressRate(labelingRate)
.labelingStatus(labelingStatus)
.labelingTotalCount(labelingTotal)
.labelingCompletedCount(labelCompleted)
.labelingSkipCount(skipped)
.labelingRemainingCount(labelingRemaining)
.labelerCount(labelerCount != null ? labelerCount : 0L)
.inspectionProgressRate(inspectionRate)
.inspectionStatus(inspectionStatus)
.inspectionTotalCount(inspectionTotal)
.inspectionCompletedCount(inspectCompleted)
.inspectionSkipCount(skipped)
.inspectionRemainingCount(inspectionRemaining)
.inspectorCount(inspectorCount != null ? inspectorCount : 0L)
.progressRate(labelingRate)
.totalAssignedCount(labelingTotal)
.completedCount(labelCompleted)
.remainingLabelCount(labelingRemaining)
.remainingInspectCount(inspectionRemaining)
.workStatus(labelingStatus)
.build();
}
@@ -677,7 +887,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
mapSheetAnalInferenceEntity.stage,
mapSheetAnalInferenceEntity.gukyuinApplyDttm,
mapSheetAnalInferenceEntity.createdDttm,
mapSheetAnalInferenceEntity.uuid)
mapSheetAnalInferenceEntity.uuid,
mapSheetAnalInferenceEntity.labelingClosedYn,
mapSheetAnalInferenceEntity.inspectionClosedYn)
.from(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.id.eq(analUid))
.fetchOne();
@@ -692,6 +904,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalInferenceEntity.gukyuinApplyDttm);
ZonedDateTime createdDttm = result.get(mapSheetAnalInferenceEntity.createdDttm);
UUID uuid = result.get(mapSheetAnalInferenceEntity.uuid);
String labelingClosedYn = result.get(mapSheetAnalInferenceEntity.labelingClosedYn);
String inspectionClosedYn = result.get(mapSheetAnalInferenceEntity.inspectionClosedYn);
// 변화탐지년도 생성
String detectionYear =
@@ -706,6 +920,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.gukyuinApplyDttm(gukyuinApplyDttm)
.startDttm(createdDttm)
.uuid(uuid != null ? uuid.toString() : null)
.labelingClosedYn(labelingClosedYn)
.inspectionClosedYn(inspectionClosedYn)
.build();
}
@@ -727,7 +943,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
mapSheetAnalInferenceEntity.stage,
mapSheetAnalInferenceEntity.gukyuinApplyDttm,
mapSheetAnalInferenceEntity.createdDttm,
mapSheetAnalInferenceEntity.uuid)
mapSheetAnalInferenceEntity.uuid,
mapSheetAnalInferenceEntity.labelingClosedYn,
mapSheetAnalInferenceEntity.inspectionClosedYn)
.from(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(uuid))
.fetchOne();
@@ -741,6 +959,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
Integer stage = result.get(mapSheetAnalInferenceEntity.stage);
ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalInferenceEntity.gukyuinApplyDttm);
ZonedDateTime createdDttm = result.get(mapSheetAnalInferenceEntity.createdDttm);
String labelingClosedYn = result.get(mapSheetAnalInferenceEntity.labelingClosedYn);
String inspectionClosedYn = result.get(mapSheetAnalInferenceEntity.inspectionClosedYn);
// 변화탐지년도 생성
String detectionYear =
@@ -755,6 +975,58 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.gukyuinApplyDttm(gukyuinApplyDttm)
.startDttm(createdDttm)
.uuid(uuid.toString())
.labelingClosedYn(labelingClosedYn)
.inspectionClosedYn(inspectionClosedYn)
.build();
}
@Override
public ProjectInfo findProjectInfoByUuid(String uuid) {
if (uuid == null || uuid.isBlank()) {
return null;
}
UUID targetUuid = UUID.fromString(uuid);
var result =
queryFactory
.select(
mapSheetAnalInferenceEntity.compareYyyy,
mapSheetAnalInferenceEntity.targetYyyy,
mapSheetAnalInferenceEntity.stage,
mapSheetAnalInferenceEntity.gukyuinApplyDttm,
mapSheetAnalInferenceEntity.createdDttm,
mapSheetAnalInferenceEntity.uuid,
mapSheetAnalInferenceEntity.labelingClosedYn,
mapSheetAnalInferenceEntity.inspectionClosedYn)
.from(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(targetUuid))
.fetchOne();
if (result == null) {
return null;
}
Integer compareYyyy = result.get(mapSheetAnalInferenceEntity.compareYyyy);
Integer targetYyyy = result.get(mapSheetAnalInferenceEntity.targetYyyy);
Integer stage = result.get(mapSheetAnalInferenceEntity.stage);
ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalInferenceEntity.gukyuinApplyDttm);
ZonedDateTime createdDttm = result.get(mapSheetAnalInferenceEntity.createdDttm);
String labelingClosedYn = result.get(mapSheetAnalInferenceEntity.labelingClosedYn);
String inspectionClosedYn = result.get(mapSheetAnalInferenceEntity.inspectionClosedYn);
String detectionYear =
(compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null;
String round = stage != null ? String.valueOf(stage) : null;
return ProjectInfo.builder()
.detectionYear(detectionYear)
.stage(round)
.gukyuinApplyDttm(gukyuinApplyDttm)
.startDttm(createdDttm)
.uuid(uuid)
.labelingClosedYn(labelingClosedYn)
.inspectionClosedYn(inspectionClosedYn)
.build();
}
@@ -1160,4 +1432,23 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
labelingLabelerEntity.workerUid.eq(paramUserId))
.execute();
}
@Override
public void updateClosedYnByUuid(String uuid, String closedType, String closedYn) {
var updateQuery = queryFactory.update(mapSheetAnalInferenceEntity);
if ("LABELING".equals(closedType)) {
updateQuery.set(mapSheetAnalInferenceEntity.labelingClosedYn, closedYn);
} else if ("INSPECTION".equals(closedType)) {
updateQuery.set(mapSheetAnalInferenceEntity.inspectionClosedYn, closedYn);
}
updateQuery
.set(mapSheetAnalInferenceEntity.updatedDttm, ZonedDateTime.now())
.where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid)))
.execute();
em.flush();
em.clear();
}
}

View File

@@ -57,7 +57,6 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
QLabelingAssignmentEntity.labelingAssignmentEntity;
private final QMemberEntity memberEntity = QMemberEntity.memberEntity;
/**
* 변화탐지 년도 셀렉트박스 조회
*
@@ -81,8 +80,7 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
}
/**
* 라벨링 작업관리 목록 조회
* (복잡한 집계 쿼리로 인해 DTO 직접 반환)
* 라벨링 작업관리 목록 조회 (복잡한 집계 쿼리로 인해 DTO 직접 반환)
*
* @param searchReq 검색 조건
* @return 라벨링 작업관리 목록 페이지
@@ -171,38 +169,42 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
mapSheetAnalInferenceEntity.compareYyyy,
mapSheetAnalInferenceEntity.targetYyyy,
mapSheetAnalInferenceEntity.stage,
// createdDttm: tb_map_sheet_anal_inference.created_dttm 사용
mapSheetAnalInferenceEntity.createdDttm,
// 국유인 반영 컬럼 gukyuinApplyDttm
mapSheetAnalInferenceEntity.gukyuinApplyDttm,
mapSheetAnalDataInferenceGeomEntity.dataUid.count(),
// labelTotCnt: pnu 있고 pass_yn = false인 건수
labelTotCntExpr,
new CaseBuilder()
.when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("ASSIGNED"))
.when(
mapSheetAnalDataInferenceGeomEntity.labelState.eq(
LabelState.ASSIGNED.getId()))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("STOP"))
.when(
mapSheetAnalDataInferenceGeomEntity.labelState.eq(
LabelState.SKIP.getId())) // "STOP"?
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("LABEL_ING"))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("LABEL_COMPLETE"))
.when(
mapSheetAnalDataInferenceGeomEntity.labelState.eq(
LabelState.DONE.getId())) // "LABEL_COMPLETE"?
.then(1L)
.otherwise(0L)
.sum(),
mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min(),
// analState: tb_map_sheet_anal_inference.anal_state
mapSheetAnalInferenceEntity.analState,
// analState: tb_map_sheet_anal_inference.anal_state -> 우선은 미사용, java 단에서 로직화 해서
// 내려줌
// mapSheetAnalInferenceEntity.analState,
// normalProgressCnt: stagnation_yn = 'N'인 건수 (서브쿼리)
normalProgressCntSubQuery,
// totalAssignmentCnt: 총 배정 건수 (서브쿼리)
totalAssignmentCntSubQuery))
totalAssignmentCntSubQuery,
mapSheetAnalInferenceEntity.labelingClosedYn,
mapSheetAnalInferenceEntity.inspectionClosedYn))
.from(mapSheetAnalInferenceEntity)
.innerJoin(mapSheetAnalDataInferenceEntity)
.on(whereSubDataBuilder)
@@ -267,32 +269,29 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
whereSubBuilder.and(labelingAssignmentEntity.workerUid.eq(memberEntity.userId));
// 공통 조건 추출
BooleanExpression doneStateCondition =
labelingAssignmentEntity.workState.eq(LabelState.DONE.name());
NumberExpression<Long> assignedCnt = labelingAssignmentEntity.workerUid.count();
NumberExpression<Long> doneCnt =
this.caseSumExpression(labelingAssignmentEntity.workState.eq(LabelState.DONE.name()));
NumberExpression<Long> doneCnt = this.caseSumExpression(doneStateCondition);
NumberExpression<Long> skipCnt =
this.caseSumExpression(labelingAssignmentEntity.workState.eq(LabelState.SKIP.name()));
NumberExpression<Long> day3AgoDoneCnt =
this.caseSumExpression(
labelingAssignmentEntity
.workState
.eq(LabelState.DONE.name())
.and(this.fromDateEqExpression(labelingAssignmentEntity.modifiedDate, -3)));
doneStateCondition.and(
this.fromDateEqExpression(labelingAssignmentEntity.modifiedDate, -3)));
NumberExpression<Long> day2AgoDoneCnt =
this.caseSumExpression(
labelingAssignmentEntity
.workState
.eq(LabelState.DONE.name())
.and(this.fromDateEqExpression(labelingAssignmentEntity.modifiedDate, -2)));
doneStateCondition.and(
this.fromDateEqExpression(labelingAssignmentEntity.modifiedDate, -2)));
NumberExpression<Long> day1AgoDoneCnt =
this.caseSumExpression(
labelingAssignmentEntity
.workState
.eq(LabelState.DONE.name())
.and(this.fromDateEqExpression(labelingAssignmentEntity.modifiedDate, -1)));
doneStateCondition.and(
this.fromDateEqExpression(labelingAssignmentEntity.modifiedDate, -1)));
NumberExpression<Long> remainingCnt = assignedCnt.subtract(doneCnt);
@@ -345,7 +344,9 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
mapSheetAnalInferenceEntity
.uuid
.eq(uuid)
.and(labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id)))
.and(
labelingAssignmentEntity.analUid.eq(
mapSheetAnalInferenceEntity.id)))
.innerJoin(memberEntity)
.on(whereSubBuilder)
.where(whereBuilder)
@@ -390,8 +391,7 @@ public class LabelWorkRepositoryImpl implements LabelWorkRepositoryCustom {
}
/**
* 작업배정 상세조회
* (복잡한 집계 쿼리로 인해 DTO 직접 반환)
* 작업배정 상세조회 (복잡한 집계 쿼리로 인해 DTO 직접 반환)
*
* @param uuid 작업배정 UUID
* @return 작업배정 상세 정보

View File

@@ -11,8 +11,11 @@ spring:
hibernate:
default_batch_fetch_size: 100 # ✅ 성능 - N+1 쿼리 방지
order_updates: true # ✅ 성능 - 업데이트 순서 정렬로 데드락 방지
order_inserts: true
use_sql_comments: true # ⚠️ 선택 - SQL에 주석 추가 (디버깅용)
format_sql: true # ⚠️ 선택 - SQL 포맷팅 (가독성)
jdbc:
batch_size: 1000 # ✅ 추가 (JDBC batch)
datasource:
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds

View File

@@ -4,14 +4,18 @@ spring:
on-profile: prod
jpa:
show-sql: false
show-sql: true
hibernate:
ddl-auto: validate
properties:
hibernate:
default_batch_fetch_size: 100 # ✅ 성능 - N+1 쿼리 방지
order_updates: true # ✅ 성능 - 업데이트 순서 정렬로 데드락 방지
order_inserts: true
use_sql_comments: true # ⚠️ 선택 - SQL에 주석 추가 (디버깅용)
format_sql: true # ⚠️ 선택 - SQL 포맷팅 (가독성)
jdbc:
batch_size: 1000 # ✅ 추가 (JDBC batch)
datasource:
url: jdbc:postgresql://10.100.0.10:25432/temp