@mrtdown/core 2.0.0-alpha.3 → 2.0.0-alpha.4

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 (109) hide show
  1. package/dist/cli/commands/create.d.ts +30 -0
  2. package/dist/cli/commands/create.js +189 -0
  3. package/dist/cli/commands/create.js.map +1 -0
  4. package/dist/cli/commands/list.d.ts +6 -0
  5. package/dist/cli/commands/list.js +106 -0
  6. package/dist/cli/commands/list.js.map +1 -0
  7. package/dist/cli/commands/show.d.ts +6 -0
  8. package/dist/cli/commands/show.js +156 -0
  9. package/dist/cli/commands/show.js.map +1 -0
  10. package/dist/cli/commands/validate.d.ts +6 -0
  11. package/dist/cli/commands/validate.js +19 -0
  12. package/dist/cli/commands/validate.js.map +1 -0
  13. package/dist/cli/index.d.ts +2 -0
  14. package/dist/cli/index.js +162 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/llm/client.d.ts +2 -0
  17. package/dist/llm/client.js +5 -0
  18. package/dist/llm/client.js.map +1 -0
  19. package/dist/llm/common/MemoryStore.d.ts +21 -0
  20. package/dist/llm/common/MemoryStore.js +100 -0
  21. package/dist/llm/common/MemoryStore.js.map +1 -0
  22. package/dist/llm/common/MemoryStore.test.d.ts +1 -0
  23. package/dist/llm/common/MemoryStore.test.js +225 -0
  24. package/dist/llm/common/MemoryStore.test.js.map +1 -0
  25. package/dist/llm/common/formatCurrentState.d.ts +10 -0
  26. package/dist/llm/common/formatCurrentState.js +342 -0
  27. package/dist/llm/common/formatCurrentState.js.map +1 -0
  28. package/dist/llm/common/tool.d.ts +32 -0
  29. package/dist/llm/common/tool.js +6 -0
  30. package/dist/llm/common/tool.js.map +1 -0
  31. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.d.ts +1 -0
  32. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js +433 -0
  33. package/dist/llm/functions/extractClaimsFromNewEvidence/eval.test.js.map +1 -0
  34. package/dist/llm/functions/extractClaimsFromNewEvidence/index.d.ts +18 -0
  35. package/dist/llm/functions/extractClaimsFromNewEvidence/index.js +153 -0
  36. package/dist/llm/functions/extractClaimsFromNewEvidence/index.js.map +1 -0
  37. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.d.ts +1 -0
  38. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js +168 -0
  39. package/dist/llm/functions/extractClaimsFromNewEvidence/prompt.js.map +1 -0
  40. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.d.ts +19 -0
  41. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js +65 -0
  42. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindLinesTool.js.map +1 -0
  43. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.d.ts +21 -0
  44. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js +115 -0
  45. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindServicesTool.js.map +1 -0
  46. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.d.ts +24 -0
  47. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js +110 -0
  48. package/dist/llm/functions/extractClaimsFromNewEvidence/tools/FindStationsTool.js.map +1 -0
  49. package/dist/llm/functions/generateIssueTitleAndSlug/index.d.ts +14 -0
  50. package/dist/llm/functions/generateIssueTitleAndSlug/index.js +38 -0
  51. package/dist/llm/functions/generateIssueTitleAndSlug/index.js.map +1 -0
  52. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.d.ts +1 -0
  53. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js +23 -0
  54. package/dist/llm/functions/generateIssueTitleAndSlug/prompt.js.map +1 -0
  55. package/dist/llm/functions/translate/index.d.ts +1 -0
  56. package/dist/llm/functions/translate/index.js +59 -0
  57. package/dist/llm/functions/translate/index.js.map +1 -0
  58. package/dist/llm/functions/triageNewEvidence/eval.test.d.ts +1 -0
  59. package/dist/llm/functions/triageNewEvidence/eval.test.js +139 -0
  60. package/dist/llm/functions/triageNewEvidence/eval.test.js.map +1 -0
  61. package/dist/llm/functions/triageNewEvidence/index.d.ts +37 -0
  62. package/dist/llm/functions/triageNewEvidence/index.js +121 -0
  63. package/dist/llm/functions/triageNewEvidence/index.js.map +1 -0
  64. package/dist/llm/functions/triageNewEvidence/prompt.d.ts +1 -0
  65. package/dist/llm/functions/triageNewEvidence/prompt.js +60 -0
  66. package/dist/llm/functions/triageNewEvidence/prompt.js.map +1 -0
  67. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.d.ts +19 -0
  68. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js +65 -0
  69. package/dist/llm/functions/triageNewEvidence/tools/FindIssuesTool.js.map +1 -0
  70. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.d.ts +19 -0
  71. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js +37 -0
  72. package/dist/llm/functions/triageNewEvidence/tools/GetIssueTool.js.map +1 -0
  73. package/dist/scripts/ingestViaWebhook.d.ts +1 -0
  74. package/dist/scripts/ingestViaWebhook.js +9 -0
  75. package/dist/scripts/ingestViaWebhook.js.map +1 -0
  76. package/dist/validators/buildContext.d.ts +7 -0
  77. package/dist/validators/buildContext.js +164 -0
  78. package/dist/validators/buildContext.js.map +1 -0
  79. package/dist/validators/index.d.ts +17 -0
  80. package/dist/validators/index.js +58 -0
  81. package/dist/validators/index.js.map +1 -0
  82. package/dist/validators/issue.d.ts +13 -0
  83. package/dist/validators/issue.js +220 -0
  84. package/dist/validators/issue.js.map +1 -0
  85. package/dist/validators/landmark.d.ts +7 -0
  86. package/dist/validators/landmark.js +43 -0
  87. package/dist/validators/landmark.js.map +1 -0
  88. package/dist/validators/line.d.ts +8 -0
  89. package/dist/validators/line.js +87 -0
  90. package/dist/validators/line.js.map +1 -0
  91. package/dist/validators/operator.d.ts +7 -0
  92. package/dist/validators/operator.js +43 -0
  93. package/dist/validators/operator.js.map +1 -0
  94. package/dist/validators/service.d.ts +8 -0
  95. package/dist/validators/service.js +87 -0
  96. package/dist/validators/service.js.map +1 -0
  97. package/dist/validators/station.d.ts +8 -0
  98. package/dist/validators/station.js +93 -0
  99. package/dist/validators/station.js.map +1 -0
  100. package/dist/validators/town.d.ts +7 -0
  101. package/dist/validators/town.js +43 -0
  102. package/dist/validators/town.js.map +1 -0
  103. package/dist/validators/types.d.ts +19 -0
  104. package/dist/validators/types.js +2 -0
  105. package/dist/validators/types.js.map +1 -0
  106. package/dist/validators/utils.d.ts +2 -0
  107. package/dist/validators/utils.js +9 -0
  108. package/dist/validators/utils.js.map +1 -0
  109. package/package.json +11 -7
