@tmlmobilidade/controllers 20260505.1315.54 → 20260505.2251.5

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.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './agencies/index.js';
2
2
  export * from './exporter/index.js';
3
- export * from './rides/index.js';
3
+ export * from './operation/index.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export * from './agencies/index.js';
2
2
  export * from './exporter/index.js';
3
- export * from './rides/index.js';
3
+ export * from './operation/index.js';
@@ -0,0 +1,12 @@
1
+ import { type FastifyReply, type FastifyRequest } from '@tmlmobilidade/fastify';
2
+ import { type ActionsOf, type GetRidesBatchQuery, type HashedTrip, type Permission } from '@tmlmobilidade/types';
3
+ export declare class HashedTripsSharedController {
4
+ /**
5
+ * Gets a batch of HashedTrips built with an aggregation pipeline.
6
+ * @param request The Fastify request object.
7
+ * @param reply The Fastify reply object.
8
+ */
9
+ static getBatch<S extends Permission['scope']>(request: FastifyRequest<{
10
+ Querystring: GetRidesBatchQuery;
11
+ }>, reply: FastifyReply<HashedTrip[]>, scope: S, action: ActionsOf<S>): Promise<never>;
12
+ }
@@ -0,0 +1,66 @@
1
+ /* * */
2
+ import { HTTP_STATUS } from '@tmlmobilidade/consts';
3
+ import { hashedTrips, rides, ridesBatchAggregationPipeline } from '@tmlmobilidade/interfaces';
4
+ import { Logger } from '@tmlmobilidade/logger';
5
+ import { GetRidesBatchQuerySchema, PermissionCatalog } from '@tmlmobilidade/types';
6
+ /* * */
7
+ export class HashedTripsSharedController {
8
+ //
9
+ /**
10
+ * Gets a batch of HashedTrips built with an aggregation pipeline.
11
+ * @param request The Fastify request object.
12
+ * @param reply The Fastify reply object.
13
+ */
14
+ static async getBatch(request, reply, scope, action) {
15
+ //
16
+ //
17
+ // Validate the request query parameters
18
+ const parsedQuery = GetRidesBatchQuerySchema.parse(request.query);
19
+ //
20
+ // Detect which agency_ids the user has access to,
21
+ // based on their permissions. If none, return an empty array.
22
+ const ridesPermission = PermissionCatalog.get(request.permissions, scope, action);
23
+ if (!ridesPermission['resources']?.agency_ids?.length)
24
+ return reply.send({ data: [], error: null, statusCode: HTTP_STATUS.OK });
25
+ const allowAllAgencies = ridesPermission['resources'].agency_ids.includes(PermissionCatalog.ALLOW_ALL_FLAG);
26
+ //
27
+ // Get the rides batch using native MongoDB cursor
28
+ // with batchSize to prevent memory issues
29
+ const pipeline = ridesBatchAggregationPipeline({
30
+ acceptance_status: parsedQuery.acceptance_status,
31
+ agency_ids: parsedQuery.agency_ids?.filter(id => allowAllAgencies || ridesPermission['resources'].agency_ids.includes(id)) ?? [],
32
+ analysis_ended_at_last_stop_grade: parsedQuery.analysis_ended_at_last_stop_grade,
33
+ analysis_expected_apex_validation_interval: parsedQuery.analysis_expected_apex_validation_interval,
34
+ analysis_simple_three_vehicle_events_grade: parsedQuery.analysis_simple_three_vehicle_events_grade,
35
+ analysis_transaction_sequentiality: parsedQuery.analysis_transaction_sequentiality,
36
+ date_end: parsedQuery.date_end,
37
+ date_start: parsedQuery.date_start,
38
+ delay_statuses: parsedQuery.delay_statuses,
39
+ line_ids: parsedQuery.line_ids,
40
+ operational_statuses: parsedQuery.operational_statuses,
41
+ search: parsedQuery.search,
42
+ seen_statuses: parsedQuery.seen_statuses,
43
+ stop_ids: parsedQuery.stop_ids,
44
+ });
45
+ //
46
+ // Limit the number of rides to 2000 and sort by start_time_scheduled
47
+ pipeline.push({ $limit: 2000 }, { $project: { hashed_trip_id: 1 } }, { $sort: { start_time_scheduled: 1 } });
48
+ //
49
+ // Fetch the rides batch from the database
50
+ const ridesBatch = await rides.aggregate(pipeline);
51
+ Logger.info(`HashedTripsSharedController.getBatch - ridesBatch count: ${ridesBatch?.length ?? 0}`);
52
+ //
53
+ // From the given batch of hashed_trip_ids,
54
+ // fetch the full HashedTrip documents with a single query.
55
+ const hashedTripIds = ridesBatch.map(ride => ride.hashed_trip_id);
56
+ const hashedTripsBatch = await hashedTrips.findMany({ _id: { $in: hashedTripIds } });
57
+ //
58
+ // Send the response
59
+ reply.send({
60
+ data: hashedTripsBatch ?? [],
61
+ error: null,
62
+ statusCode: HTTP_STATUS.OK,
63
+ });
64
+ //
65
+ }
66
+ }
@@ -0,0 +1 @@
1
+ export * from './hashed-trips.js';
@@ -0,0 +1 @@
1
+ export * from './hashed-trips.js';
@@ -0,0 +1,4 @@
1
+ export * from './hashed-trips/index.js';
2
+ export * from './lines/index.js';
3
+ export * from './rides/index.js';
4
+ export * from './stops/index.js';
@@ -0,0 +1,4 @@
1
+ export * from './hashed-trips/index.js';
2
+ export * from './lines/index.js';
3
+ export * from './rides/index.js';
4
+ export * from './stops/index.js';
@@ -0,0 +1,2 @@
1
+ import { type GetOperationalLinesBatchQuery, type OperationalLine } from '@tmlmobilidade/types';
2
+ export declare function getOperationalLinesBatch(query: GetOperationalLinesBatchQuery): Promise<OperationalLine[]>;
@@ -0,0 +1,118 @@
1
+ /* * */
2
+ import { rides } from '@tmlmobilidade/interfaces';
3
+ import { Logger } from '@tmlmobilidade/logger';
4
+ /* * */
5
+ export async function getOperationalLinesBatch(query) {
6
+ //
7
+ //
8
+ // Use Rides as the baseline to fetch distinct hashed_trip_ids matching the query parameters.
9
+ // Rides are the glue between the different entities (Patterns, Lines, Stops, etc...) that compose an Operation,
10
+ // Stream the rides to build the Operation Lines batch on the fly, avoiding loading everything in memory at once.
11
+ const pipeline = [
12
+ {
13
+ $match: {
14
+ agency_id: { $in: query.agency_ids ?? [] },
15
+ start_time_scheduled: { $gte: query.date_start, $lte: query.date_end },
16
+ },
17
+ },
18
+ {
19
+ $sort: {
20
+ start_time_scheduled: -1, // newest first
21
+ },
22
+ },
23
+ {
24
+ $group: {
25
+ _id: '$hashed_trip_id',
26
+ agency_id: { $first: '$agency_id' },
27
+ hashed_trip_id: { $first: '$hashed_trip_id' },
28
+ line_id: { $first: '$line_id' },
29
+ operational_date: { $first: '$operational_date' },
30
+ plan_id: { $first: '$plan_id' },
31
+ start_time_scheduled: { $first: '$start_time_scheduled' },
32
+ },
33
+ },
34
+ {
35
+ $sort: {
36
+ start_time_scheduled: -1,
37
+ },
38
+ },
39
+ {
40
+ $project: {
41
+ _id: 0,
42
+ agency_id: 1,
43
+ hashed_trip_id: 1,
44
+ line_id: 1,
45
+ operational_date: 1,
46
+ plan_id: 1,
47
+ start_time_scheduled: 1,
48
+ },
49
+ },
50
+ {
51
+ $lookup: {
52
+ as: 'hashed_trip_doc',
53
+ foreignField: '_id',
54
+ from: 'hashed_trips',
55
+ localField: 'hashed_trip_id',
56
+ },
57
+ },
58
+ {
59
+ $unwind: {
60
+ path: '$hashed_trip_doc',
61
+ preserveNullAndEmptyArrays: true,
62
+ },
63
+ },
64
+ {
65
+ $sort: {
66
+ start_time_scheduled: -1,
67
+ },
68
+ },
69
+ {
70
+ $project: {
71
+ _id: 0,
72
+ agency_id: 1,
73
+ hashed_trip_doc: 1, // full joined document
74
+ operational_date: 1,
75
+ plan_id: 1,
76
+ },
77
+ },
78
+ ];
79
+ const ridesCollection = await rides.getCollection();
80
+ const pipelineResult = await ridesCollection
81
+ .aggregate(pipeline)
82
+ .toArray();
83
+ Logger.info(`OperationalLinesController.getBatch - pipeline result count: ${pipelineResult?.length ?? 0}`);
84
+ //
85
+ // Setup the final Map to keep track of the Operation Lines,
86
+ // using the line_id as the key to avoid duplicates,
87
+ // since multiple hashed_trip_ids can belong to the same line_id.
88
+ const operationalLinesMap = new Map();
89
+ pipelineResult.forEach((item) => {
90
+ // Initialize the line in the map if it doesn't exist yet
91
+ if (!operationalLinesMap.has(item.hashed_trip_doc.line_id)) {
92
+ operationalLinesMap.set(item.hashed_trip_doc.line_id, {
93
+ agency_id: item.agency_id,
94
+ hashed_trips: [],
95
+ last_operational_date: item.operational_date,
96
+ last_plan_id: item.plan_id,
97
+ line_id: item.hashed_trip_doc.line_id,
98
+ line_long_name: item.hashed_trip_doc.line_long_name,
99
+ line_short_name: item.hashed_trip_doc.line_short_name,
100
+ pattern_ids: [],
101
+ route_color: item.hashed_trip_doc.route_color,
102
+ route_ids: [],
103
+ stop_ids: [],
104
+ });
105
+ }
106
+ // Get the saved line from the map
107
+ const savedOperationalLine = operationalLinesMap.get(item.hashed_trip_doc.line_id);
108
+ // Update the object with the latest fields
109
+ savedOperationalLine.route_ids = Array.from(new Set([...savedOperationalLine.route_ids, item.hashed_trip_doc.route_id]));
110
+ savedOperationalLine.pattern_ids = Array.from(new Set([...savedOperationalLine.pattern_ids, item.hashed_trip_doc.pattern_id]));
111
+ savedOperationalLine.stop_ids = Array.from(new Set([...savedOperationalLine.stop_ids, ...(item.hashed_trip_doc.path.map(stop => stop.stop_id) ?? [])]));
112
+ savedOperationalLine.hashed_trips.push(item.hashed_trip_doc);
113
+ });
114
+ //
115
+ // Send the response
116
+ return Array.from(operationalLinesMap.values());
117
+ //
118
+ }
@@ -0,0 +1,12 @@
1
+ import { type FastifyReply, type FastifyRequest } from '@tmlmobilidade/fastify';
2
+ import { type ActionsOf, type GetOperationalLinesBatchQuery, type OperationalLine, type Permission } from '@tmlmobilidade/types';
3
+ export declare class OperationalLinesSharedController {
4
+ /**
5
+ * Gets a batch of Operation Lines built with an aggregation pipeline.
6
+ * @param request The Fastify request object.
7
+ * @param reply The Fastify reply object.
8
+ */
9
+ static getBatch<S extends Permission['scope']>(request: FastifyRequest<{
10
+ Querystring: GetOperationalLinesBatchQuery;
11
+ }>, reply: FastifyReply<OperationalLine[]>, scope: S, action: ActionsOf<S>): Promise<never>;
12
+ }
@@ -0,0 +1,38 @@
1
+ /* * */
2
+ import { getOperationalLinesBatch } from './batch.js';
3
+ import { HTTP_STATUS } from '@tmlmobilidade/consts';
4
+ import { GetOperationalLinesBatchQuerySchema, PermissionCatalog } from '@tmlmobilidade/types';
5
+ /* * */
6
+ export class OperationalLinesSharedController {
7
+ //
8
+ /**
9
+ * Gets a batch of Operation Lines built with an aggregation pipeline.
10
+ * @param request The Fastify request object.
11
+ * @param reply The Fastify reply object.
12
+ */
13
+ static async getBatch(request, reply, scope, action) {
14
+ //
15
+ const parsedQuery = GetOperationalLinesBatchQuerySchema.parse(request.query);
16
+ //
17
+ // Detect which agency_ids the user has access to,
18
+ // based on their permissions. If none, return an empty array.
19
+ const ridesPermission = PermissionCatalog.get(request.permissions, scope, action);
20
+ if (!ridesPermission['resources']?.agency_ids?.length)
21
+ return reply.send({ data: [], error: null, statusCode: HTTP_STATUS.OK });
22
+ const allowAllAgencies = ridesPermission['resources'].agency_ids.includes(PermissionCatalog.ALLOW_ALL_FLAG);
23
+ //
24
+ // Run the query
25
+ const result = await getOperationalLinesBatch({
26
+ ...parsedQuery,
27
+ agency_ids: parsedQuery.agency_ids?.filter(id => allowAllAgencies || ridesPermission['resources'].agency_ids.includes(id)) ?? [],
28
+ });
29
+ //
30
+ // Send the response
31
+ reply.send({
32
+ data: result,
33
+ error: null,
34
+ statusCode: HTTP_STATUS.OK,
35
+ });
36
+ //
37
+ }
38
+ }
@@ -0,0 +1,2 @@
1
+ export * from './batch.js';
2
+ export * from './controller.js';
@@ -0,0 +1,2 @@
1
+ export * from './batch.js';
2
+ export * from './controller.js';
@@ -1 +1,2 @@
1
1
  export * from './rides.js';
