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

This commit is contained in:
Moon
2026-01-19 16:19:00 +09:00
26 changed files with 705 additions and 78 deletions

Binary file not shown.

View File

@@ -20,6 +20,10 @@ public enum DetectionClassification {
TUMULUS("tumulus", "토분(무덤)", 120), TUMULUS("tumulus", "토분(무덤)", 120),
WASTE("waste", "폐기물", 130), WASTE("waste", "폐기물", 130),
WATER("water", "", 140), WATER("water", "", 140),
CONSTRUCTION("construction", "건설", 150),
NDC("NDC", "미분류", 160),
RICE("rice", "", 170),
WOOD("wood", "산림", 180),
ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other) ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other)
private final String id; private final String id;

View File

@@ -1,7 +1,5 @@
package com.kamco.cd.kamcoback.common.service; 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.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -17,28 +15,8 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ExternalJarRunner { public class ExternalJarRunner {
private final InferenceResultCoreService inferenceResultCoreService;
public void run(String jarPath, String batchIds, String inferenceId, String mapIds) { public void run(String jarPath, String batchIds, String inferenceId, String mapIds) {
List<Long> batch = new ArrayList<>();
batch.add(285L);
batch.add(286L);
batch.add(287L);
List<InferenceResultsTestingDto.ShpDto> 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(); StringBuilder out = new StringBuilder();
try { try {

View File

@@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServerHttpResponse;
@@ -45,7 +46,8 @@ public class ApiResponseAdvice implements ResponseBodyAdvice<Object> {
public boolean supports( public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// ApiResponseDto를 반환하는 경우에만 적용 // ApiResponseDto를 반환하는 경우에만 적용
return returnType.getParameterType().equals(ApiResponseDto.class); return returnType.getParameterType().equals(ApiResponseDto.class)
|| returnType.getParameterType().equals(ResponseEntity.class);
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
package com.kamco.cd.kamcoback.inference; 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.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto; import com.kamco.cd.kamcoback.inference.dto.InferenceDetailDto;
import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto; 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.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.data.domain.Page; 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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriUtils;
@Tag(name = "추론관리", description = "추론관리 API") @Tag(name = "추론관리", description = "추론관리 API")
@RequestMapping("/api/inference") @RequestMapping("/api/inference")
@@ -388,4 +398,47 @@ public class InferenceResultApiController {
inferenceResultService.getInferenceGeomList(uuid, searchGeoReq); inferenceResultService.getInferenceGeomList(uuid, searchGeoReq);
return ApiResponseDto.ok(geomList); 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<Resource> 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);
}
} }

View File

@@ -622,4 +622,11 @@ public class InferenceResultDto {
private Integer completedJobs; private Integer completedJobs;
private Integer failedJobs; private Integer failedJobs;
} }
@Getter
@Setter
public static class InferenceLearnDto {
private String uid;
}
} }

View File

@@ -5,6 +5,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
/** AI API 추론 실행 DTO */
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor

View File

@@ -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.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.InferenceLearnDto;
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.inference.dto.InferenceResultDto.MapSheetNumDto; 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.MapSheetMngCoreService;
import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService; import com.kamco.cd.kamcoback.postgres.core.ModelMngCoreService;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.nio.file.Path;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -70,6 +72,9 @@ public class InferenceResultService {
@Value("${inference.batch-url}") @Value("${inference.batch-url}")
private String batchUrl; private String batchUrl;
@Value("${file.dataset-dir}")
private String datasetDir;
@Value("${spring.profiles.active}") @Value("${spring.profiles.active}")
private String profile; private String profile;
@@ -536,4 +541,17 @@ public class InferenceResultService {
Long learnId = inferenceResultCoreService.getInferenceLearnIdByUuid(dto.getUuid()); Long learnId = inferenceResultCoreService.getInferenceLearnIdByUuid(dto.getUuid());
inferenceResultCoreService.upsertGeomData(learnId); 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");
}
} }

View File

@@ -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.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.InferenceLearnDto;
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.inference.dto.InferenceResultDto.ResultList; import com.kamco.cd.kamcoback.inference.dto.InferenceResultDto.ResultList;
@@ -461,4 +462,20 @@ public class InferenceResultCoreService {
inferenceResultsTetingRepository.getInferenceResultList(batchIds); inferenceResultsTetingRepository.getInferenceResultList(batchIds);
return list.stream().map(InferenceResultsTestingDto.ShpDto::fromEntity).toList(); 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;
}
} }

