diff --git a/.github/workflows/unix.yml b/.github/workflows/unix.yml index d58343cb9..f135cbdd1 100644 --- a/.github/workflows/unix.yml +++ b/.github/workflows/unix.yml @@ -287,8 +287,8 @@ jobs: - preset: clang-tidy skipui: true skiptests: true - - preset: linux-sanitizer - skipui: true + #- preset: linux-sanitizer + # skipui: true - preset: linux-debug skipui: true emulator: valgrind --leak-check=full --error-exitcode=1 diff --git a/base/core/include/motis/core/schedule/trip.h b/base/core/include/motis/core/schedule/trip.h index 2b6d90c8b..384c9a0df 100644 --- a/base/core/include/motis/core/schedule/trip.h +++ b/base/core/include/motis/core/schedule/trip.h @@ -177,6 +177,8 @@ struct trip { mcd::vector stop_seq_numbers_; boost::uuids::uuid uuid_{}; bool unscheduled_{false}; + ptr original_first_connection_info_{}; + service_class original_first_clasz_{service_class::OTHER}; }; } // namespace motis diff --git a/base/loader/include/motis/loader/graph_builder.h b/base/loader/include/motis/loader/graph_builder.h index 9d2cdde5a..82cb63441 100644 --- a/base/loader/include/motis/loader/graph_builder.h +++ b/base/loader/include/motis/loader/graph_builder.h @@ -136,8 +136,8 @@ struct graph_builder { full_trip_id get_full_trip_id(Service const* s, int day, int section_idx = 0); - std::optional create_merged_trips(Service const* s, - int day_idx); + std::optional create_merged_trips( + Service const* s, int day_idx, light_connection const* first_lcon); trip* register_service(Service const* s, int day_idx, bool allow_duplicates); diff --git a/base/loader/src/graph_builder.cc b/base/loader/src/graph_builder.cc index 0964399c2..a669379f0 100644 --- a/base/loader/src/graph_builder.cc +++ b/base/loader/src/graph_builder.cc @@ -92,9 +92,13 @@ full_trip_id graph_builder::get_full_trip_id(Service const* s, int day, } std::optional graph_builder::create_merged_trips( - Service const* s, int day_idx) { + Service const* s, int day_idx, light_connection const* first_lcon) { auto const trp = register_service(s, day_idx, false); if (trp != nullptr) { + if (first_lcon != nullptr) { + trp->original_first_connection_info_ = first_lcon->full_con_->con_info_; + trp->original_first_clasz_ = first_lcon->full_con_->clasz_; + } return static_cast( push_mem(sched_.merged_trips_, mcd::vector>({trp}))); } else { @@ -159,7 +163,8 @@ trip* graph_builder::register_service(Service const* s, int day_idx, s->seq_numbers() == nullptr ? mcd::vector{} : mcd::to_vec(*s->seq_numbers()), boost::uuids::nil_uuid(), - s->schedule_relationship() == ScheduleRelationship_UNSCHEDULED)) + s->schedule_relationship() == ScheduleRelationship_UNSCHEDULED, + nullptr, service_class::OTHER)) .get(); sched_.trips_.emplace_back(stored->id_.primary_, stored); added_full_trip_ids_[ftid] = stored->trip_idx_; @@ -267,7 +272,10 @@ void graph_builder::add_route_services( continue; } - auto const created_merged_trips_idx = create_merged_trips(s, day); + auto const first_lcon = !lcons.empty() ? &lcons.front() : nullptr; + + auto const created_merged_trips_idx = + create_merged_trips(s, day, first_lcon); if (!created_merged_trips_idx) { // duplicate trip id continue; diff --git a/base/loader/src/rule_service_graph_builder.cc b/base/loader/src/rule_service_graph_builder.cc index 438b2708e..7ee7350c1 100644 --- a/base/loader/src/rule_service_graph_builder.cc +++ b/base/loader/src/rule_service_graph_builder.cc @@ -11,6 +11,7 @@ #include "motis/core/common/logging.h" #include "motis/core/schedule/price.h" #include "motis/core/schedule/trip.h" +#include "motis/core/access/connection_access.h" #include "motis/core/access/trip_iterator.h" #include "motis/loader/util.h" @@ -496,6 +497,13 @@ struct rule_service_route_builder { auto trp = single_trips_.at(std::make_pair(s, day_idx)); trp->edges_ = edges; trp->lcon_idx_ = lcon_idx; + if (!edges->empty()) { + auto const& first_lcon = + edges->front()->m_.route_edge_.conns_.at(lcon_idx); + trp->original_first_connection_info_ = + &access::get_connection_info(gb_.sched_, first_lcon, trp); + trp->original_first_clasz_ = first_lcon.full_con_->clasz_; + } ++lcon_idx; } } @@ -636,7 +644,14 @@ struct rule_service_route_builder { push_mem(gb_.sched_.trip_mem_, ftid, "", edges_ptr, lcon_idx, static_cast(gb_.sched_.trip_mem_.size()), trip_debug{}, mcd::vector{}); - auto const trip_ptr = gb_.sched_.trip_mem_.back().get(); + auto trip_ptr = gb_.sched_.trip_mem_.back().get(); + + auto const& first_lcon = + route_edges.front()->m_.route_edge_.conns_.at(lcon_idx); + trip_ptr->original_first_connection_info_ = + first_lcon.full_con_->con_info_; + trip_ptr->original_first_clasz_ = first_lcon.full_con_->clasz_; + if (gb_.check_trip(trip_ptr)) { route_trips.push_back(trip_ptr); } diff --git a/docs/api/paths.yaml b/docs/api/paths.yaml index 3ccc857a4..3517aefe3 100644 --- a/docs/api/paths.yaml +++ b/docs/api/paths.yaml @@ -273,7 +273,7 @@ type: motis.MotisSuccess description: Universe was destroyed (or marked for destruction if still in use). -/paxmon/filter/groups: +/paxmon/filter_groups: summary: List passenger groups with filter and sort options tags: - rsl @@ -352,14 +352,32 @@ type: motis.paxmon.PaxMonGetGroupsResponse description: Information about the requested passenger groups -/paxmon/get_interchanges: - summary: List monitored interchanges at a station +/paxmon/transfers_at_station: + summary: List monitored transfers at a station tags: - rsl - input: motis.paxmon.PaxMonGetInterchangesRequest + input: motis.paxmon.PaxMonTransfersAtStationRequest output: - type: motis.paxmon.PaxMonGetInterchangesResponse - description: Information about the requested interchanges + type: motis.paxmon.PaxMonTransfersAtStationResponse + description: Information about the requested transfers + +/paxmon/broken_transfers: + summary: List broken transfers + tags: + - rsl + input: motis.paxmon.PaxMonBrokenTransfersRequest + output: + type: motis.paxmon.PaxMonBrokenTransfersResponse + description: A list of monitored broken transfers + +/paxmon/transfer_details: + summary: Transfer information + tags: + - rsl + input: motis.paxmon.PaxMonTransferDetailsRequest + output: + type: motis.paxmon.PaxMonTransferDetailsResponse + description: Information about the transfer /paxmon/group_statistics: summary: Passenger group statistics @@ -536,6 +554,20 @@ type: motis.paxmon.PaxMonMetricsResponse description: Metrics +/paxmon/revise_compact_journey: + summary: Compact journey to real-time connection + description: | + Convert compact journeys into connections (`motis.Connection`), + updated with real-time information. + + Requires the `revise` module. + tags: + - rsl + input: motis.paxmon.PaxMonReviseCompactJourneyRequest + output: + type: motis.paxmon.PaxMonReviseCompactJourneyResponse + description: Connections with real-time information + /ppr/profiles: summary: List available pedestrian routing profiles tags: diff --git a/docs/api/schemas/motis.yaml b/docs/api/schemas/motis.yaml index 22833caf9..6a7ef0ccc 100644 --- a/docs/api/schemas/motis.yaml +++ b/docs/api/schemas/motis.yaml @@ -343,6 +343,8 @@ ApiDescription: fields: methods: description: TODO + version: + description: TODO MsgContent: description: TODO DestinationType: diff --git a/docs/api/schemas/motis/intermodal.yaml b/docs/api/schemas/motis/intermodal.yaml index a500e3451..721263e01 100644 --- a/docs/api/schemas/motis/intermodal.yaml +++ b/docs/api/schemas/motis/intermodal.yaml @@ -78,14 +78,54 @@ IntermodalRoutingRequest: start: description: TODO start_modes: - description: TODO + description: The transport modes allowed at the start. destination: description: TODO destination_modes: - description: TODO + description: The transport modes allowed at the destination. search_type: - description: TODO + description: | + The set of optimization criteria to use. + + Possible values include: + - `Default`: Optimize travel time and number of transfers. + This computes the Pareto set using multi-criteria optimization. + - `Accessibility`: Same as `Default` but including an additional + accessibility criterion. + - `SingleCriterion`: Optimizes a weighted sum where every transfer + adds 20 minutes. + - `SingleCriterionNoIntercity`: Same as `SingleCriterion` but + excluding all long distance (intercity) trains. + + Note that not all routers support all search types. + examples: + - "Default" search_dir: - description: TODO + description: | + The search direction. + + This defines whether the algorithm searches from departure to arrival + (forward in time) or from arrival to departure (backward in time). + Thus, `start` is either specifying the source (`search_dir=Forward`) + or the destination (`search_dir=Backward`). + + Possible values: + - `Forward`: The `start` parameter specifies the departure of the journey. + The destination parameter specifies the arrival of the journey. + - `Backward`: The `start` parameter specifies the arrival of the journey. + The destination parameter specifies the departure of the journey. router: - description: TODO + description: | + The public transport routing endpoint to use. + + Set to an empty string to use the default router (based on the + configuration parameter `intermodal.router`). + + To override the default router, set it to the desired endpoint, + e.g. `/routing` or `/tripbased`. + + Note that not all routers support all features such as + intermodal routing or real-time updates. + examples: + - "" + - "/routing" diff --git a/docs/api/schemas/motis/lookup.yaml b/docs/api/schemas/motis/lookup.yaml index 96055ee7f..efffda6fb 100644 --- a/docs/api/schemas/motis/lookup.yaml +++ b/docs/api/schemas/motis/lookup.yaml @@ -94,6 +94,8 @@ LookupScheduleInfoResponse: description: First loaded day (unix timestamp) end: description: Last loaded day (unix timestamp) + schedules: + description: TODO TableType: description: TODO LookupStationEventsRequest: @@ -184,3 +186,12 @@ LookupStationLocationResponse: fields: position: description: TODO +LookupSchedule: + description: TODO + fields: + tag: + description: TODO + hash: + description: TODO + created: + description: TODO diff --git a/docs/api/schemas/motis/paxforecast.yaml b/docs/api/schemas/motis/paxforecast.yaml index 6de7c0e2a..cb14ce530 100644 --- a/docs/api/schemas/motis/paxforecast.yaml +++ b/docs/api/schemas/motis/paxforecast.yaml @@ -40,7 +40,7 @@ MeasureRecipients: `time`). Does not include passengers in a trip that is currently waiting at the station. LoadLevel: - description: TODO + description: The expected train occupancy level. TripLoadInfoMeasure: description: > This measure type is deprecated. Use `TripLoadRecommendationMeasure` instead. @@ -68,12 +68,12 @@ TripRecommendationMeasure: recommended_trip: description: TODO TripWithLoadLevel: - description: TODO + description: A trip with an associated expected occupancy level. fields: trip: description: TODO level: - description: TODO + description: The expected train occupancy level. TripLoadRecommendationMeasure: description: | Simulate an announcement that a trip is overcrowded and recommend @@ -93,11 +93,14 @@ TripLoadRecommendationMeasure: description: > Time at which the announcement is made (unix timestamp). planned_destinations: - description: TODO + description: | + A list of planned destination stations (station ids). full_trips: - description: TODO + description: | + A list of overcrowded trips that should not be used. recommended_trips: - description: TODO + description: | + A list of recommended alternatives. RtUpdateMeasure: description: | Simulate a real-time update. diff --git a/docs/api/schemas/motis/paxmon.yaml b/docs/api/schemas/motis/paxmon.yaml index cc78d06bc..96b54e1e3 100644 --- a/docs/api/schemas/motis/paxmon.yaml +++ b/docs/api/schemas/motis/paxmon.yaml @@ -509,33 +509,124 @@ PaxMonFilterGroupsRequest: description: TODO fields: universe: - description: TODO + description: The universe ID. sort_by: - description: TODO + description: | + The sort order. + + Possible values: + - `GroupId`: Sort by group ID (ascending). + - `ScheduledDepartureTime`: Sort by scheduled departure time (ascending). + - `MaxEstimatedDelay`: Sort by the maximum estimated delay (descending). + - `ExpectedEstimatedDelay`: Sort by the expected estimated delay (descending). + - `MinEstimatedDelay`: Sort by the minimum expected delay (descending). + - `RerouteLogEntries`: Sort by the number of entries in the reroute log + (descending), i.e. the number of times the groups predicted routes + were changed. + examples: + - ExpectedEstimatedDelay max_results: - description: TODO + description: > + The maximum number of groups to return in response to this request. + examples: + - 100 skip_first: - description: TODO + description: | + Set to `0` if not using pagination. + + To use pagination: + - For the first request, set `skip_first` to `0` and `max_results` + to the desired page size (e.g. `100`). + - For the next requests, set `skip_first` to the `next_skip` value + of the previous response. + examples: + - 0 include_reroute_log: - description: TODO + description: | + Whether the reroute log information should be returned for each group. + + If set to `false`, the reroute log information in the response is + empty. + examples: 0 filter_by_start: - description: TODO + description: | + A list of station IDs. If non-empty, only groups departing at one + of the listed stations are included in the response. + + Set to an empty array to disable this filter. + examples: + - [] filter_by_destination: - description: TODO + description: | + A list of station IDs. If non-empty, only groups arriving at one + of the listed stations are included in the response. + + Set to an empty array to disable this filter. + examples: + - [] filter_by_via: - description: TODO + description: | + A list of station IDs. If non-empty, only groups with a transfer at one + of the listed stations are included in the response. + + Note that boarding the first train and exiting the last train also + count as transfers. Therefore, the start and destination stations + are also checked by this filter. + + Set to an empty array to disable this filter. + examples: + - [] filter_by_group_id: - description: TODO + description: | + A list of group IDs. If non-empty, only these groups are included + in the response. + + Set to an empty array to disable this filter. + examples: + - [] filter_by_data_source: - description: TODO + description: | + A list of group data sources. If non-empty, only groups with these + data sources are included in the repsonse. + + Set to an empty array to disable this filter. + examples: + - [] filter_by_train_nr: - description: TODO + description: | + A list of train numbers. If non-empty, only groups using a train + with one of the given train numbers are included in the response. + + Set to an empty array to disable this filter. + examples: + - [] filter_by_time: - description: TODO + description: | + Filter groups by time. + + Possible values: + - `NoFilter`: Disable the time filter. `filter_interval` is ignored. + - `DepartureTime`: Only include groups with an initial departure time + in the filter interval. + - `DepartureOrArrivalTime`: Only include groups with an initial + departure time or destination arrival time in the filter interval. + - `ActiveTime`: Only include groups that are en route in the filter + interval. + + The desired time interval is given in `filter_interval`. + examples: + - NoFilter + - ActiveTime filter_interval: description: TODO filter_by_reroute_reason: - description: TODO + description: | + A list of reroute reasons. If non-empty, only groups with a reroute + log entry of one of the given reasons are included in the response. + + Set to an empty array to disable this filter. + examples: + - [] PaxMonGroupWithStats: description: TODO fields: @@ -875,27 +966,6 @@ PaxMonUniverseDestroyed: fields: universe: description: TODO -PaxMonGetInterchangesRequest: - description: TODO - fields: - universe: - description: TODO - station: - description: TODO - start_time: - description: TODO - end_time: - description: TODO - max_count: - description: TODO - include_meta_stations: - description: TODO - include_group_infos: - description: TODO - include_broken_interchanges: - description: TODO - include_disabled_group_routes: - description: TODO PaxMonTripStopInfo: description: TODO fields: @@ -907,31 +977,7 @@ PaxMonTripStopInfo: description: TODO station: description: TODO -PaxMonInterchangeInfo: - description: TODO - fields: - arrival: - description: TODO - departure: - description: TODO - groups: - description: TODO - transfer_time: - description: TODO - valid: - description: TODO - disabled: - description: TODO - broken: - description: TODO -PaxMonGetInterchangesResponse: - description: TODO - fields: - station: - description: TODO - interchanges: - description: TODO - max_count_reached: + canceled: description: TODO PaxMonGetAddressableGroupsRequest: description: TODO @@ -1822,3 +1868,158 @@ PaxMonFeedStatus: description: > The creation time of the latest real-time updated received from this real-time feed (unix timestamp). +PaxMonTransfersAtStationRequest: + description: TODO + fields: + universe: + description: TODO + station: + description: TODO + filter_interval: + description: TODO + ignore_past_transfers: + description: TODO + include_meta_stations: + description: TODO + include_group_infos: + description: TODO + include_broken_transfers: + description: TODO + include_disabled_group_routes: + description: TODO + max_results: + description: TODO +PaxMonTransferId: + description: TODO + fields: + n: + description: TODO + e: + description: TODO +PaxMonTransferDelayInfo: + description: TODO + fields: + min_delay_increase: + description: TODO + max_delay_increase: + description: TODO + total_delay_increase: + description: TODO + squared_total_delay_increase: + description: TODO + unreachable_pax: + description: TODO +PaxMonDetailedTransferInfo: + description: TODO + fields: + id: + description: TODO + arrival: + description: TODO + departure: + description: TODO + groups: + description: TODO + group_count: + description: TODO + pax_count: + description: TODO + transfer_time: + description: TODO + valid: + description: TODO + disabled: + description: TODO + broken: + description: TODO + canceled: + description: TODO + delay: + description: TODO +PaxMonTransfersAtStationResponse: + description: TODO + fields: + station: + description: TODO + transfers: + description: TODO + max_count_reached: + description: TODO +PaxMonBrokenTransfersSortOrder: + description: TODO +PaxMonBrokenTransfersRequest: + description: TODO + fields: + universe: + description: TODO + filter_interval: + description: TODO + ignore_past_transfers: + description: TODO + include_insufficient_transfer_time: + description: TODO + include_missed_initial_departure: + description: TODO + include_canceled_transfer: + description: TODO + include_canceled_initial_departure: + description: TODO + include_canceled_final_arrival: + description: TODO + only_planned_routes: + description: TODO + sort_by: + description: TODO + max_results: + description: TODO + skip_first: + description: TODO +PaxMonBrokenTransfersResponse: + description: TODO + fields: + total_matching_transfers: + description: TODO + transfers_in_this_response: + description: TODO + remaining_transfers: + description: TODO + next_skip: + description: TODO + transfers: + description: TODO +PaxMonTransferDetailsRequest: + description: TODO + fields: + universe: + description: TODO + id: + description: TODO + include_disabled_group_routes: + description: TODO + include_full_groups: + description: TODO + include_reroute_log: + description: TODO +PaxMonTransferDetailsResponse: + description: TODO + fields: + info: + description: TODO + normal_routes: + description: TODO + broken_routes: + description: TODO + groups: + description: TODO +PaxMonReviseCompactJourneyRequest: + description: TODO + fields: + universe: + description: TODO + journeys: + description: TODO +PaxMonReviseCompactJourneyResponse: + description: TODO + fields: + connections: + description: TODO diff --git a/docs/api/schemas/motis/revise.yaml b/docs/api/schemas/motis/revise.yaml index 14c0ce61f..3205dde96 100644 --- a/docs/api/schemas/motis/revise.yaml +++ b/docs/api/schemas/motis/revise.yaml @@ -3,6 +3,8 @@ ReviseRequest: fields: connections: description: TODO + schedule: + description: TODO ReviseResponse: description: TODO fields: diff --git a/docs/api/schemas/motis/routing.yaml b/docs/api/schemas/motis/routing.yaml index f884f0f33..01bc66bc2 100644 --- a/docs/api/schemas/motis/routing.yaml +++ b/docs/api/schemas/motis/routing.yaml @@ -118,7 +118,14 @@ RoutingRequest: use_start_footpaths: description: TODO schedule: - description: TODO + description: | + The schedule ID. + + This should always be set to `0` unless a paxmon parallel universe + should be used for routing, in which case it should be set to the + schedule ID (not universe ID) of that universe. + examples: + - 0 RoutingResponse: description: TODO fields: diff --git a/modules/paxmon/include/motis/paxmon/api/get_interchanges.h b/modules/paxmon/include/motis/paxmon/api/broken_transfers.h similarity index 79% rename from modules/paxmon/include/motis/paxmon/api/get_interchanges.h rename to modules/paxmon/include/motis/paxmon/api/broken_transfers.h index af7375276..2f993809b 100644 --- a/modules/paxmon/include/motis/paxmon/api/get_interchanges.h +++ b/modules/paxmon/include/motis/paxmon/api/broken_transfers.h @@ -6,7 +6,7 @@ namespace motis::paxmon::api { -motis::module::msg_ptr get_interchanges(paxmon_data& data, +motis::module::msg_ptr broken_transfers(paxmon_data& data, motis::module::msg_ptr const& msg); } // namespace motis::paxmon::api diff --git a/modules/paxmon/include/motis/paxmon/api/revise_compact_journey.h b/modules/paxmon/include/motis/paxmon/api/revise_compact_journey.h new file mode 100644 index 000000000..c2bb7e551 --- /dev/null +++ b/modules/paxmon/include/motis/paxmon/api/revise_compact_journey.h @@ -0,0 +1,12 @@ +#pragma once + +#include "motis/module/message.h" + +#include "motis/paxmon/paxmon_data.h" + +namespace motis::paxmon::api { + +motis::module::msg_ptr revise_compact_journey( + paxmon_data& data, motis::module::msg_ptr const& msg); + +} // namespace motis::paxmon::api diff --git a/modules/paxmon/include/motis/paxmon/api/transfer_details.h b/modules/paxmon/include/motis/paxmon/api/transfer_details.h new file mode 100644 index 000000000..255462063 --- /dev/null +++ b/modules/paxmon/include/motis/paxmon/api/transfer_details.h @@ -0,0 +1,12 @@ +#pragma once + +#include "motis/module/message.h" + +#include "motis/paxmon/paxmon_data.h" + +namespace motis::paxmon::api { + +motis::module::msg_ptr transfer_details(paxmon_data& data, + motis::module::msg_ptr const& msg); + +} // namespace motis::paxmon::api diff --git a/modules/paxmon/include/motis/paxmon/api/transfers_at_station.h b/modules/paxmon/include/motis/paxmon/api/transfers_at_station.h new file mode 100644 index 000000000..e9b50c3fe --- /dev/null +++ b/modules/paxmon/include/motis/paxmon/api/transfers_at_station.h @@ -0,0 +1,12 @@ +#pragma once + +#include "motis/module/message.h" + +#include "motis/paxmon/paxmon_data.h" + +namespace motis::paxmon::api { + +motis::module::msg_ptr transfers_at_station(paxmon_data& data, + motis::module::msg_ptr const& msg); + +} // namespace motis::paxmon::api diff --git a/modules/paxmon/include/motis/paxmon/paxmon.h b/modules/paxmon/include/motis/paxmon/paxmon.h index 2fdf0d5cf..c867499a8 100644 --- a/modules/paxmon/include/motis/paxmon/paxmon.h +++ b/modules/paxmon/include/motis/paxmon/paxmon.h @@ -64,6 +64,7 @@ struct paxmon : public motis::module::module { bool reroute_unmatched_{false}; int arrival_delay_threshold_{20}; int preparation_time_{15}; + int early_departure_tolerance_{0}; bool check_graph_times_{false}; bool check_graph_integrity_{false}; std::string mcfp_scenario_dir_{}; diff --git a/modules/paxmon/include/motis/paxmon/service_info.h b/modules/paxmon/include/motis/paxmon/service_info.h index 6817d3773..2d905a180 100644 --- a/modules/paxmon/include/motis/paxmon/service_info.h +++ b/modules/paxmon/include/motis/paxmon/service_info.h @@ -25,8 +25,8 @@ struct service_info { service_class clasz_{service_class::OTHER}; }; -service_info get_service_info(schedule const& sched, connection const& fc, - connection_info const* ci); +service_info get_service_info(schedule const& sched, connection_info const* ci, + service_class const clasz); std::vector> get_service_infos( schedule const& sched, trip const* trp); diff --git a/modules/paxmon/include/motis/paxmon/universe.h b/modules/paxmon/include/motis/paxmon/universe.h index 250f9e781..598c106e1 100644 --- a/modules/paxmon/include/motis/paxmon/universe.h +++ b/modules/paxmon/include/motis/paxmon/universe.h @@ -65,7 +65,9 @@ struct edge { inline edge_type type() const { return type_; } - inline bool has_trips() const { return is_trip() || is_wait(); } + inline bool has_trips() const { + return is_trip() || is_wait() || is_disabled(); + } inline merged_trips_idx get_merged_trips_idx() const { return trips_; } @@ -171,6 +173,8 @@ struct universe { tick_statistics tick_stats_; metrics metrics_; update_tracker update_tracker_; + + int early_departure_tolerance_{0}; // minutes }; } // namespace motis::paxmon diff --git a/modules/paxmon/include/motis/paxmon/util/detailed_transfer_info.h b/modules/paxmon/include/motis/paxmon/util/detailed_transfer_info.h new file mode 100644 index 000000000..0c6c5cc0b --- /dev/null +++ b/modules/paxmon/include/motis/paxmon/util/detailed_transfer_info.h @@ -0,0 +1,210 @@ +#pragma once + +#include +#include + +#include "boost/range/join.hpp" + +#include "flatbuffers/flatbuffers.h" + +#include "utl/to_vec.h" + +#include "motis/hash_set.h" + +#include "motis/core/schedule/schedule.h" +#include "motis/core/conv/station_conv.h" + +#include "motis/paxmon/get_load.h" +#include "motis/paxmon/messages.h" +#include "motis/paxmon/universe.h" +#include "motis/paxmon/util/group_delay.h" + +namespace motis::paxmon::util { + +struct detailed_transfer_info { + flatbuffers::Offset to_fbs_transfer_info( + flatbuffers::FlatBufferBuilder& fbb, universe const& uv, + schedule const& sched, bool const include_delay_info) const { + auto const id = PaxMonTransferId{ei_.node_, ei_.out_edge_idx_}; + auto const* e = ei_.get(uv); + + auto const make_fbs_event = [&](event_node const* ev, bool const arrival) { + auto res = std::vector>{}; + if (!ev->is_enter_exit_node()) { + auto trips = mcd::hash_set{}; + // TODO(pablo): service infos only for arriving trip section + if (arrival) { + for (auto const& trp_edge : ev->incoming_edges(uv)) { + if (trp_edge.is_trip() || trp_edge.is_disabled()) { + for (auto const& trp : trp_edge.get_trips(sched)) { + trips.insert(trp); + } + } + } + } else { + for (auto const& trp_edge : ev->outgoing_edges(uv)) { + if (trp_edge.is_trip() || trp_edge.is_disabled()) { + for (auto const& trp : trp_edge.get_trips(sched)) { + trips.insert(trp); + } + } + } + } + auto const fbs_trips = utl::to_vec(trips, [&](trip const* trp) { + return to_fbs_trip_service_info(fbb, sched, trp); + }); + res.emplace_back(CreatePaxMonTripStopInfo( + fbb, motis_to_unixtime(sched, ev->schedule_time()), + motis_to_unixtime(sched, ev->current_time()), ev->is_canceled(), + fbb.CreateVector(fbs_trips), + to_fbs(fbb, *sched.stations_.at(ev->station_idx())))); + } + return fbb.CreateVector(res); + }; + + return CreatePaxMonDetailedTransferInfo( + fbb, &id, make_fbs_event(from_, true), make_fbs_event(to_, false), + groups_, group_count_, pax_count_, e->transfer_time(), e->is_valid(uv), + e->is_disabled(), e->is_broken(), e->is_canceled(uv), + include_delay_info + ? CreatePaxMonTransferDelayInfo( + fbb, min_delay_increase_, max_delay_increase_, + total_delay_increase_, squared_total_delay_increase_, + unreachable_pax_) + : 0); + } + + edge_index ei_; + + event_node const* from_{}; + event_node const* to_{}; + + std::int32_t group_count_{}; + std::int32_t pax_count_{}; + + std::int16_t min_delay_increase_{std::numeric_limits::max()}; + std::int16_t max_delay_increase_{}; + std::int64_t total_delay_increase_{}; + std::uint64_t squared_total_delay_increase_{}; + std::int32_t unreachable_pax_{}; + + std::uint32_t normal_routes_{}; + std::uint32_t broken_routes_{}; + + flatbuffers::Offset groups_{}; +}; + +struct get_detailed_transfer_info_options { + bool include_group_infos_{}; + bool include_disabled_group_routes_{}; + bool include_delay_info_{}; + bool only_planned_routes_{}; +}; + +inline detailed_transfer_info get_detailed_transfer_info( + universe const& uv, schedule const& sched, edge_index const ei, + flatbuffers::FlatBufferBuilder& fbb, + get_detailed_transfer_info_options const& options) { + auto const* ic_edge = ei.get(uv); + auto info = detailed_transfer_info{ + .ei_ = ei, .from_ = ic_edge->from(uv), .to_ = ic_edge->to(uv)}; + + auto group_route_infos = std::vector{}; + auto last_pg = std::numeric_limits::max(); + + auto const handle_pgwr = [&](auto const& pgwr) { + if (options.only_planned_routes_ && pgwr.route_ != 0) { + return; + } + auto const& pg = uv.passenger_groups_.at(pgwr.pg_); + auto const& gr = uv.passenger_groups_.route(pgwr); + + auto const is_new_group = last_pg != pgwr.pg_; + + if (is_new_group) { + last_pg = pgwr.pg_; + ++info.group_count_; + info.pax_count_ += pg->passengers_; + } + + if (!options.include_disabled_group_routes_ && gr.probability_ == 0.0F) { + return; + } + + if (options.include_delay_info_) { + auto const current_group_delay_info = + get_current_estimated_delay(uv, pgwr.pg_); + auto const current_group_est_delay = + static_cast(current_group_delay_info.estimated_delay_); + + auto const sched_delay = get_scheduled_delay(uv, pgwr); + + auto const delay_increase = + static_cast(current_group_est_delay - sched_delay); + + if (!current_group_delay_info.possibly_unreachable_) { + info.min_delay_increase_ = + std::min(info.min_delay_increase_, delay_increase); + info.max_delay_increase_ = + std::max(info.max_delay_increase_, delay_increase); + } + + if (delay_increase > 0) { + info.total_delay_increase_ += delay_increase; + info.squared_total_delay_increase_ += + static_cast(delay_increase) * + static_cast(delay_increase); + } + + if (is_new_group && current_group_delay_info.possibly_unreachable_) { + info.unreachable_pax_ += pg->passengers_; + } + } + + if (options.include_group_infos_) { + group_route_infos.emplace_back(to_fbs_base_info(fbb, *pg, gr)); + } + }; + + auto const normal_routes = + uv.pax_connection_info_.group_routes(ic_edge->pci_); + auto const broken_routes = + uv.pax_connection_info_.broken_group_routes(ic_edge->pci_); + + info.normal_routes_ = normal_routes.size(); + info.broken_routes_ = broken_routes.size(); + + for (auto const& pgwr : normal_routes) { + handle_pgwr(pgwr); + } + + if (options.include_disabled_group_routes_) { + for (auto const& pgwr : broken_routes) { + handle_pgwr(pgwr); + } + } + + if (options.include_group_infos_) { + std::sort(begin(group_route_infos), end(group_route_infos), + [](PaxMonGroupRouteBaseInfo const& a, + PaxMonGroupRouteBaseInfo const& b) { + return std::make_pair(a.g(), a.r()) < + std::make_pair(b.g(), b.r()); + }); + auto const pdf = + options.include_disabled_group_routes_ + ? get_load_pdf(uv.passenger_groups_, + boost::join(normal_routes, broken_routes)) + : get_load_pdf(uv.passenger_groups_, normal_routes); + auto const cdf = get_cdf(pdf); + auto stats = get_pax_stats(cdf); + stats.limits_.max_ = info.pax_count_; + info.groups_ = CreatePaxMonCombinedGroupRoutes( + fbb, fbb.CreateVectorOfStructs(group_route_infos), + to_fbs_distribution(fbb, pdf, stats)); + } + + return info; +} + +} // namespace motis::paxmon::util diff --git a/modules/paxmon/include/motis/paxmon/util/group_delay.h b/modules/paxmon/include/motis/paxmon/util/group_delay.h new file mode 100644 index 000000000..526814bd5 --- /dev/null +++ b/modules/paxmon/include/motis/paxmon/util/group_delay.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include "motis/paxmon/universe.h" + +namespace motis::paxmon::util { + +struct group_delay_info { + float estimated_delay_{}; + bool possibly_unreachable_{}; +}; + +inline group_delay_info get_current_estimated_delay( + universe const& uv, passenger_group_index const pgi, + std::int16_t const unreachable_delay = 24 * 60) { + auto info = group_delay_info{}; + auto at_least_one_active_route = false; + for (auto const& gr : uv.passenger_groups_.routes(pgi)) { + if (gr.probability_ != 0) { + auto const route_est_delay = + gr.destination_unreachable_ ? unreachable_delay : gr.estimated_delay_; + info.estimated_delay_ += gr.probability_ * route_est_delay; + at_least_one_active_route = true; + if (gr.destination_unreachable_) { + info.possibly_unreachable_ = true; + } + } + } + if (!at_least_one_active_route) { + info.estimated_delay_ = unreachable_delay; + info.possibly_unreachable_ = true; + } + return info; +} + +inline std::int16_t get_scheduled_delay(universe const& uv, + passenger_group_with_route const pgwr, + std::int16_t const invalid_delay = 24 * + 60) { + // assumption in this function: route 0 is the (only) planned route + if (pgwr.route_ == 0) { + return 0; + } + auto const this_planned_arrival = + uv.passenger_groups_.route(pgwr).planned_arrival_time_; + if (this_planned_arrival == INVALID_TIME) { + return invalid_delay; + } + auto const original_planned_arrival = + uv.passenger_groups_.route(passenger_group_with_route{pgwr.pg_, 0}) + .planned_arrival_time_; + return static_cast(static_cast(this_planned_arrival) - + static_cast(original_planned_arrival)); +} + +} // namespace motis::paxmon::util diff --git a/modules/paxmon/src/api/broken_transfers.cc b/modules/paxmon/src/api/broken_transfers.cc new file mode 100644 index 000000000..fa5d1f1b6 --- /dev/null +++ b/modules/paxmon/src/api/broken_transfers.cc @@ -0,0 +1,214 @@ +#include "motis/paxmon/api/broken_transfers.h" + +#include +#include +#include +#include + +#include "utl/to_vec.h" + +#include "motis/core/access/time_access.h" + +#include "motis/paxmon/get_universe.h" +#include "motis/paxmon/messages.h" +#include "motis/paxmon/util/detailed_transfer_info.h" + +using namespace motis::module; +using namespace motis::paxmon; +using namespace motis::paxmon::util; +using namespace flatbuffers; + +namespace motis::paxmon::api { + +msg_ptr broken_transfers(paxmon_data& data, msg_ptr const& msg) { + auto const req = motis_content(PaxMonBrokenTransfersRequest, msg); + auto const uv_access = get_universe_and_schedule(data, req->universe()); + auto const& sched = uv_access.sched_; + auto const& uv = uv_access.uv_; + + auto const max_results = req->max_results(); + auto const skip_first = req->skip_first(); + + auto const filter_interval_begin = + req->filter_interval()->begin() != 0 + ? unix_to_motistime(sched.schedule_begin_, + req->filter_interval()->begin()) + : 0; + auto const filter_interval_end = + req->filter_interval()->end() != 0 + ? unix_to_motistime(sched.schedule_begin_, + req->filter_interval()->end()) + : std::numeric_limits