support db regio-guide trip id/endpoint

This commit is contained in:
Traines 2025-01-14 19:48:14 +00:00
parent 715541f060
commit 2b55f7148f
16 changed files with 985 additions and 28 deletions

View file

@ -45,6 +45,7 @@ Notes:
* uses RIS trip IDs, does not expose them directly in the routing-search response
* loadFactor for some regional services, not for long distance services
* boards up to 12 hours
* routing-search returns polylines (!)
## Vendo/Movas Navigator API
https://app.vendo.noncd.db.de/mob/
@ -57,6 +58,7 @@ EPs:
* zuglauf
* zuglaeufe/ICE_947/halte/by-abfahrt/8000207_2024 (coach sequence)
* angebote/recon (tickets)
* trip/recon (polylines)
Notes:
* see [traffic dumps](dumps/)
@ -66,6 +68,7 @@ Notes:
* boards only 1 hour (or unknown param)
* does not contain machine-readable cancelled info in the boards (only "Halt entfällt" string), but contains relevant remarks
* loadFactor only on journeys (?)
* polylines only for zuglauf and trip/recon
## Vendo/Movas bahn.de API
https://int.bahn.de/web/api/

View file

@ -4,6 +4,7 @@
"locationsEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte",
"nearbyEndpoint": "https://int.bahn.de/web/api/reiseloesung/orte/nearby",
"tripEndpoint": "https://int.bahn.de/web/api/reiseloesung/fahrt",
"regioGuideTripEndpoint": "https://regio-guide.de/@prd/zupo-travel-information/api/public/ri/journey/",
"boardEndpoint": "https://regio-guide.de/@prd/zupo-travel-information/api/public/ri/board/",
"defaultLanguage": "en"
}

View file

@ -4,6 +4,7 @@ const require = createRequire(import.meta.url);
const baseProfile = require('./base.json');
import {products} from '../../lib/products.js';
import {formatJourneysReq, formatRefreshJourneyReq} from './journeys-req.js';
import {formatTripReq} from './trip-req.js';
import {formatLocationFilter} from './location-filter.js';
import {formatLocationsReq} from './locations-req.js';
@ -15,6 +16,7 @@ const profile = {
products,
formatJourneysReq,
formatRefreshJourneyReq,
formatTripReq,
formatLocationsReq,
formatLocationFilter,
};

17
p/db/trip-req.js Normal file
View file

@ -0,0 +1,17 @@
import {formatTripReq as hafasFormatTripReq} from '../../format/trip-req.js';
const formatTripReq = ({profile, opt}, id) => {
if (id.includes('#')) {
return hafasFormatTripReq({profile, opt}, id);
}
return {
endpoint: profile.regioGuideTripEndpoint,
path: id,
method: 'get',
};
};
export {
formatTripReq,
};

View file

