@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,138 @@
1
+ import SequelizeCore from "@golemio/core/dist/shared/sequelize.js";
2
+ const { DataTypes, Model } = SequelizeCore;
3
+ const stopJsonSchema = {
4
+ type: "object",
5
+ properties: {
6
+ id: { type: "string" },
7
+ name: { type: "string" },
8
+ },
9
+ additionalProperties: false,
10
+ required: ["id", "name"],
11
+ };
12
+ export class JISDelayedSegmentsModel extends Model {
13
+ }
14
+ JISDelayedSegmentsModel.tableName = "jis_delayed_segments";
15
+ JISDelayedSegmentsModel.attributeModel = {
16
+ id: {
17
+ primaryKey: true,
18
+ type: DataTypes.TEXT,
19
+ allowNull: false,
20
+ },
21
+ external_from_stop_id: {
22
+ type: DataTypes.STRING(50),
23
+ allowNull: false,
24
+ },
25
+ external_to_stop_id: {
26
+ type: DataTypes.STRING(50),
27
+ allowNull: false,
28
+ },
29
+ from_stop_id: {
30
+ type: DataTypes.STRING(50),
31
+ allowNull: false,
32
+ },
33
+ from_stop_name: {
34
+ type: DataTypes.TEXT,
35
+ allowNull: false,
36
+ },
37
+ to_stop_id: {
38
+ type: DataTypes.STRING(50),
39
+ allowNull: false,
40
+ },
41
+ to_stop_name: {
42
+ type: DataTypes.TEXT,
43
+ allowNull: false,
44
+ },
45
+ expected_delay_seconds: {
46
+ type: DataTypes.INTEGER,
47
+ allowNull: false,
48
+ },
49
+ stops: {
50
+ type: DataTypes.JSONB,
51
+ allowNull: false,
52
+ },
53
+ routes: {
54
+ type: DataTypes.JSONB,
55
+ allowNull: false,
56
+ },
57
+ info_panel_stops: {
58
+ type: DataTypes.JSONB,
59
+ allowNull: false,
60
+ },
61
+ created_at: {
62
+ type: DataTypes.DATE,
63
+ allowNull: false,
64
+ },
65
+ updated_at: {
66
+ type: DataTypes.DATE,
67
+ allowNull: false,
68
+ },
69
+ };
70
+ JISDelayedSegmentsModel.jsonSchema = {
71
+ type: "array",
72
+ items: {
73
+ type: "object",
74
+ properties: {
75
+ id: { type: "string" },
76
+ external_from_stop_id: { type: "string" },
77
+ external_to_stop_id: { type: "string" },
78
+ from_stop_id: { type: "string" },
79
+ from_stop_name: { type: "string" },
80
+ to_stop_id: { type: "string" },
81
+ to_stop_name: { type: "string" },
82
+ expected_delay_seconds: { type: "integer" },
83
+ stops: { type: "array", items: stopJsonSchema },
84
+ routes: {
85
+ type: "array",
86
+ items: {
87
+ type: "object",
88
+ properties: {
89
+ id: { type: "string" },
90
+ short_name: { type: "string" },
91
+ type: { type: "integer" },
92
+ },
93
+ additionalProperties: false,
94
+ required: ["id", "short_name", "type"],
95
+ },
96
+ },
97
+ info_panel_stops: {
98
+ type: "array",
99
+ items: {
100
+ type: "object",
101
+ properties: {
102
+ stop: stopJsonSchema,
103
+ preset_route_names: { type: "array", items: { type: "string" } },
104
+ routes: {
105
+ type: "array",
106
+ items: {
107
+ type: "object",
108
+ properties: {
109
+ id: { type: "string" },
110
+ distance_stops: { type: "integer" },
111
+ },
112
+ additionalProperties: false,
113
+ required: ["id", "distance_stops"],
114
+ },
115
+ },
116
+ },
117
+ additionalProperties: false,
118
+ required: ["stop", "preset_route_names", "routes"],
119
+ },
120
+ },
121
+ },
122
+ additionalProperties: false,
123
+ required: [
124
+ "id",
125
+ "external_from_stop_id",
126
+ "external_to_stop_id",
127
+ "from_stop_id",
128
+ "from_stop_name",
129
+ "to_stop_id",
130
+ "to_stop_name",
131
+ "expected_delay_seconds",
132
+ "stops",
133
+ "routes",
134
+ "info_panel_stops",
135
+ ],
136
+ },
137
+ };
138
+ //# sourceMappingURL=JISDelayedSegmentsModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JISDelayedSegmentsModel.js","sourceRoot":"","sources":["../../../../src/schema-definitions/jis/models/JISDelayedSegmentsModel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ export interface IJISDelayedSegmentStop {
2
+ id: string;
3
+ name: string;
4
+ }
5
+ export interface IJISDelayedSegmentRoute {
6
+ id: string;
7
+ short_name: string;
8
+ type: number;
9
+ }
10
+ /**
11
+ * Route passing an info panel stop, with the signed distance of the stop from the segment
12
+ * (negative before the segment, 0 inside, positive after it, in stops along the route)
13
+ */
14
+ export interface IJISDelayedSegmentInfoPanelRoute {
15
+ id: string;
16
+ distance_stops: number;
17
+ }
18
+ export interface IJISDelayedSegmentInfoPanelStop {
19
+ stop: IJISDelayedSegmentStop;
20
+ preset_route_names: string[];
21
+ routes: IJISDelayedSegmentInfoPanelRoute[];
22
+ }
23
+ /**
24
+ * Stops, routes and info panels of a delayed segment derived from GTFS
25
+ */
26
+ export interface IJISDelayedSegmentEnrichment extends Pick<IJISDelayedSegment, "stops" | "routes" | "info_panel_stops"> {
27
+ /** Whether both endpoints exist in GTFS; endpoints without stops then mean that no trip runs the whole segment */
28
+ has_endpoint_stops: boolean;
29
+ from_stop: IJISDelayedSegmentStop | null;
30
+ to_stop: IJISDelayedSegmentStop | null;
31
+ }
32
+ export interface IJISDelayedSegment {
33
+ id: string;
34
+ external_from_stop_id: string;
35
+ external_to_stop_id: string;
36
+ from_stop_id: string;
37
+ from_stop_name: string;
38
+ to_stop_id: string;
39
+ to_stop_name: string;
40
+ expected_delay_seconds: number;
41
+ stops: IJISDelayedSegmentStop[];
42
+ routes: IJISDelayedSegmentRoute[];
43
+ info_panel_stops: IJISDelayedSegmentInfoPanelStop[];
44
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=IJISDelayedSegment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IJISDelayedSegment.js","sourceRoot":"","sources":["../../../../../src/schema-definitions/jis/models/interfaces/IJISDelayedSegment.ts"],"names":[],"mappings":""}
@@ -1,3 +1,4 @@
1
+ export * from "./IJISDelayedSegment.js";
1
2
  export * from "./IJISEvent.js";
2
3
  export * from "./IJISEventTranslationText.js";
3
4
  export * from "./IJISEventsRopidGTFSRoutes.js";
@@ -1,3 +1,4 @@
1
+ export * from "./IJISDelayedSegment.js";
1
2
  export * from "./IJISEvent.js";
2
3
  export * from "./IJISEventTranslationText.js";
3
4
  export * from "./IJISEventsRopidGTFSRoutes.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schema-definitions/jis/models/interfaces/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/schema-definitions/jis/models/interfaces/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC"}
