mostlyright 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,4033 @@
1
+ import {
2
+ IEM_BASE_URL,
3
+ IEM_POLITE_DELAY_MS,
4
+ buildIemUrl,
5
+ downloadIemAsos,
6
+ yearlyChunksExclusiveEnd
7
+ } from "./chunk-UKIFUUDX.mjs";
8
+ import {
9
+ CLIMATE_REPORT_TYPE_PRIORITY,
10
+ LiveStreamError,
11
+ NoLiveDataError,
12
+ NotFoundError,
13
+ STATION_BY_CODE,
14
+ STATION_BY_ICAO,
15
+ SourceMismatchError,
16
+ TherminalError,
17
+ fetchWithRetry,
18
+ helloCore,
19
+ settlementDateFor,
20
+ src_exports
21
+ } from "./chunk-WYZFDCNR.mjs";
22
+ import {
23
+ awcToObservation,
24
+ celsiusToFahrenheit,
25
+ hpaToInhg,
26
+ icaoToStationCode,
27
+ iemToObservation,
28
+ mapCloudCover,
29
+ parseAwcVisibility,
30
+ parseIemCsv
31
+ } from "./chunk-6ERO2BIY.mjs";
32
+ import {
33
+ MAX_RAW_METAR_LEN,
34
+ MAX_VISIBILITY_MILES,
35
+ MAX_WX_CODES_LEN,
36
+ MAX_YEAR,
37
+ MIN_YEAR,
38
+ SKY_BASE_MAX_FT,
39
+ SLP_MAX_MB,
40
+ SLP_MIN_MB,
41
+ STATION_CODE_RE,
42
+ TEMP_MAX_C,
43
+ TEMP_MIN_C,
44
+ boundedFloat,
45
+ boundedFloatMin,
46
+ boundedInt
47
+ } from "./chunk-VESWR46G.mjs";
48
+ import {
49
+ __export
50
+ } from "./chunk-J5LGTIGS.mjs";
51
+
52
+ // ../weather/src/index.ts
53
+ var src_exports2 = {};
54
+ __export(src_exports2, {
55
+ AWC_MAX_HOURS: () => AWC_MAX_HOURS,
56
+ AWC_METAR_URL: () => AWC_METAR_URL,
57
+ GHCNH_BASE_URL: () => GHCNH_BASE_URL,
58
+ HIGH_TEMP_MAX_F: () => HIGH_TEMP_MAX_F,
59
+ HIGH_TEMP_MIN_F: () => HIGH_TEMP_MIN_F,
60
+ IEM_BASE_URL: () => IEM_BASE_URL,
61
+ IEM_CLI_BASE_URL: () => IEM_CLI_BASE_URL,
62
+ IEM_CLI_POLITE_DELAY_MS: () => IEM_CLI_POLITE_DELAY_MS,
63
+ IEM_POLITE_DELAY_MS: () => IEM_POLITE_DELAY_MS,
64
+ LOW_TEMP_MAX_F: () => LOW_TEMP_MAX_F,
65
+ LOW_TEMP_MIN_F: () => LOW_TEMP_MIN_F,
66
+ LiveStreamError: () => LiveStreamError,
67
+ NCEI_POLITE_DELAY_MS: () => NCEI_POLITE_DELAY_MS,
68
+ NoLiveDataError: () => NoLiveDataError,
69
+ POLITE_FLOORS_S: () => POLITE_FLOORS_S,
70
+ SOURCE_IDENTITY_TAGS: () => SOURCE_IDENTITY_TAGS,
71
+ SSID_COLUMNS: () => SSID_COLUMNS,
72
+ SUPPORTED_SOURCES: () => SUPPORTED_SOURCES,
73
+ awcToObservation: () => awcToObservation,
74
+ buildIemUrl: () => buildIemUrl,
75
+ downloadCli: () => downloadCli,
76
+ downloadCliRange: () => downloadCliRange,
77
+ downloadGhcnh: () => downloadGhcnh,
78
+ downloadGhcnhRange: () => downloadGhcnhRange,
79
+ downloadIemAsos: () => downloadIemAsos,
80
+ extractStationCode: () => extractStationCode,
81
+ fetchAwcMetars: () => fetchAwcMetars,
82
+ forecastNwp: () => forecastNwp,
83
+ ghcnhStationToCode: () => ghcnhStationToCode,
84
+ helloWeather: () => helloWeather,
85
+ icaoToStationCode: () => icaoToStationCode,
86
+ iemMosForecasts: () => iemMosForecasts,
87
+ iemToObservation: () => iemToObservation,
88
+ inferReportType: () => inferReportType,
89
+ isLiveSource: () => isLiveSource,
90
+ latest: () => latest,
91
+ mapCloudCover: () => mapCloudCover,
92
+ mergeClimate: () => mergeClimate,
93
+ parseAwcVisibility: () => parseAwcVisibility,
94
+ parseCliRecord: () => parseCliRecord,
95
+ parseCliResponse: () => parseCliResponse,
96
+ parseGhcnhPsv: () => parseGhcnhPsv,
97
+ parseGhcnhRow: () => parseGhcnhRow,
98
+ parseIemCsv: () => parseIemCsv,
99
+ sourceTag: () => sourceTag,
100
+ stream: () => stream,
101
+ validatePollSeconds: () => validatePollSeconds,
102
+ validateSource: () => validateSource,
103
+ version: () => version,
104
+ yearlyChunksExclusiveEnd: () => yearlyChunksExclusiveEnd
105
+ });
106
+
107
+ // ../weather/src/_fetchers/awc.ts
108
+ var AWC_METAR_URL = "https://aviationweather.gov/api/data/metar";
109
+ var AWC_MAX_HOURS = 168;
110
+ async function fetchAwcMetars(stationIcaos, opts = {}) {
111
+ if (stationIcaos.length === 0) {
112
+ return [];
113
+ }
114
+ const hours = Math.min(opts.hours ?? AWC_MAX_HOURS, AWC_MAX_HOURS);
115
+ const idsCsv = stationIcaos.map((s) => encodeURIComponent(s)).join(",");
116
+ const url = `${AWC_METAR_URL}?ids=${idsCsv}&format=json&taf=false&hours=${hours}`;
117
+ const { hours: _consumed, ...retryOpts } = opts;
118
+ let response;
119
+ try {
120
+ response = await fetchWithRetry(url, retryOpts);
121
+ } catch (err) {
122
+ if (err instanceof TherminalError) {
123
+ return [];
124
+ }
125
+ if (err instanceof DOMException && (err.name === "AbortError" || err.name === "TimeoutError")) {
126
+ if (opts.signal?.aborted) {
127
+ throw err;
128
+ }
129
+ return [];
130
+ }
131
+ return [];
132
+ }
133
+ let data;
134
+ try {
135
+ data = await response.json();
136
+ } catch {
137
+ return [];
138
+ }
139
+ if (!Array.isArray(data)) {
140
+ return [];
141
+ }
142
+ return data;
143
+ }
144
+
145
+ // ../weather/src/_fetchers/iem-cli.ts
146
+ var IEM_CLI_BASE_URL = "https://mesonet.agron.iastate.edu/json/cli.py";
147
+ var IEM_CLI_POLITE_DELAY_MS = 1e3;
148
+ var STATION_CODE_RE2 = /^[A-Z]{3,4}$/;
149
+ function sleep(ms) {
150
+ return new Promise((resolve) => setTimeout(resolve, ms));
151
+ }
152
+ function validateIcao(stationIcao) {
153
+ if (typeof stationIcao !== "string" || !STATION_CODE_RE2.test(stationIcao)) {
154
+ throw new Error(
155
+ `station_icao=${JSON.stringify(
156
+ stationIcao
157
+ )} does not match STATION_CODE_RE (3-4 uppercase letters); refusing to use as URL component`
158
+ );
159
+ }
160
+ }
161
+ function unwrapResults(data) {
162
+ if (Array.isArray(data)) {
163
+ return data;
164
+ }
165
+ if (data !== null && typeof data === "object" && "results" in data && Array.isArray(data.results)) {
166
+ return data.results;
167
+ }
168
+ throw new Error(
169
+ `Unexpected IEM CLI response shape: ${data === null ? "null" : Array.isArray(data) ? "array" : typeof data}`
170
+ );
171
+ }
172
+ async function downloadCli(stationIcao, year, opts = {}) {
173
+ validateIcao(stationIcao);
174
+ const url = `${IEM_CLI_BASE_URL}?station=${stationIcao}&year=${year}`;
175
+ const response = await fetchWithRetry(url, opts);
176
+ const data = await response.json();
177
+ return unwrapResults(data);
178
+ }
179
+ async function downloadCliRange(stationIcao, startYear, endYear, opts = {}) {
180
+ if (endYear < startYear) {
181
+ throw new Error(`endYear (${endYear}) must be >= startYear (${startYear})`);
182
+ }
183
+ validateIcao(stationIcao);
184
+ const politenessMs = opts.politenessMs ?? IEM_CLI_POLITE_DELAY_MS;
185
+ const { politenessMs: _drop, ...fetchOpts } = opts;
186
+ const out = [];
187
+ for (let year = startYear; year <= endYear; year++) {
188
+ if (year > startYear && politenessMs > 0) {
189
+ await sleep(politenessMs);
190
+ }
191
+ try {
192
+ const records = await downloadCli(stationIcao, year, fetchOpts);
193
+ out.push(...records);
194
+ } catch (err) {
195
+ if (err instanceof NotFoundError) {
196
+ continue;
197
+ }
198
+ throw err;
199
+ }
200
+ }
201
+ return out;
202
+ }
203
+
204
+ // ../core/dist/internal/merge/index.mjs
205
+ var SOURCE_PRIORITY = Object.freeze({
206
+ awc: 3,
207
+ iem: 2,
208
+ ghcnh: 1
209
+ });
210
+ function mergeObservations(rows) {
211
+ const best = /* @__PURE__ */ new Map();
212
+ for (const row of rows) {
213
+ const key = `${row.station_code}\0${row.observed_at}\0${row.observation_type}`;
214
+ const existing = best.get(key);
215
+ if (existing === void 0) {
216
+ best.set(key, row);
217
+ continue;
218
+ }
219
+ const priority = SOURCE_PRIORITY[row.source] ?? 0;
220
+ const existingPriority = SOURCE_PRIORITY[existing.source] ?? 0;
221
+ if (priority > existingPriority) {
222
+ best.set(key, row);
223
+ }
224
+ }
225
+ return Array.from(best.values());
226
+ }
227
+ function mergeClimate(rows) {
228
+ const best = /* @__PURE__ */ new Map();
229
+ for (const row of rows) {
230
+ const key = `${row.station_code}\0${row.observation_date}`;
231
+ const existing = best.get(key);
232
+ if (existing === void 0) {
233
+ best.set(key, row);
234
+ continue;
235
+ }
236
+ if (row.report_type_priority > existing.report_type_priority) {
237
+ best.set(key, row);
238
+ }
239
+ }
240
+ return Array.from(best.values());
241
+ }
242
+
243
+ // ../weather/src/_parsers/cli.ts
244
+ var HIGH_TEMP_MIN_F = -60;
245
+ var HIGH_TEMP_MAX_F = 150;
246
+ var LOW_TEMP_MIN_F = -80;
247
+ var LOW_TEMP_MAX_F = 130;
248
+ var PRODUCT_TS_RE = /^(\d{12})/;
249
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
250
+ function parseProductTimestamp(product) {
251
+ if (!product) return null;
252
+ const m = PRODUCT_TS_RE.exec(product);
253
+ if (m === null) return null;
254
+ const ts = m[1];
255
+ if (ts === void 0) return null;
256
+ const year = Number.parseInt(ts.slice(0, 4), 10);
257
+ const month = Number.parseInt(ts.slice(4, 6), 10);
258
+ const day = Number.parseInt(ts.slice(6, 8), 10);
259
+ const hour = Number.parseInt(ts.slice(8, 10), 10);
260
+ const minute = Number.parseInt(ts.slice(10, 12), 10);
261
+ if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day) || !Number.isFinite(hour) || !Number.isFinite(minute)) {
262
+ return null;
263
+ }
264
+ if (month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59) {
265
+ return null;
266
+ }
267
+ const millis = Date.UTC(year, month - 1, day, hour, minute, 0, 0);
268
+ if (!Number.isFinite(millis)) return null;
269
+ const d = new Date(millis);
270
+ if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day || d.getUTCHours() !== hour || d.getUTCMinutes() !== minute) {
271
+ return null;
272
+ }
273
+ return d;
274
+ }
275
+ function parseObservationDate(observationDate) {
276
+ if (!DATE_RE.test(observationDate)) return null;
277
+ const year = Number.parseInt(observationDate.slice(0, 4), 10);
278
+ const month = Number.parseInt(observationDate.slice(5, 7), 10);
279
+ const day = Number.parseInt(observationDate.slice(8, 10), 10);
280
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
281
+ const millis = Date.UTC(year, month - 1, day);
282
+ const d = new Date(millis);
283
+ if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day) {
284
+ return null;
285
+ }
286
+ return d;
287
+ }
288
+ function inferReportType(product, observationDate) {
289
+ if (!product) return "preliminary";
290
+ const issued = parseProductTimestamp(product);
291
+ if (issued === null) return "preliminary";
292
+ const obs = parseObservationDate(observationDate);
293
+ if (obs === null) return "preliminary";
294
+ const issuedDayUtc = Date.UTC(issued.getUTCFullYear(), issued.getUTCMonth(), issued.getUTCDate());
295
+ const obsDayUtc = obs.getTime();
296
+ const deltaDays = Math.round((issuedDayUtc - obsDayUtc) / 864e5);
297
+ if (deltaDays <= 0) return "preliminary";
298
+ if (deltaDays === 1) {
299
+ const hour = issued.getUTCHours();
300
+ if (hour >= 4 && hour <= 10) return "final";
301
+ return "correction";
302
+ }
303
+ return "correction";
304
+ }
305
+ function parseTemp(val) {
306
+ if (val === null || val === void 0 || val === "M" || val === "") return null;
307
+ const n = typeof val === "number" ? val : Number(val);
308
+ if (!Number.isFinite(n)) return null;
309
+ return Math.round(n);
310
+ }
311
+ function parseCliRecord(record, stationCode) {
312
+ const observationDate = record.valid;
313
+ if (typeof observationDate !== "string" || observationDate.length === 0) return null;
314
+ if (parseObservationDate(observationDate) === null) return null;
315
+ let high = parseTemp(record.high);
316
+ let low = parseTemp(record.low);
317
+ if (high !== null && (high < HIGH_TEMP_MIN_F || high > HIGH_TEMP_MAX_F)) {
318
+ high = null;
319
+ }
320
+ if (low !== null && (low < LOW_TEMP_MIN_F || low > LOW_TEMP_MAX_F)) {
321
+ low = null;
322
+ }
323
+ if (high === null && low === null) return null;
324
+ const product = typeof record.product === "string" && record.product.length > 0 ? record.product : null;
325
+ const reportType = inferReportType(product, observationDate);
326
+ const priority = CLIMATE_REPORT_TYPE_PRIORITY[reportType];
327
+ if (priority === void 0) {
328
+ throw new Error(
329
+ `report type ${JSON.stringify(reportType)} missing from CLIMATE_REPORT_TYPE_PRIORITY (codegen drift)`
330
+ );
331
+ }
332
+ let issuedAt = null;
333
+ if (product !== null) {
334
+ const issuedDt = parseProductTimestamp(product);
335
+ if (issuedDt !== null) {
336
+ issuedAt = `${issuedDt.toISOString().slice(0, 19)}Z`;
337
+ }
338
+ }
339
+ return {
340
+ station_code: stationCode,
341
+ observation_date: observationDate,
342
+ high_temp_f: high,
343
+ low_temp_f: low,
344
+ report_type: reportType,
345
+ report_type_priority: priority,
346
+ source: "iem",
347
+ product_id: product,
348
+ issued_at: issuedAt
349
+ };
350
+ }
351
+ function parseCliResponse(data, stationCode) {
352
+ const out = [];
353
+ for (const record of data) {
354
+ const parsed = parseCliRecord(record, stationCode);
355
+ if (parsed !== null) out.push(parsed);
356
+ }
357
+ return out;
358
+ }
359
+
360
+ // ../weather/src/_fetchers/ghcnh.ts
361
+ var GHCNH_BASE_URL = "https://www.ncei.noaa.gov/oa/global-historical-climatology-network/hourly/access";
362
+ var NCEI_POLITE_DELAY_MS = 1e3;
363
+ var GHCNH_STATION_ID_RE = /^[A-Z0-9-]{1,32}$/;
364
+ function sleep2(ms) {
365
+ return new Promise((resolve) => setTimeout(resolve, ms));
366
+ }
367
+ function validateStationId(stationId) {
368
+ if (typeof stationId !== "string" || !GHCNH_STATION_ID_RE.test(stationId)) {
369
+ throw new Error(
370
+ `station_id=${JSON.stringify(
371
+ stationId
372
+ )} does not match GHCNH_STATION_ID_RE (uppercase alphanumeric + hyphen, 1-32 chars); refusing to use as URL component`
373
+ );
374
+ }
375
+ }
376
+ function buildGhcnhUrl(stationId, year) {
377
+ return `${GHCNH_BASE_URL}/by-year/${year}/psv/GHCNh_${stationId}_${year}.psv`;
378
+ }
379
+ async function downloadGhcnh(stationId, year, opts = {}) {
380
+ validateStationId(stationId);
381
+ const url = buildGhcnhUrl(stationId, year);
382
+ const response = await fetchWithRetry(url, opts);
383
+ const psv = await response.text();
384
+ return { stationId, year, psv };
385
+ }
386
+ async function downloadGhcnhRange(stationId, startYear, endYear, opts = {}) {
387
+ validateStationId(stationId);
388
+ if (endYear < startYear) {
389
+ return [];
390
+ }
391
+ const politenessMs = opts.politenessMs ?? NCEI_POLITE_DELAY_MS;
392
+ const { politenessMs: _pmDrop, ...fetchOpts } = opts;
393
+ const out = [];
394
+ for (let year = startYear; year <= endYear; year++) {
395
+ let result;
396
+ try {
397
+ result = await downloadGhcnh(stationId, year, fetchOpts);
398
+ } catch (err) {
399
+ if (err instanceof NotFoundError) {
400
+ continue;
401
+ }
402
+ throw err;
403
+ }
404
+ out.push(result);
405
+ if (politenessMs > 0) {
406
+ await sleep2(politenessMs);
407
+ }
408
+ }
409
+ return out;
410
+ }
411
+
412
+ // ../weather/src/_parsers/_station_translator.ts
413
+ var SSID_COLUMNS = [
414
+ "temperature_Source_Station_ID",
415
+ "dew_point_temperature_Source_Station_ID",
416
+ "wind_speed_Source_Station_ID",
417
+ "wind_direction_Source_Station_ID",
418
+ "sea_level_pressure_Source_Station_ID",
419
+ "altimeter_Source_Station_ID",
420
+ "visibility_Source_Station_ID",
421
+ "sky_cover_summation_1_Source_Station_ID",
422
+ "sky_cover_summation_2_Source_Station_ID",
423
+ "sky_cover_summation_3_Source_Station_ID",
424
+ "sky_cover_summation_4_Source_Station_ID"
425
+ ];
426
+ function ghcnhStationToCode(sourceStationId) {
427
+ if (!sourceStationId || !sourceStationId.startsWith("ICAO-")) {
428
+ return null;
429
+ }
430
+ const icao = sourceStationId.slice(5);
431
+ if (!icao) {
432
+ return null;
433
+ }
434
+ const code = icaoToStationCode(icao);
435
+ if (STATION_CODE_RE.test(code)) {
436
+ return code;
437
+ }
438
+ return null;
439
+ }
440
+ function extractStationCode(row) {
441
+ for (const col of SSID_COLUMNS) {
442
+ const ssid = row[col] ?? "";
443
+ const code = ghcnhStationToCode(ssid);
444
+ if (code !== null) {
445
+ return code;
446
+ }
447
+ }
448
+ return null;
449
+ }
450
+
451
+ // ../weather/src/_parsers/ghcnh.ts
452
+ var MS_TO_KT = 1 / 0.514444;
453
+ var KM_TO_MI = 1 / 1.60934;
454
+ var M_TO_FT = 3.28084;
455
+ var MM_TO_IN = 1 / 25.4;
456
+ var CM_TO_IN = 1 / 2.54;
457
+ var DATE_RE2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z?$/;
458
+ var ALLOWED_QC = /* @__PURE__ */ new Set(["0", "1", "4", "5"]);
459
+ function safeFloat(val) {
460
+ if (!val || val === "NA") return null;
461
+ const f = Number(val);
462
+ return Number.isFinite(f) ? f : null;
463
+ }
464
+ function safeInt(val) {
465
+ const f = safeFloat(val);
466
+ return f === null ? null : Math.round(f);
467
+ }
468
+ function isQcAccepted(qc) {
469
+ const stripped = qc.trim();
470
+ if (!stripped) return true;
471
+ return ALLOWED_QC.has(stripped);
472
+ }
473
+ function parseSkyCover(val) {
474
+ if (!val) return null;
475
+ let code;
476
+ if (val.includes(":")) {
477
+ const colonIdx = val.indexOf(":");
478
+ code = val.slice(0, colonIdx);
479
+ } else {
480
+ code = val.endsWith(";") ? val.slice(0, -1) : val;
481
+ }
482
+ return mapCloudCover(code);
483
+ }
484
+ function parseSkyBaseht(val) {
485
+ const meters = safeFloat(val);
486
+ if (meters === null || meters < 0) return null;
487
+ const feet = Math.round(meters * M_TO_FT);
488
+ return feet <= SKY_BASE_MAX_FT ? feet : null;
489
+ }
490
+ function parseWeatherCodes(row) {
491
+ const codes = [];
492
+ for (let i = 1; i <= 3; i++) {
493
+ const val = row[`pres_wx_AW${i}`] ?? "";
494
+ if (!val) continue;
495
+ const qc = (row[`pres_wx_AW${i}_Quality_Code`] ?? "").trim();
496
+ if (qc === "3" || qc === "P") continue;
497
+ const code = val.includes(":") ? val.slice(0, val.indexOf(":")) : val;
498
+ if (!code) continue;
499
+ const numericProbe = code.replace(/^[+-]/, "");
500
+ if (numericProbe.length > 0 && /^\d+$/.test(numericProbe)) continue;
501
+ codes.push(code);
502
+ }
503
+ if (codes.length === 0) return null;
504
+ const result = codes.join(" ");
505
+ return result.length > MAX_WX_CODES_LEN ? result.slice(0, MAX_WX_CODES_LEN) : result;
506
+ }
507
+ function isValidCalendarDate(iso) {
508
+ const stripped = iso.endsWith("Z") ? iso.slice(0, -1) : iso;
509
+ const year = Number.parseInt(stripped.slice(0, 4), 10);
510
+ const month = Number.parseInt(stripped.slice(5, 7), 10);
511
+ const day = Number.parseInt(stripped.slice(8, 10), 10);
512
+ const hour = Number.parseInt(stripped.slice(11, 13), 10);
513
+ const minute = Number.parseInt(stripped.slice(14, 16), 10);
514
+ const second = Number.parseInt(stripped.slice(17, 19), 10);
515
+ if (month < 1 || month > 12) return false;
516
+ if (day < 1 || day > 31) return false;
517
+ if (hour > 23 || minute > 59 || second > 59) return false;
518
+ const millis = Date.UTC(year, month - 1, day, hour, minute, second, 0);
519
+ if (!Number.isFinite(millis)) return false;
520
+ const d = new Date(millis);
521
+ return d.getUTCFullYear() === year && d.getUTCMonth() === month - 1 && d.getUTCDate() === day && d.getUTCHours() === hour && d.getUTCMinutes() === minute && d.getUTCSeconds() === second;
522
+ }
523
+ function parseGhcnhRow(row) {
524
+ const stationCode = extractStationCode(row);
525
+ if (stationCode === null) return null;
526
+ const dateStr = row.DATE ?? "";
527
+ if (!dateStr || !DATE_RE2.test(dateStr)) return null;
528
+ if (!isValidCalendarDate(dateStr)) return null;
529
+ const year = Number.parseInt(dateStr.slice(0, 4), 10);
530
+ if (year < MIN_YEAR || year > MAX_YEAR) return null;
531
+ const observedAt = dateStr.endsWith("Z") ? dateStr : `${dateStr}Z`;
532
+ const reportType = row.temperature_Report_Type ?? "";
533
+ const observationType = reportType === "FM16" ? "SPECI" : "METAR";
534
+ const tempOk = isQcAccepted(row.temperature_Quality_Code ?? "");
535
+ const dewpOk = isQcAccepted(row.dew_point_temperature_Quality_Code ?? "");
536
+ const wspdOk = isQcAccepted(row.wind_speed_Quality_Code ?? "");
537
+ const wdirOk = isQcAccepted(row.wind_direction_Quality_Code ?? "");
538
+ const wgustOk = isQcAccepted(row.wind_gust_Quality_Code ?? "");
539
+ const slpOk = isQcAccepted(row.sea_level_pressure_Quality_Code ?? "");
540
+ const altimOk = isQcAccepted(row.altimeter_Quality_Code ?? "");
541
+ const visOk = isQcAccepted(row.visibility_Quality_Code ?? "");
542
+ const precipOk = isQcAccepted(row.precipitation_Quality_Code ?? "");
543
+ const snowOk = isQcAccepted(row.snow_depth_Quality_Code ?? "");
544
+ if (!(tempOk || dewpOk || wspdOk || slpOk)) return null;
545
+ const tempC = tempOk ? boundedFloat(safeFloat(row.temperature ?? ""), TEMP_MIN_C, TEMP_MAX_C, { field: "temp_c" }) : null;
546
+ const dewpC = dewpOk ? boundedFloat(safeFloat(row.dew_point_temperature ?? ""), TEMP_MIN_C, TEMP_MAX_C, {
547
+ field: "dewpoint_c"
548
+ }) : null;
549
+ const tempF = celsiusToFahrenheit(tempC);
550
+ const dewpF = celsiusToFahrenheit(dewpC);
551
+ const windDir = wdirOk ? boundedInt(safeInt(row.wind_direction ?? ""), 0, 360) : null;
552
+ const windSpeedMs = wspdOk ? safeFloat(row.wind_speed ?? "") : null;
553
+ const windGustMs = wgustOk ? safeFloat(row.wind_gust ?? "") : null;
554
+ const windSpeedKt = boundedInt(
555
+ windSpeedMs !== null ? Math.round(windSpeedMs * MS_TO_KT) : null,
556
+ 0,
557
+ 200
558
+ );
559
+ const windGustKt = boundedInt(
560
+ windGustMs !== null ? Math.round(windGustMs * MS_TO_KT) : null,
561
+ 0,
562
+ 250
563
+ );
564
+ let slp = slpOk ? safeFloat(row.sea_level_pressure ?? "") : null;
565
+ if (slp !== null && (slp < SLP_MIN_MB || slp > SLP_MAX_MB)) {
566
+ slp = null;
567
+ }
568
+ const altimHpa = altimOk ? safeFloat(row.altimeter ?? "") : null;
569
+ const altimInhg = hpaToInhg(altimHpa);
570
+ const visKm = visOk ? safeFloat(row.visibility ?? "") : null;
571
+ let visMiles = null;
572
+ if (visKm !== null && visKm >= 0) {
573
+ visMiles = Math.min(visKm * KM_TO_MI, MAX_VISIBILITY_MILES);
574
+ }
575
+ const precipMm = precipOk ? safeFloat(row.precipitation ?? "") : null;
576
+ const precipInches = precipMm !== null ? boundedFloatMin(precipMm * MM_TO_IN, 0) : null;
577
+ const snowCm = snowOk ? safeFloat(row.snow_depth ?? "") : null;
578
+ const snowInches = snowCm !== null ? boundedFloatMin(snowCm * CM_TO_IN, 0) : null;
579
+ const skyCovers = [];
580
+ const skyBases = [];
581
+ for (let i = 1; i <= 4; i++) {
582
+ const covQc = isQcAccepted(row[`sky_cover_summation_${i}_Quality_Code`] ?? "");
583
+ const baseQc = isQcAccepted(row[`sky_cover_summation_baseht_${i}_Quality_Code`] ?? "");
584
+ skyCovers.push(covQc ? parseSkyCover(row[`sky_cover_summation_${i}`] ?? "") : null);
585
+ skyBases.push(baseQc ? parseSkyBaseht(row[`sky_cover_summation_baseht_${i}`] ?? "") : null);
586
+ }
587
+ const weatherCodes = parseWeatherCodes(row);
588
+ const rem = row.REM ?? "";
589
+ let rawMetar = null;
590
+ if (rem) {
591
+ const idxMetar = rem.indexOf("METAR ");
592
+ const idxSpeci = rem.indexOf("SPECI ");
593
+ let cleaned;
594
+ if (idxMetar >= 0 && (idxSpeci < 0 || idxMetar < idxSpeci)) {
595
+ cleaned = rem.slice(idxMetar);
596
+ } else if (idxSpeci >= 0) {
597
+ cleaned = rem.slice(idxSpeci);
598
+ } else {
599
+ cleaned = rem;
600
+ }
601
+ rawMetar = cleaned.length > MAX_RAW_METAR_LEN ? cleaned.slice(0, MAX_RAW_METAR_LEN) : cleaned;
602
+ }
603
+ return {
604
+ station_code: stationCode,
605
+ observed_at: observedAt,
606
+ observation_type: observationType,
607
+ source: "ghcnh",
608
+ temp_c: tempC,
609
+ dewpoint_c: dewpC,
610
+ temp_f: tempF,
611
+ dewpoint_f: dewpF,
612
+ wind_dir_degrees: windDir,
613
+ wind_speed_kt: windSpeedKt,
614
+ wind_gust_kt: windGustKt,
615
+ altimeter_inhg: altimInhg,
616
+ sea_level_pressure_mb: slp,
617
+ sky_cover_1: skyCovers[0] ?? null,
618
+ sky_base_1_ft: skyBases[0] ?? null,
619
+ sky_cover_2: skyCovers[1] ?? null,
620
+ sky_base_2_ft: skyBases[1] ?? null,
621
+ sky_cover_3: skyCovers[2] ?? null,
622
+ sky_base_3_ft: skyBases[2] ?? null,
623
+ sky_cover_4: skyCovers[3] ?? null,
624
+ sky_base_4_ft: skyBases[3] ?? null,
625
+ visibility_miles: visMiles,
626
+ weather_codes: weatherCodes,
627
+ precip_1hr_inches: precipInches,
628
+ peak_wind_gust_kt: null,
629
+ peak_wind_dir: null,
630
+ peak_wind_time: null,
631
+ snow_depth_inches: snowInches,
632
+ qc_field: null,
633
+ raw_metar: rawMetar
634
+ };
635
+ }
636
+ function parseGhcnhPsv(psvBody) {
637
+ if (!psvBody) return [];
638
+ const lines = psvBody.replace(/\r/g, "").split("\n");
639
+ let headerIdx = -1;
640
+ for (let i = 0; i < lines.length; i++) {
641
+ if (lines[i].length > 0) {
642
+ headerIdx = i;
643
+ break;
644
+ }
645
+ }
646
+ if (headerIdx < 0) return [];
647
+ const header = lines[headerIdx].split("|");
648
+ const out = [];
649
+ for (let i = headerIdx + 1; i < lines.length; i++) {
650
+ const line = lines[i];
651
+ if (line.length === 0) continue;
652
+ const cells = line.split("|");
653
+ const row = {};
654
+ for (let c = 0; c < header.length; c++) {
655
+ const key = header[c];
656
+ row[key] = c < cells.length ? cells[c] : "";
657
+ }
658
+ const obs = parseGhcnhRow(row);
659
+ if (obs !== null) out.push(obs);
660
+ }
661
+ return out;
662
+ }
663
+
664
+ // ../weather/src/live/sources.ts
665
+ var SUPPORTED_SOURCES = ["awc", "iem"];
666
+ var POLITE_FLOORS_S = {
667
+ awc: 30,
668
+ iem: 60
669
+ };
670
+ var SOURCE_IDENTITY_TAGS = {
671
+ awc: "awc.live",
672
+ iem: "iem.live"
673
+ };
674
+ function validateSource(source) {
675
+ if (source === void 0 || source === null) {
676
+ return SUPPORTED_SOURCES[0];
677
+ }
678
+ const normalized = source.trim().toLowerCase();
679
+ if (!isLiveSource(normalized)) {
680
+ throw new Error(
681
+ `unknown live source ${JSON.stringify(source)}; supported: ${JSON.stringify(
682
+ SUPPORTED_SOURCES
683
+ )}`
684
+ );
685
+ }
686
+ return normalized;
687
+ }
688
+ function isLiveSource(s) {
689
+ return SUPPORTED_SOURCES.includes(s);
690
+ }
691
+ function validatePollSeconds(pollSeconds, source) {
692
+ const floor = POLITE_FLOORS_S[source];
693
+ if (pollSeconds === void 0 || pollSeconds === null) {
694
+ return floor;
695
+ }
696
+ if (!Number.isFinite(pollSeconds)) {
697
+ throw new Error(
698
+ `pollSeconds=${pollSeconds} is not a finite number; polite floor ${floor}s required for source=${JSON.stringify(
699
+ source
700
+ )}`
701
+ );
702
+ }
703
+ if (pollSeconds < floor) {
704
+ throw new Error(
705
+ `pollSeconds=${pollSeconds} below polite floor ${floor}s for source=${JSON.stringify(
706
+ source
707
+ )}`
708
+ );
709
+ }
710
+ return pollSeconds;
711
+ }
712
+ function sourceTag(source) {
713
+ return SOURCE_IDENTITY_TAGS[source];
714
+ }
715
+
716
+ // ../weather/src/live/_fetch.ts
717
+ function normalizeStation(station) {
718
+ const s = station.trim().toUpperCase();
719
+ if (s.length === 3) return `K${s}`;
720
+ return s;
721
+ }
722
+ function asLiveObservation(obs, tag) {
723
+ return { ...obs, source: tag };
724
+ }
725
+ async function fetchAwcLatest(station) {
726
+ const icao = normalizeStation(station);
727
+ const raw = await fetchAwcMetars([icao], { hours: 1 });
728
+ const tag = sourceTag("awc");
729
+ const rows = [];
730
+ for (const m of raw) {
731
+ const obs = awcToObservation(m);
732
+ if (obs !== null) {
733
+ rows.push(asLiveObservation(obs, tag));
734
+ }
735
+ }
736
+ return rows;
737
+ }
738
+ function todayUtcIso() {
739
+ const d = /* @__PURE__ */ new Date();
740
+ const y = d.getUTCFullYear();
741
+ const m = String(d.getUTCMonth() + 1).padStart(2, "0");
742
+ const day = String(d.getUTCDate()).padStart(2, "0");
743
+ return `${y}-${m}-${day}`;
744
+ }
745
+ function nextDayIso(iso) {
746
+ const [y, m, d] = iso.split("-").map(Number);
747
+ const dt = new Date(Date.UTC(y, m - 1, d));
748
+ dt.setUTCDate(dt.getUTCDate() + 1);
749
+ const ny = dt.getUTCFullYear();
750
+ const nm = String(dt.getUTCMonth() + 1).padStart(2, "0");
751
+ const nd = String(dt.getUTCDate()).padStart(2, "0");
752
+ return `${ny}-${nm}-${nd}`;
753
+ }
754
+ function previousDayIso(iso) {
755
+ const [y, m, d] = iso.split("-").map(Number);
756
+ const dt = new Date(Date.UTC(y, m - 1, d));
757
+ dt.setUTCDate(dt.getUTCDate() - 1);
758
+ const py = dt.getUTCFullYear();
759
+ const pm = String(dt.getUTCMonth() + 1).padStart(2, "0");
760
+ const pd = String(dt.getUTCDate()).padStart(2, "0");
761
+ return `${py}-${pm}-${pd}`;
762
+ }
763
+ async function fetchIemLatest(station) {
764
+ const [{ fetchWithRetry: fetchWithRetry2 }, { STATION_CODE_RE: STATION_CODE_RE3 }, { buildIemUrl: buildIemUrl2 }, { parseIemCsv: parseIemCsv2 }] = await Promise.all([
765
+ import("./src-5L5C2EE7.mjs"),
766
+ import("./bounds-KSTXL77E.mjs"),
767
+ import("./iem-asos-O4CQWBXK.mjs"),
768
+ import("./iem-5RVPI3TY.mjs")
769
+ ]);
770
+ const icao = normalizeStation(station);
771
+ const stationCode = icao.length === 4 && icao.startsWith("K") ? icao.slice(1) : icao;
772
+ if (!STATION_CODE_RE3.test(stationCode)) {
773
+ throw new Error(
774
+ `station=${JSON.stringify(
775
+ stationCode
776
+ )} does not match STATION_CODE_RE (3-4 uppercase letters); refusing to use as IEM URL component`
777
+ );
778
+ }
779
+ const todayIso = todayUtcIso();
780
+ const startIso = previousDayIso(todayIso);
781
+ const endIso = nextDayIso(todayIso);
782
+ const tag = sourceTag("iem");
783
+ const rows = [];
784
+ for (const reportType of [3, 4]) {
785
+ const url = buildIemUrl2(stationCode, startIso, endIso, reportType);
786
+ const response = await fetchWithRetry2(url);
787
+ const csv = await response.text();
788
+ const override = reportType === 3 ? "METAR" : "SPECI";
789
+ const obs = parseIemCsv2(csv, { observationTypeOverride: override });
790
+ for (const row of obs) {
791
+ rows.push(asLiveObservation(row, tag));
792
+ }
793
+ }
794
+ return rows;
795
+ }
796
+ async function fetchLatest(station, source) {
797
+ switch (source) {
798
+ case "awc":
799
+ return fetchAwcLatest(station);
800
+ case "iem":
801
+ return fetchIemLatest(station);
802
+ }
803
+ }
804
+ function pickMostRecent(rows) {
805
+ if (rows.length === 0) return null;
806
+ let best = rows[0];
807
+ for (let i = 1; i < rows.length; i++) {
808
+ const cur = rows[i];
809
+ if (cur.observed_at > best.observed_at) {
810
+ best = cur;
811
+ } else if (cur.observed_at === best.observed_at && cur.observation_type === "SPECI" && best.observation_type !== "SPECI") {
812
+ best = cur;
813
+ }
814
+ }
815
+ return best;
816
+ }
817
+
818
+ // ../weather/src/live/latest.ts
819
+ async function latest(station, opts = {}) {
820
+ const src = validateSource(opts.source ?? void 0);
821
+ const rows = await fetchLatest(station, src);
822
+ const picked = pickMostRecent(rows);
823
+ if (picked === null) {
824
+ throw new NoLiveDataError(
825
+ `no live data for station=${JSON.stringify(station)} source=${JSON.stringify(src)}`,
826
+ {
827
+ station: normalizeStation(station),
828
+ source: sourceTag(src)
829
+ }
830
+ );
831
+ }
832
+ return picked;
833
+ }
834
+
835
+ // ../weather/src/live/stream.ts
836
+ async function sleep3(ms, signal) {
837
+ if (signal?.aborted) return;
838
+ await new Promise((resolve) => {
839
+ const onAbort = () => {
840
+ clearTimeout(t);
841
+ resolve();
842
+ };
843
+ const t = setTimeout(() => {
844
+ signal?.removeEventListener("abort", onAbort);
845
+ resolve();
846
+ }, ms);
847
+ signal?.addEventListener("abort", onAbort, { once: true });
848
+ });
849
+ }
850
+ async function* stream(station, opts = {}) {
851
+ const src = validateSource(opts.source ?? void 0);
852
+ const cadenceS = validatePollSeconds(opts.pollSeconds ?? void 0, src);
853
+ const cadenceMs = cadenceS * 1e3;
854
+ const icaoUpper = station.trim().toUpperCase();
855
+ const stationCheck = icaoUpper.length === 3 ? `K${icaoUpper}` : icaoUpper;
856
+ const STATION_CODE_RE3 = /^[A-Z]{3,4}$/;
857
+ if (!STATION_CODE_RE3.test(stationCheck)) {
858
+ throw new Error(
859
+ `station=${JSON.stringify(stationCheck)} does not match STATION_CODE_RE; refusing to start stream.`
860
+ );
861
+ }
862
+ let lastObservedAt = null;
863
+ while (true) {
864
+ if (opts.signal?.aborted) return;
865
+ let rows = [];
866
+ try {
867
+ rows = await fetchLatest(station, src);
868
+ } catch (err) {
869
+ if (err instanceof Error && /STATION_CODE_RE/.test(err.message)) {
870
+ throw err;
871
+ }
872
+ rows = [];
873
+ }
874
+ const picked = pickMostRecent(rows);
875
+ if (picked !== null) {
876
+ const current = picked.observed_at;
877
+ if (current && current !== lastObservedAt) {
878
+ lastObservedAt = current;
879
+ yield picked;
880
+ }
881
+ }
882
+ if (opts.signal?.aborted) return;
883
+ await sleep3(cadenceMs, opts.signal);
884
+ }
885
+ }
886
+
887
+ // ../weather/src/forecasts/iem-mos.ts
888
+ var IEM_MOS_URL = "https://mesonet.agron.iastate.edu/api/1/mos.json";
889
+ var SUPPORTED_MODELS = /* @__PURE__ */ new Set([
890
+ "nbe",
891
+ "gfs",
892
+ "lav",
893
+ "met",
894
+ "ecm"
895
+ ]);
896
+ var KT_TO_MS = 0.5144444;
897
+ var NBE_CYCLE_CUTOVER = Date.UTC(2026, 5 - 1, 5, 0, 0, 0);
898
+ function runtimeHoursFor(model, fromDt, toDt) {
899
+ if (model !== "nbe") return [0, 6, 12, 18];
900
+ const fromMs = fromDt.getTime();
901
+ const toMs = toDt.getTime();
902
+ const pre = fromMs < NBE_CYCLE_CUTOVER;
903
+ const post = toMs >= NBE_CYCLE_CUTOVER;
904
+ if (pre && post) return [0, 1, 6, 7, 12, 13, 18, 19];
905
+ if (pre) return [1, 7, 13, 19];
906
+ return [0, 6, 12, 18];
907
+ }
908
+ function fahrenheitToCelsius(f) {
909
+ if (f === null || Number.isNaN(f)) return null;
910
+ return (f - 32) * 5 / 9;
911
+ }
912
+ function knotsToMs(kt) {
913
+ if (kt === null || Number.isNaN(kt)) return null;
914
+ return kt * KT_TO_MS;
915
+ }
916
+ function percentToUnit(pct) {
917
+ if (pct === null || Number.isNaN(pct)) return null;
918
+ return pct / 100;
919
+ }
920
+ function maybeNumber(value) {
921
+ if (value === null || value === void 0 || value === "M" || value === "") {
922
+ return null;
923
+ }
924
+ const num = typeof value === "number" ? value : Number(value);
925
+ if (Number.isNaN(num)) return null;
926
+ return num;
927
+ }
928
+ function parseDate(value) {
929
+ if (typeof value !== "string" || !value) return null;
930
+ const dt = new Date(value);
931
+ if (Number.isNaN(dt.getTime())) return null;
932
+ return dt;
933
+ }
934
+ function parseRow(raw, station, model, retrievedAt) {
935
+ const issuedDt = parseDate(raw.runtime ?? raw.model_runtime);
936
+ const validDt = parseDate(raw.ftime ?? raw.valid_time);
937
+ if (issuedDt === null || validDt === null) return null;
938
+ const forecastHour = Math.round(
939
+ (validDt.getTime() - issuedDt.getTime()) / 36e5
940
+ );
941
+ return {
942
+ station,
943
+ model: model.toUpperCase(),
944
+ issuedAt: issuedDt.toISOString(),
945
+ validAt: validDt.toISOString(),
946
+ forecastHour,
947
+ tempC: fahrenheitToCelsius(maybeNumber(raw.tmp)),
948
+ dewPointC: fahrenheitToCelsius(maybeNumber(raw.dpt)),
949
+ windSpeedMs: knotsToMs(maybeNumber(raw.wsp)),
950
+ windDirDeg: (() => {
951
+ const d = maybeNumber(raw.wdr);
952
+ return d === null ? null : Math.round(d);
953
+ })(),
954
+ precipProbability: percentToUnit(maybeNumber(raw.pop12)),
955
+ skyCoverPct: null,
956
+ source: "iem.archive",
957
+ retrievedAt
958
+ };
959
+ }
960
+ function parseIsoDate(iso, endOfDay) {
961
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
962
+ if (match === null) {
963
+ throw new Error(
964
+ `iemMosForecasts: from/to dates must be ISO YYYY-MM-DD; got ${iso}`
965
+ );
966
+ }
967
+ const [, y, m, d] = match;
968
+ if (endOfDay) {
969
+ return new Date(Date.UTC(Number(y), Number(m) - 1, Number(d), 23, 59, 59));
970
+ }
971
+ return new Date(Date.UTC(Number(y), Number(m) - 1, Number(d)));
972
+ }
973
+ async function iemMosForecasts(station, fromDate, toDate, opts = {}) {
974
+ const model = opts.model ?? "nbe";
975
+ if (!SUPPORTED_MODELS.has(model)) {
976
+ throw new Error(
977
+ `iemMosForecasts: model must be one of ${[...SUPPORTED_MODELS].sort().join(",")}; got ${model}`
978
+ );
979
+ }
980
+ const fetchFn = opts.fetchFn ?? fetch;
981
+ const fromDt = parseIsoDate(fromDate, false);
982
+ const toDt = parseIsoDate(toDate, true);
983
+ const hours = runtimeHoursFor(model, fromDt, toDt);
984
+ const retrievedAt = (/* @__PURE__ */ new Date()).toISOString();
985
+ const rows = [];
986
+ const dayMs = 864e5;
987
+ for (let day = fromDt.getTime(); day <= toDt.getTime(); day += dayMs) {
988
+ for (const h of hours) {
989
+ const rt = new Date(day);
990
+ rt.setUTCHours(h, 0, 0, 0);
991
+ if (rt < fromDt || rt > toDt) continue;
992
+ const url = `${IEM_MOS_URL}?station=${encodeURIComponent(
993
+ station
994
+ )}&model=${encodeURIComponent(model)}&runtime=${encodeURIComponent(
995
+ rt.toISOString()
996
+ )}`;
997
+ const resp = await fetchFn(url);
998
+ if (resp.status === 404) continue;
999
+ if (!resp.ok) {
1000
+ throw new Error(
1001
+ `iemMosForecasts: HTTP ${resp.status} on ${url}`
1002
+ );
1003
+ }
1004
+ const payload = await resp.json();
1005
+ for (const raw of payload.data ?? []) {
1006
+ const projected = parseRow(raw, station, model, retrievedAt);
1007
+ if (projected !== null) rows.push(projected);
1008
+ }
1009
+ }
1010
+ }
1011
+ return rows;
1012
+ }
1013
+
1014
+ // ../weather/src/forecasts/nwp-stub.ts
1015
+ async function forecastNwp(_station, _model, _opts = {}) {
1016
+ throw new Error(
1017
+ "forecastNwp: TS NWP deferred to v1.1 per CONTEXT decision 7. Browser GRIB2 decode is not production-ready in May 2026; the v1.0 TS forecast surface ships iemMosForecasts() only. Use the Python SDK's mostlyright.forecast_nwp() for NWP in v1.0."
1018
+ );
1019
+ }
1020
+
1021
+ // ../weather/src/index.ts
1022
+ var version = "0.0.0";
1023
+ function helloWeather() {
1024
+ return "hello @mostlyrightmd/weather";
1025
+ }
1026
+
1027
+ // ../markets/src/index.ts
1028
+ var src_exports3 = {};
1029
+ __export(src_exports3, {
1030
+ ContractIdError: () => ContractIdError,
1031
+ KALSHI_SETTLEMENT_STATIONS: () => KALSHI_SETTLEMENT_STATIONS,
1032
+ KNOWN_WRONG_STATIONS: () => KNOWN_WRONG_STATIONS,
1033
+ POLYMARKET_CITY_STATIONS: () => POLYMARKET_CITY_STATIONS,
1034
+ helloMarkets: () => helloMarkets,
1035
+ kalshiNhighResolve: () => kalshiNhighResolve,
1036
+ kalshiNlowResolve: () => kalshiNlowResolve,
1037
+ kalshiSettlementFor: () => kalshiSettlementFor,
1038
+ version: () => version2
1039
+ });
1040
+
1041
+ // ../markets/src/data/generated/kalshi-stations.ts
1042
+ var KALSHI_SETTLEMENT_STATIONS = {
1043
+ ATL: {
1044
+ citation: "https://kalshi.com/markets/khighatl (Atlanta Hartsfield-Jackson)",
1045
+ station: "KATL"
1046
+ },
1047
+ AUS: {
1048
+ citation: "https://kalshi.com/markets/khighaus (Austin-Bergstrom; the only Austin station Kalshi cites)",
1049
+ station: "KAUS"
1050
+ },
1051
+ BNA: {
1052
+ citation: "https://kalshi.com/markets/khighbna (Nashville International)",
1053
+ station: "KBNA"
1054
+ },
1055
+ BOS: {
1056
+ citation: "https://kalshi.com/markets/khighbos (Boston Logan)",
1057
+ station: "KBOS"
1058
+ },
1059
+ CHI: {
1060
+ citation: "https://kalshi.com/markets/khighchi (Midway, NOT ORD)",
1061
+ station: "KMDW"
1062
+ },
1063
+ CVG: {
1064
+ citation: "https://kalshi.com/markets/khighcvg (Cincinnati/Northern Kentucky International)",
1065
+ station: "KCVG"
1066
+ },
1067
+ DAL: {
1068
+ citation: "https://kalshi.com/markets/khighdal (DFW, NOT Love Field)",
1069
+ station: "KDFW"
1070
+ },
1071
+ DCA: {
1072
+ citation: "https://kalshi.com/markets/khighdca (Reagan National, NOT Dulles or BWI)",
1073
+ station: "KDCA"
1074
+ },
1075
+ DEN: {
1076
+ citation: "https://kalshi.com/markets/khighden (Denver International)",
1077
+ station: "KDEN"
1078
+ },
1079
+ DTW: {
1080
+ citation: "https://kalshi.com/markets/khighdtw (Detroit Metropolitan)",
1081
+ station: "KDTW"
1082
+ },
1083
+ HOU: {
1084
+ citation: "https://kalshi.com/markets/khighhou (Intercontinental, NOT Hobby; Kalshi cites IAH)",
1085
+ station: "KIAH"
1086
+ },
1087
+ LAX: {
1088
+ citation: "https://kalshi.com/markets/khighlax (LAX international)",
1089
+ station: "KLAX"
1090
+ },
1091
+ MIA: {
1092
+ citation: "https://kalshi.com/markets/khighmia (Miami International)",
1093
+ station: "KMIA"
1094
+ },
1095
+ MSP: {
1096
+ citation: "https://kalshi.com/markets/khighmsp (Minneapolis-St. Paul International)",
1097
+ station: "KMSP"
1098
+ },
1099
+ NYC: {
1100
+ citation: "https://kalshi.com/markets/khighny (Central Park, NOT LGA/JFK)",
1101
+ station: "KNYC"
1102
+ },
1103
+ PHL: {
1104
+ citation: "https://kalshi.com/markets/khighphl (Philadelphia International)",
1105
+ station: "KPHL"
1106
+ },
1107
+ PHX: {
1108
+ citation: "https://kalshi.com/markets/khighphx (Sky Harbor International)",
1109
+ station: "KPHX"
1110
+ },
1111
+ SEA: {
1112
+ citation: "https://kalshi.com/markets/khighsea (SeaTac, NOT BFI)",
1113
+ station: "KSEA"
1114
+ },
1115
+ SFO: {
1116
+ citation: "https://kalshi.com/markets/khighsfo (San Francisco International, NOT OAK)",
1117
+ station: "KSFO"
1118
+ },
1119
+ SLC: {
1120
+ citation: "https://kalshi.com/markets/khighslc (Salt Lake City International)",
1121
+ station: "KSLC"
1122
+ }
1123
+ };
1124
+ var KNOWN_WRONG_STATIONS = /* @__PURE__ */ new Set([
1125
+ "KBWI",
1126
+ "KDAL",
1127
+ "KEWR",
1128
+ "KHOU",
1129
+ "KIAD",
1130
+ "KJFK",
1131
+ "KLGA",
1132
+ "KOAK",
1133
+ "KORD"
1134
+ ]);
1135
+
1136
+ // ../markets/src/data/generated/polymarket-city-stations.ts
1137
+ var POLYMARKET_CITY_STATIONS = {
1138
+ amsterdam: {
1139
+ default: "EHAM"
1140
+ },
1141
+ atlanta: {
1142
+ default: "KATL"
1143
+ },
1144
+ auckland: {
1145
+ default: "NZAA"
1146
+ },
1147
+ austin: {
1148
+ default: "KAUS"
1149
+ },
1150
+ bangkok: {
1151
+ default: "VTBS"
1152
+ },
1153
+ barcelona: {
1154
+ default: "LEBL"
1155
+ },
1156
+ beijing: {
1157
+ default: "ZBAA"
1158
+ },
1159
+ berlin: {
1160
+ default: "EDDB"
1161
+ },
1162
+ boston: {
1163
+ default: "KBOS"
1164
+ },
1165
+ brisbane: {
1166
+ default: "YBBN"
1167
+ },
1168
+ buenos_aires: {
1169
+ default: "SAEZ"
1170
+ },
1171
+ chicago: {
1172
+ default: "KORD",
1173
+ high: "KORD",
1174
+ low: "KORD"
1175
+ },
1176
+ copenhagen: {
1177
+ default: "EKCH"
1178
+ },
1179
+ dallas: {
1180
+ default: "KDFW"
1181
+ },
1182
+ delhi: {
1183
+ default: "VIDP"
1184
+ },
1185
+ denver: {
1186
+ default: "KDEN"
1187
+ },
1188
+ detroit: {
1189
+ default: "KDTW"
1190
+ },
1191
+ doha: {
1192
+ default: "OTHH"
1193
+ },
1194
+ dubai: {
1195
+ default: "OMDB"
1196
+ },
1197
+ frankfurt: {
1198
+ default: "EDDF"
1199
+ },
1200
+ helsinki: {
1201
+ default: "EFHK"
1202
+ },
1203
+ hong_kong: {
1204
+ default: "VHHH",
1205
+ high: "VHHH",
1206
+ low: "VHHH"
1207
+ },
1208
+ houston: {
1209
+ default: "KIAH"
1210
+ },
1211
+ london: {
1212
+ default: "EGLL"
1213
+ },
1214
+ london_gatwick: {
1215
+ default: "EGKK"
1216
+ },
1217
+ los_angeles: {
1218
+ default: "KLAX",
1219
+ high: "KLAX",
1220
+ low: "KLAX"
1221
+ },
1222
+ madrid: {
1223
+ default: "LEMD"
1224
+ },
1225
+ melbourne: {
1226
+ default: "YMML"
1227
+ },
1228
+ miami: {
1229
+ default: "KMIA"
1230
+ },
1231
+ milan: {
1232
+ default: "LIMC"
1233
+ },
1234
+ minneapolis: {
1235
+ default: "KMSP"
1236
+ },
1237
+ moscow: {
1238
+ default: "UUEE"
1239
+ },
1240
+ mumbai: {
1241
+ default: "VABB"
1242
+ },
1243
+ munich: {
1244
+ default: "EDDM"
1245
+ },
1246
+ nyc: {
1247
+ default: "KLGA",
1248
+ high: "KLGA",
1249
+ low: "KLGA"
1250
+ },
1251
+ paris: {
1252
+ default: "LFPG",
1253
+ high: "LFPG",
1254
+ low: "LFPB"
1255
+ },
1256
+ paris_orly: {
1257
+ default: "LFPO"
1258
+ },
1259
+ philadelphia: {
1260
+ default: "KPHL"
1261
+ },
1262
+ phoenix: {
1263
+ default: "KPHX"
1264
+ },
1265
+ riyadh: {
1266
+ default: "OERK"
1267
+ },
1268
+ rome: {
1269
+ default: "LIRF"
1270
+ },
1271
+ san_francisco: {
1272
+ default: "KSFO"
1273
+ },
1274
+ sao_paulo: {
1275
+ default: "SBGR"
1276
+ },
1277
+ seattle: {
1278
+ default: "KSEA"
1279
+ },
1280
+ seoul: {
1281
+ default: "RKSI"
1282
+ },
1283
+ shanghai: {
1284
+ default: "ZSPD"
1285
+ },
1286
+ singapore: {
1287
+ default: "WSSS"
1288
+ },
1289
+ stockholm: {
1290
+ default: "ESSA"
1291
+ },
1292
+ sydney: {
1293
+ default: "YSSY"
1294
+ },
1295
+ taipei: {
1296
+ default: "RCTP"
1297
+ },
1298
+ tokyo: {
1299
+ default: "RJTT",
1300
+ high: "RJTT",
1301
+ low: "RJTT"
1302
+ },
1303
+ tokyo_narita: {
1304
+ default: "RJAA"
1305
+ },
1306
+ vienna: {
1307
+ default: "LOWW"
1308
+ },
1309
+ warsaw: {
1310
+ default: "EPWA"
1311
+ },
1312
+ washington_dc: {
1313
+ default: "KDCA"
1314
+ },
1315
+ wellington: {
1316
+ default: "NZWN"
1317
+ },
1318
+ zurich: {
1319
+ default: "LSZH"
1320
+ }
1321
+ };
1322
+
1323
+ // ../markets/src/resolvers/kalshi-nhigh.ts
1324
+ var ContractIdError = class extends Error {
1325
+ constructor(message) {
1326
+ super(message);
1327
+ this.name = "ContractIdError";
1328
+ }
1329
+ };
1330
+ function coerceContractDate(value) {
1331
+ if (typeof value === "string") {
1332
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
1333
+ throw new ContractIdError(
1334
+ `settlementDate string must be YYYY-MM-DD; got ${JSON.stringify(value)}`
1335
+ );
1336
+ }
1337
+ const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
1338
+ if (Number.isNaN(parsed.getTime())) {
1339
+ throw new ContractIdError(
1340
+ `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`
1341
+ );
1342
+ }
1343
+ const iso = parsed.toISOString().slice(0, 10);
1344
+ if (iso !== value) {
1345
+ throw new ContractIdError(
1346
+ `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`
1347
+ );
1348
+ }
1349
+ return value;
1350
+ }
1351
+ if (value instanceof Date) {
1352
+ if (Number.isNaN(value.getTime())) {
1353
+ throw new ContractIdError("settlementDate is an invalid Date instance");
1354
+ }
1355
+ if (value.getUTCHours() !== 0 || value.getUTCMinutes() !== 0 || value.getUTCSeconds() !== 0 || value.getUTCMilliseconds() !== 0) {
1356
+ throw new ContractIdError(
1357
+ `settlementDate must be a UTC date-only Date (H/M/S/ms = 0); got ${value.toISOString()}. Use new Date('YYYY-MM-DDT00:00:00Z') or pass a 'YYYY-MM-DD' string.`
1358
+ );
1359
+ }
1360
+ return value.toISOString().slice(0, 10);
1361
+ }
1362
+ throw new ContractIdError("settlementDate must be a Date instance or YYYY-MM-DD string");
1363
+ }
1364
+ function kalshiNhighResolve(contractId, settlementDate) {
1365
+ if (typeof contractId !== "string") {
1366
+ throw new ContractIdError(`contractId must be a string; got ${typeof contractId}`);
1367
+ }
1368
+ const contractDate = coerceContractDate(settlementDate);
1369
+ const cid = contractId.toUpperCase();
1370
+ if (!cid.startsWith("KHIGH") || cid.length <= 5) {
1371
+ throw new ContractIdError(
1372
+ `NHIGH contractId must follow 'KHIGH<CITY>' format; got ${JSON.stringify(contractId)}`
1373
+ );
1374
+ }
1375
+ const cityTicker = cid.slice(5);
1376
+ const station = KALSHI_SETTLEMENT_STATIONS[cityTicker];
1377
+ if (station === void 0) {
1378
+ const known = Object.keys(KALSHI_SETTLEMENT_STATIONS).sort();
1379
+ throw new ContractIdError(
1380
+ `unknown city ${JSON.stringify(cityTicker)} (known: ${known.join(", ")})`
1381
+ );
1382
+ }
1383
+ return Object.freeze({
1384
+ settlementSource: "cli.archive",
1385
+ settlementStation: station.station,
1386
+ cityTicker,
1387
+ contractDate
1388
+ });
1389
+ }
1390
+
1391
+ // ../markets/src/resolvers/kalshi-nlow.ts
1392
+ function coerceContractDate2(value) {
1393
+ if (typeof value === "string") {
1394
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
1395
+ throw new ContractIdError(
1396
+ `settlementDate string must be YYYY-MM-DD; got ${JSON.stringify(value)}`
1397
+ );
1398
+ }
1399
+ const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
1400
+ if (Number.isNaN(parsed.getTime())) {
1401
+ throw new ContractIdError(
1402
+ `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`
1403
+ );
1404
+ }
1405
+ const iso = parsed.toISOString().slice(0, 10);
1406
+ if (iso !== value) {
1407
+ throw new ContractIdError(
1408
+ `settlementDate string is not a valid calendar date; got ${JSON.stringify(value)}`
1409
+ );
1410
+ }
1411
+ return value;
1412
+ }
1413
+ if (value instanceof Date) {
1414
+ if (Number.isNaN(value.getTime())) {
1415
+ throw new ContractIdError("settlementDate is an invalid Date instance");
1416
+ }
1417
+ if (value.getUTCHours() !== 0 || value.getUTCMinutes() !== 0 || value.getUTCSeconds() !== 0 || value.getUTCMilliseconds() !== 0) {
1418
+ throw new ContractIdError(
1419
+ `settlementDate must be a UTC date-only Date (H/M/S/ms = 0); got ${value.toISOString()}. Use new Date('YYYY-MM-DDT00:00:00Z') or pass a 'YYYY-MM-DD' string.`
1420
+ );
1421
+ }
1422
+ return value.toISOString().slice(0, 10);
1423
+ }
1424
+ throw new ContractIdError("settlementDate must be a Date instance or YYYY-MM-DD string");
1425
+ }
1426
+ function kalshiNlowResolve(contractId, settlementDate) {
1427
+ if (typeof contractId !== "string") {
1428
+ throw new ContractIdError(`contractId must be a string; got ${typeof contractId}`);
1429
+ }
1430
+ const contractDate = coerceContractDate2(settlementDate);
1431
+ const cid = contractId.toUpperCase();
1432
+ if (!cid.startsWith("KLOW") || cid.length <= 4) {
1433
+ throw new ContractIdError(
1434
+ `NLOW contractId must follow 'KLOW<CITY>' format; got ${JSON.stringify(contractId)}`
1435
+ );
1436
+ }
1437
+ const cityTicker = cid.slice(4);
1438
+ const station = KALSHI_SETTLEMENT_STATIONS[cityTicker];
1439
+ if (station === void 0) {
1440
+ const known = Object.keys(KALSHI_SETTLEMENT_STATIONS).sort();
1441
+ throw new ContractIdError(
1442
+ `unknown city ${JSON.stringify(cityTicker)} (known: ${known.join(", ")})`
1443
+ );
1444
+ }
1445
+ return Object.freeze({
1446
+ settlementSource: "cli.archive",
1447
+ settlementStation: station.station,
1448
+ cityTicker,
1449
+ contractDate
1450
+ });
1451
+ }
1452
+
1453
+ // ../markets/src/kalshi-settlement.ts
1454
+ function kalshiSettlementFor(contractId, date) {
1455
+ if (typeof contractId !== "string" || contractId.length === 0) {
1456
+ throw new ContractIdError("contractId must be a non-empty string");
1457
+ }
1458
+ const upper = contractId.toUpperCase();
1459
+ if (upper.startsWith("KHIGH")) return kalshiNhighResolve(upper, date);
1460
+ if (upper.startsWith("KLOW")) return kalshiNlowResolve(upper, date);
1461
+ throw new ContractIdError(
1462
+ `contractId ${JSON.stringify(contractId)} does not start with KHIGH or KLOW`
1463
+ );
1464
+ }
1465
+
1466
+ // ../markets/src/index.ts
1467
+ var version2 = "0.0.0";
1468
+ function helloMarkets() {
1469
+ return "hello @mostlyrightmd/markets";
1470
+ }
1471
+
1472
+ // ../../node_modules/.pnpm/idb@8.0.3/node_modules/idb/build/index.js
1473
+ var instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
1474
+ var idbProxyableTypes;
1475
+ var cursorAdvanceMethods;
1476
+ function getIdbProxyableTypes() {
1477
+ return idbProxyableTypes || (idbProxyableTypes = [
1478
+ IDBDatabase,
1479
+ IDBObjectStore,
1480
+ IDBIndex,
1481
+ IDBCursor,
1482
+ IDBTransaction
1483
+ ]);
1484
+ }
1485
+ function getCursorAdvanceMethods() {
1486
+ return cursorAdvanceMethods || (cursorAdvanceMethods = [
1487
+ IDBCursor.prototype.advance,
1488
+ IDBCursor.prototype.continue,
1489
+ IDBCursor.prototype.continuePrimaryKey
1490
+ ]);
1491
+ }
1492
+ var transactionDoneMap = /* @__PURE__ */ new WeakMap();
1493
+ var transformCache = /* @__PURE__ */ new WeakMap();
1494
+ var reverseTransformCache = /* @__PURE__ */ new WeakMap();
1495
+ function promisifyRequest(request) {
1496
+ const promise = new Promise((resolve, reject) => {
1497
+ const unlisten = () => {
1498
+ request.removeEventListener("success", success);
1499
+ request.removeEventListener("error", error);
1500
+ };
1501
+ const success = () => {
1502
+ resolve(wrap(request.result));
1503
+ unlisten();
1504
+ };
1505
+ const error = () => {
1506
+ reject(request.error);
1507
+ unlisten();
1508
+ };
1509
+ request.addEventListener("success", success);
1510
+ request.addEventListener("error", error);
1511
+ });
1512
+ reverseTransformCache.set(promise, request);
1513
+ return promise;
1514
+ }
1515
+ function cacheDonePromiseForTransaction(tx) {
1516
+ if (transactionDoneMap.has(tx))
1517
+ return;
1518
+ const done = new Promise((resolve, reject) => {
1519
+ const unlisten = () => {
1520
+ tx.removeEventListener("complete", complete);
1521
+ tx.removeEventListener("error", error);
1522
+ tx.removeEventListener("abort", error);
1523
+ };
1524
+ const complete = () => {
1525
+ resolve();
1526
+ unlisten();
1527
+ };
1528
+ const error = () => {
1529
+ reject(tx.error || new DOMException("AbortError", "AbortError"));
1530
+ unlisten();
1531
+ };
1532
+ tx.addEventListener("complete", complete);
1533
+ tx.addEventListener("error", error);
1534
+ tx.addEventListener("abort", error);
1535
+ });
1536
+ transactionDoneMap.set(tx, done);
1537
+ }
1538
+ var idbProxyTraps = {
1539
+ get(target, prop, receiver) {
1540
+ if (target instanceof IDBTransaction) {
1541
+ if (prop === "done")
1542
+ return transactionDoneMap.get(target);
1543
+ if (prop === "store") {
1544
+ return receiver.objectStoreNames[1] ? void 0 : receiver.objectStore(receiver.objectStoreNames[0]);
1545
+ }
1546
+ }
1547
+ return wrap(target[prop]);
1548
+ },
1549
+ set(target, prop, value) {
1550
+ target[prop] = value;
1551
+ return true;
1552
+ },
1553
+ has(target, prop) {
1554
+ if (target instanceof IDBTransaction && (prop === "done" || prop === "store")) {
1555
+ return true;
1556
+ }
1557
+ return prop in target;
1558
+ }
1559
+ };
1560
+ function replaceTraps(callback) {
1561
+ idbProxyTraps = callback(idbProxyTraps);
1562
+ }
1563
+ function wrapFunction(func) {
1564
+ if (getCursorAdvanceMethods().includes(func)) {
1565
+ return function(...args) {
1566
+ func.apply(unwrap(this), args);
1567
+ return wrap(this.request);
1568
+ };
1569
+ }
1570
+ return function(...args) {
1571
+ return wrap(func.apply(unwrap(this), args));
1572
+ };
1573
+ }
1574
+ function transformCachableValue(value) {
1575
+ if (typeof value === "function")
1576
+ return wrapFunction(value);
1577
+ if (value instanceof IDBTransaction)
1578
+ cacheDonePromiseForTransaction(value);
1579
+ if (instanceOfAny(value, getIdbProxyableTypes()))
1580
+ return new Proxy(value, idbProxyTraps);
1581
+ return value;
1582
+ }
1583
+ function wrap(value) {
1584
+ if (value instanceof IDBRequest)
1585
+ return promisifyRequest(value);
1586
+ if (transformCache.has(value))
1587
+ return transformCache.get(value);
1588
+ const newValue = transformCachableValue(value);
1589
+ if (newValue !== value) {
1590
+ transformCache.set(value, newValue);
1591
+ reverseTransformCache.set(newValue, value);
1592
+ }
1593
+ return newValue;
1594
+ }
1595
+ var unwrap = (value) => reverseTransformCache.get(value);
1596
+ function openDB(name, version4, { blocked, upgrade, blocking, terminated } = {}) {
1597
+ const request = indexedDB.open(name, version4);
1598
+ const openPromise = wrap(request);
1599
+ if (upgrade) {
1600
+ request.addEventListener("upgradeneeded", (event) => {
1601
+ upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
1602
+ });
1603
+ }
1604
+ if (blocked) {
1605
+ request.addEventListener("blocked", (event) => blocked(
1606
+ // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405
1607
+ event.oldVersion,
1608
+ event.newVersion,
1609
+ event
1610
+ ));
1611
+ }
1612
+ openPromise.then((db) => {
1613
+ if (terminated)
1614
+ db.addEventListener("close", () => terminated());
1615
+ if (blocking) {
1616
+ db.addEventListener("versionchange", (event) => blocking(event.oldVersion, event.newVersion, event));
1617
+ }
1618
+ }).catch(() => {
1619
+ });
1620
+ return openPromise;
1621
+ }
1622
+ var readMethods = ["get", "getKey", "getAll", "getAllKeys", "count"];
1623
+ var writeMethods = ["put", "add", "delete", "clear"];
1624
+ var cachedMethods = /* @__PURE__ */ new Map();
1625
+ function getMethod(target, prop) {
1626
+ if (!(target instanceof IDBDatabase && !(prop in target) && typeof prop === "string")) {
1627
+ return;
1628
+ }
1629
+ if (cachedMethods.get(prop))
1630
+ return cachedMethods.get(prop);
1631
+ const targetFuncName = prop.replace(/FromIndex$/, "");
1632
+ const useIndex = prop !== targetFuncName;
1633
+ const isWrite = writeMethods.includes(targetFuncName);
1634
+ if (
1635
+ // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
1636
+ !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || !(isWrite || readMethods.includes(targetFuncName))
1637
+ ) {
1638
+ return;
1639
+ }
1640
+ const method = async function(storeName, ...args) {
1641
+ const tx = this.transaction(storeName, isWrite ? "readwrite" : "readonly");
1642
+ let target2 = tx.store;
1643
+ if (useIndex)
1644
+ target2 = target2.index(args.shift());
1645
+ return (await Promise.all([
1646
+ target2[targetFuncName](...args),
1647
+ isWrite && tx.done
1648
+ ]))[0];
1649
+ };
1650
+ cachedMethods.set(prop, method);
1651
+ return method;
1652
+ }
1653
+ replaceTraps((oldTraps) => ({
1654
+ ...oldTraps,
1655
+ get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
1656
+ has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop)
1657
+ }));
1658
+ var advanceMethodProps = ["continue", "continuePrimaryKey", "advance"];
1659
+ var methodMap = {};
1660
+ var advanceResults = /* @__PURE__ */ new WeakMap();
1661
+ var ittrProxiedCursorToOriginalProxy = /* @__PURE__ */ new WeakMap();
1662
+ var cursorIteratorTraps = {
1663
+ get(target, prop) {
1664
+ if (!advanceMethodProps.includes(prop))
1665
+ return target[prop];
1666
+ let cachedFunc = methodMap[prop];
1667
+ if (!cachedFunc) {
1668
+ cachedFunc = methodMap[prop] = function(...args) {
1669
+ advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
1670
+ };
1671
+ }
1672
+ return cachedFunc;
1673
+ }
1674
+ };
1675
+ async function* iterate(...args) {
1676
+ let cursor = this;
1677
+ if (!(cursor instanceof IDBCursor)) {
1678
+ cursor = await cursor.openCursor(...args);
1679
+ }
1680
+ if (!cursor)
1681
+ return;
1682
+ cursor = cursor;
1683
+ const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
1684
+ ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
1685
+ reverseTransformCache.set(proxiedCursor, unwrap(cursor));
1686
+ while (cursor) {
1687
+ yield proxiedCursor;
1688
+ cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
1689
+ advanceResults.delete(proxiedCursor);
1690
+ }
1691
+ }
1692
+ function isIteratorProp(target, prop) {
1693
+ return prop === Symbol.asyncIterator && instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor]) || prop === "iterate" && instanceOfAny(target, [IDBIndex, IDBObjectStore]);
1694
+ }
1695
+ replaceTraps((oldTraps) => ({
1696
+ ...oldTraps,
1697
+ get(target, prop, receiver) {
1698
+ if (isIteratorProp(target, prop))
1699
+ return iterate;
1700
+ return oldTraps.get(target, prop, receiver);
1701
+ },
1702
+ has(target, prop) {
1703
+ return isIteratorProp(target, prop) || oldTraps.has(target, prop);
1704
+ }
1705
+ }));
1706
+
1707
+ // ../core/dist/internal/chunk-PKJXHY27.mjs
1708
+ function lockKeyFor(key) {
1709
+ return `mostlyright:cache:lock:${key}`;
1710
+ }
1711
+ var MemoryStore = class {
1712
+ #entries = /* @__PURE__ */ new Map();
1713
+ #chain = /* @__PURE__ */ new Map();
1714
+ async get(key) {
1715
+ const e = this.#entries.get(key);
1716
+ if (e === void 0) return null;
1717
+ if (e.expiresAt !== void 0 && Date.now() >= e.expiresAt) {
1718
+ this.#entries.delete(key);
1719
+ return null;
1720
+ }
1721
+ return structuredClone(e.value);
1722
+ }
1723
+ async set(key, value, opts) {
1724
+ const cloned = structuredClone(value);
1725
+ const entry = opts?.ttlMs !== void 0 ? { value: cloned, expiresAt: Date.now() + opts.ttlMs } : { value: cloned };
1726
+ this.#entries.set(key, entry);
1727
+ }
1728
+ async delete(key) {
1729
+ this.#entries.delete(key);
1730
+ }
1731
+ /**
1732
+ * Enumerate live (non-expired) keys with the given prefix.
1733
+ *
1734
+ * TS-W6 Wave 1: `availability()` uses this to count cached observation
1735
+ * months and climate years per station. Expired entries are evicted as a
1736
+ * side effect (same lazy-eviction policy as `.get`).
1737
+ */
1738
+ async listKeys(prefix) {
1739
+ const now = Date.now();
1740
+ const out = [];
1741
+ for (const [key, entry] of this.#entries) {
1742
+ if (entry.expiresAt !== void 0 && now >= entry.expiresAt) {
1743
+ this.#entries.delete(key);
1744
+ continue;
1745
+ }
1746
+ if (key.startsWith(prefix)) {
1747
+ out.push(key);
1748
+ }
1749
+ }
1750
+ return Object.freeze(out);
1751
+ }
1752
+ async withLock(key, fn) {
1753
+ const prev = this.#chain.get(key) ?? Promise.resolve();
1754
+ const next = prev.then(
1755
+ () => fn(),
1756
+ () => fn()
1757
+ );
1758
+ const absorbed = next.then(
1759
+ () => void 0,
1760
+ () => void 0
1761
+ );
1762
+ this.#chain.set(key, absorbed);
1763
+ absorbed.finally(() => {
1764
+ if (this.#chain.get(key) === absorbed) {
1765
+ this.#chain.delete(key);
1766
+ }
1767
+ });
1768
+ return next;
1769
+ }
1770
+ };
1771
+ var DB_NAME = "mostlyright-cache-v1";
1772
+ var STORE_NAME = "entries";
1773
+ var SCHEMA_VERSION = 1;
1774
+ function getWebLocks() {
1775
+ if (typeof navigator === "undefined") return null;
1776
+ const nav = navigator;
1777
+ return nav.locks ?? null;
1778
+ }
1779
+ var IndexedDBStore = class {
1780
+ #dbName;
1781
+ #dbPromise;
1782
+ #chain = /* @__PURE__ */ new Map();
1783
+ constructor(opts = {}) {
1784
+ this.#dbName = opts.dbName ?? DB_NAME;
1785
+ this.#dbPromise = openDB(this.#dbName, SCHEMA_VERSION, {
1786
+ upgrade(db) {
1787
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
1788
+ db.createObjectStore(STORE_NAME);
1789
+ }
1790
+ }
1791
+ });
1792
+ }
1793
+ async get(key) {
1794
+ const db = await this.#dbPromise;
1795
+ const entry = await db.get(STORE_NAME, key);
1796
+ if (entry === void 0) return null;
1797
+ if (entry.expiresAt !== void 0 && Date.now() >= entry.expiresAt) {
1798
+ try {
1799
+ await db.delete(STORE_NAME, key);
1800
+ } catch {
1801
+ }
1802
+ return null;
1803
+ }
1804
+ return entry.value;
1805
+ }
1806
+ async set(key, value, opts) {
1807
+ const db = await this.#dbPromise;
1808
+ const entry = opts?.ttlMs !== void 0 ? { value, expiresAt: Date.now() + opts.ttlMs } : { value };
1809
+ await db.put(STORE_NAME, entry, key);
1810
+ }
1811
+ async delete(key) {
1812
+ const db = await this.#dbPromise;
1813
+ await db.delete(STORE_NAME, key);
1814
+ }
1815
+ /**
1816
+ * Enumerate keys with the given prefix using IndexedDB's bounded range
1817
+ * query. Live keys only — expired entries are lazy-evicted on read by
1818
+ * `get()`, so a stale-but-not-yet-evicted entry can appear here; callers
1819
+ * who care about expiration should `get()` to confirm.
1820
+ *
1821
+ * TS-W6 Wave 1: `availability()` uses this to count observation months and
1822
+ * climate years for a station.
1823
+ */
1824
+ async listKeys(prefix) {
1825
+ const db = await this.#dbPromise;
1826
+ const range = IDBKeyRange.bound(prefix, `${prefix}\uFFFF`, false, false);
1827
+ const keys = await db.getAllKeys(STORE_NAME, range);
1828
+ const out = [];
1829
+ for (const k of keys) {
1830
+ if (typeof k === "string" && k.startsWith(prefix)) {
1831
+ out.push(k);
1832
+ }
1833
+ }
1834
+ return Object.freeze(out);
1835
+ }
1836
+ async withLock(key, fn) {
1837
+ const locks = getWebLocks();
1838
+ if (locks !== null) {
1839
+ return locks.request(lockKeyFor(key), { mode: "exclusive" }, () => fn());
1840
+ }
1841
+ const prev = this.#chain.get(key) ?? Promise.resolve();
1842
+ const next = prev.then(
1843
+ () => fn(),
1844
+ () => fn()
1845
+ );
1846
+ const absorbed = next.then(
1847
+ () => void 0,
1848
+ () => void 0
1849
+ );
1850
+ this.#chain.set(key, absorbed);
1851
+ absorbed.finally(() => {
1852
+ if (this.#chain.get(key) === absorbed) this.#chain.delete(key);
1853
+ });
1854
+ return next;
1855
+ }
1856
+ };
1857
+ var STATIONS = [
1858
+ {
1859
+ code: "EDDB",
1860
+ country: "DE",
1861
+ ghcnh_id: null,
1862
+ icao: "EDDB",
1863
+ latitude: 52.3667,
1864
+ longitude: 13.5033,
1865
+ name: "Berlin Brandenburg",
1866
+ tz: "Europe/Berlin"
1867
+ },
1868
+ {
1869
+ code: "EDDF",
1870
+ country: "DE",
1871
+ ghcnh_id: null,
1872
+ icao: "EDDF",
1873
+ latitude: 50.0379,
1874
+ longitude: 8.5622,
1875
+ name: "Frankfurt am Main",
1876
+ tz: "Europe/Berlin"
1877
+ },
1878
+ {
1879
+ code: "EDDM",
1880
+ country: "DE",
1881
+ ghcnh_id: null,
1882
+ icao: "EDDM",
1883
+ latitude: 48.3538,
1884
+ longitude: 11.7861,
1885
+ name: "Munich Franz Josef Strauss",
1886
+ tz: "Europe/Berlin"
1887
+ },
1888
+ {
1889
+ code: "EFHK",
1890
+ country: "FI",
1891
+ ghcnh_id: null,
1892
+ icao: "EFHK",
1893
+ latitude: 60.3172,
1894
+ longitude: 24.9633,
1895
+ name: "Helsinki-Vantaa",
1896
+ tz: "Europe/Helsinki"
1897
+ },
1898
+ {
1899
+ code: "EGKK",
1900
+ country: "GB",
1901
+ ghcnh_id: null,
1902
+ icao: "EGKK",
1903
+ latitude: 51.1481,
1904
+ longitude: -0.1903,
1905
+ name: "London Gatwick",
1906
+ tz: "Europe/London"
1907
+ },
1908
+ {
1909
+ code: "EGLL",
1910
+ country: "GB",
1911
+ ghcnh_id: null,
1912
+ icao: "EGLL",
1913
+ latitude: 51.4706,
1914
+ longitude: -0.4619,
1915
+ name: "London Heathrow",
1916
+ tz: "Europe/London"
1917
+ },
1918
+ {
1919
+ code: "EHAM",
1920
+ country: "NL",
1921
+ ghcnh_id: null,
1922
+ icao: "EHAM",
1923
+ latitude: 52.3086,
1924
+ longitude: 4.7639,
1925
+ name: "Amsterdam Schiphol",
1926
+ tz: "Europe/Amsterdam"
1927
+ },
1928
+ {
1929
+ code: "EKCH",
1930
+ country: "DK",
1931
+ ghcnh_id: null,
1932
+ icao: "EKCH",
1933
+ latitude: 55.6181,
1934
+ longitude: 12.6561,
1935
+ name: "Copenhagen Kastrup",
1936
+ tz: "Europe/Copenhagen"
1937
+ },
1938
+ {
1939
+ code: "EPWA",
1940
+ country: "PL",
1941
+ ghcnh_id: null,
1942
+ icao: "EPWA",
1943
+ latitude: 52.1657,
1944
+ longitude: 20.9671,
1945
+ name: "Warsaw Chopin",
1946
+ tz: "Europe/Warsaw"
1947
+ },
1948
+ {
1949
+ code: "ESSA",
1950
+ country: "SE",
1951
+ ghcnh_id: null,
1952
+ icao: "ESSA",
1953
+ latitude: 59.6519,
1954
+ longitude: 17.9186,
1955
+ name: "Stockholm Arlanda",
1956
+ tz: "Europe/Stockholm"
1957
+ },
1958
+ {
1959
+ code: "ATL",
1960
+ country: "US",
1961
+ ghcnh_id: "USW00013874",
1962
+ icao: "KATL",
1963
+ latitude: 33.6407,
1964
+ longitude: -84.4277,
1965
+ name: "Hartsfield-Jackson Atlanta International",
1966
+ tz: "America/New_York"
1967
+ },
1968
+ {
1969
+ code: "AUS",
1970
+ country: "US",
1971
+ ghcnh_id: "USW00013904",
1972
+ icao: "KAUS",
1973
+ latitude: 30.1975,
1974
+ longitude: -97.6664,
1975
+ name: "Austin-Bergstrom International",
1976
+ tz: "America/Chicago"
1977
+ },
1978
+ {
1979
+ code: "BOS",
1980
+ country: "US",
1981
+ ghcnh_id: "USW00014739",
1982
+ icao: "KBOS",
1983
+ latitude: 42.3656,
1984
+ longitude: -71.0096,
1985
+ name: "Boston Logan International",
1986
+ tz: "America/New_York"
1987
+ },
1988
+ {
1989
+ code: "DCA",
1990
+ country: "US",
1991
+ ghcnh_id: "USW00013743",
1992
+ icao: "KDCA",
1993
+ latitude: 38.8512,
1994
+ longitude: -77.0402,
1995
+ name: "Washington Reagan National",
1996
+ tz: "America/New_York"
1997
+ },
1998
+ {
1999
+ code: "DEN",
2000
+ country: "US",
2001
+ ghcnh_id: "USW00003017",
2002
+ icao: "KDEN",
2003
+ latitude: 39.8561,
2004
+ longitude: -104.6737,
2005
+ name: "Denver International",
2006
+ tz: "America/Denver"
2007
+ },
2008
+ {
2009
+ code: "DFW",
2010
+ country: "US",
2011
+ ghcnh_id: "USW00003927",
2012
+ icao: "KDFW",
2013
+ latitude: 32.8998,
2014
+ longitude: -97.0403,
2015
+ name: "Dallas-Fort Worth International",
2016
+ tz: "America/Chicago"
2017
+ },
2018
+ {
2019
+ code: "HOU",
2020
+ country: "US",
2021
+ ghcnh_id: "USW00012918",
2022
+ icao: "KHOU",
2023
+ latitude: 29.6454,
2024
+ longitude: -95.2789,
2025
+ name: "Houston Hobby",
2026
+ tz: "America/Chicago"
2027
+ },
2028
+ {
2029
+ code: "LAS",
2030
+ country: "US",
2031
+ ghcnh_id: "USW00023169",
2032
+ icao: "KLAS",
2033
+ latitude: 36.084,
2034
+ longitude: -115.1537,
2035
+ name: "Harry Reid (McCarran) International",
2036
+ tz: "America/Los_Angeles"
2037
+ },
2038
+ {
2039
+ code: "LAX",
2040
+ country: "US",
2041
+ ghcnh_id: "USW00023174",
2042
+ icao: "KLAX",
2043
+ latitude: 33.9425,
2044
+ longitude: -118.4081,
2045
+ name: "Los Angeles International",
2046
+ tz: "America/Los_Angeles"
2047
+ },
2048
+ {
2049
+ code: "MDW",
2050
+ country: "US",
2051
+ ghcnh_id: "USW00014819",
2052
+ icao: "KMDW",
2053
+ latitude: 41.7868,
2054
+ longitude: -87.7522,
2055
+ name: "Chicago Midway International",
2056
+ tz: "America/Chicago"
2057
+ },
2058
+ {
2059
+ code: "MIA",
2060
+ country: "US",
2061
+ ghcnh_id: "USW00012839",
2062
+ icao: "KMIA",
2063
+ latitude: 25.7959,
2064
+ longitude: -80.287,
2065
+ name: "Miami International",
2066
+ tz: "America/New_York"
2067
+ },
2068
+ {
2069
+ code: "MSP",
2070
+ country: "US",
2071
+ ghcnh_id: "USW00014922",
2072
+ icao: "KMSP",
2073
+ latitude: 44.8848,
2074
+ longitude: -93.2223,
2075
+ name: "Minneapolis-St Paul International",
2076
+ tz: "America/Chicago"
2077
+ },
2078
+ {
2079
+ code: "MSY",
2080
+ country: "US",
2081
+ ghcnh_id: "USW00012916",
2082
+ icao: "KMSY",
2083
+ latitude: 29.9934,
2084
+ longitude: -90.258,
2085
+ name: "New Orleans Louis Armstrong International",
2086
+ tz: "America/Chicago"
2087
+ },
2088
+ {
2089
+ code: "NYC",
2090
+ country: "US",
2091
+ ghcnh_id: "USW00094728",
2092
+ icao: "KNYC",
2093
+ latitude: 40.7789,
2094
+ longitude: -73.9692,
2095
+ name: "Central Park, New York",
2096
+ tz: "America/New_York"
2097
+ },
2098
+ {
2099
+ code: "OKC",
2100
+ country: "US",
2101
+ ghcnh_id: "USW00013967",
2102
+ icao: "KOKC",
2103
+ latitude: 35.3931,
2104
+ longitude: -97.6007,
2105
+ name: "Oklahoma City Will Rogers World",
2106
+ tz: "America/Chicago"
2107
+ },
2108
+ {
2109
+ code: "PHL",
2110
+ country: "US",
2111
+ ghcnh_id: "USW00013739",
2112
+ icao: "KPHL",
2113
+ latitude: 39.8721,
2114
+ longitude: -75.2411,
2115
+ name: "Philadelphia International",
2116
+ tz: "America/New_York"
2117
+ },
2118
+ {
2119
+ code: "PHX",
2120
+ country: "US",
2121
+ ghcnh_id: "USW00023183",
2122
+ icao: "KPHX",
2123
+ latitude: 33.4373,
2124
+ longitude: -112.0078,
2125
+ name: "Phoenix Sky Harbor International",
2126
+ tz: "America/Phoenix"
2127
+ },
2128
+ {
2129
+ code: "SAT",
2130
+ country: "US",
2131
+ ghcnh_id: "USW00012921",
2132
+ icao: "KSAT",
2133
+ latitude: 29.5337,
2134
+ longitude: -98.4698,
2135
+ name: "San Antonio International",
2136
+ tz: "America/Chicago"
2137
+ },
2138
+ {
2139
+ code: "SEA",
2140
+ country: "US",
2141
+ ghcnh_id: "USW00024233",
2142
+ icao: "KSEA",
2143
+ latitude: 47.4502,
2144
+ longitude: -122.3088,
2145
+ name: "Seattle-Tacoma International",
2146
+ tz: "America/Los_Angeles"
2147
+ },
2148
+ {
2149
+ code: "SFO",
2150
+ country: "US",
2151
+ ghcnh_id: "USW00023234",
2152
+ icao: "KSFO",
2153
+ latitude: 37.6213,
2154
+ longitude: -122.379,
2155
+ name: "San Francisco International",
2156
+ tz: "America/Los_Angeles"
2157
+ },
2158
+ {
2159
+ code: "LEBL",
2160
+ country: "ES",
2161
+ ghcnh_id: null,
2162
+ icao: "LEBL",
2163
+ latitude: 41.2974,
2164
+ longitude: 2.0833,
2165
+ name: "Barcelona El Prat",
2166
+ tz: "Europe/Madrid"
2167
+ },
2168
+ {
2169
+ code: "LEMD",
2170
+ country: "ES",
2171
+ ghcnh_id: null,
2172
+ icao: "LEMD",
2173
+ latitude: 40.4719,
2174
+ longitude: -3.5626,
2175
+ name: "Madrid Barajas",
2176
+ tz: "Europe/Madrid"
2177
+ },
2178
+ {
2179
+ code: "LFPB",
2180
+ country: "FR",
2181
+ ghcnh_id: null,
2182
+ icao: "LFPB",
2183
+ latitude: 48.9694,
2184
+ longitude: 2.4414,
2185
+ name: "Paris Le Bourget",
2186
+ tz: "Europe/Paris"
2187
+ },
2188
+ {
2189
+ code: "LFPG",
2190
+ country: "FR",
2191
+ ghcnh_id: null,
2192
+ icao: "LFPG",
2193
+ latitude: 49.0097,
2194
+ longitude: 2.5479,
2195
+ name: "Paris Charles de Gaulle",
2196
+ tz: "Europe/Paris"
2197
+ },
2198
+ {
2199
+ code: "LFPO",
2200
+ country: "FR",
2201
+ ghcnh_id: null,
2202
+ icao: "LFPO",
2203
+ latitude: 48.7233,
2204
+ longitude: 2.3794,
2205
+ name: "Paris Orly",
2206
+ tz: "Europe/Paris"
2207
+ },
2208
+ {
2209
+ code: "LIMC",
2210
+ country: "IT",
2211
+ ghcnh_id: null,
2212
+ icao: "LIMC",
2213
+ latitude: 45.6306,
2214
+ longitude: 8.7281,
2215
+ name: "Milan Malpensa",
2216
+ tz: "Europe/Rome"
2217
+ },
2218
+ {
2219
+ code: "LIRF",
2220
+ country: "IT",
2221
+ ghcnh_id: null,
2222
+ icao: "LIRF",
2223
+ latitude: 41.8003,
2224
+ longitude: 12.2389,
2225
+ name: "Rome Fiumicino",
2226
+ tz: "Europe/Rome"
2227
+ },
2228
+ {
2229
+ code: "LOWW",
2230
+ country: "AT",
2231
+ ghcnh_id: null,
2232
+ icao: "LOWW",
2233
+ latitude: 48.1103,
2234
+ longitude: 16.5697,
2235
+ name: "Vienna International",
2236
+ tz: "Europe/Vienna"
2237
+ },
2238
+ {
2239
+ code: "LSZH",
2240
+ country: "CH",
2241
+ ghcnh_id: null,
2242
+ icao: "LSZH",
2243
+ latitude: 47.4647,
2244
+ longitude: 8.5492,
2245
+ name: "Zurich",
2246
+ tz: "Europe/Zurich"
2247
+ },
2248
+ {
2249
+ code: "NZAA",
2250
+ country: "NZ",
2251
+ ghcnh_id: null,
2252
+ icao: "NZAA",
2253
+ latitude: -37.0081,
2254
+ longitude: 174.7917,
2255
+ name: "Auckland",
2256
+ tz: "Pacific/Auckland"
2257
+ },
2258
+ {
2259
+ code: "NZWN",
2260
+ country: "NZ",
2261
+ ghcnh_id: null,
2262
+ icao: "NZWN",
2263
+ latitude: -41.3272,
2264
+ longitude: 174.8053,
2265
+ name: "Wellington",
2266
+ tz: "Pacific/Auckland"
2267
+ },
2268
+ {
2269
+ code: "OERK",
2270
+ country: "SA",
2271
+ ghcnh_id: null,
2272
+ icao: "OERK",
2273
+ latitude: 24.9576,
2274
+ longitude: 46.6988,
2275
+ name: "Riyadh King Khalid International",
2276
+ tz: "Asia/Riyadh"
2277
+ },
2278
+ {
2279
+ code: "OMDB",
2280
+ country: "AE",
2281
+ ghcnh_id: null,
2282
+ icao: "OMDB",
2283
+ latitude: 25.2532,
2284
+ longitude: 55.3657,
2285
+ name: "Dubai International",
2286
+ tz: "Asia/Dubai"
2287
+ },
2288
+ {
2289
+ code: "OTHH",
2290
+ country: "QA",
2291
+ ghcnh_id: null,
2292
+ icao: "OTHH",
2293
+ latitude: 25.2731,
2294
+ longitude: 51.608,
2295
+ name: "Doha Hamad International",
2296
+ tz: "Asia/Qatar"
2297
+ },
2298
+ {
2299
+ code: "RCTP",
2300
+ country: "TW",
2301
+ ghcnh_id: null,
2302
+ icao: "RCTP",
2303
+ latitude: 25.0777,
2304
+ longitude: 121.2328,
2305
+ name: "Taipei Taoyuan",
2306
+ tz: "Asia/Taipei"
2307
+ },
2308
+ {
2309
+ code: "RJAA",
2310
+ country: "JP",
2311
+ ghcnh_id: null,
2312
+ icao: "RJAA",
2313
+ latitude: 35.7647,
2314
+ longitude: 140.3864,
2315
+ name: "Tokyo Narita",
2316
+ tz: "Asia/Tokyo"
2317
+ },
2318
+ {
2319
+ code: "RJTT",
2320
+ country: "JP",
2321
+ ghcnh_id: null,
2322
+ icao: "RJTT",
2323
+ latitude: 35.5522,
2324
+ longitude: 139.78,
2325
+ name: "Tokyo Haneda",
2326
+ tz: "Asia/Tokyo"
2327
+ },
2328
+ {
2329
+ code: "RKSI",
2330
+ country: "KR",
2331
+ ghcnh_id: null,
2332
+ icao: "RKSI",
2333
+ latitude: 37.4691,
2334
+ longitude: 126.4505,
2335
+ name: "Seoul Incheon",
2336
+ tz: "Asia/Seoul"
2337
+ },
2338
+ {
2339
+ code: "SAEZ",
2340
+ country: "AR",
2341
+ ghcnh_id: null,
2342
+ icao: "SAEZ",
2343
+ latitude: -34.8222,
2344
+ longitude: -58.5358,
2345
+ name: "Buenos Aires Ezeiza",
2346
+ tz: "America/Argentina/Buenos_Aires"
2347
+ },
2348
+ {
2349
+ code: "SBGR",
2350
+ country: "BR",
2351
+ ghcnh_id: null,
2352
+ icao: "SBGR",
2353
+ latitude: -23.4356,
2354
+ longitude: -46.4731,
2355
+ name: "S\xE3o Paulo Guarulhos",
2356
+ tz: "America/Sao_Paulo"
2357
+ },
2358
+ {
2359
+ code: "UUEE",
2360
+ country: "RU",
2361
+ ghcnh_id: null,
2362
+ icao: "UUEE",
2363
+ latitude: 55.9728,
2364
+ longitude: 37.4147,
2365
+ name: "Moscow Sheremetyevo",
2366
+ tz: "Europe/Moscow"
2367
+ },
2368
+ {
2369
+ code: "VABB",
2370
+ country: "IN",
2371
+ ghcnh_id: null,
2372
+ icao: "VABB",
2373
+ latitude: 19.0887,
2374
+ longitude: 72.8679,
2375
+ name: "Mumbai Chhatrapati Shivaji",
2376
+ tz: "Asia/Kolkata"
2377
+ },
2378
+ {
2379
+ code: "VHHH",
2380
+ country: "HK",
2381
+ ghcnh_id: null,
2382
+ icao: "VHHH",
2383
+ latitude: 22.308,
2384
+ longitude: 113.9185,
2385
+ name: "Hong Kong International",
2386
+ tz: "Asia/Hong_Kong"
2387
+ },
2388
+ {
2389
+ code: "VIDP",
2390
+ country: "IN",
2391
+ ghcnh_id: null,
2392
+ icao: "VIDP",
2393
+ latitude: 28.5562,
2394
+ longitude: 77.1,
2395
+ name: "Delhi Indira Gandhi",
2396
+ tz: "Asia/Kolkata"
2397
+ },
2398
+ {
2399
+ code: "VTBS",
2400
+ country: "TH",
2401
+ ghcnh_id: null,
2402
+ icao: "VTBS",
2403
+ latitude: 13.69,
2404
+ longitude: 100.7501,
2405
+ name: "Bangkok Suvarnabhumi",
2406
+ tz: "Asia/Bangkok"
2407
+ },
2408
+ {
2409
+ code: "WSSS",
2410
+ country: "SG",
2411
+ ghcnh_id: null,
2412
+ icao: "WSSS",
2413
+ latitude: 1.3644,
2414
+ longitude: 103.9915,
2415
+ name: "Singapore Changi",
2416
+ tz: "Asia/Singapore"
2417
+ },
2418
+ {
2419
+ code: "YBBN",
2420
+ country: "AU",
2421
+ ghcnh_id: null,
2422
+ icao: "YBBN",
2423
+ latitude: -27.3842,
2424
+ longitude: 153.1175,
2425
+ name: "Brisbane",
2426
+ tz: "Australia/Brisbane"
2427
+ },
2428
+ {
2429
+ code: "YMML",
2430
+ country: "AU",
2431
+ ghcnh_id: null,
2432
+ icao: "YMML",
2433
+ latitude: -37.6733,
2434
+ longitude: 144.8433,
2435
+ name: "Melbourne Tullamarine",
2436
+ tz: "Australia/Melbourne"
2437
+ },
2438
+ {
2439
+ code: "YSSY",
2440
+ country: "AU",
2441
+ ghcnh_id: null,
2442
+ icao: "YSSY",
2443
+ latitude: -33.9461,
2444
+ longitude: 151.1772,
2445
+ name: "Sydney Kingsford Smith",
2446
+ tz: "Australia/Sydney"
2447
+ },
2448
+ {
2449
+ code: "ZBAA",
2450
+ country: "CN",
2451
+ ghcnh_id: null,
2452
+ icao: "ZBAA",
2453
+ latitude: 40.0801,
2454
+ longitude: 116.5846,
2455
+ name: "Beijing Capital",
2456
+ tz: "Asia/Shanghai"
2457
+ },
2458
+ {
2459
+ code: "ZSPD",
2460
+ country: "CN",
2461
+ ghcnh_id: null,
2462
+ icao: "ZSPD",
2463
+ latitude: 31.1443,
2464
+ longitude: 121.8083,
2465
+ name: "Shanghai Pudong",
2466
+ tz: "Asia/Shanghai"
2467
+ }
2468
+ ];
2469
+ var STATION_BY_CODE2 = /* @__PURE__ */ new Map([
2470
+ ["ATL", STATIONS[10]],
2471
+ ["AUS", STATIONS[11]],
2472
+ ["BOS", STATIONS[12]],
2473
+ ["DCA", STATIONS[13]],
2474
+ ["DEN", STATIONS[14]],
2475
+ ["DFW", STATIONS[15]],
2476
+ ["EDDB", STATIONS[0]],
2477
+ ["EDDF", STATIONS[1]],
2478
+ ["EDDM", STATIONS[2]],
2479
+ ["EFHK", STATIONS[3]],
2480
+ ["EGKK", STATIONS[4]],
2481
+ ["EGLL", STATIONS[5]],
2482
+ ["EHAM", STATIONS[6]],
2483
+ ["EKCH", STATIONS[7]],
2484
+ ["EPWA", STATIONS[8]],
2485
+ ["ESSA", STATIONS[9]],
2486
+ ["HOU", STATIONS[16]],
2487
+ ["LAS", STATIONS[17]],
2488
+ ["LAX", STATIONS[18]],
2489
+ ["LEBL", STATIONS[30]],
2490
+ ["LEMD", STATIONS[31]],
2491
+ ["LFPB", STATIONS[32]],
2492
+ ["LFPG", STATIONS[33]],
2493
+ ["LFPO", STATIONS[34]],
2494
+ ["LIMC", STATIONS[35]],
2495
+ ["LIRF", STATIONS[36]],
2496
+ ["LOWW", STATIONS[37]],
2497
+ ["LSZH", STATIONS[38]],
2498
+ ["MDW", STATIONS[19]],
2499
+ ["MIA", STATIONS[20]],
2500
+ ["MSP", STATIONS[21]],
2501
+ ["MSY", STATIONS[22]],
2502
+ ["NYC", STATIONS[23]],
2503
+ ["NZAA", STATIONS[39]],
2504
+ ["NZWN", STATIONS[40]],
2505
+ ["OERK", STATIONS[41]],
2506
+ ["OKC", STATIONS[24]],
2507
+ ["OMDB", STATIONS[42]],
2508
+ ["OTHH", STATIONS[43]],
2509
+ ["PHL", STATIONS[25]],
2510
+ ["PHX", STATIONS[26]],
2511
+ ["RCTP", STATIONS[44]],
2512
+ ["RJAA", STATIONS[45]],
2513
+ ["RJTT", STATIONS[46]],
2514
+ ["RKSI", STATIONS[47]],
2515
+ ["SAEZ", STATIONS[48]],
2516
+ ["SAT", STATIONS[27]],
2517
+ ["SBGR", STATIONS[49]],
2518
+ ["SEA", STATIONS[28]],
2519
+ ["SFO", STATIONS[29]],
2520
+ ["UUEE", STATIONS[50]],
2521
+ ["VABB", STATIONS[51]],
2522
+ ["VHHH", STATIONS[52]],
2523
+ ["VIDP", STATIONS[53]],
2524
+ ["VTBS", STATIONS[54]],
2525
+ ["WSSS", STATIONS[55]],
2526
+ ["YBBN", STATIONS[56]],
2527
+ ["YMML", STATIONS[57]],
2528
+ ["YSSY", STATIONS[58]],
2529
+ ["ZBAA", STATIONS[59]],
2530
+ ["ZSPD", STATIONS[60]]
2531
+ ]);
2532
+ var STATION_BY_ICAO2 = /* @__PURE__ */ new Map([
2533
+ ["EDDB", STATIONS[0]],
2534
+ ["EDDF", STATIONS[1]],
2535
+ ["EDDM", STATIONS[2]],
2536
+ ["EFHK", STATIONS[3]],
2537
+ ["EGKK", STATIONS[4]],
2538
+ ["EGLL", STATIONS[5]],
2539
+ ["EHAM", STATIONS[6]],
2540
+ ["EKCH", STATIONS[7]],
2541
+ ["EPWA", STATIONS[8]],
2542
+ ["ESSA", STATIONS[9]],
2543
+ ["KATL", STATIONS[10]],
2544
+ ["KAUS", STATIONS[11]],
2545
+ ["KBOS", STATIONS[12]],
2546
+ ["KDCA", STATIONS[13]],
2547
+ ["KDEN", STATIONS[14]],
2548
+ ["KDFW", STATIONS[15]],
2549
+ ["KHOU", STATIONS[16]],
2550
+ ["KLAS", STATIONS[17]],
2551
+ ["KLAX", STATIONS[18]],
2552
+ ["KMDW", STATIONS[19]],
2553
+ ["KMIA", STATIONS[20]],
2554
+ ["KMSP", STATIONS[21]],
2555
+ ["KMSY", STATIONS[22]],
2556
+ ["KNYC", STATIONS[23]],
2557
+ ["KOKC", STATIONS[24]],
2558
+ ["KPHL", STATIONS[25]],
2559
+ ["KPHX", STATIONS[26]],
2560
+ ["KSAT", STATIONS[27]],
2561
+ ["KSEA", STATIONS[28]],
2562
+ ["KSFO", STATIONS[29]],
2563
+ ["LEBL", STATIONS[30]],
2564
+ ["LEMD", STATIONS[31]],
2565
+ ["LFPB", STATIONS[32]],
2566
+ ["LFPG", STATIONS[33]],
2567
+ ["LFPO", STATIONS[34]],
2568
+ ["LIMC", STATIONS[35]],
2569
+ ["LIRF", STATIONS[36]],
2570
+ ["LOWW", STATIONS[37]],
2571
+ ["LSZH", STATIONS[38]],
2572
+ ["NZAA", STATIONS[39]],
2573
+ ["NZWN", STATIONS[40]],
2574
+ ["OERK", STATIONS[41]],
2575
+ ["OMDB", STATIONS[42]],
2576
+ ["OTHH", STATIONS[43]],
2577
+ ["RCTP", STATIONS[44]],
2578
+ ["RJAA", STATIONS[45]],
2579
+ ["RJTT", STATIONS[46]],
2580
+ ["RKSI", STATIONS[47]],
2581
+ ["SAEZ", STATIONS[48]],
2582
+ ["SBGR", STATIONS[49]],
2583
+ ["UUEE", STATIONS[50]],
2584
+ ["VABB", STATIONS[51]],
2585
+ ["VHHH", STATIONS[52]],
2586
+ ["VIDP", STATIONS[53]],
2587
+ ["VTBS", STATIONS[54]],
2588
+ ["WSSS", STATIONS[55]],
2589
+ ["YBBN", STATIONS[56]],
2590
+ ["YMML", STATIONS[57]],
2591
+ ["YSSY", STATIONS[58]],
2592
+ ["ZBAA", STATIONS[59]],
2593
+ ["ZSPD", STATIONS[60]]
2594
+ ]);
2595
+ var _STATION_TZ = Object.freeze({
2596
+ // Eastern (UTC-5 standard / UTC-4 DST)
2597
+ NYC: "America/New_York",
2598
+ JFK: "America/New_York",
2599
+ LGA: "America/New_York",
2600
+ EWR: "America/New_York",
2601
+ ATL: "America/New_York",
2602
+ BOS: "America/New_York",
2603
+ PHL: "America/New_York",
2604
+ DCA: "America/New_York",
2605
+ IAD: "America/New_York",
2606
+ BWI: "America/New_York",
2607
+ MIA: "America/New_York",
2608
+ MCO: "America/New_York",
2609
+ TPA: "America/New_York",
2610
+ CLT: "America/New_York",
2611
+ RDU: "America/New_York",
2612
+ CLE: "America/New_York",
2613
+ PIT: "America/New_York",
2614
+ BUF: "America/New_York",
2615
+ DTW: "America/Detroit",
2616
+ IND: "America/Indiana/Indianapolis",
2617
+ CVG: "America/New_York",
2618
+ CMH: "America/New_York",
2619
+ SYR: "America/New_York",
2620
+ ALB: "America/New_York",
2621
+ BTV: "America/New_York",
2622
+ ORF: "America/New_York",
2623
+ RIC: "America/New_York",
2624
+ GSO: "America/New_York",
2625
+ CHS: "America/New_York",
2626
+ SAV: "America/New_York",
2627
+ JAX: "America/New_York",
2628
+ RSW: "America/New_York",
2629
+ PBI: "America/New_York",
2630
+ FLL: "America/New_York",
2631
+ // Central (UTC-6 standard / UTC-5 DST)
2632
+ ORD: "America/Chicago",
2633
+ MDW: "America/Chicago",
2634
+ DFW: "America/Chicago",
2635
+ DAL: "America/Chicago",
2636
+ IAH: "America/Chicago",
2637
+ HOU: "America/Chicago",
2638
+ MSP: "America/Chicago",
2639
+ STL: "America/Chicago",
2640
+ MCI: "America/Chicago",
2641
+ OMA: "America/Chicago",
2642
+ MKE: "America/Chicago",
2643
+ MSY: "America/Chicago",
2644
+ MEM: "America/Chicago",
2645
+ BNA: "America/Chicago",
2646
+ OKC: "America/Chicago",
2647
+ SAT: "America/Chicago",
2648
+ AUS: "America/Chicago",
2649
+ DSM: "America/Chicago",
2650
+ TUL: "America/Chicago",
2651
+ LIT: "America/Chicago",
2652
+ BIR: "America/Chicago",
2653
+ SDF: "America/Chicago",
2654
+ HSV: "America/Chicago",
2655
+ BHM: "America/Chicago",
2656
+ MOB: "America/Chicago",
2657
+ BTR: "America/Chicago",
2658
+ SHV: "America/Chicago",
2659
+ // Mountain (UTC-7 standard / UTC-6 DST)
2660
+ DEN: "America/Denver",
2661
+ SLC: "America/Denver",
2662
+ ABQ: "America/Denver",
2663
+ BOI: "America/Boise",
2664
+ BZN: "America/Denver",
2665
+ GJT: "America/Denver",
2666
+ // Arizona: no DST (UTC-7 always)
2667
+ PHX: "America/Phoenix",
2668
+ TUS: "America/Phoenix",
2669
+ // Pacific (UTC-8 standard / UTC-7 DST)
2670
+ LAX: "America/Los_Angeles",
2671
+ SFO: "America/Los_Angeles",
2672
+ SEA: "America/Los_Angeles",
2673
+ PDX: "America/Los_Angeles",
2674
+ LAS: "America/Los_Angeles",
2675
+ SAN: "America/Los_Angeles",
2676
+ OAK: "America/Los_Angeles",
2677
+ SJC: "America/Los_Angeles",
2678
+ SMF: "America/Los_Angeles",
2679
+ RNO: "America/Los_Angeles",
2680
+ FAT: "America/Los_Angeles",
2681
+ SNA: "America/Los_Angeles",
2682
+ ONT: "America/Los_Angeles",
2683
+ BUR: "America/Los_Angeles",
2684
+ // Alaska (UTC-9 standard / UTC-8 DST)
2685
+ ANC: "America/Anchorage",
2686
+ FAI: "America/Anchorage",
2687
+ JNU: "America/Juneau",
2688
+ // Hawaii (UTC-10, no DST)
2689
+ HNL: "Pacific/Honolulu",
2690
+ OGG: "Pacific/Honolulu",
2691
+ KOA: "Pacific/Honolulu",
2692
+ // International (iter-6 H12): minimal set required to un-skip the
2693
+ // case-5 RJTT year-wrap cache behavior test. Python's
2694
+ // `mostlyright.snapshot._resolve_tz` falls back to the broader STATIONS
2695
+ // registry for intl ICAOs; the TS port hasn't ported that fallback
2696
+ // yet (tracked as TS-W6 — exhaustive intl-station tz coverage). This
2697
+ // entry closes H12 cleanly without pulling the whole STATIONS map in.
2698
+ // ICAO key (RJTT) — international stations have no 3-letter NWS code.
2699
+ // Tokyo Haneda — UTC+9 LST, no DST.
2700
+ RJTT: "Asia/Tokyo"
2701
+ });
2702
+ var _JAN_REF = new Date(Date.UTC(2024, 0, 15, 12, 0, 0));
2703
+ var _OFFSET_CACHE = /* @__PURE__ */ new Map();
2704
+ function _lstOffsetHours(stationTz) {
2705
+ const cached = _OFFSET_CACHE.get(stationTz);
2706
+ if (cached !== void 0) return cached;
2707
+ const fmt = new Intl.DateTimeFormat("en-US", {
2708
+ timeZone: stationTz,
2709
+ hour12: false,
2710
+ year: "numeric",
2711
+ month: "2-digit",
2712
+ day: "2-digit",
2713
+ hour: "2-digit",
2714
+ minute: "2-digit",
2715
+ second: "2-digit"
2716
+ });
2717
+ const parts = fmt.formatToParts(_JAN_REF);
2718
+ const get = (type) => {
2719
+ const part = parts.find((p) => p.type === type);
2720
+ if (!part) {
2721
+ throw new Error(`Intl.DateTimeFormat missing ${type} for tz=${stationTz}`);
2722
+ }
2723
+ return Number(part.value);
2724
+ };
2725
+ const year = get("year");
2726
+ const month = get("month");
2727
+ const day = get("day");
2728
+ let hour = get("hour");
2729
+ const minute = get("minute");
2730
+ const second = get("second");
2731
+ if (hour === 24) hour = 0;
2732
+ const localAsUtc = Date.UTC(year, month - 1, day, hour, minute, second);
2733
+ const offsetMs = localAsUtc - _JAN_REF.getTime();
2734
+ const offsetHours = offsetMs / 36e5;
2735
+ _OFFSET_CACHE.set(stationTz, offsetHours);
2736
+ return offsetHours;
2737
+ }
2738
+ function _lstOffsetHoursFor(station) {
2739
+ const upper = station.trim().toUpperCase();
2740
+ const byCode = STATION_BY_CODE2.get(upper);
2741
+ if (byCode !== void 0) return _lstOffsetHours(byCode.tz);
2742
+ const byIcao = STATION_BY_ICAO2.get(upper);
2743
+ if (byIcao !== void 0) return _lstOffsetHours(byIcao.tz);
2744
+ if (upper.length === 4 && upper.startsWith("K")) {
2745
+ const stripped = upper.slice(1);
2746
+ const retry = STATION_BY_CODE2.get(stripped);
2747
+ if (retry !== void 0) return _lstOffsetHours(retry.tz);
2748
+ }
2749
+ throw new RangeError(`unknown station: ${JSON.stringify(station)}`);
2750
+ }
2751
+ function _nowLst(station, now = /* @__PURE__ */ new Date()) {
2752
+ const offsetHours = _lstOffsetHoursFor(station);
2753
+ return new Date(now.getTime() + offsetHours * 36e5);
2754
+ }
2755
+ function shouldSkipCacheForCurrentLstMonth(station, year, month, now) {
2756
+ const lst = _nowLst(station, now);
2757
+ return lst.getUTCFullYear() === year && lst.getUTCMonth() + 1 === month;
2758
+ }
2759
+ function shouldSkipCacheForCurrentLstYear(station, year, now) {
2760
+ const lst = _nowLst(station, now);
2761
+ return lst.getUTCFullYear() === year;
2762
+ }
2763
+ function isWritableMonth(year, month, now) {
2764
+ const nowYear = now.getUTCFullYear();
2765
+ const nowMonth = now.getUTCMonth() + 1;
2766
+ if (year < nowYear) return true;
2767
+ if (year > nowYear) return false;
2768
+ return month < nowMonth;
2769
+ }
2770
+ function isWritableYear(year, now) {
2771
+ return year < now.getUTCFullYear();
2772
+ }
2773
+ function isLiveSource2(source) {
2774
+ return typeof source === "string" && source.length > 0 && source.endsWith(".live");
2775
+ }
2776
+ function isWithinVolatileWindow(eventDate, archiveAsOf, days = 30) {
2777
+ const e = Date.parse(`${eventDate}T00:00:00Z`);
2778
+ const a = Date.parse(`${archiveAsOf}T00:00:00Z`);
2779
+ if (!Number.isFinite(e) || !Number.isFinite(a)) {
2780
+ throw new RangeError(
2781
+ `invalid YYYY-MM-DD: eventDate=${JSON.stringify(eventDate)} archiveAsOf=${JSON.stringify(archiveAsOf)}`
2782
+ );
2783
+ }
2784
+ const deltaDays = (a - e) / 864e5;
2785
+ return deltaDays >= 0 && deltaDays <= days;
2786
+ }
2787
+ var MIN_YEAR2 = 1900;
2788
+ var MAX_YEAR2 = 2100;
2789
+ var SOURCE_RE = /^[a-z0-9_-]+$/;
2790
+ function cacheKeyForObservations(station, year, month, source) {
2791
+ if (!Number.isInteger(year) || year < MIN_YEAR2 || year > MAX_YEAR2) {
2792
+ throw new RangeError(`year out of range: ${year}`);
2793
+ }
2794
+ if (!Number.isInteger(month) || month < 1 || month > 12) {
2795
+ throw new RangeError(`month out of range: ${month}`);
2796
+ }
2797
+ const yyyy = String(year).padStart(4, "0");
2798
+ const mm = String(month).padStart(2, "0");
2799
+ const base = `mostlyright:v1:observations:${station.toUpperCase()}:${yyyy}:${mm}`;
2800
+ if (source === void 0) return base;
2801
+ if (typeof source !== "string" || !SOURCE_RE.test(source)) {
2802
+ throw new RangeError(
2803
+ `source must match ${SOURCE_RE.source} (lowercase alnum / hyphen / underscore); got ${JSON.stringify(source)}`
2804
+ );
2805
+ }
2806
+ return `${base}:${source}`;
2807
+ }
2808
+ function cacheKeyForClimate(station, year) {
2809
+ if (!Number.isInteger(year) || year < MIN_YEAR2 || year > MAX_YEAR2) {
2810
+ throw new RangeError(`year out of range: ${year}`);
2811
+ }
2812
+ const yyyy = String(year).padStart(4, "0");
2813
+ return `mostlyright:v1:climate:${station.toUpperCase()}:${yyyy}`;
2814
+ }
2815
+
2816
+ // ../core/dist/internal/cache/index.browser.mjs
2817
+ async function defaultCacheStore() {
2818
+ if (typeof indexedDB !== "undefined") return new IndexedDBStore();
2819
+ return new MemoryStore();
2820
+ }
2821
+
2822
+ // ../core/dist/internal/pairs.mjs
2823
+ var _STATION_TZ2 = Object.freeze({
2824
+ // Eastern (UTC-5 standard / UTC-4 DST)
2825
+ NYC: "America/New_York",
2826
+ JFK: "America/New_York",
2827
+ LGA: "America/New_York",
2828
+ EWR: "America/New_York",
2829
+ ATL: "America/New_York",
2830
+ BOS: "America/New_York",
2831
+ PHL: "America/New_York",
2832
+ DCA: "America/New_York",
2833
+ IAD: "America/New_York",
2834
+ BWI: "America/New_York",
2835
+ MIA: "America/New_York",
2836
+ MCO: "America/New_York",
2837
+ TPA: "America/New_York",
2838
+ CLT: "America/New_York",
2839
+ RDU: "America/New_York",
2840
+ CLE: "America/New_York",
2841
+ PIT: "America/New_York",
2842
+ BUF: "America/New_York",
2843
+ DTW: "America/Detroit",
2844
+ IND: "America/Indiana/Indianapolis",
2845
+ CVG: "America/New_York",
2846
+ CMH: "America/New_York",
2847
+ SYR: "America/New_York",
2848
+ ALB: "America/New_York",
2849
+ BTV: "America/New_York",
2850
+ ORF: "America/New_York",
2851
+ RIC: "America/New_York",
2852
+ GSO: "America/New_York",
2853
+ CHS: "America/New_York",
2854
+ SAV: "America/New_York",
2855
+ JAX: "America/New_York",
2856
+ RSW: "America/New_York",
2857
+ PBI: "America/New_York",
2858
+ FLL: "America/New_York",
2859
+ // Central (UTC-6 standard / UTC-5 DST)
2860
+ ORD: "America/Chicago",
2861
+ MDW: "America/Chicago",
2862
+ DFW: "America/Chicago",
2863
+ DAL: "America/Chicago",
2864
+ IAH: "America/Chicago",
2865
+ HOU: "America/Chicago",
2866
+ MSP: "America/Chicago",
2867
+ STL: "America/Chicago",
2868
+ MCI: "America/Chicago",
2869
+ OMA: "America/Chicago",
2870
+ MKE: "America/Chicago",
2871
+ MSY: "America/Chicago",
2872
+ MEM: "America/Chicago",
2873
+ BNA: "America/Chicago",
2874
+ OKC: "America/Chicago",
2875
+ SAT: "America/Chicago",
2876
+ AUS: "America/Chicago",
2877
+ DSM: "America/Chicago",
2878
+ TUL: "America/Chicago",
2879
+ LIT: "America/Chicago",
2880
+ BIR: "America/Chicago",
2881
+ SDF: "America/Chicago",
2882
+ HSV: "America/Chicago",
2883
+ BHM: "America/Chicago",
2884
+ MOB: "America/Chicago",
2885
+ BTR: "America/Chicago",
2886
+ SHV: "America/Chicago",
2887
+ // Mountain (UTC-7 standard / UTC-6 DST)
2888
+ DEN: "America/Denver",
2889
+ SLC: "America/Denver",
2890
+ ABQ: "America/Denver",
2891
+ BOI: "America/Boise",
2892
+ BZN: "America/Denver",
2893
+ GJT: "America/Denver",
2894
+ // Arizona: no DST (UTC-7 always)
2895
+ PHX: "America/Phoenix",
2896
+ TUS: "America/Phoenix",
2897
+ // Pacific (UTC-8 standard / UTC-7 DST)
2898
+ LAX: "America/Los_Angeles",
2899
+ SFO: "America/Los_Angeles",
2900
+ SEA: "America/Los_Angeles",
2901
+ PDX: "America/Los_Angeles",
2902
+ LAS: "America/Los_Angeles",
2903
+ SAN: "America/Los_Angeles",
2904
+ OAK: "America/Los_Angeles",
2905
+ SJC: "America/Los_Angeles",
2906
+ SMF: "America/Los_Angeles",
2907
+ RNO: "America/Los_Angeles",
2908
+ FAT: "America/Los_Angeles",
2909
+ SNA: "America/Los_Angeles",
2910
+ ONT: "America/Los_Angeles",
2911
+ BUR: "America/Los_Angeles",
2912
+ // Alaska (UTC-9 standard / UTC-8 DST)
2913
+ ANC: "America/Anchorage",
2914
+ FAI: "America/Anchorage",
2915
+ JNU: "America/Juneau",
2916
+ // Hawaii (UTC-10, no DST)
2917
+ HNL: "Pacific/Honolulu",
2918
+ OGG: "Pacific/Honolulu",
2919
+ KOA: "Pacific/Honolulu",
2920
+ // International (iter-6 H12): minimal set required to un-skip the
2921
+ // case-5 RJTT year-wrap cache behavior test. Python's
2922
+ // `mostlyright.snapshot._resolve_tz` falls back to the broader STATIONS
2923
+ // registry for intl ICAOs; the TS port hasn't ported that fallback
2924
+ // yet (tracked as TS-W6 — exhaustive intl-station tz coverage). This
2925
+ // entry closes H12 cleanly without pulling the whole STATIONS map in.
2926
+ // ICAO key (RJTT) — international stations have no 3-letter NWS code.
2927
+ // Tokyo Haneda — UTC+9 LST, no DST.
2928
+ RJTT: "Asia/Tokyo"
2929
+ });
2930
+ var _JAN_REF2 = new Date(Date.UTC(2024, 0, 15, 12, 0, 0));
2931
+ var _MARKET_CLOSE_HOUR_LST = 16;
2932
+ var _MARKET_CLOSE_MINUTE_LST = 30;
2933
+ var _OFFSET_CACHE2 = /* @__PURE__ */ new Map();
2934
+ function _lstOffsetHours2(stationTz) {
2935
+ const cached = _OFFSET_CACHE2.get(stationTz);
2936
+ if (cached !== void 0) return cached;
2937
+ const fmt = new Intl.DateTimeFormat("en-US", {
2938
+ timeZone: stationTz,
2939
+ hour12: false,
2940
+ year: "numeric",
2941
+ month: "2-digit",
2942
+ day: "2-digit",
2943
+ hour: "2-digit",
2944
+ minute: "2-digit",
2945
+ second: "2-digit"
2946
+ });
2947
+ const parts = fmt.formatToParts(_JAN_REF2);
2948
+ const get = (type) => {
2949
+ const part = parts.find((p) => p.type === type);
2950
+ if (!part) {
2951
+ throw new Error(`Intl.DateTimeFormat missing ${type} for tz=${stationTz}`);
2952
+ }
2953
+ return Number(part.value);
2954
+ };
2955
+ const year = get("year");
2956
+ const month = get("month");
2957
+ const day = get("day");
2958
+ let hour = get("hour");
2959
+ const minute = get("minute");
2960
+ const second = get("second");
2961
+ if (hour === 24) hour = 0;
2962
+ const localAsUtc = Date.UTC(year, month - 1, day, hour, minute, second);
2963
+ const offsetMs = localAsUtc - _JAN_REF2.getTime();
2964
+ const offsetHours = offsetMs / 36e5;
2965
+ _OFFSET_CACHE2.set(stationTz, offsetHours);
2966
+ return offsetHours;
2967
+ }
2968
+ function _stationCodeNormalized(station) {
2969
+ const s = station.trim().toUpperCase();
2970
+ if (s.length === 4 && s.startsWith("K")) {
2971
+ return s.substring(1);
2972
+ }
2973
+ return s;
2974
+ }
2975
+ function _resolveStationTz(station, tzOverride) {
2976
+ if (tzOverride) return tzOverride;
2977
+ const code = _stationCodeNormalized(station);
2978
+ const tz = _STATION_TZ2[code];
2979
+ if (tz) return tz;
2980
+ throw new Error(
2981
+ `Unknown station timezone: ${JSON.stringify(code)}. Add it to _STATION_TZ or pass tzOverride="America/...".`
2982
+ );
2983
+ }
2984
+ function marketCloseUtc(dateStr, station, tzOverride) {
2985
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dateStr);
2986
+ if (!match) {
2987
+ throw new Error(`Invalid ISO date for market close: ${JSON.stringify(dateStr)}`);
2988
+ }
2989
+ const [, yStr, mStr, dStr] = match;
2990
+ const year = Number(yStr);
2991
+ const month = Number(mStr);
2992
+ const day = Number(dStr);
2993
+ const tz = _resolveStationTz(station, tzOverride);
2994
+ const offsetHours = _lstOffsetHours2(tz);
2995
+ const marketCloseAsUtcMs = Date.UTC(
2996
+ year,
2997
+ month - 1,
2998
+ day,
2999
+ _MARKET_CLOSE_HOUR_LST,
3000
+ _MARKET_CLOSE_MINUTE_LST,
3001
+ 0
3002
+ );
3003
+ return new Date(marketCloseAsUtcMs - offsetHours * 36e5);
3004
+ }
3005
+ function collectNonNull(obs, key) {
3006
+ const out = [];
3007
+ for (const o of obs) {
3008
+ const v = o[key];
3009
+ if (typeof v === "number" && Number.isFinite(v)) out.push(v);
3010
+ }
3011
+ return out;
3012
+ }
3013
+ function meanOrNull(vs) {
3014
+ if (vs.length === 0) return null;
3015
+ let s = 0;
3016
+ for (const v of vs) s += v;
3017
+ return s / vs.length;
3018
+ }
3019
+ function maxOrNull(vs) {
3020
+ if (vs.length === 0) return null;
3021
+ let best = vs[0];
3022
+ for (let i = 1; i < vs.length; i++) {
3023
+ const v = vs[i];
3024
+ if (v > best) best = v;
3025
+ }
3026
+ return best;
3027
+ }
3028
+ function minOrNull(vs) {
3029
+ if (vs.length === 0) return null;
3030
+ let best = vs[0];
3031
+ for (let i = 1; i < vs.length; i++) {
3032
+ const v = vs[i];
3033
+ if (v < best) best = v;
3034
+ }
3035
+ return best;
3036
+ }
3037
+ function sumOrNull(vs) {
3038
+ if (vs.length === 0) return null;
3039
+ let s = 0;
3040
+ for (const v of vs) s += v;
3041
+ return s;
3042
+ }
3043
+ function _obsAggregates(observations) {
3044
+ if (observations.length === 0) {
3045
+ return Object.freeze({
3046
+ obs_high_f: null,
3047
+ obs_low_f: null,
3048
+ obs_mean_f: null,
3049
+ obs_mean_dewpoint_f: null,
3050
+ obs_max_wind_kt: null,
3051
+ obs_max_gust_kt: null,
3052
+ obs_total_precip_in: null,
3053
+ obs_count: 0
3054
+ });
3055
+ }
3056
+ const temps = collectNonNull(observations, "temp_f");
3057
+ const dewps = collectNonNull(observations, "dewpoint_f");
3058
+ const winds = collectNonNull(observations, "wind_speed_kt");
3059
+ const gusts = collectNonNull(observations, "wind_gust_kt");
3060
+ const precips = collectNonNull(observations, "precip_1hr_inches");
3061
+ return Object.freeze({
3062
+ obs_high_f: maxOrNull(temps),
3063
+ obs_low_f: minOrNull(temps),
3064
+ obs_mean_f: meanOrNull(temps),
3065
+ obs_mean_dewpoint_f: meanOrNull(dewps),
3066
+ obs_max_wind_kt: maxOrNull(winds),
3067
+ obs_max_gust_kt: maxOrNull(gusts),
3068
+ obs_total_precip_in: sumOrNull(precips),
3069
+ obs_count: observations.length
3070
+ });
3071
+ }
3072
+ function buildPairsRow(dateStr, station, observations, climate, opts = {}) {
3073
+ const obsAgg = _obsAggregates(observations);
3074
+ const closeUtc = marketCloseUtc(dateStr, station, opts.tzOverride);
3075
+ const closeIso = `${closeUtc.toISOString().slice(0, 19)}Z`;
3076
+ return Object.freeze({
3077
+ date: dateStr,
3078
+ station,
3079
+ cli_high_f: climate ? climate.high_temp_f : null,
3080
+ cli_low_f: climate ? climate.low_temp_f : null,
3081
+ cli_report_type: climate ? climate.report_type : null,
3082
+ obs_high_f: obsAgg.obs_high_f,
3083
+ obs_low_f: obsAgg.obs_low_f,
3084
+ obs_mean_f: obsAgg.obs_mean_f,
3085
+ obs_mean_dewpoint_f: obsAgg.obs_mean_dewpoint_f,
3086
+ obs_max_wind_kt: obsAgg.obs_max_wind_kt,
3087
+ obs_max_gust_kt: obsAgg.obs_max_gust_kt,
3088
+ obs_total_precip_in: obsAgg.obs_total_precip_in,
3089
+ obs_count: obsAgg.obs_count,
3090
+ fcst_high_f: null,
3091
+ fcst_low_f: null,
3092
+ fcst_model: null,
3093
+ fcst_issued_at: null,
3094
+ fcst_pop_6hr_pct: null,
3095
+ fcst_qpf_6hr_in: null,
3096
+ market_close_utc: closeIso
3097
+ });
3098
+ }
3099
+ function buildPairs(station, dates, observationsByDate, climateByDate, opts = {}) {
3100
+ const out = [];
3101
+ for (const date of dates) {
3102
+ const obs = observationsByDate[date] ?? [];
3103
+ const climate = climateByDate[date] ?? null;
3104
+ out.push(buildPairsRow(date, station, obs, climate, opts));
3105
+ }
3106
+ return Object.freeze(out);
3107
+ }
3108
+
3109
+ // src/research.ts
3110
+ var AWC_MAX_HOURS2 = 168;
3111
+ async function resolveCache(opts) {
3112
+ if (opts.cache === null) return null;
3113
+ if (opts.cache !== void 0) return opts.cache;
3114
+ return await defaultCacheStore();
3115
+ }
3116
+ var DATE_RE3 = /^\d{4}-\d{2}-\d{2}$/;
3117
+ function normalizeStation2(input) {
3118
+ const raw = input.trim().toUpperCase();
3119
+ if (raw.length === 0) {
3120
+ throw new Error("station must be a non-empty string");
3121
+ }
3122
+ const byIcao = STATION_BY_ICAO.get(raw);
3123
+ if (byIcao !== void 0) {
3124
+ if (byIcao.code === null) {
3125
+ throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
3126
+ }
3127
+ return {
3128
+ code: byIcao.code,
3129
+ icao: byIcao.icao,
3130
+ tz: byIcao.tz,
3131
+ country: byIcao.country,
3132
+ ghcnhId: byIcao.ghcnh_id
3133
+ };
3134
+ }
3135
+ const byCode = STATION_BY_CODE.get(raw);
3136
+ if (byCode !== void 0) {
3137
+ if (byCode.code === null) {
3138
+ throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
3139
+ }
3140
+ return {
3141
+ code: byCode.code,
3142
+ icao: byCode.icao,
3143
+ tz: byCode.tz,
3144
+ country: byCode.country,
3145
+ ghcnhId: byCode.ghcnh_id
3146
+ };
3147
+ }
3148
+ if (raw.startsWith("K") && raw.length === 4) {
3149
+ const stripped = raw.slice(1);
3150
+ const retry = STATION_BY_CODE.get(stripped);
3151
+ if (retry !== void 0 && retry.code !== null) {
3152
+ return {
3153
+ code: retry.code,
3154
+ icao: retry.icao,
3155
+ tz: retry.tz,
3156
+ country: retry.country,
3157
+ ghcnhId: retry.ghcnh_id
3158
+ };
3159
+ }
3160
+ }
3161
+ throw new Error(
3162
+ `unknown station ${JSON.stringify(input)} \u2014 not found in STATION_BY_CODE or STATION_BY_ICAO`
3163
+ );
3164
+ }
3165
+ function parseIsoDate2(s) {
3166
+ if (!DATE_RE3.test(s)) {
3167
+ throw new Error(`expected YYYY-MM-DD, got ${JSON.stringify(s)}`);
3168
+ }
3169
+ const [yStr, mStr, dStr] = s.split("-");
3170
+ const year = Number(yStr);
3171
+ const month = Number(mStr);
3172
+ const day = Number(dStr);
3173
+ const ms = Date.UTC(year, month - 1, day);
3174
+ const d = new Date(ms);
3175
+ if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day) {
3176
+ throw new Error(`invalid calendar date ${JSON.stringify(s)}`);
3177
+ }
3178
+ return d;
3179
+ }
3180
+ function formatDate(d) {
3181
+ const y = d.getUTCFullYear();
3182
+ const m = d.getUTCMonth() + 1;
3183
+ const day = d.getUTCDate();
3184
+ const mm = m < 10 ? `0${m}` : `${m}`;
3185
+ const dd = day < 10 ? `0${day}` : `${day}`;
3186
+ return `${y}-${mm}-${dd}`;
3187
+ }
3188
+ function buildDateList(fromDate, toDate) {
3189
+ const from = parseIsoDate2(fromDate);
3190
+ const to = parseIsoDate2(toDate);
3191
+ if (from.getTime() > to.getTime()) {
3192
+ throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);
3193
+ }
3194
+ const dates = [];
3195
+ for (let cursor = from.getTime(); cursor <= to.getTime(); cursor += 24 * 36e5) {
3196
+ dates.push(formatDate(new Date(cursor)));
3197
+ }
3198
+ return dates;
3199
+ }
3200
+ function plusOneDay(isoDate) {
3201
+ const d = parseIsoDate2(isoDate);
3202
+ return formatDate(new Date(d.getTime() + 24 * 36e5));
3203
+ }
3204
+ function isUsStation(station) {
3205
+ return station.country === "US";
3206
+ }
3207
+ function anyDateOverlapsAwc(toDate, hours, now) {
3208
+ const to = parseIsoDate2(toDate);
3209
+ const toEndMs = to.getTime() + 24 * 36e5;
3210
+ const nowMs = now.getTime();
3211
+ const cutoffMs = nowMs - hours * 36e5;
3212
+ return toEndMs >= cutoffMs;
3213
+ }
3214
+ function observedSettlementDate(observedAt, station) {
3215
+ const ms = Date.parse(observedAt);
3216
+ if (!Number.isFinite(ms)) return null;
3217
+ try {
3218
+ return settlementDateFor(new Date(ms), station);
3219
+ } catch {
3220
+ return null;
3221
+ }
3222
+ }
3223
+ function sortByObservedAtThenSource(rows) {
3224
+ return [...rows].sort((a, b) => {
3225
+ if (a.observed_at < b.observed_at) return -1;
3226
+ if (a.observed_at > b.observed_at) return 1;
3227
+ if (a.source < b.source) return -1;
3228
+ if (a.source > b.source) return 1;
3229
+ return 0;
3230
+ });
3231
+ }
3232
+ function isYearVolatile(year, now) {
3233
+ const yearEnd = `${String(year).padStart(4, "0")}-12-31`;
3234
+ return isWithinVolatileWindow(yearEnd, formatDate(now), 30);
3235
+ }
3236
+ function lastDayOfMonth(year, month) {
3237
+ const d = new Date(Date.UTC(year, month, 0));
3238
+ return formatDate(d);
3239
+ }
3240
+ function isMonthVolatile(year, month, now) {
3241
+ return isWithinVolatileWindow(lastDayOfMonth(year, month), formatDate(now), 30);
3242
+ }
3243
+ function monthsInRange(fromIsoDate, toIsoDate) {
3244
+ const from = parseIsoDate2(fromIsoDate);
3245
+ const to = parseIsoDate2(toIsoDate);
3246
+ if (from.getTime() > to.getTime()) {
3247
+ throw new Error(`fromDate (${fromIsoDate}) must be <= toDate (${toIsoDate})`);
3248
+ }
3249
+ const pairs = [];
3250
+ let y = from.getUTCFullYear();
3251
+ let m = from.getUTCMonth() + 1;
3252
+ const endY = to.getUTCFullYear();
3253
+ const endM = to.getUTCMonth() + 1;
3254
+ while (y < endY || y === endY && m <= endM) {
3255
+ pairs.push([y, m]);
3256
+ m += 1;
3257
+ if (m > 12) {
3258
+ m = 1;
3259
+ y += 1;
3260
+ }
3261
+ }
3262
+ return pairs;
3263
+ }
3264
+ async function fetchCliWithCache(fetchIcao, cacheCode, fromYear, toYear, opts, cache, now) {
3265
+ const acc = [];
3266
+ for (let year = fromYear; year <= toYear; year++) {
3267
+ const writable = isWritableYear(year, now);
3268
+ const skipCurrentYear = shouldSkipCacheForCurrentLstYear(cacheCode, year, now);
3269
+ const skipVolatile = isYearVolatile(year, now);
3270
+ const skip = !writable || skipCurrentYear || skipVolatile;
3271
+ if (cache !== null && !skip) {
3272
+ let cached = null;
3273
+ try {
3274
+ cached = await cache.get(cacheKeyForClimate(cacheCode, year));
3275
+ } catch (cacheErr) {
3276
+ console.warn(
3277
+ `[mostlyright] CLI cache.get failed for code=${cacheCode} year=${year}; falling back to live fetch:`,
3278
+ cacheErr
3279
+ );
3280
+ }
3281
+ if (cached !== null) {
3282
+ acc.push(...cached);
3283
+ continue;
3284
+ }
3285
+ }
3286
+ const cliOpts = {};
3287
+ if (opts.signal !== void 0) cliOpts.signal = opts.signal;
3288
+ if (opts.cliPolitenessMs !== void 0) cliOpts.politenessMs = opts.cliPolitenessMs;
3289
+ const cliRaw = await downloadCliRange(fetchIcao, year, year, cliOpts);
3290
+ const parsed = parseCliResponse(cliRaw, cacheCode);
3291
+ acc.push(...parsed);
3292
+ const sample = parsed[0]?.source;
3293
+ if (cache !== null && !skip && !isLiveSource2(sample)) {
3294
+ try {
3295
+ await cache.set(cacheKeyForClimate(cacheCode, year), parsed);
3296
+ } catch (cacheErr) {
3297
+ console.warn(
3298
+ `[mostlyright] CLI cache.set failed for code=${cacheCode} year=${year}; in-memory rows preserved:`,
3299
+ cacheErr
3300
+ );
3301
+ }
3302
+ }
3303
+ }
3304
+ return acc;
3305
+ }
3306
+ async function fetchIemAsosWithCache(stationCode, _fromYear, _extendedToYear, fromDate, extendedTo, opts, cache, now) {
3307
+ const acc = [];
3308
+ const yearByReportType = /* @__PURE__ */ new Map();
3309
+ async function fetchYearOnce(year, reportType) {
3310
+ const memoKey = `${year}:${reportType}`;
3311
+ const cached = yearByReportType.get(memoKey);
3312
+ if (cached !== void 0) return cached;
3313
+ const iemOpts = {
3314
+ reportType,
3315
+ politenessMs: opts.iemPolitenessMs ?? 1e3
3316
+ };
3317
+ if (opts.signal !== void 0) iemOpts.signal = opts.signal;
3318
+ const chunks = await downloadIemAsos(stationCode, `${year}-01-01`, `${year}-12-31`, iemOpts);
3319
+ const fetched = [];
3320
+ for (const chunk of chunks) {
3321
+ const parsed = parseIemCsv(chunk.csv, {
3322
+ observationTypeOverride: reportType === 3 ? "METAR" : "SPECI"
3323
+ });
3324
+ fetched.push(...parsed);
3325
+ }
3326
+ yearByReportType.set(memoKey, fetched);
3327
+ return fetched;
3328
+ }
3329
+ function filterMonth(rows, year, month) {
3330
+ const yyyy = String(year).padStart(4, "0");
3331
+ const mm = String(month).padStart(2, "0");
3332
+ const prefix = `${yyyy}-${mm}-`;
3333
+ const out = [];
3334
+ for (const r of rows) {
3335
+ if (r.observed_at.startsWith(prefix)) out.push(r);
3336
+ }
3337
+ return out;
3338
+ }
3339
+ const pairs = monthsInRange(fromDate, extendedTo);
3340
+ for (const [year, month] of pairs) {
3341
+ const cacheKey = cacheKeyForObservations(stationCode, year, month, "iem");
3342
+ const writable = isWritableMonth(year, month, now);
3343
+ const skipCurrentMonth = shouldSkipCacheForCurrentLstMonth(stationCode, year, month, now);
3344
+ const skipVolatile = isMonthVolatile(year, month, now);
3345
+ const skipCache = !writable || skipCurrentMonth || skipVolatile;
3346
+ let monthRows = null;
3347
+ if (cache !== null && !skipCache) {
3348
+ try {
3349
+ const cached = await cache.get(cacheKey);
3350
+ if (cached !== null) monthRows = cached;
3351
+ } catch (cacheErr) {
3352
+ console.warn(
3353
+ `[mostlyright] IEM ASOS cache.get failed for key=${cacheKey}; falling back to live fetch:`,
3354
+ cacheErr
3355
+ );
3356
+ }
3357
+ }
3358
+ if (monthRows === null) {
3359
+ const metar = await fetchYearOnce(year, 3);
3360
+ const speci = await fetchYearOnce(year, 4);
3361
+ const monthMetar = filterMonth(metar, year, month);
3362
+ const monthSpeci = filterMonth(speci, year, month);
3363
+ monthRows = [...monthMetar, ...monthSpeci];
3364
+ const sample = monthRows[0]?.source;
3365
+ if (cache !== null && !skipCache && !isLiveSource2(sample)) {
3366
+ try {
3367
+ await cache.set(cacheKey, monthRows);
3368
+ } catch (cacheErr) {
3369
+ console.warn(
3370
+ `[mostlyright] IEM ASOS cache.set failed for key=${cacheKey}; in-memory rows preserved:`,
3371
+ cacheErr
3372
+ );
3373
+ }
3374
+ }
3375
+ }
3376
+ for (const obs of monthRows) {
3377
+ const obsDate = obs.observed_at.slice(0, 10);
3378
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
3379
+ }
3380
+ }
3381
+ return acc;
3382
+ }
3383
+ async function fetchGhcnhWithCache(stationCode, ghcnhId, fromDate, extendedTo, opts, cache, now) {
3384
+ const acc = [];
3385
+ const yearCache = /* @__PURE__ */ new Map();
3386
+ async function fetchYearOnce(year) {
3387
+ const cached = yearCache.get(year);
3388
+ if (cached !== void 0) return cached;
3389
+ const ghcnhOpts = {};
3390
+ if (opts.signal !== void 0) ghcnhOpts.signal = opts.signal;
3391
+ let parsed;
3392
+ try {
3393
+ const yr = await downloadGhcnh(ghcnhId, year, ghcnhOpts);
3394
+ parsed = parseGhcnhPsv(yr.psv);
3395
+ } catch (err) {
3396
+ if (err instanceof NotFoundError) {
3397
+ parsed = [];
3398
+ } else {
3399
+ throw err;
3400
+ }
3401
+ }
3402
+ yearCache.set(year, parsed);
3403
+ return parsed;
3404
+ }
3405
+ function filterMonth(rows, year, month) {
3406
+ const yyyy = String(year).padStart(4, "0");
3407
+ const mm = String(month).padStart(2, "0");
3408
+ const prefix = `${yyyy}-${mm}-`;
3409
+ const out = [];
3410
+ for (const r of rows) {
3411
+ if (r.observed_at.startsWith(prefix) && r.station_code === stationCode) out.push(r);
3412
+ }
3413
+ return out;
3414
+ }
3415
+ const pairs = monthsInRange(fromDate, extendedTo);
3416
+ for (const [year, month] of pairs) {
3417
+ const cacheKey = cacheKeyForObservations(stationCode, year, month, "ghcnh");
3418
+ const writable = isWritableMonth(year, month, now);
3419
+ const skipCurrentMonth = shouldSkipCacheForCurrentLstMonth(stationCode, year, month, now);
3420
+ const skipVolatile = isMonthVolatile(year, month, now);
3421
+ const skipCache = !writable || skipCurrentMonth || skipVolatile;
3422
+ let monthRows = null;
3423
+ if (cache !== null && !skipCache) {
3424
+ try {
3425
+ const cached = await cache.get(cacheKey);
3426
+ if (cached !== null) monthRows = cached;
3427
+ } catch (cacheErr) {
3428
+ console.warn(
3429
+ `[mostlyright] GHCNh cache.get failed for key=${cacheKey}; falling back to live fetch:`,
3430
+ cacheErr
3431
+ );
3432
+ }
3433
+ }
3434
+ if (monthRows === null) {
3435
+ const yearRows = await fetchYearOnce(year);
3436
+ monthRows = filterMonth(yearRows, year, month);
3437
+ const sample = monthRows[0]?.source;
3438
+ if (cache !== null && !skipCache && !isLiveSource2(sample)) {
3439
+ try {
3440
+ await cache.set(cacheKey, monthRows);
3441
+ } catch (cacheErr) {
3442
+ console.warn(
3443
+ `[mostlyright] GHCNh cache.set failed for key=${cacheKey}; in-memory rows preserved:`,
3444
+ cacheErr
3445
+ );
3446
+ }
3447
+ }
3448
+ }
3449
+ for (const obs of monthRows) {
3450
+ const obsDate = obs.observed_at.slice(0, 10);
3451
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
3452
+ }
3453
+ }
3454
+ return acc;
3455
+ }
3456
+ async function research(station, fromDate, toDate, opts = {}) {
3457
+ const hasCity = typeof opts.city === "string" && opts.city.length > 0;
3458
+ const hasContract = typeof opts.contract === "string" && opts.contract.length > 0;
3459
+ const hasContracts = Array.isArray(opts.contracts) && opts.contracts.length > 0;
3460
+ const hasStation = typeof station === "string" && station.length > 0;
3461
+ const selectorCount = Number(hasStation) + Number(hasCity) + Number(hasContract) + Number(hasContracts);
3462
+ if (selectorCount === 0) {
3463
+ throw new Error(
3464
+ "research(): exactly one of station, opts.city, opts.contract, opts.contracts must be provided"
3465
+ );
3466
+ }
3467
+ if (selectorCount > 1) {
3468
+ const names = [];
3469
+ if (hasStation) names.push("station");
3470
+ if (hasCity) names.push("city");
3471
+ if (hasContract) names.push("contract");
3472
+ if (hasContracts) names.push("contracts");
3473
+ throw new Error(`research(): selectors are mutually exclusive; got ${JSON.stringify(names)}`);
3474
+ }
3475
+ if (opts.sources !== void 0 && opts.source !== void 0) {
3476
+ throw new Error("research(): sources and source are mutually exclusive");
3477
+ }
3478
+ if (opts.sources !== void 0 || opts.source !== void 0) {
3479
+ throw new Error(
3480
+ "research(): sources / source validation surface is shipped in Phase 10 v0.2 but the data-selection wiring lands in v0.3. For Mode 2 single-source pinning today, use `researchBySource(station, source, ...)` from @mostlyrightmd/meta."
3481
+ );
3482
+ }
3483
+ if (opts.stationOverride !== void 0 && !hasContract) {
3484
+ throw new Error(
3485
+ "research(): stationOverride requires contract (not standalone station/city/contracts)"
3486
+ );
3487
+ }
3488
+ if (opts.includeTrades === true && !(hasContract || hasContracts)) {
3489
+ throw new Error(
3490
+ "research(): includeTrades requires contract or contracts (station/city selectors have no trade timeseries)"
3491
+ );
3492
+ }
3493
+ if (hasCity || hasContract || hasContracts) {
3494
+ throw new Error(
3495
+ "research(): city/contract/contracts selectors are validated in Phase 10 v0.2 but the multi-station/multi-issuer JOIN + trade attachment lands in v0.3. For now, use `discover({city})` to find the station then call `research(station, fromDate, toDate)` directly."
3496
+ );
3497
+ }
3498
+ const resolved = normalizeStation2(station);
3499
+ const dates = buildDateList(fromDate, toDate);
3500
+ const extendedTo = plusOneDay(toDate);
3501
+ const fromYear = Number(fromDate.slice(0, 4));
3502
+ const toYear = Number(toDate.slice(0, 4));
3503
+ const extendedToYear = Number(extendedTo.slice(0, 4));
3504
+ const baseOpts = {};
3505
+ if (opts.signal !== void 0) baseOpts.signal = opts.signal;
3506
+ const cache = await resolveCache(opts);
3507
+ const cacheNow = opts.now ?? /* @__PURE__ */ new Date();
3508
+ let mergedClimate = [];
3509
+ try {
3510
+ const cliRows = await fetchCliWithCache(
3511
+ resolved.icao,
3512
+ resolved.code,
3513
+ fromYear,
3514
+ toYear,
3515
+ opts,
3516
+ cache,
3517
+ cacheNow
3518
+ );
3519
+ mergedClimate = mergeClimate(cliRows);
3520
+ } catch (err) {
3521
+ if (err instanceof DOMException && (err.name === "AbortError" || err.name === "TimeoutError")) {
3522
+ throw err;
3523
+ }
3524
+ }
3525
+ const awcHours = opts.awcHours ?? AWC_MAX_HOURS2;
3526
+ const awcRows = [];
3527
+ if (anyDateOverlapsAwc(toDate, awcHours, opts.now ?? /* @__PURE__ */ new Date())) {
3528
+ const awcOpts = { hours: awcHours };
3529
+ if (opts.signal !== void 0) awcOpts.signal = opts.signal;
3530
+ const awcRaw = await fetchAwcMetars([resolved.icao], awcOpts);
3531
+ for (const m of awcRaw) {
3532
+ const obs = awcToObservation(m);
3533
+ if (obs !== null) awcRows.push(obs);
3534
+ }
3535
+ }
3536
+ const iemRows = await fetchIemAsosWithCache(
3537
+ resolved.code,
3538
+ fromYear,
3539
+ extendedToYear,
3540
+ fromDate,
3541
+ extendedTo,
3542
+ opts,
3543
+ cache,
3544
+ cacheNow
3545
+ );
3546
+ let ghcnhRows = [];
3547
+ if (isUsStation(resolved) && resolved.ghcnhId !== null && resolved.ghcnhId.length > 0) {
3548
+ ghcnhRows = await fetchGhcnhWithCache(
3549
+ resolved.code,
3550
+ resolved.ghcnhId,
3551
+ fromDate,
3552
+ extendedTo,
3553
+ opts,
3554
+ cache,
3555
+ cacheNow
3556
+ );
3557
+ }
3558
+ const combinedRaw = [...awcRows, ...iemRows, ...ghcnhRows];
3559
+ const sorted = sortByObservedAtThenSource(combinedRaw);
3560
+ const merged = mergeObservations(sorted);
3561
+ const observationsByDate = {};
3562
+ const dateLo = dates[0] ?? "";
3563
+ const dateHi = dates[dates.length - 1] ?? "";
3564
+ for (const obs of merged) {
3565
+ const settleDate = observedSettlementDate(obs.observed_at, resolved.code);
3566
+ if (settleDate === null) continue;
3567
+ if (settleDate < dateLo || settleDate > dateHi) continue;
3568
+ let bucket = observationsByDate[settleDate];
3569
+ if (bucket === void 0) {
3570
+ bucket = [];
3571
+ observationsByDate[settleDate] = bucket;
3572
+ }
3573
+ bucket.push(obs);
3574
+ }
3575
+ const climateByDate = {};
3576
+ for (const cli of mergedClimate) {
3577
+ climateByDate[cli.observation_date] = cli;
3578
+ }
3579
+ return buildPairs(resolved.code, dates, observationsByDate, climateByDate);
3580
+ }
3581
+
3582
+ // src/mode2.ts
3583
+ var MODE2_SOURCES = ["iem.archive", "iem.live", "awc.live", "ghcnh.archive"];
3584
+ var SOURCE_ALIASES = /* @__PURE__ */ new Map([
3585
+ ["iem.archive", /* @__PURE__ */ new Set(["iem", "iem.archive"])],
3586
+ ["iem.live", /* @__PURE__ */ new Set(["iem", "iem.live"])],
3587
+ ["awc.live", /* @__PURE__ */ new Set(["awc", "awc.live"])],
3588
+ ["ghcnh.archive", /* @__PURE__ */ new Set(["ghcnh", "ghcnh.archive"])]
3589
+ ]);
3590
+ function isMode2Source(value) {
3591
+ return typeof value === "string" && MODE2_SOURCES.includes(value);
3592
+ }
3593
+ function assertSourceIdentity(rows, expected, role = "observations") {
3594
+ const accept = typeof expected === "string" ? /* @__PURE__ */ new Set([expected]) : expected;
3595
+ const expectedLabel = typeof expected === "string" ? expected : [...accept].sort().join("|");
3596
+ const distinct = /* @__PURE__ */ new Set();
3597
+ let bad = 0;
3598
+ for (const r of rows) {
3599
+ const src = r?.source;
3600
+ if (typeof src !== "string") continue;
3601
+ if (!accept.has(src)) {
3602
+ distinct.add(src);
3603
+ bad += 1;
3604
+ }
3605
+ }
3606
+ if (bad === 0) return;
3607
+ const others = [...distinct].sort();
3608
+ const first = others[0] ?? "<unknown>";
3609
+ throw new SourceMismatchError(
3610
+ `Mode 2 dispatch requested '${expectedLabel}' but received ${bad} row(s) with other sources: [${others.map((s) => `'${s}'`).join(", ")}]`,
3611
+ {
3612
+ schemaSource: expectedLabel,
3613
+ dataSource: first,
3614
+ role,
3615
+ catalogWarning: null
3616
+ }
3617
+ );
3618
+ }
3619
+ var AWC_MAX_HOURS3 = 168;
3620
+ var DATE_RE4 = /^\d{4}-\d{2}-\d{2}$/;
3621
+ function resolveStation(input) {
3622
+ const raw = input.trim().toUpperCase();
3623
+ if (raw.length === 0) {
3624
+ throw new Error("station must be a non-empty string");
3625
+ }
3626
+ const byIcao = STATION_BY_ICAO.get(raw);
3627
+ if (byIcao !== void 0) {
3628
+ if (byIcao.code === null) {
3629
+ throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
3630
+ }
3631
+ return {
3632
+ code: byIcao.code,
3633
+ icao: byIcao.icao,
3634
+ country: byIcao.country,
3635
+ ghcnhId: byIcao.ghcnh_id
3636
+ };
3637
+ }
3638
+ const byCode = STATION_BY_CODE.get(raw);
3639
+ if (byCode !== void 0) {
3640
+ if (byCode.code === null) {
3641
+ throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
3642
+ }
3643
+ return {
3644
+ code: byCode.code,
3645
+ icao: byCode.icao,
3646
+ country: byCode.country,
3647
+ ghcnhId: byCode.ghcnh_id
3648
+ };
3649
+ }
3650
+ if (raw.startsWith("K") && raw.length === 4) {
3651
+ const stripped = raw.slice(1);
3652
+ const retry = STATION_BY_CODE.get(stripped);
3653
+ if (retry !== void 0 && retry.code !== null) {
3654
+ return {
3655
+ code: retry.code,
3656
+ icao: retry.icao,
3657
+ country: retry.country,
3658
+ ghcnhId: retry.ghcnh_id
3659
+ };
3660
+ }
3661
+ }
3662
+ throw new Error(
3663
+ `unknown station ${JSON.stringify(input)} \u2014 not found in STATION_BY_CODE or STATION_BY_ICAO`
3664
+ );
3665
+ }
3666
+ function validateDateFormat(label, value) {
3667
+ if (!DATE_RE4.test(value)) {
3668
+ throw new Error(`${label} must be YYYY-MM-DD, got ${JSON.stringify(value)}`);
3669
+ }
3670
+ }
3671
+ function yearOf(isoDate) {
3672
+ return Number(isoDate.slice(0, 4));
3673
+ }
3674
+ async function researchBySource(station, source, fromDate, toDate, opts = {}) {
3675
+ if (!isMode2Source(source)) {
3676
+ throw new Error(
3677
+ `Mode 2 source must be one of ${JSON.stringify(
3678
+ MODE2_SOURCES
3679
+ )}; got ${JSON.stringify(source)}`
3680
+ );
3681
+ }
3682
+ if (source === "iem.live") {
3683
+ throw new Error(
3684
+ "Mode 2 source 'iem.live' not yet implemented in v0.1.0 (Parity-Ticket: requires per-month live IEM endpoint not yet ported). Use 'iem.archive' for historical IEM rows."
3685
+ );
3686
+ }
3687
+ validateDateFormat("fromDate", fromDate);
3688
+ validateDateFormat("toDate", toDate);
3689
+ if (fromDate > toDate) {
3690
+ throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);
3691
+ }
3692
+ const resolved = resolveStation(station);
3693
+ const accept = SOURCE_ALIASES.get(source);
3694
+ if (accept === void 0) {
3695
+ throw new Error(`internal: no SOURCE_ALIASES entry for '${source}'`);
3696
+ }
3697
+ let rows;
3698
+ switch (source) {
3699
+ case "awc.live": {
3700
+ const awcOpts = {
3701
+ hours: opts.awcHours ?? AWC_MAX_HOURS3
3702
+ };
3703
+ if (opts.signal !== void 0) awcOpts.signal = opts.signal;
3704
+ const raw = await fetchAwcMetars([resolved.icao], awcOpts);
3705
+ const parsed = [];
3706
+ for (const m of raw) {
3707
+ const obs = awcToObservation(m);
3708
+ if (obs !== null) parsed.push(obs);
3709
+ }
3710
+ rows = parsed.filter((r) => {
3711
+ const d = r.observed_at.slice(0, 10);
3712
+ return d >= fromDate && d <= toDate;
3713
+ });
3714
+ break;
3715
+ }
3716
+ case "iem.archive": {
3717
+ const fromYear = yearOf(fromDate);
3718
+ const toYear = yearOf(toDate);
3719
+ const collected = [];
3720
+ for (let year = fromYear; year <= toYear; year++) {
3721
+ for (const reportType of [3, 4]) {
3722
+ const iemOpts = {
3723
+ reportType,
3724
+ politenessMs: opts.iemPolitenessMs ?? 1e3
3725
+ };
3726
+ if (opts.signal !== void 0) iemOpts.signal = opts.signal;
3727
+ const chunks = await downloadIemAsos(
3728
+ resolved.code,
3729
+ `${year}-01-01`,
3730
+ `${year}-12-31`,
3731
+ iemOpts
3732
+ );
3733
+ for (const chunk of chunks) {
3734
+ const parsed = parseIemCsv(chunk.csv, {
3735
+ observationTypeOverride: reportType === 3 ? "METAR" : "SPECI"
3736
+ });
3737
+ collected.push(...parsed);
3738
+ }
3739
+ }
3740
+ }
3741
+ rows = collected.filter((r) => {
3742
+ const d = r.observed_at.slice(0, 10);
3743
+ return d >= fromDate && d <= toDate;
3744
+ });
3745
+ break;
3746
+ }
3747
+ case "ghcnh.archive": {
3748
+ if (resolved.country !== "US" || resolved.ghcnhId === null || resolved.ghcnhId.length === 0) {
3749
+ throw new NotFoundError(
3750
+ `GHCNh archive is US-only; station ${JSON.stringify(station)} (country=${resolved.country ?? "null"}, ghcnh_id=${resolved.ghcnhId === null ? "null" : JSON.stringify(resolved.ghcnhId)}) has no GHCNh coverage`
3751
+ );
3752
+ }
3753
+ const fromYear = yearOf(fromDate);
3754
+ const toYear = yearOf(toDate);
3755
+ const collected = [];
3756
+ for (let year = fromYear; year <= toYear; year++) {
3757
+ const ghcnhOpts = {};
3758
+ if (opts.signal !== void 0) ghcnhOpts.signal = opts.signal;
3759
+ try {
3760
+ const yr = await downloadGhcnh(resolved.ghcnhId, year, ghcnhOpts);
3761
+ const parsed = parseGhcnhPsv(yr.psv);
3762
+ for (const r of parsed) {
3763
+ if (r.station_code === resolved.code) collected.push(r);
3764
+ }
3765
+ } catch (err) {
3766
+ if (err instanceof NotFoundError) continue;
3767
+ throw err;
3768
+ }
3769
+ }
3770
+ rows = collected.filter((r) => {
3771
+ const d = r.observed_at.slice(0, 10);
3772
+ return d >= fromDate && d <= toDate;
3773
+ });
3774
+ break;
3775
+ }
3776
+ }
3777
+ const filtered = rows.filter((r) => accept.has(r.source));
3778
+ assertSourceIdentity(filtered, accept, "observations");
3779
+ return filtered;
3780
+ }
3781
+
3782
+ // ../markets/src/polymarket/types.ts
3783
+ var NETLOC_TO_RESOLUTION_TYPE = Object.freeze({
3784
+ "wunderground.com": "wunderground",
3785
+ "www.wunderground.com": "wunderground",
3786
+ "weather.gov": "noaa_wrh",
3787
+ "www.weather.gov": "noaa_wrh"
3788
+ });
3789
+ var POLYMARKET_RESOLUTION_SOURCE_TYPES = Object.freeze([
3790
+ "wunderground",
3791
+ "noaa_wrh",
3792
+ "hko",
3793
+ "cwa",
3794
+ "other"
3795
+ ]);
3796
+ var MAX_DESCRIPTION_BYTES = 16 * 1024;
3797
+ var SETTLE_DELAY_HOURS = Object.freeze({
3798
+ wunderground: 6,
3799
+ noaa_wrh: 4,
3800
+ other: 24
3801
+ });
3802
+
3803
+ // ../markets/src/polymarket/resolver.ts
3804
+ var CITY_KEYS_SORTED = Object.freeze(
3805
+ Object.keys(POLYMARKET_CITY_STATIONS).sort((a, b) => b.length - a.length)
3806
+ );
3807
+
3808
+ // ../markets/src/polymarket/known-wrong-stations.ts
3809
+ var POLYMARKET_KNOWN_WRONG_STATIONS = Object.freeze({
3810
+ // NYC: Polymarket uses KLGA. KNYC/KJFK/KEWR are common wrong answers.
3811
+ nyc: /* @__PURE__ */ new Set(["KNYC", "KJFK", "KEWR"]),
3812
+ // Chicago: Polymarket uses KORD. KMDW is the common wrong answer.
3813
+ chicago: /* @__PURE__ */ new Set(["KMDW"]),
3814
+ // Houston: Polymarket uses KIAH. KHOU is the common wrong answer.
3815
+ houston: /* @__PURE__ */ new Set(["KHOU"]),
3816
+ // Dallas: Polymarket uses KDFW. KDAL is the common wrong answer.
3817
+ dallas: /* @__PURE__ */ new Set(["KDAL"]),
3818
+ // SF: Polymarket uses KSFO. KOAK is the common wrong answer.
3819
+ san_francisco: /* @__PURE__ */ new Set(["KOAK"]),
3820
+ // DC: Polymarket uses KDCA. KIAD/KBWI are common wrong answers.
3821
+ washington_dc: /* @__PURE__ */ new Set(["KIAD", "KBWI"])
3822
+ });
3823
+
3824
+ // src/compose.ts
3825
+ var SELECTOR_NAMES = ["station", "city", "contract", "contracts"];
3826
+ var KALSHI_TICKER_ALIASES = {
3827
+ NY: "NYC"
3828
+ };
3829
+ var CITY_SLUG_ALIASES = {
3830
+ // short_kalshi (lower) → [polymarket_long, kalshi_upper]
3831
+ nyc: ["nyc", "NYC"],
3832
+ chi: ["chicago", "CHI"],
3833
+ lax: ["los_angeles", "LAX"],
3834
+ mia: ["miami", "MIA"],
3835
+ den: ["denver", "DEN"],
3836
+ bos: ["boston", "BOS"],
3837
+ aus: ["austin", "AUS"],
3838
+ dca: ["washington_dc", "DCA"],
3839
+ phl: ["philadelphia", "PHL"],
3840
+ sfo: ["san_francisco", "SFO"],
3841
+ sea: ["seattle", "SEA"],
3842
+ atl: ["atlanta", "ATL"],
3843
+ hou: ["houston", "HOU"],
3844
+ dal: ["dallas", "DAL"],
3845
+ phx: ["phoenix", "PHX"],
3846
+ msp: ["minneapolis", "MSP"],
3847
+ dtw: ["detroit", "DTW"]
3848
+ };
3849
+ var CITY_SLUG_ALIASES_REVERSE = (() => {
3850
+ const out = {};
3851
+ for (const [shortLower, [longPoly, kalshiUpper]] of Object.entries(CITY_SLUG_ALIASES)) {
3852
+ out[longPoly] = [shortLower, kalshiUpper];
3853
+ }
3854
+ return out;
3855
+ })();
3856
+ function normalizeCitySlugs(city) {
3857
+ const lower = city.toLowerCase();
3858
+ const upper = city.toUpperCase();
3859
+ const direct = CITY_SLUG_ALIASES[lower];
3860
+ if (direct !== void 0) return direct;
3861
+ const reverse = CITY_SLUG_ALIASES_REVERSE[lower];
3862
+ if (reverse !== void 0) return [lower, reverse[1]];
3863
+ return [lower, upper];
3864
+ }
3865
+ function validateSelectors(args) {
3866
+ const provided = [];
3867
+ if (typeof args.station === "string" && args.station.length > 0) provided.push("station");
3868
+ if (typeof args.city === "string" && args.city.length > 0) provided.push("city");
3869
+ if (typeof args.contract === "string" && args.contract.length > 0) provided.push("contract");
3870
+ if (Array.isArray(args.contracts) && args.contracts.length > 0) provided.push("contracts");
3871
+ if (provided.length === 0) {
3872
+ throw new Error(
3873
+ "research(): exactly one of station, city, contract, contracts must be provided"
3874
+ );
3875
+ }
3876
+ if (provided.length > 1) {
3877
+ throw new Error(
3878
+ `research(): selectors are mutually exclusive; got ${JSON.stringify(provided)}`
3879
+ );
3880
+ }
3881
+ return provided[0];
3882
+ }
3883
+ function resolveContract(contractId) {
3884
+ if (typeof contractId !== "string" || !contractId.includes(":")) {
3885
+ throw new TypeError(`contract id must be \`<issuer>:<id>\`; got ${JSON.stringify(contractId)}`);
3886
+ }
3887
+ const colonIdx = contractId.indexOf(":");
3888
+ const issuer = contractId.slice(0, colonIdx).toLowerCase();
3889
+ const raw = contractId.slice(colonIdx + 1);
3890
+ const rawUpper = raw.toUpperCase();
3891
+ if (issuer === "kalshi") {
3892
+ let normalized = rawUpper;
3893
+ if (normalized.startsWith("KX")) {
3894
+ normalized = `K${normalized.slice(2)}`;
3895
+ }
3896
+ const cityOnly = normalized.split("-", 1)[0] ?? "";
3897
+ let cityTickerRaw = null;
3898
+ if (cityOnly.startsWith("KHIGH") && cityOnly.length > 5) {
3899
+ cityTickerRaw = cityOnly.slice(5);
3900
+ } else if (cityOnly.startsWith("KLOW") && cityOnly.length > 4) {
3901
+ cityTickerRaw = cityOnly.slice(4);
3902
+ } else {
3903
+ throw new Error(
3904
+ `unsupported kalshi contract format: ${JSON.stringify(raw)}; expected KHIGH<CITY>* / KXHIGH<CITY>* / KLOW<CITY>* / KXLOW<CITY>* prefix`
3905
+ );
3906
+ }
3907
+ const cityTicker = KALSHI_TICKER_ALIASES[cityTickerRaw] ?? cityTickerRaw;
3908
+ const entry = KALSHI_SETTLEMENT_STATIONS[cityTicker];
3909
+ if (entry === void 0) {
3910
+ throw new Error(`unknown Kalshi city ticker: ${JSON.stringify(cityTicker)}`);
3911
+ }
3912
+ return [entry.station, "kalshi"];
3913
+ }
3914
+ if (issuer === "polymarket") {
3915
+ throw new Error(
3916
+ "polymarket contract resolution requires event_id \u2192 station lookup via polymarketDiscover()/polymarketSettle(); Phase 10 v0.2 defers this to v0.3. Use `city: 'nyc'` or pass `stationOverride` until then."
3917
+ );
3918
+ }
3919
+ throw new Error(
3920
+ `unknown issuer prefix: ${JSON.stringify(issuer)}; expected kalshi or polymarket`
3921
+ );
3922
+ }
3923
+ function resolveCity(city) {
3924
+ if (typeof city !== "string" || !city) {
3925
+ throw new Error(`city must be a non-empty string; got ${JSON.stringify(city)}`);
3926
+ }
3927
+ const [polySlug, kalshiSlug] = normalizeCitySlugs(city);
3928
+ const out = [];
3929
+ const kalshi = KALSHI_SETTLEMENT_STATIONS[kalshiSlug];
3930
+ if (kalshi !== void 0 && !out.includes(kalshi.station)) {
3931
+ out.push(kalshi.station);
3932
+ }
3933
+ const poly = POLYMARKET_CITY_STATIONS[polySlug];
3934
+ if (poly !== void 0) {
3935
+ for (const measure of ["default", "high", "low"]) {
3936
+ const st = poly[measure];
3937
+ if (typeof st === "string" && !out.includes(st)) out.push(st);
3938
+ }
3939
+ }
3940
+ const wrong = POLYMARKET_KNOWN_WRONG_STATIONS[polySlug];
3941
+ if (wrong !== void 0) {
3942
+ const sortedWrong = [...wrong].sort();
3943
+ for (const st of sortedWrong) {
3944
+ if (!out.includes(st)) out.push(st);
3945
+ }
3946
+ }
3947
+ if (out.length === 0) {
3948
+ throw new Error(`unknown city ${JSON.stringify(city)}; not in kalshi or polymarket catalogs`);
3949
+ }
3950
+ return out;
3951
+ }
3952
+ function annotateSettlesFor(station, city) {
3953
+ if (city === null) return [];
3954
+ const [polySlug, kalshiSlug] = normalizeCitySlugs(city);
3955
+ const out = [];
3956
+ const kalshi = KALSHI_SETTLEMENT_STATIONS[kalshiSlug];
3957
+ if (kalshi !== void 0 && kalshi.station === station) {
3958
+ out.push(`kalshi:${kalshiSlug}`);
3959
+ }
3960
+ const poly = POLYMARKET_CITY_STATIONS[polySlug];
3961
+ if (poly !== void 0) {
3962
+ for (const measure of ["default", "high", "low"]) {
3963
+ if (poly[measure] === station) {
3964
+ out.push(`polymarket:${polySlug}`);
3965
+ break;
3966
+ }
3967
+ }
3968
+ }
3969
+ return out.sort();
3970
+ }
3971
+ function buildOverrideWarning(contractStation, overrideStation) {
3972
+ return {
3973
+ kind: "StationOverrideWarning",
3974
+ contractStation,
3975
+ overrideStation,
3976
+ message: `stationOverride=${JSON.stringify(overrideStation)} differs from contract's canonical settlement station ${JSON.stringify(contractStation)}; output row will carry settlementMismatch=true`
3977
+ };
3978
+ }
3979
+
3980
+ // src/discover.ts
3981
+ function discover(args) {
3982
+ if (typeof args !== "object" || args === null) {
3983
+ throw new TypeError(`discover(): args must be an object; got ${typeof args}`);
3984
+ }
3985
+ const stations = resolveCity(args.city);
3986
+ const rows = stations.map((station) => ({
3987
+ city: args.city,
3988
+ station,
3989
+ settlesFor: annotateSettlesFor(station, args.city)
3990
+ }));
3991
+ return Object.freeze({
3992
+ rows: Object.freeze(rows),
3993
+ city: args.city,
3994
+ source: "discover"
3995
+ });
3996
+ }
3997
+
3998
+ // src/index.ts
3999
+ var version3 = "0.0.0";
4000
+ export {
4001
+ LiveStreamError,
4002
+ MODE2_SOURCES,
4003
+ NoLiveDataError,
4004
+ POLITE_FLOORS_S,
4005
+ SELECTOR_NAMES,
4006
+ SOURCE_ALIASES,
4007
+ SOURCE_IDENTITY_TAGS,
4008
+ SUPPORTED_SOURCES,
4009
+ annotateSettlesFor,
4010
+ assertSourceIdentity,
4011
+ buildOverrideWarning,
4012
+ src_exports as core,
4013
+ discover,
4014
+ helloCore,
4015
+ helloMarkets,
4016
+ helloWeather,
4017
+ isLiveSource,
4018
+ isMode2Source,
4019
+ latest,
4020
+ src_exports3 as markets,
4021
+ research,
4022
+ researchBySource,
4023
+ resolveCity,
4024
+ resolveContract,
4025
+ sourceTag,
4026
+ stream,
4027
+ validatePollSeconds,
4028
+ validateSelectors,
4029
+ validateSource,
4030
+ version3 as version,
4031
+ src_exports2 as weather
4032
+ };
4033
+ //# sourceMappingURL=index.bundle.mjs.map