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

This commit is contained in:
2026-01-23 18:40:12 +09:00
9 changed files with 374 additions and 0 deletions

View File

@@ -1,6 +1,9 @@
package com.kamco.cd.kamcoback.postgres.core; package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.postgres.repository.scheduler.TrainingDataReviewJobRepository; import com.kamco.cd.kamcoback.postgres.repository.scheduler.TrainingDataReviewJobRepository;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalCntInfo;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import java.util.List; import java.util.List;
@@ -41,4 +44,21 @@ public class TrainingDataReviewJobCoreService {
public void updateGeomUidTestState(List<Long> geomUids) { public void updateGeomUidTestState(List<Long> geomUids) {
trainingDataReviewJobRepository.updateGeomUidTestState(geomUids); trainingDataReviewJobRepository.updateGeomUidTestState(geomUids);
} }
public List<CompleteLabelData> findCompletedYesterdayLabelingList(
Long analUid, String mapSheetNum) {
return trainingDataReviewJobRepository.findCompletedYesterdayLabelingList(analUid, mapSheetNum);
}
public List<AnalMapSheetList> findCompletedAnalMapSheetList(Long analUid) {
return trainingDataReviewJobRepository.findCompletedAnalMapSheetList(analUid);
}
public List<AnalCntInfo> findAnalCntInfoList() {
return trainingDataReviewJobRepository.findAnalCntInfoList();
}
public void updateLearnDataGeomFileCreateYn(List<Long> geoUids) {
trainingDataReviewJobRepository.updateLearnDataGeomFileCreateYn(geoUids);
}
} }

View File

@@ -40,4 +40,7 @@ public class MapSheetLearnDataGeomEntity extends CommonDateEntity {
@Column(name = "geom") @Column(name = "geom")
private Geometry geom; private Geometry geom;
@Column(name = "file_create_yn")
private Boolean fileCreateYn;
} }

View File

@@ -1,5 +1,8 @@
package com.kamco.cd.kamcoback.postgres.repository.scheduler; package com.kamco.cd.kamcoback.postgres.repository.scheduler;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalCntInfo;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import java.util.List; import java.util.List;
@@ -18,4 +21,12 @@ public interface TrainingDataReviewJobRepositoryCustom {
Tasks findAssignmentTask(String assignmentUid); Tasks findAssignmentTask(String assignmentUid);
void updateGeomUidTestState(List<Long> geomUids); void updateGeomUidTestState(List<Long> geomUids);
List<CompleteLabelData> findCompletedYesterdayLabelingList(Long analUid, String mapSheetNum);
List<AnalMapSheetList> findCompletedAnalMapSheetList(Long analUid);
List<AnalCntInfo> findAnalCntInfoList();
void updateLearnDataGeomFileCreateYn(List<Long> geoUids);
} }

View File

