Merge pull request 'feat/infer_dev_260107' (#231) from feat/infer_dev_260107 into develop

Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/231
This commit is contained in:
2026-01-14 17:03:22 +09:00
13 changed files with 484 additions and 33 deletions

View File

@@ -307,4 +307,64 @@ public class InferenceResultApiController {
String uuid) { String uuid) {
return ApiResponseDto.ok(inferenceResultService.getInferenceResultInfo(uuid)); return ApiResponseDto.ok(inferenceResultService.getInferenceResultInfo(uuid));
} }
@Operation(summary = "추론결과 분류별 탐지 건수", description = "추론결과 분류별 탐지 건수")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = InferenceDetailDto.AnalResSummary.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/infer-class-count")
public ApiResponseDto<List<InferenceDetailDto.Dashboard>> getInferenceClassCountList(
@Parameter(description = "회차 uuid", example = "8584e8d4-53b3-4582-bde2-28a81495a626")
@RequestParam
String uuid) {
return ApiResponseDto.ok(inferenceResultService.getInferenceClassCountList(uuid));
}
@Operation(summary = "추론관리 분석결과 상세 목록", description = "추론관리 분석결과 상세 목록 geojson 데이터 조회")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "검색 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = Page.class))),
@ApiResponse(responseCode = "400", description = "잘못된 검색 조건", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/geom-list")
public ApiResponseDto<Page<InferenceDetailDto.Geom>> getInferenceGeomList(
@Parameter(description = "회차 uuid", example = "8584e8d4-53b3-4582-bde2-28a81495a626")
@RequestParam(required = true)
String uuid,
@Parameter(description = "기준년도 분류", example = "land") @RequestParam(required = false)
String targetClass,
@Parameter(description = "비교년도 분류", example = "waste") @RequestParam(required = false)
String compareClass,
@Parameter(description = "5000:1 도엽번호 37801011,37801012") @RequestParam(required = false)
List<Long> mapSheetNum,
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0")
int page,
@Parameter(description = "페이지 크기", example = "20") @RequestParam(defaultValue = "20")
int size,
@Parameter(description = "정렬 조건 (형식: 필드명,방향)", example = "name,asc")
@RequestParam(required = false)
String sort) {
InferenceDetailDto.SearchGeoReq searchGeoReq =
new InferenceDetailDto.SearchGeoReq(
targetClass, compareClass, mapSheetNum, page, size, sort);
Page<InferenceDetailDto.Geom> geomList =
inferenceResultService.getInferenceGeomList(uuid, searchGeoReq);
return ApiResponseDto.ok(geomList);
}
} }

View File

