Merge remote-tracking branch 'origin/feat/infer_dev_260107' into feat/infer_dev_260107

This commit is contained in:
2026-01-12 14:43:06 +09:00
7 changed files with 775 additions and 9 deletions

View File

@@ -1,10 +1,15 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.postgres.repository.trainingdata.TrainingDataLabelRepository;
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;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.searchReq;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.locationtech.jts.geom.Geometry;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@@ -22,4 +27,52 @@ public class TrainingDataLabelCoreService {
public LabelingGeometryInfo findLabelingAssignedGeom(String assignmentUid) {
return trainingDataLabelRepository.findLabelingAssignedGeom(assignmentUid);
}
public Long findLabelingAssignmentGeoUid(String assignmentUid) {
return trainingDataLabelRepository.findLabelingAssignmentGeoUid(assignmentUid);
}
public void updateLabelingStateAssignment(String assignmentUid, String status) {
trainingDataLabelRepository.updateLabelingStateAssignment(assignmentUid, status);
}
public void updateLabelingSkipState(Long inferenceGeomUid, String status) {
trainingDataLabelRepository.updateLabelingSkipState(inferenceGeomUid, status);
}
public void updateLabelingPolygonClass(
Long inferenceGeomUid, Geometry geometry, Properties properties, String status) {
trainingDataLabelRepository.updateLabelingPolygonClass(
inferenceGeomUid, geometry, properties, status);
}
/**
* 라벨러별 작업 통계 조회
*
* @param userId 라벨러 사번
* @return 전체/미작업/Today 건수
*/
public SummaryRes getSummary(String userId) {
try {
System.out.println("[CoreService] getSummary called with userId: " + userId);
SummaryRes result = trainingDataLabelRepository.getSummary(userId);
System.out.println("[CoreService] getSummary result: " + result);
return result;
} catch (Exception e) {
System.err.println("[CoreService] getSummary ERROR: " + e.getMessage());
e.printStackTrace();
// 예외 발생 시에도 빈 통계 반환
return SummaryRes.builder().totalCnt(0L).undoneCnt(0L).todayCnt(0L).build();
}
}
/**
* 작업 배정 상세 정보 조회
*
* @param assignmentUid 작업 배정 ID
* @return 변화탐지정보 + 실태조사결과정보
*/
public DetailRes getDetail(UUID assignmentUid) {
return trainingDataLabelRepository.getDetail(assignmentUid);
}
}

View File

