daniel 작업본 추가

This commit is contained in:
2026-04-09 16:26:38 +09:00
parent ecca537670
commit 449e1dc142
2 changed files with 268 additions and 80 deletions

View File

@@ -454,34 +454,54 @@ 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.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, "디렉토리 경로 확인 중 오류가 발생했습니다.");
} }
log.info("디렉토리 발견: basePath={}", baseDir.toString());
// 요청에서 도메인 정보 추출 // 요청에서 도메인 정보 추출
String scheme = request.getScheme(); // http 또는 https String scheme = request.getScheme(); // http 또는 https
String serverName = request.getServerName(); // localhost 또는 도메인 String serverName = request.getServerName(); // localhost 또는 도메인
@@ -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,38 +533,62 @@ 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) {
String fileName = file.getFileName().toString(); try {
long fileSize = Files.size(file); String fileName = file.getFileName().toString();
totalSize += fileSize; long fileSize = Files.size(file);
totalSize += fileSize;
// 파일명에서 버전 추출 // 파일명에서 버전 추출
String version = extractVersionFromZipFileName(fileName); String version = extractVersionFromZipFileName(fileName);
boolean isCurrent = version.equals(modelInfo.getModelVer()); boolean isCurrent = version.equals(modelInfo.getModelVer());
// 전체 도메인 URL 포함한 다운로드 링크 생성 // 전체 도메인 URL 포함한 다운로드 링크 생성
String downloadUrl = baseUrl + "/api/models/download/" + uuid + "?file=" + fileName; String downloadUrl = baseUrl + "/api/models/download/" + uuid + "?file=" + fileName;
zipFiles.add( zipFiles.add(
ZipFileInfo.builder() ZipFileInfo.builder()
.fileName(fileName) .fileName(fileName)
.filePath(file.toString()) .filePath(file.toString())
.version(version) .version(version)
.fileSize(fileSize) .fileSize(fileSize)
.fileSizeFormatted(formatFileSize(fileSize)) .fileSizeFormatted(formatFileSize(fileSize))
.lastModified( .lastModified(
Files.getLastModifiedTime(file).toInstant().atZone(ZoneId.systemDefault())) Files.getLastModifiedTime(file).toInstant().atZone(ZoneId.systemDefault()))
.isCurrent(isCurrent) .isCurrent(isCurrent)
.downloadUrl(downloadUrl) .downloadUrl(downloadUrl)
.build()); .build());
log.debug(
"ZIP 파일 정보 추가: fileName={}, size={}, version={}, isCurrent={}",
fileName,
formatFileSize(fileSize),
version,
isCurrent);
} catch (IOException e) {
log.warn("ZIP 파일 정보 조회 실패 (건너뜀): file={}", file.getFileName(), e);
// 개별 파일 실패 시 전체 프로세스를 중단하지 않고 계속 진행
} catch (Exception e) {
log.error("ZIP 파일 정보 처리 중 예상치 못한 오류 (건너뜀): file={}", file.getFileName(), e);
// 예상치 못한 오류도 전체 프로세스를 중단하지 않음
}
} }
log.info("ZIP 파일 {}개 발견", zipFiles.size()); log.info(" ZIP 파일 목록 조회 완료: 총 {}개 파일, 전체 크기={}", zipFiles.size(), formatFileSize(totalSize));
} catch (IOException e) { } catch (IOException e) {
log.error("ZIP 파일 목록 조회 실패: {}", baseDir, 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()

View File

@@ -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");
List<Path> files = new ArrayList<>();
try (Stream<Path> s = Files.list(responsePath)) {
files.addAll(
s.filter(Files::isRegularFile)
.filter(p -> targetNames.contains(p.getFileName().toString()))
.collect(Collectors.toList()));
} catch (IOException e) {
throw new RuntimeException(e);
}
try (Stream<Path> s = Files.list(Path.of(ptPathDir))) {
files.addAll(
s.filter(Files::isRegularFile)
.limit(1) // yolov8_6th-6m.pt 파일 1개만
.collect(Collectors.toList()));
} catch (IOException e) {
throw new RuntimeException(e);
}
try { try {
zipFiles(files, zipPath); List<Path> files = new ArrayList<>();
try (Stream<Path> s = Files.list(responsePath)) {
files.addAll(
s.filter(Files::isRegularFile)
.filter(p -> targetNames.contains(p.getFileName().toString()))
.collect(Collectors.toList()));
}
// PT 파일 정확하게 조회
Path ptFile = Paths.get(ptPathDir, ptFileName);
if (Files.exists(ptFile)) {
files.add(ptFile);
} else {
log.warn("PT 파일을 찾을 수 없습니다: {}", ptFile);
throw new IOException("PT 파일 누락: " + ptFile);
}
// 기본 ZIP 생성
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 {