@@ -3,17 +3,29 @@ 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.QLabelingAssignmentEntity.labelingAssignmentEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity; import static com.kamco.cd.kamcoback.postgres.entity.QLabelingInspectorEntity.labelingInspectorEntity;
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.QMapSheetLearnDataGeomEntity.mapSheetLearnDataGeomEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetLearnEntity.mapSheetLearnEntity;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.InspectState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelMngState;
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState; import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.LabelState;
import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity; import com.kamco.cd.kamcoback.postgres.entity.LabelingAssignmentEntity;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalCntInfo;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData.Properties;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import com.querydsl.core.types.Projections; import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.core.types.dsl.StringExpression; import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
@@ -145,4 +157,132 @@ public class TrainingDataReviewJobRepositoryImpl extends QuerydslRepositorySuppo
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(geomUids)) .where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(geomUids))
.execute(); .execute();
} }
@Override
public List<CompleteLabelData> findCompletedYesterdayLabelingList(
Long analUid, String mapSheetNum) {
ZoneId zoneId = ZoneId.of("Asia/Seoul");
// 오늘 날짜 (시간 없음)
LocalDate today = LocalDate.now(zoneId);
ZonedDateTime end = today.atStartOfDay(zoneId); // 오늘 00:00
return queryFactory
.select(
Projections.constructor(
CompleteLabelData.class,
mapSheetLearnDataGeomEntity.geoUid,
Expressions.stringTemplate("{0}", "Feature").as("type"),
Expressions.stringTemplate("ST_AsGeoJSON({0})", mapSheetLearnDataGeomEntity.geom),
Projections.constructor(
Properties.class,
new CaseBuilder()
.when(mapSheetLearnDataGeomEntity.classAfterCd.in("building", "container"))
.then("M1")
.when(mapSheetLearnDataGeomEntity.classAfterCd.eq("waste"))
.then("M2")
.otherwise("M3"),
mapSheetLearnDataGeomEntity.classBeforeCd,
mapSheetLearnDataGeomEntity.classAfterCd)))
.from(labelingAssignmentEntity)
.leftJoin(mapSheetLearnDataGeomEntity)
.on(labelingAssignmentEntity.inferenceGeomUid.eq(mapSheetLearnDataGeomEntity.geoUid))
.where(
labelingAssignmentEntity.analUid.eq(analUid),
labelingAssignmentEntity.assignGroupId.eq(mapSheetNum),
labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()),
labelingAssignmentEntity.inspectStatDttm.lt(end))
.fetch();
}
@Override
public List<AnalMapSheetList> findCompletedAnalMapSheetList(Long analUid) {
ZoneId zoneId = ZoneId.of("Asia/Seoul");
LocalDate today = LocalDate.now(zoneId);
ZonedDateTime end = today.atStartOfDay(zoneId); // 오늘 00:00
return queryFactory
.select(
Projections.constructor(
AnalMapSheetList.class,
mapSheetAnalInferenceEntity.compareYyyy,
mapSheetAnalInferenceEntity.targetYyyy,
labelingAssignmentEntity.assignGroupId))
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id))
.where(
labelingAssignmentEntity.analUid.eq(analUid),
labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()),
labelingAssignmentEntity.inspectStatDttm.lt(end))
.groupBy(
mapSheetAnalInferenceEntity.compareYyyy,
mapSheetAnalInferenceEntity.targetYyyy,
labelingAssignmentEntity.assignGroupId)
.fetch();
}
@Override
public List<AnalCntInfo> findAnalCntInfoList() {
// 검수 제외(EXCEPT)를 뺀 나머지 cnt
NumberExpression<Long> allCnt =
new CaseBuilder()
.when(
labelingAssignmentEntity
.inspectState
.eq(InspectState.UNCONFIRM.getId())
.or(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()))
.or(labelingAssignmentEntity.inspectState.isNull()))
.then(1L)
.otherwise(0L)
.sum();
// file_cnt
NumberExpression<Long> fileCnt =
new CaseBuilder()
.when(mapSheetLearnDataGeomEntity.fileCreateYn.isTrue())
.then(1L)
.otherwise(0L)
.sum();
NumberExpression<Long> completeCnt =
new CaseBuilder()
.when(labelingAssignmentEntity.inspectState.eq(InspectState.COMPLETE.getId()))
.then(1L)
.otherwise(0L)
.sum();
return queryFactory
.select(
Projections.constructor(
AnalCntInfo.class,
labelingAssignmentEntity.analUid,
mapSheetLearnEntity.uid,
allCnt,
completeCnt,
fileCnt))
.from(labelingAssignmentEntity)
.innerJoin(mapSheetAnalInferenceEntity)
.on(
labelingAssignmentEntity.analUid.eq(mapSheetAnalInferenceEntity.id),
mapSheetAnalInferenceEntity.analState.eq(LabelMngState.ING.getId()))
.leftJoin(mapSheetLearnEntity)
.on(mapSheetAnalInferenceEntity.learnId.eq(mapSheetLearnEntity.id))
.leftJoin(mapSheetLearnDataGeomEntity)
.on(labelingAssignmentEntity.inferenceGeomUid.eq(mapSheetLearnDataGeomEntity.geoUid))
.groupBy(labelingAssignmentEntity.analUid, mapSheetLearnEntity.uid)
.having(completeCnt.gt(0L))
.fetch();
}
@Override
@Transactional
public void updateLearnDataGeomFileCreateYn(List<Long> geoUids) {
queryFactory
.update(mapSheetLearnDataGeomEntity)
.set(mapSheetLearnDataGeomEntity.fileCreateYn, true)
.set(mapSheetLearnDataGeomEntity.modifiedDate, ZonedDateTime.now())
.where(mapSheetLearnDataGeomEntity.geoUid.in(geoUids))
.execute();
}
} }

