daniel 작업본 추가
This commit is contained in:
@@ -454,33 +454,53 @@ public class ModelTrainDetailService {
|
|||||||
*/
|
*/
|
||||||
public ZipFileListResponse getZipFileListWithFullUrl(
|
public ZipFileListResponse getZipFileListWithFullUrl(
|
||||||
UUID uuid, String downloadUuid, HttpServletRequest request) {
|
UUID uuid, String downloadUuid, HttpServletRequest request) {
|
||||||
log.info("ZIP 파일 목록 조회 시작 (전체 URL): modelUuid={}, downloadUuid={}", uuid, downloadUuid);
|
log.info("=== ZIP 파일 목록 조회 시작: modelUuid={}, downloadUuid={} ===", uuid, downloadUuid);
|
||||||
|
|
||||||
// 1. 모델 정보 조회
|
// 1. 모델 정보 조회
|
||||||
Basic modelInfo;
|
Basic modelInfo;
|
||||||
try {
|
try {
|
||||||
modelInfo = findByModelByUUID(uuid);
|
modelInfo = findByModelByUUID(uuid);
|
||||||
if (modelInfo == null) {
|
if (modelInfo == null) {
|
||||||
|
log.warn("모델을 찾을 수 없음: modelUuid={}", uuid);
|
||||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
||||||
}
|
}
|
||||||
|
log.debug(
|
||||||
|
"모델 정보 조회 성공: modelNo={}, modelVer={}", modelInfo.getModelNo(), modelInfo.getModelVer());
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
log.error("모델 조회 실패: {}", uuid, e);
|
log.error("모델 조회 중 NullPointerException 발생: modelUuid={}", uuid, e);
|
||||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델을 찾을 수 없습니다: " + uuid);
|
||||||
|
} catch (CustomApiException e) {
|
||||||
|
// CustomApiException은 그대로 재throw
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("모델 조회 중 예상치 못한 오류 발생: modelUuid={}", uuid, e);
|
||||||
|
throw new CustomApiException(
|
||||||
|
"INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, "모델 조회 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 실제 디렉토리 경로 찾기 (uuid 또는 uuid-out)
|
// 2. 실제 디렉토리 경로 찾기 (uuid 또는 uuid-out)
|
||||||
Path baseDir = findActualBasePath(uuid);
|
Path baseDir;
|
||||||
|
try {
|
||||||
|
baseDir = findActualBasePath(uuid);
|
||||||
|
|
||||||
if (baseDir == null || !Files.exists(baseDir)) {
|
if (baseDir == null || !Files.exists(baseDir)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
"디렉토리를 찾을 수 없음: modelUuid={}, 시도한 경로: {} 또는 {}-out",
|
"모델 결과 디렉토리를 찾을 수 없음: modelUuid={}, 시도한 경로: {} 또는 {}-out",
|
||||||
uuid,
|
uuid,
|
||||||
responseDir + "/" + uuid,
|
responseDir + "/" + uuid,
|
||||||
responseDir + "/" + uuid);
|
responseDir + "/" + uuid);
|
||||||
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델 결과 디렉토리가 존재하지 않습니다.");
|
throw new CustomApiException("NOT_FOUND", HttpStatus.NOT_FOUND, "모델 결과 디렉토리가 존재하지 않습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("디렉토리 발견: basePath={}", baseDir.toString());
|
log.debug("디렉토리 발견: basePath={}", baseDir.toString());
|
||||||
|
} catch (CustomApiException e) {
|
||||||
|
// CustomApiException은 그대로 재throw
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("디렉토리 경로 확인 중 오류 발생: modelUuid={}", uuid, e);
|
||||||
|
throw new CustomApiException(
|
||||||
|
"INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, "디렉토리 경로 확인 중 오류가 발생했습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
// 요청에서 도메인 정보 추출
|
// 요청에서 도메인 정보 추출
|
||||||
String scheme = request.getScheme(); // http 또는 https
|
String scheme = request.getScheme(); // http 또는 https
|
||||||
@@ -497,6 +517,8 @@ public class ModelTrainDetailService {
|
|||||||
baseUrl = scheme + "://" + serverName + ":" + serverPort + contextPath;
|
baseUrl = scheme + "://" + serverName + ":" + serverPort + contextPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug("다운로드 URL 베이스: {}", baseUrl);
|
||||||
|
|
||||||
// 3. ZIP 파일 목록 검색
|
// 3. ZIP 파일 목록 검색
|
||||||
List<ZipFileInfo> zipFiles = new ArrayList<>();
|
List<ZipFileInfo> zipFiles = new ArrayList<>();
|
||||||
long totalSize = 0L;
|
long totalSize = 0L;
|
||||||
@@ -511,7 +533,10 @@ public class ModelTrainDetailService {
|
|||||||
Comparator.comparing(p -> p.getFileName().toString(), Comparator.reverseOrder()))
|
Comparator.comparing(p -> p.getFileName().toString(), Comparator.reverseOrder()))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
log.debug("ZIP 파일 필터링 완료: 발견된 파일 개수={}", files.size());
|
||||||
|
|
||||||
for (Path file : files) {
|
for (Path file : files) {
|
||||||
|
try {
|
||||||
String fileName = file.getFileName().toString();
|
String fileName = file.getFileName().toString();
|
||||||
long fileSize = Files.size(file);
|
long fileSize = Files.size(file);
|
||||||
totalSize += fileSize;
|
totalSize += fileSize;
|
||||||
@@ -535,14 +560,35 @@ public class ModelTrainDetailService {
|
|||||||
.isCurrent(isCurrent)
|
.isCurrent(isCurrent)
|
||||||
.downloadUrl(downloadUrl)
|
.downloadUrl(downloadUrl)
|
||||||
.build());
|
.build());
|
||||||
}
|
|
||||||
|
|
||||||
log.info("ZIP 파일 {}개 발견", zipFiles.size());
|
log.debug(
|
||||||
|
"ZIP 파일 정보 추가: fileName={}, size={}, version={}, isCurrent={}",
|
||||||
|
fileName,
|
||||||
|
formatFileSize(fileSize),
|
||||||
|
version,
|
||||||
|
isCurrent);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("ZIP 파일 목록 조회 실패: {}", baseDir, e);
|
log.warn("ZIP 파일 정보 조회 실패 (건너뜀): file={}", file.getFileName(), e);
|
||||||
|
// 개별 파일 실패 시 전체 프로세스를 중단하지 않고 계속 진행
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("ZIP 파일 정보 처리 중 예상치 못한 오류 (건너뜀): file={}", file.getFileName(), e);
|
||||||
|
// 예상치 못한 오류도 전체 프로세스를 중단하지 않음
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(" ZIP 파일 목록 조회 완료: 총 {}개 파일, 전체 크기={}", zipFiles.size(), formatFileSize(totalSize));
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("ZIP 파일 목록 조회 중 IO 오류 발생: basePath={}", baseDir, e);
|
||||||
throw new CustomApiException(
|
throw new CustomApiException(
|
||||||
"INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, "ZIP 파일 목록 조회 실패");
|
"INTERNAL_SERVER_ERROR",
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
"ZIP 파일 목록 조회 중 IO 오류가 발생했습니다.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("ZIP 파일 목록 조회 중 예상치 못한 오류 발생: basePath={}", baseDir, e);
|
||||||
|
throw new CustomApiException(
|
||||||
|
"INTERNAL_SERVER_ERROR", HttpStatus.INTERNAL_SERVER_ERROR, "ZIP 파일 목록 조회 중 오류가 발생했습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ZipFileListResponse.builder()
|
return ZipFileListResponse.builder()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.kamco.cd.training.common.enums.TrainStatusType;
|
import com.kamco.cd.training.common.enums.TrainStatusType;
|
||||||
import com.kamco.cd.training.postgres.core.ModelTestMetricsJobCoreService;
|
import com.kamco.cd.training.postgres.core.ModelTestMetricsJobCoreService;
|
||||||
|
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.BestPthInfo;
|
||||||
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ModelMetricJsonDto;
|
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ModelMetricJsonDto;
|
||||||
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ModelTestFileName;
|
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ModelTestFileName;
|
||||||
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ResponsePathDto;
|
import com.kamco.cd.training.train.dto.ModelTrainMetricsDto.ResponsePathDto;
|
||||||
@@ -19,6 +20,8 @@ import java.time.ZonedDateTime;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
@@ -81,13 +84,13 @@ public class ModelTestMetricsJobService {
|
|||||||
/**
|
/**
|
||||||
* 베스트 에폭 zip파일 생성, 테스트결과 db등록
|
* 베스트 에폭 zip파일 생성, 테스트결과 db등록
|
||||||
*
|
*
|
||||||
* @param modelInfo
|
* @param modelInfo 모델 정보 (modelId, responsePath, uuid)
|
||||||
*/
|
*/
|
||||||
private void createFile(ResponsePathDto modelInfo) {
|
private void createFile(ResponsePathDto modelInfo) {
|
||||||
|
|
||||||
String testPath = responseDir + "/" + modelInfo.getUuid() + "/metrics/test.csv";
|
String testPath = responseDir + "/" + modelInfo.getUuid() + "/metrics/test.csv";
|
||||||
try (BufferedReader reader =
|
try (BufferedReader reader =
|
||||||
Files.newBufferedReader(Paths.get(testPath), StandardCharsets.UTF_8); ) {
|
Files.newBufferedReader(Paths.get(testPath), StandardCharsets.UTF_8)) {
|
||||||
|
|
||||||
CSVParser parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader);
|
CSVParser parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(reader);
|
||||||
|
|
||||||
@@ -161,52 +164,60 @@ public class ModelTestMetricsJobService {
|
|||||||
fileInfo.getBestEpochFileName() + ".pth",
|
fileInfo.getBestEpochFileName() + ".pth",
|
||||||
fileInfo.getModelVersion() + ".json");
|
fileInfo.getModelVersion() + ".json");
|
||||||
|
|
||||||
|
try {
|
||||||
List<Path> files = new ArrayList<>();
|
List<Path> files = new ArrayList<>();
|
||||||
try (Stream<Path> s = Files.list(responsePath)) {
|
try (Stream<Path> s = Files.list(responsePath)) {
|
||||||
files.addAll(
|
files.addAll(
|
||||||
s.filter(Files::isRegularFile)
|
s.filter(Files::isRegularFile)
|
||||||
.filter(p -> targetNames.contains(p.getFileName().toString()))
|
.filter(p -> targetNames.contains(p.getFileName().toString()))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try (Stream<Path> s = Files.list(Path.of(ptPathDir))) {
|
// PT 파일 정확하게 조회
|
||||||
files.addAll(
|
Path ptFile = Paths.get(ptPathDir, ptFileName);
|
||||||
s.filter(Files::isRegularFile)
|
if (Files.exists(ptFile)) {
|
||||||
.limit(1) // yolov8_6th-6m.pt 파일 1개만
|
files.add(ptFile);
|
||||||
.collect(Collectors.toList()));
|
} else {
|
||||||
} catch (IOException e) {
|
log.warn("PT 파일을 찾을 수 없습니다: {}", ptFile);
|
||||||
throw new RuntimeException(e);
|
throw new IOException("PT 파일 누락: " + ptFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// 기본 ZIP 생성
|
||||||
zipFiles(files, zipPath);
|
zipFiles(files, zipPath);
|
||||||
|
log.info(" 기본 ZIP 생성 완료: {}", zipPath.getFileName());
|
||||||
|
|
||||||
|
// 개별 best*.pth ZIP 생성
|
||||||
|
int individualZipCount = createIndividualBestPthZips(modelInfo, responsePath);
|
||||||
|
|
||||||
|
// 모든 ZIP 생성 성공 시 COMPLETED
|
||||||
modelTestMetricsJobCoreService.updatePackingEnd(
|
modelTestMetricsJobCoreService.updatePackingEnd(
|
||||||
modelInfo.getModelId(), ZonedDateTime.now(), TrainStatusType.COMPLETED.getId());
|
modelInfo.getModelId(), ZonedDateTime.now(), TrainStatusType.COMPLETED.getId());
|
||||||
|
|
||||||
|
log.info(" 전체 ZIP 생성 완료: 기본 1개 + 개별 {}개", individualZipCount);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
modelTestMetricsJobCoreService.updatePackingEnd(
|
modelTestMetricsJobCoreService.updatePackingEnd(
|
||||||
modelInfo.getModelId(), ZonedDateTime.now(), TrainStatusType.ERROR.getId());
|
modelInfo.getModelId(), ZonedDateTime.now(), TrainStatusType.ERROR.getId());
|
||||||
|
log.error("ZIP 생성 중 오류 발생: modelId={}", modelInfo.getModelId(), e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 추가: 각 best*.pth 파일별 개별 ZIP 생성 =====
|
|
||||||
createIndividualBestPthZips(modelInfo, responsePath, jsonDto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response 폴더의 모든 best*.pth 파일을 각각 개별 ZIP 파일로 생성
|
* Response 폴더의 모든 best*.pth 파일을 각각 개별 ZIP 파일로 생성
|
||||||
*
|
*
|
||||||
|
* <p>각 PTH 파일의 Epoch과 메트릭 타입을 파싱하여 해당 Epoch의 메트릭 정보를 조회한 후, 개별 JSON 파일을 생성하고 ZIP으로 패키징합니다.
|
||||||
|
*
|
||||||
* @param modelInfo 모델 정보
|
* @param modelInfo 모델 정보
|
||||||
* @param responsePath Response 디렉토리 경로
|
* @param responsePath Response 디렉토리 경로
|
||||||
* @param jsonDto JSON 메타데이터
|
* @return 생성된 개별 ZIP 파일 개수
|
||||||
*/
|
*/
|
||||||
private void createIndividualBestPthZips(
|
private int createIndividualBestPthZips(ResponsePathDto modelInfo, Path responsePath) {
|
||||||
ResponsePathDto modelInfo, Path responsePath, ModelMetricJsonDto jsonDto) {
|
|
||||||
|
|
||||||
log.info("=== 개별 best*.pth ZIP 파일 생성 시작: modelId={} ===", modelInfo.getModelId());
|
log.info("=== 개별 best*.pth ZIP 파일 생성 시작: modelId={} ===", modelInfo.getModelId());
|
||||||
|
|
||||||
|
int successCount = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Response 폴더에서 모든 best*.pth 파일 찾기
|
// 1. Response 폴더에서 모든 best*.pth 파일 찾기
|
||||||
List<Path> bestPthFiles;
|
List<Path> bestPthFiles;
|
||||||
@@ -221,51 +232,88 @@ public class ModelTestMetricsJobService {
|
|||||||
|
|
||||||
if (bestPthFiles.isEmpty()) {
|
if (bestPthFiles.isEmpty()) {
|
||||||
log.warn("best*.pth 파일을 찾을 수 없습니다: path={}", responsePath);
|
log.warn("best*.pth 파일을 찾을 수 없습니다: path={}", responsePath);
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("발견된 best*.pth 파일 개수: {}", bestPthFiles.size());
|
log.info("발견된 best*.pth 파일 개수: {}", bestPthFiles.size());
|
||||||
|
|
||||||
// 2. PT 파일 경로 (모든 ZIP에 공통으로 포함)
|
// 2. PT 파일 경로 확인 (모든 ZIP에 공통으로 포함)
|
||||||
Path ptFile = Paths.get(ptPathDir, ptFileName);
|
Path ptFile = Paths.get(ptPathDir, ptFileName);
|
||||||
if (!Files.exists(ptFile)) {
|
if (!Files.exists(ptFile)) {
|
||||||
log.warn("PT 파일을 찾을 수 없습니다: {}", ptFile);
|
log.warn("PT 파일을 찾을 수 없습니다: {}", ptFile);
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// model_config.py 경로 (선택적)
|
||||||
|
Path modelConfigPath = responsePath.resolve("model_config.py");
|
||||||
|
|
||||||
// 3. 각 best*.pth 파일별로 개별 ZIP 생성
|
// 3. 각 best*.pth 파일별로 개별 ZIP 생성
|
||||||
for (Path bestPthFile : bestPthFiles) {
|
for (Path bestPthFile : bestPthFiles) {
|
||||||
String pthFileName = bestPthFile.getFileName().toString();
|
String pthFileName = bestPthFile.getFileName().toString();
|
||||||
log.info("처리 중인 best PTH 파일: {}", pthFileName);
|
log.info("처리 중인 best PTH 파일: {}", pthFileName);
|
||||||
|
|
||||||
try {
|
Path individualJsonPath = null;
|
||||||
// 3-1. 개별 JSON 파일 생성
|
|
||||||
String individualJsonName = pthFileName.replace(".pth", ".json");
|
|
||||||
Path individualJsonPath = responsePath.resolve(individualJsonName);
|
|
||||||
writeJsonFile(jsonDto, individualJsonPath);
|
|
||||||
log.info("개별 JSON 생성: {}", individualJsonName);
|
|
||||||
|
|
||||||
// 3-2. 개별 ZIP 파일명 생성
|
try {
|
||||||
|
// 3-1. PTH 파일명에서 Epoch과 메트릭 타입 추출
|
||||||
|
BestPthInfo pthInfo = parsePthFileName(pthFileName);
|
||||||
|
if (pthInfo == null) {
|
||||||
|
log.warn("PTH 파일명 파싱 실패, 기본 JSON 사용: {}", pthFileName);
|
||||||
|
// 파싱 실패 시 기본 메트릭 정보 사용
|
||||||
|
ModelMetricJsonDto defaultJsonDto =
|
||||||
|
modelTestMetricsJobCoreService.getTestMetricPackingInfo(modelInfo.getModelId());
|
||||||
|
individualJsonPath = createIndividualJson(responsePath, pthFileName, defaultJsonDto);
|
||||||
|
} else {
|
||||||
|
// 3-2. 해당 Epoch의 메트릭 정보 조회
|
||||||
|
log.debug(
|
||||||
|
"PTH 파일 파싱 결과: epoch={}, metricType={}",
|
||||||
|
pthInfo.getEpoch(),
|
||||||
|
pthInfo.getMetricType());
|
||||||
|
|
||||||
|
ModelMetricJsonDto individualJsonDto =
|
||||||
|
modelTestMetricsJobCoreService.getMetricsByEpoch(
|
||||||
|
modelInfo.getModelId(), pthInfo.getEpoch(), pthInfo.getMetricType());
|
||||||
|
|
||||||
|
if (individualJsonDto == null) {
|
||||||
|
log.warn("Epoch={} 메트릭 정보 없음, 기본 JSON 사용: {}", pthInfo.getEpoch(), pthFileName);
|
||||||
|
ModelMetricJsonDto defaultJsonDto =
|
||||||
|
modelTestMetricsJobCoreService.getTestMetricPackingInfo(modelInfo.getModelId());
|
||||||
|
individualJsonPath = createIndividualJson(responsePath, pthFileName, defaultJsonDto);
|
||||||
|
} else {
|
||||||
|
// 3-3. 개별 JSON 파일 생성
|
||||||
|
individualJsonPath =
|
||||||
|
createIndividualJson(responsePath, pthFileName, individualJsonDto);
|
||||||
|
log.info(
|
||||||
|
" Epoch별 JSON 생성: epoch={}, file={}",
|
||||||
|
pthInfo.getEpoch(),
|
||||||
|
individualJsonPath.getFileName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3-4. 개별 ZIP 파일명 생성
|
||||||
// 형식: {modelVersion}.{pthFileNameWithoutExt}.zip
|
// 형식: {modelVersion}.{pthFileNameWithoutExt}.zip
|
||||||
// 예: G1_000001.best_epoch_3.zip
|
// 예: G1_000001.best_fscore_5.zip, G1_000001.best_precision_7.zip
|
||||||
String pthFileNameWithoutExt = pthFileName.replace(".pth", "");
|
String pthFileNameWithoutExt = pthFileName.replace(".pth", "");
|
||||||
|
|
||||||
|
// modelVersion 조회 (JSON에서 추출)
|
||||||
|
ModelTestFileName fileInfo =
|
||||||
|
modelTestMetricsJobCoreService.findModelTestFileNames(modelInfo.getModelId());
|
||||||
String individualZipName =
|
String individualZipName =
|
||||||
jsonDto.getModelVersion() + "." + pthFileNameWithoutExt + ".zip";
|
fileInfo.getModelVersion() + "." + pthFileNameWithoutExt + ".zip";
|
||||||
Path individualZipPath = responsePath.resolve(individualZipName);
|
Path individualZipPath = responsePath.resolve(individualZipName);
|
||||||
|
|
||||||
// 3-3. ZIP에 포함될 파일 목록 구성
|
// 3-5. ZIP에 포함될 파일 목록 구성
|
||||||
List<Path> zipFileList = new ArrayList<>();
|
List<Path> zipFileList = new ArrayList<>();
|
||||||
zipFileList.add(bestPthFile); // best*.pth 파일
|
zipFileList.add(bestPthFile); // best*.pth 파일
|
||||||
zipFileList.add(individualJsonPath); // 개별 JSON 파일
|
zipFileList.add(individualJsonPath); // 개별 JSON 파일
|
||||||
zipFileList.add(ptFile); // PT 파일
|
zipFileList.add(ptFile); // PT 파일
|
||||||
|
|
||||||
// model_config.py 파일이 있으면 추가
|
// model_config.py 파일이 있으면 추가
|
||||||
Path modelConfigPath = responsePath.resolve("model_config.py");
|
|
||||||
if (Files.exists(modelConfigPath)) {
|
if (Files.exists(modelConfigPath)) {
|
||||||
zipFileList.add(modelConfigPath);
|
zipFileList.add(modelConfigPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3-4. 개별 ZIP 생성
|
// 3-6. 개별 ZIP 생성
|
||||||
zipFiles(zipFileList, individualZipPath);
|
zipFiles(zipFileList, individualZipPath);
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
@@ -274,18 +322,112 @@ public class ModelTestMetricsJobService {
|
|||||||
pthFileName,
|
pthFileName,
|
||||||
Files.size(individualZipPath));
|
Files.size(individualZipPath));
|
||||||
|
|
||||||
} catch (IOException e) {
|
// 3-7. 임시 JSON 파일 정리
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(individualJsonPath);
|
||||||
|
log.debug("임시 JSON 파일 삭제: {}", individualJsonPath.getFileName());
|
||||||
|
} catch (IOException deleteEx) {
|
||||||
|
log.warn("임시 JSON 파일 삭제 실패: {}", individualJsonPath, deleteEx);
|
||||||
|
}
|
||||||
|
|
||||||
|
successCount++;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
log.error("개별 ZIP 생성 실패: pthFile={}", pthFileName, e);
|
log.error("개별 ZIP 생성 실패: pthFile={}", pthFileName, e);
|
||||||
|
|
||||||
|
// 실패한 임시 JSON 파일도 정리 시도
|
||||||
|
if (individualJsonPath != null) {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(individualJsonPath);
|
||||||
|
} catch (IOException deleteEx) {
|
||||||
|
log.warn("실패한 임시 JSON 파일 삭제 실패: {}", individualJsonPath, deleteEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 개별 ZIP 실패는 전체 프로세스를 중단하지 않음
|
// 개별 ZIP 실패는 전체 프로세스를 중단하지 않음
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("=== 개별 best*.pth ZIP 파일 생성 완료: 총 {}개 ===", bestPthFiles.size());
|
log.info("=== 개별 best*.pth ZIP 파일 생성 완료: 성공 {}/{}개 ===", successCount, bestPthFiles.size());
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("개별 ZIP 생성 중 오류 발생", e);
|
log.error("개별 ZIP 생성 중 오류 발생", e);
|
||||||
// 에러 발생해도 기존 ZIP은 이미 생성되었으므로 예외를 던지지 않음
|
// 에러 발생해도 기존 ZIP은 이미 생성되었으므로 예외를 던지지 않음
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return successCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PTH 파일명에서 Epoch과 메트릭 타입을 추출
|
||||||
|
*
|
||||||
|
* <p>지원되는 파일명 패턴:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>best_{metricType}_{epoch}.pth (예: best_fscore_5.pth)
|
||||||
|
* <li>best_epoch_{epoch}.pth (예: best_epoch_10.pth)
|
||||||
|
* <li>best_changed_{metricType}_{epoch}.pth (예: best_changed_fscore_5.pth)
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param fileName PTH 파일명
|
||||||
|
* @return 파싱된 PTH 정보, 실패 시 null
|
||||||
|
*/
|
||||||
|
private BestPthInfo parsePthFileName(String fileName) {
|
||||||
|
try {
|
||||||
|
// 패턴 1: best_changed_{metricType}_{epoch}.pth
|
||||||
|
Pattern pattern1 = Pattern.compile("best_changed_([a-z_]+)_(\\d+)\\.pth");
|
||||||
|
Matcher matcher1 = pattern1.matcher(fileName);
|
||||||
|
if (matcher1.matches()) {
|
||||||
|
String metricType = matcher1.group(1); // "fscore", "precision", "recall"
|
||||||
|
Integer epoch = Integer.parseInt(matcher1.group(2));
|
||||||
|
Path filePath = Paths.get(fileName);
|
||||||
|
return new BestPthInfo(fileName, metricType, epoch, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 패턴 2: best_{metricType}_{epoch}.pth
|
||||||
|
Pattern pattern2 = Pattern.compile("best_([a-z_]+)_(\\d+)\\.pth");
|
||||||
|
Matcher matcher2 = pattern2.matcher(fileName);
|
||||||
|
if (matcher2.matches()) {
|
||||||
|
String metricType = matcher2.group(1); // "fscore", "precision", "recall"
|
||||||
|
Integer epoch = Integer.parseInt(matcher2.group(2));
|
||||||
|
Path filePath = Paths.get(fileName);
|
||||||
|
return new BestPthInfo(fileName, metricType, epoch, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 패턴 3: best_epoch_{epoch}.pth (메트릭 타입 없음)
|
||||||
|
Pattern pattern3 = Pattern.compile("best_epoch_(\\d+)\\.pth");
|
||||||
|
Matcher matcher3 = pattern3.matcher(fileName);
|
||||||
|
if (matcher3.matches()) {
|
||||||
|
Integer epoch = Integer.parseInt(matcher3.group(1));
|
||||||
|
Path filePath = Paths.get(fileName);
|
||||||
|
// 메트릭 타입을 "epoch"로 설정 (기본값)
|
||||||
|
return new BestPthInfo(fileName, "epoch", epoch, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("알 수 없는 PTH 파일명 패턴: {}", fileName);
|
||||||
|
return null;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("PTH 파일명 파싱 중 오류: {}", fileName, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 개별 JSON 파일 생성
|
||||||
|
*
|
||||||
|
* @param responsePath Response 디렉토리 경로
|
||||||
|
* @param pthFileName PTH 파일명
|
||||||
|
* @param jsonDto JSON 메타데이터
|
||||||
|
* @return 생성된 JSON 파일 경로
|
||||||
|
* @throws IOException JSON 쓰기 실패 시
|
||||||
|
*/
|
||||||
|
private Path createIndividualJson(
|
||||||
|
Path responsePath, String pthFileName, ModelMetricJsonDto jsonDto) throws IOException {
|
||||||
|
String individualJsonName = pthFileName.replace(".pth", ".json");
|
||||||
|
Path individualJsonPath = responsePath.resolve(individualJsonName);
|
||||||
|
writeJsonFile(jsonDto, individualJsonPath);
|
||||||
|
return individualJsonPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeJsonFile(Object data, Path outputPath) throws IOException {
|
private void writeJsonFile(Object data, Path outputPath) throws IOException {
|
||||||
|
|||||||
Reference in New Issue
Block a user