@mostlyrightmd/markets 0.1.0-rc.7

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.
@@ -0,0 +1,893 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/polymarket/index.ts
21
+ var polymarket_exports = {};
22
+ __export(polymarket_exports, {
23
+ DEFERRED_STATIONS: () => DEFERRED_STATIONS,
24
+ EVENT_ID_RE: () => EVENT_ID_RE,
25
+ MAX_DESCRIPTION_BYTES: () => MAX_DESCRIPTION_BYTES,
26
+ NETLOC_TO_RESOLUTION_TYPE: () => NETLOC_TO_RESOLUTION_TYPE,
27
+ POLYMARKET_KNOWN_WRONG_STATIONS: () => POLYMARKET_KNOWN_WRONG_STATIONS,
28
+ POLYMARKET_RESOLUTION_SOURCE_TYPES: () => POLYMARKET_RESOLUTION_SOURCE_TYPES,
29
+ PayloadTooLargeError: () => PayloadTooLargeError,
30
+ PolymarketEventError: () => PolymarketEventError,
31
+ PolymarketSettlementError: () => PolymarketSettlementError,
32
+ RESOLUTION_SOURCE_ALLOWLIST: () => RESOLUTION_SOURCE_ALLOWLIST,
33
+ SETTLE_DELAY_HOURS: () => SETTLE_DELAY_HOURS,
34
+ SLUG_DATE_RE: () => SLUG_DATE_RE,
35
+ TooEarlyToSettleError: () => TooEarlyToSettleError,
36
+ deriveCity: () => deriveCity,
37
+ detectMarketMeasure: () => detectMarketMeasure,
38
+ extractIcaoFromResolutionSource: () => extractIcaoFromResolutionSource,
39
+ extractResolutionSourceType: () => extractResolutionSourceType,
40
+ fetchEventById: () => fetchEventById,
41
+ fetchEvents: () => fetchEvents,
42
+ polymarketDiscover: () => polymarketDiscover,
43
+ polymarketSettle: () => polymarketSettle,
44
+ polymarketSettleById: () => polymarketSettleById,
45
+ resolveStationForEvent: () => resolveStationForEvent,
46
+ settlementDateFromSlug: () => settlementDateFromSlug,
47
+ validateDescription: () => validateDescription
48
+ });
49
+ module.exports = __toCommonJS(polymarket_exports);
50
+
51
+ // src/polymarket/client.ts
52
+ var import_core = require("@mostlyrightmd/core");
53
+ var GAMMA_BASE = "https://gamma-api.polymarket.com";
54
+ var PAGE_SIZE = 100;
55
+ var MAX_EVENTS = 1e4;
56
+ var DEFAULT_USER_AGENT = "mostlyright-ts/0.1.0 (+https://github.com/Tarabcak/mostlyright)";
57
+ var DEFAULT_SLEEP_BETWEEN_MS = 200;
58
+ var RETRY_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
59
+ async function sleep(ms, signal) {
60
+ if (ms <= 0) return;
61
+ return new Promise((resolve, reject) => {
62
+ const timer = setTimeout(() => {
63
+ cleanup();
64
+ resolve();
65
+ }, ms);
66
+ const onAbort = () => {
67
+ cleanup();
68
+ reject(signal?.reason ?? new DOMException("Aborted", "AbortError"));
69
+ };
70
+ function cleanup() {
71
+ clearTimeout(timer);
72
+ if (signal !== void 0) signal.removeEventListener("abort", onAbort);
73
+ }
74
+ if (signal !== void 0) {
75
+ if (signal.aborted) {
76
+ cleanup();
77
+ reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
78
+ return;
79
+ }
80
+ signal.addEventListener("abort", onAbort, { once: true });
81
+ }
82
+ });
83
+ }
84
+ async function fetchEvents(opts = {}) {
85
+ const sleepMs = opts.sleepBetweenMs ?? DEFAULT_SLEEP_BETWEEN_MS;
86
+ const seen = /* @__PURE__ */ new Set();
87
+ const out = [];
88
+ for (let offset = 0; offset < MAX_EVENTS; offset += PAGE_SIZE) {
89
+ const url = `${GAMMA_BASE}/events?limit=${PAGE_SIZE}&offset=${offset}&active=true&closed=false`;
90
+ const fetchOpts = {
91
+ headers: { Accept: "application/json" },
92
+ userAgent: DEFAULT_USER_AGENT,
93
+ retryStatuses: RETRY_STATUSES
94
+ };
95
+ if (opts.signal !== void 0) fetchOpts.signal = opts.signal;
96
+ const customInit = {
97
+ headers: { ...fetchOpts.headers, "User-Agent": DEFAULT_USER_AGENT }
98
+ };
99
+ if (opts.signal !== void 0) customInit.signal = opts.signal;
100
+ const resp = await (opts.fetchFn !== void 0 ? (
101
+ // Custom fetch override (tests). Bypass retry — caller mock-controls statuses.
102
+ opts.fetchFn(url, customInit)
103
+ ) : (0, import_core.fetchWithRetry)(url, fetchOpts));
104
+ if (!resp.ok) {
105
+ throw new Error(`Gamma API returned ${resp.status} ${resp.statusText} for offset=${offset}`);
106
+ }
107
+ const raw = await resp.json();
108
+ let page;
109
+ if (Array.isArray(raw)) {
110
+ page = raw;
111
+ } else if (raw !== null && typeof raw === "object" && Array.isArray(raw.data)) {
112
+ page = raw.data;
113
+ } else {
114
+ throw new Error(
115
+ `Gamma API returned an unexpected page shape at offset=${offset} (expected array or {data: array}, got ${raw === null ? "null" : typeof raw}). The upstream contract may have changed; check https://docs.polymarket.com/.`
116
+ );
117
+ }
118
+ if (page.length === 0) break;
119
+ for (const ev of page) {
120
+ const slug = typeof ev.slug === "string" ? ev.slug : null;
121
+ if (slug === null || seen.has(slug)) continue;
122
+ seen.add(slug);
123
+ out.push(ev);
124
+ }
125
+ if (page.length < PAGE_SIZE) break;
126
+ if (sleepMs > 0 && offset + PAGE_SIZE < MAX_EVENTS) {
127
+ await sleep(sleepMs, opts.signal);
128
+ }
129
+ }
130
+ return out;
131
+ }
132
+ async function fetchEventById(eventId, opts = {}) {
133
+ const url = `${GAMMA_BASE}/events/${encodeURIComponent(eventId)}`;
134
+ const fetchOpts = {
135
+ headers: { Accept: "application/json" },
136
+ userAgent: DEFAULT_USER_AGENT,
137
+ retryStatuses: RETRY_STATUSES
138
+ };
139
+ if (opts.signal !== void 0) fetchOpts.signal = opts.signal;
140
+ const customInit = {
141
+ headers: { ...fetchOpts.headers, "User-Agent": DEFAULT_USER_AGENT }
142
+ };
143
+ if (opts.signal !== void 0) customInit.signal = opts.signal;
144
+ let resp;
145
+ try {
146
+ resp = opts.fetchFn !== void 0 ? await opts.fetchFn(url, customInit) : await (0, import_core.fetchWithRetry)(url, fetchOpts);
147
+ } catch (err) {
148
+ if (err instanceof import_core.NotFoundError) return null;
149
+ throw err;
150
+ }
151
+ if (resp.status === 404) return null;
152
+ if (!resp.ok) {
153
+ throw new Error(`Gamma API returned ${resp.status} ${resp.statusText} for event=${eventId}`);
154
+ }
155
+ return await resp.json();
156
+ }
157
+
158
+ // src/polymarket/errors.ts
159
+ var import_core2 = require("@mostlyrightmd/core");
160
+ var PolymarketEventError = class extends import_core2.TradewindsError {
161
+ constructor(message) {
162
+ super(message);
163
+ this.name = "PolymarketEventError";
164
+ }
165
+ static defaultErrorCode = "POLYMARKET_EVENT_INVALID";
166
+ };
167
+ var PolymarketSettlementError = class extends import_core2.TradewindsError {
168
+ constructor(message) {
169
+ super(message);
170
+ this.name = "PolymarketSettlementError";
171
+ }
172
+ static defaultErrorCode = "POLYMARKET_SETTLEMENT_FAILED";
173
+ };
174
+ var TooEarlyToSettleError = class extends import_core2.TradewindsError {
175
+ waitHours;
176
+ resolutionSourceType;
177
+ constructor(message, opts) {
178
+ super(message);
179
+ this.name = "TooEarlyToSettleError";
180
+ this.waitHours = opts.waitHours;
181
+ this.resolutionSourceType = opts.resolutionSourceType;
182
+ }
183
+ static defaultErrorCode = "POLYMARKET_TOO_EARLY";
184
+ payload() {
185
+ return {
186
+ ...super.payload(),
187
+ wait_hours: this.waitHours,
188
+ resolution_source_type: this.resolutionSourceType
189
+ };
190
+ }
191
+ };
192
+ var PayloadTooLargeError = class extends import_core2.TradewindsError {
193
+ constructor(message) {
194
+ super(message);
195
+ this.name = "PayloadTooLargeError";
196
+ }
197
+ static defaultErrorCode = "POLYMARKET_PAYLOAD_TOO_LARGE";
198
+ };
199
+
200
+ // src/polymarket/types.ts
201
+ var RESOLUTION_SOURCE_ALLOWLIST = /* @__PURE__ */ new Set([
202
+ "wunderground.com",
203
+ "www.wunderground.com",
204
+ "weather.gov",
205
+ "www.weather.gov"
206
+ ]);
207
+ var NETLOC_TO_RESOLUTION_TYPE = Object.freeze({
208
+ "wunderground.com": "wunderground",
209
+ "www.wunderground.com": "wunderground",
210
+ "weather.gov": "noaa_wrh",
211
+ "www.weather.gov": "noaa_wrh"
212
+ });
213
+ var POLYMARKET_RESOLUTION_SOURCE_TYPES = Object.freeze([
214
+ "wunderground",
215
+ "noaa_wrh",
216
+ "hko",
217
+ "cwa",
218
+ "other"
219
+ ]);
220
+ var EVENT_ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
221
+ var MAX_DESCRIPTION_BYTES = 16 * 1024;
222
+ var SETTLE_DELAY_HOURS = Object.freeze({
223
+ wunderground: 6,
224
+ noaa_wrh: 4,
225
+ other: 24
226
+ });
227
+ var SLUG_DATE_RE = /(\d{4})-(\d{2})-(\d{2})/g;
228
+ var DEFERRED_STATIONS = /* @__PURE__ */ new Set(["VHHH", "RCTP"]);
229
+
230
+ // src/polymarket/description.ts
231
+ var URL_RE = /https?:\/\/[^\s<>"')]{1,2048}/g;
232
+ function validateDescription(description) {
233
+ if (typeof description !== "string") {
234
+ throw new PolymarketEventError(
235
+ `description must be a string; got ${description === null ? "null" : typeof description}`
236
+ );
237
+ }
238
+ const byteLen = new TextEncoder().encode(description).length;
239
+ if (byteLen > MAX_DESCRIPTION_BYTES) {
240
+ throw new PayloadTooLargeError(
241
+ `description exceeds 16 KB cap (got ${byteLen} bytes; oversized payloads indicate hostile input)`
242
+ );
243
+ }
244
+ for (const match of description.matchAll(URL_RE)) {
245
+ const url = match[0];
246
+ let netloc;
247
+ try {
248
+ netloc = new URL(url).host.toLowerCase();
249
+ } catch {
250
+ throw new PolymarketEventError(`unparseable resolution-source URL ${JSON.stringify(url)}`);
251
+ }
252
+ if (netloc.length > 0 && !RESOLUTION_SOURCE_ALLOWLIST.has(netloc)) {
253
+ throw new PolymarketEventError(
254
+ `resolution-source URL ${JSON.stringify(url)} not in allowlist [${[
255
+ ...RESOLUTION_SOURCE_ALLOWLIST
256
+ ].sort().join(", ")}]`
257
+ );
258
+ }
259
+ }
260
+ }
261
+ function extractResolutionSourceType(description) {
262
+ for (const match of description.matchAll(URL_RE)) {
263
+ const url = match[0];
264
+ let netloc;
265
+ try {
266
+ netloc = new URL(url).host.toLowerCase();
267
+ } catch {
268
+ continue;
269
+ }
270
+ const mapped = NETLOC_TO_RESOLUTION_TYPE[netloc];
271
+ if (mapped !== void 0) {
272
+ return mapped;
273
+ }
274
+ }
275
+ return "other";
276
+ }
277
+
278
+ // src/polymarket/discover.ts
279
+ var import_core4 = require("@mostlyrightmd/core");
280
+
281
+ // src/polymarket/resolver.ts
282
+ var import_core3 = require("@mostlyrightmd/core");
283
+
284
+ // src/data/generated/polymarket-city-stations.ts
285
+ var POLYMARKET_CITY_STATIONS = {
286
+ amsterdam: {
287
+ default: "EHAM"
288
+ },
289
+ atlanta: {
290
+ default: "KATL"
291
+ },
292
+ auckland: {
293
+ default: "NZAA"
294
+ },
295
+ austin: {
296
+ default: "KAUS"
297
+ },
298
+ bangkok: {
299
+ default: "VTBS"
300
+ },
301
+ barcelona: {
302
+ default: "LEBL"
303
+ },
304
+ beijing: {
305
+ default: "ZBAA"
306
+ },
307
+ berlin: {
308
+ default: "EDDB"
309
+ },
310
+ boston: {
311
+ default: "KBOS"
312
+ },
313
+ brisbane: {
314
+ default: "YBBN"
315
+ },
316
+ buenos_aires: {
317
+ default: "SAEZ"
318
+ },
319
+ chicago: {
320
+ default: "KORD",
321
+ high: "KORD",
322
+ low: "KORD"
323
+ },
324
+ copenhagen: {
325
+ default: "EKCH"
326
+ },
327
+ dallas: {
328
+ default: "KDFW"
329
+ },
330
+ delhi: {
331
+ default: "VIDP"
332
+ },
333
+ denver: {
334
+ default: "KDEN"
335
+ },
336
+ detroit: {
337
+ default: "KDTW"
338
+ },
339
+ doha: {
340
+ default: "OTHH"
341
+ },
342
+ dubai: {
343
+ default: "OMDB"
344
+ },
345
+ frankfurt: {
346
+ default: "EDDF"
347
+ },
348
+ helsinki: {
349
+ default: "EFHK"
350
+ },
351
+ hong_kong: {
352
+ default: "VHHH",
353
+ high: "VHHH",
354
+ low: "VHHH"
355
+ },
356
+ houston: {
357
+ default: "KIAH"
358
+ },
359
+ london: {
360
+ default: "EGLL"
361
+ },
362
+ london_gatwick: {
363
+ default: "EGKK"
364
+ },
365
+ los_angeles: {
366
+ default: "KLAX",
367
+ high: "KLAX",
368
+ low: "KLAX"
369
+ },
370
+ madrid: {
371
+ default: "LEMD"
372
+ },
373
+ melbourne: {
374
+ default: "YMML"
375
+ },
376
+ miami: {
377
+ default: "KMIA"
378
+ },
379
+ milan: {
380
+ default: "LIMC"
381
+ },
382
+ minneapolis: {
383
+ default: "KMSP"
384
+ },
385
+ moscow: {
386
+ default: "UUEE"
387
+ },
388
+ mumbai: {
389
+ default: "VABB"
390
+ },
391
+ munich: {
392
+ default: "EDDM"
393
+ },
394
+ nyc: {
395
+ default: "KLGA",
396
+ high: "KLGA",
397
+ low: "KLGA"
398
+ },
399
+ paris: {
400
+ default: "LFPG",
401
+ high: "LFPG",
402
+ low: "LFPB"
403
+ },
404
+ paris_orly: {
405
+ default: "LFPO"
406
+ },
407
+ philadelphia: {
408
+ default: "KPHL"
409
+ },
410
+ phoenix: {
411
+ default: "KPHX"
412
+ },
413
+ riyadh: {
414
+ default: "OERK"
415
+ },
416
+ rome: {
417
+ default: "LIRF"
418
+ },
419
+ san_francisco: {
420
+ default: "KSFO"
421
+ },
422
+ sao_paulo: {
423
+ default: "SBGR"
424
+ },
425
+ seattle: {
426
+ default: "KSEA"
427
+ },
428
+ seoul: {
429
+ default: "RKSI"
430
+ },
431
+ shanghai: {
432
+ default: "ZSPD"
433
+ },
434
+ singapore: {
435
+ default: "WSSS"
436
+ },
437
+ stockholm: {
438
+ default: "ESSA"
439
+ },
440
+ sydney: {
441
+ default: "YSSY"
442
+ },
443
+ taipei: {
444
+ default: "RCTP"
445
+ },
446
+ tokyo: {
447
+ default: "RJTT",
448
+ high: "RJTT",
449
+ low: "RJTT"
450
+ },
451
+ tokyo_narita: {
452
+ default: "RJAA"
453
+ },
454
+ vienna: {
455
+ default: "LOWW"
456
+ },
457
+ warsaw: {
458
+ default: "EPWA"
459
+ },
460
+ washington_dc: {
461
+ default: "KDCA"
462
+ },
463
+ wellington: {
464
+ default: "NZWN"
465
+ },
466
+ zurich: {
467
+ default: "LSZH"
468
+ }
469
+ };
470
+
471
+ // src/polymarket/resolver.ts
472
+ function detectMarketMeasure(event) {
473
+ const text = [event.title, event.slug, event.name].filter((v) => typeof v === "string").join(" ").toLowerCase();
474
+ const hasHigh = /\b(highest|high|hottest|warmest|max(?:imum)?)\b/.test(text);
475
+ const hasLow = /\b(lowest|low|coldest|coolest|min(?:imum)?)\b/.test(text);
476
+ if (hasLow && !hasHigh) return "low";
477
+ if (hasHigh && !hasLow) return "high";
478
+ return "default";
479
+ }
480
+ var CITY_KEYS_SORTED = Object.freeze(
481
+ Object.keys(POLYMARKET_CITY_STATIONS).sort((a, b) => b.length - a.length)
482
+ );
483
+ function deriveCity(event) {
484
+ const parts = [];
485
+ const slug = typeof event.slug === "string" ? event.slug.toLowerCase() : "";
486
+ const title = typeof event.title === "string" ? event.title.toLowerCase() : "";
487
+ parts.push(slug, title);
488
+ const tags = event.tags;
489
+ if (Array.isArray(tags)) {
490
+ for (const tag of tags) {
491
+ if (typeof tag === "string") parts.push(tag.toLowerCase());
492
+ else if (tag !== null && typeof tag === "object") {
493
+ const label = tag.label ?? tag.slug;
494
+ if (typeof label === "string") parts.push(label.toLowerCase());
495
+ }
496
+ }
497
+ }
498
+ const haystack = ` ${parts.join(" ")} `;
499
+ for (const key of CITY_KEYS_SORTED) {
500
+ const needles = [key, key.replace(/_/g, "-"), key.replace(/_/g, " ")];
501
+ for (const n of needles) {
502
+ if (n.length === 0) continue;
503
+ const escaped = n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
504
+ const re = new RegExp(`(^|[^A-Za-z0-9])${escaped}(?=[^A-Za-z0-9]|$)`);
505
+ if (re.test(haystack)) return key;
506
+ }
507
+ }
508
+ return null;
509
+ }
510
+ var WUNDERGROUND_ICAO_RE = /https?:\/\/(?:www\.)?wunderground\.com\/(?:dashboard\/)?(?:pws|history\/daily|history\/airport|weather-station|cat\/forecasts)\/(?:[a-z0-9-]+\/)*(K[A-Z]{3})(?![A-Za-z0-9_-])/g;
511
+ function extractIcaoFromResolutionSource(text) {
512
+ if (typeof text !== "string" || text.length === 0) return null;
513
+ const matches = [...text.matchAll(WUNDERGROUND_ICAO_RE)];
514
+ if (matches.length === 0) return null;
515
+ const unique = /* @__PURE__ */ new Set();
516
+ for (const m of matches) {
517
+ if (m[1] !== void 0) unique.add(m[1].toUpperCase());
518
+ }
519
+ if (unique.size !== 1) return null;
520
+ return [...unique][0] ?? null;
521
+ }
522
+ function resolveStationForEvent(event, marketMeasure) {
523
+ let cityKey = null;
524
+ const explicit = event.city;
525
+ if (typeof explicit === "string") {
526
+ const low = explicit.toLowerCase();
527
+ if (Object.prototype.hasOwnProperty.call(POLYMARKET_CITY_STATIONS, low)) {
528
+ cityKey = low;
529
+ }
530
+ }
531
+ const desc = typeof event.description === "string" ? event.description : "";
532
+ const resSrc = typeof event.resolutionSource === "string" ? event.resolutionSource : "";
533
+ const urlText = `${desc} ${resSrc}`;
534
+ const extractedIcao = extractIcaoFromResolutionSource(urlText);
535
+ if (extractedIcao !== null) {
536
+ if (extractedIcao === "RCTP") {
537
+ throw new import_core3.DeferredMarketError(
538
+ `Polymarket market for station ${extractedIcao} is deferred until the v0.2 CWA client lands`
539
+ );
540
+ }
541
+ if (extractedIcao === "VHHH" && marketMeasure === "low") {
542
+ throw new import_core3.DeferredMarketError(
543
+ `Polymarket low-extreme market for station ${extractedIcao} is deferred until the v0.2 HKO client lands`
544
+ );
545
+ }
546
+ if (DEFERRED_STATIONS.has(extractedIcao) && marketMeasure === "default") {
547
+ throw new import_core3.DeferredMarketError(
548
+ `Polymarket market for deferred station ${extractedIcao} (measure=default) requires v0.2 client`
549
+ );
550
+ }
551
+ const cityForRow = cityKey ?? deriveCity(event) ?? "";
552
+ return { city: cityForRow, icao: extractedIcao, stationMeasure: marketMeasure };
553
+ }
554
+ if (cityKey === null) {
555
+ cityKey = deriveCity(event);
556
+ }
557
+ if (cityKey === null) return null;
558
+ const entry = POLYMARKET_CITY_STATIONS[cityKey];
559
+ if (entry === void 0) return null;
560
+ let stationMeasure = "default";
561
+ if (marketMeasure === "high" && typeof entry.high === "string") stationMeasure = "high";
562
+ else if (marketMeasure === "low" && typeof entry.low === "string") stationMeasure = "low";
563
+ const icao = entry[stationMeasure] ?? entry.default;
564
+ if (typeof icao !== "string") return null;
565
+ if (icao === "RCTP") {
566
+ throw new import_core3.DeferredMarketError(
567
+ `Polymarket market for station ${icao} is deferred until the v0.2 CWA client lands`
568
+ );
569
+ }
570
+ if (icao === "VHHH" && marketMeasure === "low") {
571
+ throw new import_core3.DeferredMarketError(
572
+ `Polymarket low-extreme market for station ${icao} is deferred until the v0.2 HKO client lands`
573
+ );
574
+ }
575
+ if (DEFERRED_STATIONS.has(icao) && marketMeasure === "default") {
576
+ throw new import_core3.DeferredMarketError(
577
+ `Polymarket market for deferred station ${icao} (measure=default) requires v0.2 client`
578
+ );
579
+ }
580
+ return { city: cityKey, icao, stationMeasure };
581
+ }
582
+ function settlementDateFromSlug(slug) {
583
+ const matches = slug.matchAll(/(\d{4})-(\d{2})-(\d{2})/g);
584
+ let last = null;
585
+ for (const m2 of matches) last = m2;
586
+ if (last === null) {
587
+ throw new PolymarketSettlementError(
588
+ `no resolution date in slug ${JSON.stringify(slug)} (expected YYYY-MM-DD)`
589
+ );
590
+ }
591
+ const [_, y, m, d] = last;
592
+ const year = Number(y);
593
+ const month = Number(m);
594
+ const day = Number(d);
595
+ const ts = Date.UTC(year, month - 1, day);
596
+ const back = new Date(ts);
597
+ if (back.getUTCFullYear() !== year || back.getUTCMonth() !== month - 1 || back.getUTCDate() !== day) {
598
+ throw new PolymarketSettlementError(
599
+ `slug ${JSON.stringify(slug)} carries malformed date ${y}-${m}-${d}`
600
+ );
601
+ }
602
+ return `${y}-${m}-${d}`;
603
+ }
604
+
605
+ // src/polymarket/discover.ts
606
+ async function polymarketDiscover(opts = {}) {
607
+ const raw = await fetchEvents(opts);
608
+ const out = [];
609
+ for (const ev of raw) {
610
+ const slug = typeof ev.slug === "string" ? ev.slug : null;
611
+ const title = typeof ev.title === "string" ? ev.title : null;
612
+ const endTime = typeof ev.endDate === "string" ? ev.endDate : null;
613
+ const eventId = typeof ev.id === "string" ? ev.id : null;
614
+ const marketMeasure = detectMarketMeasure(ev);
615
+ let icao = null;
616
+ let cityKey = null;
617
+ let measureOut = null;
618
+ try {
619
+ const resolved = resolveStationForEvent(ev, marketMeasure);
620
+ if (resolved === null) {
621
+ opts.onSkip?.({ slug, reason: "no city match in catalog" });
622
+ continue;
623
+ }
624
+ icao = resolved.icao;
625
+ cityKey = resolved.city === "" ? null : resolved.city;
626
+ measureOut = marketMeasure;
627
+ } catch (err) {
628
+ if (err instanceof import_core4.DeferredMarketError) {
629
+ cityKey = deriveCity(ev);
630
+ icao = null;
631
+ measureOut = null;
632
+ } else {
633
+ throw err;
634
+ }
635
+ }
636
+ const description = typeof ev.description === "string" ? ev.description : "";
637
+ let resolutionSourceType = "other";
638
+ if (description.length > 0) {
639
+ try {
640
+ validateDescription(description);
641
+ resolutionSourceType = extractResolutionSourceType(description);
642
+ } catch (err) {
643
+ if (err instanceof PolymarketEventError || err instanceof PayloadTooLargeError) {
644
+ opts.onSkip?.({ slug, reason: `description rejected: ${err.message}` });
645
+ resolutionSourceType = null;
646
+ } else {
647
+ throw err;
648
+ }
649
+ }
650
+ }
651
+ out.push(
652
+ Object.freeze({
653
+ eventId,
654
+ slug,
655
+ title,
656
+ city: cityKey,
657
+ icao,
658
+ measure: measureOut,
659
+ endTime,
660
+ resolutionSourceType
661
+ })
662
+ );
663
+ }
664
+ return out;
665
+ }
666
+
667
+ // src/polymarket/known-wrong-stations.ts
668
+ var POLYMARKET_KNOWN_WRONG_STATIONS = Object.freeze({
669
+ // NYC: Polymarket uses KLGA. KNYC/KJFK/KEWR are common wrong answers.
670
+ nyc: /* @__PURE__ */ new Set(["KNYC", "KJFK", "KEWR"]),
671
+ // Chicago: Polymarket uses KORD. KMDW is the common wrong answer.
672
+ chicago: /* @__PURE__ */ new Set(["KMDW"]),
673
+ // Houston: Polymarket uses KIAH. KHOU is the common wrong answer.
674
+ houston: /* @__PURE__ */ new Set(["KHOU"]),
675
+ // Dallas: Polymarket uses KDFW. KDAL is the common wrong answer.
676
+ dallas: /* @__PURE__ */ new Set(["KDAL"]),
677
+ // SF: Polymarket uses KSFO. KOAK is the common wrong answer.
678
+ san_francisco: /* @__PURE__ */ new Set(["KOAK"]),
679
+ // DC: Polymarket uses KDCA. KIAD/KBWI are common wrong answers.
680
+ washington_dc: /* @__PURE__ */ new Set(["KIAD", "KBWI"])
681
+ });
682
+
683
+ // src/polymarket/settle.ts
684
+ var import_core5 = require("@mostlyrightmd/core");
685
+ var import_discovery = require("@mostlyrightmd/core/discovery");
686
+ function tzForStation(icao) {
687
+ for (const s of import_core5.STATIONS) {
688
+ if (s.icao === icao) return s.tz;
689
+ }
690
+ return null;
691
+ }
692
+ function safeUuidIsh(eventId) {
693
+ return typeof eventId === "string" && EVENT_ID_RE.test(eventId);
694
+ }
695
+ async function polymarketSettle(args) {
696
+ const event = args.event;
697
+ const eventId = typeof event.id === "string" ? event.id : "";
698
+ if (!safeUuidIsh(eventId)) {
699
+ throw new PolymarketEventError(
700
+ `event id ${JSON.stringify(eventId)} does not match ${EVENT_ID_RE.source}`
701
+ );
702
+ }
703
+ const slug = typeof event.slug === "string" ? event.slug : "";
704
+ if (slug.length === 0) {
705
+ throw new PolymarketSettlementError("event.slug is required for settlement (carries the date)");
706
+ }
707
+ const description = args.description ?? (typeof event.description === "string" ? event.description : "");
708
+ validateDescription(description);
709
+ const resolutionSourceType = description.length > 0 ? extractResolutionSourceType(description) : "other";
710
+ const marketMeasure = detectMarketMeasure(event);
711
+ const resolved = resolveStationForEvent(event, marketMeasure);
712
+ if (resolved === null) {
713
+ throw new PolymarketSettlementError(
714
+ `unable to resolve station for slug=${JSON.stringify(slug)} \u2014 no matching city in catalog`
715
+ );
716
+ }
717
+ const settlementDate = settlementDateFromSlug(slug);
718
+ const now = args.now ?? /* @__PURE__ */ new Date();
719
+ const tz = tzForStation(resolved.icao) ?? "UTC";
720
+ const localEodMs = stationLocalEodUtcMs(settlementDate, tz);
721
+ const delayHours = SETTLE_DELAY_HOURS[resolutionSourceType] ?? SETTLE_DELAY_HOURS.other ?? 24;
722
+ const delayMs = delayHours * 3600 * 1e3;
723
+ const earliestSettleMs = localEodMs + delayMs;
724
+ if (now.getTime() < earliestSettleMs) {
725
+ const waitMs = earliestSettleMs - now.getTime();
726
+ throw new TooEarlyToSettleError(
727
+ `settlement for ${resolved.icao} on ${settlementDate} (source=${resolutionSourceType}) requires another ${(waitMs / 3600 / 1e3).toFixed(2)} h`,
728
+ { waitHours: waitMs / 3600 / 1e3, resolutionSourceType }
729
+ );
730
+ }
731
+ const loader = args.loader ?? defaultEmptyLoader;
732
+ const rows = await loader({
733
+ icao: resolved.icao,
734
+ fromDate: settlementDate,
735
+ toDate: settlementDate
736
+ });
737
+ if (rows.length === 0) {
738
+ throw new PolymarketSettlementError(
739
+ `no observation rows for station=${resolved.icao} date=${settlementDate} \u2014 warm the cache first`
740
+ );
741
+ }
742
+ const extremes = (0, import_discovery.internationalDailyExtremes)(rows, { stationTz: tz });
743
+ const day = extremes.find((r) => r.localDate === settlementDate);
744
+ if (day === void 0) {
745
+ throw new PolymarketSettlementError(
746
+ `no daily extreme for station=${resolved.icao} on local date ${settlementDate}`
747
+ );
748
+ }
749
+ let resolvedValueC = null;
750
+ let resolvedValueF = null;
751
+ if (marketMeasure === "low") {
752
+ resolvedValueC = day.tempMinC;
753
+ resolvedValueF = day.tempMinF;
754
+ } else if (marketMeasure === "high") {
755
+ resolvedValueC = day.tempMaxC;
756
+ resolvedValueF = day.tempMaxF;
757
+ } else {
758
+ throw new PolymarketSettlementError(
759
+ `event ${JSON.stringify(eventId)} (slug=${JSON.stringify(slug)}) has ambiguous measure ("default" \u2014 title carries neither high nor low keyword); refusing to settle silently. Disambiguate at the caller.`
760
+ );
761
+ }
762
+ if (resolvedValueC === null || resolvedValueF === null) {
763
+ throw new PolymarketSettlementError(
764
+ `daily extreme for ${resolved.icao} on ${settlementDate} has null ${marketMeasure} (low coverage)`
765
+ );
766
+ }
767
+ const isUsStation = import_core5.STATIONS.find((s) => s.icao === resolved.icao)?.country === "US";
768
+ const unit = args.unit ?? (isUsStation ? "fahrenheit" : "celsius");
769
+ const resolvedValue = unit === "celsius" ? resolvedValueC : resolvedValueF;
770
+ let dataQualityAlert = null;
771
+ if (typeof args.polymarketPublishedValue === "number") {
772
+ const diff = Math.abs(resolvedValue - args.polymarketPublishedValue);
773
+ const threshold = unit === "celsius" ? 0.6 : 1;
774
+ const unitSym = unit === "celsius" ? "\xB0C" : "\xB0F";
775
+ if (diff > threshold) {
776
+ dataQualityAlert = `mostlyright resolved ${resolvedValue}${unitSym} but Polymarket published ${args.polymarketPublishedValue}${unitSym} (\u0394=${diff.toFixed(2)}${unitSym} > ${threshold}${unitSym} threshold)`;
777
+ }
778
+ }
779
+ return Object.freeze({
780
+ eventId,
781
+ settlementDate,
782
+ icao: resolved.icao,
783
+ measure: marketMeasure,
784
+ resolvedValue,
785
+ resolvedValueC,
786
+ resolvedValueF,
787
+ unit,
788
+ resolutionSourceType,
789
+ dataQualityAlert
790
+ });
791
+ }
792
+ async function polymarketSettleById(eventId, args) {
793
+ if (!safeUuidIsh(eventId)) {
794
+ throw new PolymarketEventError(
795
+ `event id ${JSON.stringify(eventId)} does not match ${EVENT_ID_RE.source}`
796
+ );
797
+ }
798
+ const event = await fetchEventById(eventId);
799
+ if (event === null) {
800
+ throw new PolymarketSettlementError(
801
+ `Gamma returned 404 for event id ${JSON.stringify(eventId)}`
802
+ );
803
+ }
804
+ return polymarketSettle({ ...args, event });
805
+ }
806
+ async function defaultEmptyLoader() {
807
+ return [];
808
+ }
809
+ function stationLocalEodUtcMs(localDate, tz) {
810
+ const noon = /* @__PURE__ */ new Date(`${localDate}T12:00:00Z`);
811
+ const fmt = new Intl.DateTimeFormat("en-US", {
812
+ timeZone: tz,
813
+ year: "numeric",
814
+ month: "2-digit",
815
+ day: "2-digit"
816
+ });
817
+ const parts = fmt.formatToParts(noon);
818
+ let y = "";
819
+ let m = "";
820
+ let d = "";
821
+ for (const p of parts) {
822
+ if (p.type === "year") y = p.value;
823
+ else if (p.type === "month") m = p.value;
824
+ else if (p.type === "day") d = p.value;
825
+ }
826
+ const nextDay = /* @__PURE__ */ new Date(`${localDate}T00:00:00Z`);
827
+ nextDay.setUTCDate(nextDay.getUTCDate() + 1);
828
+ const expectedDate = nextDayDate(localDate);
829
+ const minuteFmt = new Intl.DateTimeFormat("en-US", {
830
+ timeZone: tz,
831
+ hour: "2-digit",
832
+ minute: "2-digit",
833
+ hour12: false
834
+ });
835
+ const STEP_MIN = 15;
836
+ const RANGE_MIN = 15 * 60;
837
+ for (let offsetMin = -RANGE_MIN; offsetMin <= RANGE_MIN; offsetMin += STEP_MIN) {
838
+ const candidate = new Date(nextDay.getTime() + offsetMin * 60 * 1e3);
839
+ const cParts = fmt.formatToParts(candidate);
840
+ let cy = "";
841
+ let cm = "";
842
+ let cd = "";
843
+ for (const p of cParts) {
844
+ if (p.type === "year") cy = p.value;
845
+ else if (p.type === "month") cm = p.value;
846
+ else if (p.type === "day") cd = p.value;
847
+ }
848
+ if (`${cy}-${cm}-${cd}` !== expectedDate) continue;
849
+ const tParts = minuteFmt.formatToParts(candidate);
850
+ const hourStr = tParts.find((p) => p.type === "hour")?.value ?? "00";
851
+ const minStr = tParts.find((p) => p.type === "minute")?.value ?? "00";
852
+ const hour = hourStr === "24" ? 0 : Number(hourStr);
853
+ const minute = Number(minStr);
854
+ if (hour === 0 && minute === 0) {
855
+ return candidate.getTime() - 1e3;
856
+ }
857
+ }
858
+ return (/* @__PURE__ */ new Date(`${localDate}T23:59:59Z`)).getTime();
859
+ }
860
+ function nextDayDate(localDate) {
861
+ const d = /* @__PURE__ */ new Date(`${localDate}T00:00:00Z`);
862
+ d.setUTCDate(d.getUTCDate() + 1);
863
+ return d.toISOString().slice(0, 10);
864
+ }
865
+ // Annotate the CommonJS export names for ESM import in node:
866
+ 0 && (module.exports = {
867
+ DEFERRED_STATIONS,
868
+ EVENT_ID_RE,
869
+ MAX_DESCRIPTION_BYTES,
870
+ NETLOC_TO_RESOLUTION_TYPE,
871
+ POLYMARKET_KNOWN_WRONG_STATIONS,
872
+ POLYMARKET_RESOLUTION_SOURCE_TYPES,
873
+ PayloadTooLargeError,
874
+ PolymarketEventError,
875
+ PolymarketSettlementError,
876
+ RESOLUTION_SOURCE_ALLOWLIST,
877
+ SETTLE_DELAY_HOURS,
878
+ SLUG_DATE_RE,
879
+ TooEarlyToSettleError,
880
+ deriveCity,
881
+ detectMarketMeasure,
882
+ extractIcaoFromResolutionSource,
883
+ extractResolutionSourceType,
884
+ fetchEventById,
885
+ fetchEvents,
886
+ polymarketDiscover,
887
+ polymarketSettle,
888
+ polymarketSettleById,
889
+ resolveStationForEvent,
890
+ settlementDateFromSlug,
891
+ validateDescription
892
+ });
893
+ //# sourceMappingURL=index.cjs.map