feat/infer_dev_260211 #126

Merged
gina merged 2 commits from feat/infer_dev_260211 into develop 2026-02-28 00:34:35 +09:00
8 changed files with 109 additions and 21 deletions

View File

@@ -281,6 +281,7 @@ public class GukYuinApiService {
+ "&reqEpno="
+ ("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo());
log.info("##### API 호출 URL : {}", url);
ExternalCallResult<ChngDetectContDto.ResultContDto> result =
externalHttpClient.call(
url,
@@ -289,6 +290,7 @@ public class GukYuinApiService {
netUtils.jsonHeaders(),
ChngDetectContDto.ResultContDto.class);
log.info("##### API 호출 완료 : {}", result.toString());
List<ContBasic> contList = result.body().getResult();
if (contList == null || contList.isEmpty()) {
return new ResultContDto(
@@ -348,6 +350,7 @@ public class GukYuinApiService {
info.setReqIp(myip);
info.setReqEpno("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo());
log.info("##### API 호출 URL : {}", url);
ExternalCallResult<ChngDetectContDto.ResultLabelDto> result =
externalHttpClient.call(
url,
@@ -355,6 +358,7 @@ public class GukYuinApiService {
info,
netUtils.jsonHeaders(),
ChngDetectContDto.ResultLabelDto.class);
log.info("##### API 호출 완료 : {}", result.toString());
this.insertGukyuinAuditLog(
EventType.MODIFIED.getId(),
@@ -408,10 +412,12 @@ public class GukYuinApiService {
+ "&reqEpno="
+ ("Y".equals(batchYn) ? "BATCH" : userUtil.getEmployeeNo());
log.info("##### API 호출 URL : {}", url);
ExternalCallResult<ChngDetectMastDto.ResultDto> result =
externalHttpClient.call(
url, HttpMethod.GET, null, netUtils.jsonHeaders(), ChngDetectMastDto.ResultDto.class);
log.info("##### API 호출 완료 : {}", result.toString());
this.insertGukyuinAuditLog(
EventType.DETAIL.getId(),
netUtils.getLocalIP(),

View File

@@ -458,6 +458,7 @@ public class InferenceDetailDto {
private String bboxGeom;
private String bboxCenterPoint;
private UUID inferenceUuid;
private String status;
public AnalResultInfo(
String analTitle,
@@ -474,7 +475,8 @@ public class InferenceDetailDto {
String subUid,
Boolean applyYn,
ZonedDateTime applyDttm,
UUID inferenceUuid) {
UUID inferenceUuid,
String status) {
this.analTitle = analTitle;
this.modelVer1 = modelVer1;
this.modelVer2 = modelVer2;
@@ -489,6 +491,7 @@ public class InferenceDetailDto {
this.subUid = subUid;
this.applyYn = applyYn;
this.applyDttm = applyDttm;
this.status = status;
Duration elapsed =
(inferStartDttm != null && inferEndDttm != null)
? Duration.between(inferStartDttm, inferEndDttm)
@@ -538,6 +541,10 @@ public class InferenceDetailDto {
public Boolean getApplyYn() {
return this.applyYn != null && this.applyYn;
}
public String getStatusNm() {
return InferenceResultDto.Status.getDescByCode(this.status);
}
}
@Getter

View File

@@ -315,7 +315,8 @@ public class MapSheetLearnRepositoryImpl implements MapSheetLearnRepositoryCusto
Expressions.stringTemplate("substring({0} from 1 for 8)", mapSheetLearnEntity.uid),
mapSheetLearnEntity.applyYn,
mapSheetLearnEntity.applyDttm,
mapSheetAnalInferenceEntity.uuid))
mapSheetAnalInferenceEntity.uuid,
mapSheetLearnEntity.status))
.from(mapSheetLearnEntity)
.leftJoin(m1)
.on(mapSheetLearnEntity.m1ModelUuid.eq(m1.uuid))

View File

@@ -31,26 +31,33 @@ public class GukYuinApiLabelJobService {
return "local".equalsIgnoreCase(profile);
}
// @Scheduled(cron = "0 0 2 * * *")
public void runTask() {
findLabelingCompleteSend(null);
}
/** 어제 라벨링 검수 완료된 것 -> 국유인에 전송 */
public void findLabelingCompleteSend(LocalDate baseDate) {
// if (isLocalProfile()) {
// return;
// }
log.info("[Step 1-1] 어제 검수완료된 라벨링을 검색한다.");
log.info("=== baseDate : {}", baseDate);
log.info("=== baseDate 있으면 해당 일자, 없으면 어제일자로 조회");
List<GeomUidDto> list = gukYuinLabelJobCoreService.findYesterdayLabelingCompleteList(baseDate);
log.info("[Step 1-2] 검수완료된 폴리곤 객체 수 : {}", list == null ? 0 : list.size());
if (list.isEmpty()) {
log.info("[Step 1-3] 객체 없어서 return : 스케줄링 종료");
return;
}
log.info("[Step 2-1] 객체 목록으로 라벨링 전송 API 호출 시작");
for (GeomUidDto gto : list) {
log.info("[Step 2-2] 객체ID 확인 gto.getResultUid(): {}", gto.getResultUid());
ChngDetectContDto.ResultLabelDto dto =
gukYuinApiService.updateChnDtctObjtLabelingYn(gto.getResultUid(), "Y", "Y");
log.info("[Step 2-3] 결과 dto.getSuccess(): {}", dto.getSuccess());
if (dto.getSuccess()) {
log.info("[Step 2-4] 결과가 성공일 때 inference_geom에 label_send_dttm 업데이트 하기");
log.info("==== 업데이트 하는 객체 gto.getGeoUid(): {}", gto.getGeoUid());
// inference_geom 에 label_send_dttm 업데이트 하기
gukYuinLabelJobCoreService.updateAnalDataInferenceGeomSendDttm(gto.getGeoUid());
}

View File

@@ -41,16 +41,24 @@ public class GukYuinApiPnuJobService {
// return;
// }
log.info("[Step 1-1] 국유인 연동까지 완료된 추론 목록 가져오기");
log.info("=== apply_status -> 100% 다운 완료: GUK_COMPLETED, PNU매핑 실패: PNU_FAILED");
List<LearnKeyDto> list =
gukYuinPnuJobCoreService.findGukyuinApplyStatusUidList(
List.of(GukYuinStatus.GUK_COMPLETED.getId(), GukYuinStatus.PNU_FAILED.getId()));
log.info("[Step 1-2] 매핑할 추론 회차 갯수 : {}", list == null ? 0 : list.size());
if (list.isEmpty()) {
log.info("[Step 1-3] 매핑할 추론 회차 갯수 없어서 리턴하고 끝남");
return;
}
log.info("[Step 2-1] 추론 회차별 pnu 매핑 for문 시작 ");
for (LearnKeyDto dto : list) {
try {
processUid(dto.getUid(), dto.getUid());
log.info("[Step 2-2] 진행하는 추론 Uid: {}", dto.getUid());
processUid(dto.getUid());
gukYuinPnuJobCoreService.updateGukYuinApplyStateComplete(
dto.getId(), GukYuinStatus.PNU_COMPLETED);
} catch (Exception e) {
@@ -61,15 +69,21 @@ public class GukYuinApiPnuJobService {
}
}
private void processUid(String chnDtctId, String uid) {
ResultDto result = gukYuinApiService.listChnDtctId(chnDtctId, "Y");
private void processUid(String uid) {
log.info("[Step 2-4] 탐지 등록목록 상세 API 호출 시작");
ResultDto result = gukYuinApiService.listChnDtctId(uid, "Y");
if (result == null || result.getResult() == null || result.getResult().isEmpty()) {
log.info("[Step 2-5] 결과값 없어서 return");
return;
}
log.info("[Step 2-5] 결과값 첫번째 값 가져오기");
ChngDetectMastDto.Basic basic = result.getResult().get(0);
String chnDtctCnt = basic.getChnDtctCnt();
log.info("[Step 2-6] 탐지 객체 전체 갯수 chnDtctCnt = {}", chnDtctCnt);
if (chnDtctCnt == null || chnDtctCnt.isEmpty()) {
log.info("[Step 2-76] 탐지 객체 전체 갯수 없어서 return");
return;
}
@@ -78,29 +92,44 @@ public class GukYuinApiPnuJobService {
int totalCount = Integer.parseInt(chnDtctCnt);
int totalPages = (totalCount + pageSize - 1) / pageSize;
log.info("[Step 3-1] 탐지 객체 전체 수로 페이지 계산 : {}", totalPages);
for (int page = 0; page < totalPages; page++) {
log.info("[Step 3-2] 페이지 별 호출 : {}", page);
processPage(uid, page, pageSize);
}
}
private void processPage(String uid, int page, int pageSize) {
log.info("[Step 4-1] 탐지객체 목록 API 호출 시작");
ResultContDto resContList = gukYuinApiService.findChnContList(uid, page, pageSize, "Y");
if (resContList.getResult() == null || resContList.getResult().isEmpty()) {
log.info("[Step 4-2] 탐지객체 목록 결과 없어서 return");
return; // 외부 API 이상 방어
}
List<ContBasic> contList = resContList.getResult();
log.info("[Step 4-3] 탐지객체 목록 결과 contList.size : {}", contList == null ? 0 : contList.size());
for (ContBasic cont : contList) {
String[] pnuList = cont.getPnuList();
long pnuCnt = pnuList == null ? 0 : pnuList.length;
log.info("[Step 4-4] 객체에 연결된 pnuCnt : {}", pnuCnt);
if (cont.getChnDtctObjtId() != null) {
log.info(
"[Step 4-5] inference_geom 에 pnu 갯수 update : cont.getChnDtctObjtId = {}",
cont.getChnDtctObjtId());
log.info(" === cont.getChnDtctObjtId : {}", cont.getChnDtctObjtId());
log.info(" === pnuCnt : {}", pnuCnt);
gukYuinPnuJobCoreService.updateInferenceGeomDataPnuCnt(cont.getChnDtctObjtId(), pnuCnt);
if (pnuCnt > 0) {
log.info("[Step 4-6] 객체 ID로 geoUid 검색 = {}", cont.getChnDtctObjtId());
Long geoUid =
gukYuinPnuJobCoreService.findMapSheetAnalDataInferenceGeomUid(
cont.getChnDtctObjtId());
log.info("[Step 4-7] tb_pnu 에 데이터 upsert 수행");
log.info("===== geoUid = {}", geoUid);
gukYuinPnuJobCoreService.insertGeoUidPnuData(geoUid, pnuList, cont.getChnDtctObjtId());
}
}

View File

@@ -51,7 +51,7 @@ public class GukYuinApiStbltJobService {
gukYuinStbltJobCoreService.findGukYuinEligibleForSurveyList(
GukYuinStatus.PNU_COMPLETED.getId());
log.info("[Step 1-2] 국유인 연동 PNU 완료된 추론 회차 갯수 : {}", list.size());
log.info("[Step 1-2] 국유인 연동 PNU 완료된 추론 회차 갯수 : {}", list == null ? 0 : list.size());
if (list.isEmpty()) {
log.info("[Step 1-3] 국유인 연동 PNU 완료된 추론 회차 갯수 없어서 return");
return;
@@ -83,7 +83,7 @@ public class GukYuinApiStbltJobService {
}
log.info("[Step 4-1] 국유인 실태조사 적합여부 result 값으로 데이터 업데이트");
log.info(" === 데이터 갯수 : {}", result.getResult().size());
log.info(" === 데이터 갯수 : {}", result.getResult() == null ? 0 : result.getResult().size());
for (RlbDtctMastDto stbltDto : result.getResult()) {
log.info("[Step 4-2] 국유인 실태조사 적합여부 결과 가져오기");

View File

@@ -32,7 +32,6 @@ public class TrainingDataLabelJobService {
return "local".equalsIgnoreCase(profile);
}
// @Scheduled(cron = "0 0 0 * * *")
public void runTask() {
// 프록시를 통해 호출해야 @Transactional이 적용됨
applicationContext
@@ -43,55 +42,72 @@ public class TrainingDataLabelJobService {
@Transactional
public void assignReviewerYesterdayLabelComplete(LocalDate baseDate) {
// if (isLocalProfile()) {
// return;
// }
try {
log.info("[Step 1-1] 라벨링 완료된 데이터 목록 조회한다.");
log.info("=== baseDate : {}", baseDate);
log.info("=== baseDate 있으면 해당 일자, 없으면 어제일자로 조회");
List<Tasks> tasks =
trainingDataLabelJobCoreService.findCompletedYesterdayUnassigned(baseDate);
log.info("[Step 1-2] 목록 객체 건수 count : {}", tasks == null ? 0 : tasks.size());
if (tasks.isEmpty()) {
log.info("[Step 1-3] 조회된 것 없어 return");
return;
}
// 회차별로 그룹핑
log.info("[Step 2-1] 회차별로 그룹핑 시작");
Map<Long, List<Tasks>> taskByRound =
tasks.stream().collect(Collectors.groupingBy(Tasks::getAnalUid));
// 회차별 분배
log.info("[Step 3-1] 회차별로 분배 시작");
for (Map.Entry<Long, List<Tasks>> entry : taskByRound.entrySet()) {
Long analUid = entry.getKey();
List<Tasks> analTasks = entry.getValue();
// pending 계산
log.info("[Step 3-2] 수행하는 회차 analUid: {}", analUid);
log.info("해당 회차에 라벨링 할당받은 검수자별 완료 건수 count(), 완료한 게 적은 순으로 해야 일이 한 사람에게 몰리지 않음");
List<InspectorPendingDto> pendings =
trainingDataLabelJobCoreService.findInspectorPendingByRound(analUid);
log.info("검수자 수: {}", pendings == null ? 0 : pendings.size());
if (pendings.isEmpty()) {
log.info("[Step 3-3] 할당된 검수자가 없으면 return");
continue;
}
log.info("[Step 4-1] 검수자 사번 List 생성");
List<String> reviewerIds =
pendings.stream().map(InspectorPendingDto::getInspectorUid).toList();
// Lock 걸릴 수 있기 때문에 엔티티 조회하는 Repository 에서 구현
log.info("[Step 4-2] 검수자 테이블 lock 걸리지 않게 처리");
trainingDataLabelJobCoreService.lockInspectors(analUid, reviewerIds);
// 균등 분배
log.info("[Step 5-1] 검수자에게 라벨 작업 균등분배 시작");
Map<String, List<Tasks>> assignMap = distributeByLeastPending(analTasks, reviewerIds);
log.info("[Step 5-2] 검수자에게 라벨 작업 균등분배 완료");
// reviewer별 batch update
log.info("[Step 5-3] 검수자별 할당 데이터를 batch update 시작");
assignMap.forEach(
(reviewerId, assignedTasks) -> {
if (assignedTasks.isEmpty()) {
log.info("[Step 5-4] 할당된 데이터 없으면 return");
return;
}
List<UUID> assignmentUids =
assignedTasks.stream().map(Tasks::getAssignmentUid).toList();
log.info("[Step 6-1] 할당 작업에 검수자 아이디 update");
log.info("==== 검수자 사번: {}", reviewerId);
log.info("==== 할당 갯수: {}", assignmentUids == null ? 0 : assignmentUids.size());
trainingDataLabelJobCoreService.assignReviewerBatch(assignmentUids, reviewerId);
log.info("[Step 7-1] geom 테이블에 검수 상태 update");
List<Long> geomUids = assignedTasks.stream().map(Tasks::getInferenceUid).toList();
trainingDataLabelJobCoreService.updateGeomUidTestState(geomUids);
});

View File

@@ -39,52 +39,68 @@ public class TrainingDataReviewJobService {
}
@Transactional
// @Scheduled(cron = "0 0 2 * * *")
public void runTask() {
exportGeojsonLabelingGeom(null);
}
public void exportGeojsonLabelingGeom(LocalDate baseDate) {
// if (isLocalProfile()) {
// return;
// }
// 1) 경로/파일명 결정
String targetDir =
"local".equals(profile) ? System.getProperty("user.home") + "/geojson" : trainingDataDir;
log.info("[Step 1-1] geojson 파일 생성할 경로: {}", targetDir);
// 2) 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기
log.info("[Step 1-2] 진행중인 회차 중, complete_cnt 가 존재하는 회차 목록 가져오기");
List<AnalCntInfo> analList = trainingDataReviewJobCoreService.findAnalCntInfoList();
log.info("[Step 1-3] 회차 리스트 건수: {}", analList == null ? 0 : analList.size());
if (analList.isEmpty()) {
log.info("[Step 1-4] 회차 리스트 없어 return 하고 종료");
return;
}
for (AnalCntInfo info : analList) {
log.info("[Step 2-1] 회차 폴리곤 전체 건수 == 파일 생성 건수 같은지 확인");
log.info("=== info.getAllCnt(): {}", info.getAllCnt());
log.info("=== info.getFileCnt(): {}", info.getFileCnt());
if (Objects.equals(info.getAllCnt(), info.getFileCnt())) {
log.info("[Step 2-2] 회차 폴리곤 전체 건수 == 파일 생성 건수 같아서 파일 생성 진행하지 않음 continue");
continue;
}
String resultUid = info.getResultUid(); // 회차의 대문자 uid (폴더명으로 사용)
// 3) 회차 + 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기
log.info("[Step 3-1] 회차 + 어제까지 검수 완료된 총 데이터의 도엽별 목록 가져오기");
List<AnalMapSheetList> analMapList =
trainingDataReviewJobCoreService.findCompletedAnalMapSheetList(
info.getAnalUid(), baseDate);
log.info("=== analMapList cnt: {}", analMapList == null ? 0 : analMapList.size());
if (analMapList.isEmpty()) {
log.info("[Step 3-2] 도엽 목록 조회되지 않아 continue");
continue;
}
log.info("[Step 4-1] 도엽별 geom 데이터 가지고 와서 geojson 만들기 시작");
for (AnalMapSheetList mapSheet : analMapList) {
// 4) 도엽별 geom 데이터 가지고 와서 geojson 만들기
log.info("[Step 4-2] 도엽별 검수완료된 폴리곤 데이터 목록 조회");
List<CompleteLabelData> completeList =
trainingDataReviewJobCoreService.findCompletedYesterdayLabelingList(
info.getAnalUid(), mapSheet.getMapSheetNum(), baseDate);
log.info("=== completeList size: {}", completeList == null ? 0 : completeList.size());
if (!completeList.isEmpty()) {
log.info("[Step 4-3] 목록에서 filter로 geoUid List 생성, 폴리곤 feature별 리스트 생성");
List<Long> geoUids = completeList.stream().map(CompleteLabelData::getGeoUid).toList();
List<GeoJsonFeature> features = completeList.stream().map(GeoJsonFeature::from).toList();
// 5) 파일서버에 uid 폴더 생성 후 업로드 하기
log.info("[Step 5-1] 파일서버에 uid 폴더 생성 후 업로드 하기 시작");
FeatureCollection collection = new FeatureCollection(features);
String filename =
String.format(
@@ -94,15 +110,21 @@ public class TrainingDataReviewJobService {
mapSheet.getTargetYyyy(),
mapSheet.getMapSheetNum());
log.info("=== filename: {}", filename);
log.info("=== 회차의 uid: {}", resultUid);
Path outputPath = Paths.get(targetDir + "/" + resultUid, filename);
log.info("=== outputPath: {}", outputPath);
try {
log.info("[Step 6-1] Uid로 폴더 생성");
Files.createDirectories(outputPath.getParent());
log.info("[Step 6-2] geoJson 파일 생성");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.writeValue(outputPath.toFile(), collection);
// geoUids : file_create_yn = true 로 업데이트
log.info("[Step 6-3] learn_data_geom 에 file_create_yn = true 로 업데이트");
trainingDataReviewJobCoreService.updateLearnDataGeomFileCreateYn(geoUids);
} catch (IOException e) {