View File

@@ -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<Tasks> findCompletedYesterdayUnassigned() {
return trainingDataReviewJobRepository.findCompletedYesterdayUnassigned();
}
public void assignReviewer(UUID assignmentUid, String reviewerId) {
trainingDataReviewJobRepository.assignReviewer(assignmentUid, reviewerId);
}
public void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId) {
trainingDataReviewJobRepository.assignReviewerBatch(assignmentUids, reviewerId);
}
public Tasks findAssignmentTask(String assignmentUid) {
return trainingDataReviewJobRepository.findAssignmentTask(assignmentUid);
}
public List<InspectorPendingDto> findInspectorPendingByRound(Long analUid) {
return trainingDataReviewJobRepository.findInspectorPendingByRound(analUid);
}
public void lockInspectors(Long analUid, List<String> reviewerIds) {
trainingDataReviewJobRepository.lockInspectors(analUid, reviewerIds);
}
}

View File

@@ -1,7 +1,9 @@
package com.kamco.cd.kamcoback.postgres.repository.Inference; package com.kamco.cd.kamcoback.postgres.repository.Inference;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public interface InferenceResultRepositoryCustom { public interface InferenceResultRepositoryCustom {
@@ -27,4 +29,6 @@ public interface InferenceResultRepositoryCustom {
List<MapSheetAnalDataInferenceGeomEntity> findGeomEntitiesByDataUid(Long dataUid, int limit); List<MapSheetAnalDataInferenceGeomEntity> findGeomEntitiesByDataUid(Long dataUid, int limit);
Long getInferenceLearnIdByUuid(UUID uuid); Long getInferenceLearnIdByUuid(UUID uuid);
public Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid);
} }

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.postgres.repository.Inference;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity; 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.MapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetLearnEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity; import com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity;
@@ -10,6 +11,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@@ -468,4 +470,14 @@ public class InferenceResultRepositoryImpl implements InferenceResultRepositoryC
.where(mapSheetLearnEntity.uuid.eq(uuid)) .where(mapSheetLearnEntity.uuid.eq(uuid))
.fetchOne(); .fetchOne();
} }
@Override
public Optional<MapSheetLearnEntity> getInferenceUid(UUID uuid) {
return Optional.ofNullable(
queryFactory
.select(mapSheetLearnEntity)
.from(mapSheetLearnEntity)
.where(mapSheetLearnEntity.uuid.eq(uuid))
.fetchOne());
}
} }

View File

