파라미터 yyyy기준으로 변경

This commit is contained in:
2026-01-02 22:17:42 +09:00
parent c85dc1a070
commit a7b04d0be8
6 changed files with 608 additions and 551 deletions

View File

@@ -36,208 +36,214 @@ public class LabelAllocateApiController {
@Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.") @Operation(summary = "배정 가능한 사용자 목록 조회", description = "라벨링 작업 배정을 위한 활성 상태의 사용자 목록을 조회합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "200", description = "조회 성공"), @ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류") @ApiResponse(responseCode = "500", description = "서버 오류")
}) })
@GetMapping("/avail-user") @GetMapping("/avail-user")
public ApiResponseDto<List<LabelAllocateDto.UserList>> availUserList( public ApiResponseDto<List<LabelAllocateDto.UserList>> availUserList(
@Parameter( @Parameter(
description = "사용자 역할", description = "사용자 역할",
example = "LABELER", example = "LABELER",
schema = @Schema(allowableValues = {"LABELER", "REVIEWER"})) schema = @Schema(allowableValues = {"LABELER", "REVIEWER"}))
@RequestParam @RequestParam
String role) { String role) {
return ApiResponseDto.ok(labelAllocateService.availUserList(role)); return ApiResponseDto.ok(labelAllocateService.availUserList(role));
} }
@Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.") @Operation(summary = "작업현황관리(작업자 목록 및 3일치 통계 조회)", description = "학습데이터 제작 현황 조회 API입니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "200", description = "조회 성공"), @ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류") @ApiResponse(responseCode = "500", description = "서버 오류")
}) })
@GetMapping("/admin/workers") @GetMapping("/admin/workers")
public ApiResponseDto<WorkerListResponse> getWorkerStatistics( public ApiResponseDto<WorkerListResponse> getWorkerStatistics(
@Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam @Parameter(description = "분석 ID (필수)", required = true, example = "3") @RequestParam
Long analUid, Long analUid,
@Parameter( @Parameter(
description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회", description = "작업자 유형 (선택) - 미입력 시 LABELER로 조회",
example = "LABELER", example = "LABELER",
schema = schema =
@Schema( @Schema(
allowableValues = {"LABELER", "REVIEWER"}, allowableValues = {"LABELER", "REVIEWER"},
defaultValue = "LABELER")) defaultValue = "LABELER"))
@RequestParam(required = false) @RequestParam(required = false)
String type, String type,
@Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false) @Parameter(description = "작업자 이름 검색 (부분 일치)", example = "김라벨") @RequestParam(required = false)
String searchName, String searchName,
@Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567") @Parameter(description = "작업자 사번 검색 (부분 일치)", example = "1234567")
@RequestParam(required = false) @RequestParam(required = false)
String searchEmployeeNo, String searchEmployeeNo,
@Parameter( @Parameter(
description = "정렬 조건 (선택) - 미입력 시 이름 오름차순", description = "정렬 조건 (선택) - 미입력 시 이름 오름차순",
example = "REMAINING_DESC", example = "REMAINING_DESC",
schema = schema =
@Schema( @Schema(
allowableValues = { allowableValues = {
"REMAINING_DESC", "REMAINING_DESC",
"REMAINING_ASC", "REMAINING_ASC",
"NAME_ASC", "NAME_ASC",
"NAME_DESC" "NAME_DESC"
}, },
defaultValue = "NAME_ASC")) defaultValue = "NAME_ASC"))
@RequestParam(required = false) @RequestParam(required = false)
String sort) { String sort) {
// type이 null이면 기본값으로 LABELER 설정 // type이 null이면 기본값으로 LABELER 설정
String workerType = (type == null || type.isEmpty()) ? RoleType.LABELER.name() : 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 = "작업 배정") @Operation(summary = "작업 배정", description = "작업 배정")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "201", responseCode = "201",
description = "등록 성공", description = "등록 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class), schema = @Schema(implementation = Long.class),
examples = { examples = {
@ExampleObject( @ExampleObject(
name = "라벨러 할당 예시", name = "라벨러 할당 예시",
description = "라벨러 할당 예시", description = "라벨러 할당 예시",
value = value =
""" """
{ {
"autoType": "AUTO", "autoType": "AUTO",
"stage": 4, "stage": 4,
"labelers": [ "labelers": [
{ {
"userId": "123456", "userId": "123456",
"demand": 1000 "demand": 1000
}, },
{ {
"userId": "010222297501", "userId": "010222297501",
"demand": 400 "demand": 400
}, },
{ {
"userId": "01022223333", "userId": "01022223333",
"demand": 440 "demand": 440
} }
], ],
"inspectors": [ "inspectors": [
{ {
"inspectorUid": "K20251216001", "inspectorUid": "K20251216001",
"userCount": 1000 "userCount": 1000
}, },
{ {
"inspectorUid": "01022225555", "inspectorUid": "01022225555",
"userCount": 340 "userCount": 340
}, },
{ {
"inspectorUid": "K20251212001", "inspectorUid": "K20251212001",
"userCount": 500 "userCount": 500
} }
] ]
} }
""") """)
})), })),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/allocate") @PostMapping("/allocate")
public ApiResponseDto<Void> labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) { public ApiResponseDto<Void> labelAllocate(@RequestBody LabelAllocateDto.AllocateDto dto) {
labelAllocateService.allocateAsc( labelAllocateService.allocateAsc(
dto.getAutoType(), dto.getAutoType(),
dto.getStage(), dto.getStage(),
dto.getLabelers(), dto.getLabelers(),
dto.getInspectors(), dto.getInspectors(),
dto.getAnalUid()); dto.getCompareYyyy(),
dto.getTargetYyyy()
);
return ApiResponseDto.ok(null); return ApiResponseDto.ok(null);
} }
@Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.") @Operation(summary = "추론 상세 조회", description = "분석 ID에 해당하는 추론 상세 정보를 조회합니다.")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse(responseCode = "200", description = "조회 성공"), @ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"), @ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류") @ApiResponse(responseCode = "500", description = "서버 오류")
}) })
@GetMapping("/stage-detail") @GetMapping("/stage-detail")
public ApiResponseDto<InferenceDetail> findInferenceDetail( public ApiResponseDto<InferenceDetail> findInferenceDetail(
@Parameter(description = "분석 ID", required = true, example = "3") @RequestParam @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam Integer compareYyyy,
Long analUid) { @Parameter(description = "기준년도", required = true, example = "2024") @RequestParam Integer targetYyyy,
return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(analUid)); @Parameter(description = "회차", required = true, example = "4") @RequestParam Integer stage
) {
return ApiResponseDto.ok(labelAllocateService.findInferenceDetail(compareYyyy, targetYyyy, stage));
} }
@Operation(summary = "작업이관 > 라벨러 상세 정보", description = "작업이관 > 라벨러 상세 정보") @Operation(summary = "작업이관 > 라벨러 상세 정보", description = "작업이관 > 라벨러 상세 정보")
@GetMapping("/labeler-detail") @GetMapping("/labeler-detail")
public ApiResponseDto<LabelerDetail> findLabelerDetail( public ApiResponseDto<LabelerDetail> findLabelerDetail(
@RequestParam(defaultValue = "01022223333") String userId, @RequestParam(defaultValue = "01022223333") String userId,
@RequestParam(defaultValue = "3") Long analUid) { @Parameter(description = "비교년도", required = true, example = "2022") @RequestParam Integer compareYyyy,
return ApiResponseDto.ok(labelAllocateService.findLabelerDetail(userId, analUid)); @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 = "작업 이관") @Operation(summary = "작업 이관", description = "작업 이관")
@ApiResponses( @ApiResponses(
value = { value = {
@ApiResponse( @ApiResponse(
responseCode = "201", responseCode = "201",
description = "등록 성공", description = "등록 성공",
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = Long.class), schema = @Schema(implementation = Long.class),
examples = { examples = {
@ExampleObject( @ExampleObject(
name = "라벨러 할당 예시", name = "라벨러 할당 예시",
description = "라벨러 할당 예시", description = "라벨러 할당 예시",
value = value =
""" """
{ {
"autoType": "AUTO", "autoType": "AUTO",
"stage": 4, "stage": 4,
"labelers": [ "labelers": [
{ {
"userId": "123456", "userId": "123456",
"demand": 10 "demand": 10
}, },
{ {
"userId": "010222297501", "userId": "010222297501",
"demand": 5 "demand": 5
} }
] ]
} }
""") """)
})), })),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping("/allocate-move") @PostMapping("/allocate-move")
public ApiResponseDto<Void> labelAllocateMove( public ApiResponseDto<Void> labelAllocateMove(
@io.swagger.v3.oas.annotations.parameters.RequestBody( @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "라벨링 이관", description = "라벨링 이관",
required = true, required = true,
content = content =
@Content( @Content(
mediaType = "application/json", mediaType = "application/json",
schema = @Schema(implementation = LabelAllocateDto.AllocateMoveDto.class))) schema = @Schema(implementation = LabelAllocateDto.AllocateMoveDto.class)))
@RequestBody @RequestBody
LabelAllocateDto.AllocateMoveDto dto) { 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); return ApiResponseDto.ok(null);
} }

View File

@@ -87,9 +87,15 @@ public class LabelAllocateDto {
@AllArgsConstructor @AllArgsConstructor
public static class AllocateDto { public static class AllocateDto {
@Schema(description = "분석 ID", example = "3", required = true) @Schema(description = "분석 ID", example = "3")
private Long analUid; 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") @Schema(description = "자동/수동여부(AUTO/MANUAL)", example = "AUTO")
private String autoType; private String autoType;
@@ -197,5 +203,11 @@ public class LabelAllocateDto {
@Schema(description = "라벨러 할당 목록") @Schema(description = "라벨러 할당 목록")
private List<TargetUser> labelers; private List<TargetUser> labelers;
@Schema(description = "비교년도", example = "2022")
private Integer compareYyyy;
@Schema(description = "기준년도", example = "2024")
private Integer targetYyyy;
} }
} }

View File

@@ -34,23 +34,24 @@ public class LabelAllocateService {
/** /**
* 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직 * 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직
* *
* @param autoType 자동/수동 배정 타입 * @param autoType 자동/수동 배정 타입
* @param stage 회차 * @param stage 회차
* @param targetUsers 라벨러 목록 * @param targetUsers 라벨러 목록
* @param targetInspectors 검수자 목록 * @param targetInspectors 검수자 목록
* @param analUid 분석 ID
*/ */
@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,
Long analUid) { Integer compareYyyy,
Integer targetYyyy
) {
Long lastId = null; Long lastId = null;
// geom 잔여건수 조회 // geom 잔여건수 조회
Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(analUid, stage); Long chargeCnt = labelAllocateCoreService.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy);
// Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); // Long totalDemand = targetUsers.stream().mapToLong(TargetUser::getDemand).sum();
// if (!Objects.equals(chargeCnt, totalDemand)) { // if (!Objects.equals(chargeCnt, totalDemand)) {
// log.info("chargeCnt != totalDemand"); // log.info("chargeCnt != totalDemand");
@@ -61,18 +62,18 @@ public class LabelAllocateService {
return; return;
} }
List<Long> allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, analUid); List<Long> allIds = labelAllocateCoreService.fetchNextIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage);
int index = 0; int index = 0;
for (TargetUser target : targetUsers) { for (TargetUser target : targetUsers) {
int end = index + target.getDemand(); int end = index + target.getDemand();
List<Long> sub = allIds.subList(index, end); List<Long> sub = allIds.subList(index, end);
labelAllocateCoreService.assignOwner(sub, target.getUserId(), analUid); labelAllocateCoreService.assignOwner(sub, target.getUserId(), compareYyyy, targetYyyy, stage);
index = end; index = end;
} }
// 검수자에게 userCount명 만큼 할당 // 검수자에게 userCount명 만큼 할당
List<LabelAllocateDto.Basic> list = labelAllocateCoreService.findAssignedLabelerList(analUid); List<LabelAllocateDto.Basic> list = labelAllocateCoreService.findAssignedLabelerList(compareYyyy, targetYyyy, stage);
int from = 0; int from = 0;
for (TargetInspector inspector : targetInspectors) { for (TargetInspector inspector : targetInspectors) {
@@ -83,7 +84,7 @@ public class LabelAllocateService {
} }
List<UUID> assignmentUids = List<UUID> 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()); labelAllocateCoreService.assignInspectorBulk(assignmentUids, inspector.getInspectorUid());
@@ -98,50 +99,50 @@ public class LabelAllocateService {
/** /**
* 작업자 목록 및 3일치 통계 조회 * 작업자 목록 및 3일치 통계 조회
* *
* @param analUid 분석 ID * @param analUid 분석 ID
* @param workerType 작업자 유형 (LABELER/INSPECTOR) * @param workerType 작업자 유형 (LABELER/INSPECTOR)
* @param searchName 이름 검색 * @param searchName 이름 검색
* @param searchEmployeeNo 사번 검색 * @param searchEmployeeNo 사번 검색
* @param sortType 정렬 조건 * @param sortType 정렬 조건
* @return 작업자 목록 및 통계 * @return 작업자 목록 및 통계
*/ */
public WorkerListResponse getWorkerStatistics( public WorkerListResponse getWorkerStatistics(
Long analUid, Long analUid,
String workerType, String workerType,
String searchName, String searchName,
String searchEmployeeNo, String searchEmployeeNo,
String sortType) { String sortType) {
// 작업 진행 현황 조회 // 작업 진행 현황 조회
var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid); var progressInfo = labelAllocateCoreService.findWorkProgressInfo(analUid);
// 작업자 통계 조회 // 작업자 통계 조회
List<WorkerStatistics> workers = List<WorkerStatistics> workers =
labelAllocateCoreService.findWorkerStatistics( labelAllocateCoreService.findWorkerStatistics(
analUid, workerType, searchName, searchEmployeeNo, sortType); analUid, workerType, searchName, searchEmployeeNo, sortType);
// 각 작업자별 3일치 처리량 조회 // 각 작업자별 3일치 처리량 조회
LocalDate today = LocalDate.now(); LocalDate today = LocalDate.now();
for (WorkerStatistics worker : workers) { for (WorkerStatistics worker : workers) {
Long day1Count = Long day1Count =
labelAllocateCoreService.findDailyProcessedCount( labelAllocateCoreService.findDailyProcessedCount(
worker.getWorkerId(), workerType, today.minusDays(1), analUid); worker.getWorkerId(), workerType, today.minusDays(1), analUid);
Long day2Count = Long day2Count =
labelAllocateCoreService.findDailyProcessedCount( labelAllocateCoreService.findDailyProcessedCount(
worker.getWorkerId(), workerType, today.minusDays(2), analUid); worker.getWorkerId(), workerType, today.minusDays(2), analUid);
Long day3Count = Long day3Count =
labelAllocateCoreService.findDailyProcessedCount( labelAllocateCoreService.findDailyProcessedCount(
worker.getWorkerId(), workerType, today.minusDays(3), analUid); worker.getWorkerId(), workerType, today.minusDays(3), analUid);
long average = (day1Count + day2Count + day3Count) / 3; long average = (day1Count + day2Count + day3Count) / 3;
DailyHistory history = DailyHistory history =
DailyHistory.builder() DailyHistory.builder()
.day1Ago(day1Count) .day1Ago(day1Count)
.day2Ago(day2Count) .day2Ago(day2Count)
.day3Ago(day3Count) .day3Ago(day3Count)
.average(average) .average(average)
.build(); .build();
worker.setHistory(history); worker.setHistory(history);
@@ -154,11 +155,12 @@ public class LabelAllocateService {
return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build(); return WorkerListResponse.builder().progressInfo(progressInfo).workers(workers).build();
} }
public InferenceDetail findInferenceDetail(Long analUid) { public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) {
return labelAllocateCoreService.findInferenceDetail(analUid); return labelAllocateCoreService.findInferenceDetail(compareYyyy, targetYyyy, stage);
} }
public void allocateMove(String autoType, Integer stage, List<TargetUser> targetUsers) { public void allocateMove(String autoType, Integer stage, List<TargetUser> targetUsers, Integer compareYyyy,
Integer targetYyyy) {
Long lastId = null; Long lastId = null;
Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum(); Long chargeCnt = targetUsers.stream().mapToLong(TargetUser::getDemand).sum();
@@ -167,7 +169,7 @@ public class LabelAllocateService {
return; return;
} }
List<Long> allIds = labelAllocateCoreService.fetchNextMoveIds(lastId, chargeCnt); List<Long> allIds = labelAllocateCoreService.fetchNextMoveIds(lastId, chargeCnt, compareYyyy, targetYyyy, stage);
int index = 0; int index = 0;
for (TargetUser target : targetUsers) { for (TargetUser target : targetUsers) {
int end = index + target.getDemand(); int end = index + target.getDemand();
@@ -178,7 +180,7 @@ public class LabelAllocateService {
} }
} }
public LabelerDetail findLabelerDetail(String userId, Long analUid) { public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) {
return labelAllocateCoreService.findLabelerDetail(userId, analUid); return labelAllocateCoreService.findLabelerDetail(userId, compareYyyy, targetYyyy, stage);
} }
} }

