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,63 @@
/**
Comment thread
KoloMenek marked this conversation as resolved.
* Copyright (c) 2026, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.modification.server.entities.equipment.modification;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.gridsuite.modification.dto.BusbarSectionVMeasurementInfos;
import org.gridsuite.modification.server.entities.equipment.modification.attribute.BooleanModificationEmbedded;
import org.gridsuite.modification.server.entities.equipment.modification.attribute.DoubleModificationEmbedded;

import java.util.UUID;

import static org.gridsuite.modification.server.entities.equipment.modification.attribute.IAttributeModificationEmbeddable.toAttributeModification;

/**
* @author Mohamed Ben Rejeb <mohamed.ben-rejeb at rte-france.com>
*/
@NoArgsConstructor
@Getter
@Entity
@Table(name = "busbar_section_v_measurement")
public class BusbarSectionVMeasurementEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private UUID id;

@Column(name = "busbar_section_id")
private String busbarSectionId;

@Embedded
@AttributeOverrides(value = {
@AttributeOverride(name = "value", column = @Column(name = "v_measurement_value")),
@AttributeOverride(name = "opType", column = @Column(name = "v_measurement_value_op"))
})
private DoubleModificationEmbedded vMeasurementValue;

@Embedded
@AttributeOverrides(value = {
@AttributeOverride(name = "value", column = @Column(name = "v_measurement_validity")),
@AttributeOverride(name = "opType", column = @Column(name = "v_measurement_validity_op"))
})
private BooleanModificationEmbedded vMeasurementValidity;

public BusbarSectionVMeasurementEntity(BusbarSectionVMeasurementInfos infos) {
this.busbarSectionId = infos.getBusbarSectionId();
this.vMeasurementValue = infos.getVMeasurementValue() != null ? new DoubleModificationEmbedded(infos.getVMeasurementValue()) : null;
this.vMeasurementValidity = infos.getVMeasurementValidity() != null ? new BooleanModificationEmbedded(infos.getVMeasurementValidity()) : null;
}