Binary file
@@ -84,6 +84,17 @@ channels:
84
84
  messages:
85
85
  data:
86
86
  $ref: "#/components/messages/JISEvents"
87
+ jis.refreshDelayedSegments:
88
+ address: vehicle-positions.jis.refreshDelayedSegments
89
+ description: Fetch delayed segments from Mapa PID, enrich them with GTFS data and refresh them in database
90
+ bindings:
91
+ amqp:
92
+ is: queue
93
+ queue:
94
+ durable: true
95
+ messages:
96
+ empty:
97
+ $ref: "#/components/messages/emptyMessage"
87
98
  ropidgtfs.refreshPublicGtfsDepartureCache:
88
99
  address: vehicle-positions.ropidgtfs.refreshPublicGtfsDepartureCache
89
100
  description: Refresh PublicGtfsDepartureCache
@@ -382,6 +393,10 @@ operations:
382
393
  action: "send"
383
394
  channel:
384
395
  $ref: "#/channels/jis.updateEventWebhook"
396
+ jis.refreshDelayedSegments:
397
+ action: "send"
398
+ channel:
399
+ $ref: "#/channels/jis.refreshDelayedSegments"
385
400
  ropidgtfs.refreshPublicGtfsDepartureCache:
386
401
  action: "send"
387
402
  channel:
@@ -241,6 +241,26 @@ _:warning: Původním záměrem bylo využití OIS číselníku během zpracová
241
241
  - název rabbitmq fronty
242
242
  - vehicle-positions.jis.fetchJISInfotexts
243
243
 
