sbb-mcp 0.4.1 → 0.4.3

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 (54) hide show
  1. package/LICENSE +57 -57
  2. package/README.md +258 -314
  3. package/dist/cache.d.ts +2 -0
  4. package/dist/cache.js +4 -0
  5. package/dist/cache.js.map +1 -1
  6. package/dist/formatters.d.ts +13 -0
  7. package/dist/formatters.js +94 -19
  8. package/dist/formatters.js.map +1 -1
  9. package/dist/http.js +59 -3
  10. package/dist/http.js.map +1 -1
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/look2book.d.ts +98 -0
  14. package/dist/look2book.js +212 -0
  15. package/dist/look2book.js.map +1 -0
  16. package/dist/profile.js +15 -8
  17. package/dist/profile.js.map +1 -1
  18. package/dist/shortlink.d.ts +60 -0
  19. package/dist/shortlink.js +122 -0
  20. package/dist/shortlink.js.map +1 -0
  21. package/dist/structured.d.ts +7 -1
  22. package/dist/structured.js +2 -1
  23. package/dist/structured.js.map +1 -1
  24. package/dist/swisstrip.js +21 -5
  25. package/dist/swisstrip.js.map +1 -1
  26. package/dist/tools.d.ts +38 -0
  27. package/dist/tools.js +122 -77
  28. package/dist/tools.js.map +1 -1
  29. package/dist/transport/index.d.ts +1 -0
  30. package/dist/transport/index.js +4 -0
  31. package/dist/transport/index.js.map +1 -1
  32. package/dist/transport/setup.d.ts +1 -0
  33. package/dist/transport/setup.js +59 -0
  34. package/dist/transport/setup.js.map +1 -0
  35. package/dist/transport/smapi-auth.d.ts +14 -2
  36. package/dist/transport/smapi-auth.js +67 -17
  37. package/dist/transport/smapi-auth.js.map +1 -1
  38. package/dist/transport/smapi-client.d.ts +25 -1
  39. package/dist/transport/smapi-client.js +112 -9
  40. package/dist/transport/smapi-client.js.map +1 -1
  41. package/dist/transport/smapi-journey.d.ts +11 -5
  42. package/dist/transport/smapi-journey.js +16 -7
  43. package/dist/transport/smapi-journey.js.map +1 -1
  44. package/dist/transport/smapi-mock.js.map +1 -1
  45. package/dist/transport/smapi-prices.d.ts +43 -9
  46. package/dist/transport/smapi-prices.js +112 -41
  47. package/dist/transport/smapi-prices.js.map +1 -1
  48. package/dist/transport/smapi-types.d.ts +28 -0
  49. package/dist/widgets.d.ts +30 -3
  50. package/dist/widgets.js +78 -14
  51. package/dist/widgets.js.map +1 -1
  52. package/package.json +79 -73
  53. package/web/dist/widgets.css +1 -1
  54. package/web/dist/widgets.js +1 -1
@@ -1,5 +1,6 @@
1
1
  import { getWeatherSummary } from 'swiss-weather-mcp';
2
2
  import { toLocale, toSbbLinkLang, t } from './i18n.js';