@@ -294,8 +294,10 @@ public class InferenceDetailDto {
@Getter @Getter
public static class Geom { public static class Geom {
UUID uuid;
Integer compareYyyy; Integer compareYyyy;
Integer targetYyyy; Integer targetYyyy;
Double cdProb;
String classBeforeCd; String classBeforeCd;
String classBeforeName; String classBeforeName;
Double classBeforeProb; Double classBeforeProb;
@@ -303,23 +305,29 @@ public class InferenceDetailDto {
String classAfterName; String classAfterName;
Double classAfterProb; Double classAfterProb;
Long mapSheetNum; Long mapSheetNum;
String mapSheetName;
@JsonIgnore String gemoStr; @JsonIgnore String gemoStr;
@JsonIgnore String geomCenterStr; @JsonIgnore String geomCenterStr;
JsonNode gemo; JsonNode gemo;
JsonNode geomCenter; JsonNode geomCenter;
public Geom( public Geom(
UUID uuid,
Integer compareYyyy, Integer compareYyyy,
Integer targetYyyy, Integer targetYyyy,
Double cdProb,
String classBeforeCd, String classBeforeCd,
Double classBeforeProb, Double classBeforeProb,
String classAfterCd, String classAfterCd,
Double classAfterProb, Double classAfterProb,
Long mapSheetNum, Long mapSheetNum,
String mapSheetName,
String gemoStr, String gemoStr,
String geomCenterStr) { String geomCenterStr) {
this.uuid = uuid;
this.compareYyyy = compareYyyy; this.compareYyyy = compareYyyy;
this.targetYyyy = targetYyyy; this.targetYyyy = targetYyyy;
this.cdProb = cdProb;
this.classBeforeCd = classBeforeCd; this.classBeforeCd = classBeforeCd;
this.classBeforeName = DetectionClassification.fromString(classBeforeCd).getDesc(); this.classBeforeName = DetectionClassification.fromString(classBeforeCd).getDesc();
this.classBeforeProb = classBeforeProb; this.classBeforeProb = classBeforeProb;
@@ -327,6 +335,7 @@ public class InferenceDetailDto {
this.classAfterName = DetectionClassification.fromString(classAfterCd).getDesc(); this.classAfterName = DetectionClassification.fromString(classAfterCd).getDesc();
this.classAfterProb = classAfterProb; this.classAfterProb = classAfterProb;
this.mapSheetNum = mapSheetNum; this.mapSheetNum = mapSheetNum;
this.mapSheetName = mapSheetName;
this.gemoStr = gemoStr; this.gemoStr = gemoStr;
this.geomCenterStr = geomCenterStr; this.geomCenterStr = geomCenterStr;

View File

@@ -10,7 +10,9 @@ import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Detail; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Detail;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Geom;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.SearchGeoReq;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.DetectOption;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto;
@@ -486,4 +488,12 @@ public class InferenceResultService {
public AnalResultInfo getInferenceResultInfo(String uuid) { public AnalResultInfo getInferenceResultInfo(String uuid) {
return inferenceResultCoreService.getInferenceResultInfo(uuid); return inferenceResultCoreService.getInferenceResultInfo(uuid);
} }
public List<Dashboard> getInferenceClassCountList(String uuid) {
return inferenceResultCoreService.getInferenceClassCountList(uuid);
}
public Page<Geom> getInferenceGeomList(String uuid, SearchGeoReq searchGeoReq) {
return inferenceResultCoreService.getInferenceGeomList(uuid, searchGeoReq);
}
} }

View File

@@ -5,8 +5,10 @@ import com.kamco.cd.kamcoback.common.utils.UserUtil;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Geom;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.InferenceBatchSheet; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.InferenceBatchSheet;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.MapSheet;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.SearchGeoReq;
import com.kamco.cd.kamcoback.inference.dto.InferenceProgressDto; import com.kamco.cd.kamcoback.inference.dto.InferenceProgressDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto;
@@ -388,4 +390,12 @@ public class InferenceResultCoreService {
public AnalResultInfo getInferenceResultInfo(String uuid) { public AnalResultInfo getInferenceResultInfo(String uuid) {
return mapSheetLearnRepository.getInferenceResultInfo(uuid); return mapSheetLearnRepository.getInferenceResultInfo(uuid);
} }
public List<Dashboard> getInferenceClassCountList(String uuid) {
return mapSheetLearnRepository.getInferenceClassCountList(uuid);
}
public Page<Geom> getInferenceGeomList(String uuid, SearchGeoReq searchGeoReq) {
return mapSheetLearnRepository.getInferenceGeomList(uuid, searchGeoReq);
}
} }

View File

@@ -105,7 +105,43 @@ public class LabelAllocateCoreService {
} }
public LabelerDetail findLabelerDetail(String userId, String uuid) { public LabelerDetail findLabelerDetail(String userId, String uuid) {
return labelAllocateRepository.findLabelerDetail(userId, uuid); // 1. 기본 정보 조회 (순위 0으로 들어옴)
LabelerDetail detail = labelAllocateRepository.findLabelerDetail(userId, uuid);
if (detail == null) {
return null;
}
// 2. 해당 회차의 모든 라벨러 정보 조회 (완료 건수 기준 내림차순)
// UUID를 analUid로 변환
Long analUid = labelAllocateRepository.findAnalUidByUuid(uuid);
if (analUid == null) {
return detail; // analUid 없으면 순위 0으로 반환
}
List<LabelerDetail> allLabelers = labelAllocateRepository.findAllLabelersForRanking(analUid);
// 3. 순위 계산 (DENSE_RANK 로직: 동점자는 같은 순위, 다음 순위는 연속)
Long lastCompleteCnt = null;
int currentRank = 0;
for (int i = 0; i < allLabelers.size(); i++) {
LabelerDetail labeler = allLabelers.get(i);
// 완료 건수가 바뀌면 순위 갱신
if (lastCompleteCnt == null || !lastCompleteCnt.equals(labeler.getCompleteCnt())) {
currentRank = i + 1;
lastCompleteCnt = labeler.getCompleteCnt();
}
// 현재 조회한 사용자의 순위 찾기
if (labeler.getUserId().equals(userId)) {
detail.setRanking(currentRank);
break;
}
}
return detail;
} }
public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) {
@@ -127,7 +163,43 @@ public class LabelAllocateCoreService {
} }
public LabelerDetail findInspectorDetail(String userId, String uuid) { public LabelerDetail findInspectorDetail(String userId, String uuid) {
return labelAllocateRepository.findInspectorDetail(userId, uuid); // 1. 기본 정보 조회 (순위 0으로 들어옴)
LabelerDetail detail = labelAllocateRepository.findInspectorDetail(userId, uuid);
if (detail == null) {
return null;
}
// 2. 해당 회차의 모든 검수자 정보 조회 (완료 건수 기준 내림차순)
Long analUid = labelAllocateRepository.findAnalUidByUuid(uuid);
if (analUid == null) {
return detail; // analUid 없으면 순위 0으로 반환
}
List<LabelerDetail> allInspectors =
labelAllocateRepository.findAllInspectorsForRanking(analUid);
// 3. 순위 계산 (DENSE_RANK 로직: 동점자는 같은 순위, 다음 순위는 연속)
Long lastCompleteCnt = null;
int currentRank = 0;
for (int i = 0; i < allInspectors.size(); i++) {
LabelerDetail inspector = allInspectors.get(i);
// 완료 건수가 바뀌면 순위 갱신
if (lastCompleteCnt == null || !lastCompleteCnt.equals(inspector.getCompleteCnt())) {
currentRank = i + 1;
lastCompleteCnt = inspector.getCompleteCnt();
}
// 현재 조회한 사용자의 순위 찾기
if (inspector.getUserId().equals(userId)) {
detail.setRanking(currentRank);
break;
}
}
return detail;
} }
public MoveInfo moveAvailUserList(String userId, String uuid) { public MoveInfo moveAvailUserList(String userId, String uuid) {

View File

@@ -1,6 +1,9 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference; package com.kamco.cd.kamcoback.postgres.repository.Inference;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Geom;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.SearchGeoReq;
import com.kamco.cd.kamcoback.inference.dto.InferenceProgressDto; import com.kamco.cd.kamcoback.inference.dto.InferenceProgressDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto;
@@ -30,4 +33,8 @@ public interface MapSheetLearnRepositoryCustom {
Integer getLearnStage(Integer compareYear, Integer targetYear); Integer getLearnStage(Integer compareYear, Integer targetYear);
AnalResultInfo getInferenceResultInfo(String uuid); AnalResultInfo getInferenceResultInfo(String uuid);
List<Dashboard> getInferenceClassCountList(String uuid);
Page<Geom> getInferenceGeomList(String uuid, SearchGeoReq searchGeoReq);
} }

View File

@@ -1,24 +1,35 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference; package com.kamco.cd.kamcoback.postgres.repository.Inference;
import static com.kamco.cd.kamcoback.postgres.entity.QGpuMetricEntity.gpuMetricEntity; import static com.kamco.cd.kamcoback.postgres.entity.QGpuMetricEntity.gpuMetricEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
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.QMapSheetAnalSttcEntity.mapSheetAnalSttcEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QSystemMetricEntity.systemMetricEntity; import static com.kamco.cd.kamcoback.postgres.entity.QSystemMetricEntity.systemMetricEntity;
import com.kamco.cd.kamcoback.common.utils.DateRange; import com.kamco.cd.kamcoback.common.utils.DateRange;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.AnalResultInfo;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Dashboard;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.Geom;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto.SearchGeoReq;
import com.kamco.cd.kamcoback.inference.dto.InferenceProgressDto; import com.kamco.cd.kamcoback.inference.dto.InferenceProgressDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceServerStatusDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceStatusDetailDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceStatusDetailDto;
import com.kamco.cd.kamcoback.model.service.ModelMngService; import com.kamco.cd.kamcoback.model.service.ModelMngService;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity; import com.kamco.cd.kamcoback.postgres.entity.QModelMngEntity;
import com.querydsl.core.BooleanBuilder; import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections; import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.CaseBuilder; 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.NumberExpression;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityNotFoundException;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -293,4 +304,149 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.where(mapSheetLearnEntity.uuid.eq(UUID.fromString(uuid))) .where(mapSheetLearnEntity.uuid.eq(UUID.fromString(uuid)))
.fetchOne(); .fetchOne();
} }
@Override
public List<Dashboard> getInferenceClassCountList(String uuid) {
// 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: ");
}
return queryFactory
.select(
Projections.constructor(
Dashboard.class,
mapSheetAnalSttcEntity.id.classAfterCd,
mapSheetAnalSttcEntity.classAfterCnt.sum()))
.from(mapSheetAnalSttcEntity)
.where(mapSheetAnalSttcEntity.id.analUid.eq(analEntity.getId()))
.groupBy(mapSheetAnalSttcEntity.id.classAfterCd)
.orderBy(mapSheetAnalSttcEntity.id.classAfterCd.asc())
.fetch();
}
/**
* 분석결과 상세 목록
*
* @param searchGeoReq
* @return
*/
@Override
public Page<Geom> getInferenceGeomList(String uuid, SearchGeoReq searchGeoReq) {
Pageable pageable = searchGeoReq.toPageable();
BooleanBuilder builder = new BooleanBuilder();
// 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: ");
}
// 추론결과 id
builder.and(mapSheetAnalInferenceEntity.id.eq(analEntity.getId()));
// 기준년도 분류
if (searchGeoReq.getTargetClass() != null && !searchGeoReq.getTargetClass().equals("")) {
builder.and(
mapSheetAnalDataInferenceGeomEntity
.classAfterCd
.toLowerCase()
.eq(searchGeoReq.getTargetClass().toLowerCase()));
}
// 비교년도 분류
if (searchGeoReq.getCompareClass() != null && !searchGeoReq.getCompareClass().equals("")) {
builder.and(
mapSheetAnalDataInferenceGeomEntity
.classBeforeCd
.toLowerCase()
.eq(searchGeoReq.getCompareClass().toLowerCase()));
}
// 분석도엽
if (searchGeoReq.getMapSheetNum() != null && !searchGeoReq.getMapSheetNum().isEmpty()) {
List<Long> mapSheetNum = searchGeoReq.getMapSheetNum();
builder.and(mapSheetAnalDataInferenceGeomEntity.mapSheetNum.in(mapSheetNum));
}
List<Geom> content =
queryFactory
.select(
Projections.constructor(
Geom.class,
mapSheetAnalDataInferenceGeomEntity.uuid,
mapSheetAnalDataInferenceGeomEntity.compareYyyy,
mapSheetAnalDataInferenceGeomEntity.targetYyyy,
mapSheetAnalDataInferenceGeomEntity.cdProb,
mapSheetAnalDataInferenceGeomEntity.classBeforeCd,
mapSheetAnalDataInferenceGeomEntity.classBeforeProb,
mapSheetAnalDataInferenceGeomEntity.classAfterCd,
mapSheetAnalDataInferenceGeomEntity.classAfterProb,
mapSheetAnalDataInferenceGeomEntity.mapSheetNum,
mapInkx5kEntity.mapidNm,
Expressions.stringTemplate(
"ST_AsGeoJSON({0})", mapSheetAnalDataInferenceGeomEntity.geom),
Expressions.stringTemplate(
"ST_AsGeoJSON({0})", mapSheetAnalDataInferenceGeomEntity.geomCenter)))
.from(mapSheetAnalInferenceEntity)
.join(mapSheetAnalDataInferenceGeomEntity)
.on(
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(
mapSheetAnalInferenceEntity.compareYyyy),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(
mapSheetAnalInferenceEntity.targetYyyy),
mapSheetAnalDataInferenceGeomEntity.stage.eq(mapSheetAnalInferenceEntity.stage))
.join(mapInkx5kEntity)
.on(
mapSheetAnalDataInferenceGeomEntity
.mapSheetNum
.stringValue()
.eq(mapInkx5kEntity.mapidcdNo))
.where(builder)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
long total =
queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetAnalInferenceEntity)
.join(mapSheetAnalDataInferenceGeomEntity)
.on(
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(
mapSheetAnalInferenceEntity.compareYyyy),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(
mapSheetAnalInferenceEntity.targetYyyy),
mapSheetAnalDataInferenceGeomEntity.stage.eq(mapSheetAnalInferenceEntity.stage))
.from(mapSheetAnalInferenceEntity)
.join(mapSheetAnalDataInferenceGeomEntity)
.on(
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(
mapSheetAnalInferenceEntity.compareYyyy),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(
mapSheetAnalInferenceEntity.targetYyyy),
mapSheetAnalDataInferenceGeomEntity.stage.eq(mapSheetAnalInferenceEntity.stage))
.join(mapInkx5kEntity)
.on(
mapSheetAnalDataInferenceGeomEntity
.mapSheetNum
.stringValue()
.eq(mapInkx5kEntity.mapidcdNo))
.where(builder)
.fetchCount();
return new PageImpl<>(content, pageable, total);
}
} }

