Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.rte_france.antares.datamanager_back.controller;

import com.rte_france.antares.datamanager_back.dto.TrajectoryDTO;
import com.rte_france.antares.datamanager_back.service.StStorage.StStorageFileProcessorService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

import static com.rte_france.antares.datamanager_back.mapper.TrajectoryMapper.toTrajectoryDTO;

@Slf4j
@RestController
@RequestMapping("/v1/trajectory")
@RequiredArgsConstructor
public class StStorageController {

private final StStorageFileProcessorService stStorageFileProcessorService;

@Operation(summary = "import sts trajectory to database ")
@PostMapping("/st-storage")
public ResponseEntity<TrajectoryDTO> uploadThermalCapacityTrajectory(@RequestParam("area") String area, // FR, // GB, DE, IT, ES, PT, BE, NL, LU, CH //OTHER
@RequestParam(value = "technology", required = false) String technology, @RequestParam("trajectoryToUse") @Size(max = 40, message = "Trajectory name cannot exceed 40 characters") String trajectoryToUse, @RequestParam("horizon") @Pattern(regexp = "^\\d{4}-\\d{4}$") @Parameter(description = "example of horizon : 2020-2021") String horizon, @RequestParam("studyId") Integer studyId, @RequestParam("isCivilYear") boolean isCivilYear) throws IOException {

return new ResponseEntity<>(toTrajectoryDTO
(stStorageFileProcessorService.processStStorageFile(trajectoryToUse, horizon, studyId, isCivilYear, area, technology)),
HttpStatus.CREATED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.rte_france.antares.datamanager_back.repository;

import com.rte_france.antares.datamanager_back.repository.model.StStorageEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StStorageRepository extends JpaRepository<StStorageEntity, Integer> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.rte_france.antares.datamanager_back.service.StStorage;


import com.rte_france.antares.datamanager_back.repository.model.TrajectoryEntity;

import java.io.IOException;

public interface StStorageFileProcessorService {

TrajectoryEntity processStStorageFile(String trajectoryToUse, String horizon, Integer studyId, boolean isCivilYear, String area, String technology) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package com.rte_france.antares.datamanager_back.service.StStorage;

import com.rte_france.antares.datamanager_back.configuration.AntaressDataManagerProperties;
import com.rte_france.antares.datamanager_back.dto.TrajectoryType;
import com.rte_france.antares.datamanager_back.exception.BusinessException;
import com.rte_france.antares.datamanager_back.repository.TrajectoryRepository;
import com.rte_france.antares.datamanager_back.repository.model.StStorageEntity;
import com.rte_france.antares.datamanager_back.repository.model.TrajectoryEntity;
import com.rte_france.antares.datamanager_back.service.common.impl.TrajectoryServiceImpl;
import com.rte_france.antares.datamanager_back.service.user.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

import static com.rte_france.antares.datamanager_back.service.thermal.impl.ThermalFileProcessorServiceImpl.UNKNOWN_USER;
import static com.rte_france.antares.datamanager_back.util.Utils.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class StStorageFileProcessorServiceImpl implements StStorageFileProcessorService {

private final AntaressDataManagerProperties antaressDataManagerProperties;
private final TrajectoryServiceImpl trajectoryService;
private final TrajectoryRepository trajectoryRepository;
private final UserService userService;

@Transactional
@Override
public TrajectoryEntity processStStorageFile(String trajectoryToUse, String horizon, Integer studyId, boolean isCivilYear, String areaParam, String technology) throws IOException {
final String horizonYear = horizon.split("-")[1];
final String stsTrajectoryPrefix = "cluster_" + technology + "_";
if (!trajectoryToUse.startsWith(stsTrajectoryPrefix)) {
throw BusinessException.builder().message(" {0} Trajectory name must start with : {1} ")
.errorMessageArguments(List.of(trajectoryToUse, stsTrajectoryPrefix))
.build();
}

Path trajectoryFilePath = trajectoryService.getTrajectoryFilePath(TrajectoryType.STS, trajectoryToUse, technology);

List<StStorageEntity> stStorageEntityList = buildStStorageLines(horizonYear, trajectoryFilePath, areaParam, technology);
if (stStorageEntityList.isEmpty()) {
throw BusinessException.builder()
.message("No ST Storage data found in the file for horizon: " + horizonYear)
.build();
}

TrajectoryEntity trajectoryEntity = buildStStorageTrajectory(trajectoryFilePath, horizonYear, areaParam, technology);

stStorageEntityList.forEach(thermalEntity -> thermalEntity.setTrajectory(trajectoryEntity));
trajectoryEntity.setStStorageEntities(stStorageEntityList);/**/
trajectoryEntity.setHorizon(horizon);
return trajectoryRepository.save(trajectoryEntity);
}

private TrajectoryEntity buildStStorageTrajectory(Path trajectoryFilePath, String horizon, String areaParam, String technology) throws IOException {

String createdBy = userService.getCurrentUserDetails() != null ? userService.getCurrentUserDetails().getNni() : UNKNOWN_USER;
Optional<TrajectoryEntity> existingOpt = trajectoryRepository.findFirstByFileNameAndTypeAndHorizonAndAreaAndTechnologyOrderByVersionDesc(
getFileNameWithoutExtensionAndWithoutPrefix(trajectoryFilePath.getFileName().toString(), TrajectoryType.STS.name()),
TrajectoryType.STS.name(), horizon, areaParam, technology);

TrajectoryEntity trajectory;
if (existingOpt.isPresent() && checkTrajectoryVersion(trajectoryFilePath, existingOpt.get())) {
// Same identifiers but different checksum -> version +1
trajectory = buildTrajectory(trajectoryFilePath, existingOpt.get().getVersion(), horizon, createdBy, TrajectoryType.STS, areaParam, technology);
} else {
// No existing or not same file -> new trajectory with version 1
trajectory = buildTrajectory(trajectoryFilePath, 0, horizon, createdBy, TrajectoryType.STS, areaParam, technology);
}

return trajectory;
}

private List<StStorageEntity> buildStStorageLines(String horizon, Path trajectoryFilePath, String areaParam, String technology) throws IOException {
List<StStorageEntity> results = new ArrayList<>();

try (InputStream inputStream = Files.newInputStream(trajectoryFilePath);
Workbook workbook = WorkbookFactory.create(inputStream)) {
Sheet sheet = workbook.getNumberOfSheets() > 0 ? workbook.getSheet(horizon) : null;
if (sheet == null) {
throw BusinessException.builder()
.message("No sheet found in file")
.build();
}

boolean firstRow = true;
for (Row row : sheet) {
if (firstRow) {
firstRow = false;
continue;
} // skip header
if (isRowEmpty(row)) continue;

StStorageEntity stStorageEntity = new StStorageEntity();
String rowArea = row.getCell( 0).getStringCellValue();
String clusterName = row.getCell( 1).getStringCellValue();

if (rowArea == null || rowArea.isEmpty() || Objects.requireNonNull(clusterName).isEmpty()) continue;

if (!rowArea.equals(areaParam) && !areaParam.equals(OTHERS_AREA)) {
continue;
}

Boolean series = getBooleanCell(row, 11);
Path stsTs = buildStsTimeSeriesPath(trajectoryFilePath, rowArea, technology, clusterName);

if (isTsFileMissing(series, stsTs)) continue;

stStorageEntity.setArea(rowArea);
stStorageEntity.setName(clusterName);
stStorageEntity.setGroupe(row.getCell( 2).getStringCellValue());
stStorageEntity.setInjection(BigDecimal.valueOf(row.getCell(3).getNumericCellValue()));
stStorageEntity.setWithdrawal(BigDecimal.valueOf(row.getCell(4).getNumericCellValue()));
stStorageEntity.setStorage(BigDecimal.valueOf(row.getCell(5).getNumericCellValue()));
stStorageEntity.setEfficiencyInjection(BigDecimal.valueOf(row.getCell(6).getNumericCellValue()));
stStorageEntity.setEfficiencyWithdrawal((int)(row.getCell(7).getNumericCellValue()));
stStorageEntity.setInitialLevel(BigDecimal.valueOf(row.getCell(8).getNumericCellValue()));
stStorageEntity.setInitialLevelOptim(getBooleanCell(row, 9));
stStorageEntity.setEnabled(getBooleanCell(row, 10));
stStorageEntity.setSeries(series);
stStorageEntity.setConstraintsFlag(getBooleanCell(row, 12));

results.add(stStorageEntity);
}
}
return results;
}

private Path buildStsTimeSeriesPath(Path trajectoryFilePath, String areaParam, String technology, String clusterName) {
// \\STS\<techno>\series\<trajectoire>\<nom du cluster>\<area>\
return Path.of(antaressDataManagerProperties.getNasDirectory())
.resolve(antaressDataManagerProperties.getTrajectoryFilePath())
.resolve(antaressDataManagerProperties.getStsDirectory())
.resolve(technology)
.resolve("series")
.resolve(getFileNameWithoutExtensionAndWithoutPrefix(trajectoryFilePath.getFileName().toString(), TrajectoryType.STS.name()))
.resolve(clusterName)
.resolve(areaParam)
.normalize();
}

private static boolean isTsFileMissing(Boolean series, Path stsTs) {
if (Boolean.TRUE.equals(series)) {
if (!Files.exists(stsTs) || !Files.isDirectory(stsTs)) {
log.warn("ST Storage series directory not found: {}", stsTs);
return true;
}
File[] files = stsTs.toFile().listFiles();
if (files == null || files.length == 0) {
log.warn("Unable to list files in ST Storage series directory: {}", stsTs);
return true;
}

String[] required = {
"inflows.xlsx",
"lower_curve.xlsx",
"Pmax_injection.xlsx",
"Pmax_soutirage.xlsx",
"upper_curve.xlsx"
};
boolean hasAll = true;
for (String req : required) {
boolean found = Arrays.stream(files).anyMatch(f -> f.getName().equalsIgnoreCase(req));
if (!found) {
hasAll = false;
break;
}
}
if (!hasAll) {
log.warn("ST Storage series directory missing required files: {}", stsTs);
return true;
}
}
return false;
}


private boolean isRowEmpty(Row row) {
for (int c = 0; c <= 12; c++) {
Cell cell = row.getCell(c);
if (cell != null && cell.getCellType() != CellType.BLANK) return false;
}
return true;
}

private Boolean getBooleanCell(Row row, int idx) {
Cell cell = row.getCell(idx);
if (cell == null) return null;
if (cell.getCellType() == CellType.BOOLEAN) return cell.getBooleanCellValue();
String s = cell.toString().trim().toLowerCase(Locale.ROOT);
if (s.isEmpty()) return null;
return "true".equals(s) || "1".equals(s) || "yes".equals(s) || "y".equals(s);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class Utils {
private static final String THERMAL_SPECIFIC_PREFIX = "specific_param_";
private static final String THERMAL_ECONOMIC_PREFIX = "economic_param_";
private static final String THERMAL_ECONOMIC_COST_PREFIX = "costs_";
private static final String STS_PREFIX = "cluster_";

public static final String OTHERS_AREA = "OTHERS";

Expand Down Expand Up @@ -141,6 +142,7 @@ public static TrajectoryEntity buildTrajectory(Path path, int versionTrajectory,
.horizon(horizon)
.area(area)
.technology(technology)
.type(trajectoryType.name())
.build();
}

Expand Down Expand Up @@ -202,6 +204,11 @@ private static String extractChecksum(List<ThermalModulationParameterEntity> ent
.orElse(null);
}

private static String removeClusterPrefix(String fileName) {
// enlève un préfixe du type "cluster_<techno>_" au début du nom, insensible à la casse
return fileName.replaceFirst("(?i)^cluster_[^_]+_", "");
}

public static String getFileNameWithoutExtensionAndWithoutPrefix(String fileName, String trajectoryType) {
Objects.requireNonNull(fileName);
if (fileName.isBlank()) {
Expand All @@ -225,16 +232,21 @@ public static String getFileNameWithoutExtensionAndWithoutPrefix(String fileName
} else {
prefix = "";
}
if (!prefix.isEmpty() && fileName.toLowerCase().startsWith(prefix)) {

if (Objects.equals(trajectoryType, TrajectoryType.STS.toString())) {
fileName = removeClusterPrefix(fileName);
} else if (!prefix.isEmpty() && fileName.toLowerCase().startsWith(prefix)) {
fileName = fileName.substring(prefix.length());
}

int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex <= 0) {
return fileName;
}
return fileName.substring(0, lastDotIndex);
}


/**
* Finds a sheet in the provided workbook that matches the given horizon name.
* The search attempts an exact match with the horizon name if it is not null or blank.
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application-localhost.properties
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ antares.datamanager.thermal.capacity.directory=thermal_capacity
antares.datamanager.thermal.parameter.directory=thermal_parameter
antares.datamanager.thermal.parameter.modulation.directory=thermal_parameter/param_modulation
antares.datamanager.load.directory=load
antares.datamanager.sts.directory=STS
antares.datamanager.load.output.directory=output/load_arrows
antares.datamanager.thermal.modulation.output.directory=output/thermal_modulation_arrow
antares.datamanager.study.json.output.directory=output/study_json
Expand Down
Loading