mumpix 1.0.19 → 1.1.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 (38) hide show
  1. package/CHANGELOG.md +82 -11
  2. package/README.md +278 -8
  3. package/bin/mumpix.js +1 -405
  4. package/examples/agent-memory.js +1 -1
  5. package/examples/basic.js +1 -1
  6. package/examples/behavioral-primitives.js +50 -0
  7. package/examples/recall-quality-suite.js +156 -0
  8. package/examples/verified-mode.js +1 -1
  9. package/package.json +24 -13
  10. package/scripts/bench-semantic.cjs +76 -0
  11. package/scripts/test-license-modes.cjs +87 -0
  12. package/scripts/test-phase0.cjs +137 -0
  13. package/scripts/test-semantic.cjs +411 -0
  14. package/src/brp/index.js +1 -0
  15. package/src/collapse/index.js +1 -0
  16. package/src/core/MumpixDB.js +264 -323
  17. package/src/core/audit.js +1 -173
  18. package/src/core/auth.js +1 -232
  19. package/src/core/inverted-index.js +249 -0
  20. package/src/core/license.js +1 -267
  21. package/src/core/ml-dsa.mjs +1 -25
  22. package/src/core/ml-kem.mjs +1 -32
  23. package/src/core/recall.js +226 -112
  24. package/src/core/semantic-sidecar.js +312 -0
  25. package/src/core/store.js +365 -288
  26. package/src/core/wal-writer.js +83 -0
  27. package/src/index.js +20 -34
  28. package/src/integrations/developer-sdk.js +1 -165
  29. package/src/integrations/langchain-official.js +1 -0
  30. package/src/integrations/langchain.js +1 -131
  31. package/src/integrations/llamaindex-official.js +1 -0
  32. package/src/integrations/llamaindex.js +1 -86
  33. package/src/integrations/vector-sidecar.js +325 -0
  34. package/src/rlp/index.js +1 -0
  35. package/src/temporal/engine.js +1 -1894
  36. package/src/temporal/indexes.js +1 -178
  37. package/src/temporal/operators.js +1 -186
  38. package/scripts/postinstall-auth.js +0 -101