@@ -0,0 +1,433 @@
1
+ import 'dotenv/config';
2
+ import { resolve } from 'node:path';
3
+ import { describe } from 'vitest';
4
+ import { describeEval, StructuredOutputScorer } from 'vitest-evals';
5
+ import { FileStore } from '#repo/common/FileStore.js';
6
+ import { MRTDownRepository } from '#repo/MRTDownRepository.js';
7
+ import { assert } from '#util/assert.js';
8
+ import { extractClaimsFromNewEvidence, } from './index.js';
9
+ describe('extractClaimsFromNewEvidence', () => {
10
+ describeEval('should extract claims from new disruption evidence', {
11
+ // @ts-expect-error input is a string in the vitest-evals library
12
+ async data() {
13
+ const store = new FileStore(resolve(import.meta.dirname, '../../fixtures/data'));
14
+ const repo = new MRTDownRepository({ store });
15
+ const issueBundle = repo.issues.get('2026-01-01-tgl-train-fault');
16
+ assert(issueBundle != null, 'Issue bundle not found');
17
+ return [
18
+ {
19
+ input: {
20
+ newEvidence: {
21
+ ts: '2026-01-01T07:10:00+08:00',
22
+ text: '[TGL] Due to a track fault at Tengah, train services on the Tengah Line are delayed between Bukit Batok and Bukit Merah Central',
23
+ },
24
+ repo,
25
+ // This is used by vitest-evals as the test name, as the library expects `input` to be a string.
26
+ toString() {
27
+ return '[DISRUPTION] Expansion of scope';
28
+ },
29
+ },
30
+ expected: {
31
+ claims: [
32
+ {
33
+ entity: {
34
+ type: 'service',
35
+ serviceId: 'TGL_MAIN_E',
36
+ },
37
+ effect: {
38
+ facility: null,
39
+ service: {
40
+ kind: 'delay',
41
+ duration: null,
42
+ },
43
+ },
44
+ statusSignal: 'open',
45
+ scopes: {
46
+ service: [
47
+ {
48
+ type: 'service.segment',
49
+ fromStationId: 'BBT',
50
+ toStationId: 'BMC',
51
+ },
52
+ ],
53
+ },
54
+ timeHints: {
55
+ kind: 'start-only',
56
+ startAt: '2026-01-01T07:10:00+08:00',
57
+ },
58
+ causes: ['track.fault'],
59
+ },
60
+ {
61
+ entity: {
62
+ type: 'service',
63
+ serviceId: 'TGL_MAIN_W',
64
+ },
65
+ effect: {
66
+ facility: null,
67
+ service: {
68
+ kind: 'delay',
69
+ duration: null,
70
+ },
71
+ },
72
+ statusSignal: 'open',
73
+ scopes: {
74
+ service: [
75
+ {
76
+ type: 'service.segment',
77
+ fromStationId: 'BMC',
78
+ toStationId: 'BBT',
79
+ },
80
+ ],
81
+ },
82
+ timeHints: {
83
+ kind: 'start-only',
84
+ startAt: '2026-01-01T07:10:00+08:00',
85
+ },
86
+ causes: ['track.fault'],
87
+ },
88
+ ],
89
+ },
90
+ },
91
+ {
92
+ input: {
93
+ newEvidence: {
94
+ ts: '2026-01-01T07:10:00+08:00',
95
+ text: '[TGL] CLEARED: Fault has been cleared. Train service has resumed.',
96
+ },
97
+ repo,
98
+ // This is used by vitest-evals as the test name, as the library expects `input` to be a string.
99
+ toString() {
100
+ return '[DISRUPTION] Issue resolved';
101
+ },
102
+ },
103
+ expected: {
104
+ claims: [
105
+ {
106
+ entity: {
107
+ type: 'service',
108
+ serviceId: 'TGL_MAIN_E',
109
+ },
110
+ effect: {
111
+ service: null,
112
+ facility: null,
113
+ },
114
+ scopes: {
115
+ service: [{ type: 'service.whole' }],
116
+ },
117
+ statusSignal: 'cleared',
118
+ timeHints: {
119
+ kind: 'end-only',
120
+ endAt: '2026-01-01T07:10:00+08:00',
121
+ },
122
+ causes: null,
123
+ },
124
+ {
125
+ entity: {
126
+ type: 'service',
127
+ serviceId: 'TGL_MAIN_W',
128
+ },
129
+ effect: {
130
+ service: null,
131
+ facility: null,
132
+ },
133
+ statusSignal: 'cleared',
134
+ scopes: {
135
+ service: [{ type: 'service.whole' }],
136
+ },
137
+ timeHints: {
138
+ kind: 'end-only',
139
+ endAt: '2026-01-01T07:10:00+08:00',
140
+ },
141
+ causes: null,
142
+ },
143
+ ],
144
+ },
145
+ },
146
+ {
147
+ input: {
148
+ newEvidence: {
149
+ ts: '2026-01-01T07:10:00+08:00',
150
+ text: '[TGL] UPDATE: For alternative travel options, please refer to https://t.co/Le6ROZGqsm',
151
+ },
152
+ repo,
153
+ // This is used by vitest-evals as the test name, as the library expects `input` to be a string.
154
+ toString() {
155
+ return '[DISRUPTION] Irrelevant update';
156
+ },
157
+ },
158
+ expected: {
159
+ claims: [],
160
+ },
161
+ },
162
+ ];
163
+ },
164
+ async task(input) {
165
+ const result = await extractClaimsFromNewEvidence(input);
166
+ return JSON.stringify(result);
167
+ },
168
+ scorers: [StructuredOutputScorer()],
169
+ });
170
+ describeEval('should compute the impact of new maintenance evidence', {
171
+ // @ts-expect-error input is a string in the vitest-evals library
172
+ async data() {
173
+ const store = new FileStore(resolve(import.meta.dirname, '../../fixtures/data'));
174
+ const repo = new MRTDownRepository({ store });
175
+ return [
176
+ {
177
+ input: {
178
+ newEvidence: {
179
+ ts: '2026-01-01T07:10:00+08:00',
180
+ text: '[TGL] The Tengah Line will be closed for maintenance on Sat & Sun from 7 to 8 February 2026.',
181
+ },
182
+ repo,
183
+ // This is used by vitest-evals as the test name, as the library expects `input` to be a string.
184
+ toString() {
185
+ return '[MAINTENANCE] New issue';
186
+ },
187
+ },
188
+ expected: {
189
+ claims: [
190
+ {
191
+ entity: {
192
+ type: 'service',
193
+ serviceId: 'TGL_MAIN_E',
194
+ },
195
+ effect: {
196
+ service: { kind: 'no-service' },
197
+ facility: null,
198
+ },
199
+ statusSignal: 'planned',
200
+ scopes: {
201
+ service: [{ type: 'service.whole' }],
202
+ },
203
+ timeHints: {
204
+ kind: 'fixed',
205
+ startAt: '2026-02-07T00:00:00+08:00',
206
+ endAt: '2026-02-09T00:00:00+08:00',
207
+ },
208
+ causes: null,
209
+ },
210
+ {
211
+ entity: {
212
+ type: 'service',
213
+ serviceId: 'TGL_MAIN_W',
214
+ },
215
+ effect: {
216
+ service: { kind: 'no-service' },
217
+ facility: null,
218
+ },
219
+ statusSignal: 'planned',
220
+ scopes: {
221
+ service: [{ type: 'service.whole' }],
222
+ },
223
+ timeHints: {
224
+ kind: 'fixed',
225
+ startAt: '2026-02-07T00:00:00+08:00',
226
+ endAt: '2026-02-09T00:00:00+08:00',
227
+ },
228
+ causes: null,
229
+ },
230
+ ],
231
+ },
232
+ },
233
+ {
234
+ input: {
235
+ newEvidence: {
236
+ ts: '2026-01-01T07:10:00+08:00',
237
+ text: 'To continue testing the integrated systems and trains in preparation for Stage 2 of #TGL, train services from Bukit Batok to Bukit Merah Central will start later at 6.30am and end at 9pm daily from 1 to 8 February 2026.',
238
+ },
239
+ repo,
240
+ // This is used by vitest-evals as the test name, as the library expects `input` to be a string.
241
+ toString() {
242
+ return '[MAINTENANCE] Service hour adjustments';
243
+ },
244
+ },
245
+ expected: {
246
+ claims: [
247
+ {
248
+ entity: {
249
+ type: 'service',
250
+ serviceId: 'TGL_MAIN_E',
251
+ },
252
+ effect: {
253
+ service: {
254
+ kind: 'service-hours-adjustment',
255
+ },
256
+ facility: null,
257
+ },
258
+ statusSignal: 'planned',
259
+ scopes: {
260
+ service: [
261
+ {
262
+ type: 'service.segment',
263
+ fromStationId: 'BBT',
264
+ toStationId: 'BMC',
265
+ },
266
+ ],
267
+ },
268
+ timeHints: {
269
+ kind: 'recurring',
270
+ frequency: 'daily',
271
+ startAt: '2026-02-01T21:00:00+08:00',
272
+ endAt: '2026-02-08T21:00:00+08:00',
273
+ daysOfWeek: null,
274
+ timeZone: 'Asia/Singapore',
275
+ timeWindow: {
276
+ startAt: '21:00:00',
277
+ endAt: '06:30:00',
278
+ },
279
+ excludedDates: null,
280
+ },
281
+ causes: ['system.upgrade'],
282
+ },
283
+ {
284
+ entity: {
285
+ type: 'service',
286
+ serviceId: 'TGL_MAIN_W',
287
+ },
288
+ effect: {
289
+ service: {
290
+ kind: 'service-hours-adjustment',
291
+ },
292
+ facility: null,
293
+ },
294
+ statusSignal: 'planned',
295
+ scopes: {
296
+ service: [
297
+ {
298
+ type: 'service.segment',
299
+ fromStationId: 'BMC',
300
+ toStationId: 'BBT',
301
+ },
302
+ ],
303
+ },
304
+ timeHints: {
305
+ kind: 'recurring',
306
+ frequency: 'daily',
307
+ startAt: '2026-02-01T21:00:00+08:00',
308
+ endAt: '2026-02-08T21:00:00+08:00',
309
+ daysOfWeek: null,
310
+ timeZone: 'Asia/Singapore',
311
+ timeWindow: {
312
+ startAt: '21:00:00',
313
+ endAt: '06:30:00',
314
+ },
315
+ excludedDates: null,
316
+ },
317
+ causes: ['system.upgrade'],
318
+ },
319
+ ],
320
+ },
321
+ },
322
+ {
323
+ input: {
324
+ newEvidence: {
325
+ ts: '2026-01-10T22:00:00+08:00',
326
+ text: 'The Tengah & Seletar Lines train service will start at 10am on 18 Feb. For MRT Shuttle Bus pick-up points, visit http://t.co/fwb2wOqI',
327
+ },
328
+ repo,
329
+ // This is used by vitest-evals as the test name, as the library expects `input` to be a string.
330
+ toString() {
331
+ return '[MAINTENANCE] Service hour adjustments';
332
+ },
333
+ },
334
+ expected: {
335
+ claims: [
336
+ {
337
+ entity: {
338
+ type: 'service',
339
+ serviceId: 'TGL_MAIN_E',
340
+ },
341
+ effect: {
342
+ service: { kind: 'service-hours-adjustment' },
343
+ facility: null,
344
+ },
345
+ statusSignal: 'planned',
346
+ scopes: {
347
+ service: [{ type: 'service.whole' }],
348
+ },
349
+ timeHints: {
350
+ kind: 'fixed',
351
+ startAt: '2026-02-18T00:00:00+08:00',
352
+ endAt: '2026-02-18T10:00:00+08:00',
353
+ },
354
+ causes: null,
355
+ },
356
+ {
357
+ entity: {
358
+ type: 'service',
359
+ serviceId: 'TGL_MAIN_W',
360
+ },
361
+ effect: {
362
+ service: { kind: 'service-hours-adjustment' },
363
+ facility: null,
364
+ },
365
+ statusSignal: 'planned',
366
+ scopes: {
367
+ service: [{ type: 'service.whole' }],
368
+ },
369
+ timeHints: {
370
+ kind: 'fixed',
371
+ startAt: '2026-02-18T00:00:00+08:00',
372
+ endAt: '2026-02-18T10:00:00+08:00',
373
+ },
374
+ causes: null,
375
+ },
376
+ {
377
+ entity: {
378
+ type: 'service',
379
+ serviceId: 'SLL_MAIN_N',
380
+ },
381
+ effect: {
382
+ service: { kind: 'service-hours-adjustment' },
383
+ facility: null,
384
+ },
385
+ scopes: {
386
+ service: [{ type: 'service.whole' }],
387
+ },
388
+ timeHints: {
389
+ kind: 'fixed',
390
+ startAt: '2026-02-18T00:00:00+08:00',
391
+ endAt: '2026-02-18T10:00:00+08:00',
392
+ },
393
+ statusSignal: 'planned',
394
+ causes: null,
395
+ },
396
+ {
397
+ entity: {
398
+ type: 'service',
399
+ serviceId: 'SLL_MAIN_S',
400
+ },
401
+ effect: {
402
+ service: { kind: 'service-hours-adjustment' },
403
+ facility: null,
404
+ },
405
+ scopes: {
406
+ service: [{ type: 'service.whole' }],
407
+ },
408
+ timeHints: {
409
+ kind: 'fixed',
410
+ startAt: '2026-02-18T00:00:00+08:00',
411
+ endAt: '2026-02-18T10:00:00+08:00',
412
+ },
413
+ statusSignal: 'planned',
414
+ causes: null,
415
+ },
416
+ ],
417
+ },
418
+ },
419
+ ];
420
+ },
421
+ async task(input) {
422
+ const result = await extractClaimsFromNewEvidence(input);
423
+ return JSON.stringify(result);
424
+ },
425
+ scorers: [
426
+ StructuredOutputScorer({
427
+ match: 'fuzzy',
428
+ fuzzyOptions: { ignoreArrayOrder: true },
429
+ }),
430
+ ],
431
+ });
432
+ });
433
+ //# sourceMappingURL=eval.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eval.test.js","sourceRoot":"/","sources":["llm/functions/extractClaimsFromNewEvidence/eval.test.ts"],"names":[],"mappings":"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","sourcesContent":["import 'dotenv/config';\n\nimport { resolve } from 'node:path';\nimport { describe } from 'vitest';\nimport { describeEval, StructuredOutputScorer } from 'vitest-evals';\nimport { FileStore } from '#repo/common/FileStore.js';\nimport { MRTDownRepository } from '#repo/MRTDownRepository.js';\nimport { assert } from '#util/assert.js';\nimport {\n type ExtractClaimsFromNewEvidenceParams,\n type ExtractClaimsFromNewEvidenceResult,\n extractClaimsFromNewEvidence,\n} from './index.js';\n\ndescribe('extractClaimsFromNewEvidence', () => {\n describeEval('should extract claims from new disruption evidence', {\n // @ts-expect-error input is a string in the vitest-evals library\n async data() {\n const store = new FileStore(\n resolve(import.meta.dirname, '../../fixtures/data'),\n );\n const repo = new MRTDownRepository({ store });\n const issueBundle = repo.issues.get('2026-01-01-tgl-train-fault');\n assert(issueBundle != null, 'Issue bundle not found');\n\n return [\n {\n input: {\n newEvidence: {\n ts: '2026-01-01T07:10:00+08:00',\n text: '[TGL] Due to a track fault at Tengah, train services on the Tengah Line are delayed between Bukit Batok and Bukit Merah Central',\n },\n repo,\n // This is used by vitest-evals as the test name, as the library expects `input` to be a string.\n toString() {\n return '[DISRUPTION] Expansion of scope';\n },\n },\n expected: {\n claims: [\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_E',\n },\n effect: {\n facility: null,\n service: {\n kind: 'delay',\n duration: null,\n },\n },\n statusSignal: 'open',\n scopes: {\n service: [\n {\n type: 'service.segment',\n fromStationId: 'BBT',\n toStationId: 'BMC',\n },\n ],\n },\n timeHints: {\n kind: 'start-only',\n startAt: '2026-01-01T07:10:00+08:00',\n },\n causes: ['track.fault'],\n },\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_W',\n },\n effect: {\n facility: null,\n service: {\n kind: 'delay',\n duration: null,\n },\n },\n statusSignal: 'open',\n scopes: {\n service: [\n {\n type: 'service.segment',\n fromStationId: 'BMC',\n toStationId: 'BBT',\n },\n ],\n },\n timeHints: {\n kind: 'start-only',\n startAt: '2026-01-01T07:10:00+08:00',\n },\n causes: ['track.fault'],\n },\n ],\n },\n },\n {\n input: {\n newEvidence: {\n ts: '2026-01-01T07:10:00+08:00',\n text: '[TGL] CLEARED: Fault has been cleared. Train service has resumed.',\n },\n repo,\n // This is used by vitest-evals as the test name, as the library expects `input` to be a string.\n toString() {\n return '[DISRUPTION] Issue resolved';\n },\n },\n expected: {\n claims: [\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_E',\n },\n effect: {\n service: null,\n facility: null,\n },\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n statusSignal: 'cleared',\n timeHints: {\n kind: 'end-only',\n endAt: '2026-01-01T07:10:00+08:00',\n },\n causes: null,\n },\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_W',\n },\n effect: {\n service: null,\n facility: null,\n },\n statusSignal: 'cleared',\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n timeHints: {\n kind: 'end-only',\n endAt: '2026-01-01T07:10:00+08:00',\n },\n causes: null,\n },\n ],\n },\n },\n {\n input: {\n newEvidence: {\n ts: '2026-01-01T07:10:00+08:00',\n text: '[TGL] UPDATE: For alternative travel options, please refer to https://t.co/Le6ROZGqsm',\n },\n repo,\n // This is used by vitest-evals as the test name, as the library expects `input` to be a string.\n toString() {\n return '[DISRUPTION] Irrelevant update';\n },\n },\n expected: {\n claims: [],\n },\n },\n ] satisfies {\n input: ExtractClaimsFromNewEvidenceParams & { toString(): string };\n expected: ExtractClaimsFromNewEvidenceResult;\n }[];\n },\n async task(input) {\n const result = await extractClaimsFromNewEvidence(\n input as unknown as ExtractClaimsFromNewEvidenceParams,\n );\n return JSON.stringify(result);\n },\n scorers: [StructuredOutputScorer()],\n });\n describeEval('should compute the impact of new maintenance evidence', {\n // @ts-expect-error input is a string in the vitest-evals library\n async data() {\n const store = new FileStore(\n resolve(import.meta.dirname, '../../fixtures/data'),\n );\n const repo = new MRTDownRepository({ store });\n\n return [\n {\n input: {\n newEvidence: {\n ts: '2026-01-01T07:10:00+08:00',\n text: '[TGL] The Tengah Line will be closed for maintenance on Sat & Sun from 7 to 8 February 2026.',\n },\n repo,\n // This is used by vitest-evals as the test name, as the library expects `input` to be a string.\n toString() {\n return '[MAINTENANCE] New issue';\n },\n },\n expected: {\n claims: [\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_E',\n },\n effect: {\n service: { kind: 'no-service' },\n facility: null,\n },\n statusSignal: 'planned',\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n timeHints: {\n kind: 'fixed',\n startAt: '2026-02-07T00:00:00+08:00',\n endAt: '2026-02-09T00:00:00+08:00',\n },\n causes: null,\n },\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_W',\n },\n effect: {\n service: { kind: 'no-service' },\n facility: null,\n },\n statusSignal: 'planned',\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n timeHints: {\n kind: 'fixed',\n startAt: '2026-02-07T00:00:00+08:00',\n endAt: '2026-02-09T00:00:00+08:00',\n },\n causes: null,\n },\n ],\n },\n },\n {\n input: {\n newEvidence: {\n ts: '2026-01-01T07:10:00+08:00',\n text: 'To continue testing the integrated systems and trains in preparation for Stage 2 of #TGL, train services from Bukit Batok to Bukit Merah Central will start later at 6.30am and end at 9pm daily from 1 to 8 February 2026.',\n },\n repo,\n // This is used by vitest-evals as the test name, as the library expects `input` to be a string.\n toString() {\n return '[MAINTENANCE] Service hour adjustments';\n },\n },\n expected: {\n claims: [\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_E',\n },\n effect: {\n service: {\n kind: 'service-hours-adjustment',\n },\n facility: null,\n },\n statusSignal: 'planned',\n scopes: {\n service: [\n {\n type: 'service.segment',\n fromStationId: 'BBT',\n toStationId: 'BMC',\n },\n ],\n },\n timeHints: {\n kind: 'recurring',\n frequency: 'daily',\n startAt: '2026-02-01T21:00:00+08:00',\n endAt: '2026-02-08T21:00:00+08:00',\n daysOfWeek: null,\n timeZone: 'Asia/Singapore',\n timeWindow: {\n startAt: '21:00:00',\n endAt: '06:30:00',\n },\n excludedDates: null,\n },\n causes: ['system.upgrade'],\n },\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_W',\n },\n effect: {\n service: {\n kind: 'service-hours-adjustment',\n },\n facility: null,\n },\n statusSignal: 'planned',\n scopes: {\n service: [\n {\n type: 'service.segment',\n fromStationId: 'BMC',\n toStationId: 'BBT',\n },\n ],\n },\n timeHints: {\n kind: 'recurring',\n frequency: 'daily',\n startAt: '2026-02-01T21:00:00+08:00',\n endAt: '2026-02-08T21:00:00+08:00',\n daysOfWeek: null,\n timeZone: 'Asia/Singapore',\n timeWindow: {\n startAt: '21:00:00',\n endAt: '06:30:00',\n },\n excludedDates: null,\n },\n causes: ['system.upgrade'],\n },\n ],\n },\n },\n {\n input: {\n newEvidence: {\n ts: '2026-01-10T22:00:00+08:00',\n text: 'The Tengah & Seletar Lines train service will start at 10am on 18 Feb. For MRT Shuttle Bus pick-up points, visit http://t.co/fwb2wOqI',\n },\n repo,\n // This is used by vitest-evals as the test name, as the library expects `input` to be a string.\n toString() {\n return '[MAINTENANCE] Service hour adjustments';\n },\n },\n expected: {\n claims: [\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_E',\n },\n effect: {\n service: { kind: 'service-hours-adjustment' },\n facility: null,\n },\n statusSignal: 'planned',\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n timeHints: {\n kind: 'fixed',\n startAt: '2026-02-18T00:00:00+08:00',\n endAt: '2026-02-18T10:00:00+08:00',\n },\n causes: null,\n },\n {\n entity: {\n type: 'service',\n serviceId: 'TGL_MAIN_W',\n },\n effect: {\n service: { kind: 'service-hours-adjustment' },\n facility: null,\n },\n statusSignal: 'planned',\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n timeHints: {\n kind: 'fixed',\n startAt: '2026-02-18T00:00:00+08:00',\n endAt: '2026-02-18T10:00:00+08:00',\n },\n causes: null,\n },\n {\n entity: {\n type: 'service',\n serviceId: 'SLL_MAIN_N',\n },\n effect: {\n service: { kind: 'service-hours-adjustment' },\n facility: null,\n },\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n timeHints: {\n kind: 'fixed',\n startAt: '2026-02-18T00:00:00+08:00',\n endAt: '2026-02-18T10:00:00+08:00',\n },\n statusSignal: 'planned',\n causes: null,\n },\n {\n entity: {\n type: 'service',\n serviceId: 'SLL_MAIN_S',\n },\n effect: {\n service: { kind: 'service-hours-adjustment' },\n facility: null,\n },\n scopes: {\n service: [{ type: 'service.whole' }],\n },\n timeHints: {\n kind: 'fixed',\n startAt: '2026-02-18T00:00:00+08:00',\n endAt: '2026-02-18T10:00:00+08:00',\n },\n statusSignal: 'planned',\n causes: null,\n },\n ],\n },\n },\n ] satisfies {\n input: ExtractClaimsFromNewEvidenceParams & { toString(): string };\n expected: ExtractClaimsFromNewEvidenceResult;\n }[];\n },\n async task(input) {\n const result = await extractClaimsFromNewEvidence(\n input as unknown as ExtractClaimsFromNewEvidenceParams,\n );\n return JSON.stringify(result);\n },\n scorers: [\n StructuredOutputScorer({\n match: 'fuzzy',\n fuzzyOptions: { ignoreArrayOrder: true },\n }),\n ],\n });\n});\n"]}
@@ -0,0 +1,18 @@
1
+ import type { MRTDownRepository } from '#repo/MRTDownRepository.js';
2
+ import { type Claim } from '#schema/issue/claim.js';
3
+ export interface ExtractClaimsFromNewEvidenceParams {
4
+ newEvidence: {
5
+ ts: string;
6
+ text: string;
7
+ };
8
+ repo: MRTDownRepository;
9
+ }
10
+ export type ExtractClaimsFromNewEvidenceResult = {
11
+ claims: Claim[];
12
+ };
13
+ /**
14
+ * Extract claims from new evidence.
15
+ * @param params
16
+ * @returns
17
+ */
18
+ export declare function extractClaimsFromNewEvidence(params: ExtractClaimsFromNewEvidenceParams): Promise<ExtractClaimsFromNewEvidenceResult>;
@@ -0,0 +1,153 @@
1
+ import { DateTime } from 'luxon';
2
+ import z from 'zod';
3
+ import { estimateOpenAICostFromUsage, normalizeOpenAIResponsesUsage, } from '#helpers/estimateOpenAICost.js';
4
+ import { openAiClient } from '#llm/client.js';
5
+ import { ClaimSchema } from '#schema/issue/claim.js';
6
+ import { assert } from '#util/assert.js';
7
+ import { buildSystemPrompt } from './prompt.js';
8
+ import { FindLinesTool } from './tools/FindLinesTool.js';
9
+ import { FindServicesTool } from './tools/FindServicesTool.js';
10
+ import { FindStationsTool } from './tools/FindStationsTool.js';
11
+ const TOOL_CALL_LIMIT = 5;
12
+ const ResponseSchema = z.object({
13
+ claims: z.array(ClaimSchema),
14
+ });
15
+ /**
16
+ * Extract claims from new evidence.
17
+ * @param params
18
+ * @returns
19
+ */
20
+ export async function extractClaimsFromNewEvidence(params) {
21
+ const evidenceTs = DateTime.fromISO(params.newEvidence.ts);
22
+ assert(evidenceTs.isValid, `Invalid date: ${params.newEvidence.ts}`);
23
+ const findStationsTool = new FindStationsTool(evidenceTs, params.repo);
24
+ const findServicesTool = new FindServicesTool(evidenceTs, params.repo);
25
+ const findLinesTool = new FindLinesTool(params.repo);
26
+ const toolRegistry = {
27
+ [findStationsTool.name]: findStationsTool,
28
+ [findServicesTool.name]: findServicesTool,
29
+ [findLinesTool.name]: findLinesTool,
30
+ };
31
+ const context = [
32
+ {
33
+ role: 'user',
34
+ content: `
35
+ Evidence: ${params.newEvidence.text}
36
+
37
+ Timestamp: ${evidenceTs.toISO({ includeOffset: true })}
38
+ `.trim(),
39
+ },
40
+ ];
41
+ const systemPrompt = buildSystemPrompt();
42
+ const model = 'gpt-5-mini';
43
+ let toolCallCount = 0;
44
+ let response;
45
+ do {
46
+ response = await openAiClient.responses.parse({
47
+ model,
48
+ instructions: systemPrompt,
49
+ input: context,
50
+ reasoning: {
51
+ effort: 'medium',
52
+ summary: 'concise',
53
+ },
54
+ text: {
55
+ format: {
56
+ type: 'json_schema',
57
+ name: 'Response',
58
+ strict: true,
59
+ schema: z.toJSONSchema(ResponseSchema),
60
+ },
61
+ },
62
+ tools: Object.values(toolRegistry).map((tool) => {
63
+ return {
64
+ type: 'function',
65
+ name: tool.name,
66
+ description: tool.description,
67
+ parameters: tool.paramsSchema,
68
+ strict: true,
69
+ };
70
+ }),
71
+ // Don't persist conversation with OpenAI, but include reasoning content to
72
+ // continue the thread with the same reasoning.
73
+ store: false,
74
+ include: ['reasoning.encrypted_content'],
75
+ });
76
+ for (const item of response.output) {
77
+ switch (item.type) {
78
+ case 'function_call': {
79
+ /**
80
+ * Prevent the `parsed_arguments` field from being included
81
+ * https://github.com/openai/openai-python/issues/2374
82
+ */
83
+ context.push({
84
+ type: 'function_call',
85
+ id: item.id,
86
+ call_id: item.call_id,
87
+ name: item.name,
88
+ arguments: item.arguments,
89
+ });
90
+ if (toolCallCount > TOOL_CALL_LIMIT) {
91
+ context.push({
92
+ type: 'function_call_output',
93
+ call_id: item.call_id,
94
+ output: 'Ran out of tool calls. Stop Calling.',
95
+ });
96
+ console.log('Forced short-circuit, returning error message in tool call result.');
97
+ }
98
+ if (item.name in toolRegistry) {
99
+ const tool = toolRegistry[item.name];
100
+ let params;
101
+ try {
102
+ params = tool.parseParams(JSON.parse(item.arguments));
103
+ }
104
+ catch (e) {
105
+ console.error(`[extractClaimsFromNewEvidence] Error parsing parameters for tool "${item.name}" with arguments "${item.arguments}":`, e);
106
+ context.push({
107
+ type: 'function_call_output',
108
+ call_id: item.call_id,
109
+ output: `Invalid parameters for tool "${item.name}". Please try again.`,
110
+ });
111
+ continue;
112
+ }
113
+ // Call the tool's run function
114
+ const result = await tool.runner(params);
115
+ context.push({
116
+ type: 'function_call_output',
117
+ call_id: item.call_id,
118
+ output: result,
119
+ });
120
+ }
121
+ toolCallCount++;
122
+ break;
123
+ }
124
+ default: {
125
+ context.push(item);
126
+ break;
127
+ }
128
+ }
129
+ }
130
+ const usage = normalizeOpenAIResponsesUsage(response.usage);
131
+ const estimate = estimateOpenAICostFromUsage({ model, usage });
132
+ if (usage != null) {
133
+ console.log('[extractClaimsFromNewEvidence] Usage:', {
134
+ inputTokens: usage.inputTokens,
135
+ cachedInputTokens: usage.cachedInputTokens,
136
+ outputTokens: usage.outputTokens,
137
+ totalTokens: usage.totalTokens,
138
+ });
139
+ if (estimate != null) {
140
+ console.log('[extractClaimsFromNewEvidence] Estimated cost (USD):', estimate.estimatedCostUsd.toFixed(8));
141
+ }
142
+ else {
143
+ console.log(`[extractClaimsFromNewEvidence] No pricing configured for model "${model}".`);
144
+ }
145
+ }
146
+ else {
147
+ console.log('[extractClaimsFromNewEvidence] Usage is unavailable');
148
+ }
149
+ } while (response.output.some((item) => item.type === 'function_call'));
150
+ assert(response.output_parsed != null, 'Response output parsed is null');
151
+ return response.output_parsed;
152
+ }
153
+ //# sourceMappingURL=index.js.map