View File

@@ -20,22 +20,22 @@ public class LabelAllocateCoreService {
private final LabelAllocateRepository labelAllocateRepository; private final LabelAllocateRepository labelAllocateRepository;
public List<Long> fetchNextIds(Long lastId, Long batchSize, Long analUid) { public List<Long> fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) {
return labelAllocateRepository.fetchNextIds(lastId, batchSize, analUid); return labelAllocateRepository.fetchNextIds(lastId, batchSize, compareYyyy, targetYyyy, stage);
} }
public void assignOwner(List<Long> ids, String userId, Long analUid) { public void assignOwner(List<Long> ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) {
labelAllocateRepository.assignOwner(ids, userId, analUid); labelAllocateRepository.assignOwner(ids, userId, compareYyyy, targetYyyy, stage);
} }
public List<LabelAllocateDto.Basic> findAssignedLabelerList(Long analUid) { public List<LabelAllocateDto.Basic> findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage) {
return labelAllocateRepository.findAssignedLabelerList(analUid).stream() return labelAllocateRepository.findAssignedLabelerList(compareYyyy, targetYyyy, stage).stream()
.map(LabelingAssignmentEntity::toDto) .map(LabelingAssignmentEntity::toDto)
.toList(); .toList();
} }
public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { public Long findLabelUnAssignedCnt(Integer stage, Integer compareYyyy, Integer targetYyyy) {
return labelAllocateRepository.findLabelUnAssignedCnt(analUid, stage); return labelAllocateRepository.findLabelUnAssignedCnt(stage, compareYyyy, targetYyyy);
} }
public void assignInspector(UUID assignmentUid, String inspectorUid) { public void assignInspector(UUID assignmentUid, String inspectorUid) {
@@ -47,13 +47,13 @@ public class LabelAllocateCoreService {
} }
public List<WorkerStatistics> findWorkerStatistics( public List<WorkerStatistics> findWorkerStatistics(
Long analUid, Long analUid,
String workerType, String workerType,
String searchName, String searchName,
String searchEmployeeNo, String searchEmployeeNo,
String sortType) { String sortType) {
return labelAllocateRepository.findWorkerStatistics( return labelAllocateRepository.findWorkerStatistics(
analUid, workerType, searchName, searchEmployeeNo, sortType); analUid, workerType, searchName, searchEmployeeNo, sortType);
} }
public WorkProgressInfo findWorkProgressInfo(Long analUid) { public WorkProgressInfo findWorkProgressInfo(Long analUid) {
@@ -61,7 +61,7 @@ public class LabelAllocateCoreService {
} }
public Long findDailyProcessedCount( 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); return labelAllocateRepository.findDailyProcessedCount(workerId, workerType, date, analUid);
} }
@@ -69,23 +69,23 @@ public class LabelAllocateCoreService {
labelAllocateRepository.assignInspectorBulk(assignmentUids, inspectorUid); labelAllocateRepository.assignInspectorBulk(assignmentUids, inspectorUid);
} }
public InferenceDetail findInferenceDetail(Long analUid) { public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) {
return labelAllocateRepository.findInferenceDetail(analUid); return labelAllocateRepository.findInferenceDetail(compareYyyy, targetYyyy, stage);
} }
public Long findLabelUnCompleteCnt(Long analUid) { public Long findLabelUnCompleteCnt(Long analUid) {
return labelAllocateRepository.findLabelUnCompleteCnt(analUid); return labelAllocateRepository.findLabelUnCompleteCnt(analUid);
} }
public List<Long> fetchNextMoveIds(Long lastId, Long chargeCnt) { public List<Long> fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) {
return labelAllocateRepository.fetchNextMoveIds(lastId, chargeCnt); return labelAllocateRepository.fetchNextMoveIds(lastId, batchSize, compareYyyy, targetYyyy, stage);
} }
public void assignOwnerMove(List<Long> sub, String userId) { public void assignOwnerMove(List<Long> sub, String userId) {
labelAllocateRepository.assignOwnerMove(sub, userId); labelAllocateRepository.assignOwnerMove(sub, userId);
} }
public LabelerDetail findLabelerDetail(String userId, Long analUid) { public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) {
return labelAllocateRepository.findLabelerDetail(userId, analUid); return labelAllocateRepository.findLabelerDetail(userId, compareYyyy, targetYyyy, stage);
} }
} }

