ctucx.git: trainsearch

web based trip-planner, fork of https://cyberchaos.dev/yuka/trainsearch

commit 711fa3649f66384b06be7ff11c34f51038f3f081
parent 773d55c1e331fd1bff888065f3f7495e97e31e87
Author: Yureka <yuka@yuka.dev>
Date: Mon, 23 Dec 2024 12:04:12 +0100

fix train types
8 files changed, 391 insertions(+), 355 deletions(-)
M
src/canvas.js
|
12
++++++------
M
src/helpers.js
|
2
+-
M
src/journeyView.js
|
8
++++----
M
src/reihung/DB/DBMapping.js
|
387
++++++++++++++++++++++++++++++++++++++++++-------------------------------------
M
src/reihung/DB/index.js
|
51
+++++++++++++++++++++++++--------------------------
M
src/reihung/baureihe.js
|
229
+++++++++++++++++++++++++++++++++++++------------------------------------------
M
src/reihung/commonMapping.js
|
41
++++++++++++++++++++++++++++++++++-------
M
src/reihung/index.js
|
16
++++++++--------
diff --git a/src/canvas.js b/src/canvas.js
@@ -61,9 +61,9 @@ const canvasState = {
 let textCache = {};
 
 const typeTextsFor = leg => {
-	const fahrtNr = leg.line?.fahrtNr;
-	if (!fahrtNr) return [];
-	const key = coachSequenceCacheKey(fahrtNr, leg.plannedDeparture);
+	if (!leg.line) return [];
+	const [category, number] = leg.line.name.split(" ");
+	const key = coachSequenceCacheKey(category, leg.line.fahrtNr || number, leg.origin.id, leg.plannedDeparture);
 	if (!key) return [];
 	const info = coachSequenceCache[key];
 	if (!info || info instanceof Promise) {

@@ -85,9 +85,9 @@ export const setupCanvas = (data, isUpdate) => {
 		(async () => {
 			for (const journey of canvasState.data.journeys) {
 				for (const leg of journey.legs) {
-					const fahrtNr = leg.line?.fahrtNr;
-					if (!fahrtNr) continue;
-					await cachedCoachSequence(fahrtNr, leg.plannedDeparture);
+					if (!leg.line) continue;
+					const [category, number] = leg.line.name.split(" ");
+					await cachedCoachSequence(category, leg.line.fahrtNr || number, leg.origin.id, leg.plannedDeparture);
 					setupCanvas(null, true);
 				}
 			}
diff --git a/src/helpers.js b/src/helpers.js
@@ -52,7 +52,6 @@ export const formatDateTime = (date, format) => {
 
 	}
 
-	console.log(date);
 	if (date.toLocaleDateString() !== new Date().toLocaleDateString()) {
 		return padZeros(date.getHours()) + ':' + padZeros(date.getMinutes()) + ', ' + date.getDate() + '.' + (date.getMonth() + 1) + '.';
 	} else {

@@ -109,6 +108,7 @@ export const formatPrice = price => {
 
 export const formatTrainTypes = info => {
 	const counts = {};
+	console.log(info);
 	for (let group of info.sequence?.groups) {
 		const name = group.baureihe?.name;
 		if (!name) continue;
diff --git a/src/journeyView.js b/src/journeyView.js
@@ -1,4 +1,4 @@
-import { cachedCoachSequence, coachSequenceCache, coachSequenceCacheKey } from './reihung';
+import { cachedCoachSequence } from './reihung';
 import { settings } from './settings.js';
 import { showDiv, hideDiv, ElementById, formatDateTime, formatDuration, formatPrice, formatTrainTypes, lineAdditionalName, lineDisplayName, remarksTemplate, travelynxTemplate } from './helpers.js';
 import { ConsoleLog, parseName, t, timeTemplate, getJourney, refreshJourney, platformTemplate, stopTemplate } from './app_functions.js';

@@ -152,9 +152,9 @@ export const journeyView = async (match, isUpdate) => {
 		return;
 	}
 	for (const leg of data.legs) {
-		const fahrtNr = leg.line?.fahrtNr;
-		if (fahrtNr) {
-			const info = await cachedCoachSequence(fahrtNr, leg.plannedDeparture);
+		if (leg.line) {
+			const [category, number] = leg.line.name.split(" ");
+			const info = await cachedCoachSequence(category, leg.line.fahrtNr || number, leg.origin.id, leg.plannedDeparture);
 			if (info) {
 				leg.line.trainType = formatTrainTypes(info);
 			}
diff --git a/src/reihung/DB/DBMapping.js b/src/reihung/DB/DBMapping.js
@@ -1,195 +1,220 @@
 import { enrichCoachSequence } from '../commonMapping';
 import { getLineFromNumber } from '../lineNumberMapping';
 
-const mapClass = kategorie => {
-  switch (kategorie) {
-    case 'DOPPELSTOCKSTEUERWAGENZWEITEKLASSE':
-    case 'DOPPELSTOCKWAGENZWEITEKLASSE':
-    case 'REISEZUGWAGENZWEITEKLASSE':
-    case 'STEUERWAGENZWEITEKLASSE':
-    case 'HALBSPEISEWAGENZWEITEKLASSE':
-    case 'SPEISEWAGEN':
-      return 2;
-
-    case 'DOPPELSTOCKWAGENERSTEZWEITEKLASSE':
-    case 'DOPPELSTOCKSTEUERWAGENERSTEZWEITEKLASSE':
-    case 'STEUERWAGENERSTEZWEITEKLASSE':
-    case 'REISEZUGWAGENERSTEZWEITEKLASSE':
-      return 3;
-
-    case 'HALBSPEISEWAGENERSTEKLASSE':
-    case 'DOPPELSTOCKWAGENERSTEKLASSE':
-    case 'REISEZUGWAGENERSTEKLASSE':
-    case 'STEUERWAGENERSTEKLASSE':
-      return 1;
-
-    case 'TRIEBKOPF':
-    case 'LOK':
-      return 4;
-
-    default:
-      return 0;
-  }
-};
-
-const mapPosition = position => {
-  const endPercent = Number.parseFloat(position.endeprozent);
-  const startPercent = Number.parseFloat(position.startprozent);
-  if (Number.isNaN(startPercent) || Number.isNaN(endPercent)) return;
-  return {
-    endPercent,
-    startPercent
-  };
-};
-
-const mapFeatures = fahrzeug => {
-  const features = {};
-
-  if (fahrzeug.kategorie.includes('SPEISEWAGEN')) {
-    features.dining = true;
-  }
-
-  fahrzeug.allFahrzeugausstattung.forEach(ausstattung => {
-    switch (ausstattung.ausstattungsart) {
-      case 'PLAETZEROLLSTUHL':
-        features.wheelchair = true;
-        break;
-
-      case 'PLAETZEFAHRRAD':
-        features.bike = true;
-        break;
-
-      case 'BISTRO':
-        features.dining = true;
-        break;
-
-      case 'RUHE':
-        features.quiet = true;
-        break;
-
-      case 'FAMILIE':
-        features.family = true;
-        break;
-
-      case 'PLAETZEBAHNCOMFORT':
-        features.comfort = true;
-        break;
-
-      case 'PLAETZESCHWERBEH':
-        features.disabled = true;
-        break;
-
-      case 'INFO':
-        features.info = true;
-        break;
-
-      case 'ABTEILKLEINKIND':
-        features.toddler = true;
-        break;
-    }
-  });
-  return features;
-};
-
-const mapCoach = fahrzeug => {
-  const position = mapPosition(fahrzeug.positionamhalt);
-  if (!position) return;
-  const travellerClass = mapClass(fahrzeug.kategorie);
-  return {
-    class: travellerClass,
-    category: fahrzeug.kategorie,
-    closed: fahrzeug.status === 'GESCHLOSSEN' || travellerClass === 4 ? true : undefined,
-    position,
-    identificationNumber: fahrzeug.wagenordnungsnummer,
-    type: fahrzeug.fahrzeugtyp,
-    uic: fahrzeug.fahrzeugnummer,
-    features: mapFeatures(fahrzeug)
-  };
-};
+function mapSectors(
+	sectors,
+	basePercent,
+) {
+	return (
+		sectors?.map((s) => ({
+			name: s.name,
+			position: {
+				startPercent: basePercent * s.start,
+				endPercent: basePercent * s.end,
+			},
+		})) || []
+	);
+}
 
-const mapGroup = gruppe => {
-  const coaches = gruppe.allFahrzeug.map(mapCoach);
-  if (coaches.includes(undefined)) return;
-  return {
-    // @ts-expect-error we checked for undefined
-    coaches,
-    destinationName: gruppe.zielbetriebsstellename,
-    originName: gruppe.startbetriebsstellename,
-    name: gruppe.fahrzeuggruppebezeichnung,
-    number: gruppe.verkehrlichezugnummer
-  };
-};
+function mapStop(
+	evaNumber,
+	platform,
+) {
+	if (platform?.start === undefined || platform.end === undefined) {
+		return [undefined, 0];
+	}
+	const basePercent = 100 / (platform.end - platform.start);
+	return [
+		{
+			stopPlace: {
+				evaNumber,
+				name: '',
+			},
+			sectors: mapSectors(platform.sectors, basePercent),
+		},
+		basePercent,
+	];
+}
 
-const mapSequence = formation => {
-  const grouped = formation.allFahrzeuggruppe.reduce((agg, g) => {
-    const key = g.verkehrlichezugnummer + g.zielbetriebsstellename + g.startbetriebsstellename + (g.fahrzeuggruppebezeichnung.includes('IC') ? g.fahrzeuggruppebezeichnung : '');
-    agg[key] = agg[key] || [];
-    agg[key].push(g);
-    return agg;
-  }, {});
-  const regrouped = Object.values(grouped).map(groups => {
-    return { ...groups[0],
-      fahrzeuggruppebezeichnung: groups.length > 1 ? groups.reduce((n, g) => `${n}-${g.fahrzeuggruppebezeichnung}`, 'regrouped') : groups[0].fahrzeuggruppebezeichnung,
-      allFahrzeug: groups.flatMap(g => g.allFahrzeug)
-    };
-  });
-  const groups = regrouped.map(mapGroup);
-  if (groups.includes(undefined)) return;
-  return {
-    // @ts-expect-error we checked for undefined
-    groups
-  };
-};
+function mapClass(vehicleType) {
+	switch (vehicleType.category) {
+		case 'LOCOMOTIVE':
+		case 'POWERCAR': {
+			return 4;
+		}
+		case 'DININGCAR': {
+			return 2;
+		}
+	}
+	if (vehicleType.hasFirstClass && vehicleType.hasEconomyClass) {
+		return 3;
+	}
+	if (vehicleType.hasFirstClass) {
+		return 1;
+	}
+	if (vehicleType.hasEconomyClass) {
+		return 2;
+	}
+	return 0;
+}
 
-const mapSector = sektor => {
-  const position = mapPosition(sektor.positionamgleis);
-  if (!position) return;
-  return {
-    name: sektor.sektorbezeichnung,
-    position
-  };
-};
+const diningCategories = new Set([
+	'DININGCAR',
+	'HALFDININGCAR_ECONOMY_CLASS',
+	'HALFDININGCAR_FIRST_CLASS',
+]);
+function mapFeatures(vehicle) {
+	const features = {};
+
+	for (const a of vehicle.amenities) {
+		switch (a.type) {
+			case 'BIKE_SPACE': {
+				features.bike = true;
+				break;
+			}
+			case 'BISTRO': {
+				features.dining = true;
+				break;
+			}
+			case 'INFO': {
+				features.info = true;
+				break;
+			}
+			case 'SEATS_BAHN_COMFORT': {
+				features.comfort = true;
+				break;
+			}
+			case 'SEATS_SEVERELY_DISABLED': {
+				features.disabled = true;
+				break;
+			}
+			case 'WHEELCHAIR_SPACE': {
+				features.wheelchair = true;
+				break;
+			}
+			case 'WIFI': {
+				features.wifi = true;
+				break;
+			}
+			case 'ZONE_FAMILY': {
+				features.family = true;
+				break;
+			}
+			case 'ZONE_QUIET': {
+				features.quiet = true;
+				break;
+			}
+			case 'CABIN_INFANT': {
+				features.toddler = true;
+				break;
+			}
+		}
+	}
+
+	if (!features.dining && diningCategories.has(vehicle.type.category)) {
+		features.dining = true;
+		//logger.debug('Manually set dining feature');
+	}
+	return features;
+}
 
-const mapStop = halt => {
-  let sectors = halt.allSektor.map(mapSector);
+function mapVehicle(
+	vehicle,
+	basePercent,
+) {
+	if (!vehicle.platformPosition) {
+		return undefined;
+	}
+	const r = {
+		identificationNumber: vehicle.wagonIdentificationNumber?.toString(),
+		uic: vehicle.vehicleID,
+		type: vehicle.type.constructionType,
+		class: mapClass(vehicle.type),
+		vehicleCategory: vehicle.type.category,
+		closed:
+			vehicle.status === 'CLOSED' ||
+			vehicle.type.category === 'LOCOMOTIVE' ||
+			vehicle.type.category === 'POWERCAR',
+		position: {
+			startPercent: basePercent * vehicle.platformPosition.start,
+			endPercent: basePercent * vehicle.platformPosition.end,
+		},
+		features: mapFeatures(vehicle),
+	};
+	return r;
+}
 
-  if (sectors.includes(undefined)) {
-    sectors = [];
-  }
+function mapGroup(
+	group,
+	basePercent,
+) {
+	const coaches = group.vehicles.map(vehicle => mapVehicle(vehicle, basePercent));
+	if (coaches.includes(undefined)) {
+		return undefined;
+	}
+	return {
+		name: group.name,
+		destinationName: group.transport.destination.name,
+		originName: 'UNKNOWN',
+		number: group.transport.number.toString(),
+		coaches: coaches,
+	};
+}
 
-  return {
-    stopPlace: {
-      evaNumber: halt.evanummer,
-      name: halt.bahnhofsname
-    },
-    // @ts-expect-error we checked for undefined
-    sectors
-  };
-};
+function mapDirection(coaches) {
+	const first = coaches[0];
+	const last = coaches.at(-1);
 
-const mapProduct = formation => ({
-  number: formation.zugnummer,
-  type: formation.zuggattung,
-  line: getLineFromNumber(formation.zugnummer)
-});
+	return last.position.startPercent > first.position.startPercent;
+}
 
-function mapDirection(coaches) {
-  const first = coaches[0];
-  const last = coaches[coaches.length - 1];
-  return last.position.startPercent > first.position.startPercent;
+async function mapSequence(
+	sequence,
+	basePercent,
+) {
+	const groups = await Promise.all(
+		sequence.groups.map((g) => mapGroup(g, basePercent)),
+	);
+	if (groups.includes(undefined)) return undefined;
+	return {
+		groups: groups,
+	};
 }
 
-export const mapInformation = formation => {
-  const sequence = mapSequence(formation);
-  if (!sequence) return;
-  const allCoaches = sequence.groups.flatMap(g => g.coaches);
-  const information = {
-    sequence,
-    product: mapProduct(formation),
-    stop: mapStop(formation.halt),
-    direction: mapDirection(allCoaches),
-    isRealtime: allCoaches.every(c => c.uic || c.category === 'LOK')
-  };
-  enrichCoachSequence(information);
-  return information;
+export const mapInformation = async (
+	upstreamSequence,
+	trainCategory,
+	trainNumber,
+	evaNumber,
+) => {
+	if (!upstreamSequence) {
+		return undefined;
+	}
+	const [stop, basePercent] = mapStop(evaNumber, upstreamSequence.platform);
+	if (!stop) {
+		return undefined;
+	}
+	const sequence = await mapSequence(upstreamSequence, basePercent);
+	if (!sequence) {
+		return undefined;
+	}
+	const allCoaches = sequence.groups.flatMap((g) => g.coaches);
+
+	const information = {
+		source: 'DB-bahnde',
+		product: {
+			number: trainNumber,
+			type: trainCategory,
+		},
+		isRealtime: allCoaches.every(
+			(c) => c.uic || c.vehicleCategory === 'LOCOMOTIVE',
+		),
+		stop,
+		sequence,
+		direction: mapDirection(allCoaches),
+		journeyId: upstreamSequence.journeyID,
+	};
+	enrichCoachSequence(information);
+
+	return information;
 };
diff --git a/src/reihung/DB/index.js b/src/reihung/DB/index.js
@@ -1,40 +1,39 @@
-import { format } from 'date-fns';
+import {
+        addDays,
+        differenceInHours,
+        isWithinInterval,
+        subDays,
+	format,
+	formatISO,
+} from 'date-fns';
 import { mapInformation } from './DBMapping';
 
-const dbCoachSequenceUrls = {
-  apps: '/https://www.apps-bahn.de/wr/wagenreihung/1.0',
-  noncd: '/https://ist-wr.noncd.db.de/wagenreihung/1.0'
-};
 const dbCoachSequenceTimeout = process.env.NODE_ENV === 'production' ? 2500 : 10000;
-export const getDBCoachSequenceUrl = (trainNumber, date, type = 'noncd') => {
-  return `${dbCoachSequenceUrls[type]}/${trainNumber}/${formatDate(date)}`;
-};
-
-const formatDate = (date) =>
-        date ? format(date, 'yyyyMMddHHmm') : undefined;
 
-async function coachSequence(trainNumber, date) {
-  if (trainNumber.length <= 4) {
-    try {
-      const info = await fetch(getDBCoachSequenceUrl(trainNumber, date)).then(x => x.json());
-      return info;
-    } catch {// we just ignore it and try the next one
-    }
-  }
+export const getDBCoachSequenceUrl = (category, number, evaNumber, date) => {
+  const searchParams = new URLSearchParams();
+  searchParams.append("category", category);
+  searchParams.append("date", format(date, "yyyy-MM-dd"));
+  searchParams.append("time", formatISO(date));
+  searchParams.append("evaNumber", evaNumber);
+  searchParams.append("number", number);
+  return `/https://www.bahn.de/web/api/reisebegleitung/wagenreihung/vehicle-sequence?${searchParams}`;
+};
 
-  const info = await fetch(getDBCoachSequenceUrl(trainNumber, date, 'apps')).then(x => x.json());
+async function coachSequence(category, number, evaNumber, date) {
+  const info = await fetch(getDBCoachSequenceUrl(category, number, evaNumber, date)).then(x => x.json());
   return info;
 }
 
-export async function rawDBCoachSequence(trainNumber, date, retry = 2) {
+export async function rawDBCoachSequence(category, number, evaNumber, date, retry = 2) {
   try {
-    return coachSequence(trainNumber, date);
+    return coachSequence(category, number, evaNumber, date);
   } catch (e) {
-    if (retry) return rawDBCoachSequence(trainNumber, date, retry - 1);
+    if (retry) return rawDBCoachSequence(category, number, evaNumber, date, retry - 1);
   }
 }
-export async function DBCoachSequence(trainNumber, date) {
-  const rawSequence = await rawDBCoachSequence(trainNumber, date);
+export async function DBCoachSequence(category, number, evaNumber, date) {
+  const rawSequence = await rawDBCoachSequence(category, number, evaNumber, date);
   if (!rawSequence) return undefined;
-  return mapInformation(rawSequence.data.istformation);
+  return mapInformation(rawSequence, category, number, evaNumber);
 }
diff --git a/src/reihung/baureihe.js b/src/reihung/baureihe.js
@@ -1,28 +1,30 @@
 import notRedesigned from './notRedesigned.json';
 export const nameMap = {
-  '401': 'ICE 1',
-  '401.9': 'ICE 1 Kurz',
-  '401.LDV': 'ICE 1 LDV',
-  '402': 'ICE 2',
-  '403': 'ICE 3',
-  '403.S1': 'ICE 3 1. Serie',
-  '403.S2': 'ICE 3 2. Serie',
-  '403.R': 'ICE 3 Redesign',
-  '406': 'ICE 3',
-  '406.R': 'ICE 3 Redesign',
-  '407': 'ICE 3 Velaro',
-  '410.1': 'ICE S',
-  '411': 'ICE T',
-  '411.S1': 'ICE T 1. Serie',
-  '411.S2': 'ICE T 2. Serie',
-  '412': 'ICE 4',
-  '412.7': 'ICE 4 Kurz',
-  '412.13': 'ICE 4 Lang',
-  '415': 'ICE T Kurz',
-  'IC2.TWIN': 'IC 2 Twindexx',
-  'IC2.KISS': 'IC 2 KISS',
+  '401': 'ICE 1 (BR401)',
+  '401.9': 'ICE 1 Kurz (BR401)',
+  '401.LDV': 'ICE 1 Modernisiert (BR401)',
+  '402': 'ICE 2 (BR402)',
+  '403': 'ICE 3 (BR403)',
+  '403.S1': 'ICE 3 (BR403 1. Serie)',
+  '403.S2': 'ICE 3 (BR403 2. Serie)',
+  '403.R': 'ICE 3 (BR403)',
+  '406': 'ICE 3 (BR406)',
+  '406.R': 'ICE 3 (BR406 Redesign)',
+  '407': 'ICE 3 Velaro (BR407)',
+  '408': 'ICE 3neo (BR408)',
+  '410.1': 'ICE S (BR410.1)',
+  '411': 'ICE T (BR411)',
+  '411.S1': 'ICE T (BR411 1. Serie)',
+  '411.S2': 'ICE T (BR411 2. Serie)',
+  '412': 'ICE 4 (BR412)',
+  '412.7': 'ICE 4 Kurz (BR412)',
+  '412.13': 'ICE 4 Lang (BR412)',
+  '415': 'ICE T Kurz (BR415)',
+  'IC2.TRE': 'IC 2 (TRE)',
+  '4110': 'IC 2 KISS (BR4110)',
+  '4010': 'IC 2 KISS (BR4010)',
   MET: 'MET',
-  TGV: 'TGV'
+  TGV: 'TGV',
 };
 
 const getATBR = (code, _serial, _coaches) => {

@@ -51,168 +53,151 @@ const getDEBR = (code, uicOrdnungsnummer, coaches, tzn) => {
     case '7812':
     case '8812':
     case '9412':
-    case '9812':
-      {
-        let identifier = '412';
-
-        switch (coaches.length) {
-          case 13:
-            identifier = '412.13';
-            break;
-
-          case 7:
-            identifier = '412.7';
-            break;
+    case '9812': {
+      let identifier;
+      switch (coaches.length) {
+        case 13: {
+          identifier = '412.13';
+          break;
+        }
+        case 7: {
+          identifier = '412.7';
+          break;
         }
-
-        return {
-          identifier,
-          baureihe: '412'
-        };
       }
-
+      return {
+        identifier,
+        baureihe: '412',
+      };
+    }
     case '5401':
     case '5801':
     case '5802':
     case '5803':
-    case '5804':
-      {
-        let identifier = '401';
-
-        if (coaches.length === 11) {
-          if (coaches.filter(f => f.class === 1).length === 2) {
-            identifier = '401.LDV';
-          } else {
-            identifier = '401.9';
-          }
-        }
-
-        return {
-          identifier,
-          baureihe: '401'
-        };
+    case '5804': {
+      let identifier;
+      if (coaches.length === 11) {
+        identifier =
+          coaches.filter((f) => f.class === 1).length === 2
+            ? '401.LDV'
+            : '401.9';
       }
-
+      return {
+        identifier,
+        baureihe: '401',
+      };
+    }
     case '5402':
     case '5805':
     case '5806':
     case '5807':
-    case '5808':
+    case '5808': {
       return {
         baureihe: '402',
-        identifier: '402'
+        identifier: '402',
       };
-
-    case '5403':
-      {
-        const identifier = `403.S${Number.parseInt(uicOrdnungsnummer.substring(1), 10) <= 37 ? '1' : '2'}`;
-        return {
-          baureihe: '403',
-          identifier: tzn && notRedesigned.includes(tzn) ? identifier : '403.R'
-        };
-      }
-
-    case '5406':
+    }
+    case '5403': {
+      // const identifier: AvailableIdentifier = `403.S${
+      //   Number.parseInt(uicOrdnungsnummer.slice(1), 10) <= 37 ? '1' : '2'
+      // }`;
+      return {
+        baureihe: '403',
+        identifier: '403.R',
+      };
+    }
+    case '5406': {
       return {
         baureihe: '406',
-        identifier: '406.R'
+        identifier: tzn?.endsWith('4651') ? '406.R' : '406',
       };
-
-    case '5407':
+    }
+    case '5407': {
       return {
         baureihe: '407',
-        identifier: '407'
+        identifier: '407',
       };
-
-    case '5410':
+    }
+    case '5410': {
       return {
         baureihe: '410.1',
-        identifier: '410.1'
+        identifier: '410.1',
       };
-
-    case '5411':
+    }
+    case '5408': {
+      return {
+        baureihe: '408',
+        identifier: '408',
+      };
+    }
+    case '5411': {
       return {
         baureihe: '411',
-        identifier: `411.S${Number.parseInt(uicOrdnungsnummer, 10) <= 32 ? '1' : '2'}`
+        identifier: `411.S${
+          Number.parseInt(uicOrdnungsnummer, 10) <= 32 ? '1' : '2'
+        }`,
       };
-
-    case '5415':
+    }
+    case '5415': {
       return {
         baureihe: '415',
-        identifier: '415'
+        identifier: '415',
       };
-
-    case '5475':
+    }
+    case '5475': {
       return {
-        identifier: 'TGV'
+        identifier: 'TGV',
       };
+    }
   }
 };
 
 export const getBaureiheByUIC = (uic, coaches, tzn) => {
-  const country = uic.substr(2, 2);
-  const code = uic.substr(4, 4);
-  const serial = uic.substr(8, 3);
+  const country = uic.slice(2, 4);
+  const code = uic.slice(4, 8);
+  const serial = uic.slice(8, 11);
   let br;
-
   switch (country) {
-    case '80':
+    case '80': {
       br = getDEBR(code, serial, coaches, tzn);
       break;
-
-    case '81':
+    }
+    case '81': {
       br = getATBR(code, serial, coaches);
       break;
+    }
   }
-
   if (!br) return undefined;
-  return { ...br,
-    name: nameMap[br.identifier]
+
+  return {
+    ...br,
+    name: nameMap[br.identifier || br.baureihe],
   };
 };
 export const getBaureiheByCoaches = coaches => {
   let identifier;
-
   for (const c of coaches) {
     if (c.type === 'Apmbzf') {
       identifier = 'MET';
       break;
     }
-
     if (c.type === 'DBpbzfa') {
-      identifier = 'IC2.TWIN';
+      identifier = 'IC2.TRE';
       break;
     }
-
-    if (c.type === 'DBpdzfa') {
-      identifier = 'IC2.KISS';
+    if (c.uic?.slice(4, 8) === '4110') {
+      identifier = '4110';
       break;
     }
-  }
-
-  if (identifier === 'IC2.KISS') {
-    for (const c of coaches) {
-      switch (c.type) {
-        case 'DABpzfa':
-          c.features.comfort = true;
-          c.features.disabled = true;
-          break;
-
-        case 'DBpbza':
-          c.features.family = true;
-          break;
-
-        case 'DBpdzfa':
-          c.features.bike = true;
-          break;
-      }
+    if (c.uic?.slice(4, 8) === '4010') {
+      identifier = '4010';
+      break;
     }
   }
-
   if (identifier) {
     return {
       identifier,
-      name: nameMap[identifier]
+      name: nameMap[identifier],
     };
   }
 };
diff --git a/src/reihung/commonMapping.js b/src/reihung/commonMapping.js
@@ -50,17 +50,44 @@ function enrichCoachSequenceGroup(group, product) {
 
     group.baureihe = calculateBR(group.coaches, tzn);
 
-    if (group.baureihe) {
-      if (group.baureihe.identifier === '401.LDV') {
-        const wagen4 = group.coaches.find(c => c.identificationNumber === '6');
 
-        if (wagen4) {
-          wagen4.features.disabled = false;
+    if (group.baureihe) {
+      if (
+        group.baureihe.identifier === '401.LDV' ||
+        group.baureihe.identifier === '401.9'
+      ) {
+        // Schwerbehindertenplätze/Vorrangplätze sind in Wagen 11, nicht 12
+        for (const coach of group.coaches) {
+          if (coach.identificationNumber === '11') {
+            coach.features.disabled = true;
+          }
+          if (coach.identificationNumber === '12') {
+            coach.features.disabled = false;
+          }
+        }
+      }
+      if (
+        group.baureihe.identifier === '4010' ||
+        group.baureihe.identifier === '4110'
+      ) {
+        for (const c of group.coaches) {
+          switch (c.identificationNumber) {
+            case '6': {
+              c.features.disabled = true;
+              break;
+            }
+            case '5': {
+              c.features.disabled = true;
+              break;
+            }
+            case '4': {
+              c.features.disabled = false;
+            }
+          }
         }
       }
-
       for (const c of group.coaches) {
-        c.seats = getSeatsForCoach(c, group.baureihe.identifier);
+        //c.seats = getSeatsForCoach(c, group.baureihe.identifier);
       }
     }
   }
diff --git a/src/reihung/index.js b/src/reihung/index.js
@@ -1,23 +1,23 @@
 import { DBCoachSequence } from './DB';
 
-export async function coachSequence(trainNumber, departure) {
-  return await DBCoachSequence(trainNumber, departure);
+export async function coachSequence(category, number, evaNumber, departure) {
+  return await DBCoachSequence(category, number, evaNumber, departure);
 }
 
 export const coachSequenceCache = {};
 
-export const coachSequenceCacheKey = (trainNumber, departure) => {
-	if (!trainNumber || !departure) return;
-	return `${trainNumber}-${departure.toISOString()}`;
+export const coachSequenceCacheKey = (category, number, evaNumber, departure) => {
+	if (!category || !number || !evaNumber || !departure) return;
+	return `${category}-${number}-${evaNumber}-${departure.toISOString()}`;
 };
 
-export const cachedCoachSequence = (trainNumber, departure) => {
-	const key = coachSequenceCacheKey(trainNumber, departure);
+export const cachedCoachSequence = (category, number, evaNumber, departure) => {
+	const key = coachSequenceCacheKey(category, number, evaNumber, departure);
 	if (!key) return;
 	if (coachSequenceCache[key] === undefined) {
 		coachSequenceCache[key] = (async () => {
 			try {
-				const info = await coachSequence(trainNumber, departure);
+				const info = await coachSequence(category, number, evaNumber, departure);
 				coachSequenceCache[key] = info;
 				return info;
 			} catch (e) {}