@tmlmobilidade/export-data 20260304.1819.11 → 20260421.1523.13

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 (42) hide show
  1. package/dist/index.js +6 -0
  2. package/dist/prompts/export-types.js +3 -0
  3. package/dist/prompts/filter-line-ids.js +7 -13
  4. package/dist/prompts/filter-stop-ids.js +6 -13
  5. package/dist/tasks/executive-summary-setup/avg-trips-day.js +58 -0
  6. package/dist/tasks/executive-summary-setup/empty-runs.js +100 -0
  7. package/dist/tasks/executive-summary-setup/index.js +248 -0
  8. package/dist/tasks/executive-summary-setup/km.js +50 -0
  9. package/dist/tasks/executive-summary-setup/median-speed.js +80 -0
  10. package/dist/tasks/executive-summary-setup/on-board-sales.js +58 -0
  11. package/dist/tasks/executive-summary-setup/passenger-impact.js +46 -0
  12. package/dist/tasks/executive-summary-setup/passengers-transported.js +56 -0
  13. package/dist/tasks/executive-summary-setup/paxperkm.js +73 -0
  14. package/dist/tasks/executive-summary-setup/trips.js +132 -0
  15. package/dist/tasks/executive-summary-setup/tripstatus.js +114 -0
  16. package/dist/types.js +1 -0
  17. package/dist/utils/dates-helper.js +3 -0
  18. package/dist/utils/parse-id-list.js +37 -0
  19. package/package.json +4 -3
  20. package/dist/index.d.ts +0 -2
  21. package/dist/prompts/access-key.d.ts +0 -1
  22. package/dist/prompts/export-types.d.ts +0 -2
  23. package/dist/prompts/filter-agency-ids.d.ts +0 -1
  24. package/dist/prompts/filter-dates.d.ts +0 -5
  25. package/dist/prompts/filter-line-ids.d.ts +0 -1
  26. package/dist/prompts/filter-pattern-ids.d.ts +0 -1
  27. package/dist/prompts/filter-stop-ids.d.ts +0 -1
  28. package/dist/prompts/filter-types.d.ts +0 -1
  29. package/dist/prompts/filter-vehicle-ids.d.ts +0 -1
  30. package/dist/prompts/hashedshape-ids.d.ts +0 -1
  31. package/dist/prompts/validation-group-fields.d.ts +0 -2
  32. package/dist/tasks/apex-validations/validations-aggregated.d.ts +0 -18
  33. package/dist/tasks/apex-validations/validations-raw.d.ts +0 -2
  34. package/dist/tasks/hashed-shapes/hashed-shapes-geojson.d.ts +0 -4
  35. package/dist/tasks/rides/rides-raw.d.ts +0 -2
  36. package/dist/tasks/sams/sams-raw.d.ts +0 -2
  37. package/dist/tasks/sams/sams-raw.types.d.ts +0 -18
  38. package/dist/tasks/vehicle-events/vehicle-events-raw.d.ts +0 -2
  39. package/dist/types.d.ts +0 -35
  40. package/dist/utils/credential-storage.d.ts +0 -18
  41. package/dist/utils/hashed-shapes-to-geojson.d.ts +0 -3
  42. package/dist/utils/init-context.d.ts +0 -2
