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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/bounds-KSTXL77E.mjs +46 -0
- package/dist/bounds-KSTXL77E.mjs.map +1 -0
- package/dist/chunk-6ERO2BIY.mjs +436 -0
- package/dist/chunk-6ERO2BIY.mjs.map +1 -0
- package/dist/chunk-J5LGTIGS.mjs +10 -0
- package/dist/chunk-J5LGTIGS.mjs.map +1 -0
- package/dist/chunk-UKIFUUDX.mjs +108 -0
- package/dist/chunk-UKIFUUDX.mjs.map +1 -0
- package/dist/chunk-VESWR46G.mjs +82 -0
- package/dist/chunk-VESWR46G.mjs.map +1 -0
- package/dist/chunk-WYZFDCNR.mjs +1609 -0
- package/dist/chunk-WYZFDCNR.mjs.map +1 -0
- package/dist/iem-5RVPI3TY.mjs +11 -0
- package/dist/iem-5RVPI3TY.mjs.map +1 -0
- package/dist/iem-asos-O4CQWBXK.mjs +15 -0
- package/dist/iem-asos-O4CQWBXK.mjs.map +1 -0
- package/dist/index.bundle.mjs +4033 -0
- package/dist/index.bundle.mjs.map +1 -0
- package/dist/index.cjs +967 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +295 -0
- package/dist/index.d.ts +295 -0
- package/dist/index.global.js +6350 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.mjs +953 -0
- package/dist/index.mjs.map +1 -0
- package/dist/src-5L5C2EE7.mjs +88 -0
- package/dist/src-5L5C2EE7.mjs.map +1 -0
- package/package.json +46 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
LiveStreamError: () => import_core4.LiveStreamError,
|
|
34
|
+
MODE2_SOURCES: () => MODE2_SOURCES,
|
|
35
|
+
NoLiveDataError: () => import_core4.NoLiveDataError,
|
|
36
|
+
POLITE_FLOORS_S: () => import_weather4.POLITE_FLOORS_S,
|
|
37
|
+
SELECTOR_NAMES: () => SELECTOR_NAMES,
|
|
38
|
+
SOURCE_ALIASES: () => SOURCE_ALIASES,
|
|
39
|
+
SOURCE_IDENTITY_TAGS: () => import_weather4.SOURCE_IDENTITY_TAGS,
|
|
40
|
+
SUPPORTED_SOURCES: () => import_weather4.SUPPORTED_SOURCES,
|
|
41
|
+
annotateSettlesFor: () => annotateSettlesFor,
|
|
42
|
+
assertSourceIdentity: () => assertSourceIdentity,
|
|
43
|
+
buildOverrideWarning: () => buildOverrideWarning,
|
|
44
|
+
core: () => core,
|
|
45
|
+
discover: () => discover,
|
|
46
|
+
helloCore: () => import_core3.helloCore,
|
|
47
|
+
helloMarkets: () => import_markets2.helloMarkets,
|
|
48
|
+
helloWeather: () => import_weather3.helloWeather,
|
|
49
|
+
isLiveSource: () => import_weather4.isLiveSource,
|
|
50
|
+
isMode2Source: () => isMode2Source,
|
|
51
|
+
latest: () => import_weather4.latest,
|
|
52
|
+
markets: () => markets,
|
|
53
|
+
research: () => research,
|
|
54
|
+
researchBySource: () => researchBySource,
|
|
55
|
+
resolveCity: () => resolveCity,
|
|
56
|
+
resolveContract: () => resolveContract,
|
|
57
|
+
sourceTag: () => import_weather4.sourceTag,
|
|
58
|
+
stream: () => import_weather4.stream,
|
|
59
|
+
validatePollSeconds: () => import_weather4.validatePollSeconds,
|
|
60
|
+
validateSelectors: () => validateSelectors,
|
|
61
|
+
validateSource: () => import_weather4.validateSource,
|
|
62
|
+
version: () => version,
|
|
63
|
+
weather: () => weather
|
|
64
|
+
});
|
|
65
|
+
module.exports = __toCommonJS(index_exports);
|
|
66
|
+
var import_core3 = require("@mostlyrightmd/core");
|
|
67
|
+
var import_weather3 = require("@mostlyrightmd/weather");
|
|
68
|
+
var import_markets2 = require("@mostlyrightmd/markets");
|
|
69
|
+
var core = __toESM(require("@mostlyrightmd/core"), 1);
|
|
70
|
+
var markets = __toESM(require("@mostlyrightmd/markets"), 1);
|
|
71
|
+
var weather = __toESM(require("@mostlyrightmd/weather"), 1);
|
|
72
|
+
|
|
73
|
+
// src/research.ts
|
|
74
|
+
var import_core = require("@mostlyrightmd/core");
|
|
75
|
+
var import_cache = require("@mostlyrightmd/core/internal/cache");
|
|
76
|
+
var import_merge = require("@mostlyrightmd/core/internal/merge");
|
|
77
|
+
var import_pairs = require("@mostlyrightmd/core/internal/pairs");
|
|
78
|
+
var import_weather = require("@mostlyrightmd/weather");
|
|
79
|
+
var AWC_MAX_HOURS = 168;
|
|
80
|
+
async function resolveCache(opts) {
|
|
81
|
+
if (opts.cache === null) return null;
|
|
82
|
+
if (opts.cache !== void 0) return opts.cache;
|
|
83
|
+
return await (0, import_cache.defaultCacheStore)();
|
|
84
|
+
}
|
|
85
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
86
|
+
function normalizeStation(input) {
|
|
87
|
+
const raw = input.trim().toUpperCase();
|
|
88
|
+
if (raw.length === 0) {
|
|
89
|
+
throw new Error("station must be a non-empty string");
|
|
90
|
+
}
|
|
91
|
+
const byIcao = import_core.STATION_BY_ICAO.get(raw);
|
|
92
|
+
if (byIcao !== void 0) {
|
|
93
|
+
if (byIcao.code === null) {
|
|
94
|
+
throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
code: byIcao.code,
|
|
98
|
+
icao: byIcao.icao,
|
|
99
|
+
tz: byIcao.tz,
|
|
100
|
+
country: byIcao.country,
|
|
101
|
+
ghcnhId: byIcao.ghcnh_id
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const byCode = import_core.STATION_BY_CODE.get(raw);
|
|
105
|
+
if (byCode !== void 0) {
|
|
106
|
+
if (byCode.code === null) {
|
|
107
|
+
throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
code: byCode.code,
|
|
111
|
+
icao: byCode.icao,
|
|
112
|
+
tz: byCode.tz,
|
|
113
|
+
country: byCode.country,
|
|
114
|
+
ghcnhId: byCode.ghcnh_id
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (raw.startsWith("K") && raw.length === 4) {
|
|
118
|
+
const stripped = raw.slice(1);
|
|
119
|
+
const retry = import_core.STATION_BY_CODE.get(stripped);
|
|
120
|
+
if (retry !== void 0 && retry.code !== null) {
|
|
121
|
+
return {
|
|
122
|
+
code: retry.code,
|
|
123
|
+
icao: retry.icao,
|
|
124
|
+
tz: retry.tz,
|
|
125
|
+
country: retry.country,
|
|
126
|
+
ghcnhId: retry.ghcnh_id
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw new Error(
|
|
131
|
+
`unknown station ${JSON.stringify(input)} \u2014 not found in STATION_BY_CODE or STATION_BY_ICAO`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
function parseIsoDate(s) {
|
|
135
|
+
if (!DATE_RE.test(s)) {
|
|
136
|
+
throw new Error(`expected YYYY-MM-DD, got ${JSON.stringify(s)}`);
|
|
137
|
+
}
|
|
138
|
+
const [yStr, mStr, dStr] = s.split("-");
|
|
139
|
+
const year = Number(yStr);
|
|
140
|
+
const month = Number(mStr);
|
|
141
|
+
const day = Number(dStr);
|
|
142
|
+
const ms = Date.UTC(year, month - 1, day);
|
|
143
|
+
const d = new Date(ms);
|
|
144
|
+
if (d.getUTCFullYear() !== year || d.getUTCMonth() !== month - 1 || d.getUTCDate() !== day) {
|
|
145
|
+
throw new Error(`invalid calendar date ${JSON.stringify(s)}`);
|
|
146
|
+
}
|
|
147
|
+
return d;
|
|
148
|
+
}
|
|
149
|
+
function formatDate(d) {
|
|
150
|
+
const y = d.getUTCFullYear();
|
|
151
|
+
const m = d.getUTCMonth() + 1;
|
|
152
|
+
const day = d.getUTCDate();
|
|
153
|
+
const mm = m < 10 ? `0${m}` : `${m}`;
|
|
154
|
+
const dd = day < 10 ? `0${day}` : `${day}`;
|
|
155
|
+
return `${y}-${mm}-${dd}`;
|
|
156
|
+
}
|
|
157
|
+
function buildDateList(fromDate, toDate) {
|
|
158
|
+
const from = parseIsoDate(fromDate);
|
|
159
|
+
const to = parseIsoDate(toDate);
|
|
160
|
+
if (from.getTime() > to.getTime()) {
|
|
161
|
+
throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);
|
|
162
|
+
}
|
|
163
|
+
const dates = [];
|
|
164
|
+
for (let cursor = from.getTime(); cursor <= to.getTime(); cursor += 24 * 36e5) {
|
|
165
|
+
dates.push(formatDate(new Date(cursor)));
|
|
166
|
+
}
|
|
167
|
+
return dates;
|
|
168
|
+
}
|
|
169
|
+
function plusOneDay(isoDate) {
|
|
170
|
+
const d = parseIsoDate(isoDate);
|
|
171
|
+
return formatDate(new Date(d.getTime() + 24 * 36e5));
|
|
172
|
+
}
|
|
173
|
+
function isUsStation(station) {
|
|
174
|
+
return station.country === "US";
|
|
175
|
+
}
|
|
176
|
+
function anyDateOverlapsAwc(toDate, hours, now) {
|
|
177
|
+
const to = parseIsoDate(toDate);
|
|
178
|
+
const toEndMs = to.getTime() + 24 * 36e5;
|
|
179
|
+
const nowMs = now.getTime();
|
|
180
|
+
const cutoffMs = nowMs - hours * 36e5;
|
|
181
|
+
return toEndMs >= cutoffMs;
|
|
182
|
+
}
|
|
183
|
+
function observedSettlementDate(observedAt, station) {
|
|
184
|
+
const ms = Date.parse(observedAt);
|
|
185
|
+
if (!Number.isFinite(ms)) return null;
|
|
186
|
+
try {
|
|
187
|
+
return (0, import_core.settlementDateFor)(new Date(ms), station);
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function sortByObservedAtThenSource(rows) {
|
|
193
|
+
return [...rows].sort((a, b) => {
|
|
194
|
+
if (a.observed_at < b.observed_at) return -1;
|
|
195
|
+
if (a.observed_at > b.observed_at) return 1;
|
|
196
|
+
if (a.source < b.source) return -1;
|
|
197
|
+
if (a.source > b.source) return 1;
|
|
198
|
+
return 0;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function isYearVolatile(year, now) {
|
|
202
|
+
const yearEnd = `${String(year).padStart(4, "0")}-12-31`;
|
|
203
|
+
return (0, import_cache.isWithinVolatileWindow)(yearEnd, formatDate(now), 30);
|
|
204
|
+
}
|
|
205
|
+
function lastDayOfMonth(year, month) {
|
|
206
|
+
const d = new Date(Date.UTC(year, month, 0));
|
|
207
|
+
return formatDate(d);
|
|
208
|
+
}
|
|
209
|
+
function isMonthVolatile(year, month, now) {
|
|
210
|
+
return (0, import_cache.isWithinVolatileWindow)(lastDayOfMonth(year, month), formatDate(now), 30);
|
|
211
|
+
}
|
|
212
|
+
function monthsInRange(fromIsoDate, toIsoDate) {
|
|
213
|
+
const from = parseIsoDate(fromIsoDate);
|
|
214
|
+
const to = parseIsoDate(toIsoDate);
|
|
215
|
+
if (from.getTime() > to.getTime()) {
|
|
216
|
+
throw new Error(`fromDate (${fromIsoDate}) must be <= toDate (${toIsoDate})`);
|
|
217
|
+
}
|
|
218
|
+
const pairs = [];
|
|
219
|
+
let y = from.getUTCFullYear();
|
|
220
|
+
let m = from.getUTCMonth() + 1;
|
|
221
|
+
const endY = to.getUTCFullYear();
|
|
222
|
+
const endM = to.getUTCMonth() + 1;
|
|
223
|
+
while (y < endY || y === endY && m <= endM) {
|
|
224
|
+
pairs.push([y, m]);
|
|
225
|
+
m += 1;
|
|
226
|
+
if (m > 12) {
|
|
227
|
+
m = 1;
|
|
228
|
+
y += 1;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return pairs;
|
|
232
|
+
}
|
|
233
|
+
async function fetchCliWithCache(fetchIcao, cacheCode, fromYear, toYear, opts, cache, now) {
|
|
234
|
+
const acc = [];
|
|
235
|
+
for (let year = fromYear; year <= toYear; year++) {
|
|
236
|
+
const writable = (0, import_cache.isWritableYear)(year, now);
|
|
237
|
+
const skipCurrentYear = (0, import_cache.shouldSkipCacheForCurrentLstYear)(cacheCode, year, now);
|
|
238
|
+
const skipVolatile = isYearVolatile(year, now);
|
|
239
|
+
const skip = !writable || skipCurrentYear || skipVolatile;
|
|
240
|
+
if (cache !== null && !skip) {
|
|
241
|
+
let cached = null;
|
|
242
|
+
try {
|
|
243
|
+
cached = await cache.get((0, import_cache.cacheKeyForClimate)(cacheCode, year));
|
|
244
|
+
} catch (cacheErr) {
|
|
245
|
+
console.warn(
|
|
246
|
+
`[mostlyright] CLI cache.get failed for code=${cacheCode} year=${year}; falling back to live fetch:`,
|
|
247
|
+
cacheErr
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (cached !== null) {
|
|
251
|
+
acc.push(...cached);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const cliOpts = {};
|
|
256
|
+
if (opts.signal !== void 0) cliOpts.signal = opts.signal;
|
|
257
|
+
if (opts.cliPolitenessMs !== void 0) cliOpts.politenessMs = opts.cliPolitenessMs;
|
|
258
|
+
const cliRaw = await (0, import_weather.downloadCliRange)(fetchIcao, year, year, cliOpts);
|
|
259
|
+
const parsed = (0, import_weather.parseCliResponse)(cliRaw, cacheCode);
|
|
260
|
+
acc.push(...parsed);
|
|
261
|
+
const sample = parsed[0]?.source;
|
|
262
|
+
if (cache !== null && !skip && !(0, import_cache.isLiveSource)(sample)) {
|
|
263
|
+
try {
|
|
264
|
+
await cache.set((0, import_cache.cacheKeyForClimate)(cacheCode, year), parsed);
|
|
265
|
+
} catch (cacheErr) {
|
|
266
|
+
console.warn(
|
|
267
|
+
`[mostlyright] CLI cache.set failed for code=${cacheCode} year=${year}; in-memory rows preserved:`,
|
|
268
|
+
cacheErr
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return acc;
|
|
274
|
+
}
|
|
275
|
+
async function fetchIemAsosWithCache(stationCode, _fromYear, _extendedToYear, fromDate, extendedTo, opts, cache, now) {
|
|
276
|
+
const acc = [];
|
|
277
|
+
const yearByReportType = /* @__PURE__ */ new Map();
|
|
278
|
+
async function fetchYearOnce(year, reportType) {
|
|
279
|
+
const memoKey = `${year}:${reportType}`;
|
|
280
|
+
const cached = yearByReportType.get(memoKey);
|
|
281
|
+
if (cached !== void 0) return cached;
|
|
282
|
+
const iemOpts = {
|
|
283
|
+
reportType,
|
|
284
|
+
politenessMs: opts.iemPolitenessMs ?? 1e3
|
|
285
|
+
};
|
|
286
|
+
if (opts.signal !== void 0) iemOpts.signal = opts.signal;
|
|
287
|
+
const chunks = await (0, import_weather.downloadIemAsos)(stationCode, `${year}-01-01`, `${year}-12-31`, iemOpts);
|
|
288
|
+
const fetched = [];
|
|
289
|
+
for (const chunk of chunks) {
|
|
290
|
+
const parsed = (0, import_weather.parseIemCsv)(chunk.csv, {
|
|
291
|
+
observationTypeOverride: reportType === 3 ? "METAR" : "SPECI"
|
|
292
|
+
});
|
|
293
|
+
fetched.push(...parsed);
|
|
294
|
+
}
|
|
295
|
+
yearByReportType.set(memoKey, fetched);
|
|
296
|
+
return fetched;
|
|
297
|
+
}
|
|
298
|
+
function filterMonth(rows, year, month) {
|
|
299
|
+
const yyyy = String(year).padStart(4, "0");
|
|
300
|
+
const mm = String(month).padStart(2, "0");
|
|
301
|
+
const prefix = `${yyyy}-${mm}-`;
|
|
302
|
+
const out = [];
|
|
303
|
+
for (const r of rows) {
|
|
304
|
+
if (r.observed_at.startsWith(prefix)) out.push(r);
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
const pairs = monthsInRange(fromDate, extendedTo);
|
|
309
|
+
for (const [year, month] of pairs) {
|
|
310
|
+
const cacheKey = (0, import_cache.cacheKeyForObservations)(stationCode, year, month, "iem");
|
|
311
|
+
const writable = (0, import_cache.isWritableMonth)(year, month, now);
|
|
312
|
+
const skipCurrentMonth = (0, import_cache.shouldSkipCacheForCurrentLstMonth)(stationCode, year, month, now);
|
|
313
|
+
const skipVolatile = isMonthVolatile(year, month, now);
|
|
314
|
+
const skipCache = !writable || skipCurrentMonth || skipVolatile;
|
|
315
|
+
let monthRows = null;
|
|
316
|
+
if (cache !== null && !skipCache) {
|
|
317
|
+
try {
|
|
318
|
+
const cached = await cache.get(cacheKey);
|
|
319
|
+
if (cached !== null) monthRows = cached;
|
|
320
|
+
} catch (cacheErr) {
|
|
321
|
+
console.warn(
|
|
322
|
+
`[mostlyright] IEM ASOS cache.get failed for key=${cacheKey}; falling back to live fetch:`,
|
|
323
|
+
cacheErr
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (monthRows === null) {
|
|
328
|
+
const metar = await fetchYearOnce(year, 3);
|
|
329
|
+
const speci = await fetchYearOnce(year, 4);
|
|
330
|
+
const monthMetar = filterMonth(metar, year, month);
|
|
331
|
+
const monthSpeci = filterMonth(speci, year, month);
|
|
332
|
+
monthRows = [...monthMetar, ...monthSpeci];
|
|
333
|
+
const sample = monthRows[0]?.source;
|
|
334
|
+
if (cache !== null && !skipCache && !(0, import_cache.isLiveSource)(sample)) {
|
|
335
|
+
try {
|
|
336
|
+
await cache.set(cacheKey, monthRows);
|
|
337
|
+
} catch (cacheErr) {
|
|
338
|
+
console.warn(
|
|
339
|
+
`[mostlyright] IEM ASOS cache.set failed for key=${cacheKey}; in-memory rows preserved:`,
|
|
340
|
+
cacheErr
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
for (const obs of monthRows) {
|
|
346
|
+
const obsDate = obs.observed_at.slice(0, 10);
|
|
347
|
+
if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return acc;
|
|
351
|
+
}
|
|
352
|
+
async function fetchGhcnhWithCache(stationCode, ghcnhId, fromDate, extendedTo, opts, cache, now) {
|
|
353
|
+
const acc = [];
|
|
354
|
+
const yearCache = /* @__PURE__ */ new Map();
|
|
355
|
+
async function fetchYearOnce(year) {
|
|
356
|
+
const cached = yearCache.get(year);
|
|
357
|
+
if (cached !== void 0) return cached;
|
|
358
|
+
const ghcnhOpts = {};
|
|
359
|
+
if (opts.signal !== void 0) ghcnhOpts.signal = opts.signal;
|
|
360
|
+
let parsed;
|
|
361
|
+
try {
|
|
362
|
+
const yr = await (0, import_weather.downloadGhcnh)(ghcnhId, year, ghcnhOpts);
|
|
363
|
+
parsed = (0, import_weather.parseGhcnhPsv)(yr.psv);
|
|
364
|
+
} catch (err) {
|
|
365
|
+
if (err instanceof import_core.NotFoundError) {
|
|
366
|
+
parsed = [];
|
|
367
|
+
} else {
|
|
368
|
+
throw err;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
yearCache.set(year, parsed);
|
|
372
|
+
return parsed;
|
|
373
|
+
}
|
|
374
|
+
function filterMonth(rows, year, month) {
|
|
375
|
+
const yyyy = String(year).padStart(4, "0");
|
|
376
|
+
const mm = String(month).padStart(2, "0");
|
|
377
|
+
const prefix = `${yyyy}-${mm}-`;
|
|
378
|
+
const out = [];
|
|
379
|
+
for (const r of rows) {
|
|
380
|
+
if (r.observed_at.startsWith(prefix) && r.station_code === stationCode) out.push(r);
|
|
381
|
+
}
|
|
382
|
+
return out;
|
|
383
|
+
}
|
|
384
|
+
const pairs = monthsInRange(fromDate, extendedTo);
|
|
385
|
+
for (const [year, month] of pairs) {
|
|
386
|
+
const cacheKey = (0, import_cache.cacheKeyForObservations)(stationCode, year, month, "ghcnh");
|
|
387
|
+
const writable = (0, import_cache.isWritableMonth)(year, month, now);
|
|
388
|
+
const skipCurrentMonth = (0, import_cache.shouldSkipCacheForCurrentLstMonth)(stationCode, year, month, now);
|
|
389
|
+
const skipVolatile = isMonthVolatile(year, month, now);
|
|
390
|
+
const skipCache = !writable || skipCurrentMonth || skipVolatile;
|
|
391
|
+
let monthRows = null;
|
|
392
|
+
if (cache !== null && !skipCache) {
|
|
393
|
+
try {
|
|
394
|
+
const cached = await cache.get(cacheKey);
|
|
395
|
+
if (cached !== null) monthRows = cached;
|
|
396
|
+
} catch (cacheErr) {
|
|
397
|
+
console.warn(
|
|
398
|
+
`[mostlyright] GHCNh cache.get failed for key=${cacheKey}; falling back to live fetch:`,
|
|
399
|
+
cacheErr
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (monthRows === null) {
|
|
404
|
+
const yearRows = await fetchYearOnce(year);
|
|
405
|
+
monthRows = filterMonth(yearRows, year, month);
|
|
406
|
+
const sample = monthRows[0]?.source;
|
|
407
|
+
if (cache !== null && !skipCache && !(0, import_cache.isLiveSource)(sample)) {
|
|
408
|
+
try {
|
|
409
|
+
await cache.set(cacheKey, monthRows);
|
|
410
|
+
} catch (cacheErr) {
|
|
411
|
+
console.warn(
|
|
412
|
+
`[mostlyright] GHCNh cache.set failed for key=${cacheKey}; in-memory rows preserved:`,
|
|
413
|
+
cacheErr
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
for (const obs of monthRows) {
|
|
419
|
+
const obsDate = obs.observed_at.slice(0, 10);
|
|
420
|
+
if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return acc;
|
|
424
|
+
}
|
|
425
|
+
async function research(station, fromDate, toDate, opts = {}) {
|
|
426
|
+
const hasCity = typeof opts.city === "string" && opts.city.length > 0;
|
|
427
|
+
const hasContract = typeof opts.contract === "string" && opts.contract.length > 0;
|
|
428
|
+
const hasContracts = Array.isArray(opts.contracts) && opts.contracts.length > 0;
|
|
429
|
+
const hasStation = typeof station === "string" && station.length > 0;
|
|
430
|
+
const selectorCount = Number(hasStation) + Number(hasCity) + Number(hasContract) + Number(hasContracts);
|
|
431
|
+
if (selectorCount === 0) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
"research(): exactly one of station, opts.city, opts.contract, opts.contracts must be provided"
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (selectorCount > 1) {
|
|
437
|
+
const names = [];
|
|
438
|
+
if (hasStation) names.push("station");
|
|
439
|
+
if (hasCity) names.push("city");
|
|
440
|
+
if (hasContract) names.push("contract");
|
|
441
|
+
if (hasContracts) names.push("contracts");
|
|
442
|
+
throw new Error(`research(): selectors are mutually exclusive; got ${JSON.stringify(names)}`);
|
|
443
|
+
}
|
|
444
|
+
if (opts.sources !== void 0 && opts.source !== void 0) {
|
|
445
|
+
throw new Error("research(): sources and source are mutually exclusive");
|
|
446
|
+
}
|
|
447
|
+
if (opts.sources !== void 0 || opts.source !== void 0) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
"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."
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (opts.stationOverride !== void 0 && !hasContract) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
"research(): stationOverride requires contract (not standalone station/city/contracts)"
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (opts.includeTrades === true && !(hasContract || hasContracts)) {
|
|
458
|
+
throw new Error(
|
|
459
|
+
"research(): includeTrades requires contract or contracts (station/city selectors have no trade timeseries)"
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (hasCity || hasContract || hasContracts) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
"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."
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const resolved = normalizeStation(station);
|
|
468
|
+
const dates = buildDateList(fromDate, toDate);
|
|
469
|
+
const extendedTo = plusOneDay(toDate);
|
|
470
|
+
const fromYear = Number(fromDate.slice(0, 4));
|
|
471
|
+
const toYear = Number(toDate.slice(0, 4));
|
|
472
|
+
const extendedToYear = Number(extendedTo.slice(0, 4));
|
|
473
|
+
const baseOpts = {};
|
|
474
|
+
if (opts.signal !== void 0) baseOpts.signal = opts.signal;
|
|
475
|
+
const cache = await resolveCache(opts);
|
|
476
|
+
const cacheNow = opts.now ?? /* @__PURE__ */ new Date();
|
|
477
|
+
let mergedClimate = [];
|
|
478
|
+
try {
|
|
479
|
+
const cliRows = await fetchCliWithCache(
|
|
480
|
+
resolved.icao,
|
|
481
|
+
resolved.code,
|
|
482
|
+
fromYear,
|
|
483
|
+
toYear,
|
|
484
|
+
opts,
|
|
485
|
+
cache,
|
|
486
|
+
cacheNow
|
|
487
|
+
);
|
|
488
|
+
mergedClimate = (0, import_merge.mergeClimate)(cliRows);
|
|
489
|
+
} catch (err) {
|
|
490
|
+
if (err instanceof DOMException && (err.name === "AbortError" || err.name === "TimeoutError")) {
|
|
491
|
+
throw err;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const awcHours = opts.awcHours ?? AWC_MAX_HOURS;
|
|
495
|
+
const awcRows = [];
|
|
496
|
+
if (anyDateOverlapsAwc(toDate, awcHours, opts.now ?? /* @__PURE__ */ new Date())) {
|
|
497
|
+
const awcOpts = { hours: awcHours };
|
|
498
|
+
if (opts.signal !== void 0) awcOpts.signal = opts.signal;
|
|
499
|
+
const awcRaw = await (0, import_weather.fetchAwcMetars)([resolved.icao], awcOpts);
|
|
500
|
+
for (const m of awcRaw) {
|
|
501
|
+
const obs = (0, import_weather.awcToObservation)(m);
|
|
502
|
+
if (obs !== null) awcRows.push(obs);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const iemRows = await fetchIemAsosWithCache(
|
|
506
|
+
resolved.code,
|
|
507
|
+
fromYear,
|
|
508
|
+
extendedToYear,
|
|
509
|
+
fromDate,
|
|
510
|
+
extendedTo,
|
|
511
|
+
opts,
|
|
512
|
+
cache,
|
|
513
|
+
cacheNow
|
|
514
|
+
);
|
|
515
|
+
let ghcnhRows = [];
|
|
516
|
+
if (isUsStation(resolved) && resolved.ghcnhId !== null && resolved.ghcnhId.length > 0) {
|
|
517
|
+
ghcnhRows = await fetchGhcnhWithCache(
|
|
518
|
+
resolved.code,
|
|
519
|
+
resolved.ghcnhId,
|
|
520
|
+
fromDate,
|
|
521
|
+
extendedTo,
|
|
522
|
+
opts,
|
|
523
|
+
cache,
|
|
524
|
+
cacheNow
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
const combinedRaw = [...awcRows, ...iemRows, ...ghcnhRows];
|
|
528
|
+
const sorted = sortByObservedAtThenSource(combinedRaw);
|
|
529
|
+
const merged = (0, import_merge.mergeObservations)(sorted);
|
|
530
|
+
const observationsByDate = {};
|
|
531
|
+
const dateLo = dates[0] ?? "";
|
|
532
|
+
const dateHi = dates[dates.length - 1] ?? "";
|
|
533
|
+
for (const obs of merged) {
|
|
534
|
+
const settleDate = observedSettlementDate(obs.observed_at, resolved.code);
|
|
535
|
+
if (settleDate === null) continue;
|
|
536
|
+
if (settleDate < dateLo || settleDate > dateHi) continue;
|
|
537
|
+
let bucket = observationsByDate[settleDate];
|
|
538
|
+
if (bucket === void 0) {
|
|
539
|
+
bucket = [];
|
|
540
|
+
observationsByDate[settleDate] = bucket;
|
|
541
|
+
}
|
|
542
|
+
bucket.push(obs);
|
|
543
|
+
}
|
|
544
|
+
const climateByDate = {};
|
|
545
|
+
for (const cli of mergedClimate) {
|
|
546
|
+
climateByDate[cli.observation_date] = cli;
|
|
547
|
+
}
|
|
548
|
+
return (0, import_pairs.buildPairs)(resolved.code, dates, observationsByDate, climateByDate);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/mode2.ts
|
|
552
|
+
var import_core2 = require("@mostlyrightmd/core");
|
|
553
|
+
var import_weather2 = require("@mostlyrightmd/weather");
|
|
554
|
+
var MODE2_SOURCES = ["iem.archive", "iem.live", "awc.live", "ghcnh.archive"];
|
|
555
|
+
var SOURCE_ALIASES = /* @__PURE__ */ new Map([
|
|
556
|
+
["iem.archive", /* @__PURE__ */ new Set(["iem", "iem.archive"])],
|
|
557
|
+
["iem.live", /* @__PURE__ */ new Set(["iem", "iem.live"])],
|
|
558
|
+
["awc.live", /* @__PURE__ */ new Set(["awc", "awc.live"])],
|
|
559
|
+
["ghcnh.archive", /* @__PURE__ */ new Set(["ghcnh", "ghcnh.archive"])]
|
|
560
|
+
]);
|
|
561
|
+
function isMode2Source(value) {
|
|
562
|
+
return typeof value === "string" && MODE2_SOURCES.includes(value);
|
|
563
|
+
}
|
|
564
|
+
function assertSourceIdentity(rows, expected, role = "observations") {
|
|
565
|
+
const accept = typeof expected === "string" ? /* @__PURE__ */ new Set([expected]) : expected;
|
|
566
|
+
const expectedLabel = typeof expected === "string" ? expected : [...accept].sort().join("|");
|
|
567
|
+
const distinct = /* @__PURE__ */ new Set();
|
|
568
|
+
let bad = 0;
|
|
569
|
+
for (const r of rows) {
|
|
570
|
+
const src = r?.source;
|
|
571
|
+
if (typeof src !== "string") continue;
|
|
572
|
+
if (!accept.has(src)) {
|
|
573
|
+
distinct.add(src);
|
|
574
|
+
bad += 1;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (bad === 0) return;
|
|
578
|
+
const others = [...distinct].sort();
|
|
579
|
+
const first = others[0] ?? "<unknown>";
|
|
580
|
+
throw new import_core2.SourceMismatchError(
|
|
581
|
+
`Mode 2 dispatch requested '${expectedLabel}' but received ${bad} row(s) with other sources: [${others.map((s) => `'${s}'`).join(", ")}]`,
|
|
582
|
+
{
|
|
583
|
+
schemaSource: expectedLabel,
|
|
584
|
+
dataSource: first,
|
|
585
|
+
role,
|
|
586
|
+
catalogWarning: null
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
var AWC_MAX_HOURS2 = 168;
|
|
591
|
+
var DATE_RE2 = /^\d{4}-\d{2}-\d{2}$/;
|
|
592
|
+
function resolveStation(input) {
|
|
593
|
+
const raw = input.trim().toUpperCase();
|
|
594
|
+
if (raw.length === 0) {
|
|
595
|
+
throw new Error("station must be a non-empty string");
|
|
596
|
+
}
|
|
597
|
+
const byIcao = import_core2.STATION_BY_ICAO.get(raw);
|
|
598
|
+
if (byIcao !== void 0) {
|
|
599
|
+
if (byIcao.code === null) {
|
|
600
|
+
throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
code: byIcao.code,
|
|
604
|
+
icao: byIcao.icao,
|
|
605
|
+
country: byIcao.country,
|
|
606
|
+
ghcnhId: byIcao.ghcnh_id
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const byCode = import_core2.STATION_BY_CODE.get(raw);
|
|
610
|
+
if (byCode !== void 0) {
|
|
611
|
+
if (byCode.code === null) {
|
|
612
|
+
throw new Error(`station ${JSON.stringify(raw)} has no 3-letter NWS code`);
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
code: byCode.code,
|
|
616
|
+
icao: byCode.icao,
|
|
617
|
+
country: byCode.country,
|
|
618
|
+
ghcnhId: byCode.ghcnh_id
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
if (raw.startsWith("K") && raw.length === 4) {
|
|
622
|
+
const stripped = raw.slice(1);
|
|
623
|
+
const retry = import_core2.STATION_BY_CODE.get(stripped);
|
|
624
|
+
if (retry !== void 0 && retry.code !== null) {
|
|
625
|
+
return {
|
|
626
|
+
code: retry.code,
|
|
627
|
+
icao: retry.icao,
|
|
628
|
+
country: retry.country,
|
|
629
|
+
ghcnhId: retry.ghcnh_id
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
throw new Error(
|
|
634
|
+
`unknown station ${JSON.stringify(input)} \u2014 not found in STATION_BY_CODE or STATION_BY_ICAO`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
function validateDateFormat(label, value) {
|
|
638
|
+
if (!DATE_RE2.test(value)) {
|
|
639
|
+
throw new Error(`${label} must be YYYY-MM-DD, got ${JSON.stringify(value)}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function yearOf(isoDate) {
|
|
643
|
+
return Number(isoDate.slice(0, 4));
|
|
644
|
+
}
|
|
645
|
+
async function researchBySource(station, source, fromDate, toDate, opts = {}) {
|
|
646
|
+
if (!isMode2Source(source)) {
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Mode 2 source must be one of ${JSON.stringify(
|
|
649
|
+
MODE2_SOURCES
|
|
650
|
+
)}; got ${JSON.stringify(source)}`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
if (source === "iem.live") {
|
|
654
|
+
throw new Error(
|
|
655
|
+
"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."
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
validateDateFormat("fromDate", fromDate);
|
|
659
|
+
validateDateFormat("toDate", toDate);
|
|
660
|
+
if (fromDate > toDate) {
|
|
661
|
+
throw new Error(`fromDate (${fromDate}) must be <= toDate (${toDate})`);
|
|
662
|
+
}
|
|
663
|
+
const resolved = resolveStation(station);
|
|
664
|
+
const accept = SOURCE_ALIASES.get(source);
|
|
665
|
+
if (accept === void 0) {
|
|
666
|
+
throw new Error(`internal: no SOURCE_ALIASES entry for '${source}'`);
|
|
667
|
+
}
|
|
668
|
+
let rows;
|
|
669
|
+
switch (source) {
|
|
670
|
+
case "awc.live": {
|
|
671
|
+
const awcOpts = {
|
|
672
|
+
hours: opts.awcHours ?? AWC_MAX_HOURS2
|
|
673
|
+
};
|
|
674
|
+
if (opts.signal !== void 0) awcOpts.signal = opts.signal;
|
|
675
|
+
const raw = await (0, import_weather2.fetchAwcMetars)([resolved.icao], awcOpts);
|
|
676
|
+
const parsed = [];
|
|
677
|
+
for (const m of raw) {
|
|
678
|
+
const obs = (0, import_weather2.awcToObservation)(m);
|
|
679
|
+
if (obs !== null) parsed.push(obs);
|
|
680
|
+
}
|
|
681
|
+
rows = parsed.filter((r) => {
|
|
682
|
+
const d = r.observed_at.slice(0, 10);
|
|
683
|
+
return d >= fromDate && d <= toDate;
|
|
684
|
+
});
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
case "iem.archive": {
|
|
688
|
+
const fromYear = yearOf(fromDate);
|
|
689
|
+
const toYear = yearOf(toDate);
|
|
690
|
+
const collected = [];
|
|
691
|
+
for (let year = fromYear; year <= toYear; year++) {
|
|
692
|
+
for (const reportType of [3, 4]) {
|
|
693
|
+
const iemOpts = {
|
|
694
|
+
reportType,
|
|
695
|
+
politenessMs: opts.iemPolitenessMs ?? 1e3
|
|
696
|
+
};
|
|
697
|
+
if (opts.signal !== void 0) iemOpts.signal = opts.signal;
|
|
698
|
+
const chunks = await (0, import_weather2.downloadIemAsos)(
|
|
699
|
+
resolved.code,
|
|
700
|
+
`${year}-01-01`,
|
|
701
|
+
`${year}-12-31`,
|
|
702
|
+
iemOpts
|
|
703
|
+
);
|
|
704
|
+
for (const chunk of chunks) {
|
|
705
|
+
const parsed = (0, import_weather2.parseIemCsv)(chunk.csv, {
|
|
706
|
+
observationTypeOverride: reportType === 3 ? "METAR" : "SPECI"
|
|
707
|
+
});
|
|
708
|
+
collected.push(...parsed);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
rows = collected.filter((r) => {
|
|
713
|
+
const d = r.observed_at.slice(0, 10);
|
|
714
|
+
return d >= fromDate && d <= toDate;
|
|
715
|
+
});
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
case "ghcnh.archive": {
|
|
719
|
+
if (resolved.country !== "US" || resolved.ghcnhId === null || resolved.ghcnhId.length === 0) {
|
|
720
|
+
throw new import_core2.NotFoundError(
|
|
721
|
+
`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`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
const fromYear = yearOf(fromDate);
|
|
725
|
+
const toYear = yearOf(toDate);
|
|
726
|
+
const collected = [];
|
|
727
|
+
for (let year = fromYear; year <= toYear; year++) {
|
|
728
|
+
const ghcnhOpts = {};
|
|
729
|
+
if (opts.signal !== void 0) ghcnhOpts.signal = opts.signal;
|
|
730
|
+
try {
|
|
731
|
+
const yr = await (0, import_weather2.downloadGhcnh)(resolved.ghcnhId, year, ghcnhOpts);
|
|
732
|
+
const parsed = (0, import_weather2.parseGhcnhPsv)(yr.psv);
|
|
733
|
+
for (const r of parsed) {
|
|
734
|
+
if (r.station_code === resolved.code) collected.push(r);
|
|
735
|
+
}
|
|
736
|
+
} catch (err) {
|
|
737
|
+
if (err instanceof import_core2.NotFoundError) continue;
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
rows = collected.filter((r) => {
|
|
742
|
+
const d = r.observed_at.slice(0, 10);
|
|
743
|
+
return d >= fromDate && d <= toDate;
|
|
744
|
+
});
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const filtered = rows.filter((r) => accept.has(r.source));
|
|
749
|
+
assertSourceIdentity(filtered, accept, "observations");
|
|
750
|
+
return filtered;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/compose.ts
|
|
754
|
+
var import_markets = require("@mostlyrightmd/markets");
|
|
755
|
+
var import_polymarket = require("@mostlyrightmd/markets/polymarket");
|
|
756
|
+
var SELECTOR_NAMES = ["station", "city", "contract", "contracts"];
|
|
757
|
+
var KALSHI_TICKER_ALIASES = {
|
|
758
|
+
NY: "NYC"
|
|
759
|
+
};
|
|
760
|
+
var CITY_SLUG_ALIASES = {
|
|
761
|
+
// short_kalshi (lower) → [polymarket_long, kalshi_upper]
|
|
762
|
+
nyc: ["nyc", "NYC"],
|
|
763
|
+
chi: ["chicago", "CHI"],
|
|
764
|
+
lax: ["los_angeles", "LAX"],
|
|
765
|
+
mia: ["miami", "MIA"],
|
|
766
|
+
den: ["denver", "DEN"],
|
|
767
|
+
bos: ["boston", "BOS"],
|
|
768
|
+
aus: ["austin", "AUS"],
|
|
769
|
+
dca: ["washington_dc", "DCA"],
|
|
770
|
+
phl: ["philadelphia", "PHL"],
|
|
771
|
+
sfo: ["san_francisco", "SFO"],
|
|
772
|
+
sea: ["seattle", "SEA"],
|
|
773
|
+
atl: ["atlanta", "ATL"],
|
|
774
|
+
hou: ["houston", "HOU"],
|
|
775
|
+
dal: ["dallas", "DAL"],
|
|
776
|
+
phx: ["phoenix", "PHX"],
|
|
777
|
+
msp: ["minneapolis", "MSP"],
|
|
778
|
+
dtw: ["detroit", "DTW"]
|
|
779
|
+
};
|
|
780
|
+
var CITY_SLUG_ALIASES_REVERSE = (() => {
|
|
781
|
+
const out = {};
|
|
782
|
+
for (const [shortLower, [longPoly, kalshiUpper]] of Object.entries(CITY_SLUG_ALIASES)) {
|
|
783
|
+
out[longPoly] = [shortLower, kalshiUpper];
|
|
784
|
+
}
|
|
785
|
+
return out;
|
|
786
|
+
})();
|
|
787
|
+
function normalizeCitySlugs(city) {
|
|
788
|
+
const lower = city.toLowerCase();
|
|
789
|
+
const upper = city.toUpperCase();
|
|
790
|
+
const direct = CITY_SLUG_ALIASES[lower];
|
|
791
|
+
if (direct !== void 0) return direct;
|
|
792
|
+
const reverse = CITY_SLUG_ALIASES_REVERSE[lower];
|
|
793
|
+
if (reverse !== void 0) return [lower, reverse[1]];
|
|
794
|
+
return [lower, upper];
|
|
795
|
+
}
|
|
796
|
+
function validateSelectors(args) {
|
|
797
|
+
const provided = [];
|
|
798
|
+
if (typeof args.station === "string" && args.station.length > 0) provided.push("station");
|
|
799
|
+
if (typeof args.city === "string" && args.city.length > 0) provided.push("city");
|
|
800
|
+
if (typeof args.contract === "string" && args.contract.length > 0) provided.push("contract");
|
|
801
|
+
if (Array.isArray(args.contracts) && args.contracts.length > 0) provided.push("contracts");
|
|
802
|
+
if (provided.length === 0) {
|
|
803
|
+
throw new Error(
|
|
804
|
+
"research(): exactly one of station, city, contract, contracts must be provided"
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
if (provided.length > 1) {
|
|
808
|
+
throw new Error(
|
|
809
|
+
`research(): selectors are mutually exclusive; got ${JSON.stringify(provided)}`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
return provided[0];
|
|
813
|
+
}
|
|
814
|
+
function resolveContract(contractId) {
|
|
815
|
+
if (typeof contractId !== "string" || !contractId.includes(":")) {
|
|
816
|
+
throw new TypeError(`contract id must be \`<issuer>:<id>\`; got ${JSON.stringify(contractId)}`);
|
|
817
|
+
}
|
|
818
|
+
const colonIdx = contractId.indexOf(":");
|
|
819
|
+
const issuer = contractId.slice(0, colonIdx).toLowerCase();
|
|
820
|
+
const raw = contractId.slice(colonIdx + 1);
|
|
821
|
+
const rawUpper = raw.toUpperCase();
|
|
822
|
+
if (issuer === "kalshi") {
|
|
823
|
+
let normalized = rawUpper;
|
|
824
|
+
if (normalized.startsWith("KX")) {
|
|
825
|
+
normalized = `K${normalized.slice(2)}`;
|
|
826
|
+
}
|
|
827
|
+
const cityOnly = normalized.split("-", 1)[0] ?? "";
|
|
828
|
+
let cityTickerRaw = null;
|
|
829
|
+
if (cityOnly.startsWith("KHIGH") && cityOnly.length > 5) {
|
|
830
|
+
cityTickerRaw = cityOnly.slice(5);
|
|
831
|
+
} else if (cityOnly.startsWith("KLOW") && cityOnly.length > 4) {
|
|
832
|
+
cityTickerRaw = cityOnly.slice(4);
|
|
833
|
+
} else {
|
|
834
|
+
throw new Error(
|
|
835
|
+
`unsupported kalshi contract format: ${JSON.stringify(raw)}; expected KHIGH<CITY>* / KXHIGH<CITY>* / KLOW<CITY>* / KXLOW<CITY>* prefix`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
const cityTicker = KALSHI_TICKER_ALIASES[cityTickerRaw] ?? cityTickerRaw;
|
|
839
|
+
const entry = import_markets.KALSHI_SETTLEMENT_STATIONS[cityTicker];
|
|
840
|
+
if (entry === void 0) {
|
|
841
|
+
throw new Error(`unknown Kalshi city ticker: ${JSON.stringify(cityTicker)}`);
|
|
842
|
+
}
|
|
843
|
+
return [entry.station, "kalshi"];
|
|
844
|
+
}
|
|
845
|
+
if (issuer === "polymarket") {
|
|
846
|
+
throw new Error(
|
|
847
|
+
"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."
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
throw new Error(
|
|
851
|
+
`unknown issuer prefix: ${JSON.stringify(issuer)}; expected kalshi or polymarket`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
function resolveCity(city) {
|
|
855
|
+
if (typeof city !== "string" || !city) {
|
|
856
|
+
throw new Error(`city must be a non-empty string; got ${JSON.stringify(city)}`);
|
|
857
|
+
}
|
|
858
|
+
const [polySlug, kalshiSlug] = normalizeCitySlugs(city);
|
|
859
|
+
const out = [];
|
|
860
|
+
const kalshi = import_markets.KALSHI_SETTLEMENT_STATIONS[kalshiSlug];
|
|
861
|
+
if (kalshi !== void 0 && !out.includes(kalshi.station)) {
|
|
862
|
+
out.push(kalshi.station);
|
|
863
|
+
}
|
|
864
|
+
const poly = import_markets.POLYMARKET_CITY_STATIONS[polySlug];
|
|
865
|
+
if (poly !== void 0) {
|
|
866
|
+
for (const measure of ["default", "high", "low"]) {
|
|
867
|
+
const st = poly[measure];
|
|
868
|
+
if (typeof st === "string" && !out.includes(st)) out.push(st);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const wrong = import_polymarket.POLYMARKET_KNOWN_WRONG_STATIONS[polySlug];
|
|
872
|
+
if (wrong !== void 0) {
|
|
873
|
+
const sortedWrong = [...wrong].sort();
|
|
874
|
+
for (const st of sortedWrong) {
|
|
875
|
+
if (!out.includes(st)) out.push(st);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (out.length === 0) {
|
|
879
|
+
throw new Error(`unknown city ${JSON.stringify(city)}; not in kalshi or polymarket catalogs`);
|
|
880
|
+
}
|
|
881
|
+
return out;
|
|
882
|
+
}
|
|
883
|
+
function annotateSettlesFor(station, city) {
|
|
884
|
+
if (city === null) return [];
|
|
885
|
+
const [polySlug, kalshiSlug] = normalizeCitySlugs(city);
|
|
886
|
+
const out = [];
|
|
887
|
+
const kalshi = import_markets.KALSHI_SETTLEMENT_STATIONS[kalshiSlug];
|
|
888
|
+
if (kalshi !== void 0 && kalshi.station === station) {
|
|
889
|
+
out.push(`kalshi:${kalshiSlug}`);
|
|
890
|
+
}
|
|
891
|
+
const poly = import_markets.POLYMARKET_CITY_STATIONS[polySlug];
|
|
892
|
+
if (poly !== void 0) {
|
|
893
|
+
for (const measure of ["default", "high", "low"]) {
|
|
894
|
+
if (poly[measure] === station) {
|
|
895
|
+
out.push(`polymarket:${polySlug}`);
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return out.sort();
|
|
901
|
+
}
|
|
902
|
+
function buildOverrideWarning(contractStation, overrideStation) {
|
|
903
|
+
return {
|
|
904
|
+
kind: "StationOverrideWarning",
|
|
905
|
+
contractStation,
|
|
906
|
+
overrideStation,
|
|
907
|
+
message: `stationOverride=${JSON.stringify(overrideStation)} differs from contract's canonical settlement station ${JSON.stringify(contractStation)}; output row will carry settlementMismatch=true`
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/discover.ts
|
|
912
|
+
function discover(args) {
|
|
913
|
+
if (typeof args !== "object" || args === null) {
|
|
914
|
+
throw new TypeError(`discover(): args must be an object; got ${typeof args}`);
|
|
915
|
+
}
|
|
916
|
+
const stations = resolveCity(args.city);
|
|
917
|
+
const rows = stations.map((station) => ({
|
|
918
|
+
city: args.city,
|
|
919
|
+
station,
|
|
920
|
+
settlesFor: annotateSettlesFor(station, args.city)
|
|
921
|
+
}));
|
|
922
|
+
return Object.freeze({
|
|
923
|
+
rows: Object.freeze(rows),
|
|
924
|
+
city: args.city,
|
|
925
|
+
source: "discover"
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/index.ts
|
|
930
|
+
var import_weather4 = require("@mostlyrightmd/weather");
|
|
931
|
+
var import_core4 = require("@mostlyrightmd/core");
|
|
932
|
+
var version = "0.0.0";
|
|
933
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
934
|
+
0 && (module.exports = {
|
|
935
|
+
LiveStreamError,
|
|
936
|
+
MODE2_SOURCES,
|
|
937
|
+
NoLiveDataError,
|
|
938
|
+
POLITE_FLOORS_S,
|
|
939
|
+
SELECTOR_NAMES,
|
|
940
|
+
SOURCE_ALIASES,
|
|
941
|
+
SOURCE_IDENTITY_TAGS,
|
|
942
|
+
SUPPORTED_SOURCES,
|
|
943
|
+
annotateSettlesFor,
|
|
944
|
+
assertSourceIdentity,
|
|
945
|
+
buildOverrideWarning,
|
|
946
|
+
core,
|
|
947
|
+
discover,
|
|
948
|
+
helloCore,
|
|
949
|
+
helloMarkets,
|
|
950
|
+
helloWeather,
|
|
951
|
+
isLiveSource,
|
|
952
|
+
isMode2Source,
|
|
953
|
+
latest,
|
|
954
|
+
markets,
|
|
955
|
+
research,
|
|
956
|
+
researchBySource,
|
|
957
|
+
resolveCity,
|
|
958
|
+
resolveContract,
|
|
959
|
+
sourceTag,
|
|
960
|
+
stream,
|
|
961
|
+
validatePollSeconds,
|
|
962
|
+
validateSelectors,
|
|
963
|
+
validateSource,
|
|
964
|
+
version,
|
|
965
|
+
weather
|
|
966
|
+
});
|
|
967
|
+
//# sourceMappingURL=index.cjs.map
|