244
+ #### _Mapa PID – zpožděné úseky_
245
+
246
+ - zdroj dat
247
+ - url: module.pid.jis.delayedSegments.url
248
+ - interní endpoint webové [Mapy PID](https://mapa.pid.cz/), POST požadavek bez vozidel a tvarů úseků (prázdné `bounds`, `show_delay_sections: false`); zpožděné úseky vrací vždy pro celou síť
249
+ - formát dat
250
+ - protokol: http
251
+ - datový typ: json
252
+ - validační schéma: [MapaPidDelayShapesJsonSchema](../src/schema-definitions/jis/datasources/MapaPidDelayShapesJsonSchema.ts)
253
+ - příklad vstupních dat: [JISDelayedSegmentsDataSourceFactory.unit.test.ts](../test/integration-engine/jis/datasources/JISDelayedSegmentsDataSourceFactory.unit.test.ts)
254
+ - frekvence stahování
255
+ - cron definice:
256
+ - cron.vehicle-positions.jis.refreshDelayedSegments
257
+ - rabin `0 */5 * * * *`
258
+ - prod `0 */5 * * * *`
259
+ - název rabbitmq fronty
260
+ - vehicle-positions.jis.refreshDelayedSegments
261
+ - omezení
262
+ - odpověď neobsahuje čas výpočtu úseků; pokud Mapa PID přestane úseky přepočítávat, vrací poslední známý stav (až 24 hodin) a ten se u nás dál jeví jako aktuální
263
+
244
264
  ### _Ropid staticka data_
245
265
 
246
266
  - zdroj dat
@@ -1313,6 +1333,30 @@ Worker má na starost stahování VYMI (JIS) událostí a infotextů, jejich tra
1313
1333
  - data modely
1314
1334
  - žádné
1315
1335
 
1336
+ #### _task: RefreshDelayedSegmentsTask_
1337
+
1338
+ Záměr, architekturu a algoritmus podrobně popisuje [jis/delayed-segments.md](./jis/delayed-segments.md).
1339
+
1340
+ - vstupní rabbitmq fronta
1341
+ - název: vehicle-positions.jis.refreshDelayedSegments
1342
+ - TTL: 4 minuty
1343
+ - parametry: žádné
1344
+ - závislé fronty (do kterých jsou odesílány zprávy z metody workeru)
1345
+ - žádné
1346
+ - datové zdroje
1347
+ - Mapa PID (zpožděné úseky)
1348
+ - `ropidgtfs_precomputed_departures`, `ropidgtfs_trips`, `ropidgtfs_stops`, `ropid_departures_presets`, `ropid_departures_presets_ropidgtfs_stops`
1349
+ - transformace
1350
+ - úseky se zpožděním pod `module.pid.jis.delayedSegments.minDelaySeconds` (výchozí 300 s) se zahazují
1351
+ - krajní zastávky se převádí na ASW id (`uzel/sloupek`), z nichž se skládá id úseku; úseky bez ASW id (např. železniční body `T…`) se zahazují
1352
+ - úseky se obohatí o GTFS data, všechny jedním dotazem ([JISDelayedSegmentEnrichmentQuery](../src/integration-engine/jis/repositories/helpers/JISDelayedSegmentEnrichmentQuery.ts)); uvažují se pouze odjezdy v okně ± `module.pid.jis.delayedSegments.routeTimeWindowMinutes` (výchozí 120 min) od stažení a pravidelné spoje (`exceptional = 0`):
1353
+ - zastávky úseku – zastávky mezi krajními zastávkami na spojích projíždějících celý úsek, sloučené podle ASW id (reprezentované abecedně prvním stop id ze všech variant v GTFS, aby se neměnilo mezi běhy), seřazené podél úseku
1354
+ - dotčené linky – linky se spojem, který projede alespoň jeden mezizastávkový úsek ve směru úseku
1355
+ - zastávky s panely – zastávky s presetem odjezdové tabule (párované podle ASW id) do vzdálenosti `module.pid.jis.delayedSegments.maxInfoPanelDistanceStops` (výchozí 5) zastávek od úseku podél dotčených spojů; pro každou linku se uvádí vzdálenost se znaménkem (záporná před úsekem, 0 v úseku, kladná za úsekem), nejbližší k úseku; vzdálenost se počítá jako rozdíl `stop_sequence`, což předpokládá souvislé číslování zastávek spoje (platí pro všechny spoje PID); mezera v číslování by vzdálenost zvětšila o svou délku
1356
+ - zahazují se úseky, jejichž krajní zastávky nejsou v GTFS, úseky, které v časovém okně celé neprojíždí žádný spoj (Mapa PID slučuje navazující úseky různých linek), a úseky bez dotčených linek; počty vyřazených úseků podle důvodu se logují při každém běhu
1357
+ - data modely
1358
+ - Model `jis_delayed_segments` – při každém úspěšném stažení se nahradí celý obsah; `created_at` je začátek aktuální nepřerušené detekce úseku (úsek chybějící v úspěšném stažení se smaže a při dalším výskytu začíná znovu), `updated_at` čas poslední; při chybě stažení zůstávají data beze změny
1359
+
1316
1360
  ## Uložení dat
1317
1361
 
1318
1362
  - typ databáze
@@ -1344,6 +1388,7 @@ Worker má na starost stahování VYMI (JIS) událostí a infotextů, jejich tra
1344
1388
  - popis vozidel se nepromazává – přetrvávají do přepsání novými daty (fixní TTL bylo odstraněno)
1345
1389
  - preset logy si uchováváme 8 hodin
1346
1390
  - JIS události si uchováváme neomezeně; JIS infotexty jen dokud jsou v posledním přijatém seznamu z VYMI (chybějící se při každém přenačtení mažou)
1391
+ - zpožděné úseky jen dokud jsou v posledním úspěšně staženém seznamu z Mapy PID
1347
1392
 
1348
1393
  ## Input API
1349
1394
 
@@ -1622,3 +1667,14 @@ Worker má na starost stahování VYMI (JIS) událostí a infotextů, jejich tra
1622
1667
  - zdrojové tabulky: `jis_events`, `jis_events_ropidgtfs_routes`
1623
1668
  - formát odpovědi: viz schéma `EventCustomFormat` v [openapi-output.yaml](./openapi-output.yaml)
1624
1669
  - trasy v `informed_entity.routes` jsou snapshot-first (`jis_events_ropidgtfs_routes.route_details`), s GTFS fallbackem (`ropidgtfs_routes`) jen pro starší řádky bez snímku; podrobněji viz [jis/index.md](./jis/index.md#informed_entityroutesroute_details-snímek-gtfs-atributů-trasy)
1670
+
1671
+ #### _/v1/jis/delayed-segments_
1672
+
1673
+ - interní endpoint pro VYMI, vrací aktuální zpožděné úseky obohacené o GTFS data (viz [RefreshDelayedSegmentsTask](#task-refreshdelayedsegmentstask) a [jis/delayed-segments.md](./jis/delayed-segments.md#kontrakt-api))
1674
+ - úseky v čase rostou a zmenšují se, `id` (odvozené od krajních zastávek) proto není stabilní identifikace úseku; úseky se mají párovat podle zastávek
1675
+ - při chybě stažení se úseky nemažou a jejich `last_detected_at` se přestane posouvat, aktuálnost je proto třeba posuzovat podle něj; zaseknutý výpočet na straně Mapy PID tím rozpoznat nelze (viz omezení zdroje)
1676
+ - parametry (volitelné, jen zužují uložená data):
1677
+ - `minDelaySeconds` – minimální `expected_delay_seconds` úseku
1678
+ - `minDistanceStops`, `maxDistanceStops` – rozsah `distance_stops` linek zastávek s panely včetně; zastávky bez linky v rozsahu se vynechají
1679
+ - zdrojové tabulky: `jis_delayed_segments`
1680
+ - formát odpovědi: viz schéma `JISDelayedSegment` v [openapi-output.yaml](./openapi-output.yaml)
package/docs/index.md CHANGED
@@ -28,6 +28,7 @@ PID Polohy je soubor služeb datové platformy [Golemio](https://golemio.cz/) na
28
28
  - [Upřesňující popis architektury platformy Golemio]
29
29
  - Podrobná dokumentace:
30
30
  - [Dokumentace propisu VYMI dat do datové platformy Golemio]
31
+ - [Zpožděné úseky – podklad pro automatické infotexty](./jis/delayed-segments.md)
31
32
  - [Dokumentace zpracování RT dat](./processing/index.md)
32
33
  - [Vstupní data HTTP MPVnet (autobusy mimo DPP, přívozy, vlaky)](./processing/input_realtime_data/http_mpvnet.md)
33
34
  - [Vstupní data TCP DPP tramvaje](./processing/input_realtime_data/tcp_dpp_tram.md)
@@ -0,0 +1,241 @@
1
+ # Zpožděné úseky – podklad pro automatické infotexty
2
+
3
+ Stránka popisuje, jak datová platforma Golemio připravuje pro VYMI podklad pro automatické infotexty o zpožděných úsecích sítě PID ([vymi-general#365](https://gitlab.com/operator-ict/golemio/jis/vymi-alerts/vymi-general/-/work_items/365)): záměr, architekturu řešení, algoritmus zpracování, datový model, výstupní API, výkon, konfiguraci, provoz, známá omezení a klíčová rozhodnutí. Stručný přehled pro odběratele je v [index.md](./index.md#zpožděné-úseky--podklad-pro-automatické-infotexty-get-v1jisdelayed-segments), technické údaje workeru a endpointu v [implementační dokumentaci](../implementation_documentation.md#task-refreshdelayedsegmentstask).
4
+
5
+ ## Záměr
6
+
7
+ Při velkém zpoždění na úseku sítě (typicky kolona nebo nehoda) mají cestující na odjezdových tabulích dostat informaci, že spoje určitých linek v daném místě nabírají zpoždění. Dispečeři ROPID takové infotexty dnes zadávají ručně. Cílem je, aby je VYMI navrhovalo nebo zakládalo automaticky.
8
+
9
+ Zpožděné úseky již počítá webová [Mapa PID](https://mapa.pid.cz/) z poloh vozidel Golemia, samotná informace o úseku ale pro infotext nestačí. Je třeba vědět, které linky úsekem jezdí a na kterých zastávkách s odjezdovými tabulemi má smysl infotext zobrazit. Odpovědnosti jsou proto rozděleny takto:
10
+
11
+ | Kdo | Odpovědnost |
12
+ | --- | --- |
13
+ | Mapa PID | detekce zpožděných úseků a odhad zpoždění |
14
+ | Golemio (modul pid) | pravidelné stažení úseků, obohacení o GTFS data (zastávky úseku, dotčené linky, zastávky s tabulemi a jejich vzdálenost od úseku), uložení a poskytnutí přes interní API |
15
+ | VYMI | párování úseků v čase, rozhodnutí o vzniku, úpravě a ukončení události a infotextů, jejich text a výběr tabulí |
16
+
17
+ Golemio tedy nerozhoduje o infotextech, poskytuje jen fakta potřebná k rozhodnutí. VYMI si úseky stahuje každých 5 minut a zakládá z nich interní události s infotexty jménem uživatele `OICT_Robot`; ve zkušebním provozu jen u úseků poblíž testovacích zastávek (Vozovna Střešovice B 876/2, U Památníku B 789/2) a za přepínačem v konfiguraci, podrobně viz [dokumentace VYMI](https://gitlab.com/operator-ict/golemio/jis/vymi-alerts/vymi-alerts-backend/-/blob/master/docs/delayed-segments.md). Rozhraní je navrženo nezávisle na zdroji úseků (viz [Kontrakt API](#kontrakt-api)), aby šlo zdroj v budoucnu vyměnit nebo doplnit bez změny pro VYMI.
18
+
19
+ ## Architektura
20
+
21
+ ```mermaid
22
+ flowchart LR
23
+ mapa["Mapa PID<br/>(detekce zpožděných úseků)"]
24
+ cron["Cron Tasks<br/>(každých 5 minut)"]
25
+ rabbit[("RabbitMQ<br/>vehicle-positions.jis.refreshDelayedSegments")]
26
+ ie["VP Integration Engine<br/>RefreshDelayedSegmentsTask"]
27
+ pg[("PostgreSQL<br/>jis_delayed_segments<br/>+ GTFS tabulky")]
28
+ og["VP Output Gateway<br/>GET /v1/jis/delayed-segments"]
29
+ proxy["Access Proxy<br/>(interní role)"]
30
+ vymi["VYMI<br/>(automatické infotexty)"]
31
+
32
+ cron --> rabbit --> ie
33
+ mapa -. "HTTP JSON" .-> ie
34
+ ie <--> pg
35
+ pg -. "čtení" .-> og
36
+ vymi -. "HTTP JSON" .-> proxy -.-> og
37
+ ```
38
+
39
+ Plná šipka znamená aktivní posílání, přerušovaná stahování příjemcem. Integration Engine si úseky z Mapy PID stahuje, obohacuje je dotazem nad GTFS daty ve stejné databázi a výsledek ukládá do tabulky `jis_delayed_segments`. Output Gateway tabulku jen čte. VYMI si data stahuje z interního endpointu, přístup je omezen rolí v Access Proxy.
40
+
41
+ Průběh jednoho běhu:
42
+
43
+ ```mermaid
44
+ sequenceDiagram
45
+ participant cron as Cron Tasks
46
+ participant task as RefreshDelayedSegmentsTask
47
+ participant db as PostgreSQL
48
+ participant mapa as Mapa PID
49
+
50
+ cron->>task: zpráva do fronty (každých 5 minut)
51
+ task->>task: detectedAt = čas stažení
52
+ task->>mapa: POST bez vozidel a tvarů úseků
53
+ mapa-->>task: zpožděné úseky celé sítě
54
+ task->>task: filtr podle zpoždění, převod krajních zastávek na ASW id
55
+ task->>db: úseky s obohacením mladším než TTL
56
+ db-->>task: znovupoužitelná obohacení
57
+ task->>db: obohacení ostatních úseků (jeden dotaz pro všechny)
58
+ db-->>task: zastávky, linky, zastávky s tabulemi
59
+ task->>db: v transakci upsert všech úseků a smazání nedetekovaných
60
+ ```
61
+
62
+ ## Zdroj dat: Mapa PID
63
+
64
+ Mapa PID počítá zpožděné úseky průběžně z poloh vozidel. Úsek vzniká sloučením navazujících zpožděných mezizastávkových úseků, jeho odhadované zpoždění (`expected_delay`, v sekundách) je součtem zpoždění sloučených částí. Úseky se proto v čase prodlužují, zkracují a štěpí.
65
+
66
+ Golemio volá interní endpoint webové Mapy PID (adresa je jen v konfiguraci, `module.pid.jis.delayedSegments.url`). Endpoint vrací zpožděné úseky vždy pro celou síť bez ohledu na zobrazenou oblast mapy. Požadavek proto posílá prázdnou oblast (`bounds`) a vypnuté tvary úseků (`show_delay_sections: false`), aby odpověď neobsahovala vozidla ani geometrie a byla co nejmenší. Z odpovědi se používá jen pole `delay_shapes`, validované schématem [MapaPidDelayShapesJsonSchema](../../src/schema-definitions/jis/datasources/MapaPidDelayShapesJsonSchema.ts). Krajní zastávky úseku jsou v něm identifikované stop id ve formátu Mapy PID (např. `U1347Z2`, železniční body `T…`).
67
+
68
+ Endpoint není veřejné API. Úpravy na straně Mapy PID nejsou v naší moci, řešení proto pracuje s jeho současným chováním (viz [Známá omezení](#známá-omezení-a-rizika)).
69
+
70
+ ## Zpracování
71
+
72
+ Zpracování obstarává [RefreshDelayedSegmentsTask](../../src/integration-engine/jis/workers/tasks/RefreshDelayedSegmentsTask.ts) a [JISDelayedSegmentsDataService](../../src/integration-engine/jis/services/JISDelayedSegmentsDataService.ts):
73
+
74
+ 1. **Čas detekce.** Před stažením se zaznamená `detectedAt`. Všechny úseky jednoho běhu mají stejný čas poslední detekce a od něj se počítá i časové okno obohacení.
75
+ 2. **Stažení.** Chyba stažení nebo neplatná odpověď ukončí běh chybou a uložené úseky zůstanou beze změny. Prázdný seznam úseků je platná odpověď a uložené úseky smaže.
76
+ 3. **Filtr podle zpoždění.** Úseky se zpožděním pod `minDelaySeconds` (výchozí 300 s) se zahodí. VYMI si minimum zvyšuje parametrem `minDelaySeconds` endpointu (viz [Kontrakt API](#kontrakt-api)).
77
+ 4. **Krajní zastávky na ASW id.** Stop id Mapy PID se převede na ASW id (`uzel/sloupek`, např. `U1347Z2` → `1347/2`). Z ASW id krajních zastávek se skládá `id` úseku `{uzel}/{sloupek}-{uzel}/{sloupek}`. Úseky, jejichž krajní zastávky ASW id nemají (železniční body `T…`), se zahodí.
78
+ 5. **Obohacení.** Všechny úseky se v každém běhu obohatí jedním dotazem (viz [Obohacení](#obohacení)), každé `id` jen jednou. Obohacené úseky by šly pro optimalizaci cachovat, ale v prvotní implementaci to bylo prozatím vynecháno (bude-li v budoucnu potřeba, lze najít hotovou implementaci v Git historii).
79
+ 6. **Vyřazení a duplicity.** Úsek se zahodí, pokud jeho krajní zastávky v GTFS neexistují, pokud ho v časovém okně žádný spoj neprojíždí celý (viz [Známá omezení](#známá-omezení-a-rizika)) nebo pokud nemá dotčené linky. Vyřazené úseky se počítají podle důvodu. Varianty stop id jedné fyzické zastávky mohou vést na stejné `id`; z duplicit se ponechá úsek s největším zpožděním.
80
+ 7. **Uložení.** V jedné transakci se všechny úseky upsertují (`updated_at = detectedAt`, `created_at` zůstává z první detekce) a úseky, které v běhu nebyly, se smažou. Úsek, který se později znovu objeví, začíná s novým `created_at`, stejně jako úsek se změněným `id`; `first_detected_at` je proto začátek aktuální nepřerušené detekce.
81
+
82
+ ## Obohacení
83
+
84
+ Obohacení odvozuje z GTFS dat (předpočítané odjezdy `ropidgtfs_precomputed_departures`, spoje, zastávky a presety odjezdových tabulí) tři údaje. Počítá je dotaz [JISDelayedSegmentEnrichmentQuery](../../src/integration-engine/jis/repositories/helpers/JISDelayedSegmentEnrichmentQuery.ts) najednou pro všechny úseky běhu. Uvažují se jen odjezdy v okně ± `routeTimeWindowMinutes` (výchozí 120 min) od `detectedAt` a jen pravidelné spoje (`exceptional = 0`; výjimečné spoje jsou jednorázové spoje na konkrétní den, např. náhradní doprava). Okno zasahuje i do minulosti, protože zpožděný spoj s odjezdem plánovaným dříve může být v úseku ještě teď (spoj plánovaný na 13:00 může úsekem projíždět ve 13:45).
85
+
86
+ **Párování podle ASW id.** Jedna fyzická zastávka může mít v GTFS více stop id (zónové a dočasné varianty, např. `U9904Z1P` a `U9904Z1`), presety tabulí přitom bývají přiřazené jen k jedné z nich. Zastávky se proto všude párují podle ASW id, nikoli podle GTFS stop id.
87
+
88
+ ### Zastávky úseku
89
+
90
+ Zastávky mezi krajními zastávkami (včetně nich) na spojích, které v časovém okně projíždějí celý úsek, sloučené podle ASW id a seřazené podél úseku. Pořadí určuje relativní poloha zastávky na spoji (0 na začátku úseku, 1 na konci), nejmenší ze všech spojů, aby se spoje vynechávající některé zastávky (např. rychlík A→D a zastávkový spoj A→B→C→D) shodly na krajních zastávkách. Pokud dvě větve úseku řadí společnou zastávku různě daleko od začátku, může být pořadí zastávek mezi větvemi přeházené; párování ve VYMI pořadí nepoužívá. Úsek, kterým v okně žádný spoj celý neprojíždí, nemá zastávky ani dotčené linky a zahodí se. Zastávku úseku reprezentuje abecedně první GTFS stop id ze všech jejích variant, nejen z těch, které obsluhují spoje v okně, aby se id mezi běhy neměnilo (VYMI podle něj páruje úseky). Zastávky bez ASW id (např. železniční body `T…`) mezi zastávky úseku nepatří.
91
+
92
+ ### Dotčené linky
93
+
94
+ Linky, jejichž spoj v časovém okně projede alespoň jeden mezizastávkový úsek mezi dvěma zastávkami úseku, a to ve směru úseku. Směr se neurčuje z pořadí zastávek úseku, ale z dvojic zastávek, které některý spoj jedoucí celým úsekem navštíví v tomto pořadí; platí tak i pro větve, které společnou zastávku řadí různě. Spoj tedy nemusí jet celým úsekem. Linka, která úseku jen dotkne krajní zastávkou nebo jede opačným směrem, dotčená není.
95
+
96
+ ### Zastávky s tabulemi a jejich vzdálenost
97
+
98
+ Zastávky s presetem odjezdové tabule na dotčených spojích do vzdálenosti `maxInfoPanelDistanceStops` (výchozí 5) zastávek od úseku. Pro každou zastávku se uvádí seznam presetů a pro každou dotčenou linku vzdálenost od úseku ve stanicích se znaménkem:
99
+
100
+ ```text
101
+ spoj linky X: P0 ─ P1 ─ P2 ─ [A ─ B ─ C ─ D] ─ N1
102
+ vzdálenost: -3 -2 -1 0 0 0 0 +1
103
+ ```
104
+
105
+ Záporná vzdálenost je před úsekem (tabule, kde cestující nastupují do spojů, které teprve úsekem pojedou), nula v úseku, kladná za úsekem. Pokud spoje jedné linky dávají různé vzdálenosti (např. různé trasy), ponechá se ta nejbližší k úseku. Které tabule dostanou infotext, rozhoduje VYMI: rozsah vzdáleností (výchozí −2 až +2 zastávky) posílá v parametrech `minDistanceStops` a `maxDistanceStops` endpointu a dostane jen tabule v něm.
106
+
107
+ **Vzdálenost jako rozdíl `stop_sequence`.** Vzdálenost se počítá jako rozdíl pořadí zastávky ve spoji (`stop_sequence`) od první, resp. poslední zastávky spoje na úseku. To předpokládá souvislé číslování zastávek spoje, což platí pro všechny spoje PID (ověřeno na všech 127 635 spojodnech produkčních dat v říjnu 2026). Pokud by číslování mezeru mělo, vzdálenost se zvětší o její délku a tabule může vypadnout z limitu. Přesné pořadí by vyžadovalo seřadit všechny zastávky všech dotčených spojů, což byla nejdražší část dotazu (viz [Výkon](#výkon)).
108
+
109
+ ## Datový model
110
+
111
+ Tabulka `jis_delayed_segments` (migrace `20261027100000-jis-delayed-segments`), jeden řádek na aktuálně detekovaný úsek:
112
+
113
+ | Sloupec | Typ | Popis |
114
+ | --- | --- | --- |
115
+ | `id` | `text` | složené z ASW id krajních zastávek `{uzel}/{sloupek}-{uzel}/{sloupek}` |
116
+ | `external_from_stop_id`, `external_to_stop_id` | `varchar(50)` | krajní zastávky ve formátu zdroje, pro dohledání problémů |
117
+ | `from_stop_id`, `from_stop_name`, `to_stop_id`, `to_stop_name` | `varchar(50)`, `text` | krajní zastávky jako GTFS zastávky |
118
+ | `expected_delay_seconds` | `integer` | odhadované zpoždění úseku |
119
+ | `stops`, `routes`, `info_panel_stops` | `jsonb` | obohacení, struktura odpovídá API |
120
+ | `created_at`, `updated_at` | `timestamptz` | začátek aktuální nepřerušené detekce úseku a poslední detekce |
121
+
122
+ Databázové schéma záměrně nezmiňuje Mapu PID, zdroj je v komentářích sloupců popsán obecně. Úseky se uchovávají jen dokud jsou v posledním úspěšně staženém seznamu.
123
+
124
+ Pro obohacení byl index `ropidgtfs_precomputed_departures (stop_id)` nahrazen indexem `(stop_id, departure_datetime)`, který stejně dobře slouží dotazům jen podle zastávky (např. odjezdové tabule). Tabulka předpočítaných odjezdů se při každém importu GTFS vytváří znovu jako `LIKE … including all`, index se tedy přenáší automaticky. Názvy indexů se ale při importech mění, migrace proto index hledá podle sloupců, ne podle názvu, a skončí chybou, pokud takový index není právě jeden. Migrace nový index nejdříve vytvoří a starý smaže až na konci: smazání drží výhradní zámek tabulky až do konce migrace a při opačném pořadí by odjezdové tabule čekaly po celou dobu stavby indexu (na produkčních datech ~3 s).
125
+
126
+ ## Kontrakt API
127
+
128
+ `GET /v1/jis/delayed-segments` (Output Gateway, tag `⚠ JIS (v1) - internal`) vrací uložené úseky seřazené od největšího zpoždění. Volitelné parametry je zužují:
129
+
130
+ - `minDelaySeconds` – jen úseky se zpožděním alespoň takovým,
131
+ - `minDistanceStops`, `maxDistanceStops` – u zastávek s tabulemi jen linky se vzdáleností (`distance_stops`) v tomto rozsahu včetně; zastávky bez takové linky se vynechají.
132
+
133
+ Parametry mohou data jen zúžit: úseky se zpožděním pod `minDelaySeconds` a tabule dál než `maxInfoPanelDistanceStops` z konfigurace Integration Engine se vůbec neukládají. Filtrovat v Output Gateway místo ve VYMI znamená, že VYMI nestahuje data, která by zahodilo, a nastavení filtrů zůstává na jednom místě ve VYMI. Schéma je v [openapi-output.yaml](../openapi-output.yaml) (`JISDelayedSegment`). Odpověď je cachovaná 5 s (`s-maxage=5, stale-while-revalidate=5`).
134
+
135
+ ```json
136
+ {
137
+ "id": "9903/1-9906/1",
138
+ "from_stop": { "id": "U9903Z1P", "name": "Zastávka A" },
139
+ "to_stop": { "id": "U9906Z1P", "name": "Zastávka D" },
140
+ "expected_delay_seconds": 900,
141
+ "first_detected_at": "2026-10-27T08:50:00.000Z",
142
+ "last_detected_at": "2026-10-27T09:00:00.000Z",
143
+ "stops": [{ "id": "U9903Z1P", "name": "Zastávka A" }, "…"],
144
+ "routes": [{ "id": "L9901", "short_name": "9901", "type": 3 }],
145
+ "info_panel_stops": [
146
+ {
147
+ "stop": { "id": "U9901Z1P", "name": "Zastávka P1" },
148
+ "preset_route_names": ["preset-p1"],
149
+ "routes": [{ "id": "L9901", "distance_stops": -2 }]
150
+ }
151
+ ]
152
+ }
153
+ ```
154
+
155
+ Zásady kontraktu:
156
+
157
+ - **Nezávislost na zdroji.** API neobsahuje nic specifického pro Mapu PID (ani její stop id), všechny zastávky a linky jsou GTFS entity. Zdroj úseků lze vyměnit bez změny kontraktu.
158
+ - **`id` není stabilní identifikace.** Úseky v čase rostou, zkracují se a štěpí, `id` se tím mění. VYMI páruje úseky mezi běhy podle `stops`: úsek pokračuje sledovanou kongesci, pokud s ní sdílí alespoň dvě zastávky (dvojice 1:1, větší překryv dřív). Zastávky jsou GTFS stop id abecedně první ze všech variant zastávky v GTFS, nezávisle na tom, které varianty obsluhují spoje v časovém okně; id se tak změní jen se změnou zastávek v GTFS (např. přibude varianta řazená dřív), a i pak jedna změněná zastávka párování podle dvou společných zastávek nerozbije.
159
+ - **Id zastávky s tabulí je id varianty s presetem.** `info_panel_stops[].stop.id` je varianta, ke které je přiřazen preset, a může se lišit od id stejné zastávky ve `stops`. Tabuli v úseku poznáte podle `distance_stops` = 0, ne podle shody id se `stops`.
160
+ - **Aktuálnost podle `last_detected_at`.** Při výpadku zdroje se úseky nemažou. VYMI drží událost platnou do `last_detected_at` + 15 minut (nastavitelné) a při každé další detekci ji prodlouží; když se `last_detected_at` přestane posouvat, událost sama vyprší.
161
+
162
+ ## Výkon
163
+
164
+ Obohacení byla věnována pozornost, protože běží každých 5 minut nad produkční databází. Měřeno v říjnu 2026 na snímku produkčních dat (2,5 mil. předpočítaných odjezdů), čas dotazu včetně plánování:
165
+
166
+ | Varianta | 13 úseků | 40 úseků |
167
+ | --- | --- | --- |
168
+ | první verze, dotaz pro každý úsek | až 13 s na úsek | – |
169
+ | dotaz pro každý úsek po optimalizacích | 0,45 s | 1,2 s |
170
+ | jeden dotaz pro všechny úseky | 0,12 s | 0,25 s |
171
+ | současný stav | 0,06–0,08 s | 0,11–0,13 s |
172
+
173
+ Kontrola směru přes dvojice zastávek přidala ~5–8 %: na stejném snímku 93 → 99 ms pro 13 úseků a 184 → 199 ms pro 40 úseků (medián 7 běhů).
174
+
175
+ Hlavní kroky:
176
+
177
+ - **Párování presetů přes malou množinu.** ASW id zastávek nejsou indexovaná, párování všech zastávek dotčených spojů proti všem GTFS zastávkám trvalo sekundy. Dotaz páruje jen proti zastávkám s presetem a jejich variantám.
178
+ - **Jeden dotaz pro všechny úseky.** Plánování (~10 ms) a sdílené části (presety, zastávky) se platí jednou za běh, ne za úsek.
179
+ - **Odolnost vůči odhadům plánovače.** Plánovač u mezivýsledků (CTE) podhodnocuje počty řádků o dva až tři řády a volí vnořené smyčky. Spojení, kde to vedlo k milionům porovnání, nahrazuje vyhledání v malých mapách (`jsonb`) zastávek úseku a dvojic zastávek ve směru úseku.
180
+ - **Vzdálenost z `stop_sequence`** místo řazení všech zastávek všech dotčených spojů (viz výše).
181
+ - **Index `(stop_id, departure_datetime)`** zrychlí hledání odjezdů ze zastávek v časovém okně.
182
+
183
+ JIT kompilace PostgreSQL by dotaz jen zpomalila: samotná kompilace plánu trvá ~3 s. Odhadovaná cena dotazu (~7 000) je sice hluboko pod výchozím `jit_above_cost` (100 000, nastaveno na rabínu i produkci v říjnu 2026), na rozdíl od prototypu, kterému vypnutí JIT na produkci zrychlilo dotaz zhruba desetkrát. Dotaz přesto běží v transakci s `SET LOCAL jit = off`, aby změna odhadu nebo nastavení nemohla běh zpomalit o sekundy. Nastavení platí jen pro tuto transakci.
184
+
185
+ ## Konfigurace
186
+
187
+ Klíče modulu pid v konfiguraci Integration Engine:
188
+
189
+ | Klíč | Výchozí hodnota | Význam |
190
+ | --- | --- | --- |
191
+ | `module.pid.jis.delayedSegments.url` | – (povinný) | adresa endpointu Mapy PID; záměrně jen v konfiguraci nasazení |
192
+ | `module.pid.jis.delayedSegments.minDelaySeconds` | `300` | minimální zpoždění úseku |
193
+ | `module.pid.jis.delayedSegments.routeTimeWindowMinutes` | `120` | časové okno odjezdů pro obohacení (± od detekce) |
194
+ | `module.pid.jis.delayedSegments.maxInfoPanelDistanceStops` | `5` | maximální vzdálenost zastávky s tabulí od úseku |
195
+
196
+ ## Provoz
197
+
198
+ - **Spouštění:** cron `cron.vehicle-positions.jis.refreshDelayedSegments`, `0 */5 * * * *` (rabín i produkce), fronta `vehicle-positions.jis.refreshDelayedSegments` s TTL 4 minuty (zprávy starší než další běh propadnou). VYMI si úseky stahuje o dvě minuty později (minuta 2, 7, 12, …), aby četl data právě dokončeného běhu; při změně času spouštění je třeba posunout i VYMI.
199
+ - **Předpoklady nasazení:** nastavený `module.pid.jis.delayedSegments.url`, záznam v Cron Tasks a role v Access Proxy pro přístup VYMI k endpointu.
200
+ - **Logování:** každý běh loguje (úroveň info) počet převzatých a nově spočítaných obohacení, počet uložených úseků a počty vyřazených úseků podle důvodu (pod minimálním zpožděním, bez ASW id, krajní zastávky mimo GTFS, úsek neprojíždí celý žádný spoj, bez dotčených linek). Jednotlivé vyřazené úseky se logují jen na úrovni debug, protože se opakují každých 5 minut po celou dobu zpoždění.
201
+ - **Chyby:** chyba stažení, validace nebo databáze ukončí běh chybou a uložená data zůstanou beze změny. Další běh proběhne normálně.
202
+ - **Chybějící jízdní řády:** pokud běh vyřadí všechny úseky s důvodem `no trip runs the whole segment`, pravděpodobně chybí předpočítané odjezdy pro aktuální čas (`ropidgtfs_precomputed_departures`, např. po neúspěšném importu GTFS). Úseky se pak neuloží a VYMI je nedostane.
203
+
204
+ ## Známá omezení a rizika
205
+
206
+ - **Zaseknutý výpočet Mapy PID nelze rozpoznat.** Odpověď neobsahuje čas výpočtu úseků. Pokud Mapa PID přestane úseky přepočítávat, vrací poslední stav (až 24 hodin) a ten se jeví jako aktuální. Změnu na straně Mapy PID nelze zajistit, riziko je pro zkušební provoz přijaté.
207
+ - **Úseky sloučené přes různé linky se zahodí.** Mapa PID slučuje navazující zpožděné mezizastávkové úseky podle zastávek bez ohledu na linku (úsek A→B jedné linky s úsekem B→C jiné linky). Pokud takový sloučený úsek celý neprojíždí žádný spoj, nelze určit jeho zastávky ani linky a úsek se zahodí. Zpoždění tak může zůstat bez infotextu. Počet těchto úseků je v logu každého běhu (`no trip runs the whole segment`). Řešením by bylo rozdělit úsek na části projížděné jednotlivými spoji; zda je to potřeba, ukáže zkušební provoz.
208
+ - **Mezery v `stop_sequence`** by zvětšily vzdálenosti tabulí (viz [Obohacení](#zastávky-s-tabulemi-a-jejich-vzdálenost)).
209
+ - **Úseky přes železniční body** (krajní zastávka `T…` bez ASW id) se nezpracují.
210
+ - **Chybějící název zastávky nebo linky** v GTFS datech by neprošel validací a běh by skončil chybou; data zůstanou z předchozího běhu.
211
+ - **Souběžné běhy** (např. při zdržení fronty) mohou v krajním případě přepsat novější data staršími, další běh je opraví.
212
+
213
+ ## Klíčová rozhodnutí
214
+
215
+ | Rozhodnutí | Důvod |
216
+ | --- | --- |
217
+ | Golemio úseky jen obohacuje, o infotextech rozhoduje VYMI | oddělení faktů od pravidel provozu; VYMI vlastní infotexty i jejich životní cyklus |
218
+ | `id` z ASW id krajních zastávek, ale bez stability; párování podle `stops` | Mapa PID úseky slučuje a štěpí, stabilní identita úseku neexistuje |
219
+ | API nezávislé na zdroji, data zdroje jen v DB (`external_*`) | algoritmus Mapy PID se může změnit, zdroj může být vyměněn |
220
+ | Bez nových časových sloupců pro detekci, `created_at` / `updated_at` | první a poslední detekce přirozeně odpovídají vložení a aktualizaci řádku |
221
+ | Neúspěšný běh nemění data, platnost řeší VYMI podle `last_detected_at` | výpadek zdroje nemá smazat informaci o probíhajícím zpoždění |
222
+ | Minimální zpoždění a rozsah vzdáleností tabulí určuje VYMI parametry endpointu, filtruje Output Gateway | jde o provozní rozhodnutí VYMI, Golemio ale posílá jen relevantní data; konfigurace Integration Engine jen omezuje, co se obohacuje a ukládá; testovací zastávky zůstávají filtrem ve VYMI |
223
+ | Vzdálenost tabulí se znaménkem, nejbližší za linku | VYMI rozliší tabule před úsekem, v úseku a za ním |
224
+ | Jedna tabulka s `jsonb` sloupci | data se vždy čtou celá a vyměňují celá, normalizace by nic nepřinesla |
225
+ | Vzdálenost z `stop_sequence` | výkon (viz [Výkon](#výkon)); kompromis je zdokumentovaný výše |
226
+ | Obohacení v každém běhu, bez cache | znovupoužití obohacení přetrvávajících úseků šetřilo jen desetiny sekundy za běh (v listopadu 2026 ~0,1–0,3 s na snímku produkčních dat, ~0,3–0,9 s na rabínu pro 13–40 úseků), za cenu až 30 minut starých zastávek, linek a tabulí po změně jízdních řádů, presetů nebo přechodu mezi denním a nočním provozem |
227
+ | Adresa Mapy PID jen v konfiguraci | endpoint je veřejně dostupný, ale není určen k širokému použití |
228
+
229
+ ## Zdrojový kód a testy
230
+
231
+ | Část | Soubor |
232
+ | --- | --- |
233
+ | datový zdroj | [JISDelayedSegmentsDataSourceFactory](../../src/integration-engine/jis/datasources/JISDelayedSegmentsDataSourceFactory.ts) |
234
+ | task | [RefreshDelayedSegmentsTask](../../src/integration-engine/jis/workers/tasks/RefreshDelayedSegmentsTask.ts) |
235
+ | zpracování | [JISDelayedSegmentsDataService](../../src/integration-engine/jis/services/JISDelayedSegmentsDataService.ts) |
236
+ | obohacení | [JISDelayedSegmentEnrichmentRepository](../../src/integration-engine/jis/repositories/JISDelayedSegmentEnrichmentRepository.ts), [JISDelayedSegmentEnrichmentQuery](../../src/integration-engine/jis/repositories/helpers/JISDelayedSegmentEnrichmentQuery.ts) |
237
+ | uložení | [JISDelayedSegmentsRepository (IE)](../../src/integration-engine/jis/repositories/JISDelayedSegmentsRepository.ts), [JISDelayedSegmentsModel](../../src/schema-definitions/jis/models/JISDelayedSegmentsModel.ts) |
238
+ | výstup | [V1DelayedSegmentsController](../../src/output-gateway/jis/controllers/v1/V1DelayedSegmentsController.ts), [JISDelayedSegmentsTransformation](../../src/output-gateway/jis/transformations/JISDelayedSegmentsTransformation.ts) |
239
+ | migrace | [20261027100000-jis-delayed-segments](../../db/migrations/postgresql/sqls/20261027100000-jis-delayed-segments-up.sql) |
240
+ | testovací data | [24_jis_delayed_segments.sql](../../db/example/24_jis_delayed_segments.sql) – syntetický koridor s linkami, které úsek projíždějí celý, částečně, jen se ho dotknou, jsou výjimečné, jedou mimo časové okno nebo mají mezeru v `stop_sequence` |
241
+ | testy | `test/integration-engine/jis/**/*DelayedSegment*`, `test/output-gateway/jis/**/*DelayedSegments*` |
package/docs/jis/index.md CHANGED
@@ -306,3 +306,12 @@ Experimentální feed pro e-paper displeje, generovaný stejným taskem. Obsahuj
306
306
 
307
307
  - `/v2/pid/departureboards`, `/v3/pid/transferboards` a `/v3/pid/infotexts` čtou infotexty z PostgreSQL; u odjezdových tabulí s parametrem `preset` se vrací jen infotexty navázané na daný preset (`jis_infotexts_presets.route_name`);
308
308
  - `/v4/pid/transferboards` čte infotexty z Redis cache `jisCache:*`.
309
+
310
+ ### Zpožděné úseky — podklad pro automatické infotexty (`GET /v1/jis/delayed-segments`)
311
+
312
+ Endpoint je interní a slouží VYMI k automatickému vytváření interních událostí a infotextů o zpožděných úsecích ([vymi-general#365](https://gitlab.com/operator-ict/golemio/jis/vymi-alerts/vymi-general/-/work_items/365)). Úseky detekuje Mapa PID, Golemio je stahuje, obohacuje a poskytuje, VYMI rozhoduje, zda a jak vytvořit událost/infotext. Záměr, architekturu, algoritmus, výkon a rozhodnutí podrobně popisuje stránka [Zpožděné úseky](./delayed-segments.md).
313
+
314
+ Úseky každých 5 minut stahuje `RefreshDelayedSegmentsTask` z Mapy PID a obohacuje je o zastávky úseku, dotčené linky a zastávky s panely včetně jejich vzdálenosti od úseku (viz [Zpracování](./delayed-segments.md#zpracování) a [implementační dokumentace](../implementation_documentation.md#task-refreshdelayedsegmentstask)). Pro VYMI platí:
315
+ - úseky v čase rostou a zmenšují se (Mapa PID slučuje navazující zpožděné mezizastávkové úseky), `id` proto není stabilní identifikace; VYMI úseky páruje podle `stops` (alespoň dvě společné zastávky);
316
+ - při chybě stažení zůstávají uložené úseky beze změny a nemažou se; VYMI proto drží událost platnou do `last_detected_at` + 15 minut a nechá ji vypršet, když se `last_detected_at` přestane posouvat; Mapa PID navíc neposílá čas výpočtu úseků, takže zaseknutý výpočet na její straně nelze rozpoznat;
317
+ - minimální zpoždění úseku (`minDelaySeconds`) a rozsah vzdáleností zastávek s panely od úseku (`minDistanceStops`, `maxDistanceStops`) si VYMI určuje parametry endpointu.