@golemio/pid 6.4.1-dev.3053867815 → 6.4.1-dev.3054859807

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/db/example/00_clear_test_data.sql +1 -0
  2. package/db/example/24_jis_delayed_segments.sql +142 -0
  3. package/db/migrations/postgresql/20261027100000-jis-delayed-segments.js +16 -0
  4. package/db/migrations/postgresql/sqls/20250127134438-departure-additional-indexes-down.sql +20 -2
  5. package/db/migrations/postgresql/sqls/20261027100000-jis-delayed-segments-down.sql +17 -0
  6. package/db/migrations/postgresql/sqls/20261027100000-jis-delayed-segments-up.sql +42 -0
  7. package/dist/integration-engine/jis/datasources/JISDelayedSegmentsDataSourceFactory.d.ts +9 -0
  8. package/dist/integration-engine/jis/datasources/JISDelayedSegmentsDataSourceFactory.js +54 -0
  9. package/dist/integration-engine/jis/datasources/JISDelayedSegmentsDataSourceFactory.js.map +1 -0
  10. package/dist/integration-engine/jis/ioc/Di.js +10 -0
  11. package/dist/integration-engine/jis/ioc/Di.js.map +1 -1
  12. package/dist/integration-engine/jis/ioc/JISContainerToken.d.ts +5 -0
  13. package/dist/integration-engine/jis/ioc/JISContainerToken.js +5 -0
  14. package/dist/integration-engine/jis/ioc/JISContainerToken.js.map +1 -1
  15. package/dist/integration-engine/jis/repositories/JISDelayedSegmentEnrichmentRepository.d.ts +14 -0
  16. package/dist/integration-engine/jis/repositories/JISDelayedSegmentEnrichmentRepository.js +69 -0
  17. package/dist/integration-engine/jis/repositories/JISDelayedSegmentEnrichmentRepository.js.map +1 -0
  18. package/dist/integration-engine/jis/repositories/JISDelayedSegmentsRepository.d.ts +23 -0
  19. package/dist/integration-engine/jis/repositories/JISDelayedSegmentsRepository.js +73 -0
  20. package/dist/integration-engine/jis/repositories/JISDelayedSegmentsRepository.js.map +1 -0
  21. package/dist/integration-engine/jis/repositories/helpers/JISDelayedSegmentEnrichmentQuery.d.ts +13 -0
  22. package/dist/integration-engine/jis/repositories/helpers/JISDelayedSegmentEnrichmentQuery.js +266 -0
  23. package/dist/integration-engine/jis/repositories/helpers/JISDelayedSegmentEnrichmentQuery.js.map +1 -0
  24. package/dist/integration-engine/jis/repositories/interfaces/IJISDelayedSegmentEnrichmentParams.d.ts +23 -0
  25. package/dist/integration-engine/jis/repositories/interfaces/IJISDelayedSegmentEnrichmentParams.js +2 -0
  26. package/dist/integration-engine/jis/repositories/interfaces/IJISDelayedSegmentEnrichmentParams.js.map +1 -0
  27. package/dist/integration-engine/jis/services/JISDelayedSegmentsDataService.d.ts +38 -0
  28. package/dist/integration-engine/jis/services/JISDelayedSegmentsDataService.js +174 -0
  29. package/dist/integration-engine/jis/services/JISDelayedSegmentsDataService.js.map +1 -0
  30. package/dist/integration-engine/jis/workers/JISWorker.js +1 -0
  31. package/dist/integration-engine/jis/workers/JISWorker.js.map +1 -1
  32. package/dist/integration-engine/jis/workers/tasks/RefreshDelayedSegmentsTask.d.ts +15 -0
  33. package/dist/integration-engine/jis/workers/tasks/RefreshDelayedSegmentsTask.js +55 -0
  34. package/dist/integration-engine/jis/workers/tasks/RefreshDelayedSegmentsTask.js.map +1 -0
  35. package/dist/output-gateway/jis/controllers/v1/V1DelayedSegmentsController.d.ts +9 -0
  36. package/dist/output-gateway/jis/controllers/v1/V1DelayedSegmentsController.js +54 -0
  37. package/dist/output-gateway/jis/controllers/v1/V1DelayedSegmentsController.js.map +1 -0
  38. package/dist/output-gateway/jis/data-access/JISDelayedSegmentsRepository.d.ts +14 -0
  39. package/dist/output-gateway/jis/data-access/JISDelayedSegmentsRepository.js +56 -0
  40. package/dist/output-gateway/jis/data-access/JISDelayedSegmentsRepository.js.map +1 -0
  41. package/dist/output-gateway/jis/domain/IJISDelayedSegmentDto.d.ts +12 -0
  42. package/dist/output-gateway/jis/domain/IJISDelayedSegmentDto.js +2 -0
  43. package/dist/output-gateway/jis/domain/IJISDelayedSegmentDto.js.map +1 -0
  44. package/dist/output-gateway/jis/dto/interfaces/IDelayedSegmentsQueryDto.d.ts +5 -0
  45. package/dist/output-gateway/jis/dto/interfaces/IDelayedSegmentsQueryDto.js +2 -0
  46. package/dist/output-gateway/jis/dto/interfaces/IDelayedSegmentsQueryDto.js.map +1 -0
  47. package/dist/output-gateway/jis/helpers/JISDelayedSegmentInfoPanelStopsFilter.d.ts +8 -0
  48. package/dist/output-gateway/jis/helpers/JISDelayedSegmentInfoPanelStopsFilter.js +13 -0
  49. package/dist/output-gateway/jis/helpers/JISDelayedSegmentInfoPanelStopsFilter.js.map +1 -0
  50. package/dist/output-gateway/jis/ioc/Di.js +6 -0
  51. package/dist/output-gateway/jis/ioc/Di.js.map +1 -1
  52. package/dist/output-gateway/jis/ioc/OgJisToken.d.ts +3 -0
  53. package/dist/output-gateway/jis/ioc/OgJisToken.js +3 -0
  54. package/dist/output-gateway/jis/ioc/OgJisToken.js.map +1 -1
  55. package/dist/output-gateway/jis/routers/v1/V1JISRouter.d.ts +2 -0
  56. package/dist/output-gateway/jis/routers/v1/V1JISRouter.js +9 -0
  57. package/dist/output-gateway/jis/routers/v1/V1JISRouter.js.map +1 -1
  58. package/dist/output-gateway/jis/transformations/JISDelayedSegmentsTransformation.d.ts +7 -0
  59. package/dist/output-gateway/jis/transformations/JISDelayedSegmentsTransformation.js +32 -0
  60. package/dist/output-gateway/jis/transformations/JISDelayedSegmentsTransformation.js.map +1 -0
  61. package/dist/schema-definitions/jis/datasources/MapaPidDelayShapesJsonSchema.d.ts +3 -0
  62. package/dist/schema-definitions/jis/datasources/MapaPidDelayShapesJsonSchema.js +16 -0
  63. package/dist/schema-definitions/jis/datasources/MapaPidDelayShapesJsonSchema.js.map +1 -0
  64. package/dist/schema-definitions/jis/datasources/interfaces/IMapaPidDelayShape.d.ts +10 -0
  65. package/dist/schema-definitions/jis/datasources/interfaces/IMapaPidDelayShape.js +2 -0
  66. package/dist/schema-definitions/jis/datasources/interfaces/IMapaPidDelayShape.js.map +1 -0
  67. package/dist/schema-definitions/jis/datasources/interfaces/index.d.ts +1 -0
  68. package/dist/schema-definitions/jis/datasources/interfaces/index.js +1 -0
  69. package/dist/schema-definitions/jis/datasources/interfaces/index.js.map +1 -1
  70. package/dist/schema-definitions/jis/models/JISDelayedSegmentsModel.d.ts +24 -0
  71. package/dist/schema-definitions/jis/models/JISDelayedSegmentsModel.js +138 -0
  72. package/dist/schema-definitions/jis/models/JISDelayedSegmentsModel.js.map +1 -0
  73. package/dist/schema-definitions/jis/models/interfaces/IJISDelayedSegment.d.ts +44 -0
  74. package/dist/schema-definitions/jis/models/interfaces/IJISDelayedSegment.js +2 -0
  75. package/dist/schema-definitions/jis/models/interfaces/IJISDelayedSegment.js.map +1 -0
  76. package/dist/schema-definitions/jis/models/interfaces/index.d.ts +1 -0
  77. package/dist/schema-definitions/jis/models/interfaces/index.js +1 -0
  78. package/dist/schema-definitions/jis/models/interfaces/index.js.map +1 -1
  79. package/docs/assets/pid_jis_erd.png +0 -0
  80. package/docs/asyncapi.yaml +15 -0
  81. package/docs/implementation_documentation.md +56 -0
  82. package/docs/index.md +1 -0
  83. package/docs/jis/delayed-segments.md +241 -0
  84. package/docs/jis/index.md +9 -0
  85. package/docs/openapi-output.yaml +176 -0
  86. package/package.json +1 -1