@ -15,12 +15,17 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
const stops = pt.halte?.length && pt.halte || pt.stops?.length && pt.stops || [];
const res = {
origin: stops.length && profile.parseLocation(ctx, stops[0].ort || stops[0].station || stops[0]) || pt.abgangsOrt?.name && profile.parseLocation(ctx, pt.abgangsOrt) || locationFallback(pt.abfahrtsOrtExtId, pt.abfahrtsOrt, fallbackLocations),
destination: stops.length && profile.parseLocation(ctx, stops[stops.length - 1].ort || stops[stops.length - 1].station || stops[stops.length - 1]) || pt.ankunftsOrt?.name && profile.parseLocation(ctx, pt.ankunftsOrt) || locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt, fallbackLocations),
origin: stops.length && profile.parseLocation(ctx, stops[0].ort || stops[0].station || stops[0])
|| pt.abgangsOrt?.name && profile.parseLocation(ctx, pt.abgangsOrt)
|| locationFallback(pt.abfahrtsOrtExtId, pt.abfahrtsOrt, fallbackLocations),
destination: stops.length && profile.parseLocation(ctx, stops[stops.length - 1].ort || stops[stops.length - 1].station || stops[stops.length - 1])
|| pt.ankunftsOrt?.name && profile.parseLocation(ctx, pt.ankunftsOrt)
|| locationFallback(pt.ankunftsOrtExtId, pt.ankunftsOrt, fallbackLocations),
};
const cancelledDep = stops.length && profile.parseCancelled(stops[0]);
const dep = profile.parseWhen(ctx, date, pt.abfahrtsZeitpunkt || pt.abgangsDatum || stops.length && stops[0].abgangsDatum, pt.ezAbfahrtsZeitpunkt || pt.ezAbgangsDatum || stops.length && stops[0].ezAbgangsDatum, cancelledDep);
const dep = profile.parseWhen(ctx, date, pt.abfahrtsZeitpunkt || pt.abgangsDatum || stops.length && (stops[0].abgangsDatum || stops[0].departureTime?.target), pt.ezAbfahrtsZeitpunkt || pt.ezAbgangsDatum || stops.length && (stops[0].ezAbgangsDatum || stops[0].departureTime?.timeType != 'SCHEDULE' && stops[0].departureTime?.predicted), cancelledDep,
);
res.departure = dep.when;
res.plannedDeparture = dep.plannedWhen;
res.departureDelay = dep.delay;
@ -29,7 +34,8 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
}
const cancelledArr = stops.length && profile.parseCancelled(stops[stops.length - 1]);
const arr = profile.parseWhen(ctx, date, pt.ankunftsZeitpunkt || pt.ankunftsDatum || stops.length && stops[stops.length - 1].ankunftsDatum, pt.ezAnkunftsZeitpunkt || pt.ezAnkunftsDatum || stops.length && stops[stops.length - 1].ezAnkunftsDatum, cancelledArr);
const arr = profile.parseWhen(ctx, date, pt.ankunftsZeitpunkt || pt.ankunftsDatum || stops.length && (stops[stops.length - 1].ankunftsDatum || stops[stops.length - 1].arrivalTime?.target), pt.ezAnkunftsZeitpunkt || pt.ezAnkunftsDatum || stops.length && (stops[stops.length - 1].ezAnkunftsDatum || stops[stops.length - 1].arrivalTime?.timeType != 'SCHEDULE' && stops[stops.length - 1].arrivalTime?.predicted), cancelledArr,
);
res.arrival = arr.when;
res.plannedArrival = arr.plannedWhen;
res.arrivalDelay = arr.delay;
@ -69,9 +75,13 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
// TODO res.currentLocation
// TODO trainStartDate?
if (stops.length) {
const arrPl = profile.parsePlatform(ctx, stops[stops.length - 1].gleis, stops[stops.length - 1].ezGleis, cancelledArr);
const arrPl = profile.parsePlatform(ctx,
stops[stops.length - 1].gleis || stops[stops.length - 1].track?.target,
stops[stops.length - 1].ezGleis || stops[stops.length - 1].track?.prediction,
cancelledArr,
);
res.arrivalPlatform = arrPl.platform;
res.plannedArrivalPlatform = arrPl.plannedPlatform;
if (arrPl.prognosedPlatform) {
@ -79,7 +89,11 @@ const parseJourneyLeg = (ctx, pt, date, fallbackLocations) => { // pt = raw leg
}
// res.arrivalPrognosisType = null; // TODO
const depPl = profile.parsePlatform(ctx, stops[0].gleis, stops[0].ezGleis, cancelledDep);
const depPl = profile.parsePlatform(ctx,
stops[0].gleis || stops[0].track?.target,
stops[0].ezGleis || stops[0].track?.prediction,
cancelledDep,
);
res.departurePlatform = depPl.platform;
res.plannedDeparturePlatform = depPl.plannedPlatform;
if (depPl.prognosedPlatform) {

View file

@ -2,12 +2,12 @@ import slugg from 'slugg';
const parseLine = (ctx, p) => {
const profile = ctx.profile;
const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no || ((p.risZuglaufId || '') + '_').split('_')[1] || p.verkehrsmittelNummer || ((p.mitteltext || '') + ' ').split(' ')[1] || ((p.zugName || '') + ' ').split(' ')[1];
const fahrtNr = p.verkehrsmittel?.nummer || p.transport?.number || p.train?.no || p.no || ((p.risZuglaufId || '') + '_').split('_')[1] || p.verkehrsmittelNummer || ((p.mitteltext || '') + ' ').split(' ')[1] || ((p.zugName || '') + ' ').split(' ')[1];
const res = {
type: 'line',
id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription || p.risZuglaufId || p.train && p.train.category + ' ' + p.train.lineName + ' ' + p.train.no || p.langtext || p.mitteltext || p.zugName), // TODO terrible
id: slugg(p.verkehrsmittel?.langText || p.transport?.journeyDescription || p.risZuglaufId || p.train && p.train.category + ' ' + p.train.lineName + ' ' + p.train.no || p.no && p.name + ' ' + p.no || p.langtext || p.mitteltext || p.zugName), // TODO terrible
fahrtNr: String(fahrtNr),
name: p.verkehrsmittel?.langText || p.verkehrsmittel?.name || p.zugName || p.transport && p.transport.category + ' ' + p.transport.line || p.train && p.train.category + ' ' + p.train.lineName || p.mitteltext || p.langtext,
name: p.verkehrsmittel?.langText || p.verkehrsmittel?.name || p.zugName || p.transport && p.transport.category + ' ' + p.transport.line || p.train && p.train.category + ' ' + p.train.lineName || p.name || p.mitteltext || p.langtext,
public: true,
};
@ -15,12 +15,12 @@ const parseLine = (ctx, p) => {
if (adminCode) {
res.adminCode = adminCode;
}
res.productName = p.verkehrsmittel?.kurzText || p.transport?.category || p.train?.category || p.kurztext;
const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.ris == p.transport?.type || pp.ris == p.train?.type || pp.ris_alt == p.train?.type || pp.dbnav_short == p.produktGattung);
res.productName = p.verkehrsmittel?.kurzText || p.transport?.category || p.train?.category || p.category || p.kurztext;
const foundProduct = profile.products.find(pp => pp.vendo == p.verkehrsmittel?.produktGattung || pp.ris == p.transport?.type || pp.ris == p.train?.type || pp.ris == p.type || pp.ris_alt == p.train?.type || pp.ris_alt == p.type || pp.dbnav_short == p.produktGattung);
res.mode = foundProduct?.mode;
res.product = foundProduct?.id;
res.operator = profile.parseOperator(ctx, p.verkehrsmittel?.zugattribute || p.zugattribute || p.attributNotizen || p.administration);
res.operator = profile.parseOperator(ctx, p.verkehrsmittel?.zugattribute || p.zugattribute || p.attributNotizen || p.administration || p);
return res;
};

View file

@ -1,16 +1,16 @@
import slugg from 'slugg';
const parseOperator = (ctx, zugattrib) => {
if (!zugattrib) {
return null;
}
if (zugattrib.operatorName) {
if (zugattrib?.operatorName) {
return {
type: 'operator',
id: zugattrib.operatorCode,
name: zugattrib.operatorName,
};
}
if (!zugattrib || !Array.isArray(zugattrib)) {
return null;
}
const bef = zugattrib.find(z => z.key == 'BEF' || z.key == 'OP');
if (!bef) {
return null;

View file

@ -10,6 +10,7 @@ const parseRemarks = (ctx, ref) => {
}) || [],
ref.himMeldungen || [],
ref.himNotizen || [],
ref.hims || [],
ref.serviceNotiz && [ref.serviceNotiz] || [],
ref.messages || [],
ref.meldungenAsObject || [],
@ -33,11 +34,11 @@ const parseRemarks = (ctx, ref) => {
type = 'warning';
}
let res = {
code: remark.code || remark.key,
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text
code: remark.code || remark.key || remark.id,
summary: remark.nachrichtKurz || remark.value || remark.ueberschrift || remark.text || remark.shortText
|| Object.values(remark.descriptions || {})
.shift()?.textShort,
text: remark.nachrichtLang || remark.value || remark.text
text: remark.nachrichtLang || remark.value || remark.text || remark.caption
|| Object.values(remark.descriptions || {})
.shift()?.text,
type: type,
@ -201,7 +202,7 @@ const parseRemarks = (ctx, ref) => {
*/
const parseCancelled = (ref) => {
return ref.canceled || ref.cancelled || (ref.risNotizen || ref.echtzeitNotizen) && Boolean((ref.risNotizen || ref.echtzeitNotizen).find(r => r.key == 'text.realtime.stop.cancelled'
return ref.canceled || ref.cancelled || ref.journeyCancelled || (ref.risNotizen || ref.echtzeitNotizen) && Boolean((ref.risNotizen || ref.echtzeitNotizen).find(r => r.key == 'text.realtime.stop.cancelled'
|| r.type == 'HALT_AUSFALL'
|| r.text == 'Halt entfällt'
|| r.text == 'Stop cancelled',

View file

@ -2,13 +2,15 @@ const parseStopover = (ctx, st, date) => { // st = raw stopover
const {profile, opt} = ctx;
const cancelled = profile.parseCancelled(st);
const arr = profile.parseWhen(ctx, date, st.ankunftsZeitpunkt || st.ankunftsDatum, st.ezAnkunftsZeitpunkt || st.ezAnkunftsDatum, cancelled);
const arrPl = profile.parsePlatform(ctx, st.gleis, st.ezGleis);
const dep = profile.parseWhen(ctx, date, st.abfahrtsZeitpunkt || st.abgangsDatum, st.ezAbfahrtsZeitpunkt || st.ezAbgangsDatum, cancelled);
const depPl = profile.parsePlatform(ctx, st.gleis, st.ezGleis);
const arr = profile.parseWhen(ctx, date, st.ankunftsZeitpunkt || st.ankunftsDatum || st.arrivalTime?.target, st.ezAnkunftsZeitpunkt || st.ezAnkunftsDatum || st.arrivalTime?.timeType != 'SCHEDULE' && st.arrivalTime?.predicted, cancelled,
);
const arrPl = profile.parsePlatform(ctx, st.gleis || st.track?.target, st.ezGleis || st.track?.prediction);
const dep = profile.parseWhen(ctx, date, st.abfahrtsZeitpunkt || st.abgangsDatum || st.departureTime?.target, st.ezAbfahrtsZeitpunkt || st.ezAbgangsDatum || st.departureTime?.timeType != 'SCHEDULE' && st.departureTime?.predicted, cancelled,
);
const depPl = arrPl;
const res = {
stop: profile.parseLocation(ctx, st.ort || st) || null,
stop: profile.parseLocation(ctx, st.ort || st.station || st) || null,
arrival: arr.when,
plannedArrival: arr.plannedWhen,
arrivalDelay: arr.delay,

View file

@ -3,10 +3,10 @@ const parseTrip = (ctx, t, id) => { // t = raw trip
// pretend the trip is a leg in a journey
const trip = profile.parseJourneyLeg(ctx, t);
trip.id = trip.tripId || id; // TODO journeyId
trip.id = trip.tripId || id;
delete trip.tripId;
delete trip.reachable;
trip.cancelled = profile.parseCancelled(t);
trip.cancelled = Boolean(profile.parseCancelled(t));
// TODO opt.scheduledDays
return trip;

View file

@ -0,0 +1,28 @@
// todo: use import assertions once they're supported by Node.js & ESLint
// https://github.com/tc39/proposal-import-assertions
import {createRequire} from 'module';
const require = createRequire(import.meta.url);
import tap from 'tap';
import {createClient} from '../index.js';
import {profile as rawProfile} from '../p/db/index.js';
const res = require('./fixtures/db-trip-regio-guide.json');
import {dbTrip as expected} from './fixtures/db-trip-regio-guide.js';
const client = createClient(rawProfile, 'public-transport/hafas-client:test', {enrichStations: false});
const {profile} = client;
const opt = {
stopovers: true,
remarks: true,
products: {},
};
tap.test('parses a regio guide trip correctly (DB)', (t) => {
const ctx = {profile, opt, common: null, res};
const trip = profile.parseTrip(ctx, res, 'foo');
t.same(trip, expected.trip);
t.end();
});

376
test/fixtures/db-trip-regio-guide.js vendored Normal file
View file

@ -0,0 +1,376 @@
const dbTrip = {
trip: {
id: '20250117-c85e57a7-7ac5-3736-9f8f-b37a1f660e4c',
origin: {
type: 'station',
id: '8004168',
name: 'München Flughafen Terminal',
location: {
type: 'location',
id: '8004168',
latitude: 48.353728,
longitude: 11.78597,
},
},
destination: {
type: 'station',
id: '8000309',
name: 'Regensburg Hbf',
location: {
type: 'location',
id: '8000309',
latitude: 49.011672,
longitude: 12.099617,
},
},
departure: '2025-01-17T15:16:00+01:00',
plannedDeparture: '2025-01-17T15:16:00+01:00',
departureDelay: null,
arrival: '2025-01-17T16:41:00+01:00',
plannedArrival: '2025-01-17T16:41:00+01:00',
arrivalDelay: null,
line: {
type: 'line',
id: 'ag-re22-84100',
fahrtNr: '84100',
name: 'ag RE22',
adminCode: 'S9',
productName: 'ag',
product: 'regional',
mode: 'train',
public: true,
operator: {
type: 'operator',
id: 'ag',
name: 'agilis',
},
},
direction: null,
arrivalPlatform: '5',
plannedArrivalPlatform: '5',
departurePlatform: '1',
plannedDeparturePlatform: '1',
stopovers: [
{
stop: {
type: 'station',
id: '8004168',
name: 'München Flughafen Terminal',
location: {
type: 'location',
id: '8004168',
latitude: 48.353728,
longitude: 11.78597,
},
},
arrival: null,
plannedArrival: null,
arrivalDelay: null,
arrivalPlatform: '1',
arrivalPrognosisType: null,
plannedArrivalPlatform: '1',
departure: '2025-01-17T15:16:00+01:00',
plannedDeparture: '2025-01-17T15:16:00+01:00',
departureDelay: null,
departurePlatform: '1',
departurePrognosisType: null,
plannedDeparturePlatform: '1',
remarks: [],
},
{
stop: {
type: 'station',
id: '8004167',
name: 'München Flughafen Besucherpark',
location: {
type: 'location',
id: '8004167',
latitude: 48.352095,
longitude: 11.764185,
},
},
arrival: '2025-01-17T15:18:00+01:00',
plannedArrival: '2025-01-17T15:18:00+01:00',
arrivalDelay: null,
arrivalPlatform: '1',
arrivalPrognosisType: null,
plannedArrivalPlatform: '1',
departure: '2025-01-17T15:18:00+01:00',
plannedDeparture: '2025-01-17T15:18:00+01:00',
departureDelay: null,
departurePlatform: '1',
departurePrognosisType: null,
plannedDeparturePlatform: '1',
remarks: [],
},
{
stop: {
type: 'station',
id: '8002078',
name: 'Freising',
location: {
type: 'location',
id: '8002078',
latitude: 48.395195,
longitude: 11.744539,
},
},
arrival: '2025-01-17T15:28:00+01:00',
plannedArrival: '2025-01-17T15:28:00+01:00',
arrivalDelay: null,
arrivalPlatform: '4',
arrivalPrognosisType: null,
plannedArrivalPlatform: '4',
departure: '2025-01-17T15:29:00+01:00',
plannedDeparture: '2025-01-17T15:29:00+01:00',
departureDelay: null,
departurePlatform: '4',
departurePrognosisType: null,
plannedDeparturePlatform: '4',
remarks: [],
},
{
stop: {
type: 'station',
id: '8004084',
name: 'Moosburg',
location: {
type: 'location',
id: '8004084',
latitude: 48.47033,
longitude: 11.930382,
},
},
arrival: '2025-01-17T15:37:00+01:00',
plannedArrival: '2025-01-17T15:37:00+01:00',
arrivalDelay: null,
arrivalPlatform: '1',
arrivalPrognosisType: null,
plannedArrivalPlatform: '1',
departure: '2025-01-17T15:38:00+01:00',
plannedDeparture: '2025-01-17T15:38:00+01:00',
departureDelay: null,
departurePlatform: '1',
departurePrognosisType: null,
plannedDeparturePlatform: '1',
remarks: [],
},
{
stop: {
type: 'station',
id: '8000217',
name: 'Landshut(Bay)Hbf',
location: {
type: 'location',
id: '8000217',
latitude: 48.547492,
longitude: 12.13593,
},
},
arrival: '2025-01-17T15:50:00+01:00',
plannedArrival: '2025-01-17T15:50:00+01:00',
arrivalDelay: null,
arrivalPlatform: '5',
arrivalPrognosisType: null,
plannedArrivalPlatform: '5',
departure: '2025-01-17T15:53:00+01:00',
plannedDeparture: '2025-01-17T15:53:00+01:00',
departureDelay: null,
departurePlatform: '5',
departurePrognosisType: null,
plannedDeparturePlatform: '5',
remarks: [],
},
{
stop: {
type: 'station',
id: '8001835',
name: 'Ergoldsbach',
location: {
type: 'location',
id: '8001835',
latitude: 48.693868,
longitude: 12.201874,
},
},
arrival: '2025-01-17T16:05:00+01:00',
plannedArrival: '2025-01-17T16:05:00+01:00',
arrivalDelay: null,
arrivalPlatform: '1',
arrivalPrognosisType: null,
plannedArrivalPlatform: '1',
departure: '2025-01-17T16:06:00+01:00',
plannedDeparture: '2025-01-17T16:06:00+01:00',
departureDelay: null,
departurePlatform: '1',
departurePrognosisType: null,
plannedDeparturePlatform: '1',
remarks: [],
},
{
stop: {
type: 'station',
id: '8000688',
name: 'Neufahrn(Niederbay)',
location: {
type: 'location',
id: '8000688',
latitude: 48.729884,
longitude: 12.19046,
},
},
arrival: '2025-01-17T16:09:00+01:00',
plannedArrival: '2025-01-17T16:09:00+01:00',
arrivalDelay: null,
arrivalPlatform: '2',
arrivalPrognosisType: null,
plannedArrivalPlatform: '2',
departure: '2025-01-17T16:10:00+01:00',
plannedDeparture: '2025-01-17T16:10:00+01:00',
departureDelay: null,
departurePlatform: '2',
departurePrognosisType: null,
plannedDeparturePlatform: '2',
remarks: [],
},
{
stop: {
type: 'station',
id: '8001679',
name: 'Eggmühl',
location: {
type: 'location',
id: '8001679',
latitude: 48.836497,
longitude: 12.182192,
},
},
arrival: '2025-01-17T16:19:00+01:00',
plannedArrival: '2025-01-17T16:19:00+01:00',
arrivalDelay: null,
arrivalPlatform: '3',
arrivalPrognosisType: null,
plannedArrivalPlatform: '3',
departure: '2025-01-17T16:20:00+01:00',
plannedDeparture: '2025-01-17T16:20:00+01:00',
departureDelay: null,
departurePlatform: '3',
departurePrognosisType: null,
plannedDeparturePlatform: '3',
remarks: [],
},
{
stop: {
type: 'station',
id: '8002506',
name: 'Hagelstadt',
location: {
type: 'location',
id: '8002506',
latitude: 48.895859,
longitude: 12.214829,
},
},
arrival: '2025-01-17T16:25:00+01:00',
plannedArrival: '2025-01-17T16:25:00+01:00',
arrivalDelay: null,
arrivalPlatform: '2',
arrivalPrognosisType: null,
plannedArrivalPlatform: '2',
departure: '2025-01-17T16:26:00+01:00',
plannedDeparture: '2025-01-17T16:26:00+01:00',
departureDelay: null,
departurePlatform: '2',
departurePrognosisType: null,
plannedDeparturePlatform: '2',
remarks: [],
},
{
stop: {
type: 'station',
id: '8003357',
name: 'Köfering',
location: {
type: 'location',
id: '8003357',
latitude: 48.931716,
longitude: 12.20875,
},
},
arrival: '2025-01-17T16:29:00+01:00',
plannedArrival: '2025-01-17T16:29:00+01:00',
arrivalDelay: null,
arrivalPlatform: '2',
arrivalPrognosisType: null,
plannedArrivalPlatform: '2',
departure: '2025-01-17T16:30:00+01:00',
plannedDeparture: '2025-01-17T16:30:00+01:00',
departureDelay: null,
departurePlatform: '2',
departurePrognosisType: null,
plannedDeparturePlatform: '2',
remarks: [],
},
{
stop: {
type: 'station',
id: '8004592',
name: 'Obertraubling',
location: {
type: 'location',
id: '8004592',
latitude: 48.967537,
longitude: 12.169996,
},
},
arrival: '2025-01-17T16:33:00+01:00',
plannedArrival: '2025-01-17T16:33:00+01:00',
arrivalDelay: null,
arrivalPlatform: '2',
arrivalPrognosisType: null,
plannedArrivalPlatform: '2',
departure: '2025-01-17T16:34:00+01:00',
plannedDeparture: '2025-01-17T16:34:00+01:00',
departureDelay: null,
departurePlatform: '2',
departurePrognosisType: null,
plannedDeparturePlatform: '2',
remarks: [],
},
{
stop: {
type: 'station',
id: '8000309',
name: 'Regensburg Hbf',
location: {
type: 'location',
id: '8000309',
latitude: 49.011672,
longitude: 12.099617,
},
},
arrival: '2025-01-17T16:41:00+01:00',
plannedArrival: '2025-01-17T16:41:00+01:00',
arrivalDelay: null,
arrivalPlatform: '5',
arrivalPrognosisType: null,
plannedArrivalPlatform: '5',
departure: null,
plannedDeparture: null,
departureDelay: null,
departurePlatform: '5',
departurePrognosisType: null,
plannedDeparturePlatform: '5',
remarks: [],
},
// train split
],
remarks: [],
cancelled: false,
},
realtimeDataUpdatedAt: null,
};
export {
dbTrip,
};

410
test/fixtures/db-trip-regio-guide.json vendored Normal file
View file

@ -0,0 +1,410 @@
{
"name": "ag RE22",
"no": 84100,
"journeyId": "20250117-c85e57a7-7ac5-3736-9f8f-b37a1f660e4c",
"tenantId": "bayern",
"administrationId": "S9",
"operatorName": "agilis",
"operatorCode": "ag",
"category": "ag",
"type": "REGIONAL_TRAIN",
"date": "2025-01-17T15:16:00+01:00",
"stops": [
{
"status": "Normal",
"departureId": "8004168_D_1",
"station": {
"evaNo": "8004168",
"name": "München Flughafen Terminal",
"position": {
"latitude": 48.353728,
"longitude": 11.78597
}
},
"track": {
"target": "1",
"prediction": "1"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T15:16:00+01:00",
"predicted": "2025-01-17T15:16:00+01:00",
"diff": 0,
"targetTimeInMs": 1737123360000,
"predictedTimeInMs": 1737123360000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8004167_A_1",
"departureId": "8004167_D_1",
"station": {
"evaNo": "8004167",
"name": "München Flughafen Besucherpark",
"position": {
"latitude": 48.352095,
"longitude": 11.764185
}
},
"track": {
"target": "1",
"prediction": "1"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T15:18:00+01:00",
"predicted": "2025-01-17T15:18:00+01:00",
"diff": 0,
"targetTimeInMs": 1737123480000,
"predictedTimeInMs": 1737123480000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T15:18:00+01:00",
"predicted": "2025-01-17T15:18:00+01:00",
"diff": 0,
"targetTimeInMs": 1737123480000,
"predictedTimeInMs": 1737123480000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8002078_A_1",
"departureId": "8002078_D_1",
"station": {
"evaNo": "8002078",
"name": "Freising",
"position": {
"latitude": 48.395195,
"longitude": 11.744539
}
},
"track": {
"target": "4",
"prediction": "4"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T15:29:00+01:00",
"predicted": "2025-01-17T15:29:00+01:00",
"diff": 0,
"targetTimeInMs": 1737124140000,
"predictedTimeInMs": 1737124140000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T15:28:00+01:00",
"predicted": "2025-01-17T15:28:00+01:00",
"diff": 0,
"targetTimeInMs": 1737124080000,
"predictedTimeInMs": 1737124080000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8004084_A_1",
"departureId": "8004084_D_1",
"station": {
"evaNo": "8004084",
"name": "Moosburg",
"position": {
"latitude": 48.47033,
"longitude": 11.930382
}
},
"track": {
"target": "1",
"prediction": "1"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T15:38:00+01:00",
"predicted": "2025-01-17T15:38:00+01:00",
"diff": 0,
"targetTimeInMs": 1737124680000,
"predictedTimeInMs": 1737124680000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T15:37:00+01:00",
"predicted": "2025-01-17T15:37:00+01:00",
"diff": 0,
"targetTimeInMs": 1737124620000,
"predictedTimeInMs": 1737124620000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8000217_A_1",
"departureId": "8000217_D_1",
"station": {
"evaNo": "8000217",
"name": "Landshut(Bay)Hbf",
"position": {
"latitude": 48.547492,
"longitude": 12.13593
}
},
"track": {
"target": "5",
"prediction": "5"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T15:53:00+01:00",
"predicted": "2025-01-17T15:53:00+01:00",
"diff": 0,
"targetTimeInMs": 1737125580000,
"predictedTimeInMs": 1737125580000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T15:50:00+01:00",
"predicted": "2025-01-17T15:50:00+01:00",
"diff": 0,
"targetTimeInMs": 1737125400000,
"predictedTimeInMs": 1737125400000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8001835_A_1",
"departureId": "8001835_D_1",
"station": {
"evaNo": "8001835",
"name": "Ergoldsbach",
"position": {
"latitude": 48.693868,
"longitude": 12.201874
}
},
"track": {
"target": "1",
"prediction": "1"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T16:06:00+01:00",
"predicted": "2025-01-17T16:06:00+01:00",
"diff": 0,
"targetTimeInMs": 1737126360000,
"predictedTimeInMs": 1737126360000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T16:05:00+01:00",
"predicted": "2025-01-17T16:05:00+01:00",
"diff": 0,
"targetTimeInMs": 1737126300000,
"predictedTimeInMs": 1737126300000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8000688_A_1",
"departureId": "8000688_D_1",
"station": {
"evaNo": "8000688",
"name": "Neufahrn(Niederbay)",
"position": {
"latitude": 48.729884,
"longitude": 12.19046
}
},
"track": {
"target": "2",
"prediction": "2"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T16:10:00+01:00",
"predicted": "2025-01-17T16:10:00+01:00",
"diff": 0,
"targetTimeInMs": 1737126600000,
"predictedTimeInMs": 1737126600000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T16:09:00+01:00",
"predicted": "2025-01-17T16:09:00+01:00",
"diff": 0,
"targetTimeInMs": 1737126540000,
"predictedTimeInMs": 1737126540000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8001679_A_1",
"departureId": "8001679_D_1",
"station": {
"evaNo": "8001679",
"name": "Eggmühl",
"position": {
"latitude": 48.836497,
"longitude": 12.182192
}
},
"track": {
"target": "3",
"prediction": "3"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T16:20:00+01:00",
"predicted": "2025-01-17T16:20:00+01:00",
"diff": 0,
"targetTimeInMs": 1737127200000,
"predictedTimeInMs": 1737127200000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T16:19:00+01:00",
"predicted": "2025-01-17T16:19:00+01:00",
"diff": 0,
"targetTimeInMs": 1737127140000,
"predictedTimeInMs": 1737127140000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8002506_A_1",
"departureId": "8002506_D_1",
"station": {
"evaNo": "8002506",
"name": "Hagelstadt",
"position": {
"latitude": 48.895859,
"longitude": 12.214829
}
},
"track": {
"target": "2",
"prediction": "2"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T16:26:00+01:00",
"predicted": "2025-01-17T16:26:00+01:00",
"diff": 0,
"targetTimeInMs": 1737127560000,
"predictedTimeInMs": 1737127560000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T16:25:00+01:00",
"predicted": "2025-01-17T16:25:00+01:00",
"diff": 0,
"targetTimeInMs": 1737127500000,
"predictedTimeInMs": 1737127500000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8003357_A_1",
"departureId": "8003357_D_1",
"station": {
"evaNo": "8003357",
"name": "Köfering",
"position": {
"latitude": 48.931716,
"longitude": 12.20875
}
},
"track": {
"target": "2",
"prediction": "2"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T16:30:00+01:00",
"predicted": "2025-01-17T16:30:00+01:00",
"diff": 0,
"targetTimeInMs": 1737127800000,
"predictedTimeInMs": 1737127800000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T16:29:00+01:00",
"predicted": "2025-01-17T16:29:00+01:00",
"diff": 0,
"targetTimeInMs": 1737127740000,
"predictedTimeInMs": 1737127740000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8004592_A_1",
"departureId": "8004592_D_1",
"station": {
"evaNo": "8004592",
"name": "Obertraubling",
"position": {
"latitude": 48.967537,
"longitude": 12.169996
}
},
"track": {
"target": "2",
"prediction": "2"
},
"messages": [],
"departureTime": {
"target": "2025-01-17T16:34:00+01:00",
"predicted": "2025-01-17T16:34:00+01:00",
"diff": 0,
"targetTimeInMs": 1737128040000,
"predictedTimeInMs": 1737128040000,
"timeType": "SCHEDULE"
},
"arrivalTime": {
"target": "2025-01-17T16:33:00+01:00",
"predicted": "2025-01-17T16:33:00+01:00",
"diff": 0,
"targetTimeInMs": 1737127980000,
"predictedTimeInMs": 1737127980000,
"timeType": "SCHEDULE"
}
},
{
"status": "Normal",
"arrivalId": "8000309_A_1",
"station": {
"evaNo": "8000309",
"name": "Regensburg Hbf",
"position": {
"latitude": 49.011672,
"longitude": 12.099617
}
},
"track": {
"target": "5",
"prediction": "5"
},
"messages": [],
"arrivalTime": {
"target": "2025-01-17T16:41:00+01:00",
"predicted": "2025-01-17T16:41:00+01:00",
"diff": 0,
"targetTimeInMs": 1737128460000,
"predictedTimeInMs": 1737128460000,
"timeType": "SCHEDULE"
}
}
],
"started": false,
"finished": false,
"hims": [],
"validUntil": "2025-01-17T15:46:00.000Z",
"validFrom": "2025-01-17T14:06:00.000Z",
"isLoyaltyCaseEligible": false
}

View file

@ -297,3 +297,33 @@ tap.test('parses regio guide ruf correctly', (t) => {
t.same(parse(ctx, input), expected);
t.end();
});
tap.test('parses regio guide trip line correctly', (t) => {
const input = {
name: 'S 5',
no: 36552,
journeyId: '20250114-2080f6df-62d4-3c0f-8a89-0db06bc5c2c8',
tenantId: 'hessen',
administrationId: '800528',
operatorName: 'DB Regio, S-Bahn Rhein-Main',
operatorCode: 'DB',
category: 'S',
type: 'CITY_TRAIN',
};
const expected = {
type: 'line',
id: 's-5-36552',
name: 'S 5',
fahrtNr: '36552',
public: true,
product: 'suburban',
productName: 'S',
mode: 'train',
adminCode: '800528',
operator: null,
};
t.same(parse(ctx, input), expected);
t.end();
});

View file

@ -50,3 +50,25 @@ tap.test('parses dbnav operator correctly', (t) => {
t.end();
});
tap.test('parses db regio guide trip operator correctly', (t) => {
const op = {
name: 'S 5',
no: 36552,
journeyId: '20250114-2080f6df-62d4-3c0f-8a89-0db06bc5c2c8',
tenantId: 'hessen',
administrationId: '800528',
operatorName: 'DB Regio, S-Bahn Rhein-Main',
operatorCode: 'DB',
category: 'S',
type: 'CITY_TRAIN',
date: '2025-01-14T16:08:00+01:00',
};
t.same(parse(ctx, op), {
type: 'operator',
id: 'DB',
name: 'DB Regio, S-Bahn Rhein-Main',
});
t.end();
});

View file

@ -242,3 +242,54 @@ tap.test('parses dbnav ruf attributes correctly', (t) => {
t.end();
});
tap.test('parses regio guide trip attributes correctly', (t) => {
const input = {
messages: [
{
code: '51',
text: 'verspätetes Personal aus vorheriger Fahrt',
textShort: 'verspätetes Personal aus vorheriger Fahrt',
},
],
hims: [
{
id: '1',
caption: 'Bahnhof Hanau Hbf beeinträchtigt. Verspätungen und Teilausfälle wahrscheinlich. Grund: Entschärfung einer Fliegerbombe.',
shortText: 'Bahnhof Hanau Hbf beeinträchtigt. Verspätungen und Teilausfälle wahrscheinlich. Grund: Entschärfung einer Fliegerbombe.',
captionHtml: 'Bahnhof Hanau Hbf beeinträchtigt. Verspätungen und Teilausfälle wahrscheinlich. Grund: Entschärfung einer Fliegerbombe.',
shortTextHtml: 'Bahnhof Hanau Hbf beeinträchtigt. Verspätungen und Teilausfälle wahrscheinlich. Grund: Entschärfung einer Fliegerbombe.',
},
{
id: '6',
caption: 'Lüneburg: Aufzug Gl. 2/3 bis April 2025 aufgrund von Neubau außer Betrieb.',
shortText: 'Lüneburg: Aufzug Gl. 2/3 bis April 2025 aufgrund von Neubau außer Betrieb.',
captionHtml: 'Lüneburg: Aufzug Gl. 2/3 bis April 2025 aufgrund von Neubau außer Betrieb.',
shortTextHtml: 'Lüneburg: Aufzug Gl. 2/3 bis April 2025 aufgrund von Neubau außer Betrieb.',
},
],
};
const expected = [
{
code: '1',
summary: 'Bahnhof Hanau Hbf beeinträchtigt. Verspätungen und Teilausfälle wahrscheinlich. Grund: Entschärfung einer Fliegerbombe.',
text: 'Bahnhof Hanau Hbf beeinträchtigt. Verspätungen und Teilausfälle wahrscheinlich. Grund: Entschärfung einer Fliegerbombe.',
type: 'hint',
},
{
code: '6',
summary: 'Lüneburg: Aufzug Gl. 2/3 bis April 2025 aufgrund von Neubau außer Betrieb.',
text: 'Lüneburg: Aufzug Gl. 2/3 bis April 2025 aufgrund von Neubau außer Betrieb.',
type: 'hint',
},
{
code: '51',
summary: 'verspätetes Personal aus vorheriger Fahrt',
text: 'verspätetes Personal aus vorheriger Fahrt',
type: 'hint', // TODO?
},
];
t.same(parse(ctx, input), expected);
t.end();
});