ctucx.git: trainsearch

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

commit d325d40aafa264af0d969146f90a2b19e15989a4
parent ff8d980e2e60752b16286f35863e7d99114bac5f
Author: Katja (ctucx) <git@ctu.cx>
Date: Thu, 30 Jan 2025 11:27:45 +0100

coach-sequence: refactor, add attribution for bahn-expert code
23 files changed, 779 insertions(+), 975 deletions(-)
M
flake.nix
|
2
+-
M
package-lock.json
|
21
---------------------
M
package.json
|
18
++++++++----------
M
src/canvas.js
|
2
+-
A
src/coach-sequence/DB/DBMapping.js
|
199
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
R
src/reihung/TrainNames.js -> src/coach-sequence/DB/TrainNames.js
|
0
A
src/coach-sequence/DB/baureihe.js
|
211
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/coach-sequence/DB/commonMapping.js
|
109
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/coach-sequence/DB/lineNumberMapping.js
|
11
+++++++++++
R
src/reihung/lines.json -> src/coach-sequence/DB/lines.json
|
0
R
src/reihung/notRedesigned.json -> src/coach-sequence/DB/notRedesigned.json
|
0
A
src/coach-sequence/DB/specialSeats.js
|
181
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/coach-sequence/index.js
|
55
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
src/helpers.js
|
2
++
M
src/journeyView.js
|
2
+-
D
src/reihung/DB/DBMapping.js
|
220
-------------------------------------------------------------------------------
D
src/reihung/DB/index.js
|
40
----------------------------------------
D
src/reihung/DB/plannedSequence.js
|
164
-------------------------------------------------------------------------------
D
src/reihung/baureihe.js
|
203
-------------------------------------------------------------------------------
D
src/reihung/commonMapping.js
|
104
-------------------------------------------------------------------------------
D
src/reihung/index.js
|
28
----------------------------
D
src/reihung/lineNumberMapping.js
|
6
------
D
src/reihung/specialSeats.js
|
176
-------------------------------------------------------------------------------
diff --git a/flake.nix b/flake.nix
@@ -15,7 +15,7 @@
         name = "trainsearch";
         src  = self;
 
-        npmDepsHash = "sha256-Ce0opId1cDrcO77kjCoUs7sIzTFQFlrkBRoaSTg7sIQ=";
+        npmDepsHash = "sha256-anEajLsr+iiZ1nYLBerc7VqJops5Ln2mn9EN+x1zVts=";
         makeCacheWritable = true;
 
         npmBuildScript = "build";
diff --git a/package-lock.json b/package-lock.json
@@ -11,8 +11,6 @@
       "dependencies": {
         "assert": "^2.1.0",
         "buffer": "^6.0.3",
-        "date-fns": "^4.1.0",
-        "date-fns-tz": "^3.2.0",
         "db-vendo-client": "https://github.com/yuyuyureka/db-vendo-client#main",
         "hafas-client": "https://github.com/yu-re-ka/hafas-client#main",
         "idb": "^8.0.1",

@@ -3916,25 +3914,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/date-fns": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
-      "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
-      "license": "MIT",
-      "funding": {
-        "type": "github",
-        "url": "https://github.com/sponsors/kossnocorp"
-      }
-    },
-    "node_modules/date-fns-tz": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz",
-      "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==",
-      "license": "MIT",
-      "peerDependencies": {
-        "date-fns": "^3.0.0 || ^4.0.0"
-      }
-    },
     "node_modules/db-hafas-stations": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/db-hafas-stations/-/db-hafas-stations-1.0.0.tgz",
diff --git a/package.json b/package.json
@@ -12,25 +12,23 @@
   "dependencies": {
     "assert": "^2.1.0",
     "buffer": "^6.0.3",
-    "date-fns": "^4.1.0",
-    "date-fns-tz": "^3.2.0",
     "db-vendo-client": "https://github.com/yuyuyureka/db-vendo-client#main",
     "hafas-client": "https://github.com/yu-re-ka/hafas-client#main",
     "idb": "^8.0.1",
     "lit-html": "^3.2.1"
   },
   "devDependencies": {
-    "webpack": "^5.97.1",
-    "webpack-cli": "^6.0.1",
-    "webpack-dev-server": "^5.2.0",
+    "@principalstudio/html-webpack-inject-preload": "^1.2.7",
     "copy-webpack-plugin": "^12.0.2",
+    "css-loader": "^7.1.2",
+    "css-minimizer-webpack-plugin": "^7.0.0",
     "git-revision-webpack-plugin": "^5.0.0",
     "html-webpack-plugin": "^5.6.3",
-    "workbox-webpack-plugin": "^7.3.0",
-    "@principalstudio/html-webpack-inject-preload": "^1.2.7",
-    "style-loader": "^4.0.0",
-    "css-loader": "^7.1.2",
     "mini-css-extract-plugin": "^2.9.2",
-    "css-minimizer-webpack-plugin": "^7.0.0"
+    "style-loader": "^4.0.0",
+    "webpack": "^5.97.1",
+    "webpack-cli": "^6.0.1",
+    "webpack-dev-server": "^5.2.0",
+    "workbox-webpack-plugin": "^7.3.0"
   }
 }
