PATCH v1.0.1 //

-> Amélioration de la mise en relation des TYPES DE TRAINS PAR CODE DE CIRCULATION (BETA)
This commit is contained in:
AdminGit committed 2026-10-07 01:10:54 +02:00
1 parent d7d4f9cecf
commit c30a0e9002
32 files changed
+2042 -532

No files matched your search

+935
View File
@@ -0,0 +1,935 @@
/**
* Moteur de classification et de corrélation des types de trains et opérateurs ferroviaires
* ferrovia-infogare - Compatible afficheurs classiques (Hall & Quai)
*
* Hiérarchie de décision :
* 1. Codes d'exploitation GTFS officiels (trip_id SNCF Open Data)
* 2. Détection des services spécifiques (Lyria, Ouigo Classique, Intercités de Nuit, etc.)
* 3. Plan de Numérotation officiel SNCF (séries régionales TER hermétiques par blocs de numéros)
* 4. Détection géographique par score d'arrêts et de terminus (sans faux-positifs)
* 5. Re-routage vers TER générique ou SNCF Voyageurs en dernier recours
*/
// Normalisation des chaînes (minuscules, sans accents, espaces normalisés)
export const normalize = (str) => {
if (!str) return '';
return String(str)
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9]/g, ' ')
.replace(/\s+/g, ' ')
.trim();
};
// Extraction des chiffres d'un numéro de train (sans zéros initiaux)
export const extractTrainDigits = (num) => {
if (!num) return '';
const digits = String(num).replace(/\D/g, '');
return digits.replace(/^0+/, '') || digits;
};
// Services routiers par autocar (3xxxx / 3xxxxx)
export const isCarTrainNumber = (num) => {
const d = String(num || '').replace(/\D/g, '');
return (d.length === 5 && d.startsWith('3')) || (d.length === 6 && d.startsWith('3'));
};
/**
* Référentiel des marques, types de trains et logos (monochrome Hall & couleur Quai)
*/
export const BRANDS = {
INOUi: {
id: 'inoui',
typeLabel: 'TGV inOUI',
quaiLabel: 'TGV INOUI',
operator: 'SNCF Voyageurs',
category: 'TGV',
isTgv: true,
hallLogo: '/type/logo-inoui.svg',
quaiLogo: '/type/quais/logo-inoui-color.svg',
showVoyageursText: false
},
TGV_GENERIC: {
id: 'tgv',
typeLabel: 'TGV',
quaiLabel: 'TGV',
operator: 'SNCF Voyageurs',
category: 'TGV',
isTgv: true,
hallLogo: '/type/logo-tgv.svg',
quaiLogo: '/type/quais/logo-tgv-color.svg',
showVoyageursText: false
},
LYRIA: {
id: 'lyria',
typeLabel: 'TGV Lyria',
quaiLabel: 'TGV Lyria',
operator: 'Lyria SAS',
category: 'TGV',
isTgv: true,
hallLogo: '/type/logo-lyria.svg',
quaiLogo: '/type/quais/logo-lyria-color.svg',
showVoyageursText: false
},
OUIGO_CLASSIQUE: {
id: 'ouigo-classique',
typeLabel: 'OUIGO Train Classique',
quaiLabel: 'OUIGO Classique',
operator: 'SNCF Voyageurs',
category: 'GRANDES_LIGNES',
isTgv: false,
hallLogo: '/type/logo-ouigo-classique.svg',
quaiLogo: '/type/quais/logo-ouigo-classique-color.svg',
showVoyageursText: false
},
OUIGO_GV: {
id: 'ouigo',
typeLabel: 'OUIGO',
quaiLabel: 'OUIGO',
operator: 'SNCF Voyageurs',
category: 'TGV',
isTgv: true,
hallLogo: '/type/logo-ouigo.svg',
quaiLogo: '/type/quais/logo-ouigo-color.svg',
showVoyageursText: false
},
INTERCITES_NUIT: {
id: 'intercites-nuit',
typeLabel: 'INTERCITÉS DE NUIT',
quaiLabel: 'Intercités de Nuit',
operator: 'SNCF Voyageurs',
category: 'GRANDES_LIGNES',
isTgv: false,
hallLogo: '/type/logo-lunea.svg',
quaiLogo: '/type/quais/logo-lunea-color.svg',
showVoyageursText: false
},
INTERCITES: {
id: 'intercites',
typeLabel: 'INTERCITÉS',
quaiLabel: 'INTERCITÉS',
operator: 'SNCF Voyageurs',
category: 'GRANDES_LIGNES',
isTgv: false,
hallLogo: '/type/logo-intercites.svg',
quaiLogo: '/type/quais/logo-intercite-color.svg',
showVoyageursText: false
},
EUROSTAR: {
id: 'eurostar',
typeLabel: 'EUROSTAR',
quaiLabel: 'Eurostar',
operator: 'Eurostar Group',
category: 'INTERNATIONAL',
isTgv: true,
hallLogo: '/type/logo-eurostar.svg',
quaiLogo: '/type/quais/logo-eurostar-color.svg',
showVoyageursText: false
},
THALYS: {
id: 'thalys',
typeLabel: 'THALYS',
quaiLabel: 'Thalys',
operator: 'Eurostar Group',
category: 'INTERNATIONAL',
isTgv: true,
hallLogo: '/type/logo-thalys.svg',
quaiLogo: '/type/quais/logo-thalys-color.svg',
showVoyageursText: false
},
ICE: {
id: 'ice',
typeLabel: 'ICE',
quaiLabel: 'ICE',
operator: 'DB / SNCF',
category: 'INTERNATIONAL',
isTgv: true,
hallLogo: '/type/logo-ice.svg',
quaiLogo: '/type/quais/logo-ice-color.svg',
showVoyageursText: false
},
DB: {
id: 'db',
typeLabel: 'DB',
quaiLabel: 'DB',
operator: 'Deutsche Bahn',
category: 'INTERNATIONAL',
isTgv: false,
hallLogo: '/type/logo-db.svg',
quaiLogo: '/type/quais/logo-db-color.svg',
showVoyageursText: false
},
FRECCIAROSSA: {
id: 'frecciarossa',
typeLabel: 'FRECCIAROSSA',
quaiLabel: 'Frecciarossa',
operator: 'Trenitalia France',
category: 'INTERNATIONAL',
isTgv: true,
hallLogo: '/type/logo-frecciarossa.svg',
quaiLogo: '/type/quais/logo-frecciarossa-color.svg',
showVoyageursText: false
},
RENFE_AVE: {
id: 'renfe-ave',
typeLabel: 'Renfe AVE',
quaiLabel: 'Renfe AVE',
operator: 'Renfe',
category: 'INTERNATIONAL',
isTgv: true,
hallLogo: '/type/logo-renfeave.svg',
quaiLogo: '/type/quais/logo-renfeave-color.svg',
showVoyageursText: false
},
AUTOCAR: {
id: 'autocar',
typeLabel: 'AUTOCAR',
quaiLabel: 'Autocar',
operator: 'SNCF Voyageurs',
category: 'AUTOCAR',
isTgv: false,
hallLogo: '/type/track-car.svg',
quaiLogo: '/type/track-car.svg',
showVoyageursText: false
},
TRAMTRAIN: {
id: 'tramtrain',
typeLabel: 'Tram-train',
quaiLabel: 'Tram-train',
operator: 'TER',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-tramtrain.svg',
quaiLogo: '/type/quais/logo-tramtrain-color.svg',
showVoyageursText: false
},
MOBIGO: {
id: 'mobigo',
typeLabel: 'MOBIGO',
quaiLabel: 'Mobigo',
operator: 'Région Bourgogne-Franche-Comté',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-mobigo.svg',
quaiLogo: '/type/quais/logo-mobigo-color.svg',
showVoyageursText: false
},
ZOU: {
id: 'zou',
typeLabel: 'ZOU !',
quaiLabel: 'ZOU!',
operator: 'Région Sud / PACA',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-zou.svg',
quaiLogo: '/type/quais/logo-zou-color.svg',
showVoyageursText: false
},
FLUO: {
id: 'fluo',
typeLabel: 'FLUO',
quaiLabel: 'TER Fluo',
operator: 'Région Grand Est',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-fluo.svg',
quaiLogo: '/type/quais/logo-fluo-color.svg',
showVoyageursText: false
},
ALEOP: {
id: 'aleop',
typeLabel: 'ALÉOP',
quaiLabel: 'Aléop',
operator: 'Région Pays de la Loire',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-aleop.svg',
quaiLogo: '/type/quais/logo-aleop-color.svg',
showVoyageursText: false
},
BREIZHGO: {
id: 'breizhgo',
typeLabel: 'BREIZHGO',
quaiLabel: 'BreizhGo',
operator: 'Région Bretagne',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-breizhgo.svg',
quaiLogo: '/type/quais/logo-breizhgo-color.svg',
showVoyageursText: false
},
NOMAD: {
id: 'nomad',
typeLabel: 'NOMAD',
quaiLabel: 'Nomad',
operator: 'Région Normandie',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-nomad.svg',
quaiLogo: '/type/quais/logo-nomad-color.svg',
showVoyageursText: false
},
REMI: {
id: 'remi',
typeLabel: 'RÉMI',
quaiLabel: 'Rémi',
operator: 'Région Centre-Val de Loire',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-remi.svg',
quaiLogo: '/type/quais/logo-remi-color.svg',
showVoyageursText: false
},
LIO: {
id: 'lio',
typeLabel: 'LIÒ',
quaiLabel: 'liO',
operator: 'Région Occitanie',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-lio.svg',
quaiLogo: '/type/quais/logo-lio-color.svg',
showVoyageursText: false
},
HAUTS_DE_FRANCE: {
id: 'hautsdefrance',
typeLabel: 'HAUTS-DE-FRANCE',
quaiLabel: 'Hauts-de-France',
operator: 'Région Hauts-de-France',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-hautsdefrance.svg',
quaiLogo: '/type/quais/logo-hautsdefrance-color.svg',
showVoyageursText: false
},
AUVERGNE_RHONE_ALPES: {
id: 'auvergerhonealpes',
typeLabel: 'AUVERGNE-RHÔNE-ALPES',
quaiLabel: 'Auvergne-Rhône-Alpes',
operator: 'Région Auvergne-Rhône-Alpes',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-auvergerhonealpes.svg',
quaiLogo: '/type/quais/logo-auvergerhonealpes-color.svg',
showVoyageursText: false
},
NOUVELLE_AQUITAINE: {
id: 'nouvelleaquitaine',
typeLabel: 'NOUVELLE-AQUITAINE',
quaiLabel: 'Nouvelle-Aquitaine',
operator: 'Région Nouvelle-Aquitaine',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-teraquitaine.svg',
quaiLogo: '/type/quais/logo-nouvelleaquitaine-color.svg',
showVoyageursText: false
},
TER_GENERIC: {
id: 'ter',
typeLabel: 'TER',
quaiLabel: 'TER',
operator: 'SNCF Voyageurs',
category: 'TER',
isTgv: false,
hallLogo: '/type/logo-ter.svg',
quaiLogo: '/type/quais/logo-ter-color.svg',
showVoyageursText: false
},
SBB: {
id: 'sbb',
typeLabel: 'SBB',
quaiLabel: 'SBB',
operator: 'CFF / SBB',
category: 'INTERNATIONAL',
isTgv: false,
hallLogo: '/type/logo-sbb.svg',
quaiLogo: '/type/quais/logo-sbb-color.svg',
showVoyageursText: false
},
SNCB: {
id: 'sncb',
typeLabel: 'SNCB',
quaiLabel: 'SNCB',
operator: 'SNCB / NMBS',
category: 'INTERNATIONAL',
isTgv: false,
hallLogo: '/type/logo-sncb.svg',
quaiLogo: '/type/quais/logo-sncb-color.svg',
showVoyageursText: false
},
CFL: {
id: 'cfl',
typeLabel: 'CFL',
quaiLabel: 'CFL',
operator: 'CFL',
category: 'INTERNATIONAL',
isTgv: false,
hallLogo: '/type/logo-cfl.svg',
quaiLogo: '/type/quais/logo-cfl-color.svg',
showVoyageursText: false
},
SNCF_GENERIC: {
id: 'sncf',
typeLabel: 'SNCF',
quaiLabel: 'SNCF',
operator: 'SNCF Voyageurs',
category: 'SNCF',
isTgv: false,
hallLogo: '/type/logo-sncf.svg',
quaiLogo: '/type/quais/logo-sncf-color.svg',
showVoyageursText: true
}
};
/**
* Plan de Numérotation officiel SNCF (Séries régionales TER)
* Chaque autorité organisatrice régionale dispose de tranches numériques réservées.
*/
function getRegionByTrainNumber(numStr) {
const clean = String(numStr || '').replace(/\D/g, '');
if (!clean || clean.length < 3) return null;
// 1. Occitanie (liO) : 869xxx, 870xxx, 871xxx, 872xxx, 876xxx, 877xxx (et 5 chiffres : 869xx, 870xx, 876xx, 877xx)
if (
clean.startsWith('869') ||
clean.startsWith('870') ||
clean.startsWith('871') ||
clean.startsWith('872') ||
clean.startsWith('876') ||
clean.startsWith('877')
) {
return BRANDS.LIO;
}
// 2. Centre-Val de Loire (Rémi) : 860xxx, 861xxx, 862xxx, 863xxx (et 860xx à 863xx)
if (
clean.startsWith('860') ||
clean.startsWith('861') ||
clean.startsWith('862') ||
clean.startsWith('863')
) {
return BRANDS.REMI;
}
// 3. Nouvelle-Aquitaine : 864xxx, 865xxx, 866xxx, 867xxx, 868xxx (et 864xx à 868xx)
if (
clean.startsWith('864') ||
clean.startsWith('865') ||
clean.startsWith('866') ||
clean.startsWith('867') ||
clean.startsWith('868')
) {
return BRANDS.NOUVELLE_AQUITAINE;
}
// 4. Grand Est (Fluo) : 83xxxx (et 83xxx)
if (clean.startsWith('83')) {
return BRANDS.FLUO;
}
// 5. Bourgogne-Franche-Comté (Mobigo) : 89xxxx, 840xxx, 841xxx (et 89xxx, 840xx, 841xx)
if (
clean.startsWith('89') ||
clean.startsWith('840') ||
clean.startsWith('841')
) {
return BRANDS.MOBIGO;
}
// 6. Hauts-de-France : 842xxx à 849xxx (et 842xx à 849xx)
if (['842', '843', '844', '845', '846', '847', '848', '849'].some(p => clean.startsWith(p))) {
return BRANDS.HAUTS_DE_FRANCE;
}
// 7. Normandie (Nomad) : 852xxx, 853xxx (et 852xx, 853xx)
if (clean.startsWith('852') || clean.startsWith('853')) {
return BRANDS.NOMAD;
}
// 8. Bretagne (BreizhGo) : 854xxx, 855xxx, 856xxx (et 854xx à 856xx)
if (clean.startsWith('854') || clean.startsWith('855') || clean.startsWith('856')) {
return BRANDS.BREIZHGO;
}
// 9. Pays de la Loire (Aléop) : 857xxx, 858xxx, 859xxx (et 857xx à 859xx)
if (clean.startsWith('857') || clean.startsWith('858') || clean.startsWith('859')) {
return BRANDS.ALEOP;
}
// 10. Auvergne-Rhône-Alpes : 873xxx à 875xxx, 883xxx, 884xxx, 889xxx
if (['873', '874', '875', '883', '884', '889'].some(p => clean.startsWith(p))) {
return BRANDS.AUVERGNE_RHONE_ALPES;
}
// 11. PACA / Sud (Zou !) : 880xxx, 881xxx, 882xxx, 885xxx, 886xxx, 887xxx, 888xxx
if (['880', '881', '882', '885', '886', '887', '888'].some(p => clean.startsWith(p))) {
return BRANDS.ZOU;
}
return null;
}
/**
* Gares de référence par région ferroviaire pour analyse de parcours
* (Seuls des noms de villes ou gares réelles sans mots génériques comme 'centre' ou 'est')
*/
const REGIONAL_STATIONS = {
lio: [
'perpignan', 'narbonne', 'toulouse', 'nimes', 'beziers', 'sete', 'montpellier',
'cerbere', 'portbou', 'villefranche', 'carcassonne', 'agde', 'elne', 'argeles',
'collioure', 'port vendres', 'banyuls', 'rodez', 'albi', 'tarbes', 'montauban',
'castres', 'lourdes', 'mende', 'foix', 'latour de carol', 'ax les thermes',
'castelnaudary', 'frontignan', 'port la nouvelle', 'leucate', 'salses', 'rivesaltes'
],
remi: [
'orleans', 'tours', 'bourges', 'blois', 'chartres', 'chateauroux', 'vierzon',
'vendome', 'saint pierre des corps', 'amboise', 'nogent le rotrou', 'salbris',
'chinon', 'loches', 'issoudun', 'romorantin', 'montargis', 'gien'
],
mobigo: [
'dijon', 'besancon', 'belfort', 'auxerre', 'nevers', 'chalon sur saone',
'montbeliard', 'macon', 'sens', 'dole', 'creusot', 'paray le monial',
'beaune', 'lons le saunier', 'pontarlier', 'vesoul'
],
zou: [
'marseille', 'nice', 'toulon', 'cannes', 'antibes', 'avignon', 'grasse',
'aubagne', 'miramas', 'arles', 'hyeres', 'menton', 'cagnes sur mer',
'frejus', 'saint raphael', 'la ciotat', 'gap', 'briancon', 'manosque'
],
fluo: [
'strasbourg', 'nancy', 'metz', 'mulhouse', 'colmar', 'reims', 'troyes',
'epinal', 'thionville', 'charleville', 'saint die', 'haguenau',
'sarreguemines', 'bar le duc', 'luneville', 'sedan', 'forbach', 'selestat'
],
aleop: [
'nantes', 'angers', 'le mans', 'saint nazaire', 'cholet', 'la roche sur yon',
'saumur', 'laval', 'les sables d olonne', 'ancenis', 'clisson', 'chateaubriant'
],
breizhgo: [
'rennes', 'brest', 'quimper', 'saint malo', 'vannes', 'lorient',
'saint brieuc', 'morlaix', 'lannion', 'vitre', 'redon', 'guingamp'
],
nomad: [
'rouen', 'caen', 'le havre', 'cherbourg', 'evreux', 'trouville deauville',
'dieppe', 'lisieux', 'granville', 'alencon', 'bernay', 'bayeux'
],
hautsdefrance: [
'lille', 'amiens', 'calais', 'dunkerque', 'valenciennes', 'arras',
'boulogne sur mer', 'douai', 'creil', 'saint quentin', 'lens', 'bethune',
'compiegne', 'cambrai', 'tourcoing', 'roubaix'
],
auvergerhonealpes: [
'lyon', 'grenoble', 'chambery', 'annecy', 'valence', 'clermont ferrand',
'saint etienne', 'bourg en bresse', 'roanne', 'vichy', 'aix les bains',
'annemasse', 'amberieu', 'riom', 'moulins'
],
nouvelleaquitaine: [
'bordeaux', 'limoges', 'poitiers', 'la rochelle', 'bayonne', 'pau',
'angouleme', 'perigueux', 'agen', 'niort', 'brive la gaillarde', 'dax',
'hendaye', 'saintes', 'arcachon'
]
};
/**
* Calcule la région la plus probable selon les arrêts desservis et le terminus
*/
function detectRegionByRouteScore(routeText) {
if (!routeText) return null;
const normalized = ` ${normalize(routeText)} `;
let bestRegion = null;
let maxScore = 0;
for (const [regionKey, stations] of Object.entries(REGIONAL_STATIONS)) {
let score = 0;
for (const station of stations) {
const stationNorm = ` ${normalize(station)} `;
if (normalized.includes(stationNorm)) {
score++;
}
}
if (score > maxScore) {
maxScore = score;
bestRegion = regionKey;
}
}
if (maxScore > 0 && bestRegion) {
switch (bestRegion) {
case 'lio': return BRANDS.LIO;
case 'remi': return BRANDS.REMI;
case 'mobigo': return BRANDS.MOBIGO;
case 'zou': return BRANDS.ZOU;
case 'fluo': return BRANDS.FLUO;
case 'aleop': return BRANDS.ALEOP;
case 'breizhgo': return BRANDS.BREIZHGO;
case 'nomad': return BRANDS.NOMAD;
case 'hautsdefrance': return BRANDS.HAUTS_DE_FRANCE;
case 'auvergerhonealpes': return BRANDS.AUVERGNE_RHONE_ALPES;
case 'nouvelleaquitaine': return BRANDS.NOUVELLE_AQUITAINE;
default: return null;
}
}
return null;
}
/**
* Fonction centrale de classification d'un train à partir de ses données SNCF
* @param {Object|string} trainOrType - Objet horaire ou type brut
* @param {string} [operatorParam] - Opérateur ou transporteur optionnel
* @param {string} [numParam] - Numéro de train optionnel
* @returns {Object} Résultat complet de classification
*/
export function classifyTrain(trainOrType, operatorParam = '', numParam = '') {
let horaire = null;
let rawType = '';
let opRaw = operatorParam || '';
let numRaw = numParam || '';
if (trainOrType && typeof trainOrType === 'object') {
horaire = trainOrType;
rawType = horaire.type_train || horaire.type || horaire.train_type || horaire.commercial_mode || horaire.mode || '';
opRaw = horaire.operator || horaire.op || horaire.transporteur || horaire.carrier || opRaw;
numRaw = horaire.numero_train || horaire.numero || horaire.number || horaire.num || horaire.trip_short_name || numRaw;
} else {
rawType = String(trainOrType || '');
}
const numTrain = extractTrainDigits(numRaw);
const numInt = parseInt(numTrain, 10) || 0;
const tripId = horaire ? String(horaire.trip_id || '') : '';
const isSubstitution = Boolean(horaire && (horaire.is_substitution || horaire.is_sub));
const origName = horaire ? (horaire.gare_depart_nom || horaire.orig || horaire.origin || '') : '';
const destName = horaire ? (horaire.gare_arrivee_nom || horaire.dest || horaire.destination || '') : '';
const stopsText = Array.isArray(horaire?.stops) ? horaire.stops.map(s => (s.nom || s.gare_nom || s.name || '')).join(' ') : '';
const fullRouteText = `${origName} ${destName} ${stopsText}`;
const routeNormalized = normalize(fullRouteText);
const typeNorm = normalize(rawType);
const opNorm = normalize(opRaw);
const combined = `${typeNorm} ${opNorm}`.trim();
// Helper pour créer le résultat enrichi
const buildResult = (brand, customProps = {}) => ({
...brand,
numTrain,
rawType,
rawOperator: opRaw,
...customProps
});
// =========================================================================
// 1. SERVICES AUTOCAR / CAR TER / BUS DE SUBSTITUTION
// =========================================================================
if (
isSubstitution ||
isCarTrainNumber(numTrain) ||
combined.includes('autocar') ||
combined.includes('carter') ||
combined.includes('substitution') ||
combined === 'car' ||
combined === 'bus'
) {
return buildResult(BRANDS.AUTOCAR);
}
// =========================================================================
// 2. TGV LYRIA (Paris - Suisse : Genève, Lausanne, Zurich, Bâle)
// =========================================================================
if (
combined.includes('lyria') ||
tripId.includes(':LYR:FR') ||
(numInt >= 9200 && numInt <= 9299) ||
(numInt >= 9750 && numInt <= 9799)
) {
return buildResult(BRANDS.LYRIA);
}
// =========================================================================
// 3. OUIGO TRAIN CLASSIQUE (4000-4399 ou 8040-8099, Paris Austerlitz/Bercy)
// =========================================================================
const isOuigoClassique = (
combined.includes('ouigoclassique') ||
combined.includes('trainclassique') ||
combined.includes('classique') ||
(combined.includes('ouigo') && ((numInt >= 4000 && numInt <= 4399) || (numInt >= 8040 && numInt <= 8099))) ||
(tripId.includes(':OGO:FR') && ((numInt >= 4000 && numInt <= 4399) || (numInt >= 8040 && numInt <= 8099)))
);
if (isOuigoClassique) {
return buildResult(BRANDS.OUIGO_CLASSIQUE);
}
// =========================================================================
// 4. OUIGO (GRANDE VITESSE)
// =========================================================================
if (
combined.includes('ouigo') ||
tripId.includes(':OGO:FR') ||
(numInt >= 7600 && numInt <= 7999)
) {
return buildResult(BRANDS.OUIGO_GV);
}
// =========================================================================
// 5. INTERCITÉS DE NUIT (Lunéa)
// =========================================================================
if (
combined.includes('denuit') ||
combined.includes('nuit') ||
combined.includes('lunea') ||
tripId.includes(':ICN:FR') ||
(numInt >= 3690 && numInt <= 3799) ||
(numInt >= 3950 && numInt <= 3999) ||
(numInt >= 5700 && numInt <= 5799)
) {
return buildResult(BRANDS.INTERCITES_NUIT);
}
// =========================================================================
// 6. INTERCITÉS (Trains d'Équilibre du Territoire de jour)
// =========================================================================
if (
combined.includes('intercites') ||
combined === 'ic' ||
combined.startsWith('ic ') ||
tripId.includes(':IC:FR') ||
(numInt >= 3000 && numInt <= 3949) ||
(numInt >= 4400 && numInt <= 4999)
) {
return buildResult(BRANDS.INTERCITES);
}
// =========================================================================
// 7. EUROSTAR & THALYS
// =========================================================================
const isEurostarOrThalys = (
combined.includes('eurostar') ||
combined.includes('thalys') ||
(tripId.includes(':TRN:FR') && (routeNormalized.includes('bruxelles') || routeNormalized.includes('london') || routeNormalized.includes('amsterdam') || routeNormalized.includes('koln'))) ||
((numInt >= 60 && numInt <= 99) && (routeNormalized.includes('bruxelles') || routeNormalized.includes('nord'))) ||
(numInt >= 9000 && numInt <= 9499 && (routeNormalized.includes('bruxelles') || routeNormalized.includes('london') || routeNormalized.includes('amsterdam')))
);
if (isEurostarOrThalys) {
if (combined.includes('thalys')) {
return buildResult(BRANDS.THALYS);
}
return buildResult(BRANDS.EUROSTAR);
}
// =========================================================================
// 8. FRECCIAROSSA / TRENITALIA (Paris Lyon - Lyon - Milan)
// =========================================================================
if (
combined.includes('frecciarossa') ||
combined.includes('freccia') ||
combined.includes('trenitalia') ||
(numInt >= 6640 && numInt <= 6670)
) {
return buildResult(BRANDS.FRECCIAROSSA);
}
// =========================================================================
// 9. RENFE AVE (Marseille / Lyon - Madrid / Barcelone)
// =========================================================================
if (
combined.includes('renfe') ||
combined.includes('ave') ||
[9724, 9725, 9731, 9732].includes(numInt)
) {
return buildResult(BRANDS.RENFE_AVE);
}
// =========================================================================
// 10. ICE / DB (Paris Est - Allemagne)
// =========================================================================
if (
combined.includes('ice') ||
combined === 'db' ||
tripId.includes(':ICE:FR') ||
(numInt >= 9550 && numInt <= 9599)
) {
return buildResult(BRANDS.ICE);
}
// =========================================================================
// 11. TGV INOUI & TGV GÉNÉRIQUE
// =========================================================================
if (
combined.includes('inoui') ||
combined.includes('tgvinoui') ||
tripId.includes(':OUI:FR') ||
combined.includes('tgv')
) {
return buildResult(BRANDS.INOUi);
}
// =========================================================================
// 12. TRAM-TRAIN
// =========================================================================
if (
combined.includes('tramtrain') ||
combined.includes('tram-train') ||
tripId.includes(':TT:FR')
) {
return buildResult(BRANDS.TRAMTRAIN);
}
// =========================================================================
// 13. TERGV
// =========================================================================
if (combined.includes('tergv') || combined.includes('ter-gv')) {
return buildResult(BRANDS.HAUTS_DE_FRANCE, { typeLabel: 'TERGV', quaiLabel: 'TERGV' });
}
// =========================================================================
// 14. OPÉRATEURS SUISSE, BELGE, LUXEMBOURGEOIS
// =========================================================================
if (combined.includes('sbb') || combined.includes('cff') || combined.includes('ffs')) {
return buildResult(BRANDS.SBB);
}
if (combined.includes('sncb') || combined.includes('nmbs')) {
return buildResult(BRANDS.SNCB);
}
if (combined.includes('cfl')) {
return buildResult(BRANDS.CFL);
}
// =========================================================================
// 15. TER RÉGIONAUX - DÉTECTION ROBUSTE PAR PALIER
// =========================================================================
// Palier A : Mots-clés régionaux explicites dans l'opérateur ou type
if (combined.includes('lio') || combined.includes('occitanie') || combined.includes('languedoc') || combined.includes('midipyrenees')) {
return buildResult(BRANDS.LIO);
}
if (combined.includes('mobigo') || combined.includes('bourgogne') || combined.includes('franchecomte')) {
return buildResult(BRANDS.MOBIGO);
}
if (combined.includes('zou') || combined.includes('paca') || combined.includes('ler')) {
return buildResult(BRANDS.ZOU);
}
if (combined.includes('fluo') || combined.includes('grandest') || combined.includes('alsace') || combined.includes('lorraine') || combined.includes('metrolor')) {
return buildResult(BRANDS.FLUO);
}
if (combined.includes('aleop') || combined.includes('paysdelaloire')) {
return buildResult(BRANDS.ALEOP);
}
if (combined.includes('breizh') || combined.includes('bretagne')) {
return buildResult(BRANDS.BREIZHGO);
}
if (combined.includes('nomad') || combined.includes('normandie')) {
return buildResult(BRANDS.NOMAD);
}
if (combined.includes('remi') || combined.includes('centrevaldeloire')) {
return buildResult(BRANDS.REMI);
}
if (combined.includes('hautsdefrance') || combined.includes('nordpasdecalais') || combined.includes('picardie')) {
return buildResult(BRANDS.HAUTS_DE_FRANCE);
}
if (combined.includes('auvergerhonealpes') || combined.includes('rhonealpes') || combined.includes('auvergne')) {
return buildResult(BRANDS.AUVERGNE_RHONE_ALPES);
}
if (combined.includes('nouvelleaquitaine') || combined.includes('aquitaine') || combined.includes('poitoucharentes') || combined.includes('limousin')) {
return buildResult(BRANDS.NOUVELLE_AQUITAINE);
}
// Palier B : Plan de numérotation SNCF (Strict & Déterministe)
const regionByNum = getRegionByTrainNumber(numTrain);
if (regionByNum) {
return buildResult(regionByNum);
}
// Palier C : Analyse géographique par score d'arrêts (si train hors numérotation standard)
const regionByRoute = detectRegionByRouteScore(fullRouteText);
if (regionByRoute) {
return buildResult(regionByRoute);
}
// =========================================================================
// 16. TER STANDARD (Fallback)
// =========================================================================
if (combined.includes('ter')) {
return buildResult(BRANDS.TER_GENERIC);
}
// =========================================================================
// 17. SNCF VOYAGEURS (Fallback par défaut)
// =========================================================================
return buildResult(BRANDS.SNCF_GENERIC, {
typeLabel: rawType || 'SNCF',
quaiLabel: rawType || 'SNCF'
});
}
/**
* Interface pour les afficheurs de Quai classiques (ClassiquesDepartsQuais & ClassiquesArriveesQuais)
* @param {Object|string} trainTypeOrHoraire
* @param {string} [operator]
* @param {boolean} [isDeparture=true]
* @param {string} [numTrainOverride]
* @returns {{ logo: string, label: string, showVoyageursText: boolean, isTgv: boolean, classification: Object }}
*/
export function getTrainBrand(trainTypeOrHoraire = '', operator = '', isDeparture = true, numTrainOverride = '') {
const c = classifyTrain(trainTypeOrHoraire, operator, numTrainOverride);
// Pour les trains sans marque spécifique :
if (c.id === 'sncf' || c.id === 'ter') {
if (isDeparture) {
return {
logo: BRANDS.SNCF_GENERIC.quaiLogo,
label: c.quaiLabel || 'SNCF',
showVoyageursText: true,
isTgv: Boolean(c.isTgv),
classification: c
};
}
return {
logo: BRANDS.TER_GENERIC.quaiLogo,
label: c.quaiLabel || 'TER',
showVoyageursText: false,
isTgv: Boolean(c.isTgv),
classification: c
};
}
return {
logo: c.quaiLogo,
label: c.quaiLabel,
showVoyageursText: Boolean(c.showVoyageursText),
isTgv: Boolean(c.isTgv),
classification: c
};
}
/**
* Interface pour les afficheurs Hall classiques (ClassiquesDeparts & ClassiquesArrivees)
* @param {Object} horaire
* @returns {{ logoSrc: string, typeLabel: string, numTrain: string, isTgv: boolean, brand: Object, classification: Object }}
*/
export function getTrainLogoAndMeta(horaire) {
if (!horaire) {
return {
logoSrc: BRANDS.TER_GENERIC.hallLogo,
typeLabel: 'TER',
numTrain: '',
isTgv: false,
brand: getTrainBrand(),
classification: BRANDS.TER_GENERIC
};
}
const c = classifyTrain(horaire);
const brand = getTrainBrand(horaire, '', true);
return {
logoSrc: c.hallLogo,
typeLabel: c.typeLabel,
numTrain: c.numTrain,
isTgv: Boolean(c.isTgv),
brand,
classification: c
};
}
+41 -7
View File
@@ -180,7 +180,7 @@ export function getTrainLogoAndMeta(horaire) {
} else if (opNorm.includes('breizhgo')) {
typeLabel = 'BREIZHGO';
} else if (opNorm.includes('hautsdefrance')) {
typeLabel = 'HAUTS-DE-FRANCE';
typeLabel = 'TER HDF';
} else if (opNorm.includes('lio')) {
typeLabel = 'LIÒ';
} else if (opNorm.includes('zou')) {
@@ -199,14 +199,48 @@ export function getTrainLogoAndMeta(horaire) {
typeLabel = 'TER';
}
// Spécificité régionale Bourgogne-Franche-Comté : TER commençant par 89xxxx ou 84xxxx -> MOBIGO
if (typeLabel === 'TER' && (numTrain.startsWith('89') || numTrain.startsWith('84'))) {
typeLabel = 'MOBIGO';
if (!matchedLogo || matchedLogo.file === 'logo-ter.svg' || matchedLogo.file === 'logo-sncf.svg') {
matchedLogo = logoLookup.get('mobigo') || matchedLogo;
// Application du plan de numérotation déterministe TER
if ((typeLabel === 'TER' || typeLabel === 'AUTOCAR' || typeLabel === 'Car TER') && numTrain) {
const cleanNum = numTrain.replace(/\D/g, '');
let matchedBrandSlug = null;
let matchedBrandLabel = null;
if (cleanNum.length >= 4) {
const p3 = parseInt(cleanNum.substring(0, 3), 10);
const p4 = parseInt(cleanNum.substring(0, 4), 10);
if ((p3 >= 830 && p3 <= 840) || (p3 >= 160 && p3 <= 162)) {
matchedBrandSlug = 'fluo'; matchedBrandLabel = 'FLUO';
} else if ((p3 >= 841 && p3 <= 849) || p3 === 163 || p3 === 164) {
matchedBrandSlug = 'hautsdefrance'; matchedBrandLabel = 'TER HDF';
} else if ((p3 >= 850 && p3 <= 853) || p3 === 165) {
matchedBrandSlug = 'nomad'; matchedBrandLabel = 'NOMAD';
} else if ((p3 >= 854 && p3 <= 856) || (p4 >= 1660 && p4 <= 1664)) {
matchedBrandSlug = 'breizhgo'; matchedBrandLabel = 'BREIZHGO';
} else if ((p3 >= 857 && p3 <= 859) || (p4 >= 1665 && p4 <= 1674)) {
matchedBrandSlug = 'aleop'; matchedBrandLabel = 'ALÉOP';
} else if ((p3 >= 860 && p3 <= 862) || (p4 >= 1675 && p4 <= 1684)) {
matchedBrandSlug = 'remi'; matchedBrandLabel = 'RÉMI';
} else if ((p3 >= 863 && p3 <= 869) || (p4 >= 1685 && p4 <= 1709)) {
matchedBrandSlug = 'teraquitaine'; matchedBrandLabel = 'TER NOUVELLE-AQUITAINE';
} else if ((p3 >= 870 && p3 <= 872) || (p3 >= 876 && p3 <= 878) || (p4 >= 1710 && p4 <= 1719) || (p4 >= 1730 && p4 <= 1734)) {
matchedBrandSlug = 'lio'; matchedBrandLabel = 'liO';
} else if ((p3 >= 873 && p3 <= 875) || (p3 >= 882 && p3 <= 890) || (p4 >= 1720 && p4 <= 1729) || (p4 >= 1750 && p4 <= 1774) || (p4 >= 1790 && p4 <= 1799)) {
matchedBrandSlug = 'ter-rhone-alpes'; matchedBrandLabel = 'TER AUVERGNE-RHÔNE-ALPES';
} else if ((p3 >= 879 && p3 <= 881) || (p4 >= 1735 && p4 <= 1749)) {
matchedBrandSlug = 'zou'; matchedBrandLabel = 'ZOU !';
} else if ((p3 >= 891 && p3 <= 895) || (p4 >= 1775 && p4 <= 1789)) {
matchedBrandSlug = 'mobigo'; matchedBrandLabel = 'MOBIGO';
}
if (matchedBrandSlug && matchedBrandLabel) {
typeLabel = matchedBrandLabel;
if (!matchedLogo || matchedLogo.file === 'logo-ter.svg' || matchedLogo.file === 'logo-sncf.svg') {
matchedLogo = logoLookup.get(matchedBrandSlug) || matchedLogo;
}
}
}
}
const logoFile = (matchedLogo && matchedLogo.file) || 'logo-sncf.svg';
const logoSrc = `/type/${logoFile}`;