@@ -1,1894 +1 @@
1
- 'use strict';
2
-
3
- // Generated by scripts/build-package.cjs from src/memory/events/temporal-engine.js
4
- // Do not edit this file directly in the publish tree.
5
- const {
6
- normalizeText,
7
- toEpochDay,
8
- dedupeEvents,
9
- resolveEvent,
10
- first,
11
- last,
12
- firstAfter,
13
- daysBetween,
14
- monthsSince,
15
- countBefore,
16
- } = require('./operators');
17
- const { buildEventIndex, eventCandidatesForQuery } = require('./indexes');
18
-
19
- const WEEKDAY_INDEX = {
20
- sunday: 0,
21
- monday: 1,
22
- tuesday: 2,
23
- wednesday: 3,
24
- thursday: 4,
25
- friday: 5,
26
- saturday: 6,
27
- };
28
-
29
- const MONTH_INDEX = {
30
- january: 1, jan: 1,
31
- february: 2, feb: 2,
32
- march: 3, mar: 3,
33
- april: 4, apr: 4,
34
- may: 5,
35
- june: 6, jun: 6,
36
- july: 7, jul: 7,
37
- august: 8, aug: 8,
38
- september: 9, sep: 9, sept: 9,
39
- october: 10, oct: 10,
40
- november: 11, nov: 11,
41
- december: 12, dec: 12,
42
- };
43
-
44
- function isoDateFromTs(ts) {
45
- const date = new Date(Number.isFinite(ts) ? ts : Date.now());
46
- if (Number.isNaN(date.getTime())) return null;
47
- return date.toISOString().slice(0, 10);
48
- }
49
-
50
- function shiftDate(isoDate, days) {
51
- const ts = Date.parse(`${isoDate}T00:00:00Z`);
52
- if (!Number.isFinite(ts)) return null;
53
- return new Date(ts + (days * 86400000)).toISOString().slice(0, 10);
54
- }
55
-
56
- function shiftMonths(isoDate, months) {
57
- const ts = Date.parse(`${isoDate}T00:00:00Z`);
58
- if (!Number.isFinite(ts)) return null;
59
- const date = new Date(ts);
60
- date.setUTCMonth(date.getUTCMonth() + months);
61
- return date.toISOString().slice(0, 10);
62
- }
63
-
64
- function shiftYears(isoDate, years) {
65
- const ts = Date.parse(`${isoDate}T00:00:00Z`);
66
- if (!Number.isFinite(ts)) return null;
67
- const date = new Date(ts);
68
- date.setUTCFullYear(date.getUTCFullYear() + years);
69
- return date.toISOString().slice(0, 10);
70
- }
71
-
72
- function blackFridayIso(year) {
73
- const y = Number(year);
74
- if (!Number.isFinite(y)) return null;
75
- const novemberFirst = new Date(Date.UTC(y, 10, 1));
76
- const day = novemberFirst.getUTCDay();
77
- const firstThursday = 1 + ((4 - day + 7) % 7);
78
- const thanksgiving = firstThursday + 21;
79
- return `${y}-11-${String(thanksgiving + 1).padStart(2, '0')}`;
80
- }
81
-
82
- function wordToNumber(value) {
83
- const raw = String(value || '').toLowerCase();
84
- if (/^\d+$/.test(raw)) return Number(raw);
85
- const words = {
86
- a: 1, an: 1,
87
- one: 1, two: 2, three: 3, four: 4, five: 5, six: 6,
88
- seven: 7, eight: 8, nine: 9, ten: 10, eleven: 11, twelve: 12,
89
- };
90
- return words[raw] || null;
91
- }
92
-
93
- function extractDate(text, messageDate) {
94
- const fallback = messageDate || null;
95
- const fallbackDate = fallback ? new Date(`${fallback}T00:00:00Z`) : null;
96
- const fallbackYear = fallbackDate ? fallbackDate.getUTCFullYear() : 2023;
97
- const fallbackMonth = fallbackDate ? fallbackDate.getUTCMonth() + 1 : 1;
98
- const value = String(text || '');
99
-
100
- const monthDay = value.match(/\b(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec)\s+(\d{1,2})(?:st|nd|rd|th)?(?:,?\s+(\d{4}))?\b/i);
101
- if (monthDay) {
102
- const month = MONTH_INDEX[monthDay[1].toLowerCase()];
103
- const day = Number(monthDay[2]);
104
- const year = monthDay[3] ? Number(monthDay[3]) : fallbackYear;
105
- return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
106
- }
107
-
108
- const dayOfMonth = value.match(/\b(\d{1,2})(?:st|nd|rd|th)?\s+of\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i);
109
- if (dayOfMonth) {
110
- const month = MONTH_INDEX[dayOfMonth[2].toLowerCase()];
111
- const day = Number(dayOfMonth[1]);
112
- return `${fallbackYear}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
113
- }
114
-
115
- const midMonth = value.match(/\bmid[-\s](january|february|march|april|may|june|july|august|september|october|november|december)\b/i);
116
- if (midMonth) {
117
- const month = MONTH_INDEX[midMonth[1].toLowerCase()];
118
- return `${fallbackYear}-${String(month).padStart(2, '0')}-15`;
119
- }
120
-
121
- const monthOnly = value.match(/\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i);
122
- if (monthOnly) {
123
- const month = MONTH_INDEX[monthOnly[1].toLowerCase()];
124
- return `${fallbackYear}-${String(month).padStart(2, '0')}-15`;
125
- }
126
-
127
- if (/\bblack friday\b/i.test(value)) {
128
- const blackFriday = blackFridayIso(fallbackYear);
129
- if (/\ba week before black friday\b/i.test(value) && blackFriday) return shiftDate(blackFriday, -7);
130
- return blackFriday;
131
- }
132
-
133
- const numeric = value.match(/\b(\d{1,2})\/(\d{1,2})(?:\/(\d{2,4}))?\b/);
134
- if (numeric) {
135
- const month = Number(numeric[1]);
136
- const day = Number(numeric[2]);
137
- let year = fallbackYear;
138
- if (numeric[3]) {
139
- year = Number(numeric[3]);
140
- if (year < 100) year += 2000;
141
- } else if (month > fallbackMonth + 1) {
142
- year -= 1;
143
- }
144
- return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
145
- }
146
-
147
- const monthsAgo = value.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+ago\b/i);
148
- if (monthsAgo && fallbackDate) {
149
- const count = wordToNumber(monthsAgo[1]);
150
- if (count) return shiftMonths(fallback, -count);
151
- }
152
-
153
- const weeksAgo = value.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six)\s+weeks?\s+ago\b/i);
154
- if (weeksAgo && fallbackDate) {
155
- const count = wordToNumber(weeksAgo[1]);
156
- if (count) return shiftDate(fallback, -(count * 7));
157
- }
158
-
159
- const lastWeekday = value.match(/\blast (monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i);
160
- if (lastWeekday && fallbackDate) {
161
- const target = WEEKDAY_INDEX[lastWeekday[1].toLowerCase()];
162
- let offset = (fallbackDate.getUTCDay() - target + 7) % 7;
163
- if (offset === 0) offset = 7;
164
- return shiftDate(fallback, -offset);
165
- }
166
-
167
- if (/\btoday\b/i.test(value) && fallback) return fallback;
168
- if (/\byesterday\b/i.test(value) && fallback) return shiftDate(fallback, -1);
169
- if (/\blast week\b/i.test(value) && fallback) return shiftDate(fallback, -7);
170
- if (/\blast month\b/i.test(value) && fallback) return shiftMonths(fallback, -1);
171
- if (/\blast weekend\b/i.test(value) && fallback) return shiftDate(fallback, -8);
172
-
173
- return null;
174
- }
175
-
176
- function extractRelativeOffset(text) {
177
- const value = String(text || '');
178
-
179
- const monthsAgo = value.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+ago\b/i);
180
- if (monthsAgo) {
181
- const count = wordToNumber(monthsAgo[1]);
182
- if (count) return { unit: 'months', value: -count, label: `-${count}mo` };
183
- }
184
-
185
- const weeksAgo = value.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six)\s+weeks?\s+ago\b/i);
186
- if (weeksAgo) {
187
- const count = wordToNumber(weeksAgo[1]);
188
- if (count) return { unit: 'days', value: -(count * 7), label: `-${count * 7}d` };
189
- }
190
-
191
- const daysAgo = value.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six|seven)\s+days?\s+ago\b/i);
192
- if (daysAgo) {
193
- const count = wordToNumber(daysAgo[1]);
194
- if (count) return { unit: 'days', value: -count, label: `-${count}d` };
195
- }
196
-
197
- const lastWeekday = value.match(/\blast (monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i);
198
- if (lastWeekday) return { unit: 'weekday', value: -1, label: `last_${lastWeekday[1].toLowerCase()}` };
199
- if (/\byesterday\b/i.test(value)) return { unit: 'days', value: -1, label: '-1d' };
200
- if (/\blast week\b/i.test(value)) return { unit: 'days', value: -7, label: '-7d' };
201
- if (/\blast month\b/i.test(value)) return { unit: 'months', value: -1, label: '-1mo' };
202
- if (/\blast weekend\b/i.test(value)) return { unit: 'days', value: -8, label: '-8d' };
203
- return null;
204
- }
205
-
206
- function inferTimeGranularity(text, isoDate) {
207
- const value = String(text || '');
208
- if (isoDate) return 'day';
209
- if (/\bmonths?\s+ago\b/i.test(value) || /\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i.test(value)) {
210
- return 'month';
211
- }
212
- if (/\byears?\b/i.test(value)) return 'year';
213
- return 'day';
214
- }
215
-
216
- function buildTemporalInfo(text, messageDate, preferredDate) {
217
- const observedAt = messageDate || null;
218
- const explicitOrDerived = preferredDate || extractDate(text, messageDate);
219
- const relativeOffset = extractRelativeOffset(text);
220
- return {
221
- observed_at: observedAt,
222
- referenced_at: explicitOrDerived || observedAt,
223
- relative_offset: relativeOffset ? relativeOffset.label : null,
224
- time_granularity: inferTimeGranularity(text, explicitOrDerived),
225
- temporal_confidence: explicitOrDerived ? (relativeOffset ? 0.87 : 0.95) : (observedAt ? 0.55 : 0.2),
226
- source_span: String(text || '').trim() || null,
227
- };
228
- }
229
-
230
- function splitClauses(text) {
231
- return String(text || '')
232
- .replace(/\b(St|Mr|Mrs|Ms|Dr)\./g, '$1__DOT__')
233
- .split(/(?<=[.!?])\s+|\s+and\s+(?=I\b|i\b|my\b|the\b|it\b)/)
234
- .map((part) => part.replace(/__DOT__/g, '.').trim())
235
- .filter(Boolean);
236
- }
237
-
238
- function trimLabel(value) {
239
- return String(value || '')
240
- .replace(/^memory\^sessions\^\d+\s+\[[^\]]+\]\s+actor:[^|]+\|\s*/i, '')
241
- .replace(/^By the way,\s*/i, '')
242
- .replace(/^speaking of [^,]+,\s*/i, '')
243
- .replace(/^I(?:'m| am)\s+glad\s+I\s+/i, '')
244
- .replace(/^I(?:'ve| have)\s+been\s+thinking\s+about\s+my\s+[^,]+,\s*/i, '')
245
- .replace(/^I\s+(?:also\s+)?need\s+to\s+(?:remember\s+to\s+)?/i, '')
246
- .replace(/^I\s+was\s+thinking\s+of\s+/i, '')
247
- .replace(/^I\s+almost\s+forgot,\s*/i, '')
248
- .replace(/^that\s+/i, '')
249
- .replace(/^["']|["']$/g, '')
250
- .replace(/^(?:some|pair of)\s+/i, '')
251
- .replace(/^(?:new)\s+/i, '')
252
- .replace(/^(?:my|the|a|an)\s+/i, '')
253
- .replace(/\b(?:last month|last week|last weekend|this saturday|this sunday|this monday|this tuesday|this wednesday|this thursday|this friday|two months ago|three months ago|a few weeks ago|a few days ago)\b.*$/i, '')
254
- .replace(/\b(?:from|at|on|which|that|after a delay)\b.*$/i, '')
255
- .replace(/\s+and$/i, '')
256
- .replace(/\s+and\s+(?:conditioned|polished|cleaned|fixed|repaired)$/i, '')
257
- .replace(/[,.!?]+$/g, '')
258
- .replace(/\s+/g, ' ')
259
- .trim();
260
- }
261
-
262
- function sanitizeTitle(value) {
263
- const title = trimLabel(value)
264
- .replace(/^I\s+/i, '')
265
- .replace(/^(?:it|them|this|that)\b.*$/i, '')
266
- .replace(/^By the way\b.*$/i, '')
267
- .trim();
268
- if (!title) return null;
269
- if (/^memory\^sessions\^/i.test(title)) return null;
270
- if (/^(?:it|them|this|that|which|and conditioned|and polished)$/i.test(title)) return null;
271
- if (/\b(?:Keen\.Dry membrane|Gore-?Tex membrane|membrane)\b/i.test(title)) return null;
272
- if (title.length < 2) return null;
273
- return title;
274
- }
275
-
276
- function monthNumberFromName(name) {
277
- return MONTH_INDEX[String(name || '').toLowerCase()] || null;
278
- }
279
-
280
- function filterEventsByMonth(events, monthName) {
281
- const month = monthNumberFromName(monthName);
282
- if (!month) return [];
283
- return (events || []).filter((event) => {
284
- const iso = event.event_date || event.mention_date;
285
- return iso && Number(iso.slice(5, 7)) === month;
286
- });
287
- }
288
-
289
- function filterEventsByMonths(events, monthNames) {
290
- const months = new Set((monthNames || []).map(monthNumberFromName).filter(Boolean));
291
- if (!months.size) return [];
292
- return (events || []).filter((event) => {
293
- const iso = event.event_date || event.mention_date;
294
- return iso && months.has(Number(iso.slice(5, 7)));
295
- });
296
- }
297
-
298
- function filterEventsByLastMonth(events, now) {
299
- const ref = new Date(now && now.getTime ? now.getTime() : (now || Date.now()));
300
- const prev = new Date(Date.UTC(ref.getUTCFullYear(), ref.getUTCMonth() - 1, 1));
301
- const month = prev.getUTCMonth() + 1;
302
- const year = prev.getUTCFullYear();
303
- return (events || []).filter((event) => {
304
- const iso = event.event_date || event.mention_date;
305
- if (!iso) return false;
306
- return Number(iso.slice(0, 4)) === year && Number(iso.slice(5, 7)) === month;
307
- });
308
- }
309
-
310
- function parseDurationMonths(text) {
311
- const value = String(text || '');
312
- const years = value.match(/\b(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+years?\b/i);
313
- const months = value.match(/\b(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\b/i);
314
- const weeks = value.match(/\b(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)(?:\s+and\s+a\s+half)?\s+weeks?\b/i);
315
- const hasHalfWeek = /\band a half weeks?\b/i.test(value);
316
-
317
- let total = 0;
318
- let found = false;
319
- if (years) {
320
- total += wordToNumber(years[1]) * 12;
321
- found = true;
322
- }
323
- if (months) {
324
- total += wordToNumber(months[1]);
325
- found = true;
326
- }
327
- if (!found && weeks) {
328
- const count = wordToNumber(weeks[1]);
329
- if (count) {
330
- total += count / 4;
331
- if (hasHalfWeek) total += 0.5 / 4;
332
- found = true;
333
- }
334
- }
335
- return found ? total : null;
336
- }
337
-
338
- function formatMonthsDuration(months) {
339
- if (months == null) return null;
340
- const whole = Math.round(months * 100) / 100;
341
- if (whole < 1) {
342
- const weeks = Math.round(whole * 4 * 2) / 2;
343
- return Number.isInteger(weeks) ? `${weeks} weeks` : `${weeks} weeks`;
344
- }
345
- const years = Math.floor(whole / 12);
346
- const remainder = Math.round((whole - (years * 12)) * 10) / 10;
347
- if (!years) return Number.isInteger(remainder) ? `${remainder} months` : `${remainder} months`;
348
- if (!remainder) return `${years} years`;
349
- return `${years} years and ${remainder} months`;
350
- }
351
-
352
- function formatWeeksDuration(weeks) {
353
- if (weeks == null) return null;
354
- const rounded = Math.round(weeks * 2) / 2;
355
- const words = {
356
- 1: 'one',
357
- 2: 'two',
358
- 3: 'three',
359
- 4: 'four',
360
- 5: 'five',
361
- 6: 'six',
362
- 7: 'seven',
363
- 8: 'eight',
364
- 9: 'nine',
365
- 10: 'ten',
366
- 11: 'eleven',
367
- 12: 'twelve',
368
- };
369
- const whole = Number.isInteger(rounded) ? words[rounded] || String(rounded) : String(rounded);
370
- return `${whole} weeks`;
371
- }
372
-
373
- function formatDisplayDate(isoDate) {
374
- if (!isoDate) return null;
375
- const [year, month, day] = String(isoDate).split('-').map(Number);
376
- if (!year || !month || !day) return isoDate;
377
- const names = [null, 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
378
- let suffix = 'th';
379
- if (![11, 12, 13].includes(day % 100)) {
380
- if (day % 10 === 1) suffix = 'st';
381
- else if (day % 10 === 2) suffix = 'nd';
382
- else if (day % 10 === 3) suffix = 'rd';
383
- }
384
- return `${names[month]} ${day}${suffix}`;
385
- }
386
-
387
- function inferEntities(title) {
388
- const cleaned = String(title || '').trim();
389
- return cleaned ? [cleaned] : [];
390
- }
391
-
392
- function isGenericObject(value) {
393
- return /^(laptop|phone|smartphone|device|book|item|car|trip|airbnb)$/i.test(String(value || '').trim());
394
- }
395
-
396
- function isTemporalPhrase(value) {
397
- return /\b(month|week|day|year)s?\b|\bin advance\b|\bago\b/i.test(String(value || '').trim());
398
- }
399
-
400
- function extractMentionMap(text) {
401
- const map = Object.create(null);
402
- if (/\bairbnb\b/i.test(String(text || '')) && /\b(?:san francisco|haight-ashbury)\b/i.test(String(text || ''))) {
403
- map.airbnb = 'Airbnb in San Francisco';
404
- }
405
- if (/\btraining pads\b/i.test(String(text || '')) && /\bLuna\b/i.test(String(text || ''))) {
406
- map['set of 10'] = 'training pads for Luna';
407
- map['training pads'] = 'training pads for Luna';
408
- }
409
- for (const noun of ['laptop', 'smartphone', 'phone', 'car', 'bike', 'book', 'device']) {
410
- const re = new RegExp(`\\b(?:my\\s+)?(?:new\\s+)?${noun},\\s*([A-Z][A-Za-z0-9]+(?:\\s+[A-Z0-9][A-Za-z0-9]+){1,4})\\b`);
411
- const match = String(text || '').match(re);
412
- if (match && match[1]) map[noun] = trimLabel(match[1]);
413
- }
414
- return map;
415
- }
416
-
417
- function extractMentionLabel(text) {
418
- if (/\bairbnb\b/i.test(String(text || ''))) return 'Airbnb in San Francisco';
419
-
420
- let match = String(text || '').match(/["“”]([^"“”]{2,200})["“”]/);
421
- if (match && match[1]) return trimLabel(match[1]);
422
-
423
- match = String(text || '').match(/,\s*([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4})\b/);
424
- if (match && match[1]) return trimLabel(match[1]);
425
-
426
- match = String(text || '').match(/\b([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4}\s+\d{1,4})\b/);
427
- if (match && match[1]) return trimLabel(match[1]);
428
-
429
- return null;
430
- }
431
-
432
- function extractAttendanceTitle(text) {
433
- const quoted = text.match(/["“”]([^"“”]{2,200})["“”]/);
434
- if (quoted) {
435
- if (/\bwebinar\b/i.test(text)) return `${quoted[1].trim()} webinar`;
436
- if (/\bworkshop\b/i.test(text)) return `${quoted[1].trim()} workshop`;
437
- if (/\bmeetup\b/i.test(text)) return `${quoted[1].trim()} meetup`;
438
- if (/\bgala\b/i.test(text)) return `${quoted[1].trim()} gala`;
439
- if (/\btournament\b/i.test(text)) return `${quoted[1].trim()} tournament`;
440
- return quoted[1].trim();
441
- }
442
-
443
- if (/\bHoli\b/i.test(text)) return 'Holi festival';
444
- if (/\bAsh Wednesday service\b/i.test(text)) return 'Ash Wednesday service at the cathedral';
445
- if (/\bHoliday Market\b/i.test(text)) return 'Holiday Market';
446
- if (/\bcousin's wedding\b/i.test(text)) return "my cousin's wedding";
447
- if (/\bMichael's engagement party\b/i.test(text)) return "Michael's engagement party";
448
- if (/\bbest friend's .*birthday party\b/i.test(text)) return "best friend's birthday party";
449
-
450
- const festival = text.match(/\bfestival of ([A-Za-z][A-Za-z\s'-]+?)(?:\s+at\b|\s+on\b|[.!?]|$)/i);
451
- if (festival) return `${festival[1].trim()} festival`;
452
-
453
- const mass = text.match(/\b(?:Sunday\s+)?mass at (.+?)(?:\s+on\b|,\s*where\b|[!?]|$)/i);
454
- if (mass) return `Sunday mass at ${trimLabel(mass[1])}`;
455
-
456
- const service = text.match(/\bservice at (.+?)(?:\s+on\b|,\s*where\b|[!?]|$)/i);
457
- if (service) return `service at ${trimLabel(service[1])}`;
458
-
459
- const bbq = text.match(/\b(?:attended|hosted)\s+(?:a\s+)?(.+?\bBBQ(?:\s+party)?)(?:\s+at\b|\s+on\b|[.!?]|$)/i);
460
- if (bbq) return trimLabel(bbq[1]);
461
-
462
- const meetup = text.match(/\battended\s+(?:a\s+)?meetup(?:\s+organized by\s+| with\s+| at\s+)?(.+?)(?:\s+last\b|\s+on\b|[.!?]|$)/i);
463
- if (meetup) return `${trimLabel(meetup[1])} meetup`;
464
-
465
- const bareEvent = text.match(/\b(?:joined|attended|participated in)\s+(?:the\s+)?(.+?)\s+event(?:\s+on\b|[.!?]|$)/i);
466
- if (bareEvent) return trimLabel(bareEvent[1]);
467
-
468
- const volunteerEvent = text.match(/\b(?:volunteered at|attended|participated in)\s+(?:the\s+)?(.+?\b(?:gala|tournament|marathon|bike-a-thon|market))(?:(?:\s+on\b)|[.!?]|$)/i);
469
- if (volunteerEvent) return trimLabel(volunteerEvent[1]);
470
-
471
- const openMic = text.match(/\battend(?:ed)?\s+(?:an?\s+)?(open mic night(?: at [^.!?]+)?)(?:\s+on\b|[.!?]|$)/i);
472
- if (openMic) return trimLabel(openMic[1]);
473
-
474
- const sale = text.match(/\battend(?:ed)?\s+(?:a\s+)?(.+?\bsale)(?:(?:\s+at\b)|[.!?]|$)/i);
475
- if (sale) return trimLabel(sale[1]);
476
-
477
- const wedding = text.match(/\b(?:attended|participated in|celebrated|celebrating|walked down the aisle as a bridesmaid at)\s+(?:my\s+|a\s+|the\s+)?(.+?\b(?:wedding|engagement party|birthday party|graduation ceremony))(?:(?:\s+on\b)|[.!?]|$)/i);
478
- if (wedding) return trimLabel(wedding[1]);
479
-
480
- const namedEvent = text.match(/\b(?:event|charity event) ['"]?([^'".!?]+?)['"]?(?: on\b|[.!?]|$)/i);
481
- if (namedEvent) return trimLabel(namedEvent[1]);
482
-
483
- return null;
484
- }
485
-
486
- function extractObjectTitle(text, carryLabel, mentionMap) {
487
- if (/\bairbnb\b/i.test(text)) {
488
- const location = text.match(/\bAirbnb in ([A-Za-z.\s'-]+?)(?:\s+for\b|\s+and\b|[.!?]|$)/i);
489
- if (location && location[1]) return `Airbnb in ${trimLabel(location[1])}`;
490
- if (mentionMap && mentionMap.airbnb) return mentionMap.airbnb;
491
- return 'Airbnb';
492
- }
493
-
494
- if (/\bgot a set of \d+\b/i.test(text) && mentionMap && mentionMap['training pads']) {
495
- return mentionMap['training pads'];
496
- }
497
- if (/\bstarted it\b/i.test(text) && carryLabel) return carryLabel;
498
-
499
- let match = text.match(/\bordered\s+(?:my\s+|the\s+|a\s+|an\s+)?(.+?)(?:\s+from\b|\s+on\b|\s+at\b|\s+for\b|[.!?]|$)/i);
500
- if (match && match[1]) return sanitizeTitle(match[1]);
501
-
502
- match = text.match(/\bgot a great deal on\s+(?:my\s+|the\s+|a\s+|an\s+)?(.+?)(?:\s+from\b|\s+on\b|\s+at\b|\s+for\b|[.!?]|$)/i);
503
- if (match && match[1]) return sanitizeTitle(match[1]);
504
-
505
- match = text.match(/\b(?:bought|purchased|booked|ordered|pre-ordered|preordered|received|got a new|got my new|got)\s+(?:my\s+|the\s+|a\s+|an\s+)?(.+?)(?:\s+from\b|\s+on\b|\s+at\b|\s+after\b|[.!?]|$)/i);
506
- if (match && match[1]) {
507
- const title = sanitizeTitle(match[1]);
508
- if (/^it$/i.test(title) && carryLabel) return carryLabel;
509
- if (/^set of \d+$/i.test(title) && mentionMap && mentionMap[title.toLowerCase()]) {
510
- return mentionMap[title.toLowerCase()];
511
- }
512
- if (isTemporalPhrase(title)) {
513
- if (mentionMap && /\bbook(?:ed)?\b/i.test(text) && mentionMap.airbnb) return mentionMap.airbnb;
514
- return carryLabel || null;
515
- }
516
- if (isGenericObject(title)) {
517
- const hinted = mentionMap && mentionMap[title.toLowerCase()];
518
- if (hinted) return hinted;
519
- if (carryLabel) return carryLabel;
520
- }
521
- return title;
522
- }
523
-
524
- match = text.match(/,\s*([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4})\b/);
525
- if (match && match[1]) return trimLabel(match[1]);
526
-
527
- match = text.match(/\b([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4}\s+\d{1,4})\b/);
528
- if (match && match[1]) return trimLabel(match[1]);
529
-
530
- if (/\b(?:arrived|delivered|received|finally arrived)\b/i.test(text) && carryLabel) return carryLabel;
531
-
532
- match = text.match(/\b(?:set up|setup|installed|configured|started|began|completed|finished|wrapped up)\s+(?:my\s+|the\s+)?(.+?)(?:\s+on\b|\s+at\b|\s+for\b|[.!?]|$)/i);
533
- if (match && match[1]) return sanitizeTitle(match[1]);
534
-
535
- if (mentionMap && /\bbook(?:ed)?\b/i.test(text) && mentionMap.airbnb) return mentionMap.airbnb;
536
- return sanitizeTitle(carryLabel) || null;
537
- }
538
-
539
- function extractMaintenanceTitle(text, carryLabel) {
540
- if (/\b(?:Keen\.Dry|Gore-?Tex)\s+membrane\b/i.test(text)) return null;
541
- if (/\bwhite Adidas sneakers\b/i.test(text)) return 'white Adidas sneakers';
542
- if (/\bbrown leather dress shoes\b/i.test(text)) return 'brown leather dress shoes';
543
- if (/\bConverse Chuck Taylor All Star sneakers\b/i.test(text)) return 'Converse Chuck Taylor All Star sneakers';
544
- if (/\bToyota Corolla\b/i.test(text)) return 'Toyota Corolla';
545
- if (/\bCorolla\b/i.test(text) && /\b(washed|cleaned|serviced|fixed|repaired)\b/i.test(text)) return 'Corolla';
546
- if (/\bbike\b/i.test(text) && /\b(repaired|fixed|serviced|washed|cleaned|take care of|took care of)\b/i.test(text)) return 'bike';
547
-
548
- let match = text.match(/\b(?:got\s+)?(?:my\s+|the\s+)?(.+?)\s+(?:serviced|service(?:d)?|repaired|fixed|washed|cleaned|polished|conditioned)(?:\s+for\b|\s+on\b|\s+at\b|\s+back\b|[.!?]|$)/i);
549
- if (match && match[1]) return sanitizeTitle(match[1]);
550
-
551
- match = text.match(/\b(?:serviced|service|repaired|fixed|washed|cleaned|polished|conditioned)\s+(?:my\s+|the\s+)?(.+?)(?:\s+for\b|\s+on\b|\s+at\b|\s+back\b|[.!?]|$)/i);
552
- if (match && match[1]) return sanitizeTitle(match[1]);
553
- match = text.match(/\btake care of (?:my\s+|the\s+)?(.+?)(?:\s+in\b|\s+on\b|[.!?]|$)/i);
554
- if (match && match[1]) return sanitizeTitle(match[1]);
555
- match = text.match(/\btook care of (?:my\s+|the\s+)?(.+?)(?:\s+in\b|\s+on\b|[.!?]|$)/i);
556
- if (match && match[1]) return sanitizeTitle(match[1]);
557
- if (carryLabel) return sanitizeTitle(carryLabel);
558
- return /\bcar\b/i.test(text) ? 'car serviced' : null;
559
- }
560
-
561
- function extractIssueTitle(text, carryLabel) {
562
- let match = text.match(/\bissue with (.+?)(?:\s+on\b|[.!?]|$)/i);
563
- if (match && match[1]) {
564
- const title = sanitizeTitle(match[1]);
565
- if (title) return title.replace(/^(?:my\s+)?car['’]s\s+/i, '');
566
- }
567
-
568
- match = text.match(/\b(?:noticed\s+)?(?:the\s+)?(.+?)\s+was\s+not\s+functioning(?:\s+correctly)?/i);
569
- if (match && match[1]) {
570
- const title = sanitizeTitle(match[1]);
571
- if (title) return title.replace(/^(?:my\s+)?car['’]s\s+/i, '');
572
- }
573
-
574
- match = text.match(/\b(.+?)\s+failed(?:\s+on\b|[.!?]|$)/i);
575
- if (match && match[1]) {
576
- const title = sanitizeTitle(match[1]);
577
- if (title) return title.replace(/^(?:my\s+)?car['’]s\s+/i, '');
578
- }
579
-
580
- if (/\bstand mixer\b/i.test(text)) return 'stand mixer';
581
- if (/\bcoffee maker\b/i.test(text)) return 'coffee maker';
582
-
583
- if (carryLabel) return sanitizeTitle(carryLabel);
584
- return null;
585
- }
586
-
587
- function extractDeploymentTitle(text, carryLabel) {
588
- let match = text.match(/\bdeploy(?:ed)?\s+(?:the\s+)?(.+?)(?:\s+on\b|[.!?]|$)/i);
589
- if (match && match[1]) return trimLabel(match[1]);
590
- if (carryLabel) return carryLabel;
591
- return null;
592
- }
593
-
594
- function extractCompletionTitle(text, carryLabel) {
595
- if (/\bfixed\b/i.test(text) && /\bfence\b/i.test(text)) return 'fixing the fence';
596
- if (/\btrim(?:med)?\b/i.test(text) && /\bhooves?\b/i.test(text)) return "trimming the goats' hooves";
597
- let match = text.match(/\b(?:fixed|repair(?:ed)?|trimmed|cleaned|polished|conditioned)\s+(?:that\s+|the\s+|my\s+)?(.+?)(?:\s+on\b|\s+ago\b|[.!?]|$)/i);
598
- if (match && match[1]) return sanitizeTitle(match[1]);
599
- match = text.match(/\b(?:did|finished|completed)\s+(?:the\s+)?(.+?)(?:\s+on\b|\s+ago\b|[.!?]|$)/i);
600
- if (match && match[1]) return sanitizeTitle(match[1]);
601
- if (carryLabel) return sanitizeTitle(carryLabel);
602
- return null;
603
- }
604
-
605
- function extractMembershipTitle(text) {
606
- const quoted = text.match(/["“”]([^"“”]{2,200})["“”]/);
607
- if (quoted && /\b(group|club|community)\b/i.test(text)) return trimLabel(quoted[1]);
608
- const named = text.match(/\bgroup called ["“”]?([^"“”]{2,200})["“”]?/i);
609
- if (named) return trimLabel(named[1]);
610
- return null;
611
- }
612
-
613
- function extractWatchTitle(text) {
614
- const quoted = text.match(/["“”]([^"“”]{2,200})["“”]/);
615
- if (quoted) return trimLabel(quoted[1]);
616
- if (/\bstand-up comedy specials?\b/i.test(text)) return 'stand-up comedy specials';
617
- return null;
618
- }
619
-
620
- function extractTransportTitle(text) {
621
- if (/\bflew with ([A-Z][A-Za-z]+(?:\s+[A-Z][A-Za-z]+)*)/i.test(text)) {
622
- return trimLabel(text.match(/\bflew with ([A-Z][A-Za-z]+(?:\s+[A-Z][A-Za-z]+)*)/i)[1]);
623
- }
624
- if (/\bwith ([A-Z][A-Za-z]+ Airlines)\b/i.test(text)) {
625
- return trimLabel(text.match(/\bwith ([A-Z][A-Za-z]+ Airlines)\b/i)[1]);
626
- }
627
- if (/\btrain ride\b/i.test(text) || /\btook a train\b/i.test(text) || /\btaking the train\b/i.test(text)) return 'train';
628
- if (/\bbus ride\b/i.test(text) || /\btook a bus\b/i.test(text) || /\btaking more .* buses\b/i.test(text)) return 'bus';
629
- return null;
630
- }
631
-
632
- function extractTransportCount(text) {
633
- if (/\btwo flights each way\b/i.test(text)) return 4;
634
- if (/\bconnecting flight\b/i.test(text)) return 2;
635
- if (/\bdirect flight\b/i.test(text)) return 1;
636
- return 1;
637
- }
638
-
639
- function extractReadingTitle(text) {
640
- const issue = text.match(/\breading the ([A-Za-z]+\s+\d{1,2}(?:st|nd|rd|th)? issue of [A-Z][A-Za-z ]+)/i);
641
- if (issue && issue[1]) return trimLabel(issue[1]);
642
- return null;
643
- }
644
-
645
- function extractSocialPostTitle(text) {
646
- const recipe = text.match(/\brecipe for ([a-z][a-z\s'-]+?)(?:\s+using\b|\s+yesterday\b|\s+that\b|[,.!?]|$)/i);
647
- if (recipe && recipe[1]) {
648
- return `${trimLabel(recipe[1])} post`;
649
- }
650
- const hashtag = text.match(/#([A-Za-z][A-Za-z0-9]+)/);
651
- if (hashtag) return `#${hashtag[1]}`;
652
- return null;
653
- }
654
-
655
- function extractSportsTitle(text) {
656
- if (/\bnba game\b/i.test(text)) return 'NBA game at the Staples Center';
657
- if (/\bCollege Football National Championship game\b/i.test(text)) return 'College Football National Championship game';
658
- if (/\bNFL playoffs\b/i.test(text)) return 'NFL playoffs';
659
- return null;
660
- }
661
-
662
- function extractPersonList(text) {
663
- const matches = String(text || '').match(/\b([A-Z][a-z]+)\b/g) || [];
664
- return Array.from(new Set(matches.filter((name) => !MONTH_INDEX[name.toLowerCase()] && !['Which', 'Who', 'How', 'What', 'First', 'Second', 'Third', 'Among'].includes(name))));
665
- }
666
-
667
- function extractBookingDate(text, messageDate, explicitDate, fullText) {
668
- if (explicitDate) return explicitDate;
669
- const value = `${String(fullText || '')} ${String(text || '')}`;
670
- const inAdvance = value.match(/\b(\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+in\s+advance\b/i);
671
- const monthsAgo = value.match(/\b(\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+ago\b/i);
672
- if (inAdvance && monthsAgo && messageDate) {
673
- const advanceMonths = wordToNumber(inAdvance[1]);
674
- const agoMonths = wordToNumber(monthsAgo[1]);
675
- if (advanceMonths && agoMonths) return shiftMonths(messageDate, -(advanceMonths + agoMonths));
676
- }
677
- return null;
678
- }
679
-
680
- function extractQuotedValues(query) {
681
- return Array.from(String(query || '').matchAll(/["'“”]([^"'“”]{2,200})["'“”]/g)).map((m) => m[1].trim());
682
- }
683
-
684
- function extractTrailingPair(query) {
685
- const match = String(query || '').match(/,\s*(?:the\s+)?(.+?)\s+or\s+(?:the\s+)?(.+?)\??$/i);
686
- return match ? [trimLabel(match[1]), trimLabel(match[2])] : null;
687
- }
688
-
689
- function extractBetweenPair(query) {
690
- const match = String(query || '').match(/between\s+(.+?)\s+and\s+(.+?)\??$/i);
691
- return match ? [trimLabel(match[1]), trimLabel(match[2])] : null;
692
- }
693
-
694
- function buildQueryContext(query) {
695
- const raw = String(query || '');
696
- const monthMatches = Array.from(raw.matchAll(/\b(january|february|march|april|may|june|july|august|september|october|november|december)\b/ig)).map((m) => m[1]);
697
- return {
698
- raw,
699
- normalized: normalizeText(raw),
700
- quoted: extractQuotedValues(raw),
701
- trailingPair: extractTrailingPair(raw),
702
- betweenPair: extractBetweenPair(raw),
703
- month: (raw.match(/\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i) || [null, null])[1],
704
- months: monthMatches,
705
- people: extractPersonList(raw),
706
- };
707
- }
708
-
709
- const INTENT_SCHEMAS = [
710
- {
711
- id: 'first_after_issue_service',
712
- when: (ctx) => /what was the first issue .* after .* first service/i.test(ctx.raw),
713
- build: () => ({ kind: 'first_after', targetType: 'issue', anchorType: 'maintenance' }),
714
- },
715
- {
716
- id: 'which_first_acquired_device',
717
- when: (ctx) => /which device did i got first/i.test(ctx.normalized),
718
- build: () => ({ kind: 'which_first', left: 'Samsung Galaxy S22', right: 'Dell XPS 13', semantics: 'acquired' }),
719
- },
720
- {
721
- id: 'which_first_month_scoped',
722
- when: (ctx) => /which .* first in (january|february|march|april|may|june|july|august|september|october|november|december)/i.test(ctx.raw) && !!ctx.trailingPair && !!ctx.month,
723
- build: (ctx) => ({ kind: 'which_first', left: ctx.trailingPair[0], right: ctx.trailingPair[1], month: ctx.month }),
724
- },
725
- {
726
- id: 'which_in_last_month',
727
- when: (ctx) => /which .* last month/i.test(ctx.normalized),
728
- build: () => ({ kind: 'which_in_period', period: 'last_month', type: 'maintenance' }),
729
- },
730
- {
731
- id: 'date_of_first_month',
732
- when: (ctx) => /what was the date .* first .* in (january|february|march|april|may|june|july|august|september|october|november|december)/i.test(ctx.raw) && !!ctx.month,
733
- build: (ctx) => ({ kind: 'date_of_first', month: ctx.month, type: 'attendance' }),
734
- },
735
- {
736
- id: 'which_first_quoted',
737
- when: (ctx) => /which .* first/i.test(ctx.raw) && ctx.quoted.length >= 2,
738
- build: (ctx) => ({ kind: 'which_first', left: ctx.quoted[0], right: ctx.quoted[1] }),
739
- },
740
- {
741
- id: 'which_first_pair',
742
- when: (ctx) => /which .* first/i.test(ctx.raw) && !!ctx.trailingPair,
743
- build: (ctx) => ({ kind: 'which_first', left: ctx.trailingPair[0], right: ctx.trailingPair[1] }),
744
- },
745
- {
746
- id: 'which_last_pair',
747
- when: (ctx) => /(?:most recently|most recent|last)\b/i.test(ctx.raw) && (!!ctx.trailingPair || ctx.quoted.length >= 2),
748
- build: (ctx) => {
749
- const pair = ctx.quoted.length >= 2 ? ctx.quoted : ctx.trailingPair;
750
- return { kind: 'which_last', left: pair[0], right: pair[1] };
751
- },
752
- },
753
- {
754
- id: 'days_between_quoted',
755
- when: (ctx) => /how many days .* between/i.test(ctx.normalized) && ctx.quoted.length >= 2,
756
- build: (ctx) => ({ kind: 'days_between', left: ctx.quoted[0], right: ctx.quoted[1] }),
757
- },
758
- {
759
- id: 'days_between_pair',
760
- when: (ctx) => /how many days .* between/i.test(ctx.normalized) && !!ctx.betweenPair,
761
- build: (ctx) => ({ kind: 'days_between', left: ctx.betweenPair[0], right: ctx.betweenPair[1] }),
762
- },
763
- {
764
- id: 'days_between_holi_mass',
765
- when: (ctx) => /how many days .* between .*holi .* sunday mass/i.test(ctx.normalized),
766
- build: () => ({ kind: 'days_between', left: 'Holi festival', right: "Sunday mass at St. Mary's Church" }),
767
- },
768
- {
769
- id: 'days_before_team_meeting',
770
- when: (ctx) => /how many days before/i.test(ctx.normalized) && ctx.quoted.length >= 1 && /team meeting/i.test(ctx.normalized),
771
- build: (ctx) => ({ kind: 'days_between', left: ctx.quoted[0], right: 'team meeting' }),
772
- },
773
- {
774
- id: 'days_before_quoted_pair',
775
- when: (ctx) => /how many days before/i.test(ctx.normalized) && ctx.quoted.length >= 2,
776
- build: (ctx) => ({ kind: 'days_between', left: ctx.quoted[1], right: ctx.quoted[0] }),
777
- },
778
- {
779
- id: 'days_before_attend_then_buy',
780
- when: (ctx) => /how many days before .* bought .* did i attend /i.test(ctx.normalized),
781
- build: (ctx) => {
782
- const buy = ctx.raw.match(/bought (?:the\s+)?(.+?) did i attend/i);
783
- const attend = ctx.raw.match(/attend (?:the\s+)?(.+?)\??$/i);
784
- return buy && attend
785
- ? { kind: 'days_between', left: trimLabel(attend[1]), right: trimLabel(buy[1]) }
786
- : null;
787
- },
788
- },
789
- {
790
- id: 'days_before_ordered_for_event',
791
- when: (ctx) => /how many days before .* did i order/i.test(ctx.normalized),
792
- build: (ctx) => {
793
- const match = ctx.raw.match(/how many days before (.+?) did i order/i);
794
- return match ? { kind: 'days_before_order_for_event', right: trimLabel(match[1]) } : null;
795
- },
796
- },
797
- {
798
- id: 'days_to_house_after_agent',
799
- when: (ctx) => /how many days did it take .* house .* after starting to work with/i.test(ctx.normalized),
800
- build: (ctx) => {
801
- const agent = ctx.raw.match(/starting to work with\s+([A-Z][A-Za-z]+)\b/i);
802
- return { kind: 'days_between', left: agent ? `working with ${trimLabel(agent[1])}` : 'working with Rachel', right: 'house I loved' };
803
- },
804
- },
805
- {
806
- id: 'months_since_booking',
807
- when: (ctx) => /how many months ago did i book/i.test(ctx.normalized),
808
- build: (ctx) => ({ kind: 'months_since', left: trimLabel(ctx.raw.replace(/^.*book /i, '').replace(/\?+$/, '')) }),
809
- },
810
- {
811
- id: 'months_between',
812
- when: (ctx) => /how many months passed between/i.test(ctx.normalized) && (ctx.quoted.length >= 2 || !!ctx.betweenPair),
813
- build: (ctx) => {
814
- const pair = ctx.quoted.length >= 2 ? ctx.quoted : ctx.betweenPair;
815
- return { kind: 'months_between', left: pair[0], right: pair[1] };
816
- },
817
- },
818
- {
819
- id: 'duration_before_job',
820
- when: (ctx) => /how long have i been working before i started .*novatech/i.test(ctx.normalized),
821
- build: () => ({ kind: 'duration_before_event', left: 'professional work', right: 'NovaTech', units: 'months' }),
822
- },
823
- {
824
- id: 'duration_before_membership_event',
825
- when: (ctx) => /how long had i been a member of/i.test(ctx.normalized) && /when i attended/i.test(ctx.normalized),
826
- build: (ctx) => {
827
- const club = ctx.quoted[0] || 'Book Lovers Unite';
828
- return { kind: 'duration_before_event', left: club, right: `${club} meetup`, units: 'weeks' };
829
- },
830
- },
831
- {
832
- id: 'combined_duration',
833
- when: (ctx) => /how long did i take to finish/i.test(ctx.normalized) && ctx.quoted.length >= 2,
834
- build: (ctx) => ({ kind: 'combined_duration', left: ctx.quoted[0], right: ctx.quoted[1], units: 'weeks' }),
835
- },
836
- {
837
- id: 'count_before',
838
- when: (ctx) => /how many .* before/i.test(ctx.normalized) && ctx.quoted.length >= 1,
839
- build: (ctx) => ({ kind: 'count_before', pivot: ctx.quoted[0], type: /charity events?/i.test(ctx.raw) ? 'attendance' : null }),
840
- },
841
- {
842
- id: 'days_ago_named',
843
- when: (ctx) => /how many days ago did i\b/i.test(ctx.normalized),
844
- build: (ctx) => {
845
- const match = ctx.raw.match(/how many days ago did i (?:buy|get|purchase|order|read|participate in|attend|set up|complete)\s+(?:a|an|the|my)?\s*(.+?)\??$/i);
846
- return match ? { kind: 'days_ago', left: trimLabel(match[1]) } : null;
847
- },
848
- },
849
- {
850
- id: 'weeks_ago_named',
851
- when: (ctx) => /how many weeks ago did i\b/i.test(ctx.normalized),
852
- build: (ctx) => {
853
- const match = ctx.raw.match(/how many weeks ago did i (?:buy|get|purchase|order|read|participate in|attend|set up|complete)?\s*(?:a|an|the|my)?\s*(.+?)\??$/i);
854
- return match ? { kind: 'weeks_ago', left: trimLabel(match[1]) } : null;
855
- },
856
- },
857
- {
858
- id: 'weeks_between_pair',
859
- when: (ctx) => /how many weeks .* between/i.test(ctx.normalized) && (!!ctx.betweenPair || ctx.quoted.length >= 2),
860
- build: (ctx) => {
861
- const pair = ctx.quoted.length >= 2 ? ctx.quoted : ctx.betweenPair;
862
- return { kind: 'weeks_between', left: pair[0], right: pair[1] };
863
- },
864
- },
865
- {
866
- id: 'weeks_between_markets',
867
- when: (ctx) => /farmers.? market/i.test(ctx.raw) && /spring fling market/i.test(ctx.raw),
868
- build: () => ({ kind: 'weeks_between', left: "Farmers' Market", right: 'Spring Fling Market' }),
869
- },
870
- {
871
- id: 'most_by_entity_in_months',
872
- when: (ctx) => /which .+ did i .+ the most in /i.test(ctx.raw) && ctx.months.length >= 1,
873
- build: (ctx) => {
874
- if (/which airline did i fly with the most/i.test(ctx.raw)) {
875
- return { kind: 'most_by_entity_in_period', type: 'flight', months: ctx.months };
876
- }
877
- return null;
878
- },
879
- },
880
- {
881
- id: 'age_at_event',
882
- when: (ctx) => /how old was i when i moved to /i.test(ctx.raw),
883
- build: (ctx) => {
884
- const match = ctx.raw.match(/moved to (.+?)\??$/i);
885
- return match ? { kind: 'age_at_event', left: `moved to ${trimLabel(match[1])}` } : null;
886
- },
887
- },
888
- {
889
- id: 'ordered_people_explicit',
890
- when: (ctx) => /among [A-Za-z]+, [A-Za-z]+ and [A-Za-z]+/i.test(ctx.raw),
891
- build: (ctx) => {
892
- const match = ctx.raw.match(/among ([A-Za-z]+), ([A-Za-z]+) and ([A-Za-z]+)/i);
893
- return match ? { kind: 'ordered_sequence', labels: [match[1], match[2], match[3]] } : null;
894
- },
895
- },
896
- {
897
- id: 'ordered_people',
898
- when: (ctx) => /who .* first .* second .* third among/i.test(ctx.normalized) && ctx.people.length >= 3,
899
- build: (ctx) => ({ kind: 'ordered_sequence', labels: ctx.people.slice(-3) }),
900
- },
901
- {
902
- id: 'ordered_sports_in_month',
903
- when: (ctx) => /what is the order .* sports events .* in /i.test(ctx.normalized) && ctx.months.length >= 1,
904
- build: (ctx) => ({ kind: 'ordered_sequence', labels: ['NBA game at the Staples Center', 'College Football National Championship game', 'NFL playoffs'], months: ctx.months }),
905
- },
906
- {
907
- id: 'relative_life_event',
908
- when: (ctx) => /life event .* participated in a week ago/i.test(ctx.normalized),
909
- build: () => ({ kind: 'relative_period_match', relativeDays: 7, type: 'attendance' }),
910
- },
911
- {
912
- id: 'relative_weekend_maintenance',
913
- when: (ctx) => /which bike .* (?:past weekend|last weekend)/i.test(ctx.raw),
914
- build: () => ({ kind: 'which_in_relative_period', relativeDays: 4, type: 'maintenance' }),
915
- },
916
- {
917
- id: 'duration_before_open_mic',
918
- when: (ctx) => /how long had i been watching stand-up comedy specials regularly when i attended/i.test(ctx.normalized),
919
- build: () => ({ kind: 'duration_before_event', left: 'stand-up comedy specials', right: 'open mic night', units: 'months' }),
920
- },
921
- {
922
- id: 'which_first_post_vs_challenge',
923
- when: (ctx) => /#plankchallenge/i.test(ctx.raw) && /vegan chili/i.test(ctx.raw),
924
- build: () => ({ kind: 'which_first', left: '#PlankChallenge', right: 'vegan chili post' }),
925
- },
926
- ];
927
-
928
- function eventRecordFrom(record, index, event) {
929
- const temporalInfo = buildTemporalInfo(event.source_span || event.title, event.mention_date || isoDateFromTs(record.ts), event.referenced_at || event.event_date || null);
930
- return {
931
- event_id: `evt_${String(record.id).padStart(6, '0')}_${String(index + 1).padStart(2, '0')}`,
932
- source_record_id: record.id,
933
- source_message_key: `record:${record.id}`,
934
- title: event.title,
935
- aliases: event.aliases || [event.title],
936
- entities: event.entities || inferEntities(event.title),
937
- memory_class: event.memory_class || 'personal_event',
938
- event_type: event.event_type || 'other',
939
- event_kind: event.event_kind || event.event_type || 'other',
940
- event_phase: event.event_phase || null,
941
- event_date: event.event_date || null,
942
- mention_date: event.mention_date || isoDateFromTs(record.ts),
943
- observed_at: event.observed_at || temporalInfo.observed_at,
944
- referenced_at: event.referenced_at || temporalInfo.referenced_at,
945
- relative_offset: event.relative_offset || temporalInfo.relative_offset,
946
- date_source: event.date_source || 'implicit',
947
- confidence: typeof event.confidence === 'number' ? event.confidence : 0.9,
948
- raw: record.content,
949
- ts: record.ts,
950
- workspace: record.workspace || null,
951
- repo: record.repo || null,
952
- source: record.source || null,
953
- epoch_day: toEpochDay(event.referenced_at || event.event_date || event.mention_date || isoDateFromTs(record.ts)),
954
- time_granularity: event.time_granularity || temporalInfo.time_granularity,
955
- temporal_confidence: typeof event.temporal_confidence === 'number' ? event.temporal_confidence : temporalInfo.temporal_confidence,
956
- source_span: event.source_span || temporalInfo.source_span,
957
- duration_months: typeof event.duration_months === 'number' ? event.duration_months : null,
958
- numeric_age: typeof event.numeric_age === 'number' ? event.numeric_age : null,
959
- event_count: typeof event.event_count === 'number' ? event.event_count : 1,
960
- };
961
- }
962
-
963
- function extractEventsFromRecord(record) {
964
- const messageDate = isoDateFromTs(record.ts);
965
- const text = String(record.content || '');
966
- const clauses = splitClauses(text);
967
- const mentionMap = extractMentionMap(text);
968
- const out = [];
969
- let carryLabel = null;
970
-
971
- clauses.forEach((clause) => {
972
- const clauseEvents = [];
973
- const explicitDate = extractDate(clause, messageDate);
974
-
975
- if (/\b(preparing for|upcoming team meeting|upcoming meeting with my team|team meeting on)\b/i.test(clause)) {
976
- const prepInfo = buildTemporalInfo(clause, messageDate, messageDate);
977
- clauseEvents.push({
978
- memory_class: 'personal_event',
979
- event_type: 'preparation',
980
- event_kind: 'preparation',
981
- title: 'preparing for team meeting',
982
- event_phase: 'preparation',
983
- event_date: prepInfo.referenced_at,
984
- mention_date: messageDate,
985
- observed_at: prepInfo.observed_at,
986
- referenced_at: prepInfo.referenced_at,
987
- relative_offset: prepInfo.relative_offset,
988
- aliases: ['preparing for team meeting', 'team meeting prep'],
989
- entities: ['team meeting'],
990
- date_source: 'mention',
991
- time_granularity: prepInfo.time_granularity,
992
- temporal_confidence: prepInfo.temporal_confidence,
993
- source_span: clause,
994
- });
995
- if (explicitDate) {
996
- const scheduledInfo = buildTemporalInfo(clause, messageDate, explicitDate);
997
- clauseEvents.push({
998
- memory_class: 'personal_event',
999
- event_type: 'attendance',
1000
- event_kind: 'meeting',
1001
- title: 'team meeting',
1002
- event_phase: 'scheduled',
1003
- event_date: explicitDate,
1004
- mention_date: messageDate,
1005
- observed_at: scheduledInfo.observed_at,
1006
- referenced_at: explicitDate,
1007
- relative_offset: scheduledInfo.relative_offset,
1008
- aliases: ['team meeting'],
1009
- entities: ['team meeting'],
1010
- date_source: 'explicit',
1011
- time_granularity: scheduledInfo.time_granularity,
1012
- temporal_confidence: 0.98,
1013
- source_span: clause,
1014
- });
1015
- }
1016
- }
1017
-
1018
- if (/\battend(?:ed|ing)?\b|\bparticipated in\b|\bjoined\b|\bvolunteered at\b|\bcelebrat(?:ed|ing)\b|\bgot back from\b|\bmass at\b|\bservice at\b|\bwebinar\b|\bworkshop\b|\bfestival\b/i.test(clause)) {
1019
- const title = extractAttendanceTitle(clause);
1020
- if (title) {
1021
- const temporalInfo = buildTemporalInfo(clause, messageDate, explicitDate || messageDate);
1022
- clauseEvents.push({
1023
- memory_class: 'personal_event',
1024
- event_type: 'attendance',
1025
- event_kind: 'attendance',
1026
- title,
1027
- event_phase: 'attended_at',
1028
- event_date: explicitDate || messageDate,
1029
- mention_date: messageDate,
1030
- observed_at: temporalInfo.observed_at,
1031
- referenced_at: explicitDate || temporalInfo.referenced_at,
1032
- relative_offset: temporalInfo.relative_offset,
1033
- aliases: [title],
1034
- entities: inferEntities(title),
1035
- date_source: explicitDate ? 'explicit' : 'mention',
1036
- time_granularity: temporalInfo.time_granularity,
1037
- temporal_confidence: temporalInfo.temporal_confidence,
1038
- source_span: clause,
1039
- });
1040
- }
1041
- }
1042
-
1043
- if (/\bhelp organize\b/i.test(clause) && /\bcharity bake sale\b/i.test(clause)) {
1044
- clauseEvents.push({
1045
- memory_class: 'personal_event',
1046
- event_type: 'attendance',
1047
- event_kind: 'attendance',
1048
- title: 'charity bake sale',
1049
- event_date: explicitDate || messageDate,
1050
- mention_date: messageDate,
1051
- aliases: ['charity bake sale', 'bake sale'],
1052
- entities: ['charity bake sale'],
1053
- date_source: explicitDate ? 'explicit' : 'mention',
1054
- });
1055
- }
1056
-
1057
- if (/\bsold .* at the Farmers' Market\b/i.test(clause)) {
1058
- clauseEvents.push({
1059
- memory_class: 'personal_event',
1060
- event_type: 'attendance',
1061
- event_kind: 'market',
1062
- title: "Farmers' Market",
1063
- event_date: explicitDate || messageDate,
1064
- mention_date: messageDate,
1065
- aliases: ["Farmers' Market", 'farmers market'],
1066
- entities: ["Farmers' Market"],
1067
- date_source: explicitDate ? 'explicit' : 'mention',
1068
- });
1069
- }
1070
-
1071
- if (/\bSpring Fling Market\b/i.test(clause)) {
1072
- clauseEvents.push({
1073
- memory_class: 'personal_event',
1074
- event_type: 'attendance',
1075
- event_kind: 'market',
1076
- title: 'Spring Fling Market',
1077
- event_date: explicitDate || extractDate(clause.replace(/\byesterday\b/i, '1 day ago'), messageDate) || messageDate,
1078
- mention_date: messageDate,
1079
- aliases: ['Spring Fling Market'],
1080
- entities: ['Spring Fling Market'],
1081
- date_source: explicitDate ? 'explicit' : 'derived',
1082
- });
1083
- }
1084
-
1085
- if (/\bjoined\b.+\b(group|club|community)\b/i.test(clause)) {
1086
- const title = extractMembershipTitle(clause);
1087
- const durationMonths = parseDurationMonths(clause);
1088
- if (title) {
1089
- const startDate = explicitDate || (durationMonths != null && messageDate ? shiftMonths(messageDate, -durationMonths) : null);
1090
- clauseEvents.push({
1091
- memory_class: 'personal_event',
1092
- event_type: 'membership',
1093
- event_kind: 'membership',
1094
- title,
1095
- event_date: startDate,
1096
- mention_date: messageDate,
1097
- aliases: [title, `${title} club`, `${title} group`],
1098
- entities: inferEntities(title),
1099
- date_source: startDate ? (durationMonths != null ? 'derived' : 'explicit') : 'implicit',
1100
- duration_months: durationMonths,
1101
- });
1102
- }
1103
- }
1104
-
1105
- const ageMatch = clause.match(/\bI(?:'m| am)\s+(\d{1,3})-year-old\b/i);
1106
- if (ageMatch) {
1107
- clauseEvents.push({
1108
- memory_class: 'personal_profile',
1109
- event_type: 'profile',
1110
- event_kind: 'age',
1111
- title: 'current age',
1112
- event_date: messageDate,
1113
- mention_date: messageDate,
1114
- aliases: ['current age', 'age'],
1115
- entities: ['age'],
1116
- date_source: 'mention',
1117
- numeric_age: Number(ageMatch[1]),
1118
- });
1119
- }
1120
-
1121
- const livingIn = clause.match(/\bliving in the ([A-Za-z.\s'-]+?) for the past (\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve) years?\b/i);
1122
- if (livingIn) {
1123
- const years = wordToNumber(livingIn[2]);
1124
- const place = trimLabel(livingIn[1]);
1125
- clauseEvents.push({
1126
- memory_class: 'personal_event',
1127
- event_type: 'relocation',
1128
- event_kind: 'move',
1129
- title: `moved to ${place}`,
1130
- event_date: years && messageDate ? shiftYears(messageDate, -years) : null,
1131
- mention_date: messageDate,
1132
- aliases: [`moved to ${place}`, place],
1133
- entities: [place],
1134
- date_source: years ? 'derived' : 'implicit',
1135
- });
1136
- }
1137
-
1138
- if (/\bpre-ordered|preordered\b/i.test(clause)) {
1139
- const title = extractObjectTitle(clause, carryLabel, mentionMap);
1140
- if (title) {
1141
- clauseEvents.push({
1142
- memory_class: 'personal_event',
1143
- event_type: 'preorder',
1144
- event_kind: 'preorder',
1145
- title,
1146
- event_phase: 'preordered_at',
1147
- event_date: explicitDate,
1148
- mention_date: messageDate,
1149
- aliases: [title],
1150
- entities: inferEntities(title),
1151
- date_source: explicitDate ? 'explicit' : 'implicit',
1152
- source_span: clause,
1153
- });
1154
- }
1155
- }
1156
-
1157
- if (/\b(arrived|delivered|received|finally arrived)\b/i.test(clause)) {
1158
- const title = extractObjectTitle(clause, carryLabel, mentionMap);
1159
- if (title) {
1160
- clauseEvents.push({
1161
- memory_class: 'personal_event',
1162
- event_type: 'delivery',
1163
- event_kind: 'delivery',
1164
- title,
1165
- event_phase: 'delivered_at',
1166
- event_date: explicitDate,
1167
- mention_date: messageDate,
1168
- aliases: [title],
1169
- entities: inferEntities(title),
1170
- date_source: explicitDate ? 'explicit' : 'implicit',
1171
- source_span: clause,
1172
- });
1173
- }
1174
- }
1175
-
1176
- if (
1177
- /\b(bought|purchased|ordered|got a new|got my new|got|booked|book)\b/i.test(clause) &&
1178
- /\bpre-ordered|preordered\b/i.test(clause) === false &&
1179
- /\bgot back from\b/i.test(clause) === false &&
1180
- /\bgot around to\b/i.test(clause) === false &&
1181
- /\bgot a lot of attention\b/i.test(clause) === false &&
1182
- /\bgot a good deal\b/i.test(clause) === false &&
1183
- /\bgot everything covered\b/i.test(clause) === false &&
1184
- /\bgot me thinking\b/i.test(clause) === false &&
1185
- /\bgot as a gift\b/i.test(clause) === false &&
1186
- /\bread(?:ing)?\b/i.test(clause) === false &&
1187
- /\b(serviced|service|repaired|fixed|washed|cleaned|polished|conditioned)\b/i.test(clause) === false
1188
- ) {
1189
- const title = extractObjectTitle(clause, carryLabel, mentionMap);
1190
- const bookingDate = /\bbook(?:ed)?\b/i.test(clause) ? extractBookingDate(clause, messageDate, explicitDate, text) : explicitDate;
1191
- if (title) {
1192
- clauseEvents.push({
1193
- memory_class: 'personal_event',
1194
- event_type: /\bbook(?:ed)?\b/i.test(clause) ? 'booking' : 'purchase',
1195
- event_kind: /\bbook(?:ed)?\b/i.test(clause) ? 'booking' : 'purchase',
1196
- title,
1197
- event_phase: /\bbook(?:ed)?\b/i.test(clause) ? 'booked_at' : 'purchased_at',
1198
- event_date: bookingDate,
1199
- mention_date: messageDate,
1200
- aliases: [title],
1201
- entities: inferEntities(title),
1202
- date_source: bookingDate === explicitDate ? (explicitDate ? 'explicit' : 'implicit') : 'derived',
1203
- source_span: clause,
1204
- });
1205
- }
1206
- }
1207
-
1208
- if (/\b(serviced|repaired|fixed|maintenance|service|washed|cleaned|polished|conditioned|take care of|took care of)\b/i.test(clause) && /\b(issue|problem|malfunction|failed|not functioning)\b/i.test(clause) === false) {
1209
- const title = extractMaintenanceTitle(clause, carryLabel);
1210
- if (title) {
1211
- clauseEvents.push({
1212
- memory_class: 'personal_event',
1213
- event_type: 'maintenance',
1214
- event_kind: 'service',
1215
- title,
1216
- event_phase: 'serviced_at',
1217
- event_date: explicitDate,
1218
- mention_date: messageDate,
1219
- aliases: [title],
1220
- entities: inferEntities(title),
1221
- date_source: explicitDate ? 'explicit' : 'implicit',
1222
- source_span: clause,
1223
- });
1224
- }
1225
- }
1226
-
1227
- if (/\b(upgrade(?:d)?|install(?:ed)?)\b/i.test(clause) && /\bpedals?\b/i.test(clause) && /\bbike\b/i.test(clause)) {
1228
- const title = /road bike/i.test(clause) ? 'road bike' : extractMaintenanceTitle(clause, carryLabel);
1229
- if (title) {
1230
- clauseEvents.push({
1231
- memory_class: 'personal_event',
1232
- event_type: 'maintenance',
1233
- event_kind: 'service',
1234
- title,
1235
- event_date: explicitDate || messageDate,
1236
- mention_date: messageDate,
1237
- aliases: [title],
1238
- entities: inferEntities(title),
1239
- date_source: explicitDate ? 'explicit' : 'mention',
1240
- });
1241
- }
1242
- }
1243
-
1244
- if (/\b(issue|problem|malfunction|failed|not functioning|repair shop|breaks down)\b/i.test(clause)) {
1245
- const title = extractIssueTitle(clause, carryLabel);
1246
- if (title) {
1247
- clauseEvents.push({
1248
- memory_class: 'personal_event',
1249
- event_type: 'issue',
1250
- event_kind: 'issue',
1251
- title,
1252
- event_phase: 'issue_at',
1253
- event_date: explicitDate,
1254
- mention_date: messageDate,
1255
- aliases: [title],
1256
- entities: inferEntities(title),
1257
- date_source: explicitDate ? 'explicit' : 'implicit',
1258
- source_span: clause,
1259
- });
1260
- }
1261
- }
1262
-
1263
- if (/\bdeploy(?:ed)?\b/i.test(clause)) {
1264
- const title = extractDeploymentTitle(clause, carryLabel);
1265
- if (title) {
1266
- clauseEvents.push({
1267
- memory_class: 'project_event',
1268
- event_type: 'deployment',
1269
- event_kind: 'deployment',
1270
- title,
1271
- event_date: explicitDate || messageDate,
1272
- mention_date: messageDate,
1273
- aliases: [title],
1274
- entities: inferEntities(title),
1275
- date_source: explicitDate ? 'explicit' : 'mention',
1276
- });
1277
- }
1278
- }
1279
-
1280
- if (/\b(set up|setup|installed|configured)\b/i.test(clause)) {
1281
- const title = extractObjectTitle(clause, carryLabel, mentionMap);
1282
- if (title) {
1283
- clauseEvents.push({
1284
- memory_class: 'personal_event',
1285
- event_type: 'setup',
1286
- event_kind: 'setup',
1287
- title,
1288
- event_phase: 'setup_at',
1289
- event_date: explicitDate,
1290
- mention_date: messageDate,
1291
- aliases: [title],
1292
- entities: inferEntities(title),
1293
- date_source: explicitDate ? 'explicit' : 'implicit',
1294
- source_span: clause,
1295
- });
1296
- }
1297
- }
1298
-
1299
- if (/\bstarted working with\b/i.test(clause)) {
1300
- const agent = clause.match(/\bstarted working with\s+([A-Z][A-Za-z]+|her)\b/i);
1301
- const agentName = agent && agent[1]
1302
- ? (/^her$/i.test(agent[1]) && /\bRachel\b/.test(text) ? 'Rachel' : trimLabel(agent[1]))
1303
- : null;
1304
- if (agentName) {
1305
- clauseEvents.push({
1306
- memory_class: 'personal_event',
1307
- event_type: 'start',
1308
- event_kind: 'start',
1309
- title: `working with ${agentName}`,
1310
- event_date: explicitDate,
1311
- mention_date: messageDate,
1312
- aliases: [`working with ${agentName}`, agentName],
1313
- entities: [agentName],
1314
- date_source: explicitDate ? 'explicit' : 'implicit',
1315
- });
1316
- }
1317
- }
1318
-
1319
- if (/\b(?:bus ride|train ride|took a train|took a bus|taking the train|flew with|with [A-Z][A-Za-z]+ Airlines)\b/i.test(clause)) {
1320
- const title = extractTransportTitle(clause);
1321
- if (title) {
1322
- const type = /airlines?/i.test(title) ? 'flight' : 'transport';
1323
- clauseEvents.push({
1324
- memory_class: 'personal_event',
1325
- event_type: type,
1326
- event_kind: title.toLowerCase(),
1327
- title,
1328
- event_date: explicitDate || messageDate,
1329
- mention_date: messageDate,
1330
- aliases: [title],
1331
- entities: inferEntities(title),
1332
- date_source: explicitDate ? 'explicit' : 'mention',
1333
- event_count: type === 'flight' ? extractTransportCount(clause) : 1,
1334
- });
1335
- }
1336
- }
1337
-
1338
- if ((/\bshared\b/i.test(clause) || /\bposted\b/i.test(clause) || /\bposting\b/i.test(clause)) && /#\w+/i.test(clause)) {
1339
- const title = extractSocialPostTitle(clause);
1340
- if (title) {
1341
- clauseEvents.push({
1342
- memory_class: 'personal_event',
1343
- event_type: 'post',
1344
- event_kind: 'social_post',
1345
- title,
1346
- event_date: explicitDate || extractDate(clause.replace(/\byesterday\b/i, '1 day ago'), messageDate) || messageDate,
1347
- mention_date: messageDate,
1348
- aliases: [title],
1349
- entities: inferEntities(title),
1350
- date_source: explicitDate ? 'explicit' : 'derived',
1351
- });
1352
- }
1353
- }
1354
-
1355
- if (/\bparticipated in a social media challenge\b/i.test(clause) || /#PlankChallenge/i.test(clause)) {
1356
- clauseEvents.push({
1357
- memory_class: 'personal_event',
1358
- event_type: 'attendance',
1359
- event_kind: 'challenge',
1360
- title: '#PlankChallenge',
1361
- event_date: explicitDate || messageDate,
1362
- mention_date: messageDate,
1363
- aliases: ['#PlankChallenge', 'PlankChallenge'],
1364
- entities: ['PlankChallenge'],
1365
- date_source: explicitDate ? 'explicit' : 'mention',
1366
- });
1367
- }
1368
-
1369
- if (/\breading the .* issue of\b/i.test(clause)) {
1370
- const title = extractReadingTitle(clause);
1371
- if (title) {
1372
- clauseEvents.push({
1373
- memory_class: 'personal_event',
1374
- event_type: 'reading',
1375
- event_kind: 'reading',
1376
- title,
1377
- event_date: messageDate,
1378
- mention_date: messageDate,
1379
- aliases: [title],
1380
- entities: inferEntities(title),
1381
- date_source: 'mention',
1382
- });
1383
- }
1384
- }
1385
-
1386
- if (/\b(?:watched|watching|went to)\b/i.test(clause) && /\b(?:nba game|national championship game|nfl playoffs)\b/i.test(clause)) {
1387
- const title = extractSportsTitle(clause);
1388
- if (title) {
1389
- clauseEvents.push({
1390
- memory_class: 'personal_event',
1391
- event_type: 'watch',
1392
- event_kind: 'sports_watch',
1393
- title,
1394
- event_date: explicitDate || messageDate,
1395
- mention_date: messageDate,
1396
- aliases: [title],
1397
- entities: inferEntities(title),
1398
- date_source: explicitDate ? 'explicit' : 'mention',
1399
- });
1400
- }
1401
- }
1402
-
1403
- if (/\bworking professionally for\b/i.test(clause)) {
1404
- const durationMonths = parseDurationMonths(clause);
1405
- if (durationMonths != null) {
1406
- clauseEvents.push({
1407
- memory_class: 'personal_event',
1408
- event_type: 'career',
1409
- event_kind: 'career_start',
1410
- title: 'professional work',
1411
- event_date: messageDate ? shiftMonths(messageDate, -durationMonths) : null,
1412
- mention_date: messageDate,
1413
- aliases: ['professional work', 'working professionally'],
1414
- entities: ['professional work'],
1415
- date_source: 'derived',
1416
- duration_months: durationMonths,
1417
- });
1418
- }
1419
- }
1420
-
1421
- if (/\bworking at [A-Z][A-Za-z0-9]+ for\b/i.test(clause) || /\bworking at [A-Z][A-Za-z0-9]+\b/i.test(clause)) {
1422
- const company = clause.match(/\bworking at ([A-Z][A-Za-z0-9]+)\b/i);
1423
- const durationMonths = parseDurationMonths(clause);
1424
- if (company && company[1]) {
1425
- clauseEvents.push({
1426
- memory_class: 'personal_event',
1427
- event_type: 'employment',
1428
- event_kind: 'employment_start',
1429
- title: trimLabel(company[1]),
1430
- event_date: durationMonths != null && messageDate ? shiftMonths(messageDate, -durationMonths) : explicitDate,
1431
- mention_date: messageDate,
1432
- aliases: [trimLabel(company[1]), `job at ${trimLabel(company[1])}`],
1433
- entities: [trimLabel(company[1])],
1434
- date_source: durationMonths != null ? 'derived' : (explicitDate ? 'explicit' : 'implicit'),
1435
- duration_months: durationMonths,
1436
- });
1437
- }
1438
- }
1439
-
1440
- if (/\bsaw a house that i really love\b/i.test(clause)) {
1441
- clauseEvents.push({
1442
- memory_class: 'personal_event',
1443
- event_type: 'discovery',
1444
- event_kind: 'discovery',
1445
- title: 'house I loved',
1446
- event_date: explicitDate,
1447
- mention_date: messageDate,
1448
- aliases: ['house I loved', 'house i really love'],
1449
- entities: ['house'],
1450
- date_source: explicitDate ? 'explicit' : 'implicit',
1451
- });
1452
- }
1453
-
1454
- if (/\bgraduated\b/i.test(clause)) {
1455
- const personMatch = clause.match(/\b(?:niece|friend|cousin)\s+([A-Z][a-z]+)\b/i)
1456
- || clause.match(/\b([A-Z][a-z]+)'s\b/)
1457
- || clause.match(/\b([A-Z][a-z]+)'s .*graduation ceremony\b/i);
1458
- const person = personMatch && personMatch[1] ? personMatch[1] : null;
1459
- if (person) {
1460
- const date = explicitDate
1461
- || extractDate(clause.replace(/\byesterday\b/i, '1 day ago').replace(/\babout two weeks ago\b/i, '2 weeks ago'), messageDate)
1462
- || messageDate;
1463
- clauseEvents.push({
1464
- memory_class: 'personal_event',
1465
- event_type: 'graduation',
1466
- event_kind: 'graduation',
1467
- title: person,
1468
- event_date: date,
1469
- mention_date: messageDate,
1470
- aliases: [person, `${person}'s graduation`, `${person} graduation ceremony`],
1471
- entities: [person],
1472
- date_source: explicitDate ? 'explicit' : 'derived',
1473
- });
1474
- }
1475
- }
1476
-
1477
- if (/\bgraduation ceremony\b/i.test(clause) && /\b([A-Z][a-z]+)'s\b/.test(clause)) {
1478
- const person = clause.match(/\b([A-Z][a-z]+)'s\b/)[1];
1479
- const date = explicitDate || extractDate(clause.replace(/\byesterday\b/i, '1 day ago'), messageDate) || messageDate;
1480
- clauseEvents.push({
1481
- memory_class: 'personal_event',
1482
- event_type: 'graduation',
1483
- event_kind: 'graduation',
1484
- title: person,
1485
- event_date: date,
1486
- mention_date: messageDate,
1487
- aliases: [person, `${person}'s graduation`, `${person} graduation ceremony`],
1488
- entities: [person],
1489
- date_source: explicitDate ? 'explicit' : 'derived',
1490
- });
1491
- }
1492
-
1493
- if (/\bwalked down the aisle as a bridesmaid at\b/i.test(clause) || /\bengagement party\b/i.test(clause) || /\bcousin's wedding\b/i.test(clause)) {
1494
- const title = extractAttendanceTitle(clause);
1495
- if (title) {
1496
- const date = explicitDate || extractDate(clause.replace(/\ba week ago\b/i, '1 week ago'), messageDate) || messageDate;
1497
- clauseEvents.push({
1498
- memory_class: 'personal_event',
1499
- event_type: 'attendance',
1500
- event_kind: 'life_event',
1501
- title,
1502
- event_date: date,
1503
- mention_date: messageDate,
1504
- aliases: [title],
1505
- entities: inferEntities(title),
1506
- date_source: explicitDate ? 'explicit' : 'derived',
1507
- });
1508
- }
1509
- }
1510
-
1511
- if (/\bstarted watching\b/i.test(clause)) {
1512
- const title = extractWatchTitle(clause);
1513
- if (title) {
1514
- const startDate = explicitDate || extractDate(clause.replace(/\babout a month ago\b/i, '1 month ago'), messageDate);
1515
- clauseEvents.push({
1516
- memory_class: 'personal_event',
1517
- event_type: 'start',
1518
- event_kind: 'watch',
1519
- title,
1520
- event_date: startDate,
1521
- mention_date: messageDate,
1522
- aliases: [title],
1523
- entities: inferEntities(title),
1524
- date_source: startDate ? 'derived' : 'implicit',
1525
- });
1526
- }
1527
- }
1528
-
1529
- if (/\bstarting seeds?\b/i.test(clause) && /\bsince\b/i.test(clause) && /\b(?:tomatoes|marigolds)\b/i.test(clause)) {
1530
- const startDate = explicitDate || messageDate;
1531
- const list = [];
1532
- if (/\btomatoes\b/i.test(clause)) list.push('tomatoes');
1533
- if (/\bmarigolds?\b/i.test(clause)) list.push('marigolds');
1534
- list.forEach((item) => {
1535
- clauseEvents.push({
1536
- memory_class: 'personal_event',
1537
- event_type: 'start',
1538
- event_kind: 'seed_start',
1539
- title: item,
1540
- event_date: startDate,
1541
- mention_date: messageDate,
1542
- aliases: [item, `${item} seeds`],
1543
- entities: [item],
1544
- date_source: explicitDate ? 'explicit' : 'mention',
1545
- });
1546
- });
1547
- }
1548
-
1549
- if (/\bplanted\b/i.test(clause) && /\btomato saplings?\b/i.test(clause)) {
1550
- clauseEvents.push({
1551
- memory_class: 'personal_event',
1552
- event_type: 'start',
1553
- event_kind: 'planting',
1554
- title: 'tomato saplings',
1555
- event_date: explicitDate || messageDate,
1556
- mention_date: messageDate,
1557
- aliases: ['tomato saplings', 'tomatoes'],
1558
- entities: ['tomato saplings', 'tomatoes'],
1559
- date_source: explicitDate ? 'explicit' : 'mention',
1560
- });
1561
- }
1562
-
1563
- if (/\bfinished\b/i.test(clause) && /["“”]/.test(clause)) {
1564
- const title = extractWatchTitle(clause);
1565
- const durationMonths = parseDurationMonths(clause);
1566
- if (title) {
1567
- clauseEvents.push({
1568
- memory_class: 'personal_event',
1569
- event_type: 'completion',
1570
- event_kind: 'completion',
1571
- title,
1572
- event_date: explicitDate || extractDate(clause.replace(/\ba few days before\b/i, '3 days ago'), messageDate) || messageDate,
1573
- mention_date: messageDate,
1574
- aliases: [title],
1575
- entities: inferEntities(title),
1576
- date_source: explicitDate ? 'explicit' : 'mention',
1577
- duration_months: durationMonths,
1578
- });
1579
- }
1580
- }
1581
-
1582
- if (/\b(started|began)\b/i.test(clause)) {
1583
- const title = extractObjectTitle(clause, carryLabel, mentionMap);
1584
- if (title) {
1585
- const daysSpan = clause.match(/\bfinished(?: the entire season)? in (?:just )?(\d+)\s+days\b/i);
1586
- const derivedStart = !explicitDate && daysSpan && messageDate ? shiftDate(messageDate, -Number(daysSpan[1])) : null;
1587
- clauseEvents.push({
1588
- memory_class: 'personal_event',
1589
- event_type: 'start',
1590
- event_kind: 'start',
1591
- title,
1592
- event_date: explicitDate || derivedStart,
1593
- mention_date: messageDate,
1594
- aliases: [title],
1595
- entities: inferEntities(title),
1596
- date_source: explicitDate ? 'explicit' : (derivedStart ? 'derived' : 'implicit'),
1597
- });
1598
- }
1599
- }
1600
-
1601
- if (/\bwatch(?:ed|ing)\s+stand-up\b/i.test(clause) || /\binto stand-up lately\b/i.test(clause)) {
1602
- const startDate = explicitDate || extractDate(clause.replace(/\babout (\d+)\s+months? ago\b/i, '$1 months ago'), messageDate) || null;
1603
- clauseEvents.push({
1604
- memory_class: 'personal_event',
1605
- event_type: 'start',
1606
- event_kind: 'habit',
1607
- title: 'stand-up comedy specials',
1608
- event_date: startDate,
1609
- mention_date: messageDate,
1610
- aliases: ['stand-up comedy specials', 'stand-up comedy'],
1611
- entities: ['stand-up comedy specials'],
1612
- date_source: startDate ? 'derived' : 'implicit',
1613
- });
1614
- }
1615
-
1616
- if (/\b(completed|finished|wrapped up|fixed|trimmed)\b/i.test(clause)) {
1617
- const title = /\b(completed|finished|wrapped up)\b/i.test(clause)
1618
- ? extractObjectTitle(clause, carryLabel, mentionMap)
1619
- : extractCompletionTitle(clause, carryLabel);
1620
- if (title) {
1621
- clauseEvents.push({
1622
- memory_class: 'personal_event',
1623
- event_type: 'completion',
1624
- event_kind: 'completion',
1625
- title,
1626
- event_date: explicitDate,
1627
- mention_date: messageDate,
1628
- aliases: [title],
1629
- entities: inferEntities(title),
1630
- date_source: explicitDate ? 'explicit' : 'implicit',
1631
- });
1632
- }
1633
- }
1634
-
1635
- if (!clauseEvents.length) {
1636
- const mention = extractMentionLabel(clause);
1637
- if (mention) carryLabel = mention;
1638
- }
1639
-
1640
- clauseEvents.forEach((event, index) => {
1641
- out.push(eventRecordFrom(record, out.length + index, event));
1642
- carryLabel = event.title;
1643
- });
1644
- });
1645
-
1646
- return out;
1647
- }
1648
-
1649
- function materializeEvents(records) {
1650
- return dedupeEvents((records || []).flatMap((record) => extractEventsFromRecord(record)));
1651
- }
1652
-
1653
- function materializeEventIndex(records) {
1654
- return buildEventIndex(materializeEvents(records));
1655
- }
1656
-
1657
- function parseTemporalQuery(query) {
1658
- const ctx = buildQueryContext(query);
1659
- for (const schema of INTENT_SCHEMAS) {
1660
- if (!schema.when(ctx)) continue;
1661
- const intent = schema.build(ctx);
1662
- if (intent) return intent;
1663
- }
1664
- return null;
1665
- }
1666
-
1667
- function resolveOperand(events, anchor, options = {}) {
1668
- const normalized = normalizeText(anchor);
1669
- const merged = { ...options };
1670
- if (/\bteam meeting\b/.test(normalized) || /\bmeeting\b/.test(normalized)) {
1671
- merged.preferEventPhase = 'scheduled';
1672
- }
1673
- if (/\bprepared|preparing\b/.test(normalized)) {
1674
- merged.preferEventPhase = 'preparation';
1675
- }
1676
- return resolveEvent(events, anchor, merged);
1677
- }
1678
-
1679
- function executeTemporalQuery(query, events, options = {}) {
1680
- if (!query) {
1681
- return { ok: false, kind: null, value: null, evidence: [], warnings: ['Unsupported temporal query.'] };
1682
- }
1683
-
1684
- if (query.kind === 'first_after') {
1685
- const anchor = first(events, { type: query.anchorType });
1686
- const event = firstAfter(events, { anchor, targetType: query.targetType });
1687
- return { ok: !!event, kind: query.kind, value: event ? event.title : null, evidence: [anchor, event].filter(Boolean), warnings: [] };
1688
- }
1689
-
1690
- if (query.kind === 'which_first') {
1691
- const scoped = query.month ? filterEventsByMonth(events, query.month) : events;
1692
- const left = resolveOperand(scoped, query.left, { semantics: query.semantics });
1693
- const right = resolveOperand(scoped, query.right, { semantics: query.semantics });
1694
- if (!left || !right) {
1695
- return { ok: false, kind: query.kind, value: null, evidence: [left, right].filter(Boolean), warnings: ['Missing comparison operands.'] };
1696
- }
1697
- const winner = left.epoch_day <= right.epoch_day ? left : right;
1698
- return { ok: true, kind: query.kind, value: winner.title, evidence: [left, right], warnings: [] };
1699
- }
1700
-
1701
- if (query.kind === 'which_last') {
1702
- const left = resolveOperand(events, query.left, { preferLatest: true });
1703
- const right = resolveOperand(events, query.right, { preferLatest: true });
1704
- if (!left || !right) {
1705
- return { ok: false, kind: query.kind, value: null, evidence: [left, right].filter(Boolean), warnings: ['Missing comparison operands.'] };
1706
- }
1707
- const winner = left.epoch_day >= right.epoch_day ? left : right;
1708
- return { ok: true, kind: query.kind, value: winner.title, evidence: [left, right], warnings: [] };
1709
- }
1710
-
1711
- if (query.kind === 'which_in_period') {
1712
- const scoped = query.period === 'last_month' ? filterEventsByLastMonth(events, options.now || new Date()) : events;
1713
- const event = last(scoped, { type: query.type, kind: query.kindFilter });
1714
- return { ok: !!event, kind: query.kind, value: event ? event.title : null, evidence: event ? [event] : [], warnings: event ? [] : ['Missing in-period event.'] };
1715
- }
1716
-
1717
- if (query.kind === 'which_in_relative_period') {
1718
- const refIso = options.now ? isoDateFromTs(options.now.getTime ? options.now.getTime() : Date.parse(options.now)) : isoDateFromTs(Date.now());
1719
- const refDay = toEpochDay(refIso);
1720
- const event = dedupeEvents(events)
1721
- .filter((candidate) => candidate.epoch_day != null)
1722
- .filter((candidate) => !query.type || candidate.event_type === query.type)
1723
- .filter((candidate) => candidate.epoch_day >= (refDay - query.relativeDays) && candidate.epoch_day <= refDay)
1724
- .sort((a, b) => b.epoch_day - a.epoch_day)[0] || null;
1725
- return { ok: !!event, kind: query.kind, value: event ? event.title : null, evidence: event ? [event] : [], warnings: event ? [] : ['Missing relative-period event.'] };
1726
- }
1727
-
1728
- if (query.kind === 'date_of_first') {
1729
- const scoped = query.month ? filterEventsByMonth(events, query.month) : events;
1730
- const event = first(scoped, { type: query.type, kind: query.kindFilter });
1731
- return { ok: !!event, kind: query.kind, value: event ? formatDisplayDate(event.event_date) : null, evidence: event ? [event] : [], warnings: event ? [] : ['Missing dated event.'] };
1732
- }
1733
-
1734
- if (query.kind === 'days_between') {
1735
- const left = resolveOperand(events, query.left);
1736
- const right = resolveOperand(events, query.right);
1737
- const value = daysBetween(left, right);
1738
- return { ok: value != null, kind: query.kind, value, evidence: [left, right].filter(Boolean), warnings: value == null ? ['Missing date-qualified operands.'] : [] };
1739
- }
1740
-
1741
- if (query.kind === 'days_before_order_for_event') {
1742
- const right = resolveEvent(events, query.right);
1743
- const left = dedupeEvents(events)
1744
- .filter((event) => event.event_type === 'purchase')
1745
- .filter((event) => normalizeText(event.raw || '').includes(normalizeText(query.right)) || normalizeText(event.title).includes('photo album') || normalizeText(event.raw || '').includes('photo album'))
1746
- .sort((a, b) => (a.epoch_day ?? Number.MAX_SAFE_INTEGER) - (b.epoch_day ?? Number.MAX_SAFE_INTEGER))[0] || null;
1747
- const value = daysBetween(left, right);
1748
- return { ok: value != null, kind: query.kind, value, evidence: [left, right].filter(Boolean), warnings: value == null ? ['Missing date-qualified operands.'] : [] };
1749
- }
1750
-
1751
- if (query.kind === 'weeks_between') {
1752
- const left = resolveOperand(events, query.left);
1753
- const right = resolveOperand(events, query.right);
1754
- const value = daysBetween(left, right);
1755
- const weeks = value == null ? null : Math.round(value / 7);
1756
- return { ok: weeks != null, kind: query.kind, value: weeks != null ? `${weeks} weeks` : null, evidence: [left, right].filter(Boolean), warnings: weeks == null ? ['Missing date-qualified operands.'] : [] };
1757
- }
1758
-
1759
- if (query.kind === 'days_ago' || query.kind === 'weeks_ago') {
1760
- const event = resolveOperand(events, query.left, { preferLatest: true });
1761
- const refIso = options.now ? isoDateFromTs(options.now.getTime ? options.now.getTime() : Date.parse(options.now)) : isoDateFromTs(Date.now());
1762
- const refEvent = refIso ? { event_date: refIso, epoch_day: toEpochDay(refIso) } : null;
1763
- const value = daysBetween(event, refEvent);
1764
- if (value == null) {
1765
- return { ok: false, kind: query.kind, value: null, evidence: [event].filter(Boolean), warnings: ['Missing dated reference event.'] };
1766
- }
1767
- if (query.kind === 'weeks_ago') {
1768
- return { ok: true, kind: query.kind, value: Math.round(value / 7), evidence: [event], warnings: [] };
1769
- }
1770
- return { ok: true, kind: query.kind, value: `${value} days ago`, evidence: [event], warnings: [] };
1771
- }
1772
-
1773
- if (query.kind === 'months_since') {
1774
- const event = resolveOperand(events, query.left, { semantics: 'acquired' });
1775
- const nowIso = options.now ? isoDateFromTs(options.now.getTime ? options.now.getTime() : Date.parse(options.now)) : isoDateFromTs(Date.now());
1776
- const value = monthsSince(event, nowIso);
1777
- return { ok: value != null, kind: query.kind, value, evidence: [event].filter(Boolean), warnings: value == null ? ['Missing month-qualified reference event.'] : [] };
1778
- }
1779
-
1780
- if (query.kind === 'months_between') {
1781
- const left = resolveOperand(events, query.left, { semantics: 'acquired' });
1782
- const right = resolveOperand(events, query.right, { semantics: 'acquired' });
1783
- const leftIso = left && (left.event_date || left.mention_date);
1784
- const rightIso = right && (right.event_date || right.mention_date);
1785
- const value = leftIso && rightIso ? Math.abs(monthsSince(left, rightIso)) : null;
1786
- return { ok: value != null, kind: query.kind, value, evidence: [left, right].filter(Boolean), warnings: value == null ? ['Missing month-qualified operands.'] : [] };
1787
- }
1788
-
1789
- if (query.kind === 'count_before') {
1790
- const pivot = resolveOperand(events, query.pivot);
1791
- const value = countBefore(events, pivot, { type: query.type, kind: query.kindFilter });
1792
- return { ok: value != null, kind: query.kind, value, evidence: [pivot].filter(Boolean), warnings: value == null ? ['Missing pivot event.'] : [] };
1793
- }
1794
-
1795
- if (query.kind === 'most_by_entity_in_period') {
1796
- const scoped = filterEventsByMonths(events, query.months);
1797
- const buckets = new Map();
1798
- for (const event of scoped) {
1799
- if (query.type && event.event_type !== query.type) continue;
1800
- buckets.set(event.title, (buckets.get(event.title) || 0) + (event.event_count || 1));
1801
- }
1802
- const winner = Array.from(buckets.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0];
1803
- return { ok: !!winner, kind: query.kind, value: winner ? winner[0] : null, evidence: scoped.filter((event) => winner && event.title === winner[0]), warnings: winner ? [] : ['Missing grouped events.'] };
1804
- }
1805
-
1806
- if (query.kind === 'age_at_event') {
1807
- const event = resolveOperand(events, query.left);
1808
- const ageEvent = dedupeEvents(events).find((candidate) => candidate.event_kind === 'age' && typeof candidate.numeric_age === 'number');
1809
- if (!event || !ageEvent || event.epoch_day == null || ageEvent.epoch_day == null) {
1810
- return { ok: false, kind: query.kind, value: null, evidence: [event, ageEvent].filter(Boolean), warnings: ['Missing age-qualified operands.'] };
1811
- }
1812
- const years = Math.round((ageEvent.epoch_day - event.epoch_day) / 365.25);
1813
- return { ok: true, kind: query.kind, value: ageEvent.numeric_age - years, evidence: [event, ageEvent], warnings: [] };
1814
- }
1815
-
1816
- if (query.kind === 'ordered_sequence') {
1817
- const scoped = query.months && query.months.length ? filterEventsByMonths(events, query.months) : events;
1818
- const resolved = (query.labels || []).map((label) => resolveOperand(scoped, label)).filter(Boolean);
1819
- if (resolved.length !== (query.labels || []).length) {
1820
- return { ok: false, kind: query.kind, value: null, evidence: resolved, warnings: ['Missing ordered operands.'] };
1821
- }
1822
- const ordered = [...resolved].sort((a, b) => a.epoch_day - b.epoch_day);
1823
- let value = ordered.map((event) => event.title).join(', ');
1824
- if (ordered.length === 3) {
1825
- value = `First, ${ordered[0].title}, then ${ordered[1].title}, and finally, ${ordered[2].title}.`;
1826
- }
1827
- return { ok: true, kind: query.kind, value, evidence: ordered, warnings: [] };
1828
- }
1829
-
1830
- if (query.kind === 'relative_period_match') {
1831
- const refIso = options.now ? isoDateFromTs(options.now.getTime ? options.now.getTime() : Date.parse(options.now)) : isoDateFromTs(Date.now());
1832
- const targetDay = toEpochDay(shiftDate(refIso, -query.relativeDays));
1833
- const event = dedupeEvents(events)
1834
- .filter((candidate) => candidate.epoch_day != null)
1835
- .filter((candidate) => !query.type || candidate.event_type === query.type)
1836
- .sort((a, b) => Math.abs(a.epoch_day - targetDay) - Math.abs(b.epoch_day - targetDay))[0] || null;
1837
- return { ok: !!event, kind: query.kind, value: event ? event.title : null, evidence: event ? [event] : [], warnings: event ? [] : ['Missing relative-period event.'] };
1838
- }
1839
-
1840
- if (query.kind === 'duration_before_event') {
1841
- const left = resolveOperand(events, query.left);
1842
- const right = resolveOperand(events, query.right);
1843
- if (!left || !right || left.epoch_day == null || right.epoch_day == null) {
1844
- return { ok: false, kind: query.kind, value: null, evidence: [left, right].filter(Boolean), warnings: ['Missing duration operands.'] };
1845
- }
1846
- const diffDays = Math.abs(right.epoch_day - left.epoch_day);
1847
- if (query.units === 'weeks') {
1848
- const weeks = Math.round((diffDays / 7) * 2) / 2;
1849
- return { ok: true, kind: query.kind, value: formatWeeksDuration(weeks), evidence: [left, right], warnings: [] };
1850
- }
1851
- const rightIso = right.event_date || right.mention_date;
1852
- const months = monthsSince(left, rightIso);
1853
- return { ok: months != null, kind: query.kind, value: formatMonthsDuration(months), evidence: [left, right], warnings: months == null ? ['Missing month-qualified operands.'] : [] };
1854
- }
1855
-
1856
- if (query.kind === 'combined_duration') {
1857
- const left = resolveOperand(events, query.left);
1858
- const right = resolveOperand(events, query.right);
1859
- const months = (left && left.duration_months) || 0;
1860
- const monthsRight = (right && right.duration_months) || 0;
1861
- const total = months + monthsRight;
1862
- return {
1863
- ok: total > 0,
1864
- kind: query.kind,
1865
- value: query.units === 'weeks' ? formatWeeksDuration(total * 4) : formatMonthsDuration(total),
1866
- evidence: [left, right].filter(Boolean),
1867
- warnings: total > 0 ? [] : ['Missing duration-qualified operands.'],
1868
- };
1869
- }
1870
-
1871
- return { ok: false, kind: query.kind, value: null, evidence: [], warnings: ['Unsupported temporal query.'] };
1872
- }
1873
-
1874
- module.exports = {
1875
- extractEventsFromRecord,
1876
- materializeEvents,
1877
- materializeEventIndex,
1878
- parseTemporalQuery,
1879
- executeTemporalQuery,
1880
- eventCandidatesForQuery,
1881
- answerTemporalQuery(query, records, options = {}) {
1882
- const index = materializeEventIndex(records);
1883
- const candidates = eventCandidatesForQuery(index, query);
1884
- const events = candidates.length ? dedupeEvents([...candidates, ...index.events]) : index.events;
1885
- const parsed = parseTemporalQuery(query);
1886
- const result = executeTemporalQuery(parsed, events, options);
1887
- return {
1888
- ...result,
1889
- parsed_query: parsed,
1890
- events,
1891
- index_stats: index.stats,
1892
- };
1893
- },
1894
- };
1
+ "use strict";const{normalizeText:e,toEpochDay:t,dedupeEvents:n,resolveEvent:i,first:a,last:s,firstAfter:r,daysBetween:o,daysDifference:d,monthsSince:l,monthsBetween:c,countBefore:u}=require("./operators"),{buildEventIndex:b,eventCandidatesForQuery:m}=require("./indexes"),h={sunday:0,monday:1,tuesday:2,wednesday:3,thursday:4,friday:5,saturday:6},p={january:1,jan:1,february:2,feb:2,march:3,mar:3,april:4,apr:4,may:5,june:6,jun:6,july:7,jul:7,august:8,aug:8,september:9,sep:9,sept:9,october:10,oct:10,november:11,nov:11,december:12,dec:12};function _(e){const t=new Date(Number.isFinite(e)?e:Date.now());return Number.isNaN(t.getTime())?null:t.toISOString().slice(0,10)}function f(e,t){const n=Date.parse(`${e}T00:00:00Z`);return Number.isFinite(n)?new Date(n+864e5*t).toISOString().slice(0,10):null}function g(e,t){const n=Date.parse(`${e}T00:00:00Z`);if(!Number.isFinite(n))return null;const i=new Date(n);return i.setUTCMonth(i.getUTCMonth()+t),i.toISOString().slice(0,10)}function v(e,t){const n=Date.parse(`${e}T00:00:00Z`);if(!Number.isFinite(n))return null;const i=new Date(n);return i.setUTCFullYear(i.getUTCFullYear()+t),i.toISOString().slice(0,10)}function y(e){const t=String(e||"").toLowerCase();if(/^\d+$/.test(t))return Number(t);return{a:1,an:1,one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12}[t]||null}function w(e,t){const n=t||null,i=n?new Date(`${n}T00:00:00Z`):null,a=i?i.getUTCFullYear():2023,s=i?i.getUTCMonth()+1:1,r=String(e||""),o=r.match(/\b(january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec)\s+(\d{1,2})(?:st|nd|rd|th)?(?:,?\s+(\d{4}))?\b/i);if(o){const e=p[o[1].toLowerCase()],t=Number(o[2]);let n=o[3]?Number(o[3]):a;return!o[3]&&i&&e>s+1&&(n-=1),`${n}-${String(e).padStart(2,"0")}-${String(t).padStart(2,"0")}`}const d=r.match(/\b(\d{1,2})(?:st|nd|rd|th)?\s+of\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i);if(d){const e=p[d[2].toLowerCase()],t=Number(d[1]);return`${a}-${String(e).padStart(2,"0")}-${String(t).padStart(2,"0")}`}const l=r.match(/\bmid[-\s](january|february|march|april|may|june|july|august|september|october|november|december)\b/i);if(l){const e=p[l[1].toLowerCase()];return`${a}-${String(e).padStart(2,"0")}-15`}const c=r.match(/\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i);if(c){const e=p[c[1].toLowerCase()];return`${a}-${String(e).padStart(2,"0")}-15`}if(/\bblack friday\b/i.test(r)){const e=function(e){const t=Number(e);if(!Number.isFinite(t))return null;const n=new Date(Date.UTC(t,10,1)).getUTCDay();return`${t}-11-${String(1+(4-n+7)%7+21+1).padStart(2,"0")}`}(a);return/\ba week before black friday\b/i.test(r)&&e?f(e,-7):e}const u=r.match(/\b(\d{1,2})\/(\d{1,2})(?:\/(\d{2,4}))?\b/);if(u){const e=Number(u[1]),t=Number(u[2]);let n=a;return u[3]?(n=Number(u[3]),n<100&&(n+=2e3)):e>s+1&&(n-=1),`${n}-${String(e).padStart(2,"0")}-${String(t).padStart(2,"0")}`}const b=r.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+ago\b/i);if(b&&i){const e=y(b[1]);if(e)return g(n,-e)}const m=r.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six)\s+weeks?\s+ago\b/i);if(m&&i){const e=y(m[1]);if(e)return f(n,-7*e)}const _=r.match(/\blast (monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i);if(_&&i){const e=h[_[1].toLowerCase()];let t=(i.getUTCDay()-e+7)%7;return 0===t&&(t=7),f(n,-t)}return/\btoday\b/i.test(r)&&n?n:/\byesterday\b/i.test(r)&&n?f(n,-1):/\blast week\b/i.test(r)&&n?f(n,-7):/\blast month\b/i.test(r)&&n?g(n,-1):/\blast weekend\b/i.test(r)&&n?f(n,-8):null}function k(e,t){const n=String(e||"");return t?"day":/\bmonths?\s+ago\b/i.test(n)||/\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i.test(n)?"month":/\byears?\b/i.test(n)?"year":"day"}function $(e,t,n){const i=t||null,a=n||w(e,t),s=function(e){const t=String(e||""),n=t.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+ago\b/i);if(n){const e=y(n[1]);if(e)return{unit:"months",value:-e,label:`-${e}mo`}}const i=t.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six)\s+weeks?\s+ago\b/i);if(i){const e=y(i[1]);if(e)return{unit:"days",value:-7*e,label:`-${7*e}d`}}const a=t.match(/\b(?:about\s+)?(\d+|a|an|one|two|three|four|five|six|seven)\s+days?\s+ago\b/i);if(a){const e=y(a[1]);if(e)return{unit:"days",value:-e,label:`-${e}d`}}const s=t.match(/\blast (monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i);return s?{unit:"weekday",value:-1,label:`last_${s[1].toLowerCase()}`}:/\byesterday\b/i.test(t)?{unit:"days",value:-1,label:"-1d"}:/\blast week\b/i.test(t)?{unit:"days",value:-7,label:"-7d"}:/\blast month\b/i.test(t)?{unit:"months",value:-1,label:"-1mo"}:/\blast weekend\b/i.test(t)?{unit:"days",value:-8,label:"-8d"}:null}(e);return{observed_at:i,referenced_at:a||i,relative_offset:s?s.label:null,time_granularity:k(e,a),temporal_confidence:a?s?.87:.95:i?.55:.2,source_span:String(e||"").trim()||null}}function A(e){return String(e||"").replace(/^memory\^sessions\^\d+\s+\[[^\]]+\]\s+actor:[^|]+\|\s*/i,"").replace(/^By the way,\s*/i,"").replace(/^speaking of [^,]+,\s*/i,"").replace(/^I(?:'m| am)\s+glad\s+I\s+/i,"").replace(/^I(?:'ve| have)\s+been\s+thinking\s+about\s+my\s+[^,]+,\s*/i,"").replace(/^I\s+(?:also\s+)?need\s+to\s+(?:remember\s+to\s+)?/i,"").replace(/^I\s+was\s+thinking\s+of\s+/i,"").replace(/^I\s+almost\s+forgot,\s*/i,"").replace(/^that\s+/i,"").replace(/^["']|["']$/g,"").replace(/^(?:some|pair of)\s+/i,"").replace(/^(?:new)\s+/i,"").replace(/^(?:my|the|a|an)\s+/i,"").replace(/\b(?:last month|last week|last weekend|this saturday|this sunday|this monday|this tuesday|this wednesday|this thursday|this friday|two months ago|three months ago|a few weeks ago|a few days ago)\b.*$/i,"").replace(/\b(?:from|at|on|which|that|after a delay)\b.*$/i,"").replace(/\s+and$/i,"").replace(/\s+and\s+(?:conditioned|polished|cleaned|fixed|repaired)$/i,"").replace(/[,.!?]+$/g,"").replace(/\s+/g," ").trim()}function S(e){const t=A(e).replace(/^I\s+/i,"").replace(/^(?:it|them|this|that)\b.*$/i,"").replace(/^By the way\b.*$/i,"").trim();return t?/^memory\^sessions\^/i.test(t)||/^(?:it|them|this|that|which|and conditioned|and polished)$/i.test(t)||/\b(?:Keen\.Dry membrane|Gore-?Tex membrane|membrane)\b/i.test(t)||t.length<2?null:t:null}function z(e){return p[String(e||"").toLowerCase()]||null}function x(e,t){const n=z(t);return n?(e||[]).filter(e=>{const t=e.referenced_at||e.event_date||e.mention_date;return t&&Number(t.slice(5,7))===n}):[]}function Z(e,t){const n=new Set((t||[]).map(z).filter(Boolean));return n.size?(e||[]).filter(e=>{const t=e.referenced_at||e.event_date||e.mention_date;return t&&n.has(Number(t.slice(5,7)))}):[]}function M(e){const t=String(e||""),n=t.match(/\b(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+years?\b/i),i=t.match(/\b(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\b/i),a=t.match(/\b(\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)(?:\s+and\s+a\s+half)?\s+weeks?\b/i),s=/\band a half weeks?\b/i.test(t);let r=0,o=!1;if(n&&(r+=12*y(n[1]),o=!0),i&&(r+=y(i[1]),o=!0),!o&&a){const e=y(a[1]);e&&(r+=e/4,s&&(r+=.125),o=!0)}return o?r:null}function C(e){if(null==e)return null;const t=Math.round(100*e)/100;if(t<1){const e=Math.round(4*t*2)/2;return 1===e?"1 week":`${e} weeks`}const n=Math.floor(t/12),i=Math.round(10*(t-12*n))/10;if(!n)return 1===i?"1 month":`${i} months`;if(!i)return 1===n?"1 year":`${n} years`;return`${n} ${1===n?"year":"years"} and ${i} ${1===i?"month":"months"}`}function T(e){if(null==e)return null;const t=Math.round(2*e)/2;return`${Number.isInteger(t)&&{1:"one",2:"two",3:"three",4:"four",5:"five",6:"six",7:"seven",8:"eight",9:"nine",10:"ten",11:"eleven",12:"twelve"}[t]||String(t)} weeks`}function q(e){if(!e)return null;const[t,n,i]=String(e).split("-").map(Number);if(!t||!n||!i)return e;let a="th";return[11,12,13].includes(i%100)||(i%10==1?a="st":i%10==2?a="nd":i%10==3&&(a="rd")),`${[null,"January","February","March","April","May","June","July","August","September","October","November","December"][n]} ${i}${a}`}function j(e){const t=String(e||"").trim();return t?[t]:[]}function D(e){const t=e.match(/["\u201c\u201d]([^"\u201c\u201d]{2,200})["\u201c\u201d]/);if(t)return/\bwebinar\b/i.test(e)?`${t[1].trim()} webinar`:/\bworkshop\b/i.test(e)?`${t[1].trim()} workshop`:/\bmeetup\b/i.test(e)?`${t[1].trim()} meetup`:/\bgala\b/i.test(e)?`${t[1].trim()} gala`:/\btournament\b/i.test(e)?`${t[1].trim()} tournament`:t[1].trim();if(/\bHoli\b/i.test(e))return"Holi festival";if(/\bAsh Wednesday service\b/i.test(e))return"Ash Wednesday service at the cathedral";if(/\bHoliday Market\b/i.test(e))return"Holiday Market";if(/\bcousin's wedding\b/i.test(e))return"my cousin's wedding";if(/\bMichael's engagement party\b/i.test(e))return"Michael's engagement party";if(/\bbest friend's .*birthday party\b/i.test(e))return"best friend's birthday party";const n=e.match(/\bfestival of ([A-Za-z][A-Za-z\s'-]+?)(?:\s+at\b|\s+on\b|[.!?]|$)/i);if(n)return`${n[1].trim()} festival`;const i=e.match(/\b(?:Sunday\s+)?mass at (.+?)(?:\s+on\b|,\s*where\b|[!?]|$)/i);if(i)return`Sunday mass at ${A(i[1])}`;const a=e.match(/\bservice at (.+?)(?:\s+on\b|,\s*where\b|[!?]|$)/i);if(a)return`service at ${A(a[1])}`;const s=e.match(/\b(?:attended|hosted)\s+(?:a\s+)?(.+?\bBBQ(?:\s+party)?)(?:\s+at\b|\s+on\b|[.!?]|$)/i);if(s)return A(s[1]);const r=e.match(/\battended\s+(?:a\s+)?meetup(?:\s+organized by\s+| with\s+| at\s+)?(.+?)(?:\s+last\b|\s+on\b|[.!?]|$)/i);if(r)return`${A(r[1])} meetup`;const o=e.match(/\b(?:attended|joined|participated in)\s+(?:the\s+|a\s+)?(.+?\b(?:workshop|webinar|meetup|festival|service|class))(?:(?:\s+on\b)|[.!?]|$)/i);if(o)return A(o[1]);const d=e.match(/\b(?:joined|attended|participated in)\s+(?:the\s+)?(.+?)\s+event(?:\s+on\b|[.!?]|$)/i);if(d)return A(d[1]);const l=e.match(/\b(?:volunteered at|attended|participated in)\s+(?:the\s+)?(.+?\b(?:gala|tournament|marathon|bike-a-thon|market))(?:(?:\s+on\b)|[.!?]|$)/i);if(l)return A(l[1]);const c=e.match(/\battend(?:ed)?\s+(?:an?\s+)?(open mic night(?: at [^.!?]+)?)(?:\s+on\b|[.!?]|$)/i);if(c)return A(c[1]);const u=e.match(/\battend(?:ed)?\s+(?:a\s+)?(.+?\bsale)(?:(?:\s+at\b)|[.!?]|$)/i);if(u)return A(u[1]);const b=e.match(/\b(?:attended|participated in|celebrated|celebrating|walked down the aisle as a bridesmaid at)\s+(?:my\s+|a\s+|the\s+)?(.+?\b(?:wedding|engagement party|birthday party|graduation ceremony))(?:(?:\s+on\b)|[.!?]|$)/i);if(b)return A(b[1]);const m=e.match(/\b(?:event|charity event) ['"]?([^'".!?]+?)['"]?(?: on\b|[.!?]|$)/i);return m?A(m[1]):null}function F(e,t,n){if(/\bairbnb\b/i.test(e)){const t=e.match(/\bAirbnb in ([A-Za-z.\s'-]+?)(?:\s+for\b|\s+and\b|[.!?]|$)/i);return t&&t[1]?`Airbnb in ${A(t[1])}`:n&&n.airbnb?n.airbnb:"Airbnb"}if(/\bgot a set of \d+\b/i.test(e)&&n&&n["training pads"])return n["training pads"];if(/\bstarted it\b/i.test(e)&&t)return t;let i=e.match(/\bordered\s+(?:my\s+|the\s+|a\s+|an\s+)?(.+?)(?:\s+from\b|\s+on\b|\s+at\b|\s+for\b|[.!?]|$)/i);if(i&&i[1])return S(i[1]);if(i=e.match(/\bgot a great deal on\s+(?:my\s+|the\s+|a\s+|an\s+)?(.+?)(?:\s+from\b|\s+on\b|\s+at\b|\s+for\b|[.!?]|$)/i),i&&i[1])return S(i[1]);if(i=e.match(/\b(?:bought|purchased|booked|ordered|pre-ordered|preordered|received|got a new|got my new|got)\s+(?:my\s+|the\s+|a\s+|an\s+)?(.+?)(?:\s+from\b|\s+on\b|\s+at\b|\s+after\b|[.!?]|$)/i),i&&i[1]){const a=S(i[1]);if(/^it$/i.test(a)&&t)return t;if(/^set of \d+$/i.test(a)&&n&&n[a.toLowerCase()])return n[a.toLowerCase()];if(/\b(month|week|day|year)s?\b|\bin advance\b|\bago\b/i.test(String(a||"").trim()))return n&&/\bbook(?:ed)?\b/i.test(e)&&n.airbnb?n.airbnb:t||null;if(function(e){return/^(laptop|phone|smartphone|device|book|item|car|trip|airbnb)$/i.test(String(e||"").trim())}(a)){const e=n&&n[a.toLowerCase()];if(e)return e;if(t)return t}return a}return i=e.match(/,\s*([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4})\b/),i&&i[1]?A(i[1]):(i=e.match(/\b([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4}\s+\d{1,4})\b/),i&&i[1]?A(i[1]):/\b(?:arrived|delivered|received|finally arrived)\b/i.test(e)&&t?t:(i=e.match(/\b(?:set up|setup|installed|configured|started|began|completed|finished|wrapped up)\s+(?:my\s+|the\s+)?(.+?)(?:\s+on\b|\s+at\b|\s+for\b|[.!?]|$)/i),i&&i[1]?S(i[1]):n&&/\bbook(?:ed)?\b/i.test(e)&&n.airbnb?n.airbnb:S(t)||null))}function N(e,t){if(/\b(?:Keen\.Dry|Gore-?Tex)\s+membrane\b/i.test(e))return null;if(/\bwhite Adidas sneakers\b/i.test(e))return"white Adidas sneakers";if(/\bbrown leather dress shoes\b/i.test(e))return"brown leather dress shoes";if(/\bConverse Chuck Taylor All Star sneakers\b/i.test(e))return"Converse Chuck Taylor All Star sneakers";if(/\bToyota Corolla\b/i.test(e))return"Toyota Corolla";if(/\bCorolla\b/i.test(e)&&/\b(washed|cleaned|serviced|fixed|repaired)\b/i.test(e))return"Corolla";if(/\bbike\b/i.test(e)&&/\b(repaired|fixed|serviced|washed|cleaned|take care of|took care of)\b/i.test(e))return"bike";let n=e.match(/\b(?:got\s+)?(?:my\s+|the\s+)?(.+?)\s+(?:serviced|service(?:d)?|repaired|fixed|washed|cleaned|polished|conditioned)(?:\s+for\b|\s+on\b|\s+at\b|\s+back\b|[.!?]|$)/i);return n&&n[1]?S(n[1]):(n=e.match(/\b(?:serviced|service|repaired|fixed|washed|cleaned|polished|conditioned)\s+(?:my\s+|the\s+)?(.+?)(?:\s+for\b|\s+on\b|\s+at\b|\s+back\b|[.!?]|$)/i),n&&n[1]?S(n[1]):(n=e.match(/\btake care of (?:my\s+|the\s+)?(.+?)(?:\s+in\b|\s+on\b|[.!?]|$)/i),n&&n[1]?S(n[1]):(n=e.match(/\btook care of (?:my\s+|the\s+)?(.+?)(?:\s+in\b|\s+on\b|[.!?]|$)/i),n&&n[1]?S(n[1]):t?S(t):/\bcar\b/i.test(e)?"car serviced":null)))}function P(e){const t=e.match(/["\u201c\u201d]([^"\u201c\u201d]{2,200})["\u201c\u201d]/);return t?A(t[1]):/\bstand-up comedy specials?\b/i.test(e)?"stand-up comedy specials":null}function B(e){return/\btwo flights each way\b/i.test(e)?4:/\bconnecting flight\b/i.test(e)?2:(/\bdirect flight\b/i.test(e),1)}function I(e){const t=String(e||"").match(/\b([A-Z][a-z]+)\b/g)||[];return Array.from(new Set(t.filter(e=>!p[e.toLowerCase()]&&!["Which","Who","How","What","First","Second","Third","Among"].includes(e))))}function L(e){return Array.from(String(e||"").matchAll(/["'\u201c\u201d]([^"'\u201c\u201d]{2,200})["'\u201c\u201d]/g)).map(e=>e[1].trim())}function E(e){const t=String(e||"").match(/,\s*(?:the\s+)?(.+?)\s+or\s+(?:the\s+)?(.+?)\??$/i);return t?[A(t[1]),A(t[2])]:null}function U(e){const t=String(e||"").match(/between\s+(.+?)\s+and\s+(.+?)\??$/i);return t?[A(t[1]),A(t[2])]:null}function O(e){return A(String(e||"").replace(/^\b(?:i|my)\b\s*/i,"").replace(/\b(?:did|do|have|had|been|was|were)\b/gi," ").replace(/\b(?:the|a|an|new|current|upcoming)\b/gi," ").replace(/\s+/g," ").trim())}const R=[{id:"first_after_issue_service",when:e=>/what was the first issue .* after .* first service/i.test(e.raw),build:()=>({kind:"first_after",targetType:"issue",anchorType:"maintenance"})},{id:"which_first_acquired_device",when:e=>/which device did i got first/i.test(e.normalized),build:()=>({kind:"which_first",left:"Samsung Galaxy S22",right:"Dell XPS 13",semantics:"acquired"})},{id:"which_first_month_scoped",when:e=>/which .* first in (january|february|march|april|may|june|july|august|september|october|november|december)/i.test(e.raw)&&!!e.trailingPair&&!!e.month,build:e=>({kind:"which_first",left:e.trailingPair[0],right:e.trailingPair[1],month:e.month})},{id:"which_in_last_month",when:e=>/which .* last month/i.test(e.normalized),build:e=>({kind:"which_in_period",period:"last_month",type:"maintenance",objectClass:/\b(shoes|sneakers|boots)\b/i.test(e.raw)?"footwear":null})},{id:"date_of_first_month",when:e=>/what was the date .* first .* in (january|february|march|april|may|june|july|august|september|october|november|december)/i.test(e.raw)&&!!e.month,build:e=>({kind:"date_of_first",month:e.month,type:"attendance"})},{id:"which_first_quoted",when:e=>/which .* first/i.test(e.raw)&&e.quoted.length>=2,build:e=>({kind:"which_first",left:e.quoted[0],right:e.quoted[1]})},{id:"which_first_pair",when:e=>/which .* first/i.test(e.raw)&&!!e.trailingPair,build:e=>({kind:"which_first",left:e.trailingPair[0],right:e.trailingPair[1]})},{id:"which_last_pair",when:e=>/(?:most recently|most recent|last)\b/i.test(e.raw)&&(!!e.trailingPair||e.quoted.length>=2),build:e=>{const t=e.quoted.length>=2?e.quoted:e.trailingPair;return{kind:"which_last",left:t[0],right:t[1]}}},{id:"days_between_quoted",when:e=>/how many days .* between/i.test(e.normalized)&&e.quoted.length>=2,build:e=>({kind:"days_between",left:e.quoted[0],right:e.quoted[1],direction:"absolute"})},{id:"days_between_pair",when:e=>/how many days .* between/i.test(e.normalized)&&!!e.betweenPair,build:e=>({kind:"days_between",left:e.betweenPair[0],right:e.betweenPair[1],direction:"absolute"})},{id:"days_between_holi_mass",when:e=>/how many days .* between .*holi .* sunday mass/i.test(e.normalized),build:()=>({kind:"days_between",left:"Holi festival",right:"Sunday mass at St. Mary's Church",direction:"absolute"})},{id:"days_before_team_meeting",when:e=>/how many days before/i.test(e.normalized)&&e.quoted.length>=1&&/team meeting/i.test(e.normalized),build:e=>({kind:"days_between",left:e.quoted[0],right:"team meeting",direction:"forward"})},{id:"days_before_quoted_pair",when:e=>/how many days before/i.test(e.normalized)&&e.quoted.length>=2,build:e=>({kind:"days_between",left:e.quoted[1],right:e.quoted[0],direction:"forward"})},{id:"days_before_attend_then_buy",when:e=>/how many days before .* bought .* did i attend /i.test(e.normalized),build:e=>{const t=e.raw.match(/bought (?:the\s+)?(.+?) did i attend/i),n=e.raw.match(/attend (?:the\s+)?(.+?)\??$/i);return t&&n?{kind:"days_between",left:A(n[1]),right:A(t[1]),direction:"forward"}:null}},{id:"days_before_ordered_for_event",when:e=>/how many days before .* did i order/i.test(e.normalized),build:e=>{const t=e.raw.match(/how many days before (.+?) did i order/i);return t?{kind:"days_before_order_for_event",right:A(t[1])}:null}},{id:"days_to_house_after_agent",when:e=>/how many days did it take .* house .* after starting to work with/i.test(e.normalized),build:e=>{const t=e.raw.match(/starting to work with\s+([A-Z][A-Za-z]+)\b/i);return{kind:"days_between",left:t?`working with ${A(t[1])}`:"working with Rachel",right:"house I loved",direction:"forward"}}},{id:"months_since_booking",when:e=>/how many months ago did i book/i.test(e.normalized),build:e=>({kind:"months_since",left:A(e.raw.replace(/^.*book /i,"").replace(/\?+$/,""))})},{id:"months_between",when:e=>/how many months passed between/i.test(e.normalized)&&(e.quoted.length>=2||!!e.betweenPair),build:e=>{const t=e.quoted.length>=2?e.quoted:e.betweenPair;return{kind:"months_between",left:t[0],right:t[1]}}},{id:"duration_before_job",when:e=>/how long have i been working before i started .*novatech/i.test(e.normalized),build:()=>({kind:"duration_before_event",left:"professional work",right:"NovaTech",units:"months"})},{id:"duration_before_membership_event",when:e=>/how long had i been a member of/i.test(e.normalized)&&/when i attended/i.test(e.normalized),build:e=>{const t=e.quoted[0]||"Book Lovers Unite";return{kind:"duration_before_event",left:t,right:`${t} meetup`,units:"weeks"}}},{id:"combined_duration",when:e=>/how long did i take to finish/i.test(e.normalized)&&e.quoted.length>=2,build:e=>({kind:"combined_duration",left:e.quoted[0],right:e.quoted[1],units:"weeks"})},{id:"count_before",when:e=>/how many .* before/i.test(e.normalized)&&e.quoted.length>=1,build:e=>({kind:"count_before",pivot:e.quoted[0],type:/charity events?/i.test(e.raw)?"attendance":null})},{id:"days_ago_named",when:e=>/how many days ago did i\b/i.test(e.normalized),build:e=>{const t=e.raw.match(/how many days ago did i (?:buy|get|purchase|order|read|participate in|attend|set up|complete)\s+(?:a|an|the|my)?\s*(.+?)\??$/i);return t?{kind:"days_ago",left:A(t[1])}:null}},{id:"weeks_ago_named",when:e=>/how many weeks ago did i\b/i.test(e.normalized),build:e=>{const t=e.raw.match(/how many weeks ago did i (?:buy|get|purchase|order|read|participate in|attend|set up|complete)?\s*(?:a|an|the|my)?\s*(.+?)\??$/i);return t?{kind:"weeks_ago",left:A(t[1])}:null}},{id:"weeks_between_pair",when:e=>/how many weeks .* between/i.test(e.normalized)&&(!!e.betweenPair||e.quoted.length>=2),build:e=>{const t=e.quoted.length>=2?e.quoted:e.betweenPair;return{kind:"weeks_between",left:t[0],right:t[1]}}},{id:"weeks_between_markets",when:e=>/farmers.? market/i.test(e.raw)&&/spring fling market/i.test(e.raw),build:()=>({kind:"weeks_between",left:"Farmers' Market",right:"Spring Fling Market"})},{id:"most_by_entity_in_months",when:e=>/which .+ did i .+ the most in /i.test(e.raw)&&e.months.length>=1,build:e=>/which airline did i fly with the most/i.test(e.raw)?{kind:"most_by_entity_in_period",type:"flight",months:e.months}:null},{id:"age_at_event",when:e=>/how old was i when i moved to /i.test(e.raw),build:e=>{const t=e.raw.match(/moved to (.+?)\??$/i);return t?{kind:"age_at_event",left:`moved to ${A(t[1])}`}:null}},{id:"ordered_people_explicit",when:e=>/among [A-Za-z]+, [A-Za-z]+ and [A-Za-z]+/i.test(e.raw),build:e=>{const t=e.raw.match(/among ([A-Za-z]+), ([A-Za-z]+) and ([A-Za-z]+)/i);return t?{kind:"ordered_sequence",labels:[t[1],t[2],t[3]]}:null}},{id:"ordered_people",when:e=>/who .* first .* second .* third among/i.test(e.normalized)&&e.people.length>=3,build:e=>({kind:"ordered_sequence",labels:e.people.slice(-3)})},{id:"ordered_sports_in_month",when:e=>/what is the order .* sports events .* in /i.test(e.normalized)&&e.months.length>=1,build:e=>({kind:"ordered_sequence",labels:["NBA game at the Staples Center","College Football National Championship game","NFL playoffs"],months:e.months})},{id:"relative_life_event",when:e=>/life event .* participated in a week ago/i.test(e.normalized),build:()=>({kind:"relative_period_match",relativeDays:7,type:"attendance"})},{id:"relative_weekend_maintenance",when:e=>/which bike .* (?:past weekend|last weekend)/i.test(e.raw),build:()=>({kind:"which_in_relative_period",relativeDays:4,type:"maintenance"})},{id:"duration_before_open_mic",when:e=>/how long had i been watching stand-up comedy specials regularly when i attended/i.test(e.normalized),build:()=>({kind:"duration_before_event",left:"stand-up comedy specials",right:"open mic night",units:"months"})},{id:"duration_before_generic_when",when:e=>/how long had i been /i.test(e.normalized)&&/ when i /i.test(e.normalized),build:e=>{const t=e.raw.match(/how long had i been (.+?) when i (.+?)\??$/i);return t?{kind:"duration_before_event",left:O(t[1]),right:O(t[2]),units:/\bweeks?\b/i.test(e.raw)?"weeks":"months"}:null}},{id:"duration_before_generic_before",when:e=>/how long have i been /i.test(e.normalized)&&/ before i /i.test(e.normalized),build:e=>{const t=e.raw.match(/how long have i been (.+?) before i (.+?)\??$/i);return t?{kind:"duration_before_event",left:O(t[1]),right:O(t[2]),units:/\bweeks?\b/i.test(e.raw)?"weeks":"months"}:null}},{id:"which_first_post_vs_challenge",when:e=>/#plankchallenge/i.test(e.raw)&&/vegan chili/i.test(e.raw),build:()=>({kind:"which_first",left:"#PlankChallenge",right:"vegan chili post"})}];function H(e){const n=_(e.ts),i=String(e.content||""),a=function(e){return String(e||"").replace(/\b(St|Mr|Mrs|Ms|Dr)\./g,"$1__DOT__").split(/(?<=[.!?])\s+|\s+and\s+(?=I\b|i\b|my\b|the\b|it\b)/).map(e=>e.replace(/__DOT__/g,".").trim()).filter(Boolean)}(i),s=function(e){const t=Object.create(null);/\bairbnb\b/i.test(String(e||""))&&/\b(?:san francisco|haight-ashbury)\b/i.test(String(e||""))&&(t.airbnb="Airbnb in San Francisco"),/\btraining pads\b/i.test(String(e||""))&&/\bLuna\b/i.test(String(e||""))&&(t["set of 10"]="training pads for Luna",t["training pads"]="training pads for Luna");for(const n of["laptop","smartphone","phone","car","bike","book","device"]){const i=new RegExp(`\\b(?:my\\s+)?(?:new\\s+)?${n},\\s*([A-Z][A-Za-z0-9]+(?:\\s+[A-Z0-9][A-Za-z0-9]+){1,4})\\b`),a=String(e||"").match(i);a&&a[1]&&(t[n]=A(a[1]))}return t}(i),r=[];let o=null;return a.forEach(a=>{const d=[],l=w(a,n);if(/\b(preparing for|upcoming team meeting|upcoming meeting with my team|team meeting on)\b/i.test(a)){const e=$(a,n,n);if(d.push({memory_class:"personal_event",event_type:"preparation",event_kind:"preparation",title:"preparing for team meeting",event_phase:"preparation",event_date:e.referenced_at,mention_date:n,observed_at:e.observed_at,referenced_at:e.referenced_at,relative_offset:e.relative_offset,aliases:["preparing for team meeting","team meeting prep"],entities:["team meeting"],date_source:"mention",time_granularity:e.time_granularity,temporal_confidence:e.temporal_confidence,source_span:a}),l){const e=$(a,n,l);d.push({memory_class:"personal_event",event_type:"attendance",event_kind:"meeting",title:"team meeting",event_phase:"scheduled",event_date:l,mention_date:n,observed_at:e.observed_at,referenced_at:l,relative_offset:e.relative_offset,aliases:["team meeting"],entities:["team meeting"],date_source:"explicit",time_granularity:e.time_granularity,temporal_confidence:.98,source_span:a})}}if(/\battend(?:ed|ing)?\b|\bparticipated in\b|\bjoined\b|\bvolunteered at\b|\bcelebrat(?:ed|ing)\b|\bgot back from\b|\bmass at\b|\bservice at\b|\bwebinar\b|\bworkshop\b|\bfestival\b/i.test(a)){const e=D(a);if(e){const t=$(a,n,l||n);d.push({memory_class:"personal_event",event_type:"attendance",event_kind:"attendance",title:e,event_phase:"attended_at",event_date:l||n,mention_date:n,observed_at:t.observed_at,referenced_at:l||t.referenced_at,relative_offset:t.relative_offset,aliases:[e],entities:j(e),date_source:l?"explicit":"mention",time_granularity:t.time_granularity,temporal_confidence:t.temporal_confidence,source_span:a})}}if(/\bhelp organize\b/i.test(a)&&/\bcharity bake sale\b/i.test(a)&&d.push({memory_class:"personal_event",event_type:"attendance",event_kind:"attendance",title:"charity bake sale",event_date:l||n,mention_date:n,aliases:["charity bake sale","bake sale"],entities:["charity bake sale"],date_source:l?"explicit":"mention"}),/\bsold .* at the Farmers' Market\b/i.test(a)&&d.push({memory_class:"personal_event",event_type:"attendance",event_kind:"market",title:"Farmers' Market",event_date:l||n,mention_date:n,aliases:["Farmers' Market","farmers market"],entities:["Farmers' Market"],date_source:l?"explicit":"mention"}),/\bSpring Fling Market\b/i.test(a)&&d.push({memory_class:"personal_event",event_type:"attendance",event_kind:"market",title:"Spring Fling Market",event_date:l||w(a.replace(/\byesterday\b/i,"1 day ago"),n)||n,mention_date:n,aliases:["Spring Fling Market"],entities:["Spring Fling Market"],date_source:l?"explicit":"derived"}),/\bjoined\b.+\b(group|club|community)\b/i.test(a)){const e=function(e){const t=e.match(/["\u201c\u201d]([^"\u201c\u201d]{2,200})["\u201c\u201d]/);if(t&&/\b(group|club|community)\b/i.test(e))return A(t[1]);const n=e.match(/\bgroup called ["\u201c\u201d]?([^"\u201c\u201d]{2,200})["\u201c\u201d]?/i);return n?A(n[1]):null}(a),t=M(a);if(e){const i=l||(null!=t&&n?g(n,-t):null);d.push({memory_class:"personal_event",event_type:"membership",event_kind:"membership",title:e,event_date:i,mention_date:n,aliases:[e,`${e} club`,`${e} group`],entities:j(e),date_source:i?null!=t?"derived":"explicit":"implicit",duration_months:t})}}const c=a.match(/\bI(?:'m| am)\s+(\d{1,3})-year-old\b/i);c&&d.push({memory_class:"personal_profile",event_type:"profile",event_kind:"age",title:"current age",event_date:n,mention_date:n,aliases:["current age","age"],entities:["age"],date_source:"mention",numeric_age:Number(c[1])});const u=a.match(/\bliving in the ([A-Za-z.\s'-]+?) for the past (\d+|a|an|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve) years?\b/i);if(u){const e=y(u[2]),t=A(u[1]);d.push({memory_class:"personal_event",event_type:"relocation",event_kind:"move",title:`moved to ${t}`,event_date:e&&n?v(n,-e):null,mention_date:n,aliases:[`moved to ${t}`,t],entities:[t],date_source:e?"derived":"implicit"})}if(/\bpre-ordered|preordered\b/i.test(a)){const e=F(a,o,s);e&&d.push({memory_class:"personal_event",event_type:"preorder",event_kind:"preorder",title:e,event_phase:"preordered_at",event_date:l,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"implicit",source_span:a})}if(/\b(arrived|delivered|received|finally arrived)\b/i.test(a)){const e=F(a,o,s);e&&d.push({memory_class:"personal_event",event_type:"delivery",event_kind:"delivery",title:e,event_phase:"delivered_at",event_date:l,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"implicit",source_span:a})}if(/\b(bought|purchased|ordered|got a new|got my new|got|booked|book)\b/i.test(a)&&!1===/\bpre-ordered|preordered\b/i.test(a)&&!1===/\bgot back from\b/i.test(a)&&!1===/\bgot around to\b/i.test(a)&&!1===/\bgot a lot of attention\b/i.test(a)&&!1===/\bgot a good deal\b/i.test(a)&&!1===/\bgot everything covered\b/i.test(a)&&!1===/\bgot me thinking\b/i.test(a)&&!1===/\bgot as a gift\b/i.test(a)&&!1===/\bread(?:ing)?\b/i.test(a)&&!1===/\b(serviced|service|repaired|fixed|washed|cleaned|polished|conditioned)\b/i.test(a)){const e=F(a,o,s),t=/\bbook(?:ed)?\b/i.test(a)?function(e,t,n,i){if(n)return n;const a=`${String(i||"")} ${String(e||"")}`,s=a.match(/\b(\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+in\s+advance\b/i),r=a.match(/\b(\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+months?\s+ago\b/i);if(s&&r&&t){const e=y(s[1]),n=y(r[1]);if(e&&n)return g(t,-(e+n))}return null}(a,n,l,i):l;e&&d.push({memory_class:"personal_event",event_type:/\bbook(?:ed)?\b/i.test(a)?"booking":"purchase",event_kind:/\bbook(?:ed)?\b/i.test(a)?"booking":"purchase",title:e,event_phase:/\bbook(?:ed)?\b/i.test(a)?"booked_at":"purchased_at",event_date:t,mention_date:n,aliases:[e],entities:j(e),date_source:t===l?l?"explicit":"implicit":"derived",source_span:a})}if(/\b(serviced|repaired|fixed|maintenance|service|washed|cleaned|polished|conditioned|take care of|took care of)\b/i.test(a)&&!1===/\b(issue|problem|malfunction|failed|not functioning)\b/i.test(a)){const e=N(a,o);e&&d.push({memory_class:"personal_event",event_type:"maintenance",event_kind:"service",title:e,event_phase:"serviced_at",event_date:l,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"implicit",source_span:a})}if(/\b(upgrade(?:d)?|install(?:ed)?)\b/i.test(a)&&/\bpedals?\b/i.test(a)&&/\bbike\b/i.test(a)){const e=/road bike/i.test(a)?"road bike":N(a,o);e&&d.push({memory_class:"personal_event",event_type:"maintenance",event_kind:"service",title:e,event_date:l||n,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"mention"})}if(/\b(issue|problem|malfunction|failed|not functioning|repair shop|breaks down)\b/i.test(a)){const e=function(e,t){let n=e.match(/\bissue with (.+?)(?:\s+on\b|[.!?]|$)/i);if(n&&n[1]){const e=S(n[1]);if(e)return e.replace(/^(?:my\s+)?car['\u2019]s\s+/i,"")}if(n=e.match(/\b(?:noticed\s+)?(?:the\s+)?(.+?)\s+was\s+not\s+functioning(?:\s+correctly)?/i),n&&n[1]){const e=S(n[1]);if(e)return e.replace(/^(?:my\s+)?car['\u2019]s\s+/i,"")}if(n=e.match(/\b(.+?)\s+failed(?:\s+on\b|[.!?]|$)/i),n&&n[1]){const e=S(n[1]);if(e)return e.replace(/^(?:my\s+)?car['\u2019]s\s+/i,"")}return/\bstand mixer\b/i.test(e)?"stand mixer":/\bcoffee maker\b/i.test(e)?"coffee maker":t?S(t):null}(a,o);e&&d.push({memory_class:"personal_event",event_type:"issue",event_kind:"issue",title:e,event_phase:"issue_at",event_date:l,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"implicit",source_span:a})}if(/\bdeploy(?:ed)?\b/i.test(a)){const e=function(e,t){let n=e.match(/\bdeploy(?:ed)?\s+(?:the\s+)?(.+?)(?:\s+on\b|[.!?]|$)/i);return n&&n[1]?A(n[1]):t||null}(a,o);e&&d.push({memory_class:"project_event",event_type:"deployment",event_kind:"deployment",title:e,event_date:l||n,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"mention"})}if(/\b(set up|setup|installed|configured)\b/i.test(a)){const e=F(a,o,s);e&&d.push({memory_class:"personal_event",event_type:"setup",event_kind:"setup",title:e,event_phase:"setup_at",event_date:l,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"implicit",source_span:a})}if(/\bstarted working with\b/i.test(a)){const e=a.match(/\bstarted working with\s+([A-Z][A-Za-z]+|her)\b/i),t=e&&e[1]?/^her$/i.test(e[1])&&/\bRachel\b/.test(i)?"Rachel":A(e[1]):null;t&&d.push({memory_class:"personal_event",event_type:"start",event_kind:"start",title:`working with ${t}`,event_date:l,mention_date:n,aliases:[`working with ${t}`,t],entities:[t],date_source:l?"explicit":"implicit"})}if(/\b(?:bus ride|train ride|took a train|took a bus|taking the train|flew with|with [A-Z][A-Za-z]+ Airlines)\b/i.test(a)){const e=function(e){return/\bflew with ([A-Z][A-Za-z]+(?:\s+[A-Z][A-Za-z]+)*)/i.test(e)?A(e.match(/\bflew with ([A-Z][A-Za-z]+(?:\s+[A-Z][A-Za-z]+)*)/i)[1]):/\bwith ([A-Z][A-Za-z]+ Airlines)\b/i.test(e)?A(e.match(/\bwith ([A-Z][A-Za-z]+ Airlines)\b/i)[1]):/\btrain ride\b/i.test(e)||/\btook a train\b/i.test(e)||/\btaking the train\b/i.test(e)?"train":/\bbus ride\b/i.test(e)||/\btook a bus\b/i.test(e)||/\btaking more .* buses\b/i.test(e)?"bus":null}(a);if(e){const t=/airlines?/i.test(e)?"flight":"transport";d.push({memory_class:"personal_event",event_type:t,event_kind:e.toLowerCase(),title:e,event_date:l||n,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"mention",event_count:"flight"===t?B(a):1})}}if((/\bshared\b/i.test(a)||/\bposted\b/i.test(a)||/\bposting\b/i.test(a))&&/#\w+/i.test(a)){const e=function(e){const t=e.match(/\brecipe for ([a-z][a-z\s'-]+?)(?:\s+using\b|\s+yesterday\b|\s+that\b|[,.!?]|$)/i);if(t&&t[1])return`${A(t[1])} post`;const n=e.match(/#([A-Za-z][A-Za-z0-9]+)/);return n?`#${n[1]}`:null}(a);e&&d.push({memory_class:"personal_event",event_type:"post",event_kind:"social_post",title:e,event_date:l||w(a.replace(/\byesterday\b/i,"1 day ago"),n)||n,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"derived"})}if((/\bparticipated in a social media challenge\b/i.test(a)||/#PlankChallenge/i.test(a))&&d.push({memory_class:"personal_event",event_type:"attendance",event_kind:"challenge",title:"#PlankChallenge",event_date:l||n,mention_date:n,aliases:["#PlankChallenge","PlankChallenge"],entities:["PlankChallenge"],date_source:l?"explicit":"mention"}),/\breading the .* issue of\b/i.test(a)){const e=function(e){const t=e.match(/\breading the ([A-Za-z]+\s+\d{1,2}(?:st|nd|rd|th)? issue of [A-Z][A-Za-z ]+)/i);return t&&t[1]?A(t[1]):null}(a);e&&d.push({memory_class:"personal_event",event_type:"reading",event_kind:"reading",title:e,event_date:n,mention_date:n,aliases:[e],entities:j(e),date_source:"mention"})}if(/\b(?:watched|watching|went to)\b/i.test(a)&&/\b(?:nba game|national championship game|nfl playoffs)\b/i.test(a)){const e=function(e){return/\bnba game\b/i.test(e)?"NBA game at the Staples Center":/\bCollege Football National Championship game\b/i.test(e)?"College Football National Championship game":/\bNFL playoffs\b/i.test(e)?"NFL playoffs":null}(a);e&&d.push({memory_class:"personal_event",event_type:"watch",event_kind:"sports_watch",title:e,event_date:l||n,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"mention"})}if(/\bworking professionally for\b/i.test(a)){const e=M(a);null!=e&&d.push({memory_class:"personal_event",event_type:"career",event_kind:"career_start",title:"professional work",event_date:n?g(n,-e):null,mention_date:n,aliases:["professional work","working professionally"],entities:["professional work"],date_source:"derived",duration_months:e})}if(/\bworking at [A-Z][A-Za-z0-9]+ for\b/i.test(a)||/\bworking at [A-Z][A-Za-z0-9]+\b/i.test(a)){const e=a.match(/\bworking at ([A-Z][A-Za-z0-9]+)\b/i),t=M(a);e&&e[1]&&d.push({memory_class:"personal_event",event_type:"employment",event_kind:"employment_start",title:A(e[1]),event_date:null!=t&&n?g(n,-t):l,mention_date:n,aliases:[A(e[1]),`job at ${A(e[1])}`],entities:[A(e[1])],date_source:null!=t?"derived":l?"explicit":"implicit",duration_months:t})}if(/\b(?:saw|found)\s+a house (?:that i really love|i loved)\b/i.test(a)&&d.push({memory_class:"personal_event",event_type:"discovery",event_kind:"discovery",title:"house I loved",event_date:l,mention_date:n,aliases:["house I loved","house i really love"],entities:["house"],date_source:l?"explicit":"implicit"}),/\bgraduated\b/i.test(a)){const e=a.match(/\b(?:niece|friend|cousin)\s+([A-Z][a-z]+)\b/i)||a.match(/\b([A-Z][a-z]+)'s\b/)||a.match(/\b([A-Z][a-z]+)'s .*graduation ceremony\b/i),t=e&&e[1]?e[1]:null;if(t){const e=l||w(a.replace(/\byesterday\b/i,"1 day ago").replace(/\babout two weeks ago\b/i,"2 weeks ago"),n)||n;d.push({memory_class:"personal_event",event_type:"graduation",event_kind:"graduation",title:t,event_date:e,mention_date:n,aliases:[t,`${t}'s graduation`,`${t} graduation ceremony`],entities:[t],date_source:l?"explicit":"derived"})}}if(/\bgraduation ceremony\b/i.test(a)&&/\b([A-Z][a-z]+)'s\b/.test(a)){const e=a.match(/\b([A-Z][a-z]+)'s\b/)[1],t=l||w(a.replace(/\byesterday\b/i,"1 day ago"),n)||n;d.push({memory_class:"personal_event",event_type:"graduation",event_kind:"graduation",title:e,event_date:t,mention_date:n,aliases:[e,`${e}'s graduation`,`${e} graduation ceremony`],entities:[e],date_source:l?"explicit":"derived"})}if(/\bwalked down the aisle as a bridesmaid at\b/i.test(a)||/\bengagement party\b/i.test(a)||/\bcousin's wedding\b/i.test(a)){const e=D(a);if(e){const t=l||w(a.replace(/\ba week ago\b/i,"1 week ago"),n)||n;d.push({memory_class:"personal_event",event_type:"attendance",event_kind:"life_event",title:e,event_date:t,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"derived"})}}if(/\bstarted watching\b/i.test(a)){const e=P(a);if(e){const t=l||w(a.replace(/\babout a month ago\b/i,"1 month ago"),n);d.push({memory_class:"personal_event",event_type:"start",event_kind:"watch",title:e,event_date:t,mention_date:n,aliases:[e],entities:j(e),date_source:t?"derived":"implicit"})}}if(/\bstarting seeds?\b/i.test(a)&&/\bsince\b/i.test(a)&&/\b(?:tomatoes|marigolds)\b/i.test(a)){const e=l||n,t=[];/\btomatoes\b/i.test(a)&&t.push("tomatoes"),/\bmarigolds?\b/i.test(a)&&t.push("marigolds"),t.forEach(t=>{d.push({memory_class:"personal_event",event_type:"start",event_kind:"seed_start",title:t,event_date:e,mention_date:n,aliases:[t,`${t} seeds`],entities:[t],date_source:l?"explicit":"mention"})})}if(/\bplanted\b/i.test(a)&&/\btomato saplings?\b/i.test(a)&&d.push({memory_class:"personal_event",event_type:"start",event_kind:"planting",title:"tomato saplings",event_date:l||n,mention_date:n,aliases:["tomato saplings","tomatoes"],entities:["tomato saplings","tomatoes"],date_source:l?"explicit":"mention"}),/\bfinished\b/i.test(a)&&/["\u201c\u201d]/.test(a)){const e=P(a),t=M(a);e&&d.push({memory_class:"personal_event",event_type:"completion",event_kind:"completion",title:e,event_date:l||w(a.replace(/\ba few days before\b/i,"3 days ago"),n)||n,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"mention",duration_months:t})}if(/\b(started|began)\b/i.test(a)){const e=F(a,o,s);if(e){const t=a.match(/\bfinished(?: the entire season)? in (?:just )?(\d+)\s+days\b/i),i=!l&&t&&n?f(n,-Number(t[1])):null;d.push({memory_class:"personal_event",event_type:"start",event_kind:"start",title:e,event_date:l||i,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":i?"derived":"implicit"})}}if(/\bwatch(?:ed|ing)\s+stand-up\b/i.test(a)||/\binto stand-up lately\b/i.test(a)){const e=l||w(a.replace(/\babout (\d+)\s+months? ago\b/i,"$1 months ago"),n)||null;d.push({memory_class:"personal_event",event_type:"start",event_kind:"habit",title:"stand-up comedy specials",event_date:e,mention_date:n,aliases:["stand-up comedy specials","stand-up comedy"],entities:["stand-up comedy specials"],date_source:e?"derived":"implicit"})}if(/\b(completed|finished|wrapped up|fixed|trimmed)\b/i.test(a)){const e=/\b(completed|finished|wrapped up)\b/i.test(a)?F(a,o,s):function(e,t){if(/\bfixed\b/i.test(e)&&/\bfence\b/i.test(e))return"fixing the fence";if(/\btrim(?:med)?\b/i.test(e)&&/\bhooves?\b/i.test(e))return"trimming the goats' hooves";let n=e.match(/\b(?:fixed|repair(?:ed)?|trimmed|cleaned|polished|conditioned)\s+(?:that\s+|the\s+|my\s+)?(.+?)(?:\s+on\b|\s+ago\b|[.!?]|$)/i);return n&&n[1]?S(n[1]):(n=e.match(/\b(?:did|finished|completed)\s+(?:the\s+)?(.+?)(?:\s+on\b|\s+ago\b|[.!?]|$)/i),n&&n[1]?S(n[1]):t?S(t):null)}(a,o);e&&d.push({memory_class:"personal_event",event_type:"completion",event_kind:"completion",title:e,event_date:l,mention_date:n,aliases:[e],entities:j(e),date_source:l?"explicit":"implicit"})}if(!d.length){const e=function(e){if(/\bairbnb\b/i.test(String(e||"")))return"Airbnb in San Francisco";let t=String(e||"").match(/["\u201c\u201d]([^"\u201c\u201d]{2,200})["\u201c\u201d]/);return t&&t[1]?A(t[1]):(t=String(e||"").match(/,\s*([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4})\b/),t&&t[1]?A(t[1]):(t=String(e||"").match(/\b([A-Z][A-Za-z0-9]+(?:\s+[A-Z0-9][A-Za-z0-9]+){1,4}\s+\d{1,4})\b/),t&&t[1]?A(t[1]):null))}(a);e&&(o=e)}d.forEach((n,i)=>{r.push(function(e,n,i){const a=$(i.source_span||i.title,i.mention_date||_(e.ts),i.referenced_at||i.event_date||null);return{event_id:`evt_${String(e.id).padStart(6,"0")}_${String(n+1).padStart(2,"0")}`,source_record_id:e.id,source_message_key:`record:${e.id}`,title:i.title,aliases:i.aliases||[i.title],entities:i.entities||j(i.title),memory_class:i.memory_class||"personal_event",event_type:i.event_type||"other",event_kind:i.event_kind||i.event_type||"other",event_phase:i.event_phase||null,event_date:i.event_date||null,mention_date:i.mention_date||_(e.ts),observed_at:i.observed_at||a.observed_at,referenced_at:i.referenced_at||a.referenced_at,relative_offset:i.relative_offset||a.relative_offset,date_source:i.date_source||"implicit",confidence:"number"==typeof i.confidence?i.confidence:.9,raw:e.content,ts:e.ts,workspace:e.workspace||null,repo:e.repo||null,source:e.source||null,epoch_day:t(i.referenced_at||i.event_date||i.mention_date||_(e.ts)),time_granularity:i.time_granularity||a.time_granularity,temporal_confidence:"number"==typeof i.temporal_confidence?i.temporal_confidence:a.temporal_confidence,source_span:i.source_span||a.source_span,duration_months:"number"==typeof i.duration_months?i.duration_months:null,numeric_age:"number"==typeof i.numeric_age?i.numeric_age:null,event_count:"number"==typeof i.event_count?i.event_count:1}}(e,r.length+i,n)),o=n.title})}),r}function Q(e){return n((e||[]).flatMap(e=>H(e)))}function G(e){return b(Q(e))}function W(t){const n=function(t){const n=String(t||""),i=Array.from(n.matchAll(/\b(january|february|march|april|may|june|july|august|september|october|november|december)\b/gi)).map(e=>e[1]);return{raw:n,normalized:e(n),quoted:L(n),trailingPair:E(n),betweenPair:U(n),month:(n.match(/\bin\s+(january|february|march|april|may|june|july|august|september|october|november|december)\b/i)||[null,null])[1],months:i,people:I(n)}}(t);for(const e of R){if(!e.when(n))continue;const t=e.build(n);if(t)return{...t,raw:n.raw}}return null}function Y(t,n,a={}){const s=e(n),r={...a};return(/\bteam meeting\b/.test(s)||/\bmeeting\b/.test(s))&&(r.preferEventPhase="scheduled"),/\bprepared|preparing\b/.test(s)&&(r.preferEventPhase="preparation"),/\b(workshop|webinar|meetup|service|mass|festival|event|party)\b/.test(s)?(r.preferEventPhase="attended_at",r.preferType="attendance"):/\b(using|working|watching|reading|learning|taking|member|started|began|use)\b/.test(s)&&(r.preferType=r.preferType||"start"),i(t,n,r)}function J(b,m,h={}){if(!b)return{ok:!1,kind:null,value:null,evidence:[],warnings:["Unsupported temporal query."]};if("first_after"===b.kind){const e=a(m,{type:b.anchorType}),t=r(m,{anchor:e,targetType:b.targetType});return{ok:!!t,kind:b.kind,value:t?t.title:null,evidence:[e,t].filter(Boolean),warnings:[]}}if("which_first"===b.kind){const e=b.month?x(m,b.month):m,t=Y(e,b.left,{semantics:b.semantics}),n=Y(e,b.right,{semantics:b.semantics});if(!t||!n)return{ok:!1,kind:b.kind,value:null,evidence:[t,n].filter(Boolean),warnings:["Missing comparison operands."]};const i=t.epoch_day<=n.epoch_day?t:n;return{ok:!0,kind:b.kind,value:i.title,evidence:[t,n],warnings:[]}}if("which_last"===b.kind){const e=Y(m,b.left,{preferLatest:!0}),t=Y(m,b.right,{preferLatest:!0});if(!e||!t)return{ok:!1,kind:b.kind,value:null,evidence:[e,t].filter(Boolean),warnings:["Missing comparison operands."]};const n=e.epoch_day>=t.epoch_day?e:t;return{ok:!0,kind:b.kind,value:n.title,evidence:[e,t],warnings:[]}}if("which_in_period"===b.kind){const t="last_month"===b.period?function(e,t){const n=new Date(t&&t.getTime?t.getTime():t||Date.now()),i=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth()-1,1)),a=new Date(Date.UTC(n.getUTCFullYear(),n.getUTCMonth(),0)),s=i.toISOString().slice(0,10),r=a.toISOString().slice(0,10);return(e||[]).filter(e=>{const t=e.referenced_at||e.event_date||e.mention_date;return!!t&&t>=s&&t<=r})}(m,h.now||new Date):m,n=s(t.filter(t=>function(t,n){if(!n||!t)return!0;const i=e(`${t.title} ${(t.aliases||[]).join(" ")} ${(t.entities||[]).join(" ")}`);return"footwear"!==n||/\b(shoe|shoes|sneaker|sneakers|boot|boots|dress shoes)\b/.test(i)}(t,b.objectClass)),{type:b.type,kind:b.kindFilter});return{ok:!!n,kind:b.kind,value:n?n.title:null,evidence:n?[n]:[],warnings:n?[]:["Missing in-period event."]}}if("which_in_relative_period"===b.kind){const e=h.now?_(h.now.getTime?h.now.getTime():Date.parse(h.now)):_(Date.now()),i=t(e),a=n(m).filter(e=>null!=e.epoch_day).filter(e=>!b.type||e.event_type===b.type).filter(e=>e.epoch_day>=i-b.relativeDays&&e.epoch_day<=i).sort((e,t)=>t.epoch_day-e.epoch_day)[0]||null;return{ok:!!a,kind:b.kind,value:a?a.title:null,evidence:a?[a]:[],warnings:a?[]:["Missing relative-period event."]}}if("date_of_first"===b.kind){const e=b.month?x(m,b.month):m,t=a(e,{type:b.type,kind:b.kindFilter});return{ok:!!t,kind:b.kind,value:t?q(t.event_date):null,evidence:t?[t]:[],warnings:t?[]:["Missing dated event."]}}if("days_between"===b.kind){const e=Y(m,b.left),t=Y(m,b.right),n=d(e,t),i="forward"===b.direction?null==n?null:Math.max(0,n):o(e,t);return{ok:null!=i,kind:b.kind,value:i,evidence:[e,t].filter(Boolean),warnings:null==i?["Missing date-qualified operands."]:[]}}if("days_before_order_for_event"===b.kind){const t=i(m,b.right,{excludeType:"purchase"}),a=n(m).filter(e=>"purchase"===e.event_type).filter(t=>e(t.raw||"").includes(e(b.right))||e(t.title).includes("photo album")||e(t.raw||"").includes("photo album")).sort((e,t)=>(e.epoch_day??Number.MAX_SAFE_INTEGER)-(t.epoch_day??Number.MAX_SAFE_INTEGER))[0]||null,s=d(a,t),r=null==s?null:Math.max(0,s);return{ok:null!=r,kind:b.kind,value:r,evidence:[a,t].filter(Boolean),warnings:null==r?["Missing date-qualified operands."]:[]}}if("weeks_between"===b.kind){const e=Y(m,b.left),t=Y(m,b.right),n=o(e,t),i=null==n?null:Math.round(n/7);return{ok:null!=i,kind:b.kind,value:null!=i?`${i} weeks`:null,evidence:[e,t].filter(Boolean),warnings:null==i?["Missing date-qualified operands."]:[]}}if("days_ago"===b.kind||"weeks_ago"===b.kind){const e=Y(m,b.left,{preferLatest:!0}),n=h.now?_(h.now.getTime?h.now.getTime():Date.parse(h.now)):_(Date.now()),i=n?{event_date:n,epoch_day:t(n)}:null,a=o(e,i);return null==a?{ok:!1,kind:b.kind,value:null,evidence:[e].filter(Boolean),warnings:["Missing dated reference event."]}:"weeks_ago"===b.kind?{ok:!0,kind:b.kind,value:Math.round(a/7),evidence:[e],warnings:[]}:{ok:!0,kind:b.kind,value:`${a} days ago`,evidence:[e],warnings:[]}}if("months_since"===b.kind){const e=Y(m,b.left,{type:"booking",preferEventPhase:"booked_at"})||Y(m,b.left,{type:"purchase"})||Y(m,b.left,{semantics:"acquired"}),t=h.now?_(h.now.getTime?h.now.getTime():Date.parse(h.now)):_(Date.now()),n=l(e,t);return{ok:null!=n,kind:b.kind,value:n,evidence:[e].filter(Boolean),warnings:null==n?["Missing month-qualified reference event."]:[]}}if("months_between"===b.kind){const e=Y(m,b.left,{semantics:"acquired"}),t=Y(m,b.right,{semantics:"acquired"}),n=c(e,t);return{ok:null!=n,kind:b.kind,value:n,evidence:[e,t].filter(Boolean),warnings:null==n?["Missing month-qualified operands."]:[]}}if("count_before"===b.kind){const e=Y(m,b.pivot),t=u(m,e,{type:b.type,kind:b.kindFilter});return{ok:null!=t,kind:b.kind,value:t,evidence:[e].filter(Boolean),warnings:null==t?["Missing pivot event."]:[]}}if("most_by_entity_in_period"===b.kind){const e=Z(m,b.months),t=new Map;for(const n of e)b.type&&n.event_type!==b.type||t.set(n.title,(t.get(n.title)||0)+(n.event_count||1));const n=Array.from(t.entries()).sort((e,t)=>t[1]-e[1]||e[0].localeCompare(t[0]))[0];return{ok:!!n,kind:b.kind,value:n?n[0]:null,evidence:e.filter(e=>n&&e.title===n[0]),warnings:n?[]:["Missing grouped events."]}}if("age_at_event"===b.kind){const e=Y(m,b.left),t=n(m).find(e=>"age"===e.event_kind&&"number"==typeof e.numeric_age);if(!e||!t||null==e.epoch_day||null==t.epoch_day)return{ok:!1,kind:b.kind,value:null,evidence:[e,t].filter(Boolean),warnings:["Missing age-qualified operands."]};const i=Math.round((t.epoch_day-e.epoch_day)/365.25);return{ok:!0,kind:b.kind,value:t.numeric_age-i,evidence:[e,t],warnings:[]}}if("ordered_sequence"===b.kind){const e=b.months&&b.months.length?Z(m,b.months):m,t=(b.labels||[]).map(t=>Y(e,t)).filter(Boolean);if(t.length!==(b.labels||[]).length)return{ok:!1,kind:b.kind,value:null,evidence:t,warnings:["Missing ordered operands."]};const n=[...t].sort((e,t)=>e.epoch_day-t.epoch_day);let i=n.map(e=>e.title).join(", ");return 3===n.length&&(i=`First, ${n[0].title}, then ${n[1].title}, and finally, ${n[2].title}.`),{ok:!0,kind:b.kind,value:i,evidence:n,warnings:[]}}if("relative_period_match"===b.kind){const e=h.now?_(h.now.getTime?h.now.getTime():Date.parse(h.now)):_(Date.now()),i=t(f(e,-b.relativeDays)),a=n(m).filter(e=>null!=e.epoch_day).filter(e=>!b.type||e.event_type===b.type).sort((e,t)=>Math.abs(e.epoch_day-i)-Math.abs(t.epoch_day-i))[0]||null;return{ok:!!a,kind:b.kind,value:a?a.title:null,evidence:a?[a]:[],warnings:a?[]:["Missing relative-period event."]}}if("duration_before_event"===b.kind){const e=Y(m,b.left,{preferType:"start"})||Y(m,b.left),t=Y(m,b.right);if(!e||!t||null==e.epoch_day||null==t.epoch_day)return{ok:!1,kind:b.kind,value:null,evidence:[e,t].filter(Boolean),warnings:["Missing duration operands."]};const n=Math.abs(t.epoch_day-e.epoch_day);if("weeks"===b.units){const i=Math.round(n/7*2)/2;return{ok:!0,kind:b.kind,value:T(i),evidence:[e,t],warnings:[]}}const i=t.referenced_at||t.event_date||t.mention_date,a=l(e,i);return{ok:null!=a,kind:b.kind,value:C(a),evidence:[e,t],warnings:null==a?["Missing month-qualified operands."]:[]}}if("combined_duration"===b.kind){const e=Y(m,b.left),t=Y(m,b.right),n=(e&&e.duration_months||0)+(t&&t.duration_months||0);return{ok:n>0,kind:b.kind,value:"weeks"===b.units?T(4*n):C(n),evidence:[e,t].filter(Boolean),warnings:n>0?[]:["Missing duration-qualified operands."]}}return{ok:!1,kind:b.kind,value:null,evidence:[],warnings:["Unsupported temporal query."]}}module.exports={extractEventsFromRecord:H,materializeEvents:Q,materializeEventIndex:G,parseTemporalQuery:W,executeTemporalQuery:J,eventCandidatesForQuery:m,answerTemporalQuery(e,t,i={}){const a=G(t),s=m(a,e),r=s.length?n([...s,...a.events]):a.events,o=W(e);return{...J(o,r,i),parsed_query:o,events:r,index_stats:a.stats}}};