ctucx.git: trainsearch

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

commit 7a0cd5f02045e5fc00083c88ae7a15afb4f61e8f
parent e6d8088c3a198cafe6a6dfa50a6802aa9df33d1b
Author: Yureka <yuka@yuka.dev>
Date: Mon, 5 Sep 2022 17:33:08 +0200

missing files
11 files changed, 2274 insertions(+), 0 deletions(-)
A
src/reihung/DB/DBMapping.js
|
195
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/reihung/DB/index.js
|
39
+++++++++++++++++++++++++++++++++++++++
A
src/reihung/DB/plannedSequence.js
|
164
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/reihung/TrainNames.js
|
275
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/reihung/baureihe.js
|
218
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/reihung/commonMapping.js
|
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/reihung/index.js
|
28
++++++++++++++++++++++++++++
A
src/reihung/lineNumberMapping.js
|
6
++++++
A
src/reihung/lines.json
|
1095
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
src/reihung/notRedesigned.json
|
1
+
A
src/reihung/specialSeats.js
|
176
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/src/reihung/DB/DBMapping.js b/src/reihung/DB/DBMapping.js
@@ -0,0 +1,195 @@
+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)
+  };
+};
+
+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
+  };
+};
+
+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
+  };
+};
+
+const mapSector = sektor => {
+  const position = mapPosition(sektor.positionamgleis);
+  if (!position) return;
+  return {
+    name: sektor.sektorbezeichnung,
+    position
+  };
+};
+
+const mapStop = halt => {
+  let sectors = halt.allSektor.map(mapSector);
+
+  if (sectors.includes(undefined)) {
+    sectors = [];
+  }
+
+  return {
+    stopPlace: {
+      evaNumber: halt.evanummer,
+      name: halt.bahnhofsname
+    },
+    // @ts-expect-error we checked for undefined
+    sectors
+  };
+};
+
+const mapProduct = formation => ({
+  number: formation.zugnummer,
+  type: formation.zuggattung,
+  line: getLineFromNumber(formation.zugnummer)
+});
+
+function mapDirection(coaches) {
+  const first = coaches[0];
+  const last = coaches[coaches.length - 1];
+  return last.position.startPercent > first.position.startPercent;
+}
+
+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;
+};
diff --git a/src/reihung/DB/index.js b/src/reihung/DB/index.js
@@ -0,0 +1,39 @@
+import { format } from 'date-fns';
+import { mapInformation } from './DBMapping';
+import { utcToZonedTime } from 'date-fns-tz';
+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 => format(utcToZonedTime(date, 'Europe/Berlin'), 'yyyyMMddHHmm');
+
+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
+    }
+  }
+
+  const info = await fetch(getDBCoachSequenceUrl(trainNumber, date, 'apps')).then(x => x.json());
+  return info;
+}
+
+export async function rawDBCoachSequence(trainNumber, date, retry = 2) {
+  try {
+    return coachSequence(trainNumber, date);
+  } catch (e) {
+    if (retry) return rawDBCoachSequence(trainNumber, date, retry - 1);
+  }
+}
+export async function DBCoachSequence(trainNumber, date) {
+  const rawSequence = await rawDBCoachSequence(trainNumber, date);
+  if (!rawSequence) return undefined;
+  return mapInformation(rawSequence.data.istformation);
+}
diff --git a/src/reihung/DB/plannedSequence.js b/src/reihung/DB/plannedSequence.js
@@ -0,0 +1,164 @@
+import { getSeatsForCoach } from '../specialSeats';
+import { nameMap } from '../baureihe';
+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/TrainNames.js b/src/reihung/TrainNames.js
@@ -0,0 +1,274 @@
+// List from https://de.wikipedia.org/wiki/Liste_nach_Gemeinden_und_Regionen_benannter_IC/ICE-Fahrzeuge#Namensgebung_ICE-Triebz%C3%BCge_nach_Gemeinden
+const naming = {
+  // ICE 1 - BR 401
+  101: 'Gießen',
+  102: 'Jever',
+  103: 'Neu-Isenburg',
+  104: 'Fulda',
+  105: 'Offenbach am Main',
+  106: 'Itzehoe',
+  107: 'Plattling',
+  108: 'Lichtenfels',
+  110: 'Gelsenkirchen',
+  112: 'Memmingen',
+  113: 'Frankenthal/Pfalz',
+  114: 'Friedrichshafen',
+  115: 'Regensburg',
+  116: 'Pforzheim',
+  117: 'Hof',
+  118: 'Gelnhausen',
+  119: 'Osnabrück',
+  120: 'Lüneburg',
+  152: 'Hanau',
+  153: 'Neumünster',
+  154: 'Flensburg',
+  155: 'Rosenheim',
+  156: 'Heppenheim/Bergstraße',
+  157: 'Landshut',
+  158: 'Gütersloh',
+  159: 'Bad Oldesloe',
+  160: 'Mülheim an der Ruhr',
+  161: 'Bebra',
+  162: 'Geisenheim/Rheingau',
+  166: 'Gelnhausen',
+  167: 'Garmisch-Partenkirchen',
+  168: 'Crailsheim',
+  169: 'Worms',
+  171: 'Heusenstamm',
+  172: 'Aschaffenburg',
+  173: 'Basel',
+  174: 'Zürich',
+  175: 'Nürnberg',
+  176: 'Bremen',
+  177: 'Rendsburg',
+  178: 'Bremerhaven',
+  180: 'Castrop-Rauxel',
+  181: 'Interlaken',
+  182: 'Rüdesheim am Rhein',
+  183: 'Timmendorfer Strand',
+  184: 'Bruchsal',
+  185: 'Freilassing',
+  186: 'Chur',
+  187: 'Mühldorf a. Inn',
+  188: 'Hildesheim',
+  190: 'Ludwigshafen am Rhein',
+  //
+  // ICE 2 - BR 402
+  201: 'Rheinsberg',
+  202: 'Wuppertal',
+  203: 'Cottbus/Chóśebuz',
+  204: 'Bielefeld',
+  205: 'Zwickau',
+  206: 'Magdeburg',
+  207: 'Stendal',
+  208: 'Bonn',
+  209: 'Riesa',
+  210: 'Fontanestadt Neuruppin',
+  211: 'Uelzen',
+  212: 'Potsdam',
+  213: 'Nauen',
+  214: 'Hamm (Westf.)',
+  215: 'Bitterfeld-Wolfen',
+  216: 'Dessau',
+  217: 'Bergen auf Rügen',
+  218: 'Braunschweig',
+  219: 'Hagen',
+  220: 'Meiningen',
+  221: 'Lübbenau/Spreewald',
+  222: 'Eberswalde',
+  223: 'Schwerin',
+  224: 'Saalfeld (Saale)',
+  225: 'Oldenburg (Oldb)',
+  226: 'Lutherstadt Wittenberg',
+  227: 'Ludwigslust',
+  228: 'Altenburg',
+  229: 'Templin',
+  230: 'Delitzsch',
+  231: 'Brandenburg an der Havel',
+  232: 'Frankfurt (Oder)',
+  233: 'Ulm',
+  234: 'Minden',
+  235: 'Görlitz',
+  236: 'Jüterbog',
+  237: 'Neustrelitz',
+  238: 'Saarbrücken',
+  239: 'Essen',
+  240: 'Bochum',
+  241: 'Bad Hersfeld',
+  242: 'Quedlinburg',
+  243: 'Bautzen/Budyšin',
+  244: 'Koblenz',
+  //
+  // ICE 3 - BR 403
+  301: 'Freiburg im Breisgau',
+  302: 'Hansestadt Lübeck',
+  303: 'Dortmund',
+  304: 'München',
+  305: 'Baden-Baden',
+  306: 'Nördlingen',
+  307: 'Oberhausen',
+  308: 'Murnau am Staffelsee',
+  309: 'Aalen',
+  310: 'Wolfsburg',
+  311: 'Wiesbaden',
+  312: 'Montabaur',
+  313: 'Treuchtlingen',
+  314: 'Bergisch Gladbach',
+  315: 'Singen (Hohentwiel)',
+  316: 'Siegburg',
+  317: 'Recklinghausen',
+  318: 'Münster (Westf.)',
+  319: 'Duisburg',
+  320: 'Weil am Rhein',
+  321: 'Krefeld',
+  322: 'Solingen',
+  323: 'Schaffhausen',
+  324: 'Fürth',
+  325: 'Ravensburg',
+  326: 'Neunkirchen',
+  327: 'Siegen',
+  328: 'Aachen',
+  330: 'Göttingen',
+  331: 'Westerland/Sylt',
+  332: 'Augsburg',
+  333: 'Goslar',
+  334: 'Offenburg',
+  335: 'Konstanz',
+  336: 'Ingolstadt',
+  337: 'Stuttgart',
+  351: 'Herford',
+  352: 'Mönchengladbach',
+  353: 'Neu-Ulm',
+  354: 'Mittenwald',
+  355: 'Tuttlingen',
+  357: 'Esslingen am Neckar',
+  358: 'St. Ingbert',
+  359: 'Leverkusen',
+  360: 'Linz am Rhein',
+  361: 'Celle',
+  362: 'Schwerte (Ruhr)',
+  363: 'Weilheim i. OB',
+  //
+  // ICE T - BR 411
+  1101: 'Neustadt an der Weinstraße',
+  1102: 'Neubrandenburg',
+  1103: 'Paderborn',
+  1104: 'Erfurt',
+  1105: 'Dresden',
+  1107: 'Pirna',
+  1108: 'Berlin',
+  1109: 'Güstrow',
+  1110: 'Naumburg (Saale)',
+  1111: 'Hansestadt Wismar',
+  1112: 'Freie und Hansestadt Hamburg',
+  1113: 'Hansestadt Stralsund',
+  1117: 'Erlangen',
+  1118: 'Plauen/Vogtland',
+  1119: 'Meißen',
+  1125: 'Arnstadt',
+  1126: 'Leipzig',
+  1127: 'Weimar',
+  1128: 'Reutlingen',
+  1129: 'Kiel',
+  1130: 'Jena',
+  1131: 'Trier',
+  1132: 'Wittenberge',
+  1151: 'Elsterwerda',
+  1152: 'Travemünde',
+  1153: 'Ilmenau',
+  1154: 'Sonneberg',
+  1155: 'Mühlhausen/Thüringen',
+  1156: 'Waren (Müritz)',
+  1157: 'Innsbruck',
+  1158: 'Falkenberg/Elster',
+  1159: 'Passau',
+  1160: 'Markt Holzkirchen',
+  1161: 'Andernach',
+  1162: 'Vaihingen an der Enz',
+  1163: 'Ostseebad Binz',
+  1164: 'Rödental',
+  1165: 'Bad Oeynhausen',
+  1166: 'Bingen am Rhein',
+  1167: 'Traunstein',
+  1168: 'Ellwangen',
+  1169: 'Tutzing',
+  1170: 'Prenzlau',
+  1171: 'Oschatz',
+  1172: 'Bamberg',
+  1173: 'Halle (Saale)',
+  1174: 'Hansestadt Warburg',
+  1175: 'Villingen-Schwenningen',
+  1176: 'Coburg',
+  1177: 'Rathenow',
+  1178: 'Ostseebad Warnemünde',
+  1180: 'Darmstadt',
+  1181: 'Horb am Neckar',
+  1182: 'Mainz',
+  1183: 'Oberursel (Taunus)',
+  1184: 'Kaiserslautern',
+  1190: 'Wien',
+  1191: 'Salzburg',
+  1192: 'Linz',
+  //
+  // ICE T - BR 415
+  1501: 'Eisenach',
+  1502: 'Karlsruhe',
+  1503: 'Altenbeken',
+  1504: 'Heidelberg',
+  1505: 'Marburg/Lahn',
+  1506: 'Kassel',
+  1520: 'Gotha',
+  1521: 'Homburg/Saar',
+  1522: 'Torgau',
+  1523: 'Hansestadt Greifswald',
+  1524: 'Hansestadt Rostock',
+  //
+  // Intercity2
+  2853: 'Nationalpark Sächsische Schweiz',
+  2865: 'Remstal',
+  2868: 'Nationalpark Niedersächsisches Wattenmeer',
+  2871: 'Leipziger Neuseenland',
+  2874: 'Oberer Neckar',
+  2875: 'Magdeburger Börde',
+  //
+  // Intercity2 KISS - BR 4110
+  4111: 'Gäu',
+  4114: 'Dresden Elbland',
+  4117: 'Mecklenburgische Ostseeküste',
+  //
+  // ICE 3 - BR 406
+  4601: 'Europa/Europe',
+  4602: 'Euregio Maas-Rhein',
+  4603: 'Mannheim',
+  4604: 'Brussel/Bruxelles',
+  4607: 'Hannover',
+  4610: 'Frankfurt am Main',
+  4611: 'Düsseldorf',
+  4651: 'Amsterdam',
+  4652: 'Arnhem',
+  4680: 'Würzburg',
+  4682: 'Köln',
+  4683: 'Limburg an der Lahn',
+  4684: 'Forbach-Lorraine',
+  4685: 'Schwäbisch Hall',
+  //
+  // ICE 3 - BR 407
+  4712: 'Dillingen a.d. Donau',
+  4717: 'Paris',
+  //
+  // ICE 4 - BR 412
+  9006: 'Martin Luther',
+  9018: 'Freistaat Bayern',
+  9025: 'Nordrhein-Westfalen',
+  9026: 'Zürichsee',
+  9028: 'Freistaat Sachsen',
+  9041: 'Baden-Württemberg',
+  9046: 'Female ICE',
+  9050: 'Metropole Ruhr',
+  9202: 'Schleswig-Holstein',
+  9457: 'Bundesrepublik Deutschland'
+};
+export default (tzn => {
+  // @ts-expect-error ???
+  if (tzn) return naming[Number.parseInt(tzn, 10)];
+});+
\ No newline at end of file
diff --git a/src/reihung/baureihe.js b/src/reihung/baureihe.js
@@ -0,0 +1,218 @@
+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',
+  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 = '412';
+
+        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 = '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 '5402':
+    case '5805':
+    case '5806':
+    case '5807':
+    case '5808':
+      return {
+        baureihe: '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':
+      return {
+        baureihe: '406',
+        identifier: '406.R'
+      };
+
+    case '5407':
+      return {
+        baureihe: '407',
+        identifier: '407'
+      };
+
+    case '5410':
+      return {
+        baureihe: '410.1',
+        identifier: '410.1'
+      };
+
+    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.substr(2, 2);
+  const code = uic.substr(4, 4);
+  const serial = uic.substr(8, 3);
+  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]
+  };
+};
+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';
+      break;
+    }
+
+    if (c.type === 'DBpdzfa') {
+      identifier = 'IC2.KISS';
+      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 (identifier) {
+    return {
+      identifier,
+      name: nameMap[identifier]
+    };
+  }
+};
diff --git a/src/reihung/commonMapping.js b/src/reihung/commonMapping.js
@@ -0,0 +1,77 @@
+import { getBaureiheByCoaches, getBaureiheByUIC } from './baureihe';
+import { getSeatsForCoach } from './specialSeats';
+import TrainNames from './TrainNames';
+
+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') {
+        const wagen4 = group.coaches.find(c => c.identificationNumber === '6');
+
+        if (wagen4) {
+          wagen4.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
@@ -0,0 +1,28 @@
+import { DBCoachSequence } from './DB';
+
+export async function coachSequence(trainNumber, departure) {
+  return await DBCoachSequence(trainNumber, departure);
+}
+
+export const coachSequenceCache = {};
+
+export const coachSequenceCacheKey = (trainNumber, departure) => {
+	if (!trainNumber || !departure) return;
+	return `${trainNumber}-${departure.toISOString()}`;
+};
+
+export const cachedCoachSequence = (trainNumber, departure) => {
+	const key = coachSequenceCacheKey(trainNumber, departure);
+	if (!key) return;
+	if (coachSequenceCache[key] === undefined) {
+		coachSequenceCache[key] = (async () => {
+			try {
+				const info = await coachSequence(trainNumber, 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
@@ -0,0 +1,6 @@
+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/reihung/lines.json
@@ -0,0 +1,1095 @@
+{
+  "3": "12",
+  "4": "12",
+  "5": "12",
+  "6": "30",
+  "7": "30",
+  "8": "30",
+  "9": "30",
+  "10": "79",
+  "11": "79",
+  "12": "79",
+  "13": "79",
+  "14": "79",
+  "15": "79",
+  "16": "79",
+  "17": "79",
+  "18": "79",
+  "19": "79",
+  "20": "91",
+  "21": "91",
+  "22": "91",
+  "23": "91",
+  "26": "91",
+  "27": "91",
+  "28": "91",
+  "29": "91",
+  "40": "95",
+  "41": "95",
+  "44": "95",
+  "45": "95",
+  "48": "95",
+  "49": "95",
+  "52": "85",
+  "56": "95",
+  "57": "95",
+  "58": "95",
+  "59": "95",
+  "60": "90",
+  "61": "90",
+  "62": "90",
+  "63": "90",
+  "64": "90",
+  "65": "90",
+  "66": "90",
+  "67": "90",
+  "68": "90",
+  "69": "90",
+  "70": "20",
+  "71": "20",
+  "72": "20",
+  "73": "20",
+  "74": "20",
+  "75": "20",
+  "76": "20",
+  "77": "20",
+  "78": "20",
+  "79": "20",
+  "80": "89",
+  "81": "89",
+  "82": "89",
+  "83": "89",
+  "84": "89",
+  "85": "89",
+  "86": "89",
+  "87": "89",
+  "88": "89",
+  "89": "89",
+  "90": "91",
+  "91": "91",
+  "92": "91",
+  "93": "91",
+  "94": "17",
+  "95": "17",
+  "96": "88",
+  "97": "88",
+  "98": "88",
+  "99": "88",
+  "100": "43",
+  "101": "43",
+  "102": "43",
+  "103": "43",
+  "104": "43",
+  "105": "43",
+  "106": "43",
+  "107": "43",
+  "108": "43",
+  "109": "43",
+  "110": "62",
+  "111": "62",
+  "112": "62",
+  "113": "62",
+  "114": "32",
+  "115": "32",
+  "117": "62",
+  "118": "32",
+  "119": "32",
+  "120": "78",
+  "121": "78",
+  "122": "78",
+  "123": "78",
+  "124": "78",
+  "125": "78",
+  "126": "78",
+  "127": "78",
+  "128": "78",
+  "129": "78",
+  "140": "77",
+  "141": "77",
+  "142": "77",
+  "143": "77",
+  "144": "77",
+  "145": "77",
+  "146": "77",
+  "147": "77",
+  "148": "77",
+  "149": "77",
+  "151": "85",
+  "152": "78",
+  "153": "78",
+  "154": "78",
+  "155": "78",
+  "156": "78",
+  "157": "78",
+  "158": "78",
+  "159": "78",
+  "170": "27",
+  "171": "27",
+  "172": "27",
+  "173": "27",
+  "174": "27",
+  "175": "27",
+  "176": "27",
+  "177": "27",
+  "178": "27",
+  "179": "27",
+  "180": "87",
+  "181": "87",
+  "182": "87",
+  "183": "87",
+  "184": "87",
+  "185": "87",
+  "186": "87",
+  "187": "87",
+  "188": "87",
+  "189": "87",
+  "190": "88",
+  "191": "88",
+  "192": "88",
+  "193": "88",
+  "194": "88",
+  "195": "88",
+  "196": "88",
+  "197": "88",
+  "198": "88",
+  "199": "88",
+  "200": "43",
+  "201": "43",
+  "202": "43",
+  "203": "43",
+  "204": "43",
+  "205": "43",
+  "206": "43",
+  "212": "62",
+  "213": "62",
+  "216": "62",
+  "217": "62",
+  "218": "62",
+  "219": "62",
+  "220": "78",
+  "221": "78",
+  "222": "78",
+  "228": "91",
+  "229": "91",
+  "240": "77",
+  "241": "77",
+  "242": "77",
+  "243": "77",
+  "244": "77",
+  "245": "77",
+  "246": "95",
+  "247": "95",
+  "248": "95",
+  "249": "95",
+  "250": "78",
+  "251": "78",
+  "254": "43",
+  "255": "43",
+  "256": "27",
+  "257": "27",
+  "260": "90",
+  "261": "90",
+  "262": "90",
+  "264": "90",
+  "265": "90",
+  "266": "60",
+  "267": "60",
+  "270": "12",
+  "271": "12",
+  "272": "12",
+  "273": "20",
+  "274": "12",
+  "275": "12",
+  "276": "12",
+  "277": "12",
+  "278": "12",
+  "279": "12",
+  "280": "87",
+  "281": "87",
+  "282": "87",
+  "283": "87",
+  "284": "87",
+  "285": "87",
+  "286": "89",
+  "287": "89",
+  "288": "89",
+  "289": "89",
+  "292": "12",
+  "314": "79",
+  "315": "79",
+  "316": "79",
+  "317": "79",
+  "318": "79",
+  "319": "79",
+  "333": "12",
+  "336": "12",
+  "338": "12",
+  "370": "12",
+  "371": "12",
+  "372": "12",
+  "373": "12",
+  "374": "12",
+  "375": "12",
+  "376": "20",
+  "377": "12",
+  "378": "27",
+  "379": "27",
+  "380": "87",
+  "381": "87",
+  "383": "76",
+  "384": "76",
+  "385": "76",
+  "386": "76",
+  "388": "87",
+  "389": "87",
+  "390": "75",
+  "392": "75",
+  "393": "75",
+  "394": "75",
+  "395": "75",
+  "396": "75",
+  "397": "75",
+  "398": "75",
+  "399": "75",
+  "474": "20",
+  "476": "20",
+  "480": "87",
+  "481": "87",
+  "482": "87",
+  "483": "87",
+  "484": "87",
+  "485": "87",
+  "486": "87",
+  "487": "87",
+  "488": "87",
+  "489": "87",
+  "500": "28",
+  "501": "28",
+  "502": "28",
+  "503": "28",
+  "504": "28",
+  "505": "28",
+  "506": "28",
+  "507": "28",
+  "508": "28",
+  "509": "28",
+  "510": "42",
+  "511": "42",
+  "512": "42",
+  "513": "42",
+  "514": "42",
+  "515": "42",
+  "516": "42",
+  "517": "42",
+  "518": "42",
+  "519": "42",
+  "520": "41",
+  "521": "41",
+  "522": "41",
+  "523": "41",
+  "524": "41",
+  "525": "41",
+  "526": "41",
+  "527": "41",
+  "528": "41",
+  "529": "41",
+  "532": "25",
+  "533": "25",
+  "535": "25",
+  "536": "25",
+  "537": "25",
+  "538": "25",
+  "540": "10",
+  "541": "10",
+  "542": "10",
+  "543": "10",
+  "544": "10",
+  "545": "10",
+  "546": "10",
+  "547": "10",
+  "548": "10",
+  "549": "10",
+  "552": "10",
+  "553": "10",
+  "554": "10",
+  "555": "10",
+  "556": "10",
+  "557": "10",
+  "558": "10",
+  "559": "10",
+  "570": "22",
+  "571": "22",
+  "572": "22",
+  "573": "22",
+  "574": "22",
+  "575": "22",
+  "576": "22",
+  "577": "22",
+  "578": "22",
+  "579": "22",
+  "580": "25",
+  "581": "25",
+  "582": "25",
+  "583": "25",
+  "584": "25",
+  "585": "25",
+  "586": "25",
+  "587": "25",
+  "588": "25",
+  "590": "11",
+  "591": "11",
+  "592": "11",
+  "593": "11",
+  "594": "11",
+  "595": "11",
+  "596": "11",
+  "597": "11",
+  "598": "11",
+  "599": "11",
+  "602": "28",
+  "603": "28",
+  "608": "28",
+  "609": "28",
+  "610": "42",
+  "611": "42",
+  "612": "42",
+  "613": "42",
+  "614": "42",
+  "615": "42",
+  "616": "42",
+  "617": "42",
+  "618": "42",
+  "619": "42",
+  "620": "41",
+  "621": "41",
+  "622": "41",
+  "623": "41",
+  "624": "41",
+  "625": "41",
+  "626": "41",
+  "627": "41",
+  "628": "41",
+  "629": "41",
+  "630": "25",
+  "631": "25",
+  "632": "25",
+  "633": "25",
+  "634": "25",
+  "635": "26",
+  "636": "26",
+  "639": "25",
+  "640": "10",
+  "641": "10",
+  "642": "10",
+  "643": "10",
+  "644": "10",
+  "645": "10",
+  "646": "10",
+  "649": "10",
+  "650": "10",
+  "651": "10",
+  "652": "10",
+  "653": "10",
+  "654": "10",
+  "655": "10",
+  "656": "10",
+  "657": "10",
+  "660": "90",
+  "662": "90",
+  "672": "20",
+  "680": "25",
+  "681": "25",
+  "682": "25",
+  "683": "25",
+  "684": "25",
+  "685": "25",
+  "689": "25",
+  "690": "11",
+  "691": "11",
+  "692": "11",
+  "693": "11",
+  "694": "11",
+  "695": "11",
+  "696": "11",
+  "698": "11",
+  "699": "11",
+  "701": "18",
+  "702": "18",
+  "703": "18",
+  "704": "18",
+  "705": "18",
+  "706": "18",
+  "707": "18",
+  "708": "18",
+  "709": "18",
+  "710": "45",
+  "711": "45",
+  "712": "45",
+  "713": "30",
+  "714": "30",
+  "717": "32",
+  "718": "62",
+  "719": "62",
+  "720": "41",
+  "721": "41",
+  "722": "41",
+  "723": "41",
+  "724": "41",
+  "725": "41",
+  "726": "41",
+  "727": "41",
+  "728": "41",
+  "729": "41",
+  "730": "39",
+  "732": "39",
+  "735": "39",
+  "750": "14",
+  "753": "24",
+  "758": "14",
+  "759": "14",
+  "763": "90",
+  "769": "90",
+  "770": "22",
+  "771": "22",
+  "772": "22",
+  "773": "22",
+  "774": "22",
+  "775": "22",
+  "776": "22",
+  "780": "25",
+  "781": "25",
+  "782": "25",
+  "783": "25",
+  "784": "25",
+  "785": "25",
+  "786": "25",
+  "787": "25",
+  "788": "25",
+  "789": "25",
+  "791": "13",
+  "792": "13",
+  "793": "13",
+  "794": "13",
+  "795": "13",
+  "796": "13",
+  "797": "13",
+  "798": "13",
+  "800": "18",
+  "801": "18",
+  "802": "18",
+  "803": "18",
+  "804": "18",
+  "806": "18",
+  "808": "18",
+  "810": "49",
+  "811": "49",
+  "812": "49",
+  "813": "49",
+  "814": "49",
+  "815": "49",
+  "816": "49",
+  "817": "49",
+  "818": "49",
+  "819": "49",
+  "820": "41",
+  "821": "41",
+  "822": "41",
+  "823": "41",
+  "824": "41",
+  "825": "41",
+  "826": "41",
+  "827": "41",
+  "828": "49",
+  "830": "10",
+  "831": "15",
+  "832": "10",
+  "833": "15",
+  "835": "15",
+  "836": "15",
+  "838": "15",
+  "839": "15",
+  "840": "10",
+  "841": "10",
+  "842": "10",
+  "843": "10",
+  "844": "10",
+  "845": "10",
+  "846": "10",
+  "847": "10",
+  "848": "10",
+  "849": "10",
+  "852": "10",
+  "853": "10",
+  "854": "10",
+  "855": "10",
+  "856": "10",
+  "857": "10",
+  "858": "10",
+  "859": "10",
+  "860": "90",
+  "861": "90",
+  "862": "90",
+  "863": "90",
+  "864": "90",
+  "866": "90",
+  "867": "90",
+  "868": "90",
+  "869": "90",
+  "870": "27",
+  "871": "27",
+  "876": "12",
+  "877": "12",
+  "879": "12",
+  "880": "25",
+  "881": "25",
+  "882": "25",
+  "883": "25",
+  "885": "25",
+  "886": "25",
+  "887": "25",
+  "888": "25",
+  "890": "32",
+  "891": "62",
+  "895": "11",
+  "897": "32",
+  "902": "18",
+  "905": "18",
+  "907": "28",
+  "908": "29",
+  "911": "49",
+  "912": "47",
+  "913": "47",
+  "914": "47",
+  "915": "47",
+  "917": "47",
+  "918": "47",
+  "919": "30",
+  "920": "31",
+  "921": "31",
+  "922": "31",
+  "926": "41",
+  "927": "31",
+  "928": "31",
+  "929": "31",
+  "930": "15",
+  "932": "15",
+  "933": "15",
+  "934": "15",
+  "935": "15",
+  "936": "15",
+  "938": "15",
+  "939": "15",
+  "940": "10",
+  "941": "10",
+  "942": "10",
+  "943": "10",
+  "944": "10",
+  "945": "10",
+  "946": "10",
+  "947": "10",
+  "948": "10",
+  "949": "10",
+  "950": "10",
+  "951": "10",
+  "952": "10",
+  "953": "10",
+  "954": "10",
+  "955": "10",
+  "956": "10",
+  "957": "10",
+  "976": "26",
+  "981": "25",
+  "984": "25",
+  "985": "25",
+  "987": "28",
+  "989": "25",
+  "990": "11",
+  "991": "11",
+  "992": "13",
+  "993": "11",
+  "994": "62",
+  "995": "13",
+  "997": "13",
+  "998": "13",
+  "999": "62",
+  "1000": "29",
+  "1001": "29",
+  "1002": "29",
+  "1003": "29",
+  "1004": "29",
+  "1005": "29",
+  "1006": "29",
+  "1007": "29",
+  "1008": "29",
+  "1009": "29",
+  "1010": "47",
+  "1011": "47",
+  "1012": "47",
+  "1013": "47",
+  "1014": "47",
+  "1015": "47",
+  "1020": "31",
+  "1021": "31",
+  "1022": "31",
+  "1028": "31",
+  "1031": "39",
+  "1038": "39",
+  "1040": "14",
+  "1041": "10",
+  "1042": "14",
+  "1043": "14",
+  "1044": "14",
+  "1047": "14",
+  "1048": "14",
+  "1049": "14",
+  "1050": "14",
+  "1051": "14",
+  "1053": "14",
+  "1055": "14",
+  "1059": "14",
+  "1061": "60",
+  "1062": "60",
+  "1063": "60",
+  "1079": "20",
+  "1081": "87",
+  "1085": "24",
+  "1086": "87",
+  "1088": "25",
+  "1091": "13",
+  "1092": "29",
+  "1093": "29",
+  "1094": "04",
+  "1095": "13",
+  "1097": "04",
+  "1100": "29",
+  "1103": "35",
+  "1107": "35",
+  "1108": "35",
+  "1109": "29",
+  "1110": "32",
+  "1114": "32",
+  "1116": "32",
+  "1120": "02",
+  "1121": "02",
+  "1122": "02",
+  "1123": "02",
+  "1128": "41",
+  "1133": "56",
+  "1150": "14",
+  "1151": "14",
+  "1152": "14",
+  "1154": "09",
+  "1155": "09",
+  "1156": "09",
+  "1157": "09",
+  "1158": "09",
+  "1159": "09",
+  "1160": "60",
+  "1163": "60",
+  "1164": "60",
+  "1171": "20",
+  "1174": "24",
+  "1176": "24",
+  "1177": "26",
+  "1200": "89",
+  "1201": "89",
+  "1202": "89",
+  "1203": "89",
+  "1204": "24",
+  "1205": "29",
+  "1206": "24",
+  "1207": "24",
+  "1208": "24",
+  "1209": "24",
+  "1222": "31",
+  "1223": "41",
+  "1224": "41",
+  "1251": "20",
+  "1253": "20",
+  "1255": "20",
+  "1256": "20",
+  "1258": "20",
+  "1260": "20",
+  "1271": "12",
+  "1280": "89",
+  "1281": "89",
+  "1282": "24",
+  "1283": "24",
+  "1284": "24",
+  "1285": "24",
+  "1286": "89",
+  "1287": "89",
+  "1290": "62",
+  "1291": "62",
+  "1296": "62",
+  "1298": "60",
+  "1299": "60",
+  "1500": "18",
+  "1501": "28",
+  "1502": "28",
+  "1510": "32",
+  "1513": "32",
+  "1518": "30",
+  "1534": "15",
+  "1537": "15",
+  "1545": "14",
+  "1548": "14",
+  "1550": "50",
+  "1552": "50",
+  "1553": "50",
+  "1554": "50",
+  "1555": "50",
+  "1556": "50",
+  "1557": "50",
+  "1558": "50",
+  "1559": "50",
+  "1560": "60",
+  "1561": "60",
+  "1570": "26",
+  "1571": "26",
+  "1572": "26",
+  "1573": "26",
+  "1574": "26",
+  "1575": "26",
+  "1576": "26",
+  "1577": "26",
+  "1578": "26",
+  "1579": "26",
+  "1580": "24",
+  "1585": "24",
+  "1586": "24",
+  "1587": "24",
+  "1590": "13",
+  "1594": "24",
+  "1597": "24",
+  "1598": "13",
+  "1599": "26",
+  "1600": "28",
+  "1601": "28",
+  "1604": "28",
+  "1605": "28",
+  "1606": "28",
+  "1607": "28",
+  "1621": "31",
+  "1626": "31",
+  "1627": "31",
+  "1630": "15",
+  "1632": "10",
+  "1650": "50",
+  "1651": "50",
+  "1652": "50",
+  "1653": "50",
+  "1654": "50",
+  "1655": "50",
+  "1656": "50",
+  "1657": "50",
+  "1658": "50",
+  "1659": "50",
+  "1671": "26",
+  "1672": "26",
+  "1674": "26",
+  "1675": "26",
+  "1676": "26",
+  "1677": "26",
+  "1678": "26",
+  "1679": "26",
+  "1683": "24",
+  "1686": "24",
+  "1687": "24",
+  "1688": "24",
+  "1689": "24",
+  "1690": "13",
+  "1696": "17",
+  "1699": "13",
+  "1701": "18",
+  "1704": "28",
+  "1705": "28",
+  "1706": "29",
+  "1710": "28",
+  "1711": "28",
+  "1714": "28",
+  "1715": "28",
+  "1731": "15",
+  "1740": "27",
+  "1743": "27",
+  "1910": "32",
+  "1911": "32",
+  "1912": "32",
+  "1913": "32",
+  "1915": "32",
+  "1916": "32",
+  "1917": "32",
+  "1932": "14",
+  "1934": "56",
+  "1949": "55",
+  "1950": "51",
+  "1952": "51",
+  "1956": "51",
+  "1957": "51",
+  "1959": "51",
+  "1960": "60",
+  "1961": "60",
+  "1964": "60",
+  "1965": "60",
+  "1967": "60",
+  "1968": "60",
+  "1972": "24",
+  "1975": "17",
+  "1978": "17",
+  "1983": "24",
+  "1995": "62",
+  "1997": "62",
+  "1998": "62",
+  "1999": "26",
+  "2002": "35",
+  "2003": "35",
+  "2004": "35",
+  "2005": "35",
+  "2006": "35",
+  "2009": "35",
+  "2010": "32",
+  "2011": "32",
+  "2012": "32",
+  "2013": "32",
+  "2014": "35",
+  "2015": "32",
+  "2022": "31",
+  "2030": "56",
+  "2031": "56",
+  "2032": "56",
+  "2033": "56",
+  "2034": "56",
+  "2035": "56",
+  "2036": "56",
+  "2037": "56",
+  "2038": "56",
+  "2039": "56",
+  "2040": "55",
+  "2041": "55",
+  "2042": "55",
+  "2043": "55",
+  "2044": "55",
+  "2045": "55",
+  "2046": "55",
+  "2047": "55",
+  "2048": "55",
+  "2049": "55",
+  "2058": "62",
+  "2059": "62",
+  "2060": "61",
+  "2061": "61",
+  "2062": "61",
+  "2063": "61",
+  "2064": "61",
+  "2065": "61",
+  "2066": "61",
+  "2067": "61",
+  "2068": "61",
+  "2069": "61",
+  "2070": "27",
+  "2073": "27",
+  "2074": "27",
+  "2075": "27",
+  "2082": "24",
+  "2083": "24",
+  "2084": "24",
+  "2085": "24",
+  "2150": "51",
+  "2151": "51",
+  "2152": "51",
+  "2155": "51",
+  "2156": "51",
+  "2157": "51",
+  "2160": "61",
+  "2161": "61",
+  "2162": "61",
+  "2163": "61",
+  "2164": "61",
+  "2165": "61",
+  "2167": "61",
+  "2169": "60",
+  "2172": "17",
+  "2173": "17",
+  "2174": "17",
+  "2175": "17",
+  "2176": "17",
+  "2177": "17",
+  "2178": "17",
+  "2179": "17",
+  "2183": "26",
+  "2184": "26",
+  "2185": "26",
+  "2194": "24",
+  "2198": "62",
+  "2200": "35",
+  "2201": "35",
+  "2202": "35",
+  "2203": "35",
+  "2204": "35",
+  "2205": "35",
+  "2206": "35",
+  "2207": "35",
+  "2208": "35",
+  "2209": "35",
+  "2210": "30",
+  "2212": "30",
+  "2213": "30",
+  "2214": "30",
+  "2215": "30",
+  "2216": "30",
+  "2217": "30",
+  "2218": "30",
+  "2221": "34",
+  "2222": "34",
+  "2223": "34",
+  "2224": "34",
+  "2225": "34",
+  "2226": "34",
+  "2227": "34",
+  "2228": "34",
+  "2229": "34",
+  "2238": "56",
+  "2239": "56",
+  "2240": "77",
+  "2241": "77",
+  "2242": "77",
+  "2248": "55",
+  "2249": "55",
+  "2265": "60",
+  "2266": "60",
+  "2269": "60",
+  "2270": "17",
+  "2271": "17",
+  "2272": "17",
+  "2273": "17",
+  "2274": "17",
+  "2275": "17",
+  "2280": "87",
+  "2281": "87",
+  "2284": "87",
+  "2285": "87",
+  "2286": "87",
+  "2287": "87",
+  "2288": "87",
+  "2289": "87",
+  "2290": "62",
+  "2296": "62",
+  "2297": "62",
+  "2298": "62",
+  "2299": "62",
+  "2305": "35",
+  "2306": "35",
+  "2307": "30",
+  "2310": "30",
+  "2311": "30",
+  "2312": "30",
+  "2318": "30",
+  "2319": "30",
+  "2320": "34",
+  "2321": "34",
+  "2322": "34",
+  "2323": "34",
+  "2324": "34",
+  "2325": "34",
+  "2326": "34",
+  "2327": "34",
+  "2328": "34",
+  "2341": "55",
+  "2364": "26",
+  "2365": "26",
+  "2366": "60",
+  "2368": "60",
+  "2369": "60",
+  "2372": "26",
+  "2374": "26",
+  "2375": "26",
+  "2378": "26",
+  "2380": "87",
+  "2381": "87",
+  "2382": "87",
+  "2383": "87",
+  "2384": "87",
+  "2385": "87",
+  "2386": "87",
+  "2387": "87",
+  "2388": "87",
+  "2389": "87",
+  "2392": "62",
+  "2393": "62",
+  "2396": "62",
+  "2397": "62",
+  "2398": "62",
+  "2399": "62",
+  "2408": "39",
+  "2409": "39",
+  "2413": "39",
+  "2414": "39",
+  "2415": "39",
+  "2416": "39",
+  "2430": "56",
+  "2431": "56",
+  "2432": "56",
+  "2433": "56",
+  "2434": "56",
+  "2435": "56",
+  "2436": "56",
+  "2437": "56",
+  "2438": "56",
+  "2439": "56",
+  "2440": "55",
+  "2441": "55",
+  "2442": "55",
+  "2443": "55",
+  "2444": "55",
+  "2445": "55",
+  "2446": "55",
+  "2447": "55",
+  "2448": "55",
+  "2449": "55",
+  "5750": "76",
+  "5751": "76",
+  "5752": "76",
+  "5753": "76",
+  "5754": "76",
+  "5755": "76",
+  "5758": "76",
+  "5759": "76",
+  "5762": "76",
+  "5763": "76",
+  "5764": "76",
+  "5765": "76",
+  "5766": "76",
+  "5767": "76",
+  "9550": "82",
+  "9551": "82",
+  "9552": "82",
+  "9553": "82",
+  "9554": "82",
+  "9555": "82",
+  "9556": "82",
+  "9557": "82",
+  "9558": "82",
+  "9559": "82",
+  "9560": "82",
+  "9561": "82",
+  "9563": "82",
+  "9566": "82",
+  "9568": "82",
+  "9570": "83",
+  "9571": "83",
+  "9572": "83",
+  "9573": "83",
+  "9574": "83",
+  "9575": "83",
+  "9576": "83",
+  "9577": "83",
+  "9578": "83",
+  "9579": "83",
+  "9580": "84",
+  "9583": "84",
+  "9586": "82",
+  "9592": "83",
+  "9593": "83",
+  "90172": "27",
+  "91100": "15"
+}
diff --git a/src/reihung/notRedesigned.json b/src/reihung/notRedesigned.json
@@ -0,0 +1 @@
+["0301", "0317"]
diff --git a/src/reihung/specialSeats.js b/src/reihung/specialSeats.js
@@ -0,0 +1,175 @@
+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