View File

@@ -1,5 +1,13 @@
package com.kamco.cd.kamcoback.scheduler.dto; package com.kamco.cd.kamcoback.scheduler.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData.GeoJsonFeature;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@@ -28,4 +36,102 @@ public class TrainingDataReviewJobDto {
String inspectorUid; String inspectorUid;
Long pendingCount; Long pendingCount;
} }
@Getter
@Setter
@RequiredArgsConstructor
@AllArgsConstructor
public static class AnalCntInfo {
Long analUid;
String resultUid;
Long allCnt;
Long completeCnt;
Long fileCnt;
}
@Getter
@Setter
@RequiredArgsConstructor
@AllArgsConstructor
public static class AnalMapSheetList {
private Integer compareYyyy;
private Integer targetYyyy;
private String mapSheetNum;
}
@Getter
@Setter
@JsonPropertyOrder({"type", "features"})
public static class FeatureCollection {
private final String type = "FeatureCollection";
private List<GeoJsonFeature> features;
public FeatureCollection(List<GeoJsonFeature> features) {
this.features = features;
}
}
@Getter
@Setter
@JsonPropertyOrder({"type", "geometry", "properties"})
public static class CompleteLabelData {
private Long geoUid;
private String type;
@JsonIgnore private String geomStr;
private JsonNode geometry;
private Properties properties;
public CompleteLabelData(Long geoUid, String type, String geomStr, Properties properties) {
this.geoUid = geoUid;
this.type = type;
this.geomStr = geomStr;
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = null;
try {
if (geomStr != null) {
jsonNode = mapper.readTree(this.geomStr);
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
this.geometry = jsonNode;
if (jsonNode != null && jsonNode.isObject()) {
((ObjectNode) jsonNode).remove("crs");
}
this.properties = properties;
}
@Getter
@Setter
@RequiredArgsConstructor
@AllArgsConstructor
public static class Properties {
private String modelId;
private String before;
private String after;
}
@Getter
@AllArgsConstructor
public static class GeoJsonFeature {
private String type;
private JsonNode geometry;
private Properties properties;
public static GeoJsonFeature from(CompleteLabelData data) {
return new GeoJsonFeature(
data.getType(),
data.getGeometry(), // geoUid 없음
data.getProperties());
}
}
}
} }

View File