View File

@@ -12,13 +12,13 @@ import java.util.UUID;
public interface LabelAllocateRepositoryCustom { public interface LabelAllocateRepositoryCustom {
List<Long> fetchNextIds(Long lastId, Long batchSize, Long analUid); List<Long> fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage);
void assignOwner(List<Long> ids, String userId, Long analUid); void assignOwner(List<Long> ids, String userId, Integer compareYyyy, Integer targetYyyy, Integer stage);
List<LabelingAssignmentEntity> findAssignedLabelerList(Long analUid); List<LabelingAssignmentEntity> 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); void assignInspector(UUID assignmentUid, String userId);
@@ -26,7 +26,7 @@ public interface LabelAllocateRepositoryCustom {
// 작업자 통계 조회 // 작업자 통계 조회
List<WorkerStatistics> findWorkerStatistics( List<WorkerStatistics> 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); WorkProgressInfo findWorkProgressInfo(Long analUid);
@@ -36,13 +36,13 @@ public interface LabelAllocateRepositoryCustom {
void assignInspectorBulk(List<UUID> assignmentUids, String inspectorUid); void assignInspectorBulk(List<UUID> assignmentUids, String inspectorUid);
InferenceDetail findInferenceDetail(Long analUid); InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage);
List<Long> fetchNextMoveIds(Long lastId, Long batchSize); public List<Long> fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage);
Long findLabelUnCompleteCnt(Long analUid); Long findLabelUnCompleteCnt(Long analUid);
void assignOwnerMove(List<Long> sub, String userId); void assignOwnerMove(List<Long> sub, String userId);
LabelerDetail findLabelerDetail(String userId, Long analUid); LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage);
} }

