diff --git a/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt b/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt index b856271dcfc..03febf3e375 100644 --- a/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt +++ b/core/kt-osrd-rjs-parser/src/main/kotlin/fr/sncf/osrd/RawInfraRJSParser.kt @@ -751,19 +751,16 @@ fun parseRJSInfra(rjsInfra: RJSInfra): RawInfra { val trackSectionName = opPart.track val trackSectionOffset = Offset(opPart.position.meters) val props = mutableMapOf() - if (operationalPoint.extensions?.identifier != null) { - val identifier = operationalPoint.extensions!!.identifier!! - props["identifier"] = identifier.name - props["uic"] = identifier.uic.toString() - } - if (operationalPoint.extensions?.sncf != null) { - val sncf = operationalPoint.extensions!!.sncf!! - props["ci"] = sncf.ci.toString() - props["ch"] = sncf.ch - props["chShortLabel"] = sncf.chShortLabel - props["chLongLabel"] = sncf.chLongLabel - props["trigram"] = sncf.trigram - } + props["name"] = operationalPoint.name + props["uic"] = operationalPoint.uic.toString() + props["mainCode"] = operationalPoint.mainCode + props["countryCode"] = operationalPoint.countryCode + props["isPassengerStation"] = operationalPoint.isPassengerStation.toString() + if (operationalPoint.plc != null) props["plc"] = operationalPoint.plc!! + if (operationalPoint.secondaryCode != null) + props["secondaryCode"] = operationalPoint.secondaryCode!! + if (operationalPoint.secondaryName != null) + props["secondaryName"] = operationalPoint.secondaryName!! val weight = operationalPoint.weight if (weight != null) { props["weight"] = weight diff --git a/core/osrd-railjson/build.gradle b/core/osrd-railjson/build.gradle index 806f57f433f..fdd3f7d6066 100644 --- a/core/osrd-railjson/build.gradle +++ b/core/osrd-railjson/build.gradle @@ -13,7 +13,6 @@ java { dependencies { // PLEASE ADD AND UPDATE DEPENDENCIES USING libs.versions.toml - implementation project(':osrd-mp') // fast primitive collections diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSInfra.java b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSInfra.java index a43d91fee04..b2217850c29 100644 --- a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSInfra.java +++ b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSInfra.java @@ -22,7 +22,7 @@ public class RJSInfra { .build() .adapter(RJSInfra.class); - public static final transient String CURRENT_VERSION = "3.5.2"; + public static final transient String CURRENT_VERSION = "3.5.3"; /** The version of the infra format used */ public String version; diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPoint.java b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPoint.java deleted file mode 100644 index d02d6f35738..00000000000 --- a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPoint.java +++ /dev/null @@ -1,40 +0,0 @@ -package fr.sncf.osrd.railjson.schema.infra; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import fr.sncf.osrd.railjson.schema.common.Identified; -import fr.sncf.osrd.railjson.schema.infra.trackranges.RJSOperationalPointPart; -import java.util.List; -import org.jetbrains.annotations.Nullable; - -@SuppressFBWarnings({"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD"}) -public class RJSOperationalPoint implements Identified { - public String id; - public List parts; - - @Nullable - public RJSOperationalPointExtensions extensions; - - @Nullable - public String weight; - - @Nullable - public String plc; - - public RJSOperationalPoint( - String id, - List parts, - @Nullable RJSOperationalPointExtensions extensions, - @Nullable String weight, - @Nullable String plc) { - this.id = id; - this.parts = parts; - this.extensions = extensions; - this.weight = weight; - this.plc = plc; - } - - @Override - public String getId() { - return id; - } -} diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPoint.kt b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPoint.kt new file mode 100644 index 00000000000..ae39f02cf88 --- /dev/null +++ b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPoint.kt @@ -0,0 +1,19 @@ +package fr.sncf.osrd.railjson.schema.infra + +import com.squareup.moshi.Json +import fr.sncf.osrd.railjson.schema.common.Identified +import fr.sncf.osrd.railjson.schema.infra.trackranges.RJSOperationalPointPart + +class RJSOperationalPoint( + override val id: String, + val parts: List, + val weight: String?, + val name: String, + val uic: Long?, + val plc: String?, + @Json(name = "country_code") val countryCode: String, + @Json(name = "main_code") val mainCode: String, + @Json(name = "secondary_code") val secondaryCode: String?, + @Json(name = "is_passenger_station") val isPassengerStation: Boolean, + @Json(name = "secondary_name") val secondaryName: String?, +) : Identified diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointExtensions.java b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointExtensions.java deleted file mode 100644 index 0eff9280938..00000000000 --- a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointExtensions.java +++ /dev/null @@ -1,18 +0,0 @@ -package fr.sncf.osrd.railjson.schema.infra; - -import org.jetbrains.annotations.Nullable; - -public class RJSOperationalPointExtensions { - @Nullable - public RJSOperationalPointSncfExtension sncf; - - @Nullable - public RJSOperationalPointIdentifierExtension identifier; - - public RJSOperationalPointExtensions( - @Nullable RJSOperationalPointSncfExtension sncf, - @Nullable RJSOperationalPointIdentifierExtension identifier) { - this.sncf = sncf; - this.identifier = identifier; - } -} diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointIdentifierExtension.java b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointIdentifierExtension.java deleted file mode 100644 index 8e38e8858c6..00000000000 --- a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointIdentifierExtension.java +++ /dev/null @@ -1,11 +0,0 @@ -package fr.sncf.osrd.railjson.schema.infra; - -public class RJSOperationalPointIdentifierExtension { - public String name; - public long uic; - - public RJSOperationalPointIdentifierExtension(String name, long uic) { - this.name = name; - this.uic = uic; - } -} diff --git a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointSncfExtension.java b/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointSncfExtension.java deleted file mode 100644 index acb1f85b7b7..00000000000 --- a/core/osrd-railjson/src/main/java/fr/sncf/osrd/railjson/schema/infra/RJSOperationalPointSncfExtension.java +++ /dev/null @@ -1,25 +0,0 @@ -package fr.sncf.osrd.railjson.schema.infra; - -import com.squareup.moshi.Json; - -public class RJSOperationalPointSncfExtension { - public Long ci; - public String ch; - - @Json(name = "ch_short_label") - public String chShortLabel; - - @Json(name = "ch_long_label") - public String chLongLabel; - - public String trigram; - - public RJSOperationalPointSncfExtension( - Long ci, String ch, String chShortLabel, String chLongLabel, String trigram) { - this.ci = ci; - this.ch = ch; - this.chShortLabel = chShortLabel; - this.chLongLabel = chLongLabel; - this.trigram = trigram; - } -} diff --git a/core/settings.gradle b/core/settings.gradle index 93764ae2467..ee457160eb1 100644 --- a/core/settings.gradle +++ b/core/settings.gradle @@ -1,7 +1,7 @@ pluginManagement { repositories { gradlePluginPortal() - maven { url "https://maven-central.storage-download.googleapis.com/maven2/" } + maven { url = "https://maven-central.storage-download.googleapis.com/maven2/" } mavenCentral() } } @@ -30,7 +30,7 @@ dependencyResolutionManagement { versionCatalogs repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - maven { url "https://maven-central-eu.storage-download.googleapis.com/maven2/" } + maven { url = "https://maven-central-eu.storage-download.googleapis.com/maven2/" } mavenCentral() } } diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponse.kt b/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponse.kt index bd1594629db..0467418972a 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponse.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponse.kt @@ -31,9 +31,16 @@ class NonElectrified : Electrification data class OperationalPointResponse( val id: String, val part: OperationalPointPartResponse, - val extensions: OperationalPointExtensions?, val position: Offset, val weight: Long?, + val name: String, + val uic: Long?, + val plc: String?, + @Json(name = "country_code") val countryCode: String, + @Json(name = "main_code") val mainCode: String, + @Json(name = "secondary_code") val secondaryCode: String?, + @Json(name = "is_passenger_station") val isPassengerStation: Boolean, + @Json(name = "secondary_name") val secondaryName: String?, ) data class OperationalPointPartResponse( @@ -43,21 +50,6 @@ data class OperationalPointPartResponse( val extensions: OperationalPointPartExtension?, ) -data class OperationalPointExtensions( - val sncf: OperationalPointSncfExtension?, - val identifier: OperationalPointIdentifierExtension?, -) - -data class OperationalPointSncfExtension( - val ci: Long, - val ch: String, - @Json(name = "ch_short_label") val chShortLabel: String, - @Json(name = "ch_long_label") val chLongLabel: String, - val trigram: String, -) - -data class OperationalPointIdentifierExtension(val name: String, val uic: Long) - data class OperationalPointPartExtension(val sncf: OperationalPointPartSncfExtension?) data class OperationalPointPartSncfExtension(val kp: String) diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponseConverter.kt b/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponseConverter.kt index 5f6eb5b77ad..cc538475648 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponseConverter.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/path_properties/PathPropResponseConverter.kt @@ -84,31 +84,22 @@ private fun makeOperationalPoints( OperationalPointPartSncfExtension(opPartProps["kp"]!!) ), ) - // If ci is null, then all its other values and the entire op sncf extension are null - val opSncfExtension = - if (opPartProps["ci"] == null) null - else - OperationalPointSncfExtension( - opPartProps["ci"]!!.toLong(), - opPartProps["ch"]!!, - opPartProps["chShortLabel"]!!, - opPartProps["chLongLabel"]!!, - opPartProps["trigram"]!!, - ) - // if name is null, uic and the op id extension are null - val opIdExtension = - if (opPartProps["identifier"] == null) null - else - OperationalPointIdentifierExtension( - opPartProps["identifier"]!!, - opPartProps["uic"]!!.toLong(), - ) - val opExtensions = - if (opSncfExtension == null && opIdExtension == null) null - else OperationalPointExtensions(opSncfExtension, opIdExtension) val weight = if (opPartProps["weight"] == null) null else opPartProps["weight"]!!.toLong() val opResult = - OperationalPointResponse(operationalPointId, opPartResult, opExtensions, offset, weight) + OperationalPointResponse( + operationalPointId, + opPartResult, + offset, + weight, + opPartProps["name"]!!, + opPartProps["uic"]?.toLong(), + opPartProps["plc"], + opPartProps["countryCode"]!!, + opPartProps["mainCode"]!!, + opPartProps["secondaryCode"], + opPartProps["isPassengerStation"] == "true", + opPartProps["secondaryName"], + ) res.add(opResult) } return res diff --git a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt index 7abd572910e..f6ef6bbc712 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/pathfinding/PathPropEndpointTest.kt @@ -59,22 +59,30 @@ class PathPropEndpointTest : ApiTest() { OperationalPointResponse( "West_station", OperationalPointPartResponse("TA0", 700.0, "V1", null), - OperationalPointExtensions( - OperationalPointSncfExtension(22, "BV", "BV", "0", "WS"), - OperationalPointIdentifierExtension("West_station", 8722), - ), Offset(650.meters), null, + "West_station", + 8722, + null, + "FR", + "WS", + "BV", + true, + "0", ), OperationalPointResponse( "West_station", OperationalPointPartResponse("TA1", 500.0, "V2", null), - OperationalPointExtensions( - OperationalPointSncfExtension(22, "BV", "BV", "0", "WS"), - OperationalPointIdentifierExtension("West_station", 8722), - ), Offset(2450.meters), null, + "West_station", + 8722, + null, + "FR", + "WS", + "BV", + true, + "0", ), ) assertEquals(parsed.operationalPoints, oPs) diff --git a/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt b/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt index 9ad33c67e0f..2dd4a0132cd 100644 --- a/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt +++ b/core/src/test/kotlin/fr/sncf/osrd/sim_infra_adapter/PathPropertiesTests.kt @@ -7,8 +7,6 @@ import fr.sncf.osrd.path.implementations.buildTrainPathFromBlock import fr.sncf.osrd.railjson.schema.common.graph.ApplicableDirection import fr.sncf.osrd.railjson.schema.geom.RJSLineString import fr.sncf.osrd.railjson.schema.infra.RJSOperationalPoint -import fr.sncf.osrd.railjson.schema.infra.RJSOperationalPointExtensions -import fr.sncf.osrd.railjson.schema.infra.RJSOperationalPointSncfExtension import fr.sncf.osrd.railjson.schema.infra.trackranges.* import fr.sncf.osrd.railjson.schema.rollingstock.RJSLoadingGaugeType import fr.sncf.osrd.sim_infra.api.BlockId @@ -105,7 +103,13 @@ class PathPropertiesTests { "new_op_1", listOf(RJSOperationalPointPart("ne.micro.foo_a", 200.0, "V1", null)), null, + "new_op_1", + 0, + null, + "FR", + "TRI", null, + false, null, ) ) @@ -114,7 +118,13 @@ class PathPropertiesTests { "new_op_2", listOf(RJSOperationalPointPart("ne.micro.bar_a", 0.0, "V1", null)), null, + "new_op_2", + 0, + null, + "FR", + "TRI", null, + false, null, ) ) @@ -172,12 +182,15 @@ class PathPropertiesTests { ), ), ), - RJSOperationalPointExtensions( - RJSOperationalPointSncfExtension(0, "BV", "B", "0", "TRI"), - null, - ), null, + "point1", + 0, null, + "FR", + "TRI", + "0", + false, + "0", ), RJSOperationalPoint( "point2", @@ -193,8 +206,14 @@ class PathPropertiesTests { RJSOperationalPointPart("TA1", 1_950.0, "V1", null), ), null, + "point2", + 0, null, - null, + "FR", + "TRI", + "0", + false, + "0", ), ) val infra = fullInfraFromRJS(rjsInfra, InfraMetadata("modified_small_infra")) diff --git a/editoast/core_client/src/path_properties.rs b/editoast/core_client/src/path_properties.rs index 505adaf8fd1..f1027a39813 100644 --- a/editoast/core_client/src/path_properties.rs +++ b/editoast/core_client/src/path_properties.rs @@ -1,7 +1,7 @@ use geos::geojson::Geometry; -use schemas::infra::OperationalPointExtensions; use schemas::infra::OperationalPointPart; use schemas::primitives::Identifier; +use schemas::primitives::NonBlankString; use serde::Deserialize; use serde::Serialize; use utoipa::ToSchema; @@ -12,7 +12,6 @@ use crate::WorkerKey; use crate::pathfinding::TrackRange; use schemas::infra::OperationalPointPartExtension; -use schemas::infra::OperationalPointSncfExtension; #[derive(Debug, Hash, Serialize)] pub struct PathPropertiesRequest<'a> { @@ -100,18 +99,30 @@ pub struct OperationalPointOnPath { pub id: Identifier, /// The part along the path pub part: OperationalPointPart, - /// Extensions associated to the operational point - #[serde(default)] - pub extensions: OperationalPointExtensions, /// Distance from the beginning of the path in mm pub position: u64, /// Importance of the operational point #[schema(required, minimum = 0, maximum = 100)] pub weight: Option, + #[schema(inline)] + pub name: NonBlankString, + pub uic: Option, + /// Primary Location Code : https://rne.eu/it/products/ccs/crd/ + #[schema(inline)] + pub plc: Option, + #[schema(inline)] + pub country_code: NonBlankString, + #[schema(inline)] + pub main_code: NonBlankString, + #[schema(inline)] + pub secondary_code: Option, + pub is_passenger_station: bool, + #[schema(inline)] + pub secondary_name: Option, } impl OperationalPointOnPath { - pub fn new_test(id: &str, ci: i64, trigram: &str) -> Self { + pub fn new_test(id: &str, uic: u32, main_code: &str) -> Self { OperationalPointOnPath { id: Identifier(id.into()), part: OperationalPointPart { @@ -120,12 +131,16 @@ impl OperationalPointOnPath { local_track_name: "V1".into(), extensions: OperationalPointPartExtension { sncf: None }, }, - extensions: OperationalPointExtensions { - sncf: Some(OperationalPointSncfExtension::new(ci, "BV", trigram)), - identifier: None, - }, position: 0, weight: None, + name: "TEST OP".into(), + uic: Some(uic), + plc: None, + country_code: "FR".into(), + main_code: main_code.into(), + secondary_code: Some("BV".into()), + is_passenger_station: true, + secondary_name: Some("Test OP".into()), } } } diff --git a/editoast/database/src/tables.rs b/editoast/database/src/tables.rs index 15456c9b580..fd8f7c3188c 100644 --- a/editoast/database/src/tables.rs +++ b/editoast/database/src/tables.rs @@ -597,10 +597,11 @@ diesel::table! { infra_id -> Nullable, uic -> Nullable, #[max_length = 255] - trigram -> Nullable, - ci -> Nullable, - ch -> Nullable, + main_code -> Nullable, + secondary_code -> Nullable, name -> Nullable, + is_passenger_station -> Nullable, + secondary_name -> Nullable, } } diff --git a/editoast/editoast_models/src/infra.rs b/editoast/editoast_models/src/infra.rs index ea6bdb5edbb..e429fffaa06 100644 --- a/editoast/editoast_models/src/infra.rs +++ b/editoast/editoast_models/src/infra.rs @@ -191,8 +191,8 @@ impl Infra { .bind::(self.id) .execute(conn.write().await.deref_mut()).await?; - sql_query("INSERT INTO search_operational_point(id, infra_id, obj_id, uic, trigram, ci, ch, name) - SELECT op.id, $1, op.obj_id, uic, trigram, ci, ch, name FROM search_operational_point + sql_query("INSERT INTO search_operational_point(id, infra_id, obj_id, uic, main_code, secondary_code, name, is_passenger_station, secondary_name) + SELECT op.id, $1, op.obj_id, uic, main_code, secondary_code, name, is_passenger_station, secondary_name FROM search_operational_point JOIN infra_object_operational_point AS op ON search_operational_point.obj_id = op.obj_id and op.infra_id = $1 WHERE search_operational_point.infra_id = $2") .bind::(cloned_infra.id) diff --git a/editoast/editoast_models/src/infra_objects.rs b/editoast/editoast_models/src/infra_objects.rs index 6f4032ebf55..4e8cf6ef006 100644 --- a/editoast/editoast_models/src/infra_objects.rs +++ b/editoast/editoast_models/src/infra_objects.rs @@ -282,10 +282,7 @@ impl OperationalPointModel { Ok(dsl::infra_object_operational_point .filter(dsl::infra_id.eq(infra_id)) - .filter( - sql::>("(data->'extensions'->'identifier'->'uic')::int") - .eq_any(uic), - ) + .filter(sql::>("(data->'uic')::int").eq_any(uic)) .load(&mut conn.write().await) .await? .into_iter() @@ -306,9 +303,7 @@ impl OperationalPointModel { Ok(dsl::infra_object_operational_point .filter(dsl::infra_id.eq(infra_id)) - .filter( - sql::>("data->'extensions'->'sncf'->>'trigram'").eq_any(trigrams), - ) + .filter(sql::>("data->>'main_code'").eq_any(trigrams)) .load(&mut conn.write().await) .await? .into_iter() diff --git a/editoast/migrations/2026-05-05-133055-0000_rename_operational_point_fields/down.sql b/editoast/migrations/2026-05-05-133055-0000_rename_operational_point_fields/down.sql new file mode 100644 index 00000000000..c98f23099a5 --- /dev/null +++ b/editoast/migrations/2026-05-05-133055-0000_rename_operational_point_fields/down.sql @@ -0,0 +1,31 @@ +-- This file should undo anything in `up.sql` +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions}', '{}'::jsonb); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, identifier}', '{}'::jsonb); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, sncf}', '{}'::jsonb); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, identifier, name}', data->'name'); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, identifier, uic}', data->'uic'); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, sncf, trigram}', data->'main_code'); + +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, sncf, ch}', + CASE + WHEN (data->>'is_passenger_station')::boolean THEN '"BV"'::jsonb + ELSE COALESCE(NULLIF(data->'secondary_code', 'null'::jsonb), '""'::jsonb) + END +); + +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, sncf, ch_long_label}', COALESCE(NULLIF(data->'secondary_name', 'null'::jsonb), '""'::jsonb)); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, sncf, ci}', '0'::jsonb); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{extensions, sncf, ch_short_label}', '""'::jsonb); +UPDATE infra_object_operational_point +SET data = data - 'name' - 'uic' - 'main_code' - 'secondary_code' - 'secondary_name' - 'country_code' - 'is_passenger_station' ; +UPDATE infra SET railjson_version = '3.5.2' diff --git a/editoast/migrations/2026-05-05-133055-0000_rename_operational_point_fields/up.sql b/editoast/migrations/2026-05-05-133055-0000_rename_operational_point_fields/up.sql new file mode 100644 index 00000000000..e53910e4d1c --- /dev/null +++ b/editoast/migrations/2026-05-05-133055-0000_rename_operational_point_fields/up.sql @@ -0,0 +1,20 @@ +-- Your SQL goes here +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{name}', COALESCE(data->'extensions'->'identifier'->'name', '"name"'::jsonb)); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{uic}', COALESCE(data->'extensions'->'identifier'->'uic', 'null'::jsonb)); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{main_code}', COALESCE(data->'extensions'->'sncf'->'trigram', '"main_code"'::jsonb)); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{secondary_code}', COALESCE(data->'extensions'->'sncf'->'ch', 'null'::jsonb)); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{secondary_name}', COALESCE(data->'extensions'->'sncf'->'ch_long_label', 'null'::jsonb)); +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{country_code}', '"FR"'::jsonb); + +UPDATE infra_object_operational_point +SET data = jsonb_set(data, '{is_passenger_station}', to_jsonb(data->'extensions'->'sncf'->>'ch' = 'BV' OR data->'extensions'->'sncf'->>'ch' = '00')); + +UPDATE infra_object_operational_point +SET data = data - 'extensions'; +UPDATE infra SET railjson_version = '3.5.3' diff --git a/editoast/migrations/2026-05-11-140835-0000_search_op_rename_fields/down.sql b/editoast/migrations/2026-05-11-140835-0000_search_op_rename_fields/down.sql new file mode 100644 index 00000000000..ef3f892f02e --- /dev/null +++ b/editoast/migrations/2026-05-11-140835-0000_search_op_rename_fields/down.sql @@ -0,0 +1,7 @@ +-- DO NOT EDIT THIS FILE MANUALLY! + +DROP TABLE IF EXISTS "search_operational_point"; +DROP TRIGGER IF EXISTS search_operational_point__ins_trig ON "infra_object_operational_point"; +DROP TRIGGER IF EXISTS search_operational_point__upd_trig ON "infra_object_operational_point"; +DROP FUNCTION IF EXISTS search_operational_point__ins_trig_fun; +DROP FUNCTION IF EXISTS search_operational_point__upd_trig_fun; diff --git a/editoast/migrations/2026-05-11-140835-0000_search_op_rename_fields/up.sql b/editoast/migrations/2026-05-11-140835-0000_search_op_rename_fields/up.sql new file mode 100644 index 00000000000..71807c5a86b --- /dev/null +++ b/editoast/migrations/2026-05-11-140835-0000_search_op_rename_fields/up.sql @@ -0,0 +1,90 @@ +-- DO NOT EDIT THIS FILE MANUALLY! +-- To change the migration's content, use `editoast search make-migration`. +-- To add custom SQL code, check out `#[derive(Search)]` attributes `prepend_sql` and `append_sql`. + +DROP TABLE IF EXISTS "search_operational_point"; + +CREATE TABLE "search_operational_point" ( + id BIGINT PRIMARY KEY REFERENCES "infra_object_operational_point"("id") ON UPDATE CASCADE ON DELETE CASCADE, + "obj_id" varchar(255), + "infra_id" integer, + "uic" integer, + "main_code" varchar(255), + "secondary_code" text, + "name" text, + "is_passenger_station" boolean, + "secondary_name" text +); + +CREATE INDEX "search_operational_point_obj_id" ON "search_operational_point" ("obj_id"); +CREATE INDEX "search_operational_point_infra_id" ON "search_operational_point" ("infra_id"); +CREATE INDEX "search_operational_point_uic" ON "search_operational_point" ("uic"); +CREATE INDEX "search_operational_point_main_code" ON "search_operational_point" ("main_code"); +CREATE INDEX "search_operational_point_secondary_code" ON "search_operational_point" ("secondary_code"); +CREATE INDEX "search_operational_point_name" ON "search_operational_point" USING gin ("name" gin_trgm_ops); +CREATE INDEX "search_operational_point_is_passenger_station" ON "search_operational_point" ("is_passenger_station"); +CREATE INDEX "search_operational_point_secondary_name" ON "search_operational_point" ("secondary_name"); + +CREATE OR REPLACE FUNCTION search_operational_point__ins_trig_fun() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + INSERT INTO "search_operational_point" (id, obj_id, infra_id, uic, main_code, secondary_code, name, is_passenger_station, secondary_name) + SELECT "infra_object_operational_point".id AS id, (infra_object_operational_point.obj_id) AS obj_id, + (infra_object_operational_point.infra_id) AS infra_id, + ((infra_object_operational_point.data->>'uic')::integer) AS uic, + (infra_object_operational_point.data->>'main_code') AS main_code, + (infra_object_operational_point.data->>'secondary_code') AS secondary_code, + osrd_prepare_for_search(infra_object_operational_point.data->>'name') AS name, + ((infra_object_operational_point.data->>'is_passenger_station')::boolean) AS is_passenger_station, + (infra_object_operational_point.data->>'secondary_name') AS secondary_name + FROM (SELECT NEW.*) AS "infra_object_operational_point" + ; + RETURN NEW; +END; +$$; +CREATE OR REPLACE TRIGGER search_operational_point__ins_trig +AFTER INSERT ON "infra_object_operational_point" +FOR EACH ROW EXECUTE FUNCTION search_operational_point__ins_trig_fun(); + + +CREATE OR REPLACE FUNCTION search_operational_point__upd_trig_fun() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE "search_operational_point" + SET "obj_id" = (infra_object_operational_point.obj_id), + "infra_id" = (infra_object_operational_point.infra_id), + "uic" = ((infra_object_operational_point.data->>'uic')::integer), + "main_code" = (infra_object_operational_point.data->>'main_code'), + "secondary_code" = (infra_object_operational_point.data->>'secondary_code'), + "name" = osrd_prepare_for_search(infra_object_operational_point.data->>'name'), + "is_passenger_station" = ((infra_object_operational_point.data->>'is_passenger_station')::boolean), + "secondary_name" = (infra_object_operational_point.data->>'secondary_name') + FROM (SELECT NEW.*) AS "infra_object_operational_point" + + WHERE "infra_object_operational_point".id = "search_operational_point".id; + RETURN NEW; +END; +$$; +CREATE OR REPLACE TRIGGER search_operational_point__upd_trig +AFTER UPDATE ON "infra_object_operational_point" +FOR EACH ROW EXECUTE FUNCTION search_operational_point__upd_trig_fun(); + + + +INSERT INTO "search_operational_point" (id, "obj_id", "infra_id", "uic", "main_code", "secondary_code", "name", "is_passenger_station", "secondary_name") +SELECT + "infra_object_operational_point"."id" AS id, + (infra_object_operational_point.obj_id) AS obj_id +, (infra_object_operational_point.infra_id) AS infra_id +, ((infra_object_operational_point.data->>'uic')::integer) AS uic +, (infra_object_operational_point.data->>'main_code') AS main_code +, (infra_object_operational_point.data->>'secondary_code') AS secondary_code +, osrd_prepare_for_search(infra_object_operational_point.data->>'name') AS name +, ((infra_object_operational_point.data->>'is_passenger_station')::boolean) AS is_passenger_station +, (infra_object_operational_point.data->>'secondary_name') AS secondary_name +FROM "infra_object_operational_point" + ; diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index 279df70719d..fd709e844b4 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -4288,9 +4288,10 @@ paths: - type properties: secondary_code: - type: - - string - - 'null' + oneOf: + - type: 'null' + - type: string + minLength: 1 description: An optional secondary code to identify a more specific location trigram: oneOf: @@ -4308,9 +4309,10 @@ paths: - type properties: secondary_code: - type: - - string - - 'null' + oneOf: + - type: 'null' + - type: string + minLength: 1 description: An optional secondary code to identify a more specific location type: type: string @@ -5429,24 +5431,58 @@ components: - part - position - weight + - name + - country_code + - main_code + - is_passenger_station properties: - extensions: - $ref: '#/components/schemas/OperationalPointExtensions' - description: Extensions associated to the operational point + country_code: + type: string + minLength: 1 id: oneOf: - type: string maxLength: 255 minLength: 1 description: Id of the operational point + is_passenger_station: + type: boolean + main_code: + type: string + minLength: 1 + name: + type: string + minLength: 1 part: $ref: '#/components/schemas/OperationalPointPart' description: The part along the path + plc: + oneOf: + - type: 'null' + - type: string + minLength: 1 + description: 'Primary Location Code : https://rne.eu/it/products/ccs/crd/' position: type: integer format: int64 description: Distance from the beginning of the path in mm minimum: 0 + secondary_code: + oneOf: + - type: 'null' + - type: string + minLength: 1 + secondary_name: + oneOf: + - type: 'null' + - type: string + minLength: 1 + uic: + type: + - integer + - 'null' + format: int32 + minimum: 0 weight: type: - integer @@ -11477,13 +11513,26 @@ components: required: - id - parts + - name + - country_code + - main_code + - is_passenger_station properties: - extensions: - $ref: '#/components/schemas/OperationalPointExtensions' + country_code: + type: string + minLength: 1 id: type: string maxLength: 255 minLength: 1 + is_passenger_station: + type: boolean + main_code: + type: string + minLength: 1 + name: + type: string + minLength: 1 parts: type: array items: @@ -11491,38 +11540,29 @@ components: plc: oneOf: - type: 'null' - - $ref: '#/components/schemas/NonBlankString' - description: 'Primary Location Code : https://rne.eu/it/products/ccs/crd/' - weight: - type: - - integer - - 'null' - format: int32 - minimum: 0 - additionalProperties: false - OperationalPointExtensions: - type: object - properties: - identifier: + - type: string + minLength: 1 + description: 'Primary Location Code : https://rne.eu/it/products/ccs/crd/' + secondary_code: oneOf: - type: 'null' - - $ref: '#/components/schemas/OperationalPointIdentifierExtension' - sncf: + - type: string + minLength: 1 + secondary_name: oneOf: - type: 'null' - - $ref: '#/components/schemas/OperationalPointSncfExtension' - additionalProperties: false - OperationalPointIdentifierExtension: - type: object - required: - - name - - uic - properties: - name: - type: string - minLength: 1 + - type: string + minLength: 1 uic: - type: integer + type: + - integer + - 'null' + format: int32 + minimum: 0 + weight: + type: + - integer + - 'null' format: int32 minimum: 0 additionalProperties: false @@ -11599,9 +11639,10 @@ components: - type properties: secondary_code: - type: - - string - - 'null' + oneOf: + - type: 'null' + - type: string + minLength: 1 description: An optional secondary code to identify a more specific location trigram: oneOf: @@ -11619,9 +11660,10 @@ components: - type properties: secondary_code: - type: - - string - - 'null' + oneOf: + - type: 'null' + - type: string + minLength: 1 description: An optional secondary code to identify a more specific location type: type: string @@ -11632,29 +11674,6 @@ components: format: int32 description: The [UIC](https://en.wikipedia.org/wiki/List_of_UIC_country_codes) code of an operational point minimum: 0 - OperationalPointSncfExtension: - type: object - required: - - ci - - ch - - ch_short_label - - ch_long_label - - trigram - properties: - ch: - type: string - ch_long_label: - type: string - minLength: 1 - ch_short_label: - type: string - minLength: 1 - ci: - type: integer - format: int64 - trigram: - type: string - additionalProperties: false OptionsChangeGroup: type: object required: @@ -12609,9 +12628,14 @@ components: required: - id - parts + - name + - country_code + - main_code + - is_passenger_station properties: - extensions: - $ref: '#/components/schemas/OperationalPointExtensions' + country_code: + type: string + minLength: 1 geo: oneOf: - type: 'null' @@ -12620,10 +12644,39 @@ components: type: string maxLength: 255 minLength: 1 + is_passenger_station: + type: boolean + main_code: + type: string + minLength: 1 + name: + type: string + minLength: 1 parts: type: array items: $ref: '#/components/schemas/RelatedOperationalPointPart' + plc: + oneOf: + - type: 'null' + - type: string + minLength: 1 + secondary_code: + oneOf: + - type: 'null' + - type: string + minLength: 1 + secondary_name: + oneOf: + - type: 'null' + - type: string + minLength: 1 + uic: + type: + - integer + - 'null' + format: int32 + minimum: 0 weight: type: - integer @@ -13467,37 +13520,41 @@ components: required: - obj_id - infra_id - - uic + - main_code - name - - trigram - - ch - - ci + - is_passenger_station - geographic - track_sections properties: - ch: - type: string - ci: - type: integer - format: int64 - minimum: 0 geographic: $ref: '#/components/schemas/GeoJsonPoint' infra_id: type: integer format: int64 + is_passenger_station: + type: boolean + main_code: + type: string name: type: string obj_id: type: string + secondary_code: + type: + - string + - 'null' + secondary_name: + type: + - string + - 'null' track_sections: type: array items: $ref: '#/components/schemas/SearchResultItemOperationalPointTrackSections' - trigram: - type: string uic: - type: integer + type: + - integer + - 'null' format: int64 SearchResultItemOperationalPointTrackSections: type: object diff --git a/editoast/osm_to_railjson/src/osm_to_railjson.rs b/editoast/osm_to_railjson/src/osm_to_railjson.rs index 2a5c2cc0c01..7b9d3a7bd8c 100644 --- a/editoast/osm_to_railjson/src/osm_to_railjson.rs +++ b/editoast/osm_to_railjson/src/osm_to_railjson.rs @@ -311,10 +311,8 @@ mod tests { assert_eq!(1, rj.operational_points.len()); let op = &rj.operational_points[0]; assert_eq!(2, op.parts.len()); - let identifier_ext = op.extensions.identifier.as_ref().unwrap(); - assert_eq!("atlantis", identifier_ext.name); - assert_eq!(1234, identifier_ext.uic); - let sncf_ext = op.extensions.sncf.as_ref().unwrap(); - assert_eq!("TRI", sncf_ext.trigram); + assert_eq!("atlantis", op.name); + assert_eq!(Some(1234), op.uic); + assert_eq!("TRI", op.main_code); } } diff --git a/editoast/osm_to_railjson/src/utils.rs b/editoast/osm_to_railjson/src/utils.rs index 0ffdfaa0a0a..d1c6d2dacbb 100644 --- a/editoast/osm_to_railjson/src/utils.rs +++ b/editoast/osm_to_railjson/src/utils.rs @@ -13,10 +13,7 @@ use schemas::infra::Electrification; use schemas::infra::Endpoint; use schemas::infra::LogicalSignal; use schemas::infra::OperationalPoint; -use schemas::infra::OperationalPointExtensions; -use schemas::infra::OperationalPointIdentifierExtension; use schemas::infra::OperationalPointPart; -use schemas::infra::OperationalPointSncfExtension; use schemas::infra::Side; use schemas::infra::Signal; use schemas::infra::SignalExtensions; @@ -527,7 +524,7 @@ pub fn operational_points( .collect(); // Get operational point trigram // Look through the nodes member of the relation and find one that has a "railway:ref" tag - let trigram = rel + let main_code = rel .refs .iter() .filter_map(|r| match r.member { @@ -543,22 +540,21 @@ pub fn operational_points( if parts.is_empty() { None } else { + let (identifier_name, identifier_uic) = identifier(&rel.tags); Some(OperationalPoint { id: rel.id.0.to_string().into(), parts, - extensions: OperationalPointExtensions { - identifier: identifier(&rel.tags), - sncf: Some(OperationalPointSncfExtension { - ci: 0, - ch: String::from("BV"), - ch_short_label: NonBlankString::from("BV"), - ch_long_label: NonBlankString::from("BV"), - trigram - }), - }, weight: None, + name: identifier_name, + uic: Some(identifier_uic), plc: None, + country_code: "FR".into(), + main_code: main_code.into(), + secondary_code: Some("BV".into()), + is_passenger_station: true, + secondary_name: Some("BV".into()), }) + } }) .collect() @@ -568,9 +564,7 @@ pub fn operational_points( // The front crashes when this function return None. // This function will probably be changed when the data model will change. // The necessity of a fake UIC and name should be re-evaluated at that time. -fn identifier( - tags: &osm4routing::osmpbfreader::Tags, -) -> Option { +fn identifier(tags: &osm4routing::osmpbfreader::Tags) -> (NonBlankString, u32) { let uic = tags .get("uic_ref") .and_then(|uic| match u32::from_str(uic.as_str()) { @@ -586,16 +580,11 @@ fn identifier( 11_00000 + UIC_COUNTER.fetch_add(1, Ordering::Relaxed) }); - tags.get("name") - .map(|name| OperationalPointIdentifierExtension { - name: name.as_str().into(), - uic, - }) - .or(Some(OperationalPointIdentifierExtension { - // Generate a fake name from the UIC - name: format!("op_{}", uic).into(), - uic, - })) + tags.get("name").map_or( + // Generate a fake name from the UIC + (format!("op_{}", uic).into(), uic), + |name| (name.as_str().into(), uic), + ) } #[cfg(test)] diff --git a/editoast/schemas/src/infra.rs b/editoast/schemas/src/infra.rs index 7a81fc1189f..865802208e8 100644 --- a/editoast/schemas/src/infra.rs +++ b/editoast/schemas/src/infra.rs @@ -51,11 +51,8 @@ pub use level_crossing::LevelCrossingPart; pub use loading_gauge_limit::LoadingGaugeLimit; pub use neutral_section::NeutralSection; pub use operational_point::OperationalPoint; -pub use operational_point::OperationalPointExtensions; -pub use operational_point::OperationalPointIdentifierExtension; pub use operational_point::OperationalPointPart; pub use operational_point::OperationalPointPartExtension; -pub use operational_point::OperationalPointSncfExtension; pub use railjson::RAILJSON_VERSION; pub use railjson::RailJson; pub use railjson::major_version; diff --git a/editoast/schemas/src/infra/operational_point.rs b/editoast/schemas/src/infra/operational_point.rs index 85415529a74..ee19fd01900 100644 --- a/editoast/schemas/src/infra/operational_point.rs +++ b/editoast/schemas/src/infra/operational_point.rs @@ -17,11 +17,22 @@ pub struct OperationalPoint { pub id: Identifier, pub parts: Vec, #[serde(default)] - pub extensions: OperationalPointExtensions, - #[serde(default)] pub weight: Option, + #[schema(inline)] + pub name: NonBlankString, + pub uic: Option, /// Primary Location Code : https://rne.eu/it/products/ccs/crd/ + #[schema(inline)] pub plc: Option, + #[schema(inline)] + pub country_code: NonBlankString, + #[schema(inline)] + pub main_code: NonBlankString, + #[schema(inline)] + pub secondary_code: Option, + pub is_passenger_station: bool, + #[schema(inline)] + pub secondary_name: Option, } #[derive(Debug, Educe, Clone, PartialEq, Serialize, Deserialize, ToSchema)] @@ -50,45 +61,6 @@ pub struct OperationalPointPartSncfExtension { pub kp: String, } -#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, ToSchema)] -#[serde(deny_unknown_fields)] -pub struct OperationalPointExtensions { - pub sncf: Option, - pub identifier: Option, -} - -#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, ToSchema)] -#[serde(deny_unknown_fields)] -pub struct OperationalPointSncfExtension { - pub ci: i64, - pub ch: String, - #[schema(inline)] - pub ch_short_label: NonBlankString, - #[schema(inline)] - pub ch_long_label: NonBlankString, - pub trigram: String, -} - -impl OperationalPointSncfExtension { - pub fn new(ci: i64, ch: &str, trigram: &str) -> Self { - Self { - ci, - ch: ch.into(), - ch_short_label: ch.into(), - ch_long_label: ch.into(), - trigram: trigram.into(), - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ToSchema)] -#[serde(deny_unknown_fields)] -pub struct OperationalPointIdentifierExtension { - #[schema(inline)] - pub name: NonBlankString, - pub uic: u32, -} - impl OSRDTyped for OperationalPoint { fn get_type() -> ObjectType { ObjectType::OperationalPoint @@ -123,15 +95,3 @@ impl OperationalPoint { .collect() } } - -#[cfg(test)] -mod tests { - use serde_json::from_str; - - use super::OperationalPointExtensions; - - #[test] - fn test_op_extensions_deserialization() { - from_str::(r#"{}"#).unwrap(); - } -} diff --git a/editoast/schemas/src/infra/railjson.rs b/editoast/schemas/src/infra/railjson.rs index 4f087af16e0..27c70f4ac45 100644 --- a/editoast/schemas/src/infra/railjson.rs +++ b/editoast/schemas/src/infra/railjson.rs @@ -16,7 +16,7 @@ use super::Switch; use super::SwitchType; use super::TrackSection; -pub const RAILJSON_VERSION: &str = "3.5.2"; +pub const RAILJSON_VERSION: &str = "3.5.3"; /// An infrastructure description in the RailJson format #[derive(Deserialize, Educe, Serialize, Clone, Debug, ToSchema)] diff --git a/editoast/schemas/src/train_schedule.rs b/editoast/schemas/src/train_schedule.rs index 7d9e56f6dac..395bcaa79fb 100644 --- a/editoast/schemas/src/train_schedule.rs +++ b/editoast/schemas/src/train_schedule.rs @@ -240,7 +240,7 @@ impl TrainOccurrence { OperationalPointPartReference { operational_point: OperationalPointReference::Uic { uic: 8711, - secondary_code: Some("BV".to_string()), + secondary_code: Some("BV".into()), }, local_track_name: None, }, @@ -259,7 +259,7 @@ impl TrainOccurrence { OperationalPointPartReference { operational_point: OperationalPointReference::Trigram { trigram: NonBlankString::from("MWS"), - secondary_code: Some("BV".to_string()), + secondary_code: Some("BV".into()), }, local_track_name: None, }, diff --git a/editoast/schemas/src/train_schedule/path_item.rs b/editoast/schemas/src/train_schedule/path_item.rs index c062a66b098..bd8948aada0 100644 --- a/editoast/schemas/src/train_schedule/path_item.rs +++ b/editoast/schemas/src/train_schedule/path_item.rs @@ -68,14 +68,16 @@ pub enum OperationalPointReference { #[schema(inline)] trigram: NonBlankString, /// An optional secondary code to identify a more specific location - secondary_code: Option, + #[schema(inline)] + secondary_code: Option, }, #[schema(title = "OperationalPointReferenceUic")] Uic { /// The [UIC](https://en.wikipedia.org/wiki/List_of_UIC_country_codes) code of an operational point uic: u32, /// An optional secondary code to identify a more specific location - secondary_code: Option, + #[schema(inline)] + secondary_code: Option, }, } diff --git a/editoast/src/views/infra/mod.rs b/editoast/src/views/infra/mod.rs index 57ccc68bcc3..db94992752c 100644 --- a/editoast/src/views/infra/mod.rs +++ b/editoast/src/views/infra/mod.rs @@ -27,6 +27,7 @@ use geos::Geom; use itertools::Itertools; use schemas::infra::SwitchType; use schemas::primitives::Identifier; +use schemas::primitives::NonBlankString; use serde::Deserialize; use serde::Serialize; use std::collections::HashSet; @@ -53,7 +54,6 @@ use authz::Role; use editoast_models::Infra; use editoast_models::SwitchTypeModel; use schemas::infra::OperationalPoint; -use schemas::infra::OperationalPointExtensions; use schemas::infra::OperationalPointPart; use schemas::infra::builtin_node_types_list; use schemas::train_schedule::OperationalPointReference; @@ -681,11 +681,23 @@ struct RelatedOperationalPoint { id: Identifier, parts: Vec, #[serde(default)] - extensions: OperationalPointExtensions, - #[serde(default)] weight: Option, #[schema(value_type = Option)] geo: Option, + #[schema(inline)] + pub name: NonBlankString, + pub uic: Option, + #[schema(inline)] + pub plc: Option, + #[schema(inline)] + pub country_code: NonBlankString, + #[schema(inline)] + pub main_code: NonBlankString, + #[schema(inline)] + pub secondary_code: Option, + pub is_passenger_station: bool, + #[schema(inline)] + pub secondary_name: Option, } #[derive(Serialize, ToSchema)] @@ -782,9 +794,16 @@ fn build_related_operational_point( geo: geo_points.and_then(|points| points.get(i).cloned()), }) .collect(), - extensions: op.extensions.clone(), weight: op.weight, geo: geo_points.and_then(|points| compute_operational_point_geo(points)), + name: op.name.clone(), + uic: op.uic, + plc: op.plc.clone(), + country_code: op.country_code.clone(), + main_code: op.main_code.clone(), + secondary_code: op.secondary_code.clone(), + is_passenger_station: op.is_passenger_station, + secondary_name: op.secondary_name.clone(), } } diff --git a/editoast/src/views/path/operational_point_cache.rs b/editoast/src/views/path/operational_point_cache.rs index 864075842b2..28d620c0bb0 100644 --- a/editoast/src/views/path/operational_point_cache.rs +++ b/editoast/src/views/path/operational_point_cache.rs @@ -29,7 +29,7 @@ pub struct OperationalPointCache { /// Maps UIC code to indices in the ops Vec uic_to_indices: HashMap>, /// Maps trigram to indices in the ops Vec - trigram_to_indices: HashMap>, + trigram_to_indices: HashMap>, /// Maps obj_id to index in the ops Vec obj_id_to_index: HashMap, track_ids: HashSet, @@ -138,7 +138,7 @@ impl OperationalPointCache { // Step 3: Build index maps from the ops vector let mut obj_id_to_index: HashMap = HashMap::new(); let mut uic_to_indices: HashMap> = HashMap::new(); - let mut trigram_to_indices: HashMap> = HashMap::new(); + let mut trigram_to_indices: HashMap> = HashMap::new(); let track_ids_to_local_track_name: HashMap = HashMap::new(); let track_ids: HashSet = HashSet::new(); @@ -147,20 +147,15 @@ impl OperationalPointCache { obj_id_to_index.insert(op.obj_id.clone(), index); // Build UIC index if present - if let Some(identifier) = &op.extensions.identifier { - uic_to_indices - .entry(identifier.uic) - .or_default() - .push(index); + if let Some(op_uic) = op.uic { + uic_to_indices.entry(op_uic).or_default().push(index); } // Build trigram index if present - if let Some(sncf) = &op.extensions.sncf { - trigram_to_indices - .entry(sncf.trigram.clone()) - .or_default() - .push(index); - } + trigram_to_indices + .entry(op.main_code.clone()) + .or_default() + .push(index); } Ok(OperationalPointCache { @@ -347,7 +342,7 @@ impl OperationalPointCache { pub(crate) fn new( ops: Vec, uic_to_indices: HashMap>, - trigram_to_indices: HashMap>, + trigram_to_indices: HashMap>, obj_id_to_index: HashMap, track_ids: HashSet, track_ids_to_local_track_name: HashMap, @@ -453,47 +448,36 @@ async fn retrieve_op_from_ids( /// If secondary_code is None, searches for an OP without sncf extension. /// If secondary_code is Some, searches for an OP whose sncf extension matches the given code. fn find_op_by_secondary_code<'a>( - secondary_code: Option<&String>, + secondary_code: Option<&NonBlankString>, ops: Vec<&'a OperationalPointModel>, ) -> Option<&'a OperationalPointModel> { ops.into_iter() - .find(|op| secondary_code == op.extensions.sncf.as_ref().map(|sncf| &sncf.ch)) + .find(|op| secondary_code == op.secondary_code.as_ref()) } #[cfg(test)] mod tests { use database::DbConnectionPoolV2; use schemas::infra::OperationalPoint; - use schemas::infra::OperationalPointExtensions; - use schemas::infra::OperationalPointIdentifierExtension; - use schemas::infra::OperationalPointSncfExtension; use schemas::primitives::Identifier; use super::*; use crate::fixtures::create_empty_infra; use crate::fixtures::create_infra_object; - fn create_op(obj_id: &str, trigram: Option<&str>, uic: Option) -> OperationalPoint { - let extensions = OperationalPointExtensions { - sncf: trigram.map(|t| OperationalPointSncfExtension { - ci: 0, - ch: "00".to_string(), - ch_short_label: "Test".into(), - ch_long_label: "Test OP".into(), - trigram: t.to_string(), - }), - identifier: uic.map(|u| OperationalPointIdentifierExtension { - name: "Test OP".into(), - uic: u, - }), - }; - + fn create_op(obj_id: &str, main_code: &str, uic: u32) -> OperationalPoint { OperationalPoint { id: Identifier::from(obj_id), parts: vec![], - extensions, weight: None, + name: "Test OP".into(), + uic: Some(uic), plc: None, + country_code: "FR".into(), + main_code: main_code.into(), + secondary_code: Some("00".into()), + is_passenger_station: false, + secondary_name: Some("Test OP".into()), } } @@ -504,9 +488,9 @@ mod tests { let infra = create_empty_infra(&mut conn).await; // Create three operational points with different identifier combinations - let op1 = create_op("op_1", Some("ABC"), Some(1234)); - let op2 = create_op("op_2", Some("DEF"), None); // No UIC - let op3 = create_op("op_3", None, Some(5678)); // No trigram + let op1 = create_op("op_1", "ABC", 1234); + let op2 = create_op("op_2", "DEF", 91011); // UIC not revelant + let op3 = create_op("op_3", "HIJ", 5678); // trigram not revelant // Insert OPs into the database create_infra_object(&mut conn, infra.id, op1).await; diff --git a/editoast/src/views/projection.rs b/editoast/src/views/projection.rs index 3144ee02ad5..8bdcadbdd47 100644 --- a/editoast/src/views/projection.rs +++ b/editoast/src/views/projection.rs @@ -1147,7 +1147,7 @@ mod tests { fn create_path_item_from_trigram(trigram: &str) -> OperationalPointReference { OperationalPointReference::Trigram { trigram: trigram.into(), - secondary_code: Some("BV".to_string()), + secondary_code: Some("BV".into()), } } diff --git a/editoast/src/views/search.rs b/editoast/src/views/search.rs index d0d2235ae1a..434492f9246 100644 --- a/editoast/src/views/search.rs +++ b/editoast/src/views/search.rs @@ -445,28 +445,33 @@ pub(super) struct SearchResultItemTrack { column( name = "uic", data_type = "integer", - sql = "(infra_object_operational_point.data->'extensions'->'identifier'->>'uic')::integer", + sql = "(infra_object_operational_point.data->>'uic')::integer", ), column( - name = "trigram", - data_type = "varchar(3)", - sql = "infra_object_operational_point.data->'extensions'->'sncf'->>'trigram'", - ), - column( - name = "ci", - data_type = "integer", - sql = "(infra_object_operational_point.data->'extensions'->'sncf'->>'ci')::integer", + name = "main_code", + data_type = "varchar(255)", + sql = "infra_object_operational_point.data->>'main_code'", ), column( - name = "ch", + name = "secondary_code", data_type = "text", - sql = "infra_object_operational_point.data->'extensions'->'sncf'->>'ch'", + sql = "infra_object_operational_point.data->>'secondary_code'", ), column( name = "name", data_type = "text", - sql = "infra_object_operational_point.data->'extensions'->'identifier'->>'name'", + sql = "infra_object_operational_point.data->>'name'", textual_search, + ), + column( + name = "is_passenger_station", + data_type = "boolean", + sql = "(infra_object_operational_point.data->>'is_passenger_station')::boolean", + ), + column( + name = "secondary_name", + data_type = "text", + sql = "infra_object_operational_point.data->>'secondary_name'", ) )] /// A search result item for a query with `object = "operationalpoint"` @@ -477,16 +482,18 @@ pub(super) struct SearchResultItemOperationalPoint { obj_id: String, #[search(sql = "OP.infra_id")] infra_id: i64, - #[search(sql = "OP.data->'extensions'->'identifier'->'uic'")] - uic: i64, - #[search(sql = "OP.data#>>'{extensions,identifier,name}'")] + #[search(sql = "OP.data->'uic'")] + uic: Option, + #[search(sql = "OP.data#>>'{main_code}'")] + main_code: String, + #[search(sql = "OP.data#>>'{secondary_code}'")] + secondary_code: Option, + #[search(sql = "OP.data#>>'{name}'")] name: String, - #[search(sql = "OP.data#>>'{extensions,sncf,trigram}'")] - trigram: String, - #[search(sql = "OP.data#>>'{extensions,sncf,ch}'")] - ch: String, - #[search(sql = "OP.data#>>'{extensions,sncf,ci}'")] - ci: u64, + #[search(sql = "OP.data->'is_passenger_station'")] + is_passenger_station: bool, + #[search(sql = "OP.data#>>'{secondary_name}'")] + secondary_name: Option, #[search(sql = "ST_AsGeoJSON(ST_Transform(lay.geographic, 4326))::json")] #[schema(value_type = GeoJsonPoint)] geographic: Geometry, diff --git a/editoast/src/views/timetable/stdcm.rs b/editoast/src/views/timetable/stdcm.rs index 4cb5b6640f2..eecfedd3cb9 100644 --- a/editoast/src/views/timetable/stdcm.rs +++ b/editoast/src/views/timetable/stdcm.rs @@ -631,7 +631,7 @@ mod tests { OperationalPointPartReference { operational_point: OperationalPointReference::Trigram { trigram: "WS".into(), - secondary_code: Some("BV".to_string()), + secondary_code: Some("BV".into()), }, local_track_name: None, }, @@ -649,7 +649,7 @@ mod tests { OperationalPointPartReference { operational_point: OperationalPointReference::Trigram { trigram: "MWS".into(), - secondary_code: Some("BV".to_string()), + secondary_code: Some("BV".into()), }, local_track_name: None, }, @@ -703,7 +703,7 @@ mod tests { OperationalPointPartReference { operational_point: OperationalPointReference::Trigram { trigram: trigram.into(), - secondary_code: Some("BV".to_string()), + secondary_code: Some("BV".into()), }, local_track_name: None, }, diff --git a/editoast/src/views/timetable/train_schedule.rs b/editoast/src/views/timetable/train_schedule.rs index b663054ed37..091e0012d6c 100644 --- a/editoast/src/views/timetable/train_schedule.rs +++ b/editoast/src/views/timetable/train_schedule.rs @@ -1854,7 +1854,7 @@ mod tests { pub fn new_op_with_trigram_and_local_track_name( id: &str, trigram: &str, - secondary_code: Option, + secondary_code: Option, local_track_name: Option, ) -> PathItem { PathItem { diff --git a/front/public/locales/de/operational-studies.json b/front/public/locales/de/operational-studies.json index 7eeabd93159..f958386a656 100644 --- a/front/public/locales/de/operational-studies.json +++ b/front/public/locales/de/operational-studies.json @@ -413,8 +413,8 @@ "title_on": "{{count}} von {{total}} verletzten Beschränkungen" }, "infraLoading": "Infrastruktur wird geladen…", - "inputOPTrigrams": "Geben Sie die Kürzel der zu durchfahrenden Betriebsstellen ein, getrennt durch Leerzeichen.", - "inputOPTrigramsExample": "Bsp.: HHA HGÖ FKS", + "inputOPMainCodes": "Geben Sie die Kürzel der zu durchfahrenden Betriebsstellen ein, getrennt durch Leerzeichen.", + "inputOPMainCodesExample": "Bsp.: HHA HGÖ FKS", "invalidTrainScheduleStep": "Mindestens ein Wegepunkt konnte nicht erkannt werden.", "inverseOD": "Route umkehren", "itineraryModal": { diff --git a/front/public/locales/de/translation.json b/front/public/locales/de/translation.json index cc4839510fa..b8126289372 100644 --- a/front/public/locales/de/translation.json +++ b/front/public/locales/de/translation.json @@ -820,7 +820,6 @@ "calculatedArrivalTime": "berechnete Ankunft", "calculatedDepartureTime": "berechnete Abfahrt", "calculatedDepartureTimeFull": "berechnete Abfahrtszeit", - "ch": "CH", "computedTheoreticalMargin": "berechnete theoretische Marge", "dayCounter": "T+{{count}}", "departureTime": "angeforderte Abfahrt", diff --git a/front/public/locales/en/infraEditor.json b/front/public/locales/en/infraEditor.json index 159c60816d6..61d02beda84 100644 --- a/front/public/locales/en/infraEditor.json +++ b/front/public/locales/en/infraEditor.json @@ -411,10 +411,26 @@ "OperationalPoint": { "description": "This class describes the operational points of the corresponding infra.", "properties": { + "country_code": { + "description": "Country code of the operational point (E.g: FR for France)", + "title": "Country code" + }, "id": { "description": "Unique identifier of the object", "title": "Id" }, + "is_passenger_station": { + "description": "Whether or not the operational point is a passenger station", + "title": "Is passenger station" + }, + "main_code": { + "description": "Unique code of the operational point (E.g : trigram for SNCF)", + "title": "Main code" + }, + "name": { + "description": "Name of the operational point", + "title": "Name" + }, "parts": { "title": "Parts" }, @@ -422,28 +438,24 @@ "description": "Primary Location Code : https://rne.eu/it/products/ccs/crd/", "title": "Plc" }, - "weight": { - "description": "represents the significance of a PR", - "title": "Weight" - } - }, - "title": "Operational point" - }, - "OperationalPointExtensions": { - "title": "Operational point extensions" - }, - "OperationalPointIdentifierExtension": { - "properties": { - "name": { - "description": "Name of the operational point", - "title": "Name" + "secondary_code": { + "description": "THOR site code of the operational point", + "title": "Secondary code" + }, + "secondary_name": { + "description": "THOR site code long label of the operational point", + "title": "Secondary name" }, "uic": { "description": "International Union of Railways code of the operational point", "title": "Uic" + }, + "weight": { + "description": "represents the significance of a PR", + "title": "Weight" } }, - "title": "Operational point identifier extension" + "title": "Operational point" }, "OperationalPointPart": { "description": "Operational point part is a single point on the infrastructure. It's linked to an operational point.", @@ -475,31 +487,6 @@ }, "title": "Operational point part sncf extension" }, - "OperationalPointSncfExtension": { - "properties": { - "ch": { - "description": "THOR site code of the operational point", - "title": "Ch" - }, - "ch_long_label": { - "description": "THOR site code long label of the operational point", - "title": "Ch long label" - }, - "ch_short_label": { - "description": "THOR site code short label of the operational point", - "title": "Ch short label" - }, - "ci": { - "description": "THOR immutable code of the operational point", - "title": "Ci" - }, - "trigram": { - "description": "Unique SNCF trigram of the operational point", - "title": "Trigram" - } - }, - "title": "Operational point sncf extension" - }, "Route": { "description": "This class is used to describe routes on the infrastructure.", "properties": { diff --git a/front/public/locales/en/operational-studies.json b/front/public/locales/en/operational-studies.json index fa23d7a344b..1504fe2a024 100644 --- a/front/public/locales/en/operational-studies.json +++ b/front/public/locales/en/operational-studies.json @@ -407,8 +407,8 @@ "title_on": "{{count}} on {{total}} incompatible constraints" }, "infraLoading": "Infrastructure is loading…", - "inputOPTrigrams": "Enter the trigrams of operational points you want to the path to go through, separated by a space.", - "inputOPTrigramsExample": "E.g., LSN CIG CCS", + "inputOPMainCodes": "Enter the trigrams of operational points you want to the path to go through, separated by a space.", + "inputOPMainCodesExample": "E.g., LSN CIG CCS", "invalidTrainScheduleStep": "At least one of the waypoints could not be recognized", "inverseOD": "Reverse itinerary", "itineraryModal": { diff --git a/front/public/locales/en/translation.json b/front/public/locales/en/translation.json index a580a10fe7a..830c3e60243 100644 --- a/front/public/locales/en/translation.json +++ b/front/public/locales/en/translation.json @@ -823,7 +823,7 @@ "calculatedArrivalTime": "calculated arrival time", "calculatedDepartureTime": "calculated depart. time", "calculatedDepartureTimeFull": "calculated departure time", - "ch": "CH", + "secondaryCode": "secondary code", "computedTheoreticalMargin": "computed theoretical margin", "dayCounter": "D+{{count}}", "departureTime": "requested depart. time", diff --git a/front/public/locales/fr/operational-studies.json b/front/public/locales/fr/operational-studies.json index 5a92e2db08e..dab813b9b91 100644 --- a/front/public/locales/fr/operational-studies.json +++ b/front/public/locales/fr/operational-studies.json @@ -407,8 +407,8 @@ "title_on": "{{count}} sur {{total}} contraintes incompatibles" }, "infraLoading": "Infra en cours de chargement…", - "inputOPTrigrams": "Entrez la liste des trigrammes des points remarquables composant l'itinéraire souhaité.", - "inputOPTrigramsExample": "Ex : LSN CIG CCS", + "inputOPMainCodes": "Entrez la liste des trigrammes des points remarquables composant l'itinéraire souhaité.", + "inputOPMainCodesExample": "Ex : LSN CIG CCS", "invalidTrainScheduleStep": "Au moins un des points de passage n'a pas été reconnu", "inverseOD": "Inverser l'itinéraire", "itineraryModal": { diff --git a/front/public/locales/fr/translation.json b/front/public/locales/fr/translation.json index 5a5e99802ab..7e0a28ffe39 100644 --- a/front/public/locales/fr/translation.json +++ b/front/public/locales/fr/translation.json @@ -823,7 +823,7 @@ "calculatedArrivalTime": "arrivée calculée", "calculatedDepartureTime": "départ calculé", "calculatedDepartureTimeFull": "départ calculé", - "ch": "CH", + "secondaryCode": "code secondaire", "computedTheoreticalMargin": "marge théorique calculée", "dayCounter": "J+{{count}}", "departureTime": "départ demandé", diff --git a/front/src/applications/operationalStudies/__tests__/upsertMapWaypointsInOperationalPoints.spec.ts b/front/src/applications/operationalStudies/__tests__/upsertMapWaypointsInOperationalPoints.spec.ts index 4d5f0271223..e1c8baa1bad 100644 --- a/front/src/applications/operationalStudies/__tests__/upsertMapWaypointsInOperationalPoints.spec.ts +++ b/front/src/applications/operationalStudies/__tests__/upsertMapWaypointsInOperationalPoints.spec.ts @@ -22,17 +22,16 @@ type Op = { const getOperationalPoints = (inputs: Op[]): NonNullable => inputs.map((op) => ({ id: op.name, + name: op.name, + uic: op.uic, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: op.track, position: op.positionOnTrack, local_track_name: 'V1', }, - extensions: { - identifier: { - name: op.name, - uic: op.uic, - }, - }, position: op.positionOnPath, weight: null, })); @@ -108,28 +107,26 @@ describe('upsertMapWaypointsInOperationalPoints', () => { expect(operationalPointsWithAllWaypoints).toEqual([ { id: 'West_station', + name: 'West_station', + uic: 2, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: 'TA1', position: 500, local_track_name: 'V1', }, - extensions: { - identifier: { - name: 'West_station', - uic: 2, - }, - }, position: 0, weight: null, }, { id: '2', - extensions: { - identifier: { - name: 't_requestedPoint', - uic: 0, - }, - }, + name: 't_requestedPoint', + uic: 0, + country_code: '??', + is_passenger_station: false, + main_code: '', part: { track: 'TA6', position: 7746000, @@ -140,33 +137,31 @@ describe('upsertMapWaypointsInOperationalPoints', () => { }, { id: 'Mid_West_station', + name: 'Mid_West_station', + uic: 3, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: 'TC1', position: 550, local_track_name: 'V1', }, - extensions: { - identifier: { - name: 'Mid_West_station', - uic: 3, - }, - }, position: 12050000, weight: null, }, { id: 'Mid_East_station', + name: 'Mid_East_station', + uic: 4, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: 'TD0', position: 14000, local_track_name: 'V1', }, - extensions: { - identifier: { - name: 'Mid_East_station', - uic: 4, - }, - }, position: 26500000, weight: null, }, @@ -221,12 +216,11 @@ describe('upsertMapWaypointsInOperationalPoints', () => { expect(operationalPointsWithAllWaypoints).toEqual([ { id: '1', - extensions: { - identifier: { - name: 't_requestedOrigin', - uic: 0, - }, - }, + name: 't_requestedOrigin', + uic: 0, + country_code: '??', + is_passenger_station: false, + main_code: '', part: { track: 'TA6', position: 6481000, @@ -237,28 +231,26 @@ describe('upsertMapWaypointsInOperationalPoints', () => { }, { id: 'Mid_West_station', + name: 'Mid_West_station', + uic: 3, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: 'TC0', position: 550, local_track_name: 'V1', }, - extensions: { - identifier: { - name: 'Mid_West_station', - uic: 3, - }, - }, position: 4069000, weight: null, }, { id: '2', - extensions: { - identifier: { - name: 't_requestedPoint', - uic: 0, - }, - }, + name: 't_requestedPoint', + uic: 0, + country_code: '??', + is_passenger_station: false, + main_code: '', part: { track: 'TC0', position: 679000, @@ -269,12 +261,11 @@ describe('upsertMapWaypointsInOperationalPoints', () => { }, { id: '3', - extensions: { - identifier: { - name: 't_requestedDestination', - uic: 0, - }, - }, + name: 't_requestedDestination', + uic: 0, + country_code: '??', + is_passenger_station: false, + main_code: '', part: { track: 'TC0', position: 883000, @@ -318,12 +309,11 @@ describe('upsertMapWaypointsInOperationalPoints', () => { expect(operationalPointsWithAllWaypoints).toEqual([ { id: '1', - extensions: { - identifier: { - name: 't_requestedOrigin', - uic: 0, - }, - }, + name: 't_requestedOrigin', + uic: 0, + country_code: '??', + is_passenger_station: false, + main_code: '', part: { track: 'TA6', position: 6481000, @@ -334,12 +324,11 @@ describe('upsertMapWaypointsInOperationalPoints', () => { }, { id: '2', - extensions: { - identifier: { - name: 't_requestedDestination', - uic: 0, - }, - }, + name: 't_requestedDestination', + uic: 0, + country_code: '??', + is_passenger_station: false, + main_code: '', part: { track: 'TA6', position: 4733000, @@ -400,49 +389,46 @@ describe('upsertMapWaypointsInOperationalPoints', () => { expect(operationalPointsWithAllWaypoints).toEqual([ { id: 'West_station', + name: 'West_station', + uic: 2, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: 'TA1', position: 500, local_track_name: 'V1', }, - extensions: { - identifier: { - name: 'West_station', - uic: 2, - }, - }, position: 0, weight: null, }, { id: 'Mid_West_station', + name: 'Mid_West_station', + uic: 3, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: 'TC1', position: 550, local_track_name: 'V1', }, - extensions: { - identifier: { - name: 'Mid_West_station', - uic: 3, - }, - }, position: 12050000, weight: null, }, { id: 'Mid_East_station', + name: 'Mid_East_station', + uic: 4, + country_code: '??', + is_passenger_station: true, + main_code: '', part: { track: 'TD0', position: 14000, local_track_name: 'V1', }, - extensions: { - identifier: { - name: 'Mid_East_station', - uic: 4, - }, - }, position: 26500000, weight: null, }, diff --git a/front/src/applications/operationalStudies/helpers/rankingSuggestions.ts b/front/src/applications/operationalStudies/helpers/rankingSuggestions.ts index 3b7374928d6..db147132774 100644 --- a/front/src/applications/operationalStudies/helpers/rankingSuggestions.ts +++ b/front/src/applications/operationalStudies/helpers/rankingSuggestions.ts @@ -7,7 +7,7 @@ import { secondaryCodeStarts, secondaryCodeIncludes, lastTokenMatchesIncludes, - shouldKeepTrigramLock, + shouldKeepMainCodeLock, tokenMatchesStartNoCh, tokenMatchesIncludesNoCh, lastTokenMatchesStarts, @@ -15,7 +15,7 @@ import { /** * ranks and filters suggestions based on a single search token. - * returns suggestions ordered by match quality: exact name > exact trigram > starts with > includes > secondary code matches + * returns suggestions ordered by match quality: exact name > exact mainCode > starts with > includes > secondary code matches */ export const rankSingleTokenSuggestions = ( suggestions: OperationalPointSuggestion[], @@ -28,12 +28,12 @@ export const rankSingleTokenSuggestions = ( const exactNames = suggestions.filter((s) => normalizeName(s.name) === tokenNormalized); - const trigramExact = suggestions.filter((s) => toUpper(s.trigram) === tokenUpper); - if (trigramExact.length && endWithSpace) return uniqBy(trigramExact, 'id'); + const mainCodeExact = suggestions.filter((s) => toUpper(s.mainCode) === tokenUpper); + if (mainCodeExact.length && endWithSpace) return uniqBy(mainCodeExact, 'id'); - const trigramStarts: OperationalPointSuggestion[] = []; + const mainCodeStarts: OperationalPointSuggestion[] = []; const nameStarts: OperationalPointSuggestion[] = []; - const trigramIncludes: OperationalPointSuggestion[] = []; + const mainCodeIncludes: OperationalPointSuggestion[] = []; const nameIncludes: OperationalPointSuggestion[] = []; const secondaryCodePrefixMatches: OperationalPointSuggestion[] = []; @@ -41,10 +41,10 @@ export const rankSingleTokenSuggestions = ( for (const s of suggestions) { const nameNorm = normalizeName(s.name); - const trigramUpper = toUpper(s.trigram); - if (trigramUpper.startsWith(tokenUpper)) trigramStarts.push(s); + const mainCodeUpper = toUpper(s.mainCode); + if (mainCodeUpper.startsWith(tokenUpper)) mainCodeStarts.push(s); if (nameNorm.startsWith(tokenNormalized)) nameStarts.push(s); - if (trigramUpper.includes(tokenUpper)) trigramIncludes.push(s); + if (mainCodeUpper.includes(tokenUpper)) mainCodeIncludes.push(s); if (nameNorm.includes(tokenNormalized)) nameIncludes.push(s); if (secondaryCodeStarts(s, tokenUpper)) secondaryCodePrefixMatches.push(s); if (secondaryCodeIncludes(s, tokenUpper)) secondaryCodeSubstringMatches.push(s); @@ -53,10 +53,10 @@ export const rankSingleTokenSuggestions = ( return uniqBy( [ ...exactNames, - ...trigramExact, - ...trigramStarts, + ...mainCodeExact, + ...mainCodeStarts, ...nameStarts, - ...trigramIncludes, + ...mainCodeIncludes, ...nameIncludes, ...secondaryCodePrefixMatches, ...secondaryCodeSubstringMatches, @@ -67,7 +67,7 @@ export const rankSingleTokenSuggestions = ( /** * Ranks suggestions when the user input contains multiple tokens. - * Prioritizes: trigram lock --> full phrase match --> base phrase + secondary code --> all tokens match. + * Prioritizes: mainCode lock --> full phrase match --> base phrase + secondary code --> all tokens match. */ export const rankMultiTokenSuggestions = ( suggestions: OperationalPointSuggestion[], @@ -96,7 +96,7 @@ export const rankMultiTokenSuggestions = ( const sortingBase = strictMatches.length ? strictMatches : suggestions; // Buckets grouped by match type - const trigramPinned: OperationalPointSuggestion[] = []; + const mainCodePinned: OperationalPointSuggestion[] = []; const allTokensStart: OperationalPointSuggestion[] = []; const fullPhraseMatches = { starts: [] as OperationalPointSuggestion[], @@ -111,12 +111,12 @@ export const rankMultiTokenSuggestions = ( for (const s of sortingBase) { const nameNorm = normalizeName(s.name); - const trigramUpper = toUpper(s.trigram); + const mainCodeUpper = toUpper(s.mainCode); const hasSecondaryCodePrefix = secondaryCodeStarts(s, lastTokenUpper); - // Trigram pinned - if (firstTokenUpper && trigramUpper === firstTokenUpper && shouldKeepTrigramLock(s, tokens)) { - trigramPinned.push(s); + // Main code pinned + if (firstTokenUpper && mainCodeUpper === firstTokenUpper && shouldKeepMainCodeLock(s, tokens)) { + mainCodePinned.push(s); } // Full phrase matching @@ -148,7 +148,7 @@ export const rankMultiTokenSuggestions = ( return uniqBy( [ - ...trigramPinned, + ...mainCodePinned, ...fullPhraseMatches.starts, ...fullPhraseMatches.includes, ...basePhraseMatches.startsWithSecondaryCode, diff --git a/front/src/applications/operationalStudies/helpers/searchPayload.ts b/front/src/applications/operationalStudies/helpers/searchPayload.ts index a44d9906743..6e0220ab578 100644 --- a/front/src/applications/operationalStudies/helpers/searchPayload.ts +++ b/front/src/applications/operationalStudies/helpers/searchPayload.ts @@ -2,10 +2,10 @@ import { toUpper } from 'utils/strings'; export const tokenClause = (token: string) => [ 'or', - ['=', ['trigram'], toUpper(token)], + ['=', ['main_code'], toUpper(token)], ['search', ['name'], token], - ['search', ['trigram'], token], - ['search', ['ch'], token], + ['search', ['main_code'], token], + ['search', ['secondary_code'], token], ]; export const buildMultiTokenQuery = (tokens: string[]) => ['and', ...tokens.map(tokenClause)]; @@ -13,8 +13,8 @@ export const buildMultiTokenQuery = (tokens: string[]) => ['and', ...tokens.map( export const searchQuery = (debouncedTrimmedInput: string) => [ 'or', ['search', ['name'], debouncedTrimmedInput], - ['search', ['trigram'], debouncedTrimmedInput], - ['search', ['ch'], debouncedTrimmedInput], + ['search', ['main_code'], debouncedTrimmedInput], + ['search', ['secondary_code'], debouncedTrimmedInput], ]; export const largePayload = (infraId: number | undefined, debouncedTrimmedInput: string) => ({ @@ -30,7 +30,7 @@ export const exactTrigramPayload = (infraId: number | undefined, firstTokenUpper object: 'operationalpoint', query: [ 'and', - ['=', ['trigram'], firstTokenUpper], + ['=', ['main_code'], firstTokenUpper], infraId !== undefined ? ['=', ['infra_id'], infraId] : true, ], }); diff --git a/front/src/applications/operationalStudies/helpers/suggestionMatchers.ts b/front/src/applications/operationalStudies/helpers/suggestionMatchers.ts index b119700bb86..7ed58043816 100644 --- a/front/src/applications/operationalStudies/helpers/suggestionMatchers.ts +++ b/front/src/applications/operationalStudies/helpers/suggestionMatchers.ts @@ -34,14 +34,16 @@ export const hasChPrefix = (s: OperationalPointSuggestion, chTokenUpper: string) export const tokenMatchesIncludesNoCh = (s: OperationalPointSuggestion, token: string) => { const tokenNormalized = normalizeName(token); const tokenUpper = toUpper(token); - return normalizeName(s.name).includes(tokenNormalized) || toUpper(s.trigram).includes(tokenUpper); + return ( + normalizeName(s.name).includes(tokenNormalized) || toUpper(s.mainCode).includes(tokenUpper) + ); }; export const tokenMatchesStartNoCh = (s: OperationalPointSuggestion, token: string) => { const tokenNormalized = normalizeName(token); const tokenUpper = toUpper(token); return ( - normalizeName(s.name).startsWith(tokenNormalized) || toUpper(s.trigram).startsWith(tokenUpper) + normalizeName(s.name).startsWith(tokenNormalized) || toUpper(s.mainCode).startsWith(tokenUpper) ); }; @@ -52,8 +54,8 @@ export const lastTokenMatchesIncludes = (s: OperationalPointSuggestion, lastToke export const lastTokenMatchesStarts = (s: OperationalPointSuggestion, lastToken: string) => tokenMatchesStartNoCh(s, lastToken) || secondaryCodeStarts(s, toUpper(lastToken)); -// Trigram helpers // -export const shouldKeepTrigramLock = (s: OperationalPointSuggestion, tokens: string[]) => { +// Main code helpers // +export const shouldKeepMainCodeLock = (s: OperationalPointSuggestion, tokens: string[]) => { if (tokens.length <= 1) return true; const nameNorm = normalizeName(s.name); diff --git a/front/src/applications/operationalStudies/helpers/upsertMapWaypointsInOperationalPoints.ts b/front/src/applications/operationalStudies/helpers/upsertMapWaypointsInOperationalPoints.ts index 0083f272b8c..be91c28ba7e 100644 --- a/front/src/applications/operationalStudies/helpers/upsertMapWaypointsInOperationalPoints.ts +++ b/front/src/applications/operationalStudies/helpers/upsertMapWaypointsInOperationalPoints.ts @@ -48,12 +48,11 @@ export function upsertMapWaypointsInOperationalPoints( } const baseFormattedStep = { - extensions: { - identifier: { - name: stepName, - uic: 0, - }, - }, + name: stepName, + uic: 0, + country_code: '??', + is_passenger_station: false, + main_code: '', part: { track: location.track, position: location.offset, local_track_name: 'V1' }, position: positionOnPath, weight: HIGHEST_PRIORITY_WEIGHT, @@ -84,8 +83,8 @@ export function upsertMapWaypointsInOperationalPoints( const matchedIndex = operationalPointsWithAllWaypoints.findIndex( (op) => location.operational_point.type === 'uic' && - location.operational_point.uic === op.extensions?.identifier?.uic && - location.operational_point.secondary_code === op.extensions?.sncf?.ch + location.operational_point.uic === op.uic && + location.operational_point.secondary_code === op.secondary_code ); if (matchedIndex !== -1) { diff --git a/front/src/applications/operationalStudies/hooks/useOperationalPointSearch.ts b/front/src/applications/operationalStudies/hooks/useOperationalPointSearch.ts index b3eccbc7662..5e46868849e 100644 --- a/front/src/applications/operationalStudies/hooks/useOperationalPointSearch.ts +++ b/front/src/applications/operationalStudies/hooks/useOperationalPointSearch.ts @@ -13,7 +13,7 @@ import { exactTrigramPayload, multiPayloadFromTokens, } from '../helpers/searchPayload'; -import { selectSecondaryCode, shouldKeepTrigramLock } from '../helpers/suggestionMatchers'; +import { selectSecondaryCode, shouldKeepMainCodeLock } from '../helpers/suggestionMatchers'; import { buildOpSuggestion } from '../views/Scenario/components/ManageTrainSchedule/helpers/buildOpSuggestion'; import type { OperationalPointSuggestion } from '../views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent'; @@ -137,7 +137,7 @@ export const useOperationalPointSearch = ({ // 2) We try to lock trigram from the "large" call if (firstTokenUpper) { const keptFromLarge = suggestionsLarge.filter( - (s) => toUpper(s.trigram) === firstTokenUpper && shouldKeepTrigramLock(s, tokens) + (s) => toUpper(s.mainCode) === firstTokenUpper && shouldKeepMainCodeLock(s, tokens) ); if (keptFromLarge.length > 0) { @@ -158,7 +158,7 @@ export const useOperationalPointSearch = ({ if (exactRes.length > 0) { const suggestionsExact = buildOpSuggestion(exactRes); - const keptExact = suggestionsExact.filter((s) => shouldKeepTrigramLock(s, tokens)); + const keptExact = suggestionsExact.filter((s) => shouldKeepMainCodeLock(s, tokens)); if (keptExact.length > 0) { apply(uniqBy([...keptExact, ...suggestionsExact, ...suggestionsLarge], 'id')); diff --git a/front/src/applications/operationalStudies/hooks/usePathProjection.ts b/front/src/applications/operationalStudies/hooks/usePathProjection.ts index 895d290234b..1dc9631e03f 100644 --- a/front/src/applications/operationalStudies/hooks/usePathProjection.ts +++ b/front/src/applications/operationalStudies/hooks/usePathProjection.ts @@ -19,7 +19,6 @@ import { getTrainIdUsedForProjection, } from 'reducers/simulationResults/selectors'; import { useAppDispatch } from 'store'; -import { formatUicToCi } from 'utils/strings'; import { extractEditoastIdFromPacedTrainId, extractPacedTrainIdFromOccurrenceId, @@ -66,21 +65,14 @@ const createVirtualOp = ( return { id: virtualId, - extensions: { - identifier: { - name: virtualName, - uic: opRef.type === 'uic' ? opRef.uic : 0, - }, - sncf: { - ch: (opRef.type !== 'id' && opRef.secondary_code) || '', - ch_long_label: '', - ch_short_label: '', - ci: opRef.type === 'uic' ? Number(formatUicToCi(opRef.uic)) : 0, - trigram: opRef.type === 'trigram' ? opRef.trigram : '', - }, - }, + name: virtualName, + uic: opRef.type === 'uic' ? opRef.uic : 0, + secondary_code: (opRef.type !== 'id' && opRef.secondary_code) || '', + main_code: opRef.type === 'trigram' ? opRef.trigram : '', position, weight, + country_code: '??', + is_passenger_station: false, }; }; @@ -241,9 +233,14 @@ const usePathProjection = ( if (matchedOp) { // MATCHED: Point exists in infrastructure normalizedOps.push({ + country_code: matchedOp.country_code, id: matchedOp.id, - extensions: matchedOp.extensions, + is_passenger_station: matchedOp.is_passenger_station, + main_code: matchedOp.main_code, + name: matchedOp.name, position, + secondary_code: matchedOp.secondary_code, + uic: matchedOp.uic, weight, }); } else { diff --git a/front/src/applications/operationalStudies/utils.ts b/front/src/applications/operationalStudies/utils.ts index c4b8b34359f..edc15741d5d 100644 --- a/front/src/applications/operationalStudies/utils.ts +++ b/front/src/applications/operationalStudies/utils.ts @@ -416,12 +416,12 @@ export const matchOpRefAndOp = ( } if (location.operational_point.type === 'uic') { return ( - location.operational_point.uic === op.extensions?.identifier?.uic && - location.operational_point.secondary_code === op.extensions?.sncf?.ch + location.operational_point.uic === op.uic && + location.operational_point.secondary_code === op.secondary_code ); } return ( - location.operational_point.trigram === op.extensions?.sncf?.trigram && - location.operational_point.secondary_code === op.extensions?.sncf?.ch + location.operational_point.trigram === op.main_code && + location.operational_point.secondary_code === op.secondary_code ); }; diff --git a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/MacroEditorState.ts b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/MacroEditorState.ts index 60565749a05..17c858a0ea4 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/MacroEditorState.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/MacroEditorState.ts @@ -258,13 +258,12 @@ export default class MacroEditorState { * Given a search result item, returns all possible pathKeys, ordered by weight. */ static getPathKeys(op: OperationalPoint): string[] { - const { uic } = op.extensions?.identifier ?? {}; - const { trigram, ch } = op.extensions?.sncf ?? {}; + const { main_code, secondary_code, uic } = op ?? {}; const result = []; result.push(`op_id:${op.id}`); - if (trigram) result.push(`trigram:${trigram}${ch ? `/${ch}` : ''}`); - if (uic) result.push(`uic:${uic}${ch ? `/${ch}` : ''}`); + if (main_code) result.push(`trigram:${main_code}${secondary_code ? `/${secondary_code}` : ''}`); + if (uic) result.push(`uic:${uic}${secondary_code ? `/${secondary_code}` : ''}`); for (const opPart of op.parts) { result.push(`track_offset:${opPart.track}+${opPart.position}`); } diff --git a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/node.ts b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/node.ts index 6c7e9c51270..ca8b5ce7c85 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/node.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/ngeToOsrd/node.ts @@ -62,26 +62,26 @@ export const handleNodeOperation = async ({ case 'update': { if (indexNode) { if (indexNode.dbId) { - // Update the key if trigram has changed and key is based on it + // Update the key if mainCode has changed and key is based on it let nodeKey = indexNode.path_item_key; - let trigram = node.betriebspunktName; - if (nodeKey.startsWith('trigram:') && indexNode.trigram !== trigram) { - if (!trigram.includes('/')) { + let mainCode = node.betriebspunktName; + if (nodeKey.startsWith('trigram:') && indexNode.trigram !== mainCode) { + if (!mainCode.includes('/')) { const secondaryCode = await fetchStationSecondaryCode( - trigram, + mainCode, state.infraId, dispatch ); if (secondaryCode) { - trigram = `${trigram}/${secondaryCode}`; + mainCode = `${mainCode}/${secondaryCode}`; } } - nodeKey = `trigram:${trigram}`; + nodeKey = `trigram:${mainCode}`; } await updateMacroNode(state, dispatch, { ...indexNode, ...castNgeNode(node, netzgrafikDto.labels), - trigram, + trigram: mainCode, dbId: indexNode.dbId, path_item_key: nodeKey, }); diff --git a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/osrdToNge.ts b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/osrdToNge.ts index 48d94ff00e6..180e3588dcc 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/osrdToNge.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/osrdToNge.ts @@ -325,11 +325,11 @@ export const loadAndIndexNge = async ( .flatMap((trainSchedule) => trainSchedule.pathOps) .filter((op) => op !== null); for (const op of pathOps) { - const { trigram, ch } = op.extensions?.sncf ?? {}; + const { main_code, secondary_code } = op ?? {}; for (const pathKey of MacroEditorState.getPathKeys(op)) { state.updateNodeDataByKey(pathKey, { - full_name: op.extensions?.identifier?.name, - trigram: trigram ? trigram + (ch ? `/${ch}` : '') : null, + full_name: op.name, + trigram: main_code ? main_code + (secondary_code ? `/${secondary_code}` : '') : null, geocoord: op.geo ? { lng: op.geo.coordinates[0], lat: op.geo.coordinates[1] } : undefined, }); } diff --git a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/utils.ts b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/utils.ts index 7481af5d42f..1b72ca9f3a2 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/utils.ts +++ b/front/src/applications/operationalStudies/views/Scenario/components/MacroEditor/utils.ts @@ -270,7 +270,7 @@ export const fetchStationSecondaryCode = async ( ) => { const searchPayload = { object: 'operationalpoint', - query: ['and', ['=', ['infra_id'], infraId], ['=', ['trigram'], trigram]], + query: ['and', ['=', ['infra_id'], infraId], ['=', ['main_code'], trigram]], }; const searchResults = (await dispatch( osrdEditoastApi.endpoints.postSearch.initiate({ @@ -278,6 +278,6 @@ export const fetchStationSecondaryCode = async ( }) ).unwrap()) as SearchResultItemOperationalPoint[]; - const stationOp = searchResults.find((op) => op.ch === 'BV' || op.ch === '00'); - return stationOp?.ch; + const stationOp = searchResults.find((op) => op.is_passenger_station); + return stationOp?.secondary_code; }; diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.scss b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.scss index e40464ddf3c..328eba2277d 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.scss +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.scss @@ -8,7 +8,7 @@ border-radius: 4px; background-color: #ffffff; - &-trigram { + &-main-code { display: inline-flex; justify-content: center; align-items: center; diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.tsx index 8b63e3934ff..ea67692c4e8 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/Itinerary/ComboBoxCustomList/ListElementComponent.tsx @@ -13,7 +13,7 @@ export type OpSecondaryCode = { export type OperationalPointSuggestion = { id: string; - trigram: string; + mainCode: string; uic: number; name: string; secondaryCodeList: OpSecondaryCode[]; @@ -51,7 +51,7 @@ export const ListElementComponent = ({ onSelect?.(suggestion, defaultSecondaryCode); }} > - {suggestion.trigram} + {suggestion.mainCode} { - const ch = op.extensions?.sncf?.ch; - const uic = op.extensions?.identifier?.uic; + const ch = op.secondary_code; + const uic = op.uic; if (uic != null) return { type: 'uic', uic, secondary_code: ch, }; - const trigram = op.extensions?.sncf?.trigram; + const trigram = op.main_code; if (trigram) return { type: 'trigram', @@ -50,7 +50,7 @@ export const buildOpRef = (op: OperationalPoint): OperationalPointReference => { }; export const buildOpDisplayName = (op: OperationalPoint): string => { - const name = op.extensions?.identifier?.name ?? op.extensions?.sncf?.trigram ?? ''; - const ch = op.extensions?.sncf?.ch; - return ch ? `${name} ${ch}` : name; + const name = op.name ?? op.main_code; + const secondaryCode = op.secondary_code ?? ''; + return secondaryCode ? `${name} ${secondaryCode}` : name; }; diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx index 2595492be35..db4703ca113 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/AddPathStepPopup.tsx @@ -120,16 +120,16 @@ const AddPathStepPopup = ({ }); let opRef: OperationalPointReference; - const uic = operationalPoint.extensions?.identifier?.uic; + const uic = operationalPoint.uic; if (uic) { - opRef = { type: 'uic', uic, secondary_code: operationalPoint.extensions?.sncf?.ch }; + opRef = { type: 'uic', uic, secondary_code: operationalPoint.secondary_code }; } else { opRef = { type: 'id', operational_point: operationalPoint.id }; } setClickedOp({ id: uuidV4(), - name: operationalPoint.extensions?.identifier?.name, + name: operationalPoint.name, location: { type: 'operational_point_part_reference', operational_point: opRef }, tracks: trackPartCoordinates, }); diff --git a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx index 0c84fbb53a0..a6261d47d87 100644 --- a/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx +++ b/front/src/applications/operationalStudies/views/Scenario/components/ManageTrainSchedule/ManageTrainScheduleMap/OperationalPointPopupDetails.tsx @@ -40,9 +40,9 @@ const OperationalPointPopupDetails = ({ {operationalPoint.feature.properties!.extensions_sncf_line_code}
- {operationalPoint.feature.properties!.extensions_identifier_name}
- {operationalPoint.feature.properties!.extensions_sncf_trigram}{' '} - {operationalPoint.feature.properties!.extensions_sncf_ch} + {operationalPoint.feature.properties!.name}
+ {operationalPoint.feature.properties!.main_code}{' '} + {operationalPoint.feature.properties!.secondary_code}