public BusbarSectionVMeasurementInfos toInfos() {
return BusbarSectionVMeasurementInfos.builder()
.busbarSectionId(busbarSectionId)
.vMeasurementValue(toAttributeModification(vMeasurementValue))
.vMeasurementValidity(toAttributeModification(vMeasurementValidity))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@
import org.gridsuite.modification.server.entities.equipment.modification.attribute.DoubleModificationEmbedded;
import org.gridsuite.modification.server.entities.equipment.modification.attribute.IAttributeModificationEmbeddable;

import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;

import static org.gridsuite.modification.dto.AttributeModification.toAttributeModification;

/**
Expand Down Expand Up @@ -72,6 +68,11 @@ public class VoltageLevelModificationEntity extends BasicEquipmentModificationEn
})
private DoubleModificationEmbedded ipMax;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name = "voltage_level_modification_id", nullable = false,
foreignKey = @ForeignKey(name = "busbar_section_v_measurement_vl_id_fk"))
private List<BusbarSectionVMeasurementEntity> busbarSectionVMeasurements;

public VoltageLevelModificationEntity(VoltageLevelModificationInfos voltageLevelModificationInfos) {
super(voltageLevelModificationInfos);
assignAttributes(voltageLevelModificationInfos);
Expand All @@ -89,6 +90,15 @@ private void assignAttributes(VoltageLevelModificationInfos voltageLevelModifica
this.highVoltageLimit = voltageLevelModificationInfos.getHighVoltageLimit() != null ? new DoubleModificationEmbedded(voltageLevelModificationInfos.getHighVoltageLimit()) : null;
this.ipMin = voltageLevelModificationInfos.getIpMin() != null ? new DoubleModificationEmbedded(voltageLevelModificationInfos.getIpMin()) : null;
this.ipMax = voltageLevelModificationInfos.getIpMax() != null ? new DoubleModificationEmbedded(voltageLevelModificationInfos.getIpMax()) : null;
if (this.busbarSectionVMeasurements == null) {
this.busbarSectionVMeasurements = new ArrayList<>();
}
this.busbarSectionVMeasurements.clear();
if (!CollectionUtils.isEmpty(voltageLevelModificationInfos.getBusbarSectionVMeasurements())) {
voltageLevelModificationInfos.getBusbarSectionVMeasurements().stream()
.map(BusbarSectionVMeasurementEntity::new)
.forEach(this.busbarSectionVMeasurements::add);
}
}

@Override
Expand All @@ -110,6 +120,10 @@ public VoltageLevelModificationInfos toModificationInfos() {
.highVoltageLimit(IAttributeModificationEmbeddable.toAttributeModification(getHighVoltageLimit()))
.ipMin(IAttributeModificationEmbeddable.toAttributeModification(this.getIpMin()))
.ipMax(IAttributeModificationEmbeddable.toAttributeModification(this.getIpMax()))
.busbarSectionVMeasurements(CollectionUtils.isEmpty(busbarSectionVMeasurements) ? null :
busbarSectionVMeasurements.stream()
.map(BusbarSectionVMeasurementEntity::toInfos)
.toList())
// properties
.properties(CollectionUtils.isEmpty(getProperties()) ? null :
getProperties().stream()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="benrejebmoh (generated)" id="1779788708250-3">
<createTable tableName="busbar_section_v_measurement">
<column name="id" type="UUID">
<constraints nullable="false" primaryKey="true" primaryKeyName="busbar_section_v_measurementPK"/>
</column>
<column name="busbar_section_id" type="VARCHAR(255)"/>
<column name="v_measurement_validity_op" type="VARCHAR(255)"/>
<column name="v_measurement_validity" type="BOOLEAN"/>
<column name="v_measurement_value_op" type="VARCHAR(255)"/>
<column name="v_measurement_value" type="FLOAT(53)"/>
<column name="voltage_level_modification_id" type="UUID">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
<changeSet author="benrejebmoh (generated)" id="1779788708250-6">
<addForeignKeyConstraint baseColumnNames="voltage_level_modification_id" baseTableName="busbar_section_v_measurement" constraintName="busbar_section_v_measurement_vl_id_fk" deferrable="false" initiallyDeferred="false" referencedColumnNames="id" referencedTableName="voltage_level_modification" validate="true"/>
</changeSet>
Comment on lines +4 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce busbar measurement row uniqueness and required target busbar.

The schema currently allows multiple rows for the same (voltage_level_modification_id, busbar_section_id) and allows busbar_section_id to be null. That can break deterministic upsert behavior and permit invalid records.

Suggested migration hardening
     <changeSet author="benrejebmoh (generated)" id="1779788708250-3">
         <createTable tableName="busbar_section_v_measurement">
@@
-            <column name="busbar_section_id" type="VARCHAR(255)"/>
+            <column name="busbar_section_id" type="VARCHAR(255)">
+                <constraints nullable="false"/>
+            </column>
@@
         </createTable>
+        <addUniqueConstraint
+                tableName="busbar_section_v_measurement"
+                columnNames="voltage_level_modification_id,busbar_section_id"
+                constraintName="uk_busbar_section_v_measurement_vl_bbs"/>
     </changeSet>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/db/changelog/changesets/changelog_20260526T094439Z.xml`
around lines 4 - 20, Add a NOT NULL constraint to busbar_section_id and add a
unique constraint on (voltage_level_modification_id, busbar_section_id) for the
busbar_section_v_measurement table: update the createTable for
busbar_section_v_measurement to mark column busbar_section_id as
nullable="false" and add an addUniqueConstraint changeSet (or inline constraint)
for columns voltage_level_modification_id and busbar_section_id (name it e.g.
busbar_section_v_measurement_vl_bs_uk) so duplicate rows for the same
voltage_level_modification_id + busbar_section_id are prevented and the target
busbar is required.

</databaseChangeLog>
3 changes: 3 additions & 0 deletions src/main/resources/db/changelog/db.changelog-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,6 @@ databaseChangeLog:
- include:
file: changesets/changelog_20260522T132309Z.xml
relativeToChangelogFile: true
- include:
file: changesets/changelog_20260526T094439Z.xml
relativeToChangelogFile: true
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
package org.gridsuite.modification.server.modifications;

import com.fasterxml.jackson.core.type.TypeReference;
import com.powsybl.iidm.network.BusbarSection;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.iidm.network.extensions.IdentifiableShortCircuit;
import com.powsybl.iidm.network.extensions.IdentifiableShortCircuitAdder;
import com.powsybl.iidm.network.extensions.Measurement;
import com.powsybl.iidm.network.extensions.Measurements;
import org.gridsuite.modification.NetworkModificationException;
import org.gridsuite.modification.dto.*;
import org.gridsuite.modification.server.utils.NetworkCreation;
Expand All @@ -19,10 +22,13 @@
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.ResultActions;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

import static org.gridsuite.modification.NetworkModificationException.Type.MODIFY_VOLTAGE_LEVEL_ERROR;
import static org.gridsuite.modification.server.report.NetworkModificationServerReportResourceBundle.ERROR_MESSAGE_KEY;
import static org.gridsuite.modification.server.utils.TestUtils.assertLogMessage;
Expand All @@ -39,6 +45,8 @@
class VoltageLevelModificationTest extends AbstractNetworkModificationTest {
private static final String PROPERTY_NAME = "property-name";
private static final String PROPERTY_VALUE = "property-value";
private static final Double MEASUREMENT_V_VALUE = 400.0;
private static final Boolean MEASUREMENT_V_VALID = true;

@Override
protected Network createNetwork(UUID networkUuid) {
Expand All @@ -57,6 +65,12 @@ protected ModificationInfos buildModification() {
.ipMax(new AttributeModification<>(0.8, OperationType.SET))
.ipMin(new AttributeModification<>(0.7, OperationType.SET))
.properties(List.of(FreePropertyInfos.builder().name(PROPERTY_NAME).value(PROPERTY_VALUE).build()))
.busbarSectionVMeasurements(List.of(
BusbarSectionVMeasurementInfos.builder()
.busbarSectionId("1.1")
.vMeasurementValue(new AttributeModification<>(MEASUREMENT_V_VALUE, OperationType.SET))
.vMeasurementValidity(new AttributeModification<>(MEASUREMENT_V_VALID, OperationType.SET))
.build()))
.build();
}

Expand All @@ -71,6 +85,12 @@ protected ModificationInfos buildModificationUpdate() {
.highVoltageLimit(new AttributeModification<>(55D, OperationType.SET))
.ipMax(new AttributeModification<>(0.9, OperationType.SET))
.ipMin(new AttributeModification<>(0.5, OperationType.SET))
.busbarSectionVMeasurements(List.of(
BusbarSectionVMeasurementInfos.builder()
.busbarSectionId("1.1")
.vMeasurementValue(new AttributeModification<>(380D, OperationType.SET))
.vMeasurementValidity(new AttributeModification<>(false, OperationType.SET))
.build()))
.build();
}

Expand All @@ -87,6 +107,18 @@ protected void assertAfterNetworkModificationCreation() {
assertEquals(0.8, identifiableShortCircuit.getIpMax(), 0);
assertEquals(0.7, identifiableShortCircuit.getIpMin(), 0);
assertEquals(PROPERTY_VALUE, getNetwork().getVoltageLevel("v1").getProperty(PROPERTY_NAME));
assertBusbarSectionMeasurement(getNetwork().getBusbarSection("1.1"), MEASUREMENT_V_VALUE, MEASUREMENT_V_VALID);
}

private void assertBusbarSectionMeasurement(BusbarSection bbs, double expectedValue, boolean expectedValid) {
assertNotNull(bbs);
Measurements<?> measurements = (Measurements<?>) bbs.getExtension(Measurements.class);
assertNotNull(measurements);
Collection<Measurement> voltageMeasurements = measurements.getMeasurements(Measurement.Type.VOLTAGE).stream().toList();
assertThat(voltageMeasurements).isNotEmpty().allSatisfy(m -> {
assertThat(m.getValue()).isEqualTo(expectedValue);
assertThat(m.isValid()).isEqualTo(expectedValid);
});
Comment on lines +117 to +121
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert single voltage measurement to actually validate upsert semantics.

Current checks allow duplicate measurements with the same value/validity. For the update-existing path, assert cardinality is exactly 1 before validating fields.

Suggested test tightening
-        Collection<Measurement> voltageMeasurements = measurements.getMeasurements(Measurement.Type.VOLTAGE).stream().toList();
-        assertThat(voltageMeasurements).isNotEmpty().allSatisfy(m -> {
+        Collection<Measurement> voltageMeasurements = measurements.getMeasurements(Measurement.Type.VOLTAGE).stream().toList();
+        assertThat(voltageMeasurements).hasSize(1).allSatisfy(m -> {
             assertThat(m.getValue()).isEqualTo(expectedValue);
             assertThat(m.isValid()).isEqualTo(expectedValid);
         });

Also applies to: 279-300

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/org/gridsuite/modification/server/modifications/VoltageLevelModificationTest.java`
around lines 117 - 121, The test currently allows multiple voltage measurements;
tighten it by asserting that the collection returned by
measurements.getMeasurements(Measurement.Type.VOLTAGE) has size 1 before
checking fields—e.g., replace or augment the current
assertThat(voltageMeasurements).isNotEmpty() check with an exact cardinality
assertion (sizeEquals/hasSize(1)) on voltageMeasurements, then validate
m.getValue() and m.isValid() for the single element; apply the same change to
the other occurrence around lines 279-300 to enforce upsert semantics.

}

@Override
Expand Down Expand Up @@ -243,6 +275,30 @@ void testSetIpMaxOnEquipmentWitOnlyIpMaxExtension() throws Exception {
assertEquals(targetIpMax, identifiableShortCircuit1.getIpMax(), 0);
}

@Test
void testBusbarSectionMeasurementUpdateExistingPath() throws Exception {
// Apply first modification: adds a new voltage measurement to the BBS (add path)
applyModification((VoltageLevelModificationInfos) buildModification());
assertBusbarSectionMeasurement(getNetwork().getBusbarSection("1.1"), MEASUREMENT_V_VALUE, MEASUREMENT_V_VALID);

// Apply second modification: updates the existing voltage measurement (update/upsert path)
final double updatedValue = 380.0;
final boolean updatedValidity = false;
VoltageLevelModificationInfos updateModif = VoltageLevelModificationInfos.builder()
.stashed(false)
.equipmentId("v1")
.busbarSectionVMeasurements(List.of(
BusbarSectionVMeasurementInfos.builder()
.busbarSectionId("1.1")
.vMeasurementValue(new AttributeModification<>(updatedValue, OperationType.SET))
.vMeasurementValidity(new AttributeModification<>(updatedValidity, OperationType.SET))
.build()))
.build();
applyModification(updateModif);

assertBusbarSectionMeasurement(getNetwork().getBusbarSection("1.1"), updatedValue, updatedValidity);
}

private void applyModification(VoltageLevelModificationInfos infos) throws Exception {
String body = getJsonBody(infos, null);
ResultActions mockMvcResultActions = mockMvc.perform(post(getNetworkModificationUri())
Expand Down
Loading