@@ -0,0 +1,73 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { PG_SCHEMA } from "#sch/const";
14
+ import { JISDelayedSegmentsModel } from "#sch/jis/models/JISDelayedSegmentsModel";
15
+ import { AbstractValidatableRepository } from "@golemio/core/dist/helpers/data-access/postgres/repositories/AbstractValidatableRepository.js";
16
+ import { CoreToken } from "@golemio/core/dist/helpers/ioc/CoreToken.js";
17
+ import GolemioErrorsCore from "@golemio/core/dist/shared/golemio-errors.js";
18
+ const { GeneralError } = GolemioErrorsCore;
19
+ import GolemioValidatorCore from "@golemio/core/dist/shared/golemio-validator.js";
20
+ const { JSONSchemaValidator } = GolemioValidatorCore;
21
+ import SequelizeCore from "@golemio/core/dist/shared/sequelize.js";
22
+ const { Op } = SequelizeCore;
23
+ import { inject, injectable } from "@golemio/core/dist/shared/tsyringe.js";
24
+ let JISDelayedSegmentsRepository = class JISDelayedSegmentsRepository extends AbstractValidatableRepository {
25
+ constructor(connector, logger) {
26
+ super(connector, logger);
27
+ this.schema = PG_SCHEMA;
28
+ this.tableName = JISDelayedSegmentsModel.tableName;
29
+ this.validator = new JSONSchemaValidator("JISDelayedSegmentsRepository", JISDelayedSegmentsModel.jsonSchema);
30
+ this.sequelizeModel = connector
31
+ .getConnection()
32
+ .define(this.tableName, JISDelayedSegmentsModel.attributeModel, { schema: this.schema });
33
+ }
34
+ /**
35
+ * Replaces all delayed segments with the given ones. `updated_at` of every given segment is set to `detectedAt`,
36
+ * `created_at` keeps the time the segment was first detected. Segments absent from the given data are deleted.
37
+ */
38
+ async refreshData(segments, detectedAt) {
39
+ await this.validator.Validate(segments);
40
+ const transaction = await this.connector.getConnection().transaction();
41
+ try {
42
+ const rows = segments.map((segment) => ({ ...segment, created_at: detectedAt, updated_at: detectedAt }));
43
+ await this.sequelizeModel.bulkCreate(rows, {
44
+ updateOnDuplicate: this.getUpdateAttributes(),
45
+ transaction,
46
+ });
47
+ await this.sequelizeModel.destroy({
48
+ where: { updated_at: { [Op.lt]: detectedAt } },
49
+ transaction,
50
+ });
51
+ await transaction.commit();
52
+ }
53
+ catch (error) {
54
+ await transaction.rollback();
55
+ throw new GeneralError("Error while refreshing delayed segments", this.constructor.name, error);
56
+ }
57
+ }
58
+ /**
59
+ * All attributes except created_at, which keeps the time the segment was first detected
60
+ */
61
+ getUpdateAttributes() {
62
+ const attributes = Object.keys(JISDelayedSegmentsModel.attributeModel);
63
+ return attributes.filter((attribute) => attribute !== "created_at");
64
+ }
65
+ };
66
+ JISDelayedSegmentsRepository = __decorate([
67
+ injectable(),
68
+ __param(0, inject(CoreToken.PostgresConnector)),
69
+ __param(1, inject(CoreToken.Logger)),
70
+ __metadata("design:paramtypes", [Object, Object])
71
+ ], JISDelayedSegmentsRepository);
72
+ export { JISDelayedSegmentsRepository };
73
+ //# sourceMappingURL=JISDelayedSegmentsRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JISDelayedSegmentsRepository.js","sourceRoot":"","sources":["../../../../src/integration-engine/jis/repositories/JISDelayedSegmentsRepository.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAGlF,OAAO,EAAE,6BAA6B,EAAE,MAAM,+FAA+F,CAAC;AAC9I,OAAO,EAAE,SAAS,EAAE,MAAM,6CAA6C,CAAC;AACxE,OAAO,iBAAiB,MAAM,6CAA6C,CAAC;AAC5E,MAAM,EAAE,YAAY,EAAE,GAAG,iBAAiB,CAAC;AAE3C,OAAO,oBAAoB,MAAM,gDAAgD,CAAC;AAClF,MAAM,EAAE,mBAAmB,EAAE,GAAG,oBAAoB,CAAC;AAErD,OAAO,aAAa,MAAM,wCAAwC,CAAC;AACnE,MAAM,EAAE,EAAE,EAAE,GAAG,aAAa,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,uCAAuC,CAAC;AAGpE,IAAM,4BAA4B,GAAlC,MAAM,4BAA6B,SAAQ,6BAA6B;IAM3E,YAAiD,SAA6B,EAA4B,MAAe;QACrH,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QALtB,WAAM,GAAG,SAAS,CAAC;QACnB,cAAS,GAAG,uBAAuB,CAAC,SAAS,CAAC;QAKjD,IAAI,CAAC,SAAS,GAAG,IAAI,mBAAmB,CAAC,8BAA8B,EAAE,uBAAuB,CAAC,UAAU,CAAC,CAAC;QAC7G,IAAI,CAAC,cAAc,GAAG,SAAS;aAC1B,aAAa,EAAE;aACf,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,uBAAuB,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjG,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,WAAW,CAAC,QAA8B,EAAE,UAAgB;QACrE,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,CAAC,WAAW,EAAE,CAAC;QAEvE,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;YACzG,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,EAAE;gBACvC,iBAAiB,EAAE,IAAI,CAAC,mBAAmB,EAAE;gBAC7C,WAAW;aACd,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;gBAC9B,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE;gBAC9C,WAAW;aACd,CAAC,CAAC;YAEH,MAAM,WAAW,CAAC,MAAM,EAAE,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,YAAY,CAAC,yCAAyC,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACpG,CAAC;IACL,CAAC;IAED;;OAEG;IACK,mBAAmB;QACvB,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAEpE,CAAC;QACF,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,KAAK,YAAY,CAAC,CAAC;IACxE,CAAC;CACJ,CAAA;AAjDY,4BAA4B;IADxC,UAAU,EAAE;IAOI,WAAA,MAAM,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;IAAiC,WAAA,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;;GANhG,4BAA4B,CAiDxC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Enriches delayed segments given by the ASW ids of their endpoints with their stops, affected routes and info panel
3
+ * stops. All segments of a refresh are enriched by one query, so that planning and lookups shared by the segments
4
+ * are done only once.
5
+ *
6
+ * Replacements: segments (JSON array of objects with segment_index, from_node_id, from_stop_id, to_node_id and
7
+ * to_stop_id), windowStart, windowEnd (departures taken into account) and maxInfoPanelDistanceStops (max distance
8
+ * of an info panel stop from the segment).
9
+ *
10
+ * Returns one row per segment, ordered by segment_index. Stops are matched by ASW id rather than by GTFS stop_id,
11
+ * since one physical stop can have several GTFS stop ids (zone and temporary variants).
12
+ */
13
+ export declare const JIS_DELAYED_SEGMENT_ENRICHMENT_QUERY = "\nWITH\nsegments AS MATERIALIZED (\n SELECT *\n FROM jsonb_to_recordset(CAST(:segments AS jsonb))\n AS segment(segment_index int, from_node_id int, from_stop_id int, to_node_id int, to_stop_id int)\n),\n-- GTFS stop ids of the segment endpoints, all variants of their ASW ids\nendpoint_stops AS MATERIALIZED (\n SELECT seg.segment_index, TRUE AS is_from, s.stop_id\n FROM segments seg\n JOIN pid.ropidgtfs_stops s ON s.asw_node_id = seg.from_node_id AND s.asw_stop_id = seg.from_stop_id\n UNION ALL\n SELECT seg.segment_index, FALSE AS is_from, s.stop_id\n FROM segments seg\n JOIN pid.ropidgtfs_stops s ON s.asw_node_id = seg.to_node_id AND s.asw_stop_id = seg.to_stop_id\n),\n-- Regular trips in the time window running the whole segment, they define which stops lie on the segment\nsegment_trips AS MATERIALIZED (\n SELECT DISTINCT\n from_stop.segment_index,\n d_from.trip_id,\n d_from.date,\n d_from.stop_sequence AS from_sequence,\n d_to.stop_sequence AS to_sequence\n FROM endpoint_stops from_stop\n JOIN pid.ropidgtfs_precomputed_departures d_from\n ON d_from.stop_id = from_stop.stop_id\n AND d_from.departure_datetime BETWEEN :windowStart AND :windowEnd\n JOIN pid.ropidgtfs_precomputed_departures d_to\n ON d_to.date = d_from.date\n AND d_to.trip_id = d_from.trip_id\n AND d_to.stop_sequence > d_from.stop_sequence\n JOIN endpoint_stops to_stop\n ON to_stop.segment_index = from_stop.segment_index\n AND NOT to_stop.is_from\n AND to_stop.stop_id = d_to.stop_id\n JOIN pid.ropidgtfs_trips t ON t.trip_id = d_from.trip_id AND t.exceptional = 0\n WHERE from_stop.is_from\n),\n-- Stops between the endpoints along each segment trip, with their position relative to the trip's run of the segment\n-- (0 at the start, 1 at the end), so that trips skipping stops agree on the positions of the stops they share\nsegment_trip_stops AS MATERIALIZED (\n SELECT\n st.segment_index,\n st.trip_id,\n st.date,\n d.stop_sequence,\n s.stop_id,\n s.stop_name,\n s.asw_node_id,\n s.asw_stop_id,\n CAST(d.stop_sequence - st.from_sequence AS numeric) / (st.to_sequence - st.from_sequence) AS relative_position\n FROM segment_trips st\n JOIN pid.ropidgtfs_precomputed_departures d\n ON d.date = st.date\n AND d.trip_id = st.trip_id\n AND d.stop_sequence BETWEEN st.from_sequence AND st.to_sequence\n JOIN pid.ropidgtfs_stops s ON s.stop_id = d.stop_id\n WHERE s.asw_node_id IS NOT NULL\n),\n-- One row per segment and physical stop (ASW id), positioned for ordering the stops along the segment. The stop is\n-- represented by the first of all its GTFS stop id variants rather than of those served in the time window, so that\n-- the id stays the same between refreshes, which consumers match segments by.\nsegment_stops AS MATERIALIZED (\n SELECT\n trip_stops.segment_index,\n trip_stops.asw_node_id,\n trip_stops.asw_stop_id,\n MIN(s.stop_id) AS stop_id,\n (ARRAY_AGG(s.stop_name ORDER BY s.stop_id))[1] AS stop_name,\n trip_stops.position\n FROM (\n SELECT segment_index, asw_node_id, asw_stop_id, MIN(relative_position) AS position\n FROM segment_trip_stops\n GROUP BY segment_index, asw_node_id, asw_stop_id\n ) trip_stops\n JOIN pid.ropidgtfs_stops s ON s.asw_node_id = trip_stops.asw_node_id AND s.asw_stop_id = trip_stops.asw_stop_id\n GROUP BY trip_stops.segment_index, trip_stops.asw_node_id, trip_stops.asw_stop_id, trip_stops.position\n),\n-- Ordered pairs of segment stops (\"earlier>later\" by ASW id) visited in this order by some segment trip. Unlike\n-- comparing positions, this holds for any mix of stopping patterns, e.g. a stop placed differently by two branches.\nsegment_stop_precedences AS MATERIALIZED (\n SELECT segment_index, jsonb_object_agg(stop_pair, TRUE) AS precedences\n FROM (\n SELECT DISTINCT pattern.segment_index, earlier.stop_key || '>' || later.stop_key AS stop_pair\n FROM (\n SELECT DISTINCT segment_index, ARRAY_AGG(asw_node_id || '/' || asw_stop_id ORDER BY stop_sequence) AS stop_keys\n FROM segment_trip_stops\n GROUP BY segment_index, trip_id, date\n ) pattern\n CROSS JOIN LATERAL unnest(pattern.stop_keys) WITH ORDINALITY AS earlier(stop_key, stop_order)\n JOIN LATERAL unnest(pattern.stop_keys) WITH ORDINALITY AS later(stop_key, stop_order)\n ON later.stop_order > earlier.stop_order\n ) stop_pairs\n GROUP BY segment_index\n),\n-- All GTFS stop ids of the segment stops, so that trips using any variant are recognized\nsegment_stop_variants AS MATERIALIZED (\n SELECT ss.segment_index, s.stop_id, ss.asw_node_id || '/' || ss.asw_stop_id AS stop_key\n FROM segment_stops ss\n JOIN pid.ropidgtfs_stops s ON s.asw_node_id = ss.asw_node_id AND s.asw_stop_id = ss.asw_stop_id\n),\n-- Departures from the segment stops in the time window, fetched once per stop. Otherwise the planner fetches them\n-- again for every pair of segment stops.\nsegment_stop_departures AS MATERIALIZED (\n SELECT d.trip_id, d.date, d.route_id, d.route_short_name, d.route_type, d.stop_id, d.next_stop_id\n FROM pid.ropidgtfs_precomputed_departures d\n WHERE d.stop_id IN (SELECT stop_id FROM segment_stop_variants)\n AND d.route_id IS NOT NULL\n AND d.departure_datetime BETWEEN :windowStart AND :windowEnd\n),\n-- ASW ids of the segment stop variants and the stop precedences per segment. Looking the stops up in these small maps\n-- instead of joining them keeps the query fast, as the planner heavily underestimates the row counts of the CTEs and\n-- picks nested loops.\nsegment_lookups AS MATERIALIZED (\n SELECT variants.segment_index, variants.stop_keys, ssp.precedences\n FROM (\n SELECT segment_index, jsonb_object_agg(stop_id, stop_key) AS stop_keys\n FROM segment_stop_variants\n GROUP BY segment_index\n ) variants\n JOIN segment_stop_precedences ssp ON ssp.segment_index = variants.segment_index\n),\n-- Trips in the time window travelling at least one hop between two segment stops in the segment direction,\n-- unlike segment trips they may join or leave the segment in its middle\naffected_trip_candidates AS MATERIALIZED (\n SELECT DISTINCT current_stop.segment_index, d.trip_id, d.date, d.route_id, d.route_short_name, d.route_type\n FROM segment_stop_departures d\n JOIN segment_stop_variants current_stop ON current_stop.stop_id = d.stop_id\n JOIN segment_lookups sl ON sl.segment_index = current_stop.segment_index\n WHERE (sl.precedences -> (current_stop.stop_key || '>' || (sl.stop_keys ->> d.next_stop_id))) IS NOT NULL\n),\n-- Regular affected trips, with the segment stops for the lookups below\naffected_trips AS MATERIALIZED (\n SELECT atc.*, sl.stop_keys AS segment_stop_keys\n FROM affected_trip_candidates atc\n JOIN segment_lookups sl ON sl.segment_index = atc.segment_index\n WHERE EXISTS (SELECT 1 FROM pid.ropidgtfs_trips t WHERE t.trip_id = atc.trip_id AND t.exceptional = 0)\n),\n-- Stops with a departure preset (info panels) by each GTFS stop id variant of their ASW id. Links to presets that\n-- no longer exist are ignored (the tables are refreshed separately and have no foreign key).\npreset_stop_variants AS MATERIALIZED (\n SELECT DISTINCT variant.stop_id AS variant_stop_id, preset_stop.stop_id AS preset_stop_id\n FROM pid.ropid_departures_presets_ropidgtfs_stops ps\n JOIN pid.ropid_departures_presets p ON p.id = ps.preset_id\n JOIN pid.ropidgtfs_stops preset_stop ON preset_stop.stop_id = ps.stop_id\n JOIN pid.ropidgtfs_stops variant\n ON variant.asw_node_id = preset_stop.asw_node_id AND variant.asw_stop_id = preset_stop.asw_stop_id\n),\n-- First and last stop sequence of each affected trip on the segment.\n-- Distances in stops are computed as differences of stop sequences, which relies on the stop sequences of a trip\n-- being consecutive (true for all PID trips). Ranking all stops of all affected trips instead takes most of the\n-- query time. Should there be gaps, a distance would grow by the gap size.\naffected_trip_bounds AS MATERIALIZED (\n SELECT\n at.segment_index,\n at.trip_id,\n at.date,\n at.route_id,\n MIN(d.stop_sequence) AS first_sequence,\n MAX(d.stop_sequence) AS last_sequence\n FROM affected_trips at\n JOIN pid.ropidgtfs_precomputed_departures d ON d.date = at.date AND d.trip_id = at.trip_id\n WHERE (at.segment_stop_keys -> d.stop_id) IS NOT NULL\n GROUP BY at.segment_index, at.trip_id, at.date, at.route_id\n),\n-- Distance of each preset stop from the segment per affected route, up to the max distance: negative before\n-- the segment, 0 on it, positive after it. Of the distances on different trips of a route, the closest is kept.\npreset_stop_distances AS MATERIALIZED (\n SELECT DISTINCT ON (segment_index, stop_id, route_id) segment_index, stop_id, route_id, distance\n FROM (\n SELECT\n atb.segment_index,\n preset_stop.preset_stop_id AS stop_id,\n atb.route_id,\n CASE\n WHEN d.stop_sequence < atb.first_sequence THEN d.stop_sequence - atb.first_sequence\n WHEN d.stop_sequence > atb.last_sequence THEN d.stop_sequence - atb.last_sequence\n ELSE 0\n END AS distance\n FROM affected_trip_bounds atb\n JOIN pid.ropidgtfs_precomputed_departures d\n ON d.date = atb.date\n AND d.trip_id = atb.trip_id\n AND d.stop_sequence BETWEEN atb.first_sequence - :maxInfoPanelDistanceStops\n AND atb.last_sequence + :maxInfoPanelDistanceStops\n JOIN preset_stop_variants preset_stop ON preset_stop.variant_stop_id = d.stop_id\n ) distances\n ORDER BY segment_index, stop_id, route_id, ABS(distance), distance\n)\n-- One row per segment with the enrichment as JSON: endpoints, ordered segment stops, affected routes and info panel stops\nSELECT\n (\n SELECT COUNT(DISTINCT es.is_from) = 2\n FROM endpoint_stops es\n WHERE es.segment_index = seg.segment_index\n ) AS has_endpoint_stops,\n (\n SELECT json_build_object('id', ss.stop_id, 'name', ss.stop_name)\n FROM segment_stops ss\n WHERE ss.segment_index = seg.segment_index\n AND ss.asw_node_id = seg.from_node_id\n AND ss.asw_stop_id = seg.from_stop_id\n ) AS from_stop,\n (\n SELECT json_build_object('id', ss.stop_id, 'name', ss.stop_name)\n FROM segment_stops ss\n WHERE ss.segment_index = seg.segment_index\n AND ss.asw_node_id = seg.to_node_id\n AND ss.asw_stop_id = seg.to_stop_id\n ) AS to_stop,\n (\n SELECT COALESCE(json_agg(\n json_build_object('id', ss.stop_id, 'name', ss.stop_name) ORDER BY ss.position, ss.stop_id\n ), '[]'::json)\n FROM segment_stops ss\n WHERE ss.segment_index = seg.segment_index\n ) AS stops,\n (\n SELECT COALESCE(json_agg(json_build_object(\n 'id', r.route_id,\n 'short_name', r.route_short_name,\n 'type', r.route_type\n ) ORDER BY r.route_id), '[]'::json)\n FROM (\n SELECT DISTINCT route_id, route_short_name, route_type\n FROM affected_trips\n WHERE segment_index = seg.segment_index\n ) r\n ) AS routes,\n (\n SELECT COALESCE(json_agg(json_build_object(\n 'stop', json_build_object('id', s.stop_id, 'name', s.stop_name),\n 'preset_route_names', (\n SELECT json_agg(DISTINCT p.route_name ORDER BY p.route_name)\n FROM pid.ropid_departures_presets_ropidgtfs_stops ps\n JOIN pid.ropid_departures_presets p ON p.id = ps.preset_id\n WHERE ps.stop_id = s.stop_id\n ),\n 'routes', (\n SELECT json_agg(json_build_object('id', psd.route_id, 'distance_stops', psd.distance) ORDER BY psd.route_id)\n FROM preset_stop_distances psd\n WHERE psd.segment_index = seg.segment_index AND psd.stop_id = s.stop_id\n )\n ) ORDER BY s.stop_id), '[]'::json)\n FROM pid.ropidgtfs_stops s\n WHERE s.stop_id IN (SELECT psd.stop_id FROM preset_stop_distances psd WHERE psd.segment_index = seg.segment_index)\n ) AS info_panel_stops\nFROM segments seg\nORDER BY seg.segment_index\n";
@@ -0,0 +1,266 @@
1
+ import { PG_SCHEMA } from "#sch/const";
2
+ /**
3
+ * Enriches delayed segments given by the ASW ids of their endpoints with their stops, affected routes and info panel
4
+ * stops. All segments of a refresh are enriched by one query, so that planning and lookups shared by the segments
5
+ * are done only once.
6
+ *
7
+ * Replacements: segments (JSON array of objects with segment_index, from_node_id, from_stop_id, to_node_id and
8
+ * to_stop_id), windowStart, windowEnd (departures taken into account) and maxInfoPanelDistanceStops (max distance
9
+ * of an info panel stop from the segment).
10
+ *
11
+ * Returns one row per segment, ordered by segment_index. Stops are matched by ASW id rather than by GTFS stop_id,
12
+ * since one physical stop can have several GTFS stop ids (zone and temporary variants).
13
+ */
14
+ export const JIS_DELAYED_SEGMENT_ENRICHMENT_QUERY = `
15
+ WITH
16
+ segments AS MATERIALIZED (
17
+ SELECT *
18
+ FROM jsonb_to_recordset(CAST(:segments AS jsonb))
19
+ AS segment(segment_index int, from_node_id int, from_stop_id int, to_node_id int, to_stop_id int)
20
+ ),
21
+ -- GTFS stop ids of the segment endpoints, all variants of their ASW ids
22
+ endpoint_stops AS MATERIALIZED (
23
+ SELECT seg.segment_index, TRUE AS is_from, s.stop_id
24
+ FROM segments seg
25
+ JOIN ${PG_SCHEMA}.ropidgtfs_stops s ON s.asw_node_id = seg.from_node_id AND s.asw_stop_id = seg.from_stop_id
26
+ UNION ALL
27
+ SELECT seg.segment_index, FALSE AS is_from, s.stop_id
28
+ FROM segments seg
29
+ JOIN ${PG_SCHEMA}.ropidgtfs_stops s ON s.asw_node_id = seg.to_node_id AND s.asw_stop_id = seg.to_stop_id
30
+ ),
31
+ -- Regular trips in the time window running the whole segment, they define which stops lie on the segment
32
+ segment_trips AS MATERIALIZED (
33
+ SELECT DISTINCT
34
+ from_stop.segment_index,
35
+ d_from.trip_id,
36
+ d_from.date,
37
+ d_from.stop_sequence AS from_sequence,
38
+ d_to.stop_sequence AS to_sequence
39
+ FROM endpoint_stops from_stop
40
+ JOIN ${PG_SCHEMA}.ropidgtfs_precomputed_departures d_from
41
+ ON d_from.stop_id = from_stop.stop_id
42
+ AND d_from.departure_datetime BETWEEN :windowStart AND :windowEnd
43
+ JOIN ${PG_SCHEMA}.ropidgtfs_precomputed_departures d_to
44
+ ON d_to.date = d_from.date
45
+ AND d_to.trip_id = d_from.trip_id
46
+ AND d_to.stop_sequence > d_from.stop_sequence
47
+ JOIN endpoint_stops to_stop
48
+ ON to_stop.segment_index = from_stop.segment_index
49
+ AND NOT to_stop.is_from
50
+ AND to_stop.stop_id = d_to.stop_id
51
+ JOIN ${PG_SCHEMA}.ropidgtfs_trips t ON t.trip_id = d_from.trip_id AND t.exceptional = 0
52
+ WHERE from_stop.is_from
53
+ ),
54
+ -- Stops between the endpoints along each segment trip, with their position relative to the trip's run of the segment
55
+ -- (0 at the start, 1 at the end), so that trips skipping stops agree on the positions of the stops they share
56
+ segment_trip_stops AS MATERIALIZED (
57
+ SELECT
58
+ st.segment_index,
59
+ st.trip_id,
60
+ st.date,
61
+ d.stop_sequence,
62
+ s.stop_id,
63
+ s.stop_name,
64
+ s.asw_node_id,
65
+ s.asw_stop_id,
66
+ CAST(d.stop_sequence - st.from_sequence AS numeric) / (st.to_sequence - st.from_sequence) AS relative_position
67
+ FROM segment_trips st
68
+ JOIN ${PG_SCHEMA}.ropidgtfs_precomputed_departures d
69
+ ON d.date = st.date
70
+ AND d.trip_id = st.trip_id
71
+ AND d.stop_sequence BETWEEN st.from_sequence AND st.to_sequence
72
+ JOIN ${PG_SCHEMA}.ropidgtfs_stops s ON s.stop_id = d.stop_id
73
+ WHERE s.asw_node_id IS NOT NULL
74
+ ),
75
+ -- One row per segment and physical stop (ASW id), positioned for ordering the stops along the segment. The stop is
76
+ -- represented by the first of all its GTFS stop id variants rather than of those served in the time window, so that
77
+ -- the id stays the same between refreshes, which consumers match segments by.
78
+ segment_stops AS MATERIALIZED (
79
+ SELECT
80
+ trip_stops.segment_index,
81
+ trip_stops.asw_node_id,
82
+ trip_stops.asw_stop_id,
83
+ MIN(s.stop_id) AS stop_id,
84
+ (ARRAY_AGG(s.stop_name ORDER BY s.stop_id))[1] AS stop_name,
85
+ trip_stops.position
86
+ FROM (
87
+ SELECT segment_index, asw_node_id, asw_stop_id, MIN(relative_position) AS position
88
+ FROM segment_trip_stops
89
+ GROUP BY segment_index, asw_node_id, asw_stop_id
90
+ ) trip_stops
91
+ JOIN ${PG_SCHEMA}.ropidgtfs_stops s ON s.asw_node_id = trip_stops.asw_node_id AND s.asw_stop_id = trip_stops.asw_stop_id
92
+ GROUP BY trip_stops.segment_index, trip_stops.asw_node_id, trip_stops.asw_stop_id, trip_stops.position
93
+ ),
94
+ -- Ordered pairs of segment stops ("earlier>later" by ASW id) visited in this order by some segment trip. Unlike
95
+ -- comparing positions, this holds for any mix of stopping patterns, e.g. a stop placed differently by two branches.
96
+ segment_stop_precedences AS MATERIALIZED (
97
+ SELECT segment_index, jsonb_object_agg(stop_pair, TRUE) AS precedences
98
+ FROM (
99
+ SELECT DISTINCT pattern.segment_index, earlier.stop_key || '>' || later.stop_key AS stop_pair
100
+ FROM (
101
+ SELECT DISTINCT segment_index, ARRAY_AGG(asw_node_id || '/' || asw_stop_id ORDER BY stop_sequence) AS stop_keys
102
+ FROM segment_trip_stops
103
+ GROUP BY segment_index, trip_id, date
104
+ ) pattern
105
+ CROSS JOIN LATERAL unnest(pattern.stop_keys) WITH ORDINALITY AS earlier(stop_key, stop_order)
106
+ JOIN LATERAL unnest(pattern.stop_keys) WITH ORDINALITY AS later(stop_key, stop_order)
107
+ ON later.stop_order > earlier.stop_order
108
+ ) stop_pairs
109
+ GROUP BY segment_index
110
+ ),
111
+ -- All GTFS stop ids of the segment stops, so that trips using any variant are recognized
112
+ segment_stop_variants AS MATERIALIZED (
113
+ SELECT ss.segment_index, s.stop_id, ss.asw_node_id || '/' || ss.asw_stop_id AS stop_key
114
+ FROM segment_stops ss
115
+ JOIN ${PG_SCHEMA}.ropidgtfs_stops s ON s.asw_node_id = ss.asw_node_id AND s.asw_stop_id = ss.asw_stop_id
116
+ ),
117
+ -- Departures from the segment stops in the time window, fetched once per stop. Otherwise the planner fetches them
118
+ -- again for every pair of segment stops.
119
+ segment_stop_departures AS MATERIALIZED (
120
+ SELECT d.trip_id, d.date, d.route_id, d.route_short_name, d.route_type, d.stop_id, d.next_stop_id
121
+ FROM ${PG_SCHEMA}.ropidgtfs_precomputed_departures d
122
+ WHERE d.stop_id IN (SELECT stop_id FROM segment_stop_variants)
123
+ AND d.route_id IS NOT NULL
124
+ AND d.departure_datetime BETWEEN :windowStart AND :windowEnd
125
+ ),
126
+ -- ASW ids of the segment stop variants and the stop precedences per segment. Looking the stops up in these small maps
127
+ -- instead of joining them keeps the query fast, as the planner heavily underestimates the row counts of the CTEs and
128
+ -- picks nested loops.
129
+ segment_lookups AS MATERIALIZED (
130
+ SELECT variants.segment_index, variants.stop_keys, ssp.precedences
131
+ FROM (
132
+ SELECT segment_index, jsonb_object_agg(stop_id, stop_key) AS stop_keys
133
+ FROM segment_stop_variants
134
+ GROUP BY segment_index
135
+ ) variants
136
+ JOIN segment_stop_precedences ssp ON ssp.segment_index = variants.segment_index
137
+ ),
138
+ -- Trips in the time window travelling at least one hop between two segment stops in the segment direction,
139
+ -- unlike segment trips they may join or leave the segment in its middle
140
+ affected_trip_candidates AS MATERIALIZED (
141
+ SELECT DISTINCT current_stop.segment_index, d.trip_id, d.date, d.route_id, d.route_short_name, d.route_type
142
+ FROM segment_stop_departures d
143
+ JOIN segment_stop_variants current_stop ON current_stop.stop_id = d.stop_id
144
+ JOIN segment_lookups sl ON sl.segment_index = current_stop.segment_index
145
+ WHERE (sl.precedences -> (current_stop.stop_key || '>' || (sl.stop_keys ->> d.next_stop_id))) IS NOT NULL
146
+ ),
147
+ -- Regular affected trips, with the segment stops for the lookups below
148
+ affected_trips AS MATERIALIZED (
149
+ SELECT atc.*, sl.stop_keys AS segment_stop_keys
150
+ FROM affected_trip_candidates atc
151
+ JOIN segment_lookups sl ON sl.segment_index = atc.segment_index
152
+ WHERE EXISTS (SELECT 1 FROM ${PG_SCHEMA}.ropidgtfs_trips t WHERE t.trip_id = atc.trip_id AND t.exceptional = 0)
153
+ ),
154
+ -- Stops with a departure preset (info panels) by each GTFS stop id variant of their ASW id. Links to presets that
155
+ -- no longer exist are ignored (the tables are refreshed separately and have no foreign key).
156
+ preset_stop_variants AS MATERIALIZED (
157
+ SELECT DISTINCT variant.stop_id AS variant_stop_id, preset_stop.stop_id AS preset_stop_id
158
+ FROM ${PG_SCHEMA}.ropid_departures_presets_ropidgtfs_stops ps
159
+ JOIN ${PG_SCHEMA}.ropid_departures_presets p ON p.id = ps.preset_id
160
+ JOIN ${PG_SCHEMA}.ropidgtfs_stops preset_stop ON preset_stop.stop_id = ps.stop_id
161
+ JOIN ${PG_SCHEMA}.ropidgtfs_stops variant
162
+ ON variant.asw_node_id = preset_stop.asw_node_id AND variant.asw_stop_id = preset_stop.asw_stop_id
163
+ ),
164
+ -- First and last stop sequence of each affected trip on the segment.
165
+ -- Distances in stops are computed as differences of stop sequences, which relies on the stop sequences of a trip
166
+ -- being consecutive (true for all PID trips). Ranking all stops of all affected trips instead takes most of the
167
+ -- query time. Should there be gaps, a distance would grow by the gap size.
168
+ affected_trip_bounds AS MATERIALIZED (
169
+ SELECT
170
+ at.segment_index,
171
+ at.trip_id,
172
+ at.date,
173
+ at.route_id,
174
+ MIN(d.stop_sequence) AS first_sequence,
175
+ MAX(d.stop_sequence) AS last_sequence
176
+ FROM affected_trips at
177
+ JOIN ${PG_SCHEMA}.ropidgtfs_precomputed_departures d ON d.date = at.date AND d.trip_id = at.trip_id
178
+ WHERE (at.segment_stop_keys -> d.stop_id) IS NOT NULL
179
+ GROUP BY at.segment_index, at.trip_id, at.date, at.route_id
180
+ ),
181
+ -- Distance of each preset stop from the segment per affected route, up to the max distance: negative before
182
+ -- the segment, 0 on it, positive after it. Of the distances on different trips of a route, the closest is kept.
183
+ preset_stop_distances AS MATERIALIZED (
184
+ SELECT DISTINCT ON (segment_index, stop_id, route_id) segment_index, stop_id, route_id, distance
185
+ FROM (
186
+ SELECT
187
+ atb.segment_index,
188
+ preset_stop.preset_stop_id AS stop_id,
189
+ atb.route_id,
190
+ CASE
191
+ WHEN d.stop_sequence < atb.first_sequence THEN d.stop_sequence - atb.first_sequence
192
+ WHEN d.stop_sequence > atb.last_sequence THEN d.stop_sequence - atb.last_sequence
193
+ ELSE 0
194
+ END AS distance
195
+ FROM affected_trip_bounds atb
196
+ JOIN ${PG_SCHEMA}.ropidgtfs_precomputed_departures d
197
+ ON d.date = atb.date
198
+ AND d.trip_id = atb.trip_id
199
+ AND d.stop_sequence BETWEEN atb.first_sequence - :maxInfoPanelDistanceStops
200
+ AND atb.last_sequence + :maxInfoPanelDistanceStops
201
+ JOIN preset_stop_variants preset_stop ON preset_stop.variant_stop_id = d.stop_id
202
+ ) distances
203
+ ORDER BY segment_index, stop_id, route_id, ABS(distance), distance
204
+ )
205
+ -- One row per segment with the enrichment as JSON: endpoints, ordered segment stops, affected routes and info panel stops
206
+ SELECT
207
+ (
208
+ SELECT COUNT(DISTINCT es.is_from) = 2
209
+ FROM endpoint_stops es
210
+ WHERE es.segment_index = seg.segment_index
211
+ ) AS has_endpoint_stops,
212
+ (
213
+ SELECT json_build_object('id', ss.stop_id, 'name', ss.stop_name)
214
+ FROM segment_stops ss
215
+ WHERE ss.segment_index = seg.segment_index
216
+ AND ss.asw_node_id = seg.from_node_id
217
+ AND ss.asw_stop_id = seg.from_stop_id
218
+ ) AS from_stop,
219
+ (
220
+ SELECT json_build_object('id', ss.stop_id, 'name', ss.stop_name)
221
+ FROM segment_stops ss
222
+ WHERE ss.segment_index = seg.segment_index
223
+ AND ss.asw_node_id = seg.to_node_id
224
+ AND ss.asw_stop_id = seg.to_stop_id
225
+ ) AS to_stop,
226
+ (
227
+ SELECT COALESCE(json_agg(
228
+ json_build_object('id', ss.stop_id, 'name', ss.stop_name) ORDER BY ss.position, ss.stop_id
229
+ ), '[]'::json)
230
+ FROM segment_stops ss
231
+ WHERE ss.segment_index = seg.segment_index
232
+ ) AS stops,
233
+ (
234
+ SELECT COALESCE(json_agg(json_build_object(
235
+ 'id', r.route_id,
236
+ 'short_name', r.route_short_name,
237
+ 'type', r.route_type
238
+ ) ORDER BY r.route_id), '[]'::json)
239
+ FROM (
240
+ SELECT DISTINCT route_id, route_short_name, route_type
241
+ FROM affected_trips
242
+ WHERE segment_index = seg.segment_index
243
+ ) r
244
+ ) AS routes,
245
+ (
246
+ SELECT COALESCE(json_agg(json_build_object(
247
+ 'stop', json_build_object('id', s.stop_id, 'name', s.stop_name),
248
+ 'preset_route_names', (
249
+ SELECT json_agg(DISTINCT p.route_name ORDER BY p.route_name)
250
+ FROM ${PG_SCHEMA}.ropid_departures_presets_ropidgtfs_stops ps
251
+ JOIN ${PG_SCHEMA}.ropid_departures_presets p ON p.id = ps.preset_id
252
+ WHERE ps.stop_id = s.stop_id
253
+ ),
254
+ 'routes', (
255
+ SELECT json_agg(json_build_object('id', psd.route_id, 'distance_stops', psd.distance) ORDER BY psd.route_id)
256
+ FROM preset_stop_distances psd
257
+ WHERE psd.segment_index = seg.segment_index AND psd.stop_id = s.stop_id
258
+ )
259
+ ) ORDER BY s.stop_id), '[]'::json)
260
+ FROM ${PG_SCHEMA}.ropidgtfs_stops s
261
+ WHERE s.stop_id IN (SELECT psd.stop_id FROM preset_stop_distances psd WHERE psd.segment_index = seg.segment_index)
262
+ ) AS info_panel_stops
263
+ FROM segments seg
264
+ ORDER BY seg.segment_index
265
+ `;
266
+ //# sourceMappingURL=JISDelayedSegmentEnrichmentQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JISDelayedSegmentEnrichmentQuery.js","sourceRoot":"","sources":["../../../../../src/integration-engine/jis/repositories/helpers/JISDelayedSegmentEnrichmentQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oCAAoC,GAAG;;;;;;;;;;;WAWzC,SAAS;;;;WAIT,SAAS;;;;;;;;;;;WAWT,SAAS;;;WAGT,SAAS;;;;;;;;WAQT,SAAS;;;;;;;;;;;;;;;;;WAiBT,SAAS;;;;WAIT,SAAS;;;;;;;;;;;;;;;;;;;WAmBT,SAAS;;;;;;;;;;;;;;;;;;;;;;;;WAwBT,SAAS;;;;;;WAMT,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCA+Bc,SAAS;;;;;;WAMhC,SAAS;WACT,SAAS;WACT,SAAS;WACT,SAAS;;;;;;;;;;;;;;;;WAgBT,SAAS;;;;;;;;;;;;;;;;;;;eAmBL,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAsDD,SAAS;uBACT,SAAS;;;;;;;;;eASjB,SAAS;;;;;CAKvB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ASW id of a stop: node (uzel) and stop (sloupek)
3
+ */
4
+ export interface IAswStopId {
5
+ nodeId: number;
6
+ stopId: number;
7
+ }
8
+ /**
9
+ * Endpoints of a delayed segment as ASW ids
10
+ */
11
+ export interface IAswSegmentEndpoints {
12
+ from: IAswStopId;
13
+ to: IAswStopId;
14
+ }
15
+ /**
16
+ * Delayed segments to enrich and the parameters of the enrichment
17
+ */
18
+ export interface IJISDelayedSegmentEnrichmentParams {
19
+ segments: IAswSegmentEndpoints[];
20
+ windowStart: Date;
21
+ windowEnd: Date;
22
+ maxInfoPanelDistanceStops: number;
23
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IJISDelayedSegmentEnrichmentParams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IJISDelayedSegmentEnrichmentParams.js","sourceRoot":"","sources":["../../../../../src/integration-engine/jis/repositories/interfaces/IJISDelayedSegmentEnrichmentParams.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { JISDelayedSegmentEnrichmentRepository } from "#ie/jis/repositories/JISDelayedSegmentEnrichmentRepository";
2
+ import { JISDelayedSegmentsRepository } from "#ie/jis/repositories/JISDelayedSegmentsRepository";
3
+ import { type IMapaPidDelayShape } from "#sch/jis/datasources/interfaces/index";
4
+ import { type ISimpleConfig } from "@golemio/core/dist/helpers/configuration/ISimpleConfig.js";
5
+ import { type ILogger } from "@golemio/core/dist/helpers/index.js";
6
+ /**
7
+ * Turns delayed segments detected by Mapa PID into delayed segments enriched with GTFS data and stores them
8
+ */
9
+ export declare class JISDelayedSegmentsDataService {
10
+ private enrichmentRepository;
11
+ private delayedSegmentsRepository;
12
+ private logger;
13
+ private readonly minDelaySeconds;
14
+ private readonly routeTimeWindowMinutes;
15
+ private readonly maxInfoPanelDistanceStops;
16
+ constructor(enrichmentRepository: JISDelayedSegmentEnrichmentRepository, delayedSegmentsRepository: JISDelayedSegmentsRepository, config: ISimpleConfig, logger: ILogger);
17
+ /**
18
+ * Replaces stored delayed segments with the given detected ones. Segments below the minimum delay, segments whose
19
+ * endpoints cannot be mapped to GTFS stops, segments no trip runs as a whole and segments without any affected
20
+ * route are skipped and counted by reason.
21
+ */
22
+ refreshDelayedSegments(delayShapes: IMapaPidDelayShape[], detectedAt: Date): Promise<void>;
23
+ /**
24
+ * Significant delay shapes with endpoints mapped to ASW ids
25
+ */
26
+ private getCandidates;
27
+ /**
28
+ * Enrichments of the candidates by segment id, derived once per segment
29
+ */
30
+ private getEnrichments;
31
+ private parseAswStopId;
32
+ private isValidAswIdPart;
33
+ /**
34
+ * Skipped segments are logged only at the debug level, as they repeat every refresh while the delay lasts
35
+ */
36
+ private skip;
37
+ private toDelayedSegment;
38
+ }
@@ -0,0 +1,174 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { GtfsStopParser } from "#helpers/GtfsStopParser";
14
+ import { JISContainerToken } from "#ie/jis/ioc/JISContainerToken";
15
+ import { JISDelayedSegmentEnrichmentRepository } from "#ie/jis/repositories/JISDelayedSegmentEnrichmentRepository";
16
+ import { JISDelayedSegmentsRepository } from "#ie/jis/repositories/JISDelayedSegmentsRepository";
17
+ import { CoreToken } from "@golemio/core/dist/helpers/ioc/CoreToken.js";
18
+ import GolemioErrorsCore from "@golemio/core/dist/shared/golemio-errors.js";
19
+ const { GeneralError } = GolemioErrorsCore;
20
+ import { inject, injectable } from "@golemio/core/dist/shared/tsyringe.js";
21
+ import { PG_INT_MAX } from "../../../const.js";
22
+ const MINUTE_IN_MS = 60 * 1000;
23
+ var SkipReason;
24
+ (function (SkipReason) {
25
+ SkipReason["BelowMinimumDelay"] = "below minimum delay";
26
+ SkipReason["NoAswIds"] = "no ASW ids";
27
+ SkipReason["EndpointStopsNotInGtfs"] = "endpoint stops not in GTFS";
28
+ // Mapa PID merges adjacent delayed sections regardless of the lines running them
29
+ SkipReason["NoTripRunsWholeSegment"] = "no trip runs the whole segment";
30
+ SkipReason["NoAffectedRoutes"] = "no affected routes";
31
+ })(SkipReason || (SkipReason = {}));
32
+ /**
33
+ * Turns delayed segments detected by Mapa PID into delayed segments enriched with GTFS data and stores them
34
+ */
35
+ let JISDelayedSegmentsDataService = class JISDelayedSegmentsDataService {
36
+ constructor(enrichmentRepository, delayedSegmentsRepository, config, logger) {
37
+ this.enrichmentRepository = enrichmentRepository;
38
+ this.delayedSegmentsRepository = delayedSegmentsRepository;
39
+ this.logger = logger;
40
+ this.minDelaySeconds = Number(config.getValue("module.pid.jis.delayedSegments.minDelaySeconds", 300));
41
+ this.routeTimeWindowMinutes = Number(config.getValue("module.pid.jis.delayedSegments.routeTimeWindowMinutes", 120));
42
+ this.maxInfoPanelDistanceStops = Number(config.getValue("module.pid.jis.delayedSegments.maxInfoPanelDistanceStops", 5));
43
+ }
44
+ /**
45
+ * Replaces stored delayed segments with the given detected ones. Segments below the minimum delay, segments whose
46
+ * endpoints cannot be mapped to GTFS stops, segments no trip runs as a whole and segments without any affected
47
+ * route are skipped and counted by reason.
48
+ */
49
+ async refreshDelayedSegments(delayShapes, detectedAt) {
50
+ const skipCounts = new Map();
51
+ const candidates = this.getCandidates(delayShapes, skipCounts);
52
+ const enrichments = await this.getEnrichments(candidates, detectedAt);
53
+ const segments = new Map();
54
+ for (const candidate of candidates) {
55
+ const enrichment = enrichments.get(candidate.id);
56
+ if (!enrichment) {
57
+ throw new GeneralError(`Missing enrichment of delayed segment ${candidate.id}`, this.constructor.name);
58
+ }
59
+ const segment = this.toDelayedSegment(candidate, enrichment);
60
+ if (typeof segment === "string") {
61
+ this.skip(skipCounts, segment, candidate.id);
62
+ continue;
63
+ }
64
+ // stop id variants of one physical stop may result in duplicate segments, the biggest delay wins
65
+ const duplicate = segments.get(segment.id);
66
+ if (!duplicate || duplicate.expected_delay_seconds < segment.expected_delay_seconds) {
67
+ segments.set(segment.id, segment);
68
+ }
69
+ }
70
+ await this.delayedSegmentsRepository.refreshData(Array.from(segments.values()), detectedAt);
71
+ const skipped = Array.from(skipCounts, ([reason, count]) => `${reason} ${count}`).join(", ") || "none";
72
+ this.logger.info(`${this.constructor.name}: stored ${segments.size} of ${delayShapes.length} delayed segments, skipped: ${skipped}.`);
73
+ }
74
+ /**
75
+ * Significant delay shapes with endpoints mapped to ASW ids
76
+ */
77
+ getCandidates(delayShapes, skipCounts) {
78
+ const candidates = [];
79
+ for (const delayShape of delayShapes) {
80
+ if (delayShape.expected_delay < this.minDelaySeconds) {
81
+ skipCounts.set(SkipReason.BelowMinimumDelay, (skipCounts.get(SkipReason.BelowMinimumDelay) ?? 0) + 1);
82
+ continue;
83
+ }
84
+ const from = this.parseAswStopId(delayShape.from_stop_id);
85
+ const to = this.parseAswStopId(delayShape.to_stop_id);
86
+ if (!from || !to) {
87
+ this.skip(skipCounts, SkipReason.NoAswIds, `${delayShape.from_stop_id} -> ${delayShape.to_stop_id}`);
88
+ continue;
89
+ }
90
+ candidates.push({
91
+ id: `${from.nodeId}/${from.stopId}-${to.nodeId}/${to.stopId}`,
92
+ delayShape,
93
+ endpoints: { from, to },
94
+ });
95
+ }
96
+ return candidates;
97
+ }
98
+ /**
99
+ * Enrichments of the candidates by segment id, derived once per segment
100
+ */
101
+ async getEnrichments(candidates, detectedAt) {
102
+ const endpointsToEnrich = new Map();
103
+ for (const candidate of candidates) {
104
+ endpointsToEnrich.set(candidate.id, candidate.endpoints);
105
+ }
106
+ const windowInMs = this.routeTimeWindowMinutes * MINUTE_IN_MS;
107
+ const newEnrichments = await this.enrichmentRepository.getEnrichments({
108
+ segments: Array.from(endpointsToEnrich.values()),
109
+ windowStart: new Date(detectedAt.getTime() - windowInMs),
110
+ windowEnd: new Date(detectedAt.getTime() + windowInMs),
111
+ maxInfoPanelDistanceStops: this.maxInfoPanelDistanceStops,
112
+ });
113
+ const enrichments = new Map();
114
+ for (const [index, id] of Array.from(endpointsToEnrich.keys()).entries()) {
115
+ const enrichment = newEnrichments[index];
116
+ if (enrichment) {
117
+ enrichments.set(id, enrichment);
118
+ }
119
+ }
120
+ return enrichments;
121
+ }
122
+ parseAswStopId(stopId) {
123
+ const [nodeId, aswStopId] = GtfsStopParser.normalizedStopId(stopId).split("/").map(Number);
124
+ if (!this.isValidAswIdPart(nodeId) || !this.isValidAswIdPart(aswStopId)) {
125
+ return null;
126
+ }
127
+ return { nodeId, stopId: aswStopId };
128
+ }
129
+ isValidAswIdPart(value) {
130
+ return value !== undefined && Number.isInteger(value) && value >= 0 && value <= PG_INT_MAX;
131
+ }
132
+ /**
133
+ * Skipped segments are logged only at the debug level, as they repeat every refresh while the delay lasts
134
+ */
135
+ skip(skipCounts, reason, segment) {
136
+ skipCounts.set(reason, (skipCounts.get(reason) ?? 0) + 1);
137
+ this.logger.debug(`${this.constructor.name}: skipping segment ${segment} (${reason}).`);
138
+ }
139
+ toDelayedSegment(candidate, enrichment) {
140
+ if (!enrichment.has_endpoint_stops) {
141
+ return SkipReason.EndpointStopsNotInGtfs;
142
+ }
143
+ if (!enrichment.from_stop || !enrichment.to_stop) {
144
+ return SkipReason.NoTripRunsWholeSegment;
145
+ }
146
+ if (enrichment.routes.length === 0) {
147
+ return SkipReason.NoAffectedRoutes;
148
+ }
149
+ return {
150
+ id: candidate.id,
151
+ external_from_stop_id: candidate.delayShape.from_stop_id,
152
+ external_to_stop_id: candidate.delayShape.to_stop_id,
153
+ from_stop_id: enrichment.from_stop.id,
154
+ from_stop_name: enrichment.from_stop.name,
155
+ to_stop_id: enrichment.to_stop.id,
156
+ to_stop_name: enrichment.to_stop.name,
157
+ expected_delay_seconds: candidate.delayShape.expected_delay,
158
+ stops: enrichment.stops,
159
+ routes: enrichment.routes,
160
+ info_panel_stops: enrichment.info_panel_stops,
161
+ };
162
+ }
163
+ };
164
+ JISDelayedSegmentsDataService = __decorate([
165
+ injectable(),
166
+ __param(0, inject(JISContainerToken.JISDelayedSegmentEnrichmentRepository)),
167
+ __param(1, inject(JISContainerToken.JISDelayedSegmentsRepository)),
168
+ __param(2, inject(CoreToken.SimpleConfig)),
169
+ __param(3, inject(CoreToken.Logger)),
170
+ __metadata("design:paramtypes", [JISDelayedSegmentEnrichmentRepository,
171
+ JISDelayedSegmentsRepository, Object, Object])
172
+ ], JISDelayedSegmentsDataService);
173
+ export { JISDelayedSegmentsDataService };
174
+ //# sourceMappingURL=JISDelayedSegmentsDataService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JISDelayedSegmentsDataService.js","sourceRoot":"","sources":["../../../../src/integration-engine/jis/services/JISDelayedSegmentsDataService.ts"],"names":[],"mappings":"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"}