diff --git a/src/canvas.js b/src/canvas.js
@@ -2,7 +2,7 @@ import { moreJourneys } from './journeysView.js';
 import { go } from './router.js';
 import { padZeros } from './helpers.js';
 import { formatTrainTypes, formatLineDisplayName } from './formatters.js'
-import { cachedCoachSequence, coachSequenceCache, coachSequenceCacheKey } from './reihung/index.js';
+import { cachedCoachSequence, coachSequenceCache, coachSequenceCacheKey } from './coach-sequence/index.js';
 
 const formatTime = (date) => {
 	return `${padZeros(date.getHours())}:${padZeros(date.getMinutes())}`;
diff --git a/src/coach-sequence/DB/DBMapping.js b/src/coach-sequence/DB/DBMapping.js
@@ -0,0 +1,199 @@
+//  This code is mostly from marudor's great bahn.expert project.
+//  See here: https://github.com/marudor/bahn.expert/tree/main/src/server/coachSequence
+//  Since the source is MIT licensed, to following code is it too.
+//
+
+import { enrichCoachSequence } from './commonMapping.js';
+import { getLineFromNumber } from './lineNumberMapping.js';
+
+const mapSectors = (sectors, basePercent) => sectors?.map((s) => ({
+	name: s.name,
+	position: {
+		startPercent: basePercent * s.start,
+		endPercent: basePercent * s.end,
+	},
+}) || []);
+
+const 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 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 diningCategories = new Set([
+	'DININGCAR',
+	'HALFDININGCAR_ECONOMY_CLASS',
+	'HALFDININGCAR_FIRST_CLASS',
+]);
+
+const 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;
+	}
+
+	return features;
+}
+
+const mapVehicle = (vehicle, basePercent) => {
+
+	if (!vehicle.platformPosition) return undefined;
+
+	return {
+		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),
+	};
+}
+
+const 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,
+	};
+}
+
+const mapDirection = coaches => {
+	const first = coaches[0];
+	const last = coaches.at(-1);
+
+	return last.position.startPercent > first.position.startPercent;
+}
+
+const mapSequence = async (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 = 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/TrainNames.js b/src/coach-sequence/DB/TrainNames.js
diff --git a/src/coach-sequence/DB/baureihe.js b/src/coach-sequence/DB/baureihe.js
@@ -0,0 +1,211 @@
+//
+//  This code is mostly from marudor's great bahn.expert project.
+//  See here: https://github.com/marudor/bahn.expert/tree/main/src/server/coachSequence
+//  Since the source is MIT licensed, to following code is it too.
+//
+
+import notRedesigned from './notRedesigned.json';
+
+export const nameMap = {
+  '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',
+};
+
+const getATBR = (code, _serial, _coaches) => {
+  switch (code) {
+    case '4011':
+      return {
+        baureihe: '411',
+        identifier: '411.S1'
+      };
+  }
+};
+
+const getDEBR = (code, uicOrdnungsnummer, coaches, tzn) => {
+  switch (code) {
+    case '0812':
+    case '1412':
+    case '1812':
+    case '2412':
+    case '2812':
+    case '3412':
+    case '4812':
+    case '5812':
+    case '6412':
+    case '6812':
+    case '7412':
+    case '7812':
+    case '8812':
+    case '9412':
+    case '9812': {
+      let identifier;
+      switch (coaches.length) {
+        case 13: {
+          identifier = '412.13';
+          break;
+        }
+        case 7: {
+          identifier = '412.7';
+          break;
+        }
+      }
+      return {
+        identifier,
+        baureihe: '412',
+      };
+    }
+    case '5401':
+    case '5801':
+    case '5802':
+    case '5803':
+    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': {
+      return {
+        baureihe: '402',
+        identifier: '402',
+      };
+    }
+    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: tzn?.endsWith('4651') ? '406.R' : '406',
+      };
+    }
+    case '5407': {
+      return {
+        baureihe: '407',
+        identifier: '407',
+      };
+    }
+    case '5410': {
+      return {
+        baureihe: '410.1',
+        identifier: '410.1',
+      };
+    }
+    case '5408': {
+      return {
+        baureihe: '408',
+        identifier: '408',
+      };
+    }
+    case '5411': {
+      return {
+        baureihe: '411',
+        identifier: `411.S${
+          Number.parseInt(uicOrdnungsnummer, 10) <= 32 ? '1' : '2'
+        }`,
+      };
+    }
+    case '5415': {
+      return {
+        baureihe: '415',
+        identifier: '415',
+      };
+    }
+    case '5475': {
+      return {
+        identifier: 'TGV',
+      };
+    }
+  }
+};
+
+export const getBaureiheByUIC = (uic, coaches, tzn) => {
+  const country = uic.slice(2, 4);
+  const code = uic.slice(4, 8);
+  const serial = uic.slice(8, 11);
+  let br;
+  switch (country) {
+    case '80': {
+      br = getDEBR(code, serial, coaches, tzn);
+      break;
+    }
+    case '81': {
+      br = getATBR(code, serial, coaches);
+      break;
+    }
+  }
+  if (!br) return undefined;
+
+  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.TRE';
+      break;
+    }
+    if (c.uic?.slice(4, 8) === '4110') {
+      identifier = '4110';
+      break;
+    }
+    if (c.uic?.slice(4, 8) === '4010') {
+      identifier = '4010';
+      break;
+    }
+  }
+  if (identifier) {
+    return {
+      identifier,
+      name: nameMap[identifier],
+    };
+  }
+};
diff --git a/src/coach-sequence/DB/commonMapping.js b/src/coach-sequence/DB/commonMapping.js
@@ -0,0 +1,109 @@
+//  This code is mostly from marudor's great bahn.expert project.
+//  See here: https://github.com/marudor/bahn.expert/tree/main/src/server/coachSequence
+//  Since the source is MIT licensed, to following code is it too.
+//
+
+import { getBaureiheByCoaches, getBaureiheByUIC } from './baureihe.js';
+import { getSeatsForCoach } from './specialSeats.js';
+import TrainNames from './TrainNames.js';
+
+const hasNonLokCoach = group => group.coaches.some(c => c.category !== 'LOK' && c.category !== 'TRIEBKOPF');
+
+export function enrichCoachSequence(coachSequence) {
+  let prevGroup;
+
+  for (const group of coachSequence.sequence.groups) {
+    enrichCoachSequenceGroup(group, coachSequence.product);
+
+    if (!hasNonLokCoach(group)) {
+      continue;
+    }
+
+    if (prevGroup && prevGroup.destinationName !== group.destinationName) {
+      coachSequence.multipleDestinations = true;
+    }
+
+    if (prevGroup && prevGroup.number !== group.number) {
+      coachSequence.multipleTrainNumbers = true;
+    }
+
+    prevGroup = group;
+  }
+}
+const allowedBR = ['IC', 'EC', 'ICE', 'ECE'];
+const tznRegex = /(\d+)/;
+
+function enrichCoachSequenceGroup(group, product) {
+  // https://inside.bahn.de/entstehung-zugnummern/?dbkanal_006=L01_S01_D088_KTL0006_INSIDE-BAHN-2019_Zugnummern_LZ01
+  const trainNumberAsNumber = Number.parseInt(group.number, 10);
+
+  if (trainNumberAsNumber >= 9550 && trainNumberAsNumber <= 9599) {
+    group.coaches.forEach(c => {
+      if (c.features.comfort) {
+        c.features.comfort = false;
+      }
+    });
+  }
+
+  if (allowedBR.includes(product.type)) {
+    let tzn;
+
+    if (group.name.startsWith('IC')) {
+      tzn = tznRegex.exec(group.name)?.[0];
+      group.trainName = TrainNames(tzn);
+    }
+
+    group.baureihe = calculateBR(group.coaches, tzn);
+
+
+    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);
+      }
+    }
+  }
+}
+
+function calculateBR(coaches, tzn) {
+  for (const c of coaches) {
+    if (!c.uic) continue;
+    const br = getBaureiheByUIC(c.uic, coaches, tzn);
+    if (br) return br;
+  }
+
+  return getBaureiheByCoaches(coaches);
+}
diff --git a/src/coach-sequence/DB/lineNumberMapping.js b/src/coach-sequence/DB/lineNumberMapping.js
@@ -0,0 +1,11 @@
+//  This code is mostly from marudor's great bahn.expert project.
+//  See here: https://github.com/marudor/bahn.expert/tree/main/src/server/coachSequence
+//  Since the source is MIT licensed, to following code is it too.
+//
+
+import lines from './lines.json';
+
+export function getLineFromNumber(journeyNumber) {
+  if (!journeyNumber) return undefined;
+  return lines[journeyNumber];
+}
diff --git a/src/reihung/lines.json b/src/coach-sequence/DB/lines.json
diff --git a/src/reihung/notRedesigned.json b/src/coach-sequence/DB/notRedesigned.json
diff --git a/src/coach-sequence/DB/specialSeats.js b/src/coach-sequence/DB/specialSeats.js
@@ -0,0 +1,180 @@
+//  This code is mostly from marudor's great bahn.expert project.
+//  See here: https://github.com/marudor/bahn.expert/tree/main/src/server/coachSequence
+//  Since the source is MIT licensed, to following code is it too.
+//
+
+export function getComfortSeats(identifier, klasse) {
+  switch (identifier) {
+    case '401':
+      return klasse === 1 ? '11-36' : '11-57';
+
+    case '401.9':
+    case '401.LDV':
+      return klasse === 1 ? '12-31' : '11-44';
+
+    case '402':
+      return klasse === 1 ? '11-16, 21, 22' : '81-108';
+
+    case '403':
+    case '403.S1':
+    case '403.S2':
+    case '406':
+      return klasse === 1 ? '12-26' : '11-38';
+
+    case '403.R':
+    case '406.R':
+      return klasse === 1 ? '12-26' : '11-37';
+
+    case '407':
+      return klasse === 1 ? '21-26, 31, 33, 35' : '31-55, 57';
+
+    case '411':
+      return klasse === 1 ? '46, 52-56' : '92, 94, 96, 98, 101-118';
+
+    case '412.7':
+      return klasse === 1 ? '41, 44-53' : '11-44';
+
+    case '412':
+    case '412.13':
+      return klasse === 1 ? '11-46' : '11-68';
+
+    case '415':
+      return klasse === 1 ? '52, 54, 56' : '81-88, 91-98';
+
+    case 'MET':
+      return klasse === 1 ? '61-66' : '91-106';
+
+    case 'IC2.TWIN':
+      return klasse === 1 ? '73, 75, 83-86' : '31-38, 41-45, 47';
+
+    case 'IC2.KISS':
+      return klasse === 3 ? '144, 145' : '55-68';
+  }
+}
+export function getDisabledSeats(identifier, klasse, wagenordnungsnummer) {
+  switch (identifier) {
+    case '401':
+      return klasse === 1 ? '51, 52, 53, 55' : '111-116';
+
+    case '401.9':
+    case '401.LDV':
+      return klasse === 1 ? '11, 13, 15' : '11, 13, 111-116';
+
+    case '402':
+      return klasse === 1 ? '12, 21' : '81, 85-88';
+
+    case '403':
+    case '403.R':
+    case '403.S1':
+    case '403.S2':
+    case '406':
+      if (klasse === 1) return '64, 66';
+
+      if (wagenordnungsnummer === '25' || wagenordnungsnummer === '35') {
+        // redesign slighlty different
+        return ['403R', '403.S1R', '403.S2R'].includes(identifier) ? '61, 63, 65-67' : '61, 63, 65, 67';
+      }
+
+      return '106, 108';
+
+    case '407':
+      if (klasse === 1) return '13, 15';
+
+      if (wagenordnungsnummer === '11' || wagenordnungsnummer === '21') {
+        return '11-18';
+      }
+
+      return '28, 33-34';
+
+    case '411':
+      return klasse === 1 ? '21, 22' : '15-18';
+
+    case '412.7':
+      return klasse === 1 ? '12, 13' : '11-18';
+
+    case '412':
+    case '412.13':
+      if (klasse === 1) return wagenordnungsnummer === '10' ? '12, 13' : '11, 14, 21';
+
+      switch (wagenordnungsnummer) {
+        case '1':
+          return '11-24';
+
+        case '8':
+          return '11, 12';
+
+        case '9':
+          return '41, 45, 46';
+      }
+
+      break;
+
+    case '415':
+      return klasse === 1 ? '21' : '15, 17';
+
+    case 'MET':
+      return klasse === 1 ? '16, 21' : '12, 14, 16';
+
+    case 'IC2.TWIN':
+      return klasse === 1 ? '21, 71' : '25, 101-105, 171-173';
+
+    case 'IC2.KISS':
+      return klasse === 3 ? '143' : '21-26';
+  }
+}
+export function getFamilySeats(identifier) {
+  switch (identifier) {
+    case '401':
+      return '81-116';
+
+    case '401.9':
+    case '401.LDV':
+      return '91-116';
+
+    case '402':
+      return '61-78';
+
+    case '403':
+    case '403.R':
+    case '403.S1':
+    case '403.S2':
+    case '406':
+    case '406.R':
+      return '11-28';
+
+    case '407':
+      return '11-28';
+
+    case '411':
+      return '11-18, 31-38';
+
+    case '412.7':
+      return '61-78';
+
+    case '412':
+    case '412.13':
+      return '61-78';
+
+    case 'MET':
+      return '11-26';
+
+    case 'IC2.TWIN':
+      return '121, 123, 131-138';
+
+    case 'IC2.KISS':
+      return '42, 43, 45, 46, 52-56';
+  }
+}
+export function getSeatsForCoach(coach, identifier) {
+  const family = coach.features.family ? getFamilySeats(identifier) : undefined;
+  const disabled = coach.features.disabled ? getDisabledSeats(identifier, coach.class, coach.identificationNumber) : undefined;
+  const comfort = coach.features.comfort ? getComfortSeats(identifier, coach.class) : undefined;
+
+  if (family || disabled || comfort) {
+    return {
+      comfort,
+      disabled,
+      family
+    };
+  }
+}+
\ No newline at end of file
diff --git a/src/coach-sequence/index.js b/src/coach-sequence/index.js
@@ -0,0 +1,55 @@
+import { padZeros, sleep } from '../helpers.js';
+import { mapInformation } from './DB/DBMapping.js';
+
+const dbCoachSequenceTimeout    = 1000;
+export const coachSequenceCache = {};
+
+const rawDBCoachSequence = async (category, number, evaNumber, date, retry = 2) => {
+	try {
+		const searchParams = new URLSearchParams();
+
+		searchParams.append("category",  category);
+		searchParams.append("date",      `${date.getFullYear()}-${padZeros(date.getMonth()+1)}-${padZeros(date.getDate())}`);
+		searchParams.append("time",      `${date.getFullYear()}-${padZeros(date.getMonth()+1)}-${padZeros(date.getDate())}T${padZeros(date.getHours())}:${padZeros(date.getMinutes())}:${padZeros(date.getSeconds())}Z`);
+		searchParams.append("evaNumber", evaNumber);
+		searchParams.append("number",    number);
+
+
+		return await fetch(`/db/vehicle-sequence?${searchParams}`).then(x => x.json());
+	} catch (e) {
+		sleep(dbCoachSequenceTimeout);
+		if (retry) return rawDBCoachSequence(category, number, evaNumber, date, retry - 1);
+	}
+}
+
+const DBCoachSequence = async (category, number, evaNumber, date) => {
+  const rawSequence = await rawDBCoachSequence(category, number, evaNumber, date);
+
+  if (!rawSequence) return undefined;
+
+  return mapInformation(rawSequence, category, number, evaNumber);
+}
+
+export const coachSequenceCacheKey = (category, number, evaNumber, departure) => {
+	if (!category || !number || !evaNumber || !departure) return;
+	return `${category}-${number}-${evaNumber}-${departure.toISOString()}`;
+};
+
+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 DBCoachSequence(category, number, evaNumber, departure);
+				coachSequenceCache[key] = info;
+				return info;
+			} catch (e) {}
+			coachSequenceCache[key] = null;
+		}) ();
+	}
+
+	return coachSequenceCache[key];
+};
diff --git a/src/helpers.js b/src/helpers.js
@@ -18,6 +18,8 @@ const loyaltyCardsReverse = {
 	'Symbol(General-Abonnement)': 'GENERALABONNEMENT',
 };
 
