diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java index 7bc9b3eb..e51e587a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.LocalDate; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -458,4 +459,43 @@ public class LabelAllocateApiController { UUID uuid) { return ApiResponseDto.ok(labelAllocateService.isDownloadable(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) + }) + @PostMapping("/allocate-add-stblt") + public ApiResponseDto labelAllocateAddStblt( + @RequestBody @Valid LabelAllocateDto.AllocateAddStbltDto dto) { + + return ApiResponseDto.okObject( + labelAllocateService.allocateAddStbltYn( + dto.getTotalCnt(), dto.getUuid(), dto.getLabelers(), dto.getBaseDate())); + } + + @Operation(summary = "라벨링 추가 할당 가능한 건수", description = "라벨링 추가 할당 가능한 건수 API") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/allocate-add-cnt") + public ApiResponseDto allocateAddCnt( + @RequestParam UUID uuid, @RequestParam LocalDate baseDate) { + + return ApiResponseDto.ok(labelAllocateService.findAllocateAddCnt(uuid, baseDate)); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java index 2a5e4c97..7bda7d92 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java @@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.label.dto; import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; import com.kamco.cd.kamcoback.common.utils.enums.EnumType; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; import java.time.ZonedDateTime; import java.util.List; import java.util.UUID; @@ -365,9 +366,35 @@ public class LabelAllocateDto { @AllArgsConstructor @NoArgsConstructor public static class InferenceLearnDto { + private UUID analUuid; private String learnUid; private String analState; private Long analId; } + + @Getter + @Setter + @AllArgsConstructor + public static class AllocateAddStbltDto { + + @Schema(description = "총 잔여 건수", example = "179") + private Integer totalCnt; + + @Schema( + description = "추가할당할 라벨러", + example = + """ + [ + "123454", "654321", "222233", "777222" + ] + """) + private List labelers; + + @Schema(description = "회차 마스터 key", example = "c0e77cc7-8c28-46ba-9ca4-11e90246ab44") + private UUID uuid; + + @Schema(description = "기준일자", example = "2026-02-20") + private LocalDate baseDate; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java index c317d973..7c604a23 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java @@ -20,6 +20,7 @@ import com.kamco.cd.kamcoback.log.dto.AuditLogDto; import com.kamco.cd.kamcoback.log.dto.AuditLogDto.DownloadReq; import com.kamco.cd.kamcoback.postgres.core.AuditLogCoreService; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; +import java.time.LocalDate; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -302,4 +303,52 @@ public class LabelAllocateService { public boolean isDownloadable(UUID uuid) { return labelAllocateCoreService.isDownloadable(uuid); } + + /** + * 실태조사가 값 들어온 기간만큼 할당하는 로직 (최초 할당 이후 작업) + * + * @param uuid + * @param targetUsers + * @return + */ + @Transactional + public ApiResponseDto.ResponseObj allocateAddStbltYn( + Integer totalCnt, UUID uuid, List targetUsers, LocalDate baseDate) { + + int userCount = targetUsers.size(); + if (userCount == 0) { + return new ApiResponseDto.ResponseObj(ApiResponseCode.BAD_REQUEST, "추가 할당할 라벨러를 선택해주세요."); + } + + int base = totalCnt / userCount; + int remainder = totalCnt % userCount; + Long lastId = null; + List allIds = + labelAllocateCoreService.fetchNextIdsAddStbltYn( + uuid, baseDate, lastId, totalCnt.longValue()); + + // MapSheetAnalInferenceEntity analUid 가져오기 + Long analUid = labelAllocateCoreService.findMapSheetAnalInferenceUid(uuid); + + int index = 0; + for (int i = 0; i < userCount; i++) { + int assignCount = base; + // 마지막 사람에게 나머지 몰아주기 + if (i == userCount - 1) { + assignCount += remainder; + } + + int end = index + assignCount; + List sub = allIds.subList(index, end); + + labelAllocateCoreService.assignOwner(sub, targetUsers.get(i), analUid); + index = end; + } + + return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "추가 할당이 완료되었습니다."); + } + + public Long findAllocateAddCnt(UUID uuid, LocalDate baseDate) { + return labelAllocateCoreService.findAllocateAddCnt(uuid, baseDate); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java index 20f2f4b1..2bc4cbf2 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java @@ -257,7 +257,9 @@ public class LabelAllocateCoreService { // 파일이 있는지만 확인 Path path = Paths.get(responsePath).resolve(dto.getLearnUid() + ".zip"); - if (!Files.isRegularFile(path)) return false; // exists 포함 + if (!Files.isRegularFile(path)) { + return false; // exists 포함 + } String state = dto.getAnalState(); boolean isLabelingIng = @@ -265,7 +267,9 @@ public class LabelAllocateCoreService { if (isLabelingIng) { Long analId = dto.getAnalId(); - if (analId == null) return false; + if (analId == null) { + return false; + } return batchStepHistoryRepository.isDownloadable(analId); } @@ -277,4 +281,13 @@ public class LabelAllocateCoreService { .findLearnUid(uuid) .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND)); } + + public List fetchNextIdsAddStbltYn( + UUID uuid, LocalDate baseDate, Long lastId, Long totalCnt) { + return labelAllocateRepository.fetchNextIdsAddStbltYn(uuid, baseDate, lastId, totalCnt); + } + + public Long findAllocateAddCnt(UUID uuid, LocalDate baseDate) { + return labelAllocateRepository.findAllocateAddCnt(uuid, baseDate); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java index 39e94a7d..e3b85aeb 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java @@ -110,4 +110,9 @@ public interface LabelAllocateRepositoryCustom { InferenceLearnDto findLabelingIngProcessId(UUID uuid); Optional findLearnUid(UUID uuid); + + List fetchNextIdsAddStbltYn( + UUID uuid, LocalDate baseDate, Long lastId, Long totalCnt); + + Long findAllocateAddCnt(UUID uuid, LocalDate baseDate); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java index 34624974..82cc330d 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java @@ -1856,4 +1856,56 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .where(mapSheetAnalInferenceEntity.uuid.eq(uuid)) .fetchOne()); } + + @Override + public List fetchNextIdsAddStbltYn( + UUID uuid, LocalDate baseDate, Long lastId, Long totalCnt) { + ZoneId zone = ZoneId.of("Asia/Seoul"); // 기준 타임존 명확히 + ZonedDateTime nextDayStart = baseDate.plusDays(1).atStartOfDay(zone); + + return queryFactory + .select( + Projections.constructor( + AllocateInfoDto.class, + mapSheetAnalDataInferenceGeomEntity.geoUid, + mapSheetAnalDataInferenceGeomEntity.mapSheetNum, + mapSheetAnalDataInferenceGeomEntity.pnu)) + .from(mapSheetAnalInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceEntity) + .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) + .innerJoin(mapSheetAnalDataInferenceGeomEntity) + .on( + mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid), + mapSheetAnalDataInferenceGeomEntity.pnu.gt(0), + mapSheetAnalDataInferenceGeomEntity.fitState.eq(ImageryFitStatus.UNFIT.getId()), + mapSheetAnalDataInferenceGeomEntity.fitStateDttm.lt(nextDayStart), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .where( + mapSheetAnalInferenceEntity.uuid.eq(uuid), + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId)) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(totalCnt) + .fetch(); + } + + @Override + public Long findAllocateAddCnt(UUID uuid, LocalDate baseDate) { + ZoneId zone = ZoneId.of("Asia/Seoul"); // 기준 타임존 명확히 + ZonedDateTime nextDayStart = baseDate.plusDays(1).atStartOfDay(zone); + + return queryFactory + .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) + .from(mapSheetAnalInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceEntity) + .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) + .innerJoin(mapSheetAnalDataInferenceGeomEntity) + .on( + mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid), + mapSheetAnalDataInferenceGeomEntity.pnu.gt(0), + mapSheetAnalDataInferenceGeomEntity.fitState.eq(ImageryFitStatus.UNFIT.getId()), + mapSheetAnalDataInferenceGeomEntity.fitStateDttm.lt(nextDayStart), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .where(mapSheetAnalInferenceEntity.uuid.eq(uuid)) + .fetchOne(); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataLabelRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataLabelRepositoryImpl.java index 823b02e5..82f73c5d 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataLabelRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataLabelRepositoryImpl.java @@ -120,6 +120,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport .orderBy( mapSheetAnalInferenceEntity.targetYyyy.asc(), mapSheetAnalInferenceEntity.compareYyyy.asc(), + labelingAssignmentEntity.assignGroupId.asc(), labelingAssignmentEntity.createdDate.asc(), labelingAssignmentEntity.inferenceGeomUid.asc()) .fetch(); @@ -641,6 +642,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport queryFactory .select( labelingAssignmentEntity.assignmentUid, + labelingAssignmentEntity.assignGroupId, labelingAssignmentEntity.createdDate, labelingAssignmentEntity.inferenceGeomUid, mapSheetAnalInferenceEntity.targetYyyy, @@ -659,6 +661,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport .orderBy( mapSheetAnalInferenceEntity.targetYyyy.asc(), mapSheetAnalInferenceEntity.compareYyyy.asc(), + labelingAssignmentEntity.assignGroupId.asc(), labelingAssignmentEntity.createdDate.asc(), labelingAssignmentEntity.inferenceGeomUid.asc()) .limit(1) @@ -673,6 +676,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport Long inferenceGeomUid = firstAssigned.get(labelingAssignmentEntity.inferenceGeomUid); Integer targetYyyy = firstAssigned.get(mapSheetAnalInferenceEntity.targetYyyy); Integer compareYyyy = firstAssigned.get(mapSheetAnalInferenceEntity.compareYyyy); + String assignGroupId = firstAssigned.get(labelingAssignmentEntity.assignGroupId); BooleanExpression beforeCondition = mapSheetAnalInferenceEntity @@ -688,12 +692,20 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport .targetYyyy .eq(targetYyyy) .and(mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy)) + .and(labelingAssignmentEntity.assignGroupId.lt(assignGroupId))) + .or( + mapSheetAnalInferenceEntity + .targetYyyy + .eq(targetYyyy) + .and(mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy)) + .and(labelingAssignmentEntity.assignGroupId.eq(assignGroupId)) .and(labelingAssignmentEntity.createdDate.lt(createdDttm))) .or( mapSheetAnalInferenceEntity .targetYyyy .eq(targetYyyy) .and(mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy)) + .and(labelingAssignmentEntity.assignGroupId.eq(assignGroupId)) .and(labelingAssignmentEntity.createdDate.eq(createdDttm)) .and(labelingAssignmentEntity.inferenceGeomUid.lt(inferenceGeomUid)));