From e0c78854c013d28e8778f8b130068ea1c2e3f15a Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 29 Dec 2025 15:39:13 +0900 Subject: [PATCH 01/70] =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=B2=98=EB=A6=AC,?= =?UTF-8?q?=EB=AF=B8=EC=B2=98=EB=A6=AC=20enum=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/enums/SyncCheckStateType.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/com/kamco/cd/kamcoback/common/enums/SyncCheckStateType.java diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncCheckStateType.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncCheckStateType.java new file mode 100644 index 00000000..2b4f1e81 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncCheckStateType.java @@ -0,0 +1,26 @@ +package com.kamco.cd.kamcoback.common.enums; + +import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; +import com.kamco.cd.kamcoback.common.utils.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@CodeExpose +@Getter +@AllArgsConstructor +public enum SyncCheckStateType implements EnumType { + NOTYET("미처리"), + DONE("완료"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } +} From 756d148f4cf03cb6fcbcf612bcfa49729fc70d66 Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 29 Dec 2025 15:48:38 +0900 Subject: [PATCH 02/70] =?UTF-8?q?enum=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java index fc06e79b..b80b670e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java @@ -27,4 +27,5 @@ public enum SyncStateType implements EnumType { public String getText() { return desc; } + } From 2d037f9839699092832dfa9b05b2c0f93a4a4f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dean=5B=EB=B0=B1=EB=B3=91=EB=82=A8=5D?= Date: Tue, 30 Dec 2025 12:16:30 +0900 Subject: [PATCH 03/70] api scene --- .../common/enums/CommonUseStatus.java | 13 ++- .../postgres/core/MapInkxMngCoreService.java | 8 ++ .../postgres/entity/MapInkx50kEntity.java | 5 ++ .../postgres/entity/MapInkx5kEntity.java | 11 +++ .../scene/MapInkx5kRepositoryCustom.java | 4 + .../scene/MapInkx5kRepositoryImpl.java | 25 ++++++ .../scene/MapInkxMngApiV2Controller.java | 52 ++++++++++++ .../cd/kamcoback/scene/dto/MapInkxMngDto.java | 85 ++++++++++++++----- .../scene/service/MapInkxMngService.java | 7 ++ 9 files changed, 186 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/scene/MapInkxMngApiV2Controller.java diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/CommonUseStatus.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/CommonUseStatus.java index c099d74f..75596c54 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/enums/CommonUseStatus.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/CommonUseStatus.java @@ -1,5 +1,7 @@ package com.kamco.cd.kamcoback.common.enums; +import com.kamco.cd.kamcoback.common.enums.ApiConfigEnum.EnumDto; +import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; import com.kamco.cd.kamcoback.common.utils.enums.EnumType; import java.util.Arrays; import lombok.AllArgsConstructor; @@ -11,18 +13,19 @@ import lombok.Getter; *

This enum represents whether a resource is active, excluded from processing, or inactive. It * is commonly used for filtering, business rules, and status management. */ +@CodeExpose @Getter @AllArgsConstructor public enum CommonUseStatus implements EnumType { // @formatter:off - USE("USE", "Active", 100) + USE("USE", "사용중", 100) /** Actively used and available */ , - EXCEPT("EXCEPT", "Excluded", 200) + EXCEPT("EXCEPT", "영구 추론제외", 200) /** Explicitly excluded from use or processing */ , - NOT_USE("NOT_USE", "Inactive", 999) + NOT_USE("NOT_USE", "사용안", 999) /** Not used or disabled */ ; // @formatter:on @@ -37,4 +40,8 @@ public enum CommonUseStatus implements EnumType { .findFirst() .orElse(CommonUseStatus.NOT_USE); } + + public EnumDto getEnumDto() { + return new EnumDto<>(this, this.id, this.text); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapInkxMngCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapInkxMngCoreService.java index 67e2dfa3..78aecaa1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapInkxMngCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MapInkxMngCoreService.java @@ -9,6 +9,7 @@ import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity; import com.kamco.cd.kamcoback.postgres.repository.scene.MapInkx50kRepository; import com.kamco.cd.kamcoback.postgres.repository.scene.MapInkx5kRepository; import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto; +import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto.MapListEntity; import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto.UseInferReq; import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; @@ -101,4 +102,11 @@ public class MapInkxMngCoreService { return getScene5k.toEntity(); } + + public Page getSceneListByPage( + CommonUseStatus useInference, String searchVal, MapInkxMngDto.searchReq searchReq) { + return mapInkx5kRepository + .getSceneListByPage(useInference, searchVal, searchReq) + .map(MapInkx5kEntity::toDto); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx50kEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx50kEntity.java index 95649a18..df11dee0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx50kEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx50kEntity.java @@ -1,5 +1,6 @@ package com.kamco.cd.kamcoback.postgres.entity; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheet; import com.kamco.cd.kamcoback.postgres.CommonDateEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -46,4 +47,8 @@ public class MapInkx50kEntity extends CommonDateEntity { this.mapidNo = mapidNo; this.geom = geom; } + + public MapSheet toEntity() { + return new MapSheet(mapidcdNo, mapidNm); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java index 80247365..b4d26049 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapInkx5kEntity.java @@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.common.enums.CommonUseStatus; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.MapSheet; import com.kamco.cd.kamcoback.postgres.CommonDateEntity; +import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto.MapListEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -73,4 +74,14 @@ public class MapInkx5kEntity extends CommonDateEntity { public InferenceResultDto.MapSheet toEntity() { return new MapSheet(mapidcdNo, mapidNm); } + + public MapListEntity toDto() { + return MapListEntity.builder() + .scene5k(this.toEntity()) + .scene50k(this.mapInkx50k.toEntity()) + .useInference(useInference) + .createdDttm(super.getCreatedDate()) + .updatedDttm(super.getModifiedDate()) + .build(); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryCustom.java index 50bdf94f..ee971c5c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryCustom.java @@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.common.enums.CommonUseStatus; import com.kamco.cd.kamcoback.postgres.entity.MapInkx5kEntity; import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto; import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto.MapList; +import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto.searchReq; import java.util.List; import java.util.Optional; import org.springframework.data.domain.Page; @@ -18,4 +19,7 @@ public interface MapInkx5kRepositoryCustom { Long findByMapidCdNoExists(String mapidcdNo); Optional findByMapidCdNoInfo(String mapidcdNo); + + Page getSceneListByPage( + CommonUseStatus useInference, String searchVal, searchReq searchReq); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryImpl.java index 6485e9c1..6e3789d3 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scene/MapInkx5kRepositoryImpl.java @@ -102,6 +102,31 @@ public class MapInkx5kRepositoryImpl extends QuerydslRepositorySupport .fetchOne()); } + @Override + public Page getSceneListByPage( + CommonUseStatus useInference, String searchVal, searchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + List content = + queryFactory + .selectFrom(mapInkx5kEntity) + .innerJoin(mapInkx5kEntity.mapInkx50k, mapInkx50kEntity) + .fetchJoin() + .where(searchUseInference(useInference), searchValueMapCdNm(searchVal)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(mapInkx5kEntity.mapidcdNo.asc()) + .fetch(); + Long count = + queryFactory + .select(mapInkx5kEntity.count()) + .from(mapInkx5kEntity) + .innerJoin(mapInkx5kEntity.mapInkx50k, mapInkx50kEntity) + .where(searchUseInference(useInference), searchValueMapCdNm(searchVal)) + .fetchOne(); + + return new PageImpl<>(content, pageable, count); + } + private BooleanExpression searchUseInference(CommonUseStatus useInference) { if (Objects.isNull(useInference)) { return null; diff --git a/src/main/java/com/kamco/cd/kamcoback/scene/MapInkxMngApiV2Controller.java b/src/main/java/com/kamco/cd/kamcoback/scene/MapInkxMngApiV2Controller.java new file mode 100644 index 00000000..39a1dae7 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/scene/MapInkxMngApiV2Controller.java @@ -0,0 +1,52 @@ +package com.kamco.cd.kamcoback.scene; + +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +import com.kamco.cd.kamcoback.common.enums.CommonUseStatus; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.scene.dto.MapInkxMngDto; +import com.kamco.cd.kamcoback.scene.service.MapInkxMngService; +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 lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "도엽 관리", description = "도엽 관리 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v2/scene") +public class MapInkxMngApiV2Controller { + + private final MapInkxMngService mapInkxMngService; + + @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) + }) + @GetMapping + public ApiResponseDto> findMapInkxMngList( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size, + @RequestParam(required = false) CommonUseStatus useInference, + @RequestParam(required = false) String searchVal) { + MapInkxMngDto.searchReq searchReq = new MapInkxMngDto.searchReq(page, size, ""); + return ApiResponseDto.ok( + mapInkxMngService.findMapInkxMngLists(useInference, searchVal, searchReq)); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/scene/dto/MapInkxMngDto.java b/src/main/java/com/kamco/cd/kamcoback/scene/dto/MapInkxMngDto.java index caa995a6..32464f83 100644 --- a/src/main/java/com/kamco/cd/kamcoback/scene/dto/MapInkxMngDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/scene/dto/MapInkxMngDto.java @@ -1,15 +1,17 @@ package com.kamco.cd.kamcoback.scene.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.JsonNode; +import com.kamco.cd.kamcoback.common.enums.ApiConfigEnum.EnumDto; import com.kamco.cd.kamcoback.common.enums.CommonUseStatus; -import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; -import com.kamco.cd.kamcoback.common.utils.enums.EnumType; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.EntityNotFoundException; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -19,25 +21,26 @@ import org.springframework.data.domain.Sort; public class MapInkxMngDto { - @CodeExpose - @Getter - @AllArgsConstructor - public enum UseInferenceType implements EnumType { - USE("사용중"), - EXCEPT("영구 추론제외"); - - private final String desc; - - @Override - public String getId() { - return name(); - } - - @Override - public String getText() { - return desc; - } - } + // CommonUseStatus class로 통합 20251230 + // @CodeExpose + // @Getter + // @AllArgsConstructor + // public enum UseInferenceType implements EnumType { + // USE("사용중"), + // EXCEPT("영구 추론제외"); + // + // private final String desc; + // + // @Override + // public String getId() { + // return name(); + // } + // + // @Override + // public String getText() { + // return desc; + // } + // } @Schema(name = "Basic", description = "Basic") @Getter @@ -55,6 +58,46 @@ public class MapInkxMngDto { private ZonedDateTime updatedDttm; } + @Getter + @Schema(name = "MapListEntity", description = "목록 항목") + public static class MapListEntity { + + private InferenceResultDto.MapSheet scene50k; + private InferenceResultDto.MapSheet scene5k; + private CommonUseStatus useInference; + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", + timezone = "Asia/Seoul") + private ZonedDateTime createdDttm; + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", + timezone = "Asia/Seoul") + private ZonedDateTime updatedDttm; + + public EnumDto getUseInference() { + EnumDto enumDto = useInference.getEnumDto(); + return enumDto; + } + + @Builder + public MapListEntity( + InferenceResultDto.MapSheet scene50k, + InferenceResultDto.MapSheet scene5k, + CommonUseStatus useInference, + ZonedDateTime createdDttm, + ZonedDateTime updatedDttm) { + this.scene50k = scene50k; + this.scene5k = scene5k; + this.useInference = useInference; + this.createdDttm = createdDttm; + this.updatedDttm = updatedDttm; + } + } + @Schema(name = "MapList", description = "목록 항목") @Getter @Setter diff --git a/src/main/java/com/kamco/cd/kamcoback/scene/service/MapInkxMngService.java b/src/main/java/com/kamco/cd/kamcoback/scene/service/MapInkxMngService.java index 5cd06c7c..bab97bbf 100644 --- a/src/main/java/com/kamco/cd/kamcoback/scene/service/MapInkxMngService.java +++ b/src/main/java/com/kamco/cd/kamcoback/scene/service/MapInkxMngService.java @@ -25,11 +25,18 @@ public class MapInkxMngService { private final MapInkxMngCoreService mapInkxMngCoreService; + // 도엽의 리스트 조회 public Page findMapInkxMngList( MapInkxMngDto.searchReq searchReq, CommonUseStatus useInference, String searchVal) { return mapInkxMngCoreService.findMapInkxMngList(searchReq, useInference, searchVal); } + // 도엽의 리스트 조회 + public Page findMapInkxMngLists( + CommonUseStatus useInference, String searchVal, MapInkxMngDto.searchReq searchReq) { + return mapInkxMngCoreService.getSceneListByPage(useInference, searchVal, searchReq); + } + public ResponseObj saveMapInkx5k(@Valid MapInkxMngDto.AddMapReq req) { String[] coordinates = req.getCoordinates().split("\\r?\\n"); From ff2c54d38e117f9071ad7d64ab90a7f796d51e74 Mon Sep 17 00:00:00 2001 From: teddy Date: Tue, 30 Dec 2025 18:32:21 +0900 Subject: [PATCH 04/70] =?UTF-8?q?=EC=9D=B4=EB=85=B8=ED=8E=A8=20=EB=AA=A9?= =?UTF-8?q?=EC=97=85=20=EC=9E=91=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Innopam/InnopamApiController.java | 145 ++++++++++++++++++ .../kamcoback/Innopam/dto/DetectMastDto.java | 91 +++++++++++ .../postgres/core/DetectMastCoreService.java | 62 ++++++++ .../postgres/entity/DetectMastEntity.java | 85 ++++++++++ .../repository/DetectMastRepository.java | 7 + .../DetectMastRepositoryCustom.java | 12 ++ .../repository/DetectMastRepositoryImpl.java | 59 +++++++ .../Innopam/service/DetectMastService.java | 59 +++++++ 8 files changed, 520 insertions(+) create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastEntity.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepository.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java new file mode 100644 index 00000000..13bd6456 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java @@ -0,0 +1,145 @@ +package com.kamco.cd.kamcoback.Innopam; + +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.service.DetectMastService; +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 org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +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; + +@Tag(name = "이노펨 mockup API", description = "이노펨 mockup API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/kcd/cdi/detect") +public class InnopamApiController { + + private final DetectMastService detectMastService; + + /** 탐지결과 등록 */ + @Operation(summary = "탐지결과 등록", description = "탐지결과 등록") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = DetectMastReq.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/mast/regist") + public DetectMastReq setChangeDetection( + @RequestBody @Valid DetectMastDto.DetectMastReq detectMast) { + detectMastService.saveDetectMast(detectMast); + return detectMast; + } + + @Operation(summary = "탐지결과 삭제", description = "탐지결과 삭제") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = DetectMastReq.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping("/mast/remove") + public String deleteChangeDetection(@RequestBody DetectMastReq detectMast) { + return "OK"; + } + + @Operation(summary = "탐지결과 등록목록 조회", description = "탐지결과 등록목록 조회") + @GetMapping("/mast/list") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "목록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + public List selectChangeDetectionList( + @RequestParam(required = false) String cprsBfYr, + @RequestParam(required = false) String cprsAdYr, + @RequestParam(required = false) Integer dtctSno) { + DetectMastSearch detectMastSearch = new DetectMastSearch(); + detectMastSearch.setCprsAdYr(cprsAdYr); + detectMastSearch.setCprsBfYr(cprsBfYr); + detectMastSearch.setDtctSno(dtctSno); + return detectMastService.selectDetectMast(detectMastSearch); + } + + @Operation(summary = "탐지결과 등록목록 상세 조회", description = "탐지결과 등록목록 상세 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "목록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/mast/list/{dtctMstId}") + public Basic selectChangeDetectionDetail(@PathVariable Long dtctMstId) { + return detectMastService.selectDetectMast(dtctMstId); + } + + @Operation(summary = "탐지객체 PNU 리스트 조회", description = "탐지객체 PNU 리스트 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "목록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}") + public void selectPnuList( + @PathVariable String cprsBfYr, @PathVariable String cprsAfYr, @PathVariable String dtctSno) { + DetectMastSearch detectMastSearch = new DetectMastSearch(); + detectMastSearch.setCprsAdYr(cprsAfYr); + detectMastSearch.setCprsBfYr(cprsBfYr); + detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); + detectMastService.findPnuData(detectMastSearch); + } + + /** + * 탐지객체 PNU 단건 조회 + * + * @param detectMast + */ + @GetMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}/{featureId}") + public void selectPnuDetail(@RequestBody DetectMastDto detectMast) {} +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java new file mode 100644 index 00000000..77ef145e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java @@ -0,0 +1,91 @@ +package com.kamco.cd.kamcoback.Innopam.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class DetectMastDto { + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Basic { + + private Long dtctMstId; + private String cprsBfYr; + private String cprsAdYr; + private Integer dtctSno; + private String pathNm; + private String crtEpno; + private String crtIp; + } + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class DetectMastReq { + + @NotBlank + @Schema(description = "before 연도", example = "2023") + private String cprsBfYr; + + @NotBlank + @Schema(description = "after 연도", example = "2024") + private String cprsAdYr; + + @NotBlank + @Schema(description = "차수(회차)", example = "4") + private Integer dtctSno; + + @NotBlank + @Schema(description = "파일경로", example = "/app/detect/result/2023_2024/4") + private String pathNm; + + @NotBlank + @Schema(description = "사원번호", example = "1234567") + private String crtEpno; + + @NotBlank + @Schema(description = "아이피", example = "0.0.0.0") + private String crtIp; + } + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class DetectMastSearch { + + private String cprsBfYr; + private String cprsAdYr; + private Integer dtctSno; + private String featureId; + } + + /** before 연도 */ + private String cprsBfYr; + + /** after 연도 */ + private String cprsAdYr; + + /** 차수 */ + private Integer dtctSno; + + /** shp 파일경로 */ + private String pathNm; + + /** 등록한 사람 사번 */ + private String crtEpno; + + /** 등록한 사람 ip */ + private String crtIp; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java new file mode 100644 index 00000000..6125209b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java @@ -0,0 +1,62 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.core; + +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastEntity; +import com.kamco.cd.kamcoback.Innopam.postgres.repository.DetectMastRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class DetectMastCoreService { + + private final DetectMastRepository detectMastRepository; + + public void saveDetectMast(DetectMastReq detectMast) { + DetectMastEntity detectMastEntity = new DetectMastEntity(); + detectMastEntity.setCprsBfYr(detectMast.getCprsBfYr()); + detectMastEntity.setCprsAdYr(detectMast.getCprsAdYr()); + detectMastEntity.setDtctSno(detectMast.getDtctSno()); + detectMastEntity.setPathNm(detectMast.getPathNm()); + detectMastEntity.setCrtEpno(detectMast.getCrtEpno()); + detectMastEntity.setCrtIp(detectMast.getCrtIp()); + detectMastRepository.save(detectMastEntity); + } + + public List selectDetectMast(DetectMastSearch detectMast) { + return detectMastRepository.findDetectMastList(detectMast).stream() + .map( + e -> + new DetectMastDto.Basic( + e.getId(), + e.getCprsBfYr(), + e.getCprsAdYr(), + e.getDtctSno(), + e.getPathNm(), + e.getCrtEpno(), + e.getCrtIp())) + .toList(); + } + + public Basic selectDetectMast(Long id) { + DetectMastEntity e = + detectMastRepository.findById(id).orElseThrow(() -> new RuntimeException("등록 데이터가 없습니다.")); + return new DetectMastDto.Basic( + e.getId(), + e.getCprsBfYr(), + e.getCprsAdYr(), + e.getDtctSno(), + e.getPathNm(), + e.getCrtEpno(), + e.getCrtIp()); + } + + public String findPnuData(DetectMastSearch detectMast) { + DetectMastEntity detectMastEntity = detectMastRepository.findPnuData(detectMast); + return detectMastEntity.getPathNm(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastEntity.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastEntity.java new file mode 100644 index 00000000..5d326b83 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastEntity.java @@ -0,0 +1,85 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "detect_mast") +public class DetectMastEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "detect_mast_id_gen") + @SequenceGenerator( + name = "detect_mast_id_gen", + sequenceName = "seq_detect_mast_id", + allocationSize = 1) + @Column(name = "dtct_mst_id", nullable = false) + private Long id; + + @NotNull + @ColumnDefault("gen_random_uuid()") + @Column(name = "dtct_mst_uuid", nullable = false) + private UUID dtctMstUuid = UUID.randomUUID(); + + @Size(max = 4) + @NotNull + @Column(name = "cprs_bf_yr", nullable = false, length = 4) + private String cprsBfYr; + + @Size(max = 4) + @NotNull + @Column(name = "cprs_ad_yr", nullable = false, length = 4) + private String cprsAdYr; + + @NotNull + @Column(name = "dtct_sno", nullable = false) + private Integer dtctSno; + + @NotNull + @Column(name = "path_nm", nullable = false, length = Integer.MAX_VALUE) + private String pathNm; + + @Size(max = 50) + @Column(name = "feature_id", length = 50) + private String featureId; + + @Size(max = 30) + @NotNull + @Column(name = "crt_epno", nullable = false, length = 30) + private String crtEpno; + + @Size(max = 45) + @NotNull + @Column(name = "crt_ip", nullable = false, length = 45) + private String crtIp; + + @NotNull + @ColumnDefault("now()") + @Column(name = "crt_dttm", nullable = false) + private ZonedDateTime crtDttm = ZonedDateTime.now(); + + @Size(max = 30) + @Column(name = "chg_epno", length = 30) + private String chgEpno; + + @Size(max = 45) + @Column(name = "chg_ip", length = 45) + private String chgIp; + + @Column(name = "chg_dttm") + private ZonedDateTime chgDttm; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepository.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepository.java new file mode 100644 index 00000000..c6aa24f3 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepository.java @@ -0,0 +1,7 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.repository; + +import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DetectMastRepository + extends JpaRepository, DetectMastRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java new file mode 100644 index 00000000..93ce4fb4 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java @@ -0,0 +1,12 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.repository; + +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastEntity; +import java.util.List; + +public interface DetectMastRepositoryCustom { + + public List findDetectMastList(DetectMastSearch detectMast); + + public DetectMastEntity findPnuData(DetectMastSearch detectMast); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java new file mode 100644 index 00000000..c2c658e2 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java @@ -0,0 +1,59 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.repository; + +import static com.kamco.cd.kamcoback.Innopam.postgres.entity.QDetectMastEntity.detectMastEntity; + +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastEntity; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class DetectMastRepositoryImpl implements DetectMastRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List findDetectMastList(DetectMastSearch detectMast) { + + BooleanBuilder whereBuilder = new BooleanBuilder(); + + if (StringUtils.isNotBlank(detectMast.getCprsAdYr())) { + whereBuilder.and(detectMastEntity.cprsAdYr.eq(detectMast.getCprsAdYr())); + } + + if (StringUtils.isNotBlank(detectMast.getCprsBfYr())) { + whereBuilder.and(detectMastEntity.cprsBfYr.eq(detectMast.getCprsBfYr())); + } + + if (detectMast.getDtctSno() != null) { + whereBuilder.and(detectMastEntity.dtctSno.eq(detectMast.getDtctSno())); + } + + return queryFactory.select(detectMastEntity).from(detectMastEntity).where(whereBuilder).fetch(); + } + + @Override + public DetectMastEntity findPnuData(DetectMastSearch detectMast) { + + BooleanBuilder whereBuilder = new BooleanBuilder(); + + whereBuilder.and(detectMastEntity.cprsAdYr.eq(detectMast.getCprsAdYr())); + whereBuilder.and(detectMastEntity.cprsBfYr.eq(detectMast.getCprsBfYr())); + whereBuilder.and(detectMastEntity.dtctSno.eq(detectMast.getDtctSno())); + + if (detectMast.getFeatureId() != null) { + whereBuilder.and(detectMastEntity.featureId.eq(detectMast.getFeatureId())); + } + + return queryFactory + .select(detectMastEntity) + .from(detectMastEntity) + .where(whereBuilder) + .fetchOne(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java new file mode 100644 index 00000000..9b1c9d08 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -0,0 +1,59 @@ +package com.kamco.cd.kamcoback.Innopam.service; + +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.postgres.core.DetectMastCoreService; +import java.io.File; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class DetectMastService { + + private final DetectMastCoreService detectMastCoreService; + + @Transactional + public void saveDetectMast(DetectMastReq detectMast) { + detectMastCoreService.saveDetectMast(detectMast); + } + + public List selectDetectMast(DetectMastSearch detectMast) { + return detectMastCoreService.selectDetectMast(detectMast); + } + + public Basic selectDetectMast(Long id) { + return detectMastCoreService.selectDetectMast(id); + } + + public void findPnuData(DetectMastSearch detectMast) { + String pathNm = detectMastCoreService.findPnuData(detectMast); + + File dir = new File(pathNm); + if (dir.exists() && dir.isDirectory()) { + File[] files = dir.listFiles(); + + if (files != null) { + for (File file : files) { + System.out.println(file.getName()); + } + } + } + } + + public static String randomPnu() { + ThreadLocalRandom r = ThreadLocalRandom.current(); + + String lawCode = String.valueOf(r.nextLong(1000000000L, 9999999999L)); // 10자리 + String sanFlag = r.nextBoolean() ? "1" : "2"; // 산/대지 + String bon = String.format("%04d", r.nextInt(1, 10000)); // 본번 + String bu = String.format("%04d", r.nextInt(0, 10000)); // 부번 + + return lawCode + sanFlag + bon + bu; + } +} From 0f7dda261b82f483b808504e84a97f31fd0f07ab Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Wed, 31 Dec 2025 09:10:11 +0900 Subject: [PATCH 05/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=ED=95=A0=EB=8B=B9=20=EB=A1=9C=EC=A7=81=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 76 ++++++++++++++++ .../kamcoback/label/dto/LabelAllocateDto.java | 48 ++++++++++ .../label/service/LabelAllocateService.java | 90 +++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java diff --git a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java new file mode 100644 index 00000000..29e5ab8f --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -0,0 +1,76 @@ +package com.kamco.cd.kamcoback.label; + +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; +import com.kamco.cd.kamcoback.label.service.LabelAllocateService; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.RestController; + +@Slf4j +@Tag(name = "라벨링 작업 관리", description = "라벨링 작업 관리") +@RequestMapping({"/api/label/mng"}) +@RequiredArgsConstructor +@RestController +public class LabelAllocateApiController { + + // 라벨링 수량 할당하는 로직 테스트 + @PostMapping("/allocate") + public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { + + //도엽별 카운트 쿼리 + List sheets = List.of( + new Sheet("1", 261), + new Sheet("2", 500), + new Sheet("3", 350), + new Sheet("4", 250), + new Sheet("5", 380), + new Sheet("6", 459) + ); + + //사용자별 할당 입력한 값 + List targets = List.of( + new TargetUser("A", 1000), + new TargetUser("B", 500), + new TargetUser("C", 700) + ); + + LabelAllocateService.allocate(new ArrayList<>(sheets), new ArrayList<>(targets)); + + targets.forEach(t -> { + log.info("[" + t.getUserId() + "]"); + t.getAssigned().forEach(u -> { + log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); + }); + }); + /** + * [A] 입력한 수 : 1000 + * - 도엽: 2 (500) + * - 도엽: 6 (459) + * - 도엽: 5 (41) + * + * [B] 입력한 수 : 500 + * - 도엽: 5 (339) + * - 도엽: 3 (161) + * + * [C] 입력한 수 : 700 + * - 도엽: 3 (189) + * - 도엽: 1 (261) + * - 도엽: 4 (250) + */ + //A 에게 도엽 2 asc 해서 500건 할당 -> 도엽 6 asc 해서 459 할당 -> 도엽 5 asc 해서 41건 할당 -> insert + //B 에게 도엽 5 위에 41건 할당한 것 빼고 asc 해서 339건 할당 -> 도엽 3 asc 해서 161건 할당 -> insert + //.... for문에서 할당한 것 빼고 asc 해서 건수만큼 할당 insert 하고 다음 으로 넘어가기 + + 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 new file mode 100644 index 00000000..612e3592 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelAllocateDto.java @@ -0,0 +1,48 @@ +package com.kamco.cd.kamcoback.label.dto; + +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +public class LabelAllocateDto { + + @Getter + @AllArgsConstructor + public static class Sheet { + + private final String sheetId; + private int count; + + public void decrease(int amount) { + this.count -= amount; + } + } + + @Getter + public static class TargetUser { + + private final String userId; + private final int demand; + private final List assigned = new ArrayList<>(); + private int allocated = 0; + @Setter + private int shortage = 0; + + public TargetUser(String userId, int demand) { + this.userId = userId; + this.demand = demand; + } + + public int getRemainDemand() { + return demand - allocated; + } + + public void assign(String sheetId, int count) { + assigned.add(new Sheet(sheetId, count)); + allocated += count; + } + + } +} 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 new file mode 100644 index 00000000..9336893c --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java @@ -0,0 +1,90 @@ +package com.kamco.cd.kamcoback.label.service; + +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class LabelAllocateService { + + public static void allocate(List sheets, List targetUsers) { + + // 1️⃣ 실제 도엽 기준 할당 + allocateSheets(sheets, targetUsers); + + // 2️⃣ 전체 부족분 비율 계산 + distributeShortage(targetUsers); //항상 마지막에 한 번만 호출해야함 + + } + + public static void allocateSheets( + List sheets, + List targets + ) { + // 도엽 큰 것부터 (선택 사항) + sheets.sort(Comparator.comparingInt(Sheet::getCount).reversed()); + + for (TargetUser target : targets) { + Iterator it = sheets.iterator(); + + while (it.hasNext() && target.getRemainDemand() > 0) { + Sheet sheet = it.next(); + + int assignable = Math.min( + sheet.getCount(), + target.getRemainDemand() + ); + + if (assignable > 0) { + target.assign(sheet.getSheetId(), assignable); + sheet.decrease(assignable); + } + + if (sheet.getCount() == 0) { + it.remove(); + } + } + } + } + + public static void distributeShortage(List targets) { + + int totalDemand = targets.stream() + .mapToInt(TargetUser::getDemand) + .sum(); + + int totalAllocated = targets.stream() + .mapToInt(t -> t.getAllocated()) + .sum(); + + int shortage = totalDemand - totalAllocated; + + if (shortage <= 0) { + return; + } + + int distributed = 0; + + for (int i = 0; i < targets.size(); i++) { + TargetUser t = targets.get(i); + + // 마지막 타겟이 나머지 가져가게 (반올림 오차 방지) + int share; + if (i == targets.size() - 1) { + share = shortage - distributed; + } else { + double ratio = (double) t.getDemand() / totalDemand; + share = (int) Math.round(shortage * ratio); + distributed += share; + } + + t.setShortage(share); + } + } + +} From e7df2e7d86581d7dee220fb22a8ec55dcc979ffa Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 31 Dec 2025 09:25:02 +0900 Subject: [PATCH 06/70] =?UTF-8?q?=EA=B0=9C=EB=B0=9C=EC=84=9C=EB=B2=84=20sh?= =?UTF-8?q?p=EC=83=9D=EC=84=B1=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 4a00ed71..579b7a42 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -74,5 +74,5 @@ mapsheet: upload: skipGdalValidation: true shp: - baseurl: /app/detect/result + baseurl: /app/tmp/detect/result From 0df6151777a0c1c2c3c6149a8a1666e5be53ca28 Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 31 Dec 2025 09:25:30 +0900 Subject: [PATCH 07/70] =?UTF-8?q?spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 63 ++++++++----------- .../kamcoback/label/dto/LabelAllocateDto.java | 6 +- .../label/service/LabelAllocateService.java | 22 ++----- 3 files changed, 34 insertions(+), 57 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 29e5ab8f..5a739715 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -26,51 +26,42 @@ public class LabelAllocateApiController { @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { - //도엽별 카운트 쿼리 - List sheets = List.of( - new Sheet("1", 261), - new Sheet("2", 500), - new Sheet("3", 350), - new Sheet("4", 250), - new Sheet("5", 380), - new Sheet("6", 459) - ); + // 도엽별 카운트 쿼리 + List sheets = + List.of( + new Sheet("1", 261), + new Sheet("2", 500), + new Sheet("3", 350), + new Sheet("4", 250), + new Sheet("5", 380), + new Sheet("6", 459)); - //사용자별 할당 입력한 값 - List targets = List.of( - new TargetUser("A", 1000), - new TargetUser("B", 500), - new TargetUser("C", 700) - ); + // 사용자별 할당 입력한 값 + List targets = + List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700)); LabelAllocateService.allocate(new ArrayList<>(sheets), new ArrayList<>(targets)); - targets.forEach(t -> { - log.info("[" + t.getUserId() + "]"); - t.getAssigned().forEach(u -> { - log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); - }); - }); + targets.forEach( + t -> { + log.info("[" + t.getUserId() + "]"); + t.getAssigned() + .forEach( + u -> { + log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); + }); + }); /** - * [A] 입력한 수 : 1000 - * - 도엽: 2 (500) - * - 도엽: 6 (459) - * - 도엽: 5 (41) + * [A] 입력한 수 : 1000 - 도엽: 2 (500) - 도엽: 6 (459) - 도엽: 5 (41) * - * [B] 입력한 수 : 500 - * - 도엽: 5 (339) - * - 도엽: 3 (161) + *

[B] 입력한 수 : 500 - 도엽: 5 (339) - 도엽: 3 (161) * - * [C] 입력한 수 : 700 - * - 도엽: 3 (189) - * - 도엽: 1 (261) - * - 도엽: 4 (250) + *

[C] 입력한 수 : 700 - 도엽: 3 (189) - 도엽: 1 (261) - 도엽: 4 (250) */ - //A 에게 도엽 2 asc 해서 500건 할당 -> 도엽 6 asc 해서 459 할당 -> 도엽 5 asc 해서 41건 할당 -> insert - //B 에게 도엽 5 위에 41건 할당한 것 빼고 asc 해서 339건 할당 -> 도엽 3 asc 해서 161건 할당 -> insert - //.... for문에서 할당한 것 빼고 asc 해서 건수만큼 할당 insert 하고 다음 으로 넘어가기 + // A 에게 도엽 2 asc 해서 500건 할당 -> 도엽 6 asc 해서 459 할당 -> 도엽 5 asc 해서 41건 할당 -> insert + // B 에게 도엽 5 위에 41건 할당한 것 빼고 asc 해서 339건 할당 -> 도엽 3 asc 해서 161건 할당 -> insert + // .... for문에서 할당한 것 빼고 asc 해서 건수만큼 할당 insert 하고 다음 으로 넘어가기 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 612e3592..a6802547 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 @@ -27,8 +27,7 @@ public class LabelAllocateDto { private final int demand; private final List assigned = new ArrayList<>(); private int allocated = 0; - @Setter - private int shortage = 0; + @Setter private int shortage = 0; public TargetUser(String userId, int demand) { this.userId = userId; @@ -38,11 +37,10 @@ public class LabelAllocateDto { public int getRemainDemand() { return demand - allocated; } - + public void assign(String sheetId, int count) { assigned.add(new Sheet(sheetId, count)); allocated += count; } - } } 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 9336893c..547284b6 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 @@ -18,14 +18,10 @@ public class LabelAllocateService { allocateSheets(sheets, targetUsers); // 2️⃣ 전체 부족분 비율 계산 - distributeShortage(targetUsers); //항상 마지막에 한 번만 호출해야함 - + distributeShortage(targetUsers); // 항상 마지막에 한 번만 호출해야함 } - public static void allocateSheets( - List sheets, - List targets - ) { + public static void allocateSheets(List sheets, List targets) { // 도엽 큰 것부터 (선택 사항) sheets.sort(Comparator.comparingInt(Sheet::getCount).reversed()); @@ -35,10 +31,7 @@ public class LabelAllocateService { while (it.hasNext() && target.getRemainDemand() > 0) { Sheet sheet = it.next(); - int assignable = Math.min( - sheet.getCount(), - target.getRemainDemand() - ); + int assignable = Math.min(sheet.getCount(), target.getRemainDemand()); if (assignable > 0) { target.assign(sheet.getSheetId(), assignable); @@ -54,13 +47,9 @@ public class LabelAllocateService { public static void distributeShortage(List targets) { - int totalDemand = targets.stream() - .mapToInt(TargetUser::getDemand) - .sum(); + int totalDemand = targets.stream().mapToInt(TargetUser::getDemand).sum(); - int totalAllocated = targets.stream() - .mapToInt(t -> t.getAllocated()) - .sum(); + int totalAllocated = targets.stream().mapToInt(t -> t.getAllocated()).sum(); int shortage = totalDemand - totalAllocated; @@ -86,5 +75,4 @@ public class LabelAllocateService { t.setShortage(share); } } - } From 33b13eb593374bdb76a59a548c11775f4ff4cddb Mon Sep 17 00:00:00 2001 From: Moon Date: Wed, 31 Dec 2025 09:42:44 +0900 Subject: [PATCH 08/70] =?UTF-8?q?=EC=98=81=EC=83=81=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java | 3 ++- .../mapsheet/MapSheetMngFileCheckerRepositoryImpl.java | 3 +++ .../repository/mapsheet/MapSheetMngRepositoryImpl.java | 5 ++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java index 2cf1f359..ac77ab8f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java @@ -69,10 +69,11 @@ public class MapSheetMngHstEntity extends CommonDateEntity { private Integer mngYyyy; // 년도 // JPA 연관관계: MapInkx5k 참조 (PK 기반) 소속도엽번호 1:5k + /* @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "map_sheet_code", referencedColumnName = "fid") private MapInkx5kEntity mapInkx5kByCode; - + */ // TODO 1:5k 관련 정보 추후 제거 필요 @Column(name = "map_sheet_num") private String mapSheetNum; // 도엽번호 diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java index b23d9f47..aaabf2a9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java @@ -76,8 +76,11 @@ public class MapSheetMngFileCheckerRepositoryImpl extends QuerydslRepositorySupp return new PageImpl<>(foundContent, pageable, countQuery); } + /* private NumberExpression rowNum() { return Expressions.numberTemplate( Integer.class, "row_number() over(order by {0} desc)", mapSheetMngHstEntity.createdDate); } + + */ } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java index 8fc981c8..2f93a04b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java @@ -393,9 +393,8 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport queryFactory .select(mapSheetMngHstEntity.hstUid.count()) .from(mapSheetMngHstEntity) - .innerJoin(mapInkx5kEntity, mapSheetMngHstEntity.mapInkx5kByCode) - .fetchJoin() - .leftJoin(mapInkx5kEntity.mapInkx50k, mapInkx50kEntity) + .innerJoin(mapInkx5kEntity) + .on(mapSheetMngHstEntity.mapSheetNum.eq(mapInkx5kEntity.mapidcdNo)) .where(whereBuilder) .fetchOne(); From 1d3e4f22cba8be050b0c2c52c2caa7decff61814 Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 31 Dec 2025 09:45:25 +0900 Subject: [PATCH 09/70] =?UTF-8?q?spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kamcoback/Innopam/dto/DetectMastDto.java | 28 +++------ .../Innopam/service/DetectMastService.java | 57 ++++++++++++++----- .../kamcoback/common/enums/SyncStateType.java | 1 - .../postgres/entity/MapSheetMngHstEntity.java | 3 - .../MapSheetMngFileCheckerRepositoryImpl.java | 1 - .../mapsheet/MapSheetMngRepositoryImpl.java | 1 - 6 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java index 77ef145e..3ec944e7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java @@ -7,10 +7,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor public class DetectMastDto { @Getter @@ -71,21 +67,13 @@ public class DetectMastDto { private String featureId; } - /** before 연도 */ - private String cprsBfYr; + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class FeaturePnuDto { - /** after 연도 */ - private String cprsAdYr; - - /** 차수 */ - private Integer dtctSno; - - /** shp 파일경로 */ - private String pathNm; - - /** 등록한 사람 사번 */ - private String crtEpno; - - /** 등록한 사람 ip */ - private String crtIp; + private String featureId; // polygon_id + private String pnu; // 랜덤 생성 + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java index 9b1c9d08..6924fa90 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -1,10 +1,15 @@ package com.kamco.cd.kamcoback.Innopam.service; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.postgres.core.DetectMastCoreService; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import lombok.RequiredArgsConstructor; @@ -32,21 +37,13 @@ public class DetectMastService { } public void findPnuData(DetectMastSearch detectMast) { - String pathNm = detectMastCoreService.findPnuData(detectMast); - - File dir = new File(pathNm); - if (dir.exists() && dir.isDirectory()) { - File[] files = dir.listFiles(); - - if (files != null) { - for (File file : files) { - System.out.println(file.getName()); - } - } - } + // String pathNm = detectMastCoreService.findPnuData(detectMast); + String pathNm = "/Users/bokmin/detect/result/2023_2024/4"; + File geoJson = new File(pathNm); + List list = this.extractFeaturePnus(geoJson); } - public static String randomPnu() { + private String randomPnu() { ThreadLocalRandom r = ThreadLocalRandom.current(); String lawCode = String.valueOf(r.nextLong(1000000000L, 9999999999L)); // 10자리 @@ -56,4 +53,38 @@ public class DetectMastService { return lawCode + sanFlag + bon + bu; } + + private List extractFeaturePnus(File geoJsonFile) { + + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = null; + try { + mapper.readTree(geoJsonFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + + JsonNode features = root.get("features"); + List result = new ArrayList<>(); + + if (features != null && features.isArray()) { + for (JsonNode feature : features) { + JsonNode properties = feature.get("properties"); + if (properties == null) { + continue; + } + + String polygonId = properties.path("polygon_id").asText(null); + if (polygonId == null) { + continue; + } + + String pnu = this.randomPnu(); + + result.add(new FeaturePnuDto(polygonId, pnu)); + } + } + + return result; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java index 6a9aff6a..f5c9d08f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/SyncStateType.java @@ -30,5 +30,4 @@ public enum SyncStateType implements EnumType { public String getText() { return desc; } - } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java index ac77ab8f..c5482e28 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetMngHstEntity.java @@ -3,12 +3,9 @@ package com.kamco.cd.kamcoback.postgres.entity; import com.kamco.cd.kamcoback.postgres.CommonDateEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.Size; import java.time.ZonedDateTime; diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java index aaabf2a9..28a7f9dc 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngFileCheckerRepositoryImpl.java @@ -8,7 +8,6 @@ import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngHstEntity; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.validation.Valid; diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java index 2f93a04b..59c86dce 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java @@ -1,6 +1,5 @@ package com.kamco.cd.kamcoback.postgres.repository.mapsheet; -import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx50kEntity.mapInkx50kEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngFileEntity.mapSheetMngFileEntity; From ce87fbfc136d75f91c9563039118396edd4fc140 Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 31 Dec 2025 11:07:21 +0900 Subject: [PATCH 10/70] =?UTF-8?q?=EC=9D=B4=EB=85=B8=ED=8E=A8=20=EB=AA=A9?= =?UTF-8?q?=EC=97=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Innopam/InnopamApiController.java | 23 +++-- .../Innopam/service/DetectMastService.java | 89 ++++++++++++------- 2 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java index 13bd6456..f3c835da 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java @@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.service.DetectMastService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -126,20 +127,26 @@ public class InnopamApiController { @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @GetMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}") - public void selectPnuList( + public List selectPnuList( @PathVariable String cprsBfYr, @PathVariable String cprsAfYr, @PathVariable String dtctSno) { DetectMastSearch detectMastSearch = new DetectMastSearch(); detectMastSearch.setCprsAdYr(cprsAfYr); detectMastSearch.setCprsBfYr(cprsBfYr); detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); - detectMastService.findPnuData(detectMastSearch); + return detectMastService.findPnuData(detectMastSearch); } - /** - * 탐지객체 PNU 단건 조회 - * - * @param detectMast - */ @GetMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}/{featureId}") - public void selectPnuDetail(@RequestBody DetectMastDto detectMast) {} + public FeaturePnuDto selectPnuDetail( + @PathVariable String cprsBfYr, + @PathVariable String cprsAfYr, + @PathVariable String dtctSno, + @PathVariable String featureId) { + DetectMastSearch detectMastSearch = new DetectMastSearch(); + detectMastSearch.setCprsAdYr(cprsAfYr); + detectMastSearch.setCprsBfYr(cprsBfYr); + detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); + detectMastSearch.setFeatureId(featureId); + return detectMastService.findPnuDataDetail(detectMastSearch); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java index 6924fa90..1adf7774 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -1,17 +1,23 @@ package com.kamco.cd.kamcoback.Innopam.service; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.postgres.core.DetectMastCoreService; -import java.io.File; -import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class DetectMastService { + private final JsonFactory factory = new JsonFactory(); private final DetectMastCoreService detectMastCoreService; @Transactional @@ -36,11 +43,15 @@ public class DetectMastService { return detectMastCoreService.selectDetectMast(id); } - public void findPnuData(DetectMastSearch detectMast) { - // String pathNm = detectMastCoreService.findPnuData(detectMast); - String pathNm = "/Users/bokmin/detect/result/2023_2024/4"; - File geoJson = new File(pathNm); - List list = this.extractFeaturePnus(geoJson); + public List findPnuData(DetectMastSearch detectMast) { + String pathNm = detectMastCoreService.findPnuData(detectMast); + return this.extractFeaturePnusFast(pathNm); + } + + public FeaturePnuDto findPnuDataDetail(DetectMastSearch detectMast) { + String pathNm = detectMastCoreService.findPnuData(detectMast); + List pnu = this.extractFeaturePnusFast(pathNm); + return pnu.get(0); } private String randomPnu() { @@ -54,37 +65,51 @@ public class DetectMastService { return lawCode + sanFlag + bon + bu; } - private List extractFeaturePnus(File geoJsonFile) { + public List extractFeaturePnusFast(String dirPath) { - ObjectMapper mapper = new ObjectMapper(); - JsonNode root = null; - try { - mapper.readTree(geoJsonFile); - } catch (IOException e) { - throw new RuntimeException(e); + Path basePath = Paths.get(dirPath); + if (!Files.isDirectory(basePath)) { + System.err.println("유효하지 않은 디렉터리: " + dirPath); + return List.of(); } - JsonNode features = root.get("features"); - List result = new ArrayList<>(); + // 병렬로 모으기 위한 thread-safe 컬렉션 + Queue out = new ConcurrentLinkedQueue<>(); - if (features != null && features.isArray()) { - for (JsonNode feature : features) { - JsonNode properties = feature.get("properties"); - if (properties == null) { - continue; - } + try (Stream stream = Files.walk(basePath)) { + stream + .filter(Files::isRegularFile) + .filter(p -> p.toString().toLowerCase().endsWith(".geojson")) + .parallel() // 병렬 + .forEach( + p -> { + try (InputStream in = Files.newInputStream(p); + JsonParser parser = factory.createParser(in)) { - String polygonId = properties.path("polygon_id").asText(null); - if (polygonId == null) { - continue; - } + // "polygon_id" 키를 만나면 다음 토큰 값을 읽어서 저장 + while (parser.nextToken() != null) { + if (parser.currentToken() == JsonToken.FIELD_NAME + && "polygon_id".equals(parser.getCurrentName())) { - String pnu = this.randomPnu(); + JsonToken next = parser.nextToken(); // 값으로 이동 + if (next == JsonToken.VALUE_STRING) { + String polygonId = parser.getValueAsString(); + out.add(new FeaturePnuDto(polygonId, this.randomPnu())); + } + } + } - result.add(new FeaturePnuDto(polygonId, pnu)); - } + } catch (Exception e) { + // 파일별 에러 로그는 최소화 + System.err.println("파싱 실패: " + p.getFileName() + " / " + e.getMessage()); + } + }); + + } catch (Exception e) { + System.err.println("디렉터리 탐색 실패: " + e.getMessage()); + return List.of(); } - return result; + return new ArrayList<>(out); } } From 3d3eedeec11bf1c27669948260486fede1d099d9 Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 31 Dec 2025 11:42:55 +0900 Subject: [PATCH 11/70] =?UTF-8?q?=EC=9D=B4=EB=85=B8=ED=8E=A8=20=EB=AA=A9?= =?UTF-8?q?=EC=97=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Innopam/service/DetectMastService.java | 339 +++++++++++++++--- 1 file changed, 298 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java index 1adf7774..ae6f8fa8 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -1,24 +1,39 @@ package com.kamco.cd.kamcoback.Innopam.service; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.postgres.core.DetectMastCoreService; -import java.io.InputStream; +import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ThreadLocalRandom; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.geotools.api.data.DataStore; +import org.geotools.api.data.DataStoreFinder; +import org.geotools.api.data.SimpleFeatureSource; +import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.index.strtree.STRtree; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,9 +42,20 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class DetectMastService { - private final JsonFactory factory = new JsonFactory(); private final DetectMastCoreService detectMastCoreService; + // ✅ GeoJSON 파싱용 + private final ObjectMapper om = new ObjectMapper(); + private final GeometryFactory gf = new GeometryFactory(); + + // ✅ (임시) SHP 루트/버전/필드명 + private static final String SHP_ROOT_DIR = "/app/tmp/shp"; // TODO 환경 맞게 변경 + private static final String SHP_YYYYMM = "202512"; // TODO yml 또는 DB에서 받기 + private static final String PNU_FIELD = "PNU"; // TODO 실제 컬럼명으로 변경 + + // ✅ 시도코드별 인덱스 캐시 + private final Map shpIndexCache = new ConcurrentHashMap<>(); + @Transactional public void saveDetectMast(DetectMastReq detectMast) { detectMastCoreService.saveDetectMast(detectMast); @@ -44,28 +70,19 @@ public class DetectMastService { } public List findPnuData(DetectMastSearch detectMast) { - String pathNm = detectMastCoreService.findPnuData(detectMast); - return this.extractFeaturePnusFast(pathNm); + String dirPath = detectMastCoreService.findPnuData(detectMast); + return extractFeaturePnusByIntersectionMaxCached(dirPath); } public FeaturePnuDto findPnuDataDetail(DetectMastSearch detectMast) { - String pathNm = detectMastCoreService.findPnuData(detectMast); - List pnu = this.extractFeaturePnusFast(pathNm); - return pnu.get(0); + List list = findPnuData(detectMast); + return list.isEmpty() ? null : list.get(0); } - private String randomPnu() { - ThreadLocalRandom r = ThreadLocalRandom.current(); - - String lawCode = String.valueOf(r.nextLong(1000000000L, 9999999999L)); // 10자리 - String sanFlag = r.nextBoolean() ? "1" : "2"; // 산/대지 - String bon = String.format("%04d", r.nextInt(1, 10000)); // 본번 - String bu = String.format("%04d", r.nextInt(0, 10000)); // 부번 - - return lawCode + sanFlag + bon + bu; - } - - public List extractFeaturePnusFast(String dirPath) { + // ========================= + // ✅ 캐시 적용된 메인 로직 + // ========================= + public List extractFeaturePnusByIntersectionMaxCached(String dirPath) { Path basePath = Paths.get(dirPath); if (!Files.isDirectory(basePath)) { @@ -73,35 +90,56 @@ public class DetectMastService { return List.of(); } - // 병렬로 모으기 위한 thread-safe 컬렉션 - Queue out = new ConcurrentLinkedQueue<>(); + List out = new ArrayList<>(); try (Stream stream = Files.walk(basePath)) { stream .filter(Files::isRegularFile) .filter(p -> p.toString().toLowerCase().endsWith(".geojson")) - .parallel() // 병렬 + // ✅ geojson 파싱/공간연산은 CPU+I/O 섞여서 병렬이 도움이 될 때가 많음 + .parallel() .forEach( p -> { - try (InputStream in = Files.newInputStream(p); - JsonParser parser = factory.createParser(in)) { + try { + JsonNode root = om.readTree(p.toFile()); + JsonNode features = root.path("features"); + if (!features.isArray()) { + return; + } - // "polygon_id" 키를 만나면 다음 토큰 값을 읽어서 저장 - while (parser.nextToken() != null) { - if (parser.currentToken() == JsonToken.FIELD_NAME - && "polygon_id".equals(parser.getCurrentName())) { + for (JsonNode feature : features) { + String polygonId = feature.path("properties").path("polygon_id").asText(null); + if (polygonId == null || polygonId.isBlank()) { + continue; + } - JsonToken next = parser.nextToken(); // 값으로 이동 - if (next == JsonToken.VALUE_STRING) { - String polygonId = parser.getValueAsString(); - out.add(new FeaturePnuDto(polygonId, this.randomPnu())); + Geometry target = toJtsGeometry(feature.path("geometry")); + if (target == null || target.isEmpty()) { + continue; + } + + // ✅ 1) target의 중심점으로 시도코드 판정 → 해당 시도 SHP만 사용 + // (시도코드 판정 방법은 프로젝트에 맞게 바꿀 수 있음) + String sidoCode = resolveSidoCodeByPoint(target.getCentroid().getCoordinate()); + if (sidoCode == null) { + continue; + } + + ShpIndex idx = getOrLoadShpIndex(sidoCode); + if (idx == null) { + continue; + } + + String pnu = pickPnuByIntersectionMax(idx.index, target); + if (pnu != null) { + synchronized (out) { + out.add(new FeaturePnuDto(polygonId, pnu)); } } } } catch (Exception e) { - // 파일별 에러 로그는 최소화 - System.err.println("파싱 실패: " + p.getFileName() + " / " + e.getMessage()); + System.err.println("GeoJSON 처리 실패: " + p.getFileName() + " / " + e.getMessage()); } }); @@ -110,6 +148,225 @@ public class DetectMastService { return List.of(); } - return new ArrayList<>(out); + return out; + } + + // ========================= + // ✅ SHP 인덱스 캐시 로딩 + // ========================= + + private static class ShpIndex { + + final String sidoCode; + final STRtree index; + + ShpIndex(String sidoCode, STRtree index) { + this.sidoCode = sidoCode; + this.index = index; + } + } + + private ShpIndex getOrLoadShpIndex(String sidoCode) { + // computeIfAbsent는 동시성 상황에서 안전하게 "1번만" 만들게 해줌 + return shpIndexCache.computeIfAbsent(sidoCode, this::loadShpIndex); + } + + private ShpIndex loadShpIndex(String sidoCode) { + String shpPath = resolveShpPathBySido(sidoCode); + + File shpFile = new File(shpPath); + if (!shpFile.exists()) { + System.err.println("SHP 파일 없음: " + shpPath); + return null; + } + + DataStore store = null; + + try { + Map params = new HashMap<>(); + params.put("url", shpFile.toURI().toURL()); + + store = DataStoreFinder.getDataStore(params); + if (store == null) { + System.err.println("SHP DataStore 생성 실패: " + shpPath); + return null; + } + + String typeName = store.getTypeNames()[0]; + SimpleFeatureSource source = store.getFeatureSource(typeName); + SimpleFeatureCollection collection = source.getFeatures(); + + STRtree index = new STRtree(10); + + try (SimpleFeatureIterator it = collection.features()) { + while (it.hasNext()) { + SimpleFeature f = it.next(); + + Object g = f.getDefaultGeometry(); + if (!(g instanceof Geometry)) { + continue; + } + + String pnu = Objects.toString(f.getAttribute(PNU_FIELD), null); + if (pnu == null || pnu.isBlank()) { + continue; + } + + Geometry geom = (Geometry) g; + index.insert(geom.getEnvelopeInternal(), new ShpRow(geom, pnu)); + } + } + + index.build(); + System.out.println("✅ SHP 인덱스 로딩 완료: sido=" + sidoCode + ", shp=" + shpFile.getName()); + return new ShpIndex(sidoCode, index); + + } catch (Exception e) { + System.err.println("SHP 인덱스 로딩 실패: sido=" + sidoCode + " / " + e.getMessage()); + return null; + } finally { + if (store != null) { + store.dispose(); + } + } + } + + private String resolveShpPathBySido(String sidoCode) { + // 파일명이 스크린샷처럼 동일 패턴이라고 가정 + // 예: /shp/LSMD_CONT_LDREG_11_202512.shp + return SHP_ROOT_DIR + "/LSMD_CONT_LDREG_" + sidoCode + "_" + SHP_YYYYMM + ".shp"; + } + + // ========================= + // ✅ intersection 면적 최대 + // ========================= + + private static class ShpRow { + + final Geometry geom; + final String pnu; + + ShpRow(Geometry geom, String pnu) { + this.geom = geom; + this.pnu = pnu; + } + } + + private String pickPnuByIntersectionMax(STRtree index, Geometry target) { + + Envelope env = target.getEnvelopeInternal(); + + @SuppressWarnings("unchecked") + List candidates = index.query(env); + if (candidates == null || candidates.isEmpty()) { + return null; + } + + double bestArea = 0.0; + String bestPnu = null; + + for (ShpRow row : candidates) { + Geometry parcel = row.geom; + + if (!parcel.intersects(target)) { + continue; + } + + Geometry inter = parcel.intersection(target); + double area = inter.getArea(); + + if (area > bestArea) { + bestArea = area; + bestPnu = row.pnu; + } + } + + return bestPnu; + } + + // ========================= + // ✅ GeoJSON geometry -> JTS + // ========================= + + private Geometry toJtsGeometry(JsonNode geomNode) { + String type = geomNode.path("type").asText(""); + JsonNode coords = geomNode.path("coordinates"); + + try { + if ("Polygon".equalsIgnoreCase(type)) { + return toPolygon(coords); + } + if ("MultiPolygon".equalsIgnoreCase(type)) { + return toMultiPolygon(coords); + } + return null; + } catch (Exception e) { + return null; + } + } + + private Polygon toPolygon(JsonNode coords) { + LinearRing shell = gf.createLinearRing(toCoordinateArray(coords.get(0))); + LinearRing[] holes = null; + + if (coords.size() > 1) { + holes = new LinearRing[coords.size() - 1]; + for (int i = 1; i < coords.size(); i++) { + holes[i - 1] = gf.createLinearRing(toCoordinateArray(coords.get(i))); + } + } + return gf.createPolygon(shell, holes); + } + + private MultiPolygon toMultiPolygon(JsonNode coords) { + Polygon[] polys = new Polygon[coords.size()]; + for (int i = 0; i < coords.size(); i++) { + polys[i] = toPolygon(coords.get(i)); + } + return gf.createMultiPolygon(polys); + } + + private Coordinate[] toCoordinateArray(JsonNode ring) { + int n = ring.size(); + Coordinate[] c = new Coordinate[n]; + + for (int i = 0; i < n; i++) { + double x = ring.get(i).get(0).asDouble(); + double y = ring.get(i).get(1).asDouble(); + c[i] = new Coordinate(x, y); + } + + // 링 닫힘 보장 + if (n >= 2 && !c[0].equals2D(c[n - 1])) { + Coordinate[] closed = Arrays.copyOf(c, n + 1); + closed[n] = new Coordinate(c[0]); + return closed; + } + return c; + } + + // ========================= + // ✅ 시도코드 판정 (임시) + // ========================= + private String resolveSidoCodeByPoint(Coordinate c) { + // ⚠️ 여기만 프로젝트 환경에 맞게 바꾸면 됩니다. + // 지금은 "임시로 하나 고정" 같은 방식 말고, + // 실제론 detectMastSearch나 map_id 기반으로 시도코드를 구하는 게 제일 안정적입니다. + + // 1) 만약 detectMastSearch에 sidoCode가 있다면 그걸 쓰는 걸 추천 + // 2) 또는 map_id에서 앞 2자리를 쓰는 방식 + + // 임시: 서울만 처리 (필요한 코드 추가하세요) + // return "11"; + + // TODO: 현재는 null 반환 -> 실제 로직으로 교체 필요 + return "11"; + } + + // ========================= + // ✅ 캐시 수동 초기화 (필요 시) + // ========================= + public void clearShpCache() { + shpIndexCache.clear(); } } From c65a3be807b28061f2ca345fbdcc7f94bb13c0f9 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Wed, 31 Dec 2025 12:20:10 +0900 Subject: [PATCH 12/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=ED=95=A0=EB=8B=B9=20update=20=EB=A1=9C=EC=A7=81=20->=20?= =?UTF-8?q?=EC=B6=94=ED=9B=84=20=EB=9D=BC=EB=B2=A8=EB=A7=81=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=ED=85=8C=EC=9D=B4=EB=B8=94=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=A0=20=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 62 +++++++++---------- .../label/service/LabelAllocateService.java | 46 +++++++++++++- .../core/LabelAllocateCoreService.java | 21 +++++++ .../label/LabelAllocateRepository.java | 8 +++ .../label/LabelAllocateRepositoryCustom.java | 10 +++ .../label/LabelAllocateRepositoryImpl.java | 51 +++++++++++++++ 6 files changed, 163 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepository.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java 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 5a739715..5014147e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -2,11 +2,9 @@ package com.kamco.cd.kamcoback.label; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,50 +15,46 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j @Tag(name = "라벨링 작업 관리", description = "라벨링 작업 관리") -@RequestMapping({"/api/label/mng"}) +@RequestMapping({"/api/label"}) @RequiredArgsConstructor @RestController public class LabelAllocateApiController { + private final LabelAllocateService labelAllocateService; + // 라벨링 수량 할당하는 로직 테스트 @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { // 도엽별 카운트 쿼리 - List sheets = - List.of( - new Sheet("1", 261), - new Sheet("2", 500), - new Sheet("3", 350), - new Sheet("4", 250), - new Sheet("5", 380), - new Sheet("6", 459)); + // List sheets = + // List.of( + // new Sheet("1", 261), + // new Sheet("2", 500), + // new Sheet("3", 350), + // new Sheet("4", 250), + // new Sheet("5", 380), + // new Sheet("6", 459)); // 사용자별 할당 입력한 값 + // List targets = + // List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700)); + // LabelAllocateService.allocateSheetCount(new ArrayList<>(sheets), new + // ArrayList<>(targets)); + + // targets.forEach( + // t -> { + // log.info("[" + t.getUserId() + "]"); + // t.getAssigned() + // .forEach( + // u -> { + // log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); + // }); + // }); + List targets = - List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700)); - - LabelAllocateService.allocate(new ArrayList<>(sheets), new ArrayList<>(targets)); - - targets.forEach( - t -> { - log.info("[" + t.getUserId() + "]"); - t.getAssigned() - .forEach( - u -> { - log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); - }); - }); - /** - * [A] 입력한 수 : 1000 - 도엽: 2 (500) - 도엽: 6 (459) - 도엽: 5 (41) - * - *

[B] 입력한 수 : 500 - 도엽: 5 (339) - 도엽: 3 (161) - * - *

[C] 입력한 수 : 700 - 도엽: 3 (189) - 도엽: 1 (261) - 도엽: 4 (250) - */ - // A 에게 도엽 2 asc 해서 500건 할당 -> 도엽 6 asc 해서 459 할당 -> 도엽 5 asc 해서 41건 할당 -> insert - // B 에게 도엽 5 위에 41건 할당한 것 빼고 asc 해서 339건 할당 -> 도엽 3 asc 해서 161건 할당 -> insert - // .... for문에서 할당한 것 빼고 asc 해서 건수만큼 할당 insert 하고 다음 으로 넘어가기 + List.of(new TargetUser("1", 1000), new TargetUser("2", 400), new TargetUser("3", 440)); + labelAllocateService.allocateAsc(targets); return ApiResponseDto.ok(null); } 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 547284b6..59cdbd9a 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,8 @@ package com.kamco.cd.kamcoback.label.service; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; +import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; +import jakarta.transaction.Transactional; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -12,7 +14,19 @@ import org.springframework.stereotype.Service; @Service public class LabelAllocateService { - public static void allocate(List sheets, List targetUsers) { + private final LabelAllocateCoreService labelAllocateCoreService; + + public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { + this.labelAllocateCoreService = labelAllocateCoreService; + } + + /** + * 도엽 count 수와 할당된 count 수를 비교해서 많은 수부터 먼저 배정하고 나머지를 분배 배정하는 로직 + * + * @param sheets + * @param targetUsers + */ + public static void allocateSheetCount(List sheets, List targetUsers) { // 1️⃣ 실제 도엽 기준 할당 allocateSheets(sheets, targetUsers); @@ -75,4 +89,34 @@ public class LabelAllocateService { t.setShortage(share); } } + + /** + * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 + * + * @param targetUsers + */ + @Transactional + public void allocateAsc(List targetUsers) { + Long lastId = null; + + for (TargetUser target : targetUsers) { + int remaining = target.getDemand(); + + while (remaining > 0) { + + int batchSize = Math.min(remaining, 500); + List ids = labelAllocateCoreService.fetchNextIds(lastId, batchSize); + + if (ids.isEmpty()) { + return; // 더이상 할당할 데이터가 없으면 return + } + + labelAllocateCoreService.assignOwner( + ids, Long.valueOf(target.getUserId())); // TODO : userId를 숫자값을 가져올지 사번을 가져올지 고민 + + remaining -= ids.size(); + lastId = ids.get(ids.size() - 1); + } + } + } } 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 new file mode 100644 index 00000000..d4f3da10 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelAllocateCoreService.java @@ -0,0 +1,21 @@ +package com.kamco.cd.kamcoback.postgres.core; + +import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LabelAllocateCoreService { + + private final LabelAllocateRepository labelAllocateRepository; + + public List fetchNextIds(Long lastId, int batchSize) { + return labelAllocateRepository.fetchNextIds(lastId, batchSize); + } + + public Long assignOwner(List ids, Long userId) { + return labelAllocateRepository.assignOwner(ids, userId); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepository.java new file mode 100644 index 00000000..976a083e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepository.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 LabelAllocateRepository + extends JpaRepository, + LabelAllocateRepositoryCustom {} 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 new file mode 100644 index 00000000..fa4ae5e5 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.kamco.cd.kamcoback.postgres.repository.label; + +import java.util.List; + +public interface LabelAllocateRepositoryCustom { + + List fetchNextIds(Long lastId, int batchSize); + + Long assignOwner(List ids, Long 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 new file mode 100644 index 00000000..dfdec9ae --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/label/LabelAllocateRepositoryImpl.java @@ -0,0 +1,51 @@ +package com.kamco.cd.kamcoback.postgres.repository.label; + +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; + +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; +import org.springframework.stereotype.Repository; + +@Slf4j +@Repository +public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport + implements LabelAllocateRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + public LabelAllocateRepositoryImpl(JPAQueryFactory queryFactory) { + super(MapSheetAnalDataGeomEntity.class); + this.queryFactory = queryFactory; + } + + @Override + public List fetchNextIds(Long lastId, int batchSize) { + 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.labelerUid.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.asc()) + .limit(batchSize) + .fetch(); + } + + @Override + public Long assignOwner(List ids, Long userId) { + return queryFactory + .update(mapSheetAnalDataInferenceGeomEntity) + .set(mapSheetAnalDataInferenceGeomEntity.labelerUid, userId) + .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) + .execute(); + } +} From d18d4da086b161973a18779d1981b19be3f7f478 Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 31 Dec 2025 12:29:20 +0900 Subject: [PATCH 13/70] =?UTF-8?q?=EC=9D=B4=EB=85=B8=ED=8E=A8=20=EB=AA=A9?= =?UTF-8?q?=EC=97=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Innopam/InnopamApiController.java | 2 +- .../postgres/entity/DetectMastPnuEntity.java | 48 +++ .../repository/DetectMastPnuRepository.java | 7 + .../DetectMastPnuRepositoryCustom.java | 3 + .../Innopam/service/DetectMastService.java | 343 +++--------------- .../utils/GeoJsonGeometryConverter.java | 48 +++ .../Innopam/utils/GeoJsonLoader.java | 44 +++ .../kamcoback/Innopam/utils/MapIdUtils.java | 17 + .../Innopam/utils/ShpIndexManager.java | 76 ++++ .../Innopam/utils/ShpPnuMatcher.java | 42 +++ 10 files changed, 333 insertions(+), 297 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastPnuEntity.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepository.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonGeometryConverter.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonLoader.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/utils/MapIdUtils.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpIndexManager.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpPnuMatcher.java diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java index f3c835da..56f027e4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java @@ -147,6 +147,6 @@ public class InnopamApiController { detectMastSearch.setCprsBfYr(cprsBfYr); detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); detectMastSearch.setFeatureId(featureId); - return detectMastService.findPnuDataDetail(detectMastSearch); + return new FeaturePnuDto(); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastPnuEntity.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastPnuEntity.java new file mode 100644 index 00000000..aef47a97 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/entity/DetectMastPnuEntity.java @@ -0,0 +1,48 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +@Getter +@Setter +@Entity +@Table(name = "detect_mast_pnu") +public class DetectMastPnuEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "detect_mast_pnu_id_gen") + @SequenceGenerator( + name = "detect_mast_pnu_id_gen", + sequenceName = "seq_detect_mast_pnu_id", + allocationSize = 1) + @Column(name = "dtct_mst_pnu_id", nullable = false) + private Long id; + + @NotNull + @ColumnDefault("gen_random_uuid()") + @Column(name = "detect_mast_pnu_uuid", nullable = false) + private UUID detectMastPnuUuid; + + @NotNull + @Column(name = "dtct_mst_id", nullable = false) + private Long dtctMstId; + + @Size(max = 4) + @NotNull + @Column(name = "pnu", nullable = false, length = 4) + private String pnu; + + @Column(name = "polygon", length = Integer.MAX_VALUE) + private String polygon; +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepository.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepository.java new file mode 100644 index 00000000..b44f3677 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepository.java @@ -0,0 +1,7 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.repository; + +import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastPnuEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface DetectMastPnuRepository + extends JpaRepository, DetectMastPnuRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepositoryCustom.java new file mode 100644 index 00000000..3029c658 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastPnuRepositoryCustom.java @@ -0,0 +1,3 @@ +package com.kamco.cd.kamcoback.Innopam.postgres.repository; + +public interface DetectMastPnuRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java index ae6f8fa8..21ea3d64 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -1,39 +1,23 @@ package com.kamco.cd.kamcoback.Innopam.service; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.postgres.core.DetectMastCoreService; -import java.io.File; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; -import org.geotools.api.data.DataStore; -import org.geotools.api.data.DataStoreFinder; -import org.geotools.api.data.SimpleFeatureSource; -import org.geotools.api.feature.simple.SimpleFeature; -import org.geotools.data.simple.SimpleFeatureCollection; -import org.geotools.data.simple.SimpleFeatureIterator; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.LinearRing; -import org.locationtech.jts.geom.MultiPolygon; -import org.locationtech.jts.geom.Polygon; -import org.locationtech.jts.index.strtree.STRtree; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,23 +26,22 @@ import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class DetectMastService { + @Value("${spring.profiles.active:local}") + private String profile; + private final DetectMastCoreService detectMastCoreService; - - // ✅ GeoJSON 파싱용 - private final ObjectMapper om = new ObjectMapper(); - private final GeometryFactory gf = new GeometryFactory(); - - // ✅ (임시) SHP 루트/버전/필드명 - private static final String SHP_ROOT_DIR = "/app/tmp/shp"; // TODO 환경 맞게 변경 - private static final String SHP_YYYYMM = "202512"; // TODO yml 또는 DB에서 받기 - private static final String PNU_FIELD = "PNU"; // TODO 실제 컬럼명으로 변경 - - // ✅ 시도코드별 인덱스 캐시 - private final Map shpIndexCache = new ConcurrentHashMap<>(); + private final JsonFactory jsonFactory = new JsonFactory(); @Transactional public void saveDetectMast(DetectMastReq detectMast) { detectMastCoreService.saveDetectMast(detectMast); + // + // String dirPath = + // "local".equals(profile) + // ? "/Users/bokmin/detect/result/2023_2024/4" + // : detectMast.getPathNm(); + // + // List list = this.extractFeaturePnusRandom(dirPath); } public List selectDetectMast(DetectMastSearch detectMast) { @@ -69,20 +52,19 @@ public class DetectMastService { return detectMastCoreService.selectDetectMast(id); } + /** GeoJSON → polygon_id + 랜덤 PNU */ public List findPnuData(DetectMastSearch detectMast) { - String dirPath = detectMastCoreService.findPnuData(detectMast); - return extractFeaturePnusByIntersectionMaxCached(dirPath); + + String dirPath = + "local".equals(profile) + ? "/Users/bokmin/detect/result/2023_2024/4" + : detectMastCoreService.findPnuData(detectMast); + + return extractFeaturePnusRandom(dirPath); } - public FeaturePnuDto findPnuDataDetail(DetectMastSearch detectMast) { - List list = findPnuData(detectMast); - return list.isEmpty() ? null : list.get(0); - } - - // ========================= - // ✅ 캐시 적용된 메인 로직 - // ========================= - public List extractFeaturePnusByIntersectionMaxCached(String dirPath) { + /** 하위 폴더까지 .geojson 파일들에서 polygon_id만 뽑음 병렬처리(parallel) 제거: IO + parallel은 거의 항상 느려짐 */ + private List extractFeaturePnusRandom(String dirPath) { Path basePath = Paths.get(dirPath); if (!Files.isDirectory(basePath)) { @@ -90,56 +72,32 @@ public class DetectMastService { return List.of(); } - List out = new ArrayList<>(); + List out = new ArrayList<>(4096); try (Stream stream = Files.walk(basePath)) { stream .filter(Files::isRegularFile) .filter(p -> p.toString().toLowerCase().endsWith(".geojson")) - // ✅ geojson 파싱/공간연산은 CPU+I/O 섞여서 병렬이 도움이 될 때가 많음 - .parallel() .forEach( p -> { - try { - JsonNode root = om.readTree(p.toFile()); - JsonNode features = root.path("features"); - if (!features.isArray()) { - return; - } + try (InputStream in = Files.newInputStream(p); + JsonParser parser = jsonFactory.createParser(in)) { - for (JsonNode feature : features) { - String polygonId = feature.path("properties").path("polygon_id").asText(null); - if (polygonId == null || polygonId.isBlank()) { - continue; - } + while (parser.nextToken() != null) { + if (parser.currentToken() == JsonToken.FIELD_NAME + && "polygon_id".equals(parser.getCurrentName())) { - Geometry target = toJtsGeometry(feature.path("geometry")); - if (target == null || target.isEmpty()) { - continue; - } - - // ✅ 1) target의 중심점으로 시도코드 판정 → 해당 시도 SHP만 사용 - // (시도코드 판정 방법은 프로젝트에 맞게 바꿀 수 있음) - String sidoCode = resolveSidoCodeByPoint(target.getCentroid().getCoordinate()); - if (sidoCode == null) { - continue; - } - - ShpIndex idx = getOrLoadShpIndex(sidoCode); - if (idx == null) { - continue; - } - - String pnu = pickPnuByIntersectionMax(idx.index, target); - if (pnu != null) { - synchronized (out) { - out.add(new FeaturePnuDto(polygonId, pnu)); + JsonToken next = parser.nextToken(); // 값으로 이동 + if (next == JsonToken.VALUE_STRING) { + String polygonId = parser.getValueAsString(); + out.add(new FeaturePnuDto(polygonId, randomPnu())); } } } } catch (Exception e) { - System.err.println("GeoJSON 처리 실패: " + p.getFileName() + " / " + e.getMessage()); + // 파일 단위 실패는 최소 로그 + System.err.println("GeoJSON 파싱 실패: " + p.getFileName() + " / " + e.getMessage()); } }); @@ -151,222 +109,15 @@ public class DetectMastService { return out; } - // ========================= - // ✅ SHP 인덱스 캐시 로딩 - // ========================= + /** 랜덤 PNU 생성 (임시) - 법정동코드(5) + 산구분(1) + 본번(4) + 부번(4) = 14자리 */ + private String randomPnu() { + ThreadLocalRandom r = ThreadLocalRandom.current(); - private static class ShpIndex { + String dongCode = String.format("%05d", r.nextInt(10000, 99999)); + String san = r.nextBoolean() ? "1" : "2"; + String bon = String.format("%04d", r.nextInt(1, 10000)); + String bu = String.format("%04d", r.nextInt(0, 10000)); - final String sidoCode; - final STRtree index; - - ShpIndex(String sidoCode, STRtree index) { - this.sidoCode = sidoCode; - this.index = index; - } - } - - private ShpIndex getOrLoadShpIndex(String sidoCode) { - // computeIfAbsent는 동시성 상황에서 안전하게 "1번만" 만들게 해줌 - return shpIndexCache.computeIfAbsent(sidoCode, this::loadShpIndex); - } - - private ShpIndex loadShpIndex(String sidoCode) { - String shpPath = resolveShpPathBySido(sidoCode); - - File shpFile = new File(shpPath); - if (!shpFile.exists()) { - System.err.println("SHP 파일 없음: " + shpPath); - return null; - } - - DataStore store = null; - - try { - Map params = new HashMap<>(); - params.put("url", shpFile.toURI().toURL()); - - store = DataStoreFinder.getDataStore(params); - if (store == null) { - System.err.println("SHP DataStore 생성 실패: " + shpPath); - return null; - } - - String typeName = store.getTypeNames()[0]; - SimpleFeatureSource source = store.getFeatureSource(typeName); - SimpleFeatureCollection collection = source.getFeatures(); - - STRtree index = new STRtree(10); - - try (SimpleFeatureIterator it = collection.features()) { - while (it.hasNext()) { - SimpleFeature f = it.next(); - - Object g = f.getDefaultGeometry(); - if (!(g instanceof Geometry)) { - continue; - } - - String pnu = Objects.toString(f.getAttribute(PNU_FIELD), null); - if (pnu == null || pnu.isBlank()) { - continue; - } - - Geometry geom = (Geometry) g; - index.insert(geom.getEnvelopeInternal(), new ShpRow(geom, pnu)); - } - } - - index.build(); - System.out.println("✅ SHP 인덱스 로딩 완료: sido=" + sidoCode + ", shp=" + shpFile.getName()); - return new ShpIndex(sidoCode, index); - - } catch (Exception e) { - System.err.println("SHP 인덱스 로딩 실패: sido=" + sidoCode + " / " + e.getMessage()); - return null; - } finally { - if (store != null) { - store.dispose(); - } - } - } - - private String resolveShpPathBySido(String sidoCode) { - // 파일명이 스크린샷처럼 동일 패턴이라고 가정 - // 예: /shp/LSMD_CONT_LDREG_11_202512.shp - return SHP_ROOT_DIR + "/LSMD_CONT_LDREG_" + sidoCode + "_" + SHP_YYYYMM + ".shp"; - } - - // ========================= - // ✅ intersection 면적 최대 - // ========================= - - private static class ShpRow { - - final Geometry geom; - final String pnu; - - ShpRow(Geometry geom, String pnu) { - this.geom = geom; - this.pnu = pnu; - } - } - - private String pickPnuByIntersectionMax(STRtree index, Geometry target) { - - Envelope env = target.getEnvelopeInternal(); - - @SuppressWarnings("unchecked") - List candidates = index.query(env); - if (candidates == null || candidates.isEmpty()) { - return null; - } - - double bestArea = 0.0; - String bestPnu = null; - - for (ShpRow row : candidates) { - Geometry parcel = row.geom; - - if (!parcel.intersects(target)) { - continue; - } - - Geometry inter = parcel.intersection(target); - double area = inter.getArea(); - - if (area > bestArea) { - bestArea = area; - bestPnu = row.pnu; - } - } - - return bestPnu; - } - - // ========================= - // ✅ GeoJSON geometry -> JTS - // ========================= - - private Geometry toJtsGeometry(JsonNode geomNode) { - String type = geomNode.path("type").asText(""); - JsonNode coords = geomNode.path("coordinates"); - - try { - if ("Polygon".equalsIgnoreCase(type)) { - return toPolygon(coords); - } - if ("MultiPolygon".equalsIgnoreCase(type)) { - return toMultiPolygon(coords); - } - return null; - } catch (Exception e) { - return null; - } - } - - private Polygon toPolygon(JsonNode coords) { - LinearRing shell = gf.createLinearRing(toCoordinateArray(coords.get(0))); - LinearRing[] holes = null; - - if (coords.size() > 1) { - holes = new LinearRing[coords.size() - 1]; - for (int i = 1; i < coords.size(); i++) { - holes[i - 1] = gf.createLinearRing(toCoordinateArray(coords.get(i))); - } - } - return gf.createPolygon(shell, holes); - } - - private MultiPolygon toMultiPolygon(JsonNode coords) { - Polygon[] polys = new Polygon[coords.size()]; - for (int i = 0; i < coords.size(); i++) { - polys[i] = toPolygon(coords.get(i)); - } - return gf.createMultiPolygon(polys); - } - - private Coordinate[] toCoordinateArray(JsonNode ring) { - int n = ring.size(); - Coordinate[] c = new Coordinate[n]; - - for (int i = 0; i < n; i++) { - double x = ring.get(i).get(0).asDouble(); - double y = ring.get(i).get(1).asDouble(); - c[i] = new Coordinate(x, y); - } - - // 링 닫힘 보장 - if (n >= 2 && !c[0].equals2D(c[n - 1])) { - Coordinate[] closed = Arrays.copyOf(c, n + 1); - closed[n] = new Coordinate(c[0]); - return closed; - } - return c; - } - - // ========================= - // ✅ 시도코드 판정 (임시) - // ========================= - private String resolveSidoCodeByPoint(Coordinate c) { - // ⚠️ 여기만 프로젝트 환경에 맞게 바꾸면 됩니다. - // 지금은 "임시로 하나 고정" 같은 방식 말고, - // 실제론 detectMastSearch나 map_id 기반으로 시도코드를 구하는 게 제일 안정적입니다. - - // 1) 만약 detectMastSearch에 sidoCode가 있다면 그걸 쓰는 걸 추천 - // 2) 또는 map_id에서 앞 2자리를 쓰는 방식 - - // 임시: 서울만 처리 (필요한 코드 추가하세요) - // return "11"; - - // TODO: 현재는 null 반환 -> 실제 로직으로 교체 필요 - return "11"; - } - - // ========================= - // ✅ 캐시 수동 초기화 (필요 시) - // ========================= - public void clearShpCache() { - shpIndexCache.clear(); + return dongCode + san + bon + bu; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonGeometryConverter.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonGeometryConverter.java new file mode 100644 index 00000000..c1d8842e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonGeometryConverter.java @@ -0,0 +1,48 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Polygon; + +public class GeoJsonGeometryConverter { + + private static final GeometryFactory GF = new GeometryFactory(); + + public static Geometry toGeometry(JsonNode geomNode) { + String type = geomNode.path("type").asText(); + + if ("Polygon".equals(type)) { + return toPolygon(geomNode.path("coordinates")); + } + if ("MultiPolygon".equals(type)) { + return toMultiPolygon(geomNode.path("coordinates")); + } + return null; + } + + private static Polygon toPolygon(JsonNode coords) { + LinearRing shell = GF.createLinearRing(toCoords(coords.get(0))); + return GF.createPolygon(shell); + } + + private static MultiPolygon toMultiPolygon(JsonNode coords) { + Polygon[] polys = new Polygon[coords.size()]; + for (int i = 0; i < coords.size(); i++) { + polys[i] = toPolygon(coords.get(i)); + } + return GF.createMultiPolygon(polys); + } + + private static Coordinate[] toCoords(JsonNode ring) { + Coordinate[] c = new Coordinate[ring.size() + 1]; + for (int i = 0; i < ring.size(); i++) { + c[i] = new Coordinate(ring.get(i).get(0).asDouble(), ring.get(i).get(1).asDouble()); + } + c[c.length - 1] = c[0]; + return c; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonLoader.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonLoader.java new file mode 100644 index 00000000..879cdea4 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/GeoJsonLoader.java @@ -0,0 +1,44 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class GeoJsonLoader { + + private final ObjectMapper om = new ObjectMapper(); + + public GeoJsonFile load(File geoJsonFile) throws Exception { + + JsonNode root = om.readTree(geoJsonFile); + + long mapId = root.path("properties").path("map_id").asLong(-1); + if (mapId <= 0) { + throw new IllegalStateException( + "GeoJSON top-level properties.map_id 없음: " + geoJsonFile.getName()); + } + + List features = new ArrayList<>(); + root.path("features").forEach(features::add); + + return new GeoJsonFile(mapId, features); + } + + /** ✅ feature에서 polygon_id 추출 */ + public static String polygonId(JsonNode feature) { + return feature.path("properties").path("polygon_id").asText(null); + } + + public static class GeoJsonFile { + + public final long mapId; + public final List features; + + public GeoJsonFile(long mapId, List features) { + this.mapId = mapId; + this.features = features; + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/MapIdUtils.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/MapIdUtils.java new file mode 100644 index 00000000..2ab92670 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/MapIdUtils.java @@ -0,0 +1,17 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +public class MapIdUtils { + + private MapIdUtils() { + // util class + } + + /** map_id → 시도코드 예: 34602060 → "34" */ + public static String sidoCodeFromMapId(long mapId) { + String s = String.valueOf(mapId); + if (s.length() < 2) { + throw new IllegalArgumentException("잘못된 map_id: " + mapId); + } + return s.substring(0, 2); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpIndexManager.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpIndexManager.java new file mode 100644 index 00000000..60100995 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpIndexManager.java @@ -0,0 +1,76 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import java.io.File; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import org.geotools.api.data.DataStore; +import org.geotools.api.data.DataStoreFinder; +import org.geotools.api.data.SimpleFeatureSource; +import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.data.simple.SimpleFeatureCollection; +import org.geotools.data.simple.SimpleFeatureIterator; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.index.strtree.STRtree; + +public class ShpIndexManager { + + private static final String SHP_ROOT = "/shp"; + private static final String SHP_YYYYMM = "202512"; + private static final String PNU_FIELD = "PNU"; + + private final Map cache = new ConcurrentHashMap<>(); + + public STRtree getIndex(String sidoCode) { + return cache.computeIfAbsent(sidoCode, this::loadIndex); + } + + private STRtree loadIndex(String sidoCode) { + try { + String path = SHP_ROOT + "/LSMD_CONT_LDREG_" + sidoCode + "_" + SHP_YYYYMM + ".shp"; + + File shp = new File(path); + if (!shp.exists()) { + return null; + } + + STRtree index = new STRtree(10); + + DataStore store = DataStoreFinder.getDataStore(Map.of("url", shp.toURI().toURL())); + + String typeName = store.getTypeNames()[0]; + SimpleFeatureSource source = store.getFeatureSource(typeName); + SimpleFeatureCollection col = source.getFeatures(); + + try (SimpleFeatureIterator it = col.features()) { + while (it.hasNext()) { + SimpleFeature f = it.next(); + Geometry geom = (Geometry) f.getDefaultGeometry(); + String pnu = Objects.toString(f.getAttribute(PNU_FIELD), null); + if (geom != null && pnu != null) { + index.insert(geom.getEnvelopeInternal(), new ShpRow(geom, pnu)); + } + } + } + + index.build(); + store.dispose(); + return index; + + } catch (Exception e) { + return null; + } + } + + /** SHP 한 row */ + public static class ShpRow { + + public final Geometry geom; + public final String pnu; + + public ShpRow(Geometry geom, String pnu) { + this.geom = geom; + this.pnu = pnu; + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpPnuMatcher.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpPnuMatcher.java new file mode 100644 index 00000000..f61d3852 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/utils/ShpPnuMatcher.java @@ -0,0 +1,42 @@ +package com.kamco.cd.kamcoback.Innopam.utils; + +import java.util.List; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.prep.PreparedGeometry; +import org.locationtech.jts.geom.prep.PreparedGeometryFactory; +import org.locationtech.jts.index.strtree.STRtree; + +public class ShpPnuMatcher { + + public static String pickByIntersectionMax(STRtree index, Geometry target) { + + Envelope env = target.getEnvelopeInternal(); + + @SuppressWarnings("unchecked") + List rows = index.query(env); + + double best = 0; + String bestPnu = null; + + for (ShpIndexManager.ShpRow row : rows) { + + PreparedGeometry prep = PreparedGeometryFactory.prepare(row.geom); + + if (prep.contains(target) || prep.covers(target)) { + return row.pnu; + } + + if (!prep.intersects(target)) { + continue; + } + + double area = row.geom.intersection(target).getArea(); + if (area > best) { + best = area; + bestPnu = row.pnu; + } + } + return bestPnu; + } +} From d72e9de61e07127b5462386ec4c492ee315da361 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 17:58:25 +0900 Subject: [PATCH 14/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 32 +---- .../kamcoback/label/dto/LabelAllocateDto.java | 106 ++++++++++++--- .../label/service/LabelAllocateService.java | 109 +++++----------- .../core/LabelAllocateCoreService.java | 19 ++- .../entity/LabelingAssignmentEntity.java | 57 +++++++++ .../label/LabelAllocateRepositoryCustom.java | 10 +- .../label/LabelAllocateRepositoryImpl.java | 121 +++++++++++++++--- 7 files changed, 313 insertions(+), 141 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java 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 5014147e..243965a2 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.label; 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.service.LabelAllocateService; import io.swagger.v3.oas.annotations.tags.Tag; @@ -26,35 +27,10 @@ public class LabelAllocateApiController { @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { - // 도엽별 카운트 쿼리 - // List sheets = - // List.of( - // new Sheet("1", 261), - // new Sheet("2", 500), - // new Sheet("3", 350), - // new Sheet("4", 250), - // new Sheet("5", 380), - // new Sheet("6", 459)); - - // 사용자별 할당 입력한 값 - // List targets = - // List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700)); - // LabelAllocateService.allocateSheetCount(new ArrayList<>(sheets), new - // ArrayList<>(targets)); - - // targets.forEach( - // t -> { - // log.info("[" + t.getUserId() + "]"); - // t.getAssigned() - // .forEach( - // u -> { - // log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")"); - // }); - // }); - List targets = - List.of(new TargetUser("1", 1000), new TargetUser("2", 400), new TargetUser("3", 440)); - labelAllocateService.allocateAsc(targets); + List.of(new TargetUser("1234567", 1000), new TargetUser("2345678", 400), new TargetUser("3456789", 440)); + List inspectors = List.of(new TargetInspector("9876543", 1000), new TargetInspector("8765432", 340), new TargetInspector("98765432", 500)); + labelAllocateService.allocateAsc(targets, inspectors); 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 a6802547..b3509a38 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 @@ -1,22 +1,80 @@ package com.kamco.cd.kamcoback.label.dto; -import java.util.ArrayList; -import java.util.List; +import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; +import com.kamco.cd.kamcoback.common.utils.enums.EnumType; +import java.time.ZonedDateTime; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; public class LabelAllocateDto { + @CodeExpose @Getter @AllArgsConstructor - public static class Sheet { + public enum LabelMngState implements EnumType { + PENDING("작업대기"), + ASSIGNED("작업할당"), + STOP("중단"), + LABEL_ING("라벨진행중"), + LABEL_COMPLETE("라벨완료"), + INSPECT_REQ("검수요청"), + INSPECT_ING("검수진행중"), + INSPECT_COMPLETE("검수완료"); - private final String sheetId; - private int count; + private String desc; - public void decrease(int amount) { - this.count -= amount; + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } + } + + @CodeExpose + @Getter + @AllArgsConstructor + public enum LabelState implements EnumType { + ASSIGNED("배정"), + SKIP("스킵"), + COMPLETE("완료"); + + private String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } + } + + @CodeExpose + @Getter + @AllArgsConstructor + public enum InspectState implements EnumType { + UNCONFIRM("미확인"), + EXCEPT("제외"), + COMPLETE("완료"); + + private String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; } } @@ -25,22 +83,36 @@ public class LabelAllocateDto { private final String userId; private final int demand; - private final List assigned = new ArrayList<>(); - private int allocated = 0; - @Setter private int shortage = 0; public TargetUser(String userId, int demand) { this.userId = userId; this.demand = demand; } + } - public int getRemainDemand() { - return demand - allocated; - } + @Getter + @AllArgsConstructor + public static class TargetInspector { - public void assign(String sheetId, int count) { - assigned.add(new Sheet(sheetId, count)); - allocated += count; - } + private final String inspectorUid; + private int userCount; + } + + @Getter + @Setter + @AllArgsConstructor + public static class Basic { + + private UUID assignmentUid; + private Long inferenceGeomUid; + private String workerUid; + private String inspectorUid; + private String workState; + private Character stagnationYn; + private String assignGroupId; + private Long learnGeomUid; + private Long analUid; + private ZonedDateTime createdDttm; + private ZonedDateTime updatedDttm; } } 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 59cdbd9a..28dfa8ad 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 @@ -1,12 +1,12 @@ package com.kamco.cd.kamcoback.label.service; -import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet; +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.postgres.core.LabelAllocateCoreService; import jakarta.transaction.Transactional; -import java.util.Comparator; -import java.util.Iterator; import java.util.List; +import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -20,91 +20,30 @@ public class LabelAllocateService { this.labelAllocateCoreService = labelAllocateCoreService; } - /** - * 도엽 count 수와 할당된 count 수를 비교해서 많은 수부터 먼저 배정하고 나머지를 분배 배정하는 로직 - * - * @param sheets - * @param targetUsers - */ - public static void allocateSheetCount(List sheets, List targetUsers) { - - // 1️⃣ 실제 도엽 기준 할당 - allocateSheets(sheets, targetUsers); - - // 2️⃣ 전체 부족분 비율 계산 - distributeShortage(targetUsers); // 항상 마지막에 한 번만 호출해야함 - } - - public static void allocateSheets(List sheets, List targets) { - // 도엽 큰 것부터 (선택 사항) - sheets.sort(Comparator.comparingInt(Sheet::getCount).reversed()); - - for (TargetUser target : targets) { - Iterator it = sheets.iterator(); - - while (it.hasNext() && target.getRemainDemand() > 0) { - Sheet sheet = it.next(); - - int assignable = Math.min(sheet.getCount(), target.getRemainDemand()); - - if (assignable > 0) { - target.assign(sheet.getSheetId(), assignable); - sheet.decrease(assignable); - } - - if (sheet.getCount() == 0) { - it.remove(); - } - } - } - } - - public static void distributeShortage(List targets) { - - int totalDemand = targets.stream().mapToInt(TargetUser::getDemand).sum(); - - int totalAllocated = targets.stream().mapToInt(t -> t.getAllocated()).sum(); - - int shortage = totalDemand - totalAllocated; - - if (shortage <= 0) { - return; - } - - int distributed = 0; - - for (int i = 0; i < targets.size(); i++) { - TargetUser t = targets.get(i); - - // 마지막 타겟이 나머지 가져가게 (반올림 오차 방지) - int share; - if (i == targets.size() - 1) { - share = shortage - distributed; - } else { - double ratio = (double) t.getDemand() / totalDemand; - share = (int) Math.round(shortage * ratio); - distributed += share; - } - - t.setShortage(share); - } - } - /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * * @param targetUsers */ @Transactional - public void allocateAsc(List targetUsers) { + public void allocateAsc(List targetUsers, List targetInspectors) { Long lastId = null; + //geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return + Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(3L); //TODO + Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); + if (!Objects.equals(chargeCnt, totalDemand)) { + log.info("chargeCnt != totalDemand"); + return; + } + + //라벨러에게 건수만큼 할당 for (TargetUser target : targetUsers) { int remaining = target.getDemand(); while (remaining > 0) { - int batchSize = Math.min(remaining, 500); + int batchSize = Math.min(remaining, 100); List ids = labelAllocateCoreService.fetchNextIds(lastId, batchSize); if (ids.isEmpty()) { @@ -112,11 +51,29 @@ public class LabelAllocateService { } labelAllocateCoreService.assignOwner( - ids, Long.valueOf(target.getUserId())); // TODO : userId를 숫자값을 가져올지 사번을 가져올지 고민 + ids, target.getUserId()); remaining -= ids.size(); lastId = ids.get(ids.size() - 1); } } + + //검수자에게 userCount명 만큼 할당 + List list = labelAllocateCoreService.findAssignedLabelerList(3L); + int reviewerIndex = 0; + int count = 0; + log.info("list : " + list.size()); + + for (LabelAllocateDto.Basic labeler : list) { + TargetInspector inspector = targetInspectors.get(reviewerIndex); + labelAllocateCoreService.assignInspector(labeler.getAssignmentUid(), inspector.getInspectorUid()); + count++; + + if (count == inspector.getUserCount()) { + reviewerIndex++; + count = 0; + } + } + } } 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 d4f3da10..3d7ce013 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 @@ -1,7 +1,10 @@ package com.kamco.cd.kamcoback.postgres.core; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; +import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository; import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -15,7 +18,19 @@ public class LabelAllocateCoreService { return labelAllocateRepository.fetchNextIds(lastId, batchSize); } - public Long assignOwner(List ids, Long userId) { - return labelAllocateRepository.assignOwner(ids, userId); + public void assignOwner(List ids, String userId) { + labelAllocateRepository.assignOwner(ids, userId); + } + + public List findAssignedLabelerList(Long analUid) { + return labelAllocateRepository.findAssignedLabelerList(analUid).stream().map(LabelingAssignmentEntity::toDto).toList(); + } + + public Long findLabelUnAssignedCnt(Long analUid) { + return labelAllocateRepository.findLabelUnAssignedCnt(analUid); + } + + public void assignInspector(UUID assignmentUid, String inspectorUid) { + labelAllocateRepository.assignInspector(assignmentUid, inspectorUid); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java new file mode 100644 index 00000000..513b513e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java @@ -0,0 +1,57 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; +import com.kamco.cd.kamcoback.postgres.CommonDateEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; + +@Entity +@Table(name = "tb_labeling_assignment") +public class LabelingAssignmentEntity extends CommonDateEntity { + + @Id + @Column(name = "assignment_uid") + private UUID assignmentUid; + + @Column(name = "inference_geom_uid") + private Long inferenceGeomUid; + + @Column(name = "worker_uid") + private String workerUid; + + @Column(name = "inspector_uid") + private String inspectorUid; + + @Column(name = "work_state") + private String workState; + + @Column(name = "stagnation_yn") + private Character stagnationYn; + + @Column(name = "assign_group_id") + private String assignGroupId; + + @Column(name = "learn_geom_uid") + private Long learnGeomUid; + + @Column(name = "anal_uid") + private Long analUid; + + public LabelAllocateDto.Basic toDto() { + return new LabelAllocateDto.Basic( + this.assignmentUid, + this.inferenceGeomUid, + this.workerUid, + this.inspectorUid, + this.workState, + this.stagnationYn, + this.assignGroupId, + this.learnGeomUid, + this.analUid, + super.getCreatedDate(), + super.getModifiedDate()); + } +} 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 fa4ae5e5..0fed2538 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,10 +1,18 @@ package com.kamco.cd.kamcoback.postgres.repository.label; +import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import java.util.List; +import java.util.UUID; public interface LabelAllocateRepositoryCustom { List fetchNextIds(Long lastId, int batchSize); - Long assignOwner(List ids, Long userId); + void assignOwner(List ids, String userId); + + List findAssignedLabelerList(Long analUid); + + Long findLabelUnAssignedCnt(Long analUid); + + 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 dfdec9ae..6b33ad4e 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,12 +1,22 @@ 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.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; +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.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 java.util.List; +import java.util.Objects; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; import org.springframework.stereotype.Repository; @@ -14,11 +24,14 @@ import org.springframework.stereotype.Repository; @Slf4j @Repository public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport - implements LabelAllocateRepositoryCustom { + implements LabelAllocateRepositoryCustom { private final JPAQueryFactory queryFactory; private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + @PersistenceContext + private EntityManager em; + public LabelAllocateRepositoryImpl(JPAQueryFactory queryFactory) { super(MapSheetAnalDataGeomEntity.class); this.queryFactory = queryFactory; @@ -26,26 +39,100 @@ public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport @Override public List fetchNextIds(Long lastId, int batchSize) { + 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.labelerUid.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.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(2022), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(2024), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.asc()) + .limit(batchSize) + .fetch(); } @Override - public Long assignOwner(List ids, Long userId) { - return queryFactory - .update(mapSheetAnalDataInferenceGeomEntity) - .set(mapSheetAnalDataInferenceGeomEntity.labelerUid, userId) - .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) + public void assignOwner(List ids, String userId) { + + //data_geom 테이블에 label state 를 ASSIGNED 로 update + queryFactory + .update(mapSheetAnalDataInferenceGeomEntity) + .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) + .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) + .execute(); + + //라벨러 할당 테이블에 insert + for (Long geoUid : ids) { + queryFactory + .insert(labelingAssignmentEntity) + .columns( + labelingAssignmentEntity.assignmentUid, + labelingAssignmentEntity.inferenceGeomUid, + labelingAssignmentEntity.workerUid, + labelingAssignmentEntity.workState, + labelingAssignmentEntity.assignGroupId, + labelingAssignmentEntity.analUid + ) + .values( + UUID.randomUUID(), + geoUid, + userId, + LabelState.ASSIGNED.getId(), + "", //TODO: 도엽번호 + 3 + ) .execute(); + } + + em.flush(); + em.clear(); + } + + @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(); + } + + @Override + public Long findLabelUnAssignedCnt(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.isNull() + ) + .fetchOne() + ; + } + + @Override + public void assignInspector(UUID assignmentUid, String inspectorUid) { + queryFactory + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, inspectorUid) + .where( + labelingAssignmentEntity.assignmentUid.eq(assignmentUid) + ) + .execute(); } } From fd5d0976fc38c4dc5f06bda7403bcfdc95ba16ca Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 18:17:46 +0900 Subject: [PATCH 15/70] =?UTF-8?q?=ED=95=A0=EB=8B=B9=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=9C=20=EB=9D=BC=EB=B2=A8=EB=9F=AC,=20=EA=B2=80=EC=88=98?= =?UTF-8?q?=EC=9E=90=20=EB=AA=A9=EB=A1=9D=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 37 ++++- .../kamcoback/label/dto/LabelAllocateDto.java | 10 ++ .../label/service/LabelAllocateService.java | 18 ++- .../core/LabelAllocateCoreService.java | 9 +- .../entity/LabelingAssignmentEntity.java | 22 +-- .../label/LabelAllocateRepositoryCustom.java | 3 + .../label/LabelAllocateRepositoryImpl.java | 141 ++++++++++-------- 7 files changed, 156 insertions(+), 84 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 243965a2..868cd29c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -1,17 +1,25 @@ 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.service.LabelAllocateService; +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 java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +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 @@ -23,13 +31,38 @@ public class LabelAllocateApiController { private final LabelAllocateService labelAllocateService; + @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) + }) + @GetMapping("/avail-user") + public ApiResponseDto> availUserList(@RequestParam @Schema() String role) { + return ApiResponseDto.ok(labelAllocateService.availUserList(role)); + } + // 라벨링 수량 할당하는 로직 테스트 @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { List targets = - List.of(new TargetUser("1234567", 1000), new TargetUser("2345678", 400), new TargetUser("3456789", 440)); - List inspectors = List.of(new TargetInspector("9876543", 1000), new TargetInspector("8765432", 340), new TargetInspector("98765432", 500)); + List.of( + new TargetUser("1234567", 1000), + new TargetUser("2345678", 400), + new TargetUser("3456789", 440)); + List inspectors = + List.of( + new TargetInspector("9876543", 1000), + new TargetInspector("8765432", 340), + new TargetInspector("98765432", 500)); labelAllocateService.allocateAsc(targets, inspectors); 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 b3509a38..d1705e92 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 @@ -115,4 +115,14 @@ public class LabelAllocateDto { private ZonedDateTime createdDttm; private ZonedDateTime updatedDttm; } + + @Getter + @Setter + @AllArgsConstructor + public static class UserList { + + private String userRole; + private String employeeNo; + private String name; + } } 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 28dfa8ad..6d273a95 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 @@ -3,6 +3,7 @@ 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.postgres.core.LabelAllocateCoreService; import jakarta.transaction.Transactional; import java.util.List; @@ -29,15 +30,15 @@ public class LabelAllocateService { public void allocateAsc(List targetUsers, List targetInspectors) { Long lastId = null; - //geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return - Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(3L); //TODO + // geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return + Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(3L); // TODO Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); if (!Objects.equals(chargeCnt, totalDemand)) { log.info("chargeCnt != totalDemand"); return; } - //라벨러에게 건수만큼 할당 + // 라벨러에게 건수만큼 할당 for (TargetUser target : targetUsers) { int remaining = target.getDemand(); @@ -50,15 +51,14 @@ public class LabelAllocateService { return; // 더이상 할당할 데이터가 없으면 return } - labelAllocateCoreService.assignOwner( - ids, target.getUserId()); + labelAllocateCoreService.assignOwner(ids, target.getUserId()); remaining -= ids.size(); lastId = ids.get(ids.size() - 1); } } - //검수자에게 userCount명 만큼 할당 + // 검수자에게 userCount명 만큼 할당 List list = labelAllocateCoreService.findAssignedLabelerList(3L); int reviewerIndex = 0; int count = 0; @@ -66,7 +66,8 @@ public class LabelAllocateService { for (LabelAllocateDto.Basic labeler : list) { TargetInspector inspector = targetInspectors.get(reviewerIndex); - labelAllocateCoreService.assignInspector(labeler.getAssignmentUid(), inspector.getInspectorUid()); + labelAllocateCoreService.assignInspector( + labeler.getAssignmentUid(), inspector.getInspectorUid()); count++; if (count == inspector.getUserCount()) { @@ -74,6 +75,9 @@ public class LabelAllocateService { count = 0; } } + } + public List availUserList(String role) { + return labelAllocateCoreService.availUserList(role); } } 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 3d7ce013..1d8fa075 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 @@ -1,6 +1,7 @@ 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.postgres.entity.LabelingAssignmentEntity; import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository; import java.util.List; @@ -23,7 +24,9 @@ public class LabelAllocateCoreService { } public List findAssignedLabelerList(Long analUid) { - return labelAllocateRepository.findAssignedLabelerList(analUid).stream().map(LabelingAssignmentEntity::toDto).toList(); + return labelAllocateRepository.findAssignedLabelerList(analUid).stream() + .map(LabelingAssignmentEntity::toDto) + .toList(); } public Long findLabelUnAssignedCnt(Long analUid) { @@ -33,4 +36,8 @@ public class LabelAllocateCoreService { public void assignInspector(UUID assignmentUid, String inspectorUid) { labelAllocateRepository.assignInspector(assignmentUid, inspectorUid); } + + public List availUserList(String role) { + return labelAllocateRepository.availUserList(role); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java index 513b513e..22afd4aa 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java @@ -42,16 +42,16 @@ public class LabelingAssignmentEntity extends CommonDateEntity { public LabelAllocateDto.Basic toDto() { return new LabelAllocateDto.Basic( - this.assignmentUid, - this.inferenceGeomUid, - this.workerUid, - this.inspectorUid, - this.workState, - this.stagnationYn, - this.assignGroupId, - this.learnGeomUid, - this.analUid, - super.getCreatedDate(), - super.getModifiedDate()); + this.assignmentUid, + this.inferenceGeomUid, + this.workerUid, + this.inspectorUid, + this.workState, + this.stagnationYn, + this.assignGroupId, + this.learnGeomUid, + this.analUid, + super.getCreatedDate(), + super.getModifiedDate()); } } 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 0fed2538..659f5c7b 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,5 +1,6 @@ package com.kamco.cd.kamcoback.postgres.repository.label; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import java.util.List; import java.util.UUID; @@ -15,4 +16,6 @@ public interface LabelAllocateRepositoryCustom { Long findLabelUnAssignedCnt(Long analUid); void assignInspector(UUID assignmentUid, String userId); + + List availUserList(String role); } 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 6b33ad4e..f9e419d2 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 @@ -3,11 +3,15 @@ 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.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapSheetAnalEntity; +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.LabelState; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; 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.types.Projections; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -24,13 +28,12 @@ import org.springframework.stereotype.Repository; @Slf4j @Repository public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport - implements LabelAllocateRepositoryCustom { + implements LabelAllocateRepositoryCustom { private final JPAQueryFactory queryFactory; private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); - @PersistenceContext - private EntityManager em; + @PersistenceContext private EntityManager em; public LabelAllocateRepositoryImpl(JPAQueryFactory queryFactory) { super(MapSheetAnalDataGeomEntity.class); @@ -41,50 +44,48 @@ public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport public List fetchNextIds(Long lastId, int batchSize) { 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.labelState.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.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(2022), + mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(2024), + mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.asc()) + .limit(batchSize) + .fetch(); } @Override public void assignOwner(List ids, String userId) { - //data_geom 테이블에 label state 를 ASSIGNED 로 update + // data_geom 테이블에 label state 를 ASSIGNED 로 update queryFactory - .update(mapSheetAnalDataInferenceGeomEntity) - .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) - .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) - .execute(); + .update(mapSheetAnalDataInferenceGeomEntity) + .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) + .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) + .execute(); - //라벨러 할당 테이블에 insert + // 라벨러 할당 테이블에 insert for (Long geoUid : ids) { queryFactory - .insert(labelingAssignmentEntity) - .columns( - labelingAssignmentEntity.assignmentUid, - labelingAssignmentEntity.inferenceGeomUid, - labelingAssignmentEntity.workerUid, - labelingAssignmentEntity.workState, - labelingAssignmentEntity.assignGroupId, - labelingAssignmentEntity.analUid - ) - .values( - UUID.randomUUID(), - geoUid, - userId, - LabelState.ASSIGNED.getId(), - "", //TODO: 도엽번호 - 3 - ) - .execute(); + .insert(labelingAssignmentEntity) + .columns( + labelingAssignmentEntity.assignmentUid, + labelingAssignmentEntity.inferenceGeomUid, + labelingAssignmentEntity.workerUid, + labelingAssignmentEntity.workState, + labelingAssignmentEntity.assignGroupId, + labelingAssignmentEntity.analUid) + .values( + UUID.randomUUID(), + geoUid, + userId, + LabelState.ASSIGNED.getId(), + "", // TODO: 도엽번호 + 3) + .execute(); } em.flush(); @@ -94,45 +95,59 @@ public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport @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) { - MapSheetAnalEntity entity = queryFactory.selectFrom(mapSheetAnalEntity).where(mapSheetAnalEntity.id.eq(analUid)).fetchOne(); + 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.isNull() - ) - .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.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("ACTIVE")) + .orderBy(memberEntity.name.asc()) + .fetch(); } } From 563d7b576851ada79571a1160afd995a3a7e8ca0 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 18:18:15 +0900 Subject: [PATCH 16/70] spotless --- .../label/LabelAllocateApiController.java | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 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 868cd29c..0a52baf2 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -33,19 +33,20 @@ public class LabelAllocateApiController { @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) - }) + 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) + }) @GetMapping("/avail-user") - public ApiResponseDto> availUserList(@RequestParam @Schema() String role) { + public ApiResponseDto> availUserList( + @RequestParam @Schema() String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } @@ -54,15 +55,15 @@ public class LabelAllocateApiController { public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { List targets = - List.of( - new TargetUser("1234567", 1000), - new TargetUser("2345678", 400), - new TargetUser("3456789", 440)); + List.of( + new TargetUser("1234567", 1000), + new TargetUser("2345678", 400), + new TargetUser("3456789", 440)); List inspectors = - List.of( - new TargetInspector("9876543", 1000), - new TargetInspector("8765432", 340), - new TargetInspector("98765432", 500)); + List.of( + new TargetInspector("9876543", 1000), + new TargetInspector("8765432", 340), + new TargetInspector("98765432", 500)); labelAllocateService.allocateAsc(targets, inspectors); return ApiResponseDto.ok(null); From 62c4b9e732c0fea8ccdc6ad017601b131baad224 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Fri, 2 Jan 2026 18:59:16 +0900 Subject: [PATCH 17/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=9F=AC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=A1=B0=ED=9A=8C=20=EC=9E=84=EC=8B=9C=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 217 ++++++++++++++- .../label/service/LabelAllocateService.java | 69 ++++- .../core/LabelAllocateCoreService.java | 22 ++ .../label/LabelAllocateRepositoryCustom.java | 13 + .../label/LabelAllocateRepositoryImpl.java | 251 +++++++++++++++++- 5 files changed, 555 insertions(+), 17 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 0a52baf2..8f53ccbf 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -5,9 +5,12 @@ 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.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; @@ -23,7 +26,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Slf4j -@Tag(name = "라벨링 작업 관리", description = "라벨링 작업 관리") +@Tag(name = "라벨링 작업 관리", description = "라벨링 작업 배정 및 통계 조회 API") @RequestMapping({"/api/label"}) @RequiredArgsConstructor @RestController @@ -31,7 +34,7 @@ public class LabelAllocateApiController { private final LabelAllocateService labelAllocateService; - @Operation(summary = "배정 가능한 사용자 목록 조회", description = "배정 가능한 사용자 목록 조회") + @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @ApiResponses( value = { @ApiResponse( @@ -40,16 +43,222 @@ public class LabelAllocateApiController { content = @Content( mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.Basic.class))), + 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) }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @RequestParam @Schema() String role) { + @Parameter(description = "사용자 역할 (LABELER: 라벨러, INSPECTOR: 검수자)", example = "LABELER") + @RequestParam + @Schema() + String role) { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } + @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": "INSPECTOR", + "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": "서버에 문제가 발생 하였습니다." + } + } + """))) + }) + @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", "INSPECTOR"}, + 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()) ? "LABELER" : type; + + return ApiResponseDto.ok( + labelAllocateService.getWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sort)); + } + // 라벨링 수량 할당하는 로직 테스트 @PostMapping("/allocate") public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { 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 6d273a95..98fd7a70 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 @@ -4,15 +4,20 @@ 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.WorkerStatsDto.DailyHistory; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; -import jakarta.transaction.Transactional; +import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service +@Transactional(readOnly = true) public class LabelAllocateService { private final LabelAllocateCoreService labelAllocateCoreService; @@ -24,7 +29,8 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param targetUsers + * @param targetUsers 라벨러 타겟 목록 + * @param targetInspectors 검수자 타겟 목록 */ @Transactional public void allocateAsc(List targetUsers, List targetInspectors) { @@ -80,4 +86,63 @@ public class LabelAllocateService { public List availUserList(String role) { return labelAllocateCoreService.availUserList(role); } + + /** + * 작업자 목록 및 3일치 통계 조회 + * + * @param analUid 분석 ID + * @param workerType 작업자 유형 (LABELER/INSPECTOR) + * @param searchName 이름 검색 + * @param searchEmployeeNo 사번 검색 + * @param sortType 정렬 조건 + * @return 작업자 목록 및 통계 + */ + public WorkerListResponse getWorkerStatistics( + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { + + // 작업 진행 현황 조회 + var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); + + // 작업자 통계 조회 + List workers = + 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); + Long day2Count = + labelAllocateCoreService.findDailyProcessedCount( + worker.getWorkerId(), workerType, today.minusDays(2), analUid); + Long day3Count = + 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(); + + worker.setHistory(history); + + // 정체 여부 판단 (3일 평균이 특정 기준 미만일 때 - 예: 10건 미만) + if (average < 10) { + worker.setIsStagnated(true); + } + } + + return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); + } } 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 1d8fa075..c1719913 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,8 +2,11 @@ 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.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.repository.label.LabelAllocateRepository; +import java.time.LocalDate; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -40,4 +43,23 @@ public class LabelAllocateCoreService { public List availUserList(String role) { return labelAllocateRepository.availUserList(role); } + + public List findWorkerStatistics( + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { + return labelAllocateRepository.findWorkerStatistics( + analUid, workerType, searchName, searchEmployeeNo, sortType); + } + + public WorkProgressInfo findWorkProgressInfo(Long analUid) { + return labelAllocateRepository.findWorkProgressInfo(analUid); + } + + public Long findDailyProcessedCount( + 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 659f5c7b..5392d88f 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,7 +1,10 @@ package com.kamco.cd.kamcoback.postgres.repository.label; 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 java.time.LocalDate; import java.util.List; import java.util.UUID; @@ -18,4 +21,14 @@ public interface LabelAllocateRepositoryCustom { void assignInspector(UUID assignmentUid, String userId); List availUserList(String role); + + // 작업자 통계 조회 + List findWorkerStatistics( + Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); + + // 작업 진행 현황 조회 + WorkProgressInfo findWorkProgressInfo(Long analUid); + + // 작업자별 일일 처리량 조회 + Long findDailyProcessedCount(String workerId, String workerType, LocalDate date, 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 f9e419d2..7ea657dc 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 @@ -8,38 +8,39 @@ 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.LabelState; 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.MapSheetAnalDataGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.NumberExpression; 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 java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.List; import java.util.Objects; import java.util.UUID; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; import org.springframework.stereotype.Repository; @Slf4j @Repository -public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport - implements LabelAllocateRepositoryCustom { +@RequiredArgsConstructor +public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCustom { private final JPAQueryFactory queryFactory; - private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); @PersistenceContext private EntityManager em; - public LabelAllocateRepositoryImpl(JPAQueryFactory queryFactory) { - super(MapSheetAnalDataGeomEntity.class); - this.queryFactory = queryFactory; - } - @Override public List fetchNextIds(Long lastId, int batchSize) { @@ -150,4 +151,232 @@ public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport .orderBy(memberEntity.name.asc()) .fetch(); } + + @Override + public List findWorkerStatistics( + Long analUid, + String workerType, + String searchName, + String searchEmployeeNo, + String sortType) { + + // 작업자 유형에 따른 필드 선택 + StringExpression workerIdField = + "INSPECTOR".equals(workerType) + ? labelingAssignmentEntity.inspectorUid + : labelingAssignmentEntity.workerUid; + + BooleanExpression workerCondition = + "INSPECTOR".equals(workerType) + ? labelingAssignmentEntity.inspectorUid.isNotNull() + : labelingAssignmentEntity.workerUid.isNotNull(); + + // 검색 조건 + BooleanExpression searchCondition = null; + if (searchName != null && !searchName.isEmpty()) { + searchCondition = memberEntity.name.contains(searchName); + } + if (searchEmployeeNo != null && !searchEmployeeNo.isEmpty()) { + BooleanExpression empCondition = memberEntity.employeeNo.contains(searchEmployeeNo); + searchCondition = searchCondition == null ? empCondition : searchCondition.and(empCondition); + } + + // 완료, 스킵, 남은 작업 계산 + NumberExpression completedSum = + 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(); + + NumberExpression remainingSum = + 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); + + // 정렬 조건 적용 + if (sortType != null) { + switch (sortType) { + case "REMAINING_DESC": + baseQuery.orderBy(remainingSum.desc()); + break; + case "REMAINING_ASC": + baseQuery.orderBy(remainingSum.asc()); + break; + case "NAME_ASC": + baseQuery.orderBy(memberEntity.name.asc()); + break; + case "NAME_DESC": + baseQuery.orderBy(memberEntity.name.desc()); + break; + default: + baseQuery.orderBy(memberEntity.name.asc()); + } + } else { + baseQuery.orderBy(memberEntity.name.asc()); + } + + // 결과를 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(); + } + + @Override + public WorkProgressInfo findWorkProgressInfo(Long analUid) { + // 전체 배정 건수 + Long totalAssigned = + 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(); + + // 투입된 라벨러 수 (고유한 worker_uid 수) + Long labelerCount = + 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(); + + // 투입된 검수자 수 (고유한 inspector_uid 수) + Long inspectorCount = + 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(); + + // 진행률 계산 + double progressRate = 0.0; + if (totalAssigned != null && totalAssigned > 0) { + progressRate = + (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(); + } + + @Override + public Long findDailyProcessedCount( + 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); + + Long count = + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analUid), + workerCondition, + labelingAssignmentEntity.workState.in("DONE", "SKIP"), + labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) + .fetchOne(); + + return count != null ? count : 0L; + } } From 513a2d3ebedf70fa985f8466cd1c06963757bd4f Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Fri, 2 Jan 2026 19:16:00 +0900 Subject: [PATCH 18/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=9F=AC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=A1=B0=ED=9A=8C=20=EC=9E=84=EC=8B=9C=EC=BB=A4?= =?UTF-8?q?=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kamcoback/label/dto/WorkerStatsDto.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java new file mode 100644 index 00000000..efca81b2 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -0,0 +1,117 @@ +package com.kamco.cd.kamcoback.label.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +public class WorkerStatsDto { + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "작업자 통계 응답") + public static class WorkerStatistics { + + @Schema(description = "작업자 ID (사번)") + private String workerId; + + @Schema(description = "작업자 이름") + private String workerName; + + @Schema(description = "작업자 유형 (LABELER/INSPECTOR)") + private String workerType; + + @Schema(description = "전체 배정 건수") + private Long totalAssigned; + + @Schema(description = "완료 건수") + private Long completed; + + @Schema(description = "스킵 건수") + private Long skipped; + + @Schema(description = "남은 작업 건수") + private Long remaining; + + @Schema(description = "최근 3일간 처리 이력") + private DailyHistory history; + + @Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") + private Boolean isStagnated; + } + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "최근 3일간 일일 처리 이력") + public static class DailyHistory { + + @Schema(description = "1일 전 (어제) 처리량") + private Long day1Ago; + + @Schema(description = "2일 전 처리량") + private Long day2Ago; + + @Schema(description = "3일 전 처리량") + private Long day3Ago; + + @Schema(description = "3일 평균 처리량") + private Long average; + } + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "작업 진행 현황 정보") + public static class WorkProgressInfo { + + @Schema(description = "라벨링 진행률 (완료건+스킵건)/배정건") + private Double labelingProgressRate; + + @Schema(description = "작업 상태 (진행중/종료)") + private String workStatus; + + @Schema(description = "진행률 수치 (완료+스킵)") + private Long completedCount; + + @Schema(description = "전체 배정 건수") + private Long totalAssignedCount; + + @Schema(description = "투입된 라벨러 수") + private Long labelerCount; + + @Schema(description = "남은 라벨링 작업 데이터 수") + private Long remainingLabelCount; + + @Schema(description = "투입된 검수자 수") + private Long inspectorCount; + + @Schema(description = "남은 검수 작업 데이터 수") + private Long remainingInspectCount; + } + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "작업자 목록 응답 (작업 정보 포함)") + public static class WorkerListResponse { + + @Schema(description = "작업 진행 현황 정보") + private WorkProgressInfo progressInfo; + + @Schema(description = "작업자 목록") + private List workers; + } +} From 89b1ea755fc6986ee6e72ecdd71586a323604f55 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Fri, 2 Jan 2026 20:19:54 +0900 Subject: [PATCH 19/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/GlobalExceptionHandler.java | 3 +- .../label/LabelAllocateApiController.java | 214 ++++++++++-------- .../kamcoback/label/dto/LabelAllocateDto.java | 39 ++++ .../label/service/LabelAllocateService.java | 72 +++--- .../core/LabelAllocateCoreService.java | 11 +- .../postgres/entity/MapSheetAnalEntity.java | 3 + .../label/LabelAllocateRepositoryCustom.java | 7 +- .../label/LabelAllocateRepositoryImpl.java | 74 ++++-- 8 files changed, 268 insertions(+), 155 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java index 161b1c11..bff7b299 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java @@ -463,7 +463,8 @@ public class GlobalExceptionHandler { String stackTraceStr = Arrays.stream(stackTrace) .map(StackTraceElement::toString) - .collect(Collectors.joining("\n")); + .collect(Collectors.joining("\n")) + .substring(0, 255); ErrorLogEntity errorLogEntity = new ErrorLogEntity( 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 8f53ccbf..9e359a82 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -3,8 +3,7 @@ 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.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; @@ -69,7 +68,7 @@ public class LabelAllocateApiController { }) @GetMapping("/avail-user") public ApiResponseDto> availUserList( - @Parameter(description = "사용자 역할 (LABELER: 라벨러, INSPECTOR: 검수자)", example = "LABELER") + @Parameter(description = "사용자 역할 (LABELER: 라벨러, REVIEWER: 검수자)", example = "LABELER") @RequestParam @Schema() String role) { @@ -78,10 +77,9 @@ public class LabelAllocateApiController { @Operation( summary = "작업자 목록 및 3일치 통계 조회", - description = - """ - 학습데이터 제작 현황 조회 API입니다. - """) + description = """ + 학습데이터 제작 현황 조회 API입니다. + """) @ApiResponses( value = { @ApiResponse( @@ -97,93 +95,93 @@ public class LabelAllocateApiController { 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 + { + "data": { + "progressInfo": { + "labelingProgressRate": 79.34, + "workStatus": "진행중", + "completedCount": 6554, + "totalAssignedCount": 8258, + "labelerCount": 5, + "remainingLabelCount": 1704, + "inspectorCount": 3, + "remainingInspectCount": 890 }, - { - "workerId": "2345678", - "workerName": "이작업", - "workerType": "LABELER", - "totalAssigned": 2000, - "completed": 1850, - "skipped": 100, - "remaining": 50, - "history": { - "day1Ago": 5, - "day2Ago": 3, - "day3Ago": 8, - "average": 5 + "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 }, - "isStagnated": true - } - ] + { + "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": "INSPECTOR", - "totalAssigned": 1200, - "completed": 980, - "skipped": 20, - "remaining": 200, - "history": { - "day1Ago": 150, - "day2Ago": 145, - "day3Ago": 155, - "average": 150 - }, - "isStagnated": false - } - ] + { + "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", @@ -227,7 +225,7 @@ public class LabelAllocateApiController { example = "LABELER", schema = @Schema( - allowableValues = {"LABELER", "INSPECTOR"}, + allowableValues = {"LABELER", "REVIEWER"}, defaultValue = "LABELER")) @RequestParam(required = false) String type, @@ -259,22 +257,40 @@ public class LabelAllocateApiController { analUid, workerType, searchName, searchEmployeeNo, sort)); } - // 라벨링 수량 할당하는 로직 테스트 + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/allocate") - public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto dto) { + 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 { - List targets = - List.of( - new TargetUser("1234567", 1000), - new TargetUser("2345678", 400), - new TargetUser("3456789", 440)); - List inspectors = - List.of( - new TargetInspector("9876543", 1000), - new TargetInspector("8765432", 340), - new TargetInspector("98765432", 500)); - labelAllocateService.allocateAsc(targets, inspectors); + labelAllocateService.allocateAsc( + dto.getAutoType(), dto.getStage(), dto.getLabelers(), dto.getInspectors()); return ApiResponseDto.ok(null); } + + @GetMapping + public ApiResponseDto findInferenceDetail(@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 d1705e92..5b508a07 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 @@ -2,7 +2,9 @@ package com.kamco.cd.kamcoback.label.dto; import com.kamco.cd.kamcoback.common.utils.enums.CodeExpose; import com.kamco.cd.kamcoback.common.utils.enums.EnumType; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.ZonedDateTime; +import java.util.List; import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Getter; @@ -78,10 +80,31 @@ public class LabelAllocateDto { } } + @Getter + @Setter + @AllArgsConstructor + public static class AllocateDto { + + @Schema(description = "자동/수동여부(AUTO/MANUAL)", example = "AUTO") + private String autoType; + + @Schema(description = "회차", example = "4") + private Integer stage; + + @Schema(description = "라벨러 할당 목록") + private List labelers; + + @Schema(description = "검수자 할당 목록") + private List inspectors; + } + @Getter public static class TargetUser { + @Schema(description = "라벨러 사번", example = "labeler44") private final String userId; + + @Schema(description = "할당 건수", example = "200") private final int demand; public TargetUser(String userId, int demand) { @@ -94,7 +117,10 @@ public class LabelAllocateDto { @AllArgsConstructor public static class TargetInspector { + @Schema(description = "검수자 사번", example = "K20251212001") private final String inspectorUid; + + @Schema(description = "할당 명수", example = "3") private int userCount; } @@ -125,4 +151,17 @@ public class LabelAllocateDto { private String employeeNo; private String name; } + + @Getter + @Setter + @AllArgsConstructor + public static class InferenceDetail { + + private String analTitle; + private Integer stage; + private ZonedDateTime gukyuinDttm; + private Long count; + private Long labelCnt; + private Long inspectorCnt; + } } 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 98fd7a70..a51a2ae7 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 @@ -1,6 +1,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.TargetInspector; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; @@ -10,7 +11,7 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import java.time.LocalDate; import java.util.List; -import java.util.Objects; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -29,57 +30,56 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param targetUsers 라벨러 타겟 목록 - * @param targetInspectors 검수자 타겟 목록 + * @param targetUsers */ @Transactional - public void allocateAsc(List targetUsers, List targetInspectors) { + public void allocateAsc( + String autoType, + Integer stage, + List targetUsers, + List targetInspectors) + throws Exception { Long lastId = null; // geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(3L); // TODO - Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); - if (!Objects.equals(chargeCnt, totalDemand)) { - log.info("chargeCnt != totalDemand"); + // Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); + // if (!Objects.equals(chargeCnt, totalDemand)) { + // log.info("chargeCnt != totalDemand"); + // return; + // } + + if (chargeCnt <= 0) { return; } - // 라벨러에게 건수만큼 할당 + List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt); + int index = 0; for (TargetUser target : targetUsers) { - int remaining = target.getDemand(); + int end = index + target.getDemand(); + List sub = allIds.subList(index, end); - while (remaining > 0) { - - int batchSize = Math.min(remaining, 100); - List ids = labelAllocateCoreService.fetchNextIds(lastId, batchSize); - - if (ids.isEmpty()) { - return; // 더이상 할당할 데이터가 없으면 return - } - - labelAllocateCoreService.assignOwner(ids, target.getUserId()); - - remaining -= ids.size(); - lastId = ids.get(ids.size() - 1); - } + labelAllocateCoreService.assignOwner(sub, target.getUserId()); + index = end; } // 검수자에게 userCount명 만큼 할당 List list = labelAllocateCoreService.findAssignedLabelerList(3L); - int reviewerIndex = 0; - int count = 0; - log.info("list : " + list.size()); + int from = 0; - for (LabelAllocateDto.Basic labeler : list) { - TargetInspector inspector = targetInspectors.get(reviewerIndex); - labelAllocateCoreService.assignInspector( - labeler.getAssignmentUid(), inspector.getInspectorUid()); - count++; + for (TargetInspector inspector : targetInspectors) { + int to = Math.min(from + inspector.getUserCount(), list.size()); - if (count == inspector.getUserCount()) { - reviewerIndex++; - count = 0; + if (from >= to) { + break; } + + List assignmentUids = + list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); + + labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid()); + + from = to; } } @@ -145,4 +145,8 @@ public class LabelAllocateService { return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); } + + public InferenceDetail findInferenceDetail(Long analUid) { + return labelAllocateCoreService.findInferenceDetail(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 c1719913..e7b53b73 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 @@ -1,6 +1,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.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -18,7 +19,7 @@ public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; - public List fetchNextIds(Long lastId, int batchSize) { + public List fetchNextIds(Long lastId, Long batchSize) { return labelAllocateRepository.fetchNextIds(lastId, batchSize); } @@ -62,4 +63,12 @@ public class LabelAllocateCoreService { String workerId, String workerType, LocalDate date, Long analUid) { return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid); } + + public void assignInspectorBulk(List assignmentUids, String inspectorUid) { + labelAllocateRepository.assignInspectorBulk(assignmentUids, inspectorUid); + } + + public InferenceDetail findInferenceDetail(Long analUid) { + return labelAllocateRepository.findInferenceDetail(analUid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java index b2aeaf6d..dc9b1340 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalEntity.java @@ -103,4 +103,7 @@ public class MapSheetAnalEntity { @ColumnDefault("now()") @Column(name = "updated_dttm") private ZonedDateTime updatedDttm; + + @Column(name = "gukyuin_apply_dttm") + private ZonedDateTime gukyuinApplyDttm; } 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 5392d88f..df73428b 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,5 +1,6 @@ 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.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -10,7 +11,7 @@ import java.util.UUID; public interface LabelAllocateRepositoryCustom { - List fetchNextIds(Long lastId, int batchSize); + List fetchNextIds(Long lastId, Long batchSize); void assignOwner(List ids, String userId); @@ -31,4 +32,8 @@ public interface LabelAllocateRepositoryCustom { // 작업자별 일일 처리량 조회 Long findDailyProcessedCount(String workerId, String workerType, LocalDate date, Long analUid); + + void assignInspectorBulk(List assignmentUids, String inspectorUid); + + InferenceDetail findInferenceDetail(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 7ea657dc..88d07305 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 @@ -6,6 +6,7 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapShee 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.LabelState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; @@ -15,6 +16,7 @@ import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalEntity; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -42,7 +44,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @PersistenceContext private EntityManager em; @Override - public List fetchNextIds(Long lastId, int batchSize) { + public List fetchNextIds(Long lastId, Long batchSize) { return queryFactory .select(mapSheetAnalDataInferenceGeomEntity.geoUid) @@ -53,7 +55,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(2022), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(2024), mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) - .orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.asc()) + .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) .limit(batchSize) .fetch(); } @@ -65,28 +67,28 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .update(mapSheetAnalDataInferenceGeomEntity) .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) + .set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, 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 (?, ?, ?, ?, ?, ?) + """; + for (Long geoUid : ids) { - queryFactory - .insert(labelingAssignmentEntity) - .columns( - labelingAssignmentEntity.assignmentUid, - labelingAssignmentEntity.inferenceGeomUid, - labelingAssignmentEntity.workerUid, - labelingAssignmentEntity.workState, - labelingAssignmentEntity.assignGroupId, - labelingAssignmentEntity.analUid) - .values( - UUID.randomUUID(), - geoUid, - userId, - LabelState.ASSIGNED.getId(), - "", // TODO: 도엽번호 - 3) - .execute(); + em.createNativeQuery(sql) + .setParameter(1, UUID.randomUUID()) + .setParameter(2, geoUid) + .setParameter(3, userId) + .setParameter(4, LabelState.ASSIGNED.getId()) + .setParameter(5, "") + .setParameter(6, 3) + .executeUpdate(); } em.flush(); @@ -379,4 +381,38 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return count != null ? count : 0L; } + + @Override + public void assignInspectorBulk(List assignmentUids, String inspectorUid) { + queryFactory + .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) + .fetchOne(); + } } From cc5015213b5dd80a31e30faba2da4cb6fc85ecd8 Mon Sep 17 00:00:00 2001 From: Moon Date: Fri, 2 Jan 2026 20:59:19 +0900 Subject: [PATCH 20/70] =?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 21/70] 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 22/70] =?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 23/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=9D=B4=EA=B4=80,?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=EC=A0=95=EB=B3=B4=20API=20=EC=B6=94?= =?UTF-8?q?=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 24/70] 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 25/70] =?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 26/70] =?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 27/70] =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20yy?= =?UTF-8?q?yy=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 28/70] 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 = From d87048b4c684d2cf63209b828e55311cbaf292cd Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 08:54:01 +0900 Subject: [PATCH 29/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=ED=95=A0=EB=8B=B9,?= =?UTF-8?q?=EC=9D=B4=EA=B4=80=20=EB=A6=AC=ED=84=B4=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kamcoback/config/api/ApiLogFunction.java | 8 ++ .../config/api/ApiResponseAdvice.java | 4 +- .../label/LabelAllocateApiController.java | 106 ++++-------------- .../kamcoback/label/dto/LabelAllocateDto.java | 67 +++++++++-- .../label/service/LabelAllocateService.java | 33 ++++-- 5 files changed, 114 insertions(+), 104 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java index 43a78ac7..446c7317 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java @@ -129,4 +129,12 @@ public class ApiLogFunction { return m != null ? m.getMenuUid() : "SYSTEM"; } + + public static String cutRequestBody(String value) { + int MAX_LEN = 255; + if (value == null) { + return null; + } + return value.length() <= MAX_LEN ? value : value.substring(0, MAX_LEN); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java index 034eccf3..72cb50bc 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java @@ -9,6 +9,7 @@ import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository; import jakarta.servlet.http.HttpServletRequest; import java.util.LinkedHashMap; import java.util.List; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; @@ -26,6 +27,7 @@ import org.springframework.web.util.ContentCachingRequestWrapper; * *

createOK() → 201 CREATED ok() → 200 OK deleteOk() → 204 NO_CONTENT */ +@Slf4j @RestControllerAdvice public class ApiResponseAdvice implements ResponseBodyAdvice { @@ -110,7 +112,7 @@ public class ApiResponseAdvice implements ResponseBodyAdvice { ApiLogFunction.getUriMenuInfo(result, servletRequest.getRequestURI()), ip, servletRequest.getRequestURI(), - requestBody, + ApiLogFunction.cutRequestBody(requestBody), apiResponse.getErrorLogUid()); auditLogRepository.save(log); } 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 b4fdf79b..fe1ed563 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -10,11 +10,11 @@ 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; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -109,63 +109,24 @@ public class LabelAllocateApiController { 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 - } - - ] - } - """) - })), + schema = @Schema(implementation = Long.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PostMapping("/allocate") - public ApiResponseDto labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) { - labelAllocateService.allocateAsc( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getInspectors(), - dto.getCompareYyyy(), - dto.getTargetYyyy()); + public ApiResponseDto labelAllocate( + @RequestBody @Valid LabelAllocateDto.AllocateDto dto) { - return ApiResponseDto.ok(null); + return ApiResponseDto.okObject( + labelAllocateService.allocateAsc( + dto.getLabelerAutoType(), + dto.getInspectorAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getCompareYyyy(), + dto.getTargetYyyy())); } @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") @@ -208,35 +169,13 @@ public class LabelAllocateApiController { 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 - } - ] - } - """) - })), + schema = @Schema(implementation = Long.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PostMapping("/allocate-move") - public ApiResponseDto labelAllocateMove( + public ApiResponseDto labelAllocateMove( @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "라벨링 이관", required = true, @@ -247,13 +186,12 @@ public class LabelAllocateApiController { @RequestBody LabelAllocateDto.AllocateMoveDto dto) { - labelAllocateService.allocateMove( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getCompareYyyy(), - dto.getTargetYyyy()); - - return ApiResponseDto.ok(null); + return ApiResponseDto.okObject( + labelAllocateService.allocateMove( + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getCompareYyyy(), + dto.getTargetYyyy())); } } 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 5fe14bbb..eb7f2f9a 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,8 +87,8 @@ public class LabelAllocateDto { @AllArgsConstructor public static class AllocateDto { - @Schema(description = "분석 ID", example = "3") - private Long analUid; + // @Schema(description = "분석 ID", example = "3") + // private Long analUid; @Schema(description = "비교년도", example = "2022", required = true) private Integer compareYyyy; @@ -96,16 +96,55 @@ public class LabelAllocateDto { @Schema(description = "기준년도", example = "2024", required = true) private Integer targetYyyy; - @Schema(description = "자동/수동여부(AUTO/MANUAL)", example = "AUTO") - private String autoType; + @Schema(description = "라벨러 자동/수동여부(AUTO/MANUAL)", example = "AUTO") + private String labelerAutoType; + + @Schema(description = "검수자 자동/수동여부(AUTO/MANUAL)", example = "AUTO") + private String inspectorAutoType; @Schema(description = "회차", example = "4") private Integer stage; - @Schema(description = "라벨러 할당 목록") + @Schema( + description = "라벨러 할당 목록", + example = + """ + [ + { + "userId": "123456", + "demand": 1000 + }, + { + "userId": "010222297501", + "demand": 400 + }, + { + "userId": "01022223333", + "demand": 440 + } + ] + """) private List labelers; - @Schema(description = "검수자 할당 목록") + @Schema( + description = "검수자 할당 목록", + example = + """ + [ + { + "inspectorUid": "K20251216001", + "userCount": 1000 + }, + { + "inspectorUid": "01022225555", + "userCount": 340 + }, + { + "inspectorUid": "K20251212001", + "userCount": 500 + } + ] + """) private List inspectors; } @@ -201,7 +240,21 @@ public class LabelAllocateDto { @Schema(description = "회차", example = "4") private Integer stage; - @Schema(description = "라벨러 할당 목록") + @Schema( + description = "라벨러 할당 목록", + example = + """ + [ + { + "userId": "123456", + "demand": 10 + }, + { + "userId": "010222297501", + "demand": 5 + } + ] + """) private List labelers; @Schema(description = "비교년도", example = "2022") 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 54d30f5b..80903df2 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 @@ -1,5 +1,7 @@ package com.kamco.cd.kamcoback.label.service; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode; 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; @@ -12,6 +14,7 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import java.time.LocalDate; import java.util.List; +import java.util.Objects; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -34,14 +37,16 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param autoType 자동/수동 배정 타입 + * @param labelerAutoType 라벨러 자동/수동 배정 타입 + * @param inspectorAutoType 검수자 자동/수동 배정 타입 * @param stage 회차 * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional - public void allocateAsc( - String autoType, + public ApiResponseDto.ResponseObj allocateAsc( + String labelerAutoType, + String inspectorAutoType, Integer stage, List targetUsers, List targetInspectors, @@ -52,14 +57,14 @@ public class LabelAllocateService { // geom 잔여건수 조회 Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); - // Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); - // if (!Objects.equals(chargeCnt, totalDemand)) { - // log.info("chargeCnt != totalDemand"); - // return; - // } - if (chargeCnt <= 0) { - return; + return new ApiResponseDto.ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 배정완료된 회차 입니다."); + } + + Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); + if (!Objects.equals(chargeCnt, totalDemand)) { + return new ApiResponseDto.ResponseObj( + ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); } List allIds = @@ -92,6 +97,8 @@ public class LabelAllocateService { from = to; } + + return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "배정이 완료되었습니다."); } public List availUserList(String role) { @@ -162,7 +169,7 @@ public class LabelAllocateService { return labelAllocateCoreService.findInferenceDetail(compareYyyy, targetYyyy, stage); } - public void allocateMove( + public ApiResponseDto.ResponseObj allocateMove( String autoType, Integer stage, List targetUsers, @@ -173,7 +180,7 @@ public class LabelAllocateService { Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); if (chargeCnt <= 0) { - return; + return new ApiResponseDto.ResponseObj(ApiResponseCode.BAD_REQUEST, "이관할 데이터를 입력해주세요."); } List allIds = @@ -187,6 +194,8 @@ public class LabelAllocateService { labelAllocateCoreService.assignOwnerMove(sub, target.getUserId()); index = end; } + + return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } public LabelerDetail findLabelerDetail( From 1cab1a34abb9f35683fa7aa392241d6925c48c54 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 09:15:13 +0900 Subject: [PATCH 30/70] =?UTF-8?q?api=20=EC=84=A4=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 14 +++++++++----- .../kamcoback/label/LabelWorkerApiController.java | 2 +- .../kamco/cd/kamcoback/label/dto/LabelWorkDto.java | 14 +++++++++----- 3 files changed, 19 insertions(+), 11 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 fe1ed563..a95f0993 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -52,7 +52,9 @@ public class LabelAllocateApiController { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } - @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.") + @Operation( + summary = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)", + description = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)") @ApiResponses( value = { @ApiResponse(responseCode = "200", description = "조회 성공"), @@ -100,7 +102,7 @@ public class LabelAllocateApiController { analUid, workerType, searchName, searchEmployeeNo, sort)); } - @Operation(summary = "작업 배정", description = "작업 배정") + @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") @ApiResponses( value = { @ApiResponse( @@ -129,7 +131,7 @@ public class LabelAllocateApiController { dto.getTargetYyyy())); } - @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") + @Operation(summary = "작업현황 관리 > 변화탐지 회차 정보", description = "작업현황 관리 > 변화탐지 회차 정보") @ApiResponses( value = { @ApiResponse(responseCode = "200", description = "조회 성공"), @@ -147,7 +149,9 @@ public class LabelAllocateApiController { labelAllocateService.findInferenceDetail(compareYyyy, targetYyyy, stage)); } - @Operation(summary = "작업이관 > 라벨러 상세 정보", description = "작업이관 > 라벨러 상세 정보") + @Operation( + summary = "작업현황 관리 > 상세 > 작업 이관 > 팝업 내 라벨러 상세 정보", + description = "작업현황 관리 > 상세 > 작업 이관 > 팝업 내 라벨러 상세 정보") @GetMapping("/labeler-detail") public ApiResponseDto findLabelerDetail( @RequestParam(defaultValue = "01022223333") String userId, @@ -160,7 +164,7 @@ public class LabelAllocateApiController { labelAllocateService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage)); } - @Operation(summary = "작업 이관", description = "작업 이관") + @Operation(summary = "작업현황 관리 > 상세 > 작업 이관", description = "작업현황 관리 > 상세 > 작업 이관") @ApiResponses( value = { @ApiResponse( 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 34524bbd..416f7738 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -29,7 +29,7 @@ public class LabelWorkerApiController { private final LabelWorkService labelWorkService; - @Operation(summary = "라벨링작업 관리 목록 조회", description = "라벨링작업 관리 목록 조회") + @Operation(summary = "라벨링작업 관리 > 목록 조회", description = "라벨링작업 관리 > 목록 조회") @ApiResponses( value = { @ApiResponse( 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 e712746b..024f89b5 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 @@ -36,9 +36,13 @@ public class LabelWorkDto { 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"; + if (this.labelTotCnt == 0) { + mngState = "PENDING"; + } else if (this.labelIngTotCnt > 0) { + mngState = "LABEL_ING"; + } else if (this.labelTotCnt <= labelCompleteTotCnt) { + mngState = "LABEL_COMPLETE"; + } return mngState; } @@ -78,10 +82,10 @@ public class LabelWorkDto { @Schema(description = "변화탐지년도", example = "2024") private Integer detectYyyy; - @Schema(description = "시작일", example = "20240101") + @Schema(description = "시작일", example = "20260101") private String strtDttm; - @Schema(description = "종료일", example = "20241201") + @Schema(description = "종료일", example = "20261201") private String endDttm; public Pageable toPageable() { From 40daa738f6d755514e2c954f34e433913274fa44 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 09:55:02 +0900 Subject: [PATCH 31/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=97=90=20=EB=8F=84=EC=97=BD=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EB=9D=BC=EB=B2=A8=EB=9F=AC=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 4 +- .../kamcoback/label/dto/LabelAllocateDto.java | 92 +++++++++++-------- .../label/service/LabelAllocateService.java | 5 +- .../core/LabelAllocateCoreService.java | 9 +- .../label/LabelAllocateRepositoryCustom.java | 9 +- .../label/LabelAllocateRepositoryImpl.java | 51 +++++++--- 6 files changed, 107 insertions(+), 63 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 a95f0993..6869b636 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -150,8 +150,8 @@ public class LabelAllocateApiController { } @Operation( - summary = "작업현황 관리 > 상세 > 작업 이관 > 팝업 내 라벨러 상세 정보", - description = "작업현황 관리 > 상세 > 작업 이관 > 팝업 내 라벨러 상세 정보") + summary = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", + description = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") @GetMapping("/labeler-detail") public ApiResponseDto findLabelerDetail( @RequestParam(defaultValue = "01022223333") String userId, 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 eb7f2f9a..c0dd75a6 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 @@ -109,42 +109,42 @@ public class LabelAllocateDto { description = "라벨러 할당 목록", example = """ - [ - { - "userId": "123456", - "demand": 1000 - }, - { - "userId": "010222297501", - "demand": 400 - }, - { - "userId": "01022223333", - "demand": 440 - } - ] - """) + [ + { + "userId": "123456", + "demand": 1000 + }, + { + "userId": "010222297501", + "demand": 400 + }, + { + "userId": "01022223333", + "demand": 440 + } + ] + """) private List labelers; @Schema( description = "검수자 할당 목록", example = """ - [ - { - "inspectorUid": "K20251216001", - "userCount": 1000 - }, - { - "inspectorUid": "01022225555", - "userCount": 340 + [ + { + "inspectorUid": "K20251216001", + "userCount": 1000 }, - { - "inspectorUid": "K20251212001", - "userCount": 500 - } - ] - """) + { + "inspectorUid": "01022225555", + "userCount": 340 + }, + { + "inspectorUid": "K20251212001", + "userCount": 500 + } + ] + """) private List inspectors; } @@ -227,6 +227,9 @@ public class LabelAllocateDto { private Long completeCnt; private Long skipCnt; private Double percent; + private Integer ranking; + private ZonedDateTime createdDttm; + private String inspectorName; } @Getter @@ -244,17 +247,17 @@ public class LabelAllocateDto { description = "라벨러 할당 목록", example = """ - [ - { - "userId": "123456", - "demand": 10 - }, - { - "userId": "010222297501", - "demand": 5 - } - ] - """) + [ + { + "userId": "123456", + "demand": 10 + }, + { + "userId": "010222297501", + "demand": 5 + } + ] + """) private List labelers; @Schema(description = "비교년도", example = "2022") @@ -263,4 +266,13 @@ public class LabelAllocateDto { @Schema(description = "기준년도", example = "2024") private Integer targetYyyy; } + + @Getter + @Setter + @AllArgsConstructor + public static class AllocateInfoDto { + + private Long geoUid; + private String mapSheetNum; + } } 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 80903df2..c919a5eb 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 @@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.label.service; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; 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; @@ -67,12 +68,12 @@ public class LabelAllocateService { ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); } - List allIds = + 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); + List sub = allIds.subList(index, end); labelAllocateCoreService.assignOwner(sub, target.getUserId(), compareYyyy, targetYyyy, stage); index = end; 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 56bca503..3e16eae5 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 @@ -1,6 +1,7 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; 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; @@ -20,13 +21,17 @@ public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; - public List fetchNextIds( + 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) { + List ids, + String userId, + Integer compareYyyy, + Integer targetYyyy, + Integer stage) { labelAllocateRepository.assignOwner(ids, 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 caf929c7..22988c98 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,5 +1,6 @@ package com.kamco.cd.kamcoback.postgres.repository.label; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; 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; @@ -12,11 +13,15 @@ import java.util.UUID; public interface LabelAllocateRepositoryCustom { - List fetchNextIds( + List fetchNextIds( Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); void assignOwner( - List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage); + List ids, + String userId, + Integer compareYyyy, + Integer targetYyyy, + Integer stage); List findAssignedLabelerList( 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 6a68e9b3..9e52da12 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,6 +7,7 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalEntity.mapShee 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.AllocateInfoDto; 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; @@ -17,6 +18,7 @@ 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.kamco.cd.kamcoback.postgres.entity.QMemberEntity; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.CaseBuilder; @@ -49,11 +51,15 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @PersistenceContext private EntityManager em; @Override - public List fetchNextIds( + public List fetchNextIds( Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid) + .select( + Projections.constructor( + AllocateInfoDto.class, + mapSheetAnalDataInferenceGeomEntity.geoUid, + mapSheetAnalDataInferenceGeomEntity.mapSheetNum)) .from(mapSheetAnalDataInferenceGeomEntity) .where( lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), @@ -68,7 +74,11 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public void assignOwner( - List ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + List ids, + String userId, + Integer compareYyyy, + Integer targetYyyy, + Integer stage) { // analUid로 분석 정보 조회 MapSheetAnalDataInferenceEntity analEntity = @@ -87,13 +97,16 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } // data_geom 테이블에 label state 를 ASSIGNED 로 update + List geoUidList = + ids.stream().map(AllocateInfoDto::getGeoUid).filter(Objects::nonNull).toList(); + 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)) + .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(geoUidList)) .execute(); // 라벨러 할당 테이블에 insert @@ -105,13 +118,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto values (?, ?, ?, ?, ?, ?) """; - for (Long geoUid : ids) { + for (AllocateInfoDto info : ids) { em.createNativeQuery(sql) .setParameter(1, UUID.randomUUID()) - .setParameter(2, geoUid) + .setParameter(2, info.getGeoUid()) .setParameter(3, userId) .setParameter(4, LabelState.ASSIGNED.getId()) - .setParameter(5, "") + .setParameter(5, info.getMapSheetNum()) .setParameter(6, analEntity.getAnalUid()) .executeUpdate(); } @@ -572,24 +585,32 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); } + QMemberEntity worker = QMemberEntity.memberEntity; + QMemberEntity inspector = new QMemberEntity("inspector"); + return queryFactory .select( Projections.constructor( LabelerDetail.class, - memberEntity.userRole, - memberEntity.name, - memberEntity.employeeNo, + worker.userRole, + worker.name, + worker.employeeNo, assignedCnt, skipCnt, completeCnt, - percent)) - .from(memberEntity) + percent, + Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + labelingAssignmentEntity.createdDate.min(), + inspector.name.min())) + .from(worker) .innerJoin(labelingAssignmentEntity) .on( - memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid), + worker.employeeNo.eq(labelingAssignmentEntity.workerUid), labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid())) - .where(memberEntity.employeeNo.eq(userId)) - .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.employeeNo) + .leftJoin(inspector) + .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) + .where(worker.employeeNo.eq(userId)) + .groupBy(worker.userRole, worker.name, worker.employeeNo) .fetchOne(); } } From 5eab38e1ea5807686cd46a037e4c5549ebf7fe99 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 10:04:57 +0900 Subject: [PATCH 32/70] =?UTF-8?q?=ED=95=99=EC=8A=B5=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelWorkerApiController.java | 48 ++++-- .../entity/MapSheetAnalInferenceEntity.java | 148 ++++++++++++++++++ 2 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.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 index 416f7738..5ca52652 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -4,20 +4,21 @@ import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngSearchReq; import com.kamco.cd.kamcoback.label.service.LabelWorkService; 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.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 lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Slf4j @@ -31,20 +32,35 @@ public class LabelWorkerApiController { @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") + 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) + }) + @GetMapping("/label-work-mng-list") public ApiResponseDto> labelWorkMngList( - @RequestBody @Valid LabelWorkDto.LabelWorkMngSearchReq searchReq) { + @Schema(description = "변화탐지년도", example = "2024") + Integer detectYyyy, + @Schema(description = "시작일", example = "20260101") + String strtDttm, + @Schema(description = "종료일", example = "20261201") + String endDttm, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size) { + LabelWorkDto.LabelWorkMngSearchReq searchReq = new LabelWorkMngSearchReq(); + searchReq.setDetectYyyy(detectYyyy); + searchReq.setStrtDttm(strtDttm); + searchReq.setEndDttm(endDttm); + searchReq.setPage(page); + searchReq.setSize(size); return ApiResponseDto.ok(labelWorkService.labelWorkMngList(searchReq)); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.java new file mode 100644 index 00000000..5a68209e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.java @@ -0,0 +1,148 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +@Getter +@Setter +@Entity +@Table(name = "tb_map_sheet_anal_inference") +public class MapSheetAnalInferenceEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_sheet_anal_inference_id_gen") + @SequenceGenerator(name = "tb_map_sheet_anal_inference_id_gen", sequenceName = "tb_map_sheet_anal_inference_uid", allocationSize = 1) + @Column(name = "anal_uid", nullable = false) + private Long id; + + @Column(name = "compare_yyyy") + private Integer compareYyyy; + + @Column(name = "target_yyyy") + private Integer targetYyyy; + + @Column(name = "model_uid") + private Long modelUid; + + @Size(max = 100) + @Column(name = "server_ids", length = 100) + private String serverIds; + + @Column(name = "anal_strt_dttm") + private ZonedDateTime analStrtDttm; + + @Column(name = "anal_end_dttm") + private ZonedDateTime analEndDttm; + + @Column(name = "anal_sec") + private Long analSec; + + @Size(max = 20) + @Column(name = "anal_state", length = 20) + private String analState; + + @Size(max = 20) + @Column(name = "gukyuin_used", length = 20) + private String gukyuinUsed; + + @Column(name = "accuracy") + private Double accuracy; + + @Size(max = 255) + @Column(name = "result_url") + private String resultUrl; + + @ColumnDefault("now()") + @Column(name = "created_dttm") + private ZonedDateTime createdDttm; + + @Column(name = "created_uid") + private Long createdUid; + + @ColumnDefault("now()") + @Column(name = "updated_dttm") + private ZonedDateTime updatedDttm; + + @Column(name = "updated_uid") + private Long updatedUid; + + @Size(max = 255) + @Column(name = "anal_title") + private String analTitle; + + @Column(name = "detecting_cnt") + private Long detectingCnt; + + @Column(name = "anal_pred_sec") + private Long analPredSec; + + @Column(name = "model_ver_uid") + private Long modelVerUid; + + @Column(name = "hyper_params") + @JdbcTypeCode(SqlTypes.JSON) + private Map hyperParams; + + @Column(name = "tranning_rate") + private List tranningRate; + + @Column(name = "validation_rate") + private List validationRate; + + @Column(name = "test_rate", length = Integer.MAX_VALUE) + private String testRate; + + @Size(max = 128) + @Column(name = "detecting_description", length = 128) + private String detectingDescription; + + @Size(max = 12) + @Column(name = "base_map_sheet_num", length = 12) + private String baseMapSheetNum; + + @ColumnDefault("gen_random_uuid()") + @Column(name = "uuid") + private UUID uuid; + + @Size(max = 50) + @Column(name = "model_m1_ver", length = 50) + private String modelM1Ver; + + @Size(max = 50) + @Column(name = "model_m2_ver", length = 50) + private String modelM2Ver; + + @Size(max = 50) + @Column(name = "model_m3_ver", length = 50) + private String modelM3Ver; + + @Size(max = 20) + @Column(name = "anal_target_type", length = 20) + private String analTargetType; + + @Column(name = "gukyuin_apply_dttm") + private ZonedDateTime gukyuinApplyDttm; + + @Size(max = 20) + @Column(name = "detection_data_option", length = 20) + private String detectionDataOption; + + @Column(name = "stage") + private Integer stage; + +} From 4cea4bb2ed5c03fd0187d1ba9a15e17e0533c0ea Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 10:17:01 +0900 Subject: [PATCH 33/70] =?UTF-8?q?=ED=95=99=EC=8A=B5=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InferenceResultRepositoryImpl.java | 148 +++++++++--------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index a1d7afb6..0fc5874a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -17,15 +17,20 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC private final JPAQueryFactory queryFactory; - @PersistenceContext private final EntityManager em; + @PersistenceContext + private final EntityManager em; - /** tb_map_sheet_anal_data_inference */ + /** + * tb_map_sheet_anal_data_inference + */ private final QMapSheetAnalDataInferenceEntity inferenceEntity = - QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; + QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; - /** tb_map_sheet_anal_data_inference_geom */ + /** + * tb_map_sheet_anal_data_inference_geom + */ private final QMapSheetAnalDataInferenceGeomEntity inferenceGeomEntity = - QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; + QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; // =============================== // Upsert (Native only) @@ -34,33 +39,28 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC @Override public int upsertGroupsFromMapSheetAnal() { String sql = - """ - INSERT INTO tb_map_sheet_anal_inference ( - compare_yyyy, - target_yyyy, - anal_map_sheet, - stage, - anal_title - ) - SELECT - r.input1 AS compare_yyyy, - r.input2 AS target_yyyy, - r.map_id AS anal_map_sheet, - r.stage, - CONCAT(r.stage ,'_', r.input1 ,'_', r.input2 ,'_', r.map_id) as anal_title - FROM inference_results r - GROUP BY r.stage, r.input1, r.input2, r.map_id - ON CONFLICT (compare_yyyy, target_yyyy, anal_map_sheet, stage) - DO UPDATE SET - updated_dttm = now() + """ + + INSERT INTO tb_map_sheet_anal_inference ( + compare_yyyy, + target_yyyy, + stage, + anal_title + ) + SELECT + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + r.stage, + CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title + FROM inference_results r + GROUP BY r.stage, r.input1, r.input2; """; return em.createNativeQuery(sql).executeUpdate(); } /** - * inference_results 테이블을 기준으로 분석 데이터 단위(stage, compare_yyyy, target_yyyy, map_sheet_num)를 - * 생성/갱신한다. + * inference_results 테이블을 기준으로 분석 데이터 단위(stage, compare_yyyy, target_yyyy, map_sheet_num)를 생성/갱신한다. * *

- 최초 생성 시 file_created_yn = false - detecting_cnt는 inference_results 건수 기준 * @@ -70,7 +70,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC public int upsertGroupsFromInferenceResults() { String sql = - """ + """ INSERT INTO tb_map_sheet_anal_data_inference ( stage, compare_yyyy, @@ -113,7 +113,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC public int upsertGeomsFromInferenceResults() { String sql = - """ + """ INSERT INTO tb_map_sheet_anal_data_inference_geom ( uuid, stage, cd_prob, compare_yyyy, target_yyyy, map_sheet_num, class_before_cd, class_before_prob, class_after_cd, class_after_prob, @@ -188,12 +188,12 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC @Override public List findPendingDataUids(int limit) { return queryFactory - .select(inferenceEntity.id) - .from(inferenceEntity) - .where(inferenceEntity.fileCreatedYn.isFalse().or(inferenceEntity.fileCreatedYn.isNull())) - .orderBy(inferenceEntity.id.asc()) - .limit(limit) - .fetch(); + .select(inferenceEntity.id) + .from(inferenceEntity) + .where(inferenceEntity.fileCreatedYn.isFalse().or(inferenceEntity.fileCreatedYn.isNull())) + .orderBy(inferenceEntity.id.asc()) + .limit(limit) + .fetch(); } // =============================== @@ -212,13 +212,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceEntity) - .set(inferenceEntity.fileCreatedYn, false) - .set(inferenceEntity.fileCreatedDttm, (ZonedDateTime) null) - .set(inferenceEntity.updatedDttm, now) - .where(inferenceEntity.id.eq(dataUid)) - .execute(); + queryFactory + .update(inferenceEntity) + .set(inferenceEntity.fileCreatedYn, false) + .set(inferenceEntity.fileCreatedDttm, (ZonedDateTime) null) + .set(inferenceEntity.updatedDttm, now) + .where(inferenceEntity.id.eq(dataUid)) + .execute(); } /** @@ -231,13 +231,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceEntity) - .set(inferenceEntity.fileCreatedYn, true) - .set(inferenceEntity.fileCreatedDttm, now) - .set(inferenceEntity.updatedDttm, now) - .where(inferenceEntity.id.eq(dataUid)) - .execute(); + queryFactory + .update(inferenceEntity) + .set(inferenceEntity.fileCreatedYn, true) + .set(inferenceEntity.fileCreatedDttm, now) + .set(inferenceEntity.updatedDttm, now) + .where(inferenceEntity.id.eq(dataUid)) + .execute(); } /** @@ -250,13 +250,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceGeomEntity) - .set(inferenceGeomEntity.fileCreatedYn, false) - .set(inferenceGeomEntity.fileCreatedDttm, (ZonedDateTime) null) - .set(inferenceGeomEntity.updatedDttm, now) - .where(inferenceGeomEntity.dataUid.eq(dataUid)) - .execute(); + queryFactory + .update(inferenceGeomEntity) + .set(inferenceGeomEntity.fileCreatedYn, false) + .set(inferenceGeomEntity.fileCreatedDttm, (ZonedDateTime) null) + .set(inferenceGeomEntity.updatedDttm, now) + .where(inferenceGeomEntity.dataUid.eq(dataUid)) + .execute(); } /** @@ -274,13 +274,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceGeomEntity) - .set(inferenceGeomEntity.fileCreatedYn, true) - .set(inferenceGeomEntity.fileCreatedDttm, now) - .set(inferenceGeomEntity.updatedDttm, now) - .where(inferenceGeomEntity.geoUid.in(geoUids)) - .execute(); + queryFactory + .update(inferenceGeomEntity) + .set(inferenceGeomEntity.fileCreatedYn, true) + .set(inferenceGeomEntity.fileCreatedDttm, now) + .set(inferenceGeomEntity.updatedDttm, now) + .where(inferenceGeomEntity.geoUid.in(geoUids)) + .execute(); } // =============================== @@ -294,18 +294,18 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC */ @Override public List findGeomEntitiesByDataUid( - Long dataUid, int limit) { + Long dataUid, int limit) { return queryFactory - .selectFrom(inferenceGeomEntity) - .where( - inferenceGeomEntity.dataUid.eq(dataUid), - inferenceGeomEntity.geom.isNotNull(), - inferenceGeomEntity - .fileCreatedYn - .isFalse() - .or(inferenceGeomEntity.fileCreatedYn.isNull())) - .orderBy(inferenceGeomEntity.geoUid.asc()) - .limit(limit) - .fetch(); + .selectFrom(inferenceGeomEntity) + .where( + inferenceGeomEntity.dataUid.eq(dataUid), + inferenceGeomEntity.geom.isNotNull(), + inferenceGeomEntity + .fileCreatedYn + .isFalse() + .or(inferenceGeomEntity.fileCreatedYn.isNull())) + .orderBy(inferenceGeomEntity.geoUid.asc()) + .limit(limit) + .fetch(); } } From 829cb510e3ab429ecacbbe6218a23bad04180b16 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 10:17:24 +0900 Subject: [PATCH 34/70] =?UTF-8?q?=20spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelWorkerApiController.java | 39 +++--- .../entity/MapSheetAnalInferenceEntity.java | 10 +- .../InferenceResultRepositoryImpl.java | 116 +++++++++--------- 3 files changed, 81 insertions(+), 84 deletions(-) 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 5ca52652..17d8844e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -32,29 +32,26 @@ public class LabelWorkerApiController { @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) - }) + 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) + }) @GetMapping("/label-work-mng-list") public ApiResponseDto> labelWorkMngList( - @Schema(description = "변화탐지년도", example = "2024") - Integer detectYyyy, - @Schema(description = "시작일", example = "20260101") - String strtDttm, - @Schema(description = "종료일", example = "20261201") - String endDttm, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") - int page, - @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") - int size) { + @Schema(description = "변화탐지년도", example = "2024") Integer detectYyyy, + @Schema(description = "시작일", example = "20260101") String strtDttm, + @Schema(description = "종료일", example = "20261201") String endDttm, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size) { LabelWorkDto.LabelWorkMngSearchReq searchReq = new LabelWorkMngSearchReq(); searchReq.setDetectYyyy(detectYyyy); searchReq.setStrtDttm(strtDttm); diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.java index 5a68209e..195721e1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MapSheetAnalInferenceEntity.java @@ -25,8 +25,13 @@ import org.hibernate.type.SqlTypes; public class MapSheetAnalInferenceEntity { @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_sheet_anal_inference_id_gen") - @SequenceGenerator(name = "tb_map_sheet_anal_inference_id_gen", sequenceName = "tb_map_sheet_anal_inference_uid", allocationSize = 1) + @GeneratedValue( + strategy = GenerationType.SEQUENCE, + generator = "tb_map_sheet_anal_inference_id_gen") + @SequenceGenerator( + name = "tb_map_sheet_anal_inference_id_gen", + sequenceName = "tb_map_sheet_anal_inference_uid", + allocationSize = 1) @Column(name = "anal_uid", nullable = false) private Long id; @@ -144,5 +149,4 @@ public class MapSheetAnalInferenceEntity { @Column(name = "stage") private Integer stage; - } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 0fc5874a..5c94e12f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -17,20 +17,15 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC private final JPAQueryFactory queryFactory; - @PersistenceContext - private final EntityManager em; + @PersistenceContext private final EntityManager em; - /** - * tb_map_sheet_anal_data_inference - */ + /** tb_map_sheet_anal_data_inference */ private final QMapSheetAnalDataInferenceEntity inferenceEntity = - QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; + QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity; - /** - * tb_map_sheet_anal_data_inference_geom - */ + /** tb_map_sheet_anal_data_inference_geom */ private final QMapSheetAnalDataInferenceGeomEntity inferenceGeomEntity = - QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; + QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; // =============================== // Upsert (Native only) @@ -39,7 +34,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC @Override public int upsertGroupsFromMapSheetAnal() { String sql = - """ + """ INSERT INTO tb_map_sheet_anal_inference ( compare_yyyy, @@ -60,7 +55,8 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC } /** - * inference_results 테이블을 기준으로 분석 데이터 단위(stage, compare_yyyy, target_yyyy, map_sheet_num)를 생성/갱신한다. + * inference_results 테이블을 기준으로 분석 데이터 단위(stage, compare_yyyy, target_yyyy, map_sheet_num)를 + * 생성/갱신한다. * *

- 최초 생성 시 file_created_yn = false - detecting_cnt는 inference_results 건수 기준 * @@ -70,7 +66,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC public int upsertGroupsFromInferenceResults() { String sql = - """ + """ INSERT INTO tb_map_sheet_anal_data_inference ( stage, compare_yyyy, @@ -113,7 +109,7 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC public int upsertGeomsFromInferenceResults() { String sql = - """ + """ INSERT INTO tb_map_sheet_anal_data_inference_geom ( uuid, stage, cd_prob, compare_yyyy, target_yyyy, map_sheet_num, class_before_cd, class_before_prob, class_after_cd, class_after_prob, @@ -188,12 +184,12 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC @Override public List findPendingDataUids(int limit) { return queryFactory - .select(inferenceEntity.id) - .from(inferenceEntity) - .where(inferenceEntity.fileCreatedYn.isFalse().or(inferenceEntity.fileCreatedYn.isNull())) - .orderBy(inferenceEntity.id.asc()) - .limit(limit) - .fetch(); + .select(inferenceEntity.id) + .from(inferenceEntity) + .where(inferenceEntity.fileCreatedYn.isFalse().or(inferenceEntity.fileCreatedYn.isNull())) + .orderBy(inferenceEntity.id.asc()) + .limit(limit) + .fetch(); } // =============================== @@ -212,13 +208,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceEntity) - .set(inferenceEntity.fileCreatedYn, false) - .set(inferenceEntity.fileCreatedDttm, (ZonedDateTime) null) - .set(inferenceEntity.updatedDttm, now) - .where(inferenceEntity.id.eq(dataUid)) - .execute(); + queryFactory + .update(inferenceEntity) + .set(inferenceEntity.fileCreatedYn, false) + .set(inferenceEntity.fileCreatedDttm, (ZonedDateTime) null) + .set(inferenceEntity.updatedDttm, now) + .where(inferenceEntity.id.eq(dataUid)) + .execute(); } /** @@ -231,13 +227,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceEntity) - .set(inferenceEntity.fileCreatedYn, true) - .set(inferenceEntity.fileCreatedDttm, now) - .set(inferenceEntity.updatedDttm, now) - .where(inferenceEntity.id.eq(dataUid)) - .execute(); + queryFactory + .update(inferenceEntity) + .set(inferenceEntity.fileCreatedYn, true) + .set(inferenceEntity.fileCreatedDttm, now) + .set(inferenceEntity.updatedDttm, now) + .where(inferenceEntity.id.eq(dataUid)) + .execute(); } /** @@ -250,13 +246,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceGeomEntity) - .set(inferenceGeomEntity.fileCreatedYn, false) - .set(inferenceGeomEntity.fileCreatedDttm, (ZonedDateTime) null) - .set(inferenceGeomEntity.updatedDttm, now) - .where(inferenceGeomEntity.dataUid.eq(dataUid)) - .execute(); + queryFactory + .update(inferenceGeomEntity) + .set(inferenceGeomEntity.fileCreatedYn, false) + .set(inferenceGeomEntity.fileCreatedDttm, (ZonedDateTime) null) + .set(inferenceGeomEntity.updatedDttm, now) + .where(inferenceGeomEntity.dataUid.eq(dataUid)) + .execute(); } /** @@ -274,13 +270,13 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC ZonedDateTime now = ZonedDateTime.now(); return (int) - queryFactory - .update(inferenceGeomEntity) - .set(inferenceGeomEntity.fileCreatedYn, true) - .set(inferenceGeomEntity.fileCreatedDttm, now) - .set(inferenceGeomEntity.updatedDttm, now) - .where(inferenceGeomEntity.geoUid.in(geoUids)) - .execute(); + queryFactory + .update(inferenceGeomEntity) + .set(inferenceGeomEntity.fileCreatedYn, true) + .set(inferenceGeomEntity.fileCreatedDttm, now) + .set(inferenceGeomEntity.updatedDttm, now) + .where(inferenceGeomEntity.geoUid.in(geoUids)) + .execute(); } // =============================== @@ -294,18 +290,18 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC */ @Override public List findGeomEntitiesByDataUid( - Long dataUid, int limit) { + Long dataUid, int limit) { return queryFactory - .selectFrom(inferenceGeomEntity) - .where( - inferenceGeomEntity.dataUid.eq(dataUid), - inferenceGeomEntity.geom.isNotNull(), - inferenceGeomEntity - .fileCreatedYn - .isFalse() - .or(inferenceGeomEntity.fileCreatedYn.isNull())) - .orderBy(inferenceGeomEntity.geoUid.asc()) - .limit(limit) - .fetch(); + .selectFrom(inferenceGeomEntity) + .where( + inferenceGeomEntity.dataUid.eq(dataUid), + inferenceGeomEntity.geom.isNotNull(), + inferenceGeomEntity + .fileCreatedYn + .isFalse() + .or(inferenceGeomEntity.fileCreatedYn.isNull())) + .orderBy(inferenceGeomEntity.geoUid.asc()) + .limit(limit) + .fetch(); } } From 6a77d0f4474cfa13febe127a36f51eef7e5e0408 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 10:17:52 +0900 Subject: [PATCH 35/70] =?UTF-8?q?=20spotless=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InferenceResultRepositoryImpl.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 5c94e12f..1876e53c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -35,20 +35,19 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC public int upsertGroupsFromMapSheetAnal() { String sql = """ - - INSERT INTO tb_map_sheet_anal_inference ( - compare_yyyy, - target_yyyy, - stage, - anal_title - ) - SELECT - r.input1 AS compare_yyyy, - r.input2 AS target_yyyy, - r.stage, - CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title - FROM inference_results r - GROUP BY r.stage, r.input1, r.input2; + INSERT INTO tb_map_sheet_anal_inference ( + compare_yyyy, + target_yyyy, + stage, + anal_title + ) + SELECT + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + r.stage, + CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title + FROM inference_results r + GROUP BY r.stage, r.input1, r.input2; """; return em.createNativeQuery(sql).executeUpdate(); From 88cf9a44872be805db8d9b075beff104e82fb756 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 10:37:17 +0900 Subject: [PATCH 36/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=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 --- .../kamco/cd/kamcoback/label/LabelWorkerApiController.java | 7 ++++--- .../Inference/InferenceResultRepositoryImpl.java | 6 ++++-- 2 files changed, 8 insertions(+), 5 deletions(-) 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 17d8844e..d07083e1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -45,9 +45,10 @@ public class LabelWorkerApiController { }) @GetMapping("/label-work-mng-list") public ApiResponseDto> labelWorkMngList( - @Schema(description = "변화탐지년도", example = "2024") Integer detectYyyy, - @Schema(description = "시작일", example = "20260101") String strtDttm, - @Schema(description = "종료일", example = "20261201") String endDttm, + @Parameter(description = "변화탐지년도", example = "2024") @RequestParam(required = false) + Integer detectYyyy, + @Parameter(description = "시작일", example = "20260101") @RequestParam String strtDttm, + @Parameter(description = "종료일", example = "20261201") @RequestParam String endDttm, @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") int page, @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 1876e53c..8e1804b9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -39,13 +39,15 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC compare_yyyy, target_yyyy, stage, - anal_title + anal_title, + detecting_cnt ) SELECT r.input1 AS compare_yyyy, r.input2 AS target_yyyy, r.stage, - CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title + CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title, + count(*) FROM inference_results r GROUP BY r.stage, r.input1, r.input2; """; From 67eb99e243d74684d5ff0adaeb902f369415dbeb Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:41:50 +0900 Subject: [PATCH 37/70] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 18 ++++++---- .../kamcoback/label/dto/WorkerStatsDto.java | 12 +++++++ .../label/service/LabelAllocateService.java | 34 +++++++++++++----- .../core/LabelAllocateCoreService.java | 5 ++- .../label/LabelAllocateRepositoryCustom.java | 2 +- .../label/LabelAllocateRepositoryImpl.java | 36 ++++++++++--------- 6 files changed, 71 insertions(+), 36 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 6869b636..7870a7fc 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -63,7 +63,7 @@ public class LabelAllocateApiController { }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam + @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) Long analUid, @Parameter( description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", @@ -74,11 +74,9 @@ public class LabelAllocateApiController { defaultValue = "LABELER")) @RequestParam(required = false) String type, - @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchName, - @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") + @Parameter(description = "검색어 (작업자 이름 또는 사번으로 검색, 부분 일치)", example = "김라벨") @RequestParam(required = false) - String searchEmployeeNo, + String search, @Parameter( description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", example = "REMAINING_DESC", @@ -92,14 +90,20 @@ public class LabelAllocateApiController { }, defaultValue = "NAME_ASC")) @RequestParam(required = false) - String sort) { + String sort, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") + @RequestParam(defaultValue = "0") + Integer page, + @Parameter(description = "페이지 크기", example = "20") + @RequestParam(defaultValue = "20") + Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( labelAllocateService.getWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sort)); + analUid, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index efca81b2..f879f0ee 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -113,5 +113,17 @@ public class WorkerStatsDto { @Schema(description = "작업자 목록") private List workers; + + @Schema(description = "현재 페이지 번호 (0부터 시작)") + private Integer currentPage; + + @Schema(description = "페이지 크기") + private Integer pageSize; + + @Schema(description = "전체 데이터 수") + private Long totalElements; + + @Schema(description = "전체 페이지 수") + private Integer totalPages; } } 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 c919a5eb..1954a294 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 @@ -107,21 +107,23 @@ public class LabelAllocateService { } /** - * 작업자 목록 및 3일치 통계 조회 + * 작업자 통계 조회 * * @param analUid 분석 ID * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param searchName 이름 검색 - * @param searchEmployeeNo 사번 검색 + * @param search 검색어 (이름 또는 사번) * @param sortType 정렬 조건 + * @param page 페이지 번호 (0부터 시작) + * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( Long analUid, String workerType, - String searchName, - String searchEmployeeNo, - String sortType) { + String search, + String sortType, + Integer page, + Integer size) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); @@ -129,7 +131,7 @@ public class LabelAllocateService { // 작업자 통계 조회 List workers = labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); @@ -162,7 +164,23 @@ public class LabelAllocateService { } } - return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); + // 페이징 처리 + long totalElements = workers.size(); + int totalPages = (int) Math.ceil((double) totalElements / size); + int fromIndex = page * size; + int toIndex = Math.min(fromIndex + size, workers.size()); + + List pagedWorkers = + (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); + + return WorkerListResponse.builder() + .progressInfo(progressInfo) + .workers(pagedWorkers) + .currentPage(page) + .pageSize(size) + .totalElements(totalElements) + .totalPages(totalPages) + .build(); } public InferenceDetail findInferenceDetail( 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 3e16eae5..c7469db6 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 @@ -57,11 +57,10 @@ public class LabelAllocateCoreService { public List findWorkerStatistics( Long analUid, String workerType, - String searchName, - String searchEmployeeNo, + String search, String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, searchName, searchEmployeeNo, sortType); + analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long 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 22988c98..8fb3d96a 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 @@ -34,7 +34,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String searchName, String searchEmployeeNo, String sortType); + Long analUid, String workerType, String search, 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 9e52da12..9f92dd3f 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 @@ -206,8 +206,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto public List findWorkerStatistics( Long analUid, String workerType, - String searchName, - String searchEmployeeNo, + String search, String sortType) { // 작업자 유형에 따른 필드 선택 @@ -221,14 +220,11 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.isNotNull() : labelingAssignmentEntity.workerUid.isNotNull(); - // 검색 조건 + // 검색 조건 (이름 또는 사번으로 검색) BooleanExpression searchCondition = null; - if (searchName != null && !searchName.isEmpty()) { - searchCondition = memberEntity.name.contains(searchName); - } - if (searchEmployeeNo != null && !searchEmployeeNo.isEmpty()) { - BooleanExpression empCondition = memberEntity.employeeNo.contains(searchEmployeeNo); - searchCondition = searchCondition == null ? empCondition : searchCondition.and(empCondition); + if (search != null && !search.isEmpty()) { + searchCondition = memberEntity.name.contains(search) + .or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 @@ -258,6 +254,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .sum(); // 기본 통계 조회 쿼리 + BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + var baseQuery = queryFactory .select( @@ -274,7 +272,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto "REVIEWER".equals(workerType) ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) - .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) + .where(analUidCondition, workerCondition, searchCondition) .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 @@ -321,12 +319,14 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { + BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + // 전체 배정 건수 Long totalAssigned = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where(labelingAssignmentEntity.analUid.eq(analUid)) + .where(analUidCondition) .fetchOne(); // 완료 + 스킵 건수 @@ -335,7 +335,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.workState.in("DONE", "SKIP")) .fetchOne(); @@ -345,7 +345,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.workerUid.countDistinct()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); @@ -355,7 +355,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.workerUid.isNotNull(), labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) .fetchOne(); @@ -366,7 +366,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.inspectorUid.countDistinct()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); @@ -376,7 +376,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull(), labelingAssignmentEntity.workState.notIn("DONE")) .fetchOne(); @@ -416,12 +416,14 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.eq(workerId) : labelingAssignmentEntity.workerUid.eq(workerId); + BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + Long count = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analUid), + analUidCondition, workerCondition, labelingAssignmentEntity.workState.in( LabelState.DONE.getId(), LabelState.SKIP.getId()), From b4ae6e148dcbf54e75ab13d9acc41480480349b4 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 11:46:21 +0900 Subject: [PATCH 38/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EA=B2=80=EC=88=98=EC=9E=90=20insert=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 32 ++--- .../kamcoback/label/dto/LabelAllocateDto.java | 31 +--- .../label/dto/LabelInspectorDto.java | 22 +++ .../label/service/LabelAllocateService.java | 62 ++++---- .../core/LabelAllocateCoreService.java | 36 +++-- .../entity/LabelingInspectorEntity.java | 33 +++++ .../label/LabelAllocateRepositoryCustom.java | 23 ++- .../label/LabelAllocateRepositoryImpl.java | 135 ++++++++---------- 8 files changed, 190 insertions(+), 184 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java 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 6869b636..3f2133f7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -122,8 +122,6 @@ public class LabelAllocateApiController { return ApiResponseDto.okObject( labelAllocateService.allocateAsc( - dto.getLabelerAutoType(), - dto.getInspectorAutoType(), dto.getStage(), dto.getLabelers(), dto.getInspectors(), @@ -140,13 +138,13 @@ public class LabelAllocateApiController { }) @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 = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid) { + return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(uuid)); } @Operation( @@ -154,14 +152,14 @@ public class LabelAllocateApiController { 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", required = true) String userId, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid) { + return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, uuid)); } @Operation(summary = "작업현황 관리 > 상세 > 작업 이관", description = "작업현황 관리 > 상세 > 작업 이관") 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 c0dd75a6..f5a060a8 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,22 +87,13 @@ public class LabelAllocateDto { @AllArgsConstructor public static class AllocateDto { - // @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 labelerAutoType; - - @Schema(description = "검수자 자동/수동여부(AUTO/MANUAL)", example = "AUTO") - private String inspectorAutoType; - - @Schema(description = "회차", example = "4") + @Schema(description = "회차", example = "4", required = true) private Integer stage; @Schema( @@ -130,22 +121,12 @@ public class LabelAllocateDto { description = "검수자 할당 목록", example = """ - [ - { - "inspectorUid": "K20251216001", - "userCount": 1000 - }, - { - "inspectorUid": "01022225555", - "userCount": 340 - }, - { - "inspectorUid": "K20251212001", - "userCount": 500 - } + ["K20251216001", + "01022225555", + "K20251212001" ] """) - private List inspectors; + private List inspectors; } @Getter @@ -273,6 +254,6 @@ public class LabelAllocateDto { public static class AllocateInfoDto { private Long geoUid; - private String mapSheetNum; + private Long mapSheetNum; } } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java new file mode 100644 index 00000000..89fd8ef7 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/LabelInspectorDto.java @@ -0,0 +1,22 @@ +package com.kamco.cd.kamcoback.label.dto; + +import java.time.ZonedDateTime; +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +public class LabelInspectorDto { + + @Getter + @Setter + @AllArgsConstructor + public static class Basic { + + private UUID operatorUid; + private Long analUid; + private String inspectorUid; + private ZonedDateTime createdDttm; + private ZonedDateTime updatedDttm; + } +} 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 c919a5eb..8a994a1f 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 @@ -6,7 +6,6 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; 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; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; @@ -16,7 +15,6 @@ import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import java.time.LocalDate; import java.util.List; import java.util.Objects; -import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -38,19 +36,15 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param labelerAutoType 라벨러 자동/수동 배정 타입 - * @param inspectorAutoType 검수자 자동/수동 배정 타입 * @param stage 회차 * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional public ApiResponseDto.ResponseObj allocateAsc( - String labelerAutoType, - String inspectorAutoType, Integer stage, List targetUsers, - List targetInspectors, + List targetInspectors, Integer compareYyyy, Integer targetYyyy) { Long lastId = null; @@ -70,35 +64,45 @@ public class LabelAllocateService { List allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + + // MapSheetAnalInferenceEntity analUid 가져오기 + Long analUid = + labelAllocateCoreService.findMapSheetAnalInferenceUid(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(), compareYyyy, targetYyyy, stage); + labelAllocateCoreService.assignOwner(sub, target.getUserId(), analUid); index = end; } // 검수자에게 userCount명 만큼 할당 - List list = - labelAllocateCoreService.findAssignedLabelerList(compareYyyy, targetYyyy, stage); - int from = 0; + List list = labelAllocateCoreService.findAssignedLabelerList(analUid); - for (TargetInspector inspector : targetInspectors) { - int to = Math.min(from + inspector.getUserCount(), list.size()); - - if (from >= to) { - break; - } - - List assignmentUids = - list.subList(from, to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); - - labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid()); - - from = to; + for (String inspector : targetInspectors) { + labelAllocateCoreService.insertInspector(analUid, inspector); } + // int from = 0; + // for (TargetInspector inspector : targetInspectors) { + // int to = Math.min(from + inspector.getUserCount(), list.size()); + // + // if (from >= to) { + // break; + // } + // + // List assignmentUids = + // list.subList(from, + // to).stream().map(LabelAllocateDto.Basic::getAssignmentUid).toList(); + // + // labelAllocateCoreService.assignInspectorBulk(assignmentUids, + // inspector.getInspectorUid()); + // + // from = to; + // } + return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "배정이 완료되었습니다."); } @@ -165,9 +169,8 @@ public class LabelAllocateService { return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); } - public InferenceDetail findInferenceDetail( - Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateCoreService.findInferenceDetail(compareYyyy, targetYyyy, stage); + public InferenceDetail findInferenceDetail(String uuid) { + return labelAllocateCoreService.findInferenceDetail(uuid); } public ApiResponseDto.ResponseObj allocateMove( @@ -199,8 +202,7 @@ public class LabelAllocateService { return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } - public LabelerDetail findLabelerDetail( - String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateCoreService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); + public LabelerDetail findLabelerDetail(String userId, String uuid) { + return labelAllocateCoreService.findLabelerDetail(userId, uuid); } } 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 3e16eae5..5fd49cf5 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 @@ -26,18 +26,12 @@ public class LabelAllocateCoreService { return labelAllocateRepository.fetchNextIds(lastId, batchSize, compareYyyy, targetYyyy, stage); } - public void assignOwner( - List ids, - String userId, - Integer compareYyyy, - Integer targetYyyy, - Integer stage) { - labelAllocateRepository.assignOwner(ids, userId, compareYyyy, targetYyyy, stage); + public void assignOwner(List ids, String userId, Long analUid) { + labelAllocateRepository.assignOwner(ids, userId, analUid); } - public List findAssignedLabelerList( - Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateRepository.findAssignedLabelerList(compareYyyy, targetYyyy, stage).stream() + public List findAssignedLabelerList(Long analUid) { + return labelAllocateRepository.findAssignedLabelerList(analUid).stream() .map(LabelingAssignmentEntity::toDto) .toList(); } @@ -77,13 +71,8 @@ public class LabelAllocateCoreService { labelAllocateRepository.assignInspectorBulk(assignmentUids, inspectorUid); } - 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 InferenceDetail findInferenceDetail(String uuid) { + return labelAllocateRepository.findInferenceDetail(uuid); } public List fetchNextMoveIds( @@ -96,8 +85,15 @@ public class LabelAllocateCoreService { labelAllocateRepository.assignOwnerMove(sub, userId); } - public LabelerDetail findLabelerDetail( - String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { - return labelAllocateRepository.findLabelerDetail(userId, compareYyyy, targetYyyy, stage); + public LabelerDetail findLabelerDetail(String userId, String uuid) { + return labelAllocateRepository.findLabelerDetail(userId, uuid); + } + + public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { + return labelAllocateRepository.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); + } + + public void insertInspector(Long analUid, String inspector) { + labelAllocateRepository.insertInspector(analUid, inspector); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java new file mode 100644 index 00000000..5da0a685 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingInspectorEntity.java @@ -0,0 +1,33 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.label.dto.LabelInspectorDto; +import com.kamco.cd.kamcoback.postgres.CommonDateEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.util.UUID; + +@Entity +@Table(name = "tb_labeling_inspector") +public class LabelingInspectorEntity extends CommonDateEntity { + + @Id + @Column(name = "operator_uid") + private UUID operatorUid; + + @Column(name = "anal_uid") + private Long analUid; + + @Column(name = "inspector_uid") + private String inspectorUid; + + public LabelInspectorDto.Basic toDto() { + return new LabelInspectorDto.Basic( + this.operatorUid, + this.analUid, + this.inspectorUid, + super.getCreatedDate(), + super.getModifiedDate()); + } +} 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 22988c98..72a1534e 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 @@ -16,15 +16,9 @@ public interface LabelAllocateRepositoryCustom { 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, Long analUid); - List findAssignedLabelerList( - Integer compareYyyy, Integer targetYyyy, Integer stage); + List findAssignedLabelerList(Long analUid); Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy); @@ -44,15 +38,16 @@ public interface LabelAllocateRepositoryCustom { void assignInspectorBulk(List assignmentUids, String inspectorUid); - InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage); + InferenceDetail findInferenceDetail(String uuid); - public List fetchNextMoveIds( + 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, String uuid); + + Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage); + + void insertInspector(Long analUid, String inspector); } 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 9e52da12..37f54a38 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,9 +1,10 @@ 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.QLabelingInspectorEntity.labelingInspectorEntity; 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.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; @@ -16,8 +17,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.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; @@ -73,27 +73,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public void assignOwner( - List ids, - String userId, - Integer compareYyyy, - Integer targetYyyy, - Integer stage) { + public void assignOwner(List ids, String userId, Long analUid) { // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) .fetchOne(); if (Objects.isNull(analEntity)) { - throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); } // data_geom 테이블에 label state 를 ASSIGNED 로 update @@ -125,7 +115,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .setParameter(3, userId) .setParameter(4, LabelState.ASSIGNED.getId()) .setParameter(5, info.getMapSheetNum()) - .setParameter(6, analEntity.getAnalUid()) + .setParameter(6, analEntity.getId()) .executeUpdate(); } @@ -134,28 +124,22 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public List findAssignedLabelerList( - Integer compareYyyy, Integer targetYyyy, Integer stage) { + public List findAssignedLabelerList(Long analUid) { // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) .fetchOne(); if (Objects.isNull(analEntity)) { - throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); + throw new EntityNotFoundException("mapSheetAnalInferenceEntity not found for analUid: "); } return queryFactory .selectFrom(labelingAssignmentEntity) .where( - labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid()), + labelingAssignmentEntity.analUid.eq(analEntity.getId()), labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), labelingAssignmentEntity.inspectorUid.isNull()) .orderBy(labelingAssignmentEntity.workerUid.asc()) @@ -443,20 +427,18 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public InferenceDetail findInferenceDetail( - Integer compareYyyy, Integer targetYyyy, Integer stage) { + public InferenceDetail findInferenceDetail(String uuid) { // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) .fetchOne(); + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); + } + return queryFactory .select( Projections.constructor( @@ -470,7 +452,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .from(mapSheetAnalEntity) .innerJoin(labelingAssignmentEntity) .on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid)) - .where(mapSheetAnalEntity.id.eq(analEntity.getAnalUid())) + .where(mapSheetAnalEntity.id.eq(analEntity.getId())) .groupBy( mapSheetAnalEntity.analTitle, mapSheetAnalEntity.gukyuinApplyDttm, @@ -498,30 +480,6 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .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) { @@ -535,8 +493,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public LabelerDetail findLabelerDetail( - String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) { + public LabelerDetail findLabelerDetail(String userId, String uuid) { NumberExpression assignedCnt = new CaseBuilder() .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) @@ -570,19 +527,14 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto completeCnt)); // analUid로 분석 정보 조회 - MapSheetAnalDataInferenceEntity analEntity = + MapSheetAnalInferenceEntity analEntity = queryFactory - .selectFrom(mapSheetAnalDataInferenceEntity) - .where( - mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalDataInferenceEntity.stage.eq(stage)) - .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc()) - .limit(1) + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) .fetchOne(); if (Objects.isNull(analEntity)) { - throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: "); + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); } QMemberEntity worker = QMemberEntity.memberEntity; @@ -606,11 +558,38 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .innerJoin(labelingAssignmentEntity) .on( worker.employeeNo.eq(labelingAssignmentEntity.workerUid), - labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid())) + labelingAssignmentEntity.analUid.eq(analEntity.getId())) .leftJoin(inspector) .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) .where(worker.employeeNo.eq(userId)) .groupBy(worker.userRole, worker.name, worker.employeeNo) .fetchOne(); } + + @Override + public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { + return queryFactory + .select(mapSheetAnalInferenceEntity.id) + .from(mapSheetAnalInferenceEntity) + .where( + mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalInferenceEntity.stage.eq(stage)) + .fetchOne(); + } + + @Override + public void insertInspector(Long analUid, String inspector) { + queryFactory + .insert(labelingInspectorEntity) + .columns( + labelingInspectorEntity.operatorUid, + labelingInspectorEntity.analUid, + labelingInspectorEntity.inspectorUid) + .values(UUID.randomUUID(), analUid, inspector) + .execute(); + + em.flush(); + em.clear(); + } } From 96f5a87b1a7173288f9625c18b8e393c7a9d7e6e Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:33:48 +0900 Subject: [PATCH 39/70] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=EA=B0=92=20=EC=B6=94=EA=B0=80=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 2 + .../kamcoback/label/dto/WorkerStatsDto.java | 87 +++++++++++++++++-- .../label/LabelAllocateRepositoryImpl.java | 6 ++ 3 files changed, 86 insertions(+), 9 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 1395f42b..4e0f9373 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -85,6 +85,8 @@ public class LabelAllocateApiController { allowableValues = { "REMAINING_DESC", "REMAINING_ASC", + "COMPLETED_DESC", + "COMPLETED_ASC", "NAME_ASC", "NAME_DESC" }, diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index f879f0ee..e95dd346 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -10,6 +10,27 @@ import lombok.Setter; public class WorkerStatsDto { + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "프로젝트 기본 정보 (상단 표시용)") + public static class ProjectInfo { + + @Schema(description = "변화탐지년도 (예: 2026-2025)") + private String detectionYear; + + @Schema(description = "회차 (예: 8)") + private String round; + + @Schema(description = "국유인 반영일 (예: 2026-03-31)") + private String reflectionDate; + + @Schema(description = "작업 시작일 (예: 2026-04-06)") + private String startDate; + } + @Getter @Setter @Builder @@ -75,29 +96,74 @@ public class WorkerStatsDto { @Schema(description = "작업 진행 현황 정보") public static class WorkProgressInfo { + // === 라벨링 관련 === @Schema(description = "라벨링 진행률 (완료건+스킵건)/배정건") private Double labelingProgressRate; - @Schema(description = "작업 상태 (진행중/종료)") - private String workStatus; + @Schema(description = "라벨링 작업 상태 (진행중/완료)") + private String labelingStatus; - @Schema(description = "진행률 수치 (완료+스킵)") - private Long completedCount; + @Schema(description = "라벨링 전체 배정 건수") + private Long labelingTotalCount; - @Schema(description = "전체 배정 건수") - private Long totalAssignedCount; + @Schema(description = "라벨링 완료 건수 (LABEL_FIN + TEST_ING + DONE)") + private Long labelingCompletedCount; + + @Schema(description = "라벨링 스킵 건수 (SKIP)") + private Long labelingSkipCount; + + @Schema(description = "라벨링 남은 작업 건수") + private Long labelingRemainingCount; @Schema(description = "투입된 라벨러 수") private Long labelerCount; - @Schema(description = "남은 라벨링 작업 데이터 수") - private Long remainingLabelCount; + // === 검수(Inspection) 관련 (신규 추가) === + @Schema(description = "검수 진행률 (완료건/대상건)") + private Double inspectionProgressRate; + + @Schema(description = "검수 작업 상태 (진행중/완료)") + private String inspectionStatus; + + @Schema(description = "검수 전체 대상 건수") + private Long inspectionTotalCount; + + @Schema(description = "검수 완료 건수 (DONE)") + private Long inspectionCompletedCount; + + @Schema(description = "검수 제외 건수 (라벨링 스킵과 동일)") + private Long inspectionSkipCount; + + @Schema(description = "검수 남은 작업 건수") + private Long inspectionRemainingCount; @Schema(description = "투입된 검수자 수") private Long inspectorCount; - @Schema(description = "남은 검수 작업 데이터 수") + // === 레거시 호환 필드 (Deprecated) === + @Deprecated + @Schema(description = "[Deprecated] labelingProgressRate 사용 권장") + private Double progressRate; + + @Deprecated + @Schema(description = "[Deprecated] labelingTotalCount 사용 권장") + private Long totalAssignedCount; + + @Deprecated + @Schema(description = "[Deprecated] labelingCompletedCount 사용 권장") + private Long completedCount; + + @Deprecated + @Schema(description = "[Deprecated] labelingRemainingCount 사용 권장") + private Long remainingLabelCount; + + @Deprecated + @Schema(description = "[Deprecated] inspectionRemainingCount 사용 권장") private Long remainingInspectCount; + + @Deprecated + @Schema(description = "[Deprecated] labelingStatus/inspectionStatus 사용 권장") + private String workStatus; } @Getter @@ -108,6 +174,9 @@ public class WorkerStatsDto { @Schema(description = "작업자 목록 응답 (작업 정보 포함)") public static class WorkerListResponse { + @Schema(description = "프로젝트 기본 정보 (상단 표시용)") + private ProjectInfo projectInfo; + @Schema(description = "작업 진행 현황 정보") private WorkProgressInfo progressInfo; 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 6d973dad..d6ceeb0f 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 @@ -268,6 +268,12 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto case "REMAINING_ASC": baseQuery.orderBy(remainingSum.asc()); break; + case "COMPLETED_DESC": + baseQuery.orderBy(completedSum.desc()); + break; + case "COMPLETED_ASC": + baseQuery.orderBy(completedSum.asc()); + break; case "NAME_ASC": baseQuery.orderBy(memberEntity.name.asc()); break; From 040aabe95e252f25d11b693555e4cf1df89f8218 Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 5 Jan 2026 12:41:07 +0900 Subject: [PATCH 40/70] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9=20DT?= =?UTF-8?q?O=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kamcoback/label/dto/WorkerStatsDto.java | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index efca81b2..6df63cf0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -31,42 +31,28 @@ public class WorkerStatsDto { private Long totalAssigned; @Schema(description = "완료 건수") - private Long completed; + private Long doneCnt; @Schema(description = "스킵 건수") - private Long skipped; + private Long skipCnt; @Schema(description = "남은 작업 건수") - private Long remaining; - - @Schema(description = "최근 3일간 처리 이력") - private DailyHistory history; - - @Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") - private Boolean isStagnated; - } - - @Getter - @Setter - @Builder - @NoArgsConstructor - @AllArgsConstructor - @Schema(description = "최근 3일간 일일 처리 이력") - public static class DailyHistory { - - @Schema(description = "1일 전 (어제) 처리량") - private Long day1Ago; - - @Schema(description = "2일 전 처리량") - private Long day2Ago; + private Long remainingCnt; @Schema(description = "3일 전 처리량") - private Long day3Ago; + private Long day3AgoDoneCnt; - @Schema(description = "3일 평균 처리량") - private Long average; + @Schema(description = "2일 전 처리량") + private Long day2AgoDoneCnt; + + @Schema(description = "1일 전 처리량") + private Long day1AgoDoneCnt; + + //@Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") + //private Boolean isStagnated; } + @Getter @Setter @Builder From 06d4b597cb32fa8bfd9bef28378f38aae7d01d16 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:42:10 +0900 Subject: [PATCH 41/70] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20Data=20accept=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/service/LabelAllocateService.java | 4 + .../core/LabelAllocateCoreService.java | 5 + .../label/LabelAllocateRepositoryCustom.java | 4 + .../label/LabelAllocateRepositoryImpl.java | 159 ++++++++++++++---- 4 files changed, 140 insertions(+), 32 deletions(-) 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 7da6156c..ae0ada1d 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 @@ -129,6 +129,9 @@ public class LabelAllocateService { Integer page, Integer size) { + // 프로젝트 정보 조회 (analUid가 있을 때만) + var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); + // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); @@ -178,6 +181,7 @@ public class LabelAllocateService { (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); return WorkerListResponse.builder() + .projectInfo(projectInfo) .progressInfo(progressInfo) .workers(pagedWorkers) .currentPage(page) 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 d71471c0..a16252fa 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 @@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; 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.ProjectInfo; 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; @@ -48,6 +49,10 @@ public class LabelAllocateCoreService { return labelAllocateRepository.availUserList(role); } + public ProjectInfo findProjectInfo(Long analUid) { + return labelAllocateRepository.findProjectInfo(analUid); + } + public List findWorkerStatistics( Long analUid, String workerType, 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 a127e142..739c30c2 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 @@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; 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.ProjectInfo; 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; @@ -26,6 +27,9 @@ public interface LabelAllocateRepositoryCustom { List availUserList(String role); + // 프로젝트 정보 조회 + ProjectInfo findProjectInfo(Long analUid); + // 작업자 통계 조회 List findWorkerStatistics( Long analUid, String workerType, String search, String sortType); 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 d6ceeb0f..141bace8 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 @@ -14,6 +14,7 @@ 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.ProjectInfo; 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; @@ -34,9 +35,12 @@ import java.time.LocalDate; import java.time.LocalTime; 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 java.util.regex.Matcher; +import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; @@ -319,17 +323,28 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .where(analUidCondition) .fetchOne(); - // 완료 + 스킵 건수 - Long completedCount = + // === 라벨링 통계 === + // 라벨링 완료: LABEL_FIN, TEST_ING, DONE (검수 포함) + Long labelingCompleted = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( analUidCondition, - labelingAssignmentEntity.workState.in("DONE", "SKIP")) + labelingAssignmentEntity.workState.in("LABEL_FIN", "TEST_ING", "DONE")) .fetchOne(); - // 투입된 라벨러 수 (고유한 worker_uid 수) + // 스킵 건수 + Long skipCount = + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workState.eq("SKIP")) + .fetchOne(); + + // 투입된 라벨러 수 Long labelerCount = queryFactory .select(labelingAssignmentEntity.workerUid.countDistinct()) @@ -339,18 +354,18 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); - // 남은 라벨링 작업 데이터 수 - Long remainingLabelCount = + // === 검수 통계 === + // 검수 완료: DONE만 + Long inspectionCompleted = queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) .where( analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + labelingAssignmentEntity.workState.eq("DONE")) .fetchOne(); - // 투입된 검수자 수 (고유한 inspector_uid 수) + // 투입된 검수자 수 Long inspectorCount = queryFactory .select(labelingAssignmentEntity.inspectorUid.countDistinct()) @@ -360,36 +375,47 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); - // 남은 검수 작업 데이터 수 - Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + // 남은 작업 건수 계산 + long total = totalAssigned != null ? totalAssigned : 0L; + long labelCompleted = labelingCompleted != null ? labelingCompleted : 0L; + long inspectCompleted = inspectionCompleted != null ? inspectionCompleted : 0L; + long skipped = skipCount != null ? skipCount : 0L; + + long labelingRemaining = total - labelCompleted - skipped; + long inspectionRemaining = total - inspectCompleted - skipped; // 진행률 계산 - double progressRate = 0.0; - if (totalAssigned != null && totalAssigned > 0) { - progressRate = - (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; - } + double labelingRate = total > 0 ? (double) labelCompleted / total * 100 : 0.0; + double inspectionRate = total > 0 ? (double) inspectCompleted / total * 100 : 0.0; - // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) - String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; + // 상태 판단 + String labelingStatus = labelingRemaining > 0 ? "진행중" : "완료"; + String inspectionStatus = inspectionRemaining > 0 ? "진행중" : "완료"; return WorkProgressInfo.builder() - .labelingProgressRate(progressRate) - .workStatus(workStatus) - .completedCount(completedCount != null ? completedCount : 0L) - .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) + // 라벨링 + .labelingProgressRate(labelingRate) + .labelingStatus(labelingStatus) + .labelingTotalCount(total) + .labelingCompletedCount(labelCompleted) + .labelingSkipCount(skipped) + .labelingRemainingCount(labelingRemaining) .labelerCount(labelerCount != null ? labelerCount : 0L) - .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) + // 검수 + .inspectionProgressRate(inspectionRate) + .inspectionStatus(inspectionStatus) + .inspectionTotalCount(total) + .inspectionCompletedCount(inspectCompleted) + .inspectionSkipCount(skipped) + .inspectionRemainingCount(inspectionRemaining) .inspectorCount(inspectorCount != null ? inspectorCount : 0L) - .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) + // 레거시 호환 필드 (Deprecated) + .progressRate(labelingRate) + .totalAssignedCount(total) + .completedCount(labelCompleted) + .remainingLabelCount(labelingRemaining) + .remainingInspectCount(inspectionRemaining) + .workStatus(labelingStatus) .build(); } @@ -600,4 +626,73 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto em.flush(); em.clear(); } + + @Override + public ProjectInfo findProjectInfo(Long analUid) { + if (analUid == null) { + return null; + } + + var result = queryFactory + .select( + mapSheetAnalEntity.compareYyyy, + mapSheetAnalEntity.targetYyyy, + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.analStrtDttm + ) + .from(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); + + if (result == null) { + return null; + } + + Integer compareYyyy = result.get(mapSheetAnalEntity.compareYyyy); + Integer targetYyyy = result.get(mapSheetAnalEntity.targetYyyy); + String analTitle = result.get(mapSheetAnalEntity.analTitle); + ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalEntity.gukyuinApplyDttm); + ZonedDateTime analStrtDttm = result.get(mapSheetAnalEntity.analStrtDttm); + + // 변화탐지년도 생성 + String detectionYear = (compareYyyy != null && targetYyyy != null) + ? compareYyyy + "-" + targetYyyy + : null; + + // 회차 추출 (예: "8회차" → "8") + String round = extractRoundFromTitle(analTitle); + + return ProjectInfo.builder() + .detectionYear(detectionYear) + .round(round) + .reflectionDate(formatDate(gukyuinApplyDttm)) + .startDate(formatDate(analStrtDttm)) + .build(); + } + + /** + * 제목에서 회차 숫자 추출 + * 예: "8회차", "제8회차" → "8" + */ + private String extractRoundFromTitle(String title) { + if (title == null || title.isEmpty()) { + return null; + } + + Pattern pattern = Pattern.compile("(\\d+)회차"); + Matcher matcher = pattern.matcher(title); + + return matcher.find() ? matcher.group(1) : null; + } + + /** + * ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 + */ + private String formatDate(ZonedDateTime dateTime) { + if (dateTime == null) { + return null; + } + return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } } From ba0e408c2b7e1d1fe4feaecb893374999c234020 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:45:50 +0900 Subject: [PATCH 42/70] Build Error Fix --- .../kamcoback/label/dto/WorkerStatsDto.java | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 581aac7e..3501da8a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -52,25 +52,59 @@ public class WorkerStatsDto { private Long totalAssigned; @Schema(description = "완료 건수") - private Long doneCnt; + private Long completed; @Schema(description = "스킵 건수") - private Long skipCnt; + private Long skipped; @Schema(description = "남은 작업 건수") - private Long remainingCnt; + private Long remaining; - @Schema(description = "3일 전 처리량") - private Long day3AgoDoneCnt; + @Schema(description = "최근 3일간 처리 이력") + private DailyHistory history; + + @Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") + private Boolean isStagnated; + + // 레거시 필드 (기존 호환성 유지) + @Deprecated + private Long doneCnt; // completed로 대체 + + @Deprecated + private Long skipCnt; // skipped로 대체 + + @Deprecated + private Long remainingCnt; // remaining으로 대체 + + @Deprecated + private Long day3AgoDoneCnt; // history.day3Ago로 대체 + + @Deprecated + private Long day2AgoDoneCnt; // history.day2Ago로 대체 + + @Deprecated + private Long day1AgoDoneCnt; // history.day1Ago로 대체 + } + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "최근 3일간 일일 처리 이력") + public static class DailyHistory { + + @Schema(description = "1일 전 (어제) 처리량") + private Long day1Ago; @Schema(description = "2일 전 처리량") - private Long day2AgoDoneCnt; + private Long day2Ago; - @Schema(description = "1일 전 처리량") - private Long day1AgoDoneCnt; + @Schema(description = "3일 전 처리량") + private Long day3Ago; - //@Schema(description = "작업 정체 여부 (3일간 실적이 저조하면 true)") - //private Boolean isStagnated; + @Schema(description = "3일 평균 처리량") + private Long average; } From 877de7f17e176353fbc149fc1f7cb5df3579ea4f Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 12:59:21 +0900 Subject: [PATCH 43/70] =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/kamcoback/label/LabelAllocateApiController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 4e0f9373..cb723a69 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -63,8 +63,8 @@ public class LabelAllocateApiController { }) @GetMapping("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) - Long analUid, + // @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) + // Long analUid, @Parameter( description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", example = "LABELER", @@ -105,7 +105,7 @@ public class LabelAllocateApiController { return ApiResponseDto.ok( labelAllocateService.getWorkerStatistics( - analUid, workerType, search, sort, page, size)); + null, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") From f5fcc2a9e3b4caaa11f452b358ccedf985aba155 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 13:25:38 +0900 Subject: [PATCH 44/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=9F=AC,=EA=B2=80?= =?UTF-8?q?=EC=88=98=EC=9E=90=20=EC=83=81=EC=84=B8=20=EC=A0=95=EB=B3=B4,?= =?UTF-8?q?=20=EC=9D=BC=EC=9E=90=EB=B3=84=20=EB=AA=A9=EB=A1=9D=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=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 | 284 +++--- .../kamcoback/label/dto/LabelAllocateDto.java | 66 +- .../label/service/LabelAllocateService.java | 120 ++- .../core/LabelAllocateCoreService.java | 37 +- .../entity/LabelingAssignmentEntity.java | 36 +- .../label/LabelAllocateRepositoryCustom.java | 15 +- .../label/LabelAllocateRepositoryImpl.java | 911 ++++++++++++------ 7 files changed, 948 insertions(+), 521 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 1395f42b..b235ccd9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +19,7 @@ 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; @@ -36,168 +38,204 @@ 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 = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)", - description = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)") + 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("/admin/workers") public ApiResponseDto getWorkerStatistics( - @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) - 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 search, - @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 = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") - Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") - Integer size) { + @Parameter(description = "분석 ID (선택)", example = "3") @RequestParam(required = false) + 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 search, + @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 = "페이지 번호 (0부터 시작)", example = "0") + @RequestParam(defaultValue = "0") + Integer page, + @Parameter(description = "페이지 크기", example = "20") + @RequestParam(defaultValue = "20") + Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - analUid, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics( + analUid, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/allocate") public ApiResponseDto labelAllocate( - @RequestBody @Valid LabelAllocateDto.AllocateDto dto) { + @RequestBody @Valid LabelAllocateDto.AllocateDto dto) { return ApiResponseDto.okObject( - labelAllocateService.allocateAsc( - dto.getStage(), - dto.getLabelers(), - dto.getInspectors(), - dto.getCompareYyyy(), - dto.getTargetYyyy())); + labelAllocateService.allocateAsc( + dto.getStage(), + dto.getLabelers(), + dto.getInspectors(), + dto.getCompareYyyy(), + dto.getTargetYyyy())); } @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("/stage-detail") public ApiResponseDto findInferenceDetail( - @Parameter( - description = "회차 마스터 key", - required = true, - example = "8584e8d4-53b3-4582-bde2-28a81495a626") - @RequestParam - String uuid) { + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid) { return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(uuid)); } @Operation( - summary = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", - description = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") - @GetMapping("/labeler-detail") - public ApiResponseDto findLabelerDetail( - @RequestParam(defaultValue = "01022223333", required = true) String userId, - @Parameter( - description = "회차 마스터 key", - required = true, - example = "8584e8d4-53b3-4582-bde2-28a81495a626") - @RequestParam - String uuid) { - return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, uuid)); + summary = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", + description = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") + @GetMapping("/user-detail") + public ApiResponseDto findUserDetail( + @RequestParam(defaultValue = "01022223333", required = true) String userId, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter( + description = "라벨러/검수자(LABELER/REVIEWER)", + required = true) @RequestParam String type + ) { + return ApiResponseDto.ok(labelAllocateService.findUserDetail(userId, uuid, type)); } @Operation(summary = "작업현황 관리 > 상세 > 작업 이관", description = "작업현황 관리 > 상세 > 작업 이관") @ApiResponses( - value = { - @ApiResponse( - responseCode = "201", - description = "등록 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), - @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) + value = { + @ApiResponse( + responseCode = "201", + description = "등록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/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) { return ApiResponseDto.okObject( - labelAllocateService.allocateMove( - dto.getAutoType(), - dto.getStage(), - dto.getLabelers(), - dto.getCompareYyyy(), - dto.getTargetYyyy())); + labelAllocateService.allocateMove( + dto.getAutoType(), + dto.getStage(), + dto.getLabelers(), + dto.getCompareYyyy(), + dto.getTargetYyyy())); + } + + @Operation( + summary = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록", + description = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록") + @GetMapping("/daily-list") + public ApiResponseDto> findDaliyList( + @RequestParam(defaultValue = "0", required = true) int page, + @RequestParam(defaultValue = "20", required = true) int size, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid, + @Parameter( + description = "사번", + required = true, + example = "123456") + @RequestParam String userId, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter( + description = "라벨러/검수자(LABELER/REVIEWER)", + required = true) @RequestParam String type + ) { + LabelAllocateDto.searchReq searchReq = new LabelAllocateDto.searchReq(page, size, ""); + return ApiResponseDto.ok(labelAllocateService.findDaliyList(searchReq, uuid, userId, type)); } } 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 f5a060a8..e931ce40 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 @@ -8,7 +8,11 @@ import java.util.List; 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 LabelAllocateDto { @@ -97,9 +101,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -118,9 +122,9 @@ public class LabelAllocateDto { private List labelers; @Schema( - description = "검수자 할당 목록", - example = - """ + description = "검수자 할당 목록", + example = + """ ["K20251216001", "01022225555", "K20251212001" @@ -171,6 +175,9 @@ public class LabelAllocateDto { private Long analUid; private ZonedDateTime createdDttm; private ZonedDateTime updatedDttm; + private String inspectState; + private ZonedDateTime workStatDttm; + private ZonedDateTime inspectStatDttm; } @Getter @@ -210,7 +217,7 @@ public class LabelAllocateDto { private Double percent; private Integer ranking; private ZonedDateTime createdDttm; - private String inspectorName; + private String ownerName; } @Getter @@ -225,9 +232,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -256,4 +263,43 @@ public class LabelAllocateDto { private Long geoUid; private Long mapSheetNum; } + + @Getter + @Setter + @AllArgsConstructor + public static class LabelingStatDto { + + private String workDate; + private Long dailyTotalCnt; + private Long totalCnt; + private Long assignedCnt; + private Long skipCnt; + private Long completeCnt; + private Long remainCnt; + } + + @Schema(name = "searchReq", description = "일자별 작업 목록 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class searchReq { + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + 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 7da6156c..057506e8 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 @@ -6,6 +6,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; @@ -16,6 +17,7 @@ import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,22 +38,22 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param stage 회차 - * @param targetUsers 라벨러 목록 + * @param stage 회차 + * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional public ApiResponseDto.ResponseObj allocateAsc( - Integer stage, - List targetUsers, - List targetInspectors, - Integer compareYyyy, - Integer targetYyyy) { + Integer stage, + List targetUsers, + List targetInspectors, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; // geom 잔여건수 조회 Long chargeCnt = - labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); + labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); if (chargeCnt <= 0) { return new ApiResponseDto.ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 배정완료된 회차 입니다."); } @@ -59,15 +61,15 @@ public class LabelAllocateService { Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); if (!Objects.equals(chargeCnt, totalDemand)) { return new ApiResponseDto.ResponseObj( - ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); + ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); } List allIds = - labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); // MapSheetAnalInferenceEntity analUid 가져오기 Long analUid = - labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); + labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { @@ -113,52 +115,52 @@ public class LabelAllocateService { /** * 작업자 통계 조회 * - * @param analUid 분석 ID + * @param analUid 분석 ID * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param search 검색어 (이름 또는 사번) - * @param sortType 정렬 조건 - * @param page 페이지 번호 (0부터 시작) - * @param size 페이지 크기 + * @param search 검색어 (이름 또는 사번) + * @param sortType 정렬 조건 + * @param page 페이지 번호 (0부터 시작) + * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType, - Integer page, - Integer size) { + Long analUid, + String workerType, + String search, + String sortType, + Integer page, + Integer size) { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); + labelAllocateCoreService.findWorkerStatistics( + analUid, workerType, search, 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); @@ -175,16 +177,16 @@ public class LabelAllocateService { int toIndex = Math.min(fromIndex + size, workers.size()); List pagedWorkers = - (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); + (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); return WorkerListResponse.builder() - .progressInfo(progressInfo) - .workers(pagedWorkers) - .currentPage(page) - .pageSize(size) - .totalElements(totalElements) - .totalPages(totalPages) - .build(); + .progressInfo(progressInfo) + .workers(pagedWorkers) + .currentPage(page) + .pageSize(size) + .totalElements(totalElements) + .totalPages(totalPages) + .build(); } public InferenceDetail findInferenceDetail(String uuid) { @@ -192,11 +194,11 @@ public class LabelAllocateService { } public ApiResponseDto.ResponseObj allocateMove( - String autoType, - Integer stage, - List targetUsers, - Integer compareYyyy, - Integer targetYyyy) { + String autoType, + Integer stage, + List targetUsers, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); @@ -206,8 +208,8 @@ public class LabelAllocateService { } List allIds = - labelAllocateCoreService.fetchNextMoveIds( - lastId, chargeCnt, compareYyyy, targetYyyy, stage); + labelAllocateCoreService.fetchNextMoveIds( + lastId, chargeCnt, compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); @@ -220,7 +222,19 @@ public class LabelAllocateService { return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } - public LabelerDetail findLabelerDetail(String userId, String uuid) { - return labelAllocateCoreService.findLabelerDetail(userId, uuid); + public LabelerDetail findUserDetail(String userId, String uuid, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDetail(userId, uuid); + } else { + return labelAllocateCoreService.findInspectorDetail(userId, uuid); + } + } + + public Page findDaliyList(LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDailyStat(searchReq, uuid, userId); + } else { + return labelAllocateCoreService.findInspectorDailyStat(searchReq, uuid, userId); + } } } 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 d71471c0..fb7301e0 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 @@ -4,7 +4,9 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.searchReq; 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; @@ -13,6 +15,7 @@ import java.time.LocalDate; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @Service @@ -22,7 +25,7 @@ public class LabelAllocateCoreService { private final LabelAllocateRepository labelAllocateRepository; public List fetchNextIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.fetchNextIds(lastId, batchSize, compareYyyy, targetYyyy, stage); } @@ -32,8 +35,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(Integer stage, Integer compareYyyy, Integer targetYyyy) { @@ -49,12 +52,12 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, + String workerType, + String search, + String sortType) { return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, search, sortType); + analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -62,7 +65,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); } @@ -75,9 +78,9 @@ public class LabelAllocateCoreService { } public List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return labelAllocateRepository.fetchNextMoveIds( - lastId, batchSize, compareYyyy, targetYyyy, stage); + lastId, batchSize, compareYyyy, targetYyyy, stage); } public void assignOwnerMove(List sub, String userId) { @@ -95,4 +98,16 @@ public class LabelAllocateCoreService { public void insertInspector(Long analUid, String inspector) { labelAllocateRepository.insertInspector(analUid, inspector); } + + public Page findLabelerDailyStat(searchReq searchReq, String uuid, String userId) { + return labelAllocateRepository.findLabelerDailyStat(searchReq, uuid, userId); + } + + public Page findInspectorDailyStat(searchReq searchReq, String uuid, String userId) { + return labelAllocateRepository.findInspectorDailyStat(searchReq, uuid, userId); + } + + public LabelerDetail findInspectorDetail(String userId, String uuid) { + return labelAllocateRepository.findInspectorDetail(userId, uuid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java index 22afd4aa..88b5a15b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java @@ -6,6 +6,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; +import java.time.ZonedDateTime; import java.util.UUID; @Entity @@ -40,18 +41,31 @@ public class LabelingAssignmentEntity extends CommonDateEntity { @Column(name = "anal_uid") private Long analUid; + @Column(name = "inspect_state") + private String inspectState; + + @Column(name = "work_stat_dttm") + private ZonedDateTime workStatDttm; + + @Column(name = "inspect_stat_dttm") + private ZonedDateTime inspectStatDttm; + public LabelAllocateDto.Basic toDto() { return new LabelAllocateDto.Basic( - this.assignmentUid, - this.inferenceGeomUid, - this.workerUid, - this.inspectorUid, - this.workState, - this.stagnationYn, - this.assignGroupId, - this.learnGeomUid, - this.analUid, - super.getCreatedDate(), - super.getModifiedDate()); + this.assignmentUid, + this.inferenceGeomUid, + this.workerUid, + this.inspectorUid, + this.workState, + this.stagnationYn, + this.assignGroupId, + this.learnGeomUid, + this.analUid, + super.getCreatedDate(), + super.getModifiedDate(), + this.inspectState, + this.workStatDttm, + this.inspectStatDttm + ); } } 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 a127e142..9177bceb 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,8 +1,10 @@ package com.kamco.cd.kamcoback.postgres.repository.label; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkProgressInfo; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; @@ -10,11 +12,12 @@ import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import java.time.LocalDate; import java.util.List; import java.util.UUID; +import org.springframework.data.domain.Page; public interface LabelAllocateRepositoryCustom { List fetchNextIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); void assignOwner(List ids, String userId, Long analUid); @@ -28,7 +31,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String search, String sortType); + Long analUid, String workerType, String search, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(Long analUid); @@ -41,7 +44,7 @@ public interface LabelAllocateRepositoryCustom { InferenceDetail findInferenceDetail(String uuid); List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); void assignOwnerMove(List sub, String userId); @@ -50,4 +53,10 @@ public interface LabelAllocateRepositoryCustom { Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage); void insertInspector(Long analUid, String inspector); + + Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId); + + Page findInspectorDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId); + + LabelerDetail findInspectorDetail(String userId, String uuid); } 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 6d973dad..c7173589 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 @@ -13,12 +13,15 @@ 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.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.searchReq; 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.MapSheetAnalInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMemberEntity; +import com.querydsl.core.types.Expression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.CaseBuilder; @@ -39,6 +42,9 @@ import java.util.Objects; import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; @Slf4j @@ -48,28 +54,29 @@ 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) { + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select( - Projections.constructor( - AllocateInfoDto.class, - mapSheetAnalDataInferenceGeomEntity.geoUid, - mapSheetAnalDataInferenceGeomEntity.mapSheetNum)) - .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( + Projections.constructor( + AllocateInfoDto.class, + mapSheetAnalDataInferenceGeomEntity.geoUid, + mapSheetAnalDataInferenceGeomEntity.mapSheetNum)) + .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 @@ -77,10 +84,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.id.eq(analUid)) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); @@ -88,20 +95,20 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // data_geom 테이블에 label state 를 ASSIGNED 로 update List geoUidList = - ids.stream().map(AllocateInfoDto::getGeoUid).filter(Objects::nonNull).toList(); + ids.stream().map(AllocateInfoDto::getGeoUid).filter(Objects::nonNull).toList(); 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(geoUidList)) - .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(geoUidList)) + .execute(); // 라벨러 할당 테이블에 insert String sql = - """ + """ insert into tb_labeling_assignment (assignment_uid, inference_geom_uid, worker_uid, work_state, assign_group_id, anal_uid) @@ -110,13 +117,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto for (AllocateInfoDto info : ids) { em.createNativeQuery(sql) - .setParameter(1, UUID.randomUUID()) - .setParameter(2, info.getGeoUid()) - .setParameter(3, userId) - .setParameter(4, LabelState.ASSIGNED.getId()) - .setParameter(5, info.getMapSheetNum()) - .setParameter(6, analEntity.getId()) - .executeUpdate(); + .setParameter(1, UUID.randomUUID()) + .setParameter(2, info.getGeoUid()) + .setParameter(3, userId) + .setParameter(4, LabelState.ASSIGNED.getId()) + .setParameter(5, info.getMapSheetNum()) + .setParameter(6, analEntity.getId()) + .executeUpdate(); } em.flush(); @@ -127,137 +134,137 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto public List findAssignedLabelerList(Long analUid) { // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.id.eq(analUid)) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("mapSheetAnalInferenceEntity not found for analUid: "); } return queryFactory - .selectFrom(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.analUid.eq(analEntity.getId()), - labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), - labelingAssignmentEntity.inspectorUid.isNull()) - .orderBy(labelingAssignmentEntity.workerUid.asc()) - .fetch(); + .selectFrom(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.analUid.eq(analEntity.getId()), + 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 search, - String sortType) { + Long analUid, + String workerType, + String search, + 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; if (search != null && !search.isEmpty()) { searchCondition = memberEntity.name.contains(search) - .or(memberEntity.employeeNo.contains(search)); + .or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 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(); // 기본 통계 조회 쿼리 BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; 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(analUidCondition, 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(analUidCondition, workerCondition, searchCondition) + .groupBy(workerIdField, memberEntity.name); // 정렬 조건 적용 if (sortType != null) { @@ -283,22 +290,22 @@ 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 @@ -307,112 +314,112 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 전체 배정 건수 Long totalAssigned = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where(analUidCondition) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where(analUidCondition) + .fetchOne(); // 완료 + 스킵 건수 Long completedCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.in("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workState.in("DONE", "SKIP")) + .fetchOne(); // 투입된 라벨러 수 (고유한 worker_uid 수) Long labelerCount = - queryFactory - .select(labelingAssignmentEntity.workerUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.workerUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workerUid.isNotNull()) + .fetchOne(); // 남은 라벨링 작업 데이터 수 Long remainingLabelCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.workerUid.isNotNull(), + labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) + .fetchOne(); // 투입된 검수자 수 (고유한 inspector_uid 수) Long inspectorCount = - queryFactory - .select(labelingAssignmentEntity.inspectorUid.countDistinct()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull()) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.inspectorUid.countDistinct()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + labelingAssignmentEntity.inspectorUid.isNotNull()) + .fetchOne(); // 남은 검수 작업 데이터 수 Long remainingInspectCount = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull(), - labelingAssignmentEntity.workState.notIn("DONE")) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + 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); BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; Long count = - queryFactory - .select(labelingAssignmentEntity.count()) - .from(labelingAssignmentEntity) - .where( - analUidCondition, - workerCondition, - labelingAssignmentEntity.workState.in( - LabelState.DONE.getId(), LabelState.SKIP.getId()), - labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.count()) + .from(labelingAssignmentEntity) + .where( + analUidCondition, + workerCondition, + labelingAssignmentEntity.workState.in( + LabelState.DONE.getId(), LabelState.SKIP.getId()), + labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) + .fetchOne(); return count != null ? count : 0L; } @@ -420,10 +427,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(); } @@ -432,64 +439,64 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto public InferenceDetail findInferenceDetail(String uuid) { // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for 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(analEntity.getId())) - .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.getId())) + .groupBy( + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.detectingCnt) + .fetchOne(); } @Override public List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + 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(); } @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(); } @@ -497,43 +504,43 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public LabelerDetail findLabelerDetail(String userId, String uuid) { 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로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); @@ -543,55 +550,339 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto QMemberEntity inspector = new QMemberEntity("inspector"); return queryFactory - .select( - Projections.constructor( - LabelerDetail.class, - worker.userRole, - worker.name, - worker.employeeNo, - assignedCnt, - skipCnt, - completeCnt, - percent, - Expressions.constant(0), // TODO: 순위, 꼭 해야할지? - labelingAssignmentEntity.createdDate.min(), - inspector.name.min())) - .from(worker) - .innerJoin(labelingAssignmentEntity) - .on( - worker.employeeNo.eq(labelingAssignmentEntity.workerUid), - labelingAssignmentEntity.analUid.eq(analEntity.getId())) - .leftJoin(inspector) - .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) - .where(worker.employeeNo.eq(userId)) - .groupBy(worker.userRole, worker.name, worker.employeeNo) - .fetchOne(); + .select( + Projections.constructor( + LabelerDetail.class, + worker.userRole, + worker.name, + worker.employeeNo, + assignedCnt, + skipCnt, + completeCnt, + percent, + Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + labelingAssignmentEntity.workStatDttm.min(), + inspector.name.min())) + .from(worker) + .innerJoin(labelingAssignmentEntity) + .on( + worker.employeeNo.eq(labelingAssignmentEntity.workerUid), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .leftJoin(inspector) + .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) + .where(worker.employeeNo.eq(userId)) + .groupBy(worker.userRole, worker.name, worker.employeeNo) + .fetchOne(); } @Override public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { return queryFactory - .select(mapSheetAnalInferenceEntity.id) - .from(mapSheetAnalInferenceEntity) - .where( - mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy), - mapSheetAnalInferenceEntity.targetYyyy.eq(targetYyyy), - mapSheetAnalInferenceEntity.stage.eq(stage)) - .fetchOne(); + .select(mapSheetAnalInferenceEntity.id) + .from(mapSheetAnalInferenceEntity) + .where( + mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalInferenceEntity.stage.eq(stage)) + .fetchOne(); } @Override public void insertInspector(Long analUid, String inspector) { queryFactory - .insert(labelingInspectorEntity) - .columns( - labelingInspectorEntity.operatorUid, - labelingInspectorEntity.analUid, - labelingInspectorEntity.inspectorUid) - .values(UUID.randomUUID(), analUid, inspector) - .execute(); + .insert(labelingInspectorEntity) + .columns( + labelingInspectorEntity.operatorUid, + labelingInspectorEntity.analUid, + labelingInspectorEntity.inspectorUid) + .values(UUID.randomUUID(), analUid, inspector) + .execute(); em.flush(); em.clear(); } + + @Override + public Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId) { + // 날짜 포맷 + Expression workDate = + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", + labelingAssignmentEntity.workStatDttm + ); + + // 날짜별 전체 건수 + Expression dailyTotalCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*)" + ); + + // ⭐ 전체 기간 총 건수 (윈도우 함수) + Expression totalCnt = + Expressions.numberTemplate( + Long.class, + "SUM(COUNT(*)) OVER ()" + ); + + // 상태별 카운트 (Postgres FILTER 사용) + Expression assignedCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'ASSIGNED')", + labelingAssignmentEntity.workState + ); + + Expression skipCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'SKIP')", + labelingAssignmentEntity.workState + ); + + Expression completeCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.workState + ); + + Expression remainCnt = + Expressions.numberTemplate( + Long.class, + "({0} - {1} - {2})", + totalCnt, + skipCnt, + completeCnt + ); + + // analUid로 분석 정보 조회 + MapSheetAnalInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); + } + + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + LabelingStatDto.class, + workDate, + dailyTotalCnt, + totalCnt, // ⭐ 전체 일자 배정 건수 + assignedCnt, + skipCnt, + completeCnt, + remainCnt + ) + ) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.workerUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .groupBy(workDate) + .orderBy(labelingAssignmentEntity.workStatDttm.min().asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long countQuery = queryFactory + .select(workDate) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.workerUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .distinct() + .fetch() + .stream() + .count(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findInspectorDailyStat(searchReq searchReq, String uuid, String userId) { + // 날짜 포맷 + Expression workDate = + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", + labelingAssignmentEntity.inspectStatDttm + ); + + // 날짜별 전체 건수 + Expression dailyTotalCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*)" + ); + + // ⭐ 전체 기간 총 건수 (윈도우 함수) + Expression totalCnt = + Expressions.numberTemplate( + Long.class, + "SUM(COUNT(*)) OVER ()" + ); + + // 상태별 카운트 (Postgres FILTER 사용) + Expression assignedCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'UNCONFIRM')", + labelingAssignmentEntity.inspectState + ); + + Expression skipCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'EXCEPT')", + labelingAssignmentEntity.inspectState + ); + + Expression completeCnt = + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.inspectState + ); + + Expression remainCnt = + Expressions.numberTemplate( + Long.class, + "({0} - {1} - {2})", + totalCnt, + skipCnt, + completeCnt + ); + + // analUid로 분석 정보 조회 + MapSheetAnalInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); + } + + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + LabelingStatDto.class, + workDate, + dailyTotalCnt, + totalCnt, // ⭐ 전체 일자 배정 건수 + assignedCnt, + skipCnt, + completeCnt, + remainCnt + ) + ) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.inspectorUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .groupBy(workDate) + .orderBy(labelingAssignmentEntity.inspectStatDttm.min().asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long countQuery = queryFactory + .select(workDate) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.inspectorUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId()) + ) + .distinct() + .fetch() + .stream() + .count(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public LabelerDetail findInspectorDetail(String userId, String uuid) { + NumberExpression assignedCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.UNCONFIRM.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression skipCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.EXCEPT.getId())) + .then(1L) + .otherwise((Long) null) + .count(); + + NumberExpression completeCnt = + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.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)); + + // analUid로 분석 정보 조회 + MapSheetAnalInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); + } + + QMemberEntity inspector = QMemberEntity.memberEntity; + QMemberEntity worker = new QMemberEntity("worker"); + + return queryFactory + .select( + Projections.constructor( + LabelerDetail.class, + inspector.userRole, + inspector.name, + inspector.employeeNo, + assignedCnt, + skipCnt, + completeCnt, + percent, + Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + labelingAssignmentEntity.inspectStatDttm.min(), + worker.name.min())) + .from(inspector) + .innerJoin(labelingAssignmentEntity) + .on( + inspector.employeeNo.eq(labelingAssignmentEntity.inspectorUid), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .leftJoin(worker) + .on(labelingAssignmentEntity.workerUid.eq(worker.employeeNo)) + .where(inspector.employeeNo.eq(userId)) + .groupBy(inspector.userRole, inspector.name, inspector.employeeNo) + .fetchOne(); + } } From 03d8114897fdee4dfa45dd8323f69537d7bac22d Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 13:36:07 +0900 Subject: [PATCH 45/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=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 --- .../label/LabelAllocateApiController.java | 9 +- .../label/LabelWorkerApiController.java | 53 +++++- .../cd/kamcoback/label/dto/LabelWorkDto.java | 40 ++++- .../kamcoback/label/dto/WorkerStatsDto.java | 19 +- .../label/service/LabelAllocateService.java | 10 +- .../label/service/LabelWorkService.java | 38 +++- .../core/LabelAllocateCoreService.java | 8 +- .../postgres/core/LabelWorkCoreService.java | 37 ++++ .../InferenceResultRepositoryImpl.java | 167 +++++++++++------- .../label/LabelAllocateRepositoryImpl.java | 70 +++----- .../label/LabelWorkRepositoryCustom.java | 8 + .../label/LabelWorkRepositoryImpl.java | 142 ++++++++++++--- 12 files changed, 430 insertions(+), 171 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 cb723a69..e23a4104 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -93,19 +93,16 @@ public class LabelAllocateApiController { defaultValue = "NAME_ASC")) @RequestParam(required = false) String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") 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 d07083e1..eca1c7f0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -1,9 +1,10 @@ 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.LabelWorkDto; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngSearchReq; import com.kamco.cd.kamcoback.label.service.LabelWorkService; import io.swagger.v3.oas.annotations.Operation; @@ -13,10 +14,13 @@ 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 java.util.List; +import java.util.UUID; 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.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -30,6 +34,24 @@ public class LabelWorkerApiController { private final LabelWorkService labelWorkService; + @Operation(summary = "변화탐지 년도 셀렉트박스 조회", description = "라벨링작업 관리 > 목록 조회 변화탐지 년도 셀렉트박스 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = ChangeDetectYear.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/change-detect-year") + public ApiResponseDto> getChangeDetectYear() { + return ApiResponseDto.ok(labelWorkService.getChangeDetectYear()); + } + @Operation(summary = "라벨링작업 관리 > 목록 조회", description = "라벨링작업 관리 > 목록 조회") @ApiResponses( value = { @@ -39,26 +61,45 @@ public class LabelWorkerApiController { content = @Content( mediaType = "application/json", - schema = @Schema(implementation = CommonCodeDto.Basic.class))), + schema = @Schema(implementation = Page.class))), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @GetMapping("/label-work-mng-list") public ApiResponseDto> labelWorkMngList( - @Parameter(description = "변화탐지년도", example = "2024") @RequestParam(required = false) - Integer detectYyyy, - @Parameter(description = "시작일", example = "20260101") @RequestParam String strtDttm, + @Parameter(description = "변화탐지년도", example = "2022-2024") @RequestParam(required = false) + String detectYear, + @Parameter(description = "시작일", example = "20220101") @RequestParam String strtDttm, @Parameter(description = "종료일", example = "20261201") @RequestParam String endDttm, @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") int page, @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") int size) { LabelWorkDto.LabelWorkMngSearchReq searchReq = new LabelWorkMngSearchReq(); - searchReq.setDetectYyyy(detectYyyy); + searchReq.setDetectYear(detectYear); searchReq.setStrtDttm(strtDttm); searchReq.setEndDttm(endDttm); searchReq.setPage(page); searchReq.setSize(size); return ApiResponseDto.ok(labelWorkService.labelWorkMngList(searchReq)); } + + @Operation(summary = "라벨링작업 관리 > 작업 배정 정보조회", description = "작업 배정 정보조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = LabelWorkMngDetail.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/label-work-mng-detail/{uuid}") + public ApiResponseDto labelWorkMngDetail( + @Parameter(description = "uuid") @PathVariable UUID uuid) { + return ApiResponseDto.ok(labelWorkService.findLabelWorkMngDetail(uuid)); + } } 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 024f89b5..55272f9f 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,10 +1,12 @@ package com.kamco.cd.kamcoback.label.dto; +import com.fasterxml.jackson.annotation.JsonProperty; 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; @@ -14,6 +16,15 @@ import org.springframework.data.domain.Pageable; public class LabelWorkDto { + @Getter + @Setter + @AllArgsConstructor + public static class ChangeDetectYear { + + private String code; + private String name; + } + @Schema(name = "LabelWorkMng", description = "라벨작업관리") @Getter @Setter @@ -21,8 +32,9 @@ public class LabelWorkDto { @AllArgsConstructor public static class LabelWorkMng { - private int compareYyyy; - private int targetYyyy; + private UUID uuid; + private Integer compareYyyy; + private Integer targetYyyy; private int stage; @JsonFormatDttm private ZonedDateTime createdDttm; private Long detectionTotCnt; @@ -32,6 +44,14 @@ public class LabelWorkDto { private Long labelCompleteTotCnt; @JsonFormatDttm private ZonedDateTime labelStartDttm; + @JsonProperty("detectYear") + public String getDetectYear() { + if (compareYyyy == null || targetYyyy == null) { + return null; + } + return compareYyyy + "-" + targetYyyy; + } + public String getLabelState() { String mngState = "PENDING"; @@ -65,6 +85,20 @@ public class LabelWorkDto { } } + @Getter + @Setter + @AllArgsConstructor + @NoArgsConstructor + public static class LabelWorkMngDetail { + + private String detectionYear; + private Integer stage; + @JsonFormatDttm private ZonedDateTime createdDttm; + private Long labelTotCnt; + private Long labeler; + private Long reviewer; + } + @Schema(name = "LabelWorkMngSearchReq", description = "라벨작업관리 검색 요청") @Getter @Setter @@ -80,7 +114,7 @@ public class LabelWorkDto { private int size = 20; @Schema(description = "변화탐지년도", example = "2024") - private Integer detectYyyy; + private String detectYear; @Schema(description = "시작일", example = "20260101") private String strtDttm; diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 3501da8a..435c2060 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -67,23 +67,17 @@ public class WorkerStatsDto { private Boolean isStagnated; // 레거시 필드 (기존 호환성 유지) - @Deprecated - private Long doneCnt; // completed로 대체 + @Deprecated private Long doneCnt; // completed로 대체 - @Deprecated - private Long skipCnt; // skipped로 대체 + @Deprecated private Long skipCnt; // skipped로 대체 - @Deprecated - private Long remainingCnt; // remaining으로 대체 + @Deprecated private Long remainingCnt; // remaining으로 대체 - @Deprecated - private Long day3AgoDoneCnt; // history.day3Ago로 대체 + @Deprecated private Long day3AgoDoneCnt; // history.day3Ago로 대체 - @Deprecated - private Long day2AgoDoneCnt; // history.day2Ago로 대체 + @Deprecated private Long day2AgoDoneCnt; // history.day2Ago로 대체 - @Deprecated - private Long day1AgoDoneCnt; // history.day1Ago로 대체 + @Deprecated private Long day1AgoDoneCnt; // history.day1Ago로 대체 } @Getter @@ -107,7 +101,6 @@ public class WorkerStatsDto { private Long average; } - @Getter @Setter @Builder 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 ae0ada1d..bf551dfb 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 @@ -122,12 +122,7 @@ public class LabelAllocateService { * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType, - Integer page, - Integer size) { + Long analUid, String workerType, String search, String sortType, Integer page, Integer size) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -137,8 +132,7 @@ public class LabelAllocateService { // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); + labelAllocateCoreService.findWorkerStatistics(analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); 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 3a8d6553..c597c5db 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,24 +1,52 @@ package com.kamco.cd.kamcoback.label.service; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.core.LabelWorkCoreService; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service +@RequiredArgsConstructor +@Transactional(rollbackFor = Exception.class) public class LabelWorkService { private final LabelWorkCoreService labelWorkCoreService; - public LabelWorkService(LabelWorkCoreService labelWorkCoreService) { - this.labelWorkCoreService = labelWorkCoreService; - } - + /** + * 라벨링작업 관리 목록조회 + * + * @param searchReq + * @return + */ public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { - return labelWorkCoreService.labelWorkMngList(searchReq); } + + /** + * 작업배정 정보 조회 + * + * @param uuid + * @return + */ + public LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid) { + return labelWorkCoreService.findLabelWorkMngDetail(uuid); + } + + /** + * 변화탐지 셀렉트박스 조회 + * + * @return + */ + public List getChangeDetectYear() { + return labelWorkCoreService.getChangeDetectYear(); + } } 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 a16252fa..2ad6ba67 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 @@ -54,12 +54,8 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { - return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, search, sortType); + Long analUid, String workerType, String search, String sortType) { + return labelAllocateRepository.findWorkerStatistics(analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/LabelWorkCoreService.java index a1be92a5..cc3c3754 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,8 +1,13 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.repository.label.LabelWorkRepository; +import com.kamco.cd.kamcoback.postgres.repository.members.MembersRepository; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -12,8 +17,40 @@ import org.springframework.stereotype.Service; public class LabelWorkCoreService { private final LabelWorkRepository labelWorkRepository; + private final MembersRepository membersRepository; + /** + * 변화탐지 년도 셀렉트박스 조회 + * + * @return + */ + public List getChangeDetectYear() { + return labelWorkRepository.findChangeDetectYearList().stream() + .map( + e -> + new ChangeDetectYear( + e.getCompareYyyy() + "-" + e.getTargetYyyy(), + e.getCompareYyyy() + "-" + e.getTargetYyyy())) + .toList(); + } + + /** + * 라벨링작업 관리 목록 조회 + * + * @param searchReq + * @return + */ public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { return labelWorkRepository.labelWorkMngList(searchReq); } + + /** + * 작업배정 정보 조회 + * + * @param uuid + * @return + */ + public LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid) { + return labelWorkRepository.findLabelWorkMngDetail(uuid); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 8e1804b9..37f0c8a6 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -36,20 +36,29 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC String sql = """ INSERT INTO tb_map_sheet_anal_inference ( - compare_yyyy, - target_yyyy, - stage, - anal_title, - detecting_cnt - ) - SELECT - r.input1 AS compare_yyyy, - r.input2 AS target_yyyy, - r.stage, - CONCAT(r.stage ,'_', r.input1 ,'_', r.input2) AS anal_title, - count(*) - FROM inference_results r - GROUP BY r.stage, r.input1, r.input2; + stage, + compare_yyyy, + target_yyyy, + anal_title, + detecting_cnt, + created_dttm, + updated_dttm + ) + SELECT + r.stage, + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + CONCAT(r.stage, '_', r.input1, '_', r.input2) AS anal_title, + COUNT(*) AS detecting_cnt, + now(), + now() + FROM inference_results r + GROUP BY r.stage, r.input1, r.input2 + ON CONFLICT (stage, compare_yyyy, target_yyyy) + DO UPDATE SET + detecting_cnt = EXCLUDED.detecting_cnt, + anal_title = EXCLUDED.anal_title, + updated_dttm = now() """; return em.createNativeQuery(sql).executeUpdate(); @@ -69,30 +78,42 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC String sql = """ INSERT INTO tb_map_sheet_anal_data_inference ( + anal_uid, stage, compare_yyyy, target_yyyy, map_sheet_num, - created_dttm, - updated_dttm, + detecting_cnt, file_created_yn, - detecting_cnt + created_dttm, + updated_dttm ) SELECT + ai.id AS anal_uid, r.stage, r.input1 AS compare_yyyy, r.input2 AS target_yyyy, r.map_id AS map_sheet_num, - now() AS created_dttm, - now() AS updated_dttm, - false AS file_created_yn, - count(*) AS detecting_cnt + COUNT(*) AS detecting_cnt, + false AS file_created_yn, + now(), + now() FROM inference_results r - GROUP BY r.stage, r.input1, r.input2, r.map_id + JOIN tb_map_sheet_anal_inference ai + ON ai.stage = r.stage + AND ai.compare_yyyy = r.input1 + AND ai.target_yyyy = r.input2 + GROUP BY + ai.id, + r.stage, + r.input1, + r.input2, + r.map_id ON CONFLICT (stage, compare_yyyy, target_yyyy, map_sheet_num) DO UPDATE SET - updated_dttm = now(), - detecting_cnt = EXCLUDED.detecting_cnt + anal_uid = EXCLUDED.anal_uid, + detecting_cnt = EXCLUDED.detecting_cnt, + updated_dttm = now() """; return em.createNativeQuery(sql).executeUpdate(); @@ -111,46 +132,70 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC String sql = """ - INSERT INTO tb_map_sheet_anal_data_inference_geom ( - uuid, stage, cd_prob, compare_yyyy, target_yyyy, map_sheet_num, - class_before_cd, class_before_prob, class_after_cd, class_after_prob, - geom, area, data_uid, created_dttm, updated_dttm, - file_created_yn + INSERT INTO tb_map_sheet_anal_data_inference_geom ( + uuid, + stage, + cd_prob, + compare_yyyy, + target_yyyy, + map_sheet_num, + class_before_cd, + class_before_prob, + class_after_cd, + class_after_prob, + geom, + area, + data_uid, + file_created_yn, + created_dttm, + updated_dttm ) SELECT - x.uuid, x.stage, x.cd_prob, x.compare_yyyy, x.target_yyyy, x.map_sheet_num, - x.class_before_cd, x.class_before_prob, x.class_after_cd, x.class_after_prob, - x.geom, x.area, x.data_uid, x.created_dttm, x.updated_dttm, - false AS file_created_yn + x.uuid, + x.stage, + x.cd_prob, + x.compare_yyyy, + x.target_yyyy, + x.map_sheet_num, + x.class_before_cd, + x.class_before_prob, + x.class_after_cd, + x.class_after_prob, + x.geom, + x.area, + x.data_uid, + false, + x.created_dttm, + x.updated_dttm FROM ( - SELECT DISTINCT ON (r.uuid) - r.uuid, - r.stage, - r.cd_prob, - r.input1 AS compare_yyyy, - r.input2 AS target_yyyy, - r.map_id AS map_sheet_num, - r.before_class AS class_before_cd, - r.before_probability AS class_before_prob, - r.after_class AS class_after_cd, - r.after_probability AS class_after_prob, - CASE - WHEN r.geometry IS NULL THEN NULL - WHEN left(r.geometry, 2) = '01' - THEN ST_SetSRID(ST_GeomFromWKB(decode(r.geometry, 'hex')), 5186) - ELSE ST_GeomFromText(r.geometry, 5186) - END AS geom, - r.area, - di.data_uid, - r.created_dttm, - r.updated_dttm - FROM inference_results r - JOIN tb_map_sheet_anal_data_inference di - ON di.stage = r.stage - AND di.compare_yyyy = r.input1 - AND di.target_yyyy = r.input2 - AND di.map_sheet_num = r.map_id - ORDER BY r.uuid, r.updated_dttm DESC NULLS LAST, r.uid DESC + SELECT DISTINCT ON (r.uuid) + r.uuid, + r.stage, + r.cd_prob, + r.input1 AS compare_yyyy, + r.input2 AS target_yyyy, + r.map_id AS map_sheet_num, + r.before_class AS class_before_cd, + r.before_probability AS class_before_prob, + r.after_class AS class_after_cd, + r.after_probability AS class_after_prob, + CASE + WHEN r.geometry IS NULL THEN NULL + WHEN LEFT(r.geometry, 2) = '01' + THEN ST_SetSRID(ST_GeomFromWKB(decode(r.geometry, 'hex')), 5186) + ELSE ST_GeomFromText(r.geometry, 5186) + END AS geom, + r.area, + di.data_uid, + r.created_dttm, + r.updated_dttm + FROM inference_results r + JOIN tb_map_sheet_anal_data_inference di + ON di.stage = r.stage + AND di.compare_yyyy = r.input1 + AND di.target_yyyy = r.input2 + AND di.map_sheet_num = r.map_id + ORDER BY r.uuid, r.updated_dttm DESC NULLS LAST, r.uid DESC ) x ON CONFLICT (uuid) DO UPDATE SET 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 141bace8..b08617b8 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 @@ -192,10 +192,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, String workerType, String search, String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = @@ -211,8 +208,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 검색 조건 (이름 또는 사번으로 검색) BooleanExpression searchCondition = null; if (search != null && !search.isEmpty()) { - searchCondition = memberEntity.name.contains(search) - .or(memberEntity.employeeNo.contains(search)); + searchCondition = + memberEntity.name.contains(search).or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 @@ -242,7 +239,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .sum(); // 기본 통계 조회 쿼리 - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; var baseQuery = queryFactory @@ -313,7 +311,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; // 전체 배정 건수 Long totalAssigned = @@ -339,9 +338,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("SKIP")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("SKIP")) .fetchOne(); // 투입된 라벨러 수 @@ -349,9 +346,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.workerUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); // === 검수 통계 === @@ -360,9 +355,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("DONE")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("DONE")) .fetchOne(); // 투입된 검수자 수 @@ -370,9 +363,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.inspectorUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); // 남은 작업 건수 계산 @@ -432,7 +423,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.eq(workerId) : labelingAssignmentEntity.workerUid.eq(workerId); - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; Long count = queryFactory @@ -633,17 +625,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return null; } - var result = queryFactory - .select( - mapSheetAnalEntity.compareYyyy, - mapSheetAnalEntity.targetYyyy, - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.analStrtDttm - ) - .from(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + var result = + queryFactory + .select( + mapSheetAnalEntity.compareYyyy, + mapSheetAnalEntity.targetYyyy, + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.analStrtDttm) + .from(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); if (result == null) { return null; @@ -656,9 +648,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ZonedDateTime analStrtDttm = result.get(mapSheetAnalEntity.analStrtDttm); // 변화탐지년도 생성 - String detectionYear = (compareYyyy != null && targetYyyy != null) - ? compareYyyy + "-" + targetYyyy - : null; + String detectionYear = + (compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null; // 회차 추출 (예: "8회차" → "8") String round = extractRoundFromTitle(analTitle); @@ -671,10 +662,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .build(); } - /** - * 제목에서 회차 숫자 추출 - * 예: "8회차", "제8회차" → "8" - */ + /** 제목에서 회차 숫자 추출 예: "8회차", "제8회차" → "8" */ private String extractRoundFromTitle(String title) { if (title == null || title.isEmpty()) { return null; @@ -686,9 +674,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return matcher.find() ? matcher.group(1) : null; } - /** - * ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 - */ + /** ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 */ private String formatDate(ZonedDateTime dateTime) { if (dateTime == null) { return null; 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 6a27f5d0..9c0a55cb 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 @@ -2,9 +2,17 @@ package com.kamco.cd.kamcoback.postgres.repository.label; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; +import java.util.List; +import java.util.UUID; import org.springframework.data.domain.Page; public interface LabelWorkRepositoryCustom { + List findChangeDetectYearList(); + public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq); + + LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid); } 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 e17da42c..761d9f12 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,22 +1,29 @@ 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.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; 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.NumberExpression; import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; -import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -39,20 +46,59 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport this.queryFactory = queryFactory; } + /** + * 변화탐지 년도 셀렉트박스 조회 + * + * @return + */ + @Override + public List findChangeDetectYearList() { + return queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where( + mapSheetAnalInferenceEntity.id.in( + JPAExpressions.select(mapSheetAnalInferenceEntity.id.min()) + .from(mapSheetAnalInferenceEntity) + .groupBy( + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy))) + .orderBy( + mapSheetAnalInferenceEntity.compareYyyy.asc(), + mapSheetAnalInferenceEntity.targetYyyy.asc()) + .fetch(); + } + + /** + * 라벨링 작업관리 목록 조회 + * + * @param searchReq + * @return + */ @Override public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize()); BooleanBuilder whereBuilder = new BooleanBuilder(); + BooleanBuilder whereSubDataBuilder = new BooleanBuilder(); BooleanBuilder whereSubBuilder = new BooleanBuilder(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd"); + if (StringUtils.isNotBlank(searchReq.getDetectYear())) { + String[] years = searchReq.getDetectYear().split("-"); - if (searchReq.getDetectYyyy() != null) { - whereBuilder.and(mapSheetAnalDataInferenceEntity.targetYyyy.eq(searchReq.getDetectYyyy())); + if (years.length == 2) { + Integer compareYear = Integer.valueOf(years[0]); + Integer targetYear = Integer.valueOf(years[1]); + + whereBuilder.and( + mapSheetAnalDataInferenceEntity + .compareYyyy + .eq(compareYear) + .and(mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYear))); + } } - // mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id) + whereSubDataBuilder.and( + mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)); whereSubBuilder.and( mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)); @@ -61,15 +107,10 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport && !searchReq.getStrtDttm().isEmpty() && searchReq.getEndDttm() != null && !searchReq.getEndDttm().isEmpty()) { - - // whereSubBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.isNotNull()); whereSubBuilder.and( Expressions.stringTemplate( "to_char({0}, 'YYYYMMDD')", mapSheetAnalDataInferenceGeomEntity.labelStateDttm) .between(searchReq.getStrtDttm(), searchReq.getEndDttm())); - - // whereBuilder.and(mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min().isNotNull()); - } List foundContent = @@ -77,9 +118,10 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .select( Projections.constructor( LabelWorkMng.class, - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage, + mapSheetAnalInferenceEntity.uuid, + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.stage, mapSheetAnalDataInferenceEntity.createdDttm.min(), mapSheetAnalDataInferenceGeomEntity.dataUid.count(), mapSheetAnalDataInferenceGeomEntity.dataUid.count(), @@ -99,17 +141,21 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .otherwise(0L) .sum(), mapSheetAnalDataInferenceGeomEntity.labelStateDttm.min())) - .from(mapSheetAnalDataInferenceEntity) + .from(mapSheetAnalInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceEntity) + .on(whereSubDataBuilder) .innerJoin(mapSheetAnalDataInferenceGeomEntity) .on(whereSubBuilder) .where(whereBuilder) .groupBy( - mapSheetAnalDataInferenceEntity.compareYyyy, - mapSheetAnalDataInferenceEntity.targetYyyy, - mapSheetAnalDataInferenceEntity.stage) + mapSheetAnalInferenceEntity.uuid, + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.stage) .orderBy( - mapSheetAnalDataInferenceEntity.targetYyyy.desc(), - mapSheetAnalDataInferenceEntity.stage.desc()) + mapSheetAnalInferenceEntity.targetYyyy.desc(), + mapSheetAnalInferenceEntity.compareYyyy.desc(), + mapSheetAnalInferenceEntity.stage.desc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); @@ -131,8 +177,62 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport */ - Long countQuery = foundContent.stream().count(); + Long total = + queryFactory + .select(mapSheetAnalInferenceEntity.uuid.countDistinct()) + .from(mapSheetAnalInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceEntity) + .on(whereSubDataBuilder) + .innerJoin(mapSheetAnalDataInferenceGeomEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .fetchOne(); - return new PageImpl<>(foundContent, pageable, countQuery); + return new PageImpl<>(foundContent, pageable, total); + } + + /** + * 작업배정 상세조회 + * + * @param uuid + * @return + */ + @Override + public LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid) { + + NumberExpression labelTotCnt = mapSheetAnalDataInferenceGeomEntity.geoUid.count(); + NumberExpression labelerCnt = labelingAssignmentEntity.workerUid.count(); + NumberExpression reviewerCnt = labelingAssignmentEntity.inspectorUid.count(); + + return queryFactory + .select( + Projections.constructor( + LabelWorkMngDetail.class, + mapSheetAnalInferenceEntity + .compareYyyy + .stringValue() + .concat("-") + .concat(mapSheetAnalInferenceEntity.targetYyyy.stringValue()), + mapSheetAnalInferenceEntity.stage, + mapSheetAnalInferenceEntity.createdDttm, + labelTotCnt, + labelerCnt, + reviewerCnt)) + .from(mapSheetAnalInferenceEntity) + .leftJoin(mapSheetAnalDataInferenceEntity) + .on(mapSheetAnalInferenceEntity.id.eq(mapSheetAnalDataInferenceEntity.analUid)) + .leftJoin(mapSheetAnalDataInferenceGeomEntity) + .on(mapSheetAnalDataInferenceEntity.id.eq(mapSheetAnalDataInferenceGeomEntity.dataUid)) + .leftJoin(labelingAssignmentEntity) + .on( + mapSheetAnalDataInferenceGeomEntity.geoUid.eq( + labelingAssignmentEntity.inferenceGeomUid)) + .where(mapSheetAnalInferenceEntity.uuid.eq(uuid)) + .groupBy( + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.stage, + mapSheetAnalInferenceEntity.createdDttm) + .fetchOne(); } } From 8c149605e5556add606169ae3f90306357922cae Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 13:36:15 +0900 Subject: [PATCH 46/70] spotless --- .../label/LabelAllocateApiController.java | 82 ++- .../kamcoback/label/dto/LabelAllocateDto.java | 21 +- .../kamcoback/label/dto/WorkerStatsDto.java | 19 +- .../label/service/LabelAllocateService.java | 13 +- .../core/LabelAllocateCoreService.java | 14 +- .../entity/LabelingAssignmentEntity.java | 29 +- .../label/LabelAllocateRepositoryCustom.java | 12 +- .../label/LabelAllocateRepositoryImpl.java | 476 ++++++++---------- 8 files changed, 295 insertions(+), 371 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 7f12077e..2dfaf04c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +19,7 @@ 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; @@ -93,19 +95,16 @@ public class LabelAllocateApiController { defaultValue = "NAME_ASC")) @RequestParam(required = false) String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") Integer size) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort, page, size)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") @@ -154,24 +153,23 @@ public class LabelAllocateApiController { } @Operation( - summary = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", - description = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") + summary = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", + description = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") @GetMapping("/user-detail") public ApiResponseDto findUserDetail( - @RequestParam(defaultValue = "01022223333", required = true) String userId, - @Parameter( - description = "회차 마스터 key", - required = true, - example = "8584e8d4-53b3-4582-bde2-28a81495a626") - @RequestParam - String uuid, - @Schema( - allowableValues = {"LABELER", "REVIEWER"}, - defaultValue = "LABELER") - @Parameter( - description = "라벨러/검수자(LABELER/REVIEWER)", - required = true) @RequestParam String type - ) { + @RequestParam(defaultValue = "01022223333", required = true) String userId, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter(description = "라벨러/검수자(LABELER/REVIEWER)", required = true) + @RequestParam + String type) { return ApiResponseDto.ok(labelAllocateService.findUserDetail(userId, uuid, type)); } @@ -211,30 +209,26 @@ public class LabelAllocateApiController { } @Operation( - summary = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록", - description = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록") + summary = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록", + description = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록") @GetMapping("/daily-list") public ApiResponseDto> findDaliyList( - @RequestParam(defaultValue = "0", required = true) int page, - @RequestParam(defaultValue = "20", required = true) int size, - @Parameter( - description = "회차 마스터 key", - required = true, - example = "8584e8d4-53b3-4582-bde2-28a81495a626") - @RequestParam - String uuid, - @Parameter( - description = "사번", - required = true, - example = "123456") - @RequestParam String userId, - @Schema( - allowableValues = {"LABELER", "REVIEWER"}, - defaultValue = "LABELER") - @Parameter( - description = "라벨러/검수자(LABELER/REVIEWER)", - required = true) @RequestParam String type - ) { + @RequestParam(defaultValue = "0", required = true) int page, + @RequestParam(defaultValue = "20", required = true) int size, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid, + @Parameter(description = "사번", required = true, example = "123456") @RequestParam + String userId, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter(description = "라벨러/검수자(LABELER/REVIEWER)", required = true) + @RequestParam + String type) { LabelAllocateDto.searchReq searchReq = new LabelAllocateDto.searchReq(page, size, ""); return ApiResponseDto.ok(labelAllocateService.findDaliyList(searchReq, uuid, userId, type)); } 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 e931ce40..75381dd8 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 @@ -101,9 +101,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -122,9 +122,9 @@ public class LabelAllocateDto { private List labelers; @Schema( - description = "검수자 할당 목록", - example = - """ + description = "검수자 할당 목록", + example = + """ ["K20251216001", "01022225555", "K20251212001" @@ -232,9 +232,9 @@ public class LabelAllocateDto { private Integer stage; @Schema( - description = "라벨러 할당 목록", - example = - """ + description = "라벨러 할당 목록", + example = + """ [ { "userId": "123456", @@ -295,11 +295,10 @@ public class LabelAllocateDto { String[] sortParams = sort.split(","); String property = sortParams[0]; Sort.Direction direction = - sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; return PageRequest.of(page, size, Sort.by(direction, property)); } return PageRequest.of(page, size); } } - } diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 3501da8a..435c2060 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -67,23 +67,17 @@ public class WorkerStatsDto { private Boolean isStagnated; // 레거시 필드 (기존 호환성 유지) - @Deprecated - private Long doneCnt; // completed로 대체 + @Deprecated private Long doneCnt; // completed로 대체 - @Deprecated - private Long skipCnt; // skipped로 대체 + @Deprecated private Long skipCnt; // skipped로 대체 - @Deprecated - private Long remainingCnt; // remaining으로 대체 + @Deprecated private Long remainingCnt; // remaining으로 대체 - @Deprecated - private Long day3AgoDoneCnt; // history.day3Ago로 대체 + @Deprecated private Long day3AgoDoneCnt; // history.day3Ago로 대체 - @Deprecated - private Long day2AgoDoneCnt; // history.day2Ago로 대체 + @Deprecated private Long day2AgoDoneCnt; // history.day2Ago로 대체 - @Deprecated - private Long day1AgoDoneCnt; // history.day1Ago로 대체 + @Deprecated private Long day1AgoDoneCnt; // history.day1Ago로 대체 } @Getter @@ -107,7 +101,6 @@ public class WorkerStatsDto { private Long average; } - @Getter @Setter @Builder 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 ab5a33ac..eb41c6b3 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 @@ -124,12 +124,7 @@ public class LabelAllocateService { * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType, - Integer page, - Integer size) { + Long analUid, String workerType, String search, String sortType, Integer page, Integer size) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -139,8 +134,7 @@ public class LabelAllocateService { // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); + labelAllocateCoreService.findWorkerStatistics(analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); @@ -234,7 +228,8 @@ public class LabelAllocateService { } } - public Page findDaliyList(LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { + public Page findDaliyList( + LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { if (type.equals("LABELER")) { return labelAllocateCoreService.findLabelerDailyStat(searchReq, uuid, userId); } else { 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 2716e8c9..4e8cacfd 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 @@ -57,12 +57,8 @@ public class LabelAllocateCoreService { } public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { - return labelAllocateRepository.findWorkerStatistics( - analUid, workerType, search, sortType); + Long analUid, String workerType, String search, String sortType) { + return labelAllocateRepository.findWorkerStatistics(analUid, workerType, search, sortType); } public WorkProgressInfo findWorkProgressInfo(Long analUid) { @@ -104,11 +100,13 @@ public class LabelAllocateCoreService { labelAllocateRepository.insertInspector(analUid, inspector); } - public Page findLabelerDailyStat(searchReq searchReq, String uuid, String userId) { + public Page findLabelerDailyStat( + searchReq searchReq, String uuid, String userId) { return labelAllocateRepository.findLabelerDailyStat(searchReq, uuid, userId); } - public Page findInspectorDailyStat(searchReq searchReq, String uuid, String userId) { + public Page findInspectorDailyStat( + searchReq searchReq, String uuid, String userId) { return labelAllocateRepository.findInspectorDailyStat(searchReq, uuid, userId); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java index 88b5a15b..ea8e6893 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/LabelingAssignmentEntity.java @@ -52,20 +52,19 @@ public class LabelingAssignmentEntity extends CommonDateEntity { public LabelAllocateDto.Basic toDto() { return new LabelAllocateDto.Basic( - this.assignmentUid, - this.inferenceGeomUid, - this.workerUid, - this.inspectorUid, - this.workState, - this.stagnationYn, - this.assignGroupId, - this.learnGeomUid, - this.analUid, - super.getCreatedDate(), - super.getModifiedDate(), - this.inspectState, - this.workStatDttm, - this.inspectStatDttm - ); + this.assignmentUid, + this.inferenceGeomUid, + this.workerUid, + this.inspectorUid, + this.workState, + this.stagnationYn, + this.assignGroupId, + this.learnGeomUid, + this.analUid, + super.getCreatedDate(), + super.getModifiedDate(), + this.inspectState, + this.workStatDttm, + this.inspectStatDttm); } } 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 a4e7807f..659f03bd 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 @@ -18,7 +18,7 @@ import org.springframework.data.domain.Page; public interface LabelAllocateRepositoryCustom { List fetchNextIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); void assignOwner(List ids, String userId, Long analUid); @@ -35,7 +35,7 @@ public interface LabelAllocateRepositoryCustom { // 작업자 통계 조회 List findWorkerStatistics( - Long analUid, String workerType, String search, String sortType); + Long analUid, String workerType, String search, String sortType); // 작업 진행 현황 조회 WorkProgressInfo findWorkProgressInfo(Long analUid); @@ -48,7 +48,7 @@ public interface LabelAllocateRepositoryCustom { InferenceDetail findInferenceDetail(String uuid); List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); void assignOwnerMove(List sub, String userId); @@ -58,9 +58,11 @@ public interface LabelAllocateRepositoryCustom { void insertInspector(Long analUid, String inspector); - Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId); + Page findLabelerDailyStat( + LabelAllocateDto.searchReq searchReq, String uuid, String userId); - Page findInspectorDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId); + Page findInspectorDailyStat( + LabelAllocateDto.searchReq searchReq, String uuid, String userId); LabelerDetail findInspectorDetail(String userId, String uuid); } 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 dd4ae190..4fee9c19 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 @@ -197,10 +197,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public List findWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, String workerType, String search, String sortType) { // 작업자 유형에 따른 필드 선택 StringExpression workerIdField = @@ -216,8 +213,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto // 검색 조건 (이름 또는 사번으로 검색) BooleanExpression searchCondition = null; if (search != null && !search.isEmpty()) { - searchCondition = memberEntity.name.contains(search) - .or(memberEntity.employeeNo.contains(search)); + searchCondition = + memberEntity.name.contains(search).or(memberEntity.employeeNo.contains(search)); } // 완료, 스킵, 남은 작업 계산 @@ -247,7 +244,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .sum(); // 기본 통계 조회 쿼리 - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; var baseQuery = queryFactory @@ -318,7 +316,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public WorkProgressInfo findWorkProgressInfo(Long analUid) { - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; // 전체 배정 건수 Long totalAssigned = @@ -344,9 +343,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("SKIP")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("SKIP")) .fetchOne(); // 투입된 라벨러 수 @@ -354,9 +351,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.workerUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workerUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.workerUid.isNotNull()) .fetchOne(); // === 검수 통계 === @@ -365,9 +360,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.count()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.workState.eq("DONE")) + .where(analUidCondition, labelingAssignmentEntity.workState.eq("DONE")) .fetchOne(); // 투입된 검수자 수 @@ -375,9 +368,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto queryFactory .select(labelingAssignmentEntity.inspectorUid.countDistinct()) .from(labelingAssignmentEntity) - .where( - analUidCondition, - labelingAssignmentEntity.inspectorUid.isNotNull()) + .where(analUidCondition, labelingAssignmentEntity.inspectorUid.isNotNull()) .fetchOne(); // 남은 작업 건수 계산 @@ -437,7 +428,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ? labelingAssignmentEntity.inspectorUid.eq(workerId) : labelingAssignmentEntity.workerUid.eq(workerId); - BooleanExpression analUidCondition = analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; + BooleanExpression analUidCondition = + analUid != null ? labelingAssignmentEntity.analUid.eq(analUid) : null; Long count = queryFactory @@ -580,29 +572,29 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto QMemberEntity inspector = new QMemberEntity("inspector"); return queryFactory - .select( - Projections.constructor( - LabelerDetail.class, - worker.userRole, - worker.name, - worker.employeeNo, - assignedCnt, - skipCnt, - completeCnt, - percent, - Expressions.constant(0), // TODO: 순위, 꼭 해야할지? - labelingAssignmentEntity.workStatDttm.min(), - inspector.name.min())) - .from(worker) - .innerJoin(labelingAssignmentEntity) - .on( - worker.employeeNo.eq(labelingAssignmentEntity.workerUid), - labelingAssignmentEntity.analUid.eq(analEntity.getId())) - .leftJoin(inspector) - .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) - .where(worker.employeeNo.eq(userId)) - .groupBy(worker.userRole, worker.name, worker.employeeNo) - .fetchOne(); + .select( + Projections.constructor( + LabelerDetail.class, + worker.userRole, + worker.name, + worker.employeeNo, + assignedCnt, + skipCnt, + completeCnt, + percent, + Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + labelingAssignmentEntity.workStatDttm.min(), + inspector.name.min())) + .from(worker) + .innerJoin(labelingAssignmentEntity) + .on( + worker.employeeNo.eq(labelingAssignmentEntity.workerUid), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .leftJoin(inspector) + .on(labelingAssignmentEntity.inspectorUid.eq(inspector.employeeNo)) + .where(worker.employeeNo.eq(userId)) + .groupBy(worker.userRole, worker.name, worker.employeeNo) + .fetchOne(); } @Override @@ -638,17 +630,17 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return null; } - var result = queryFactory - .select( - mapSheetAnalEntity.compareYyyy, - mapSheetAnalEntity.targetYyyy, - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.analStrtDttm - ) - .from(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) - .fetchOne(); + var result = + queryFactory + .select( + mapSheetAnalEntity.compareYyyy, + mapSheetAnalEntity.targetYyyy, + mapSheetAnalEntity.analTitle, + mapSheetAnalEntity.gukyuinApplyDttm, + mapSheetAnalEntity.analStrtDttm) + .from(mapSheetAnalEntity) + .where(mapSheetAnalEntity.id.eq(analUid)) + .fetchOne(); if (result == null) { return null; @@ -661,9 +653,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto ZonedDateTime analStrtDttm = result.get(mapSheetAnalEntity.analStrtDttm); // 변화탐지년도 생성 - String detectionYear = (compareYyyy != null && targetYyyy != null) - ? compareYyyy + "-" + targetYyyy - : null; + String detectionYear = + (compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null; // 회차 추출 (예: "8회차" → "8") String round = extractRoundFromTitle(analTitle); @@ -676,10 +667,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .build(); } - /** - * 제목에서 회차 숫자 추출 - * 예: "8회차", "제8회차" → "8" - */ + /** 제목에서 회차 숫자 추출 예: "8회차", "제8회차" → "8" */ private String extractRoundFromTitle(String title) { if (title == null || title.isEmpty()) { return null; @@ -691,9 +679,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return matcher.find() ? matcher.group(1) : null; } - /** - * ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 - */ + /** ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 */ private String formatDate(ZonedDateTime dateTime) { if (dateTime == null) { return null; @@ -702,211 +688,169 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto } @Override - public Page findLabelerDailyStat(LabelAllocateDto.searchReq searchReq, String uuid, String userId) { + public Page findLabelerDailyStat( + LabelAllocateDto.searchReq searchReq, String uuid, String userId) { // 날짜 포맷 Expression workDate = - Expressions.stringTemplate( - "TO_CHAR({0}, 'YYYY-MM-DD')", - labelingAssignmentEntity.workStatDttm - ); + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.workStatDttm); // 날짜별 전체 건수 - Expression dailyTotalCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*)" - ); + Expression dailyTotalCnt = Expressions.numberTemplate(Long.class, "COUNT(*)"); // ⭐ 전체 기간 총 건수 (윈도우 함수) - Expression totalCnt = - Expressions.numberTemplate( - Long.class, - "SUM(COUNT(*)) OVER ()" - ); + Expression totalCnt = Expressions.numberTemplate(Long.class, "SUM(COUNT(*)) OVER ()"); // 상태별 카운트 (Postgres FILTER 사용) Expression assignedCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'ASSIGNED')", - labelingAssignmentEntity.workState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'ASSIGNED')", + labelingAssignmentEntity.workState); Expression skipCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'SKIP')", - labelingAssignmentEntity.workState - ); + Expressions.numberTemplate( + Long.class, "COUNT(*) FILTER (WHERE {0} = 'SKIP')", labelingAssignmentEntity.workState); Expression completeCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", - labelingAssignmentEntity.workState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.workState); Expression remainCnt = - Expressions.numberTemplate( - Long.class, - "({0} - {1} - {2})", - totalCnt, - skipCnt, - completeCnt - ); + Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt); // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); } Pageable pageable = searchReq.toPageable(); - List foundContent = queryFactory - .select( - Projections.constructor( - LabelingStatDto.class, - workDate, - dailyTotalCnt, - totalCnt, // ⭐ 전체 일자 배정 건수 - assignedCnt, - skipCnt, - completeCnt, - remainCnt - ) - ) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.workerUid.eq(userId), - labelingAssignmentEntity.analUid.eq(analEntity.getId()) - ) - .groupBy(workDate) - .orderBy(labelingAssignmentEntity.workStatDttm.min().asc()) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); + List foundContent = + queryFactory + .select( + Projections.constructor( + LabelingStatDto.class, + workDate, + dailyTotalCnt, + totalCnt, // ⭐ 전체 일자 배정 건수 + assignedCnt, + skipCnt, + completeCnt, + remainCnt)) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.workerUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .groupBy(workDate) + .orderBy(labelingAssignmentEntity.workStatDttm.min().asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); - Long countQuery = queryFactory - .select(workDate) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.workerUid.eq(userId), - labelingAssignmentEntity.analUid.eq(analEntity.getId()) - ) - .distinct() - .fetch() - .stream() - .count(); + Long countQuery = + queryFactory + .select(workDate) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.workerUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .distinct() + .fetch() + .stream() + .count(); return new PageImpl<>(foundContent, pageable, countQuery); } @Override - public Page findInspectorDailyStat(searchReq searchReq, String uuid, String userId) { + public Page findInspectorDailyStat( + searchReq searchReq, String uuid, String userId) { // 날짜 포맷 Expression workDate = - Expressions.stringTemplate( - "TO_CHAR({0}, 'YYYY-MM-DD')", - labelingAssignmentEntity.inspectStatDttm - ); + Expressions.stringTemplate( + "TO_CHAR({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.inspectStatDttm); // 날짜별 전체 건수 - Expression dailyTotalCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*)" - ); + Expression dailyTotalCnt = Expressions.numberTemplate(Long.class, "COUNT(*)"); // ⭐ 전체 기간 총 건수 (윈도우 함수) - Expression totalCnt = - Expressions.numberTemplate( - Long.class, - "SUM(COUNT(*)) OVER ()" - ); + Expression totalCnt = Expressions.numberTemplate(Long.class, "SUM(COUNT(*)) OVER ()"); // 상태별 카운트 (Postgres FILTER 사용) Expression assignedCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'UNCONFIRM')", - labelingAssignmentEntity.inspectState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'UNCONFIRM')", + labelingAssignmentEntity.inspectState); Expression skipCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'EXCEPT')", - labelingAssignmentEntity.inspectState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'EXCEPT')", + labelingAssignmentEntity.inspectState); Expression completeCnt = - Expressions.numberTemplate( - Long.class, - "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", - labelingAssignmentEntity.inspectState - ); + Expressions.numberTemplate( + Long.class, + "COUNT(*) FILTER (WHERE {0} = 'COMPLETE')", + labelingAssignmentEntity.inspectState); Expression remainCnt = - Expressions.numberTemplate( - Long.class, - "({0} - {1} - {2})", - totalCnt, - skipCnt, - completeCnt - ); + Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt); // analUid로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); } Pageable pageable = searchReq.toPageable(); - List foundContent = queryFactory - .select( - Projections.constructor( - LabelingStatDto.class, - workDate, - dailyTotalCnt, - totalCnt, // ⭐ 전체 일자 배정 건수 - assignedCnt, - skipCnt, - completeCnt, - remainCnt - ) - ) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.inspectorUid.eq(userId), - labelingAssignmentEntity.analUid.eq(analEntity.getId()) - ) - .groupBy(workDate) - .orderBy(labelingAssignmentEntity.inspectStatDttm.min().asc()) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); + List foundContent = + queryFactory + .select( + Projections.constructor( + LabelingStatDto.class, + workDate, + dailyTotalCnt, + totalCnt, // ⭐ 전체 일자 배정 건수 + assignedCnt, + skipCnt, + completeCnt, + remainCnt)) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.inspectorUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .groupBy(workDate) + .orderBy(labelingAssignmentEntity.inspectStatDttm.min().asc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); - Long countQuery = queryFactory - .select(workDate) - .from(labelingAssignmentEntity) - .where( - labelingAssignmentEntity.inspectorUid.eq(userId), - labelingAssignmentEntity.analUid.eq(analEntity.getId()) - ) - .distinct() - .fetch() - .stream() - .count(); + Long countQuery = + queryFactory + .select(workDate) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.inspectorUid.eq(userId), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .distinct() + .fetch() + .stream() + .count(); return new PageImpl<>(foundContent, pageable, countQuery); } @@ -914,43 +858,43 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public LabelerDetail findInspectorDetail(String userId, String uuid) { NumberExpression assignedCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.inspectState.eq(InspectState.UNCONFIRM.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.UNCONFIRM.getId())) + .then(1L) + .otherwise((Long) null) + .count(); NumberExpression skipCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.inspectState.eq(InspectState.EXCEPT.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.EXCEPT.getId())) + .then(1L) + .otherwise((Long) null) + .count(); NumberExpression completeCnt = - new CaseBuilder() - .when(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId())) - .then(1L) - .otherwise((Long) null) - .count(); + new CaseBuilder() + .when(labelingAssignmentEntity.inspectState.eq(InspectState.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로 분석 정보 조회 MapSheetAnalInferenceEntity analEntity = - queryFactory - .selectFrom(mapSheetAnalInferenceEntity) - .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) - .fetchOne(); + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid))) + .fetchOne(); if (Objects.isNull(analEntity)) { throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); @@ -960,28 +904,28 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto QMemberEntity worker = new QMemberEntity("worker"); return queryFactory - .select( - Projections.constructor( - LabelerDetail.class, - inspector.userRole, - inspector.name, - inspector.employeeNo, - assignedCnt, - skipCnt, - completeCnt, - percent, - Expressions.constant(0), // TODO: 순위, 꼭 해야할지? - labelingAssignmentEntity.inspectStatDttm.min(), - worker.name.min())) - .from(inspector) - .innerJoin(labelingAssignmentEntity) - .on( - inspector.employeeNo.eq(labelingAssignmentEntity.inspectorUid), - labelingAssignmentEntity.analUid.eq(analEntity.getId())) - .leftJoin(worker) - .on(labelingAssignmentEntity.workerUid.eq(worker.employeeNo)) - .where(inspector.employeeNo.eq(userId)) - .groupBy(inspector.userRole, inspector.name, inspector.employeeNo) - .fetchOne(); + .select( + Projections.constructor( + LabelerDetail.class, + inspector.userRole, + inspector.name, + inspector.employeeNo, + assignedCnt, + skipCnt, + completeCnt, + percent, + Expressions.constant(0), // TODO: 순위, 꼭 해야할지? + labelingAssignmentEntity.inspectStatDttm.min(), + worker.name.min())) + .from(inspector) + .innerJoin(labelingAssignmentEntity) + .on( + inspector.employeeNo.eq(labelingAssignmentEntity.inspectorUid), + labelingAssignmentEntity.analUid.eq(analEntity.getId())) + .leftJoin(worker) + .on(labelingAssignmentEntity.workerUid.eq(worker.employeeNo)) + .where(inspector.employeeNo.eq(userId)) + .groupBy(inspector.userRole, inspector.name, inspector.employeeNo) + .fetchOne(); } } From 462cdc0241a03667e65e6e9a52274e395e67190b Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:37:30 +0900 Subject: [PATCH 47/70] worklist delete --- .../label/LabelAllocateApiController.java | 11 +--- .../kamcoback/label/dto/WorkerStatsDto.java | 15 +---- .../label/service/LabelAllocateService.java | 61 +------------------ 3 files changed, 4 insertions(+), 83 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 cb723a69..e78b9919 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -92,20 +92,13 @@ public class LabelAllocateApiController { }, defaultValue = "NAME_ASC")) @RequestParam(required = false) - String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") - Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") - Integer size) { + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 3501da8a..1bcdc1af 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -200,19 +200,6 @@ public class WorkerStatsDto { @Schema(description = "작업 진행 현황 정보") private WorkProgressInfo progressInfo; - @Schema(description = "작업자 목록") - private List workers; - - @Schema(description = "현재 페이지 번호 (0부터 시작)") - private Integer currentPage; - - @Schema(description = "페이지 크기") - private Integer pageSize; - - @Schema(description = "전체 데이터 수") - private Long totalElements; - - @Schema(description = "전체 페이지 수") - private Integer totalPages; + // workers 필드는 제거되었습니다 (프로젝트 정보와 진행현황만 반환) } } 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 ae0ada1d..40bfa6ca 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 @@ -8,11 +8,8 @@ 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.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; -import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -24,8 +21,6 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class LabelAllocateService { - private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량) - private static final int BATCH_SIZE = 100; // 배정 배치 크기 private final LabelAllocateCoreService labelAllocateCoreService; @@ -117,17 +112,13 @@ public class LabelAllocateService { * @param workerType 작업자 유형 (LABELER/INSPECTOR) * @param search 검색어 (이름 또는 사번) * @param sortType 정렬 조건 - * @param page 페이지 번호 (0부터 시작) - * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( Long analUid, String workerType, String search, - String sortType, - Integer page, - Integer size) { + String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -135,59 +126,9 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - // 작업자 통계 조회 - List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); - - // 각 작업자별 3일치 처리량 조회 - LocalDate today = LocalDate.now(); - for (WorkerStatistics worker : workers) { - Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); - Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); - Long day3Count = - 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(); - - worker.setHistory(history); - - // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때) - if (average < STAGNATION_THRESHOLD) { - worker.setIsStagnated(true); - } - } - - // 페이징 처리 - long totalElements = workers.size(); - int totalPages = (int) Math.ceil((double) totalElements / size); - int fromIndex = page * size; - int toIndex = Math.min(fromIndex + size, workers.size()); - - List pagedWorkers = - (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); - return WorkerListResponse.builder() .projectInfo(projectInfo) .progressInfo(progressInfo) - .workers(pagedWorkers) - .currentPage(page) - .pageSize(size) - .totalElements(totalElements) - .totalPages(totalPages) .build(); } From 78c8cb4a75d6e186b0f8fc978ffe792a6b0ebfbf Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:41:32 +0900 Subject: [PATCH 48/70] build error fix --- .../label/LabelAllocateApiController.java | 11 +--- .../label/service/LabelAllocateService.java | 62 +------------------ .../label/LabelAllocateRepositoryImpl.java | 2 +- 3 files changed, 4 insertions(+), 71 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 cb723a69..e78b9919 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -92,20 +92,13 @@ public class LabelAllocateApiController { }, defaultValue = "NAME_ASC")) @RequestParam(required = false) - String sort, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") - @RequestParam(defaultValue = "0") - Integer page, - @Parameter(description = "페이지 크기", example = "20") - @RequestParam(defaultValue = "20") - Integer size) { + String sort) { // type이 null이면 기본값으로 LABELER 설정 String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type; return ApiResponseDto.ok( - labelAllocateService.getWorkerStatistics( - null, workerType, search, sort, page, size)); + labelAllocateService.getWorkerStatistics(null, workerType, search, sort)); } @Operation(summary = "라벨링작업 관리 > 작업 배정", description = "라벨링작업 관리 > 작업 배정") 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 ae0ada1d..5a31c4af 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 @@ -8,11 +8,8 @@ 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.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; -import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -24,9 +21,6 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class LabelAllocateService { - private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량) - private static final int BATCH_SIZE = 100; // 배정 배치 크기 - private final LabelAllocateCoreService labelAllocateCoreService; public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { @@ -117,17 +111,13 @@ public class LabelAllocateService { * @param workerType 작업자 유형 (LABELER/INSPECTOR) * @param search 검색어 (이름 또는 사번) * @param sortType 정렬 조건 - * @param page 페이지 번호 (0부터 시작) - * @param size 페이지 크기 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( Long analUid, String workerType, String search, - String sortType, - Integer page, - Integer size) { + String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -135,59 +125,9 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - // 작업자 통계 조회 - List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); - - // 각 작업자별 3일치 처리량 조회 - LocalDate today = LocalDate.now(); - for (WorkerStatistics worker : workers) { - Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); - Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); - Long day3Count = - 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(); - - worker.setHistory(history); - - // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때) - if (average < STAGNATION_THRESHOLD) { - worker.setIsStagnated(true); - } - } - - // 페이징 처리 - long totalElements = workers.size(); - int totalPages = (int) Math.ceil((double) totalElements / size); - int fromIndex = page * size; - int toIndex = Math.min(fromIndex + size, workers.size()); - - List pagedWorkers = - (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); - return WorkerListResponse.builder() .projectInfo(projectInfo) .progressInfo(progressInfo) - .workers(pagedWorkers) - .currentPage(page) - .pageSize(size) - .totalElements(totalElements) - .totalPages(totalPages) .build(); } 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 4fee9c19..1abb30f3 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 @@ -772,7 +772,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public Page findInspectorDailyStat( - searchReq searchReq, String uuid, String userId) { + LabelAllocateDto.searchReq searchReq, String uuid, String userId) { // 날짜 포맷 Expression workDate = Expressions.stringTemplate( From a15b319bc2ffcd3051da88a872814d39ad0fd483 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 13:45:39 +0900 Subject: [PATCH 49/70] spotless --- .../kamco/cd/kamcoback/label/dto/WorkerStatsDto.java | 1 - .../kamcoback/label/service/LabelAllocateService.java | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 040f12d2..5b4dab5c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -1,7 +1,6 @@ package com.kamco.cd.kamcoback.label.dto; import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; 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 5a31c4af..f11bb7de 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 @@ -114,10 +114,7 @@ public class LabelAllocateService { * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, String workerType, String search, String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -125,10 +122,7 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - return WorkerListResponse.builder() - .projectInfo(projectInfo) - .progressInfo(progressInfo) - .build(); + return WorkerListResponse.builder().projectInfo(projectInfo).progressInfo(progressInfo).build(); } public InferenceDetail findInferenceDetail(String uuid) { From 3bc801053a01efdbd79b9f92d53b867ef13d1f73 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 13:54:51 +0900 Subject: [PATCH 50/70] =?UTF-8?q?merge=20=ED=95=A0=20=EB=95=8C=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=EB=90=9C=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 45 ++++++++++++++++--- .../label/service/LabelAllocateService.java | 19 +++++++- 2 files changed, 56 insertions(+), 8 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 e78b9919..db1c7521 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import io.swagger.v3.oas.annotations.Operation; @@ -18,6 +19,7 @@ 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; @@ -147,18 +149,24 @@ public class LabelAllocateApiController { } @Operation( - summary = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", - description = "작업현황 관리 > 라벨러 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") - @GetMapping("/labeler-detail") - public ApiResponseDto findLabelerDetail( + summary = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일", + description = "작업현황 관리 > 라벨러/검수자 상세 정보, 작업이관 팝업 내 라벨러 상세 정보 동일") + @GetMapping("/user-detail") + public ApiResponseDto findUserDetail( @RequestParam(defaultValue = "01022223333", required = true) String userId, @Parameter( description = "회차 마스터 key", required = true, example = "8584e8d4-53b3-4582-bde2-28a81495a626") @RequestParam - String uuid) { - return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, uuid)); + String uuid, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter(description = "라벨러/검수자(LABELER/REVIEWER)", required = true) + @RequestParam + String type) { + return ApiResponseDto.ok(labelAllocateService.findUserDetail(userId, uuid, type)); } @Operation(summary = "작업현황 관리 > 상세 > 작업 이관", description = "작업현황 관리 > 상세 > 작업 이관") @@ -195,4 +203,29 @@ public class LabelAllocateApiController { dto.getCompareYyyy(), dto.getTargetYyyy())); } + + @Operation( + summary = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록", + description = "라벨링작업 관리 > 상세 > 라벨러/검수자 일별 작업량 목록") + @GetMapping("/daily-list") + public ApiResponseDto> findDaliyList( + @RequestParam(defaultValue = "0", required = true) int page, + @RequestParam(defaultValue = "20", required = true) int size, + @Parameter( + description = "회차 마스터 key", + required = true, + example = "8584e8d4-53b3-4582-bde2-28a81495a626") + @RequestParam + String uuid, + @Parameter(description = "사번", required = true, example = "123456") @RequestParam + String userId, + @Schema( + allowableValues = {"LABELER", "REVIEWER"}, + defaultValue = "LABELER") + @Parameter(description = "라벨러/검수자(LABELER/REVIEWER)", required = true) + @RequestParam + String type) { + LabelAllocateDto.searchReq searchReq = new LabelAllocateDto.searchReq(page, size, ""); + return ApiResponseDto.ok(labelAllocateService.findDaliyList(searchReq, uuid, userId, type)); + } } 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 f11bb7de..1ea0f5b9 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 @@ -6,6 +6,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; @@ -13,6 +14,7 @@ import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -158,7 +160,20 @@ public class LabelAllocateService { return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } - public LabelerDetail findLabelerDetail(String userId, String uuid) { - return labelAllocateCoreService.findLabelerDetail(userId, uuid); + public LabelerDetail findUserDetail(String userId, String uuid, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDetail(userId, uuid); + } else { + return labelAllocateCoreService.findInspectorDetail(userId, uuid); + } + } + + public Page findDaliyList( + LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDailyStat(searchReq, uuid, userId); + } else { + return labelAllocateCoreService.findInspectorDailyStat(searchReq, uuid, userId); + } } } From 097884240080f2595c73dd2f924170630c9af261 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 14:13:24 +0900 Subject: [PATCH 51/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=20=EC=9D=B4=EA=B4=80?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelAllocateApiController.java | 4 +- .../kamcoback/label/dto/LabelAllocateDto.java | 8 ++-- .../label/service/LabelAllocateService.java | 6 +-- .../core/LabelAllocateCoreService.java | 9 ++++- .../label/LabelAllocateRepositoryCustom.java | 7 +++- .../label/LabelAllocateRepositoryImpl.java | 37 +++++++++++++------ 6 files changed, 48 insertions(+), 23 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 db1c7521..00e507de 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -197,11 +197,11 @@ public class LabelAllocateApiController { return ApiResponseDto.okObject( labelAllocateService.allocateMove( - dto.getAutoType(), dto.getStage(), dto.getLabelers(), dto.getCompareYyyy(), - dto.getTargetYyyy())); + dto.getTargetYyyy(), + dto.getUserId())); } @Operation( 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 75381dd8..12c95bb7 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 @@ -225,14 +225,11 @@ public class LabelAllocateDto { @AllArgsConstructor public static class AllocateMoveDto { - @Schema(description = "자동/수동여부(AUTO/MANUAL)", example = "AUTO") - private String autoType; - @Schema(description = "회차", example = "4") private Integer stage; @Schema( - description = "라벨러 할당 목록", + description = "이관할 라벨러 할당량", example = """ [ @@ -253,6 +250,9 @@ public class LabelAllocateDto { @Schema(description = "기준년도", example = "2024") private Integer targetYyyy; + + @Schema(description = "대상 사번", example = "01022223333") + private String userId; } @Getter 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 1ea0f5b9..fa850685 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 @@ -132,11 +132,11 @@ public class LabelAllocateService { } public ApiResponseDto.ResponseObj allocateMove( - String autoType, Integer stage, List targetUsers, Integer compareYyyy, - Integer targetYyyy) { + Integer targetYyyy, + String userId) { Long lastId = null; Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); @@ -147,7 +147,7 @@ public class LabelAllocateService { List allIds = labelAllocateCoreService.fetchNextMoveIds( - lastId, chargeCnt, compareYyyy, targetYyyy, stage); + lastId, chargeCnt, compareYyyy, targetYyyy, stage, userId); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); 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 4e8cacfd..30f788bd 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 @@ -79,9 +79,14 @@ public class LabelAllocateCoreService { } public List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, + Long batchSize, + Integer compareYyyy, + Integer targetYyyy, + Integer stage, + String userId) { return labelAllocateRepository.fetchNextMoveIds( - lastId, batchSize, compareYyyy, targetYyyy, stage); + lastId, batchSize, compareYyyy, targetYyyy, stage, userId); } public void assignOwnerMove(List sub, String userId) { 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 659f03bd..ff30ac7a 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 @@ -48,7 +48,12 @@ public interface LabelAllocateRepositoryCustom { InferenceDetail findInferenceDetail(String uuid); List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage); + Long lastId, + Long batchSize, + Integer compareYyyy, + Integer targetYyyy, + Integer stage, + String userId); void assignOwnerMove(List sub, 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 1abb30f3..f42b83c2 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 @@ -493,20 +493,35 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto @Override public List fetchNextMoveIds( - Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) { + Long lastId, + Long batchSize, + Integer compareYyyy, + Integer targetYyyy, + Integer stage, + String userId) { + + MapSheetAnalInferenceEntity analEntity = + queryFactory + .selectFrom(mapSheetAnalInferenceEntity) + .where( + mapSheetAnalInferenceEntity.compareYyyy.eq(compareYyyy), + mapSheetAnalInferenceEntity.targetYyyy.eq(targetYyyy), + mapSheetAnalInferenceEntity.stage.eq(stage)) + .fetchOne(); + + if (Objects.isNull(analEntity)) { + throw new EntityNotFoundException("MapSheetAnalInferenceEntity not found for analUid: "); + } return queryFactory - .select(mapSheetAnalDataInferenceGeomEntity.geoUid) - .from(mapSheetAnalDataInferenceGeomEntity) + .select(labelingAssignmentEntity.inferenceGeomUid) + .from(labelingAssignmentEntity) .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()) + labelingAssignmentEntity.workerUid.eq(userId), + labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), + labelingAssignmentEntity.analUid.eq(analEntity.getId()), + lastId == null ? null : labelingAssignmentEntity.inferenceGeomUid.gt(lastId)) + .orderBy(labelingAssignmentEntity.inferenceGeomUid.asc()) .limit(batchSize) .fetch(); } From 2db7177eddced5637a3801096cc1e2626c135d89 Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 5 Jan 2026 14:22:38 +0900 Subject: [PATCH 52/70] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20API=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelWorkerApiController.java | 33 +++++ .../cd/kamcoback/label/dto/LabelWorkDto.java | 84 ++++++++++++ .../label/service/LabelAllocateService.java | 15 +-- .../label/service/LabelWorkService.java | 8 ++ .../postgres/core/LabelWorkCoreService.java | 7 + .../label/LabelWorkRepositoryCustom.java | 5 + .../label/LabelWorkRepositoryImpl.java | 125 ++++++++++++++++++ 7 files changed, 265 insertions(+), 12 deletions(-) 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 d07083e1..0337123c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -5,6 +5,9 @@ import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngSearchReq; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerStateSearchReq; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.service.LabelWorkService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -61,4 +64,34 @@ public class LabelWorkerApiController { searchReq.setSize(size); return ApiResponseDto.ok(labelWorkService.labelWorkMngList(searchReq)); } + + @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) + }) + @GetMapping("/work-state-list") + public ApiResponseDto> findWorkStateList( + @Parameter(description = "유형", example = "LABELER") @RequestParam(required = false) String userRole, + @Parameter(description = "검색어", example = "20261201") @RequestParam(required = false) String searchVal, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size) { + + LabelWorkDto.WorkerStateSearchReq searchReq = new WorkerStateSearchReq(); + searchReq.setUserRole(userRole); + searchReq.setSearchVal(searchVal); + searchReq.setPage(page); + searchReq.setSize(size); + return ApiResponseDto.ok(labelWorkService.findlabelWorkStateList(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 index 024f89b5..db5f5cb5 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 @@ -3,9 +3,11 @@ package com.kamco.cd.kamcoback.label.dto; 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 com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import io.swagger.v3.oas.annotations.media.Schema; import java.time.ZonedDateTime; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -93,4 +95,86 @@ public class LabelWorkDto { return PageRequest.of(page, size); } } + + + @Getter + @Setter + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Schema(description = "작업자 통계 응답") + public static class WorkerState { + + @Schema(description = "작업자 유형 (LABELER/INSPECTOR)") + private String userRole; + + @Schema(description = "작업자 ID (사번)") + private String name; + + @Schema(description = "작업자 이름") + private String userId; + + @Schema(description = "배정개수") + private Long assignedCnt; + + @Schema(description = "완료개수") + private Long doneCnt; + + @Schema(description = "Skip개수") + private Long skipCnt; + + @Schema(description = "Skip개수") + private Long day3AgoDoneCnt; + + @Schema(description = "Skip개수") + private Long day2AgoDoneCnt; + + @Schema(description = "Skip개수") + private Long day1AgoDoneCnt; + + public Long getremindCnt() { + return this.assignedCnt - this.doneCnt; + } + + public double getDoneRate() { + if (this.doneCnt == null || this.assignedCnt == 0) { + return 0.0; + } + return (double) this.doneCnt / this.assignedCnt * 100.0; + } + + public String getUserRoleName() { + if (this.userRole.equals("LABELER")) { + return "라벨러"; + } + return "검수자"; + } + + } + + @Schema(name = "WorkerStateSearchReq", description = "라벨작업관리 검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class WorkerStateSearchReq { + + // 페이징 파라미터 + @Schema(description = "페이지 번호 (0부터 시작) ", example = "0") + private int page = 0; + + @Schema(description = "페이지 크기", example = "20") + private int size = 20; + + @Schema(description = "유형", example = "LABELER") + private String userRole; + + @Schema(description = "종료일", example = "20261201") + private String searchVal; + + public Pageable toPageable() { + + 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 ae0ada1d..6681d1e4 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 @@ -135,6 +135,8 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); + + // 작업자 통계 조회 List workers = labelAllocateCoreService.findWorkerStatistics( @@ -155,20 +157,9 @@ public class LabelAllocateService { long average = (day1Count + day2Count + day3Count) / 3; - DailyHistory history = - DailyHistory.builder() - .day1Ago(day1Count) - .day2Ago(day2Count) - .day3Ago(day3Count) - .average(average) - .build(); - worker.setHistory(history); - // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때) - if (average < STAGNATION_THRESHOLD) { - worker.setIsStagnated(true); - } + } // 페이징 처리 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 3a8d6553..ceff3603 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 @@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.label.service; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelWorkCoreService; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -21,4 +23,10 @@ public class LabelWorkService { return labelWorkCoreService.labelWorkMngList(searchReq); } + + + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) { + + return labelWorkCoreService.findlabelWorkStateList(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 index a1be92a5..3336c07d 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 @@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.repository.label.LabelWorkRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -16,4 +18,9 @@ public class LabelWorkCoreService { public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq) { return labelWorkRepository.labelWorkMngList(searchReq); } + + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) + { + return labelWorkRepository.findlabelWorkStateList(searchReq); + }; } 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 6a27f5d0..c123cf9f 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 @@ -2,9 +2,14 @@ package com.kamco.cd.kamcoback.postgres.repository.label; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import org.springframework.data.domain.Page; public interface LabelWorkRepositoryCustom { public Page labelWorkMngList(LabelWorkDto.LabelWorkMngSearchReq searchReq); + + Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq 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 index e17da42c..8d3e3154 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,10 +1,18 @@ 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.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.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; +import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; +import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Projections; @@ -14,6 +22,7 @@ import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; import lombok.extern.slf4j.Slf4j; @@ -135,4 +144,120 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport return new PageImpl<>(foundContent, pageable, countQuery); } + + + @Override + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq){ + Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize()); + BooleanBuilder whereBuilder = new BooleanBuilder(); + BooleanBuilder whereSubBuilder = new BooleanBuilder(); + + LocalDate threeDaysAgo = LocalDate.now().minusDays(3); + String s3 = threeDaysAgo.format(DateTimeFormatter.ofPattern("YYYY-MM-DD")); + + LocalDate twoDaysAgo = LocalDate.now().minusDays(2); + String s2 = twoDaysAgo.format(DateTimeFormatter.ofPattern("YYYY-MM-DD")); + + LocalDate oneDaysAgo = LocalDate.now().minusDays(1); + String s1 = oneDaysAgo.format(DateTimeFormatter.ofPattern("YYYY-MM-DD")); + + if (searchReq.getUserRole() != null && ! searchReq.getUserRole().isEmpty()) { + whereSubBuilder.and(memberEntity.userRole.eq(searchReq.getUserRole())); + } + + if (searchReq.getSearchVal() != null && ! searchReq.getSearchVal().isEmpty()) { + whereSubBuilder.and( + Expressions.stringTemplate( + "{0}",memberEntity.userId) + .likeIgnoreCase("%" + searchReq.getSearchVal() + "%") + .or( + Expressions.stringTemplate( + "{0}",memberEntity.name) + .likeIgnoreCase("%" + searchReq.getSearchVal() + "%") + ) + ); + } + + whereSubBuilder.and( + labelingAssignmentEntity.workerUid.eq(memberEntity.userId)); + + List foundContent = + queryFactory + .select( + Projections.constructor( + WorkerState.class, + memberEntity.userRole, + memberEntity.name, + memberEntity.userId, + labelingAssignmentEntity.workerUid.count().as("assignedCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum() + .as("doneCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum() + .as("skipCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s3) ) + ) + .then(1L) + .otherwise(0L) + .sum() + .as("day3AgoDoneCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s2) ) + ) + .then(1L) + .otherwise(0L) + .sum() + .as("day2AgoDoneCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s1) ) + ) + .then(1L) + .otherwise(0L) + .sum() + .as("day1AgoDoneCnt") + )) + .from(labelingAssignmentEntity) + .innerJoin(memberEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy( + memberEntity.userRole, + memberEntity.name, + memberEntity.userId) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + /* + Long countQuery = + queryFactory + .select(labelingAssignmentEntity.workerUid.count()) + .from(labelingAssignmentEntity) + .where(whereBuilder) + .fetchOne(); + */ + + Long countQuery = foundContent.stream().count(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + } From 539831c71268eedf185f8076c7b26b1e232f9f12 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 14:39:08 +0900 Subject: [PATCH 53/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=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 --- .../Innopam/InnopamApiController.java | 28 ++++++++++++++- .../postgres/core/DetectMastCoreService.java | 5 +++ .../DetectMastRepositoryCustom.java | 3 ++ .../repository/DetectMastRepositoryImpl.java | 35 +++++++++++++++++++ .../Innopam/service/DetectMastService.java | 24 ++++++++++++- .../InferenceResultRepositoryImpl.java | 4 +-- .../label/LabelWorkRepositoryImpl.java | 10 +++++- 7 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java index 56f027e4..e547f1b8 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java @@ -18,6 +18,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -113,7 +114,7 @@ public class InnopamApiController { return detectMastService.selectDetectMast(dtctMstId); } - @Operation(summary = "탐지객체 PNU 리스트 조회", description = "탐지객체 PNU 리스트 조회") + @Operation(summary = "탐지객체 랜덤 PNU 리스트 조회", description = "탐지객체 PNU 랜덤값을 생성해서 보여준다") @ApiResponses( value = { @ApiResponse( @@ -149,4 +150,29 @@ public class InnopamApiController { detectMastSearch.setFeatureId(featureId); return new FeaturePnuDto(); } + + @Operation( + summary = "탐지객체 랜덤 PNU GEOM 업데이트(이노펨에 없는 API)", + description = "탐지객체 랜덤 PNU GEOM 업데이트(이노펨에 없는 API)") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "201", + description = "pnu 업데이트 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Integer.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}") + public Integer updatePnuList( + @PathVariable String cprsBfYr, @PathVariable String cprsAfYr, @PathVariable String dtctSno) { + DetectMastSearch detectMastSearch = new DetectMastSearch(); + detectMastSearch.setCprsAdYr(cprsAfYr); + detectMastSearch.setCprsBfYr(cprsBfYr); + detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); + return detectMastService.updatePnuData(detectMastSearch); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java index 6125209b..6a77bcee 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/core/DetectMastCoreService.java @@ -4,6 +4,7 @@ import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.Basic; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastReq; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastEntity; import com.kamco.cd.kamcoback.Innopam.postgres.repository.DetectMastRepository; import java.util.List; @@ -59,4 +60,8 @@ public class DetectMastCoreService { DetectMastEntity detectMastEntity = detectMastRepository.findPnuData(detectMast); return detectMastEntity.getPathNm(); } + + public Integer updatePnu(List list) { + return detectMastRepository.updateGeomPnu(list); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java index 93ce4fb4..47d1ea99 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryCustom.java @@ -1,6 +1,7 @@ package com.kamco.cd.kamcoback.Innopam.postgres.repository; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastEntity; import java.util.List; @@ -9,4 +10,6 @@ public interface DetectMastRepositoryCustom { public List findDetectMastList(DetectMastSearch detectMast); public DetectMastEntity findPnuData(DetectMastSearch detectMast); + + Integer updateGeomPnu(List list); } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java index c2c658e2..78888af4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/postgres/repository/DetectMastRepositoryImpl.java @@ -2,10 +2,14 @@ package com.kamco.cd.kamcoback.Innopam.postgres.repository; import static com.kamco.cd.kamcoback.Innopam.postgres.entity.QDetectMastEntity.detectMastEntity; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; +import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastEntity; import com.querydsl.core.BooleanBuilder; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; @@ -15,7 +19,9 @@ import org.springframework.stereotype.Repository; @RequiredArgsConstructor public class DetectMastRepositoryImpl implements DetectMastRepositoryCustom { + private final EntityManager em; private final JPAQueryFactory queryFactory; + private final ObjectMapper objectMapper; @Override public List findDetectMastList(DetectMastSearch detectMast) { @@ -56,4 +62,33 @@ public class DetectMastRepositoryImpl implements DetectMastRepositoryCustom { .where(whereBuilder) .fetchOne(); } + + @Override + public Integer updateGeomPnu(List list) { + if (list == null || list.isEmpty()) { + return 0; + } + + String sql = + """ + UPDATE tb_map_sheet_anal_data_inference_geom g + SET pnu = j.pnu + FROM ( + SELECT + (elem->>'featureId')::uuid AS feature_uuid, + (elem->>'pnu')::bigint AS pnu + FROM jsonb_array_elements(CAST(:json AS jsonb)) AS elem + ) j + WHERE g.uuid = j.feature_uuid; + """; + + String json = ""; + try { + json = objectMapper.writeValueAsString(list); + } catch (JsonProcessingException e) { + throw new RuntimeException("PNU 업데이트 실패", e); + } + + return em.createNativeQuery(sql).setParameter("json", json).executeUpdate(); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java index 21ea3d64..a2f41dfe 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -57,12 +57,34 @@ public class DetectMastService { String dirPath = "local".equals(profile) - ? "/Users/bokmin/detect/result/2023_2024/4" + ? "/Users/bokmin/detect/result/" + + detectMast.getCprsBfYr() + + "_" + + detectMast.getCprsAdYr() + + "/" + + detectMast.getDtctSno() : detectMastCoreService.findPnuData(detectMast); return extractFeaturePnusRandom(dirPath); } + @Transactional + public Integer updatePnuData(DetectMastSearch detectMast) { + + String dirPath = + "local".equals(profile) + ? "/Users/bokmin/detect/result/" + + detectMast.getCprsBfYr() + + "_" + + detectMast.getCprsAdYr() + + "/" + + detectMast.getDtctSno() + : detectMastCoreService.findPnuData(detectMast); + + List list = extractFeaturePnusRandom(dirPath); + return detectMastCoreService.updatePnu(list); + } + /** 하위 폴더까지 .geojson 파일들에서 polygon_id만 뽑음 병렬처리(parallel) 제거: IO + parallel은 거의 항상 느려짐 */ private List extractFeaturePnusRandom(String dirPath) { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index 37f0c8a6..2ea1a66a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -5,7 +5,6 @@ import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; import java.time.ZonedDateTime; import java.util.List; import lombok.RequiredArgsConstructor; @@ -16,8 +15,7 @@ import org.springframework.stereotype.Repository; public class InferenceResultRepositoryImpl implements InferenceResultRepositoryCustom { private final JPAQueryFactory queryFactory; - - @PersistenceContext private final EntityManager em; + private final EntityManager em; /** tb_map_sheet_anal_data_inference */ private final QMapSheetAnalDataInferenceEntity inferenceEntity = 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 761d9f12..55d5586f 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 @@ -124,7 +124,15 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport mapSheetAnalInferenceEntity.stage, mapSheetAnalDataInferenceEntity.createdDttm.min(), mapSheetAnalDataInferenceGeomEntity.dataUid.count(), - mapSheetAnalDataInferenceGeomEntity.dataUid.count(), + new CaseBuilder() + .when( + mapSheetAnalDataInferenceGeomEntity + .pnu + .isNotNull() + .and(mapSheetAnalDataInferenceGeomEntity.pnu.ne(0L))) + .then(1L) + .otherwise(0L) + .sum(), new CaseBuilder() .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("STOP")) .then(1L) From 338e9c448c9548876cd0ff4338d85105adff30ca Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 14:46:26 +0900 Subject: [PATCH 54/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=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 --- .../label/LabelWorkerApiController.java | 37 ++-- .../cd/kamcoback/label/dto/LabelWorkDto.java | 5 +- .../label/service/LabelAllocateService.java | 17 +- .../label/service/LabelWorkService.java | 4 +- .../postgres/core/LabelWorkCoreService.java | 11 +- .../label/LabelWorkRepositoryCustom.java | 4 +- .../label/LabelWorkRepositoryImpl.java | 180 +++++++++--------- 7 files changed, 118 insertions(+), 140 deletions(-) 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 fa7cf915..3d85d264 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelWorkerApiController.java @@ -8,7 +8,6 @@ import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngSearchReq; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerStateSearchReq; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.service.LabelWorkService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -108,25 +107,27 @@ public class LabelWorkerApiController { @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) - }) + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Page.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @GetMapping("/work-state-list") public ApiResponseDto> findWorkStateList( - @Parameter(description = "유형", example = "LABELER") @RequestParam(required = false) String userRole, - @Parameter(description = "검색어", example = "20261201") @RequestParam(required = false) String searchVal, - @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") - int page, - @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") - int size) { + @Parameter(description = "유형", example = "LABELER") @RequestParam(required = false) + String userRole, + @Parameter(description = "검색어", example = "20261201") @RequestParam(required = false) + String searchVal, + @Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") + int page, + @Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20") + int size) { LabelWorkDto.WorkerStateSearchReq searchReq = new WorkerStateSearchReq(); searchReq.setUserRole(userRole); 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 d895ad1b..de845206 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 @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; 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 com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import io.swagger.v3.oas.annotations.media.Schema; import java.time.ZonedDateTime; import java.util.UUID; @@ -130,7 +129,6 @@ public class LabelWorkDto { } } - @Getter @Setter @Builder @@ -167,7 +165,7 @@ public class LabelWorkDto { private Long day1AgoDoneCnt; public Long getremindCnt() { - return this.assignedCnt - this.doneCnt; + return this.assignedCnt - this.doneCnt; } public double getDoneRate() { @@ -183,7 +181,6 @@ public class LabelWorkDto { } return "검수자"; } - } @Schema(name = "WorkerStateSearchReq", description = "라벨작업관리 검색 요청") 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 6681d1e4..df8b039a 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 @@ -8,7 +8,6 @@ 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.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.DailyHistory; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; @@ -122,12 +121,7 @@ public class LabelAllocateService { * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType, - Integer page, - Integer size) { + Long analUid, String workerType, String search, String sortType, Integer page, Integer size) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -135,12 +129,9 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - - // 작업자 통계 조회 List workers = - labelAllocateCoreService.findWorkerStatistics( - analUid, workerType, search, sortType); + labelAllocateCoreService.findWorkerStatistics(analUid, workerType, search, sortType); // 각 작업자별 3일치 처리량 조회 LocalDate today = LocalDate.now(); @@ -156,10 +147,6 @@ public class LabelAllocateService { worker.getWorkerId(), workerType, today.minusDays(3), analUid); long average = (day1Count + day2Count + day3Count) / 3; - - - - } // 페이징 처리 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 3e336a38..f6405984 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 @@ -3,9 +3,8 @@ package com.kamco.cd.kamcoback.label.service; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; -import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; import com.kamco.cd.kamcoback.postgres.core.LabelWorkCoreService; import java.util.List; import java.util.UUID; @@ -52,7 +51,6 @@ public class LabelWorkService { return labelWorkCoreService.getChangeDetectYear(); } - public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) { return labelWorkCoreService.findlabelWorkStateList(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 index ffec3b34..4b236c13 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,10 +1,13 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.ChangeDetectYear; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.repository.label.LabelWorkRepository; +import java.util.List; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -40,10 +43,10 @@ public class LabelWorkCoreService { return labelWorkRepository.labelWorkMngList(searchReq); } - public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) - { + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) { return labelWorkRepository.findlabelWorkStateList(searchReq); - }; + } + ; /** * 작업배정 정보 조회 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 4db287c3..baf773e7 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 @@ -2,9 +2,8 @@ package com.kamco.cd.kamcoback.postgres.repository.label; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; -import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; +import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import java.util.List; import java.util.UUID; @@ -19,5 +18,4 @@ public interface LabelWorkRepositoryCustom { LabelWorkMngDetail findLabelWorkMngDetail(UUID uuid); Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq 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 index 0b56ace1..dbc1d4cb 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,21 +1,15 @@ 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.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.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; -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.LabelWorkDto; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMng; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.LabelWorkMngDetail; import com.kamco.cd.kamcoback.label.dto.LabelWorkDto.WorkerState; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; -import com.kamco.cd.kamcoback.mapsheet.dto.MapSheetMngDto; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity; import com.querydsl.core.BooleanBuilder; @@ -196,22 +190,21 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport */ Long total = - queryFactory - .select(mapSheetAnalInferenceEntity.uuid.countDistinct()) - .from(mapSheetAnalInferenceEntity) - .innerJoin(mapSheetAnalDataInferenceEntity) - .on(whereSubDataBuilder) - .innerJoin(mapSheetAnalDataInferenceGeomEntity) - .on(whereSubBuilder) - .where(whereBuilder) - .fetchOne(); + queryFactory + .select(mapSheetAnalInferenceEntity.uuid.countDistinct()) + .from(mapSheetAnalInferenceEntity) + .innerJoin(mapSheetAnalDataInferenceEntity) + .on(whereSubDataBuilder) + .innerJoin(mapSheetAnalDataInferenceGeomEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .fetchOne(); return new PageImpl<>(foundContent, pageable, total); } - @Override - public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq){ + public Page findlabelWorkStateList(LabelWorkDto.WorkerStateSearchReq searchReq) { Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize()); BooleanBuilder whereBuilder = new BooleanBuilder(); BooleanBuilder whereSubBuilder = new BooleanBuilder(); @@ -225,89 +218,92 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport LocalDate oneDaysAgo = LocalDate.now().minusDays(1); String s1 = oneDaysAgo.format(DateTimeFormatter.ofPattern("YYYY-MM-DD")); - if (searchReq.getUserRole() != null && ! searchReq.getUserRole().isEmpty()) { + if (searchReq.getUserRole() != null && !searchReq.getUserRole().isEmpty()) { whereSubBuilder.and(memberEntity.userRole.eq(searchReq.getUserRole())); } - if (searchReq.getSearchVal() != null && ! searchReq.getSearchVal().isEmpty()) { + if (searchReq.getSearchVal() != null && !searchReq.getSearchVal().isEmpty()) { whereSubBuilder.and( - Expressions.stringTemplate( - "{0}",memberEntity.userId) - .likeIgnoreCase("%" + searchReq.getSearchVal() + "%") - .or( - Expressions.stringTemplate( - "{0}",memberEntity.name) + Expressions.stringTemplate("{0}", memberEntity.userId) .likeIgnoreCase("%" + searchReq.getSearchVal() + "%") - ) - ); + .or( + Expressions.stringTemplate("{0}", memberEntity.name) + .likeIgnoreCase("%" + searchReq.getSearchVal() + "%"))); } - whereSubBuilder.and( - labelingAssignmentEntity.workerUid.eq(memberEntity.userId)); + whereSubBuilder.and(labelingAssignmentEntity.workerUid.eq(memberEntity.userId)); List foundContent = - queryFactory - .select( - Projections.constructor( - WorkerState.class, - memberEntity.userRole, - memberEntity.name, - memberEntity.userId, - labelingAssignmentEntity.workerUid.count().as("assignedCnt"), - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum() - .as("doneCnt"), - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("SKIP")) - .then(1L) - .otherwise(0L) - .sum() - .as("skipCnt"), - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE") - .and( - Expressions.stringTemplate( - "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s3) ) - ) - .then(1L) - .otherwise(0L) - .sum() - .as("day3AgoDoneCnt"), - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE") - .and( - Expressions.stringTemplate( - "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s2) ) - ) - .then(1L) - .otherwise(0L) - .sum() - .as("day2AgoDoneCnt"), - new CaseBuilder() - .when(labelingAssignmentEntity.workState.eq("DONE") - .and( - Expressions.stringTemplate( - "to_char({0}, 'YYYY-MM-DD')", labelingAssignmentEntity.modifiedDate).eq(s1) ) - ) - .then(1L) - .otherwise(0L) - .sum() - .as("day1AgoDoneCnt") - )) - .from(labelingAssignmentEntity) - .innerJoin(memberEntity) - .on(whereSubBuilder) - .where(whereBuilder) - .groupBy( - memberEntity.userRole, - memberEntity.name, - memberEntity.userId) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); + queryFactory + .select( + Projections.constructor( + WorkerState.class, + memberEntity.userRole, + memberEntity.name, + memberEntity.userId, + labelingAssignmentEntity.workerUid.count().as("assignedCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum() + .as("doneCnt"), + new CaseBuilder() + .when(labelingAssignmentEntity.workState.eq("SKIP")) + .then(1L) + .otherwise(0L) + .sum() + .as("skipCnt"), + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", + labelingAssignmentEntity.modifiedDate) + .eq(s3))) + .then(1L) + .otherwise(0L) + .sum() + .as("day3AgoDoneCnt"), + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", + labelingAssignmentEntity.modifiedDate) + .eq(s2))) + .then(1L) + .otherwise(0L) + .sum() + .as("day2AgoDoneCnt"), + new CaseBuilder() + .when( + labelingAssignmentEntity + .workState + .eq("DONE") + .and( + Expressions.stringTemplate( + "to_char({0}, 'YYYY-MM-DD')", + labelingAssignmentEntity.modifiedDate) + .eq(s1))) + .then(1L) + .otherwise(0L) + .sum() + .as("day1AgoDoneCnt"))) + .from(labelingAssignmentEntity) + .innerJoin(memberEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.userId) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); /* Long countQuery = @@ -376,6 +372,4 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport mapSheetAnalInferenceEntity.createdDttm) .fetchOne(); } - - } From 053d33731432105aa0e660987b539a7286abbd1a Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 13:41:32 +0900 Subject: [PATCH 55/70] build error fix # Conflicts: # src/main/java/com/kamco/cd/kamcoback/label/service/LabelAllocateService.java --- .../label/service/LabelAllocateService.java | 95 ++++++------------- 1 file changed, 29 insertions(+), 66 deletions(-) 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 486065ab..a7864eaa 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 @@ -9,9 +9,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; -import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; -import java.time.LocalDate; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; @@ -23,9 +21,6 @@ import org.springframework.transaction.annotation.Transactional; @Transactional public class LabelAllocateService { - private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량) - private static final int BATCH_SIZE = 100; // 배정 배치 크기 - private final LabelAllocateCoreService labelAllocateCoreService; public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { @@ -35,22 +30,22 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param stage 회차 - * @param targetUsers 라벨러 목록 + * @param stage 회차 + * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional public ApiResponseDto.ResponseObj allocateAsc( - Integer stage, - List targetUsers, - List targetInspectors, - Integer compareYyyy, - Integer targetYyyy) { + Integer stage, + List targetUsers, + List targetInspectors, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; // geom 잔여건수 조회 Long chargeCnt = - labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); + labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); if (chargeCnt <= 0) { return new ApiResponseDto.ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 배정완료된 회차 입니다."); } @@ -58,15 +53,15 @@ public class LabelAllocateService { Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); if (!Objects.equals(chargeCnt, totalDemand)) { return new ApiResponseDto.ResponseObj( - ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); + ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); } List allIds = - labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); // MapSheetAnalInferenceEntity analUid 가져오기 Long analUid = - labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); + labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { @@ -112,16 +107,17 @@ public class LabelAllocateService { /** * 작업자 통계 조회 * - * @param analUid 분석 ID + * @param analUid 분석 ID * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param search 검색어 (이름 또는 사번) - * @param sortType 정렬 조건 - * @param page 페이지 번호 (0부터 시작) - * @param size 페이지 크기 + * @param search 검색어 (이름 또는 사번) + * @param sortType 정렬 조건 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, String workerType, String search, String sortType, Integer page, Integer size) { + Long analUid, + String workerType, + String search, + String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -129,44 +125,10 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - // 작업자 통계 조회 - List workers = - labelAllocateCoreService.findWorkerStatistics(analUid, workerType, search, sortType); - - // 각 작업자별 3일치 처리량 조회 - LocalDate today = LocalDate.now(); - for (WorkerStatistics worker : workers) { - Long day1Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(1), analUid); - Long day2Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(2), analUid); - Long day3Count = - labelAllocateCoreService.findDailyProcessedCount( - worker.getWorkerId(), workerType, today.minusDays(3), analUid); - - long average = (day1Count + day2Count + day3Count) / 3; - } - - // 페이징 처리 - long totalElements = workers.size(); - int totalPages = (int) Math.ceil((double) totalElements / size); - int fromIndex = page * size; - int toIndex = Math.min(fromIndex + size, workers.size()); - - List pagedWorkers = - (fromIndex < workers.size()) ? workers.subList(fromIndex, toIndex) : List.of(); - return WorkerListResponse.builder() - .projectInfo(projectInfo) - .progressInfo(progressInfo) - .workers(pagedWorkers) - .currentPage(page) - .pageSize(size) - .totalElements(totalElements) - .totalPages(totalPages) - .build(); + .projectInfo(projectInfo) + .progressInfo(progressInfo) + .build(); } public InferenceDetail findInferenceDetail(String uuid) { @@ -174,11 +136,12 @@ public class LabelAllocateService { } public ApiResponseDto.ResponseObj allocateMove( - Integer stage, - List targetUsers, - Integer compareYyyy, - Integer targetYyyy, - String userId) { + String autoType, + Integer stage, + List targetUsers, + Integer compareYyyy, + Integer targetYyyy, + String userId) { Long lastId = null; Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); @@ -188,8 +151,8 @@ public class LabelAllocateService { } List allIds = - labelAllocateCoreService.fetchNextMoveIds( - lastId, chargeCnt, compareYyyy, targetYyyy, stage, userId); + labelAllocateCoreService.fetchNextMoveIds( + lastId, chargeCnt, compareYyyy, targetYyyy, stage, userId); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); From 7acfd32b498eeee947a7363778ba72193b6ee889 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 14:54:42 +0900 Subject: [PATCH 56/70] merge --- .../label/service/LabelAllocateService.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) 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 a7864eaa..4581c2af 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 @@ -6,6 +6,7 @@ import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.AllocateInfoDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InferenceDetail; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelerDetail; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelingStatDto; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.UserList; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse; @@ -13,6 +14,7 @@ import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService; import java.util.List; import java.util.Objects; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -136,7 +138,6 @@ public class LabelAllocateService { } public ApiResponseDto.ResponseObj allocateMove( - String autoType, Integer stage, List targetUsers, Integer compareYyyy, @@ -165,7 +166,20 @@ public class LabelAllocateService { return new ApiResponseDto.ResponseObj(ApiResponseCode.OK, "이관을 완료하였습니다."); } - public LabelerDetail findLabelerDetail(String userId, String uuid) { - return labelAllocateCoreService.findLabelerDetail(userId, uuid); + public LabelerDetail findUserDetail(String userId, String uuid, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDetail(userId, uuid); + } else { + return labelAllocateCoreService.findInspectorDetail(userId, uuid); + } + } + + public Page findDaliyList( + LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { + if (type.equals("LABELER")) { + return labelAllocateCoreService.findLabelerDailyStat(searchReq, uuid, userId); + } else { + return labelAllocateCoreService.findInspectorDailyStat(searchReq, uuid, userId); + } } } From 067862ff121afdb553331391146fe4449056f127 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 15:01:51 +0900 Subject: [PATCH 57/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EB=A7=81=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EA=B4=80=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=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 --- .../label/service/LabelAllocateService.java | 54 +++++++++---------- .../label/LabelWorkRepositoryImpl.java | 23 ++++---- 2 files changed, 36 insertions(+), 41 deletions(-) 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 4581c2af..fa850685 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 @@ -32,22 +32,22 @@ public class LabelAllocateService { /** * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * - * @param stage 회차 - * @param targetUsers 라벨러 목록 + * @param stage 회차 + * @param targetUsers 라벨러 목록 * @param targetInspectors 검수자 목록 */ @Transactional public ApiResponseDto.ResponseObj allocateAsc( - Integer stage, - List targetUsers, - List targetInspectors, - Integer compareYyyy, - Integer targetYyyy) { + Integer stage, + List targetUsers, + List targetInspectors, + Integer compareYyyy, + Integer targetYyyy) { Long lastId = null; // geom 잔여건수 조회 Long chargeCnt = - labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); + labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy); if (chargeCnt <= 0) { return new ApiResponseDto.ResponseObj(ApiResponseCode.DUPLICATE_DATA, "이미 배정완료된 회차 입니다."); } @@ -55,15 +55,15 @@ public class LabelAllocateService { Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); if (!Objects.equals(chargeCnt, totalDemand)) { return new ApiResponseDto.ResponseObj( - ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); + ApiResponseCode.BAD_REQUEST, "총 잔여건수와 요청 값의 합계가 맞지 않습니다."); } List allIds = - labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); + labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage); // MapSheetAnalInferenceEntity analUid 가져오기 Long analUid = - labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); + labelAllocateCoreService.findMapSheetAnalInferenceUid(compareYyyy, targetYyyy, stage); int index = 0; for (TargetUser target : targetUsers) { @@ -109,17 +109,14 @@ public class LabelAllocateService { /** * 작업자 통계 조회 * - * @param analUid 분석 ID + * @param analUid 분석 ID * @param workerType 작업자 유형 (LABELER/INSPECTOR) - * @param search 검색어 (이름 또는 사번) - * @param sortType 정렬 조건 + * @param search 검색어 (이름 또는 사번) + * @param sortType 정렬 조건 * @return 작업자 목록 및 통계 */ public WorkerListResponse getWorkerStatistics( - Long analUid, - String workerType, - String search, - String sortType) { + Long analUid, String workerType, String search, String sortType) { // 프로젝트 정보 조회 (analUid가 있을 때만) var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); @@ -127,10 +124,7 @@ public class LabelAllocateService { // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); - return WorkerListResponse.builder() - .projectInfo(projectInfo) - .progressInfo(progressInfo) - .build(); + return WorkerListResponse.builder().projectInfo(projectInfo).progressInfo(progressInfo).build(); } public InferenceDetail findInferenceDetail(String uuid) { @@ -138,11 +132,11 @@ public class LabelAllocateService { } public ApiResponseDto.ResponseObj allocateMove( - Integer stage, - List targetUsers, - Integer compareYyyy, - Integer targetYyyy, - String userId) { + Integer stage, + List targetUsers, + Integer compareYyyy, + Integer targetYyyy, + String userId) { Long lastId = null; Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); @@ -152,8 +146,8 @@ public class LabelAllocateService { } List allIds = - labelAllocateCoreService.fetchNextMoveIds( - lastId, chargeCnt, compareYyyy, targetYyyy, stage, userId); + labelAllocateCoreService.fetchNextMoveIds( + lastId, chargeCnt, compareYyyy, targetYyyy, stage, userId); int index = 0; for (TargetUser target : targetUsers) { int end = index + target.getDemand(); @@ -175,7 +169,7 @@ public class LabelAllocateService { } public Page findDaliyList( - LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { + LabelAllocateDto.searchReq searchReq, String uuid, String userId, String type) { if (type.equals("LABELER")) { return labelAllocateCoreService.findLabelerDailyStat(searchReq, uuid, userId); } else { 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 dbc1d4cb..fc27a3c8 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 @@ -314,18 +314,19 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .fetchOne(); */ - Long total = - queryFactory - .select(mapSheetAnalInferenceEntity.uuid.countDistinct()) - .from(mapSheetAnalInferenceEntity) - .innerJoin(mapSheetAnalDataInferenceEntity) - .on(whereSubDataBuilder) - .innerJoin(mapSheetAnalDataInferenceGeomEntity) - .on(whereSubBuilder) - .where(whereBuilder) - .fetchOne(); + Long totalCnt = + (long) + queryFactory + .select(memberEntity.userRole, memberEntity.name, memberEntity.userId) + .from(labelingAssignmentEntity) + .innerJoin(memberEntity) + .on(whereSubBuilder) + .where(whereBuilder) + .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.userId) + .fetch() + .size(); - return new PageImpl<>(foundContent, pageable, total); + return new PageImpl<>(foundContent, pageable, totalCnt); } /** From ed075e47bd8051b26560aa17abd72b5f2556737b Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 5 Jan 2026 15:16:09 +0900 Subject: [PATCH 58/70] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../label/LabelWorkRepositoryImpl.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 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 dbc1d4cb..39fc3860 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 @@ -305,27 +305,17 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .limit(pageable.getPageSize()) .fetch(); - /* Long countQuery = queryFactory - .select(labelingAssignmentEntity.workerUid.count()) + .select(labelingAssignmentEntity.workerUid.countDistinct()) .from(labelingAssignmentEntity) + .innerJoin(memberEntity) + .on(whereSubBuilder) .where(whereBuilder) + //.groupBy(memberEntity.userRole, memberEntity.name, memberEntity.userId) .fetchOne(); - */ - Long total = - queryFactory - .select(mapSheetAnalInferenceEntity.uuid.countDistinct()) - .from(mapSheetAnalInferenceEntity) - .innerJoin(mapSheetAnalDataInferenceEntity) - .on(whereSubDataBuilder) - .innerJoin(mapSheetAnalDataInferenceGeomEntity) - .on(whereSubBuilder) - .where(whereBuilder) - .fetchOne(); - - return new PageImpl<>(foundContent, pageable, total); + return new PageImpl<>(foundContent, pageable, countQuery); } /** From 0129dcacc5b7bba89bfc84eb1d18286e3d41c2cf Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 15:19:15 +0900 Subject: [PATCH 59/70] spotless --- .../label/LabelWorkRepositoryImpl.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 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 39fc3860..a7de04c2 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 @@ -306,14 +306,14 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .fetch(); Long countQuery = - queryFactory - .select(labelingAssignmentEntity.workerUid.countDistinct()) - .from(labelingAssignmentEntity) - .innerJoin(memberEntity) - .on(whereSubBuilder) - .where(whereBuilder) - //.groupBy(memberEntity.userRole, memberEntity.name, memberEntity.userId) - .fetchOne(); + queryFactory + .select(labelingAssignmentEntity.workerUid.countDistinct()) + .from(labelingAssignmentEntity) + .innerJoin(memberEntity) + .on(whereSubBuilder) + .where(whereBuilder) + // .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.userId) + .fetchOne(); return new PageImpl<>(foundContent, pageable, countQuery); } From 133b22d2991eb934ff21101653e8792394300f28 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 15:41:32 +0900 Subject: [PATCH 60/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95(=EA=B2=80=EC=88=98?= =?UTF-8?q?=EC=9E=90=EB=8A=94=20=EB=82=98=EC=A4=91=EC=97=90=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=EB=A1=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgres/repository/label/LabelAllocateRepositoryImpl.java | 2 -- 1 file changed, 2 deletions(-) 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 f42b83c2..3b4cb2a4 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 @@ -103,8 +103,6 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .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(geoUidList)) .execute(); From d8f97c1137d2e34b62bafbf01a1002edd8671ba5 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 15:43:05 +0900 Subject: [PATCH 61/70] =?UTF-8?q?=EC=9D=B4=EB=85=B8=ED=8E=A8=20mockup=20ap?= =?UTF-8?q?i=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java index 3ec944e7..3a4a7e7e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/dto/DetectMastDto.java @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.Innopam.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -38,7 +39,7 @@ public class DetectMastDto { @Schema(description = "after 연도", example = "2024") private String cprsAdYr; - @NotBlank + @NotNull @Schema(description = "차수(회차)", example = "4") private Integer dtctSno; From 0595f9ad5ba3c563d83ce183ef59c2e1f133e9b7 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:54:22 +0900 Subject: [PATCH 62/70] =?UTF-8?q?projectInfo=20=EC=A1=B0=ED=9A=8C=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 --- .../kamcoback/label/dto/WorkerStatsDto.java | 3 + .../label/service/LabelAllocateService.java | 6 +- .../core/LabelAllocateCoreService.java | 4 + .../label/LabelAllocateRepositoryCustom.java | 3 + .../label/LabelAllocateRepositoryImpl.java | 74 ++++++++++++++++--- 5 files changed, 76 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 5b4dab5c..9d03e89e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -28,6 +28,9 @@ public class WorkerStatsDto { @Schema(description = "작업 시작일 (예: 2026-04-06)") private String startDate; + + @Schema(description = "프로젝트 UUID") + private String uuid; } @Getter 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 fa850685..8d3211ac 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 @@ -118,8 +118,10 @@ public class LabelAllocateService { public WorkerListResponse getWorkerStatistics( Long analUid, String workerType, String search, String sortType) { - // 프로젝트 정보 조회 (analUid가 있을 때만) - var projectInfo = labelAllocateCoreService.findProjectInfo(analUid); + // 프로젝트 정보 조회 (analUid가 없으면 최신 프로젝트 정보 조회) + var projectInfo = analUid != null + ? labelAllocateCoreService.findProjectInfo(analUid) + : labelAllocateCoreService.findLatestProjectInfo(); // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(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 30f788bd..ca31c812 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 @@ -56,6 +56,10 @@ public class LabelAllocateCoreService { return labelAllocateRepository.findProjectInfo(analUid); } + public ProjectInfo findLatestProjectInfo() { + return labelAllocateRepository.findLatestProjectInfo(); + } + public List findWorkerStatistics( Long analUid, String workerType, String search, String sortType) { return labelAllocateRepository.findWorkerStatistics(analUid, workerType, search, sortType); 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 ff30ac7a..187ded4d 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 @@ -33,6 +33,9 @@ public interface LabelAllocateRepositoryCustom { // 프로젝트 정보 조회 ProjectInfo findProjectInfo(Long analUid); + // 최신 프로젝트 정보 조회 (analUid 없이) + ProjectInfo findLatestProjectInfo(); + // 작업자 통계 조회 List findWorkerStatistics( Long analUid, String workerType, String search, String sortType); 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 f42b83c2..fbc7a3c3 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 @@ -648,24 +648,26 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto var result = queryFactory .select( - mapSheetAnalEntity.compareYyyy, - mapSheetAnalEntity.targetYyyy, - mapSheetAnalEntity.analTitle, - mapSheetAnalEntity.gukyuinApplyDttm, - mapSheetAnalEntity.analStrtDttm) - .from(mapSheetAnalEntity) - .where(mapSheetAnalEntity.id.eq(analUid)) + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.analTitle, + mapSheetAnalInferenceEntity.gukyuinApplyDttm, + mapSheetAnalInferenceEntity.analStrtDttm, + mapSheetAnalInferenceEntity.uuid) + .from(mapSheetAnalInferenceEntity) + .where(mapSheetAnalInferenceEntity.id.eq(analUid)) .fetchOne(); if (result == null) { return null; } - Integer compareYyyy = result.get(mapSheetAnalEntity.compareYyyy); - Integer targetYyyy = result.get(mapSheetAnalEntity.targetYyyy); - String analTitle = result.get(mapSheetAnalEntity.analTitle); - ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalEntity.gukyuinApplyDttm); - ZonedDateTime analStrtDttm = result.get(mapSheetAnalEntity.analStrtDttm); + Integer compareYyyy = result.get(mapSheetAnalInferenceEntity.compareYyyy); + Integer targetYyyy = result.get(mapSheetAnalInferenceEntity.targetYyyy); + String analTitle = result.get(mapSheetAnalInferenceEntity.analTitle); + ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalInferenceEntity.gukyuinApplyDttm); + ZonedDateTime analStrtDttm = result.get(mapSheetAnalInferenceEntity.analStrtDttm); + UUID uuid = result.get(mapSheetAnalInferenceEntity.uuid); // 변화탐지년도 생성 String detectionYear = @@ -679,6 +681,54 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .round(round) .reflectionDate(formatDate(gukyuinApplyDttm)) .startDate(formatDate(analStrtDttm)) + .uuid(uuid != null ? uuid.toString() : null) + .build(); + } + + @Override + public ProjectInfo findLatestProjectInfo() { + // 최신 target_yyyy를 기준으로 프로젝트 정보 조회 + var result = + queryFactory + .select( + mapSheetAnalInferenceEntity.compareYyyy, + mapSheetAnalInferenceEntity.targetYyyy, + mapSheetAnalInferenceEntity.analTitle, + mapSheetAnalInferenceEntity.gukyuinApplyDttm, + mapSheetAnalInferenceEntity.analStrtDttm, + mapSheetAnalInferenceEntity.uuid) + .from(mapSheetAnalInferenceEntity) + .orderBy( + mapSheetAnalInferenceEntity.targetYyyy.desc(), + mapSheetAnalInferenceEntity.compareYyyy.desc(), + mapSheetAnalInferenceEntity.createdDttm.desc()) + .limit(1) + .fetchOne(); + + if (result == null) { + return null; + } + + Integer compareYyyy = result.get(mapSheetAnalInferenceEntity.compareYyyy); + Integer targetYyyy = result.get(mapSheetAnalInferenceEntity.targetYyyy); + String analTitle = result.get(mapSheetAnalInferenceEntity.analTitle); + ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalInferenceEntity.gukyuinApplyDttm); + ZonedDateTime analStrtDttm = result.get(mapSheetAnalInferenceEntity.analStrtDttm); + UUID uuid = result.get(mapSheetAnalInferenceEntity.uuid); + + // 변화탐지년도 생성 + String detectionYear = + (compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null; + + // 회차 추출 (예: "8회차" → "8") + String round = extractRoundFromTitle(analTitle); + + return ProjectInfo.builder() + .detectionYear(detectionYear) + .round(round) + .reflectionDate(formatDate(gukyuinApplyDttm)) + .startDate(formatDate(analStrtDttm)) + .uuid(uuid != null ? uuid.toString() : null) .build(); } From 023ac5be49b87ba647ffe76a78706007bb300315 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:00:59 +0900 Subject: [PATCH 63/70] name change --- .../kamco/cd/kamcoback/label/LabelAllocateApiController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 00e507de..f0b65270 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -55,8 +55,8 @@ public class LabelAllocateApiController { } @Operation( - summary = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)", - description = "작업현황 관리 (라벨링, 검수 진행률 요약정보, 작업자 목록)") + summary = "작업현황 관리 (라벨링, 검수 진행률 요약정보)", + description = "작업현황 관리 (라벨링, 검수 진행률 요약정보)") @ApiResponses( value = { @ApiResponse(responseCode = "200", description = "조회 성공"), From c76a0dff1ed3abfc5d1b017d86b431ce6ab9e340 Mon Sep 17 00:00:00 2001 From: Moon Date: Mon, 5 Jan 2026 16:05:56 +0900 Subject: [PATCH 64/70] =?UTF-8?q?=EB=9D=BC=EB=B2=A8=EC=83=81=ED=83=9CDTO?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java | 5 +++++ 1 file changed, 5 insertions(+) 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 de845206..63a286cd 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 @@ -59,6 +59,8 @@ public class LabelWorkDto { if (this.labelTotCnt == 0) { mngState = "PENDING"; + }else if(this.labelTotCnt > 0 && this.labelIngTotCnt == 0 ){ + mngState = "ASSIGNED"; } else if (this.labelIngTotCnt > 0) { mngState = "LABEL_ING"; } else if (this.labelTotCnt <= labelCompleteTotCnt) { @@ -203,6 +205,9 @@ public class LabelWorkDto { @Schema(description = "종료일", example = "20261201") private String searchVal; + @Schema(description = "종료일", example = "20261201") + private String uuid; + public Pageable toPageable() { return PageRequest.of(page, size); From a86c0b5c4a9c5040e6f2f6cdaabedd0ac392d6f7 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 16:08:15 +0900 Subject: [PATCH 65/70] spotless --- .../cd/kamcoback/label/LabelAllocateApiController.java | 4 +--- .../com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java | 2 +- .../cd/kamcoback/label/service/LabelAllocateService.java | 7 ++++--- 3 files changed, 6 insertions(+), 7 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 f0b65270..b3855573 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/LabelAllocateApiController.java @@ -54,9 +54,7 @@ public class LabelAllocateApiController { return ApiResponseDto.ok(labelAllocateService.availUserList(role)); } - @Operation( - summary = "작업현황 관리 (라벨링, 검수 진행률 요약정보)", - description = "작업현황 관리 (라벨링, 검수 진행률 요약정보)") + @Operation(summary = "작업현황 관리 (라벨링, 검수 진행률 요약정보)", description = "작업현황 관리 (라벨링, 검수 진행률 요약정보)") @ApiResponses( value = { @ApiResponse(responseCode = "200", description = "조회 성공"), 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 63a286cd..aaea021b 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 @@ -59,7 +59,7 @@ public class LabelWorkDto { if (this.labelTotCnt == 0) { mngState = "PENDING"; - }else if(this.labelTotCnt > 0 && this.labelIngTotCnt == 0 ){ + } else if (this.labelTotCnt > 0 && this.labelIngTotCnt == 0) { mngState = "ASSIGNED"; } else if (this.labelIngTotCnt > 0) { mngState = "LABEL_ING"; 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 8d3211ac..8b6b82b9 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 @@ -119,9 +119,10 @@ public class LabelAllocateService { Long analUid, String workerType, String search, String sortType) { // 프로젝트 정보 조회 (analUid가 없으면 최신 프로젝트 정보 조회) - var projectInfo = analUid != null - ? labelAllocateCoreService.findProjectInfo(analUid) - : labelAllocateCoreService.findLatestProjectInfo(); + var projectInfo = + analUid != null + ? labelAllocateCoreService.findProjectInfo(analUid) + : labelAllocateCoreService.findLatestProjectInfo(); // 작업 진행 현황 조회 var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); From 27d33dac3bd4d76af3c4116bce45e7dd785b4af6 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 16:09:56 +0900 Subject: [PATCH 66/70] =?UTF-8?q?=EC=9D=B4=EB=85=B8=ED=8E=A8=20mockup=20ap?= =?UTF-8?q?i=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Innopam/InnopamApiController.java | 42 ++++++++++++------- .../Innopam/service/DetectMastService.java | 8 ++++ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java index e547f1b8..e266552e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/InnopamApiController.java @@ -7,6 +7,7 @@ import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.DetectMastSearch; import com.kamco.cd.kamcoback.Innopam.dto.DetectMastDto.FeaturePnuDto; import com.kamco.cd.kamcoback.Innopam.service.DetectMastService; 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.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -14,6 +15,7 @@ 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 java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -123,32 +125,42 @@ public class InnopamApiController { content = @Content( mediaType = "application/json", - schema = @Schema(implementation = Basic.class))), + schema = @Schema(implementation = FeaturePnuDto.class))), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @GetMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}") public List selectPnuList( - @PathVariable String cprsBfYr, @PathVariable String cprsAfYr, @PathVariable String dtctSno) { + @PathVariable String cprsBfYr, @PathVariable String cprsAfYr, @PathVariable Integer dtctSno) { DetectMastSearch detectMastSearch = new DetectMastSearch(); detectMastSearch.setCprsAdYr(cprsAfYr); detectMastSearch.setCprsBfYr(cprsBfYr); - detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); + detectMastSearch.setDtctSno(dtctSno); return detectMastService.findPnuData(detectMastSearch); } + @Operation(summary = "탐지객체 랜덤 PNU 상세 조회", description = "탐지객체 PNU 랜덤값을 생성해서 보여준다") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "목록 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = FeaturePnuDto.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @GetMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}/{featureId}") public FeaturePnuDto selectPnuDetail( - @PathVariable String cprsBfYr, - @PathVariable String cprsAfYr, - @PathVariable String dtctSno, - @PathVariable String featureId) { - DetectMastSearch detectMastSearch = new DetectMastSearch(); - detectMastSearch.setCprsAdYr(cprsAfYr); - detectMastSearch.setCprsBfYr(cprsBfYr); - detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); - detectMastSearch.setFeatureId(featureId); - return new FeaturePnuDto(); + @Parameter(description = "이전년도", example = "2022") @PathVariable String cprsBfYr, + @Parameter(description = "기준년도", example = "2024") @PathVariable String cprsAfYr, + @Parameter(description = "회차", example = "4") @PathVariable Integer dtctSno, + @Parameter(description = "featureId", example = "000e161b-1955-4c89-ad87-0b3b4a91d00f") + @PathVariable + UUID featureId) { + return detectMastService.selectPnuDetail(featureId); } @Operation( @@ -168,11 +180,11 @@ public class InnopamApiController { }) @PutMapping("/pnu/{cprsBfYr}/{cprsAfYr}/{dtctSno}") public Integer updatePnuList( - @PathVariable String cprsBfYr, @PathVariable String cprsAfYr, @PathVariable String dtctSno) { + @PathVariable String cprsBfYr, @PathVariable String cprsAfYr, @PathVariable Integer dtctSno) { DetectMastSearch detectMastSearch = new DetectMastSearch(); detectMastSearch.setCprsAdYr(cprsAfYr); detectMastSearch.setCprsBfYr(cprsBfYr); - detectMastSearch.setDtctSno(Integer.parseInt(dtctSno)); + detectMastSearch.setDtctSno(dtctSno); return detectMastService.updatePnuData(detectMastSearch); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java index a2f41dfe..f4485d20 100644 --- a/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java +++ b/src/main/java/com/kamco/cd/kamcoback/Innopam/service/DetectMastService.java @@ -14,6 +14,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; @@ -68,6 +69,13 @@ public class DetectMastService { return extractFeaturePnusRandom(dirPath); } + public FeaturePnuDto selectPnuDetail(UUID uuid) { + FeaturePnuDto dto = new FeaturePnuDto(); + dto.setPnu(randomPnu()); + dto.setFeatureId(uuid.toString()); + return dto; + } + @Transactional public Integer updatePnuData(DetectMastSearch detectMast) { From bd1f82a5d09d0adde1216286e30853b8a7a5dbda Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:25:38 +0900 Subject: [PATCH 67/70] Bring Data fix --- .../label/LabelAllocateRepositoryImpl.java | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) 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 1e68e5a9..53478db8 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 @@ -648,9 +648,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select( mapSheetAnalInferenceEntity.compareYyyy, mapSheetAnalInferenceEntity.targetYyyy, - mapSheetAnalInferenceEntity.analTitle, + mapSheetAnalInferenceEntity.stage, mapSheetAnalInferenceEntity.gukyuinApplyDttm, - mapSheetAnalInferenceEntity.analStrtDttm, + mapSheetAnalInferenceEntity.createdDttm, mapSheetAnalInferenceEntity.uuid) .from(mapSheetAnalInferenceEntity) .where(mapSheetAnalInferenceEntity.id.eq(analUid)) @@ -662,23 +662,23 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto Integer compareYyyy = result.get(mapSheetAnalInferenceEntity.compareYyyy); Integer targetYyyy = result.get(mapSheetAnalInferenceEntity.targetYyyy); - String analTitle = result.get(mapSheetAnalInferenceEntity.analTitle); + Integer stage = result.get(mapSheetAnalInferenceEntity.stage); ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalInferenceEntity.gukyuinApplyDttm); - ZonedDateTime analStrtDttm = result.get(mapSheetAnalInferenceEntity.analStrtDttm); + ZonedDateTime createdDttm = result.get(mapSheetAnalInferenceEntity.createdDttm); UUID uuid = result.get(mapSheetAnalInferenceEntity.uuid); // 변화탐지년도 생성 String detectionYear = (compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null; - // 회차 추출 (예: "8회차" → "8") - String round = extractRoundFromTitle(analTitle); + // 회차를 stage 컬럼에서 가져옴 + String round = stage != null ? String.valueOf(stage) : null; return ProjectInfo.builder() .detectionYear(detectionYear) .round(round) .reflectionDate(formatDate(gukyuinApplyDttm)) - .startDate(formatDate(analStrtDttm)) + .startDate(formatDate(createdDttm)) .uuid(uuid != null ? uuid.toString() : null) .build(); } @@ -691,9 +691,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select( mapSheetAnalInferenceEntity.compareYyyy, mapSheetAnalInferenceEntity.targetYyyy, - mapSheetAnalInferenceEntity.analTitle, + mapSheetAnalInferenceEntity.stage, mapSheetAnalInferenceEntity.gukyuinApplyDttm, - mapSheetAnalInferenceEntity.analStrtDttm, + mapSheetAnalInferenceEntity.createdDttm, mapSheetAnalInferenceEntity.uuid) .from(mapSheetAnalInferenceEntity) .orderBy( @@ -709,38 +709,27 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto Integer compareYyyy = result.get(mapSheetAnalInferenceEntity.compareYyyy); Integer targetYyyy = result.get(mapSheetAnalInferenceEntity.targetYyyy); - String analTitle = result.get(mapSheetAnalInferenceEntity.analTitle); + Integer stage = result.get(mapSheetAnalInferenceEntity.stage); ZonedDateTime gukyuinApplyDttm = result.get(mapSheetAnalInferenceEntity.gukyuinApplyDttm); - ZonedDateTime analStrtDttm = result.get(mapSheetAnalInferenceEntity.analStrtDttm); + ZonedDateTime createdDttm = result.get(mapSheetAnalInferenceEntity.createdDttm); UUID uuid = result.get(mapSheetAnalInferenceEntity.uuid); // 변화탐지년도 생성 String detectionYear = (compareYyyy != null && targetYyyy != null) ? compareYyyy + "-" + targetYyyy : null; - // 회차 추출 (예: "8회차" → "8") - String round = extractRoundFromTitle(analTitle); + // 회차를 stage 컬럼에서 가져옴 + String round = stage != null ? String.valueOf(stage) : null; return ProjectInfo.builder() .detectionYear(detectionYear) .round(round) .reflectionDate(formatDate(gukyuinApplyDttm)) - .startDate(formatDate(analStrtDttm)) + .startDate(formatDate(createdDttm)) .uuid(uuid != null ? uuid.toString() : null) .build(); } - /** 제목에서 회차 숫자 추출 예: "8회차", "제8회차" → "8" */ - private String extractRoundFromTitle(String title) { - if (title == null || title.isEmpty()) { - return null; - } - - Pattern pattern = Pattern.compile("(\\d+)회차"); - Matcher matcher = pattern.matcher(title); - - return matcher.find() ? matcher.group(1) : null; - } /** ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 */ private String formatDate(ZonedDateTime dateTime) { From eb47698c2fa934efb165f802a4469ee78b0ce171 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 16:31:43 +0900 Subject: [PATCH 68/70] =?UTF-8?q?labelState=20=EC=A1=B0=EA=B1=B4=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kamco/cd/kamcoback/label/dto/LabelWorkDto.java | 3 ++- .../repository/label/LabelAllocateRepositoryImpl.java | 2 ++ .../postgres/repository/label/LabelWorkRepositoryImpl.java | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) 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 aaea021b..cdd0f1c0 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 @@ -40,6 +40,7 @@ public class LabelWorkDto { @JsonFormatDttm private ZonedDateTime createdDttm; private Long detectionTotCnt; private Long labelTotCnt; + private Long labelAssignCnt; private Long labelStopTotCnt; private Long labelIngTotCnt; private Long labelCompleteTotCnt; @@ -59,7 +60,7 @@ public class LabelWorkDto { if (this.labelTotCnt == 0) { mngState = "PENDING"; - } else if (this.labelTotCnt > 0 && this.labelIngTotCnt == 0) { + } else if (this.labelTotCnt > 0 && this.labelAssignCnt > 0 && this.labelIngTotCnt == 0) { mngState = "ASSIGNED"; } else if (this.labelIngTotCnt > 0) { mngState = "LABEL_ING"; 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 1e68e5a9..f23bbaad 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 @@ -72,6 +72,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .from(mapSheetAnalDataInferenceGeomEntity) .where( lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), + mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull(), mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), @@ -160,6 +161,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) .from(mapSheetAnalDataInferenceGeomEntity) .where( + mapSheetAnalDataInferenceGeomEntity.pnu.isNotNull(), mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy), mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), 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 a7de04c2..c4010415 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 @@ -137,6 +137,11 @@ public class LabelWorkRepositoryImpl extends QuerydslRepositorySupport .then(1L) .otherwise(0L) .sum(), + new CaseBuilder() + .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("ASSIGNED")) + .then(1L) + .otherwise(0L) + .sum(), new CaseBuilder() .when(mapSheetAnalDataInferenceGeomEntity.labelState.eq("STOP")) .then(1L) From c30deb5efedc3c47c8d2da29b2c55e42dabc9cf9 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 5 Jan 2026 16:32:57 +0900 Subject: [PATCH 69/70] spotless --- .../postgres/repository/label/LabelAllocateRepositoryImpl.java | 3 --- 1 file changed, 3 deletions(-) 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 28a88a03..c7c7d737 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 @@ -41,8 +41,6 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Objects; import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -732,7 +730,6 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto .build(); } - /** ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 */ private String formatDate(ZonedDateTime dateTime) { if (dateTime == null) { From 6e1ad0a9441590ccf4f92f39eea6ae118231dae3 Mon Sep 17 00:00:00 2001 From: teddy Date: Mon, 5 Jan 2026 16:39:32 +0900 Subject: [PATCH 70/70] =?UTF-8?q?=EC=9E=91=EC=97=85=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=A1=B0=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 --- .../kamcoback/label/dto/WorkerStatsDto.java | 10 ++++++--- .../label/LabelAllocateRepositoryImpl.java | 21 ++++++------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java index 9d03e89e..e7a3e035 100644 --- a/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/label/dto/WorkerStatsDto.java @@ -1,6 +1,8 @@ package com.kamco.cd.kamcoback.label.dto; +import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import io.swagger.v3.oas.annotations.media.Schema; +import java.time.ZonedDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -21,13 +23,15 @@ public class WorkerStatsDto { private String detectionYear; @Schema(description = "회차 (예: 8)") - private String round; + private String stage; @Schema(description = "국유인 반영일 (예: 2026-03-31)") - private String reflectionDate; + @JsonFormatDttm + private ZonedDateTime gukyuinApplyDttm; @Schema(description = "작업 시작일 (예: 2026-04-06)") - private String startDate; + @JsonFormatDttm + private ZonedDateTime startDttm; @Schema(description = "프로젝트 UUID") private String uuid; 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 c7c7d737..fa94a7b5 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 @@ -37,7 +37,6 @@ import java.time.LocalDate; import java.time.LocalTime; 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; @@ -676,9 +675,9 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return ProjectInfo.builder() .detectionYear(detectionYear) - .round(round) - .reflectionDate(formatDate(gukyuinApplyDttm)) - .startDate(formatDate(createdDttm)) + .stage(round) + .gukyuinApplyDttm(gukyuinApplyDttm) + .startDttm(createdDttm) .uuid(uuid != null ? uuid.toString() : null) .build(); } @@ -723,21 +722,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto return ProjectInfo.builder() .detectionYear(detectionYear) - .round(round) - .reflectionDate(formatDate(gukyuinApplyDttm)) - .startDate(formatDate(createdDttm)) + .stage(round) + .gukyuinApplyDttm(gukyuinApplyDttm) + .startDttm(createdDttm) .uuid(uuid != null ? uuid.toString() : null) .build(); } - /** ZonedDateTime을 "yyyy-MM-dd" 형식으로 변환 */ - private String formatDate(ZonedDateTime dateTime) { - if (dateTime == null) { - return null; - } - return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); - } - @Override public Page findLabelerDailyStat( LabelAllocateDto.searchReq searchReq, String uuid, String userId) {