@@ -1,8 +1,13 @@
package com.kamco.cd.kamcoback.postgres.repository.trainingdata;
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;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.searchReq;
import java.util.UUID;
import org.locationtech.jts.geom.Geometry;
import org.springframework.data.domain.Page;
public interface TrainingDataLabelRepositoryCustom {
@@ -10,4 +15,17 @@ public interface TrainingDataLabelRepositoryCustom {
Page<LabelingListDto> findLabelingAssignedList(searchReq searchReq, String userId, String status);
LabelingGeometryInfo findLabelingAssignedGeom(String assignmentUid);
Long findLabelingAssignmentGeoUid(String assignmentUid);
void updateLabelingStateAssignment(String assignmentUid, String status);
void updateLabelingSkipState(Long inferenceGeomUid, String status);
void updateLabelingPolygonClass(
Long inferenceGeomUid, Geometry geometry, Properties properties, String status);
SummaryRes getSummary(String userId);
DetailRes getDetail(UUID assignmentUid);
}

View File

@@ -5,9 +5,13 @@ import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.l
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import com.fasterxml.jackson.databind.JsonNode;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
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;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.SummaryRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.searchReq;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
@@ -16,10 +20,13 @@ import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import org.locationtech.jts.geom.Geometry;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
@@ -132,6 +139,356 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
.fetchOne();
}
@Override
public Long findLabelingAssignmentGeoUid(String assignmentUid) {
return queryFactory
.select(labelingAssignmentEntity.inferenceGeomUid)
.from(labelingAssignmentEntity)
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(assignmentUid)))
.fetchOne();
}
@Override
public void updateLabelingStateAssignment(String assignmentUid, String status) {
queryFactory
.update(labelingAssignmentEntity)
.set(labelingAssignmentEntity.workState, status)
.set(labelingAssignmentEntity.workStatDttm, ZonedDateTime.now())
.where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(assignmentUid)))
.execute();
}
@Override
public void updateLabelingSkipState(Long inferenceGeomUid, String status) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now())
.set(mapSheetAnalDataInferenceGeomEntity.labelState, status)
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.eq(inferenceGeomUid))
.execute();
}
@Override
public void updateLabelingPolygonClass(
Long mapSheetAnalDataInferenceGeomEntityUid,
Geometry geometry,
Properties properties,
String status) {
queryFactory
.update(mapSheetAnalDataInferenceGeomEntity)
.set(mapSheetAnalDataInferenceGeomEntity.geom, geometry)
.set(
mapSheetAnalDataInferenceGeomEntity.classBeforeCd,
properties.getBeforeClass().toLowerCase())
.set(
mapSheetAnalDataInferenceGeomEntity.classAfterCd,
properties.getAfterClass().toLowerCase())
.set(mapSheetAnalDataInferenceGeomEntity.labelStateDttm, ZonedDateTime.now())
.set(mapSheetAnalDataInferenceGeomEntity.labelState, status)
.set(mapSheetAnalDataInferenceGeomEntity.geomCenter, geometry.getCentroid())
.where(
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(mapSheetAnalDataInferenceGeomEntityUid))
.execute();
}
@Override
public SummaryRes getSummary(String userId) {
// 기본값 설정
Long totalCnt = 0L;
Long undoneCnt = 0L;
Long todayCnt = 0L;
try {
System.out.println("=== getSummary START ===");
System.out.println("userId: " + userId);
// 1. 전체 배정 건수
try {
Long result =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(labelingAssignmentEntity.workerUid.eq(userId))
.fetchOne();
totalCnt = (result != null) ? result : 0L;
System.out.println("totalCnt: " + totalCnt);
} catch (Exception e) {
System.err.println(
"totalCnt query error: " + e.getClass().getName() + " - " + e.getMessage());
if (e.getCause() != null) {
System.err.println("Caused by: " + e.getCause().getMessage());
}
totalCnt = 0L;
}
// 2. 미작업 건수 (ASSIGNED 상태)
try {
Long result =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(
labelingAssignmentEntity.workerUid.eq(userId),
labelingAssignmentEntity.workState.eq("ASSIGNED"))
.fetchOne();
undoneCnt = (result != null) ? result : 0L;
System.out.println("undoneCnt: " + undoneCnt);
} catch (Exception e) {
System.err.println(
"undoneCnt query error: " + e.getClass().getName() + " - " + e.getMessage());
if (e.getCause() != null) {
System.err.println("Caused by: " + e.getCause().getMessage());
}
undoneCnt = 0L;
}
// 3. 오늘 완료 건수
try {
// 오늘 날짜의 시작과 끝 시간 계산
ZonedDateTime startOfToday = LocalDate.now().atStartOfDay(java.time.ZoneId.systemDefault());
ZonedDateTime endOfToday = startOfToday.plusDays(1);
System.out.println("startOfToday: " + startOfToday);
System.out.println("endOfToday: " + endOfToday);
Long result =
queryFactory
.select(labelingAssignmentEntity.count())
.from(labelingAssignmentEntity)
.where(
labelingAssignmentEntity.workerUid.eq(userId),
labelingAssignmentEntity.workState.eq("DONE"),
labelingAssignmentEntity.modifiedDate.isNotNull(),
labelingAssignmentEntity.modifiedDate.goe(startOfToday),
labelingAssignmentEntity.modifiedDate.lt(endOfToday))
.fetchOne();
todayCnt = (result != null) ? result : 0L;
System.out.println("todayCnt: " + todayCnt);
} catch (Exception e) {
System.err.println(
"todayCnt query error: " + e.getClass().getName() + " - " + e.getMessage());
if (e.getCause() != null) {
System.err.println("Caused by: " + e.getCause().getMessage());
}
todayCnt = 0L;
}
System.out.println("=== getSummary END ===");
System.out.println(
"Final result - totalCnt: "
+ totalCnt
+ ", undoneCnt: "
+ undoneCnt
+ ", todayCnt: "
+ todayCnt);
} catch (Exception e) {
// 최상위 예외 처리
System.err.println("=== getSummary OUTER ERROR ===");
System.err.println("Error: " + e.getClass().getName() + " - " + e.getMessage());
if (e.getCause() != null) {
System.err.println("Caused by: " + e.getCause().getMessage());
}
e.printStackTrace();
}
// 항상 정상 응답 반환 (예외를 throw하지 않음)
return SummaryRes.builder().totalCnt(totalCnt).undoneCnt(undoneCnt).todayCnt(todayCnt).build();
}
@Override
public DetailRes getDetail(UUID assignmentUid) {
try {
// 1. 작업 배정 정보 조회
var assignment =
queryFactory
.selectFrom(labelingAssignmentEntity)
.where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid))
.fetchOne();
if (assignment == null) {
throw new RuntimeException("Assignment not found: " + assignmentUid);
}
// 2. 추론 결과 정보 조회
var mapSheetAnalDataInferenceGeomEntityEntity =
queryFactory
.selectFrom(mapSheetAnalDataInferenceGeomEntity)
.where(
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(
assignment.toDto().getInferenceGeomUid()))
.fetchOne();
if (mapSheetAnalDataInferenceGeomEntityEntity == null) {
throw new RuntimeException(
"Inference geometry not found: " + assignment.toDto().getInferenceGeomUid());
}
// 3. 도엽 정보 조회
var mapSheetEntity =
queryFactory
.selectFrom(mapInkx5kEntity)
.where(mapInkx5kEntity.mapidcdNo.eq(assignment.toDto().getAssignGroupId()))
.fetchOne();
// 4. COG URL 조회 - imagery만 사용
String beforeCogUrl = "";
String afterCogUrl = "";
try {
var beforeImagery =
queryFactory
.select(
Expressions.stringTemplate(
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
.from(imageryEntity)
.where(
imageryEntity.scene5k.eq(assignment.toDto().getAssignGroupId()),
imageryEntity.year.eq(
mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy()))
.fetchFirst();
beforeCogUrl = beforeImagery != null ? beforeImagery : "";
var afterImagery =
queryFactory
.select(
Expressions.stringTemplate(
"{0} || {1}", imageryEntity.cogMiddlePath, imageryEntity.cogFilename))
.from(imageryEntity)
.where(
imageryEntity.scene5k.eq(assignment.toDto().getAssignGroupId()),
imageryEntity.year.eq(
mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy()))
.fetchFirst();
afterCogUrl = afterImagery != null ? afterImagery : "";
} catch (Exception e) {
System.err.println("COG URL retrieval error: " + e.getMessage());
// COG URL 조회 실패 시 빈 문자열 유지
}
// 5. DTO 생성
var changeDetectionInfo =
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ChangeDetectionInfo.builder()
.mapSheetInfo(mapSheetEntity != null ? mapSheetEntity.getMapidNm() : "")
.detectionYear(
(mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getCompareYyyy()
: 0)
+ "-"
+ (mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getTargetYyyy()
: 0))
.beforeClass(
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ClassificationInfo
.builder()
.classification(
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeCd()
: "")
.probability(
mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassBeforeProb()
: 0.0)
.build())
.afterClass(
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.ClassificationInfo
.builder()
.classification(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterCd()
: "")
.probability(
mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getClassAfterProb()
: 0.0)
.build())
.area(
mapSheetAnalDataInferenceGeomEntityEntity.getArea() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getArea()
: 0.0)
.detectionAccuracy(
mapSheetAnalDataInferenceGeomEntityEntity.getCdProb() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getCdProb()
: 0.0)
.pnu(
mapSheetAnalDataInferenceGeomEntityEntity.getPnu() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getPnu()
: 0L)
.build();
var inspectionResultInfo =
com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.InspectionResultInfo
.builder()
.verificationResult(convertInspectState(assignment.toDto().getInspectState()))
.inappropriateReason("")
// .memo(assignment.toDto().getInspectMemo() != null ?
// assignment.toDto().getInspectMemo() : "")
.build();
// 6. Geometry를 GeoJSON으로 변환
com.fasterxml.jackson.databind.JsonNode geomJson = null;
if (mapSheetAnalDataInferenceGeomEntityEntity.getGeom() != null) {
try {
String geomString =
queryFactory
.select(
Expressions.stringTemplate(
"ST_AsGeoJSON({0})", mapSheetAnalDataInferenceGeomEntity.geom))
.from(mapSheetAnalDataInferenceGeomEntity)
.where(
mapSheetAnalDataInferenceGeomEntity.geoUid.eq(
mapSheetAnalDataInferenceGeomEntityEntity.getGeoUid()))
.fetchOne();
if (geomString != null) {
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
geomJson = mapper.readTree(geomString);
}
} catch (Exception e) {
System.err.println("GeoJSON parsing error: " + e.getMessage());
// JSON 파싱 실패 시 null 유지
}
}
// 도엽 bbox json으로 가져오기
JsonNode mapBbox = null;
if (mapSheetEntity.getGeom() != null) {
try {
String bboxString =
queryFactory
.select(Expressions.stringTemplate("ST_AsGeoJSON({0})", mapInkx5kEntity.geom))
.from(mapInkx5kEntity)
.where(mapInkx5kEntity.mapidcdNo.eq(assignment.toDto().getAssignGroupId()))
.fetchOne();
if (bboxString != null) {
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
mapBbox = mapper.readTree(bboxString);
}
} catch (Exception e) {
throw new RuntimeException("GeoJSON parsing error: " + e.getMessage());
}
}
return DetailRes.builder()
.assignmentUid(assignmentUid)
.changeDetectionInfo(changeDetectionInfo)
.inspectionResultInfo(inspectionResultInfo)
.geom(geomJson)
.beforeCogUrl(beforeCogUrl)
.afterCogUrl(afterCogUrl)
.mapBox(mapBbox)
.build();
} catch (Exception e) {
System.err.println("getDetail Error: " + e.getMessage());
e.printStackTrace();
throw new RuntimeException("Failed to get detail for assignmentUid: " + assignmentUid, e);
}
}
private StringExpression makeCogUrl(NumberPath<Integer> year) {
return new CaseBuilder()
.when(imageryEntity.year.eq(year))
@@ -149,4 +506,20 @@ public class TrainingDataLabelRepositoryImpl extends QuerydslRepositorySupport
String[] arrStatus = status.split(",");
return labelingAssignmentEntity.workState.in(arrStatus);
}
private String convertInspectState(String inspectState) {
if (inspectState == null) {
return "미확인";
}
switch (inspectState) {
case "UNCONFIRM":
return "미확인";
case "EXCEPT":
return "제외";
case "COMPLETE":
return "완료";
default:
return "미확인";
}
}
}