diff --git a/jar/makeshp-1.0.0.jar b/jar/makeshp-1.0.0.jar index 2614032b..89ba81a0 100644 Binary files a/jar/makeshp-1.0.0.jar and b/jar/makeshp-1.0.0.jar differ diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/DetectionClassification.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/DetectionClassification.java index 42d231bd..427f6b9e 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/enums/DetectionClassification.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/DetectionClassification.java @@ -20,6 +20,10 @@ public enum DetectionClassification { TUMULUS("tumulus", "토분(무덤)", 120), WASTE("waste", "폐기물", 130), WATER("water", "물", 140), + CONSTRUCTION("construction", "건설", 150), + NDC("NDC", "미분류", 160), + RICE("rice", "논", 170), + WOOD("wood", "산림", 180), ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other) private final String id; diff --git a/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java b/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java index eb7d830a..8c665423 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/service/ExternalJarRunner.java @@ -1,7 +1,5 @@ package com.kamco.cd.kamcoback.common.service; -import com.kamco.cd.kamcoback.inference.dto.InferenceResultsTestingDto; -import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; @@ -17,28 +15,8 @@ import org.springframework.stereotype.Component; @RequiredArgsConstructor public class ExternalJarRunner { - private final InferenceResultCoreService inferenceResultCoreService; - public void run(String jarPath, String batchIds, String inferenceId, String mapIds) { - List batch = new ArrayList<>(); - batch.add(285L); - batch.add(286L); - batch.add(287L); - List resultList = - inferenceResultCoreService.getInferenceResults(batch); - StringBuilder sb = new StringBuilder(); - mapIds = sb.toString(); - for (InferenceResultsTestingDto.ShpDto dto : resultList) { - if (dto.getMapId() == null) { - continue; - } - if (!sb.isEmpty()) { - sb.append(","); - } - sb.append("\"").append(dto.getMapId()).append("\""); - } - StringBuilder out = new StringBuilder(); try { diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java index 72cb50bc..eb18fd88 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java @@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -45,7 +46,8 @@ public class ApiResponseAdvice implements ResponseBodyAdvice { public boolean supports( MethodParameter returnType, Class> converterType) { // ApiResponseDto를 반환하는 경우에만 적용 - return returnType.getParameterType().equals(ApiResponseDto.class); + return returnType.getParameterType().equals(ApiResponseDto.class) + || returnType.getParameterType().equals(ResponseEntity.class); } @Override diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java index 5f4189c4..cfb122c8 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/InferenceResultApiController.java @@ -1,5 +1,6 @@ package com.kamco.cd.kamcoback.inference; +import com.kamco.cd.kamcoback.common.exception.CustomApiException; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; @@ -20,11 +21,19 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.time.LocalDate; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; import org.springframework.data.domain.Page; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -33,6 +42,7 @@ 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; +import org.springframework.web.util.UriUtils; @Tag(name = "추론관리", description = "추론관리 API") @RequestMapping("/api/inference") @@ -388,4 +398,47 @@ public class InferenceResultApiController { inferenceResultService.getInferenceGeomList(uuid, searchGeoReq); return ApiResponseDto.ok(geomList); } + + @Operation(summary = "shp 파일 다운로드", description = "추론관리 분석결과 shp 파일 다운로드") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "shp zip파일 다운로드", + content = + @Content( + mediaType = "application/octet-stream", + schema = @Schema(type = "string", format = "binary"))), + @ApiResponse(responseCode = "404", description = "파일 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping(value = "/download/{uuid}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public ResponseEntity downloadZip(@PathVariable UUID uuid) throws IOException { + + String path; + try { + path = String.valueOf(inferenceResultService.shpDownloadPath(uuid)); + } catch (CustomApiException e) { + // 데이터 없음 등 404 + return ResponseEntity.status(e.getStatus()).build(); + } + + Path zipPath = Path.of(path); + FileSystemResource resource = new FileSystemResource(zipPath); + + if (!resource.exists() || !resource.isReadable()) { + return ResponseEntity.notFound().build(); + } + + String filename = zipPath.getFileName().toString(); + String encodedFilename = UriUtils.encode(filename, StandardCharsets.UTF_8); + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + filename + "\"; filename*=UTF-8''" + encodedFilename) + .contentLength(resource.contentLength()) + .body((Resource) resource); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java index 9d579903..2efdbadb 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceResultDto.java @@ -622,4 +622,11 @@ public class InferenceResultDto { private Integer completedJobs; private Integer failedJobs; } + + @Getter + @Setter + public static class InferenceLearnDto { + + private String uid; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceSendDto.java b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceSendDto.java index bb09413f..ab90238a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceSendDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/dto/InferenceSendDto.java @@ -5,6 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +/** AI API 추론 실행 DTO */ @Getter @Setter @NoArgsConstructor diff --git a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java index 3586f22b..8634d357 100644 --- a/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java +++ b/src/main/java/com/kamco/cd/kamcoback/inference/service/InferenceResultService.java @@ -16,6 +16,7 @@ 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.DetectOption; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto; 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.MapSheetNumDto; @@ -32,6 +33,7 @@ import com.kamco.cd.kamcoback.postgres.core.InferenceResultCoreService; import com.kamco.cd.kamcoback.postgres.core.MapSheetMngCoreService; import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService; import jakarta.validation.constraints.NotNull; +import java.nio.file.Path; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; @@ -70,6 +72,9 @@ public class InferenceResultService { @Value("${inference.batch-url}") private String batchUrl; + @Value("${file.dataset-dir}") + private String datasetDir; + @Value("${spring.profiles.active}") private String profile; @@ -536,4 +541,17 @@ public class InferenceResultService { Long learnId = inferenceResultCoreService.getInferenceLearnIdByUuid(dto.getUuid()); inferenceResultCoreService.upsertGeomData(learnId); } + + /** + * 추론결과 shp zip 파일 다운로드 경로 생성 + * + * @param uuid + * @return + */ + public Path shpDownloadPath(UUID uuid) { + InferenceLearnDto dto = inferenceResultCoreService.getInferenceUid(uuid); + String uid = dto.getUid(); + + return Path.of(datasetDir).resolve(uid).resolve("merge").resolve(uid + ".zip"); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java index 75c40180..174ee4eb 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/InferenceResultCoreService.java @@ -11,6 +11,7 @@ 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.InferenceResultDto; +import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.InferenceLearnDto; 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.ResultList; @@ -461,4 +462,20 @@ public class InferenceResultCoreService { inferenceResultsTetingRepository.getInferenceResultList(batchIds); return list.stream().map(InferenceResultsTestingDto.ShpDto::fromEntity).toList(); } + + /** + * uid 조회 + * + * @param uuid + * @return + */ + public InferenceLearnDto getInferenceUid(UUID uuid) { + MapSheetLearnEntity entity = + inferenceResultRepository + .getInferenceUid(uuid) + .orElseThrow(() -> new CustomApiException("NOT_FOUND_DATA", HttpStatus.NOT_FOUND)); + InferenceLearnDto dto = new InferenceLearnDto(); + dto.setUid(entity.getUid()); + return dto; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/TrainingDataReviewJobCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/TrainingDataReviewJobCoreService.java new file mode 100644 index 00000000..5260f7af --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/TrainingDataReviewJobCoreService.java @@ -0,0 +1,40 @@ +package com.kamco.cd.kamcoback.postgres.core; + +import com.kamco.cd.kamcoback.postgres.repository.scheduler.TrainingDataReviewJobRepository; +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class TrainingDataReviewJobCoreService { + + private final TrainingDataReviewJobRepository trainingDataReviewJobRepository; + + public List findCompletedYesterdayUnassigned() { + return trainingDataReviewJobRepository.findCompletedYesterdayUnassigned(); + } + + public void assignReviewer(UUID assignmentUid, String reviewerId) { + trainingDataReviewJobRepository.assignReviewer(assignmentUid, reviewerId); + } + + public void assignReviewerBatch(List assignmentUids, String reviewerId) { + trainingDataReviewJobRepository.assignReviewerBatch(assignmentUids, reviewerId); + } + + public Tasks findAssignmentTask(String assignmentUid) { + return trainingDataReviewJobRepository.findAssignmentTask(assignmentUid); + } + + public List findInspectorPendingByRound(Long analUid) { + return trainingDataReviewJobRepository.findInspectorPendingByRound(analUid); + } + + public void lockInspectors(Long analUid, List reviewerIds) { + trainingDataReviewJobRepository.lockInspectors(analUid, reviewerIds); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java index 0d0887c7..c3162853 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryCustom.java @@ -1,7 +1,9 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity; import java.util.List; +import java.util.Optional; import java.util.UUID; public interface InferenceResultRepositoryCustom { @@ -27,4 +29,6 @@ public interface InferenceResultRepositoryCustom { List findGeomEntitiesByDataUid(Long dataUid, int limit); Long getInferenceLearnIdByUuid(UUID uuid); + + public Optional getInferenceUid(UUID uuid); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java index f6f02e28..3d51412b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/InferenceResultRepositoryImpl.java @@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; +import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity; @@ -10,6 +11,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import java.time.ZonedDateTime; import java.util.List; +import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -468,4 +470,14 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC .where(mapSheetLearnEntity.uuid.eq(uuid)) .fetchOne(); } + + @Override + public Optional getInferenceUid(UUID uuid) { + return Optional.ofNullable( + queryFactory + .select(mapSheetLearnEntity) + .from(mapSheetLearnEntity) + .where(mapSheetLearnEntity.uuid.eq(uuid)) + .fetchOne()); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java index 2b108692..9038d6d5 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/Inference/MapSheetLearnRepositoryImpl.java @@ -2,6 +2,7 @@ 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.QMapInkx5kEntity.mapInkx5kEntity; +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.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalSttcEntity.mapSheetAnalSttcEntity; @@ -339,7 +340,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto .select( Projections.constructor( Dashboard.class, - mapSheetAnalSttcEntity.id.classAfterCd, + mapSheetAnalSttcEntity.id.classAfterCd.toUpperCase(), mapSheetAnalSttcEntity.classAfterCnt.sum())) .from(mapSheetAnalInferenceEntity) .innerJoin(mapSheetAnalSttcEntity) @@ -419,13 +420,10 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto // mapSheetAnalDataInferenceGeomEntity.geomCenter) )) .from(mapSheetAnalInferenceEntity) + .join(mapSheetAnalDataInferenceEntity) + .on(mapSheetAnalDataInferenceEntity.analUid.eq(mapSheetAnalInferenceEntity.id)) .join(mapSheetAnalDataInferenceGeomEntity) - .on( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq( - mapSheetAnalInferenceEntity.compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq( - mapSheetAnalInferenceEntity.targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(mapSheetAnalInferenceEntity.stage)) + .on(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)) .join(mapInkx5kEntity) .on( mapSheetAnalDataInferenceGeomEntity.mapSheetNum.eq( @@ -440,13 +438,10 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto queryFactory .select(mapSheetAnalDataInferenceGeomEntity.geoUid) .from(mapSheetAnalInferenceEntity) + .join(mapSheetAnalDataInferenceEntity) + .on(mapSheetAnalDataInferenceEntity.analUid.eq(mapSheetAnalInferenceEntity.id)) .join(mapSheetAnalDataInferenceGeomEntity) - .on( - mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq( - mapSheetAnalInferenceEntity.compareYyyy), - mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq( - mapSheetAnalInferenceEntity.targetYyyy), - mapSheetAnalDataInferenceGeomEntity.stage.eq(mapSheetAnalInferenceEntity.stage)) + .on(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id)) .join(mapInkx5kEntity) .on( mapSheetAnalDataInferenceGeomEntity.mapSheetNum.eq( diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepository.java new file mode 100644 index 00000000..430dacd0 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepository.java @@ -0,0 +1,23 @@ +package com.kamco.cd.kamcoback.postgres.repository.scheduler; + +import com.kamco.cd.kamcoback.postgres.entity.LabelingInspectorEntity; +import jakarta.persistence.LockModeType; +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.data.jpa.repository.Query; + +public interface TrainingDataReviewJobRepository + extends JpaRepository, TrainingDataReviewJobRepositoryCustom { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query( + """ + select r + from LabelingInspectorEntity r + where r.analUid = :analUid + and r.inspectorUid in :inspectorUids + """) + List lockInspectors(Long analUid, List inspectorUids); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepositoryCustom.java new file mode 100644 index 00000000..f83b2f77 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepositoryCustom.java @@ -0,0 +1,19 @@ +package com.kamco.cd.kamcoback.postgres.repository.scheduler; + +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; +import java.util.List; +import java.util.UUID; + +public interface TrainingDataReviewJobRepositoryCustom { + + List findCompletedYesterdayUnassigned(); + + List findInspectorPendingByRound(Long analUid); + + void assignReviewer(UUID assignmentUid, String reviewerId); + + void assignReviewerBatch(List assignmentUids, String reviewerId); + + Tasks findAssignmentTask(String assignmentUid); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepositoryImpl.java new file mode 100644 index 00000000..d7f22994 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/TrainingDataReviewJobRepositoryImpl.java @@ -0,0 +1,135 @@ +package com.kamco.cd.kamcoback.postgres.repository.scheduler; + +import static com.kamco.cd.kamcoback.postgres.entity.QLabelingAssignmentEntity.labelingAssignmentEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity; + +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState; +import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; +import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.UUID; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; + +public class TrainingDataReviewJobRepositoryImpl extends QuerydslRepositorySupport + implements TrainingDataReviewJobRepositoryCustom { + + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + public TrainingDataReviewJobRepositoryImpl(JPAQueryFactory queryFactory) { + super(LabelingAssignmentEntity.class); + this.queryFactory = queryFactory; + } + + @Override + public List findCompletedYesterdayUnassigned() { + ZoneId zone = ZoneId.of("Asia/Seoul"); + ZonedDateTime todayStart = ZonedDateTime.now(zone).toLocalDate().atStartOfDay(zone); + ZonedDateTime yesterdayStart = todayStart.minusDays(1); + + BooleanExpression isYesterday = + labelingAssignmentEntity + .workStatDttm + .goe(yesterdayStart) + .and(labelingAssignmentEntity.workStatDttm.lt(todayStart)); + + return queryFactory + .select( + Projections.constructor( + Tasks.class, + labelingAssignmentEntity.assignmentUid, + labelingAssignmentEntity.inferenceGeomUid, + labelingAssignmentEntity.analUid)) + .from(labelingAssignmentEntity) + .where( + labelingAssignmentEntity.workState.in(LabelState.SKIP.getId(), LabelState.DONE.getId()), + labelingAssignmentEntity.inspectorUid.isNull(), + isYesterday) + .orderBy( + labelingAssignmentEntity.analUid.asc(), + labelingAssignmentEntity.assignGroupId.asc(), + labelingAssignmentEntity.inferenceGeomUid.asc()) + .fetch(); + } + + /** + * 해당 회차에 라벨링 할당받은 검수자별 완료 건수 count(), 완료한 게 적은 순으로 해야 일이 한 사람에게 몰리지 않음 + * + * @param analUid + * @return + */ + @Override + public List findInspectorPendingByRound(Long analUid) { + return queryFactory + .select( + Projections.constructor( + InspectorPendingDto.class, + labelingInspectorEntity.inspectorUid, + labelingAssignmentEntity.assignmentUid.count())) + .from(labelingInspectorEntity) + .leftJoin(labelingAssignmentEntity) + .on( + labelingInspectorEntity.inspectorUid.eq(labelingAssignmentEntity.inspectorUid), + labelingAssignmentEntity.inspectState.in( + InspectState.EXCEPT.getId(), InspectState.COMPLETE.getId())) + .where(labelingInspectorEntity.analUid.eq(analUid)) + .groupBy(labelingInspectorEntity.inspectorUid) + .orderBy(labelingAssignmentEntity.assignmentUid.count().asc()) + .fetch(); + } + + /** + * 실시간 분배용 1건 update + * + * @param assignmentUid + * @param reviewerId + */ + @Override + public void assignReviewer(UUID assignmentUid, String reviewerId) { + queryFactory + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, reviewerId) + .set(labelingAssignmentEntity.inspectState, InspectState.UNCONFIRM.getId()) + .where(labelingAssignmentEntity.assignmentUid.eq(assignmentUid)) + .execute(); + } + + /** + * 배치용 여러 건 update + * + * @param assignmentUids + * @param reviewerId + */ + @Override + public void assignReviewerBatch(List assignmentUids, String reviewerId) { + queryFactory + .update(labelingAssignmentEntity) + .set(labelingAssignmentEntity.inspectorUid, reviewerId) + .set(labelingAssignmentEntity.inspectState, InspectState.UNCONFIRM.getId()) + .where(labelingAssignmentEntity.assignmentUid.in(assignmentUids)) + .execute(); + } + + @Override + public Tasks findAssignmentTask(String assignmentUid) { + return queryFactory + .select( + Projections.constructor( + Tasks.class, + labelingAssignmentEntity.assignmentUid, + labelingAssignmentEntity.inferenceGeomUid, + labelingAssignmentEntity.analUid)) + .from(labelingAssignmentEntity) + .where(labelingAssignmentEntity.assignmentUid.eq(UUID.fromString(assignmentUid))) + .fetchOne(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java index 89a60b5d..94358faf 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/trainingdata/TrainingDataReviewRepositoryImpl.java @@ -5,6 +5,7 @@ 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 static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -442,6 +443,26 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport // COG URL 조회 실패 시 빈 문자열 유지 } + // 4-1. 라벨러 성명 조회 (worker_uid로 tb_member의 name 조회) + String workerName = ""; + try { + if (assignment.toDto().getWorkerUid() != null + && !assignment.toDto().getWorkerUid().isEmpty()) { + workerName = + queryFactory + .select(memberEntity.name) + .from(memberEntity) + .where(memberEntity.userId.eq(assignment.toDto().getWorkerUid())) + .fetchFirst(); + if (workerName == null) { + workerName = ""; + } + } + } catch (Exception e) { + System.err.println("Worker name retrieval error: " + e.getMessage()); + // 라벨러 성명 조회 실패 시 빈 문자열 유지 + } + // 5. DTO 생성 var changeDetectionInfo = ChangeDetectionInfo.builder() @@ -492,6 +513,7 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() != null ? mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() : 0L) + .workerName(workerName) .build(); var inspectionResultInfo = diff --git a/src/main/java/com/kamco/cd/kamcoback/scheduler/dto/TrainingDataReviewJobDto.java b/src/main/java/com/kamco/cd/kamcoback/scheduler/dto/TrainingDataReviewJobDto.java new file mode 100644 index 00000000..343d9d4d --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/scheduler/dto/TrainingDataReviewJobDto.java @@ -0,0 +1,31 @@ +package com.kamco.cd.kamcoback.scheduler.dto; + +import java.util.UUID; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +public class TrainingDataReviewJobDto { + + @Getter + @Setter + @RequiredArgsConstructor + @AllArgsConstructor + public static class Tasks { + + private UUID assignmentUid; + private Long inferenceUid; + private Long analUid; + } + + @Getter + @Setter + @RequiredArgsConstructor + @AllArgsConstructor + public static class InspectorPendingDto { + + String inspectorUid; + Long pendingCount; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/scheduler/service/MapSheetInferenceJobService.java b/src/main/java/com/kamco/cd/kamcoback/scheduler/service/MapSheetInferenceJobService.java index 791038bf..bc4e1cf7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/scheduler/service/MapSheetInferenceJobService.java +++ b/src/main/java/com/kamco/cd/kamcoback/scheduler/service/MapSheetInferenceJobService.java @@ -237,8 +237,10 @@ public class MapSheetInferenceJobService { String mapIds = sb.toString(); String batchId = sheet.getM1BatchId() + "," + sheet.getM2BatchId() + "," + sheet.getM3BatchId(); + // uid 기준 도엽별 shp, geojson 파일 생성 externalJarRunner.run(jarPath, batchId, inferenceId, mapIds); + // uid 기준 merge shp, geojson 파일 생성 externalJarRunner.run(jarPath, batchId, inferenceId, ""); } diff --git a/src/main/java/com/kamco/cd/kamcoback/scheduler/service/TrainingDataReviewJobService.java b/src/main/java/com/kamco/cd/kamcoback/scheduler/service/TrainingDataReviewJobService.java new file mode 100644 index 00000000..25bf791b --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/scheduler/service/TrainingDataReviewJobService.java @@ -0,0 +1,131 @@ +package com.kamco.cd.kamcoback.scheduler.service; + +import com.kamco.cd.kamcoback.postgres.core.TrainingDataReviewJobCoreService; +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; +import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class TrainingDataReviewJobService { + + private final TrainingDataReviewJobCoreService trainingDataReviewJobCoreService; + + @Value("${spring.profiles.active}") + private String profile; + + private boolean isLocalProfile() { + return "local".equalsIgnoreCase(profile); + } + + @Transactional + @Scheduled(cron = "0 0 0 * * *") + public void assignReviewerYesterdayLabelComplete() { + + if (isLocalProfile()) { + return; + } + + try { + List tasks = trainingDataReviewJobCoreService.findCompletedYesterdayUnassigned(); + + if (tasks.isEmpty()) { + return; + } + + // 회차별로 그룹핑 + Map> taskByRound = + tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid)); + + // 회차별 분배 + for (Map.Entry> entry : taskByRound.entrySet()) { + Long analUid = entry.getKey(); + List analTasks = entry.getValue(); + + // pending 계산 + List pendings = + trainingDataReviewJobCoreService.findInspectorPendingByRound(analUid); + + if (pendings.isEmpty()) { + continue; + } + + List reviewerIds = + pendings.stream().map(InspectorPendingDto::getInspectorUid).toList(); + + // Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현 + trainingDataReviewJobCoreService.lockInspectors(analUid, reviewerIds); + + // 균등 분배 + Map> assignMap = distributeByLeastPending(analTasks, reviewerIds); + + // reviewer별 batch update + assignMap.forEach( + (reviewerId, assignedTasks) -> { + if (assignedTasks.isEmpty()) { + return; + } + + List assignmentUids = + assignedTasks.stream().map(Tasks::getAssignmentUid).toList(); + + trainingDataReviewJobCoreService.assignReviewerBatch(assignmentUids, reviewerId); + }); + } + } catch (Exception e) { + log.error("배치 처리 중 예외", e); + } + } + + private Map> distributeByLeastPending( + List tasks, List reviewerIds) { + Map> result = new LinkedHashMap<>(); + + // 순서 유지 중요 (ASC 정렬된 상태) + for (String reviewerId : reviewerIds) { + result.put(reviewerId, new ArrayList<>()); + } + + int reviewerCount = reviewerIds.size(); + + for (int i = 0; i < tasks.size(); i++) { + String reviewerId = reviewerIds.get(i % reviewerCount); + result.get(reviewerId).add(tasks.get(i)); + } + + return result; + } + + // 라벨러 완료,SKIP 시 호출 + @Transactional + public void assignRealtime(String assignmentUid) { + Tasks task = trainingDataReviewJobCoreService.findAssignmentTask(assignmentUid); + Long analUid = task.getAnalUid(); + + // pending 계산 + List pendings = + trainingDataReviewJobCoreService.findInspectorPendingByRound(analUid); + + if (pendings.isEmpty()) { + return; + } + + List order = pendings.stream().map(InspectorPendingDto::getInspectorUid).toList(); + + trainingDataReviewJobCoreService.lockInspectors(analUid, order); + + trainingDataReviewJobCoreService.assignReviewer(task.getAssignmentUid(), order.getFirst()); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataLabelApiController.java b/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataLabelApiController.java index a1948b18..4139bccc 100644 --- a/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataLabelApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataLabelApiController.java @@ -88,9 +88,9 @@ public class TrainingDataLabelApiController { @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PostMapping - public ApiResponseDto saveLabelingFeature( + public ApiResponseDto saveLabelingFeature( @RequestBody TrainingDataLabelDto.LabelFeatureRequest request) { - return ApiResponseDto.okObject(trainingDataLabelService.saveLabelingFeature(request)); + return ApiResponseDto.ok(trainingDataLabelService.saveLabelingFeature(request)); } @Operation(summary = "작업 통계 조회", description = "라벨러의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)") @@ -376,10 +376,29 @@ public class TrainingDataLabelApiController { "type": "Polygon", "coordinates": [ [ - [126.663, 34.588], - [126.662, 34.587], - [126.664, 34.589], - [126.663, 34.588] + [255968.87499999875, 522096.0392135622], + [255969.3749999955, 522097.7892135601], + [255973.8750000003, 522103.53921356186], + [255976.62499999997, 522108.53921356145], + [255978.9607864372, 522110.87500000006], + [255980.7107864374, 522111.624999999], + [255982.53921356448, 522111.6250000003], + [255988.37499999587, 522109.87499999726], + [255988.37499999927, 522108.28921356244], + [255990.12499999776, 522106.5392135628], + [255990.87499999846, 522105.039213567], + [255990.87500000012, 522102.4607864364], + [255990.12499999718, 522100.4607864384], + [255988.37499999863, 522097.46078643657], + [255988.37499999968, 522096.46078643645], + [255983.28921356171, 522092.8749999984], + [255979.5392135627, 522088.37499999907], + [255978.53921355822, 522087.8750000016], + [255974.46078643817, 522087.874999998], + [255971.21078643805, 522088.87499999977], + [255969.87500000134, 522090.21078643604], + [255968.87499999907, 522092.21078643575], + [255968.87499999875, 522096.0392135622] ] ] }, @@ -408,10 +427,16 @@ public class TrainingDataLabelApiController { "type": "Polygon", "coordinates": [ [ - [126.663, 34.588], - [126.662, 34.587], - [126.664, 34.589], - [126.663, 34.588] + [168526.71078643727, 544547.3749999998], + [168527.71078643794, 544548.3749999991], + [168530.28921356675, 544548.6249999998], + [168538.53921356046, 544547.1250000003], + [168547.28921356154, 544547.374999999], + [168549.53921357248, 544545.1250000008], + [168526.9607864362, 544544.6249999998], + [168525.87500000178, 544545.710786436], + [168525.87499999133, 544547.3749999998], + [168526.71078643727, 544547.3749999998] ] ] }, @@ -426,10 +451,22 @@ public class TrainingDataLabelApiController { "type": "Polygon", "coordinates": [ [ - [126.665, 34.590], - [126.664, 34.589], - [126.666, 34.591], - [126.665, 34.590] + [321550.124999999, 399476.9607864386], + [321550.12500000146, 399480.53921356204], + [321551.96078643796, 399482.37499999895], + [321553.46078643616, 399483.12499999907], + [321558.21078643785, 399484.62500000035], + [321560.96078643884, 399486.1250000005], + [321563.78921356366, 399486.1249999994], + [321565.62500000204, 399484.28921355924], + [321565.87499999726, 399479.7107864349], + [321565.37500000506, 399478.71078644204], + [321562.0392135627, 399476.12499999604], + [321559.0392135677, 399474.62499999924], + [321556.0392135648, 399473.6249999991], + [321552.4607864374, 399473.6249999991], + [321550.8750000004, 399474.96078643366], + [321550.124999999, 399476.9607864386] ] ] }, @@ -444,10 +481,29 @@ public class TrainingDataLabelApiController { "type": "Polygon", "coordinates": [ [ - [126.667, 34.592], - [126.666, 34.591], - [126.668, 34.593], - [126.667, 34.592] + [386684.62499999895, 310943.28921356204], + [386684.87499999773, 310944.7892135619], + [386686.87500000314, 310949.03921356524], + [386689.71078643756, 310952.37499999907], + [386692.5392135586, 310952.6249999989], + [386694.0392135624, 310951.87500000035], + [386697.28921356064, 310951.87500000035], + [386700.53921356215, 310948.62499999854], + [386709.78921356826, 310946.37499999977], + [386715.0392135615, 310942.87500000285], + [386717.8750000029, 310939.2892135617], + [386718.3750000007, 310937.28921356343], + [386718.124999998, 310933.71078643954], + [386716.87500000326, 310931.4607864389], + [386712.7892135609, 310927.12499999825], + [386708.4607864385, 310927.124999999], + [386699.2107864381, 310931.3749999989], + [386694.9607864365, 310932.6249999988], + [386692.21078644064, 310932.8750000003], + [386686.4607864376, 310935.12499999994], + [386685.3750000027, 310936.2107864344], + [386684.3750000007, 310938.71078643826], + [386684.62499999895, 310943.28921356204] ] ] }, diff --git a/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataReviewApiController.java b/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataReviewApiController.java index 376a55a1..18ead37a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataReviewApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/trainingdata/TrainingDataReviewApiController.java @@ -3,6 +3,7 @@ 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.scheduler.service.TrainingDataReviewJobService; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto; @@ -31,6 +32,7 @@ import org.springframework.web.bind.annotation.RestController; public class TrainingDataReviewApiController { private final TrainingDataReviewService trainingDataReviewService; + private final TrainingDataReviewJobService trainingDataReviewJobService; @Operation(summary = "목록 조회", description = "검수 할당 목록 조회") @ApiResponses( @@ -88,9 +90,9 @@ public class TrainingDataReviewApiController { @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PostMapping - public ApiResponseDto saveReviewFeature( + public ApiResponseDto saveReviewFeature( @RequestBody TrainingDataReviewDto.GeoFeatureRequest request) { - return ApiResponseDto.okObject(trainingDataReviewService.saveReviewFeature(request)); + return ApiResponseDto.ok(trainingDataReviewService.saveReviewFeature(request)); } @Operation(summary = "작업 통계 조회", description = "검수자의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)") @@ -376,10 +378,29 @@ public class TrainingDataReviewApiController { "type": "Polygon", "coordinates": [ [ - [126.663, 34.588], - [126.662, 34.587], - [126.664, 34.589], - [126.663, 34.588] + [255968.87499999875, 522096.0392135622], + [255969.3749999955, 522097.7892135601], + [255973.8750000003, 522103.53921356186], + [255976.62499999997, 522108.53921356145], + [255978.9607864372, 522110.87500000006], + [255980.7107864374, 522111.624999999], + [255982.53921356448, 522111.6250000003], + [255988.37499999587, 522109.87499999726], + [255988.37499999927, 522108.28921356244], + [255990.12499999776, 522106.5392135628], + [255990.87499999846, 522105.039213567], + [255990.87500000012, 522102.4607864364], + [255990.12499999718, 522100.4607864384], + [255988.37499999863, 522097.46078643657], + [255988.37499999968, 522096.46078643645], + [255983.28921356171, 522092.8749999984], + [255979.5392135627, 522088.37499999907], + [255978.53921355822, 522087.8750000016], + [255974.46078643817, 522087.874999998], + [255971.21078643805, 522088.87499999977], + [255969.87500000134, 522090.21078643604], + [255968.87499999907, 522092.21078643575], + [255968.87499999875, 522096.0392135622] ] ] }, @@ -408,10 +429,16 @@ public class TrainingDataReviewApiController { "type": "Polygon", "coordinates": [ [ - [126.663, 34.588], - [126.662, 34.587], - [126.664, 34.589], - [126.663, 34.588] + [168526.71078643727, 544547.3749999998], + [168527.71078643794, 544548.3749999991], + [168530.28921356675, 544548.6249999998], + [168538.53921356046, 544547.1250000003], + [168547.28921356154, 544547.374999999], + [168549.53921357248, 544545.1250000008], + [168526.9607864362, 544544.6249999998], + [168525.87500000178, 544545.710786436], + [168525.87499999133, 544547.3749999998], + [168526.71078643727, 544547.3749999998] ] ] }, @@ -426,10 +453,22 @@ public class TrainingDataReviewApiController { "type": "Polygon", "coordinates": [ [ - [126.665, 34.590], - [126.664, 34.589], - [126.666, 34.591], - [126.665, 34.590] + [321550.124999999, 399476.9607864386], + [321550.12500000146, 399480.53921356204], + [321551.96078643796, 399482.37499999895], + [321553.46078643616, 399483.12499999907], + [321558.21078643785, 399484.62500000035], + [321560.96078643884, 399486.1250000005], + [321563.78921356366, 399486.1249999994], + [321565.62500000204, 399484.28921355924], + [321565.87499999726, 399479.7107864349], + [321565.37500000506, 399478.71078644204], + [321562.0392135627, 399476.12499999604], + [321559.0392135677, 399474.62499999924], + [321556.0392135648, 399473.6249999991], + [321552.4607864374, 399473.6249999991], + [321550.8750000004, 399474.96078643366], + [321550.124999999, 399476.9607864386] ] ] }, @@ -444,10 +483,29 @@ public class TrainingDataReviewApiController { "type": "Polygon", "coordinates": [ [ - [126.667, 34.592], - [126.666, 34.591], - [126.668, 34.593], - [126.667, 34.592] + [386684.62499999895, 310943.28921356204], + [386684.87499999773, 310944.7892135619], + [386686.87500000314, 310949.03921356524], + [386689.71078643756, 310952.37499999907], + [386692.5392135586, 310952.6249999989], + [386694.0392135624, 310951.87500000035], + [386697.28921356064, 310951.87500000035], + [386700.53921356215, 310948.62499999854], + [386709.78921356826, 310946.37499999977], + [386715.0392135615, 310942.87500000285], + [386717.8750000029, 310939.2892135617], + [386718.3750000007, 310937.28921356343], + [386718.124999998, 310933.71078643954], + [386716.87500000326, 310931.4607864389], + [386712.7892135609, 310927.12499999825], + [386708.4607864385, 310927.124999999], + [386699.2107864381, 310931.3749999989], + [386694.9607864365, 310932.6249999988], + [386692.21078644064, 310932.8750000003], + [386686.4607864376, 310935.12499999994], + [386685.3750000027, 310936.2107864344], + [386684.3750000007, 310938.71078643826], + [386684.62499999895, 310943.28921356204] ] ] }, @@ -498,4 +556,11 @@ public class TrainingDataReviewApiController { return ApiResponseDto.ok( trainingDataReviewService.getCogImageUrl(mapSheetNum, beforeYear, afterYear)); } + + @Hidden + @GetMapping("/run-schedule") + public ApiResponseDto runTrainingReviewSchedule() { + trainingDataReviewJobService.assignReviewerYesterdayLabelComplete(); + return ApiResponseDto.ok(null); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/trainingdata/dto/TrainingDataReviewDto.java b/src/main/java/com/kamco/cd/kamcoback/trainingdata/dto/TrainingDataReviewDto.java index 35176897..9eb914c4 100644 --- a/src/main/java/com/kamco/cd/kamcoback/trainingdata/dto/TrainingDataReviewDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/trainingdata/dto/TrainingDataReviewDto.java @@ -340,6 +340,9 @@ public class TrainingDataReviewDto { @Schema(description = "도엽번호 (map_sheet_num)", example = "34602057") private Long mapSheetNum; + + @Schema(description = "라벨러 성명 (기존 작업자)", example = "홍길동") + private String workerName; } @Schema(name = "ClassificationInfo", description = "분류정보") diff --git a/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataLabelService.java b/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataLabelService.java index 6b5696da..b7af6862 100644 --- a/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataLabelService.java +++ b/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataLabelService.java @@ -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.scheduler.service.TrainingDataReviewJobService; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes; @@ -22,9 +23,13 @@ import org.springframework.stereotype.Service; public class TrainingDataLabelService { private final TrainingDataLabelCoreService trainingDataLabelCoreService; + private final TrainingDataReviewJobService trainingDataReviewJobService; - public TrainingDataLabelService(TrainingDataLabelCoreService trainingDataLabelCoreService) { + public TrainingDataLabelService( + TrainingDataLabelCoreService trainingDataLabelCoreService, + TrainingDataReviewJobService trainingDataReviewJobService) { this.trainingDataLabelCoreService = trainingDataLabelCoreService; + this.trainingDataReviewJobService = trainingDataReviewJobService; } public Page findLabelingAssignedList(searchReq searchReq, String userId) { @@ -36,7 +41,7 @@ public class TrainingDataLabelService { } @Transactional - public ResponseObj saveLabelingFeature(LabelFeatureRequest request) { + public String saveLabelingFeature(LabelFeatureRequest request) { String status = ""; String assignmentUid = request.getAssignmentUid(); Long inferenceGeomUid = @@ -52,7 +57,11 @@ public class TrainingDataLabelService { trainingDataLabelCoreService.updateLabelingPolygonClass( inferenceGeomUid, request.getGeometry(), request.getProperties(), status); } - return new ResponseObj(ApiResponseCode.OK, "저장되었습니다."); + + // 라벨링 완료하면 실시간 검수 할당 (1건) + trainingDataReviewJobService.assignRealtime(assignmentUid); + + return status; } /** diff --git a/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataReviewService.java b/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataReviewService.java index e7916847..3343bdb6 100644 --- a/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataReviewService.java +++ b/src/main/java/com/kamco/cd/kamcoback/trainingdata/service/TrainingDataReviewService.java @@ -36,7 +36,7 @@ public class TrainingDataReviewService { } @Transactional - public ResponseObj saveReviewFeature(GeoFeatureRequest request) { + public String saveReviewFeature(GeoFeatureRequest request) { String status = ""; String operatorUid = request.getOperatorUid(); Long inferenceGeomUid = trainingDataReviewCoreService.findReviewOperatorGeoUid(operatorUid); @@ -52,7 +52,7 @@ public class TrainingDataReviewService { trainingDataReviewCoreService.updateReviewPolygonClass( inferenceGeomUid, request.getGeometry(), request.getProperties(), status); } - return new ResponseObj(ApiResponseCode.OK, "저장되었습니다."); + return status; } /** diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 39297fdf..2974a3ad 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -100,4 +100,4 @@ inference: url: http://10.100.0.11:8000/jobs batch-url: http://10.100.0.11:8000/batches geojson-dir: /kamco-nfs/requests/ - jar-path: jar/makeshp-1.0.0.jar + jar-path: /kamco-nfs/dataset/makeshp-1.0.0.jar