@@ -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.QGpuMetricEntity.gpuMetricEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity; 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.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalInferenceEntity.mapSheetAnalInferenceEntity; 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.QMapSheetAnalSttcEntity.mapSheetAnalSttcEntity;
@@ -339,7 +340,7 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
.select( .select(
Projections.constructor( Projections.constructor(
Dashboard.class, Dashboard.class,
mapSheetAnalSttcEntity.id.classAfterCd, mapSheetAnalSttcEntity.id.classAfterCd.toUpperCase(),
mapSheetAnalSttcEntity.classAfterCnt.sum())) mapSheetAnalSttcEntity.classAfterCnt.sum()))
.from(mapSheetAnalInferenceEntity) .from(mapSheetAnalInferenceEntity)
.innerJoin(mapSheetAnalSttcEntity) .innerJoin(mapSheetAnalSttcEntity)
@@ -419,13 +420,10 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
// mapSheetAnalDataInferenceGeomEntity.geomCenter) // mapSheetAnalDataInferenceGeomEntity.geomCenter)
)) ))
.from(mapSheetAnalInferenceEntity) .from(mapSheetAnalInferenceEntity)
.join(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalDataInferenceEntity.analUid.eq(mapSheetAnalInferenceEntity.id))
.join(mapSheetAnalDataInferenceGeomEntity) .join(mapSheetAnalDataInferenceGeomEntity)
.on( .on(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id))
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(
mapSheetAnalInferenceEntity.compareYyyy),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(
mapSheetAnalInferenceEntity.targetYyyy),
mapSheetAnalDataInferenceGeomEntity.stage.eq(mapSheetAnalInferenceEntity.stage))
.join(mapInkx5kEntity) .join(mapInkx5kEntity)
.on( .on(
mapSheetAnalDataInferenceGeomEntity.mapSheetNum.eq( mapSheetAnalDataInferenceGeomEntity.mapSheetNum.eq(
@@ -440,13 +438,10 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
queryFactory queryFactory
.select(mapSheetAnalDataInferenceGeomEntity.geoUid) .select(mapSheetAnalDataInferenceGeomEntity.geoUid)
.from(mapSheetAnalInferenceEntity) .from(mapSheetAnalInferenceEntity)
.join(mapSheetAnalDataInferenceEntity)
.on(mapSheetAnalDataInferenceEntity.analUid.eq(mapSheetAnalInferenceEntity.id))
.join(mapSheetAnalDataInferenceGeomEntity) .join(mapSheetAnalDataInferenceGeomEntity)
.on( .on(mapSheetAnalDataInferenceGeomEntity.dataUid.eq(mapSheetAnalDataInferenceEntity.id))
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(
mapSheetAnalInferenceEntity.compareYyyy),
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(
mapSheetAnalInferenceEntity.targetYyyy),
mapSheetAnalDataInferenceGeomEntity.stage.eq(mapSheetAnalInferenceEntity.stage))
.join(mapInkx5kEntity) .join(mapInkx5kEntity)
.on( .on(
mapSheetAnalDataInferenceGeomEntity.mapSheetNum.eq( mapSheetAnalDataInferenceGeomEntity.mapSheetNum.eq(

View File

@@ -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<LabelingInspectorEntity, UUID>, TrainingDataReviewJobRepositoryCustom {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query(
"""
select r
from LabelingInspectorEntity r
where r.analUid = :analUid
and r.inspectorUid in :inspectorUids
""")
List<LabelingInspectorEntity> lockInspectors(Long analUid, List<String> inspectorUids);
}

View File

@@ -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<Tasks> findCompletedYesterdayUnassigned();
List<InspectorPendingDto> findInspectorPendingByRound(Long analUid);
void assignReviewer(UUID assignmentUid, String reviewerId);
void assignReviewerBatch(List<UUID> assignmentUids, String reviewerId);
Tasks findAssignmentTask(String assignmentUid);
}

View File

@@ -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<Tasks> 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<InspectorPendingDto> 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<UUID> 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();
}
}

View File

@@ -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.QMapInkx5kEntity.mapInkx5kEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity; 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.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMemberEntity.memberEntity;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -442,6 +443,26 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
// COG URL 조회 실패 시 빈 문자열 유지 // 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 생성 // 5. DTO 생성
var changeDetectionInfo = var changeDetectionInfo =
ChangeDetectionInfo.builder() ChangeDetectionInfo.builder()
@@ -492,6 +513,7 @@ public class TrainingDataReviewRepositoryImpl extends QuerydslRepositorySupport
mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() != null mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() != null
? mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum() ? mapSheetAnalDataInferenceGeomEntityEntity.getMapSheetNum()
: 0L) : 0L)
.workerName(workerName)
.build(); .build();
var inspectionResultInfo = var inspectionResultInfo =

View File

@@ -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;
}
}

View File

@@ -237,8 +237,10 @@ public class MapSheetInferenceJobService {
String mapIds = sb.toString(); String mapIds = sb.toString();
String batchId = sheet.getM1BatchId() + "," + sheet.getM2BatchId() + "," + sheet.getM3BatchId(); String batchId = sheet.getM1BatchId() + "," + sheet.getM2BatchId() + "," + sheet.getM3BatchId();
// uid 기준 도엽별 shp, geojson 파일 생성
externalJarRunner.run(jarPath, batchId, inferenceId, mapIds); externalJarRunner.run(jarPath, batchId, inferenceId, mapIds);
// uid 기준 merge shp, geojson 파일 생성
externalJarRunner.run(jarPath, batchId, inferenceId, ""); externalJarRunner.run(jarPath, batchId, inferenceId, "");
} }

