Merge pull request '[KC-168] 라벨링 툴 > 목록 - 기본 페이징 API' (#218) from feat/infer_dev_260107 into develop

Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/218
This commit is contained in:
2026-01-13 13:30:20 +09:00
7 changed files with 145 additions and 9 deletions

View File

@@ -1,6 +1,7 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.postgres.repository.trainingdata.TrainingDataLabelRepository;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
@@ -75,4 +76,8 @@ public class TrainingDataLabelCoreService {
public DetailRes getDetail(UUID assignmentUid) {
return trainingDataLabelRepository.getDetail(assignmentUid);
}
public DefaultPaging getDefaultPagingNumber(String userId) {
return trainingDataLabelRepository.getDefaultPagingNumber(userId);
}
}

View File

@@ -559,8 +559,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(
analUidCondition,
labelingAssignmentEntity.workState.in("LABEL_FIN", "TEST_ING", "DONE"))
analUidCondition, labelingAssignmentEntity.workState.in("ASSIGNED", "SKIP", "DONE"))
.fetchOne();
Long skipCount =
@@ -582,7 +581,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(analUidCondition, labelingAssignmentEntity.workState.eq("DONE"))
.where(analUidCondition, labelingAssignmentEntity.inspectState.eq("COMPLETE"))
.fetchOne();
Long inspectorCount =
@@ -641,7 +640,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.inspectionStatus(inspectionStatus)
.inspectionTotalCount(inspectionTotal)
.inspectionCompletedCount(inspectCompleted)
.inspectionSkipCount(skipped)
.inspectionSkipCount(skipped) // TODO
.inspectionRemainingCount(inspectionRemaining)
.inspectorCount(inspectorCount != null ? inspectorCount : 0L)
.progressRate(labelingRate)

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.postgres.repository.trainingdata;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
@@ -28,4 +29,6 @@ public interface TrainingDataLabelRepositoryCustom {
SummaryRes getSummary(String userId);
DetailRes getDetail(UUID assignmentUid);
DefaultPaging getDefaultPagingNumber(String userId);
}

View File