View File

@@ -68,6 +68,12 @@ public interface LabelAllocateRepositoryCustom {
LabelerDetail findLabelerDetail(String userId, String uuid); LabelerDetail findLabelerDetail(String userId, String uuid);
/** 순위 계산용 - 특정 회차의 모든 라벨러 통계 조회 */
List<LabelerDetail> findAllLabelersForRanking(Long analUid);
/** UUID로 analUid 조회 */
Long findAnalUidByUuid(String uuid);
Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage); Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage);
void insertInspector(Long analUid, String inspector); void insertInspector(Long analUid, String inspector);
@@ -80,6 +86,9 @@ public interface LabelAllocateRepositoryCustom {
LabelerDetail findInspectorDetail(String userId, String uuid); LabelerDetail findInspectorDetail(String userId, String uuid);
/** 순위 계산용 - 특정 회차의 모든 검수자 통계 조회 */
List<LabelerDetail> findAllInspectorsForRanking(Long analUid);
MoveInfo moveAvailUserList(String userId, String uuid); MoveInfo moveAvailUserList(String userId, String uuid);
void insertLabelerUser(Long analUid, String userId, int demand); void insertLabelerUser(Long analUid, String userId, int demand);

View File

@@ -777,11 +777,25 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
@Override @Override
public LabelerDetail findLabelerDetail(String userId, String uuid) { public LabelerDetail findLabelerDetail(String userId, String uuid) {
// 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 uuid: " + uuid);
}
QMemberEntity worker = QMemberEntity.memberEntity;
QMemberEntity inspector = new QMemberEntity("inspector");
NumberExpression<Long> totalCnt = labelingAssignmentEntity.assignmentUid.count(); NumberExpression<Long> totalCnt = labelingAssignmentEntity.assignmentUid.count();
NumberExpression<Long> assignedCnt = NumberExpression<Long> completeCnt =
new CaseBuilder() new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.ASSIGNED.getId())) .when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId()))
.then(1L) .then(1L)
.otherwise((Long) null) .otherwise((Long) null)
.count(); .count();
@@ -793,13 +807,6 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.otherwise((Long) null) .otherwise((Long) null)
.count(); .count();
NumberExpression<Long> completeCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Double> percent = NumberExpression<Double> percent =
new CaseBuilder() new CaseBuilder()
.when(completeCnt.eq(0L)) .when(completeCnt.eq(0L))
@@ -807,25 +814,10 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.otherwise( .otherwise(
Expressions.numberTemplate( Expressions.numberTemplate(
Double.class, Double.class,
"round({0} / {1}, 2)", "ROUND(({0} * 1.0 / NULLIF({1}, 0)) * 100, 2)",
completeCnt, completeCnt,
labelingAssignmentEntity.count())); totalCnt));
// 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 worker = QMemberEntity.memberEntity;
QMemberEntity inspector = new QMemberEntity("inspector");
// remainCnt
Expression<Long> remainCnt = Expression<Long> remainCnt =
Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt); Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt);
@@ -840,7 +832,7 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
completeCnt, completeCnt,
skipCnt, skipCnt,
percent, percent,
Expressions.constant(0), // TODO: 순위, 꼭 해야할지? Expressions.constant(0), // 순위는 Service 레이어에서 계산
labelingAssignmentEntity.workStatDttm.min(), labelingAssignmentEntity.workStatDttm.min(),
inspector.name.min(), inspector.name.min(),
remainCnt)) remainCnt))
@@ -856,6 +848,64 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.fetchOne(); .fetchOne();
} }
/** 특정 회차의 모든 라벨러 통계 조회 (순위 계산용) */
public List<LabelerDetail> findAllLabelersForRanking(Long analUid) {
QMemberEntity worker = QMemberEntity.memberEntity;
NumberExpression<Long> totalCnt = labelingAssignmentEntity.assignmentUid.count();
NumberExpression<Long> completeCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.DONE.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Long> skipCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.workState.eq(LabelState.SKIP.getId()))
.then(1L)
.otherwise((Long) null)
.count();
Expression<Long> remainCnt =
Expressions.numberTemplate(Long.class, "({0} - {1} - {2})", totalCnt, skipCnt, completeCnt);
return queryFactory
.select(
Projections.constructor(
LabelerDetail.class,
worker.userRole,
worker.name,
worker.employeeNo,
totalCnt,
completeCnt,
skipCnt,
Expressions.constant(0.0), // percent
Expressions.constant(0), // ranking
labelingAssignmentEntity.workStatDttm.min(),
Expressions.nullExpression(String.class), // ownerName
remainCnt))
.from(worker)
.innerJoin(labelingAssignmentEntity)
.on(
worker.employeeNo.eq(labelingAssignmentEntity.workerUid),
labelingAssignmentEntity.analUid.eq(analUid))
.groupBy(worker.userRole, worker.name, worker.employeeNo)
.orderBy(completeCnt.desc()) // 완료 건수 내림차순
.fetch();
}
/** UUID로 analUid 조회 */
@Override
public Long findAnalUidByUuid(String uuid) {
return queryFactory
.select(mapSheetAnalInferenceEntity.id)
.from(mapSheetAnalInferenceEntity)
.where(mapSheetAnalInferenceEntity.uuid.eq(UUID.fromString(uuid)))
.fetchOne();
}
@Override @Override
public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) { public Long findMapSheetAnalInferenceUid(Integer compareYyyy, Integer targetYyyy, Integer stage) {
return queryFactory return queryFactory
@@ -1301,11 +1351,11 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
inspector.userRole, inspector.userRole,
inspector.name, inspector.name,
inspector.employeeNo, inspector.employeeNo,
assignedCnt, assignedCnt, // count (총 배정 건수)
skipCnt, completeCnt, // completeCnt (완료 건수)
completeCnt, skipCnt, // skipCnt (스킵 건수)
percent, percent,
Expressions.constant(0), // TODO: 순위, 꼭 해야할지? Expressions.constant(0), // 순위는 Service 레이어에서 계산
labelingAssignmentEntity.inspectStatDttm.min(), labelingAssignmentEntity.inspectStatDttm.min(),
worker.name.min(), worker.name.min(),
remainCnt)) remainCnt))
@@ -1321,6 +1371,60 @@ public class LabelAllocateRepositoryImpl implements LabelAllocateRepositoryCusto
.fetchOne(); .fetchOne();
} }
/** 순위 계산용 - 특정 회차의 모든 검수자 통계 조회 */
public List<LabelerDetail> findAllInspectorsForRanking(Long analUid) {
QMemberEntity inspector = QMemberEntity.memberEntity;
NumberExpression<Long> assignedCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.inspectState.eq(InspectState.UNCONFIRM.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Long> skipCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.inspectState.eq(InspectState.EXCEPT.getId()))
.then(1L)
.otherwise((Long) null)
.count();
NumberExpression<Long> completeCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()))
.then(1L)
.otherwise((Long) null)
.count();
Expression<Long> remainCnt =
Expressions.numberTemplate(
Long.class, "({0} - {1} - {2})", assignedCnt, skipCnt, completeCnt);
return queryFactory
.select(
Projections.constructor(
LabelerDetail.class,
inspector.userRole,
inspector.name,
inspector.employeeNo,
assignedCnt, // count (총 배정 건수)
completeCnt, // completeCnt (완료 건수)
skipCnt, // skipCnt (스킵 건수)
Expressions.constant(0.0), // percent
Expressions.constant(0), // ranking
labelingAssignmentEntity.inspectStatDttm.min(),
Expressions.nullExpression(String.class), // ownerName
remainCnt))
.from(inspector)
.innerJoin(labelingAssignmentEntity)
.on(
inspector.employeeNo.eq(labelingAssignmentEntity.inspectorUid),
labelingAssignmentEntity.analUid.eq(analUid))
.groupBy(inspector.userRole, inspector.name, inspector.employeeNo)
.orderBy(completeCnt.desc()) // 완료 건수 내림차순
.fetch();
}
@Override @Override
public MoveInfo moveAvailUserList(String userId, String uuid) { public MoveInfo moveAvailUserList(String userId, String uuid) {

View File

@@ -472,6 +472,10 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getPnu() ? mapSheetAnalDataInferenceGeomEntityEntity.getPnu()
: 0L) : 0L)
.mapSheetNum(
mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum()
: 0L)
.build(); .build();
var inspectionResultInfo = var inspectionResultInfo =

