Build Error Fix

This commit is contained in:
DanielLee
2026-01-02 21:07:14 +09:00
parent f742aa91a4
commit 3a458225c9
4 changed files with 43 additions and 203 deletions

View File

@@ -1,6 +1,6 @@
package com.kamco.cd.kamcoback.label; 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.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto; 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.InferenceDetail;
@@ -8,8 +8,6 @@ import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerListResponse;
import com.kamco.cd.kamcoback.label.service.LabelAllocateService; import com.kamco.cd.kamcoback.label.service.LabelAllocateService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; 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.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
@@ -36,185 +34,27 @@ public class LabelAllocateApiController {
@Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(responseCode = "200", description = "조회 성공"),
responseCode = "200", @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"),
description = "조회 성공", @ApiResponse(responseCode = "500", 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)
}) })
@GetMapping("/avail-user") @GetMapping("/avail-user")
public ApiResponseDto<List<LabelAllocateDto.UserList>> availUserList( public ApiResponseDto<List<LabelAllocateDto.UserList>> availUserList(
@Parameter(description = "사용자 역할 (LABELER: 라벨러, REVIEWER: 검수자)", example = "LABELER") @Parameter(
description = "사용자 역할",
example = "LABELER",
schema = @Schema(allowableValues = {"LABELER", "REVIEWER"}))
@RequestParam @RequestParam
@Schema()
String role) { String role) {
return ApiResponseDto.ok(labelAllocateService.availUserList(role)); return ApiResponseDto.ok(labelAllocateService.availUserList(role));
} }
@Operation( @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.")
summary = "작업자 목록 및 3일치 통계 조회",
description = """
학습데이터 제작 현황 조회 API입니다.
""")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(responseCode = "200", description = "조회 성공"),
responseCode = "200", @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"),
description = "조회 성공", @ApiResponse(responseCode = "500", 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": "서버에 문제가 발생 하였습니다."
}
}
""")))
}) })
@GetMapping("/admin/workers") @GetMapping("/admin/workers")
public ApiResponseDto<WorkerListResponse> getWorkerStatistics( public ApiResponseDto<WorkerListResponse> getWorkerStatistics(
@@ -249,48 +89,40 @@ public class LabelAllocateApiController {
@RequestParam(required = false) @RequestParam(required = false)
String sort) { String sort) {
// type이 null이면 전체 조회 (일단 LABELER로 기본 설정) // type이 null이면 기본값으로 LABELER 설정
String workerType = (type == null || type.isEmpty()) ? "LABELER" : type; String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : type;
return ApiResponseDto.ok( return ApiResponseDto.ok(
labelAllocateService.getWorkerStatistics( labelAllocateService.getWorkerStatistics(
analUid, workerType, searchName, searchEmployeeNo, sort)); analUid, workerType, searchName, searchEmployeeNo, sort));
} }
@Operation(summary = "라벨링 작업 배정", description = "라벨러 및 검수자에게 작업을 배정합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(responseCode = "200", description = "배정 성공"),
responseCode = "201", @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터"),
description = "등록 성공", @ApiResponse(responseCode = "500", 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") @PostMapping("/allocate")
public ApiResponseDto<Void> labelAllocate( public ApiResponseDto<Void> labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) {
@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 {
labelAllocateService.allocateAsc( labelAllocateService.allocateAsc(
dto.getAutoType(), dto.getStage(), dto.getLabelers(), dto.getInspectors()); dto.getAutoType(), dto.getStage(), dto.getLabelers(), dto.getInspectors());
return ApiResponseDto.ok(null); return ApiResponseDto.ok(null);
} }
@Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@GetMapping @GetMapping
public ApiResponseDto<InferenceDetail> findInferenceDetail(@RequestParam Long analUid) { public ApiResponseDto<InferenceDetail> findInferenceDetail(
@Parameter(description = "분석 ID", required = true, example = "3") @RequestParam
Long analUid) {
return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(analUid)); return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(analUid));
} }
} }

View File

@@ -42,8 +42,10 @@ public class LabelAllocateDto {
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
public enum LabelState implements EnumType { public enum LabelState implements EnumType {
WAIT("대기"),
ASSIGNED("배정"), ASSIGNED("배정"),
SKIP("스킵"), SKIP("스킵"),
DONE("완료"),
COMPLETE("완료"); COMPLETE("완료");
private String desc; private String desc;

View File

@@ -21,6 +21,9 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class LabelAllocateService { public class LabelAllocateService {
private static final int STAGNATION_THRESHOLD = 10; // 정체 판단 기준 (3일 평균 처리량)
private static final int BATCH_SIZE = 100; // 배정 배치 크기
private final LabelAllocateCoreService labelAllocateCoreService; private final LabelAllocateCoreService labelAllocateCoreService;
public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) { public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) {
@@ -30,15 +33,17 @@ public class LabelAllocateService {
/** /**
* 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직
* *
* @param targetUsers * @param autoType 자동/수동 배정 타입
* @param stage 회차
* @param targetUsers 라벨러 목록
* @param targetInspectors 검수자 목록
*/ */
@Transactional @Transactional
public void allocateAsc( public void allocateAsc(
String autoType, String autoType,
Integer stage, Integer stage,
List<TargetUser> targetUsers, List<TargetUser> targetUsers,
List<TargetInspector> targetInspectors) List<TargetInspector> targetInspectors) {
throws Exception {
Long lastId = null; Long lastId = null;
// geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return // geom 잔여건수 != 프론트에서 넘어 온 총 건수 -> return
@@ -137,8 +142,8 @@ public class LabelAllocateService {
worker.setHistory(history); worker.setHistory(history);
// 정체 여부 판단 (3일 평균이 특정 기준 미만일 때 - 예: 10건 미만) // 정체 여부 판단 (3일 평균이 STAGNATION_THRESHOLD 미만일 때)
if (average < 10) { if (average < STAGNATION_THRESHOLD) {
worker.setIsStagnated(true); worker.setIsStagnated(true);
} }
} }

View File

@@ -375,7 +375,8 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.where( .where(
labelingAssignmentEntity.analUid.eq(analUid), labelingAssignmentEntity.analUid.eq(analUid),
workerCondition, workerCondition,
labelingAssignmentEntity.workState.in("DONE", "SKIP"), labelingAssignmentEntity.workState.in(
LabelState.DONE.getId(), LabelState.SKIP.getId()),
labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay))
.fetchOne(); .fetchOne();