3
+ import { buildShortLink } from './shortlink.js';
3
4
  function formatTime(iso, lang) {
4
5
  const d = new Date(iso);
5
6
  return d.toLocaleTimeString(toLocale(lang), {
@@ -34,6 +35,55 @@ function transfersLabel(n, lang) {
34
35
  return tr.oneChange;
35
36
  return tr.nChanges(n);
36
37
  }
38
+ /**
39
+ * Terse one-line summary used when the client renders the Apps SDK widget
40
+ * (see `isWidgetRenderingClient`). Widget clients already show a full card
41
+ * list — returning the long markdown table here just causes the LLM to
42
+ * paraphrase it into a redundant narration right next to the iframe. The
43
+ * structured content still carries all the data the model needs to answer
44
+ * follow-up questions; this string is purely for the chat bubble above the
45
+ * widget.
46
+ */
47
+ export function formatStationsTerse(stations, _lang = 'en') {
48
+ if (stations.length === 0) {
49
+ return 'No stations found.';
50
+ }
51
+ const names = stations.slice(0, 3).map(s => s.name).join(', ');
52
+ const more = stations.length > 3 ? ` (+${stations.length - 3} more)` : '';
53
+ return `Found ${stations.length} station${stations.length === 1 ? '' : 's'}: ${names}${more}.`;
54
+ }
55
+ export function formatConnectionsTerse(collection, lang = 'en') {
56
+ const trips = collection.trips;
57
+ const tr = t(lang);
58
+ if (trips.length === 0)
59
+ return tr.noConnections;
60
+ const first = trips[0];
61
+ const date = formatDate(first.startTime, lang);
62
+ const dep = formatTime(first.startTime, lang);
63
+ const arr = formatTime(first.endTime, lang);
64
+ const dur = parseDuration(first.duration);
65
+ const changes = transfersLabel(first.transfers, lang);
66
+ return `${trips.length} connection${trips.length === 1 ? '' : 's'} ${first.origin.name} → ${first.destination.name} · ${date}. Next: ${dep} → ${arr} (${dur}, ${changes}).`;
67
+ }
68
+ export function formatTripDetailsTerse(trip, lang = 'en') {
69
+ const date = formatDate(trip.startTime, lang);
70
+ const dep = formatTime(trip.startTime, lang);
71
+ const arr = formatTime(trip.endTime, lang);
72
+ const dur = parseDuration(trip.duration);
73
+ const changes = transfersLabel(trip.transfers, lang);
74
+ return `${trip.origin.name} → ${trip.destination.name} · ${date} · ${dep}–${arr} (${dur}, ${changes}).`;
75
+ }
76
+ export function formatPricesTerse(results, lang = 'en') {
77
+ const tr = t(lang);
78
+ if (results.length === 0)
79
+ return 'No price information available.';
80
+ const cheapest = results
81
+ .map(r => r.prices.find(p => p.class === '2')?.amount)
82
+ .filter((a) => typeof a === 'number')
83
+ .sort((a, b) => a - b)[0];
84
+ const from = typeof cheapest === 'number' ? ` from CHF ${cheapest.toFixed(2)}` : '';
85
+ return `${tr.prices} for ${results.length} trip${results.length === 1 ? '' : 's'}${from}.`;
86
+ }
37
87
  export function formatStations(stations, lang = 'en') {
38
88
  if (stations.length === 0) {
39
89
  return 'No stations found.';
@@ -49,18 +99,20 @@ export function formatStations(stations, lang = 'en') {
49
99
  }
50
100
  return lines.join('\n');
51
101
  }
52
- function formatLegSummary(leg, lang) {
53
- if (leg.type === 'timed') {
54
- const tl = leg;
55
- const line = tl.service.publishedLineName || 'Train';
56
- const platform = tl.board.platform ? ` ${t(lang).pl}.${tl.board.platform}` : '';
57
- return `${line}${platform}`;
58
- }
59
- if (leg.type === 'transfer') {
60
- const tr = leg;
61
- return `Walk ${parseDuration(tr.duration)}`;
62
- }
63
- return t(lang).transfer;
102
+ /**
103
+ * Convert an ISO timestamp into Europe/Zurich-local YYYY-MM-DD + HH:MM pair.
104
+ * Matches `toZurichDateTime` in structured.ts. Used to build SBB deep links
105
+ * in the markdown text path.
106
+ */
107
+ function toZurichDateTime(iso) {
108
+ const d = new Date(iso);
109
+ const date = d.toLocaleDateString('en-CA', { timeZone: 'Europe/Zurich' });
110
+ const time = d.toLocaleTimeString('en-GB', {
111
+ timeZone: 'Europe/Zurich',
112
+ hour: '2-digit',
113
+ minute: '2-digit',
114
+ });
115
+ return { date, time };
64
116
  }
65
117
  export function formatConnections(collection, lang = 'en') {
66
118
  const trips = collection.trips;
@@ -70,22 +122,45 @@ export function formatConnections(collection, lang = 'en') {
70
122
  }
71
123
  const first = trips[0];
72
124
  const date = formatDate(first.startTime, lang);
125
+ //
126
+ // Markdown table format.
127
+ //
128
+ // We tried a prose bullet list first (expecting ChatGPT's rewrite to keep
129
+ // the links intact) but ChatGPT ignores the text entirely when it has
130
+ // structuredContent — it paraphrases from the JSON and picks just one
131
+ // connection to feature. The table format is here as the canonical text
132
+ // response for MCP clients that DO render markdown (Claude Desktop, the
133
+ // WhatsApp bot, our own web UI) — which want all 5 options side-by-side.
134
+ //
135
+ // The booking URL is intentionally hidden behind "Book here" in the last
136
+ // column (not exposed as a bare URL) so rendering is clean in every
137
+ // markdown-capable client.
138
+ //
73
139
  const lines = [
74
- `**${first.origin.name} -> ${first.destination.name}** | ${date}`,
75
- `Collection ID: \`${collection.id}\` (use with get_more_connections)`,
140
+ `**${first.origin.name} ${first.destination.name}** · ${date}`,
76
141
  '',
77
- `| ${tr.dep} | ${tr.arr} | ${tr.dur} | ${tr.changes} | Route | Trip ID |`,
78
- '|-----|-----|----------|-----------|-------|---------|',
142
+ `| ${tr.dep} | ${tr.arr} | ${tr.dur} | ${tr.changes} | |`,
143
+ '|---|---|---|---|---|',
79
144
  ];
80
145
  for (const trip of trips) {
81
146
  const dep = formatTime(trip.startTime, lang);
82
147
  const arr = formatTime(trip.endTime, lang);
83
148
  const dur = parseDuration(trip.duration);
84
- const route = trip.legs.map(l => formatLegSummary(l, lang)).join(' > ');
85
- lines.push(`| ${dep} | ${arr} | ${dur} | ${transfersLabel(trip.transfers, lang)} | ${route} | \`${trip.id}\` |`);
149
+ const { date: d, time } = toZurichDateTime(trip.startTime);
150
+ const bookingUrl = buildShortLink({
151
+ tripId: trip.id,
152
+ fromId: trip.origin.id,
153
+ fromName: trip.origin.name,
154
+ toId: trip.destination.id,
155
+ toName: trip.destination.name,
156
+ date: d,
157
+ time,
158
+ lang,
159
+ });
160
+ lines.push(`| ${dep} | ${arr} | ${dur} | ${transfersLabel(trip.transfers, lang)} | [${tr.bookHere}](${bookingUrl}) |`);
86
161
  }
87
162
  lines.push('');
88
- lines.push(`*Use \`get_prices\` with trip IDs to see ticket prices. Use \`get_more_connections\` to see earlier/later trains.*`);
163
+ lines.push(`*Collection ID: \`${collection.id}\` pass to \`get_more_connections\` for earlier/later trains.*`);
89
164
  return lines.join('\n');
90
165
  }
91
166
  export function formatTripDetails(trip, lang = 'en') {
@@ -1 +1 @@
1
- {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
package/dist/http.js CHANGED
@@ -2,12 +2,15 @@
2
2
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
3
  import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
4
4
  import { isSmapiConfigured } from './transport/index.js';
5
+ import { installSbbTransportAdapters } from './transport/setup.js';
5
6
  import { isSwissTripConfigured } from './swisstrip.js';
6
7
  import { createSbbMcpServer } from './tools.js';
7
8
  import { isRateLimited } from './rate-limit.js';
9
+ import { decodeShortlink, resolveTargetUrl } from './shortlink.js';
10
+ import { recordClick, getRatioSnapshot } from './look2book.js';
8
11
  const useMock = !isSmapiConfigured();
9
- const useCloud = isSwissTripConfigured();
10
12
  async function main() {
13
+ installSbbTransportAdapters();
11
14
  const port = parseInt(process.env.PORT || '3001');
12
15
  const app = createMcpExpressApp({ host: '0.0.0.0' });
13
16
  app.post('/mcp', async (req, res) => {
@@ -28,7 +31,58 @@ async function main() {
28
31
  }
29
32
  });
30
33
  app.get('/health', (_req, res) => {
31
- res.json({ status: 'ok', mode: useMock ? 'mock' : 'live' });
34
+ // Expose Look2Book state so readiness probes + dashboards can surface
35
+ // contractual ratio violations without parsing stdout logs.
36
+ res.json({
37
+ status: 'ok',
38
+ mode: useMock ? 'mock' : 'live',
39
+ look2book: getRatioSnapshot(),
40
+ });
41
+ });
42
+ // ─── Booking redirect ─────────────────────────────────────────────────
43
+ //
44
+ // Every booking URL in our tool responses points at this endpoint. We
45
+ // log the click (for SBB Look2Book telemetry + channel attribution),
46
+ // then 302 to the real sbb.ch deep link. Stateless: everything needed
47
+ // is encoded in `:token`.
48
+ //
49
+ // Log format is intentionally a single JSON line per click so Railway's
50
+ // log drain (or future log forwarder) can ingest it directly.
51
+ app.get('/r/:token', (req, res) => {
52
+ const { token } = req.params;
53
+ try {
54
+ const payload = decodeShortlink(token);
55
+ const target = resolveTargetUrl(payload);
56
+ const log = {
57
+ event: 'sbb_booking_click',
58
+ ts: new Date().toISOString(),
59
+ tripId: payload.tid,
60
+ origin: payload.o,
61
+ destination: payload.d,
62
+ date: payload.dt,
63
+ time: payload.t,
64
+ lang: payload.l,
65
+ ip: req.ip || req.socket.remoteAddress || 'unknown',
66
+ ua: req.get('user-agent') || 'unknown',
67
+ referer: req.get('referer') || '',
68
+ };
69
+ // One-line JSON for easy grep/ingest. Railway captures stdout.
70
+ console.log(JSON.stringify(log));
71
+ // Credit the Look2Book ratio — this represents real booking intent
72
+ // and offsets SMAPI calls in both the trips and offers windows.
73
+ recordClick({ tripId: payload.tid });
74
+ res.redirect(302, target);
75
+ }
76
+ catch (err) {
77
+ const msg = err instanceof Error ? err.message : String(err);
78
+ console.warn(JSON.stringify({
79
+ event: 'sbb_booking_click_invalid',
80
+ ts: new Date().toISOString(),
81
+ reason: msg,
82
+ token: token.slice(0, 32), // truncate in case it's garbage
83
+ }));
84
+ res.status(400).type('text/plain').send(`Invalid booking link: ${msg}`);
85
+ }
32
86
  });
33
87
  app.get('/.well-known/mcp/server-card.json', (_req, res) => {
34
88
  res.json({
@@ -50,8 +104,10 @@ async function main() {
50
104
  console.log(`[sbb-mcp] HTTP server listening on port ${port}`);
51
105
  console.log(`[sbb-mcp] MCP endpoint: http://0.0.0.0:${port}/mcp`);
52
106
  console.log(`[sbb-mcp] Mode: ${useMock ? 'MOCK' : 'LIVE'}`);
53
- console.log(`[sbb-mcp] SwissTrip cloud: ${useCloud ? 'enabled' : 'disabled'}`);
107
+ console.log(`[sbb-mcp] SwissTrip cloud: ${isSwissTripConfigured() ? 'enabled' : 'disabled'}`);
54
108
  console.log(`[sbb-mcp] Rate limit: 60 req/min per IP`);
109
+ console.log(`[sbb-mcp] Booking redirects: /r/:token (logs to stdout)`);
110
+ console.log(`[sbb-mcp] Look2Book telemetry: 1h window, caps trips=100:1 offers=50:1`);
55
111
  });
56
112
  }
57
113
  main().catch((err) => {
package/dist/http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { isSmapiConfigured } from './transport/index.js';
4
+ import { installSbbTransportAdapters } from './transport/setup.js';
4
5
  import { isSwissTripConfigured } from './swisstrip.js';
5
6
  import { createSbbMcpServer } from './tools.js';
6
7
  async function main() {
8
+ installSbbTransportAdapters();
7
9
  const server = createSbbMcpServer();
8
10
  const transport = new StdioServerTransport();
9
11
  await server.connect(transport);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAE/B,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;QACjF,OAAO,CAAC,KAAK,CAAC,+EAA+E,CAAC,CAAA;IAChG,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAA;IACnD,CAAC;IAED,IAAI,qBAAqB,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;IACnF,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAA;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,KAAK,UAAU,IAAI;IACjB,2BAA2B,EAAE,CAAA;IAC7B,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAA;IACnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IAE/B,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;QACjF,OAAO,CAAC,KAAK,CAAC,+EAA+E,CAAC,CAAA;IAChG,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAA;IACnD,CAAC;IAED,IAAI,qBAAqB,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,kEAAkE,CAAC,CAAA;IACnF,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAA;IAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * look2book.ts — Look2Book ratio telemetry for SBB SMAPI calls.
3
+ *
4
+ * ## Why
5
+ * SBB's Distributionsvertrag caps the API-call-to-booking-click ratio:
6
+ * - Trips API (places, trips, trip-details, pagination): max 100 : 1
7
+ * - Offers API (prices, trip-offers): max 50 : 1
8
+ *
9
+ * Violating the ratio is a contractual issue, not a technical one — SBB
10
+ * audits quarterly. We need to: (1) observe the ratio in production so we
11
+ * can intervene before an audit finds us over, and (2) emit an operator
12
+ * warning when a running window crosses the threshold so the on-call can
13
+ * investigate (bot traffic? broken booking flow? missing CTAs?).
14
+ *
15
+ * ## What this module does — and doesn't do
16
+ * Does:
17
+ * - Count every SMAPI request (classified `trips` vs `offers` by base URL)
18
+ * and every booking-link click (from `/r/:token`) on a sliding 1-hour
19
+ * window.
20
+ * - Emit a JSON line per event to stdout so Railway's log drain (or any
21
+ * log forwarder) can ship it to long-term storage for trend analysis.
22
+ * - Log a `sbb_look2book_violation` warning JSON line once per 5-minute
23
+ * cooldown per family when the current window's ratio exceeds the cap.
24
+ * - Expose a synchronous `getRatioSnapshot()` so `/health` can surface the
25
+ * current state for readiness probes / dashboards.
26
+ *
27
+ * Does NOT:
28
+ * - Block or rate-limit violating requests. Look2Book is a business metric,
29
+ * not a request gate — gating tool calls based on a ratio would break UX
30
+ * catastrophically (e.g. a single user who hasn't clicked yet would see
31
+ * subsequent searches fail). See rate-limit.ts for the actual per-IP gate.
32
+ * - Persist counters. Railway restarts reset the window — acceptable since
33
+ * the contractual window is aggregate and real analysis happens offline
34
+ * via the JSON-line logs.
35
+ *
36
+ * ## Design: in-memory sliding window
37
+ * A single array of `{ts, kind}` events, pruned at every read/write so old
38
+ * entries fall off after `WINDOW_MS`. O(n) prune on each call, but n is
39
+ * bounded by the per-IP rate limit (60/min × 60min ≈ 3.6k events worst
40
+ * case) so this stays well under a millisecond.
41
+ *
42
+ * Events are timestamped with an injectable `now()` so tests can drive the
43
+ * window deterministically without `vi.useFakeTimers`.
44
+ */
45
+ /** Tool family — matches the two SMAPI base URLs in smapi-client.ts. */
46
+ export type ApiFamily = 'trips' | 'offers';
47
+ /** Snapshot returned by getRatioSnapshot() — safe to JSON-serialize. */
48
+ export interface RatioSnapshot {
49
+ windowMs: number;
50
+ trips: {
51
+ calls: number;
52
+ clicks: number;
53
+ ratio: number | null;
54
+ cap: number;
55
+ violated: boolean;
56
+ };
57
+ offers: {
58
+ calls: number;
59
+ clicks: number;
60
+ ratio: number | null;
61
+ cap: number;
62
+ violated: boolean;
63
+ };
64
+ }
65
+ /**
66
+ * Record a successful SMAPI call. Call AFTER the request succeeds so failed
67
+ * requests (auth errors, network blips) don't pollute the ratio — only
68
+ * billable, data-returning calls count against the cap.
69
+ *
70
+ * The `endpoint` arg is logged for offline attribution (which tool is the
71
+ * worst offender?) but doesn't affect bucketing.
72
+ */
73
+ export declare function recordApiCall(family: ApiFamily, endpoint: string): void;
74
+ /**
75
+ * Record a booking-link click (from /r/:token). The click "pays back" one
76
+ * unit of ratio for either family — SBB's contract counts all clicks toward
77
+ * both caps since a single click represents real user booking intent.
78
+ */
79
+ export declare function recordClick(meta?: {
80
+ tripId?: string;
81
+ }): void;
82
+ /**
83
+ * Current window state. Cheap (bounded prune + single pass). Safe to call
84
+ * from a health endpoint handler without a cache.
85
+ */
86
+ export declare function getRatioSnapshot(): RatioSnapshot;
87
+ /** Reset all state. Call between tests. */
88
+ export declare function __resetForTesting(): void;
89
+ /** Override the clock for deterministic window tests. */
90
+ export declare function __setClockForTesting(fn: () => number): void;
91
+ /** Expose tunables so tests can read them instead of duplicating the numbers. */
92
+ export declare const __internals: {
93
+ readonly WINDOW_MS: number;
94
+ readonly CAP_TRIPS: 100;
95
+ readonly CAP_OFFERS: 50;
96
+ readonly ALERT_COOLDOWN_MS: number;
97
+ readonly MIN_CALLS_BEFORE_ALERT: 10;
98
+ };
@@ -0,0 +1,212 @@
1
+ /**
2
+ * look2book.ts — Look2Book ratio telemetry for SBB SMAPI calls.
3
+ *
4
+ * ## Why
5
+ * SBB's Distributionsvertrag caps the API-call-to-booking-click ratio:
6
+ * - Trips API (places, trips, trip-details, pagination): max 100 : 1
7
+ * - Offers API (prices, trip-offers): max 50 : 1
8
+ *
9
+ * Violating the ratio is a contractual issue, not a technical one — SBB
10
+ * audits quarterly. We need to: (1) observe the ratio in production so we
11
+ * can intervene before an audit finds us over, and (2) emit an operator
12
+ * warning when a running window crosses the threshold so the on-call can
13
+ * investigate (bot traffic? broken booking flow? missing CTAs?).
14
+ *
15
+ * ## What this module does — and doesn't do
16
+ * Does:
17
+ * - Count every SMAPI request (classified `trips` vs `offers` by base URL)
18
+ * and every booking-link click (from `/r/:token`) on a sliding 1-hour
19
+ * window.
20
+ * - Emit a JSON line per event to stdout so Railway's log drain (or any
21
+ * log forwarder) can ship it to long-term storage for trend analysis.
22
+ * - Log a `sbb_look2book_violation` warning JSON line once per 5-minute
23
+ * cooldown per family when the current window's ratio exceeds the cap.
24
+ * - Expose a synchronous `getRatioSnapshot()` so `/health` can surface the
25
+ * current state for readiness probes / dashboards.
26
+ *
27
+ * Does NOT:
28
+ * - Block or rate-limit violating requests. Look2Book is a business metric,
29
+ * not a request gate — gating tool calls based on a ratio would break UX
30
+ * catastrophically (e.g. a single user who hasn't clicked yet would see
31
+ * subsequent searches fail). See rate-limit.ts for the actual per-IP gate.
32
+ * - Persist counters. Railway restarts reset the window — acceptable since
33
+ * the contractual window is aggregate and real analysis happens offline
34
+ * via the JSON-line logs.
35
+ *
36
+ * ## Design: in-memory sliding window
37
+ * A single array of `{ts, kind}` events, pruned at every read/write so old
38
+ * entries fall off after `WINDOW_MS`. O(n) prune on each call, but n is
39
+ * bounded by the per-IP rate limit (60/min × 60min ≈ 3.6k events worst
40
+ * case) so this stays well under a millisecond.
41
+ *
42
+ * Events are timestamped with an injectable `now()` so tests can drive the
43
+ * window deterministically without `vi.useFakeTimers`.
44
+ */
45
+ // ─── Tunables ───────────────────────────────────────────────────────────────
46
+ /** Rolling window length. 1 hour balances responsiveness vs noise. */
47
+ const WINDOW_MS = 60 * 60 * 1000;
48
+ /** Maximum API-calls-to-clicks ratio per family — contractual caps. */
49
+ const CAP_TRIPS = 100;
50
+ const CAP_OFFERS = 50;
51
+ /** Minimum gap between duplicate violation warnings per family. */
52
+ const ALERT_COOLDOWN_MS = 5 * 60 * 1000;
53
+ /**
54
+ * Don't warn on the first few calls of a fresh window — a single call with
55
+ * zero clicks gives infinite ratio, which is technically a violation but
56
+ * operationally meaningless. Wait until we have enough calls to reason about.
57
+ */
58
+ const MIN_CALLS_BEFORE_ALERT = 10;
59
+ // ─── Mutable module state ───────────────────────────────────────────────────
60
+ const events = [];
61
+ const lastAlertTs = { trips: 0, offers: 0 };
62
+ /** Clock injector for deterministic tests. Defaults to Date.now. */
63
+ let nowFn = () => Date.now();
64
+ // ─── Internals ──────────────────────────────────────────────────────────────
65
+ function pruneOld(now) {
66
+ const cutoff = now - WINDOW_MS;
67
+ // Array.shift is O(n) but events is bounded (~3.6k max); fine.
68
+ while (events.length > 0 && events[0].ts < cutoff) {
69
+ events.shift();
70
+ }
71
+ }
72
+ function countsFor(family) {
73
+ let calls = 0;
74
+ let clicks = 0;
75
+ for (const ev of events) {
76
+ if (ev.kind === family)
77
+ calls++;
78
+ else if (ev.kind === 'click')
79
+ clicks++;
80
+ }
81
+ return { calls, clicks };
82
+ }
83
+ /**
84
+ * Current ratio for a family. Returns `null` when no calls yet (undefined
85
+ * ratio). When calls > 0 and clicks == 0, returns `Infinity` — callers
86
+ * should treat this as "over the cap" but violation logic also checks
87
+ * MIN_CALLS_BEFORE_ALERT to avoid spurious warnings.
88
+ */
89
+ function ratioFor(family) {
90
+ const { calls, clicks } = countsFor(family);
91
+ if (calls === 0)
92
+ return null;
93
+ if (clicks === 0)
94
+ return Infinity;
95
+ return calls / clicks;
96
+ }
97
+ function capFor(family) {
98
+ return family === 'trips' ? CAP_TRIPS : CAP_OFFERS;
99
+ }
100
+ function logJson(line) {
101
+ // Single JSON line per event — consistent with the /r/:token click log.
102
+ // Using stdout directly; Railway (and most log drains) capture it verbatim.
103
+ console.log(JSON.stringify(line));
104
+ }
105
+ function checkAndWarn(family, now) {
106
+ const { calls, clicks } = countsFor(family);
107
+ if (calls < MIN_CALLS_BEFORE_ALERT)
108
+ return;
109
+ const cap = capFor(family);
110
+ const ratio = ratioFor(family);
111
+ if (ratio === null || ratio <= cap)
112
+ return;
113
+ if (now - lastAlertTs[family] < ALERT_COOLDOWN_MS)
114
+ return;
115
+ lastAlertTs[family] = now;
116
+ console.warn(JSON.stringify({
117
+ event: 'sbb_look2book_violation',
118
+ ts: new Date(now).toISOString(),
119
+ family,
120
+ calls,
121
+ clicks,
122
+ ratio: ratio === Infinity ? 'infinity' : Number(ratio.toFixed(2)),
123
+ cap,
124
+ windowMs: WINDOW_MS,
125
+ }));
126
+ }
127
+ // ─── Public API ─────────────────────────────────────────────────────────────
128
+ /**
129
+ * Record a successful SMAPI call. Call AFTER the request succeeds so failed
130
+ * requests (auth errors, network blips) don't pollute the ratio — only
131
+ * billable, data-returning calls count against the cap.
132
+ *
133
+ * The `endpoint` arg is logged for offline attribution (which tool is the
134
+ * worst offender?) but doesn't affect bucketing.
135
+ */
136
+ export function recordApiCall(family, endpoint) {
137
+ const now = nowFn();
138
+ pruneOld(now);
139
+ events.push({ ts: now, kind: family, endpoint });
140
+ logJson({
141
+ event: 'sbb_smapi_call',
142
+ ts: new Date(now).toISOString(),
143
+ family,
144
+ endpoint,
145
+ });
146
+ checkAndWarn(family, now);
147
+ }
148
+ /**
149
+ * Record a booking-link click (from /r/:token). The click "pays back" one
150
+ * unit of ratio for either family — SBB's contract counts all clicks toward
151
+ * both caps since a single click represents real user booking intent.
152
+ */
153
+ export function recordClick(meta = {}) {
154
+ const now = nowFn();
155
+ pruneOld(now);
156
+ events.push({ ts: now, kind: 'click' });
157
+ logJson({
158
+ event: 'sbb_look2book_click',
159
+ ts: new Date(now).toISOString(),
160
+ ...(meta.tripId && { tripId: meta.tripId }),
161
+ });
162
+ }
163
+ /**
164
+ * Current window state. Cheap (bounded prune + single pass). Safe to call
165
+ * from a health endpoint handler without a cache.
166
+ */
167
+ export function getRatioSnapshot() {
168
+ const now = nowFn();
169
+ pruneOld(now);
170
+ const t = countsFor('trips');
171
+ const o = countsFor('offers');
172
+ const tRatio = ratioFor('trips');
173
+ const oRatio = ratioFor('offers');
174
+ return {
175
+ windowMs: WINDOW_MS,
176
+ trips: {
177
+ calls: t.calls,
178
+ clicks: t.clicks,
179
+ ratio: tRatio === Infinity ? null : tRatio,
180
+ cap: CAP_TRIPS,
181
+ violated: tRatio !== null && tRatio > CAP_TRIPS,
182
+ },
183
+ offers: {
184
+ calls: o.calls,
185
+ clicks: o.clicks,
186
+ ratio: oRatio === Infinity ? null : oRatio,
187
+ cap: CAP_OFFERS,
188
+ violated: oRatio !== null && oRatio > CAP_OFFERS,
189
+ },
190
+ };
191
+ }
192
+ // ─── Test hooks ─────────────────────────────────────────────────────────────
193
+ /** Reset all state. Call between tests. */
194
+ export function __resetForTesting() {
195
+ events.length = 0;
196
+ lastAlertTs.trips = 0;
197
+ lastAlertTs.offers = 0;
198
+ nowFn = () => Date.now();
199
+ }
200
+ /** Override the clock for deterministic window tests. */
201
+ export function __setClockForTesting(fn) {
202
+ nowFn = fn;
203
+ }
204
+ /** Expose tunables so tests can read them instead of duplicating the numbers. */
205
+ export const __internals = {
206
+ WINDOW_MS,
207
+ CAP_TRIPS,
208
+ CAP_OFFERS,
209
+ ALERT_COOLDOWN_MS,
210
+ MIN_CALLS_BEFORE_ALERT,
211
+ };
212
+ //# sourceMappingURL=look2book.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"look2book.js","sourceRoot":"","sources":["../src/look2book.ts"],"names":[],"mappings":"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"}