go-transit-mcp 0.0.0
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/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/entry/http.js +78 -0
- package/dist/entry/http.js.map +1 -0
- package/dist/entry/stdio.js +16 -0
- package/dist/entry/stdio.js.map +1 -0
- package/dist/errors.js +30 -0
- package/dist/errors.js.map +1 -0
- package/dist/metrolinx/cache.js +29 -0
- package/dist/metrolinx/cache.js.map +1 -0
- package/dist/metrolinx/client.js +180 -0
- package/dist/metrolinx/client.js.map +1 -0
- package/dist/metrolinx/types.js +5 -0
- package/dist/metrolinx/types.js.map +1 -0
- package/dist/normalize/fares.js +44 -0
- package/dist/normalize/fares.js.map +1 -0
- package/dist/normalize/fleet-consist.js +31 -0
- package/dist/normalize/fleet-consist.js.map +1 -0
- package/dist/normalize/journey.js +184 -0
- package/dist/normalize/journey.js.map +1 -0
- package/dist/normalize/line-schedule.js +56 -0
- package/dist/normalize/line-schedule.js.map +1 -0
- package/dist/normalize/list-lines.js +29 -0
- package/dist/normalize/list-lines.js.map +1 -0
- package/dist/normalize/next-service.js +59 -0
- package/dist/normalize/next-service.js.map +1 -0
- package/dist/normalize/search-stops.js +113 -0
- package/dist/normalize/search-stops.js.map +1 -0
- package/dist/normalize/service-alerts.js +61 -0
- package/dist/normalize/service-alerts.js.map +1 -0
- package/dist/normalize/service-exceptions.js +51 -0
- package/dist/normalize/service-exceptions.js.map +1 -0
- package/dist/normalize/service-guarantee.js +23 -0
- package/dist/normalize/service-guarantee.js.map +1 -0
- package/dist/normalize/stop-destinations.js +54 -0
- package/dist/normalize/stop-destinations.js.map +1 -0
- package/dist/normalize/stop-details.js +44 -0
- package/dist/normalize/stop-details.js.map +1 -0
- package/dist/normalize/trip-status.js +88 -0
- package/dist/normalize/trip-status.js.map +1 -0
- package/dist/normalize/trip-updates.js +113 -0
- package/dist/normalize/trip-updates.js.map +1 -0
- package/dist/normalize/union-departures.js +52 -0
- package/dist/normalize/union-departures.js.map +1 -0
- package/dist/normalize/vehicle-positions.js +67 -0
- package/dist/normalize/vehicle-positions.js.map +1 -0
- package/dist/prompts.js +96 -0
- package/dist/prompts.js.map +1 -0
- package/dist/resources.js +61 -0
- package/dist/resources.js.map +1 -0
- package/dist/schemas/fares.js +30 -0
- package/dist/schemas/fares.js.map +1 -0
- package/dist/schemas/fleet-consist.js +32 -0
- package/dist/schemas/fleet-consist.js.map +1 -0
- package/dist/schemas/journey.js +127 -0
- package/dist/schemas/journey.js.map +1 -0
- package/dist/schemas/line-schedule.js +43 -0
- package/dist/schemas/line-schedule.js.map +1 -0
- package/dist/schemas/list-lines.js +29 -0
- package/dist/schemas/list-lines.js.map +1 -0
- package/dist/schemas/next-service.js +29 -0
- package/dist/schemas/next-service.js.map +1 -0
- package/dist/schemas/search-stops.js +35 -0
- package/dist/schemas/search-stops.js.map +1 -0
- package/dist/schemas/service-alerts.js +55 -0
- package/dist/schemas/service-alerts.js.map +1 -0
- package/dist/schemas/service-exceptions.js +27 -0
- package/dist/schemas/service-exceptions.js.map +1 -0
- package/dist/schemas/service-guarantee.js +32 -0
- package/dist/schemas/service-guarantee.js.map +1 -0
- package/dist/schemas/stop-destinations.js +32 -0
- package/dist/schemas/stop-destinations.js.map +1 -0
- package/dist/schemas/stop-details.js +27 -0
- package/dist/schemas/stop-details.js.map +1 -0
- package/dist/schemas/trip-status.js +39 -0
- package/dist/schemas/trip-status.js.map +1 -0
- package/dist/schemas/trip-updates.js +51 -0
- package/dist/schemas/trip-updates.js.map +1 -0
- package/dist/schemas/union-departures.js +26 -0
- package/dist/schemas/union-departures.js.map +1 -0
- package/dist/schemas/vehicle-positions.js +48 -0
- package/dist/schemas/vehicle-positions.js.map +1 -0
- package/dist/server.js +58 -0
- package/dist/server.js.map +1 -0
- package/dist/time.js +159 -0
- package/dist/time.js.map +1 -0
- package/dist/tools/get-fares.js +26 -0
- package/dist/tools/get-fares.js.map +1 -0
- package/dist/tools/get-fleet-consist.js +34 -0
- package/dist/tools/get-fleet-consist.js.map +1 -0
- package/dist/tools/get-line-schedule.js +27 -0
- package/dist/tools/get-line-schedule.js.map +1 -0
- package/dist/tools/get-next-service.js +25 -0
- package/dist/tools/get-next-service.js.map +1 -0
- package/dist/tools/get-service-alerts.js +45 -0
- package/dist/tools/get-service-alerts.js.map +1 -0
- package/dist/tools/get-service-exceptions.js +25 -0
- package/dist/tools/get-service-exceptions.js.map +1 -0
- package/dist/tools/get-service-guarantee.js +26 -0
- package/dist/tools/get-service-guarantee.js.map +1 -0
- package/dist/tools/get-stop-destinations.js +28 -0
- package/dist/tools/get-stop-destinations.js.map +1 -0
- package/dist/tools/get-stop-details.js +25 -0
- package/dist/tools/get-stop-details.js.map +1 -0
- package/dist/tools/get-trip-status.js +31 -0
- package/dist/tools/get-trip-status.js.map +1 -0
- package/dist/tools/get-trip-updates.js +26 -0
- package/dist/tools/get-trip-updates.js.map +1 -0
- package/dist/tools/get-union-departures.js +29 -0
- package/dist/tools/get-union-departures.js.map +1 -0
- package/dist/tools/get-vehicle-positions.js +30 -0
- package/dist/tools/get-vehicle-positions.js.map +1 -0
- package/dist/tools/list-lines.js +27 -0
- package/dist/tools/list-lines.js.map +1 -0
- package/dist/tools/plan-journey.js +46 -0
- package/dist/tools/plan-journey.js.map +1 -0
- package/dist/tools/plan-trip.js +81 -0
- package/dist/tools/plan-trip.js.map +1 -0
- package/dist/tools/search-stops.js +25 -0
- package/dist/tools/search-stops.js.map +1 -0
- package/dist/tools/test-support.js +93 -0
- package/dist/tools/test-support.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { addHoursToTime, combineDateAndHhmm, dateToWire, diffMinutes, hhmmToWire, toIsoWithTorontoOffset, } from "../time.js";
|
|
2
|
+
// GO Transit's known line roster. Schedule/Journey's Trip object carries no
|
|
3
|
+
// line-name field (only Line/LineVariant codes and a Display string that
|
|
4
|
+
// mixes the code with the destination, e.g. "LW - Aldershot GO") — same gap
|
|
5
|
+
// and same best-effort static map already used for get_stop_destinations.
|
|
6
|
+
const LINE_NAMES = {
|
|
7
|
+
LW: "Lakeshore West",
|
|
8
|
+
LE: "Lakeshore East",
|
|
9
|
+
MI: "Milton",
|
|
10
|
+
GT: "Kitchener",
|
|
11
|
+
RH: "Richmond Hill",
|
|
12
|
+
BR: "Barrie",
|
|
13
|
+
ST: "Stouffville",
|
|
14
|
+
UP: "UP Express",
|
|
15
|
+
};
|
|
16
|
+
// Mirrors Stop/NextService's ServiceType (T/B) single-letter vocabulary
|
|
17
|
+
// (tool-schemas spec §1.1) — not yet independently confirmed for this
|
|
18
|
+
// endpoint's Trip.Type field, same best-effort default as normalize/next-service.ts.
|
|
19
|
+
function normalizeLegMode(type) {
|
|
20
|
+
return type === "B" ? "bus" : "train";
|
|
21
|
+
}
|
|
22
|
+
function firstAndLastByOrder(stops) {
|
|
23
|
+
let first = stops[0];
|
|
24
|
+
let last = stops[0];
|
|
25
|
+
for (const stop of stops) {
|
|
26
|
+
if (stop.Order < first.Order)
|
|
27
|
+
first = stop;
|
|
28
|
+
if (stop.Order > last.Order)
|
|
29
|
+
last = stop;
|
|
30
|
+
}
|
|
31
|
+
return [first, last];
|
|
32
|
+
}
|
|
33
|
+
// Leg boundaries come from the trip's own Stops list, not from
|
|
34
|
+
// departFromCode/destinationStopCode. The captured fixture shows upstream
|
|
35
|
+
// trims each trip's Stops to the ridden portion (trip 1961's Display says
|
|
36
|
+
// "Niagara Falls GO" yet its Stops end at the journey destination OA) and
|
|
37
|
+
// stamps departFromCode/destinationStopCode with journey-level endpoints,
|
|
38
|
+
// so those codes can't be trusted as per-leg boundaries on any multi-leg
|
|
39
|
+
// journey. Note: no multi-leg journey has ever been observed live —
|
|
40
|
+
// Schedule/Journey returned SchJourneys: [] for a cross-line pair the
|
|
41
|
+
// official site routes via a transfer (tool-schemas spec §5) — this is
|
|
42
|
+
// defensive correctness from the fixture evidence, not a confirmed shape.
|
|
43
|
+
function normalizeLeg(trip, journeyDate, stopNames) {
|
|
44
|
+
const stops = trip.Stops?.Stop ?? [];
|
|
45
|
+
if (stops.length === 0)
|
|
46
|
+
return undefined;
|
|
47
|
+
const [first, last] = firstAndLastByOrder(stops);
|
|
48
|
+
return {
|
|
49
|
+
mode: normalizeLegMode(trip.Type),
|
|
50
|
+
line_code: trip.Line,
|
|
51
|
+
line_name: LINE_NAMES[trip.Line] ?? trip.Line,
|
|
52
|
+
direction: trip.Direction,
|
|
53
|
+
from: {
|
|
54
|
+
stop_code: first.Code,
|
|
55
|
+
stop_name: stopNames.get(first.Code) ?? first.Code,
|
|
56
|
+
time: combineDateAndHhmm(journeyDate, first.Time),
|
|
57
|
+
},
|
|
58
|
+
to: {
|
|
59
|
+
stop_code: last.Code,
|
|
60
|
+
stop_name: stopNames.get(last.Code) ?? last.Code,
|
|
61
|
+
time: combineDateAndHhmm(journeyDate, last.Time),
|
|
62
|
+
},
|
|
63
|
+
trip_number: trip.Number,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function normalizeService(service, journeyDate, stopNames) {
|
|
67
|
+
const legs = (service.Trips?.Trip ?? [])
|
|
68
|
+
.map((trip) => normalizeLeg(trip, journeyDate, stopNames))
|
|
69
|
+
.filter((leg) => leg !== undefined);
|
|
70
|
+
if (legs.length === 0)
|
|
71
|
+
return undefined;
|
|
72
|
+
return {
|
|
73
|
+
departure_time: toIsoWithTorontoOffset(service.StartTime),
|
|
74
|
+
arrival_time: toIsoWithTorontoOffset(service.EndTime),
|
|
75
|
+
duration_minutes: diffMinutes(service.EndTime, service.StartTime),
|
|
76
|
+
transfers: legs.length - 1,
|
|
77
|
+
// "" / "R" / "B" / "RB" (tool-schemas spec §5 pattern, unconfirmed
|
|
78
|
+
// positive case) — any non-empty code collapses to accessible: true.
|
|
79
|
+
accessible: service.Accessible !== "",
|
|
80
|
+
legs,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Raw journey response -> itinerary DTOs. Journey trips carry no stop names
|
|
85
|
+
* of their own (same gap as Schedule/Trip) — resolved via a stop_code ->
|
|
86
|
+
* stop_name index built from the cached Stop/All dataset (get_trip_status
|
|
87
|
+
* precedent), passed in by the caller.
|
|
88
|
+
*/
|
|
89
|
+
export function normalizeJourney(raw, stopNames) {
|
|
90
|
+
const entry = raw.SchJourneys?.[0];
|
|
91
|
+
if (!entry)
|
|
92
|
+
return [];
|
|
93
|
+
const itineraries = [];
|
|
94
|
+
for (const service of entry.Services ?? []) {
|
|
95
|
+
const itinerary = normalizeService(service, entry.Date, stopNames);
|
|
96
|
+
if (itinerary)
|
|
97
|
+
itineraries.push(itinerary);
|
|
98
|
+
}
|
|
99
|
+
return itineraries;
|
|
100
|
+
}
|
|
101
|
+
async function fetchItineraries(client, fromStopCode, toStopCode, date, time, maxResults, stopNames) {
|
|
102
|
+
const raw = await client.getJourney(dateToWire(date), fromStopCode, toStopCode, hhmmToWire(time), maxResults);
|
|
103
|
+
return normalizeJourney(raw, stopNames);
|
|
104
|
+
}
|
|
105
|
+
// Via-Union transfer composition (ADR 0002): Schedule/Journey only returns
|
|
106
|
+
// single-service journeys — both documented endpoint variants confirmed live
|
|
107
|
+
// (2026-07-18) to return SchJourneys: [] for a cross-line pair the official
|
|
108
|
+
// planner routes via a transfer. Union is GO's hub, so when a direct query
|
|
109
|
+
// comes back empty, plan_trip composes one transfer there from two journey
|
|
110
|
+
// calls. Each leg is upstream-attested; only the pairing is ours. The
|
|
111
|
+
// transfer buffer is a starting heuristic (same spirit as the arrive_by
|
|
112
|
+
// window, spec §5); bus-hub composition is a separate grilled decision.
|
|
113
|
+
const UNION_STOP_CODE = "UN";
|
|
114
|
+
const MIN_TRANSFER_MINUTES = 10;
|
|
115
|
+
function isoToMs(iso) {
|
|
116
|
+
return new Date(iso).getTime();
|
|
117
|
+
}
|
|
118
|
+
// Toronto-local clock of one of our own ISO outputs
|
|
119
|
+
// ("2026-07-20T08:50:00-04:00" -> "08:50") — safe to slice because every
|
|
120
|
+
// itinerary timestamp is produced by combineDateAndHhmm/toIsoWithTorontoOffset.
|
|
121
|
+
function hhmmOfIso(iso) {
|
|
122
|
+
return iso.slice(11, 16);
|
|
123
|
+
}
|
|
124
|
+
function combine(first, second) {
|
|
125
|
+
const legs = [...first.legs, ...second.legs];
|
|
126
|
+
return {
|
|
127
|
+
departure_time: first.departure_time,
|
|
128
|
+
arrival_time: second.arrival_time,
|
|
129
|
+
duration_minutes: Math.round((isoToMs(second.arrival_time) - isoToMs(first.departure_time)) / 60_000),
|
|
130
|
+
transfers: legs.length - 1,
|
|
131
|
+
accessible: first.accessible && second.accessible,
|
|
132
|
+
legs,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function composeViaUnion(client, fromStopCode, toStopCode, date, time, maxResults, stopNames) {
|
|
136
|
+
const inbound = await fetchItineraries(client, fromStopCode, UNION_STOP_CODE, date, time, maxResults, stopNames);
|
|
137
|
+
if (inbound.length === 0)
|
|
138
|
+
return [];
|
|
139
|
+
const earliest = inbound.reduce((a, b) => isoToMs(a.arrival_time) <= isoToMs(b.arrival_time) ? a : b);
|
|
140
|
+
const onward = await fetchItineraries(client, UNION_STOP_CODE, toStopCode, date, hhmmOfIso(earliest.arrival_time), maxResults, stopNames);
|
|
141
|
+
const combined = [];
|
|
142
|
+
for (const first of inbound) {
|
|
143
|
+
const readyMs = isoToMs(first.arrival_time) + MIN_TRANSFER_MINUTES * 60_000;
|
|
144
|
+
const second = onward.find((candidate) => isoToMs(candidate.departure_time) >= readyMs);
|
|
145
|
+
if (second)
|
|
146
|
+
combined.push(combine(first, second));
|
|
147
|
+
}
|
|
148
|
+
return combined.slice(0, maxResults);
|
|
149
|
+
}
|
|
150
|
+
// Direct query first; the via-Union fallback only runs when it comes back
|
|
151
|
+
// empty and neither endpoint already is Union, so every currently-working
|
|
152
|
+
// direct journey is untouched. Worst case 3 upstream calls (6 under
|
|
153
|
+
// arrive_by's widening retry).
|
|
154
|
+
async function fetchDirectOrComposed(client, query, time, stopNames) {
|
|
155
|
+
const direct = await fetchItineraries(client, query.from, query.to, query.date, time, query.maxResults, stopNames);
|
|
156
|
+
if (direct.length > 0 || !query.viaUnionFallback)
|
|
157
|
+
return direct;
|
|
158
|
+
if (query.from === UNION_STOP_CODE || query.to === UNION_STOP_CODE) {
|
|
159
|
+
return direct;
|
|
160
|
+
}
|
|
161
|
+
return composeViaUnion(client, query.from, query.to, query.date, time, query.maxResults, stopNames);
|
|
162
|
+
}
|
|
163
|
+
// arrive_by is emulated (tool-schemas spec §2.1): one Schedule/Journey call
|
|
164
|
+
// with StartTime back-shifted, filtered on arrival <= target, one retry with
|
|
165
|
+
// a wider window if empty. Both windows are starting heuristics to tune
|
|
166
|
+
// against real journey data (spec §5).
|
|
167
|
+
const ARRIVE_BY_BACKSHIFT_HOURS = 2;
|
|
168
|
+
const ARRIVE_BY_WIDE_BACKSHIFT_HOURS = 4;
|
|
169
|
+
async function itinerariesArrivingBy(client, query, stopNames, backshiftHours) {
|
|
170
|
+
const startTime = addHoursToTime(query.time, -backshiftHours);
|
|
171
|
+
const itineraries = await fetchDirectOrComposed(client, query, startTime, stopNames);
|
|
172
|
+
const targetMs = new Date(combineDateAndHhmm(query.date, query.time)).getTime();
|
|
173
|
+
return itineraries.filter((itinerary) => new Date(itinerary.arrival_time).getTime() <= targetMs);
|
|
174
|
+
}
|
|
175
|
+
export async function planItineraries(client, query, stopNames) {
|
|
176
|
+
if (query.timeMode === "depart_after") {
|
|
177
|
+
return fetchDirectOrComposed(client, query, query.time, stopNames);
|
|
178
|
+
}
|
|
179
|
+
const narrow = await itinerariesArrivingBy(client, query, stopNames, ARRIVE_BY_BACKSHIFT_HOURS);
|
|
180
|
+
if (narrow.length > 0)
|
|
181
|
+
return narrow;
|
|
182
|
+
return itinerariesArrivingBy(client, query, stopNames, ARRIVE_BY_WIDE_BACKSHIFT_HOURS);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=journey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journey.js","sourceRoot":"","sources":["../../src/normalize/journey.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { MetrolinxError } from "../errors.js";
|
|
2
|
+
import { toIsoWithTorontoOffset } from "../time.js";
|
|
3
|
+
function firstAndLastStop(stops) {
|
|
4
|
+
let first = stops[0];
|
|
5
|
+
let last = stops[0];
|
|
6
|
+
for (const stop of stops) {
|
|
7
|
+
if (stop.Order < first.Order)
|
|
8
|
+
first = stop;
|
|
9
|
+
if (stop.Order > last.Order)
|
|
10
|
+
last = stop;
|
|
11
|
+
}
|
|
12
|
+
return [first, last];
|
|
13
|
+
}
|
|
14
|
+
function summaryTrip(trip) {
|
|
15
|
+
const stops = trip.Stops ?? [];
|
|
16
|
+
if (stops.length === 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
const [first, last] = firstAndLastStop(stops);
|
|
19
|
+
return {
|
|
20
|
+
trip_number: trip.Number,
|
|
21
|
+
display: trip.Display,
|
|
22
|
+
departs_first_stop: toIsoWithTorontoOffset(first.Time),
|
|
23
|
+
arrives_last_stop: toIsoWithTorontoOffset(last.Time),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function stopTimeTrip(trip, stopCode) {
|
|
27
|
+
const stop = (trip.Stops ?? []).find((s) => s.Code === stopCode);
|
|
28
|
+
if (!stop)
|
|
29
|
+
return undefined;
|
|
30
|
+
return {
|
|
31
|
+
trip_number: trip.Number,
|
|
32
|
+
display: trip.Display,
|
|
33
|
+
time: toIsoWithTorontoOffset(stop.Time),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Schedule/Line/{Date}/{LineCode}/{LineDirection} is already scoped by the
|
|
37
|
+
// request URL, so the response carries at most one Lines.Line entry for that
|
|
38
|
+
// line/direction (confirmed live, issue #3 fixture capture).
|
|
39
|
+
export function normalizeLineSchedule(raw, stopCode) {
|
|
40
|
+
const entry = raw.Lines?.Line?.[0];
|
|
41
|
+
if (!entry) {
|
|
42
|
+
throw new MetrolinxError("not_found", "No schedule for that line/direction. Verify both via list_lines.", false);
|
|
43
|
+
}
|
|
44
|
+
const trips = [];
|
|
45
|
+
for (const trip of entry.Trip ?? []) {
|
|
46
|
+
const mapped = stopCode ? stopTimeTrip(trip, stopCode) : summaryTrip(trip);
|
|
47
|
+
if (mapped)
|
|
48
|
+
trips.push(mapped);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
trips,
|
|
52
|
+
truncated: false,
|
|
53
|
+
total_matched: trips.length,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=line-schedule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"line-schedule.js","sourceRoot":"","sources":["../../src/normalize/line-schedule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAU9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,SAAS,gBAAgB,CACvB,KAA4B;IAE5B,IAAI,KAAK,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACtB,IAAI,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;YAAE,KAAK,GAAG,IAAI,CAAC;QAC3C,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK;YAAE,IAAI,GAAG,IAAI,CAAC;IAC3C,CAAC;IACD,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,WAAW,CAAC,IAAyB;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,MAAM;QACxB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,kBAAkB,EAAE,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC;QACtD,iBAAiB,EAAE,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;KACrD,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,IAAyB,EACzB,QAAgB;IAEhB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IACjE,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,MAAM;QACxB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;KACxC,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,6EAA6E;AAC7E,6DAA6D;AAC7D,MAAM,UAAU,qBAAqB,CACnC,GAA4B,EAC5B,QAAiB;IAEjB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,cAAc,CACtB,WAAW,EACX,kEAAkE,EAClE,KAAK,CACN,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3E,IAAI,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,OAAO;QACL,KAAK;QACL,SAAS,EAAE,KAAK;QAChB,aAAa,EAAE,KAAK,CAAC,MAAM;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function modesOf(entry) {
|
|
2
|
+
const modes = [];
|
|
3
|
+
if (entry.IsTrain)
|
|
4
|
+
modes.push("train");
|
|
5
|
+
if (entry.IsBus)
|
|
6
|
+
modes.push("bus");
|
|
7
|
+
return modes;
|
|
8
|
+
}
|
|
9
|
+
function normalizeLine(entry) {
|
|
10
|
+
return {
|
|
11
|
+
line_code: entry.Code,
|
|
12
|
+
line_name: entry.Name,
|
|
13
|
+
modes: modesOf(entry),
|
|
14
|
+
variants: (entry.Variant ?? []).map((variant) => ({
|
|
15
|
+
code: variant.Code,
|
|
16
|
+
direction: variant.Direction,
|
|
17
|
+
display: variant.Display,
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function normalizeListLines(raw) {
|
|
22
|
+
const lines = (raw.AllLines?.Line ?? []).map(normalizeLine);
|
|
23
|
+
return {
|
|
24
|
+
lines,
|
|
25
|
+
truncated: false,
|
|
26
|
+
total_matched: lines.length,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=list-lines.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-lines.js","sourceRoot":"","sources":["../../src/normalize/list-lines.ts"],"names":[],"mappings":"AAMA,SAAS,OAAO,CAAC,KAAsB;IACrC,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,IAAI,KAAK,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAsB;IAC3C,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,IAAI;QACrB,SAAS,EAAE,KAAK,CAAC,IAAI;QACrB,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC;QACrB,QAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAChD,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAuB;IACxD,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC5D,OAAO;QACL,KAAK;QACL,SAAS,EAAE,KAAK;QAChB,aAAa,EAAE,KAAK,CAAC,MAAM;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { MetrolinxError } from "../errors.js";
|
|
2
|
+
import { diffMinutes, toIsoWithTorontoOffset } from "../time.js";
|
|
3
|
+
function normalizeMode(serviceType) {
|
|
4
|
+
if (serviceType === "T")
|
|
5
|
+
return "train";
|
|
6
|
+
if (serviceType === "B")
|
|
7
|
+
return "bus";
|
|
8
|
+
// Stop/NextService captures have only shown "T" so far (issue #3); this is
|
|
9
|
+
// a best-effort default per the tool-schemas spec §1.1 single-letter
|
|
10
|
+
// enum-expansion pattern — revisit if another code is ever observed.
|
|
11
|
+
return "bus";
|
|
12
|
+
}
|
|
13
|
+
function normalizeStatus(departureStatus) {
|
|
14
|
+
// Confirmed empirically (tool-schemas spec §5): E = expected (not yet
|
|
15
|
+
// departed), A = actual (has departed). C never appeared live; "cancelled"
|
|
16
|
+
// is the best-effort inference carried over from ticket 006/#3.
|
|
17
|
+
if (departureStatus === "E")
|
|
18
|
+
return "expected";
|
|
19
|
+
if (departureStatus === "A")
|
|
20
|
+
return "actual";
|
|
21
|
+
if (departureStatus === "C")
|
|
22
|
+
return "cancelled";
|
|
23
|
+
return departureStatus;
|
|
24
|
+
}
|
|
25
|
+
function normalizeDeparture(line) {
|
|
26
|
+
const platform = {};
|
|
27
|
+
if (line.ScheduledPlatform)
|
|
28
|
+
platform.scheduled = line.ScheduledPlatform;
|
|
29
|
+
if (line.ActualPlatform)
|
|
30
|
+
platform.actual = line.ActualPlatform;
|
|
31
|
+
return {
|
|
32
|
+
line_code: line.LineCode,
|
|
33
|
+
line_name: line.LineName,
|
|
34
|
+
direction: line.DirectionName.trim(),
|
|
35
|
+
mode: normalizeMode(line.ServiceType),
|
|
36
|
+
scheduled_time: toIsoWithTorontoOffset(line.ScheduledDepartureTime),
|
|
37
|
+
expected_time: toIsoWithTorontoOffset(line.ComputedDepartureTime),
|
|
38
|
+
delay_minutes: diffMinutes(line.ComputedDepartureTime, line.ScheduledDepartureTime),
|
|
39
|
+
status: normalizeStatus(line.DepartureStatus),
|
|
40
|
+
platform,
|
|
41
|
+
trip_number: line.TripNumber,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function normalizeNextService(raw) {
|
|
45
|
+
// An absent NextService block (Metadata.ErrorCode "204"/"No Content",
|
|
46
|
+
// confirmed live, issue #7) means the stop code itself is unknown — a
|
|
47
|
+
// present block with an empty/absent Lines array is a valid, just-quiet
|
|
48
|
+
// stop, which is not an error.
|
|
49
|
+
if (!raw.NextService) {
|
|
50
|
+
throw new MetrolinxError("not_found", "No stop matches that code. Verify the code via search_stops.", false);
|
|
51
|
+
}
|
|
52
|
+
const departures = (raw.NextService.Lines ?? []).map(normalizeDeparture);
|
|
53
|
+
return {
|
|
54
|
+
departures,
|
|
55
|
+
truncated: false,
|
|
56
|
+
total_matched: departures.length,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=next-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-service.js","sourceRoot":"","sources":["../../src/normalize/next-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAM9C,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEjE,SAAS,aAAa,CAAC,WAAmB;IACxC,IAAI,WAAW,KAAK,GAAG;QAAE,OAAO,OAAO,CAAC;IACxC,IAAI,WAAW,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACtC,2EAA2E;IAC3E,qEAAqE;IACrE,qEAAqE;IACrE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,eAAe,CAAC,eAAuB;IAC9C,sEAAsE;IACtE,2EAA2E;IAC3E,gEAAgE;IAChE,IAAI,eAAe,KAAK,GAAG;QAAE,OAAO,UAAU,CAAC;IAC/C,IAAI,eAAe,KAAK,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC7C,IAAI,eAAe,KAAK,GAAG;QAAE,OAAO,WAAW,CAAC;IAChD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAwB;IAClD,MAAM,QAAQ,GAA0B,EAAE,CAAC;IAC3C,IAAI,IAAI,CAAC,iBAAiB;QAAE,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC;IACxE,IAAI,IAAI,CAAC,cAAc;QAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC;IAE/D,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,SAAS,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE;QACpC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC;QACrC,cAAc,EAAE,sBAAsB,CAAC,IAAI,CAAC,sBAAsB,CAAC;QACnE,aAAa,EAAE,sBAAsB,CAAC,IAAI,CAAC,qBAAqB,CAAC;QACjE,aAAa,EAAE,WAAW,CACxB,IAAI,CAAC,qBAAqB,EAC1B,IAAI,CAAC,sBAAsB,CAC5B;QACD,MAAM,EAAE,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC;QAC7C,QAAQ;QACR,WAAW,EAAE,IAAI,CAAC,UAAU;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,GAA2B;IAE3B,sEAAsE;IACtE,sEAAsE;IACtE,wEAAwE;IACxE,+BAA+B;IAC/B,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,IAAI,cAAc,CACtB,WAAW,EACX,8DAA8D,EAC9D,KAAK,CACN,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACzE,OAAO;QACL,UAAU;QACV,SAAS,EAAE,KAAK;QAChB,aAAa,EAAE,UAAU,CAAC,MAAM;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
function normalizeText(text) {
|
|
2
|
+
return text
|
|
3
|
+
.toLowerCase()
|
|
4
|
+
.normalize("NFKD")
|
|
5
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
6
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
7
|
+
.trim();
|
|
8
|
+
}
|
|
9
|
+
// Match tiers, best first — exact, prefix, then substring anywhere. Both
|
|
10
|
+
// entries in an ambiguous name collision (e.g. "Oakville GO" as a Bus Stop
|
|
11
|
+
// and as a Train & Bus Station) land in the same tier, so both surface as
|
|
12
|
+
// candidates — required for plan_trip's later disambiguation reuse.
|
|
13
|
+
function matchTier(name, query) {
|
|
14
|
+
const n = normalizeText(name);
|
|
15
|
+
const q = normalizeText(query);
|
|
16
|
+
if (!q)
|
|
17
|
+
return null;
|
|
18
|
+
if (n === q)
|
|
19
|
+
return 0;
|
|
20
|
+
if (n.startsWith(q))
|
|
21
|
+
return 1;
|
|
22
|
+
if (n.includes(q))
|
|
23
|
+
return 2;
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export function stopType(locationType) {
|
|
27
|
+
if (locationType === "Train & Bus Station")
|
|
28
|
+
return "both";
|
|
29
|
+
if (locationType === "Train Station")
|
|
30
|
+
return "train";
|
|
31
|
+
return "bus";
|
|
32
|
+
}
|
|
33
|
+
function matchesFilter(type, filter) {
|
|
34
|
+
return filter === "any" || type === filter || type === "both";
|
|
35
|
+
}
|
|
36
|
+
// Station LocationCodes ("UN", "OA") are what Stop/Details and
|
|
37
|
+
// Stop/NextService accept for train (and mixed) stops. Pure bus stops have
|
|
38
|
+
// no such alpha code — their unified stop_code is the 6-digit PublicStopId
|
|
39
|
+
// instead (tool-schemas spec §1.4). Unconfirmed against a live Stop/Details
|
|
40
|
+
// call on a bus-only stop; revisit if that ever surfaces a mismatch.
|
|
41
|
+
export function resolveStopCode(entry) {
|
|
42
|
+
return entry.LocationType.includes("Train")
|
|
43
|
+
? entry.LocationCode
|
|
44
|
+
: entry.PublicStopId;
|
|
45
|
+
}
|
|
46
|
+
export function toStopMatch(entry) {
|
|
47
|
+
return {
|
|
48
|
+
stop_code: resolveStopCode(entry),
|
|
49
|
+
stop_name: entry.LocationName,
|
|
50
|
+
stop_type: stopType(entry.LocationType),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Full stop dataset in the search_stops match shape (tool-schemas spec §3,
|
|
54
|
+
// gotransit://stops resource) — every stop, unfiltered, sharing the same
|
|
55
|
+
// per-entry mapping as normalizeSearchStops so the two paths cannot drift.
|
|
56
|
+
export function normalizeAllStops(raw) {
|
|
57
|
+
const matches = (raw.Stations?.Station ?? []).map(toStopMatch);
|
|
58
|
+
return { matches, truncated: false, total_matched: matches.length };
|
|
59
|
+
}
|
|
60
|
+
function scoredMatches(entries, query, filter) {
|
|
61
|
+
return entries
|
|
62
|
+
.map((entry) => ({ entry, tier: matchTier(entry.LocationName, query) }))
|
|
63
|
+
.filter((scored) => scored.tier !== null)
|
|
64
|
+
.filter((scored) => matchesFilter(stopType(scored.entry.LocationType), filter));
|
|
65
|
+
}
|
|
66
|
+
export function normalizeSearchStops(raw, query, filter = "any", limit = 10) {
|
|
67
|
+
const scored = scoredMatches(raw.Stations?.Station ?? [], query, filter);
|
|
68
|
+
scored.sort((a, b) => a.tier - b.tier);
|
|
69
|
+
const totalMatched = scored.length;
|
|
70
|
+
const matches = scored
|
|
71
|
+
.slice(0, limit)
|
|
72
|
+
.map(({ entry }) => toStopMatch(entry));
|
|
73
|
+
return {
|
|
74
|
+
matches,
|
|
75
|
+
truncated: totalMatched > limit,
|
|
76
|
+
total_matched: totalMatched,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** code -> stop_name, keyed by the unified stop_code (tool-schemas spec §1.4). */
|
|
80
|
+
export function buildStopNameIndex(raw) {
|
|
81
|
+
const index = new Map();
|
|
82
|
+
for (const entry of raw.Stations?.Station ?? []) {
|
|
83
|
+
index.set(resolveStopCode(entry), entry.LocationName);
|
|
84
|
+
}
|
|
85
|
+
return index;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolves a `plan_trip` `from`/`to` input, which per tool-schemas spec §2.1
|
|
89
|
+
* accepts either a station/stop name (fuzzy-matched, same matcher as
|
|
90
|
+
* search_stops) or an exact stop code. An exact unified stop_code match
|
|
91
|
+
* short-circuits the fuzzy search. Otherwise, only the best-scoring tier is
|
|
92
|
+
* considered: a single best-tier match resolves; more than one (e.g. the
|
|
93
|
+
* real "Oakville GO" name collision, tool-schemas spec §5) is ambiguous,
|
|
94
|
+
* with candidates reproducible via search_stops for the same query.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveStopByName(raw, query) {
|
|
97
|
+
const entries = raw.Stations?.Station ?? [];
|
|
98
|
+
const trimmed = query.trim().toLowerCase();
|
|
99
|
+
const codeMatch = entries.find((entry) => resolveStopCode(entry).toLowerCase() === trimmed);
|
|
100
|
+
if (codeMatch)
|
|
101
|
+
return { status: "resolved", match: toStopMatch(codeMatch) };
|
|
102
|
+
const scored = scoredMatches(entries, query, "any");
|
|
103
|
+
if (scored.length === 0)
|
|
104
|
+
return { status: "not_found" };
|
|
105
|
+
const bestTier = Math.min(...scored.map((s) => s.tier));
|
|
106
|
+
const candidates = scored
|
|
107
|
+
.filter((s) => s.tier === bestTier)
|
|
108
|
+
.map(({ entry }) => toStopMatch(entry));
|
|
109
|
+
if (candidates.length === 1)
|
|
110
|
+
return { status: "resolved", match: candidates[0] };
|
|
111
|
+
return { status: "ambiguous", candidates };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=search-stops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-stops.js","sourceRoot":"","sources":["../../src/normalize/search-stops.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { toIsoWithTorontoOffset } from "../time.js";
|
|
2
|
+
// INIT/UPD/CORR/FINAL confirmed live across the ServiceAlert feed (issue #9:
|
|
3
|
+
// only INIT/UPD observed so far; CORR/FINAL carried over from ticket 006's
|
|
4
|
+
// documented mapping).
|
|
5
|
+
function expandStatus(code) {
|
|
6
|
+
if (code === "INIT")
|
|
7
|
+
return "new";
|
|
8
|
+
if (code === "UPD")
|
|
9
|
+
return "updated";
|
|
10
|
+
if (code === "CORR")
|
|
11
|
+
return "corrected";
|
|
12
|
+
if (code === "FINAL")
|
|
13
|
+
return "final";
|
|
14
|
+
return "new";
|
|
15
|
+
}
|
|
16
|
+
function pickLang(en, fr, lang) {
|
|
17
|
+
if (lang === "fr" && fr)
|
|
18
|
+
return fr;
|
|
19
|
+
return en;
|
|
20
|
+
}
|
|
21
|
+
function normalizeMessage(message, category, lang) {
|
|
22
|
+
return {
|
|
23
|
+
id: message.Code,
|
|
24
|
+
category,
|
|
25
|
+
status: expandStatus(message.Status),
|
|
26
|
+
posted_at: toIsoWithTorontoOffset(message.PostedDateTime),
|
|
27
|
+
subject: pickLang(message.SubjectEnglish, message.SubjectFrench, lang),
|
|
28
|
+
body: pickLang(message.BodyEnglish, message.BodyFrench, lang),
|
|
29
|
+
affected: {
|
|
30
|
+
lines: (message.Lines ?? []).map((l) => l.Code),
|
|
31
|
+
stops: (message.Stops ?? []).map((s) => ({
|
|
32
|
+
stop_code: s.Code,
|
|
33
|
+
stop_name: s.Name ?? s.Code,
|
|
34
|
+
})),
|
|
35
|
+
trips: (message.Trips ?? []).map((t) => t.TripNumber),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// The category filter selects which upstream feeds get fetched (tool layer)
|
|
40
|
+
// rather than being re-applied here — every message in `feeds` is already
|
|
41
|
+
// in-scope by category.
|
|
42
|
+
export function normalizeServiceAlerts(feeds, filters = {}) {
|
|
43
|
+
const lang = filters.lang ?? "en";
|
|
44
|
+
const limit = filters.limit ?? 20;
|
|
45
|
+
const alerts = feeds.flatMap((feed) => (feed.raw.Messages?.Message ?? []).map((m) => normalizeMessage(m, feed.category, lang)));
|
|
46
|
+
const filtered = alerts.filter((alert) => {
|
|
47
|
+
if (filters.line && !alert.affected.lines.includes(filters.line))
|
|
48
|
+
return false;
|
|
49
|
+
if (filters.stop &&
|
|
50
|
+
!alert.affected.stops.some((s) => s.stop_code === filters.stop))
|
|
51
|
+
return false;
|
|
52
|
+
return true;
|
|
53
|
+
});
|
|
54
|
+
const totalMatched = filtered.length;
|
|
55
|
+
return {
|
|
56
|
+
alerts: filtered.slice(0, limit),
|
|
57
|
+
truncated: totalMatched > limit,
|
|
58
|
+
total_matched: totalMatched,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=service-alerts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-alerts.js","sourceRoot":"","sources":["../../src/normalize/service-alerts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { toIsoWithTorontoOffset } from "../time.js";
|
|
2
|
+
// Raw stops carry separate SchArrival/SchDeparture; the DTO wants one
|
|
3
|
+
// scheduled_time per affected stop. Departure preferred (matches the rest
|
|
4
|
+
// of the codebase's departure-first convention, e.g. Stop/NextService),
|
|
5
|
+
// falling back to arrival for a trip's final stop.
|
|
6
|
+
function scheduledTime(stop) {
|
|
7
|
+
const naive = stop.SchDeparture ?? stop.SchArrival;
|
|
8
|
+
return naive ? toIsoWithTorontoOffset(naive) : "";
|
|
9
|
+
}
|
|
10
|
+
// Confirmed live (issue #9 follow-up): IsCancelled/IsOverride arrive as
|
|
11
|
+
// JSON strings ("True"/"False"), not native booleans — contrary to the
|
|
12
|
+
// ticket 001 Help-page shape this endpoint's types were sourced from (never
|
|
13
|
+
// part of issue #3's capture batch). Coerced case-insensitively; anything
|
|
14
|
+
// that isn't exactly "true" is treated as false, same as a real `false`.
|
|
15
|
+
function toBool(value) {
|
|
16
|
+
return typeof value === "boolean"
|
|
17
|
+
? value
|
|
18
|
+
: value.trim().toLowerCase() === "true";
|
|
19
|
+
}
|
|
20
|
+
function normalizeStop(stop) {
|
|
21
|
+
const dto = {
|
|
22
|
+
stop_code: stop.Code,
|
|
23
|
+
stop_name: stop.Name,
|
|
24
|
+
scheduled_time: scheduledTime(stop),
|
|
25
|
+
cancelled: toBool(stop.IsCancelled),
|
|
26
|
+
};
|
|
27
|
+
if (stop.ActualTime)
|
|
28
|
+
dto.actual_time = toIsoWithTorontoOffset(stop.ActualTime);
|
|
29
|
+
return dto;
|
|
30
|
+
}
|
|
31
|
+
function normalizeTrip(trip) {
|
|
32
|
+
// "affected_stops" per the field's own name — only stops actually flagged
|
|
33
|
+
// cancelled or overridden, not every stop on the trip (anti-dump, same
|
|
34
|
+
// spirit as get_line_schedule's two-mode design).
|
|
35
|
+
const affected = (trip.Stop ?? []).filter((s) => toBool(s.IsCancelled) || toBool(s.IsOverride));
|
|
36
|
+
return {
|
|
37
|
+
trip_number: trip.TripNumber,
|
|
38
|
+
trip_name: trip.TripName,
|
|
39
|
+
cancelled: toBool(trip.IsCancelled),
|
|
40
|
+
affected_stops: affected.map(normalizeStop),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function normalizeServiceExceptions(raw) {
|
|
44
|
+
const exceptions = (raw.Trip ?? []).map(normalizeTrip);
|
|
45
|
+
return {
|
|
46
|
+
exceptions,
|
|
47
|
+
truncated: false,
|
|
48
|
+
total_matched: exceptions.length,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=service-exceptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-exceptions.js","sourceRoot":"","sources":["../../src/normalize/service-exceptions.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,sEAAsE;AACtE,0EAA0E;AAC1E,wEAAwE;AACxE,mDAAmD;AACnD,SAAS,aAAa,CAAC,IAAsB;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,UAAU,CAAC;IACnD,OAAO,KAAK,CAAC,CAAC,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACpD,CAAC;AAED,wEAAwE;AACxE,uEAAuE;AACvE,4EAA4E;AAC5E,0EAA0E;AAC1E,yEAAyE;AACzE,SAAS,MAAM,CAAC,KAAuB;IACrC,OAAO,OAAO,KAAK,KAAK,SAAS;QAC/B,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;AAC5C,CAAC;AAED,SAAS,aAAa,CAAC,IAAsB;IAC3C,MAAM,GAAG,GAAiB;QACxB,SAAS,EAAE,IAAI,CAAC,IAAI;QACpB,SAAS,EAAE,IAAI,CAAC,IAAI;QACpB,cAAc,EAAE,aAAa,CAAC,IAAI,CAAC;QACnC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;KACpC,CAAC;IACF,IAAI,IAAI,CAAC,UAAU;QACjB,GAAG,CAAC,WAAW,GAAG,sBAAsB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC5D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,IAAsB;IAC3C,0EAA0E;IAC1E,uEAAuE;IACvE,kDAAkD;IAClD,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CACrD,CAAC;IACF,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,UAAU;QAC5B,SAAS,EAAE,IAAI,CAAC,QAAQ;QACxB,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC;QACnC,cAAc,EAAE,QAAQ,CAAC,GAAG,CAAC,aAAa,CAAC;KAC5C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,GAAiC;IAEjC,MAAM,UAAU,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACvD,OAAO;QACL,UAAU;QACV,SAAS,EAAE,KAAK;QAChB,aAAa,EAAE,UAAU,CAAC,MAAM;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function normalizeStop(stop, lang) {
|
|
2
|
+
return {
|
|
3
|
+
stop_code: stop.Code,
|
|
4
|
+
scope: stop.Scope,
|
|
5
|
+
reason: lang === "fr" && stop.ReasonFr ? stop.ReasonFr : stop.ReasonEn,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function normalizeServiceGuarantee(raw, lang = "en") {
|
|
9
|
+
// Confirmed live (issue #9 follow-up): a real, unambiguously-valid
|
|
10
|
+
// trip_number/date returned an absent Stops container. The
|
|
11
|
+
// presence-vs-absence "not_found" signal confirmed for other endpoints
|
|
12
|
+
// (Stop/Details, Stop/NextService, Stop/Destinations) does NOT hold here
|
|
13
|
+
// — an absent container is the ordinary shape for "this trip wasn't
|
|
14
|
+
// delayed enough to be guarantee-eligible," not a signal that the
|
|
15
|
+
// trip/date itself is wrong. Previously this threw not_found for every
|
|
16
|
+
// non-eligible trip, which is most of them.
|
|
17
|
+
const stops = raw.Stops?.Stop ?? [];
|
|
18
|
+
return {
|
|
19
|
+
eligible: stops.length > 0,
|
|
20
|
+
stops: stops.map((s) => normalizeStop(s, lang)),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=service-guarantee.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-guarantee.js","sourceRoot":"","sources":["../../src/normalize/service-guarantee.ts"],"names":[],"mappings":"AASA,SAAS,aAAa,CACpB,IAAsB,EACtB,IAAiB;IAEjB,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,IAAI;QACpB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,MAAM,EAAE,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ;KACvE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,GAAgC,EAChC,OAAoB,IAAI;IAExB,mEAAmE;IACnE,2DAA2D;IAC3D,uEAAuE;IACvE,yEAAyE;IACzE,oEAAoE;IACpE,kEAAkE;IAClE,uEAAuE;IACvE,4CAA4C;IAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;IACpC,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC;QAC1B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;KAChD,CAAC;AACJ,CAAC"}
|