+export const sleep         = delay   => new Promise((resolve) => setTimeout(resolve, delay));
+
 export const ElementById   = id      => document.getElementById(id);
 
 export const showElement   = element => element.classList.remove('hidden');
diff --git a/src/journeyView.js b/src/journeyView.js
@@ -1,5 +1,5 @@
-import { cachedCoachSequence } from './reihung/index.js';
 import { html, nothing, render } from 'lit-html';
+import { cachedCoachSequence } from './coach-sequence/index.js';
 import { settings } from './settings.js';
 import { remarksModalTemplate, platformTemplate, stopTemplate, timeTemplate } from './templates.js';
 import { ElementById, setThemeColor, queryBackgroundColor } from './helpers.js';
diff --git a/src/reihung/DB/DBMapping.js b/src/reihung/DB/DBMapping.js
@@ -1,220 +0,0 @@
-import { enrichCoachSequence } from '../commonMapping.js';
-import { getLineFromNumber } from '../lineNumberMapping.js';
-
-function mapSectors(
-	sectors,
-	basePercent,
-) {
-	return (
-		sectors?.map((s) => ({
-			name: s.name,
-			position: {
-				startPercent: basePercent * s.start,
-				endPercent: basePercent * s.end,
-			},
-		})) || []
-	);
-}
-
-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,
-	];
-}
-
-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 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;
-}
-
-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;
-}
-
-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,
-	};
-}
-
-function mapDirection(coaches) {
-	const first = coaches[0];
-	const last = coaches.at(-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 = 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 +0,0 @@
-import {
-        addDays,
-        differenceInHours,
-        isWithinInterval,
-        subDays,
-	format,
-	formatISO,
-} from 'date-fns';
-
-import { mapInformation } from './DBMapping.js';
-
-const dbCoachSequenceTimeout = process.env.NODE_ENV === 'production' ? 2500 : 10000;
-
-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 `/db/vehicle-sequence?${searchParams}`;
-};
-
-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(category, number, evaNumber, date, retry = 2) {
-  try {
-    return coachSequence(category, number, evaNumber, date);
-  } catch (e) {
-    if (retry) return rawDBCoachSequence(category, number, evaNumber, date, retry - 1);
-  }
-}
-export async function DBCoachSequence(category, number, evaNumber, date) {
-  const rawSequence = await rawDBCoachSequence(category, number, evaNumber, date);
-  if (!rawSequence) return undefined;
-  return mapInformation(rawSequence, category, number, evaNumber);
-}
diff --git a/src/reihung/DB/plannedSequence.js b/src/reihung/DB/plannedSequence.js
@@ -1,164 +0,0 @@
-import { getSeatsForCoach } from '../specialSeats.js';
-import { nameMap } from '../baureihe.js';
-import Axios from 'axios';
-const apiUrl = process.env.PLANNED_API_URL;
-const apiKey = process.env.PLANNED_API_KEY;
-export const planSequenceAxios = Axios.create({
-  baseURL: apiUrl,
-  headers: {
-    'x-api-key': apiKey || ''
-  }
-});
-export async function getPlannedSequence(trainNumber, initialDeparture, evaNumber) {
-  if (!apiKey || !apiUrl) {
-    return undefined;
-  }
-
-  try {
-    const plannedSequence = (await planSequenceAxios.get(`/sequence/${trainNumber}/${initialDeparture.toISOString()}/${evaNumber}`)).data;
-    plannedSequence.sequence.groups.forEach(g => {
-      const br = getBRFromGroupName(g.name);
-      g.baureihe = br;
-      g.name = `${g.number}-planned`;
-
-      if (br) {
-        g.coaches.forEach(coach => {
-          coach.seats = getSeatsForCoach(coach, br.identifier);
-        });
-      }
-    });
-    return plannedSequence;
-  } catch (e) {
-    return undefined;
-  }
-}
-
-function getBRWithoutNameFromGroupName(groupName) {
-  // ICE
-  if (groupName.startsWith('401_11')) {
-    return {
-      identifier: '401.9',
-      baureihe: '401'
-    };
-  }
-
-  if (groupName.startsWith('401_14')) {
-    return {
-      identifier: '401',
-      baureihe: '401'
-    };
-  }
-
-  if (groupName.startsWith('402')) {
-    return {
-      identifier: '402',
-      baureihe: '402'
-    };
-  }
-
-  if (groupName.startsWith('403E')) {
-    return {
-      identifier: '403.R',
-      baureihe: '403'
-    };
-  }
-
-  if (groupName.startsWith('406')) {
-    return {
-      identifier: '406'
-    };
-  }
-
-  if (groupName.startsWith('403')) {
-    return {
-      identifier: '403',
-      baureihe: '403'
-    };
-  }
-
-  if (groupName.startsWith('406.01')) {
-    return {
-      identifier: '406',
-      baureihe: '406'
-    };
-  }
-
-  if (groupName.startsWith('406.02')) {
-    return {
-      identifier: '406.R',
-      baureihe: '406'
-    };
-  }
-
-  if (groupName.startsWith('412_12')) {
-    return {
-      identifier: '412',
-      baureihe: '412'
-    };
-  }
-
-  if (groupName.startsWith('407')) {
-    return {
-      identifier: '407',
-      baureihe: '407'
-    };
-  }
-
-  if (groupName.startsWith('411')) {
-    return {
-      identifier: '411',
-      baureihe: '411'
-    };
-  }
-
-  if (groupName.startsWith('412_13')) {
-    return {
-      identifier: '412.13',
-      baureihe: '412'
-    };
-  }
-
-  if (groupName.startsWith('412_07')) {
-    return {
-      identifier: '412.7',
-      baureihe: '412'
-    };
-  }
-
-  if (groupName.startsWith('406')) {
-    return {
-      identifier: '406',
-      baureihe: '406'
-    };
-  }
-
-  if (groupName.startsWith('415')) {
-    return {
-      identifier: '415',
-      baureihe: '415'
-    };
-  } // IC
-
-
-  if (groupName.startsWith('KISS')) {
-    return {
-      identifier: 'IC2.KISS'
-    };
-  }
-
-  if (groupName.startsWith('Dosto')) {
-    return {
-      identifier: 'IC2.TWIN'
-    };
-  }
-}
-
-export function getBRFromGroupName(groupName) {
-  const brWithoutName = getBRWithoutNameFromGroupName(groupName);
-
-  if (brWithoutName) {
-    return { ...brWithoutName,
-      name: nameMap[brWithoutName.identifier]
-    };
-  }
-}
diff --git a/src/reihung/baureihe.js b/src/reihung/baureihe.js
@@ -1,203 +0,0 @@
-import notRedesigned from './notRedesigned.json';
-export const nameMap = {
-  '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',
-};
-
-const getATBR = (code, _serial, _coaches) => {
-  switch (code) {
-    case '4011':
-      return {
-        baureihe: '411',
-        identifier: '411.S1'
-      };
-  }
-};
-
-const getDEBR = (code, uicOrdnungsnummer, coaches, tzn) => {
-  switch (code) {
-    case '0812':
-    case '1412':
-    case '1812':
-    case '2412':
-    case '2812':
-    case '3412':
-    case '4812':
-    case '5812':
-    case '6412':
-    case '6812':
-    case '7412':
-    case '7812':
-    case '8812':
-    case '9412':
-    case '9812': {
-      let identifier;
-      switch (coaches.length) {
-        case 13: {
-          identifier = '412.13';
-          break;
-        }
-        case 7: {
-          identifier = '412.7';
-          break;
-        }
-      }
-      return {
-        identifier,
-        baureihe: '412',
-      };
-    }
-    case '5401':
-    case '5801':
-    case '5802':
-    case '5803':
-    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': {
-      return {
-        baureihe: '402',
-        identifier: '402',
-      };
-    }
-    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: tzn?.endsWith('4651') ? '406.R' : '406',
-      };
-    }
-    case '5407': {
-      return {
-        baureihe: '407',
-        identifier: '407',
-      };
-    }
-    case '5410': {
-      return {
-        baureihe: '410.1',
-        identifier: '410.1',
-      };
-    }
-    case '5408': {
-      return {
-        baureihe: '408',
-        identifier: '408',
-      };
-    }
-    case '5411': {
-      return {
-        baureihe: '411',
-        identifier: `411.S${
-          Number.parseInt(uicOrdnungsnummer, 10) <= 32 ? '1' : '2'
-        }`,
-      };
-    }
-    case '5415': {
-      return {
-        baureihe: '415',
-        identifier: '415',
-      };
-    }
-    case '5475': {
-      return {
-        identifier: 'TGV',
-      };
-    }
-  }
-};
-
-export const getBaureiheByUIC = (uic, coaches, tzn) => {
-  const country = uic.slice(2, 4);
-  const code = uic.slice(4, 8);
-  const serial = uic.slice(8, 11);
-  let br;
-  switch (country) {
-    case '80': {
-      br = getDEBR(code, serial, coaches, tzn);
-      break;
-    }
-    case '81': {
-      br = getATBR(code, serial, coaches);
-      break;
-    }
-  }
-  if (!br) return undefined;
-
-  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.TRE';
-      break;
-    }
-    if (c.uic?.slice(4, 8) === '4110') {
-      identifier = '4110';
-      break;
-    }
-    if (c.uic?.slice(4, 8) === '4010') {
-      identifier = '4010';
-      break;
-    }
-  }
-  if (identifier) {
-    return {
-      identifier,
-      name: nameMap[identifier],
-    };
-  }
-};
diff --git a/src/reihung/commonMapping.js b/src/reihung/commonMapping.js
@@ -1,104 +0,0 @@
-import { getBaureiheByCoaches, getBaureiheByUIC } from './baureihe.js';
-import { getSeatsForCoach } from './specialSeats.js';
-import TrainNames from './TrainNames.js';
-
-const hasNonLokCoach = group => group.coaches.some(c => c.category !== 'LOK' && c.category !== 'TRIEBKOPF');
-
-export function enrichCoachSequence(coachSequence) {
-  let prevGroup;
-
-  for (const group of coachSequence.sequence.groups) {
-    enrichCoachSequenceGroup(group, coachSequence.product);
-
-    if (!hasNonLokCoach(group)) {
-      continue;
-    }
-
-    if (prevGroup && prevGroup.destinationName !== group.destinationName) {
-      coachSequence.multipleDestinations = true;
-    }
-
-    if (prevGroup && prevGroup.number !== group.number) {
-      coachSequence.multipleTrainNumbers = true;
-    }
-
-    prevGroup = group;
-  }
-}
-const allowedBR = ['IC', 'EC', 'ICE', 'ECE'];
-const tznRegex = /(\d+)/;
-
-function enrichCoachSequenceGroup(group, product) {
-  // https://inside.bahn.de/entstehung-zugnummern/?dbkanal_006=L01_S01_D088_KTL0006_INSIDE-BAHN-2019_Zugnummern_LZ01
-  const trainNumberAsNumber = Number.parseInt(group.number, 10);
-
-  if (trainNumberAsNumber >= 9550 && trainNumberAsNumber <= 9599) {
-    group.coaches.forEach(c => {
-      if (c.features.comfort) {
-        c.features.comfort = false;
-      }
-    });
-  }
-
-  if (allowedBR.includes(product.type)) {
-    let tzn;
-
-    if (group.name.startsWith('IC')) {
-      tzn = tznRegex.exec(group.name)?.[0];
-      group.trainName = TrainNames(tzn);
-    }
-
-    group.baureihe = calculateBR(group.coaches, tzn);
-
-
-    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);
-      }
-    }
-  }
-}
-
-function calculateBR(coaches, tzn) {
-  for (const c of coaches) {
-    if (!c.uic) continue;
-    const br = getBaureiheByUIC(c.uic, coaches, tzn);
-    if (br) return br;
-  }
-
-  return getBaureiheByCoaches(coaches);
-}
diff --git a/src/reihung/index.js b/src/reihung/index.js
@@ -1,28 +0,0 @@
-import { DBCoachSequence } from './DB/index.js';
-
-export async function coachSequence(category, number, evaNumber, departure) {
-  return await DBCoachSequence(category, number, evaNumber, departure);
-}
-
-export const coachSequenceCache = {};
-
-export const coachSequenceCacheKey = (category, number, evaNumber, departure) => {
-	if (!category || !number || !evaNumber || !departure) return;
-	return `${category}-${number}-${evaNumber}-${departure.toISOString()}`;
-};
-
-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(category, number, evaNumber, departure);
-				coachSequenceCache[key] = info;
-				return info;
-			} catch (e) {}
-			coachSequenceCache[key] = null;
-		}) ();
-	}
-	return coachSequenceCache[key];
-};
diff --git a/src/reihung/lineNumberMapping.js b/src/reihung/lineNumberMapping.js
@@ -1,6 +0,0 @@
-import lines from './lines.json';
-
-export function getLineFromNumber(journeyNumber) {
-  if (!journeyNumber) return undefined;
-  return lines[journeyNumber];
-}
diff --git a/src/reihung/specialSeats.js b/src/reihung/specialSeats.js
@@ -1,175 +0,0 @@
-export function getComfortSeats(identifier, klasse) {
-  switch (identifier) {
-    case '401':
-      return klasse === 1 ? '11-36' : '11-57';
-
-    case '401.9':
-    case '401.LDV':
-      return klasse === 1 ? '12-31' : '11-44';
-
-    case '402':
-      return klasse === 1 ? '11-16, 21, 22' : '81-108';
-
-    case '403':
-    case '403.S1':
-    case '403.S2':
-    case '406':
-      return klasse === 1 ? '12-26' : '11-38';
-
-    case '403.R':
-    case '406.R':
-      return klasse === 1 ? '12-26' : '11-37';
-
-    case '407':
-      return klasse === 1 ? '21-26, 31, 33, 35' : '31-55, 57';
-
-    case '411':
-      return klasse === 1 ? '46, 52-56' : '92, 94, 96, 98, 101-118';
-
-    case '412.7':
-      return klasse === 1 ? '41, 44-53' : '11-44';
-
-    case '412':
-    case '412.13':
-      return klasse === 1 ? '11-46' : '11-68';
-
-    case '415':
-      return klasse === 1 ? '52, 54, 56' : '81-88, 91-98';
-
-    case 'MET':
-      return klasse === 1 ? '61-66' : '91-106';
-
-    case 'IC2.TWIN':
-      return klasse === 1 ? '73, 75, 83-86' : '31-38, 41-45, 47';
-
-    case 'IC2.KISS':
-      return klasse === 3 ? '144, 145' : '55-68';
-  }
-}
-export function getDisabledSeats(identifier, klasse, wagenordnungsnummer) {
-  switch (identifier) {
-    case '401':
-      return klasse === 1 ? '51, 52, 53, 55' : '111-116';
-
-    case '401.9':
-    case '401.LDV':
-      return klasse === 1 ? '11, 13, 15' : '11, 13, 111-116';
-
-    case '402':
-      return klasse === 1 ? '12, 21' : '81, 85-88';
-
-    case '403':
-    case '403.R':
-    case '403.S1':
-    case '403.S2':
-    case '406':
-      if (klasse === 1) return '64, 66';
-
-      if (wagenordnungsnummer === '25' || wagenordnungsnummer === '35') {
-        // redesign slighlty different
-        return ['403R', '403.S1R', '403.S2R'].includes(identifier) ? '61, 63, 65-67' : '61, 63, 65, 67';
-      }
-
-      return '106, 108';
-
-    case '407':
-      if (klasse === 1) return '13, 15';
-
-      if (wagenordnungsnummer === '11' || wagenordnungsnummer === '21') {
-        return '11-18';
-      }
-
-      return '28, 33-34';
-
-    case '411':
-      return klasse === 1 ? '21, 22' : '15-18';
-
-    case '412.7':
-      return klasse === 1 ? '12, 13' : '11-18';
-
-    case '412':
-    case '412.13':
-      if (klasse === 1) return wagenordnungsnummer === '10' ? '12, 13' : '11, 14, 21';
-
-      switch (wagenordnungsnummer) {
-        case '1':
-          return '11-24';
-
-        case '8':
-          return '11, 12';
-
-        case '9':
-          return '41, 45, 46';
-      }
-
-      break;
-
-    case '415':
-      return klasse === 1 ? '21' : '15, 17';
-
-    case 'MET':
-      return klasse === 1 ? '16, 21' : '12, 14, 16';
-
-    case 'IC2.TWIN':
-      return klasse === 1 ? '21, 71' : '25, 101-105, 171-173';
-
-    case 'IC2.KISS':
-      return klasse === 3 ? '143' : '21-26';
-  }
-}
-export function getFamilySeats(identifier) {
-  switch (identifier) {
-    case '401':
-      return '81-116';
-
-    case '401.9':
-    case '401.LDV':
-      return '91-116';
-
-    case '402':
-      return '61-78';
-
-    case '403':
-    case '403.R':
-    case '403.S1':
-    case '403.S2':
-    case '406':
-    case '406.R':
-      return '11-28';
-
-    case '407':
-      return '11-28';
-
-    case '411':
-      return '11-18, 31-38';
-
-    case '412.7':
-      return '61-78';
-
-    case '412':
-    case '412.13':
-      return '61-78';
-
-    case 'MET':
-      return '11-26';
-
-    case 'IC2.TWIN':
-      return '121, 123, 131-138';
-
-    case 'IC2.KISS':
-      return '42, 43, 45, 46, 52-56';
-  }
-}
-export function getSeatsForCoach(coach, identifier) {
-  const family = coach.features.family ? getFamilySeats(identifier) : undefined;
-  const disabled = coach.features.disabled ? getDisabledSeats(identifier, coach.class, coach.identificationNumber) : undefined;
-  const comfort = coach.features.comfort ? getComfortSeats(identifier, coach.class) : undefined;
-
-  if (family || disabled || comfort) {
-    return {
-      comfort,
-      disabled,
-      family
-    };
-  }
-}-
\ No newline at end of file