@@ -1,13 +1,25 @@
package com.kamco.cd.kamcoback.scheduler.service; package com.kamco.cd.kamcoback.scheduler.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.kamco.cd.kamcoback.postgres.core.TrainingDataReviewJobCoreService; import com.kamco.cd.kamcoback.postgres.core.TrainingDataReviewJobCoreService;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalCntInfo;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.AnalMapSheetList;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.CompleteLabelData.GeoJsonFeature;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.FeatureCollection;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.InspectorPendingDto;
import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks; import com.kamco.cd.kamcoback.scheduler.dto.TrainingDataReviewJobDto.Tasks;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -26,6 +38,9 @@ public class TrainingDataReviewJobService {
@Value("${spring.profiles.active}") @Value("${spring.profiles.active}")
private String profile; private String profile;
@Value("${training-data.geojson-dir}")
private String trainingDataDir;
private boolean isLocalProfile() { private boolean isLocalProfile() {
return "local".equalsIgnoreCase(profile); return "local".equalsIgnoreCase(profile);
} }
@@ -134,4 +149,70 @@ public class TrainingDataReviewJobService {
geomUids.add(task.getInferenceUid()); geomUids.add(task.getInferenceUid());
trainingDataReviewJobCoreService.updateGeomUidTestState(geomUids); trainingDataReviewJobCoreService.updateGeomUidTestState(geomUids);
} }
@Transactional
@Scheduled(cron = "0 0 2 * * *")
public void exportGeojsonLabelingGeom() {
// 1) 경로/파일명 결정
String targetDir =
"local".equals(profile) ? System.getProperty("user.home") + "/geojson" : trainingDataDir;
// 2) 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기
List<AnalCntInfo> analList = trainingDataReviewJobCoreService.findAnalCntInfoList();
for (AnalCntInfo info : analList) {
if (Objects.equals(info.getAllCnt(), info.getFileCnt())) {
continue;
}
String resultUid = info.getResultUid(); // 회차의 대문자 uid (폴더명으로 사용)
// 3) 회차 + 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기
List<AnalMapSheetList> analMapList =
trainingDataReviewJobCoreService.findCompletedAnalMapSheetList(info.getAnalUid());
if (analMapList.isEmpty()) {
continue;
}
for (AnalMapSheetList mapSheet : analMapList) {
// 4) 도엽별 geom 데이터 가지고 와서 geojson 만들기
List<CompleteLabelData> completeList =
trainingDataReviewJobCoreService.findCompletedYesterdayLabelingList(
info.getAnalUid(), mapSheet.getMapSheetNum());
if (!completeList.isEmpty()) {
List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList();
List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList();
// 5) 파일서버에 uid 폴더 생성 후 업로드 하기
FeatureCollection collection = new FeatureCollection(features);
String filename =
String.format(
"%s_%s_%s_%s_D15.geojson",
resultUid.substring(0, 8),
mapSheet.getCompareYyyy(),
mapSheet.getTargetYyyy(),
mapSheet.getMapSheetNum());
Path outputPath = Paths.get(targetDir + "/" + resultUid, filename);
try {
Files.createDirectories(outputPath.getParent());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.writeValue(outputPath.toFile(), collection);
// geoUids : file_create_yn = true 로 업데이트
trainingDataReviewJobCoreService.updateLearnDataGeomFileCreateYn(geoUids);
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
}
}
} }

View File

@@ -565,4 +565,11 @@ public class TrainingDataReviewApiController {
trainingDataReviewJobService.assignReviewerYesterdayLabelComplete(); trainingDataReviewJobService.assignReviewerYesterdayLabelComplete();
return ApiResponseDto.ok(null); return ApiResponseDto.ok(null);
} }
@Operation(summary = "검수완료된 라벨링 geojson 생성(스케줄링 수동 호출)", description = "검수완료된 라벨링 geojson 생성")
@GetMapping("/run-label-geojson")
public ApiResponseDto<Long> runExportGeojsonLabelingGeom() {
trainingDataReviewJobService.exportGeojsonLabelingGeom();
return ApiResponseDto.ok(0L);
}
} }

View File

@@ -115,3 +115,6 @@ gukyuin:
#url: http://localhost:8080 #url: http://localhost:8080
url: http://192.168.2.129:5301 url: http://192.168.2.129:5301
mast: ${gukyuin.url}/api/kcd/cdi/chn/mast mast: ${gukyuin.url}/api/kcd/cdi/chn/mast
training-data:
geojson-dir: /kamco-nfs/model_output/labeling/

View File

@@ -70,3 +70,6 @@ gukyuin:
#url: http://localhost:8080 #url: http://localhost:8080
url: http://192.168.2.129:5301 url: http://192.168.2.129:5301
mast: ${gukyuin.url}/api/kcd/cdi/chn/mast mast: ${gukyuin.url}/api/kcd/cdi/chn/mast
training-data:
geojson-dir: /kamco-nfs/model_output/labeling/