@@ -0,0 +1,58 @@
1
+ import { Dates } from '@tmlmobilidade/dates';
2
+ import { simplifiedApexOnBoardSales } from '@tmlmobilidade/interfaces';
3
+ /* Main function */
4
+ export async function calculateOnBoardSales({ context, message }) {
5
+ message('Calculating on-board sales...');
6
+ const collection = await simplifiedApexOnBoardSales.getCollection();
7
+ // Convert received dates (yyyymmdd) to UTC range
8
+ const start = context.dates.start;
9
+ const end = context.dates.end;
10
+ const startDate = Dates.fromOperationalDate(start, 'Europe/Lisbon').unix_timestamp;
11
+ const endDate = Dates.fromOperationalDate(end, 'Europe/Lisbon').unix_timestamp;
12
+ // MongoDB pipeline based on your query
13
+ const pipeline = [
14
+ {
15
+ $match: {
16
+ agency_id: { $exists: true },
17
+ created_at: { $gte: startDate, $lte: endDate },
18
+ is_passenger: true,
19
+ },
20
+ },
21
+ {
22
+ $group: {
23
+ _id: '$agency_id',
24
+ totalRevenueCents: { $sum: '$price' },
25
+ },
26
+ },
27
+ {
28
+ $project: {
29
+ _id: 0,
30
+ agencyId: '$_id',
31
+ totalRevenue: {
32
+ $divide: ['$totalRevenueCents', 100], // convert to euros
33
+ },
34
+ },
35
+ },
36
+ {
37
+ $sort: { agencyId: 1 },
38
+ },
39
+ ];
40
+ const cursor = collection.aggregate(pipeline);
41
+ const results = [];
42
+ // console.log('Pipeline results:', JSON.stringify(pipeline, null, 2));
43
+ for await (const doc of cursor) {
44
+ // Since the original query has no date, use startDate as reference
45
+ const date = new Date(startDate);
46
+ const day = date.getUTCDate().toString().padStart(2, '0');
47
+ const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
48
+ const year = date.getUTCFullYear();
49
+ const formattedDate = `${day}/${month}/${year}`;
50
+ results.push({
51
+ 'agencyId': doc.agencyId,
52
+ 'date': formattedDate,
53
+ 'onboard-sales-pr': parseFloat(doc.totalRevenue.toFixed(2)),
54
+ });
55
+ }
56
+ message(`Processed ${results.length} on-board sales records`);
57
+ return results;
58
+ }
@@ -0,0 +1,46 @@
1
+ import { yyyymmddToDashed } from '../../utils/dates-helper.js';
2
+ import { metrics } from '@tmlmobilidade/interfaces';
3
+ /**
4
+ * Calculates the estimated affected passengers by agency and day
5
+ * from the "demand_affected_by_failed_circulations_by_day" metric.
6
+ */
7
+ export async function calculateAffectedPassengers({ context, message }) {
8
+ message('Calculating passengers affected by failed circulations...');
9
+ const metricsCollection = await metrics.getCollection();
10
+ // Fetch only the relevant metric documents
11
+ message('Fetching affected passengers metrics...');
12
+ const docs = await metricsCollection
13
+ .find({
14
+ metric: 'demand_affected_by_failed_circulations_by_day',
15
+ })
16
+ .toArray();
17
+ message(`Found ${docs.length} documents to process`);
18
+ const results = new Map(); // key = `${date}-${agencyId}`
19
+ const startDate = yyyymmddToDashed(context.dates.start);
20
+ const endDate = yyyymmddToDashed(context.dates.end);
21
+ // Process each metric document
22
+ for (const doc of docs) {
23
+ const agencyId = doc.properties.agency_id;
24
+ for (const [date, dayData] of Object.entries(doc.data)) {
25
+ // Skip dates outside the context range
26
+ if (date < startDate.replace(/-/g, '') || date > endDate.replace(/-/g, ''))
27
+ continue;
28
+ const dashedDate = yyyymmddToDashed(date);
29
+ const key = `${dashedDate}-${agencyId}`;
30
+ // Initialize entry if it doesn't exist
31
+ if (!results.has(key)) {
32
+ results.set(key, {
33
+ agencyId,
34
+ date: dashedDate,
35
+ estimatedpax_affected_by_failures: 0,
36
+ });
37
+ }
38
+ // Add estimated affected passengers
39
+ const entry = results.get(key);
40
+ entry.estimatedpax_affected_by_failures += dayData.estimated_affected_passengers;
41
+ }
42
+ }
43
+ message(`Processed ${results.size} affected passenger records`);
44
+ // Return results as an array
45
+ return Array.from(results.values());
46
+ }
@@ -0,0 +1,56 @@
1
+ import { yyyymmddToDashed } from '../../utils/dates-helper.js';
2
+ import { metrics } from '@tmlmobilidade/interfaces';
3
+ /* * */
4
+ export async function calculateDemandFromMetrics({ context, message }) {
5
+ message('A calcular procura a partir das métricas...');
6
+ const metricsCollection = await metrics.getCollection();
7
+ //
8
+ // Fetch only relevant metrics
9
+ message('A ir buscar métricas de procura...');
10
+ const docs = await metricsCollection.find({
11
+ metric: { $in: ['demand_by_product_by_agency_by_day', 'demand_by_category_by_agency_by_day'] },
12
+ }).toArray();
13
+ message(`Encontradas ${docs.length} métricas para processar`);
14
+ const results = new Map(); // key = `${date}-${agencyId}`
15
+ const startDate = yyyymmddToDashed(context.dates.start);
16
+ const endDate = yyyymmddToDashed(context.dates.end);
17
+ //
18
+ // Process each metric document
19
+ for (const doc of docs) {
20
+ const agencyId = doc.properties.agency_id;
21
+ const isProductMetric = doc.metric === 'demand_by_product_by_agency_by_day';
22
+ for (const [date, dayData] of Object.entries(doc.data)) {
23
+ if (date < startDate || date > endDate)
24
+ continue;
25
+ const key = `${date}-${agencyId}`;
26
+ if (!results.has(key)) {
27
+ results.set(key, {
28
+ agencyId,
29
+ byCategory: {},
30
+ byProduct: {},
31
+ date,
32
+ totalpassengers: 0,
33
+ });
34
+ }
35
+ const entry = results.get(key);
36
+ const qty = dayData.qty;
37
+ if (isProductMetric) {
38
+ const productId = doc.properties.product_id;
39
+ entry.byProduct[productId] = (entry.byProduct[productId] || 0) + qty;
40
+ }
41
+ else {
42
+ const category = doc.properties.category;
43
+ entry.byCategory[category] = (entry.byCategory[category] || 0) + qty;
44
+ }
45
+ }
46
+ }
47
+ //
48
+ // Calculate totalpassengers from byProduct
49
+ message('A calcular totais de passageiros...');
50
+ for (const entry of results.values()) {
51
+ entry.totalpassengers = Object.values(entry.byProduct).reduce((sum, v) => sum + v, 0);
52
+ }
53
+ message(`Processados ${results.size} registos de procura`);
54
+ return Array.from(results.values());
55
+ //
56
+ }
@@ -0,0 +1,73 @@
1
+ import { Dates } from '@tmlmobilidade/dates';
2
+ import { rides } from '@tmlmobilidade/interfaces';
3
+ export async function calculatePassengersPerKm({ context }) {
4
+ console.log('Calculating passengers per km for executed trips...');
5
+ const ridesCollection = await rides.getCollection();
6
+ const startDateStr = Dates.fromOperationalDate(context.dates.start, 'Europe/Lisbon').unix_timestamp;
7
+ const endDateStr = Dates.fromOperationalDate(context.dates.end, 'Europe/Lisbon').unix_timestamp;
8
+ console.log(`Date range: ${startDateStr} to ${endDateStr}`);
9
+ const pipeline = [
10
+ {
11
+ $match: {
12
+ agency_id: { $exists: true },
13
+ start_time_scheduled: { $gte: startDateStr, $lte: endDateStr },
14
+ },
15
+ },
16
+ {
17
+ $project: {
18
+ agencyId: '$agency_id',
19
+ extensionScheduled: '$extension_scheduled',
20
+ isValid: {
21
+ $or: [
22
+ { $eq: ['$analysis.SIMPLE_ONE_APEX_VALIDATION.grade', 'pass'] },
23
+ { $eq: ['$analysis.SIMPLE_THREE_VEHICLE_EVENTS.grade', 'pass'] },
24
+ ],
25
+ },
26
+ passengersObserved: '$passengers_observed',
27
+ },
28
+ },
29
+ {
30
+ $group: {
31
+ _id: { agencyId: '$agencyId' },
32
+ totalKm: { $sum: { $cond: ['$isValid', '$extensionScheduled', 0] } },
33
+ totalPassengers: { $sum: { $cond: ['$isValid', '$passengersObserved', 0] } },
34
+ },
35
+ },
36
+ {
37
+ $project: {
38
+ _id: 0,
39
+ agencyId: '$_id.agencyId',
40
+ passengersPerKm: {
41
+ $cond: [
42
+ { $eq: ['$totalKm', 0] },
43
+ 0,
44
+ {
45
+ $round: [
46
+ { $divide: ['$totalPassengers', { $divide: ['$totalKm', 1000] }] },
47
+ 2,
48
+ ],
49
+ },
50
+ ],
51
+ },
52
+ totalKm: 1,
53
+ totalPassengers: 1,
54
+ },
55
+ },
56
+ ];
57
+ const aggCursor = ridesCollection.aggregate(pipeline);
58
+ const results = [];
59
+ for await (const doc of aggCursor) {
60
+ // // Total Passengers, Km, passengersPerKm DEBUG
61
+ // console.log(
62
+ // `[DEBUG] Agency: ${doc.agencyId} | Total Passengers: ${doc.totalPassengers} | Total Km: ${doc.totalKm} | passengersPerKm: ${doc.passengersPerKm}`,
63
+ // );
64
+ results.push({
65
+ agencyId: doc.agencyId,
66
+ passengersPerKm: doc.passengersPerKm,
67
+ totalKm: doc.totalKm,
68
+ totalPassengers: doc.totalPassengers,
69
+ });
70
+ }
71
+ console.log(`Processed ${results.length} agencies for the interval.`);
72
+ return results;
73
+ }
@@ -0,0 +1,132 @@
1
+ import { Dates } from '@tmlmobilidade/dates';
2
+ import { rides } from '@tmlmobilidade/interfaces';
3
+ /* * */
4
+ /**
5
+ * Calculates the number of planned trips per agency and date
6
+ */
7
+ export async function calculatePlannedTrips({ context, message }) {
8
+ message('Calculating planned trips...');
9
+ const ridesCollection = await rides.getCollection();
10
+ const startDateStr = Dates.fromOperationalDate(context.dates.start, 'Europe/Lisbon').unix_timestamp;
11
+ const endDateStr = Dates.fromOperationalDate(context.dates.end, 'Europe/Lisbon').unix_timestamp;
12
+ message(`Date range: ${startDateStr} to ${endDateStr}`);
13
+ const pipeline = [
14
+ {
15
+ $match: {
16
+ agency_id: { $exists: true },
17
+ start_time_scheduled: { $gte: startDateStr, $lte: endDateStr },
18
+ },
19
+ },
20
+ {
21
+ $group: {
22
+ _id: { agencyId: '$agency_id', date: '$operational_date' },
23
+ plannedTrips: { $sum: 1 },
24
+ },
25
+ },
26
+ {
27
+ $project: {
28
+ _id: 0,
29
+ agencyId: '$_id.agencyId',
30
+ date: '$_id.date',
31
+ plannedTrips: 1,
32
+ },
33
+ },
34
+ ];
35
+ const aggCursor = ridesCollection.aggregate(pipeline);
36
+ const results = [];
37
+ for await (const doc of aggCursor) {
38
+ const formattedDate = Dates.fromOperationalDate(doc.date, 'Europe/Lisbon').toFormat('dd/MM/yyyy');
39
+ results.push({
40
+ agencyId: doc.agencyId,
41
+ date: formattedDate,
42
+ plannedTrips: doc.plannedTrips,
43
+ });
44
+ }
45
+ message(`Processed ${results.length} planned trips metrics`);
46
+ return results;
47
+ }
48
+ /* * */
49
+ /**
50
+ * Calculates the number of completed trips per agency and date
51
+ */
52
+ export async function calculateCompletedTrips({ context, message }) {
53
+ message('Calculating completed trips...');
54
+ const ridesCollection = await rides.getCollection();
55
+ const startDateStr = Dates.fromOperationalDate(context.dates.start, 'Europe/Lisbon').toFormat('yyyyMMdd');
56
+ const endDateStr = Dates.fromOperationalDate(context.dates.end, 'Europe/Lisbon').toFormat('yyyyMMdd');
57
+ message(`Date range: ${startDateStr} to ${endDateStr}`);
58
+ const pipeline = [
59
+ {
60
+ $match: {
61
+ agency_id: { $exists: true },
62
+ operational_date: { $gte: startDateStr, $lte: endDateStr },
63
+ },
64
+ },
65
+ {
66
+ $group: {
67
+ _id: { agencyId: '$agency_id', date: '$operational_date' },
68
+ completedTrips: {
69
+ $sum: {
70
+ $cond: [
71
+ {
72
+ $or: [
73
+ { $eq: ['$analysis.SIMPLE_ONE_APEX_VALIDATION.grade', 'pass'] },
74
+ { $eq: ['$analysis.SIMPLE_THREE_VEHICLE_EVENTS.grade', 'pass'] },
75
+ ],
76
+ },
77
+ 1,
78
+ 0,
79
+ ],
80
+ },
81
+ },
82
+ },
83
+ },
84
+ {
85
+ $project: {
86
+ _id: 0,
87
+ agencyId: '$_id.agencyId',
88
+ completedTrips: 1,
89
+ date: '$_id.date',
90
+ },
91
+ },
92
+ ];
93
+ const aggCursor = ridesCollection.aggregate(pipeline);
94
+ const results = [];
95
+ for await (const doc of aggCursor) {
96
+ const formattedDate = Dates.fromOperationalDate(doc.date, 'Europe/Lisbon').toFormat('dd/MM/yyyy');
97
+ results.push({
98
+ agencyId: doc.agencyId,
99
+ completedTrips: doc.completedTrips,
100
+ date: formattedDate,
101
+ });
102
+ }
103
+ message(`Processed ${results.length} completed trips metrics`);
104
+ return results;
105
+ }
106
+ /* * */
107
+ /**
108
+ * Calculates the percentage of completed trips based on planned trips
109
+ */
110
+ export function calculateCompletedTripsPercentage(plannedTrips, completedTrips) {
111
+ const percentageResults = [];
112
+ // Create a lookup map for planned trips
113
+ const plannedMap = new Map();
114
+ for (const p of plannedTrips) {
115
+ plannedMap.set(`${p.date}-${p.agencyId}`, p.plannedTrips);
116
+ }
117
+ // Calculate % for each completed trip entry
118
+ for (const c of completedTrips) {
119
+ const key = `${c.date}-${c.agencyId}`;
120
+ const planned = plannedMap.get(key) || 0;
121
+ let percentage = 0;
122
+ if (planned > 0) {
123
+ percentage = (c.completedTrips / planned) * 100;
124
+ }
125
+ percentageResults.push({
126
+ agencyId: c.agencyId,
127
+ date: c.date,
128
+ percentage: parseFloat(percentage.toFixed(1)), // round to 1 decimal
129
+ });
130
+ }
131
+ return percentageResults;
132
+ }
@@ -0,0 +1,114 @@
1
+ import { Dates } from '@tmlmobilidade/dates';
2
+ import { rides } from '@tmlmobilidade/interfaces';
3
+ import { Logger } from '@tmlmobilidade/logger';
4
+ import { Timer } from '@tmlmobilidade/timer';
5
+ /* Helper Function */
6
+ async function processDailyRides(stream, results, agencies, agency43Trips) {
7
+ for (const rideData of stream) {
8
+ const agency = rideData.agency_id;
9
+ if (!agencies.includes(agency))
10
+ continue;
11
+ if (rideData.system_status !== 'complete')
12
+ continue;
13
+ const analysis = rideData.analysis;
14
+ if (!analysis)
15
+ continue;
16
+ const expectedStart = analysis.EXPECTED_START_TIME?.value;
17
+ if (expectedStart === undefined || expectedStart === null)
18
+ continue;
19
+ if (expectedStart <= -1) {
20
+ results.agencies[agency].early_rides++;
21
+ results.total.early_rides++;
22
+ }
23
+ if (expectedStart >= 5) {
24
+ results.agencies[agency].five_min_delays++;
25
+ results.total.five_min_delays++;
26
+ const passengers = rideData.passengers_observed ?? 0;
27
+ results.agencies[agency]['pax-affected-by-delay']
28
+ = (results.agencies[agency]['pax-affected-by-delay'] ?? 0) + passengers;
29
+ results.total['pax-affected-by-delay']
30
+ = (results.total['pax-affected-by-delay'] ?? 0) + passengers;
31
+ if (agency === '43') {
32
+ agency43Trips.push(rideData.trip_id);
33
+ }
34
+ }
35
+ if (expectedStart > -1 && expectedStart < 5) {
36
+ results.agencies[agency].ontime_rides++;
37
+ results.total.ontime_rides++;
38
+ }
39
+ }
40
+ }
41
+ /* Percentages */
42
+ function calculatePercentages(metrics) {
43
+ const total = metrics.early_rides + metrics.five_min_delays + metrics.ontime_rides;
44
+ if (total === 0)
45
+ return { early_rides_pct: 0, five_min_delays_pct: 0, ontime_rides_pct: 0 };
46
+ return {
47
+ early_rides_pct: parseFloat(((metrics.early_rides / total) * 100).toFixed(1)),
48
+ five_min_delays_pct: parseFloat(((metrics.five_min_delays / total) * 100).toFixed(1)),
49
+ ontime_rides_pct: parseFloat(((metrics.ontime_rides / total) * 100).toFixed(1)),
50
+ };
51
+ }
52
+ /* Main Function */
53
+ export const calculateDailyServiceCompliance = async (context) => {
54
+ const agencies = ['41', '42', '43', '44'];
55
+ const ridesCollection = await rides.getCollection();
56
+ const agency43Trips = [];
57
+ const processingTimer = new Timer();
58
+ let countProcessed = 0;
59
+ const resultsByDay = {};
60
+ const startDate = Dates.fromOperationalDate(context.dates.start, 'Europe/Lisbon').unix_timestamp;
61
+ const endDate = Dates.fromOperationalDate(context.dates.end, 'Europe/Lisbon').unix_timestamp;
62
+ const ridesQuery = {
63
+ start_time_scheduled: {
64
+ $gte: startDate,
65
+ $lte: endDate,
66
+ },
67
+ };
68
+ const allRidesStream = ridesCollection.find(ridesQuery).batchSize(100_000).stream();
69
+ for await (const ride of allRidesStream) {
70
+ const rideData = ride;
71
+ const dayOperationalStr = Dates.fromOperationalDate(rideData.operational_date, 'Europe/Lisbon').toFormat('yyyyMMdd');
72
+ if (!resultsByDay[dayOperationalStr]) {
73
+ resultsByDay[dayOperationalStr] = {
74
+ agencies: {},
75
+ total: { 'early_rides': 0, 'five_min_delays': 0, 'ontime_rides': 0, 'pax-affected-by-delay': 0 },
76
+ };
77
+ agencies.forEach((op) => {
78
+ resultsByDay[dayOperationalStr].agencies[op] = {
79
+ 'early_rides': 0,
80
+ 'five_min_delays': 0,
81
+ 'ontime_rides': 0,
82
+ 'pax-affected-by-delay': 0,
83
+ };
84
+ });
85
+ }
86
+ await processDailyRides([rideData], resultsByDay[dayOperationalStr], agencies, agency43Trips);
87
+ countProcessed++;
88
+ if (countProcessed % 100 === 0) {
89
+ // Logger.info(`Processed ${countProcessed} rides so far...`);
90
+ }
91
+ }
92
+ Logger.info(`Ride processing completed in ${processingTimer.get()} ms`);
93
+ /* Format results and calculate percentages */
94
+ const formattedResults = {};
95
+ for (const day in resultsByDay) {
96
+ const displayDate = Dates.fromOperationalDate(day, 'Europe/Lisbon').toFormat('dd/MM/yyyy');
97
+ const dayMetrics = resultsByDay[day];
98
+ const agenciesWithPct = {};
99
+ for (const agencyId of Object.keys(dayMetrics.agencies)) {
100
+ const percentages = calculatePercentages(dayMetrics.agencies[agencyId]);
101
+ agenciesWithPct[agencyId] = { ...dayMetrics.agencies[agencyId], ...percentages };
102
+ }
103
+ const totalPercentages = calculatePercentages(dayMetrics.total);
104
+ const totalWithPct = { ...dayMetrics.total, ...totalPercentages };
105
+ formattedResults[displayDate] = {
106
+ agencies: agenciesWithPct,
107
+ total: totalWithPct,
108
+ };
109
+ }
110
+ /* LOG: Export agency 43 trips */
111
+ // fs.writeFileSync('agency_43_trips.json', JSON.stringify(agency43Trips, null, 2));
112
+ // Logger.info(`Exported ${agency43Trips.length} trip_id for agency 43 to agency_43_trips.json`);
113
+ return formattedResults;
114
+ };
package/dist/types.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /* * */
2
2
  /* * */