View File

@@ -491,6 +491,10 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getPnu() ? mapSheetAnalDataInferenceGeomEntityEntity.getPnu()
: 0L) : 0L)
.mapSheetNum(
mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum()
: 0L)
.build(); .build();
var inspectionResultInfo = var inspectionResultInfo =

View File

@@ -331,6 +331,9 @@ public class TrainingDataLabelDto {
@Schema(description = "PNU (필지고유번호)", example = "36221202306020") @Schema(description = "PNU (필지고유번호)", example = "36221202306020")
private Long pnu; private Long pnu;
@Schema(description = "도엽번호 (map_sheet_num)", example = "34602057")
private Long mapSheetNum;
} }
@Schema(name = "ClassificationInfo", description = "분류정보") @Schema(name = "ClassificationInfo", description = "분류정보")

View File

@@ -337,6 +337,9 @@ public class TrainingDataReviewDto {
@Schema(description = "PNU (필지고유번호)", example = "36221202306020") @Schema(description = "PNU (필지고유번호)", example = "36221202306020")
private Long pnu; private Long pnu;
@Schema(description = "도엽번호 (map_sheet_num)", example = "34602057")
private Long mapSheetNum;
} }
@Schema(name = "ClassificationInfo", description = "분류정보") @Schema(name = "ClassificationInfo", description = "분류정보")