diff --git a/docs/db-apis.md b/docs/db-apis.md index 678ca0ae..02666586 100644 --- a/docs/db-apis.md +++ b/docs/db-apis.md @@ -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/ diff --git a/p/db/base.json b/p/db/base.json index 01a0952e..c04b7285 100644 --- a/p/db/base.json +++ b/p/db/base.json @@ -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" } diff --git a/p/db/index.js b/p/db/index.js index 4b347ca0..d7941ffc 100644 --- a/p/db/index.js +++ b/p/db/index.js @@ -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, }; diff --git a/p/db/trip-req.js b/p/db/trip-req.js new file mode 100644 index 00000000..7b25aa40 --- /dev/null +++ b/p/db/trip-req.js @@ -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, +}; diff --git a/parse/journey-leg.js b/parse/journey-leg.js index 9f31d6de..910c9dd0 100644 --- a/parse/journey-leg.js +++ b/parse/journey-leg.js @@ -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) { diff --git a/parse/line.js b/parse/line.js index b9c3e631..65be7189 100644 --- a/parse/line.js +++ b/parse/line.js @@ -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; }; diff --git a/parse/operator.js b/parse/operator.js index 84f9cda2..b1814f0b 100644 --- a/parse/operator.js +++ b/parse/operator.js @@ -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; diff --git a/parse/remarks.js b/parse/remarks.js index 08c56a2b..aa082803 100644 --- a/parse/remarks.js +++ b/parse/remarks.js @@ -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', diff --git a/parse/stopover.js b/parse/stopover.js index 60619c97..c96e08cd 100644 --- a/parse/stopover.js +++ b/parse/stopover.js @@ -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, diff --git a/parse/trip.js b/parse/trip.js index b0aa30bb..38e594f4 100644 --- a/parse/trip.js +++ b/parse/trip.js @@ -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; diff --git a/test/db-trip-regio-guide.js b/test/db-trip-regio-guide.js new file mode 100644 index 00000000..93b90862 --- /dev/null +++ b/test/db-trip-regio-guide.js @@ -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(); +}); diff --git a/test/fixtures/db-trip-regio-guide.js b/test/fixtures/db-trip-regio-guide.js new file mode 100644 index 00000000..09ce298b --- /dev/null +++ b/test/fixtures/db-trip-regio-guide.js @@ -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, +}; diff --git a/test/fixtures/db-trip-regio-guide.json b/test/fixtures/db-trip-regio-guide.json new file mode 100644 index 00000000..c2ac5898 --- /dev/null +++ b/test/fixtures/db-trip-regio-guide.json @@ -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 +} \ No newline at end of file diff --git a/test/parse/line.js b/test/parse/line.js index 3b4af993..4c520b6a 100644 --- a/test/parse/line.js +++ b/test/parse/line.js @@ -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(); +}); diff --git a/test/parse/operator.js b/test/parse/operator.js index fc47e219..02381291 100644 --- a/test/parse/operator.js +++ b/test/parse/operator.js @@ -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(); +}); diff --git a/test/parse/remarks.js b/test/parse/remarks.js index accb0806..9d02d531 100644 --- a/test/parse/remarks.js +++ b/test/parse/remarks.js @@ -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(); +});