3
3
  export const exportTypeLabels = {
4
+ 'executive-summary': '6.0. Sumário Executivo',
4
5
  'hashed-shapes-geojson': '4.0. HashedShapes para GeoJSON',
5
6
  'rides-raw': '2.0. Rides em bruto (SLAs)',
6
7
  'sams-raw': '5.0. SAMs em bruto (Sequencialidade)',
@@ -0,0 +1,3 @@
1
+ export function yyyymmddToDashed(date) {
2
+ return `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`;
3
+ }
@@ -0,0 +1,37 @@
1
+ /* * */
2
+ // Parses "id1,id2,start-end" into a flat list of IDs; expands ranges (e.g. 1001-1003)
3
+ export function parseIdListWithRanges(input, digits) {
4
+ const re = new RegExp(`^(\\d{${digits}})-(\\d{${digits}})$`);
5
+ const parts = input.replace(/,$/, '').split(',').map(id => id.trim());
6
+ const result = [];
7
+ for (const part of parts) {
8
+ const m = part.match(re);
9
+ if (m) {
10
+ const lo = Number(m[1]);
11
+ const hi = Number(m[2]);
12
+ for (let n = lo; n <= hi; n++)
13
+ result.push(String(n).padStart(digits, '0'));
14
+ }
15
+ else {
16
+ result.push(part);
17
+ }
18
+ }
19
+ return result;
20
+ }
21
+ // Returns an error message if the value is invalid, otherwise undefined
22
+ export function validateIdListWithRanges(value, digits) {
23
+ const re = new RegExp(`^(\\d{${digits}})-(\\d{${digits}})$`);
24
+ const singleRe = new RegExp(`^\\d{${digits}}$`);
25
+ const parts = value.replace(/,$/, '').split(',').map(id => id.trim());
26
+ for (const part of parts) {
27
+ const m = part.match(re);
28
+ if (m) {
29
+ if (Number(m[1]) > Number(m[2]))
30
+ return `Intervalo inválido: ${part}`;
31
+ }
32
+ else if (!singleRe.test(part)) {
33
+ return `ID ou intervalo inválido: ${part}`;
34
+ }
35
+ }
36
+ return undefined;
37
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tmlmobilidade/export-data",
3
3
  "description": "CLI tool to export data from GO.",
4
- "version": "20260304.1819.11",
4
+ "version": "20260421.1523.13",
5
5
  "author": {
6
6
  "email": "iso@tmlmobilidade.pt",
7
7
  "name": "TML-ISO"
@@ -36,9 +36,10 @@
36
36
  "lint:fix": "eslint . --fix"
37
37
  },
38
38
  "dependencies": {
39
- "@clack/prompts": "1.0.1",
39
+ "@clack/prompts": "1.2.0",
40
40
  "@tmlmobilidade/consts": "*",
41
41
  "@tmlmobilidade/dates": "*",
42
+ "@tmlmobilidade/go-performance-pckg-dates": "*",
42
43
  "@tmlmobilidade/interfaces": "*",
43
44
  "@tmlmobilidade/strings": "*",
44
45
  "@tmlmobilidade/timer": "*",
@@ -47,7 +48,7 @@
47
48
  "devDependencies": {
48
49
  "@tmlmobilidade/tsconfig": "*",
49
50
  "@tmlmobilidade/types": "*",
50
- "@types/node": "25.3.0",
51
+ "@types/node": "25.6.0",
51
52
  "resolve-tspaths": "0.8.23",
52
53
  "typescript": "5.9.3"
53
54
  }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
@@ -1 +0,0 @@
1
- export declare function promptAccessKey(): Promise<void>;
@@ -1,2 +0,0 @@
1
- import { type ExportType } from '../types.js';
2
- export declare function promptExportTypes(): Promise<ExportType[]>;
@@ -1 +0,0 @@
1
- export declare function promptFilterByAgencyIds(): Promise<string[]>;
@@ -1,5 +0,0 @@
1
- import { type OperationalDate } from '@tmlmobilidade/types';
2
- export declare function promptFilterByDates(): Promise<{
3
- end: OperationalDate;
4
- start: OperationalDate;
5
- }>;
@@ -1 +0,0 @@
1
- export declare function promptFilterByLineIds(): Promise<string[]>;
@@ -1 +0,0 @@
1
- export declare function promptFilterByPatternIds(): Promise<string[]>;
@@ -1 +0,0 @@
1
- export declare function promptFilterByStopIds(): Promise<string[]>;
@@ -1 +0,0 @@
1
- export declare function promptFilterTypes(): Promise<string[]>;
@@ -1 +0,0 @@
1
- export declare function promptFilterByVehicleIds(): Promise<number[]>;
@@ -1 +0,0 @@
1
- export declare function promptHashedShapeIds(): Promise<string[]>;
@@ -1,2 +0,0 @@
1
- import { type ValidationGroupField } from '../tasks/apex-validations/validations-aggregated.js';
2
- export declare function promptValidationGroupFields(): Promise<ValidationGroupField[]>;
@@ -1,18 +0,0 @@
1
- import { type TaskProps } from '../../types.js';
2
- /**
3
- * Available fields for grouping validations.
4
- * 'date' is always included by default.
5
- */
6
- export declare const validationGroupFields: readonly ["line_id", "pattern_id", "product_id", "trip_id", "stop_id", "agency_id", "vehicle_id"];
7
- export type ValidationGroupField = typeof validationGroupFields[number];
8
- export declare const validationGroupFieldLabels: Record<ValidationGroupField, string>;
9
- interface ValidationAggregatedTaskProps extends TaskProps {
10
- groupFields: ValidationGroupField[];
11
- }
12
- /**
13
- * Export Validations aggregated by user-selected fields.
14
- * Always groups by date, plus any additional fields selected by the user.
15
- * The 'validations' count is always included.
16
- */
17
- export declare function exportValidationsAggregated({ context, groupFields, message }: ValidationAggregatedTaskProps): Promise<void>;
18
- export {};
@@ -1,2 +0,0 @@
1
- import { type TaskProps } from '../../types.js';
2
- export declare function exportValidationsRaw({ context, message }: TaskProps): Promise<void>;
@@ -1,4 +0,0 @@
1
- import { type TaskProps } from '../../types.js';
2
- export declare function exportHashedShapesGeoJSON({ context, hashedShapeIds, message }: TaskProps & {
3
- hashedShapeIds: string[];
4
- }): Promise<void>;
@@ -1,2 +0,0 @@
1
- import { type TaskProps } from '../../types.js';
2
- export declare function exportRidesRaw({ context, message }: TaskProps): Promise<void>;
@@ -1,2 +0,0 @@
1
- import { type TaskProps } from '../../types.js';
2
- export declare function exportSamsRaw({ context, message }: TaskProps): Promise<void>;