2
+ export * from './watch.js';
@@ -1 +1,2 @@
1
1
  export * from './rides.js';
2
+ export * from './watch.js';
@@ -0,0 +1,2 @@
1
+ import { type GetOperationalStopsBatchQuery, type OperationalStop } from '@tmlmobilidade/types';
2
+ export declare function getOperationalStopsBatch(query: GetOperationalStopsBatchQuery): Promise<OperationalStop[]>;
@@ -0,0 +1,119 @@
1
+ /* * */
2
+ import { rides } from '@tmlmobilidade/interfaces';
3
+ import { Logger } from '@tmlmobilidade/logger';
4
+ /* * */
5
+ export async function getOperationalStopsBatch(query) {
6
+ //
7
+ //
8
+ // Use Rides as the baseline to fetch distinct hashed_trip_ids matching the query parameters.
9
+ // Rides are the glue between the different entities (Patterns, Lines, Stops, etc...) that compose an Operation,
10
+ // Stream the rides to build the Operation Stops batch on the fly, avoiding loading everything in memory at once.
11
+ const pipeline = [
12
+ {
13
+ $match: {
14
+ agency_id: { $in: query.agency_ids ?? [] },
15
+ start_time_scheduled: { $gte: query.date_start, $lte: query.date_end },
16
+ },
17
+ },
18
+ {
19
+ $sort: {
20
+ start_time_scheduled: -1, // newest first
21
+ },
22
+ },
23
+ {
24
+ $group: {
25
+ _id: '$hashed_trip_id',
26
+ agency_id: { $first: '$agency_id' },
27
+ hashed_trip_id: { $first: '$hashed_trip_id' },
28
+ line_id: { $first: '$line_id' },
29
+ operational_date: { $first: '$operational_date' },
30
+ plan_id: { $first: '$plan_id' },
31
+ start_time_scheduled: { $first: '$start_time_scheduled' },
32
+ },
33
+ },
34
+ {
35
+ $sort: {
36
+ start_time_scheduled: -1,
37
+ },
38
+ },
39
+ {
40
+ $project: {
41
+ _id: 0,
42
+ agency_id: 1,
43
+ hashed_trip_id: 1,
44
+ line_id: 1,
45
+ operational_date: 1,
46
+ plan_id: 1,
47
+ start_time_scheduled: 1,
48
+ },
49
+ },
50
+ {
51
+ $lookup: {
52
+ as: 'hashed_trip_doc',
53
+ foreignField: '_id',
54
+ from: 'hashed_trips',
55
+ localField: 'hashed_trip_id',
56
+ },
57
+ },
58
+ {
59
+ $unwind: {
60
+ path: '$hashed_trip_doc',
61
+ preserveNullAndEmptyArrays: true,
62
+ },
63
+ },
64
+ {
65
+ $sort: {
66
+ start_time_scheduled: -1,
67
+ },
68
+ },
69
+ {
70
+ $project: {
71
+ _id: 0,
72
+ agency_id: 1,
73
+ hashed_trip_doc: 1, // full joined document
74
+ operational_date: 1,
75
+ plan_id: 1,
76
+ },
77
+ },
78
+ ];
79
+ const ridesCollection = await rides.getCollection();
80
+ const pipelineResult = await ridesCollection
81
+ .aggregate(pipeline)
82
+ .toArray();
83
+ Logger.info(`OperationalStopsController.getBatch - pipeline result count: ${pipelineResult?.length ?? 0}`);
84
+ //
85
+ // Setup the final Map to keep track of the Operation Stops,
86
+ // using the stop_id as the key to avoid duplicates,
87
+ // since multiple hashed_trip_ids can belong to the same stop_id.
88
+ const operationalStopsMap = new Map();
89
+ pipelineResult.forEach((item) => {
90
+ item.hashed_trip_doc.path.forEach((waypoint) => {
91
+ // Initialize the stop in the map if it doesn't exist yet
92
+ if (!operationalStopsMap.has(waypoint.stop_id)) {
93
+ operationalStopsMap.set(waypoint.stop_id, {
94
+ agency_ids: [],
95
+ hashed_trips: [],
96
+ last_operational_date: item.operational_date,
97
+ last_plan_id: item.plan_id,
98
+ line_ids: [],
99
+ pattern_ids: [],
100
+ route_ids: [],
101
+ stop_id: waypoint.stop_id,
102
+ stop_name: waypoint.stop_name,
103
+ });
104
+ }
105
+ // Get the saved stop from the map
106
+ const savedOperationalStop = operationalStopsMap.get(waypoint.stop_id);
107
+ // Update the object with the latest fields
108
+ savedOperationalStop.agency_ids = Array.from(new Set([...savedOperationalStop.agency_ids, item.agency_id]));
109
+ savedOperationalStop.line_ids = Array.from(new Set([...savedOperationalStop.line_ids, item.hashed_trip_doc.line_id]));
110
+ savedOperationalStop.route_ids = Array.from(new Set([...savedOperationalStop.route_ids, item.hashed_trip_doc.route_id]));
111
+ savedOperationalStop.pattern_ids = Array.from(new Set([...savedOperationalStop.pattern_ids, item.hashed_trip_doc.pattern_id]));
112
+ savedOperationalStop.hashed_trips.push(item.hashed_trip_doc);
113
+ });
114
+ });
115
+ //
116
+ // Send the response
117
+ return Array.from(operationalStopsMap.values());
118
+ //
119
+ }
@@ -0,0 +1,12 @@
1
+ import { type FastifyReply, type FastifyRequest } from '@tmlmobilidade/fastify';
2
+ import { type ActionsOf, type GetOperationalStopsBatchQuery, type OperationalStop, type Permission } from '@tmlmobilidade/types';
3
+ export declare class OperationalStopsSharedController {
4
+ /**
5
+ * Gets a batch of Operational Stops built with an aggregation pipeline.
6
+ * @param request The Fastify request object.
7
+ * @param reply The Fastify reply object.
8
+ */
9
+ static getBatch<S extends Permission['scope']>(request: FastifyRequest<{
10
+ Querystring: GetOperationalStopsBatchQuery;
11
+ }>, reply: FastifyReply<OperationalStop[]>, scope: S, action: ActionsOf<S>): Promise<never>;
12
+ }
@@ -0,0 +1,38 @@
1
+ /* * */
2
+ import { getOperationalStopsBatch } from './batch.js';
3
+ import { HTTP_STATUS } from '@tmlmobilidade/consts';
4
+ import { GetOperationalStopsBatchQuerySchema, PermissionCatalog } from '@tmlmobilidade/types';
5
+ /* * */
6
+ export class OperationalStopsSharedController {
7
+ //
8
+ /**
9
+ * Gets a batch of Operational Stops built with an aggregation pipeline.
10
+ * @param request The Fastify request object.
11
+ * @param reply The Fastify reply object.
12
+ */
13
+ static async getBatch(request, reply, scope, action) {
14
+ //
15
+ const parsedQuery = GetOperationalStopsBatchQuerySchema.parse(request.query);
16
+ //
17
+ // Detect which agency_ids the user has access to,
18
+ // based on their permissions. If none, return an empty array.
19
+ const ridesPermission = PermissionCatalog.get(request.permissions, scope, action);
20
+ if (!ridesPermission['resources']?.agency_ids?.length)
21
+ return reply.send({ data: [], error: null, statusCode: HTTP_STATUS.OK });
22
+ const allowAllAgencies = ridesPermission['resources'].agency_ids.includes(PermissionCatalog.ALLOW_ALL_FLAG);
23
+ //
24
+ // Run the query
25
+ const result = await getOperationalStopsBatch({
26
+ ...parsedQuery,
27
+ agency_ids: parsedQuery.agency_ids?.filter(id => allowAllAgencies || ridesPermission['resources'].agency_ids.includes(id)) ?? [],
28
+ });
29
+ //
30
+ // Send the response
31
+ reply.send({
32
+ data: result,
33
+ error: null,
34
+ statusCode: HTTP_STATUS.OK,
35
+ });
36
+ //
37
+ }
38
+ }
@@ -0,0 +1,2 @@
1
+ export * from './batch.js';
2
+ export * from './controller.js';
@@ -0,0 +1,2 @@
1
+ export * from './batch.js';
2
+ export * from './controller.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmlmobilidade/controllers",
3
- "version": "20260505.1315.54",
3
+ "version": "20260505.2251.5",
4
4
  "author": {
5
5
  "email": "iso@tmlmobilidade.pt",
6
6
  "name": "TML-ISO"
@@ -39,6 +39,7 @@
39
39
  "@tmlmobilidade/consts": "*",
40
40
  "@tmlmobilidade/fastify": "*",
41
41
  "@tmlmobilidade/interfaces": "*",
42
+ "@tmlmobilidade/logger": "*",
42
43
  "@tmlmobilidade/normalizers": "*",
43
44
  "@tmlmobilidade/utils": "*"
44
45
  },
File without changes
File without changes
File without changes
File without changes