mostlyright 1.1.3 → 1.4.0

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.
@@ -4,12 +4,16 @@ import {
4
4
  buildIemUrl,
5
5
  downloadIemAsos,
6
6
  yearlyChunksExclusiveEnd
7
- } from "./chunk-465FJZTS.mjs";
7
+ } from "./chunk-TT6J2BKH.mjs";
8
8
  import {
9
9
  CLIMATE_REPORT_TYPE_PRIORITY,
10
+ DataAvailabilityError,
10
11
  LiveStreamError,
11
12
  NoLiveDataError,
12
13
  NotFoundError,
14
+ NwpNotAvailableError,
15
+ OpenMeteoSeamlessLeakageError,
16
+ STATIONS,
13
17
  STATION_BY_CODE,
14
18
  STATION_BY_ICAO,
15
19
  SourceMismatchError,
@@ -18,7 +22,7 @@ import {
18
22
  helloCore,
19
23
  settlementDateFor,
20
24
  src_exports
21
- } from "./chunk-IN2UOZYO.mjs";
25
+ } from "./chunk-7XWX6QZG.mjs";
22
26
  import {
23
27
  awcToObservation,
24
28
  celsiusToFahrenheit,
@@ -66,12 +70,18 @@ __export(src_exports2, {
66
70
  LiveStreamError: () => LiveStreamError,
67
71
  NCEI_POLITE_DELAY_MS: () => NCEI_POLITE_DELAY_MS,
68
72
  NoLiveDataError: () => NoLiveDataError,
73
+ OPEN_METEO_LIVE_URL: () => OPEN_METEO_LIVE_URL,
74
+ OPEN_METEO_MODELS: () => OPEN_METEO_MODELS,
75
+ OPEN_METEO_PREVIOUS_RUNS_URL: () => OPEN_METEO_PREVIOUS_RUNS_URL,
76
+ OPEN_METEO_SEAMLESS_URL: () => OPEN_METEO_SEAMLESS_URL,
77
+ OPEN_METEO_SINGLE_RUNS_URL: () => OPEN_METEO_SINGLE_RUNS_URL,
69
78
  POLITE_FLOORS_S: () => POLITE_FLOORS_S,
70
79
  SOURCE_IDENTITY_TAGS: () => SOURCE_IDENTITY_TAGS,
71
80
  SSID_COLUMNS: () => SSID_COLUMNS,
72
81
  SUPPORTED_SOURCES: () => SUPPORTED_SOURCES,
73
82
  awcToObservation: () => awcToObservation,
74
83
  buildIemUrl: () => buildIemUrl,
84
+ dailyExtremes: () => dailyExtremes,
75
85
  downloadCli: () => downloadCli,
76
86
  downloadCliRange: () => downloadCliRange,
77
87
  downloadGhcnh: () => downloadGhcnh,
@@ -90,12 +100,15 @@ __export(src_exports2, {
90
100
  latest: () => latest,
91
101
  mapCloudCover: () => mapCloudCover,
92
102
  mergeClimate: () => mergeClimate,
103
+ obs: () => obs,
104
+ openMeteoForecasts: () => openMeteoForecasts,
93
105
  parseAwcVisibility: () => parseAwcVisibility,
94
106
  parseCliRecord: () => parseCliRecord,
95
107
  parseCliResponse: () => parseCliResponse,
96
108
  parseGhcnhPsv: () => parseGhcnhPsv,
97
109
  parseGhcnhRow: () => parseGhcnhRow,
98
110
  parseIemCsv: () => parseIemCsv,
111
+ resolveAutoStrategy: () => resolveAutoStrategy,
99
112
  sourceTag: () => sourceTag,
100
113
  stream: () => stream,
101
114
  validatePollSeconds: () => validatePollSeconds,
@@ -289,10 +302,10 @@ function inferReportType(product, observationDate) {
289
302
  if (!product) return "preliminary";
290
303
  const issued = parseProductTimestamp(product);
291
304
  if (issued === null) return "preliminary";
292
- const obs = parseObservationDate(observationDate);
293
- if (obs === null) return "preliminary";
305
+ const obs2 = parseObservationDate(observationDate);
306
+ if (obs2 === null) return "preliminary";
294
307
  const issuedDayUtc = Date.UTC(issued.getUTCFullYear(), issued.getUTCMonth(), issued.getUTCDate());
295
- const obsDayUtc = obs.getTime();
308
+ const obsDayUtc = obs2.getTime();
296
309
  const deltaDays = Math.round((issuedDayUtc - obsDayUtc) / 864e5);
297
310
  if (deltaDays <= 0) return "preliminary";
298
311
  if (deltaDays === 1) {
@@ -655,8 +668,8 @@ function parseGhcnhPsv(psvBody) {
655
668
  const key = header[c];
656
669
  row[key] = c < cells.length ? cells[c] : "";
657
670
  }
658
- const obs = parseGhcnhRow(row);
659
- if (obs !== null) out.push(obs);
671
+ const obs2 = parseGhcnhRow(row);
672
+ if (obs2 !== null) out.push(obs2);
660
673
  }
661
674
  return out;
662
675
  }
@@ -719,8 +732,8 @@ function normalizeStation(station) {
719
732
  if (s.length === 3) return `K${s}`;
720
733
  return s;
721
734
  }
722
- function asLiveObservation(obs, tag) {
723
- return { ...obs, source: tag };
735
+ function asLiveObservation(obs2, tag) {
736
+ return { ...obs2, source: tag };
724
737
  }
725
738
  async function fetchAwcLatest(station) {
726
739
  const icao = normalizeStation(station);
@@ -728,9 +741,9 @@ async function fetchAwcLatest(station) {
728
741
  const tag = sourceTag("awc");
729
742
  const rows = [];
730
743
  for (const m of raw) {
731
- const obs = awcToObservation(m);
732
- if (obs !== null) {
733
- rows.push(asLiveObservation(obs, tag));
744
+ const obs2 = awcToObservation(m);
745
+ if (obs2 !== null) {
746
+ rows.push(asLiveObservation(obs2, tag));
734
747
  }
735
748
  }
736
749
  return rows;
@@ -762,9 +775,9 @@ function previousDayIso(iso) {
762
775
  }
763
776
  async function fetchIemLatest(station) {
764
777
  const [{ fetchWithRetry: fetchWithRetry2 }, { STATION_CODE_RE: STATION_CODE_RE3 }, { buildIemUrl: buildIemUrl2 }, { parseIemCsv: parseIemCsv2 }] = await Promise.all([
765
- import("./src-NP2MZ322.mjs"),
778
+ import("./src-JXVFCGSQ.mjs"),
766
779
  import("./bounds-KSTXL77E.mjs"),
767
- import("./iem-asos-ZPUMH3KM.mjs"),
780
+ import("./iem-asos-OLJNQPEM.mjs"),
768
781
  import("./iem-IO2HIL5V.mjs")
769
782
  ]);
770
783
  const icao = normalizeStation(station);
@@ -786,8 +799,8 @@ async function fetchIemLatest(station) {
786
799
  const response = await fetchWithRetry2(url);
787
800
  const csv = await response.text();
788
801
  const override = reportType === 3 ? "METAR" : "SPECI";
789
- const obs = parseIemCsv2(csv, { observationTypeOverride: override });
790
- for (const row of obs) {
802
+ const obs2 = parseIemCsv2(csv, { observationTypeOverride: override });
803
+ for (const row of obs2) {
791
804
  rows.push(asLiveObservation(row, tag));
792
805
  }
793
806
  }
@@ -1000,10 +1013,2006 @@ async function iemMosForecasts(station, fromDate, toDate, opts = {}) {
1000
1013
  }
1001
1014
 
1002
1015
  // ../weather/src/forecasts/nwp-stub.ts
1003
- async function forecastNwp(_station, _model, _opts = {}) {
1016
+ var IEM_MOS_COVERED_STATIONS = /* @__PURE__ */ new Set([
1017
+ "KNYC",
1018
+ "KLAX",
1019
+ "KORD",
1020
+ "KMIA",
1021
+ "KDEN",
1022
+ "KSEA",
1023
+ "KATL"
1024
+ ]);
1025
+ function buildHint(station, model) {
1026
+ const hasMosCoverage = IEM_MOS_COVERED_STATIONS.has(station.toUpperCase());
1027
+ const mosLine = hasMosCoverage ? `Workaround for ${station}: iemMosForecasts("${station}", ...) is available (IEM MOS catalog covers this station).` : `Workaround: this station has no IEM MOS coverage; use the Python SDK's mostlyright.forecast_nwp() in v1.x.`;
1028
+ return `forecastNwp(${station}, "${model}") is a v1.x stub. Browser GRIB2 decode is not production-ready in May 2026 (no eccodes / cfgrib equivalent for the browser; WASM compile time + bundle size make it impractical for v1.x). ${mosLine} See https://mostlyright.md/docs/sdk/typescript/nwp-forecasts/ for the architectural reason + v2.0+ tracking.`;
1029
+ }
1030
+ async function forecastNwp(station, model, _opts = {}) {
1031
+ throw new NwpNotAvailableError({
1032
+ station,
1033
+ model,
1034
+ hint: buildHint(station, model)
1035
+ });
1036
+ }
1037
+
1038
+ // ../weather/src/forecasts/open-meteo-models.ts
1039
+ var OPEN_METEO_MODELS = /* @__PURE__ */ new Set([
1040
+ // NCEP (8)
1041
+ "gfs_seamless",
1042
+ "gfs_global",
1043
+ "gfs_graphcast025",
1044
+ "aigfs025",
1045
+ "hgefs025",
1046
+ "ncep_hrrr_conus",
1047
+ "ncep_nbm_conus",
1048
+ "ncep_nam_conus",
1049
+ // ECMWF (3)
1050
+ "ecmwf_ifs025",
1051
+ "ecmwf_ifs_hres",
1052
+ "ecmwf_aifs025_single",
1053
+ // DWD (5)
1054
+ "dwd_icon_seamless",
1055
+ "dwd_icon_global",
1056
+ "dwd_icon_eu",
1057
+ "dwd_icon_d2",
1058
+ "dwd_icon_d2_15min",
1059
+ // Météo-France (6)
1060
+ "meteofrance_seamless",
1061
+ "meteofrance_arpege_world025",
1062
+ "meteofrance_arpege_europe",
1063
+ "meteofrance_arome_france0025",
1064
+ "meteofrance_arome_france_hd",
1065
+ "meteofrance_arome_france_hd_15min",
1066
+ // Asia + Oceania (8)
1067
+ "jma_seamless",
1068
+ "jma_gsm",
1069
+ "jma_msm",
1070
+ "kma_seamless",
1071
+ "kma_gdps",
1072
+ "kma_ldps",
1073
+ "cma_grapes_global",
1074
+ "bom_access_global",
1075
+ // Europe (3)
1076
+ "ukmo_global_deterministic_10km",
1077
+ "ukmo_uk_deterministic_2km",
1078
+ "metno_nordic_pp",
1079
+ // GEM Canada (3)
1080
+ "cmc_gem_gdps",
1081
+ "cmc_gem_rdps",
1082
+ "cmc_gem_hrdps"
1083
+ ]);
1084
+ var SIX_HOURLY = [0, 6, 12, 18];
1085
+ var THREE_HOURLY = [0, 3, 6, 9, 12, 15, 18, 21];
1086
+ var HOURLY = Array.from({ length: 24 }, (_, i) => i);
1087
+ var TWELVE_HOURLY = [0, 12];
1088
+ var CYCLE_HOURS = /* @__PURE__ */ new Map([
1089
+ // NCEP
1090
+ ["gfs_seamless", SIX_HOURLY],
1091
+ ["gfs_global", SIX_HOURLY],
1092
+ ["gfs_graphcast025", SIX_HOURLY],
1093
+ ["aigfs025", SIX_HOURLY],
1094
+ ["hgefs025", SIX_HOURLY],
1095
+ ["ncep_hrrr_conus", HOURLY],
1096
+ ["ncep_nbm_conus", HOURLY],
1097
+ ["ncep_nam_conus", SIX_HOURLY],
1098
+ // ECMWF
1099
+ ["ecmwf_ifs025", SIX_HOURLY],
1100
+ ["ecmwf_ifs_hres", SIX_HOURLY],
1101
+ ["ecmwf_aifs025_single", SIX_HOURLY],
1102
+ // DWD
1103
+ ["dwd_icon_seamless", SIX_HOURLY],
1104
+ ["dwd_icon_global", SIX_HOURLY],
1105
+ ["dwd_icon_eu", SIX_HOURLY],
1106
+ ["dwd_icon_d2", THREE_HOURLY],
1107
+ ["dwd_icon_d2_15min", THREE_HOURLY],
1108
+ // Météo-France
1109
+ ["meteofrance_seamless", SIX_HOURLY],
1110
+ ["meteofrance_arpege_world025", SIX_HOURLY],
1111
+ ["meteofrance_arpege_europe", SIX_HOURLY],
1112
+ ["meteofrance_arome_france0025", THREE_HOURLY],
1113
+ ["meteofrance_arome_france_hd", THREE_HOURLY],
1114
+ ["meteofrance_arome_france_hd_15min", THREE_HOURLY],
1115
+ // Asia + Oceania
1116
+ ["jma_seamless", SIX_HOURLY],
1117
+ ["jma_gsm", SIX_HOURLY],
1118
+ ["jma_msm", THREE_HOURLY],
1119
+ ["kma_seamless", SIX_HOURLY],
1120
+ ["kma_gdps", SIX_HOURLY],
1121
+ ["kma_ldps", THREE_HOURLY],
1122
+ ["cma_grapes_global", SIX_HOURLY],
1123
+ ["bom_access_global", SIX_HOURLY],
1124
+ // Europe
1125
+ ["ukmo_global_deterministic_10km", SIX_HOURLY],
1126
+ ["ukmo_uk_deterministic_2km", THREE_HOURLY],
1127
+ ["metno_nordic_pp", HOURLY],
1128
+ // GEM Canada
1129
+ ["cmc_gem_gdps", TWELVE_HOURLY],
1130
+ ["cmc_gem_rdps", SIX_HOURLY],
1131
+ ["cmc_gem_hrdps", SIX_HOURLY]
1132
+ ]);
1133
+ var PUBLISH_LAG_HOURS = /* @__PURE__ */ new Map([
1134
+ // NCEP — global 6h, regional/mesoscale 2h
1135
+ ["gfs_seamless", 6],
1136
+ ["gfs_global", 6],
1137
+ ["gfs_graphcast025", 6],
1138
+ ["aigfs025", 6],
1139
+ ["hgefs025", 6],
1140
+ ["ncep_hrrr_conus", 2],
1141
+ ["ncep_nbm_conus", 2],
1142
+ ["ncep_nam_conus", 2],
1143
+ // ECMWF — global 6h
1144
+ ["ecmwf_ifs025", 6],
1145
+ ["ecmwf_ifs_hres", 6],
1146
+ ["ecmwf_aifs025_single", 6],
1147
+ // DWD
1148
+ ["dwd_icon_seamless", 4],
1149
+ ["dwd_icon_global", 4],
1150
+ ["dwd_icon_eu", 4],
1151
+ ["dwd_icon_d2", 2],
1152
+ ["dwd_icon_d2_15min", 2],
1153
+ // Météo-France
1154
+ ["meteofrance_seamless", 4],
1155
+ ["meteofrance_arpege_world025", 6],
1156
+ ["meteofrance_arpege_europe", 4],
1157
+ ["meteofrance_arome_france0025", 2],
1158
+ ["meteofrance_arome_france_hd", 2],
1159
+ ["meteofrance_arome_france_hd_15min", 2],
1160
+ // Asia + Oceania
1161
+ ["jma_seamless", 6],
1162
+ ["jma_gsm", 6],
1163
+ ["jma_msm", 2],
1164
+ ["kma_seamless", 6],
1165
+ ["kma_gdps", 6],
1166
+ ["kma_ldps", 2],
1167
+ ["cma_grapes_global", 6],
1168
+ ["bom_access_global", 6],
1169
+ // Europe
1170
+ ["ukmo_global_deterministic_10km", 6],
1171
+ ["ukmo_uk_deterministic_2km", 2],
1172
+ ["metno_nordic_pp", 2],
1173
+ // GEM Canada
1174
+ ["cmc_gem_gdps", 6],
1175
+ ["cmc_gem_rdps", 4],
1176
+ ["cmc_gem_hrdps", 2]
1177
+ ]);
1178
+ function floorToCycleMs(valueUtcMs, cycleHours) {
1179
+ if (cycleHours.length === 0) {
1180
+ throw new Error("cycleHours must be non-empty");
1181
+ }
1182
+ const d = new Date(valueUtcMs);
1183
+ const hour = d.getUTCHours();
1184
+ const sorted = [...cycleHours].sort((a, b) => a - b);
1185
+ const candidates = sorted.filter((h) => h <= hour);
1186
+ if (candidates.length > 0) {
1187
+ const targetHour = candidates[candidates.length - 1];
1188
+ return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), targetHour, 0, 0, 0);
1189
+ }
1190
+ const lastCycle = sorted[sorted.length - 1];
1191
+ const prior = new Date(valueUtcMs - 864e5);
1192
+ return Date.UTC(
1193
+ prior.getUTCFullYear(),
1194
+ prior.getUTCMonth(),
1195
+ prior.getUTCDate(),
1196
+ lastCycle,
1197
+ 0,
1198
+ 0,
1199
+ 0
1200
+ );
1201
+ }
1202
+ function issuedAtFromPreviousDayMs(validAtUtcMs, N, cycleHours) {
1203
+ if (N < 1 || N > 7) {
1204
+ throw new Error(`N must be in 1..7 (Open-Meteo previous_dayN limit); got ${N}`);
1205
+ }
1206
+ return floorToCycleMs(validAtUtcMs - N * 864e5, cycleHours);
1207
+ }
1208
+ function issuedAtFromLiveCycleMathMs(nowUtcMs, publishLagHours, cycleHours) {
1209
+ return floorToCycleMs(nowUtcMs - publishLagHours * 36e5, cycleHours);
1210
+ }
1211
+
1212
+ // ../weather/src/forecasts/open-meteo.ts
1213
+ var OPEN_METEO_PREVIOUS_RUNS_URL = "https://previous-runs-api.open-meteo.com/v1/forecast";
1214
+ var OPEN_METEO_SINGLE_RUNS_URL = "https://single-runs-api.open-meteo.com/v1/forecast";
1215
+ var OPEN_METEO_LIVE_URL = "https://api.open-meteo.com/v1/forecast";
1216
+ var OPEN_METEO_SEAMLESS_URL = "https://historical-forecast-api.open-meteo.com/v1/forecast";
1217
+ var VALID_MODES = /* @__PURE__ */ new Set(["training", "live", "seamless"]);
1218
+ var HOURLY_VARIABLES = [
1219
+ "temperature_2m",
1220
+ "dew_point_2m",
1221
+ "apparent_temperature",
1222
+ "wind_speed_10m",
1223
+ "wind_direction_10m",
1224
+ "wind_gusts_10m",
1225
+ "precipitation",
1226
+ "precipitation_probability",
1227
+ "cloud_cover",
1228
+ "surface_pressure",
1229
+ "pressure_msl",
1230
+ "shortwave_radiation",
1231
+ "direct_radiation",
1232
+ "cape",
1233
+ "freezing_level_height",
1234
+ "snow_depth",
1235
+ "visibility",
1236
+ "weather_code"
1237
+ ];
1238
+ var STATION_COORDS = /* @__PURE__ */ new Map([
1239
+ ["KNYC", { lat: 40.78, lon: -73.97 }],
1240
+ ["KORD", { lat: 41.98, lon: -87.9 }],
1241
+ ["KDEN", { lat: 39.86, lon: -104.67 }],
1242
+ ["KMIA", { lat: 25.79, lon: -80.29 }],
1243
+ ["KSEA", { lat: 47.45, lon: -122.31 }]
1244
+ ]);
1245
+ function resolveCoords(station) {
1246
+ const coords = STATION_COORDS.get(station);
1247
+ if (coords) return coords;
1004
1248
  throw new Error(
1005
- "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."
1249
+ `openMeteoForecasts: station=${JSON.stringify(station)} not in built-in coords table; add to STATION_COORDS`
1250
+ );
1251
+ }
1252
+ function buildHourlyParam(endpoint) {
1253
+ if (endpoint === OPEN_METEO_PREVIOUS_RUNS_URL) {
1254
+ return HOURLY_VARIABLES.map((v) => `${v}_previous_day1`).join(",");
1255
+ }
1256
+ return HOURLY_VARIABLES.join(",");
1257
+ }
1258
+ function dispatchEndpoint(mode, opts) {
1259
+ if (mode === "training") {
1260
+ return opts.issuedAt ? OPEN_METEO_SINGLE_RUNS_URL : OPEN_METEO_PREVIOUS_RUNS_URL;
1261
+ }
1262
+ if (mode === "live") {
1263
+ return OPEN_METEO_LIVE_URL;
1264
+ }
1265
+ if (mode === "seamless") {
1266
+ if (!opts.allowLeakage) {
1267
+ throw new OpenMeteoSeamlessLeakageError(
1268
+ "Open-Meteo seamless endpoint is banned for training data (see Tarabcak/mostlyright#70). Pass allowLeakage: true to opt in; LeakageDetector will still reject these rows when asOf is asserted.",
1269
+ {
1270
+ model: opts.model,
1271
+ endpointUrl: OPEN_METEO_SEAMLESS_URL
1272
+ }
1273
+ );
1274
+ }
1275
+ return OPEN_METEO_SEAMLESS_URL;
1276
+ }
1277
+ throw new Error(`openMeteoForecasts: unknown mode ${JSON.stringify(mode)}`);
1278
+ }
1279
+ function isoIfNotNull(ms) {
1280
+ return ms === null ? null : new Date(ms).toISOString();
1281
+ }
1282
+ function maybeNumber2(value) {
1283
+ if (value === null || value === void 0) return null;
1284
+ const n = typeof value === "number" ? value : Number(value);
1285
+ return Number.isFinite(n) ? n : null;
1286
+ }
1287
+ function pickHourlyValue(hourly, key, isPreviousRuns, idx) {
1288
+ const arr = isPreviousRuns ? hourly[`${key}_previous_day1`] ?? hourly[key] : hourly[key];
1289
+ if (!Array.isArray(arr) || idx >= arr.length) return null;
1290
+ return arr[idx];
1291
+ }
1292
+ async function openMeteoForecasts(station, fromDate, toDate, opts = {}) {
1293
+ const model = opts.model ?? "gfs_global";
1294
+ if (!OPEN_METEO_MODELS.has(model)) {
1295
+ throw new Error(
1296
+ `openMeteoForecasts: model must be one of OPEN_METEO_MODELS (36 keys); got ${JSON.stringify(model)}`
1297
+ );
1298
+ }
1299
+ const mode = opts.mode ?? "training";
1300
+ if (!VALID_MODES.has(mode)) {
1301
+ throw new Error(
1302
+ `openMeteoForecasts: mode must be one of training,live,seamless; got ${JSON.stringify(mode)}`
1303
+ );
1304
+ }
1305
+ const endpoint = dispatchEndpoint(mode, {
1306
+ allowLeakage: opts.allowLeakage ?? false,
1307
+ model,
1308
+ issuedAt: opts.issuedAt
1309
+ });
1310
+ const { lat, lon } = resolveCoords(station);
1311
+ const params = new URLSearchParams();
1312
+ params.set("latitude", String(lat));
1313
+ params.set("longitude", String(lon));
1314
+ params.set("start_date", fromDate);
1315
+ params.set("end_date", toDate);
1316
+ params.set("hourly", buildHourlyParam(endpoint));
1317
+ params.set("models", model);
1318
+ params.set("timezone", "UTC");
1319
+ if (endpoint === OPEN_METEO_SINGLE_RUNS_URL && opts.issuedAt) {
1320
+ params.set("run", opts.issuedAt);
1321
+ }
1322
+ const fetchFn = opts.fetchFn ?? fetch;
1323
+ const url = `${endpoint}?${params.toString()}`;
1324
+ const resp = await fetchFn(url);
1325
+ if (resp.status === 404) return [];
1326
+ if (!resp.ok) {
1327
+ throw new Error(`openMeteoForecasts: HTTP ${resp.status} on ${url}`);
1328
+ }
1329
+ const payload = await resp.json();
1330
+ const hourly = payload.hourly ?? {};
1331
+ const times = hourly.time ?? [];
1332
+ if (times.length === 0) return [];
1333
+ let source;
1334
+ if (endpoint === OPEN_METEO_PREVIOUS_RUNS_URL) {
1335
+ source = "open_meteo.previous_runs";
1336
+ } else if (endpoint === OPEN_METEO_SINGLE_RUNS_URL) {
1337
+ source = "open_meteo.single_run";
1338
+ } else if (endpoint === OPEN_METEO_LIVE_URL) {
1339
+ source = "open_meteo.live";
1340
+ } else {
1341
+ source = "open_meteo.seamless";
1342
+ }
1343
+ const cycleHours = CYCLE_HOURS.get(model) ?? [0, 6, 12, 18];
1344
+ const publishLag = PUBLISH_LAG_HOURS.get(model) ?? 6;
1345
+ const retrievedAt = (/* @__PURE__ */ new Date()).toISOString();
1346
+ const nowMs = Date.now();
1347
+ const rows = [];
1348
+ for (let i = 0; i < times.length; i++) {
1349
+ const tIso = times[i] ?? "";
1350
+ if (!tIso) continue;
1351
+ const validAtMs = Date.parse(tIso.endsWith("Z") || tIso.includes("+") ? tIso : `${tIso}Z`);
1352
+ let issuedAtMs;
1353
+ if (source === "open_meteo.previous_runs") {
1354
+ issuedAtMs = issuedAtFromPreviousDayMs(validAtMs, 1, cycleHours);
1355
+ } else if (source === "open_meteo.single_run") {
1356
+ issuedAtMs = opts.issuedAt ? Date.parse(
1357
+ opts.issuedAt.endsWith("Z") || opts.issuedAt.includes("+") ? opts.issuedAt : `${opts.issuedAt}Z`
1358
+ ) : null;
1359
+ } else if (source === "open_meteo.live") {
1360
+ issuedAtMs = issuedAtFromLiveCycleMathMs(nowMs, publishLag, cycleHours);
1361
+ } else {
1362
+ issuedAtMs = null;
1363
+ }
1364
+ const validAtIso = new Date(validAtMs).toISOString();
1365
+ const forecastHour = issuedAtMs === null ? null : Math.round((validAtMs - issuedAtMs) / 36e5);
1366
+ const isPrev = source === "open_meteo.previous_runs";
1367
+ const h = hourly;
1368
+ const popPct = maybeNumber2(pickHourlyValue(h, "precipitation_probability", isPrev, i));
1369
+ rows.push({
1370
+ station,
1371
+ model,
1372
+ issuedAt: isoIfNotNull(issuedAtMs),
1373
+ validAt: validAtIso,
1374
+ forecastHour,
1375
+ tempC: maybeNumber2(pickHourlyValue(h, "temperature_2m", isPrev, i)),
1376
+ dewPointC: maybeNumber2(pickHourlyValue(h, "dew_point_2m", isPrev, i)),
1377
+ apparentTempC: maybeNumber2(pickHourlyValue(h, "apparent_temperature", isPrev, i)),
1378
+ windSpeedMs: maybeNumber2(pickHourlyValue(h, "wind_speed_10m", isPrev, i)),
1379
+ windDirDeg: maybeNumber2(pickHourlyValue(h, "wind_direction_10m", isPrev, i)),
1380
+ windGustsMs: maybeNumber2(pickHourlyValue(h, "wind_gusts_10m", isPrev, i)),
1381
+ precipProbability: popPct === null ? null : popPct / 100,
1382
+ precipitationMm: maybeNumber2(pickHourlyValue(h, "precipitation", isPrev, i)),
1383
+ cloudCoverPct: maybeNumber2(pickHourlyValue(h, "cloud_cover", isPrev, i)),
1384
+ surfacePressureHpa: maybeNumber2(pickHourlyValue(h, "surface_pressure", isPrev, i)),
1385
+ pressureMslHpa: maybeNumber2(pickHourlyValue(h, "pressure_msl", isPrev, i)),
1386
+ shortwaveRadiationWm2: maybeNumber2(pickHourlyValue(h, "shortwave_radiation", isPrev, i)),
1387
+ directRadiationWm2: maybeNumber2(pickHourlyValue(h, "direct_radiation", isPrev, i)),
1388
+ capeJkg: maybeNumber2(pickHourlyValue(h, "cape", isPrev, i)),
1389
+ freezingLevelM: maybeNumber2(pickHourlyValue(h, "freezing_level_height", isPrev, i)),
1390
+ snowDepthM: maybeNumber2(pickHourlyValue(h, "snow_depth", isPrev, i)),
1391
+ visibilityM: maybeNumber2(pickHourlyValue(h, "visibility", isPrev, i)),
1392
+ weatherCode: maybeNumber2(pickHourlyValue(h, "weather_code", isPrev, i)),
1393
+ source,
1394
+ retrievedAt
1395
+ });
1396
+ }
1397
+ return rows;
1398
+ }
1399
+
1400
+ // ../core/dist/discovery/index.mjs
1401
+ var STATIONS2 = [
1402
+ {
1403
+ code: "EDDB",
1404
+ country: "DE",
1405
+ ghcnh_id: null,
1406
+ icao: "EDDB",
1407
+ latitude: 52.3667,
1408
+ longitude: 13.5033,
1409
+ name: "Berlin Brandenburg",
1410
+ tz: "Europe/Berlin",
1411
+ venues: [
1412
+ "polymarket"
1413
+ ]
1414
+ },
1415
+ {
1416
+ code: "EDDF",
1417
+ country: "DE",
1418
+ ghcnh_id: null,
1419
+ icao: "EDDF",
1420
+ latitude: 50.0379,
1421
+ longitude: 8.5622,
1422
+ name: "Frankfurt am Main",
1423
+ tz: "Europe/Berlin",
1424
+ venues: [
1425
+ "polymarket"
1426
+ ]
1427
+ },
1428
+ {
1429
+ code: "EDDM",
1430
+ country: "DE",
1431
+ ghcnh_id: null,
1432
+ icao: "EDDM",
1433
+ latitude: 48.3538,
1434
+ longitude: 11.7861,
1435
+ name: "Munich Franz Josef Strauss",
1436
+ tz: "Europe/Berlin",
1437
+ venues: [
1438
+ "polymarket"
1439
+ ]
1440
+ },
1441
+ {
1442
+ code: "EFHK",
1443
+ country: "FI",
1444
+ ghcnh_id: null,
1445
+ icao: "EFHK",
1446
+ latitude: 60.3172,
1447
+ longitude: 24.9633,
1448
+ name: "Helsinki-Vantaa",
1449
+ tz: "Europe/Helsinki",
1450
+ venues: [
1451
+ "polymarket"
1452
+ ]
1453
+ },
1454
+ {
1455
+ code: "EGKK",
1456
+ country: "GB",
1457
+ ghcnh_id: null,
1458
+ icao: "EGKK",
1459
+ latitude: 51.1481,
1460
+ longitude: -0.1903,
1461
+ name: "London Gatwick",
1462
+ tz: "Europe/London",
1463
+ venues: [
1464
+ "polymarket"
1465
+ ]
1466
+ },
1467
+ {
1468
+ code: "EGLL",
1469
+ country: "GB",
1470
+ ghcnh_id: null,
1471
+ icao: "EGLL",
1472
+ latitude: 51.4706,
1473
+ longitude: -0.4619,
1474
+ name: "London Heathrow",
1475
+ tz: "Europe/London",
1476
+ venues: [
1477
+ "polymarket"
1478
+ ]
1479
+ },
1480
+ {
1481
+ code: "EHAM",
1482
+ country: "NL",
1483
+ ghcnh_id: null,
1484
+ icao: "EHAM",
1485
+ latitude: 52.3086,
1486
+ longitude: 4.7639,
1487
+ name: "Amsterdam Schiphol",
1488
+ tz: "Europe/Amsterdam",
1489
+ venues: [
1490
+ "polymarket"
1491
+ ]
1492
+ },
1493
+ {
1494
+ code: "EKCH",
1495
+ country: "DK",
1496
+ ghcnh_id: null,
1497
+ icao: "EKCH",
1498
+ latitude: 55.6181,
1499
+ longitude: 12.6561,
1500
+ name: "Copenhagen Kastrup",
1501
+ tz: "Europe/Copenhagen",
1502
+ venues: [
1503
+ "polymarket"
1504
+ ]
1505
+ },
1506
+ {
1507
+ code: "EPWA",
1508
+ country: "PL",
1509
+ ghcnh_id: null,
1510
+ icao: "EPWA",
1511
+ latitude: 52.1657,
1512
+ longitude: 20.9671,
1513
+ name: "Warsaw Chopin",
1514
+ tz: "Europe/Warsaw",
1515
+ venues: [
1516
+ "polymarket"
1517
+ ]
1518
+ },
1519
+ {
1520
+ code: "ESSA",
1521
+ country: "SE",
1522
+ ghcnh_id: null,
1523
+ icao: "ESSA",
1524
+ latitude: 59.6519,
1525
+ longitude: 17.9186,
1526
+ name: "Stockholm Arlanda",
1527
+ tz: "Europe/Stockholm",
1528
+ venues: [
1529
+ "polymarket"
1530
+ ]
1531
+ },
1532
+ {
1533
+ code: "ATL",
1534
+ country: "US",
1535
+ ghcnh_id: "USW00013874",
1536
+ icao: "KATL",
1537
+ latitude: 33.6407,
1538
+ longitude: -84.4277,
1539
+ name: "Hartsfield-Jackson Atlanta International",
1540
+ tz: "America/New_York",
1541
+ venues: [
1542
+ "kalshi",
1543
+ "polymarket"
1544
+ ]
1545
+ },
1546
+ {
1547
+ code: "AUS",
1548
+ country: "US",
1549
+ ghcnh_id: "USW00013904",
1550
+ icao: "KAUS",
1551
+ latitude: 30.1975,
1552
+ longitude: -97.6664,
1553
+ name: "Austin-Bergstrom International",
1554
+ tz: "America/Chicago",
1555
+ venues: [
1556
+ "kalshi",
1557
+ "polymarket"
1558
+ ]
1559
+ },
1560
+ {
1561
+ code: "BNA",
1562
+ country: "US",
1563
+ ghcnh_id: "USW00013897",
1564
+ icao: "KBNA",
1565
+ latitude: 36.1245,
1566
+ longitude: -86.6782,
1567
+ name: "Nashville International",
1568
+ tz: "America/Chicago",
1569
+ venues: [
1570
+ "kalshi"
1571
+ ]
1572
+ },
1573
+ {
1574
+ code: "BOS",
1575
+ country: "US",
1576
+ ghcnh_id: "USW00014739",
1577
+ icao: "KBOS",
1578
+ latitude: 42.3656,
1579
+ longitude: -71.0096,
1580
+ name: "Boston Logan International",
1581
+ tz: "America/New_York",
1582
+ venues: [
1583
+ "kalshi",
1584
+ "polymarket"
1585
+ ]
1586
+ },
1587
+ {
1588
+ code: "CVG",
1589
+ country: "US",
1590
+ ghcnh_id: "USW00093814",
1591
+ icao: "KCVG",
1592
+ latitude: 39.0488,
1593
+ longitude: -84.6678,
1594
+ name: "Cincinnati/Northern Kentucky International",
1595
+ tz: "America/New_York",
1596
+ venues: [
1597
+ "kalshi"
1598
+ ]
1599
+ },
1600
+ {
1601
+ code: "DCA",
1602
+ country: "US",
1603
+ ghcnh_id: "USW00013743",
1604
+ icao: "KDCA",
1605
+ latitude: 38.8512,
1606
+ longitude: -77.0402,
1607
+ name: "Washington Reagan National",
1608
+ tz: "America/New_York",
1609
+ venues: [
1610
+ "kalshi",
1611
+ "polymarket"
1612
+ ]
1613
+ },
1614
+ {
1615
+ code: "DEN",
1616
+ country: "US",
1617
+ ghcnh_id: "USW00003017",
1618
+ icao: "KDEN",
1619
+ latitude: 39.8561,
1620
+ longitude: -104.6737,
1621
+ name: "Denver International",
1622
+ tz: "America/Denver",
1623
+ venues: [
1624
+ "kalshi",
1625
+ "polymarket"
1626
+ ]
1627
+ },
1628
+ {
1629
+ code: "DFW",
1630
+ country: "US",
1631
+ ghcnh_id: "USW00003927",
1632
+ icao: "KDFW",
1633
+ latitude: 32.8998,
1634
+ longitude: -97.0403,
1635
+ name: "Dallas-Fort Worth International",
1636
+ tz: "America/Chicago",
1637
+ venues: [
1638
+ "kalshi",
1639
+ "polymarket"
1640
+ ]
1641
+ },
1642
+ {
1643
+ code: "DTW",
1644
+ country: "US",
1645
+ ghcnh_id: "USW00094847",
1646
+ icao: "KDTW",
1647
+ latitude: 42.2124,
1648
+ longitude: -83.3534,
1649
+ name: "Detroit Metropolitan Wayne County",
1650
+ tz: "America/New_York",
1651
+ venues: [
1652
+ "kalshi",
1653
+ "polymarket"
1654
+ ]
1655
+ },
1656
+ {
1657
+ code: "HOU",
1658
+ country: "US",
1659
+ ghcnh_id: "USW00012918",
1660
+ icao: "KHOU",
1661
+ latitude: 29.6454,
1662
+ longitude: -95.2789,
1663
+ name: "Houston Hobby",
1664
+ tz: "America/Chicago",
1665
+ venues: []
1666
+ },
1667
+ {
1668
+ code: "IAH",
1669
+ country: "US",
1670
+ ghcnh_id: "USW00012960",
1671
+ icao: "KIAH",
1672
+ latitude: 29.9844,
1673
+ longitude: -95.3414,
1674
+ name: "Houston George Bush Intercontinental",
1675
+ tz: "America/Chicago",
1676
+ venues: [
1677
+ "kalshi",
1678
+ "polymarket"
1679
+ ]
1680
+ },
1681
+ {
1682
+ code: "LAS",
1683
+ country: "US",
1684
+ ghcnh_id: "USW00023169",
1685
+ icao: "KLAS",
1686
+ latitude: 36.084,
1687
+ longitude: -115.1537,
1688
+ name: "Harry Reid (McCarran) International",
1689
+ tz: "America/Los_Angeles",
1690
+ venues: [
1691
+ "kalshi"
1692
+ ]
1693
+ },
1694
+ {
1695
+ code: "LAX",
1696
+ country: "US",
1697
+ ghcnh_id: "USW00023174",
1698
+ icao: "KLAX",
1699
+ latitude: 33.9425,
1700
+ longitude: -118.4081,
1701
+ name: "Los Angeles International",
1702
+ tz: "America/Los_Angeles",
1703
+ venues: [
1704
+ "kalshi",
1705
+ "polymarket"
1706
+ ]
1707
+ },
1708
+ {
1709
+ code: "MDW",
1710
+ country: "US",
1711
+ ghcnh_id: "USW00014819",
1712
+ icao: "KMDW",
1713
+ latitude: 41.7868,
1714
+ longitude: -87.7522,
1715
+ name: "Chicago Midway International",
1716
+ tz: "America/Chicago",
1717
+ venues: [
1718
+ "kalshi"
1719
+ ]
1720
+ },
1721
+ {
1722
+ code: "MIA",
1723
+ country: "US",
1724
+ ghcnh_id: "USW00012839",
1725
+ icao: "KMIA",
1726
+ latitude: 25.7959,
1727
+ longitude: -80.287,
1728
+ name: "Miami International",
1729
+ tz: "America/New_York",
1730
+ venues: [
1731
+ "kalshi",
1732
+ "polymarket"
1733
+ ]
1734
+ },
1735
+ {
1736
+ code: "MSP",
1737
+ country: "US",
1738
+ ghcnh_id: "USW00014922",
1739
+ icao: "KMSP",
1740
+ latitude: 44.8848,
1741
+ longitude: -93.2223,
1742
+ name: "Minneapolis-St Paul International",
1743
+ tz: "America/Chicago",
1744
+ venues: [
1745
+ "kalshi",
1746
+ "polymarket"
1747
+ ]
1748
+ },
1749
+ {
1750
+ code: "MSY",
1751
+ country: "US",
1752
+ ghcnh_id: "USW00012916",
1753
+ icao: "KMSY",
1754
+ latitude: 29.9934,
1755
+ longitude: -90.258,
1756
+ name: "New Orleans Louis Armstrong International",
1757
+ tz: "America/Chicago",
1758
+ venues: []
1759
+ },
1760
+ {
1761
+ code: "NYC",
1762
+ country: "US",
1763
+ ghcnh_id: "USW00094728",
1764
+ icao: "KNYC",
1765
+ latitude: 40.7789,
1766
+ longitude: -73.9692,
1767
+ name: "Central Park, New York",
1768
+ tz: "America/New_York",
1769
+ venues: [
1770
+ "kalshi"
1771
+ ]
1772
+ },
1773
+ {
1774
+ code: "OKC",
1775
+ country: "US",
1776
+ ghcnh_id: "USW00013967",
1777
+ icao: "KOKC",
1778
+ latitude: 35.3931,
1779
+ longitude: -97.6007,
1780
+ name: "Oklahoma City Will Rogers World",
1781
+ tz: "America/Chicago",
1782
+ venues: []
1783
+ },
1784
+ {
1785
+ code: "PHL",
1786
+ country: "US",
1787
+ ghcnh_id: "USW00013739",
1788
+ icao: "KPHL",
1789
+ latitude: 39.8721,
1790
+ longitude: -75.2411,
1791
+ name: "Philadelphia International",
1792
+ tz: "America/New_York",
1793
+ venues: [
1794
+ "kalshi",
1795
+ "polymarket"
1796
+ ]
1797
+ },
1798
+ {
1799
+ code: "PHX",
1800
+ country: "US",
1801
+ ghcnh_id: "USW00023183",
1802
+ icao: "KPHX",
1803
+ latitude: 33.4373,
1804
+ longitude: -112.0078,
1805
+ name: "Phoenix Sky Harbor International",
1806
+ tz: "America/Phoenix",
1807
+ venues: [
1808
+ "kalshi",
1809
+ "polymarket"
1810
+ ]
1811
+ },
1812
+ {
1813
+ code: "SAT",
1814
+ country: "US",
1815
+ ghcnh_id: "USW00012921",
1816
+ icao: "KSAT",
1817
+ latitude: 29.5337,
1818
+ longitude: -98.4698,
1819
+ name: "San Antonio International",
1820
+ tz: "America/Chicago",
1821
+ venues: []
1822
+ },
1823
+ {
1824
+ code: "SEA",
1825
+ country: "US",
1826
+ ghcnh_id: "USW00024233",
1827
+ icao: "KSEA",
1828
+ latitude: 47.4502,
1829
+ longitude: -122.3088,
1830
+ name: "Seattle-Tacoma International",
1831
+ tz: "America/Los_Angeles",
1832
+ venues: [
1833
+ "kalshi",
1834
+ "polymarket"
1835
+ ]
1836
+ },
1837
+ {
1838
+ code: "SFO",
1839
+ country: "US",
1840
+ ghcnh_id: "USW00023234",
1841
+ icao: "KSFO",
1842
+ latitude: 37.6213,
1843
+ longitude: -122.379,
1844
+ name: "San Francisco International",
1845
+ tz: "America/Los_Angeles",
1846
+ venues: [
1847
+ "kalshi",
1848
+ "polymarket"
1849
+ ]
1850
+ },
1851
+ {
1852
+ code: "SLC",
1853
+ country: "US",
1854
+ ghcnh_id: "USW00024127",
1855
+ icao: "KSLC",
1856
+ latitude: 40.7884,
1857
+ longitude: -111.9778,
1858
+ name: "Salt Lake City International",
1859
+ tz: "America/Denver",
1860
+ venues: [
1861
+ "kalshi"
1862
+ ]
1863
+ },
1864
+ {
1865
+ code: "LEBL",
1866
+ country: "ES",
1867
+ ghcnh_id: null,
1868
+ icao: "LEBL",
1869
+ latitude: 41.2974,
1870
+ longitude: 2.0833,
1871
+ name: "Barcelona El Prat",
1872
+ tz: "Europe/Madrid",
1873
+ venues: [
1874
+ "polymarket"
1875
+ ]
1876
+ },
1877
+ {
1878
+ code: "LEMD",
1879
+ country: "ES",
1880
+ ghcnh_id: null,
1881
+ icao: "LEMD",
1882
+ latitude: 40.4719,
1883
+ longitude: -3.5626,
1884
+ name: "Madrid Barajas",
1885
+ tz: "Europe/Madrid",
1886
+ venues: [
1887
+ "polymarket"
1888
+ ]
1889
+ },
1890
+ {
1891
+ code: "LFPB",
1892
+ country: "FR",
1893
+ ghcnh_id: null,
1894
+ icao: "LFPB",
1895
+ latitude: 48.9694,
1896
+ longitude: 2.4414,
1897
+ name: "Paris Le Bourget",
1898
+ tz: "Europe/Paris",
1899
+ venues: [
1900
+ "polymarket"
1901
+ ]
1902
+ },
1903
+ {
1904
+ code: "LFPG",
1905
+ country: "FR",
1906
+ ghcnh_id: null,
1907
+ icao: "LFPG",
1908
+ latitude: 49.0097,
1909
+ longitude: 2.5479,
1910
+ name: "Paris Charles de Gaulle",
1911
+ tz: "Europe/Paris",
1912
+ venues: [
1913
+ "polymarket"
1914
+ ]
1915
+ },
1916
+ {
1917
+ code: "LFPO",
1918
+ country: "FR",
1919
+ ghcnh_id: null,
1920
+ icao: "LFPO",
1921
+ latitude: 48.7233,
1922
+ longitude: 2.3794,
1923
+ name: "Paris Orly",
1924
+ tz: "Europe/Paris",
1925
+ venues: [
1926
+ "polymarket"
1927
+ ]
1928
+ },
1929
+ {
1930
+ code: "LIMC",
1931
+ country: "IT",
1932
+ ghcnh_id: null,
1933
+ icao: "LIMC",
1934
+ latitude: 45.6306,
1935
+ longitude: 8.7281,
1936
+ name: "Milan Malpensa",
1937
+ tz: "Europe/Rome",
1938
+ venues: [
1939
+ "polymarket"
1940
+ ]
1941
+ },
1942
+ {
1943
+ code: "LIRF",
1944
+ country: "IT",
1945
+ ghcnh_id: null,
1946
+ icao: "LIRF",
1947
+ latitude: 41.8003,
1948
+ longitude: 12.2389,
1949
+ name: "Rome Fiumicino",
1950
+ tz: "Europe/Rome",
1951
+ venues: [
1952
+ "polymarket"
1953
+ ]
1954
+ },
1955
+ {
1956
+ code: "LOWW",
1957
+ country: "AT",
1958
+ ghcnh_id: null,
1959
+ icao: "LOWW",
1960
+ latitude: 48.1103,
1961
+ longitude: 16.5697,
1962
+ name: "Vienna International",
1963
+ tz: "Europe/Vienna",
1964
+ venues: [
1965
+ "polymarket"
1966
+ ]
1967
+ },
1968
+ {
1969
+ code: "LSZH",
1970
+ country: "CH",
1971
+ ghcnh_id: null,
1972
+ icao: "LSZH",
1973
+ latitude: 47.4647,
1974
+ longitude: 8.5492,
1975
+ name: "Zurich",
1976
+ tz: "Europe/Zurich",
1977
+ venues: [
1978
+ "polymarket"
1979
+ ]
1980
+ },
1981
+ {
1982
+ code: "NZAA",
1983
+ country: "NZ",
1984
+ ghcnh_id: null,
1985
+ icao: "NZAA",
1986
+ latitude: -37.0081,
1987
+ longitude: 174.7917,
1988
+ name: "Auckland",
1989
+ tz: "Pacific/Auckland",
1990
+ venues: [
1991
+ "polymarket"
1992
+ ]
1993
+ },
1994
+ {
1995
+ code: "NZWN",
1996
+ country: "NZ",
1997
+ ghcnh_id: null,
1998
+ icao: "NZWN",
1999
+ latitude: -41.3272,
2000
+ longitude: 174.8053,
2001
+ name: "Wellington",
2002
+ tz: "Pacific/Auckland",
2003
+ venues: [
2004
+ "polymarket"
2005
+ ]
2006
+ },
2007
+ {
2008
+ code: "OERK",
2009
+ country: "SA",
2010
+ ghcnh_id: null,
2011
+ icao: "OERK",
2012
+ latitude: 24.9576,
2013
+ longitude: 46.6988,
2014
+ name: "Riyadh King Khalid International",
2015
+ tz: "Asia/Riyadh",
2016
+ venues: [
2017
+ "polymarket"
2018
+ ]
2019
+ },
2020
+ {
2021
+ code: "OMDB",
2022
+ country: "AE",
2023
+ ghcnh_id: null,
2024
+ icao: "OMDB",
2025
+ latitude: 25.2532,
2026
+ longitude: 55.3657,
2027
+ name: "Dubai International",
2028
+ tz: "Asia/Dubai",
2029
+ venues: [
2030
+ "polymarket"
2031
+ ]
2032
+ },
2033
+ {
2034
+ code: "OTHH",
2035
+ country: "QA",
2036
+ ghcnh_id: null,
2037
+ icao: "OTHH",
2038
+ latitude: 25.2731,
2039
+ longitude: 51.608,
2040
+ name: "Doha Hamad International",
2041
+ tz: "Asia/Qatar",
2042
+ venues: [
2043
+ "polymarket"
2044
+ ]
2045
+ },
2046
+ {
2047
+ code: "RCTP",
2048
+ country: "TW",
2049
+ ghcnh_id: null,
2050
+ icao: "RCTP",
2051
+ latitude: 25.0777,
2052
+ longitude: 121.2328,
2053
+ name: "Taipei Taoyuan",
2054
+ tz: "Asia/Taipei",
2055
+ venues: [
2056
+ "polymarket"
2057
+ ]
2058
+ },
2059
+ {
2060
+ code: "RJAA",
2061
+ country: "JP",
2062
+ ghcnh_id: null,
2063
+ icao: "RJAA",
2064
+ latitude: 35.7647,
2065
+ longitude: 140.3864,
2066
+ name: "Tokyo Narita",
2067
+ tz: "Asia/Tokyo",
2068
+ venues: [
2069
+ "polymarket"
2070
+ ]
2071
+ },
2072
+ {
2073
+ code: "RJTT",
2074
+ country: "JP",
2075
+ ghcnh_id: null,
2076
+ icao: "RJTT",
2077
+ latitude: 35.5522,
2078
+ longitude: 139.78,
2079
+ name: "Tokyo Haneda",
2080
+ tz: "Asia/Tokyo",
2081
+ venues: [
2082
+ "polymarket"
2083
+ ]
2084
+ },
2085
+ {
2086
+ code: "RKSI",
2087
+ country: "KR",
2088
+ ghcnh_id: null,
2089
+ icao: "RKSI",
2090
+ latitude: 37.4691,
2091
+ longitude: 126.4505,
2092
+ name: "Seoul Incheon",
2093
+ tz: "Asia/Seoul",
2094
+ venues: [
2095
+ "polymarket"
2096
+ ]
2097
+ },
2098
+ {
2099
+ code: "SAEZ",
2100
+ country: "AR",
2101
+ ghcnh_id: null,
2102
+ icao: "SAEZ",
2103
+ latitude: -34.8222,
2104
+ longitude: -58.5358,
2105
+ name: "Buenos Aires Ezeiza",
2106
+ tz: "America/Argentina/Buenos_Aires",
2107
+ venues: [
2108
+ "polymarket"
2109
+ ]
2110
+ },
2111
+ {
2112
+ code: "SBGR",
2113
+ country: "BR",
2114
+ ghcnh_id: null,
2115
+ icao: "SBGR",
2116
+ latitude: -23.4356,
2117
+ longitude: -46.4731,
2118
+ name: "S\xE3o Paulo Guarulhos",
2119
+ tz: "America/Sao_Paulo",
2120
+ venues: [
2121
+ "polymarket"
2122
+ ]
2123
+ },
2124
+ {
2125
+ code: "UUEE",
2126
+ country: "RU",
2127
+ ghcnh_id: null,
2128
+ icao: "UUEE",
2129
+ latitude: 55.9728,
2130
+ longitude: 37.4147,
2131
+ name: "Moscow Sheremetyevo",
2132
+ tz: "Europe/Moscow",
2133
+ venues: [
2134
+ "polymarket"
2135
+ ]
2136
+ },
2137
+ {
2138
+ code: "VABB",
2139
+ country: "IN",
2140
+ ghcnh_id: null,
2141
+ icao: "VABB",
2142
+ latitude: 19.0887,
2143
+ longitude: 72.8679,
2144
+ name: "Mumbai Chhatrapati Shivaji",
2145
+ tz: "Asia/Kolkata",
2146
+ venues: [
2147
+ "polymarket"
2148
+ ]
2149
+ },
2150
+ {
2151
+ code: "VHHH",
2152
+ country: "HK",
2153
+ ghcnh_id: null,
2154
+ icao: "VHHH",
2155
+ latitude: 22.308,
2156
+ longitude: 113.9185,
2157
+ name: "Hong Kong International",
2158
+ tz: "Asia/Hong_Kong",
2159
+ venues: [
2160
+ "polymarket"
2161
+ ]
2162
+ },
2163
+ {
2164
+ code: "VIDP",
2165
+ country: "IN",
2166
+ ghcnh_id: null,
2167
+ icao: "VIDP",
2168
+ latitude: 28.5562,
2169
+ longitude: 77.1,
2170
+ name: "Delhi Indira Gandhi",
2171
+ tz: "Asia/Kolkata",
2172
+ venues: [
2173
+ "polymarket"
2174
+ ]
2175
+ },
2176
+ {
2177
+ code: "VTBS",
2178
+ country: "TH",
2179
+ ghcnh_id: null,
2180
+ icao: "VTBS",
2181
+ latitude: 13.69,
2182
+ longitude: 100.7501,
2183
+ name: "Bangkok Suvarnabhumi",
2184
+ tz: "Asia/Bangkok",
2185
+ venues: [
2186
+ "polymarket"
2187
+ ]
2188
+ },
2189
+ {
2190
+ code: "WSSS",
2191
+ country: "SG",
2192
+ ghcnh_id: null,
2193
+ icao: "WSSS",
2194
+ latitude: 1.3644,
2195
+ longitude: 103.9915,
2196
+ name: "Singapore Changi",
2197
+ tz: "Asia/Singapore",
2198
+ venues: [
2199
+ "polymarket"
2200
+ ]
2201
+ },
2202
+ {
2203
+ code: "YBBN",
2204
+ country: "AU",
2205
+ ghcnh_id: null,
2206
+ icao: "YBBN",
2207
+ latitude: -27.3842,
2208
+ longitude: 153.1175,
2209
+ name: "Brisbane",
2210
+ tz: "Australia/Brisbane",
2211
+ venues: [
2212
+ "polymarket"
2213
+ ]
2214
+ },
2215
+ {
2216
+ code: "YMML",
2217
+ country: "AU",
2218
+ ghcnh_id: null,
2219
+ icao: "YMML",
2220
+ latitude: -37.6733,
2221
+ longitude: 144.8433,
2222
+ name: "Melbourne Tullamarine",
2223
+ tz: "Australia/Melbourne",
2224
+ venues: [
2225
+ "polymarket"
2226
+ ]
2227
+ },
2228
+ {
2229
+ code: "YSSY",
2230
+ country: "AU",
2231
+ ghcnh_id: null,
2232
+ icao: "YSSY",
2233
+ latitude: -33.9461,
2234
+ longitude: 151.1772,
2235
+ name: "Sydney Kingsford Smith",
2236
+ tz: "Australia/Sydney",
2237
+ venues: [
2238
+ "polymarket"
2239
+ ]
2240
+ },
2241
+ {
2242
+ code: "ZBAA",
2243
+ country: "CN",
2244
+ ghcnh_id: null,
2245
+ icao: "ZBAA",
2246
+ latitude: 40.0801,
2247
+ longitude: 116.5846,
2248
+ name: "Beijing Capital",
2249
+ tz: "Asia/Shanghai",
2250
+ venues: [
2251
+ "polymarket"
2252
+ ]
2253
+ },
2254
+ {
2255
+ code: "ZSPD",
2256
+ country: "CN",
2257
+ ghcnh_id: null,
2258
+ icao: "ZSPD",
2259
+ latitude: 31.1443,
2260
+ longitude: 121.8083,
2261
+ name: "Shanghai Pudong",
2262
+ tz: "Asia/Shanghai",
2263
+ venues: [
2264
+ "polymarket"
2265
+ ]
2266
+ }
2267
+ ];
2268
+ var STATION_BY_CODE2 = /* @__PURE__ */ new Map([
2269
+ ["ATL", STATIONS2[10]],
2270
+ ["AUS", STATIONS2[11]],
2271
+ ["BNA", STATIONS2[12]],
2272
+ ["BOS", STATIONS2[13]],
2273
+ ["CVG", STATIONS2[14]],
2274
+ ["DCA", STATIONS2[15]],
2275
+ ["DEN", STATIONS2[16]],
2276
+ ["DFW", STATIONS2[17]],
2277
+ ["DTW", STATIONS2[18]],
2278
+ ["EDDB", STATIONS2[0]],
2279
+ ["EDDF", STATIONS2[1]],
2280
+ ["EDDM", STATIONS2[2]],
2281
+ ["EFHK", STATIONS2[3]],
2282
+ ["EGKK", STATIONS2[4]],
2283
+ ["EGLL", STATIONS2[5]],
2284
+ ["EHAM", STATIONS2[6]],
2285
+ ["EKCH", STATIONS2[7]],
2286
+ ["EPWA", STATIONS2[8]],
2287
+ ["ESSA", STATIONS2[9]],
2288
+ ["HOU", STATIONS2[19]],
2289
+ ["IAH", STATIONS2[20]],
2290
+ ["LAS", STATIONS2[21]],
2291
+ ["LAX", STATIONS2[22]],
2292
+ ["LEBL", STATIONS2[35]],
2293
+ ["LEMD", STATIONS2[36]],
2294
+ ["LFPB", STATIONS2[37]],
2295
+ ["LFPG", STATIONS2[38]],
2296
+ ["LFPO", STATIONS2[39]],
2297
+ ["LIMC", STATIONS2[40]],
2298
+ ["LIRF", STATIONS2[41]],
2299
+ ["LOWW", STATIONS2[42]],
2300
+ ["LSZH", STATIONS2[43]],
2301
+ ["MDW", STATIONS2[23]],
2302
+ ["MIA", STATIONS2[24]],
2303
+ ["MSP", STATIONS2[25]],
2304
+ ["MSY", STATIONS2[26]],
2305
+ ["NYC", STATIONS2[27]],
2306
+ ["NZAA", STATIONS2[44]],
2307
+ ["NZWN", STATIONS2[45]],
2308
+ ["OERK", STATIONS2[46]],
2309
+ ["OKC", STATIONS2[28]],
2310
+ ["OMDB", STATIONS2[47]],
2311
+ ["OTHH", STATIONS2[48]],
2312
+ ["PHL", STATIONS2[29]],
2313
+ ["PHX", STATIONS2[30]],
2314
+ ["RCTP", STATIONS2[49]],
2315
+ ["RJAA", STATIONS2[50]],
2316
+ ["RJTT", STATIONS2[51]],
2317
+ ["RKSI", STATIONS2[52]],
2318
+ ["SAEZ", STATIONS2[53]],
2319
+ ["SAT", STATIONS2[31]],
2320
+ ["SBGR", STATIONS2[54]],
2321
+ ["SEA", STATIONS2[32]],
2322
+ ["SFO", STATIONS2[33]],
2323
+ ["SLC", STATIONS2[34]],
2324
+ ["UUEE", STATIONS2[55]],
2325
+ ["VABB", STATIONS2[56]],
2326
+ ["VHHH", STATIONS2[57]],
2327
+ ["VIDP", STATIONS2[58]],
2328
+ ["VTBS", STATIONS2[59]],
2329
+ ["WSSS", STATIONS2[60]],
2330
+ ["YBBN", STATIONS2[61]],
2331
+ ["YMML", STATIONS2[62]],
2332
+ ["YSSY", STATIONS2[63]],
2333
+ ["ZBAA", STATIONS2[64]],
2334
+ ["ZSPD", STATIONS2[65]]
2335
+ ]);
2336
+ var STATION_BY_ICAO2 = /* @__PURE__ */ new Map([
2337
+ ["EDDB", STATIONS2[0]],
2338
+ ["EDDF", STATIONS2[1]],
2339
+ ["EDDM", STATIONS2[2]],
2340
+ ["EFHK", STATIONS2[3]],
2341
+ ["EGKK", STATIONS2[4]],
2342
+ ["EGLL", STATIONS2[5]],
2343
+ ["EHAM", STATIONS2[6]],
2344
+ ["EKCH", STATIONS2[7]],
2345
+ ["EPWA", STATIONS2[8]],
2346
+ ["ESSA", STATIONS2[9]],
2347
+ ["KATL", STATIONS2[10]],
2348
+ ["KAUS", STATIONS2[11]],
2349
+ ["KBNA", STATIONS2[12]],
2350
+ ["KBOS", STATIONS2[13]],
2351
+ ["KCVG", STATIONS2[14]],
2352
+ ["KDCA", STATIONS2[15]],
2353
+ ["KDEN", STATIONS2[16]],
2354
+ ["KDFW", STATIONS2[17]],
2355
+ ["KDTW", STATIONS2[18]],
2356
+ ["KHOU", STATIONS2[19]],
2357
+ ["KIAH", STATIONS2[20]],
2358
+ ["KLAS", STATIONS2[21]],
2359
+ ["KLAX", STATIONS2[22]],
2360
+ ["KMDW", STATIONS2[23]],
2361
+ ["KMIA", STATIONS2[24]],
2362
+ ["KMSP", STATIONS2[25]],
2363
+ ["KMSY", STATIONS2[26]],
2364
+ ["KNYC", STATIONS2[27]],
2365
+ ["KOKC", STATIONS2[28]],
2366
+ ["KPHL", STATIONS2[29]],
2367
+ ["KPHX", STATIONS2[30]],
2368
+ ["KSAT", STATIONS2[31]],
2369
+ ["KSEA", STATIONS2[32]],
2370
+ ["KSFO", STATIONS2[33]],
2371
+ ["KSLC", STATIONS2[34]],
2372
+ ["LEBL", STATIONS2[35]],
2373
+ ["LEMD", STATIONS2[36]],
2374
+ ["LFPB", STATIONS2[37]],
2375
+ ["LFPG", STATIONS2[38]],
2376
+ ["LFPO", STATIONS2[39]],
2377
+ ["LIMC", STATIONS2[40]],
2378
+ ["LIRF", STATIONS2[41]],
2379
+ ["LOWW", STATIONS2[42]],
2380
+ ["LSZH", STATIONS2[43]],
2381
+ ["NZAA", STATIONS2[44]],
2382
+ ["NZWN", STATIONS2[45]],
2383
+ ["OERK", STATIONS2[46]],
2384
+ ["OMDB", STATIONS2[47]],
2385
+ ["OTHH", STATIONS2[48]],
2386
+ ["RCTP", STATIONS2[49]],
2387
+ ["RJAA", STATIONS2[50]],
2388
+ ["RJTT", STATIONS2[51]],
2389
+ ["RKSI", STATIONS2[52]],
2390
+ ["SAEZ", STATIONS2[53]],
2391
+ ["SBGR", STATIONS2[54]],
2392
+ ["UUEE", STATIONS2[55]],
2393
+ ["VABB", STATIONS2[56]],
2394
+ ["VHHH", STATIONS2[57]],
2395
+ ["VIDP", STATIONS2[58]],
2396
+ ["VTBS", STATIONS2[59]],
2397
+ ["WSSS", STATIONS2[60]],
2398
+ ["YBBN", STATIONS2[61]],
2399
+ ["YMML", STATIONS2[62]],
2400
+ ["YSSY", STATIONS2[63]],
2401
+ ["ZBAA", STATIONS2[64]],
2402
+ ["ZSPD", STATIONS2[65]]
2403
+ ]);
2404
+ var LOW_COVERAGE_THRESHOLD = 12;
2405
+ var PARTS_CACHE = /* @__PURE__ */ new Map();
2406
+ function getDateFormatter(tz) {
2407
+ let f = PARTS_CACHE.get(tz);
2408
+ if (f === void 0) {
2409
+ f = new Intl.DateTimeFormat("en-US", {
2410
+ timeZone: tz,
2411
+ year: "numeric",
2412
+ month: "2-digit",
2413
+ day: "2-digit"
2414
+ });
2415
+ PARTS_CACHE.set(tz, f);
2416
+ }
2417
+ return f;
2418
+ }
2419
+ function localDateFor(instant, tz) {
2420
+ const parts = getDateFormatter(tz).formatToParts(instant);
2421
+ let y = "";
2422
+ let m = "";
2423
+ let d = "";
2424
+ for (const p of parts) {
2425
+ if (p.type === "year") y = p.value;
2426
+ else if (p.type === "month") m = p.value;
2427
+ else if (p.type === "day") d = p.value;
2428
+ }
2429
+ return `${y}-${m}-${d}`;
2430
+ }
2431
+ function parseInstant(observed) {
2432
+ if (observed === void 0 || observed === null || observed.length === 0) {
2433
+ return null;
2434
+ }
2435
+ const ms = Date.parse(observed);
2436
+ if (Number.isNaN(ms)) return null;
2437
+ return new Date(ms);
2438
+ }
2439
+ function roundHalfUp(value, places) {
2440
+ if (!Number.isFinite(value)) return value;
2441
+ const scale = 10 ** places;
2442
+ const sign = value < 0 ? -1 : 1;
2443
+ const abs = Math.abs(value);
2444
+ const rounded = Math.floor(abs * scale + 0.5 + abs * 1e-12) / scale;
2445
+ return sign * rounded;
2446
+ }
2447
+ function cToF(c) {
2448
+ return c * 1.8 + 32;
2449
+ }
2450
+ function internationalDailyExtremes(rows, opts) {
2451
+ const tz = opts.stationTz;
2452
+ if (typeof tz !== "string" || tz.length === 0) {
2453
+ throw new RangeError("internationalDailyExtremes: stationTz is required (non-empty string)");
2454
+ }
2455
+ const precision = opts.precision ?? 0;
2456
+ const minObs = opts.minObs ?? LOW_COVERAGE_THRESHOLD;
2457
+ try {
2458
+ getDateFormatter(tz);
2459
+ } catch (e) {
2460
+ throw new RangeError(
2461
+ `internationalDailyExtremes: invalid stationTz ${JSON.stringify(tz)}: ${e.message}`
2462
+ );
2463
+ }
2464
+ const byLocalDate = /* @__PURE__ */ new Map();
2465
+ for (const row of rows) {
2466
+ const instant = parseInstant(row.observed_at);
2467
+ if (instant === null) continue;
2468
+ const localDate = localDateFor(instant, tz);
2469
+ let bucket = byLocalDate.get(localDate);
2470
+ if (bucket === void 0) {
2471
+ bucket = { temps: [], precipMm: 0 };
2472
+ byLocalDate.set(localDate, bucket);
2473
+ }
2474
+ const t = row.temp_c;
2475
+ if (typeof t === "number" && Number.isFinite(t)) {
2476
+ bucket.temps.push({ value: t, source: row.source ?? null });
2477
+ }
2478
+ const p = row.precip_mm_1h;
2479
+ if (typeof p === "number" && Number.isFinite(p)) {
2480
+ bucket.precipMm += p;
2481
+ }
2482
+ }
2483
+ const out = [];
2484
+ const sortedDates = [...byLocalDate.keys()].sort();
2485
+ for (const localDate of sortedDates) {
2486
+ const bucket = byLocalDate.get(localDate);
2487
+ if (bucket === void 0) continue;
2488
+ const nObs = bucket.temps.length;
2489
+ let tempMinC = null;
2490
+ let tempMaxC = null;
2491
+ let tempMeanC = null;
2492
+ let sourceTmin = null;
2493
+ let sourceTmax = null;
2494
+ if (nObs > 0 && nObs >= minObs) {
2495
+ let minIdx = 0;
2496
+ let maxIdx = 0;
2497
+ let sum = 0;
2498
+ for (let i = 0; i < bucket.temps.length; i += 1) {
2499
+ const v = bucket.temps[i];
2500
+ sum += v.value;
2501
+ const minRow2 = bucket.temps[minIdx];
2502
+ const maxRow2 = bucket.temps[maxIdx];
2503
+ if (v.value < minRow2.value) minIdx = i;
2504
+ if (v.value > maxRow2.value) maxIdx = i;
2505
+ }
2506
+ const mean = sum / nObs;
2507
+ const minRow = bucket.temps[minIdx];
2508
+ const maxRow = bucket.temps[maxIdx];
2509
+ tempMinC = roundHalfUp(minRow.value, precision);
2510
+ tempMaxC = roundHalfUp(maxRow.value, precision);
2511
+ tempMeanC = roundHalfUp(mean, precision);
2512
+ sourceTmin = minRow.source;
2513
+ sourceTmax = maxRow.source;
2514
+ }
2515
+ out.push(
2516
+ Object.freeze({
2517
+ localDate,
2518
+ nObs,
2519
+ tempMinC,
2520
+ tempMaxC,
2521
+ tempMeanC,
2522
+ tempMinF: tempMinC === null ? null : roundHalfUp(cToF(tempMinC), precision),
2523
+ tempMaxF: tempMaxC === null ? null : roundHalfUp(cToF(tempMaxC), precision),
2524
+ precipMm: roundHalfUp(bucket.precipMm, 4),
2525
+ sourceTmin,
2526
+ sourceTmax
2527
+ })
2528
+ );
2529
+ }
2530
+ return out;
2531
+ }
2532
+ var BUILT_IN_SCHEMAS = Object.freeze([
2533
+ {
2534
+ id: "schema.observation.v1",
2535
+ title: "schema.observation.v1",
2536
+ columnCount: 20,
2537
+ columns: [
2538
+ { name: "dew_point_c", description: "units: celsius \u2014 bounded", nullable: true },
2539
+ { name: "event_time", description: "observation valid time", nullable: false },
2540
+ {
2541
+ name: "metar_raw",
2542
+ description: "raw METAR text if source has it; null for AWC JSON (structured-only)",
2543
+ nullable: true
2544
+ },
2545
+ {
2546
+ name: "observation_type",
2547
+ description: "METAR | SPECI; defaults METAR when source can't distinguish (e.g. AWC JSON)",
2548
+ nullable: false
2549
+ },
2550
+ {
2551
+ name: "precip_mm_1h",
2552
+ description: "units: mm \u2014 hourly precip (METAR p01i, converted from inches)",
2553
+ nullable: true
2554
+ },
2555
+ {
2556
+ name: "sky_base_1_m",
2557
+ description: "units: meters \u2014 first cloud layer base height (converted from feet)",
2558
+ nullable: true
2559
+ },
2560
+ { name: "sky_base_2_m", description: "units: meters", nullable: true },
2561
+ { name: "sky_base_3_m", description: "units: meters", nullable: true },
2562
+ { name: "sky_base_4_m", description: "units: meters", nullable: true },
2563
+ { name: "sky_cover_1", description: "first cloud layer cover code", nullable: true },
2564
+ { name: "sky_cover_2", description: "second layer; null if not present", nullable: true },
2565
+ { name: "sky_cover_3", description: "third layer; null if not present", nullable: true },
2566
+ { name: "sky_cover_4", description: "fourth layer; null if not present", nullable: true },
2567
+ {
2568
+ name: "slp_hpa",
2569
+ description: "units: hPa \u2014 sea-level pressure (canonical aviation unit, not converted across modes)",
2570
+ nullable: true
2571
+ },
2572
+ { name: "station", description: "ICAO/ASOS station ID (e.g. KORD)", nullable: false },
2573
+ {
2574
+ name: "temp_c",
2575
+ description: "units: celsius \u2014 bounded TEMP_MIN_C..TEMP_MAX_C",
2576
+ nullable: true
2577
+ },
2578
+ {
2579
+ name: "visibility_m",
2580
+ description: "units: meters \u2014 converted from statute miles",
2581
+ nullable: true
2582
+ },
2583
+ {
2584
+ name: "wind_dir_deg",
2585
+ description: "units: degrees \u2014 0-360, bounded",
2586
+ nullable: true
2587
+ },
2588
+ { name: "wind_gust_ms", description: "units: m/s \u2014 converted from kt", nullable: true },
2589
+ { name: "wind_speed_ms", description: "units: m/s \u2014 converted from kt", nullable: true }
2590
+ ]
2591
+ },
2592
+ {
2593
+ id: "schema.forecast.iem_mos.v1",
2594
+ title: "schema.forecast.iem_mos.v1",
2595
+ columnCount: 11,
2596
+ columns: [
2597
+ { name: "dew_point_c", description: "units: celsius", nullable: true },
2598
+ {
2599
+ name: "forecast_hour",
2600
+ description: "units: hours \u2014 (valid_at - issued_at).total_seconds() / 3600",
2601
+ nullable: false
2602
+ },
2603
+ {
2604
+ name: "issued_at",
2605
+ description: "model run time (from source `runtime` field)",
2606
+ nullable: false
2607
+ },
2608
+ { name: "model", description: "e.g. NBE, GFS, LAV, MET", nullable: false },
2609
+ {
2610
+ name: "precip_probability",
2611
+ description: "units: probability \u2014 bounded [0, 1]",
2612
+ nullable: true
2613
+ },
2614
+ {
2615
+ name: "sky_cover_pct",
2616
+ description: "units: percent \u2014 bounded [0, 100]",
2617
+ nullable: true
2618
+ },
2619
+ { name: "station", description: "", nullable: false },
2620
+ { name: "temp_c", description: "units: celsius", nullable: true },
2621
+ {
2622
+ name: "valid_at",
2623
+ description: "forecast target time (from source `ftime`)",
2624
+ nullable: false
2625
+ },
2626
+ { name: "wind_dir_deg", description: "units: degrees", nullable: true },
2627
+ { name: "wind_speed_ms", description: "units: m/s", nullable: true }
2628
+ ]
2629
+ },
2630
+ {
2631
+ id: "schema.settlement.cli.v1",
2632
+ title: "schema.settlement.cli.v1",
2633
+ columnCount: 12,
2634
+ columns: [
2635
+ {
2636
+ name: "cli_data_quality",
2637
+ description: "NWS CLI data-quality marker (Pitfall 6/16). Allows downstream code to filter or weight settlement rows by issuer quality without re-parsing the product header.",
2638
+ nullable: false
2639
+ },
2640
+ {
2641
+ name: "event_time",
2642
+ description: "00:00 local time on observation_date converted to UTC; for sort/join only",
2643
+ nullable: false
2644
+ },
2645
+ {
2646
+ name: "observation_date",
2647
+ description: "local climate day per NWS convention (no timezone applied to the date itself)",
2648
+ nullable: false
2649
+ },
2650
+ { name: "precipitation_in", description: "units: inches", nullable: true },
2651
+ {
2652
+ name: "product_release_time",
2653
+ description: "parsed from CLI product header (_climate.py::_parse_product_timestamp)",
2654
+ nullable: false
2655
+ },
2656
+ {
2657
+ name: "report_type",
2658
+ description: "preliminary | final | correction; dedup priority preliminary < final < correction",
2659
+ nullable: false
2660
+ },
2661
+ {
2662
+ name: "settlement_finality",
2663
+ description: "provisional | final | superseded. Kalshi NHIGH/NLOW settlement contractually requires 'final'; 'provisional' values are kept for early-look research only.",
2664
+ nullable: false
2665
+ },
2666
+ { name: "snowfall_in", description: "units: inches", nullable: true },
2667
+ { name: "station", description: "ICAO/ASOS station ID", nullable: false },
2668
+ {
2669
+ name: "station_tz",
2670
+ description: "IANA timezone for the station (e.g. America/Chicago for KORD). Required for local-climate-day semantics; see \xA7U.",
2671
+ nullable: false
2672
+ },
2673
+ {
2674
+ name: "temp_max_F",
2675
+ description: "units: fahrenheit \u2014 daily high (uppercase F for consistency with obs imperial mode)",
2676
+ nullable: true
2677
+ },
2678
+ { name: "temp_min_F", description: "units: fahrenheit \u2014 daily low", nullable: true }
2679
+ ]
2680
+ },
2681
+ {
2682
+ id: "schema.observation_ledger.v1",
2683
+ title: "schema.observation_ledger.v1",
2684
+ columnCount: 15,
2685
+ columns: [
2686
+ { name: "as_of_time", description: "", nullable: true },
2687
+ { name: "dewpoint_c", description: "units: celsius", nullable: true },
2688
+ { name: "ingestion_id", description: "", nullable: true },
2689
+ { name: "observation_kind", description: "", nullable: true },
2690
+ {
2691
+ name: "observation_quality",
2692
+ description: "Lineage row-quality flag per LINEAGE-01; distinct from qc_status enum slot AND distinct from the obs_qc_status bitmask column per QC-05.",
2693
+ nullable: true
2694
+ },
2695
+ { name: "observation_type", description: "", nullable: false },
2696
+ { name: "observed_at", description: "", nullable: false },
2697
+ { name: "parser_name", description: "", nullable: true },
2698
+ { name: "parser_version", description: "", nullable: true },
2699
+ { name: "provenance", description: "", nullable: true },
2700
+ { name: "qc_status", description: "", nullable: true },
2701
+ {
2702
+ name: "source",
2703
+ description: "ncei reserved per D-2.1-09; never written in v0.1.0.",
2704
+ nullable: false
2705
+ },
2706
+ { name: "source_received_at", description: "", nullable: true },
2707
+ { name: "station_code", description: "", nullable: false },
2708
+ { name: "temp_c", description: "units: celsius", nullable: true }
2709
+ ]
2710
+ },
2711
+ {
2712
+ id: "schema.observation_qc.v1",
2713
+ title: "schema.observation_qc.v1",
2714
+ columnCount: 13,
2715
+ columns: [
2716
+ { name: "as_of_time", description: "", nullable: true },
2717
+ {
2718
+ name: "detector_metadata",
2719
+ description: "JSON-serialized detector payload; shape per qc_system.",
2720
+ nullable: true
2721
+ },
2722
+ {
2723
+ name: "field",
2724
+ description: "Observation column the rule evaluated (e.g. temp_c).",
2725
+ nullable: false
2726
+ },
2727
+ { name: "flag", description: "", nullable: false },
2728
+ { name: "ingestion_id", description: "", nullable: true },
2729
+ { name: "observation_kind", description: "", nullable: true },
2730
+ { name: "observed_at", description: "", nullable: false },
2731
+ { name: "parser_name", description: "", nullable: true },
2732
+ { name: "qc_system", description: "", nullable: false },
2733
+ { name: "qc_version", description: "", nullable: false },
2734
+ { name: "rule_id", description: "", nullable: false },
2735
+ { name: "source", description: "", nullable: false },
2736
+ { name: "station_code", description: "", nullable: false }
2737
+ ]
2738
+ }
2739
+ ]);
2740
+ function deepFreezeSchema(info) {
2741
+ const frozenCols = Object.freeze(info.columns.map((c) => Object.freeze({ ...c })));
2742
+ return Object.freeze({ ...info, columns: frozenCols });
2743
+ }
2744
+ var REGISTRY = new Map(
2745
+ BUILT_IN_SCHEMAS.map((info) => [info.id, deepFreezeSchema(info)])
2746
+ );
2747
+ var FEATURE_NAMES = Object.freeze([
2748
+ "calendarFeatures",
2749
+ "clipOutliers",
2750
+ "diff",
2751
+ "diff2",
2752
+ "heatIndex",
2753
+ "lag",
2754
+ "rolling",
2755
+ "spread",
2756
+ "windChill"
2757
+ ]);
2758
+
2759
+ // ../weather/src/dailyExtremes.ts
2760
+ var LOW_COVERAGE_THRESHOLD2 = 12;
2761
+ function addUtcDays(iso, days) {
2762
+ const [yStr, mStr, dStr] = iso.split("-");
2763
+ const dt = new Date(Date.UTC(Number(yStr), Number(mStr) - 1, Number(dStr)));
2764
+ dt.setUTCDate(dt.getUTCDate() + days);
2765
+ const yyyy = dt.getUTCFullYear();
2766
+ const mm = String(dt.getUTCMonth() + 1).padStart(2, "0");
2767
+ const dd = String(dt.getUTCDate()).padStart(2, "0");
2768
+ return `${yyyy}-${mm}-${dd}`;
2769
+ }
2770
+ function lookupStation(icao) {
2771
+ const upper = icao.toUpperCase();
2772
+ for (const s of STATIONS) {
2773
+ if (s.icao === upper) {
2774
+ return { tz: s.tz, isUs: s.country === "US" };
2775
+ }
2776
+ }
2777
+ throw new Error(`dailyExtremes: station "${icao}" not in registry \u2014 check STATIONS catalog`);
2778
+ }
2779
+ function cToF2(c) {
2780
+ if (c === null) return null;
2781
+ return c * (9 / 5) + 32;
2782
+ }
2783
+ function roundHalfUp2(value, decimals) {
2784
+ const m = 10 ** decimals;
2785
+ return Math.round(value * m) / m;
2786
+ }
2787
+ async function fetchIemAsosObservations(station, fromDate, toDate) {
2788
+ const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);
2789
+ const toYear = Number.parseInt(toDate.slice(0, 4), 10);
2790
+ const out = [];
2791
+ for (let year = fromYear; year <= toYear; year++) {
2792
+ const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {
2793
+ reportType: 3,
2794
+ politenessMs: 1e3
2795
+ });
2796
+ for (const chunk of chunks) {
2797
+ const parsed = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
2798
+ for (const row of parsed) {
2799
+ const obsDate = row.observed_at.slice(0, 10);
2800
+ if (obsDate >= fromDate && obsDate <= toDate) {
2801
+ const precipInches = row.precip_1hr_inches ?? null;
2802
+ out.push({
2803
+ observed_at: row.observed_at,
2804
+ temp_c: row.temp_c ?? null,
2805
+ precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,
2806
+ source: row.source
2807
+ });
2808
+ }
2809
+ }
2810
+ }
2811
+ }
2812
+ return out;
2813
+ }
2814
+ async function fetchAwcObservations(station, fromDate, toDate) {
2815
+ const raw = await fetchAwcMetars([station]);
2816
+ const out = [];
2817
+ for (const r of raw) {
2818
+ const obs2 = awcToObservation(r);
2819
+ if (obs2 === null) continue;
2820
+ const obsDate = obs2.observed_at.slice(0, 10);
2821
+ if (obsDate >= fromDate && obsDate <= toDate) {
2822
+ const precipInches = obs2.precip_1hr_inches ?? null;
2823
+ out.push({
2824
+ observed_at: obs2.observed_at,
2825
+ temp_c: obs2.temp_c ?? null,
2826
+ precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,
2827
+ source: obs2.source
2828
+ });
2829
+ }
2830
+ }
2831
+ return out;
2832
+ }
2833
+ async function fetchForMode(station, fromDate, toDate, mode) {
2834
+ switch (mode) {
2835
+ case "iem_only":
2836
+ return fetchIemAsosObservations(station, fromDate, toDate);
2837
+ case "awc_only":
2838
+ return fetchAwcObservations(station, fromDate, toDate);
2839
+ case "live_v1": {
2840
+ const [iem, awc] = await Promise.all([
2841
+ fetchIemAsosObservations(station, fromDate, toDate),
2842
+ fetchAwcObservations(station, fromDate, toDate).catch(() => [])
2843
+ ]);
2844
+ return [...iem, ...awc];
2845
+ }
2846
+ default: {
2847
+ const _exhaustive = mode;
2848
+ throw new TypeError(`dailyExtremes: unknown merge mode "${String(_exhaustive)}"`);
2849
+ }
2850
+ }
2851
+ }
2852
+ function projectRow(station, d, isUs) {
2853
+ const lowCoverage = d.nObs < LOW_COVERAGE_THRESHOLD2;
2854
+ const decimals = isUs ? 0 : 1;
2855
+ const precipIn = d.precipMm !== null && d.precipMm !== void 0 ? roundHalfUp2(d.precipMm / 25.4, 2) : null;
2856
+ if (lowCoverage) {
2857
+ return {
2858
+ date: d.localDate,
2859
+ station,
2860
+ tmin_f: null,
2861
+ tmax_f: null,
2862
+ tmean_f: null,
2863
+ precip_in: precipIn,
2864
+ low_coverage: true,
2865
+ n_obs: d.nObs
2866
+ };
2867
+ }
2868
+ return {
2869
+ date: d.localDate,
2870
+ station,
2871
+ tmin_f: d.tempMinF !== null ? roundHalfUp2(d.tempMinF, decimals) : null,
2872
+ tmax_f: d.tempMaxF !== null ? roundHalfUp2(d.tempMaxF, decimals) : null,
2873
+ tmean_f: d.tempMeanC !== null ? roundHalfUp2(cToF2(d.tempMeanC), decimals) : null,
2874
+ precip_in: precipIn,
2875
+ low_coverage: false,
2876
+ n_obs: d.nObs
2877
+ };
2878
+ }
2879
+ async function dailyExtremes(station, fromDate, toDate, opts = {}) {
2880
+ const { tz, isUs } = lookupStation(station);
2881
+ const merge = opts.merge ?? "live_v1";
2882
+ const fetchFrom = addUtcDays(fromDate, -1);
2883
+ const fetchTo = addUtcDays(toDate, 1);
2884
+ const rows = await fetchForMode(station, fetchFrom, fetchTo, merge);
2885
+ const extremes = internationalDailyExtremes(rows, {
2886
+ stationTz: tz,
2887
+ precision: isUs ? 1 : 0
2888
+ });
2889
+ return extremes.filter((d) => d.localDate >= fromDate && d.localDate <= toDate).map((d) => projectRow(station.toUpperCase(), d, isUs));
2890
+ }
2891
+
2892
+ // ../weather/src/obs.ts
2893
+ function daysBetween(fromDate, toDate) {
2894
+ const from = Date.UTC(
2895
+ Number.parseInt(fromDate.slice(0, 4), 10),
2896
+ Number.parseInt(fromDate.slice(5, 7), 10) - 1,
2897
+ Number.parseInt(fromDate.slice(8, 10), 10)
2898
+ );
2899
+ const to = Date.UTC(
2900
+ Number.parseInt(toDate.slice(0, 4), 10),
2901
+ Number.parseInt(toDate.slice(5, 7), 10) - 1,
2902
+ Number.parseInt(toDate.slice(8, 10), 10)
1006
2903
  );
2904
+ return Math.round((to - from) / (24 * 60 * 60 * 1e3)) + 1;
2905
+ }
2906
+ function resolveAutoStrategy(fromDate, toDate) {
2907
+ return daysBetween(fromDate, toDate) <= 7 ? "exact_window" : "warm_cache";
2908
+ }
2909
+ function inchesToMm(inches) {
2910
+ if (inches === null || inches === void 0) return null;
2911
+ return inches * 25.4;
2912
+ }
2913
+ function mbToInhg(mb) {
2914
+ if (mb === null || mb === void 0) return null;
2915
+ return mb * 0.029529983071445;
2916
+ }
2917
+ function fromObservation(o) {
2918
+ const tempC = o.temp_c ?? null;
2919
+ const dewC = o.dewpoint_c ?? null;
2920
+ return {
2921
+ station: o.station_code,
2922
+ observed_at: o.observed_at,
2923
+ source: o.source,
2924
+ temp_c: tempC,
2925
+ temp_f: tempC !== null ? tempC * (9 / 5) + 32 : null,
2926
+ dewpoint_c: dewC,
2927
+ dewpoint_f: dewC !== null ? dewC * (9 / 5) + 32 : null,
2928
+ wind_speed_kts: o.wind_speed_kt ?? null,
2929
+ wind_direction_deg: o.wind_dir_degrees ?? null,
2930
+ pressure_inhg: mbToInhg(o.sea_level_pressure_mb),
2931
+ precip_mm_1h: inchesToMm(o.precip_1hr_inches),
2932
+ raw_metar: o.raw_metar ?? null
2933
+ };
2934
+ }
2935
+ async function fetchIemForWindow(station, fromDate, toDate, resolvedStrategy) {
2936
+ const out = [];
2937
+ if (resolvedStrategy === "exact_window") {
2938
+ const chunks = await downloadIemAsos(station, fromDate, toDate, {
2939
+ reportType: 3,
2940
+ politenessMs: 1e3
2941
+ });
2942
+ for (const chunk of chunks) {
2943
+ const rows = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
2944
+ for (const r of rows) {
2945
+ const d = r.observed_at.slice(0, 10);
2946
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(r));
2947
+ }
2948
+ }
2949
+ return out;
2950
+ }
2951
+ const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);
2952
+ const toYear = Number.parseInt(toDate.slice(0, 4), 10);
2953
+ for (let year = fromYear; year <= toYear; year++) {
2954
+ const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {
2955
+ reportType: 3,
2956
+ politenessMs: 1e3
2957
+ });
2958
+ for (const chunk of chunks) {
2959
+ const rows = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
2960
+ for (const r of rows) {
2961
+ const d = r.observed_at.slice(0, 10);
2962
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(r));
2963
+ }
2964
+ }
2965
+ }
2966
+ return out;
2967
+ }
2968
+ async function fetchAwcForWindow(station, fromDate, toDate) {
2969
+ const raw = await fetchAwcMetars([station]);
2970
+ const out = [];
2971
+ for (const r of raw) {
2972
+ const obs2 = awcToObservation(r);
2973
+ if (obs2 === null) continue;
2974
+ const d = obs2.observed_at.slice(0, 10);
2975
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(obs2));
2976
+ }
2977
+ return out;
2978
+ }
2979
+ async function fetchByStrategy(station, fromDate, toDate, resolvedStrategy, source) {
2980
+ const wantsIem = source === null || source === void 0 || source === "iem";
2981
+ const wantsAwc = source === null || source === void 0 || source === "awc";
2982
+ const tasks = [];
2983
+ if (wantsIem) tasks.push(fetchIemForWindow(station, fromDate, toDate, resolvedStrategy));
2984
+ if (wantsAwc) tasks.push(fetchAwcForWindow(station, fromDate, toDate).catch(() => []));
2985
+ const results = await Promise.all(tasks);
2986
+ return results.flat();
2987
+ }
2988
+ async function obs(station, fromDate, toDate, opts = {}) {
2989
+ const strategy = opts.strategy ?? "auto";
2990
+ const source = opts.source ?? null;
2991
+ if (source === "ghcnh") {
2992
+ throw new DataAvailabilityError({
2993
+ reason: "model_unavailable",
2994
+ source: "obs.ghcnh",
2995
+ hint: "source='ghcnh' is a valid Python `obs()` filter but the GHCNh fetcher path is not yet wired in the TypeScript SDK. Use source='iem' or source='awc' (or omit `source` for merged) until the TS GHCNh fetcher ships."
2996
+ });
2997
+ }
2998
+ if (strategy === "hosted") {
2999
+ throw new DataAvailabilityError({
3000
+ reason: "model_unavailable",
3001
+ source: "obs-hosted-stub",
3002
+ hint: "hosted ingest API ships in v0.2.x \u2014 use strategy='exact_window' or 'warm_cache' for v1.x. See https://mostlyright.md/docs/sdk/typescript/ingest-strategies"
3003
+ });
3004
+ }
3005
+ let resolved;
3006
+ if (strategy === "auto") {
3007
+ resolved = resolveAutoStrategy(fromDate, toDate);
3008
+ } else if (strategy === "exact_window" || strategy === "warm_cache") {
3009
+ resolved = strategy;
3010
+ } else {
3011
+ throw new TypeError(
3012
+ `obs: unknown strategy "${String(strategy)}" \u2014 expected one of: auto, exact_window, warm_cache, hosted`
3013
+ );
3014
+ }
3015
+ return fetchByStrategy(station, fromDate, toDate, resolved, source);
1007
3016
  }
1008
3017
 
1009
3018
  // ../weather/src/index.ts
@@ -1107,6 +3116,10 @@ var KALSHI_SETTLEMENT_STATIONS = {
1107
3116
  SLC: {
1108
3117
  citation: "https://kalshi.com/markets/khighslc (Salt Lake City International)",
1109
3118
  station: "KSLC"
3119
+ },
3120
+ TLV: {
3121
+ citation: "https://kalshi.com/markets/kxhightlv (Harry Reid/McCarran; settles vs NWS CLILAS)",
3122
+ station: "KLAS"
1110
3123
  }
1111
3124
  };
1112
3125
  var KNOWN_WRONG_STATIONS = /* @__PURE__ */ new Set([
@@ -1692,10 +3705,11 @@ replaceTraps((oldTraps) => ({
1692
3705
  }
1693
3706
  }));
1694
3707
 
1695
- // ../core/dist/internal/chunk-PKJXHY27.mjs
3708
+ // ../core/dist/internal/chunk-QDQSYUFW.mjs
1696
3709
  function lockKeyFor(key) {
1697
3710
  return `mostlyright:cache:lock:${key}`;
1698
3711
  }
3712
+ var CACHE_SCHEMA_VERSION = "v2-phase18-integer-f";
1699
3713
  var MemoryStore = class {
1700
3714
  #entries = /* @__PURE__ */ new Map();
1701
3715
  #chain = /* @__PURE__ */ new Map();
@@ -1842,7 +3856,71 @@ var IndexedDBStore = class {
1842
3856
  return next;
1843
3857
  }
1844
3858
  };
1845
- var STATIONS = [
3859
+ var VERSION_FIELD = "_cache_schema_version";
3860
+ function isVersionedEntry(v) {
3861
+ if (v === null || typeof v !== "object") return false;
3862
+ if (!(VERSION_FIELD in v)) return false;
3863
+ return typeof v[VERSION_FIELD] === "string";
3864
+ }
3865
+ function hasListKeys(s) {
3866
+ return typeof s.listKeys === "function";
3867
+ }
3868
+ var VersionedCacheStore = class {
3869
+ #inner;
3870
+ #version;
3871
+ constructor(inner, version4) {
3872
+ if (typeof version4 !== "string" || version4.length === 0) {
3873
+ throw new TypeError("versionedCacheStore: version must be a non-empty string");
3874
+ }
3875
+ this.#inner = inner;
3876
+ this.#version = version4;
3877
+ }
3878
+ /**
3879
+ * Test/diagnostics seam: return the underlying store so tests can assert
3880
+ * which concrete backend `defaultCacheStore()` selected. NOT a production
3881
+ * API — production code MUST use the wrapped store so version
3882
+ * invalidation fires on stale reads.
3883
+ *
3884
+ * @internal
3885
+ */
3886
+ __peekInner() {
3887
+ return this.#inner;
3888
+ }
3889
+ async get(key) {
3890
+ const raw = await this.#inner.get(key);
3891
+ if (raw === null) return null;
3892
+ if (!isVersionedEntry(raw)) {
3893
+ return null;
3894
+ }
3895
+ if (raw._cache_schema_version !== this.#version) {
3896
+ return null;
3897
+ }
3898
+ return raw.value;
3899
+ }
3900
+ async set(key, value, opts) {
3901
+ const wrapped = {
3902
+ value,
3903
+ [VERSION_FIELD]: this.#version
3904
+ };
3905
+ await this.#inner.set(key, wrapped, opts);
3906
+ }
3907
+ async delete(key) {
3908
+ await this.#inner.delete(key);
3909
+ }
3910
+ async withLock(key, fn) {
3911
+ return this.#inner.withLock(key, fn);
3912
+ }
3913
+ async listKeys(prefix) {
3914
+ if (hasListKeys(this.#inner)) {
3915
+ return this.#inner.listKeys(prefix);
3916
+ }
3917
+ return Object.freeze([]);
3918
+ }
3919
+ };
3920
+ function versionedCacheStore(inner, version4) {
3921
+ return new VersionedCacheStore(inner, version4);
3922
+ }
3923
+ var STATIONS3 = [
1846
3924
  {
1847
3925
  code: "EDDB",
1848
3926
  country: "DE",
@@ -1851,7 +3929,10 @@ var STATIONS = [
1851
3929
  latitude: 52.3667,
1852
3930
  longitude: 13.5033,
1853
3931
  name: "Berlin Brandenburg",
1854
- tz: "Europe/Berlin"
3932
+ tz: "Europe/Berlin",
3933
+ venues: [
3934
+ "polymarket"
3935
+ ]
1855
3936
  },
1856
3937
  {
1857
3938
  code: "EDDF",
@@ -1861,7 +3942,10 @@ var STATIONS = [
1861
3942
  latitude: 50.0379,
1862
3943
  longitude: 8.5622,
1863
3944
  name: "Frankfurt am Main",
1864
- tz: "Europe/Berlin"
3945
+ tz: "Europe/Berlin",
3946
+ venues: [
3947
+ "polymarket"
3948
+ ]
1865
3949
  },
1866
3950
  {
1867
3951
  code: "EDDM",
@@ -1871,7 +3955,10 @@ var STATIONS = [
1871
3955
  latitude: 48.3538,
1872
3956
  longitude: 11.7861,
1873
3957
  name: "Munich Franz Josef Strauss",
1874
- tz: "Europe/Berlin"
3958
+ tz: "Europe/Berlin",
3959
+ venues: [
3960
+ "polymarket"
3961
+ ]
1875
3962
  },
1876
3963
  {
1877
3964
  code: "EFHK",
@@ -1881,7 +3968,10 @@ var STATIONS = [
1881
3968
  latitude: 60.3172,
1882
3969
  longitude: 24.9633,
1883
3970
  name: "Helsinki-Vantaa",
1884
- tz: "Europe/Helsinki"
3971
+ tz: "Europe/Helsinki",
3972
+ venues: [
3973
+ "polymarket"
3974
+ ]
1885
3975
  },
1886
3976
  {
1887
3977
  code: "EGKK",
@@ -1891,7 +3981,10 @@ var STATIONS = [
1891
3981
  latitude: 51.1481,
1892
3982
  longitude: -0.1903,
1893
3983
  name: "London Gatwick",
1894
- tz: "Europe/London"
3984
+ tz: "Europe/London",
3985
+ venues: [
3986
+ "polymarket"
3987
+ ]
1895
3988
  },
1896
3989
  {
1897
3990
  code: "EGLL",
@@ -1901,7 +3994,10 @@ var STATIONS = [
1901
3994
  latitude: 51.4706,
1902
3995
  longitude: -0.4619,
1903
3996
  name: "London Heathrow",
1904
- tz: "Europe/London"
3997
+ tz: "Europe/London",
3998
+ venues: [
3999
+ "polymarket"
4000
+ ]
1905
4001
  },
1906
4002
  {
1907
4003
  code: "EHAM",
@@ -1911,7 +4007,10 @@ var STATIONS = [
1911
4007
  latitude: 52.3086,
1912
4008
  longitude: 4.7639,
1913
4009
  name: "Amsterdam Schiphol",
1914
- tz: "Europe/Amsterdam"
4010
+ tz: "Europe/Amsterdam",
4011
+ venues: [
4012
+ "polymarket"
4013
+ ]
1915
4014
  },
1916
4015
  {
1917
4016
  code: "EKCH",
@@ -1921,7 +4020,10 @@ var STATIONS = [
1921
4020
  latitude: 55.6181,
1922
4021
  longitude: 12.6561,
1923
4022
  name: "Copenhagen Kastrup",
1924
- tz: "Europe/Copenhagen"
4023
+ tz: "Europe/Copenhagen",
4024
+ venues: [
4025
+ "polymarket"
4026
+ ]
1925
4027
  },
1926
4028
  {
1927
4029
  code: "EPWA",
@@ -1931,7 +4033,10 @@ var STATIONS = [
1931
4033
  latitude: 52.1657,
1932
4034
  longitude: 20.9671,
1933
4035
  name: "Warsaw Chopin",
1934
- tz: "Europe/Warsaw"
4036
+ tz: "Europe/Warsaw",
4037
+ venues: [
4038
+ "polymarket"
4039
+ ]
1935
4040
  },
1936
4041
  {
1937
4042
  code: "ESSA",
@@ -1941,7 +4046,10 @@ var STATIONS = [
1941
4046
  latitude: 59.6519,
1942
4047
  longitude: 17.9186,
1943
4048
  name: "Stockholm Arlanda",
1944
- tz: "Europe/Stockholm"
4049
+ tz: "Europe/Stockholm",
4050
+ venues: [
4051
+ "polymarket"
4052
+ ]
1945
4053
  },
1946
4054
  {
1947
4055
  code: "ATL",
@@ -1951,7 +4059,11 @@ var STATIONS = [
1951
4059
  latitude: 33.6407,
1952
4060
  longitude: -84.4277,
1953
4061
  name: "Hartsfield-Jackson Atlanta International",
1954
- tz: "America/New_York"
4062
+ tz: "America/New_York",
4063
+ venues: [
4064
+ "kalshi",
4065
+ "polymarket"
4066
+ ]
1955
4067
  },
1956
4068
  {
1957
4069
  code: "AUS",
@@ -1961,7 +4073,24 @@ var STATIONS = [
1961
4073
  latitude: 30.1975,
1962
4074
  longitude: -97.6664,
1963
4075
  name: "Austin-Bergstrom International",
1964
- tz: "America/Chicago"
4076
+ tz: "America/Chicago",
4077
+ venues: [
4078
+ "kalshi",
4079
+ "polymarket"
4080
+ ]
4081
+ },
4082
+ {
4083
+ code: "BNA",
4084
+ country: "US",
4085
+ ghcnh_id: "USW00013897",
4086
+ icao: "KBNA",
4087
+ latitude: 36.1245,
4088
+ longitude: -86.6782,
4089
+ name: "Nashville International",
4090
+ tz: "America/Chicago",
4091
+ venues: [
4092
+ "kalshi"
4093
+ ]
1965
4094
  },
1966
4095
  {
1967
4096
  code: "BOS",
@@ -1971,7 +4100,24 @@ var STATIONS = [
1971
4100
  latitude: 42.3656,
1972
4101
  longitude: -71.0096,
1973
4102
  name: "Boston Logan International",
1974
- tz: "America/New_York"
4103
+ tz: "America/New_York",
4104
+ venues: [
4105
+ "kalshi",
4106
+ "polymarket"
4107
+ ]
4108
+ },
4109
+ {
4110
+ code: "CVG",
4111
+ country: "US",
4112
+ ghcnh_id: "USW00093814",
4113
+ icao: "KCVG",
4114
+ latitude: 39.0488,
4115
+ longitude: -84.6678,
4116
+ name: "Cincinnati/Northern Kentucky International",
4117
+ tz: "America/New_York",
4118
+ venues: [
4119
+ "kalshi"
4120
+ ]
1975
4121
  },
1976
4122
  {
1977
4123
  code: "DCA",
@@ -1981,7 +4127,11 @@ var STATIONS = [
1981
4127
  latitude: 38.8512,
1982
4128
  longitude: -77.0402,
1983
4129
  name: "Washington Reagan National",
1984
- tz: "America/New_York"
4130
+ tz: "America/New_York",
4131
+ venues: [
4132
+ "kalshi",
4133
+ "polymarket"
4134
+ ]
1985
4135
  },
1986
4136
  {
1987
4137
  code: "DEN",
@@ -1991,7 +4141,11 @@ var STATIONS = [
1991
4141
  latitude: 39.8561,
1992
4142
  longitude: -104.6737,
1993
4143
  name: "Denver International",
1994
- tz: "America/Denver"
4144
+ tz: "America/Denver",
4145
+ venues: [
4146
+ "kalshi",
4147
+ "polymarket"
4148
+ ]
1995
4149
  },
1996
4150
  {
1997
4151
  code: "DFW",
@@ -2001,7 +4155,25 @@ var STATIONS = [
2001
4155
  latitude: 32.8998,
2002
4156
  longitude: -97.0403,
2003
4157
  name: "Dallas-Fort Worth International",
2004
- tz: "America/Chicago"
4158
+ tz: "America/Chicago",
4159
+ venues: [
4160
+ "kalshi",
4161
+ "polymarket"
4162
+ ]
4163
+ },
4164
+ {
4165
+ code: "DTW",
4166
+ country: "US",
4167
+ ghcnh_id: "USW00094847",
4168
+ icao: "KDTW",
4169
+ latitude: 42.2124,
4170
+ longitude: -83.3534,
4171
+ name: "Detroit Metropolitan Wayne County",
4172
+ tz: "America/New_York",
4173
+ venues: [
4174
+ "kalshi",
4175
+ "polymarket"
4176
+ ]
2005
4177
  },
2006
4178
  {
2007
4179
  code: "HOU",
@@ -2011,7 +4183,22 @@ var STATIONS = [
2011
4183
  latitude: 29.6454,
2012
4184
  longitude: -95.2789,
2013
4185
  name: "Houston Hobby",
2014
- tz: "America/Chicago"
4186
+ tz: "America/Chicago",
4187
+ venues: []
4188
+ },
4189
+ {
4190
+ code: "IAH",
4191
+ country: "US",
4192
+ ghcnh_id: "USW00012960",
4193
+ icao: "KIAH",
4194
+ latitude: 29.9844,
4195
+ longitude: -95.3414,
4196
+ name: "Houston George Bush Intercontinental",
4197
+ tz: "America/Chicago",
4198
+ venues: [
4199
+ "kalshi",
4200
+ "polymarket"
4201
+ ]
2015
4202
  },
2016
4203
  {
2017
4204
  code: "LAS",
@@ -2021,7 +4208,10 @@ var STATIONS = [
2021
4208
  latitude: 36.084,
2022
4209
  longitude: -115.1537,
2023
4210
  name: "Harry Reid (McCarran) International",
2024
- tz: "America/Los_Angeles"
4211
+ tz: "America/Los_Angeles",
4212
+ venues: [
4213
+ "kalshi"
4214
+ ]
2025
4215
  },
2026
4216
  {
2027
4217
  code: "LAX",
@@ -2031,7 +4221,11 @@ var STATIONS = [
2031
4221
  latitude: 33.9425,
2032
4222
  longitude: -118.4081,
2033
4223
  name: "Los Angeles International",
2034
- tz: "America/Los_Angeles"
4224
+ tz: "America/Los_Angeles",
4225
+ venues: [
4226
+ "kalshi",
4227
+ "polymarket"
4228
+ ]
2035
4229
  },
2036
4230
  {
2037
4231
  code: "MDW",
@@ -2041,7 +4235,10 @@ var STATIONS = [
2041
4235
  latitude: 41.7868,
2042
4236
  longitude: -87.7522,
2043
4237
  name: "Chicago Midway International",
2044
- tz: "America/Chicago"
4238
+ tz: "America/Chicago",
4239
+ venues: [
4240
+ "kalshi"
4241
+ ]
2045
4242
  },
2046
4243
  {
2047
4244
  code: "MIA",
@@ -2051,7 +4248,11 @@ var STATIONS = [
2051
4248
  latitude: 25.7959,
2052
4249
  longitude: -80.287,
2053
4250
  name: "Miami International",
2054
- tz: "America/New_York"
4251
+ tz: "America/New_York",
4252
+ venues: [
4253
+ "kalshi",
4254
+ "polymarket"
4255
+ ]
2055
4256
  },
2056
4257
  {
2057
4258
  code: "MSP",
@@ -2061,7 +4262,11 @@ var STATIONS = [
2061
4262
  latitude: 44.8848,
2062
4263
  longitude: -93.2223,
2063
4264
  name: "Minneapolis-St Paul International",
2064
- tz: "America/Chicago"
4265
+ tz: "America/Chicago",
4266
+ venues: [
4267
+ "kalshi",
4268
+ "polymarket"
4269
+ ]
2065
4270
  },
2066
4271
  {
2067
4272
  code: "MSY",
@@ -2071,7 +4276,8 @@ var STATIONS = [
2071
4276
  latitude: 29.9934,
2072
4277
  longitude: -90.258,
2073
4278
  name: "New Orleans Louis Armstrong International",
2074
- tz: "America/Chicago"
4279
+ tz: "America/Chicago",
4280
+ venues: []
2075
4281
  },
2076
4282
  {
2077
4283
  code: "NYC",
@@ -2081,7 +4287,10 @@ var STATIONS = [
2081
4287
  latitude: 40.7789,
2082
4288
  longitude: -73.9692,
2083
4289
  name: "Central Park, New York",
2084
- tz: "America/New_York"
4290
+ tz: "America/New_York",
4291
+ venues: [
4292
+ "kalshi"
4293
+ ]
2085
4294
  },
2086
4295
  {
2087
4296
  code: "OKC",
@@ -2091,7 +4300,8 @@ var STATIONS = [
2091
4300
  latitude: 35.3931,
2092
4301
  longitude: -97.6007,
2093
4302
  name: "Oklahoma City Will Rogers World",
2094
- tz: "America/Chicago"
4303
+ tz: "America/Chicago",
4304
+ venues: []
2095
4305
  },
2096
4306
  {
2097
4307
  code: "PHL",
@@ -2101,7 +4311,11 @@ var STATIONS = [
2101
4311
  latitude: 39.8721,
2102
4312
  longitude: -75.2411,
2103
4313
  name: "Philadelphia International",
2104
- tz: "America/New_York"
4314
+ tz: "America/New_York",
4315
+ venues: [
4316
+ "kalshi",
4317
+ "polymarket"
4318
+ ]
2105
4319
  },
2106
4320
  {
2107
4321
  code: "PHX",
@@ -2111,7 +4325,11 @@ var STATIONS = [
2111
4325
  latitude: 33.4373,
2112
4326
  longitude: -112.0078,
2113
4327
  name: "Phoenix Sky Harbor International",
2114
- tz: "America/Phoenix"
4328
+ tz: "America/Phoenix",
4329
+ venues: [
4330
+ "kalshi",
4331
+ "polymarket"
4332
+ ]
2115
4333
  },
2116
4334
  {
2117
4335
  code: "SAT",
@@ -2121,7 +4339,8 @@ var STATIONS = [
2121
4339
  latitude: 29.5337,
2122
4340
  longitude: -98.4698,
2123
4341
  name: "San Antonio International",
2124
- tz: "America/Chicago"
4342
+ tz: "America/Chicago",
4343
+ venues: []
2125
4344
  },
2126
4345
  {
2127
4346
  code: "SEA",
@@ -2131,7 +4350,11 @@ var STATIONS = [
2131
4350
  latitude: 47.4502,
2132
4351
  longitude: -122.3088,
2133
4352
  name: "Seattle-Tacoma International",
2134
- tz: "America/Los_Angeles"
4353
+ tz: "America/Los_Angeles",
4354
+ venues: [
4355
+ "kalshi",
4356
+ "polymarket"
4357
+ ]
2135
4358
  },
2136
4359
  {
2137
4360
  code: "SFO",
@@ -2141,7 +4364,24 @@ var STATIONS = [
2141
4364
  latitude: 37.6213,
2142
4365
  longitude: -122.379,
2143
4366
  name: "San Francisco International",
2144
- tz: "America/Los_Angeles"
4367
+ tz: "America/Los_Angeles",
4368
+ venues: [
4369
+ "kalshi",
4370
+ "polymarket"
4371
+ ]
4372
+ },
4373
+ {
4374
+ code: "SLC",
4375
+ country: "US",
4376
+ ghcnh_id: "USW00024127",
4377
+ icao: "KSLC",
4378
+ latitude: 40.7884,
4379
+ longitude: -111.9778,
4380
+ name: "Salt Lake City International",
4381
+ tz: "America/Denver",
4382
+ venues: [
4383
+ "kalshi"
4384
+ ]
2145
4385
  },
2146
4386
  {
2147
4387
  code: "LEBL",
@@ -2151,7 +4391,10 @@ var STATIONS = [
2151
4391
  latitude: 41.2974,
2152
4392
  longitude: 2.0833,
2153
4393
  name: "Barcelona El Prat",
2154
- tz: "Europe/Madrid"
4394
+ tz: "Europe/Madrid",
4395
+ venues: [
4396
+ "polymarket"
4397
+ ]
2155
4398
  },
2156
4399
  {
2157
4400
  code: "LEMD",
@@ -2161,7 +4404,10 @@ var STATIONS = [
2161
4404
  latitude: 40.4719,
2162
4405
  longitude: -3.5626,
2163
4406
  name: "Madrid Barajas",
2164
- tz: "Europe/Madrid"
4407
+ tz: "Europe/Madrid",
4408
+ venues: [
4409
+ "polymarket"
4410
+ ]
2165
4411
  },
2166
4412
  {
2167
4413
  code: "LFPB",
@@ -2171,7 +4417,10 @@ var STATIONS = [
2171
4417
  latitude: 48.9694,
2172
4418
  longitude: 2.4414,
2173
4419
  name: "Paris Le Bourget",
2174
- tz: "Europe/Paris"
4420
+ tz: "Europe/Paris",
4421
+ venues: [
4422
+ "polymarket"
4423
+ ]
2175
4424
  },
2176
4425
  {
2177
4426
  code: "LFPG",
@@ -2181,7 +4430,10 @@ var STATIONS = [
2181
4430
  latitude: 49.0097,
2182
4431
  longitude: 2.5479,
2183
4432
  name: "Paris Charles de Gaulle",
2184
- tz: "Europe/Paris"
4433
+ tz: "Europe/Paris",
4434
+ venues: [
4435
+ "polymarket"
4436
+ ]
2185
4437
  },
2186
4438
  {
2187
4439
  code: "LFPO",
@@ -2191,7 +4443,10 @@ var STATIONS = [
2191
4443
  latitude: 48.7233,
2192
4444
  longitude: 2.3794,
2193
4445
  name: "Paris Orly",
2194
- tz: "Europe/Paris"
4446
+ tz: "Europe/Paris",
4447
+ venues: [
4448
+ "polymarket"
4449
+ ]
2195
4450
  },
2196
4451
  {
2197
4452
  code: "LIMC",
@@ -2201,7 +4456,10 @@ var STATIONS = [
2201
4456
  latitude: 45.6306,
2202
4457
  longitude: 8.7281,
2203
4458
  name: "Milan Malpensa",
2204
- tz: "Europe/Rome"
4459
+ tz: "Europe/Rome",
4460
+ venues: [
4461
+ "polymarket"
4462
+ ]
2205
4463
  },
2206
4464
  {
2207
4465
  code: "LIRF",
@@ -2211,7 +4469,10 @@ var STATIONS = [
2211
4469
  latitude: 41.8003,
2212
4470
  longitude: 12.2389,
2213
4471
  name: "Rome Fiumicino",
2214
- tz: "Europe/Rome"
4472
+ tz: "Europe/Rome",
4473
+ venues: [
4474
+ "polymarket"
4475
+ ]
2215
4476
  },
2216
4477
  {
2217
4478
  code: "LOWW",
@@ -2221,7 +4482,10 @@ var STATIONS = [
2221
4482
  latitude: 48.1103,
2222
4483
  longitude: 16.5697,
2223
4484
  name: "Vienna International",
2224
- tz: "Europe/Vienna"
4485
+ tz: "Europe/Vienna",
4486
+ venues: [
4487
+ "polymarket"
4488
+ ]
2225
4489
  },
2226
4490
  {
2227
4491
  code: "LSZH",
@@ -2231,7 +4495,10 @@ var STATIONS = [
2231
4495
  latitude: 47.4647,
2232
4496
  longitude: 8.5492,
2233
4497
  name: "Zurich",
2234
- tz: "Europe/Zurich"
4498
+ tz: "Europe/Zurich",
4499
+ venues: [
4500
+ "polymarket"
4501
+ ]
2235
4502
  },
2236
4503
  {
2237
4504
  code: "NZAA",
@@ -2241,7 +4508,10 @@ var STATIONS = [
2241
4508
  latitude: -37.0081,
2242
4509
  longitude: 174.7917,
2243
4510
  name: "Auckland",
2244
- tz: "Pacific/Auckland"
4511
+ tz: "Pacific/Auckland",
4512
+ venues: [
4513
+ "polymarket"
4514
+ ]
2245
4515
  },
2246
4516
  {
2247
4517
  code: "NZWN",
@@ -2251,7 +4521,10 @@ var STATIONS = [
2251
4521
  latitude: -41.3272,
2252
4522
  longitude: 174.8053,
2253
4523
  name: "Wellington",
2254
- tz: "Pacific/Auckland"
4524
+ tz: "Pacific/Auckland",
4525
+ venues: [
4526
+ "polymarket"
4527
+ ]
2255
4528
  },
2256
4529
  {
2257
4530
  code: "OERK",
@@ -2261,7 +4534,10 @@ var STATIONS = [
2261
4534
  latitude: 24.9576,
2262
4535
  longitude: 46.6988,
2263
4536
  name: "Riyadh King Khalid International",
2264
- tz: "Asia/Riyadh"
4537
+ tz: "Asia/Riyadh",
4538
+ venues: [
4539
+ "polymarket"
4540
+ ]
2265
4541
  },
2266
4542
  {
2267
4543
  code: "OMDB",
@@ -2271,7 +4547,10 @@ var STATIONS = [
2271
4547
  latitude: 25.2532,
2272
4548
  longitude: 55.3657,
2273
4549
  name: "Dubai International",
2274
- tz: "Asia/Dubai"
4550
+ tz: "Asia/Dubai",
4551
+ venues: [
4552
+ "polymarket"
4553
+ ]
2275
4554
  },
2276
4555
  {
2277
4556
  code: "OTHH",
@@ -2281,7 +4560,10 @@ var STATIONS = [
2281
4560
  latitude: 25.2731,
2282
4561
  longitude: 51.608,
2283
4562
  name: "Doha Hamad International",
2284
- tz: "Asia/Qatar"
4563
+ tz: "Asia/Qatar",
4564
+ venues: [
4565
+ "polymarket"
4566
+ ]
2285
4567
  },
2286
4568
  {
2287
4569
  code: "RCTP",
@@ -2291,7 +4573,10 @@ var STATIONS = [
2291
4573
  latitude: 25.0777,
2292
4574
  longitude: 121.2328,
2293
4575
  name: "Taipei Taoyuan",
2294
- tz: "Asia/Taipei"
4576
+ tz: "Asia/Taipei",
4577
+ venues: [
4578
+ "polymarket"
4579
+ ]
2295
4580
  },
2296
4581
  {
2297
4582
  code: "RJAA",
@@ -2301,7 +4586,10 @@ var STATIONS = [
2301
4586
  latitude: 35.7647,
2302
4587
  longitude: 140.3864,
2303
4588
  name: "Tokyo Narita",
2304
- tz: "Asia/Tokyo"
4589
+ tz: "Asia/Tokyo",
4590
+ venues: [
4591
+ "polymarket"
4592
+ ]
2305
4593
  },
2306
4594
  {
2307
4595
  code: "RJTT",
@@ -2311,7 +4599,10 @@ var STATIONS = [
2311
4599
  latitude: 35.5522,
2312
4600
  longitude: 139.78,
2313
4601
  name: "Tokyo Haneda",
2314
- tz: "Asia/Tokyo"
4602
+ tz: "Asia/Tokyo",
4603
+ venues: [
4604
+ "polymarket"
4605
+ ]
2315
4606
  },
2316
4607
  {
2317
4608
  code: "RKSI",
@@ -2321,7 +4612,10 @@ var STATIONS = [
2321
4612
  latitude: 37.4691,
2322
4613
  longitude: 126.4505,
2323
4614
  name: "Seoul Incheon",
2324
- tz: "Asia/Seoul"
4615
+ tz: "Asia/Seoul",
4616
+ venues: [
4617
+ "polymarket"
4618
+ ]
2325
4619
  },
2326
4620
  {
2327
4621
  code: "SAEZ",
@@ -2331,7 +4625,10 @@ var STATIONS = [
2331
4625
  latitude: -34.8222,
2332
4626
  longitude: -58.5358,
2333
4627
  name: "Buenos Aires Ezeiza",
2334
- tz: "America/Argentina/Buenos_Aires"
4628
+ tz: "America/Argentina/Buenos_Aires",
4629
+ venues: [
4630
+ "polymarket"
4631
+ ]
2335
4632
  },
2336
4633
  {
2337
4634
  code: "SBGR",
@@ -2341,7 +4638,10 @@ var STATIONS = [
2341
4638
  latitude: -23.4356,
2342
4639
  longitude: -46.4731,
2343
4640
  name: "S\xE3o Paulo Guarulhos",
2344
- tz: "America/Sao_Paulo"
4641
+ tz: "America/Sao_Paulo",
4642
+ venues: [
4643
+ "polymarket"
4644
+ ]
2345
4645
  },
2346
4646
  {
2347
4647
  code: "UUEE",
@@ -2351,7 +4651,10 @@ var STATIONS = [
2351
4651
  latitude: 55.9728,
2352
4652
  longitude: 37.4147,
2353
4653
  name: "Moscow Sheremetyevo",
2354
- tz: "Europe/Moscow"
4654
+ tz: "Europe/Moscow",
4655
+ venues: [
4656
+ "polymarket"
4657
+ ]
2355
4658
  },
2356
4659
  {
2357
4660
  code: "VABB",
@@ -2361,7 +4664,10 @@ var STATIONS = [
2361
4664
  latitude: 19.0887,
2362
4665
  longitude: 72.8679,
2363
4666
  name: "Mumbai Chhatrapati Shivaji",
2364
- tz: "Asia/Kolkata"
4667
+ tz: "Asia/Kolkata",
4668
+ venues: [
4669
+ "polymarket"
4670
+ ]
2365
4671
  },
2366
4672
  {
2367
4673
  code: "VHHH",
@@ -2371,7 +4677,10 @@ var STATIONS = [
2371
4677
  latitude: 22.308,
2372
4678
  longitude: 113.9185,
2373
4679
  name: "Hong Kong International",
2374
- tz: "Asia/Hong_Kong"
4680
+ tz: "Asia/Hong_Kong",
4681
+ venues: [
4682
+ "polymarket"
4683
+ ]
2375
4684
  },
2376
4685
  {
2377
4686
  code: "VIDP",
@@ -2381,7 +4690,10 @@ var STATIONS = [
2381
4690
  latitude: 28.5562,
2382
4691
  longitude: 77.1,
2383
4692
  name: "Delhi Indira Gandhi",
2384
- tz: "Asia/Kolkata"
4693
+ tz: "Asia/Kolkata",
4694
+ venues: [
4695
+ "polymarket"
4696
+ ]
2385
4697
  },
2386
4698
  {
2387
4699
  code: "VTBS",
@@ -2391,7 +4703,10 @@ var STATIONS = [
2391
4703
  latitude: 13.69,
2392
4704
  longitude: 100.7501,
2393
4705
  name: "Bangkok Suvarnabhumi",
2394
- tz: "Asia/Bangkok"
4706
+ tz: "Asia/Bangkok",
4707
+ venues: [
4708
+ "polymarket"
4709
+ ]
2395
4710
  },
2396
4711
  {
2397
4712
  code: "WSSS",
@@ -2401,7 +4716,10 @@ var STATIONS = [
2401
4716
  latitude: 1.3644,
2402
4717
  longitude: 103.9915,
2403
4718
  name: "Singapore Changi",
2404
- tz: "Asia/Singapore"
4719
+ tz: "Asia/Singapore",
4720
+ venues: [
4721
+ "polymarket"
4722
+ ]
2405
4723
  },
2406
4724
  {
2407
4725
  code: "YBBN",
@@ -2411,7 +4729,10 @@ var STATIONS = [
2411
4729
  latitude: -27.3842,
2412
4730
  longitude: 153.1175,
2413
4731
  name: "Brisbane",
2414
- tz: "Australia/Brisbane"
4732
+ tz: "Australia/Brisbane",
4733
+ venues: [
4734
+ "polymarket"
4735
+ ]
2415
4736
  },
2416
4737
  {
2417
4738
  code: "YMML",
@@ -2421,7 +4742,10 @@ var STATIONS = [
2421
4742
  latitude: -37.6733,
2422
4743
  longitude: 144.8433,
2423
4744
  name: "Melbourne Tullamarine",
2424
- tz: "Australia/Melbourne"
4745
+ tz: "Australia/Melbourne",
4746
+ venues: [
4747
+ "polymarket"
4748
+ ]
2425
4749
  },
2426
4750
  {
2427
4751
  code: "YSSY",
@@ -2431,7 +4755,10 @@ var STATIONS = [
2431
4755
  latitude: -33.9461,
2432
4756
  longitude: 151.1772,
2433
4757
  name: "Sydney Kingsford Smith",
2434
- tz: "Australia/Sydney"
4758
+ tz: "Australia/Sydney",
4759
+ venues: [
4760
+ "polymarket"
4761
+ ]
2435
4762
  },
2436
4763
  {
2437
4764
  code: "ZBAA",
@@ -2441,7 +4768,10 @@ var STATIONS = [
2441
4768
  latitude: 40.0801,
2442
4769
  longitude: 116.5846,
2443
4770
  name: "Beijing Capital",
2444
- tz: "Asia/Shanghai"
4771
+ tz: "Asia/Shanghai",
4772
+ venues: [
4773
+ "polymarket"
4774
+ ]
2445
4775
  },
2446
4776
  {
2447
4777
  code: "ZSPD",
@@ -2451,134 +4781,147 @@ var STATIONS = [
2451
4781
  latitude: 31.1443,
2452
4782
  longitude: 121.8083,
2453
4783
  name: "Shanghai Pudong",
2454
- tz: "Asia/Shanghai"
4784
+ tz: "Asia/Shanghai",
4785
+ venues: [
4786
+ "polymarket"
4787
+ ]
2455
4788
  }
2456
4789
  ];
2457
- var STATION_BY_CODE2 = /* @__PURE__ */ new Map([
2458
- ["ATL", STATIONS[10]],
2459
- ["AUS", STATIONS[11]],
2460
- ["BOS", STATIONS[12]],
2461
- ["DCA", STATIONS[13]],
2462
- ["DEN", STATIONS[14]],
2463
- ["DFW", STATIONS[15]],
2464
- ["EDDB", STATIONS[0]],
2465
- ["EDDF", STATIONS[1]],
2466
- ["EDDM", STATIONS[2]],
2467
- ["EFHK", STATIONS[3]],
2468
- ["EGKK", STATIONS[4]],
2469
- ["EGLL", STATIONS[5]],
2470
- ["EHAM", STATIONS[6]],
2471
- ["EKCH", STATIONS[7]],
2472
- ["EPWA", STATIONS[8]],
2473
- ["ESSA", STATIONS[9]],
2474
- ["HOU", STATIONS[16]],
2475
- ["LAS", STATIONS[17]],
2476
- ["LAX", STATIONS[18]],
2477
- ["LEBL", STATIONS[30]],
2478
- ["LEMD", STATIONS[31]],
2479
- ["LFPB", STATIONS[32]],
2480
- ["LFPG", STATIONS[33]],
2481
- ["LFPO", STATIONS[34]],
2482
- ["LIMC", STATIONS[35]],
2483
- ["LIRF", STATIONS[36]],
2484
- ["LOWW", STATIONS[37]],
2485
- ["LSZH", STATIONS[38]],
2486
- ["MDW", STATIONS[19]],
2487
- ["MIA", STATIONS[20]],
2488
- ["MSP", STATIONS[21]],
2489
- ["MSY", STATIONS[22]],
2490
- ["NYC", STATIONS[23]],
2491
- ["NZAA", STATIONS[39]],
2492
- ["NZWN", STATIONS[40]],
2493
- ["OERK", STATIONS[41]],
2494
- ["OKC", STATIONS[24]],
2495
- ["OMDB", STATIONS[42]],
2496
- ["OTHH", STATIONS[43]],
2497
- ["PHL", STATIONS[25]],
2498
- ["PHX", STATIONS[26]],
2499
- ["RCTP", STATIONS[44]],
2500
- ["RJAA", STATIONS[45]],
2501
- ["RJTT", STATIONS[46]],
2502
- ["RKSI", STATIONS[47]],
2503
- ["SAEZ", STATIONS[48]],
2504
- ["SAT", STATIONS[27]],
2505
- ["SBGR", STATIONS[49]],
2506
- ["SEA", STATIONS[28]],
2507
- ["SFO", STATIONS[29]],
2508
- ["UUEE", STATIONS[50]],
2509
- ["VABB", STATIONS[51]],
2510
- ["VHHH", STATIONS[52]],
2511
- ["VIDP", STATIONS[53]],
2512
- ["VTBS", STATIONS[54]],
2513
- ["WSSS", STATIONS[55]],
2514
- ["YBBN", STATIONS[56]],
2515
- ["YMML", STATIONS[57]],
2516
- ["YSSY", STATIONS[58]],
2517
- ["ZBAA", STATIONS[59]],
2518
- ["ZSPD", STATIONS[60]]
4790
+ var STATION_BY_CODE3 = /* @__PURE__ */ new Map([
4791
+ ["ATL", STATIONS3[10]],
4792
+ ["AUS", STATIONS3[11]],
4793
+ ["BNA", STATIONS3[12]],
4794
+ ["BOS", STATIONS3[13]],
4795
+ ["CVG", STATIONS3[14]],
4796
+ ["DCA", STATIONS3[15]],
4797
+ ["DEN", STATIONS3[16]],
4798
+ ["DFW", STATIONS3[17]],
4799
+ ["DTW", STATIONS3[18]],
4800
+ ["EDDB", STATIONS3[0]],
4801
+ ["EDDF", STATIONS3[1]],
4802
+ ["EDDM", STATIONS3[2]],
4803
+ ["EFHK", STATIONS3[3]],
4804
+ ["EGKK", STATIONS3[4]],
4805
+ ["EGLL", STATIONS3[5]],
4806
+ ["EHAM", STATIONS3[6]],
4807
+ ["EKCH", STATIONS3[7]],
4808
+ ["EPWA", STATIONS3[8]],
4809
+ ["ESSA", STATIONS3[9]],
4810
+ ["HOU", STATIONS3[19]],
4811
+ ["IAH", STATIONS3[20]],
4812
+ ["LAS", STATIONS3[21]],
4813
+ ["LAX", STATIONS3[22]],
4814
+ ["LEBL", STATIONS3[35]],
4815
+ ["LEMD", STATIONS3[36]],
4816
+ ["LFPB", STATIONS3[37]],
4817
+ ["LFPG", STATIONS3[38]],
4818
+ ["LFPO", STATIONS3[39]],
4819
+ ["LIMC", STATIONS3[40]],
4820
+ ["LIRF", STATIONS3[41]],
4821
+ ["LOWW", STATIONS3[42]],
4822
+ ["LSZH", STATIONS3[43]],
4823
+ ["MDW", STATIONS3[23]],
4824
+ ["MIA", STATIONS3[24]],
4825
+ ["MSP", STATIONS3[25]],
4826
+ ["MSY", STATIONS3[26]],
4827
+ ["NYC", STATIONS3[27]],
4828
+ ["NZAA", STATIONS3[44]],
4829
+ ["NZWN", STATIONS3[45]],
4830
+ ["OERK", STATIONS3[46]],
4831
+ ["OKC", STATIONS3[28]],
4832
+ ["OMDB", STATIONS3[47]],
4833
+ ["OTHH", STATIONS3[48]],
4834
+ ["PHL", STATIONS3[29]],
4835
+ ["PHX", STATIONS3[30]],
4836
+ ["RCTP", STATIONS3[49]],
4837
+ ["RJAA", STATIONS3[50]],
4838
+ ["RJTT", STATIONS3[51]],
4839
+ ["RKSI", STATIONS3[52]],
4840
+ ["SAEZ", STATIONS3[53]],
4841
+ ["SAT", STATIONS3[31]],
4842
+ ["SBGR", STATIONS3[54]],
4843
+ ["SEA", STATIONS3[32]],
4844
+ ["SFO", STATIONS3[33]],
4845
+ ["SLC", STATIONS3[34]],
4846
+ ["UUEE", STATIONS3[55]],
4847
+ ["VABB", STATIONS3[56]],
4848
+ ["VHHH", STATIONS3[57]],
4849
+ ["VIDP", STATIONS3[58]],
4850
+ ["VTBS", STATIONS3[59]],
4851
+ ["WSSS", STATIONS3[60]],
4852
+ ["YBBN", STATIONS3[61]],
4853
+ ["YMML", STATIONS3[62]],
4854
+ ["YSSY", STATIONS3[63]],
4855
+ ["ZBAA", STATIONS3[64]],
4856
+ ["ZSPD", STATIONS3[65]]
2519
4857
  ]);
2520
- var STATION_BY_ICAO2 = /* @__PURE__ */ new Map([
2521
- ["EDDB", STATIONS[0]],
2522
- ["EDDF", STATIONS[1]],
2523
- ["EDDM", STATIONS[2]],
2524
- ["EFHK", STATIONS[3]],
2525
- ["EGKK", STATIONS[4]],
2526
- ["EGLL", STATIONS[5]],
2527
- ["EHAM", STATIONS[6]],
2528
- ["EKCH", STATIONS[7]],
2529
- ["EPWA", STATIONS[8]],
2530
- ["ESSA", STATIONS[9]],
2531
- ["KATL", STATIONS[10]],
2532
- ["KAUS", STATIONS[11]],
2533
- ["KBOS", STATIONS[12]],
2534
- ["KDCA", STATIONS[13]],
2535
- ["KDEN", STATIONS[14]],
2536
- ["KDFW", STATIONS[15]],
2537
- ["KHOU", STATIONS[16]],
2538
- ["KLAS", STATIONS[17]],
2539
- ["KLAX", STATIONS[18]],
2540
- ["KMDW", STATIONS[19]],
2541
- ["KMIA", STATIONS[20]],
2542
- ["KMSP", STATIONS[21]],
2543
- ["KMSY", STATIONS[22]],
2544
- ["KNYC", STATIONS[23]],
2545
- ["KOKC", STATIONS[24]],
2546
- ["KPHL", STATIONS[25]],
2547
- ["KPHX", STATIONS[26]],
2548
- ["KSAT", STATIONS[27]],
2549
- ["KSEA", STATIONS[28]],
2550
- ["KSFO", STATIONS[29]],
2551
- ["LEBL", STATIONS[30]],
2552
- ["LEMD", STATIONS[31]],
2553
- ["LFPB", STATIONS[32]],
2554
- ["LFPG", STATIONS[33]],
2555
- ["LFPO", STATIONS[34]],
2556
- ["LIMC", STATIONS[35]],
2557
- ["LIRF", STATIONS[36]],
2558
- ["LOWW", STATIONS[37]],
2559
- ["LSZH", STATIONS[38]],
2560
- ["NZAA", STATIONS[39]],
2561
- ["NZWN", STATIONS[40]],
2562
- ["OERK", STATIONS[41]],
2563
- ["OMDB", STATIONS[42]],
2564
- ["OTHH", STATIONS[43]],
2565
- ["RCTP", STATIONS[44]],
2566
- ["RJAA", STATIONS[45]],
2567
- ["RJTT", STATIONS[46]],
2568
- ["RKSI", STATIONS[47]],
2569
- ["SAEZ", STATIONS[48]],
2570
- ["SBGR", STATIONS[49]],
2571
- ["UUEE", STATIONS[50]],
2572
- ["VABB", STATIONS[51]],
2573
- ["VHHH", STATIONS[52]],
2574
- ["VIDP", STATIONS[53]],
2575
- ["VTBS", STATIONS[54]],
2576
- ["WSSS", STATIONS[55]],
2577
- ["YBBN", STATIONS[56]],
2578
- ["YMML", STATIONS[57]],
2579
- ["YSSY", STATIONS[58]],
2580
- ["ZBAA", STATIONS[59]],
2581
- ["ZSPD", STATIONS[60]]
4858
+ var STATION_BY_ICAO3 = /* @__PURE__ */ new Map([
4859
+ ["EDDB", STATIONS3[0]],
4860
+ ["EDDF", STATIONS3[1]],
4861
+ ["EDDM", STATIONS3[2]],
4862
+ ["EFHK", STATIONS3[3]],
4863
+ ["EGKK", STATIONS3[4]],
4864
+ ["EGLL", STATIONS3[5]],
4865
+ ["EHAM", STATIONS3[6]],
4866
+ ["EKCH", STATIONS3[7]],
4867
+ ["EPWA", STATIONS3[8]],
4868
+ ["ESSA", STATIONS3[9]],
4869
+ ["KATL", STATIONS3[10]],
4870
+ ["KAUS", STATIONS3[11]],
4871
+ ["KBNA", STATIONS3[12]],
4872
+ ["KBOS", STATIONS3[13]],
4873
+ ["KCVG", STATIONS3[14]],
4874
+ ["KDCA", STATIONS3[15]],
4875
+ ["KDEN", STATIONS3[16]],
4876
+ ["KDFW", STATIONS3[17]],
4877
+ ["KDTW", STATIONS3[18]],
4878
+ ["KHOU", STATIONS3[19]],
4879
+ ["KIAH", STATIONS3[20]],
4880
+ ["KLAS", STATIONS3[21]],
4881
+ ["KLAX", STATIONS3[22]],
4882
+ ["KMDW", STATIONS3[23]],
4883
+ ["KMIA", STATIONS3[24]],
4884
+ ["KMSP", STATIONS3[25]],
4885
+ ["KMSY", STATIONS3[26]],
4886
+ ["KNYC", STATIONS3[27]],
4887
+ ["KOKC", STATIONS3[28]],
4888
+ ["KPHL", STATIONS3[29]],
4889
+ ["KPHX", STATIONS3[30]],
4890
+ ["KSAT", STATIONS3[31]],
4891
+ ["KSEA", STATIONS3[32]],
4892
+ ["KSFO", STATIONS3[33]],
4893
+ ["KSLC", STATIONS3[34]],
4894
+ ["LEBL", STATIONS3[35]],
4895
+ ["LEMD", STATIONS3[36]],
4896
+ ["LFPB", STATIONS3[37]],
4897
+ ["LFPG", STATIONS3[38]],
4898
+ ["LFPO", STATIONS3[39]],
4899
+ ["LIMC", STATIONS3[40]],
4900
+ ["LIRF", STATIONS3[41]],
4901
+ ["LOWW", STATIONS3[42]],
4902
+ ["LSZH", STATIONS3[43]],
4903
+ ["NZAA", STATIONS3[44]],
4904
+ ["NZWN", STATIONS3[45]],
4905
+ ["OERK", STATIONS3[46]],
4906
+ ["OMDB", STATIONS3[47]],
4907
+ ["OTHH", STATIONS3[48]],
4908
+ ["RCTP", STATIONS3[49]],
4909
+ ["RJAA", STATIONS3[50]],
4910
+ ["RJTT", STATIONS3[51]],
4911
+ ["RKSI", STATIONS3[52]],
4912
+ ["SAEZ", STATIONS3[53]],
4913
+ ["SBGR", STATIONS3[54]],
4914
+ ["UUEE", STATIONS3[55]],
4915
+ ["VABB", STATIONS3[56]],
4916
+ ["VHHH", STATIONS3[57]],
4917
+ ["VIDP", STATIONS3[58]],
4918
+ ["VTBS", STATIONS3[59]],
4919
+ ["WSSS", STATIONS3[60]],
4920
+ ["YBBN", STATIONS3[61]],
4921
+ ["YMML", STATIONS3[62]],
4922
+ ["YSSY", STATIONS3[63]],
4923
+ ["ZBAA", STATIONS3[64]],
4924
+ ["ZSPD", STATIONS3[65]]
2582
4925
  ]);
2583
4926
  var _STATION_TZ = Object.freeze({
2584
4927
  // Eastern (UTC-5 standard / UTC-4 DST)
@@ -2725,13 +5068,13 @@ function _lstOffsetHours(stationTz) {
2725
5068
  }
2726
5069
  function _lstOffsetHoursFor(station) {
2727
5070
  const upper = station.trim().toUpperCase();
2728
- const byCode = STATION_BY_CODE2.get(upper);
5071
+ const byCode = STATION_BY_CODE3.get(upper);
2729
5072
  if (byCode !== void 0) return _lstOffsetHours(byCode.tz);
2730
- const byIcao = STATION_BY_ICAO2.get(upper);
5073
+ const byIcao = STATION_BY_ICAO3.get(upper);
2731
5074
  if (byIcao !== void 0) return _lstOffsetHours(byIcao.tz);
2732
5075
  if (upper.length === 4 && upper.startsWith("K")) {
2733
5076
  const stripped = upper.slice(1);
2734
- const retry = STATION_BY_CODE2.get(stripped);
5077
+ const retry = STATION_BY_CODE3.get(stripped);
2735
5078
  if (retry !== void 0) return _lstOffsetHours(retry.tz);
2736
5079
  }
2737
5080
  throw new RangeError(`unknown station: ${JSON.stringify(station)}`);
@@ -2803,8 +5146,8 @@ function cacheKeyForClimate(station, year) {
2803
5146
 
2804
5147
  // ../core/dist/internal/cache/index.browser.mjs
2805
5148
  async function defaultCacheStore() {
2806
- if (typeof indexedDB !== "undefined") return new IndexedDBStore();
2807
- return new MemoryStore();
5149
+ const inner = typeof indexedDB !== "undefined" ? new IndexedDBStore() : new MemoryStore();
5150
+ return versionedCacheStore(inner, CACHE_SCHEMA_VERSION);
2808
5151
  }
2809
5152
 
2810
5153
  // ../core/dist/internal/pairs.mjs
@@ -2990,9 +5333,9 @@ function marketCloseUtc(dateStr, station, tzOverride) {
2990
5333
  );
2991
5334
  return new Date(marketCloseAsUtcMs - offsetHours * 36e5);
2992
5335
  }
2993
- function collectNonNull(obs, key) {
5336
+ function collectNonNull(obs2, key) {
2994
5337
  const out = [];
2995
- for (const o of obs) {
5338
+ for (const o of obs2) {
2996
5339
  const v = o[key];
2997
5340
  if (typeof v === "number" && Number.isFinite(v)) out.push(v);
2998
5341
  }
@@ -3087,13 +5430,68 @@ function buildPairsRow(dateStr, station, observations, climate, opts = {}) {
3087
5430
  function buildPairs(station, dates, observationsByDate, climateByDate, opts = {}) {
3088
5431
  const out = [];
3089
5432
  for (const date of dates) {
3090
- const obs = observationsByDate[date] ?? [];
5433
+ const obs2 = observationsByDate[date] ?? [];
3091
5434
  const climate = climateByDate[date] ?? null;
3092
- out.push(buildPairsRow(date, station, obs, climate, opts));
5435
+ out.push(buildPairsRow(date, station, obs2, climate, opts));
3093
5436
  }
3094
5437
  return Object.freeze(out);
3095
5438
  }
3096
5439
 
5440
+ // src/research.types.ts
5441
+ var KNOWN_RESEARCH_OPTION_KEYS = /* @__PURE__ */ new Set([
5442
+ // Pre-Phase-21 fetcher controls.
5443
+ "signal",
5444
+ "awcHours",
5445
+ "iemPolitenessMs",
5446
+ "ghcnhPolitenessMs",
5447
+ "cliPolitenessMs",
5448
+ "now",
5449
+ "cache",
5450
+ // Pre-Phase-21 selectors.
5451
+ "city",
5452
+ "contract",
5453
+ "contracts",
5454
+ "stationOverride",
5455
+ "sources",
5456
+ "source",
5457
+ "includeTrades",
5458
+ "onWarning",
5459
+ // Phase 21 21-01: Python-parity composable kwargs.
5460
+ "include_forecast",
5461
+ "forecast_model",
5462
+ "forecast_models",
5463
+ "qc",
5464
+ "tz_override",
5465
+ "backend",
5466
+ "return_type"
5467
+ ]);
5468
+ function validateResearchKwargs(opts) {
5469
+ const present = (v) => v !== void 0 && v !== null;
5470
+ for (const key of Object.keys(opts)) {
5471
+ if (!KNOWN_RESEARCH_OPTION_KEYS.has(key)) {
5472
+ throw new TypeError(
5473
+ `research(): unknown option key ${JSON.stringify(key)}. Valid keys: ${[...KNOWN_RESEARCH_OPTION_KEYS].sort().join(", ")}`
5474
+ );
5475
+ }
5476
+ }
5477
+ if (present(opts.sources) && present(opts.source)) {
5478
+ throw new TypeError(
5479
+ "research(): sources= and source= are mutually exclusive \u2014 use `sources=` for the LIVE_V1 multi-source selector or `source=` for a single-source query, not both"
5480
+ );
5481
+ }
5482
+ if (present(opts.forecast_model) && present(opts.forecast_models)) {
5483
+ throw new TypeError(
5484
+ "research(): forecast_model= and forecast_models= are mutually exclusive \u2014 use `forecast_models=` for multi-model fan-out or `forecast_model=` for a single model, not both"
5485
+ );
5486
+ }
5487
+ const wantsForecast = present(opts.forecast_model) || present(opts.forecast_models);
5488
+ if (wantsForecast && opts.include_forecast !== true) {
5489
+ throw new TypeError(
5490
+ "research(): forecast_model=/forecast_models= require include_forecast=true; the model filter is otherwise silently ignored"
5491
+ );
5492
+ }
5493
+ }
5494
+
3097
5495
  // src/research.ts
3098
5496
  var AWC_MAX_HOURS2 = 168;
3099
5497
  async function resolveCache(opts) {
@@ -3361,9 +5759,9 @@ async function fetchIemAsosWithCache(stationCode, _fromYear, _extendedToYear, fr
3361
5759
  }
3362
5760
  }
3363
5761
  }
3364
- for (const obs of monthRows) {
3365
- const obsDate = obs.observed_at.slice(0, 10);
3366
- if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
5762
+ for (const obs2 of monthRows) {
5763
+ const obsDate = obs2.observed_at.slice(0, 10);
5764
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs2);
3367
5765
  }
3368
5766
  }
3369
5767
  return acc;
@@ -3434,14 +5832,22 @@ async function fetchGhcnhWithCache(stationCode, ghcnhId, fromDate, extendedTo, o
3434
5832
  }
3435
5833
  }
3436
5834
  }
3437
- for (const obs of monthRows) {
3438
- const obsDate = obs.observed_at.slice(0, 10);
3439
- if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
5835
+ for (const obs2 of monthRows) {
5836
+ const obsDate = obs2.observed_at.slice(0, 10);
5837
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs2);
3440
5838
  }
3441
5839
  }
3442
5840
  return acc;
3443
5841
  }
3444
5842
  async function research(station, fromDate, toDate, opts = {}) {
5843
+ validateResearchKwargs(opts);
5844
+ if (opts.backend === "polars") {
5845
+ throw new DataAvailabilityError({
5846
+ reason: "model_unavailable",
5847
+ hint: 'polars backend not available in TypeScript SDK; use Python (mostlyrightmd) for backend="polars". TS returns plain object arrays.',
5848
+ source: "research.backend"
5849
+ });
5850
+ }
3445
5851
  const hasCity = typeof opts.city === "string" && opts.city.length > 0;
3446
5852
  const hasContract = typeof opts.contract === "string" && opts.contract.length > 0;
3447
5853
  const hasContracts = Array.isArray(opts.contracts) && opts.contracts.length > 0;
@@ -3517,8 +5923,8 @@ async function research(station, fromDate, toDate, opts = {}) {
3517
5923
  if (opts.signal !== void 0) awcOpts.signal = opts.signal;
3518
5924
  const awcRaw = await fetchAwcMetars([resolved.icao], awcOpts);
3519
5925
  for (const m of awcRaw) {
3520
- const obs = awcToObservation(m);
3521
- if (obs !== null) awcRows.push(obs);
5926
+ const obs2 = awcToObservation(m);
5927
+ if (obs2 !== null) awcRows.push(obs2);
3522
5928
  }
3523
5929
  }
3524
5930
  const iemRows = await fetchIemAsosWithCache(
@@ -3549,8 +5955,8 @@ async function research(station, fromDate, toDate, opts = {}) {
3549
5955
  const observationsByDate = {};
3550
5956
  const dateLo = dates[0] ?? "";
3551
5957
  const dateHi = dates[dates.length - 1] ?? "";
3552
- for (const obs of merged) {
3553
- const settleDate = observedSettlementDate(obs.observed_at, resolved.code);
5958
+ for (const obs2 of merged) {
5959
+ const settleDate = observedSettlementDate(obs2.observed_at, resolved.code);
3554
5960
  if (settleDate === null) continue;
3555
5961
  if (settleDate < dateLo || settleDate > dateHi) continue;
3556
5962
  let bucket = observationsByDate[settleDate];
@@ -3558,7 +5964,7 @@ async function research(station, fromDate, toDate, opts = {}) {
3558
5964
  bucket = [];
3559
5965
  observationsByDate[settleDate] = bucket;
3560
5966
  }
3561
- bucket.push(obs);
5967
+ bucket.push(obs2);
3562
5968
  }
3563
5969
  const climateByDate = {};
3564
5970
  for (const cli of mergedClimate) {
@@ -3692,8 +6098,8 @@ async function researchBySource(station, source, fromDate, toDate, opts = {}) {
3692
6098
  const raw = await fetchAwcMetars([resolved.icao], awcOpts);
3693
6099
  const parsed = [];
3694
6100
  for (const m of raw) {
3695
- const obs = awcToObservation(m);
3696
- if (obs !== null) parsed.push(obs);
6101
+ const obs2 = awcToObservation(m);
6102
+ if (obs2 !== null) parsed.push(obs2);
3697
6103
  }
3698
6104
  rows = parsed.filter((r) => {
3699
6105
  const d = r.observed_at.slice(0, 10);
@@ -3983,13 +6389,135 @@ function discover(args) {
3983
6389
  });
3984
6390
  }
3985
6391
 
6392
+ // ../core/dist/preprocessing/index.mjs
6393
+ var preprocessing_exports = {};
6394
+ __export(preprocessing_exports, {
6395
+ PHYSICS_BOUNDS: () => PHYSICS_BOUNDS,
6396
+ clipOutliers: () => clipOutliers,
6397
+ iemCrosscheck: () => crosscheckIemGhcnh
6398
+ });
6399
+ var PHYSICS_BOUNDS = /* @__PURE__ */ new Map([
6400
+ ["temp_c", [-89, 57]],
6401
+ ["dew_point_c", [-89, 35]],
6402
+ ["dewpoint_c", [-89, 35]],
6403
+ ["wind_speed_ms", [0, 100]],
6404
+ ["wind_speed_kt", [0, 200]],
6405
+ ["wind_dir_deg", [0, 360]],
6406
+ ["wind_dir_degrees", [0, 360]],
6407
+ ["slp_hpa", [870, 1085]],
6408
+ ["sea_level_pressure_mb", [870, 1085]],
6409
+ ["relative_humidity_pct_2m", [0, 100]],
6410
+ ["precip_mm_1h", [0, 305]]
6411
+ ]);
6412
+ function clipOutliers(rows, col, opts = {}) {
6413
+ const std = opts.std ?? 3;
6414
+ const key = `${col}_clipped`;
6415
+ let lo;
6416
+ let hi;
6417
+ let passThrough = false;
6418
+ if (opts.bounds !== void 0) {
6419
+ [lo, hi] = opts.bounds;
6420
+ } else if (PHYSICS_BOUNDS.has(col)) {
6421
+ const b = PHYSICS_BOUNDS.get(col);
6422
+ if (b === void 0) {
6423
+ throw new Error(`PHYSICS_BOUNDS.get(${col}) unexpectedly undefined`);
6424
+ }
6425
+ [lo, hi] = b;
6426
+ } else {
6427
+ if (!Number.isFinite(std) || std <= 0) {
6428
+ throw new RangeError(
6429
+ `clipOutliers: std must be > 0 for the sigma fallback (got ${std}); pass bounds=[lo, hi] or use a physics-default column`
6430
+ );
6431
+ }
6432
+ const vals = [];
6433
+ for (const r of rows) {
6434
+ const v = r?.[col];
6435
+ if (typeof v === "number" && Number.isFinite(v)) vals.push(v);
6436
+ }
6437
+ if (vals.length < 2) {
6438
+ passThrough = true;
6439
+ lo = Number.NEGATIVE_INFINITY;
6440
+ hi = Number.POSITIVE_INFINITY;
6441
+ } else {
6442
+ const mu = vals.reduce((a, b) => a + b, 0) / vals.length;
6443
+ const sumSq = vals.reduce((a, b) => a + (b - mu) ** 2, 0);
6444
+ const sigma = Math.sqrt(sumSq / (vals.length - 1));
6445
+ if (sigma === 0 || !Number.isFinite(sigma)) {
6446
+ passThrough = true;
6447
+ lo = Number.NEGATIVE_INFINITY;
6448
+ hi = Number.POSITIVE_INFINITY;
6449
+ } else {
6450
+ lo = mu - std * sigma;
6451
+ hi = mu + std * sigma;
6452
+ }
6453
+ }
6454
+ }
6455
+ const out = [];
6456
+ for (const r of rows) {
6457
+ const v = r?.[col];
6458
+ let clipped;
6459
+ if (typeof v === "number" && Number.isFinite(v)) {
6460
+ clipped = passThrough ? v : Math.min(Math.max(v, lo), hi);
6461
+ } else {
6462
+ clipped = null;
6463
+ }
6464
+ out.push({ ...r, [key]: clipped });
6465
+ }
6466
+ return out;
6467
+ }
6468
+ function crosscheckIemGhcnh(iemRows, ghcnhRows, opts = {}) {
6469
+ const tolC = opts.tolC ?? 2;
6470
+ if (iemRows.length === 0 || ghcnhRows.length === 0) return [];
6471
+ for (const r of iemRows) {
6472
+ if (typeof r?.station !== "string" || typeof r?.eventTime !== "string") {
6473
+ throw new Error(
6474
+ "crosscheckIemGhcnh: iem rows must carry 'station' (string) and 'eventTime' (string) keys"
6475
+ );
6476
+ }
6477
+ }
6478
+ for (const r of ghcnhRows) {
6479
+ if (typeof r?.station !== "string" || typeof r?.eventTime !== "string") {
6480
+ throw new Error(
6481
+ "crosscheckIemGhcnh: ghcnh rows must carry 'station' (string) and 'eventTime' (string) keys"
6482
+ );
6483
+ }
6484
+ }
6485
+ const iemMap = /* @__PURE__ */ new Map();
6486
+ for (const r of iemRows) {
6487
+ const key = `${r.station}|${r.eventTime}`;
6488
+ iemMap.set(key, r);
6489
+ }
6490
+ const out = [];
6491
+ for (const g of ghcnhRows) {
6492
+ const key = `${g.station}|${g.eventTime}`;
6493
+ const i = iemMap.get(key);
6494
+ if (i === void 0) continue;
6495
+ const iT = typeof i.temp_c === "number" && Number.isFinite(i.temp_c) ? i.temp_c : null;
6496
+ const gT = typeof g.temp_c === "number" && Number.isFinite(g.temp_c) ? g.temp_c : null;
6497
+ if (iT === null || gT === null) continue;
6498
+ const delta = Math.abs(iT - gT);
6499
+ if (delta > tolC) {
6500
+ out.push({
6501
+ station: g.station,
6502
+ eventTime: g.eventTime,
6503
+ tempCIem: iT,
6504
+ tempCGhcnh: gT,
6505
+ deltaC: delta
6506
+ });
6507
+ }
6508
+ }
6509
+ return out;
6510
+ }
6511
+
3986
6512
  // src/index.ts
6513
+ var Preprocessing = preprocessing_exports;
3987
6514
  var version3 = "0.0.0";
3988
6515
  export {
3989
6516
  LiveStreamError,
3990
6517
  MODE2_SOURCES,
3991
6518
  NoLiveDataError,
3992
6519
  POLITE_FLOORS_S,
6520
+ Preprocessing,
3993
6521
  SELECTOR_NAMES,
3994
6522
  SOURCE_ALIASES,
3995
6523
  SOURCE_IDENTITY_TAGS,
@@ -3998,6 +6526,7 @@ export {
3998
6526
  assertSourceIdentity,
3999
6527
  buildOverrideWarning,
4000
6528
  src_exports as core,
6529
+ dailyExtremes,
4001
6530
  discover,
4002
6531
  helloCore,
4003
6532
  helloMarkets,
@@ -4006,6 +6535,8 @@ export {
4006
6535
  isMode2Source,
4007
6536
  latest,
4008
6537
  src_exports3 as markets,
6538
+ obs,
6539
+ preprocessing_exports as preprocessing,
4009
6540
  research,
4010
6541
  researchBySource,
4011
6542
  resolveCity,