View File

@@ -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> tasks = trainingDataReviewJobCoreService.findCompletedYesterdayUnassigned();
if (tasks.isEmpty()) {
return;
}
// 회차별로 그룹핑
Map<Long, List<Tasks>> taskByRound =
tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid));
// 회차별 분배
for (Map.Entry<Long, List<Tasks>> entry : taskByRound.entrySet()) {
Long analUid = entry.getKey();
List<Tasks> analTasks = entry.getValue();
// pending 계산
List<InspectorPendingDto> pendings =
trainingDataReviewJobCoreService.findInspectorPendingByRound(analUid);
if (pendings.isEmpty()) {
continue;
}
List<String> reviewerIds =
pendings.stream().map(InspectorPendingDto::getInspectorUid).toList();
// Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현
trainingDataReviewJobCoreService.lockInspectors(analUid, reviewerIds);
// 균등 분배
Map<String, List<Tasks>> assignMap = distributeByLeastPending(analTasks, reviewerIds);
// reviewer별 batch update
assignMap.forEach(
(reviewerId, assignedTasks) -> {
if (assignedTasks.isEmpty()) {
return;
}
List<UUID> assignmentUids =
assignedTasks.stream().map(Tasks::getAssignmentUid).toList();
trainingDataReviewJobCoreService.assignReviewerBatch(assignmentUids, reviewerId);
});
}
} catch (Exception e) {
log.error("배치 처리 중 예외", e);
}
}
private Map<String, List<Tasks>> distributeByLeastPending(
List<Tasks> tasks, List<String> reviewerIds) {
Map<String, List<Tasks>> 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<InspectorPendingDto> pendings =
trainingDataReviewJobCoreService.findInspectorPendingByRound(analUid);
if (pendings.isEmpty()) {
return;
}
List<String> order = pendings.stream().map(InspectorPendingDto::getInspectorUid).toList();
trainingDataReviewJobCoreService.lockInspectors(analUid, order);
trainingDataReviewJobCoreService.assignReviewer(task.getAssignmentUid(), order.getFirst());
}
}

View File

