From cc5015213b5dd80a31e30faba2da4cb6fc85ecd8 Mon Sep 17 00:00:00 2001 From: Moon Date: Fri, 2 Jan 2026 20:59:19 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=EA=B4=80=EB=A6=AC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelWorkerApiController.java | 60 +++++++ .../cd/kamcoback/label/dto/LabelWorkDto.java | 102 ++++++++++++ .../label/service/LabelWorkService.java | 34 ++++ .../postgres/core/LabelWorkCoreService.java | 31 ++++ .../repository/label/LabelWorkRepository.java | 8 + .../label/LabelWorkRepositoryCustom.java | 14 ++ .../label/LabelWorkRepositoryImpl.java | 157 ++++++++++++++++++ 7 files changed, 406 insertions(+) create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepository.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java new file mode 100644 index 00000000..9314910b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -0,0 +1,60 @@ +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; +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 +@Tag(name = "라벨링 작업 관리", description = "라벨링 작업 관리") +@RequestMapping({"/api/label"}) +@RequiredArgsConstructor +@RestController +public class LabelWorkerApiController { + + private final LabelWorkService labelWorkService; + + @Operation(summary = "라벨링작업 관리 목록 조회", description = "라벨링작업 관리 목록 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/label-work-mng-list") + public ApiResponseDto> labelWorkMngList( + @RequestBody @Valid LabelWorkDto.LabelWorkMngSearchReq searchReq) { + return ApiResponseDto.ok(labelWorkService.labelWorkMngList(searchReq)); + } + + +} 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 new file mode 100644 index 00000000..cd3d7d6e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java @@ -0,0 +1,102 @@ +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 + @NoArgsConstructor + @AllArgsConstructor + public static class LabelWorkMng { + + private int compareYyyy; + private int targetYyyy; + private int stage; + @JsonFormatDttm private ZonedDateTime createdDttm; + private Long detectionTotCnt; + private Long labelTotCnt; + private Long labelStopTotCnt; + private Long labelIngTotCnt; + private Long labelCompleteTotCnt; + @JsonFormatDttm private ZonedDateTime labelStartDttm; + + + public String getLabelState() { + + String mngState = "PENDING"; + + if (this.labelTotCnt == 0) mngState = "PENDING"; + else if (this.labelIngTotCnt > 0) mngState = "LABEL_ING"; + else if (this.labelTotCnt <= labelCompleteTotCnt) mngState = "LABEL_COMPLETE"; + + return mngState; + } + + public String getLabelStateName() { + String enumId = this.getLabelState(); + if (enumId == null || enumId.isEmpty()) { + enumId = "PENDING"; + } + + LabelMngState type = Enums.fromId(LabelMngState.class, enumId); + return type.getText(); + } + + public double getLabelRate() { + if (this.labelTotCnt == null || this.labelCompleteTotCnt == 0) { + return 0.0; + } + return (double) this.labelTotCnt / this.labelCompleteTotCnt * 100.0; + } + } + + + @Schema(name = "LabelWorkMngSearchReq", description = "라벨작업관리 검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class LabelWorkMngSearchReq { + + // 페이징 파라미터 + @Schema(description = "페이지 번호 (0부터 시작) ", example = "0") + private int page = 0; + + @Schema(description = "페이지 크기", example = "20") + private int size = 20; + + @Schema(description = "변화탐지년도", example = "2024") + private Integer detectYyyy; + + @Schema(description = "시작일", example = "20240101") + private String strtDttm; + + @Schema(description = "종료일", example = "20241201") + private String endDttm; + + public Pageable toPageable() { + + return PageRequest.of(page, size); + } + } + +} 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 new file mode 100644 index 00000000..a16d447b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelWorkService.java @@ -0,0 +1,34 @@ +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; + +@Slf4j +@Service +public class LabelWorkService { + + private final LabelWorkCoreService labelWorkCoreService; + + public LabelWorkService(LabelWorkCoreService labelWorkCoreService) { + 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/LabelWorkCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java new file mode 100644 index 00000000..9c87a7be --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java @@ -0,0 +1,31 @@ +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; + +@Service +@RequiredArgsConstructor +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/LabelWorkRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepository.java new file mode 100644 index 00000000..f094d50a --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepository.java @@ -0,0 +1,8 @@ +package com.kamco.cd.kamcoback.postgres.repository.label; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LabelWorkRepository + 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 new file mode 100644 index 00000000..37a0e289 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryCustom.java @@ -0,0 +1,14 @@ +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 { + + public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java new file mode 100644 index 00000000..a7e1c3fa --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java @@ -0,0 +1,157 @@ +package com.kamco.cd.kamcoback.postgres.repository.label; + +import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; + +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; +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.entity.MapSheetAnalDataGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityNotFoundException; +import jakarta.persistence.PersistenceContext; +import jakarta.validation.Valid; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport + implements LabelWorkRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + @PersistenceContext private EntityManager em; + + public LabelWorkRepositoryImpl(JPAQueryFactory queryFactory) { + super(MapSheetAnalDataGeomEntity.class); + this.queryFactory = queryFactory; + } + + + @Override + public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { + + Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize()); + BooleanBuilder whereBuilder = new BooleanBuilder(); + BooleanBuilder whereSubBuilder = new BooleanBuilder(); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + + if (searchReq.getDetectYyyy() != null) { + whereBuilder.and(mapSheetAnalDataInferenceEntity.targetYyyy.eq(searchReq.getDetectYyyy())); + } + + //mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id) + + whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); + + if (searchReq.getStrtDttm() != null && ! searchReq.getStrtDttm().isEmpty()) { + + whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); + + whereSubBuilder.and( + Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", mapSheetAnalDataInferenceEntity.labelStateDttm) + .gt("2024-01-01") + ); + } + + + + List foundContent = + queryFactory + .select( + Projections.constructor( + LabelWorkMng.class, + mapSheetAnalDataInferenceEntity.compareYyyy, + mapSheetAnalDataInferenceEntity.targetYyyy, + mapSheetAnalDataInferenceEntity.stage, + mapSheetAnalDataInferenceEntity.createdDttm.min(), + mapSheetAnalDataInferenceEntity.detectingCnt.sum(), + mapSheetAnalDataInferenceGeomEntity.dataUid.count(), + + new CaseBuilder() + .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("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")) + .then(1L) + .otherwise(0L) + .sum(), + + mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min() + )) + .from(mapSheetAnalDataInferenceEntity) + .leftJoin(mapSheetAnalDataInferenceGeomEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy( + mapSheetAnalDataInferenceEntity.compareYyyy, + mapSheetAnalDataInferenceEntity.targetYyyy, + mapSheetAnalDataInferenceEntity.stage + ) + .orderBy(mapSheetAnalDataInferenceEntity.targetYyyy.desc() + ,mapSheetAnalDataInferenceEntity.stage.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + /* + Long countQuery = + queryFactory + .select(mapSheetAnalDataInferenceEntity.count()) + .from(mapSheetAnalDataInferenceEntity) + .leftJoin(mapSheetAnalDataInferenceGeomEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy( + mapSheetAnalDataInferenceEntity.compareYyyy, + mapSheetAnalDataInferenceEntity.targetYyyy, + mapSheetAnalDataInferenceEntity.stage + ) + .fetchOne(); + + */ + + Long countQuery = foundContent.stream().count(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } +} From 3a458225c98d152de12059fe6b3c812360b1fed1 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:07:14 +0900 Subject: [PATCH 2/9] Build Error Fix --- .../label/LabelAllocateApiController.java | 226 +++--------------- .../kamcoback/label/dto/LabelAllocateDto.java | 2 + .../label/service/LabelAllocateService.java | 15 +- .../label/LabelAllocateRepositoryImpl.java | 3 +- 4 files changed, 43 insertions(+), 203 deletions(-) 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..88425c94 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -1,6 +1,6 @@ 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; @@ -8,8 +8,6 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -36,185 +34,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,48 +89,40 @@ 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( - 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) + @ApiResponse(responseCode = "200", description = "배정 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"), + @ApiResponse(responseCode = "500", description = "서버 오류") }) @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()); return ApiResponseDto.ok(null); } + @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping - public ApiResponseDto findInferenceDetail(@RequestParam Long analUid) { + public ApiResponseDto findInferenceDetail( + @Parameter(description = "분석 ID", required = true, example = "3") @RequestParam + Long analUid) { return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(analUid)); } } 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..80d96744 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; 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..f01de1cd 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 @@ -21,6 +21,9 @@ import org.springframework.transaction.annotation.Transactional; @Transactional(readOnly = true) 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,15 +33,17 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param targetUsers + * @param autoType 자동/수동 배정 타입 + * @param stage 회차 + * @param targetUsers 라벨러 목록 + * @param targetInspectors 검수자 목록 */ @Transactional public void allocateAsc( String autoType, Integer stage, List targetUsers, - List targetInspectors) - throws Exception { + List targetInspectors) { Long lastId = null; // geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return @@ -137,8 +142,8 @@ public class LabelAllocateService { worker.setHistory(history); - // 정체 여부 판단 (3일 평균이 특정 기준 미만일 때 - 예: 10건 미만) - if (average < 10) { + // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때) + if (average < STAGNATION_THRESHOLD) { worker.setIsStagnated(true); } } 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..c98f5308 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 @@ -375,7 +375,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(); From 358d932e9609fc1f070255523c3433f58cd0f76d Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Fri, 2 Jan 2026 21:20:46 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=EA=B0=9C=EC=84=A0=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 6 +- .../label/LabelWorkerApiController.java | 12 +- .../kamcoback/label/dto/LabelAllocateDto.java | 3 + .../cd/kamcoback/label/dto/LabelWorkDto.java | 10 -- .../label/service/LabelAllocateService.java | 14 ++- .../label/service/LabelWorkService.java | 10 -- .../core/LabelAllocateCoreService.java | 12 +- .../postgres/core/LabelWorkCoreService.java | 12 -- .../label/LabelAllocateRepositoryCustom.java | 6 +- .../label/LabelAllocateRepositoryImpl.java | 31 +++-- .../repository/label/LabelWorkRepository.java | 3 +- .../label/LabelWorkRepositoryCustom.java | 4 - .../label/LabelWorkRepositoryImpl.java | 118 +++++++----------- 13 files changed, 95 insertions(+), 146 deletions(-) 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 88425c94..07bca2c8 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -107,7 +107,11 @@ public class LabelAllocateApiController { @PostMapping("/allocate") 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); } 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 80d96744..7aa25751 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 @@ -87,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; 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 f01de1cd..daed4f6f 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 @@ -37,17 +37,19 @@ public class LabelAllocateService { * @param stage 회차 * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 + * @param analUid 분석 ID */ @Transactional public void allocateAsc( String autoType, Integer stage, List targetUsers, - List targetInspectors) { + 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"); @@ -58,18 +60,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) { 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..4047ce5e 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 @@ -19,12 +19,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 +33,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) { 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..16bb78c1 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 @@ -11,13 +11,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); 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 c98f5308..87d045dc 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 @@ -44,16 +44,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,7 +70,7 @@ 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 @@ -87,7 +96,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 +117,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 +125,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 +134,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 +158,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(); } 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 { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java index a7e1c3fa..2a60c09e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java @@ -1,23 +1,11 @@ package com.kamco.cd.kamcoback.postgres.repository.label; -import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; -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.entity.MapSheetAnalDataGeomEntity; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.CaseBuilder; @@ -25,16 +13,9 @@ import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.PersistenceContext; -import jakarta.validation.Valid; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.Objects; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -58,7 +39,6 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport this.queryFactory = queryFactory; } - @Override public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { @@ -72,66 +52,62 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport whereBuilder.and(mapSheetAnalDataInferenceEntity.targetYyyy.eq(searchReq.getDetectYyyy())); } - //mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id) + // mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id) - whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); + whereSubBuilder.and( + mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); - if (searchReq.getStrtDttm() != null && ! searchReq.getStrtDttm().isEmpty()) { + if (searchReq.getStrtDttm() != null && !searchReq.getStrtDttm().isEmpty()) { whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); whereSubBuilder.and( - Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", mapSheetAnalDataInferenceEntity.labelStateDttm) - .gt("2024-01-01") - ); + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", mapSheetAnalDataInferenceEntity.labelStateDttm) + .gt("2024-01-01")); } - - List foundContent = - queryFactory - .select( - Projections.constructor( - LabelWorkMng.class, - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage, - mapSheetAnalDataInferenceEntity.createdDttm.min(), - mapSheetAnalDataInferenceEntity.detectingCnt.sum(), - mapSheetAnalDataInferenceGeomEntity.dataUid.count(), - - new CaseBuilder() - .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("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")) - .then(1L) - .otherwise(0L) - .sum(), - - mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min() - )) - .from(mapSheetAnalDataInferenceEntity) - .leftJoin(mapSheetAnalDataInferenceGeomEntity) - .on(whereSubBuilder) - .where(whereBuilder) - .groupBy( - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage - ) - .orderBy(mapSheetAnalDataInferenceEntity.targetYyyy.desc() - ,mapSheetAnalDataInferenceEntity.stage.desc()) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); + queryFactory + .select( + Projections.constructor( + LabelWorkMng.class, + mapSheetAnalDataInferenceEntity.compareYyyy, + mapSheetAnalDataInferenceEntity.targetYyyy, + mapSheetAnalDataInferenceEntity.stage, + mapSheetAnalDataInferenceEntity.createdDttm.min(), + mapSheetAnalDataInferenceEntity.detectingCnt.sum(), + mapSheetAnalDataInferenceGeomEntity.dataUid.count(), + new CaseBuilder() + .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("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")) + .then(1L) + .otherwise(0L) + .sum(), + mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min())) + .from(mapSheetAnalDataInferenceEntity) + .leftJoin(mapSheetAnalDataInferenceGeomEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy( + mapSheetAnalDataInferenceEntity.compareYyyy, + mapSheetAnalDataInferenceEntity.targetYyyy, + mapSheetAnalDataInferenceEntity.stage) + .orderBy( + mapSheetAnalDataInferenceEntity.targetYyyy.desc(), + mapSheetAnalDataInferenceEntity.stage.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); /* Long countQuery = From e80144d5fcce5e1c8763259f54956a6f8ed707fb Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 21:41:40 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=9D=B4=EA=B4=80,=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=A0=95=EB=B3=B4=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 243 +++++--- .../kamcoback/label/dto/LabelAllocateDto.java | 30 + .../label/service/LabelAllocateService.java | 94 +-- .../core/LabelAllocateCoreService.java | 35 +- .../label/LabelAllocateRepositoryCustom.java | 11 +- .../label/LabelAllocateRepositoryImpl.java | 560 +++++++++++------- 6 files changed, 644 insertions(+), 329 deletions(-) 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 07bca2c8..8f463bdd 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -4,10 +4,13 @@ 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; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -33,100 +36,206 @@ public class LabelAllocateApiController { @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @Parameter( - description = "사용자 역할", - example = "LABELER", - schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) - @RequestParam - String role) { + @Parameter( + description = "사용자 역할", + example = "LABELER", + schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) + @RequestParam + String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam - Long analUid, - @Parameter( - description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", - example = "LABELER", - schema = - @Schema( - allowableValues = {"LABELER", "REVIEWER"}, - defaultValue = "LABELER")) - @RequestParam(required = false) - String type, - @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchName, - @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") - @RequestParam(required = false) - String searchEmployeeNo, - @Parameter( - description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", - example = "REMAINING_DESC", - schema = - @Schema( - allowableValues = { - "REMAINING_DESC", - "REMAINING_ASC", - "NAME_ASC", - "NAME_DESC" - }, - defaultValue = "NAME_ASC")) - @RequestParam(required = false) - String sort) { + @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam + Long analUid, + @Parameter( + description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", + example = "LABELER", + schema = + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER")) + @RequestParam(required = false) + String type, + @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) + String searchName, + @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") + @RequestParam(required = false) + String searchEmployeeNo, + @Parameter( + description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", + example = "REMAINING_DESC", + schema = + @Schema( + allowableValues = { + "REMAINING_DESC", + "REMAINING_ASC", + "NAME_ASC", + "NAME_DESC" + }, + defaultValue = "NAME_ASC")) + @RequestParam(required = false) + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sort)); + labelAllocateService.getWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sort)); } - @Operation(summary = "라벨링 작업 배정", description = "라벨러 및 검수자에게 작업을 배정합니다.") + @Operation(summary = "작업 배정", description = "작업 배정") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "배정 성공"), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + 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": 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(@RequestBody LabelAllocateDto.AllocateDto dto) { labelAllocateService.allocateAsc( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getInspectors(), - dto.getAnalUid()); + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getAnalUid()); return ApiResponseDto.ok(null); } @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) - @GetMapping + 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) { + @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/dto/LabelAllocateDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java index 7aa25751..3706c9c3 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 @@ -169,4 +169,34 @@ 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/service/LabelAllocateService.java b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java index daed4f6f..ef6bd4f5 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,7 +19,7 @@ import org.springframework.transaction.annotation.Transactional; @Slf4j @Service -@Transactional(readOnly = true) +@Transactional public class LabelAllocateService { private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량) @@ -33,19 +34,19 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param autoType 자동/수동 배정 타입 - * @param stage 회차 - * @param targetUsers 라벨러 목록 + * @param autoType 자동/수동 배정 타입 + * @param stage 회차 + * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 - * @param analUid 분석 ID + * @param analUid 분석 ID */ @Transactional public void allocateAsc( - String autoType, - Integer stage, - List targetUsers, - List targetInspectors, - Long analUid) { + String autoType, + Integer stage, + List targetUsers, + List targetInspectors, + Long analUid) { Long lastId = null; // geom 잔여건수 조회 @@ -82,7 +83,7 @@ public class LabelAllocateService { } List assignmentUids = - list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); + list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid()); @@ -97,50 +98,50 @@ public class LabelAllocateService { /** * 작업자 목록 및 3일치 통계 조회 * - * @param analUid 분석 ID - * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param searchName 이름 검색 + * @param analUid 분석 ID + * @param workerType 작업자 유형 (LABELER/INSPECTOR) + * @param searchName 이름 검색 * @param searchEmployeeNo 사번 검색 - * @param sortType 정렬 조건 + * @param sortType 정렬 조건 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + labelAllocateCoreService.findWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); for (WorkerStatistics worker : workers) { Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(1), analUid); Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(2), analUid); Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(3), analUid); long average = (day1Count + day2Count + day3Count) / 3; DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); + DailyHistory.builder() + .day1Ago(day1Count) + .day2Ago(day2Count) + .day3Ago(day3Count) + .average(average) + .build(); worker.setHistory(history); @@ -156,4 +157,29 @@ 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/postgres/core/LabelAllocateCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java index 4047ce5e..a205013a 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; @@ -29,8 +30,8 @@ public class LabelAllocateCoreService { public List findAssignedLabelerList(Long analUid) { return labelAllocateRepository.findAssignedLabelerList(analUid).stream() - .map(LabelingAssignmentEntity::toDto) - .toList(); + .map(LabelingAssignmentEntity::toDto) + .toList(); } public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { @@ -46,13 +47,13 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, searchName, searchEmployeeNo, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -60,7 +61,7 @@ public class LabelAllocateCoreService { } public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid); } @@ -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/repository/label/LabelAllocateRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java index 16bb78c1..4ad518eb 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; @@ -25,7 +26,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); + Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(Long analUid); @@ -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 87d045dc..b33b0509 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; @@ -41,32 +44,33 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto private final JPAQueryFactory queryFactory; - @PersistenceContext private EntityManager em; + @PersistenceContext + private EntityManager em; @Override public List fetchNextIds(Long lastId, Long batchSize, Long analUid) { // analUid로 분석 정보 조회 MapSheetAnalEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + 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( - lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) - .limit(batchSize) - .fetch(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(batchSize) + .fetch(); } @Override @@ -74,30 +78,32 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // data_geom 테이블에 label state 를 ASSIGNED 로 update queryFactory - .update(mapSheetAnalDataInferenceGeomEntity) - .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) - .set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now()) - .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) - .execute(); + .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) - .setParameter(1, UUID.randomUUID()) - .setParameter(2, geoUid) - .setParameter(3, userId) - .setParameter(4, LabelState.ASSIGNED.getId()) - .setParameter(5, "") - .setParameter(6, analUid) - .executeUpdate(); + .setParameter(1, UUID.randomUUID()) + .setParameter(2, geoUid) + .setParameter(3, userId) + .setParameter(4, LabelState.ASSIGNED.getId()) + .setParameter(5, "") + .setParameter(6, analUid) + .executeUpdate(); } em.flush(); @@ -107,82 +113,82 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public List findAssignedLabelerList(Long analUid) { return queryFactory - .selectFrom(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), - labelingAssignmentEntity.inspectorUid.isNull()) - .orderBy(labelingAssignmentEntity.workerUid.asc()) - .fetch(); + .selectFrom(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), + labelingAssignmentEntity.inspectorUid.isNull()) + .orderBy(labelingAssignmentEntity.workerUid.asc()) + .fetch(); } @Override public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { MapSheetAnalEntity entity = - queryFactory - .selectFrom(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); if (Objects.isNull(entity)) { throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: " + analUid); } return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .fetchOne(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .fetchOne(); } @Override public void assignInspector(UUID assignmentUid, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) + .execute(); } @Override public List availUserList(String role) { return queryFactory - .select( - Projections.constructor( - LabelAllocateDto.UserList.class, - memberEntity.userRole, - memberEntity.employeeNo, - memberEntity.name)) - .from(memberEntity) - .where( - memberEntity.userRole.eq(role), - memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) - .orderBy(memberEntity.name.asc()) - .fetch(); + .select( + Projections.constructor( + LabelAllocateDto.UserList.class, + memberEntity.userRole, + memberEntity.employeeNo, + memberEntity.name)) + .from(memberEntity) + .where( + memberEntity.userRole.eq(role), + memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) + .orderBy(memberEntity.name.asc()) + .fetch(); } @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = - "INSPECTOR".equals(workerType) - ? labelingAssignmentEntity.inspectorUid - : labelingAssignmentEntity.workerUid; + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid + : labelingAssignmentEntity.workerUid; BooleanExpression workerCondition = - "INSPECTOR".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.isNotNull() - : labelingAssignmentEntity.workerUid.isNotNull(); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.isNotNull() + : labelingAssignmentEntity.workerUid.isNotNull(); // 검색 조건 BooleanExpression searchCondition = null; @@ -196,49 +202,49 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 완료, 스킵, 남은 작업 계산 NumberExpression completedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression skippedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("SKIP")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression remainingSum = - new CaseBuilder() - .when( - labelingAssignmentEntity - .workState - .notIn("DONE", "SKIP") - .and(labelingAssignmentEntity.workState.isNotNull())) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .notIn("DONE", "SKIP") + .and(labelingAssignmentEntity.workState.isNotNull())) + .then(1L) + .otherwise(0L) + .sum(); // 기본 통계 조회 쿼리 var baseQuery = - queryFactory - .select( - workerIdField, - memberEntity.name, - workerIdField.count(), - completedSum, - skippedSum, - remainingSum, - labelingAssignmentEntity.stagnationYn.max()) - .from(labelingAssignmentEntity) - .leftJoin(memberEntity) - .on( - "INSPECTOR".equals(workerType) - ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) - : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) - .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) - .groupBy(workerIdField, memberEntity.name); + queryFactory + .select( + workerIdField, + memberEntity.name, + workerIdField.count(), + completedSum, + skippedSum, + remainingSum, + labelingAssignmentEntity.stagnationYn.max()) + .from(labelingAssignmentEntity) + .leftJoin(memberEntity) + .on( + "REVIEWER".equals(workerType) + ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) + : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) + .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) + .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 if (sortType != null) { @@ -264,132 +270,132 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 결과를 DTO로 변환 return baseQuery.fetch().stream() - .map( - tuple -> { - Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); - return WorkerStatistics.builder() - .workerId(tuple.get(workerIdField)) - .workerName(tuple.get(memberEntity.name)) - .workerType(workerType) - .totalAssigned(tuple.get(workerIdField.count())) - .completed(tuple.get(completedSum)) - .skipped(tuple.get(skippedSum)) - .remaining(tuple.get(remainingSum)) - .history(null) // 3일 이력은 Service에서 채움 - .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') - .build(); - }) - .toList(); + .map( + tuple -> { + Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); + return WorkerStatistics.builder() + .workerId(tuple.get(workerIdField)) + .workerName(tuple.get(memberEntity.name)) + .workerType(workerType) + .totalAssigned(tuple.get(workerIdField.count())) + .completed(tuple.get(completedSum)) + .skipped(tuple.get(skippedSum)) + .remaining(tuple.get(remainingSum)) + .history(null) // 3일 이력은 Service에서 채움 + .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') + .build(); + }) + .toList(); } @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { // 전체 배정 건수 Long totalAssigned = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where(labelingAssignmentEntity.analUid.eq(analUid)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where(labelingAssignmentEntity.analUid.eq(analUid)) + .fetchOne(); // 완료 + 스킵 건수 Long completedCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workState.in("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workState.in("DONE", "SKIP")) + .fetchOne(); // 투입된 라벨러 수 (고유한 worker_uid 수) Long labelerCount = - queryFactory - .select(labelingAssignmentEntity.workerUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.workerUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull()) + .fetchOne(); // 남은 라벨링 작업 데이터 수 Long remainingLabelCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + .fetchOne(); // 투입된 검수자 수 (고유한 inspector_uid 수) Long inspectorCount = - queryFactory - .select(labelingAssignmentEntity.inspectorUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.inspectorUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull()) + .fetchOne(); // 남은 검수 작업 데이터 수 Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE")) + .fetchOne(); // 진행률 계산 double progressRate = 0.0; if (totalAssigned != null && totalAssigned > 0) { progressRate = - (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; + (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; } // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; return WorkProgressInfo.builder() - .labelingProgressRate(progressRate) - .workStatus(workStatus) - .completedCount(completedCount != null ? completedCount : 0L) - .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) - .labelerCount(labelerCount != null ? labelerCount : 0L) - .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) - .inspectorCount(inspectorCount != null ? inspectorCount : 0L) - .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) - .build(); + .labelingProgressRate(progressRate) + .workStatus(workStatus) + .completedCount(completedCount != null ? completedCount : 0L) + .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) + .labelerCount(labelerCount != null ? labelerCount : 0L) + .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) + .inspectorCount(inspectorCount != null ? inspectorCount : 0L) + .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) + .build(); } @Override public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { // 해당 날짜의 시작과 끝 시간 ZonedDateTime startOfDay = date.atStartOfDay(ZoneId.systemDefault()); ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()); BooleanExpression workerCondition = - "INSPECTOR".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.eq(workerId) - : labelingAssignmentEntity.workerUid.eq(workerId); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.eq(workerId) + : labelingAssignmentEntity.workerUid.eq(workerId); Long count = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - workerCondition, - labelingAssignmentEntity.workState.in( - LabelState.DONE.getId(), LabelState.SKIP.getId()), - labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + workerCondition, + labelingAssignmentEntity.workState.in( + LabelState.DONE.getId(), LabelState.SKIP.getId()), + labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) + .fetchOne(); return count != null ? count : 0L; } @@ -397,10 +403,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public void assignInspectorBulk(List assignmentUids, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) + .execute(); em.clear(); } @@ -408,23 +414,141 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public InferenceDetail findInferenceDetail(Long analUid) { return queryFactory - .select( - Projections.constructor( - InferenceDetail.class, - mapSheetAnalEntity.analTitle, - Expressions.numberTemplate(Integer.class, "{0}", 4), - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt, - labelingAssignmentEntity.workerUid.countDistinct(), - labelingAssignmentEntity.inspectorUid.countDistinct())) - .from(mapSheetAnalEntity) - .innerJoin(labelingAssignmentEntity) - .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) - .where(mapSheetAnalEntity.id.eq(analUid)) - .groupBy( - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt) + .select( + Projections.constructor( + InferenceDetail.class, + mapSheetAnalEntity.analTitle, + Expressions.numberTemplate(Integer.class, "{0}", 4), + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt, + labelingAssignmentEntity.workerUid.countDistinct(), + labelingAssignmentEntity.inspectorUid.countDistinct())) + .from(mapSheetAnalEntity) + .innerJoin(labelingAssignmentEntity) + .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) + .where(mapSheetAnalEntity.id.eq(analUid)) + .groupBy( + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + 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() + ; } } From b06c5a88ff9061e8c07bc027b00d6ee70749d69b Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 21:42:13 +0900 Subject: [PATCH 5/9] spotless --- .../label/LabelAllocateApiController.java | 223 +++---- .../kamcoback/label/dto/LabelAllocateDto.java | 3 +- .../label/service/LabelAllocateService.java | 67 +- .../core/LabelAllocateCoreService.java | 18 +- .../label/LabelAllocateRepositoryCustom.java | 2 +- .../label/LabelAllocateRepositoryImpl.java | 583 +++++++++--------- 6 files changed, 448 insertions(+), 448 deletions(-) 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 8f463bdd..51520875 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -36,85 +36,86 @@ public class LabelAllocateApiController { @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @Parameter( - description = "사용자 역할", - example = "LABELER", - schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) - @RequestParam - String role) { + @Parameter( + description = "사용자 역할", + example = "LABELER", + schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) + @RequestParam + String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam - Long analUid, - @Parameter( - description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", - example = "LABELER", - schema = - @Schema( - allowableValues = {"LABELER", "REVIEWER"}, - defaultValue = "LABELER")) - @RequestParam(required = false) - String type, - @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchName, - @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") - @RequestParam(required = false) - String searchEmployeeNo, - @Parameter( - description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", - example = "REMAINING_DESC", - schema = - @Schema( - allowableValues = { - "REMAINING_DESC", - "REMAINING_ASC", - "NAME_ASC", - "NAME_DESC" - }, - defaultValue = "NAME_ASC")) - @RequestParam(required = false) - String sort) { + @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam + Long analUid, + @Parameter( + description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", + example = "LABELER", + schema = + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER")) + @RequestParam(required = false) + String type, + @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) + String searchName, + @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") + @RequestParam(required = false) + String searchEmployeeNo, + @Parameter( + description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", + example = "REMAINING_DESC", + schema = + @Schema( + allowableValues = { + "REMAINING_DESC", + "REMAINING_ASC", + "NAME_ASC", + "NAME_DESC" + }, + defaultValue = "NAME_ASC")) + @RequestParam(required = false) + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sort)); + labelAllocateService.getWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sort)); } @Operation(summary = "작업 배정", description = "작업 배정") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class), - examples = {@ExampleObject( - name = "라벨러 할당 예시", - description = "라벨러 할당 예시", - value = - """ + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class), + examples = { + @ExampleObject( + name = "라벨러 할당 예시", + description = "라벨러 할당 예시", + value = + """ { "autoType": "AUTO", "stage": 4, @@ -148,59 +149,62 @@ public class LabelAllocateApiController { ] } - """)} - )), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + """) + })), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) { labelAllocateService.allocateAsc( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getInspectors(), - dto.getAnalUid()); + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getAnalUid()); return ApiResponseDto.ok(null); } @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + 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) { + @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) { + 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 = - """ + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class), + examples = { + @ExampleObject( + name = "라벨러 할당 예시", + description = "라벨러 할당 예시", + value = + """ { "autoType": "AUTO", "stage": 4, @@ -215,26 +219,25 @@ public class LabelAllocateApiController { } ] } - """)} - )), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + """) + })), + @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) { + @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()); + labelAllocateService.allocateMove(dto.getAutoType(), dto.getStage(), dto.getLabelers()); return ApiResponseDto.ok(null); } 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 3706c9c3..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 @@ -177,7 +177,7 @@ public class LabelAllocateDto { private String roleType; private String name; - private String userId; //사번 + private String userId; // 사번 private Long count; private Long completeCnt; private Long skipCnt; @@ -198,5 +198,4 @@ public class LabelAllocateDto { @Schema(description = "라벨러 할당 목록") private List labelers; } - } 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 ef6bd4f5..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 @@ -34,19 +34,19 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param autoType 자동/수동 배정 타입 - * @param stage 회차 - * @param targetUsers 라벨러 목록 + * @param autoType 자동/수동 배정 타입 + * @param stage 회차 + * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 - * @param analUid 분석 ID + * @param analUid 분석 ID */ @Transactional public void allocateAsc( - String autoType, - Integer stage, - List targetUsers, - List targetInspectors, - Long analUid) { + String autoType, + Integer stage, + List targetUsers, + List targetInspectors, + Long analUid) { Long lastId = null; // geom 잔여건수 조회 @@ -83,7 +83,7 @@ public class LabelAllocateService { } List assignmentUids = - list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); + list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid()); @@ -98,50 +98,50 @@ public class LabelAllocateService { /** * 작업자 목록 및 3일치 통계 조회 * - * @param analUid 분석 ID - * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param searchName 이름 검색 + * @param analUid 분석 ID + * @param workerType 작업자 유형 (LABELER/INSPECTOR) + * @param searchName 이름 검색 * @param searchEmployeeNo 사번 검색 - * @param sortType 정렬 조건 + * @param sortType 정렬 조건 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + labelAllocateCoreService.findWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); for (WorkerStatistics worker : workers) { Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(1), analUid); Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(2), analUid); Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(3), analUid); long average = (day1Count + day2Count + day3Count) / 3; DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); + DailyHistory.builder() + .day1Ago(day1Count) + .day2Ago(day2Count) + .day3Ago(day3Count) + .average(average) + .build(); worker.setHistory(history); @@ -176,7 +176,6 @@ public class LabelAllocateService { labelAllocateCoreService.assignOwnerMove(sub, target.getUserId()); index = end; } - } public LabelerDetail findLabelerDetail(String userId, Long analUid) { 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 a205013a..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 @@ -30,8 +30,8 @@ public class LabelAllocateCoreService { public List findAssignedLabelerList(Long analUid) { return labelAllocateRepository.findAssignedLabelerList(analUid).stream() - .map(LabelingAssignmentEntity::toDto) - .toList(); + .map(LabelingAssignmentEntity::toDto) + .toList(); } public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { @@ -47,13 +47,13 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, searchName, searchEmployeeNo, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -61,7 +61,7 @@ public class LabelAllocateCoreService { } public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid); } 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 4ad518eb..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 @@ -26,7 +26,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); + Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(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 b33b0509..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 @@ -44,33 +44,32 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto private final JPAQueryFactory queryFactory; - @PersistenceContext - private EntityManager em; + @PersistenceContext private EntityManager em; @Override public List fetchNextIds(Long lastId, Long batchSize, Long analUid) { // analUid로 분석 정보 조회 MapSheetAnalEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + 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( - lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) - .limit(batchSize) - .fetch(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(batchSize) + .fetch(); } @Override @@ -78,17 +77,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 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(); + .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) @@ -97,13 +96,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto for (Long geoUid : ids) { em.createNativeQuery(sql) - .setParameter(1, UUID.randomUUID()) - .setParameter(2, geoUid) - .setParameter(3, userId) - .setParameter(4, LabelState.ASSIGNED.getId()) - .setParameter(5, "") - .setParameter(6, analUid) - .executeUpdate(); + .setParameter(1, UUID.randomUUID()) + .setParameter(2, geoUid) + .setParameter(3, userId) + .setParameter(4, LabelState.ASSIGNED.getId()) + .setParameter(5, "") + .setParameter(6, analUid) + .executeUpdate(); } em.flush(); @@ -113,82 +112,82 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public List findAssignedLabelerList(Long analUid) { return queryFactory - .selectFrom(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), - labelingAssignmentEntity.inspectorUid.isNull()) - .orderBy(labelingAssignmentEntity.workerUid.asc()) - .fetch(); + .selectFrom(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), + labelingAssignmentEntity.inspectorUid.isNull()) + .orderBy(labelingAssignmentEntity.workerUid.asc()) + .fetch(); } @Override public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { MapSheetAnalEntity entity = - queryFactory - .selectFrom(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); if (Objects.isNull(entity)) { throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: " + analUid); } return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .fetchOne(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .fetchOne(); } @Override public void assignInspector(UUID assignmentUid, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) + .execute(); } @Override public List availUserList(String role) { return queryFactory - .select( - Projections.constructor( - LabelAllocateDto.UserList.class, - memberEntity.userRole, - memberEntity.employeeNo, - memberEntity.name)) - .from(memberEntity) - .where( - memberEntity.userRole.eq(role), - memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) - .orderBy(memberEntity.name.asc()) - .fetch(); + .select( + Projections.constructor( + LabelAllocateDto.UserList.class, + memberEntity.userRole, + memberEntity.employeeNo, + memberEntity.name)) + .from(memberEntity) + .where( + memberEntity.userRole.eq(role), + memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) + .orderBy(memberEntity.name.asc()) + .fetch(); } @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid - : labelingAssignmentEntity.workerUid; + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid + : labelingAssignmentEntity.workerUid; BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.isNotNull() - : labelingAssignmentEntity.workerUid.isNotNull(); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.isNotNull() + : labelingAssignmentEntity.workerUid.isNotNull(); // 검색 조건 BooleanExpression searchCondition = null; @@ -202,49 +201,49 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 완료, 스킵, 남은 작업 계산 NumberExpression completedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression skippedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("SKIP")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression remainingSum = - new CaseBuilder() - .when( - labelingAssignmentEntity - .workState - .notIn("DONE", "SKIP") - .and(labelingAssignmentEntity.workState.isNotNull())) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .notIn("DONE", "SKIP") + .and(labelingAssignmentEntity.workState.isNotNull())) + .then(1L) + .otherwise(0L) + .sum(); // 기본 통계 조회 쿼리 var baseQuery = - queryFactory - .select( - workerIdField, - memberEntity.name, - workerIdField.count(), - completedSum, - skippedSum, - remainingSum, - labelingAssignmentEntity.stagnationYn.max()) - .from(labelingAssignmentEntity) - .leftJoin(memberEntity) - .on( - "REVIEWER".equals(workerType) - ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) - : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) - .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) - .groupBy(workerIdField, memberEntity.name); + queryFactory + .select( + workerIdField, + memberEntity.name, + workerIdField.count(), + completedSum, + skippedSum, + remainingSum, + labelingAssignmentEntity.stagnationYn.max()) + .from(labelingAssignmentEntity) + .leftJoin(memberEntity) + .on( + "REVIEWER".equals(workerType) + ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) + : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) + .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) + .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 if (sortType != null) { @@ -270,132 +269,132 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 결과를 DTO로 변환 return baseQuery.fetch().stream() - .map( - tuple -> { - Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); - return WorkerStatistics.builder() - .workerId(tuple.get(workerIdField)) - .workerName(tuple.get(memberEntity.name)) - .workerType(workerType) - .totalAssigned(tuple.get(workerIdField.count())) - .completed(tuple.get(completedSum)) - .skipped(tuple.get(skippedSum)) - .remaining(tuple.get(remainingSum)) - .history(null) // 3일 이력은 Service에서 채움 - .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') - .build(); - }) - .toList(); + .map( + tuple -> { + Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); + return WorkerStatistics.builder() + .workerId(tuple.get(workerIdField)) + .workerName(tuple.get(memberEntity.name)) + .workerType(workerType) + .totalAssigned(tuple.get(workerIdField.count())) + .completed(tuple.get(completedSum)) + .skipped(tuple.get(skippedSum)) + .remaining(tuple.get(remainingSum)) + .history(null) // 3일 이력은 Service에서 채움 + .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') + .build(); + }) + .toList(); } @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { // 전체 배정 건수 Long totalAssigned = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where(labelingAssignmentEntity.analUid.eq(analUid)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where(labelingAssignmentEntity.analUid.eq(analUid)) + .fetchOne(); // 완료 + 스킵 건수 Long completedCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workState.in("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workState.in("DONE", "SKIP")) + .fetchOne(); // 투입된 라벨러 수 (고유한 worker_uid 수) Long labelerCount = - queryFactory - .select(labelingAssignmentEntity.workerUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.workerUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull()) + .fetchOne(); // 남은 라벨링 작업 데이터 수 Long remainingLabelCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + .fetchOne(); // 투입된 검수자 수 (고유한 inspector_uid 수) Long inspectorCount = - queryFactory - .select(labelingAssignmentEntity.inspectorUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.inspectorUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull()) + .fetchOne(); // 남은 검수 작업 데이터 수 Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE")) + .fetchOne(); // 진행률 계산 double progressRate = 0.0; if (totalAssigned != null && totalAssigned > 0) { progressRate = - (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; + (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; } // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; return WorkProgressInfo.builder() - .labelingProgressRate(progressRate) - .workStatus(workStatus) - .completedCount(completedCount != null ? completedCount : 0L) - .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) - .labelerCount(labelerCount != null ? labelerCount : 0L) - .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) - .inspectorCount(inspectorCount != null ? inspectorCount : 0L) - .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) - .build(); + .labelingProgressRate(progressRate) + .workStatus(workStatus) + .completedCount(completedCount != null ? completedCount : 0L) + .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) + .labelerCount(labelerCount != null ? labelerCount : 0L) + .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) + .inspectorCount(inspectorCount != null ? inspectorCount : 0L) + .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) + .build(); } @Override public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { // 해당 날짜의 시작과 끝 시간 ZonedDateTime startOfDay = date.atStartOfDay(ZoneId.systemDefault()); ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()); BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.eq(workerId) - : labelingAssignmentEntity.workerUid.eq(workerId); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.eq(workerId) + : labelingAssignmentEntity.workerUid.eq(workerId); Long count = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - workerCondition, - labelingAssignmentEntity.workState.in( - LabelState.DONE.getId(), LabelState.SKIP.getId()), - labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + workerCondition, + labelingAssignmentEntity.workState.in( + LabelState.DONE.getId(), LabelState.SKIP.getId()), + labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) + .fetchOne(); return count != null ? count : 0L; } @@ -403,10 +402,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public void assignInspectorBulk(List assignmentUids, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) + .execute(); em.clear(); } @@ -414,84 +413,85 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public InferenceDetail findInferenceDetail(Long analUid) { return queryFactory - .select( - Projections.constructor( - InferenceDetail.class, - mapSheetAnalEntity.analTitle, - Expressions.numberTemplate(Integer.class, "{0}", 4), - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt, - labelingAssignmentEntity.workerUid.countDistinct(), - labelingAssignmentEntity.inspectorUid.countDistinct())) - .from(mapSheetAnalEntity) - .innerJoin(labelingAssignmentEntity) - .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) - .where(mapSheetAnalEntity.id.eq(analUid)) - .groupBy( - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt) - .fetchOne(); + .select( + Projections.constructor( + InferenceDetail.class, + mapSheetAnalEntity.analTitle, + Expressions.numberTemplate(Integer.class, "{0}", 4), + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt, + labelingAssignmentEntity.workerUid.countDistinct(), + labelingAssignmentEntity.inspectorUid.countDistinct())) + .from(mapSheetAnalEntity) + .innerJoin(labelingAssignmentEntity) + .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) + .where(mapSheetAnalEntity.id.eq(analUid)) + .groupBy( + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt) + .fetchOne(); } - @Override public List fetchNextMoveIds(Long lastId, Long batchSize) { MapSheetAnalEntity entity = - queryFactory - .selectFrom(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(3L)) //TODO - .fetchOne(); + 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(); + .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(); + 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(); + .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(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.workerUid, userId) + .where(labelingAssignmentEntity.inferenceGeomUid.in(sub)) + .execute(); em.clear(); } @@ -499,56 +499,55 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @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(); + 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(); + 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(); + 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 - ) - ); + 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() - ; + .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(); } } From 9015de5c00d6c79396386fd00eaba6319cf43ea9 Mon Sep 17 00:00:00 2001 From: Moon Date: Fri, 2 Jan 2026 21:51:44 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/label/LabelWorkRepositoryImpl.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java index a7e1c3fa..e57f5314 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java @@ -76,14 +76,17 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); - if (searchReq.getStrtDttm() != null && ! searchReq.getStrtDttm().isEmpty()) { - - whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); + if (searchReq.getStrtDttm() != null && ! searchReq.getStrtDttm().isEmpty() + && searchReq.getEndDttm() != null && ! searchReq.getEndDttm().isEmpty()) { + //whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); whereSubBuilder.and( - Expressions.stringTemplate("to_char({0}, 'YYYY-MM-DD')", mapSheetAnalDataInferenceEntity.labelStateDttm) - .gt("2024-01-01") + Expressions.stringTemplate("to_char({0}, 'YYYYMMDD')", mapSheetAnalDataInferenceGeomEntity.labelStateDttm) + .between(searchReq.getStrtDttm(), searchReq.getEndDttm()) ); + + //whereBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min().isNotNull()); + } @@ -119,7 +122,7 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min() )) .from(mapSheetAnalDataInferenceEntity) - .leftJoin(mapSheetAnalDataInferenceGeomEntity) + .innerJoin(mapSheetAnalDataInferenceGeomEntity) .on(whereSubBuilder) .where(whereBuilder) .groupBy( From c85dc1a070a4cb9abd7f2b3a7989bbceb18e0b5d Mon Sep 17 00:00:00 2001 From: Moon Date: Fri, 2 Jan 2026 22:03:33 +0900 Subject: [PATCH 7/9] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgres/repository/label/LabelWorkRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java index e57f5314..19a4454c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java @@ -100,7 +100,7 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport mapSheetAnalDataInferenceEntity.targetYyyy, mapSheetAnalDataInferenceEntity.stage, mapSheetAnalDataInferenceEntity.createdDttm.min(), - mapSheetAnalDataInferenceEntity.detectingCnt.sum(), + mapSheetAnalDataInferenceGeomEntity.dataUid.count(), mapSheetAnalDataInferenceGeomEntity.dataUid.count(), new CaseBuilder() From a7b04d0be876579de17437b1eac428442ebf6fd0 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 22:17:42 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20yyyy?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 326 ++++----- .../kamcoback/label/dto/LabelAllocateDto.java | 14 +- .../label/service/LabelAllocateService.java | 88 +-- .../core/LabelAllocateCoreService.java | 46 +- .../label/LabelAllocateRepositoryCustom.java | 16 +- .../label/LabelAllocateRepositoryImpl.java | 669 +++++++++--------- 6 files changed, 608 insertions(+), 551 deletions(-) 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 51520875..d09b99dc 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -36,208 +36,214 @@ public class LabelAllocateApiController { @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @Parameter( - description = "사용자 역할", - example = "LABELER", - schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) - @RequestParam - String role) { + @Parameter( + description = "사용자 역할", + example = "LABELER", + schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) + @RequestParam + String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam - Long analUid, - @Parameter( - description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", - example = "LABELER", - schema = - @Schema( - allowableValues = {"LABELER", "REVIEWER"}, - defaultValue = "LABELER")) - @RequestParam(required = false) - String type, - @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchName, - @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") - @RequestParam(required = false) - String searchEmployeeNo, - @Parameter( - description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", - example = "REMAINING_DESC", - schema = - @Schema( - allowableValues = { - "REMAINING_DESC", - "REMAINING_ASC", - "NAME_ASC", - "NAME_DESC" - }, - defaultValue = "NAME_ASC")) - @RequestParam(required = false) - String sort) { + @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam + Long analUid, + @Parameter( + description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", + example = "LABELER", + schema = + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER")) + @RequestParam(required = false) + String type, + @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) + String searchName, + @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") + @RequestParam(required = false) + String searchEmployeeNo, + @Parameter( + description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", + example = "REMAINING_DESC", + schema = + @Schema( + allowableValues = { + "REMAINING_DESC", + "REMAINING_ASC", + "NAME_ASC", + "NAME_DESC" + }, + defaultValue = "NAME_ASC")) + @RequestParam(required = false) + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sort)); + labelAllocateService.getWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sort)); } @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": 1000 - }, - { - "userId": "010222297501", - "demand": 400 - }, - { - "userId": "01022223333", - "demand": 440 - } - ], - "inspectors": [ - { - "inspectorUid": "K20251216001", - "userCount": 1000 - }, - { - "inspectorUid": "01022225555", - "userCount": 340 - }, - { - "inspectorUid": "K20251212001", - "userCount": 500 - } + 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": 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) - }) + ] + } + """) + })), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) { labelAllocateService.allocateAsc( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getInspectors(), - dto.getAnalUid()); + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getCompareYyyy(), + dto.getTargetYyyy() + ); return ApiResponseDto.ok(null); } @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + 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)); + @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam Integer compareYyyy, + @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam Integer targetYyyy, + @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage + ) { + return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(compareYyyy, targetYyyy, stage)); } @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)); + @RequestParam(defaultValue = "01022223333") String userId, + @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam Integer compareYyyy, + @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam Integer targetYyyy, + @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage) { + return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage)); } @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) - }) + 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) { + @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()); + labelAllocateService.allocateMove(dto.getAutoType(), dto.getStage(), dto.getLabelers(), dto.getCompareYyyy(), dto.getTargetYyyy()); return ApiResponseDto.ok(null); } 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 3d126943..16624689 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 @@ -87,9 +87,15 @@ public class LabelAllocateDto { @AllArgsConstructor public static class AllocateDto { - @Schema(description = "분석 ID", example = "3", required = true) + @Schema(description = "분석 ID", example = "3") private Long analUid; + @Schema(description = "비교년도", example = "2022", required = true) + private Integer compareYyyy; + + @Schema(description = "기준년도", example = "2024", required = true) + private Integer targetYyyy; + @Schema(description = "자동/수동여부(AUTO/MANUAL)", example = "AUTO") private String autoType; @@ -197,5 +203,11 @@ public class LabelAllocateDto { @Schema(description = "라벨러 할당 목록") private List labelers; + + @Schema(description = "비교년도", example = "2022") + private Integer compareYyyy; + + @Schema(description = "기준년도", example = "2024") + private Integer targetYyyy; } } 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 79ad856b..aa59472d 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 @@ -34,23 +34,24 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param autoType 자동/수동 배정 타입 - * @param stage 회차 - * @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, - Long analUid) { + String autoType, + Integer stage, + List targetUsers, + List targetInspectors, + Integer compareYyyy, + Integer targetYyyy + ) { Long lastId = null; // geom 잔여건수 조회 - Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(analUid, stage); + Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); // Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); // if (!Objects.equals(chargeCnt, totalDemand)) { // log.info("chargeCnt != totalDemand"); @@ -61,18 +62,18 @@ public class LabelAllocateService { return; } - List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, analUid); + List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); List sub = allIds.subList(index, end); - labelAllocateCoreService.assignOwner(sub, target.getUserId(), analUid); + labelAllocateCoreService.assignOwner(sub, target.getUserId(), compareYyyy, targetYyyy, stage); index = end; } // 검수자에게 userCount명 만큼 할당 - List list = labelAllocateCoreService.findAssignedLabelerList(analUid); + List list = labelAllocateCoreService.findAssignedLabelerList(compareYyyy, targetYyyy, stage); int from = 0; for (TargetInspector inspector : targetInspectors) { @@ -83,7 +84,7 @@ public class LabelAllocateService { } List assignmentUids = - list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); + list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid()); @@ -98,50 +99,50 @@ public class LabelAllocateService { /** * 작업자 목록 및 3일치 통계 조회 * - * @param analUid 분석 ID - * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param searchName 이름 검색 + * @param analUid 분석 ID + * @param workerType 작업자 유형 (LABELER/INSPECTOR) + * @param searchName 이름 검색 * @param searchEmployeeNo 사번 검색 - * @param sortType 정렬 조건 + * @param sortType 정렬 조건 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + labelAllocateCoreService.findWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); for (WorkerStatistics worker : workers) { Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(1), analUid); Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(2), analUid); Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(3), analUid); long average = (day1Count + day2Count + day3Count) / 3; DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); + DailyHistory.builder() + .day1Ago(day1Count) + .day2Ago(day2Count) + .day3Ago(day3Count) + .average(average) + .build(); worker.setHistory(history); @@ -154,11 +155,12 @@ public class LabelAllocateService { return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); } - public InferenceDetail findInferenceDetail(Long analUid) { - return labelAllocateCoreService.findInferenceDetail(analUid); + public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateCoreService.findInferenceDetail(compareYyyy, targetYyyy, stage); } - public void allocateMove(String autoType, Integer stage, List targetUsers) { + public void allocateMove(String autoType, Integer stage, List targetUsers, Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); @@ -167,7 +169,7 @@ public class LabelAllocateService { return; } - List allIds = labelAllocateCoreService.fetchNextMoveIds(lastId, chargeCnt); + List allIds = labelAllocateCoreService.fetchNextMoveIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); @@ -178,7 +180,7 @@ public class LabelAllocateService { } } - public LabelerDetail findLabelerDetail(String userId, Long analUid) { - return labelAllocateCoreService.findLabelerDetail(userId, analUid); + public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateCoreService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); } } 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 d9244fb0..6e9f5104 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 @@ -20,22 +20,22 @@ public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; - public List fetchNextIds(Long lastId, Long batchSize, Long analUid) { - return labelAllocateRepository.fetchNextIds(lastId, batchSize, analUid); + public List fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.fetchNextIds(lastId, batchSize, compareYyyy, targetYyyy, stage); } - public void assignOwner(List ids, String userId, Long analUid) { - labelAllocateRepository.assignOwner(ids, userId, analUid); + public void assignOwner(List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + labelAllocateRepository.assignOwner(ids, userId, compareYyyy, targetYyyy, stage); } - public List findAssignedLabelerList(Long analUid) { - return labelAllocateRepository.findAssignedLabelerList(analUid).stream() - .map(LabelingAssignmentEntity::toDto) - .toList(); + public List findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.findAssignedLabelerList(compareYyyy, targetYyyy, stage).stream() + .map(LabelingAssignmentEntity::toDto) + .toList(); } - public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { - return labelAllocateRepository.findLabelUnAssignedCnt(analUid, stage); + public Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy) { + return labelAllocateRepository.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); } public void assignInspector(UUID assignmentUid, String inspectorUid) { @@ -47,13 +47,13 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, searchName, searchEmployeeNo, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -61,7 +61,7 @@ public class LabelAllocateCoreService { } public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid); } @@ -69,23 +69,23 @@ public class LabelAllocateCoreService { labelAllocateRepository.assignInspectorBulk(assignmentUids, inspectorUid); } - public InferenceDetail findInferenceDetail(Long analUid) { - return labelAllocateRepository.findInferenceDetail(analUid); + public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.findInferenceDetail(compareYyyy, targetYyyy, stage); } public Long findLabelUnCompleteCnt(Long analUid) { return labelAllocateRepository.findLabelUnCompleteCnt(analUid); } - public List fetchNextMoveIds(Long lastId, Long chargeCnt) { - return labelAllocateRepository.fetchNextMoveIds(lastId, chargeCnt); + public List fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.fetchNextMoveIds(lastId, batchSize, compareYyyy, targetYyyy, stage); } public void assignOwnerMove(List sub, String userId) { labelAllocateRepository.assignOwnerMove(sub, userId); } - public LabelerDetail findLabelerDetail(String userId, Long analUid) { - return labelAllocateRepository.findLabelerDetail(userId, analUid); + public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); } } 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 a1309169..5c374293 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 @@ -12,13 +12,13 @@ import java.util.UUID; public interface LabelAllocateRepositoryCustom { - List fetchNextIds(Long lastId, Long batchSize, Long analUid); + List fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); - void assignOwner(List ids, String userId, Long analUid); + void assignOwner(List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); - List findAssignedLabelerList(Long analUid); + List findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage); - Long findLabelUnAssignedCnt(Long analUid, Integer stage); + Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy); void assignInspector(UUID assignmentUid, String userId); @@ -26,7 +26,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); + Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(Long analUid); @@ -36,13 +36,13 @@ public interface LabelAllocateRepositoryCustom { void assignInspectorBulk(List assignmentUids, String inspectorUid); - InferenceDetail findInferenceDetail(Long analUid); + InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage); - List fetchNextMoveIds(Long lastId, Long batchSize); + public List fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); Long findLabelUnCompleteCnt(Long analUid); void assignOwnerMove(List sub, String userId); - LabelerDetail findLabelerDetail(String userId, Long analUid); + LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); } 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 3c32e99b..c29e6f4e 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 @@ -1,6 +1,7 @@ package com.kamco.cd.kamcoback.postgres.repository.label; import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; @@ -14,6 +15,7 @@ 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; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; @@ -44,50 +46,58 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto private final JPAQueryFactory queryFactory; - @PersistenceContext private EntityManager em; + @PersistenceContext + private EntityManager em; @Override - 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); - } + public List fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) - .limit(batchSize) - .fetch(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(batchSize) + .fetch(); } @Override - public void assignOwner(List ids, String userId, Long analUid) { + public void assignOwner(List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + + // analUid로 분석 정보 조회 + MapSheetAnalDataInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage) + ) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalEntity not found for 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(); + .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) @@ -96,13 +106,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto for (Long geoUid : ids) { em.createNativeQuery(sql) - .setParameter(1, UUID.randomUUID()) - .setParameter(2, geoUid) - .setParameter(3, userId) - .setParameter(4, LabelState.ASSIGNED.getId()) - .setParameter(5, "") - .setParameter(6, analUid) - .executeUpdate(); + .setParameter(1, UUID.randomUUID()) + .setParameter(2, geoUid) + .setParameter(3, userId) + .setParameter(4, LabelState.ASSIGNED.getId()) + .setParameter(5, "") + .setParameter(6, analEntity.getAnalUid()) + .executeUpdate(); } em.flush(); @@ -110,84 +120,91 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public List findAssignedLabelerList(Long analUid) { - return queryFactory - .selectFrom(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), - labelingAssignmentEntity.inspectorUid.isNull()) - .orderBy(labelingAssignmentEntity.workerUid.asc()) - .fetch(); - } + public List findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage) { + // analUid로 분석 정보 조회 + MapSheetAnalDataInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage) + ) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) + .fetchOne(); - @Override - public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { - MapSheetAnalEntity entity = - queryFactory - .selectFrom(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); - - if (Objects.isNull(entity)) { - throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: " + analUid); + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); } return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .fetchOne(); + .selectFrom(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid()), + labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), + labelingAssignmentEntity.inspectorUid.isNull()) + .orderBy(labelingAssignmentEntity.workerUid.asc()) + .fetch(); + } + + @Override + public Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy) { + + return queryFactory + .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .fetchOne(); } @Override public void assignInspector(UUID assignmentUid, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) + .execute(); } @Override public List availUserList(String role) { return queryFactory - .select( - Projections.constructor( - LabelAllocateDto.UserList.class, - memberEntity.userRole, - memberEntity.employeeNo, - memberEntity.name)) - .from(memberEntity) - .where( - memberEntity.userRole.eq(role), - memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) - .orderBy(memberEntity.name.asc()) - .fetch(); + .select( + Projections.constructor( + LabelAllocateDto.UserList.class, + memberEntity.userRole, + memberEntity.employeeNo, + memberEntity.name)) + .from(memberEntity) + .where( + memberEntity.userRole.eq(role), + memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) + .orderBy(memberEntity.name.asc()) + .fetch(); } @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid - : labelingAssignmentEntity.workerUid; + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid + : labelingAssignmentEntity.workerUid; BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.isNotNull() - : labelingAssignmentEntity.workerUid.isNotNull(); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.isNotNull() + : labelingAssignmentEntity.workerUid.isNotNull(); // 검색 조건 BooleanExpression searchCondition = null; @@ -201,49 +218,49 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 완료, 스킵, 남은 작업 계산 NumberExpression completedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression skippedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("SKIP")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression remainingSum = - new CaseBuilder() - .when( - labelingAssignmentEntity - .workState - .notIn("DONE", "SKIP") - .and(labelingAssignmentEntity.workState.isNotNull())) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .notIn("DONE", "SKIP") + .and(labelingAssignmentEntity.workState.isNotNull())) + .then(1L) + .otherwise(0L) + .sum(); // 기본 통계 조회 쿼리 var baseQuery = - queryFactory - .select( - workerIdField, - memberEntity.name, - workerIdField.count(), - completedSum, - skippedSum, - remainingSum, - labelingAssignmentEntity.stagnationYn.max()) - .from(labelingAssignmentEntity) - .leftJoin(memberEntity) - .on( - "REVIEWER".equals(workerType) - ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) - : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) - .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) - .groupBy(workerIdField, memberEntity.name); + queryFactory + .select( + workerIdField, + memberEntity.name, + workerIdField.count(), + completedSum, + skippedSum, + remainingSum, + labelingAssignmentEntity.stagnationYn.max()) + .from(labelingAssignmentEntity) + .leftJoin(memberEntity) + .on( + "REVIEWER".equals(workerType) + ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) + : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) + .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) + .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 if (sortType != null) { @@ -269,132 +286,132 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 결과를 DTO로 변환 return baseQuery.fetch().stream() - .map( - tuple -> { - Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); - return WorkerStatistics.builder() - .workerId(tuple.get(workerIdField)) - .workerName(tuple.get(memberEntity.name)) - .workerType(workerType) - .totalAssigned(tuple.get(workerIdField.count())) - .completed(tuple.get(completedSum)) - .skipped(tuple.get(skippedSum)) - .remaining(tuple.get(remainingSum)) - .history(null) // 3일 이력은 Service에서 채움 - .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') - .build(); - }) - .toList(); + .map( + tuple -> { + Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); + return WorkerStatistics.builder() + .workerId(tuple.get(workerIdField)) + .workerName(tuple.get(memberEntity.name)) + .workerType(workerType) + .totalAssigned(tuple.get(workerIdField.count())) + .completed(tuple.get(completedSum)) + .skipped(tuple.get(skippedSum)) + .remaining(tuple.get(remainingSum)) + .history(null) // 3일 이력은 Service에서 채움 + .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') + .build(); + }) + .toList(); } @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { // 전체 배정 건수 Long totalAssigned = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where(labelingAssignmentEntity.analUid.eq(analUid)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where(labelingAssignmentEntity.analUid.eq(analUid)) + .fetchOne(); // 완료 + 스킵 건수 Long completedCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workState.in("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workState.in("DONE", "SKIP")) + .fetchOne(); // 투입된 라벨러 수 (고유한 worker_uid 수) Long labelerCount = - queryFactory - .select(labelingAssignmentEntity.workerUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.workerUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull()) + .fetchOne(); // 남은 라벨링 작업 데이터 수 Long remainingLabelCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + .fetchOne(); // 투입된 검수자 수 (고유한 inspector_uid 수) Long inspectorCount = - queryFactory - .select(labelingAssignmentEntity.inspectorUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.inspectorUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull()) + .fetchOne(); // 남은 검수 작업 데이터 수 Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE")) + .fetchOne(); // 진행률 계산 double progressRate = 0.0; if (totalAssigned != null && totalAssigned > 0) { progressRate = - (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; + (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; } // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; return WorkProgressInfo.builder() - .labelingProgressRate(progressRate) - .workStatus(workStatus) - .completedCount(completedCount != null ? completedCount : 0L) - .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) - .labelerCount(labelerCount != null ? labelerCount : 0L) - .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) - .inspectorCount(inspectorCount != null ? inspectorCount : 0L) - .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) - .build(); + .labelingProgressRate(progressRate) + .workStatus(workStatus) + .completedCount(completedCount != null ? completedCount : 0L) + .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) + .labelerCount(labelerCount != null ? labelerCount : 0L) + .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) + .inspectorCount(inspectorCount != null ? inspectorCount : 0L) + .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) + .build(); } @Override public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { // 해당 날짜의 시작과 끝 시간 ZonedDateTime startOfDay = date.atStartOfDay(ZoneId.systemDefault()); ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()); BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.eq(workerId) - : labelingAssignmentEntity.workerUid.eq(workerId); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.eq(workerId) + : labelingAssignmentEntity.workerUid.eq(workerId); Long count = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - workerCondition, - labelingAssignmentEntity.workState.in( - LabelState.DONE.getId(), LabelState.SKIP.getId()), - labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + workerCondition, + labelingAssignmentEntity.workState.in( + LabelState.DONE.getId(), LabelState.SKIP.getId()), + labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) + .fetchOne(); return count != null ? count : 0L; } @@ -402,152 +419,172 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public void assignInspectorBulk(List assignmentUids, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) + .execute(); em.clear(); } @Override - public InferenceDetail findInferenceDetail(Long analUid) { - return queryFactory - .select( - Projections.constructor( - InferenceDetail.class, - mapSheetAnalEntity.analTitle, - Expressions.numberTemplate(Integer.class, "{0}", 4), - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt, - labelingAssignmentEntity.workerUid.countDistinct(), - labelingAssignmentEntity.inspectorUid.countDistinct())) - .from(mapSheetAnalEntity) - .innerJoin(labelingAssignmentEntity) - .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) - .where(mapSheetAnalEntity.id.eq(analUid)) - .groupBy( - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt) + public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) { + // analUid로 분석 정보 조회 + MapSheetAnalDataInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage) + ) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) .fetchOne(); + + return queryFactory + .select( + Projections.constructor( + InferenceDetail.class, + mapSheetAnalEntity.analTitle, + Expressions.numberTemplate(Integer.class, "{0}", 4), + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt, + labelingAssignmentEntity.workerUid.countDistinct(), + labelingAssignmentEntity.inspectorUid.countDistinct())) + .from(mapSheetAnalEntity) + .innerJoin(labelingAssignmentEntity) + .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) + .where(mapSheetAnalEntity.id.eq(analEntity.getAnalUid())) + .groupBy( + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + 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(); - } + public List fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { 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(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + 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(); + 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(); + .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(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.workerUid, userId) + .where(labelingAssignmentEntity.inferenceGeomUid.in(sub)) + .execute(); em.clear(); } @Override - public LabelerDetail findLabelerDetail(String userId, Long analUid) { + public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { NumberExpression assignedCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + 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(); + 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(); + 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)); + new CaseBuilder() + .when(completeCnt.eq(0L)) + .then(0.0) + .otherwise( + Expressions.numberTemplate( + Double.class, + "round({0} / {1}, 2)", + labelingAssignmentEntity.count(), + completeCnt)); + + // analUid로 분석 정보 조회 + MapSheetAnalDataInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage) + ) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); + } 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(); + .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(analEntity.getAnalUid())) + .where(memberEntity.employeeNo.eq(userId)) + .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.employeeNo) + .fetchOne(); } } From dc2b9286f4853f0a0dfeb5d94e46d154cea0231c Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 22:18:09 +0900 Subject: [PATCH 9/9] spotless --- .../label/LabelAllocateApiController.java | 245 +++---- .../kamcoback/label/dto/LabelAllocateDto.java | 2 +- .../label/service/LabelAllocateService.java | 94 +-- .../core/LabelAllocateCoreService.java | 39 +- .../label/LabelAllocateRepositoryCustom.java | 17 +- .../label/LabelAllocateRepositoryImpl.java | 653 +++++++++--------- .../label/LabelWorkRepositoryImpl.java | 126 ++-- 7 files changed, 595 insertions(+), 581 deletions(-) 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 d09b99dc..b4fdf79b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -36,86 +36,86 @@ public class LabelAllocateApiController { @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @Parameter( - description = "사용자 역할", - example = "LABELER", - schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) - @RequestParam - String role) { + @Parameter( + description = "사용자 역할", + example = "LABELER", + schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) + @RequestParam + String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam - Long analUid, - @Parameter( - description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", - example = "LABELER", - schema = - @Schema( - allowableValues = {"LABELER", "REVIEWER"}, - defaultValue = "LABELER")) - @RequestParam(required = false) - String type, - @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchName, - @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") - @RequestParam(required = false) - String searchEmployeeNo, - @Parameter( - description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", - example = "REMAINING_DESC", - schema = - @Schema( - allowableValues = { - "REMAINING_DESC", - "REMAINING_ASC", - "NAME_ASC", - "NAME_DESC" - }, - defaultValue = "NAME_ASC")) - @RequestParam(required = false) - String sort) { + @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam + Long analUid, + @Parameter( + description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", + example = "LABELER", + schema = + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER")) + @RequestParam(required = false) + String type, + @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) + String searchName, + @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") + @RequestParam(required = false) + String searchEmployeeNo, + @Parameter( + description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", + example = "REMAINING_DESC", + schema = + @Schema( + allowableValues = { + "REMAINING_DESC", + "REMAINING_ASC", + "NAME_ASC", + "NAME_DESC" + }, + defaultValue = "NAME_ASC")) + @RequestParam(required = false) + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sort)); + labelAllocateService.getWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sort)); } @Operation(summary = "작업 배정", description = "작업 배정") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class), - examples = { - @ExampleObject( - name = "라벨러 할당 예시", - description = "라벨러 할당 예시", - value = - """ + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class), + examples = { + @ExampleObject( + name = "라벨러 할당 예시", + description = "라벨러 할당 예시", + value = + """ { "autoType": "AUTO", "stage": 4, @@ -150,67 +150,71 @@ public class LabelAllocateApiController { ] } """) - })), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + })), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) { labelAllocateService.allocateAsc( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getInspectors(), - dto.getCompareYyyy(), - dto.getTargetYyyy() - ); + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getCompareYyyy(), + dto.getTargetYyyy()); return ApiResponseDto.ok(null); } @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") @ApiResponses( - value = { - @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), - @ApiResponse(responseCode = "500", description = "서버 오류") - }) + value = { + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) @GetMapping("/stage-detail") public ApiResponseDto findInferenceDetail( - @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam Integer compareYyyy, - @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam Integer targetYyyy, - @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage - ) { - return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(compareYyyy, targetYyyy, stage)); + @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam + Integer compareYyyy, + @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam + Integer targetYyyy, + @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage) { + return ApiResponseDto.ok( + labelAllocateService.findInferenceDetail(compareYyyy, targetYyyy, stage)); } @Operation(summary = "작업이관 > 라벨러 상세 정보", description = "작업이관 > 라벨러 상세 정보") @GetMapping("/labeler-detail") public ApiResponseDto findLabelerDetail( - @RequestParam(defaultValue = "01022223333") String userId, - @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam Integer compareYyyy, - @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam Integer targetYyyy, - @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage) { - return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage)); + @RequestParam(defaultValue = "01022223333") String userId, + @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam + Integer compareYyyy, + @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam + Integer targetYyyy, + @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage) { + return ApiResponseDto.ok( + labelAllocateService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage)); } @Operation(summary = "작업 이관", description = "작업 이관") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class), - examples = { - @ExampleObject( - name = "라벨러 할당 예시", - description = "라벨러 할당 예시", - value = - """ + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class), + examples = { + @ExampleObject( + name = "라벨러 할당 예시", + description = "라벨러 할당 예시", + value = + """ { "autoType": "AUTO", "stage": 4, @@ -226,24 +230,29 @@ public class LabelAllocateApiController { ] } """) - })), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + })), + @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) { + @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(), dto.getCompareYyyy(), dto.getTargetYyyy()); + labelAllocateService.allocateMove( + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getCompareYyyy(), + dto.getTargetYyyy()); return ApiResponseDto.ok(null); } 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 16624689..5fe14bbb 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 @@ -206,7 +206,7 @@ public class LabelAllocateDto { @Schema(description = "비교년도", example = "2022") private Integer compareYyyy; - + @Schema(description = "기준년도", example = "2024") private Integer targetYyyy; } 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 aa59472d..54d30f5b 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 @@ -34,24 +34,24 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param autoType 자동/수동 배정 타입 - * @param stage 회차 - * @param targetUsers 라벨러 목록 + * @param autoType 자동/수동 배정 타입 + * @param stage 회차 + * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional public void allocateAsc( - String autoType, - Integer stage, - List targetUsers, - List targetInspectors, - Integer compareYyyy, - Integer targetYyyy - ) { + String autoType, + Integer stage, + List targetUsers, + List targetInspectors, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; // geom 잔여건수 조회 - Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); + Long chargeCnt = + labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); // Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); // if (!Objects.equals(chargeCnt, totalDemand)) { // log.info("chargeCnt != totalDemand"); @@ -62,7 +62,8 @@ public class LabelAllocateService { return; } - List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + List allIds = + labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); @@ -73,7 +74,8 @@ public class LabelAllocateService { } // 검수자에게 userCount명 만큼 할당 - List list = labelAllocateCoreService.findAssignedLabelerList(compareYyyy, targetYyyy, stage); + List list = + labelAllocateCoreService.findAssignedLabelerList(compareYyyy, targetYyyy, stage); int from = 0; for (TargetInspector inspector : targetInspectors) { @@ -84,7 +86,7 @@ public class LabelAllocateService { } List assignmentUids = - list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); + list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid()); @@ -99,50 +101,50 @@ public class LabelAllocateService { /** * 작업자 목록 및 3일치 통계 조회 * - * @param analUid 분석 ID - * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param searchName 이름 검색 + * @param analUid 분석 ID + * @param workerType 작업자 유형 (LABELER/INSPECTOR) + * @param searchName 이름 검색 * @param searchEmployeeNo 사번 검색 - * @param sortType 정렬 조건 + * @param sortType 정렬 조건 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + labelAllocateCoreService.findWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); for (WorkerStatistics worker : workers) { Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(1), analUid); Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(2), analUid); Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(3), analUid); long average = (day1Count + day2Count + day3Count) / 3; DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); + DailyHistory.builder() + .day1Ago(day1Count) + .day2Ago(day2Count) + .day3Ago(day3Count) + .average(average) + .build(); worker.setHistory(history); @@ -155,12 +157,17 @@ public class LabelAllocateService { return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); } - public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) { + public InferenceDetail findInferenceDetail( + Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateCoreService.findInferenceDetail(compareYyyy, targetYyyy, stage); } - public void allocateMove(String autoType, Integer stage, List targetUsers, Integer compareYyyy, - Integer targetYyyy) { + public void allocateMove( + String autoType, + Integer stage, + List targetUsers, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); @@ -169,7 +176,9 @@ public class LabelAllocateService { return; } - List allIds = labelAllocateCoreService.fetchNextMoveIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + List allIds = + labelAllocateCoreService.fetchNextMoveIds( + lastId, chargeCnt, compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); @@ -180,7 +189,8 @@ public class LabelAllocateService { } } - public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public LabelerDetail findLabelerDetail( + String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateCoreService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); } } 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 6e9f5104..56bca503 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 @@ -20,18 +20,21 @@ public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; - public List fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public List fetchNextIds( + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.fetchNextIds(lastId, batchSize, compareYyyy, targetYyyy, stage); } - public void assignOwner(List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public void assignOwner( + List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { labelAllocateRepository.assignOwner(ids, userId, compareYyyy, targetYyyy, stage); } - public List findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage) { + public List findAssignedLabelerList( + Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.findAssignedLabelerList(compareYyyy, targetYyyy, stage).stream() - .map(LabelingAssignmentEntity::toDto) - .toList(); + .map(LabelingAssignmentEntity::toDto) + .toList(); } public Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy) { @@ -47,13 +50,13 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, searchName, searchEmployeeNo, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -61,7 +64,7 @@ public class LabelAllocateCoreService { } public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid); } @@ -69,7 +72,8 @@ public class LabelAllocateCoreService { labelAllocateRepository.assignInspectorBulk(assignmentUids, inspectorUid); } - public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) { + public InferenceDetail findInferenceDetail( + Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.findInferenceDetail(compareYyyy, targetYyyy, stage); } @@ -77,15 +81,18 @@ public class LabelAllocateCoreService { return labelAllocateRepository.findLabelUnCompleteCnt(analUid); } - public List fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateRepository.fetchNextMoveIds(lastId, batchSize, compareYyyy, targetYyyy, stage); + public List fetchNextMoveIds( + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.fetchNextMoveIds( + lastId, batchSize, compareYyyy, targetYyyy, stage); } public void assignOwnerMove(List sub, String userId) { labelAllocateRepository.assignOwnerMove(sub, userId); } - public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public LabelerDetail findLabelerDetail( + String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); } } 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 5c374293..caf929c7 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 @@ -12,11 +12,14 @@ import java.util.UUID; public interface LabelAllocateRepositoryCustom { - List fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + List fetchNextIds( + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); - void assignOwner(List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); + void assignOwner( + List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); - List findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage); + List findAssignedLabelerList( + Integer compareYyyy, Integer targetYyyy, Integer stage); Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy); @@ -26,7 +29,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); + Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(Long analUid); @@ -38,11 +41,13 @@ public interface LabelAllocateRepositoryCustom { InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage); - public List fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + public List fetchNextMoveIds( + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); Long findLabelUnCompleteCnt(Long analUid); void assignOwnerMove(List sub, String userId); - LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); + LabelerDetail findLabelerDetail( + String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); } 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 c29e6f4e..6a68e9b3 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 @@ -46,40 +46,41 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto private final JPAQueryFactory queryFactory; - @PersistenceContext - private EntityManager em; + @PersistenceContext private EntityManager em; @Override - public List fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public List fetchNextIds( + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) - .limit(batchSize) - .fetch(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) + .limit(batchSize) + .fetch(); } @Override - public void assignOwner(List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public void assignOwner( + List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { // analUid로 분석 정보 조회 MapSheetAnalDataInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage) - ) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where( + mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage)) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); @@ -87,17 +88,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 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(); + .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) @@ -106,13 +107,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto for (Long geoUid : ids) { em.createNativeQuery(sql) - .setParameter(1, UUID.randomUUID()) - .setParameter(2, geoUid) - .setParameter(3, userId) - .setParameter(4, LabelState.ASSIGNED.getId()) - .setParameter(5, "") - .setParameter(6, analEntity.getAnalUid()) - .executeUpdate(); + .setParameter(1, UUID.randomUUID()) + .setParameter(2, geoUid) + .setParameter(3, userId) + .setParameter(4, LabelState.ASSIGNED.getId()) + .setParameter(5, "") + .setParameter(6, analEntity.getAnalUid()) + .executeUpdate(); } em.flush(); @@ -120,91 +121,92 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public List findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage) { + public List findAssignedLabelerList( + Integer compareYyyy, Integer targetYyyy, Integer stage) { // analUid로 분석 정보 조회 MapSheetAnalDataInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage) - ) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where( + mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage)) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); } return queryFactory - .selectFrom(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid()), - labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), - labelingAssignmentEntity.inspectorUid.isNull()) - .orderBy(labelingAssignmentEntity.workerUid.asc()) - .fetch(); + .selectFrom(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid()), + labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), + labelingAssignmentEntity.inspectorUid.isNull()) + .orderBy(labelingAssignmentEntity.workerUid.asc()) + .fetch(); } @Override public Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy) { return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .fetchOne(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .fetchOne(); } @Override public void assignInspector(UUID assignmentUid, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) + .execute(); } @Override public List availUserList(String role) { return queryFactory - .select( - Projections.constructor( - LabelAllocateDto.UserList.class, - memberEntity.userRole, - memberEntity.employeeNo, - memberEntity.name)) - .from(memberEntity) - .where( - memberEntity.userRole.eq(role), - memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) - .orderBy(memberEntity.name.asc()) - .fetch(); + .select( + Projections.constructor( + LabelAllocateDto.UserList.class, + memberEntity.userRole, + memberEntity.employeeNo, + memberEntity.name)) + .from(memberEntity) + .where( + memberEntity.userRole.eq(role), + memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) + .orderBy(memberEntity.name.asc()) + .fetch(); } @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid - : labelingAssignmentEntity.workerUid; + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid + : labelingAssignmentEntity.workerUid; BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.isNotNull() - : labelingAssignmentEntity.workerUid.isNotNull(); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.isNotNull() + : labelingAssignmentEntity.workerUid.isNotNull(); // 검색 조건 BooleanExpression searchCondition = null; @@ -218,49 +220,49 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 완료, 스킵, 남은 작업 계산 NumberExpression completedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression skippedSum = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("SKIP")) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum(); NumberExpression remainingSum = - new CaseBuilder() - .when( - labelingAssignmentEntity - .workState - .notIn("DONE", "SKIP") - .and(labelingAssignmentEntity.workState.isNotNull())) - .then(1L) - .otherwise(0L) - .sum(); + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .notIn("DONE", "SKIP") + .and(labelingAssignmentEntity.workState.isNotNull())) + .then(1L) + .otherwise(0L) + .sum(); // 기본 통계 조회 쿼리 var baseQuery = - queryFactory - .select( - workerIdField, - memberEntity.name, - workerIdField.count(), - completedSum, - skippedSum, - remainingSum, - labelingAssignmentEntity.stagnationYn.max()) - .from(labelingAssignmentEntity) - .leftJoin(memberEntity) - .on( - "REVIEWER".equals(workerType) - ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) - : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) - .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) - .groupBy(workerIdField, memberEntity.name); + queryFactory + .select( + workerIdField, + memberEntity.name, + workerIdField.count(), + completedSum, + skippedSum, + remainingSum, + labelingAssignmentEntity.stagnationYn.max()) + .from(labelingAssignmentEntity) + .leftJoin(memberEntity) + .on( + "REVIEWER".equals(workerType) + ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) + : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) + .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) + .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 if (sortType != null) { @@ -286,132 +288,132 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 결과를 DTO로 변환 return baseQuery.fetch().stream() - .map( - tuple -> { - Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); - return WorkerStatistics.builder() - .workerId(tuple.get(workerIdField)) - .workerName(tuple.get(memberEntity.name)) - .workerType(workerType) - .totalAssigned(tuple.get(workerIdField.count())) - .completed(tuple.get(completedSum)) - .skipped(tuple.get(skippedSum)) - .remaining(tuple.get(remainingSum)) - .history(null) // 3일 이력은 Service에서 채움 - .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') - .build(); - }) - .toList(); + .map( + tuple -> { + Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); + return WorkerStatistics.builder() + .workerId(tuple.get(workerIdField)) + .workerName(tuple.get(memberEntity.name)) + .workerType(workerType) + .totalAssigned(tuple.get(workerIdField.count())) + .completed(tuple.get(completedSum)) + .skipped(tuple.get(skippedSum)) + .remaining(tuple.get(remainingSum)) + .history(null) // 3일 이력은 Service에서 채움 + .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') + .build(); + }) + .toList(); } @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { // 전체 배정 건수 Long totalAssigned = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where(labelingAssignmentEntity.analUid.eq(analUid)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where(labelingAssignmentEntity.analUid.eq(analUid)) + .fetchOne(); // 완료 + 스킵 건수 Long completedCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workState.in("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workState.in("DONE", "SKIP")) + .fetchOne(); // 투입된 라벨러 수 (고유한 worker_uid 수) Long labelerCount = - queryFactory - .select(labelingAssignmentEntity.workerUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.workerUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull()) + .fetchOne(); // 남은 라벨링 작업 데이터 수 Long remainingLabelCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.workerUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + .fetchOne(); // 투입된 검수자 수 (고유한 inspector_uid 수) Long inspectorCount = - queryFactory - .select(labelingAssignmentEntity.inspectorUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.inspectorUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull()) + .fetchOne(); // 남은 검수 작업 데이터 수 Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + labelingAssignmentEntity.inspectorUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE")) + .fetchOne(); // 진행률 계산 double progressRate = 0.0; if (totalAssigned != null && totalAssigned > 0) { progressRate = - (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; + (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; } // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; return WorkProgressInfo.builder() - .labelingProgressRate(progressRate) - .workStatus(workStatus) - .completedCount(completedCount != null ? completedCount : 0L) - .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) - .labelerCount(labelerCount != null ? labelerCount : 0L) - .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) - .inspectorCount(inspectorCount != null ? inspectorCount : 0L) - .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) - .build(); + .labelingProgressRate(progressRate) + .workStatus(workStatus) + .completedCount(completedCount != null ? completedCount : 0L) + .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) + .labelerCount(labelerCount != null ? labelerCount : 0L) + .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) + .inspectorCount(inspectorCount != null ? inspectorCount : 0L) + .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) + .build(); } @Override public Long findDailyProcessedCount( - String workerId, String workerType, LocalDate date, Long analUid) { + String workerId, String workerType, LocalDate date, Long analUid) { // 해당 날짜의 시작과 끝 시간 ZonedDateTime startOfDay = date.atStartOfDay(ZoneId.systemDefault()); ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()); BooleanExpression workerCondition = - "REVIEWER".equals(workerType) - ? labelingAssignmentEntity.inspectorUid.eq(workerId) - : labelingAssignmentEntity.workerUid.eq(workerId); + "REVIEWER".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.eq(workerId) + : labelingAssignmentEntity.workerUid.eq(workerId); Long count = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analUid), - workerCondition, - labelingAssignmentEntity.workState.in( - LabelState.DONE.getId(), LabelState.SKIP.getId()), - labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + workerCondition, + labelingAssignmentEntity.workState.in( + LabelState.DONE.getId(), LabelState.SKIP.getId()), + labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) + .fetchOne(); return count != null ? count : 0L; } @@ -419,172 +421,175 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public void assignInspectorBulk(List assignmentUids, String inspectorUid) { queryFactory - .update(labelingAssignmentEntity) - .set(labelingAssignmentEntity.inspectorUid, inspectorUid) - .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) - .execute(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) + .execute(); em.clear(); } @Override - public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) { + public InferenceDetail findInferenceDetail( + Integer compareYyyy, Integer targetYyyy, Integer stage) { // analUid로 분석 정보 조회 MapSheetAnalDataInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage) - ) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where( + mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage)) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) + .fetchOne(); return queryFactory - .select( - Projections.constructor( - InferenceDetail.class, - mapSheetAnalEntity.analTitle, - Expressions.numberTemplate(Integer.class, "{0}", 4), - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt, - labelingAssignmentEntity.workerUid.countDistinct(), - labelingAssignmentEntity.inspectorUid.countDistinct())) - .from(mapSheetAnalEntity) - .innerJoin(labelingAssignmentEntity) - .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) - .where(mapSheetAnalEntity.id.eq(analEntity.getAnalUid())) - .groupBy( - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.detectingCnt) - .fetchOne(); + .select( + Projections.constructor( + InferenceDetail.class, + mapSheetAnalEntity.analTitle, + Expressions.numberTemplate(Integer.class, "{0}", 4), + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt, + labelingAssignmentEntity.workerUid.countDistinct(), + labelingAssignmentEntity.inspectorUid.countDistinct())) + .from(mapSheetAnalEntity) + .innerJoin(labelingAssignmentEntity) + .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) + .where(mapSheetAnalEntity.id.eq(analEntity.getAnalUid())) + .groupBy( + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt) + .fetchOne(); } @Override - public List fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public List fetchNextMoveIds( + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid) - .from(mapSheetAnalDataInferenceGeomEntity) - .where( - // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 - lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), - mapSheetAnalDataInferenceGeomEntity.labelState.in( - LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) - .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) - .limit(batchSize) - .fetch(); + .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .from(mapSheetAnalDataInferenceGeomEntity) + .where( + // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 + lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), + 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(); + 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(); + .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(); + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.workerUid, userId) + .where(labelingAssignmentEntity.inferenceGeomUid.in(sub)) + .execute(); em.clear(); } @Override - public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public LabelerDetail findLabelerDetail( + String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { NumberExpression assignedCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + 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(); + 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(); + 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)); + new CaseBuilder() + .when(completeCnt.eq(0L)) + .then(0.0) + .otherwise( + Expressions.numberTemplate( + Double.class, + "round({0} / {1}, 2)", + labelingAssignmentEntity.count(), + completeCnt)); // analUid로 분석 정보 조회 MapSheetAnalDataInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage) - ) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalDataInferenceEntity) + .where( + mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalDataInferenceEntity.stage.eq(stage)) + .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) + .limit(1) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); } 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(analEntity.getAnalUid())) - .where(memberEntity.employeeNo.eq(userId)) - .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.employeeNo) - .fetchOne(); + .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(analEntity.getAnalUid())) + .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/LabelWorkRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java index 19a4454c..e17da42c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelWorkRepositoryImpl.java @@ -1,23 +1,11 @@ package com.kamco.cd.kamcoback.postgres.repository.label; -import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity; -import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; -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.entity.MapSheetAnalDataGeomEntity; -import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.CaseBuilder; @@ -25,16 +13,9 @@ import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.PersistenceContext; -import jakarta.validation.Valid; -import java.time.LocalDate; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; -import java.util.Objects; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -58,7 +39,6 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport this.queryFactory = queryFactory; } - @Override public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { @@ -72,69 +52,67 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport whereBuilder.and(mapSheetAnalDataInferenceEntity.targetYyyy.eq(searchReq.getDetectYyyy())); } - //mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id) + // mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id) - whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); + whereSubBuilder.and( + mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); - if (searchReq.getStrtDttm() != null && ! searchReq.getStrtDttm().isEmpty() - && searchReq.getEndDttm() != null && ! searchReq.getEndDttm().isEmpty()) { + if (searchReq.getStrtDttm() != null + && !searchReq.getStrtDttm().isEmpty() + && searchReq.getEndDttm() != null + && !searchReq.getEndDttm().isEmpty()) { - //whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); + // whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); whereSubBuilder.and( - Expressions.stringTemplate("to_char({0}, 'YYYYMMDD')", mapSheetAnalDataInferenceGeomEntity.labelStateDttm) - .between(searchReq.getStrtDttm(), searchReq.getEndDttm()) - ); + Expressions.stringTemplate( + "to_char({0}, 'YYYYMMDD')", mapSheetAnalDataInferenceGeomEntity.labelStateDttm) + .between(searchReq.getStrtDttm(), searchReq.getEndDttm())); - //whereBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min().isNotNull()); + // whereBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min().isNotNull()); } - - List foundContent = - queryFactory - .select( - Projections.constructor( - LabelWorkMng.class, - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage, - mapSheetAnalDataInferenceEntity.createdDttm.min(), - mapSheetAnalDataInferenceGeomEntity.dataUid.count(), - mapSheetAnalDataInferenceGeomEntity.dataUid.count(), - - new CaseBuilder() - .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("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")) - .then(1L) - .otherwise(0L) - .sum(), - - mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min() - )) - .from(mapSheetAnalDataInferenceEntity) - .innerJoin(mapSheetAnalDataInferenceGeomEntity) - .on(whereSubBuilder) - .where(whereBuilder) - .groupBy( - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage - ) - .orderBy(mapSheetAnalDataInferenceEntity.targetYyyy.desc() - ,mapSheetAnalDataInferenceEntity.stage.desc()) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); + queryFactory + .select( + Projections.constructor( + LabelWorkMng.class, + mapSheetAnalDataInferenceEntity.compareYyyy, + mapSheetAnalDataInferenceEntity.targetYyyy, + mapSheetAnalDataInferenceEntity.stage, + mapSheetAnalDataInferenceEntity.createdDttm.min(), + mapSheetAnalDataInferenceGeomEntity.dataUid.count(), + mapSheetAnalDataInferenceGeomEntity.dataUid.count(), + new CaseBuilder() + .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("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")) + .then(1L) + .otherwise(0L) + .sum(), + mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min())) + .from(mapSheetAnalDataInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceGeomEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy( + mapSheetAnalDataInferenceEntity.compareYyyy, + mapSheetAnalDataInferenceEntity.targetYyyy, + mapSheetAnalDataInferenceEntity.stage) + .orderBy( + mapSheetAnalDataInferenceEntity.targetYyyy.desc(), + mapSheetAnalDataInferenceEntity.stage.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); /* Long countQuery =