View File

@@ -1,6 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository.label; 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.QMapSheetAnalDataInferenceEntity.mapSheetAnalDataInferenceEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; 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.QMapSheetAnalEntity.mapSheetAnalEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; 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.WorkProgressInfo;
import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics; import com.kamco.cd.kamcoback.label.dto.WorkerStatsDto.WorkerStatistics;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; 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.MapSheetAnalEntity;
import com.querydsl.core.types.Projections; import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.BooleanExpression;
@@ -44,50 +46,58 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
private final JPAQueryFactory queryFactory; private final JPAQueryFactory queryFactory;
@PersistenceContext private EntityManager em; @PersistenceContext
private EntityManager em;
@Override @Override
public List<Long> fetchNextIds(Long lastId, Long batchSize, Long analUid) { public List<Long> fetchNextIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) {
// 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 return queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid) .select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetAnalDataInferenceGeomEntity) .from(mapSheetAnalDataInferenceGeomEntity)
.where( .where(
lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId),
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(analEntity.getCompareYyyy()), mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(analEntity.getTargetYyyy()), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy),
mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) mapSheetAnalDataInferenceGeomEntity.stage.eq(stage),
.orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) mapSheetAnalDataInferenceGeomEntity.labelState.isNull())
.limit(batchSize) .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc())
.fetch(); .limit(batchSize)
.fetch();
} }
@Override @Override
public void assignOwner(List<Long> ids, String userId, Long analUid) { public void assignOwner(List<Long> 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 // data_geom 테이블에 label state 를 ASSIGNED 로 update
queryFactory queryFactory
.update(mapSheetAnalDataInferenceGeomEntity) .update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId()) .set(mapSheetAnalDataInferenceGeomEntity.labelState, LabelState.ASSIGNED.getId())
.set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now()) .set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now())
.set(mapSheetAnalDataInferenceGeomEntity.testState, InspectState.UNCONFIRM.getId()) .set(mapSheetAnalDataInferenceGeomEntity.testState, InspectState.UNCONFIRM.getId())
.set(mapSheetAnalDataInferenceGeomEntity.testStateDttm, ZonedDateTime.now()) .set(mapSheetAnalDataInferenceGeomEntity.testStateDttm, ZonedDateTime.now())
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids)) .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids))
.execute(); .execute();
// 라벨러 할당 테이블에 insert // 라벨러 할당 테이블에 insert
String sql = String sql =
""" """
insert into tb_labeling_assignment insert into tb_labeling_assignment
(assignment_uid, inference_geom_uid, worker_uid, (assignment_uid, inference_geom_uid, worker_uid,
work_state, assign_group_id, anal_uid) work_state, assign_group_id, anal_uid)
@@ -96,13 +106,13 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
for (Long geoUid : ids) { for (Long geoUid : ids) {
em.createNativeQuery(sql) em.createNativeQuery(sql)
.setParameter(1, UUID.randomUUID()) .setParameter(1, UUID.randomUUID())
.setParameter(2, geoUid) .setParameter(2, geoUid)
.setParameter(3, userId) .setParameter(3, userId)
.setParameter(4, LabelState.ASSIGNED.getId()) .setParameter(4, LabelState.ASSIGNED.getId())
.setParameter(5, "") .setParameter(5, "")
.setParameter(6, analUid) .setParameter(6, analEntity.getAnalUid())
.executeUpdate(); .executeUpdate();
} }
em.flush(); em.flush();
@@ -110,84 +120,91 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
} }
@Override @Override
public List<LabelingAssignmentEntity> findAssignedLabelerList(Long analUid) { public List<LabelingAssignmentEntity> findAssignedLabelerList(Integer compareYyyy, Integer targetYyyy, Integer stage) {
return queryFactory // analUid로 분석 정보 조회
.selectFrom(labelingAssignmentEntity) MapSheetAnalDataInferenceEntity analEntity =
.where( queryFactory
labelingAssignmentEntity.analUid.eq(analUid), .selectFrom(mapSheetAnalDataInferenceEntity)
labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()), .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy),
labelingAssignmentEntity.inspectorUid.isNull()) mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy),
.orderBy(labelingAssignmentEntity.workerUid.asc()) mapSheetAnalDataInferenceEntity.stage.eq(stage)
.fetch(); )
} .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc())
.limit(1)
.fetchOne();
@Override if (Objects.isNull(analEntity)) {
public Long findLabelUnAssignedCnt(Long analUid, Integer stage) { throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: ");
MapSheetAnalEntity entity =
queryFactory
.selectFrom(mapSheetAnalEntity)
.where(mapSheetAnalEntity.id.eq(analUid))
.fetchOne();
if (Objects.isNull(entity)) {
throw new EntityNotFoundException("MapSheetAnalEntity not found for analUid: " + analUid);
} }
return queryFactory return queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) .selectFrom(labelingAssignmentEntity)
.from(mapSheetAnalDataInferenceGeomEntity) .where(
.where( labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid()),
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), labelingAssignmentEntity.inspectorUid.isNull())
mapSheetAnalDataInferenceGeomEntity.stage.eq(stage), .orderBy(labelingAssignmentEntity.workerUid.asc())
mapSheetAnalDataInferenceGeomEntity.labelState.isNull()) .fetch();
.fetchOne(); }
@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 @Override
public void assignInspector(UUID assignmentUid, String inspectorUid) { public void assignInspector(UUID assignmentUid, String inspectorUid) {
queryFactory queryFactory
.update(labelingAssignmentEntity) .update(labelingAssignmentEntity)
.set(labelingAssignmentEntity.inspectorUid, inspectorUid) .set(labelingAssignmentEntity.inspectorUid, inspectorUid)
.where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid))
.execute(); .execute();
} }
@Override @Override
public List<UserList> availUserList(String role) { public List<UserList> availUserList(String role) {
return queryFactory return queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
LabelAllocateDto.UserList.class, LabelAllocateDto.UserList.class,
memberEntity.userRole, memberEntity.userRole,
memberEntity.employeeNo, memberEntity.employeeNo,
memberEntity.name)) memberEntity.name))
.from(memberEntity) .from(memberEntity)
.where( .where(
memberEntity.userRole.eq(role), memberEntity.userRole.eq(role),
memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId())) memberEntity.status.eq(com.kamco.cd.kamcoback.common.enums.StatusType.ACTIVE.getId()))
.orderBy(memberEntity.name.asc()) .orderBy(memberEntity.name.asc())
.fetch(); .fetch();
} }
@Override @Override
public List<WorkerStatistics> findWorkerStatistics( public List<WorkerStatistics> findWorkerStatistics(
Long analUid, Long analUid,
String workerType, String workerType,
String searchName, String searchName,
String searchEmployeeNo, String searchEmployeeNo,
String sortType) { String sortType) {
// 작업자 유형에 따른 필드 선택 // 작업자 유형에 따른 필드 선택
StringExpression workerIdField = StringExpression workerIdField =
"REVIEWER".equals(workerType) "REVIEWER".equals(workerType)
? labelingAssignmentEntity.inspectorUid ? labelingAssignmentEntity.inspectorUid
: labelingAssignmentEntity.workerUid; : labelingAssignmentEntity.workerUid;
BooleanExpression workerCondition = BooleanExpression workerCondition =
"REVIEWER".equals(workerType) "REVIEWER".equals(workerType)
? labelingAssignmentEntity.inspectorUid.isNotNull() ? labelingAssignmentEntity.inspectorUid.isNotNull()
: labelingAssignmentEntity.workerUid.isNotNull(); : labelingAssignmentEntity.workerUid.isNotNull();
// 검색 조건 // 검색 조건
BooleanExpression searchCondition = null; BooleanExpression searchCondition = null;
@@ -201,49 +218,49 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
// 완료, 스킵, 남은 작업 계산 // 완료, 스킵, 남은 작업 계산
NumberExpression<Long> completedSum = NumberExpression<Long> completedSum =
new CaseBuilder() new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq("DONE")) .when(labelingAssignmentEntity.workState.eq("DONE"))
.then(1L) .then(1L)
.otherwise(0L) .otherwise(0L)
.sum(); .sum();
NumberExpression<Long> skippedSum = NumberExpression<Long> skippedSum =
new CaseBuilder() new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq("SKIP")) .when(labelingAssignmentEntity.workState.eq("SKIP"))
.then(1L) .then(1L)
.otherwise(0L) .otherwise(0L)
.sum(); .sum();
NumberExpression<Long> remainingSum = NumberExpression<Long> remainingSum =
new CaseBuilder() new CaseBuilder()
.when( .when(
labelingAssignmentEntity labelingAssignmentEntity
.workState .workState
.notIn("DONE", "SKIP") .notIn("DONE", "SKIP")
.and(labelingAssignmentEntity.workState.isNotNull())) .and(labelingAssignmentEntity.workState.isNotNull()))
.then(1L) .then(1L)
.otherwise(0L) .otherwise(0L)
.sum(); .sum();
// 기본 통계 조회 쿼리 // 기본 통계 조회 쿼리
var baseQuery = var baseQuery =
queryFactory queryFactory
.select( .select(
workerIdField, workerIdField,
memberEntity.name, memberEntity.name,
workerIdField.count(), workerIdField.count(),
completedSum, completedSum,
skippedSum, skippedSum,
remainingSum, remainingSum,
labelingAssignmentEntity.stagnationYn.max()) labelingAssignmentEntity.stagnationYn.max())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.leftJoin(memberEntity) .leftJoin(memberEntity)
.on( .on(
"REVIEWER".equals(workerType) "REVIEWER".equals(workerType)
? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid) ? memberEntity.employeeNo.eq(labelingAssignmentEntity.inspectorUid)
: memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid)) : memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid))
.where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition) .where(labelingAssignmentEntity.analUid.eq(analUid), workerCondition, searchCondition)
.groupBy(workerIdField, memberEntity.name); .groupBy(workerIdField, memberEntity.name);
// 정렬 조건 적용 // 정렬 조건 적용
if (sortType != null) { if (sortType != null) {
@@ -269,132 +286,132 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
// 결과를 DTO로 변환 // 결과를 DTO로 변환
return baseQuery.fetch().stream() return baseQuery.fetch().stream()
.map( .map(
tuple -> { tuple -> {
Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max()); Character maxStagnationYn = tuple.get(labelingAssignmentEntity.stagnationYn.max());
return WorkerStatistics.builder() return WorkerStatistics.builder()
.workerId(tuple.get(workerIdField)) .workerId(tuple.get(workerIdField))
.workerName(tuple.get(memberEntity.name)) .workerName(tuple.get(memberEntity.name))
.workerType(workerType) .workerType(workerType)
.totalAssigned(tuple.get(workerIdField.count())) .totalAssigned(tuple.get(workerIdField.count()))
.completed(tuple.get(completedSum)) .completed(tuple.get(completedSum))
.skipped(tuple.get(skippedSum)) .skipped(tuple.get(skippedSum))
.remaining(tuple.get(remainingSum)) .remaining(tuple.get(remainingSum))
.history(null) // 3일 이력은 Service에서 채움 .history(null) // 3일 이력은 Service에서 채움
.isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y') .isStagnated(maxStagnationYn != null && maxStagnationYn == 'Y')
.build(); .build();
}) })
.toList(); .toList();
} }
@Override @Override
public WorkProgressInfo findWorkProgressInfo(Long analUid) { public WorkProgressInfo findWorkProgressInfo(Long analUid) {
// 전체 배정 건수 // 전체 배정 건수
Long totalAssigned = Long totalAssigned =
queryFactory queryFactory
.select(labelingAssignmentEntity.count()) .select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where(labelingAssignmentEntity.analUid.eq(analUid)) .where(labelingAssignmentEntity.analUid.eq(analUid))
.fetchOne(); .fetchOne();
// 완료 + 스킵 건수 // 완료 + 스킵 건수
Long completedCount = Long completedCount =
queryFactory queryFactory
.select(labelingAssignmentEntity.count()) .select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where( .where(
labelingAssignmentEntity.analUid.eq(analUid), labelingAssignmentEntity.analUid.eq(analUid),
labelingAssignmentEntity.workState.in("DONE", "SKIP")) labelingAssignmentEntity.workState.in("DONE", "SKIP"))
.fetchOne(); .fetchOne();
// 투입된 라벨러 수 (고유한 worker_uid 수) // 투입된 라벨러 수 (고유한 worker_uid 수)
Long labelerCount = Long labelerCount =
queryFactory queryFactory
.select(labelingAssignmentEntity.workerUid.countDistinct()) .select(labelingAssignmentEntity.workerUid.countDistinct())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where( .where(
labelingAssignmentEntity.analUid.eq(analUid), labelingAssignmentEntity.analUid.eq(analUid),
labelingAssignmentEntity.workerUid.isNotNull()) labelingAssignmentEntity.workerUid.isNotNull())
.fetchOne(); .fetchOne();
// 남은 라벨링 작업 데이터 수 // 남은 라벨링 작업 데이터 수
Long remainingLabelCount = Long remainingLabelCount =
queryFactory queryFactory
.select(labelingAssignmentEntity.count()) .select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where( .where(
labelingAssignmentEntity.analUid.eq(analUid), labelingAssignmentEntity.analUid.eq(analUid),
labelingAssignmentEntity.workerUid.isNotNull(), labelingAssignmentEntity.workerUid.isNotNull(),
labelingAssignmentEntity.workState.notIn("DONE", "SKIP")) labelingAssignmentEntity.workState.notIn("DONE", "SKIP"))
.fetchOne(); .fetchOne();
// 투입된 검수자 수 (고유한 inspector_uid 수) // 투입된 검수자 수 (고유한 inspector_uid 수)
Long inspectorCount = Long inspectorCount =
queryFactory queryFactory
.select(labelingAssignmentEntity.inspectorUid.countDistinct()) .select(labelingAssignmentEntity.inspectorUid.countDistinct())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where( .where(
labelingAssignmentEntity.analUid.eq(analUid), labelingAssignmentEntity.analUid.eq(analUid),
labelingAssignmentEntity.inspectorUid.isNotNull()) labelingAssignmentEntity.inspectorUid.isNotNull())
.fetchOne(); .fetchOne();
// 남은 검수 작업 데이터 수 // 남은 검수 작업 데이터 수
Long remainingInspectCount = Long remainingInspectCount =
queryFactory queryFactory
.select(labelingAssignmentEntity.count()) .select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where( .where(
labelingAssignmentEntity.analUid.eq(analUid), labelingAssignmentEntity.analUid.eq(analUid),
labelingAssignmentEntity.inspectorUid.isNotNull(), labelingAssignmentEntity.inspectorUid.isNotNull(),
labelingAssignmentEntity.workState.notIn("DONE")) labelingAssignmentEntity.workState.notIn("DONE"))
.fetchOne(); .fetchOne();
// 진행률 계산 // 진행률 계산
double progressRate = 0.0; double progressRate = 0.0;
if (totalAssigned != null && totalAssigned > 0) { if (totalAssigned != null && totalAssigned > 0) {
progressRate = progressRate =
(completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100; (completedCount != null ? completedCount.doubleValue() : 0.0) / totalAssigned * 100;
} }
// 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중) // 작업 상태 판단 (간단하게 진행률 100%면 종료, 아니면 진행중)
String workStatus = (progressRate >= 100.0) ? "종료" : "진행중"; String workStatus = (progressRate >= 100.0) ? "종료" : "진행중";
return WorkProgressInfo.builder() return WorkProgressInfo.builder()
.labelingProgressRate(progressRate) .labelingProgressRate(progressRate)
.workStatus(workStatus) .workStatus(workStatus)
.completedCount(completedCount != null ? completedCount : 0L) .completedCount(completedCount != null ? completedCount : 0L)
.totalAssignedCount(totalAssigned != null ? totalAssigned : 0L) .totalAssignedCount(totalAssigned != null ? totalAssigned : 0L)
.labelerCount(labelerCount != null ? labelerCount : 0L) .labelerCount(labelerCount != null ? labelerCount : 0L)
.remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L) .remainingLabelCount(remainingLabelCount != null ? remainingLabelCount : 0L)
.inspectorCount(inspectorCount != null ? inspectorCount : 0L) .inspectorCount(inspectorCount != null ? inspectorCount : 0L)
.remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L) .remainingInspectCount(remainingInspectCount != null ? remainingInspectCount : 0L)
.build(); .build();
} }
@Override @Override
public Long findDailyProcessedCount( 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 startOfDay = date.atStartOfDay(ZoneId.systemDefault());
ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()); ZonedDateTime endOfDay = date.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault());
BooleanExpression workerCondition = BooleanExpression workerCondition =
"REVIEWER".equals(workerType) "REVIEWER".equals(workerType)
? labelingAssignmentEntity.inspectorUid.eq(workerId) ? labelingAssignmentEntity.inspectorUid.eq(workerId)
: labelingAssignmentEntity.workerUid.eq(workerId); : labelingAssignmentEntity.workerUid.eq(workerId);
Long count = Long count =
queryFactory queryFactory
.select(labelingAssignmentEntity.count()) .select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity) .from(labelingAssignmentEntity)
.where( .where(
labelingAssignmentEntity.analUid.eq(analUid), labelingAssignmentEntity.analUid.eq(analUid),
workerCondition, workerCondition,
labelingAssignmentEntity.workState.in( labelingAssignmentEntity.workState.in(
LabelState.DONE.getId(), LabelState.SKIP.getId()), LabelState.DONE.getId(), LabelState.SKIP.getId()),
labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay)) labelingAssignmentEntity.modifiedDate.between(startOfDay, endOfDay))
.fetchOne(); .fetchOne();
return count != null ? count : 0L; return count != null ? count : 0L;
} }
@@ -402,152 +419,172 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
@Override @Override
public void assignInspectorBulk(List<UUID> assignmentUids, String inspectorUid) { public void assignInspectorBulk(List<UUID> assignmentUids, String inspectorUid) {
queryFactory queryFactory
.update(labelingAssignmentEntity) .update(labelingAssignmentEntity)
.set(labelingAssignmentEntity.inspectorUid, inspectorUid) .set(labelingAssignmentEntity.inspectorUid, inspectorUid)
.where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids))
.execute(); .execute();
em.clear(); em.clear();
} }
@Override @Override
public InferenceDetail findInferenceDetail(Long analUid) { public InferenceDetail findInferenceDetail(Integer compareYyyy, Integer targetYyyy, Integer stage) {
return queryFactory // analUid로 분석 정보 조회
.select( MapSheetAnalDataInferenceEntity analEntity =
Projections.constructor( queryFactory
InferenceDetail.class, .selectFrom(mapSheetAnalDataInferenceEntity)
mapSheetAnalEntity.analTitle, .where(mapSheetAnalDataInferenceEntity.compareYyyy.eq(compareYyyy),
Expressions.numberTemplate(Integer.class, "{0}", 4), mapSheetAnalDataInferenceEntity.targetYyyy.eq(targetYyyy),
mapSheetAnalEntity.gukyuinApplyDttm, mapSheetAnalDataInferenceEntity.stage.eq(stage)
mapSheetAnalEntity.detectingCnt, )
labelingAssignmentEntity.workerUid.countDistinct(), .orderBy(mapSheetAnalDataInferenceEntity.analUid.asc())
labelingAssignmentEntity.inspectorUid.countDistinct())) .limit(1)
.from(mapSheetAnalEntity)
.innerJoin(labelingAssignmentEntity)
.on(mapSheetAnalEntity.id.eq(labelingAssignmentEntity.analUid))
.where(mapSheetAnalEntity.id.eq(analUid))
.groupBy(
mapSheetAnalEntity.analTitle,
mapSheetAnalEntity.gukyuinApplyDttm,
mapSheetAnalEntity.detectingCnt)
.fetchOne(); .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 @Override
public List<Long> fetchNextMoveIds(Long lastId, Long batchSize) { public List<Long> fetchNextMoveIds(Long lastId, Long batchSize, Integer compareYyyy, Integer targetYyyy, Integer stage) {
MapSheetAnalEntity entity =
queryFactory
.selectFrom(mapSheetAnalEntity)
.where(mapSheetAnalEntity.id.eq(3L)) // TODO
.fetchOne();
if (Objects.isNull(entity)) {
throw new EntityNotFoundException();
}
return queryFactory return queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid) .select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetAnalDataInferenceGeomEntity) .from(mapSheetAnalDataInferenceGeomEntity)
.where( .where(
// mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기 // mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기
lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId), lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId),
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(compareYyyy),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(targetYyyy),
mapSheetAnalDataInferenceGeomEntity.labelState.in( mapSheetAnalDataInferenceGeomEntity.stage.eq(stage),
LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) mapSheetAnalDataInferenceGeomEntity.labelState.in(
.orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc()) LabelState.ASSIGNED.getId(), LabelState.SKIP.getId()))
.limit(batchSize) .orderBy(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.asc())
.fetch(); .limit(batchSize)
.fetch();
} }
@Override @Override
public Long findLabelUnCompleteCnt(Long analUid) { public Long findLabelUnCompleteCnt(Long analUid) {
MapSheetAnalEntity entity = MapSheetAnalEntity entity =
queryFactory queryFactory
.selectFrom(mapSheetAnalEntity) .selectFrom(mapSheetAnalEntity)
.where(mapSheetAnalEntity.id.eq(analUid)) .where(mapSheetAnalEntity.id.eq(analUid))
.fetchOne(); .fetchOne();
if (Objects.isNull(entity)) { if (Objects.isNull(entity)) {
throw new EntityNotFoundException(); throw new EntityNotFoundException();
} }
return queryFactory return queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid.count()) .select(mapSheetAnalDataInferenceGeomEntity.geoUid.count())
.from(mapSheetAnalDataInferenceGeomEntity) .from(mapSheetAnalDataInferenceGeomEntity)
.where( .where(
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()), mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(entity.getCompareYyyy()),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()), mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(entity.getTargetYyyy()),
mapSheetAnalDataInferenceGeomEntity.stage.eq(4), // TODO: 회차 컬럼을 가져와야 할 듯? mapSheetAnalDataInferenceGeomEntity.stage.eq(4), // TODO: 회차 컬럼을 가져와야 할 듯?
mapSheetAnalDataInferenceGeomEntity.labelState.in( mapSheetAnalDataInferenceGeomEntity.labelState.in(
LabelState.ASSIGNED.getId(), LabelState.SKIP.getId())) LabelState.ASSIGNED.getId(), LabelState.SKIP.getId()))
.fetchOne(); .fetchOne();
} }
@Transactional @Transactional
@Override @Override
public void assignOwnerMove(List<Long> sub, String userId) { public void assignOwnerMove(List<Long> sub, String userId) {
queryFactory queryFactory
.update(labelingAssignmentEntity) .update(labelingAssignmentEntity)
.set(labelingAssignmentEntity.workerUid, userId) .set(labelingAssignmentEntity.workerUid, userId)
.where(labelingAssignmentEntity.inferenceGeomUid.in(sub)) .where(labelingAssignmentEntity.inferenceGeomUid.in(sub))
.execute(); .execute();
em.clear(); em.clear();
} }
@Override @Override
public LabelerDetail findLabelerDetail(String userId, Long analUid) { public LabelerDetail findLabelerDetail(String userId, Integer compareYyyy, Integer targetYyyy, Integer stage) {
NumberExpression<Long> assignedCnt = NumberExpression<Long> assignedCnt =
new CaseBuilder() new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) .when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()))
.then(1L) .then(1L)
.otherwise((Long) null) .otherwise((Long) null)
.count(); .count();
NumberExpression<Long> skipCnt = NumberExpression<Long> skipCnt =
new CaseBuilder() new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId())) .when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId()))
.then(1L) .then(1L)
.otherwise((Long) null) .otherwise((Long) null)
.count(); .count();
NumberExpression<Long> completeCnt = NumberExpression<Long> completeCnt =
new CaseBuilder() new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.COMPLETE.getId())) .when(labelingAssignmentEntity.workState.eq(LabelState.COMPLETE.getId()))
.then(1L) .then(1L)
.otherwise((Long) null) .otherwise((Long) null)
.count(); .count();
NumberExpression<Double> percent = NumberExpression<Double> percent =
new CaseBuilder() new CaseBuilder()
.when(completeCnt.eq(0L)) .when(completeCnt.eq(0L))
.then(0.0) .then(0.0)
.otherwise( .otherwise(
Expressions.numberTemplate( Expressions.numberTemplate(
Double.class, Double.class,
"round({0} / {1}, 2)", "round({0} / {1}, 2)",
labelingAssignmentEntity.count(), labelingAssignmentEntity.count(),
completeCnt)); 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 return queryFactory
.select( .select(
Projections.constructor( Projections.constructor(
LabelerDetail.class, LabelerDetail.class,
memberEntity.userRole, memberEntity.userRole,
memberEntity.name, memberEntity.name,
memberEntity.employeeNo, memberEntity.employeeNo,
assignedCnt, assignedCnt,
skipCnt, skipCnt,
completeCnt, completeCnt,
percent)) percent))
.from(memberEntity) .from(memberEntity)
.innerJoin(labelingAssignmentEntity) .innerJoin(labelingAssignmentEntity)
.on( .on(
memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid), memberEntity.employeeNo.eq(labelingAssignmentEntity.workerUid),
labelingAssignmentEntity.analUid.eq(analUid)) labelingAssignmentEntity.analUid.eq(analEntity.getAnalUid()))
.where(memberEntity.employeeNo.eq(userId)) .where(memberEntity.employeeNo.eq(userId))
.groupBy(memberEntity.userRole, memberEntity.name, memberEntity.employeeNo) .groupBy(memberEntity.userRole, memberEntity.name, memberEntity.employeeNo)
.fetchOne(); .fetchOne();
} }
} }