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 9e359a82..51520875 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -1,9 +1,10 @@ package com.kamco.cd.kamcoback.label; -import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +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.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; @@ -36,185 +37,27 @@ public class LabelAllocateApiController { @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.Basic.class), - examples = - @ExampleObject( - name = "사용자 목록 응답", - value = - """ - { - "data": [ - { - "userRole": "LABELER", - "employeeNo": "1234567", - "name": "김라벨" - }, - { - "userRole": "LABELER", - "employeeNo": "2345678", - "name": "이작업" - } - ] - } - """))), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @Parameter(description = "사용자 역할 (LABELER: 라벨러, REVIEWER: 검수자)", example = "LABELER") + @Parameter( + description = "사용자 역할", + example = "LABELER", + schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) @RequestParam - @Schema() String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } - @Operation( - summary = "작업자 목록 및 3일치 통계 조회", - description = """ - 학습데이터 제작 현황 조회 API입니다. - """) + @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.") @ApiResponses( value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = WorkerListResponse.class), - examples = { - @ExampleObject( - name = "라벨러 조회 예시", - description = "라벨러 작업자들의 통계 정보", - value = - """ - { - "data": { - "progressInfo": { - "labelingProgressRate": 79.34, - "workStatus": "진행중", - "completedCount": 6554, - "totalAssignedCount": 8258, - "labelerCount": 5, - "remainingLabelCount": 1704, - "inspectorCount": 3, - "remainingInspectCount": 890 - }, - "workers": [ - { - "workerId": "1234567", - "workerName": "김라벨", - "workerType": "LABELER", - "totalAssigned": 1500, - "completed": 1100, - "skipped": 50, - "remaining": 350, - "history": { - "day1Ago": 281, - "day2Ago": 302, - "day3Ago": 294, - "average": 292 - }, - "isStagnated": false - }, - { - "workerId": "2345678", - "workerName": "이작업", - "workerType": "LABELER", - "totalAssigned": 2000, - "completed": 1850, - "skipped": 100, - "remaining": 50, - "history": { - "day1Ago": 5, - "day2Ago": 3, - "day3Ago": 8, - "average": 5 - }, - "isStagnated": true - } - ] - } - } - """), - @ExampleObject( - name = "검수자 조회 예시", - description = "검수자 작업자들의 통계 정보", - value = - """ - { - "data": { - "progressInfo": { - "labelingProgressRate": 79.34, - "workStatus": "진행중", - "completedCount": 6554, - "totalAssignedCount": 8258, - "labelerCount": 5, - "remainingLabelCount": 1704, - "inspectorCount": 3, - "remainingInspectCount": 890 - }, - "workers": [ - { - "workerId": "9876543", - "workerName": "박검수", - "workerType": "REVIEWER", - "totalAssigned": 1200, - "completed": 980, - "skipped": 20, - "remaining": 200, - "history": { - "day1Ago": 150, - "day2Ago": 145, - "day3Ago": 155, - "average": 150 - }, - "isStagnated": false - } - ] - } - } - """) - })), - @ApiResponse( - responseCode = "404", - description = "데이터를 찾을 수 없음", - content = - @Content( - examples = - @ExampleObject( - value = - """ - { - "error": { - "code": "NOT_FOUND", - "message": "해당 분석 ID의 데이터를 찾을 수 없습니다." - } - } - """))), - @ApiResponse( - responseCode = "500", - description = "서버 오류", - content = - @Content( - examples = - @ExampleObject( - value = - """ - { - "error": { - "code": "INTERNAL_SERVER_ERROR", - "message": "서버에 문제가 발생 하였습니다." - } - } - """))) + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( @@ -249,14 +92,15 @@ public class LabelAllocateApiController { @RequestParam(required = false) String sort) { - // type이 null이면 전체 조회 (일단 LABELER로 기본 설정) - String workerType = (type == null || type.isEmpty()) ? "LABELER" : type; + // type이 null이면 기본값으로 LABELER 설정 + String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( labelAllocateService.getWorkerStatistics( analUid, workerType, searchName, searchEmployeeNo, sort)); } + @Operation(summary = "작업 배정", description = "작업 배정") @ApiResponses( value = { @ApiResponse( @@ -265,32 +109,136 @@ public class LabelAllocateApiController { content = @Content( mediaType = "application/json", - schema = @Schema(implementation = Long.class))), + schema = @Schema(implementation = Long.class), + examples = { + @ExampleObject( + name = "라벨러 할당 예시", + description = "라벨러 할당 예시", + value = + """ + { + "autoType": "AUTO", + "stage": 4, + "labelers": [ + { + "userId": "123456", + "demand": 1000 + }, + { + "userId": "010222297501", + "demand": 400 + }, + { + "userId": "01022223333", + "demand": 440 + } + ], + "inspectors": [ + { + "inspectorUid": "K20251216001", + "userCount": 1000 + }, + { + "inspectorUid": "01022225555", + "userCount": 340 + }, + { + "inspectorUid": "K20251212001", + "userCount": 500 + } + + ] + } + """) + })), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PostMapping("/allocate") - public ApiResponseDto labelAllocate( - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "라벨링 수량 할당", - required = true, - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = LabelAllocateDto.AllocateDto.class))) - @RequestBody - LabelAllocateDto.AllocateDto dto) - throws Exception { - + public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) { labelAllocateService.allocateAsc( - dto.getAutoType(), dto.getStage(), dto.getLabelers(), dto.getInspectors()); + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getAnalUid()); return ApiResponseDto.ok(null); } - @GetMapping - public ApiResponseDto findInferenceDetail(@RequestParam Long analUid) { + @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/stage-detail") + public ApiResponseDto findInferenceDetail( + @Parameter(description = "분석 ID", required = true, example = "3") @RequestParam + Long analUid) { return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(analUid)); } + + @Operation(summary = "작업이관 > 라벨러 상세 정보", description = "작업이관 > 라벨러 상세 정보") + @GetMapping("/labeler-detail") + public ApiResponseDto findLabelerDetail( + @RequestParam(defaultValue = "01022223333") String userId, + @RequestParam(defaultValue = "3") Long analUid) { + return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, analUid)); + } + + @Operation(summary = "작업 이관", description = "작업 이관") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class), + examples = { + @ExampleObject( + name = "라벨러 할당 예시", + description = "라벨러 할당 예시", + value = + """ + { + "autoType": "AUTO", + "stage": 4, + "labelers": [ + { + "userId": "123456", + "demand": 10 + }, + { + "userId": "010222297501", + "demand": 5 + } + ] + } + """) + })), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/allocate-move") + public ApiResponseDto labelAllocateMove( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "라벨링 이관", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = LabelAllocateDto.AllocateMoveDto.class))) + @RequestBody + LabelAllocateDto.AllocateMoveDto dto) { + + labelAllocateService.allocateMove(dto.getAutoType(), dto.getStage(), dto.getLabelers()); + + return ApiResponseDto.ok(null); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java index 9314910b..34524bbd 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -2,14 +2,9 @@ package com.kamco.cd.kamcoback.label; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetInspector; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; -import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import com.kamco.cd.kamcoback.label.service.LabelWorkService; -import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -17,15 +12,12 @@ 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.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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; @Slf4j @@ -52,9 +44,7 @@ public class LabelWorkerApiController { }) @PostMapping("/label-work-mng-list") public ApiResponseDto> labelWorkMngList( - @RequestBody @Valid LabelWorkDto.LabelWorkMngSearchReq searchReq) { + @RequestBody @Valid LabelWorkDto.LabelWorkMngSearchReq searchReq) { return ApiResponseDto.ok(labelWorkService.labelWorkMngList(searchReq)); } - - } 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 5b508a07..3d126943 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 @@ -42,8 +42,10 @@ public class LabelAllocateDto { @Getter @AllArgsConstructor public enum LabelState implements EnumType { + WAIT("대기"), ASSIGNED("배정"), SKIP("스킵"), + DONE("완료"), COMPLETE("완료"); private String desc; @@ -85,6 +87,9 @@ public class LabelAllocateDto { @AllArgsConstructor public static class AllocateDto { + @Schema(description = "분석 ID", example = "3", required = true) + private Long analUid; + @Schema(description = "자동/수동여부(AUTO/MANUAL)", example = "AUTO") private String autoType; @@ -164,4 +169,33 @@ public class LabelAllocateDto { private Long labelCnt; private Long inspectorCnt; } + + @Getter + @Setter + @AllArgsConstructor + public static class LabelerDetail { + + private String roleType; + private String name; + private String userId; // 사번 + private Long count; + private Long completeCnt; + private Long skipCnt; + private Double percent; + } + + @Getter + @Setter + @AllArgsConstructor + public static class AllocateMoveDto { + + @Schema(description = "자동/수동여부(AUTO/MANUAL)", example = "AUTO") + private String autoType; + + @Schema(description = "회차", example = "4") + private Integer stage; + + @Schema(description = "라벨러 할당 목록") + private List labelers; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java index cd3d7d6e..e712746b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java @@ -1,26 +1,19 @@ package com.kamco.cd.kamcoback.label.dto; -import com.kamco.cd.kamcoback.common.enums.MngStateType; -import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; -import com.kamco.cd.kamcoback.common.utils.enums.EnumType; import com.kamco.cd.kamcoback.common.utils.enums.Enums; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState; import io.swagger.v3.oas.annotations.media.Schema; import java.time.ZonedDateTime; -import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; public class LabelWorkDto { - - @Schema(name = "LabelWorkMng", description = "라벨작업관리") @Getter @Setter @@ -39,7 +32,6 @@ public class LabelWorkDto { private Long labelCompleteTotCnt; @JsonFormatDttm private ZonedDateTime labelStartDttm; - public String getLabelState() { String mngState = "PENDING"; @@ -69,7 +61,6 @@ public class LabelWorkDto { } } - @Schema(name = "LabelWorkMngSearchReq", description = "라벨작업관리 검색 요청") @Getter @Setter @@ -98,5 +89,4 @@ public class LabelWorkDto { return PageRequest.of(page, size); } } - } 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 a51a2ae7..79ad856b 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 @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.label.service; 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.TargetInspector; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; @@ -18,9 +19,12 @@ import org.springframework.transaction.annotation.Transactional; @Slf4j @Service -@Transactional(readOnly = true) +@Transactional public class LabelAllocateService { + private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량) + private static final int BATCH_SIZE = 100; // 배정 배치 크기 + private final LabelAllocateCoreService labelAllocateCoreService; public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { @@ -30,19 +34,23 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param targetUsers + * @param autoType 자동/수동 배정 타입 + * @param stage 회차 + * @param targetUsers 라벨러 목록 + * @param targetInspectors 검수자 목록 + * @param analUid 분석 ID */ @Transactional public void allocateAsc( String autoType, Integer stage, List targetUsers, - List targetInspectors) - throws Exception { + List targetInspectors, + Long analUid) { Long lastId = null; - // geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return - Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(3L); // TODO + // geom 잔여건수 조회 + Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(analUid, stage); // Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); // if (!Objects.equals(chargeCnt, totalDemand)) { // log.info("chargeCnt != totalDemand"); @@ -53,18 +61,18 @@ public class LabelAllocateService { return; } - List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt); + List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, analUid); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); List sub = allIds.subList(index, end); - labelAllocateCoreService.assignOwner(sub, target.getUserId()); + labelAllocateCoreService.assignOwner(sub, target.getUserId(), analUid); index = end; } // 검수자에게 userCount명 만큼 할당 - List list = labelAllocateCoreService.findAssignedLabelerList(3L); + List list = labelAllocateCoreService.findAssignedLabelerList(analUid); int from = 0; for (TargetInspector inspector : targetInspectors) { @@ -137,8 +145,8 @@ public class LabelAllocateService { worker.setHistory(history); - // 정체 여부 판단 (3일 평균이 특정 기준 미만일 때 - 예: 10건 미만) - if (average < 10) { + // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때) + if (average < STAGNATION_THRESHOLD) { worker.setIsStagnated(true); } } @@ -149,4 +157,28 @@ public class LabelAllocateService { public InferenceDetail findInferenceDetail(Long analUid) { return labelAllocateCoreService.findInferenceDetail(analUid); } + + public void allocateMove(String autoType, Integer stage, List targetUsers) { + Long lastId = null; + + Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); + + if (chargeCnt <= 0) { + return; + } + + List allIds = labelAllocateCoreService.fetchNextMoveIds(lastId, chargeCnt); + int index = 0; + for (TargetUser target : targetUsers) { + int end = index + target.getDemand(); + List sub = allIds.subList(index, end); + + labelAllocateCoreService.assignOwnerMove(sub, target.getUserId()); + index = end; + } + } + + public LabelerDetail findLabelerDetail(String userId, Long analUid) { + return labelAllocateCoreService.findLabelerDetail(userId, analUid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java index a16d447b..3a8d6553 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java @@ -1,16 +1,8 @@ package com.kamco.cd.kamcoback.label.service; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetInspector; -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.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; -import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import com.kamco.cd.kamcoback.postgres.core.LabelWorkCoreService; -import jakarta.transaction.Transactional; -import java.util.List; -import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -25,8 +17,6 @@ public class LabelWorkService { this.labelWorkCoreService = labelWorkCoreService; } - - public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { return labelWorkCoreService.labelWorkMngList(searchReq); 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 e7b53b73..d9244fb0 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 @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.postgres.core; 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.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -19,12 +20,12 @@ public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; - public List fetchNextIds(Long lastId, Long batchSize) { - return labelAllocateRepository.fetchNextIds(lastId, batchSize); + public List fetchNextIds(Long lastId, Long batchSize, Long analUid) { + return labelAllocateRepository.fetchNextIds(lastId, batchSize, analUid); } - public void assignOwner(List ids, String userId) { - labelAllocateRepository.assignOwner(ids, userId); + public void assignOwner(List ids, String userId, Long analUid) { + labelAllocateRepository.assignOwner(ids, userId, analUid); } public List findAssignedLabelerList(Long analUid) { @@ -33,8 +34,8 @@ public class LabelAllocateCoreService { .toList(); } - public Long findLabelUnAssignedCnt(Long analUid) { - return labelAllocateRepository.findLabelUnAssignedCnt(analUid); + public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { + return labelAllocateRepository.findLabelUnAssignedCnt(analUid, stage); } public void assignInspector(UUID assignmentUid, String inspectorUid) { @@ -71,4 +72,20 @@ public class LabelAllocateCoreService { public InferenceDetail findInferenceDetail(Long analUid) { return labelAllocateRepository.findInferenceDetail(analUid); } + + public Long findLabelUnCompleteCnt(Long analUid) { + return labelAllocateRepository.findLabelUnCompleteCnt(analUid); + } + + public List fetchNextMoveIds(Long lastId, Long chargeCnt) { + return labelAllocateRepository.fetchNextMoveIds(lastId, chargeCnt); + } + + public void assignOwnerMove(List sub, String userId) { + labelAllocateRepository.assignOwnerMove(sub, userId); + } + + public LabelerDetail findLabelerDetail(String userId, Long analUid) { + return labelAllocateRepository.findLabelerDetail(userId, analUid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java index 9c87a7be..a1be92a5 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java @@ -1,17 +1,8 @@ package com.kamco.cd.kamcoback.postgres.core; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; -import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; -import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto.ErrorDataDto; -import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; -import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository; import com.kamco.cd.kamcoback.postgres.repository.label.LabelWorkRepository; -import jakarta.validation.Valid; -import java.util.List; -import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -22,10 +13,7 @@ public class LabelWorkCoreService { private final LabelWorkRepository labelWorkRepository; - public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { return labelWorkRepository.labelWorkMngList(searchReq); } - - } 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 df73428b..a1309169 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 @@ -1,6 +1,7 @@ package com.kamco.cd.kamcoback.postgres.repository.label; 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.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -11,13 +12,13 @@ import java.util.UUID; public interface LabelAllocateRepositoryCustom { - List fetchNextIds(Long lastId, Long batchSize); + List fetchNextIds(Long lastId, Long batchSize, Long analUid); - void assignOwner(List ids, String userId); + void assignOwner(List ids, String userId, Long analUid); List findAssignedLabelerList(Long analUid); - Long findLabelUnAssignedCnt(Long analUid); + Long findLabelUnAssignedCnt(Long analUid, Integer stage); void assignInspector(UUID assignmentUid, String userId); @@ -36,4 +37,12 @@ public interface LabelAllocateRepositoryCustom { void assignInspectorBulk(List assignmentUids, String inspectorUid); InferenceDetail findInferenceDetail(Long analUid); + + List fetchNextMoveIds(Long lastId, Long batchSize); + + Long findLabelUnCompleteCnt(Long analUid); + + void assignOwnerMove(List sub, String userId); + + LabelerDetail findLabelerDetail(String userId, Long analUid); } 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 88d07305..3c32e99b 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 @@ -7,7 +7,9 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; 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.InspectState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -23,6 +25,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; @@ -44,16 +47,25 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @PersistenceContext private EntityManager em; @Override - public List fetchNextIds(Long lastId, Long batchSize) { + public List fetchNextIds(Long lastId, Long batchSize, Long analUid) { + // analUid로 분석 정보 조회 + MapSheetAnalEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: " + analUid); + } return queryFactory .select(mapSheetAnalDataInferenceGeomEntity.geoUid) .from(mapSheetAnalDataInferenceGeomEntity) .where( - // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(2022), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(2024), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) .limit(batchSize) @@ -61,24 +73,26 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public void assignOwner(List ids, String userId) { + public void assignOwner(List ids, String userId, Long analUid) { // data_geom 테이블에 label state 를 ASSIGNED 로 update queryFactory .update(mapSheetAnalDataInferenceGeomEntity) .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) .set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now()) + .set(mapSheetAnalDataInferenceGeomEntity.testState, InspectState.UNCONFIRM.getId()) + .set(mapSheetAnalDataInferenceGeomEntity.testStateDttm, ZonedDateTime.now()) .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) .execute(); // 라벨러 할당 테이블에 insert String sql = """ - insert into tb_labeling_assignment - (assignment_uid, inference_geom_uid, worker_uid, - work_state, assign_group_id, anal_uid) - values (?, ?, ?, ?, ?, ?) - """; + insert into tb_labeling_assignment + (assignment_uid, inference_geom_uid, worker_uid, + work_state, assign_group_id, anal_uid) + values (?, ?, ?, ?, ?, ?) + """; for (Long geoUid : ids) { em.createNativeQuery(sql) @@ -87,7 +101,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .setParameter(3, userId) .setParameter(4, LabelState.ASSIGNED.getId()) .setParameter(5, "") - .setParameter(6, 3) + .setParameter(6, analUid) .executeUpdate(); } @@ -108,7 +122,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public Long findLabelUnAssignedCnt(Long analUid) { + public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { MapSheetAnalEntity entity = queryFactory .selectFrom(mapSheetAnalEntity) @@ -116,7 +130,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .fetchOne(); if (Objects.isNull(entity)) { - throw new EntityNotFoundException(); + throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: " + analUid); } return queryFactory @@ -125,7 +139,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .where( mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.stage.eq(4), // TODO: 회차 컬럼을 가져와야 할 듯? + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) .fetchOne(); } @@ -149,7 +163,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto memberEntity.employeeNo, memberEntity.name)) .from(memberEntity) - .where(memberEntity.userRole.eq(role), memberEntity.status.eq("ACTIVE")) + .where( + memberEntity.userRole.eq(role), + memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) .orderBy(memberEntity.name.asc()) .fetch(); } @@ -164,12 +180,12 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = - "INSPECTOR".equals(workerType) + "REVIEWER".equals(workerType) ? labelingAssignmentEntity.inspectorUid : labelingAssignmentEntity.workerUid; BooleanExpression workerCondition = - "INSPECTOR".equals(workerType) + "REVIEWER".equals(workerType) ? labelingAssignmentEntity.inspectorUid.isNotNull() : labelingAssignmentEntity.workerUid.isNotNull(); @@ -223,7 +239,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .from(labelingAssignmentEntity) .leftJoin(memberEntity) .on( - "INSPECTOR".equals(workerType) + "REVIEWER".equals(workerType) ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) @@ -364,7 +380,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()); BooleanExpression workerCondition = - "INSPECTOR".equals(workerType) + "REVIEWER".equals(workerType) ? labelingAssignmentEntity.inspectorUid.eq(workerId) : labelingAssignmentEntity.workerUid.eq(workerId); @@ -375,7 +391,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .where( labelingAssignmentEntity.analUid.eq(analUid), workerCondition, - labelingAssignmentEntity.workState.in("DONE", "SKIP"), + labelingAssignmentEntity.workState.in( + LabelState.DONE.getId(), LabelState.SKIP.getId()), labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) .fetchOne(); @@ -415,4 +432,122 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto mapSheetAnalEntity.detectingCnt) .fetchOne(); } + + @Override + public List fetchNextMoveIds(Long lastId, Long batchSize) { + MapSheetAnalEntity entity = + queryFactory + .selectFrom(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(3L)) // TODO + .fetchOne(); + + if (Objects.isNull(entity)) { + throw new EntityNotFoundException(); + } + + return queryFactory + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), + mapSheetAnalDataInferenceGeomEntity.labelState.in( + LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(batchSize) + .fetch(); + } + + @Override + public Long findLabelUnCompleteCnt(Long analUid) { + MapSheetAnalEntity entity = + queryFactory + .selectFrom(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); + + if (Objects.isNull(entity)) { + throw new EntityNotFoundException(); + } + + return queryFactory + .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), + mapSheetAnalDataInferenceGeomEntity.stage.eq(4), // TODO: 회차 컬럼을 가져와야 할 듯? + mapSheetAnalDataInferenceGeomEntity.labelState.in( + LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) + .fetchOne(); + } + + @Transactional + @Override + public void assignOwnerMove(List sub, String userId) { + queryFactory + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.workerUid, userId) + .where(labelingAssignmentEntity.inferenceGeomUid.in(sub)) + .execute(); + + em.clear(); + } + + @Override + public LabelerDetail findLabelerDetail(String userId, Long analUid) { + NumberExpression assignedCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression skipCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression completeCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq(LabelState.COMPLETE.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression percent = + new CaseBuilder() + .when(completeCnt.eq(0L)) + .then(0.0) + .otherwise( + Expressions.numberTemplate( + Double.class, + "round({0} / {1}, 2)", + labelingAssignmentEntity.count(), + completeCnt)); + + return queryFactory + .select( + Projections.constructor( + LabelerDetail.class, + memberEntity.userRole, + memberEntity.name, + memberEntity.employeeNo, + assignedCnt, + skipCnt, + completeCnt, + percent)) + .from(memberEntity) + .innerJoin(labelingAssignmentEntity) + .on( + memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid), + labelingAssignmentEntity.analUid.eq(analUid)) + .where(memberEntity.employeeNo.eq(userId)) + .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.employeeNo) + .fetchOne(); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepository.java index f094d50a..9cbad5b0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepository.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepository.java @@ -4,5 +4,4 @@ import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntit import org.springframework.data.jpa.repository.JpaRepository; public interface LabelWorkRepository - extends JpaRepository, - LabelWorkRepositoryCustom {} + extends JpaRepository, LabelWorkRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java index 37a0e289..6a27f5d0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java @@ -1,11 +1,7 @@ package com.kamco.cd.kamcoback.postgres.repository.label; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; -import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; -import java.util.List; -import java.util.UUID; import org.springframework.data.domain.Page; public interface LabelWorkRepositoryCustom {