라벨링 상세정보, 요약정보(Daniel), 저장(Gina)

This commit is contained in:
2026-01-12 14:36:08 +09:00
parent 3d612f75d7
commit fd7ec9ea9c
7 changed files with 775 additions and 9 deletions

View File

@@ -2,10 +2,12 @@ package com.kamco.cd.kamcoback.trainingdata;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingGeometryInfo;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.LabelingListDto;
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.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -15,6 +17,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -51,6 +55,7 @@ public class TrainingDataLabelApiController {
trainingDataLabelService.findLabelingAssignedList(searchReq, userId, status));
}
@Hidden
@Operation(summary = "상세 Geometry 조회", description = "라벨 할당 상세 Geometry 조회")
@ApiResponses(
value = {
@@ -69,4 +74,87 @@ public class TrainingDataLabelApiController {
@RequestParam(defaultValue = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02") String assignmentUid) {
return ApiResponseDto.ok(trainingDataLabelService.findLabelingAssignedGeom(assignmentUid));
}
@Operation(summary = "Geometry 저장", description = "Geometry 저장")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = CommonCodeDto.Basic.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PostMapping
public ApiResponseDto<ResponseObj> saveLabelingFeature(
@RequestBody TrainingDataLabelDto.GeoFeatureRequest request) {
return ApiResponseDto.okObject(trainingDataLabelService.saveLabelingFeature(request));
}
@Operation(summary = "작업 통계 조회", description = "라벨러의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TrainingDataLabelDto.SummaryRes.class))),
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@GetMapping("/summary")
public ApiResponseDto<TrainingDataLabelDto.SummaryRes> getSummary(
@io.swagger.v3.oas.annotations.Parameter(
description = "라벨러 사번",
required = true,
example = "01022223333")
@RequestParam
String userId) {
try {
System.out.println("[Controller] getSummary called with userId: " + userId);
TrainingDataLabelDto.SummaryRes result = trainingDataLabelService.getSummary(userId);
System.out.println("[Controller] getSummary result: " + result);
return ApiResponseDto.ok(result);
} catch (Exception e) {
System.err.println("[Controller] getSummary ERROR: " + e.getMessage());
e.printStackTrace();
// 예외 발생 시에도 빈 통계 반환
return ApiResponseDto.ok(
TrainingDataLabelDto.SummaryRes.builder()
.totalCnt(0L)
.undoneCnt(0L)
.todayCnt(0L)
.build());
}
}
@Operation(summary = "변화탐지정보 및 실태조사결과 조회", description = "선택한 작업의 변화탐지정보 및 실태조사결과를 조회합니다.")
@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("/detail")
public ApiResponseDto<TrainingDataLabelDto.DetailRes> getDetail(
@io.swagger.v3.oas.annotations.Parameter(
description = "작업 배정 ID (UUID)",
required = true,
example = "93c56be8-0246-4b22-b976-2476549733cc")
@RequestParam
java.util.UUID assignmentUid) {
return ApiResponseDto.ok(trainingDataLabelService.getDetail(assignmentUid));
}
}

View File

@@ -4,12 +4,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kamco.cd.kamcoback.common.utils.geometry.GeometryDeserializer;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.locationtech.jts.geom.Geometry;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@@ -30,15 +34,6 @@ public class TrainingDataLabelDto {
private String mapIdNm;
private Long pnu;
// @JsonIgnore
// private String geomData; // json string
// private JsonNode geom;
// private String beforeCogUrl;
// private String afterCogUrl;
// @JsonIgnore
// private String mapBboxString; //json string
// private JsonNode mapBbox;
public LabelingListDto(
UUID assignmentUid,
Long inferenceGeomUid,
@@ -99,6 +94,71 @@ public class TrainingDataLabelDto {
}
}
@Schema(name = "GeoFeatureRequest", description = "polygon 저장")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class GeoFeatureRequest {
@Schema(description = "assignmentUid", example = "4f9ebc8b-6635-4177-b42f-7efc9c7b4c02")
private String assignmentUid;
@Schema(description = "type", example = "Feature")
private String type;
@JsonDeserialize(using = GeometryDeserializer.class)
@Schema(
description = "라벨링 그린 polygon",
example =
"""
{
"type": "Polygon",
"coordinates": [
[
[
126.66292461969202,
34.58785236216609
],
[
126.66263801099049,
34.58740117447532
],
[
126.66293668521236,
34.5873904146878
],
[
126.66312820122245,
34.587841464427825
],
[
126.66289124481979,
34.58786048381633
],
[
126.66292461969202,
34.58785236216609
]
]
]
}
""")
private Geometry geometry;
private Properties properties;
@Getter
public static class Properties {
@Schema(description = "beforeClass", example = "WASTE")
private String beforeClass;
@Schema(description = "afterClass", example = "LAND")
private String afterClass;
}
}
@Schema(name = "searchReq", description = "검색 요청")
@Getter
@Setter
@@ -122,4 +182,115 @@ public class TrainingDataLabelDto {
return PageRequest.of(page, size);
}
}
@Schema(name = "DetailRes", description = "객체 상세 정보 응답")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class DetailRes {
@Schema(description = "작업 배정 ID")
private UUID assignmentUid;
@Schema(description = "변화탐지정보")
private ChangeDetectionInfo changeDetectionInfo;
@Schema(description = "실태조사결과정보")
private InspectionResultInfo inspectionResultInfo;
@Schema(description = "Geometry (GeoJSON)")
private JsonNode geom;
@Schema(description = "변화 전 COG 이미지 URL")
private String beforeCogUrl;
@Schema(description = "변화 후 COG 이미지 URL")
private String afterCogUrl;
@Schema(description = "도엽 bbox")
private JsonNode mapBox;
}
@Schema(name = "ChangeDetectionInfo", description = "변화탐지정보")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ChangeDetectionInfo {
@Schema(description = "도엽번호정보", example = "남해")
private String mapSheetInfo;
@Schema(description = "변화탐지연도", example = "2022-2023")
private String detectionYear;
@Schema(description = "변화 전 분류 정보")
private ClassificationInfo beforeClass;
@Schema(description = "변화 후 분류 정보")
private ClassificationInfo afterClass;
@Schema(description = "면적 (㎡)", example = "179.52")
private Double area;
@Schema(description = "탐지정확도 (%)", example = "84.8")
private Double detectionAccuracy;
@Schema(description = "PNU (필지고유번호)", example = "36221202306020")
private Long pnu;
}
@Schema(name = "ClassificationInfo", description = "분류정보")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ClassificationInfo {
@Schema(description = "분류", example = "일반토지")
private String classification;
@Schema(description = "확률", example = "80.0")
private Double probability;
}
@Schema(name = "InspectionResultInfo", description = "실태조사결과정보")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class InspectionResultInfo {
@Schema(description = "검증결과 (미확인/제외/완료)", example = "미확인")
private String verificationResult;
@Schema(description = "부적합사유")
private String inappropriateReason;
@Schema(description = "메모")
private String memo;
}
@Schema(name = "SummaryRes", description = "작업 통계 응답")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SummaryRes {
@Schema(description = "전체 배정 건수", example = "8901")
private Long totalCnt;
@Schema(description = "미작업 건수 (ASSIGNED 상태)", example = "7211")
private Long undoneCnt;
@Schema(description = "오늘 완료 건수", example = "0")
private Long todayCnt;
}
}

View File

@@ -1,12 +1,21 @@
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.DetailRes;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.GeoFeatureRequest;
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 jakarta.transaction.Transactional;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TrainingDataLabelService {
@@ -24,4 +33,54 @@ public class TrainingDataLabelService {
public LabelingGeometryInfo findLabelingAssignedGeom(String assignmentUid) {
return trainingDataLabelCoreService.findLabelingAssignedGeom(assignmentUid);
}
@Transactional
public ResponseObj saveLabelingFeature(GeoFeatureRequest request) {
String status = "";
String assignmentUid = request.getAssignmentUid();
Long inferenceGeomUid =
trainingDataLabelCoreService.findLabelingAssignmentGeoUid(assignmentUid);
if (request.getGeometry() == null || request.getGeometry().isEmpty()) {
// SKIP 상태만 업데이트
status = "SKIP";
trainingDataLabelCoreService.updateLabelingStateAssignment(assignmentUid, status);
trainingDataLabelCoreService.updateLabelingSkipState(inferenceGeomUid, status);
} else {
status = "DONE";
trainingDataLabelCoreService.updateLabelingStateAssignment(assignmentUid, status);
trainingDataLabelCoreService.updateLabelingPolygonClass(
inferenceGeomUid, request.getGeometry(), request.getProperties(), status);
}
return new ResponseObj(ApiResponseCode.OK, "저장되었습니다.");
}
/**
* 라벨러별 작업 통계 조회
*
* @param userId 라벨러 사번
* @return 전체/미작업/Today 건수
*/
public SummaryRes getSummary(String userId) {
try {
System.out.println("[Service] getSummary called with userId: " + userId);
SummaryRes result = trainingDataLabelCoreService.getSummary(userId);
System.out.println("[Service] getSummary result: " + result);
return result;
} catch (Exception e) {
System.err.println("[Service] 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 trainingDataLabelCoreService.getDetail(assignmentUid);
}
}