@@ -8,10 +8,12 @@ import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnDataGeomEntit
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ChangeDetectionInfo;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ClassificationInfo;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest.Properties;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.InferenceDataGeometry;
@@ -23,6 +25,7 @@ import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LearnDataGeo
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LearnDataGeometry.LearnProperties;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.searchReq;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.CaseBuilder;
@@ -32,11 +35,13 @@ import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityNotFoundException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Geometry;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
@@ -44,6 +49,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
@Slf4j
public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
implements TrainingDataLabelRepositoryCustom {
@@ -57,6 +63,24 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
@Override
public Page<LabelingListDto> findLabelingAssignedList(
searchReq searchReq, String userId, String status) {
// 완료된 라벨은 오늘만, 나머지는 전체 조회
LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul"));
ZonedDateTime start = today.atStartOfDay(ZoneId.of("Asia/Seoul"));
ZonedDateTime end = start.plusDays(1);
BooleanExpression doneToday =
labelingAssignmentEntity
.workState
.eq(LabelState.DONE.getId())
.and(labelingAssignmentEntity.workStatDttm.goe(start))
.and(labelingAssignmentEntity.workStatDttm.lt(end));
BooleanExpression assignedOrSkip =
labelingAssignmentEntity.workState.in(LabelState.SKIP.getId(), LabelState.ASSIGNED.getId());
BooleanExpression dayStateCondition = doneToday.or(assignedOrSkip);
Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize());
List<LabelingListDto> list =
queryFactory
@@ -77,12 +101,14 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntity.geoUid))
.innerJoin(mapInkx5kEntity)
.on(labelingAssignmentEntity.assignGroupId.eq(mapInkx5kEntity.mapidcdNo))
.where(labelingAssignmentEntity.workerUid.eq(userId), statusInLabelState(status))
.where(labelingAssignmentEntity.workerUid.eq(userId), dayStateCondition)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(
labelingAssignmentEntity.assignGroupId.asc(),
labelingAssignmentEntity.inferenceGeomUid.asc())
labelingAssignmentEntity.createdDate.asc(),
labelingAssignmentEntity.inferenceGeomUid
.asc() // 008288b5-5911-41d5-b8fc-b8c8f33d5434 / 362
)
.fetch();
Long count =
@@ -96,8 +122,7 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntity.geoUid))
.innerJoin(mapInkx5kEntity)
.on(labelingAssignmentEntity.assignGroupId.eq(mapInkx5kEntity.mapidcdNo))
.where(
labelingAssignmentEntity.workerUid.eq(userId), statusInLabelState(status))
.where(labelingAssignmentEntity.workerUid.eq(userId), dayStateCondition)
.fetchOne())
.orElse(0L);
@@ -534,6 +559,72 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
}
}
@Override
public DefaultPaging getDefaultPagingNumber(String userId) {
ZoneId KST = ZoneId.of("Asia/Seoul");
ZonedDateTime todayStart = ZonedDateTime.now(KST).toLocalDate().atStartOfDay(KST);
ZonedDateTime todayEnd = todayStart.plusDays(1);
BooleanExpression doneToday =
labelingAssignmentEntity
.workState
.eq(LabelState.DONE.getId())
.and(labelingAssignmentEntity.workStatDttm.goe(todayStart))
.and(labelingAssignmentEntity.workStatDttm.lt(todayEnd));
BooleanExpression assignedOrSkip =
labelingAssignmentEntity.workState.in(LabelState.SKIP.getId(), LabelState.ASSIGNED.getId());
BooleanExpression stateCondition = doneToday.or(assignedOrSkip);
Tuple firstAssigned =
queryFactory
.select(
labelingAssignmentEntity.assignmentUid,
labelingAssignmentEntity.createdDate,
labelingAssignmentEntity.inferenceGeomUid)
.from(labelingAssignmentEntity)
.where(
labelingAssignmentEntity.workerUid.eq(userId),
stateCondition,
labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId()))
.orderBy(
labelingAssignmentEntity.createdDate.asc(),
labelingAssignmentEntity.inferenceGeomUid.asc())
.limit(1)
.fetchOne();
if (firstAssigned == null) {
return DefaultPaging.builder().page(0).assignmentUid(null).build();
}
UUID firstAssignedUid = firstAssigned.get(labelingAssignmentEntity.assignmentUid);
ZonedDateTime createdDttm = firstAssigned.get(labelingAssignmentEntity.createdDate);
Long inferenceGeomUid = firstAssigned.get(labelingAssignmentEntity.inferenceGeomUid);
BooleanExpression beforeCondition =
labelingAssignmentEntity
.createdDate
.lt(createdDttm)
.or(
labelingAssignmentEntity
.createdDate
.eq(createdDttm)
.and(labelingAssignmentEntity.inferenceGeomUid.lt(inferenceGeomUid)));
Long beforeCnt =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(
labelingAssignmentEntity.workerUid.eq(userId), beforeCondition.and(stateCondition))
.fetchOne();
int page = (int) (beforeCnt / 20); // 기본 사이즈 20
return DefaultPaging.builder().page(page).assignmentUid(firstAssignedUid).build();
}
private StringExpression makeCogUrl(NumberPath<Integer> year) {
return new CaseBuilder()
.when(imageryEntity.year.eq(year))

View File

@@ -9,6 +9,7 @@ import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingList
import com.kamco.cd.kamcoback.trainingdata.service.TrainingDataLabelService;
import io.swagger.v3.oas.annotations.Hidden;
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;
@@ -157,4 +158,24 @@ public class TrainingDataLabelApiController {
java.util.UUID assignmentUid) {
return ApiResponseDto.ok(trainingDataLabelService.getDetail(assignmentUid));
}
@Operation(summary = "라벨러 기본 page number 제공", description = "라벨러 기본 page number 제공")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TrainingDataLabelDto.DetailRes.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "404", description = "데이터를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/default-page")
public ApiResponseDto<TrainingDataLabelDto.DefaultPaging> getDefaultPagingNumber(
@Parameter(description = "사번", example = "01022223333") @RequestParam String userId) {
return ApiResponseDto.ok(trainingDataLabelService.getDefaultPagingNumber(userId));
}
}

View File

@@ -382,4 +382,16 @@ public class TrainingDataLabelDto {
@Schema(description = "오늘 완료 건수", example = "0")
private Long todayCnt;
}
@Schema(name = "DefaultPaging", description = "페이징 기본 number, uuid 전달")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class DefaultPaging {
private int page;
private UUID assignmentUid;
}
}

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.trainingdata.service;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.postgres.core.TrainingDataLabelCoreService;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
@@ -83,4 +84,8 @@ public class TrainingDataLabelService {
public DetailRes getDetail(UUID assignmentUid) {
return trainingDataLabelCoreService.getDetail(assignmentUid);
}
public DefaultPaging getDefaultPagingNumber(String userId) {
return trainingDataLabelCoreService.getDefaultPagingNumber(userId);
}
}