@@ -88,9 +88,9 @@ public class TrainingDataLabelApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping @PostMapping
public ApiResponseDto<ResponseObj> saveLabelingFeature( public ApiResponseDto<String> saveLabelingFeature(
@RequestBody TrainingDataLabelDto.LabelFeatureRequest request) { @RequestBody TrainingDataLabelDto.LabelFeatureRequest request) {
return ApiResponseDto.okObject(trainingDataLabelService.saveLabelingFeature(request)); return ApiResponseDto.ok(trainingDataLabelService.saveLabelingFeature(request));
} }
@Operation(summary = "작업 통계 조회", description = "라벨러의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)") @Operation(summary = "작업 통계 조회", description = "라벨러의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)")
@@ -376,10 +376,29 @@ public class TrainingDataLabelApiController {
"type": "Polygon", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.663, 34.588], [255968.87499999875, 522096.0392135622],
[126.662, 34.587], [255969.3749999955, 522097.7892135601],
[126.664, 34.589], [255973.8750000003, 522103.53921356186],
[126.663, 34.588] [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", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.663, 34.588], [168526.71078643727, 544547.3749999998],
[126.662, 34.587], [168527.71078643794, 544548.3749999991],
[126.664, 34.589], [168530.28921356675, 544548.6249999998],
[126.663, 34.588] [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", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.665, 34.590], [321550.124999999, 399476.9607864386],
[126.664, 34.589], [321550.12500000146, 399480.53921356204],
[126.666, 34.591], [321551.96078643796, 399482.37499999895],
[126.665, 34.590] [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", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.667, 34.592], [386684.62499999895, 310943.28921356204],
[126.666, 34.591], [386684.87499999773, 310944.7892135619],
[126.668, 34.593], [386686.87500000314, 310949.03921356524],
[126.667, 34.592] [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]
] ]
] ]
}, },

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.trainingdata;
import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto; import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj; 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;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewGeometryInfo;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataReviewDto.ReviewListDto;
@@ -31,6 +32,7 @@ import org.springframework.web.bind.annotation.RestController;
public class TrainingDataReviewApiController { public class TrainingDataReviewApiController {
private final TrainingDataReviewService trainingDataReviewService; private final TrainingDataReviewService trainingDataReviewService;
private final TrainingDataReviewJobService trainingDataReviewJobService;
@Operation(summary = "목록 조회", description = "검수 할당 목록 조회") @Operation(summary = "목록 조회", description = "검수 할당 목록 조회")
@ApiResponses( @ApiResponses(
@@ -88,9 +90,9 @@ public class TrainingDataReviewApiController {
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
}) })
@PostMapping @PostMapping
public ApiResponseDto<ResponseObj> saveReviewFeature( public ApiResponseDto<String> saveReviewFeature(
@RequestBody TrainingDataReviewDto.GeoFeatureRequest request) { @RequestBody TrainingDataReviewDto.GeoFeatureRequest request) {
return ApiResponseDto.okObject(trainingDataReviewService.saveReviewFeature(request)); return ApiResponseDto.ok(trainingDataReviewService.saveReviewFeature(request));
} }
@Operation(summary = "작업 통계 조회", description = "검수자의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)") @Operation(summary = "작업 통계 조회", description = "검수자의 작업 현황 통계를 조회합니다. (전체/미작업/Today 건수)")
@@ -376,10 +378,29 @@ public class TrainingDataReviewApiController {
"type": "Polygon", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.663, 34.588], [255968.87499999875, 522096.0392135622],
[126.662, 34.587], [255969.3749999955, 522097.7892135601],
[126.664, 34.589], [255973.8750000003, 522103.53921356186],
[126.663, 34.588] [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", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.663, 34.588], [168526.71078643727, 544547.3749999998],
[126.662, 34.587], [168527.71078643794, 544548.3749999991],
[126.664, 34.589], [168530.28921356675, 544548.6249999998],
[126.663, 34.588] [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", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.665, 34.590], [321550.124999999, 399476.9607864386],
[126.664, 34.589], [321550.12500000146, 399480.53921356204],
[126.666, 34.591], [321551.96078643796, 399482.37499999895],
[126.665, 34.590] [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", "type": "Polygon",
"coordinates": [ "coordinates": [
[ [
[126.667, 34.592], [386684.62499999895, 310943.28921356204],
[126.666, 34.591], [386684.87499999773, 310944.7892135619],
[126.668, 34.593], [386686.87500000314, 310949.03921356524],
[126.667, 34.592] [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( return ApiResponseDto.ok(
trainingDataReviewService.getCogImageUrl(mapSheetNum, beforeYear, afterYear)); trainingDataReviewService.getCogImageUrl(mapSheetNum, beforeYear, afterYear));
} }
@Hidden
@GetMapping("/run-schedule")
public ApiResponseDto<Void> runTrainingReviewSchedule() {
trainingDataReviewJobService.assignReviewerYesterdayLabelComplete();
return ApiResponseDto.ok(null);
}
} }

View File

@@ -340,6 +340,9 @@ public class TrainingDataReviewDto {
@Schema(description = "도엽번호 (map_sheet_num)", example = "34602057") @Schema(description = "도엽번호 (map_sheet_num)", example = "34602057")
private Long mapSheetNum; private Long mapSheetNum;
@Schema(description = "라벨러 성명 (기존 작업자)", example = "홍길동")
private String workerName;
} }
@Schema(name = "ClassificationInfo", description = "분류정보") @Schema(name = "ClassificationInfo", description = "분류정보")

View File

@@ -3,6 +3,7 @@ package com.kamco.cd.kamcoback.trainingdata.service;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode; import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode;
import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj; import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ResponseObj;
import com.kamco.cd.kamcoback.postgres.core.TrainingDataLabelCoreService; 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;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DefaultPaging;
import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes; import com.kamco.cd.kamcoback.trainingdata.dto.TrainingDataLabelDto.DetailRes;
@@ -22,9 +23,13 @@ import org.springframework.stereotype.Service;
public class TrainingDataLabelService { public class TrainingDataLabelService {
private final TrainingDataLabelCoreService trainingDataLabelCoreService; private final TrainingDataLabelCoreService trainingDataLabelCoreService;
private final TrainingDataReviewJobService trainingDataReviewJobService;
public TrainingDataLabelService(TrainingDataLabelCoreService trainingDataLabelCoreService) { public TrainingDataLabelService(
TrainingDataLabelCoreService trainingDataLabelCoreService,
TrainingDataReviewJobService trainingDataReviewJobService) {
this.trainingDataLabelCoreService = trainingDataLabelCoreService; this.trainingDataLabelCoreService = trainingDataLabelCoreService;
this.trainingDataReviewJobService = trainingDataReviewJobService;
} }
public Page<LabelingListDto> findLabelingAssignedList(searchReq searchReq, String userId) { public Page<LabelingListDto> findLabelingAssignedList(searchReq searchReq, String userId) {
@@ -36,7 +41,7 @@ public class TrainingDataLabelService {
} }
@Transactional @Transactional
public ResponseObj saveLabelingFeature(LabelFeatureRequest request) { public String saveLabelingFeature(LabelFeatureRequest request) {
String status = ""; String status = "";
String assignmentUid = request.getAssignmentUid(); String assignmentUid = request.getAssignmentUid();
Long inferenceGeomUid = Long inferenceGeomUid =
@@ -52,7 +57,11 @@ public class TrainingDataLabelService {
trainingDataLabelCoreService.updateLabelingPolygonClass( trainingDataLabelCoreService.updateLabelingPolygonClass(
inferenceGeomUid, request.getGeometry(), request.getProperties(), status); inferenceGeomUid, request.getGeometry(), request.getProperties(), status);
} }
return new ResponseObj(ApiResponseCode.OK, "저장되었습니다.");
// 라벨링 완료하면 실시간 검수 할당 (1건)
trainingDataReviewJobService.assignRealtime(assignmentUid);
return status;
} }
/** /**

View File

@@ -36,7 +36,7 @@ public class TrainingDataReviewService {
} }
@Transactional @Transactional
public ResponseObj saveReviewFeature(GeoFeatureRequest request) { public String saveReviewFeature(GeoFeatureRequest request) {
String status = ""; String status = "";
String operatorUid = request.getOperatorUid(); String operatorUid = request.getOperatorUid();
Long inferenceGeomUid = trainingDataReviewCoreService.findReviewOperatorGeoUid(operatorUid); Long inferenceGeomUid = trainingDataReviewCoreService.findReviewOperatorGeoUid(operatorUid);
@@ -52,7 +52,7 @@ public class TrainingDataReviewService {
trainingDataReviewCoreService.updateReviewPolygonClass( trainingDataReviewCoreService.updateReviewPolygonClass(
inferenceGeomUid, request.getGeometry(), request.getProperties(), status); inferenceGeomUid, request.getGeometry(), request.getProperties(), status);
} }
return new ResponseObj(ApiResponseCode.OK, "저장되었습니다."); return status;
} }
/** /**

View File

@@ -100,4 +100,4 @@ inference:
url: http://10.100.0.11:8000/jobs url: http://10.100.0.11:8000/jobs
batch-url: http://10.100.0.11:8000/batches batch-url: http://10.100.0.11:8000/batches
geojson-dir: /kamco-nfs/requests/ geojson-dir: /kamco-nfs/requests/
jar-path: jar/makeshp-1.0.0.jar jar-path: /kamco-nfs/dataset/makeshp-1.0.0.jar