mostlyright 1.1.2 → 1.2.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,14 @@ import {
4
4
  buildIemUrl,
5
5
  downloadIemAsos,
6
6
  yearlyChunksExclusiveEnd
7
- } from "./chunk-465FJZTS.mjs";
7
+ } from "./chunk-64FNXDHG.mjs";
8
8
  import {
9
9
  CLIMATE_REPORT_TYPE_PRIORITY,
10
+ DataAvailabilityError,
10
11
  LiveStreamError,
11
12
  NoLiveDataError,
12
13
  NotFoundError,
14
+ STATIONS,
13
15
  STATION_BY_CODE,
14
16
  STATION_BY_ICAO,
15
17
  SourceMismatchError,
@@ -18,7 +20,7 @@ import {
18
20
  helloCore,
19
21
  settlementDateFor,
20
22
  src_exports
21
- } from "./chunk-IN2UOZYO.mjs";
23
+ } from "./chunk-WKPNA46C.mjs";
22
24
  import {
23
25
  awcToObservation,
24
26
  celsiusToFahrenheit,
@@ -72,6 +74,7 @@ __export(src_exports2, {
72
74
  SUPPORTED_SOURCES: () => SUPPORTED_SOURCES,
73
75
  awcToObservation: () => awcToObservation,
74
76
  buildIemUrl: () => buildIemUrl,
77
+ dailyExtremes: () => dailyExtremes,
75
78
  downloadCli: () => downloadCli,
76
79
  downloadCliRange: () => downloadCliRange,
77
80
  downloadGhcnh: () => downloadGhcnh,
@@ -90,12 +93,14 @@ __export(src_exports2, {
90
93
  latest: () => latest,
91
94
  mapCloudCover: () => mapCloudCover,
92
95
  mergeClimate: () => mergeClimate,
96
+ obs: () => obs,
93
97
  parseAwcVisibility: () => parseAwcVisibility,
94
98
  parseCliRecord: () => parseCliRecord,
95
99
  parseCliResponse: () => parseCliResponse,
96
100
  parseGhcnhPsv: () => parseGhcnhPsv,
97
101
  parseGhcnhRow: () => parseGhcnhRow,
98
102
  parseIemCsv: () => parseIemCsv,
103
+ resolveAutoStrategy: () => resolveAutoStrategy,
99
104
  sourceTag: () => sourceTag,
100
105
  stream: () => stream,
101
106
  validatePollSeconds: () => validatePollSeconds,
@@ -289,10 +294,10 @@ function inferReportType(product, observationDate) {
289
294
  if (!product) return "preliminary";
290
295
  const issued = parseProductTimestamp(product);
291
296
  if (issued === null) return "preliminary";
292
- const obs = parseObservationDate(observationDate);
293
- if (obs === null) return "preliminary";
297
+ const obs2 = parseObservationDate(observationDate);
298
+ if (obs2 === null) return "preliminary";
294
299
  const issuedDayUtc = Date.UTC(issued.getUTCFullYear(), issued.getUTCMonth(), issued.getUTCDate());
295
- const obsDayUtc = obs.getTime();
300
+ const obsDayUtc = obs2.getTime();
296
301
  const deltaDays = Math.round((issuedDayUtc - obsDayUtc) / 864e5);
297
302
  if (deltaDays <= 0) return "preliminary";
298
303
  if (deltaDays === 1) {
@@ -655,8 +660,8 @@ function parseGhcnhPsv(psvBody) {
655
660
  const key = header[c];
656
661
  row[key] = c < cells.length ? cells[c] : "";
657
662
  }
658
- const obs = parseGhcnhRow(row);
659
- if (obs !== null) out.push(obs);
663
+ const obs2 = parseGhcnhRow(row);
664
+ if (obs2 !== null) out.push(obs2);
660
665
  }
661
666
  return out;
662
667
  }
@@ -719,8 +724,8 @@ function normalizeStation(station) {
719
724
  if (s.length === 3) return `K${s}`;
720
725
  return s;
721
726
  }
722
- function asLiveObservation(obs, tag) {
723
- return { ...obs, source: tag };
727
+ function asLiveObservation(obs2, tag) {
728
+ return { ...obs2, source: tag };
724
729
  }
725
730
  async function fetchAwcLatest(station) {
726
731
  const icao = normalizeStation(station);
@@ -728,9 +733,9 @@ async function fetchAwcLatest(station) {
728
733
  const tag = sourceTag("awc");
729
734
  const rows = [];
730
735
  for (const m of raw) {
731
- const obs = awcToObservation(m);
732
- if (obs !== null) {
733
- rows.push(asLiveObservation(obs, tag));
736
+ const obs2 = awcToObservation(m);
737
+ if (obs2 !== null) {
738
+ rows.push(asLiveObservation(obs2, tag));
734
739
  }
735
740
  }
736
741
  return rows;
@@ -762,9 +767,9 @@ function previousDayIso(iso) {
762
767
  }
763
768
  async function fetchIemLatest(station) {
764
769
  const [{ fetchWithRetry: fetchWithRetry2 }, { STATION_CODE_RE: STATION_CODE_RE3 }, { buildIemUrl: buildIemUrl2 }, { parseIemCsv: parseIemCsv2 }] = await Promise.all([
765
- import("./src-NP2MZ322.mjs"),
770
+ import("./src-BBELB6SP.mjs"),
766
771
  import("./bounds-KSTXL77E.mjs"),
767
- import("./iem-asos-ZPUMH3KM.mjs"),
772
+ import("./iem-asos-2D5EP2FT.mjs"),
768
773
  import("./iem-IO2HIL5V.mjs")
769
774
  ]);
770
775
  const icao = normalizeStation(station);
@@ -786,8 +791,8 @@ async function fetchIemLatest(station) {
786
791
  const response = await fetchWithRetry2(url);
787
792
  const csv = await response.text();
788
793
  const override = reportType === 3 ? "METAR" : "SPECI";
789
- const obs = parseIemCsv2(csv, { observationTypeOverride: override });
790
- for (const row of obs) {
794
+ const obs2 = parseIemCsv2(csv, { observationTypeOverride: override });
795
+ for (const row of obs2) {
791
796
  rows.push(asLiveObservation(row, tag));
792
797
  }
793
798
  }
@@ -1000,10 +1005,1379 @@ async function iemMosForecasts(station, fromDate, toDate, opts = {}) {
1000
1005
  }
1001
1006
 
1002
1007
  // ../weather/src/forecasts/nwp-stub.ts
1003
- async function forecastNwp(_station, _model, _opts = {}) {
1004
- 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."
1008
+ var IEM_MOS_COVERED_STATIONS = /* @__PURE__ */ new Set([
1009
+ "KNYC",
1010
+ "KLAX",
1011
+ "KORD",
1012
+ "KMIA",
1013
+ "KDEN",
1014
+ "KSEA",
1015
+ "KATL"
1016
+ ]);
1017
+ function buildHint(station, model) {
1018
+ const hasMosCoverage = IEM_MOS_COVERED_STATIONS.has(station.toUpperCase());
1019
+ 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.`;
1020
+ 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/forecasts#typescript-lane for the architectural reason + v1.1+ tracking.`;
1021
+ }
1022
+ async function forecastNwp(station, model, _opts = {}) {
1023
+ throw new DataAvailabilityError({
1024
+ reason: "model_unavailable",
1025
+ source: "nwp-stub",
1026
+ hint: buildHint(station, model)
1027
+ });
1028
+ }
1029
+
1030
+ // ../core/dist/discovery/index.mjs
1031
+ var STATIONS2 = [
1032
+ {
1033
+ code: "EDDB",
1034
+ country: "DE",
1035
+ ghcnh_id: null,
1036
+ icao: "EDDB",
1037
+ latitude: 52.3667,
1038
+ longitude: 13.5033,
1039
+ name: "Berlin Brandenburg",
1040
+ tz: "Europe/Berlin"
1041
+ },
1042
+ {
1043
+ code: "EDDF",
1044
+ country: "DE",
1045
+ ghcnh_id: null,
1046
+ icao: "EDDF",
1047
+ latitude: 50.0379,
1048
+ longitude: 8.5622,
1049
+ name: "Frankfurt am Main",
1050
+ tz: "Europe/Berlin"
1051
+ },
1052
+ {
1053
+ code: "EDDM",
1054
+ country: "DE",
1055
+ ghcnh_id: null,
1056
+ icao: "EDDM",
1057
+ latitude: 48.3538,
1058
+ longitude: 11.7861,
1059
+ name: "Munich Franz Josef Strauss",
1060
+ tz: "Europe/Berlin"
1061
+ },
1062
+ {
1063
+ code: "EFHK",
1064
+ country: "FI",
1065
+ ghcnh_id: null,
1066
+ icao: "EFHK",
1067
+ latitude: 60.3172,
1068
+ longitude: 24.9633,
1069
+ name: "Helsinki-Vantaa",
1070
+ tz: "Europe/Helsinki"
1071
+ },
1072
+ {
1073
+ code: "EGKK",
1074
+ country: "GB",
1075
+ ghcnh_id: null,
1076
+ icao: "EGKK",
1077
+ latitude: 51.1481,
1078
+ longitude: -0.1903,
1079
+ name: "London Gatwick",
1080
+ tz: "Europe/London"
1081
+ },
1082
+ {
1083
+ code: "EGLL",
1084
+ country: "GB",
1085
+ ghcnh_id: null,
1086
+ icao: "EGLL",
1087
+ latitude: 51.4706,
1088
+ longitude: -0.4619,
1089
+ name: "London Heathrow",
1090
+ tz: "Europe/London"
1091
+ },
1092
+ {
1093
+ code: "EHAM",
1094
+ country: "NL",
1095
+ ghcnh_id: null,
1096
+ icao: "EHAM",
1097
+ latitude: 52.3086,
1098
+ longitude: 4.7639,
1099
+ name: "Amsterdam Schiphol",
1100
+ tz: "Europe/Amsterdam"
1101
+ },
1102
+ {
1103
+ code: "EKCH",
1104
+ country: "DK",
1105
+ ghcnh_id: null,
1106
+ icao: "EKCH",
1107
+ latitude: 55.6181,
1108
+ longitude: 12.6561,
1109
+ name: "Copenhagen Kastrup",
1110
+ tz: "Europe/Copenhagen"
1111
+ },
1112
+ {
1113
+ code: "EPWA",
1114
+ country: "PL",
1115
+ ghcnh_id: null,
1116
+ icao: "EPWA",
1117
+ latitude: 52.1657,
1118
+ longitude: 20.9671,
1119
+ name: "Warsaw Chopin",
1120
+ tz: "Europe/Warsaw"
1121
+ },
1122
+ {
1123
+ code: "ESSA",
1124
+ country: "SE",
1125
+ ghcnh_id: null,
1126
+ icao: "ESSA",
1127
+ latitude: 59.6519,
1128
+ longitude: 17.9186,
1129
+ name: "Stockholm Arlanda",
1130
+ tz: "Europe/Stockholm"
1131
+ },
1132
+ {
1133
+ code: "ATL",
1134
+ country: "US",
1135
+ ghcnh_id: "USW00013874",
1136
+ icao: "KATL",
1137
+ latitude: 33.6407,
1138
+ longitude: -84.4277,
1139
+ name: "Hartsfield-Jackson Atlanta International",
1140
+ tz: "America/New_York"
1141
+ },
1142
+ {
1143
+ code: "AUS",
1144
+ country: "US",
1145
+ ghcnh_id: "USW00013904",
1146
+ icao: "KAUS",
1147
+ latitude: 30.1975,
1148
+ longitude: -97.6664,
1149
+ name: "Austin-Bergstrom International",
1150
+ tz: "America/Chicago"
1151
+ },
1152
+ {
1153
+ code: "BOS",
1154
+ country: "US",
1155
+ ghcnh_id: "USW00014739",
1156
+ icao: "KBOS",
1157
+ latitude: 42.3656,
1158
+ longitude: -71.0096,
1159
+ name: "Boston Logan International",
1160
+ tz: "America/New_York"
1161
+ },
1162
+ {
1163
+ code: "DCA",
1164
+ country: "US",
1165
+ ghcnh_id: "USW00013743",
1166
+ icao: "KDCA",
1167
+ latitude: 38.8512,
1168
+ longitude: -77.0402,
1169
+ name: "Washington Reagan National",
1170
+ tz: "America/New_York"
1171
+ },
1172
+ {
1173
+ code: "DEN",
1174
+ country: "US",
1175
+ ghcnh_id: "USW00003017",
1176
+ icao: "KDEN",
1177
+ latitude: 39.8561,
1178
+ longitude: -104.6737,
1179
+ name: "Denver International",
1180
+ tz: "America/Denver"
1181
+ },
1182
+ {
1183
+ code: "DFW",
1184
+ country: "US",
1185
+ ghcnh_id: "USW00003927",
1186
+ icao: "KDFW",
1187
+ latitude: 32.8998,
1188
+ longitude: -97.0403,
1189
+ name: "Dallas-Fort Worth International",
1190
+ tz: "America/Chicago"
1191
+ },
1192
+ {
1193
+ code: "HOU",
1194
+ country: "US",
1195
+ ghcnh_id: "USW00012918",
1196
+ icao: "KHOU",
1197
+ latitude: 29.6454,
1198
+ longitude: -95.2789,
1199
+ name: "Houston Hobby",
1200
+ tz: "America/Chicago"
1201
+ },
1202
+ {
1203
+ code: "LAS",
1204
+ country: "US",
1205
+ ghcnh_id: "USW00023169",
1206
+ icao: "KLAS",
1207
+ latitude: 36.084,
1208
+ longitude: -115.1537,
1209
+ name: "Harry Reid (McCarran) International",
1210
+ tz: "America/Los_Angeles"
1211
+ },
1212
+ {
1213
+ code: "LAX",
1214
+ country: "US",
1215
+ ghcnh_id: "USW00023174",
1216
+ icao: "KLAX",
1217
+ latitude: 33.9425,
1218
+ longitude: -118.4081,
1219
+ name: "Los Angeles International",
1220
+ tz: "America/Los_Angeles"
1221
+ },
1222
+ {
1223
+ code: "MDW",
1224
+ country: "US",
1225
+ ghcnh_id: "USW00014819",
1226
+ icao: "KMDW",
1227
+ latitude: 41.7868,
1228
+ longitude: -87.7522,
1229
+ name: "Chicago Midway International",
1230
+ tz: "America/Chicago"
1231
+ },
1232
+ {
1233
+ code: "MIA",
1234
+ country: "US",
1235
+ ghcnh_id: "USW00012839",
1236
+ icao: "KMIA",
1237
+ latitude: 25.7959,
1238
+ longitude: -80.287,
1239
+ name: "Miami International",
1240
+ tz: "America/New_York"
1241
+ },
1242
+ {
1243
+ code: "MSP",
1244
+ country: "US",
1245
+ ghcnh_id: "USW00014922",
1246
+ icao: "KMSP",
1247
+ latitude: 44.8848,
1248
+ longitude: -93.2223,
1249
+ name: "Minneapolis-St Paul International",
1250
+ tz: "America/Chicago"
1251
+ },
1252
+ {
1253
+ code: "MSY",
1254
+ country: "US",
1255
+ ghcnh_id: "USW00012916",
1256
+ icao: "KMSY",
1257
+ latitude: 29.9934,
1258
+ longitude: -90.258,
1259
+ name: "New Orleans Louis Armstrong International",
1260
+ tz: "America/Chicago"
1261
+ },
1262
+ {
1263
+ code: "NYC",
1264
+ country: "US",
1265
+ ghcnh_id: "USW00094728",
1266
+ icao: "KNYC",
1267
+ latitude: 40.7789,
1268
+ longitude: -73.9692,
1269
+ name: "Central Park, New York",
1270
+ tz: "America/New_York"
1271
+ },
1272
+ {
1273
+ code: "OKC",
1274
+ country: "US",
1275
+ ghcnh_id: "USW00013967",
1276
+ icao: "KOKC",
1277
+ latitude: 35.3931,
1278
+ longitude: -97.6007,
1279
+ name: "Oklahoma City Will Rogers World",
1280
+ tz: "America/Chicago"
1281
+ },
1282
+ {
1283
+ code: "PHL",
1284
+ country: "US",
1285
+ ghcnh_id: "USW00013739",
1286
+ icao: "KPHL",
1287
+ latitude: 39.8721,
1288
+ longitude: -75.2411,
1289
+ name: "Philadelphia International",
1290
+ tz: "America/New_York"
1291
+ },
1292
+ {
1293
+ code: "PHX",
1294
+ country: "US",
1295
+ ghcnh_id: "USW00023183",
1296
+ icao: "KPHX",
1297
+ latitude: 33.4373,
1298
+ longitude: -112.0078,
1299
+ name: "Phoenix Sky Harbor International",
1300
+ tz: "America/Phoenix"
1301
+ },
1302
+ {
1303
+ code: "SAT",
1304
+ country: "US",
1305
+ ghcnh_id: "USW00012921",
1306
+ icao: "KSAT",
1307
+ latitude: 29.5337,
1308
+ longitude: -98.4698,
1309
+ name: "San Antonio International",
1310
+ tz: "America/Chicago"
1311
+ },
1312
+ {
1313
+ code: "SEA",
1314
+ country: "US",
1315
+ ghcnh_id: "USW00024233",
1316
+ icao: "KSEA",
1317
+ latitude: 47.4502,
1318
+ longitude: -122.3088,
1319
+ name: "Seattle-Tacoma International",
1320
+ tz: "America/Los_Angeles"
1321
+ },
1322
+ {
1323
+ code: "SFO",
1324
+ country: "US",
1325
+ ghcnh_id: "USW00023234",
1326
+ icao: "KSFO",
1327
+ latitude: 37.6213,
1328
+ longitude: -122.379,
1329
+ name: "San Francisco International",
1330
+ tz: "America/Los_Angeles"
1331
+ },
1332
+ {
1333
+ code: "LEBL",
1334
+ country: "ES",
1335
+ ghcnh_id: null,
1336
+ icao: "LEBL",
1337
+ latitude: 41.2974,
1338
+ longitude: 2.0833,
1339
+ name: "Barcelona El Prat",
1340
+ tz: "Europe/Madrid"
1341
+ },
1342
+ {
1343
+ code: "LEMD",
1344
+ country: "ES",
1345
+ ghcnh_id: null,
1346
+ icao: "LEMD",
1347
+ latitude: 40.4719,
1348
+ longitude: -3.5626,
1349
+ name: "Madrid Barajas",
1350
+ tz: "Europe/Madrid"
1351
+ },
1352
+ {
1353
+ code: "LFPB",
1354
+ country: "FR",
1355
+ ghcnh_id: null,
1356
+ icao: "LFPB",
1357
+ latitude: 48.9694,
1358
+ longitude: 2.4414,
1359
+ name: "Paris Le Bourget",
1360
+ tz: "Europe/Paris"
1361
+ },
1362
+ {
1363
+ code: "LFPG",
1364
+ country: "FR",
1365
+ ghcnh_id: null,
1366
+ icao: "LFPG",
1367
+ latitude: 49.0097,
1368
+ longitude: 2.5479,
1369
+ name: "Paris Charles de Gaulle",
1370
+ tz: "Europe/Paris"
1371
+ },
1372
+ {
1373
+ code: "LFPO",
1374
+ country: "FR",
1375
+ ghcnh_id: null,
1376
+ icao: "LFPO",
1377
+ latitude: 48.7233,
1378
+ longitude: 2.3794,
1379
+ name: "Paris Orly",
1380
+ tz: "Europe/Paris"
1381
+ },
1382
+ {
1383
+ code: "LIMC",
1384
+ country: "IT",
1385
+ ghcnh_id: null,
1386
+ icao: "LIMC",
1387
+ latitude: 45.6306,
1388
+ longitude: 8.7281,
1389
+ name: "Milan Malpensa",
1390
+ tz: "Europe/Rome"
1391
+ },
1392
+ {
1393
+ code: "LIRF",
1394
+ country: "IT",
1395
+ ghcnh_id: null,
1396
+ icao: "LIRF",
1397
+ latitude: 41.8003,
1398
+ longitude: 12.2389,
1399
+ name: "Rome Fiumicino",
1400
+ tz: "Europe/Rome"
1401
+ },
1402
+ {
1403
+ code: "LOWW",
1404
+ country: "AT",
1405
+ ghcnh_id: null,
1406
+ icao: "LOWW",
1407
+ latitude: 48.1103,
1408
+ longitude: 16.5697,
1409
+ name: "Vienna International",
1410
+ tz: "Europe/Vienna"
1411
+ },
1412
+ {
1413
+ code: "LSZH",
1414
+ country: "CH",
1415
+ ghcnh_id: null,
1416
+ icao: "LSZH",
1417
+ latitude: 47.4647,
1418
+ longitude: 8.5492,
1419
+ name: "Zurich",
1420
+ tz: "Europe/Zurich"
1421
+ },
1422
+ {
1423
+ code: "NZAA",
1424
+ country: "NZ",
1425
+ ghcnh_id: null,
1426
+ icao: "NZAA",
1427
+ latitude: -37.0081,
1428
+ longitude: 174.7917,
1429
+ name: "Auckland",
1430
+ tz: "Pacific/Auckland"
1431
+ },
1432
+ {
1433
+ code: "NZWN",
1434
+ country: "NZ",
1435
+ ghcnh_id: null,
1436
+ icao: "NZWN",
1437
+ latitude: -41.3272,
1438
+ longitude: 174.8053,
1439
+ name: "Wellington",
1440
+ tz: "Pacific/Auckland"
1441
+ },
1442
+ {
1443
+ code: "OERK",
1444
+ country: "SA",
1445
+ ghcnh_id: null,
1446
+ icao: "OERK",
1447
+ latitude: 24.9576,
1448
+ longitude: 46.6988,
1449
+ name: "Riyadh King Khalid International",
1450
+ tz: "Asia/Riyadh"
1451
+ },
1452
+ {
1453
+ code: "OMDB",
1454
+ country: "AE",
1455
+ ghcnh_id: null,
1456
+ icao: "OMDB",
1457
+ latitude: 25.2532,
1458
+ longitude: 55.3657,
1459
+ name: "Dubai International",
1460
+ tz: "Asia/Dubai"
1461
+ },
1462
+ {
1463
+ code: "OTHH",
1464
+ country: "QA",
1465
+ ghcnh_id: null,
1466
+ icao: "OTHH",
1467
+ latitude: 25.2731,
1468
+ longitude: 51.608,
1469
+ name: "Doha Hamad International",
1470
+ tz: "Asia/Qatar"
1471
+ },
1472
+ {
1473
+ code: "RCTP",
1474
+ country: "TW",
1475
+ ghcnh_id: null,
1476
+ icao: "RCTP",
1477
+ latitude: 25.0777,
1478
+ longitude: 121.2328,
1479
+ name: "Taipei Taoyuan",
1480
+ tz: "Asia/Taipei"
1481
+ },
1482
+ {
1483
+ code: "RJAA",
1484
+ country: "JP",
1485
+ ghcnh_id: null,
1486
+ icao: "RJAA",
1487
+ latitude: 35.7647,
1488
+ longitude: 140.3864,
1489
+ name: "Tokyo Narita",
1490
+ tz: "Asia/Tokyo"
1491
+ },
1492
+ {
1493
+ code: "RJTT",
1494
+ country: "JP",
1495
+ ghcnh_id: null,
1496
+ icao: "RJTT",
1497
+ latitude: 35.5522,
1498
+ longitude: 139.78,
1499
+ name: "Tokyo Haneda",
1500
+ tz: "Asia/Tokyo"
1501
+ },
1502
+ {
1503
+ code: "RKSI",
1504
+ country: "KR",
1505
+ ghcnh_id: null,
1506
+ icao: "RKSI",
1507
+ latitude: 37.4691,
1508
+ longitude: 126.4505,
1509
+ name: "Seoul Incheon",
1510
+ tz: "Asia/Seoul"
1511
+ },
1512
+ {
1513
+ code: "SAEZ",
1514
+ country: "AR",
1515
+ ghcnh_id: null,
1516
+ icao: "SAEZ",
1517
+ latitude: -34.8222,
1518
+ longitude: -58.5358,
1519
+ name: "Buenos Aires Ezeiza",
1520
+ tz: "America/Argentina/Buenos_Aires"
1521
+ },
1522
+ {
1523
+ code: "SBGR",
1524
+ country: "BR",
1525
+ ghcnh_id: null,
1526
+ icao: "SBGR",
1527
+ latitude: -23.4356,
1528
+ longitude: -46.4731,
1529
+ name: "S\xE3o Paulo Guarulhos",
1530
+ tz: "America/Sao_Paulo"
1531
+ },
1532
+ {
1533
+ code: "UUEE",
1534
+ country: "RU",
1535
+ ghcnh_id: null,
1536
+ icao: "UUEE",
1537
+ latitude: 55.9728,
1538
+ longitude: 37.4147,
1539
+ name: "Moscow Sheremetyevo",
1540
+ tz: "Europe/Moscow"
1541
+ },
1542
+ {
1543
+ code: "VABB",
1544
+ country: "IN",
1545
+ ghcnh_id: null,
1546
+ icao: "VABB",
1547
+ latitude: 19.0887,
1548
+ longitude: 72.8679,
1549
+ name: "Mumbai Chhatrapati Shivaji",
1550
+ tz: "Asia/Kolkata"
1551
+ },
1552
+ {
1553
+ code: "VHHH",
1554
+ country: "HK",
1555
+ ghcnh_id: null,
1556
+ icao: "VHHH",
1557
+ latitude: 22.308,
1558
+ longitude: 113.9185,
1559
+ name: "Hong Kong International",
1560
+ tz: "Asia/Hong_Kong"
1561
+ },
1562
+ {
1563
+ code: "VIDP",
1564
+ country: "IN",
1565
+ ghcnh_id: null,
1566
+ icao: "VIDP",
1567
+ latitude: 28.5562,
1568
+ longitude: 77.1,
1569
+ name: "Delhi Indira Gandhi",
1570
+ tz: "Asia/Kolkata"
1571
+ },
1572
+ {
1573
+ code: "VTBS",
1574
+ country: "TH",
1575
+ ghcnh_id: null,
1576
+ icao: "VTBS",
1577
+ latitude: 13.69,
1578
+ longitude: 100.7501,
1579
+ name: "Bangkok Suvarnabhumi",
1580
+ tz: "Asia/Bangkok"
1581
+ },
1582
+ {
1583
+ code: "WSSS",
1584
+ country: "SG",
1585
+ ghcnh_id: null,
1586
+ icao: "WSSS",
1587
+ latitude: 1.3644,
1588
+ longitude: 103.9915,
1589
+ name: "Singapore Changi",
1590
+ tz: "Asia/Singapore"
1591
+ },
1592
+ {
1593
+ code: "YBBN",
1594
+ country: "AU",
1595
+ ghcnh_id: null,
1596
+ icao: "YBBN",
1597
+ latitude: -27.3842,
1598
+ longitude: 153.1175,
1599
+ name: "Brisbane",
1600
+ tz: "Australia/Brisbane"
1601
+ },
1602
+ {
1603
+ code: "YMML",
1604
+ country: "AU",
1605
+ ghcnh_id: null,
1606
+ icao: "YMML",
1607
+ latitude: -37.6733,
1608
+ longitude: 144.8433,
1609
+ name: "Melbourne Tullamarine",
1610
+ tz: "Australia/Melbourne"
1611
+ },
1612
+ {
1613
+ code: "YSSY",
1614
+ country: "AU",
1615
+ ghcnh_id: null,
1616
+ icao: "YSSY",
1617
+ latitude: -33.9461,
1618
+ longitude: 151.1772,
1619
+ name: "Sydney Kingsford Smith",
1620
+ tz: "Australia/Sydney"
1621
+ },
1622
+ {
1623
+ code: "ZBAA",
1624
+ country: "CN",
1625
+ ghcnh_id: null,
1626
+ icao: "ZBAA",
1627
+ latitude: 40.0801,
1628
+ longitude: 116.5846,
1629
+ name: "Beijing Capital",
1630
+ tz: "Asia/Shanghai"
1631
+ },
1632
+ {
1633
+ code: "ZSPD",
1634
+ country: "CN",
1635
+ ghcnh_id: null,
1636
+ icao: "ZSPD",
1637
+ latitude: 31.1443,
1638
+ longitude: 121.8083,
1639
+ name: "Shanghai Pudong",
1640
+ tz: "Asia/Shanghai"
1641
+ }
1642
+ ];
1643
+ var STATION_BY_CODE2 = /* @__PURE__ */ new Map([
1644
+ ["ATL", STATIONS2[10]],
1645
+ ["AUS", STATIONS2[11]],
1646
+ ["BOS", STATIONS2[12]],
1647
+ ["DCA", STATIONS2[13]],
1648
+ ["DEN", STATIONS2[14]],
1649
+ ["DFW", STATIONS2[15]],
1650
+ ["EDDB", STATIONS2[0]],
1651
+ ["EDDF", STATIONS2[1]],
1652
+ ["EDDM", STATIONS2[2]],
1653
+ ["EFHK", STATIONS2[3]],
1654
+ ["EGKK", STATIONS2[4]],
1655
+ ["EGLL", STATIONS2[5]],
1656
+ ["EHAM", STATIONS2[6]],
1657
+ ["EKCH", STATIONS2[7]],
1658
+ ["EPWA", STATIONS2[8]],
1659
+ ["ESSA", STATIONS2[9]],
1660
+ ["HOU", STATIONS2[16]],
1661
+ ["LAS", STATIONS2[17]],
1662
+ ["LAX", STATIONS2[18]],
1663
+ ["LEBL", STATIONS2[30]],
1664
+ ["LEMD", STATIONS2[31]],
1665
+ ["LFPB", STATIONS2[32]],
1666
+ ["LFPG", STATIONS2[33]],
1667
+ ["LFPO", STATIONS2[34]],
1668
+ ["LIMC", STATIONS2[35]],
1669
+ ["LIRF", STATIONS2[36]],
1670
+ ["LOWW", STATIONS2[37]],
1671
+ ["LSZH", STATIONS2[38]],
1672
+ ["MDW", STATIONS2[19]],
1673
+ ["MIA", STATIONS2[20]],
1674
+ ["MSP", STATIONS2[21]],
1675
+ ["MSY", STATIONS2[22]],
1676
+ ["NYC", STATIONS2[23]],
1677
+ ["NZAA", STATIONS2[39]],
1678
+ ["NZWN", STATIONS2[40]],
1679
+ ["OERK", STATIONS2[41]],
1680
+ ["OKC", STATIONS2[24]],
1681
+ ["OMDB", STATIONS2[42]],
1682
+ ["OTHH", STATIONS2[43]],
1683
+ ["PHL", STATIONS2[25]],
1684
+ ["PHX", STATIONS2[26]],
1685
+ ["RCTP", STATIONS2[44]],
1686
+ ["RJAA", STATIONS2[45]],
1687
+ ["RJTT", STATIONS2[46]],
1688
+ ["RKSI", STATIONS2[47]],
1689
+ ["SAEZ", STATIONS2[48]],
1690
+ ["SAT", STATIONS2[27]],
1691
+ ["SBGR", STATIONS2[49]],
1692
+ ["SEA", STATIONS2[28]],
1693
+ ["SFO", STATIONS2[29]],
1694
+ ["UUEE", STATIONS2[50]],
1695
+ ["VABB", STATIONS2[51]],
1696
+ ["VHHH", STATIONS2[52]],
1697
+ ["VIDP", STATIONS2[53]],
1698
+ ["VTBS", STATIONS2[54]],
1699
+ ["WSSS", STATIONS2[55]],
1700
+ ["YBBN", STATIONS2[56]],
1701
+ ["YMML", STATIONS2[57]],
1702
+ ["YSSY", STATIONS2[58]],
1703
+ ["ZBAA", STATIONS2[59]],
1704
+ ["ZSPD", STATIONS2[60]]
1705
+ ]);
1706
+ var STATION_BY_ICAO2 = /* @__PURE__ */ new Map([
1707
+ ["EDDB", STATIONS2[0]],
1708
+ ["EDDF", STATIONS2[1]],
1709
+ ["EDDM", STATIONS2[2]],
1710
+ ["EFHK", STATIONS2[3]],
1711
+ ["EGKK", STATIONS2[4]],
1712
+ ["EGLL", STATIONS2[5]],
1713
+ ["EHAM", STATIONS2[6]],
1714
+ ["EKCH", STATIONS2[7]],
1715
+ ["EPWA", STATIONS2[8]],
1716
+ ["ESSA", STATIONS2[9]],
1717
+ ["KATL", STATIONS2[10]],
1718
+ ["KAUS", STATIONS2[11]],
1719
+ ["KBOS", STATIONS2[12]],
1720
+ ["KDCA", STATIONS2[13]],
1721
+ ["KDEN", STATIONS2[14]],
1722
+ ["KDFW", STATIONS2[15]],
1723
+ ["KHOU", STATIONS2[16]],
1724
+ ["KLAS", STATIONS2[17]],
1725
+ ["KLAX", STATIONS2[18]],
1726
+ ["KMDW", STATIONS2[19]],
1727
+ ["KMIA", STATIONS2[20]],
1728
+ ["KMSP", STATIONS2[21]],
1729
+ ["KMSY", STATIONS2[22]],
1730
+ ["KNYC", STATIONS2[23]],
1731
+ ["KOKC", STATIONS2[24]],
1732
+ ["KPHL", STATIONS2[25]],
1733
+ ["KPHX", STATIONS2[26]],
1734
+ ["KSAT", STATIONS2[27]],
1735
+ ["KSEA", STATIONS2[28]],
1736
+ ["KSFO", STATIONS2[29]],
1737
+ ["LEBL", STATIONS2[30]],
1738
+ ["LEMD", STATIONS2[31]],
1739
+ ["LFPB", STATIONS2[32]],
1740
+ ["LFPG", STATIONS2[33]],
1741
+ ["LFPO", STATIONS2[34]],
1742
+ ["LIMC", STATIONS2[35]],
1743
+ ["LIRF", STATIONS2[36]],
1744
+ ["LOWW", STATIONS2[37]],
1745
+ ["LSZH", STATIONS2[38]],
1746
+ ["NZAA", STATIONS2[39]],
1747
+ ["NZWN", STATIONS2[40]],
1748
+ ["OERK", STATIONS2[41]],
1749
+ ["OMDB", STATIONS2[42]],
1750
+ ["OTHH", STATIONS2[43]],
1751
+ ["RCTP", STATIONS2[44]],
1752
+ ["RJAA", STATIONS2[45]],
1753
+ ["RJTT", STATIONS2[46]],
1754
+ ["RKSI", STATIONS2[47]],
1755
+ ["SAEZ", STATIONS2[48]],
1756
+ ["SBGR", STATIONS2[49]],
1757
+ ["UUEE", STATIONS2[50]],
1758
+ ["VABB", STATIONS2[51]],
1759
+ ["VHHH", STATIONS2[52]],
1760
+ ["VIDP", STATIONS2[53]],
1761
+ ["VTBS", STATIONS2[54]],
1762
+ ["WSSS", STATIONS2[55]],
1763
+ ["YBBN", STATIONS2[56]],
1764
+ ["YMML", STATIONS2[57]],
1765
+ ["YSSY", STATIONS2[58]],
1766
+ ["ZBAA", STATIONS2[59]],
1767
+ ["ZSPD", STATIONS2[60]]
1768
+ ]);
1769
+ var LOW_COVERAGE_THRESHOLD = 12;
1770
+ var PARTS_CACHE = /* @__PURE__ */ new Map();
1771
+ function getDateFormatter(tz) {
1772
+ let f = PARTS_CACHE.get(tz);
1773
+ if (f === void 0) {
1774
+ f = new Intl.DateTimeFormat("en-US", {
1775
+ timeZone: tz,
1776
+ year: "numeric",
1777
+ month: "2-digit",
1778
+ day: "2-digit"
1779
+ });
1780
+ PARTS_CACHE.set(tz, f);
1781
+ }
1782
+ return f;
1783
+ }
1784
+ function localDateFor(instant, tz) {
1785
+ const parts = getDateFormatter(tz).formatToParts(instant);
1786
+ let y = "";
1787
+ let m = "";
1788
+ let d = "";
1789
+ for (const p of parts) {
1790
+ if (p.type === "year") y = p.value;
1791
+ else if (p.type === "month") m = p.value;
1792
+ else if (p.type === "day") d = p.value;
1793
+ }
1794
+ return `${y}-${m}-${d}`;
1795
+ }
1796
+ function parseInstant(observed) {
1797
+ if (observed === void 0 || observed === null || observed.length === 0) {
1798
+ return null;
1799
+ }
1800
+ const ms = Date.parse(observed);
1801
+ if (Number.isNaN(ms)) return null;
1802
+ return new Date(ms);
1803
+ }
1804
+ function roundHalfUp(value, places) {
1805
+ if (!Number.isFinite(value)) return value;
1806
+ const scale = 10 ** places;
1807
+ const sign = value < 0 ? -1 : 1;
1808
+ const abs = Math.abs(value);
1809
+ const rounded = Math.floor(abs * scale + 0.5 + abs * 1e-12) / scale;
1810
+ return sign * rounded;
1811
+ }
1812
+ function cToF(c) {
1813
+ return c * 1.8 + 32;
1814
+ }
1815
+ function internationalDailyExtremes(rows, opts) {
1816
+ const tz = opts.stationTz;
1817
+ if (typeof tz !== "string" || tz.length === 0) {
1818
+ throw new RangeError("internationalDailyExtremes: stationTz is required (non-empty string)");
1819
+ }
1820
+ const precision = opts.precision ?? 0;
1821
+ const minObs = opts.minObs ?? LOW_COVERAGE_THRESHOLD;
1822
+ try {
1823
+ getDateFormatter(tz);
1824
+ } catch (e) {
1825
+ throw new RangeError(
1826
+ `internationalDailyExtremes: invalid stationTz ${JSON.stringify(tz)}: ${e.message}`
1827
+ );
1828
+ }
1829
+ const byLocalDate = /* @__PURE__ */ new Map();
1830
+ for (const row of rows) {
1831
+ const instant = parseInstant(row.observed_at);
1832
+ if (instant === null) continue;
1833
+ const localDate = localDateFor(instant, tz);
1834
+ let bucket = byLocalDate.get(localDate);
1835
+ if (bucket === void 0) {
1836
+ bucket = { temps: [], precipMm: 0 };
1837
+ byLocalDate.set(localDate, bucket);
1838
+ }
1839
+ const t = row.temp_c;
1840
+ if (typeof t === "number" && Number.isFinite(t)) {
1841
+ bucket.temps.push({ value: t, source: row.source ?? null });
1842
+ }
1843
+ const p = row.precip_mm_1h;
1844
+ if (typeof p === "number" && Number.isFinite(p)) {
1845
+ bucket.precipMm += p;
1846
+ }
1847
+ }
1848
+ const out = [];
1849
+ const sortedDates = [...byLocalDate.keys()].sort();
1850
+ for (const localDate of sortedDates) {
1851
+ const bucket = byLocalDate.get(localDate);
1852
+ if (bucket === void 0) continue;
1853
+ const nObs = bucket.temps.length;
1854
+ let tempMinC = null;
1855
+ let tempMaxC = null;
1856
+ let tempMeanC = null;
1857
+ let sourceTmin = null;
1858
+ let sourceTmax = null;
1859
+ if (nObs > 0 && nObs >= minObs) {
1860
+ let minIdx = 0;
1861
+ let maxIdx = 0;
1862
+ let sum = 0;
1863
+ for (let i = 0; i < bucket.temps.length; i += 1) {
1864
+ const v = bucket.temps[i];
1865
+ sum += v.value;
1866
+ const minRow2 = bucket.temps[minIdx];
1867
+ const maxRow2 = bucket.temps[maxIdx];
1868
+ if (v.value < minRow2.value) minIdx = i;
1869
+ if (v.value > maxRow2.value) maxIdx = i;
1870
+ }
1871
+ const mean = sum / nObs;
1872
+ const minRow = bucket.temps[minIdx];
1873
+ const maxRow = bucket.temps[maxIdx];
1874
+ tempMinC = roundHalfUp(minRow.value, precision);
1875
+ tempMaxC = roundHalfUp(maxRow.value, precision);
1876
+ tempMeanC = roundHalfUp(mean, precision);
1877
+ sourceTmin = minRow.source;
1878
+ sourceTmax = maxRow.source;
1879
+ }
1880
+ out.push(
1881
+ Object.freeze({
1882
+ localDate,
1883
+ nObs,
1884
+ tempMinC,
1885
+ tempMaxC,
1886
+ tempMeanC,
1887
+ tempMinF: tempMinC === null ? null : roundHalfUp(cToF(tempMinC), precision),
1888
+ tempMaxF: tempMaxC === null ? null : roundHalfUp(cToF(tempMaxC), precision),
1889
+ precipMm: roundHalfUp(bucket.precipMm, 4),
1890
+ sourceTmin,
1891
+ sourceTmax
1892
+ })
1893
+ );
1894
+ }
1895
+ return out;
1896
+ }
1897
+ var BUILT_IN_SCHEMAS = Object.freeze([
1898
+ {
1899
+ id: "schema.observation.v1",
1900
+ title: "schema.observation.v1",
1901
+ columnCount: 20,
1902
+ columns: [
1903
+ { name: "dew_point_c", description: "units: celsius \u2014 bounded", nullable: true },
1904
+ { name: "event_time", description: "observation valid time", nullable: false },
1905
+ {
1906
+ name: "metar_raw",
1907
+ description: "raw METAR text if source has it; null for AWC JSON (structured-only)",
1908
+ nullable: true
1909
+ },
1910
+ {
1911
+ name: "observation_type",
1912
+ description: "METAR | SPECI; defaults METAR when source can't distinguish (e.g. AWC JSON)",
1913
+ nullable: false
1914
+ },
1915
+ {
1916
+ name: "precip_mm_1h",
1917
+ description: "units: mm \u2014 hourly precip (METAR p01i, converted from inches)",
1918
+ nullable: true
1919
+ },
1920
+ {
1921
+ name: "sky_base_1_m",
1922
+ description: "units: meters \u2014 first cloud layer base height (converted from feet)",
1923
+ nullable: true
1924
+ },
1925
+ { name: "sky_base_2_m", description: "units: meters", nullable: true },
1926
+ { name: "sky_base_3_m", description: "units: meters", nullable: true },
1927
+ { name: "sky_base_4_m", description: "units: meters", nullable: true },
1928
+ { name: "sky_cover_1", description: "first cloud layer cover code", nullable: true },
1929
+ { name: "sky_cover_2", description: "second layer; null if not present", nullable: true },
1930
+ { name: "sky_cover_3", description: "third layer; null if not present", nullable: true },
1931
+ { name: "sky_cover_4", description: "fourth layer; null if not present", nullable: true },
1932
+ {
1933
+ name: "slp_hpa",
1934
+ description: "units: hPa \u2014 sea-level pressure (canonical aviation unit, not converted across modes)",
1935
+ nullable: true
1936
+ },
1937
+ { name: "station", description: "ICAO/ASOS station ID (e.g. KORD)", nullable: false },
1938
+ {
1939
+ name: "temp_c",
1940
+ description: "units: celsius \u2014 bounded TEMP_MIN_C..TEMP_MAX_C",
1941
+ nullable: true
1942
+ },
1943
+ {
1944
+ name: "visibility_m",
1945
+ description: "units: meters \u2014 converted from statute miles",
1946
+ nullable: true
1947
+ },
1948
+ {
1949
+ name: "wind_dir_deg",
1950
+ description: "units: degrees \u2014 0-360, bounded",
1951
+ nullable: true
1952
+ },
1953
+ { name: "wind_gust_ms", description: "units: m/s \u2014 converted from kt", nullable: true },
1954
+ { name: "wind_speed_ms", description: "units: m/s \u2014 converted from kt", nullable: true }
1955
+ ]
1956
+ },
1957
+ {
1958
+ id: "schema.forecast.iem_mos.v1",
1959
+ title: "schema.forecast.iem_mos.v1",
1960
+ columnCount: 11,
1961
+ columns: [
1962
+ { name: "dew_point_c", description: "units: celsius", nullable: true },
1963
+ {
1964
+ name: "forecast_hour",
1965
+ description: "units: hours \u2014 (valid_at - issued_at).total_seconds() / 3600",
1966
+ nullable: false
1967
+ },
1968
+ {
1969
+ name: "issued_at",
1970
+ description: "model run time (from source `runtime` field)",
1971
+ nullable: false
1972
+ },
1973
+ { name: "model", description: "e.g. NBE, GFS, LAV, MET", nullable: false },
1974
+ {
1975
+ name: "precip_probability",
1976
+ description: "units: probability \u2014 bounded [0, 1]",
1977
+ nullable: true
1978
+ },
1979
+ {
1980
+ name: "sky_cover_pct",
1981
+ description: "units: percent \u2014 bounded [0, 100]",
1982
+ nullable: true
1983
+ },
1984
+ { name: "station", description: "", nullable: false },
1985
+ { name: "temp_c", description: "units: celsius", nullable: true },
1986
+ {
1987
+ name: "valid_at",
1988
+ description: "forecast target time (from source `ftime`)",
1989
+ nullable: false
1990
+ },
1991
+ { name: "wind_dir_deg", description: "units: degrees", nullable: true },
1992
+ { name: "wind_speed_ms", description: "units: m/s", nullable: true }
1993
+ ]
1994
+ },
1995
+ {
1996
+ id: "schema.settlement.cli.v1",
1997
+ title: "schema.settlement.cli.v1",
1998
+ columnCount: 12,
1999
+ columns: [
2000
+ {
2001
+ name: "cli_data_quality",
2002
+ 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.",
2003
+ nullable: false
2004
+ },
2005
+ {
2006
+ name: "event_time",
2007
+ description: "00:00 local time on observation_date converted to UTC; for sort/join only",
2008
+ nullable: false
2009
+ },
2010
+ {
2011
+ name: "observation_date",
2012
+ description: "local climate day per NWS convention (no timezone applied to the date itself)",
2013
+ nullable: false
2014
+ },
2015
+ { name: "precipitation_in", description: "units: inches", nullable: true },
2016
+ {
2017
+ name: "product_release_time",
2018
+ description: "parsed from CLI product header (_climate.py::_parse_product_timestamp)",
2019
+ nullable: false
2020
+ },
2021
+ {
2022
+ name: "report_type",
2023
+ description: "preliminary | final | correction; dedup priority preliminary < final < correction",
2024
+ nullable: false
2025
+ },
2026
+ {
2027
+ name: "settlement_finality",
2028
+ description: "provisional | final | superseded. Kalshi NHIGH/NLOW settlement contractually requires 'final'; 'provisional' values are kept for early-look research only.",
2029
+ nullable: false
2030
+ },
2031
+ { name: "snowfall_in", description: "units: inches", nullable: true },
2032
+ { name: "station", description: "ICAO/ASOS station ID", nullable: false },
2033
+ {
2034
+ name: "station_tz",
2035
+ description: "IANA timezone for the station (e.g. America/Chicago for KORD). Required for local-climate-day semantics; see \xA7U.",
2036
+ nullable: false
2037
+ },
2038
+ {
2039
+ name: "temp_max_F",
2040
+ description: "units: fahrenheit \u2014 daily high (uppercase F for consistency with obs imperial mode)",
2041
+ nullable: true
2042
+ },
2043
+ { name: "temp_min_F", description: "units: fahrenheit \u2014 daily low", nullable: true }
2044
+ ]
2045
+ },
2046
+ {
2047
+ id: "schema.observation_ledger.v1",
2048
+ title: "schema.observation_ledger.v1",
2049
+ columnCount: 15,
2050
+ columns: [
2051
+ { name: "as_of_time", description: "", nullable: true },
2052
+ { name: "dewpoint_c", description: "units: celsius", nullable: true },
2053
+ { name: "ingestion_id", description: "", nullable: true },
2054
+ { name: "observation_kind", description: "", nullable: true },
2055
+ {
2056
+ name: "observation_quality",
2057
+ 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.",
2058
+ nullable: true
2059
+ },
2060
+ { name: "observation_type", description: "", nullable: false },
2061
+ { name: "observed_at", description: "", nullable: false },
2062
+ { name: "parser_name", description: "", nullable: true },
2063
+ { name: "parser_version", description: "", nullable: true },
2064
+ { name: "provenance", description: "", nullable: true },
2065
+ { name: "qc_status", description: "", nullable: true },
2066
+ {
2067
+ name: "source",
2068
+ description: "ncei reserved per D-2.1-09; never written in v0.1.0.",
2069
+ nullable: false
2070
+ },
2071
+ { name: "source_received_at", description: "", nullable: true },
2072
+ { name: "station_code", description: "", nullable: false },
2073
+ { name: "temp_c", description: "units: celsius", nullable: true }
2074
+ ]
2075
+ },
2076
+ {
2077
+ id: "schema.observation_qc.v1",
2078
+ title: "schema.observation_qc.v1",
2079
+ columnCount: 13,
2080
+ columns: [
2081
+ { name: "as_of_time", description: "", nullable: true },
2082
+ {
2083
+ name: "detector_metadata",
2084
+ description: "JSON-serialized detector payload; shape per qc_system.",
2085
+ nullable: true
2086
+ },
2087
+ {
2088
+ name: "field",
2089
+ description: "Observation column the rule evaluated (e.g. temp_c).",
2090
+ nullable: false
2091
+ },
2092
+ { name: "flag", description: "", nullable: false },
2093
+ { name: "ingestion_id", description: "", nullable: true },
2094
+ { name: "observation_kind", description: "", nullable: true },
2095
+ { name: "observed_at", description: "", nullable: false },
2096
+ { name: "parser_name", description: "", nullable: true },
2097
+ { name: "qc_system", description: "", nullable: false },
2098
+ { name: "qc_version", description: "", nullable: false },
2099
+ { name: "rule_id", description: "", nullable: false },
2100
+ { name: "source", description: "", nullable: false },
2101
+ { name: "station_code", description: "", nullable: false }
2102
+ ]
2103
+ }
2104
+ ]);
2105
+ function deepFreezeSchema(info) {
2106
+ const frozenCols = Object.freeze(info.columns.map((c) => Object.freeze({ ...c })));
2107
+ return Object.freeze({ ...info, columns: frozenCols });
2108
+ }
2109
+ var REGISTRY = new Map(
2110
+ BUILT_IN_SCHEMAS.map((info) => [info.id, deepFreezeSchema(info)])
2111
+ );
2112
+ var FEATURE_NAMES = Object.freeze([
2113
+ "calendarFeatures",
2114
+ "clipOutliers",
2115
+ "diff",
2116
+ "diff2",
2117
+ "heatIndex",
2118
+ "lag",
2119
+ "rolling",
2120
+ "spread",
2121
+ "windChill"
2122
+ ]);
2123
+
2124
+ // ../weather/src/dailyExtremes.ts
2125
+ var LOW_COVERAGE_THRESHOLD2 = 12;
2126
+ function addUtcDays(iso, days) {
2127
+ const [yStr, mStr, dStr] = iso.split("-");
2128
+ const dt = new Date(Date.UTC(Number(yStr), Number(mStr) - 1, Number(dStr)));
2129
+ dt.setUTCDate(dt.getUTCDate() + days);
2130
+ const yyyy = dt.getUTCFullYear();
2131
+ const mm = String(dt.getUTCMonth() + 1).padStart(2, "0");
2132
+ const dd = String(dt.getUTCDate()).padStart(2, "0");
2133
+ return `${yyyy}-${mm}-${dd}`;
2134
+ }
2135
+ function lookupStation(icao) {
2136
+ const upper = icao.toUpperCase();
2137
+ for (const s of STATIONS) {
2138
+ if (s.icao === upper) {
2139
+ return { tz: s.tz, isUs: s.country === "US" };
2140
+ }
2141
+ }
2142
+ throw new Error(`dailyExtremes: station "${icao}" not in registry \u2014 check STATIONS catalog`);
2143
+ }
2144
+ function cToF2(c) {
2145
+ if (c === null) return null;
2146
+ return c * (9 / 5) + 32;
2147
+ }
2148
+ function roundHalfUp2(value, decimals) {
2149
+ const m = 10 ** decimals;
2150
+ return Math.round(value * m) / m;
2151
+ }
2152
+ async function fetchIemAsosObservations(station, fromDate, toDate) {
2153
+ const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);
2154
+ const toYear = Number.parseInt(toDate.slice(0, 4), 10);
2155
+ const out = [];
2156
+ for (let year = fromYear; year <= toYear; year++) {
2157
+ const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {
2158
+ reportType: 3,
2159
+ politenessMs: 1e3
2160
+ });
2161
+ for (const chunk of chunks) {
2162
+ const parsed = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
2163
+ for (const row of parsed) {
2164
+ const obsDate = row.observed_at.slice(0, 10);
2165
+ if (obsDate >= fromDate && obsDate <= toDate) {
2166
+ const precipInches = row.precip_1hr_inches ?? null;
2167
+ out.push({
2168
+ observed_at: row.observed_at,
2169
+ temp_c: row.temp_c ?? null,
2170
+ precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,
2171
+ source: row.source
2172
+ });
2173
+ }
2174
+ }
2175
+ }
2176
+ }
2177
+ return out;
2178
+ }
2179
+ async function fetchAwcObservations(station, fromDate, toDate) {
2180
+ const raw = await fetchAwcMetars([station]);
2181
+ const out = [];
2182
+ for (const r of raw) {
2183
+ const obs2 = awcToObservation(r);
2184
+ if (obs2 === null) continue;
2185
+ const obsDate = obs2.observed_at.slice(0, 10);
2186
+ if (obsDate >= fromDate && obsDate <= toDate) {
2187
+ const precipInches = obs2.precip_1hr_inches ?? null;
2188
+ out.push({
2189
+ observed_at: obs2.observed_at,
2190
+ temp_c: obs2.temp_c ?? null,
2191
+ precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,
2192
+ source: obs2.source
2193
+ });
2194
+ }
2195
+ }
2196
+ return out;
2197
+ }
2198
+ async function fetchForMode(station, fromDate, toDate, mode) {
2199
+ switch (mode) {
2200
+ case "iem_only":
2201
+ return fetchIemAsosObservations(station, fromDate, toDate);
2202
+ case "awc_only":
2203
+ return fetchAwcObservations(station, fromDate, toDate);
2204
+ case "live_v1": {
2205
+ const [iem, awc] = await Promise.all([
2206
+ fetchIemAsosObservations(station, fromDate, toDate),
2207
+ fetchAwcObservations(station, fromDate, toDate).catch(() => [])
2208
+ ]);
2209
+ return [...iem, ...awc];
2210
+ }
2211
+ default: {
2212
+ const _exhaustive = mode;
2213
+ throw new TypeError(`dailyExtremes: unknown merge mode "${String(_exhaustive)}"`);
2214
+ }
2215
+ }
2216
+ }
2217
+ function projectRow(station, d, isUs) {
2218
+ const lowCoverage = d.nObs < LOW_COVERAGE_THRESHOLD2;
2219
+ const decimals = isUs ? 0 : 1;
2220
+ const precipIn = d.precipMm !== null && d.precipMm !== void 0 ? roundHalfUp2(d.precipMm / 25.4, 2) : null;
2221
+ if (lowCoverage) {
2222
+ return {
2223
+ date: d.localDate,
2224
+ station,
2225
+ tmin_f: null,
2226
+ tmax_f: null,
2227
+ tmean_f: null,
2228
+ precip_in: precipIn,
2229
+ low_coverage: true,
2230
+ n_obs: d.nObs
2231
+ };
2232
+ }
2233
+ return {
2234
+ date: d.localDate,
2235
+ station,
2236
+ tmin_f: d.tempMinF !== null ? roundHalfUp2(d.tempMinF, decimals) : null,
2237
+ tmax_f: d.tempMaxF !== null ? roundHalfUp2(d.tempMaxF, decimals) : null,
2238
+ tmean_f: d.tempMeanC !== null ? roundHalfUp2(cToF2(d.tempMeanC), decimals) : null,
2239
+ precip_in: precipIn,
2240
+ low_coverage: false,
2241
+ n_obs: d.nObs
2242
+ };
2243
+ }
2244
+ async function dailyExtremes(station, fromDate, toDate, opts = {}) {
2245
+ const { tz, isUs } = lookupStation(station);
2246
+ const merge = opts.merge ?? "live_v1";
2247
+ const fetchFrom = addUtcDays(fromDate, -1);
2248
+ const fetchTo = addUtcDays(toDate, 1);
2249
+ const rows = await fetchForMode(station, fetchFrom, fetchTo, merge);
2250
+ const extremes = internationalDailyExtremes(rows, {
2251
+ stationTz: tz,
2252
+ precision: isUs ? 1 : 0
2253
+ });
2254
+ return extremes.filter((d) => d.localDate >= fromDate && d.localDate <= toDate).map((d) => projectRow(station.toUpperCase(), d, isUs));
2255
+ }
2256
+
2257
+ // ../weather/src/obs.ts
2258
+ function daysBetween(fromDate, toDate) {
2259
+ const from = Date.UTC(
2260
+ Number.parseInt(fromDate.slice(0, 4), 10),
2261
+ Number.parseInt(fromDate.slice(5, 7), 10) - 1,
2262
+ Number.parseInt(fromDate.slice(8, 10), 10)
2263
+ );
2264
+ const to = Date.UTC(
2265
+ Number.parseInt(toDate.slice(0, 4), 10),
2266
+ Number.parseInt(toDate.slice(5, 7), 10) - 1,
2267
+ Number.parseInt(toDate.slice(8, 10), 10)
1006
2268
  );
2269
+ return Math.round((to - from) / (24 * 60 * 60 * 1e3)) + 1;
2270
+ }
2271
+ function resolveAutoStrategy(fromDate, toDate) {
2272
+ return daysBetween(fromDate, toDate) <= 7 ? "exact_window" : "warm_cache";
2273
+ }
2274
+ function inchesToMm(inches) {
2275
+ if (inches === null || inches === void 0) return null;
2276
+ return inches * 25.4;
2277
+ }
2278
+ function mbToInhg(mb) {
2279
+ if (mb === null || mb === void 0) return null;
2280
+ return mb * 0.029529983071445;
2281
+ }
2282
+ function fromObservation(o) {
2283
+ const tempC = o.temp_c ?? null;
2284
+ const dewC = o.dewpoint_c ?? null;
2285
+ return {
2286
+ station: o.station_code,
2287
+ observed_at: o.observed_at,
2288
+ source: o.source,
2289
+ temp_c: tempC,
2290
+ temp_f: tempC !== null ? tempC * (9 / 5) + 32 : null,
2291
+ dewpoint_c: dewC,
2292
+ dewpoint_f: dewC !== null ? dewC * (9 / 5) + 32 : null,
2293
+ wind_speed_kts: o.wind_speed_kt ?? null,
2294
+ wind_direction_deg: o.wind_dir_degrees ?? null,
2295
+ pressure_inhg: mbToInhg(o.sea_level_pressure_mb),
2296
+ precip_mm_1h: inchesToMm(o.precip_1hr_inches),
2297
+ raw_metar: o.raw_metar ?? null
2298
+ };
2299
+ }
2300
+ async function fetchIemForWindow(station, fromDate, toDate, resolvedStrategy) {
2301
+ const out = [];
2302
+ if (resolvedStrategy === "exact_window") {
2303
+ const chunks = await downloadIemAsos(station, fromDate, toDate, {
2304
+ reportType: 3,
2305
+ politenessMs: 1e3
2306
+ });
2307
+ for (const chunk of chunks) {
2308
+ const rows = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
2309
+ for (const r of rows) {
2310
+ const d = r.observed_at.slice(0, 10);
2311
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(r));
2312
+ }
2313
+ }
2314
+ return out;
2315
+ }
2316
+ const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);
2317
+ const toYear = Number.parseInt(toDate.slice(0, 4), 10);
2318
+ for (let year = fromYear; year <= toYear; year++) {
2319
+ const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {
2320
+ reportType: 3,
2321
+ politenessMs: 1e3
2322
+ });
2323
+ for (const chunk of chunks) {
2324
+ const rows = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
2325
+ for (const r of rows) {
2326
+ const d = r.observed_at.slice(0, 10);
2327
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(r));
2328
+ }
2329
+ }
2330
+ }
2331
+ return out;
2332
+ }
2333
+ async function fetchAwcForWindow(station, fromDate, toDate) {
2334
+ const raw = await fetchAwcMetars([station]);
2335
+ const out = [];
2336
+ for (const r of raw) {
2337
+ const obs2 = awcToObservation(r);
2338
+ if (obs2 === null) continue;
2339
+ const d = obs2.observed_at.slice(0, 10);
2340
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(obs2));
2341
+ }
2342
+ return out;
2343
+ }
2344
+ async function fetchByStrategy(station, fromDate, toDate, resolvedStrategy, source) {
2345
+ const wantsIem = source === null || source === void 0 || source === "iem";
2346
+ const wantsAwc = source === null || source === void 0 || source === "awc";
2347
+ const tasks = [];
2348
+ if (wantsIem) tasks.push(fetchIemForWindow(station, fromDate, toDate, resolvedStrategy));
2349
+ if (wantsAwc) tasks.push(fetchAwcForWindow(station, fromDate, toDate).catch(() => []));
2350
+ const results = await Promise.all(tasks);
2351
+ return results.flat();
2352
+ }
2353
+ async function obs(station, fromDate, toDate, opts = {}) {
2354
+ const strategy = opts.strategy ?? "auto";
2355
+ const source = opts.source ?? null;
2356
+ if (source === "ghcnh") {
2357
+ throw new DataAvailabilityError({
2358
+ reason: "model_unavailable",
2359
+ source: "obs.ghcnh",
2360
+ 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."
2361
+ });
2362
+ }
2363
+ if (strategy === "hosted") {
2364
+ throw new DataAvailabilityError({
2365
+ reason: "model_unavailable",
2366
+ source: "obs-hosted-stub",
2367
+ 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"
2368
+ });
2369
+ }
2370
+ let resolved;
2371
+ if (strategy === "auto") {
2372
+ resolved = resolveAutoStrategy(fromDate, toDate);
2373
+ } else if (strategy === "exact_window" || strategy === "warm_cache") {
2374
+ resolved = strategy;
2375
+ } else {
2376
+ throw new TypeError(
2377
+ `obs: unknown strategy "${String(strategy)}" \u2014 expected one of: auto, exact_window, warm_cache, hosted`
2378
+ );
2379
+ }
2380
+ return fetchByStrategy(station, fromDate, toDate, resolved, source);
1007
2381
  }
1008
2382
 
1009
2383
  // ../weather/src/index.ts
@@ -1692,10 +3066,11 @@ replaceTraps((oldTraps) => ({
1692
3066
  }
1693
3067
  }));
1694
3068
 
1695
- // ../core/dist/internal/chunk-PKJXHY27.mjs
3069
+ // ../core/dist/internal/chunk-IPC4XUYW.mjs
1696
3070
  function lockKeyFor(key) {
1697
3071
  return `mostlyright:cache:lock:${key}`;
1698
3072
  }
3073
+ var CACHE_SCHEMA_VERSION = "v2-phase18-integer-f";
1699
3074
  var MemoryStore = class {
1700
3075
  #entries = /* @__PURE__ */ new Map();
1701
3076
  #chain = /* @__PURE__ */ new Map();
@@ -1842,7 +3217,71 @@ var IndexedDBStore = class {
1842
3217
  return next;
1843
3218
  }
1844
3219
  };
1845
- var STATIONS = [
3220
+ var VERSION_FIELD = "_cache_schema_version";
3221
+ function isVersionedEntry(v) {
3222
+ if (v === null || typeof v !== "object") return false;
3223
+ if (!(VERSION_FIELD in v)) return false;
3224
+ return typeof v[VERSION_FIELD] === "string";
3225
+ }
3226
+ function hasListKeys(s) {
3227
+ return typeof s.listKeys === "function";
3228
+ }
3229
+ var VersionedCacheStore = class {
3230
+ #inner;
3231
+ #version;
3232
+ constructor(inner, version4) {
3233
+ if (typeof version4 !== "string" || version4.length === 0) {
3234
+ throw new TypeError("versionedCacheStore: version must be a non-empty string");
3235
+ }
3236
+ this.#inner = inner;
3237
+ this.#version = version4;
3238
+ }
3239
+ /**
3240
+ * Test/diagnostics seam: return the underlying store so tests can assert
3241
+ * which concrete backend `defaultCacheStore()` selected. NOT a production
3242
+ * API — production code MUST use the wrapped store so version
3243
+ * invalidation fires on stale reads.
3244
+ *
3245
+ * @internal
3246
+ */
3247
+ __peekInner() {
3248
+ return this.#inner;
3249
+ }
3250
+ async get(key) {
3251
+ const raw = await this.#inner.get(key);
3252
+ if (raw === null) return null;
3253
+ if (!isVersionedEntry(raw)) {
3254
+ return null;
3255
+ }
3256
+ if (raw._cache_schema_version !== this.#version) {
3257
+ return null;
3258
+ }
3259
+ return raw.value;
3260
+ }
3261
+ async set(key, value, opts) {
3262
+ const wrapped = {
3263
+ value,
3264
+ [VERSION_FIELD]: this.#version
3265
+ };
3266
+ await this.#inner.set(key, wrapped, opts);
3267
+ }
3268
+ async delete(key) {
3269
+ await this.#inner.delete(key);
3270
+ }
3271
+ async withLock(key, fn) {
3272
+ return this.#inner.withLock(key, fn);
3273
+ }
3274
+ async listKeys(prefix) {
3275
+ if (hasListKeys(this.#inner)) {
3276
+ return this.#inner.listKeys(prefix);
3277
+ }
3278
+ return Object.freeze([]);
3279
+ }
3280
+ };
3281
+ function versionedCacheStore(inner, version4) {
3282
+ return new VersionedCacheStore(inner, version4);
3283
+ }
3284
+ var STATIONS3 = [
1846
3285
  {
1847
3286
  code: "EDDB",
1848
3287
  country: "DE",
@@ -2454,131 +3893,131 @@ var STATIONS = [
2454
3893
  tz: "Asia/Shanghai"
2455
3894
  }
2456
3895
  ];
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]]
3896
+ var STATION_BY_CODE3 = /* @__PURE__ */ new Map([
3897
+ ["ATL", STATIONS3[10]],
3898
+ ["AUS", STATIONS3[11]],
3899
+ ["BOS", STATIONS3[12]],
3900
+ ["DCA", STATIONS3[13]],
3901
+ ["DEN", STATIONS3[14]],
3902
+ ["DFW", STATIONS3[15]],
3903
+ ["EDDB", STATIONS3[0]],
3904
+ ["EDDF", STATIONS3[1]],
3905
+ ["EDDM", STATIONS3[2]],
3906
+ ["EFHK", STATIONS3[3]],
3907
+ ["EGKK", STATIONS3[4]],
3908
+ ["EGLL", STATIONS3[5]],
3909
+ ["EHAM", STATIONS3[6]],
3910
+ ["EKCH", STATIONS3[7]],
3911
+ ["EPWA", STATIONS3[8]],
3912
+ ["ESSA", STATIONS3[9]],
3913
+ ["HOU", STATIONS3[16]],
3914
+ ["LAS", STATIONS3[17]],
3915
+ ["LAX", STATIONS3[18]],
3916
+ ["LEBL", STATIONS3[30]],
3917
+ ["LEMD", STATIONS3[31]],
3918
+ ["LFPB", STATIONS3[32]],
3919
+ ["LFPG", STATIONS3[33]],
3920
+ ["LFPO", STATIONS3[34]],
3921
+ ["LIMC", STATIONS3[35]],
3922
+ ["LIRF", STATIONS3[36]],
3923
+ ["LOWW", STATIONS3[37]],
3924
+ ["LSZH", STATIONS3[38]],
3925
+ ["MDW", STATIONS3[19]],
3926
+ ["MIA", STATIONS3[20]],
3927
+ ["MSP", STATIONS3[21]],
3928
+ ["MSY", STATIONS3[22]],
3929
+ ["NYC", STATIONS3[23]],
3930
+ ["NZAA", STATIONS3[39]],
3931
+ ["NZWN", STATIONS3[40]],
3932
+ ["OERK", STATIONS3[41]],
3933
+ ["OKC", STATIONS3[24]],
3934
+ ["OMDB", STATIONS3[42]],
3935
+ ["OTHH", STATIONS3[43]],
3936
+ ["PHL", STATIONS3[25]],
3937
+ ["PHX", STATIONS3[26]],
3938
+ ["RCTP", STATIONS3[44]],
3939
+ ["RJAA", STATIONS3[45]],
3940
+ ["RJTT", STATIONS3[46]],
3941
+ ["RKSI", STATIONS3[47]],
3942
+ ["SAEZ", STATIONS3[48]],
3943
+ ["SAT", STATIONS3[27]],
3944
+ ["SBGR", STATIONS3[49]],
3945
+ ["SEA", STATIONS3[28]],
3946
+ ["SFO", STATIONS3[29]],
3947
+ ["UUEE", STATIONS3[50]],
3948
+ ["VABB", STATIONS3[51]],
3949
+ ["VHHH", STATIONS3[52]],
3950
+ ["VIDP", STATIONS3[53]],
3951
+ ["VTBS", STATIONS3[54]],
3952
+ ["WSSS", STATIONS3[55]],
3953
+ ["YBBN", STATIONS3[56]],
3954
+ ["YMML", STATIONS3[57]],
3955
+ ["YSSY", STATIONS3[58]],
3956
+ ["ZBAA", STATIONS3[59]],
3957
+ ["ZSPD", STATIONS3[60]]
2519
3958
  ]);
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]]
3959
+ var STATION_BY_ICAO3 = /* @__PURE__ */ new Map([
3960
+ ["EDDB", STATIONS3[0]],
3961
+ ["EDDF", STATIONS3[1]],
3962
+ ["EDDM", STATIONS3[2]],
3963
+ ["EFHK", STATIONS3[3]],
3964
+ ["EGKK", STATIONS3[4]],
3965
+ ["EGLL", STATIONS3[5]],
3966
+ ["EHAM", STATIONS3[6]],
3967
+ ["EKCH", STATIONS3[7]],
3968
+ ["EPWA", STATIONS3[8]],
3969
+ ["ESSA", STATIONS3[9]],
3970
+ ["KATL", STATIONS3[10]],
3971
+ ["KAUS", STATIONS3[11]],
3972
+ ["KBOS", STATIONS3[12]],
3973
+ ["KDCA", STATIONS3[13]],
3974
+ ["KDEN", STATIONS3[14]],
3975
+ ["KDFW", STATIONS3[15]],
3976
+ ["KHOU", STATIONS3[16]],
3977
+ ["KLAS", STATIONS3[17]],
3978
+ ["KLAX", STATIONS3[18]],
3979
+ ["KMDW", STATIONS3[19]],
3980
+ ["KMIA", STATIONS3[20]],
3981
+ ["KMSP", STATIONS3[21]],
3982
+ ["KMSY", STATIONS3[22]],
3983
+ ["KNYC", STATIONS3[23]],
3984
+ ["KOKC", STATIONS3[24]],
3985
+ ["KPHL", STATIONS3[25]],
3986
+ ["KPHX", STATIONS3[26]],
3987
+ ["KSAT", STATIONS3[27]],
3988
+ ["KSEA", STATIONS3[28]],
3989
+ ["KSFO", STATIONS3[29]],
3990
+ ["LEBL", STATIONS3[30]],
3991
+ ["LEMD", STATIONS3[31]],
3992
+ ["LFPB", STATIONS3[32]],
3993
+ ["LFPG", STATIONS3[33]],
3994
+ ["LFPO", STATIONS3[34]],
3995
+ ["LIMC", STATIONS3[35]],
3996
+ ["LIRF", STATIONS3[36]],
3997
+ ["LOWW", STATIONS3[37]],
3998
+ ["LSZH", STATIONS3[38]],
3999
+ ["NZAA", STATIONS3[39]],
4000
+ ["NZWN", STATIONS3[40]],
4001
+ ["OERK", STATIONS3[41]],
4002
+ ["OMDB", STATIONS3[42]],
4003
+ ["OTHH", STATIONS3[43]],
4004
+ ["RCTP", STATIONS3[44]],
4005
+ ["RJAA", STATIONS3[45]],
4006
+ ["RJTT", STATIONS3[46]],
4007
+ ["RKSI", STATIONS3[47]],
4008
+ ["SAEZ", STATIONS3[48]],
4009
+ ["SBGR", STATIONS3[49]],
4010
+ ["UUEE", STATIONS3[50]],
4011
+ ["VABB", STATIONS3[51]],
4012
+ ["VHHH", STATIONS3[52]],
4013
+ ["VIDP", STATIONS3[53]],
4014
+ ["VTBS", STATIONS3[54]],
4015
+ ["WSSS", STATIONS3[55]],
4016
+ ["YBBN", STATIONS3[56]],
4017
+ ["YMML", STATIONS3[57]],
4018
+ ["YSSY", STATIONS3[58]],
4019
+ ["ZBAA", STATIONS3[59]],
4020
+ ["ZSPD", STATIONS3[60]]
2582
4021
  ]);
2583
4022
  var _STATION_TZ = Object.freeze({
2584
4023
  // Eastern (UTC-5 standard / UTC-4 DST)
@@ -2725,13 +4164,13 @@ function _lstOffsetHours(stationTz) {
2725
4164
  }
2726
4165
  function _lstOffsetHoursFor(station) {
2727
4166
  const upper = station.trim().toUpperCase();
2728
- const byCode = STATION_BY_CODE2.get(upper);
4167
+ const byCode = STATION_BY_CODE3.get(upper);
2729
4168
  if (byCode !== void 0) return _lstOffsetHours(byCode.tz);
2730
- const byIcao = STATION_BY_ICAO2.get(upper);
4169
+ const byIcao = STATION_BY_ICAO3.get(upper);
2731
4170
  if (byIcao !== void 0) return _lstOffsetHours(byIcao.tz);
2732
4171
  if (upper.length === 4 && upper.startsWith("K")) {
2733
4172
  const stripped = upper.slice(1);
2734
- const retry = STATION_BY_CODE2.get(stripped);
4173
+ const retry = STATION_BY_CODE3.get(stripped);
2735
4174
  if (retry !== void 0) return _lstOffsetHours(retry.tz);
2736
4175
  }
2737
4176
  throw new RangeError(`unknown station: ${JSON.stringify(station)}`);
@@ -2803,8 +4242,8 @@ function cacheKeyForClimate(station, year) {
2803
4242
 
2804
4243
  // ../core/dist/internal/cache/index.browser.mjs
2805
4244
  async function defaultCacheStore() {
2806
- if (typeof indexedDB !== "undefined") return new IndexedDBStore();
2807
- return new MemoryStore();
4245
+ const inner = typeof indexedDB !== "undefined" ? new IndexedDBStore() : new MemoryStore();
4246
+ return versionedCacheStore(inner, CACHE_SCHEMA_VERSION);
2808
4247
  }
2809
4248
 
2810
4249
  // ../core/dist/internal/pairs.mjs
@@ -2990,9 +4429,9 @@ function marketCloseUtc(dateStr, station, tzOverride) {
2990
4429
  );
2991
4430
  return new Date(marketCloseAsUtcMs - offsetHours * 36e5);
2992
4431
  }
2993
- function collectNonNull(obs, key) {
4432
+ function collectNonNull(obs2, key) {
2994
4433
  const out = [];
2995
- for (const o of obs) {
4434
+ for (const o of obs2) {
2996
4435
  const v = o[key];
2997
4436
  if (typeof v === "number" && Number.isFinite(v)) out.push(v);
2998
4437
  }
@@ -3087,13 +4526,68 @@ function buildPairsRow(dateStr, station, observations, climate, opts = {}) {
3087
4526
  function buildPairs(station, dates, observationsByDate, climateByDate, opts = {}) {
3088
4527
  const out = [];
3089
4528
  for (const date of dates) {
3090
- const obs = observationsByDate[date] ?? [];
4529
+ const obs2 = observationsByDate[date] ?? [];
3091
4530
  const climate = climateByDate[date] ?? null;
3092
- out.push(buildPairsRow(date, station, obs, climate, opts));
4531
+ out.push(buildPairsRow(date, station, obs2, climate, opts));
3093
4532
  }
3094
4533
  return Object.freeze(out);
3095
4534
  }
3096
4535
 
4536
+ // src/research.types.ts
4537
+ var KNOWN_RESEARCH_OPTION_KEYS = /* @__PURE__ */ new Set([
4538
+ // Pre-Phase-21 fetcher controls.
4539
+ "signal",
4540
+ "awcHours",
4541
+ "iemPolitenessMs",
4542
+ "ghcnhPolitenessMs",
4543
+ "cliPolitenessMs",
4544
+ "now",
4545
+ "cache",
4546
+ // Pre-Phase-21 selectors.
4547
+ "city",
4548
+ "contract",
4549
+ "contracts",
4550
+ "stationOverride",
4551
+ "sources",
4552
+ "source",
4553
+ "includeTrades",
4554
+ "onWarning",
4555
+ // Phase 21 21-01: Python-parity composable kwargs.
4556
+ "include_forecast",
4557
+ "forecast_model",
4558
+ "forecast_models",
4559
+ "qc",
4560
+ "tz_override",
4561
+ "backend",
4562
+ "return_type"
4563
+ ]);
4564
+ function validateResearchKwargs(opts) {
4565
+ const present = (v) => v !== void 0 && v !== null;
4566
+ for (const key of Object.keys(opts)) {
4567
+ if (!KNOWN_RESEARCH_OPTION_KEYS.has(key)) {
4568
+ throw new TypeError(
4569
+ `research(): unknown option key ${JSON.stringify(key)}. Valid keys: ${[...KNOWN_RESEARCH_OPTION_KEYS].sort().join(", ")}`
4570
+ );
4571
+ }
4572
+ }
4573
+ if (present(opts.sources) && present(opts.source)) {
4574
+ throw new TypeError(
4575
+ "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"
4576
+ );
4577
+ }
4578
+ if (present(opts.forecast_model) && present(opts.forecast_models)) {
4579
+ throw new TypeError(
4580
+ "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"
4581
+ );
4582
+ }
4583
+ const wantsForecast = present(opts.forecast_model) || present(opts.forecast_models);
4584
+ if (wantsForecast && opts.include_forecast !== true) {
4585
+ throw new TypeError(
4586
+ "research(): forecast_model=/forecast_models= require include_forecast=true; the model filter is otherwise silently ignored"
4587
+ );
4588
+ }
4589
+ }
4590
+
3097
4591
  // src/research.ts
3098
4592
  var AWC_MAX_HOURS2 = 168;
3099
4593
  async function resolveCache(opts) {
@@ -3361,9 +4855,9 @@ async function fetchIemAsosWithCache(stationCode, _fromYear, _extendedToYear, fr
3361
4855
  }
3362
4856
  }
3363
4857
  }
3364
- for (const obs of monthRows) {
3365
- const obsDate = obs.observed_at.slice(0, 10);
3366
- if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
4858
+ for (const obs2 of monthRows) {
4859
+ const obsDate = obs2.observed_at.slice(0, 10);
4860
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs2);
3367
4861
  }
3368
4862
  }
3369
4863
  return acc;
@@ -3434,14 +4928,22 @@ async function fetchGhcnhWithCache(stationCode, ghcnhId, fromDate, extendedTo, o
3434
4928
  }
3435
4929
  }
3436
4930
  }
3437
- for (const obs of monthRows) {
3438
- const obsDate = obs.observed_at.slice(0, 10);
3439
- if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
4931
+ for (const obs2 of monthRows) {
4932
+ const obsDate = obs2.observed_at.slice(0, 10);
4933
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs2);
3440
4934
  }
3441
4935
  }
3442
4936
  return acc;
3443
4937
  }
3444
4938
  async function research(station, fromDate, toDate, opts = {}) {
4939
+ validateResearchKwargs(opts);
4940
+ if (opts.backend === "polars") {
4941
+ throw new DataAvailabilityError({
4942
+ reason: "model_unavailable",
4943
+ hint: 'polars backend not available in TypeScript SDK; use Python (mostlyrightmd) for backend="polars". TS returns plain object arrays.',
4944
+ source: "research.backend"
4945
+ });
4946
+ }
3445
4947
  const hasCity = typeof opts.city === "string" && opts.city.length > 0;
3446
4948
  const hasContract = typeof opts.contract === "string" && opts.contract.length > 0;
3447
4949
  const hasContracts = Array.isArray(opts.contracts) && opts.contracts.length > 0;
@@ -3517,8 +5019,8 @@ async function research(station, fromDate, toDate, opts = {}) {
3517
5019
  if (opts.signal !== void 0) awcOpts.signal = opts.signal;
3518
5020
  const awcRaw = await fetchAwcMetars([resolved.icao], awcOpts);
3519
5021
  for (const m of awcRaw) {
3520
- const obs = awcToObservation(m);
3521
- if (obs !== null) awcRows.push(obs);
5022
+ const obs2 = awcToObservation(m);
5023
+ if (obs2 !== null) awcRows.push(obs2);
3522
5024
  }
3523
5025
  }
3524
5026
  const iemRows = await fetchIemAsosWithCache(
@@ -3549,8 +5051,8 @@ async function research(station, fromDate, toDate, opts = {}) {
3549
5051
  const observationsByDate = {};
3550
5052
  const dateLo = dates[0] ?? "";
3551
5053
  const dateHi = dates[dates.length - 1] ?? "";
3552
- for (const obs of merged) {
3553
- const settleDate = observedSettlementDate(obs.observed_at, resolved.code);
5054
+ for (const obs2 of merged) {
5055
+ const settleDate = observedSettlementDate(obs2.observed_at, resolved.code);
3554
5056
  if (settleDate === null) continue;
3555
5057
  if (settleDate < dateLo || settleDate > dateHi) continue;
3556
5058
  let bucket = observationsByDate[settleDate];
@@ -3558,7 +5060,7 @@ async function research(station, fromDate, toDate, opts = {}) {
3558
5060
  bucket = [];
3559
5061
  observationsByDate[settleDate] = bucket;
3560
5062
  }
3561
- bucket.push(obs);
5063
+ bucket.push(obs2);
3562
5064
  }
3563
5065
  const climateByDate = {};
3564
5066
  for (const cli of mergedClimate) {
@@ -3692,8 +5194,8 @@ async function researchBySource(station, source, fromDate, toDate, opts = {}) {
3692
5194
  const raw = await fetchAwcMetars([resolved.icao], awcOpts);
3693
5195
  const parsed = [];
3694
5196
  for (const m of raw) {
3695
- const obs = awcToObservation(m);
3696
- if (obs !== null) parsed.push(obs);
5197
+ const obs2 = awcToObservation(m);
5198
+ if (obs2 !== null) parsed.push(obs2);
3697
5199
  }
3698
5200
  rows = parsed.filter((r) => {
3699
5201
  const d = r.observed_at.slice(0, 10);
@@ -3983,13 +5485,135 @@ function discover(args) {
3983
5485
  });
3984
5486
  }
3985
5487
 
5488
+ // ../core/dist/preprocessing/index.mjs
5489
+ var preprocessing_exports = {};
5490
+ __export(preprocessing_exports, {
5491
+ PHYSICS_BOUNDS: () => PHYSICS_BOUNDS,
5492
+ clipOutliers: () => clipOutliers,
5493
+ iemCrosscheck: () => crosscheckIemGhcnh
5494
+ });
5495
+ var PHYSICS_BOUNDS = /* @__PURE__ */ new Map([
5496
+ ["temp_c", [-89, 57]],
5497
+ ["dew_point_c", [-89, 35]],
5498
+ ["dewpoint_c", [-89, 35]],
5499
+ ["wind_speed_ms", [0, 100]],
5500
+ ["wind_speed_kt", [0, 200]],
5501
+ ["wind_dir_deg", [0, 360]],
5502
+ ["wind_dir_degrees", [0, 360]],
5503
+ ["slp_hpa", [870, 1085]],
5504
+ ["sea_level_pressure_mb", [870, 1085]],
5505
+ ["relative_humidity_pct_2m", [0, 100]],
5506
+ ["precip_mm_1h", [0, 305]]
5507
+ ]);
5508
+ function clipOutliers(rows, col, opts = {}) {
5509
+ const std = opts.std ?? 3;
5510
+ const key = `${col}_clipped`;
5511
+ let lo;
5512
+ let hi;
5513
+ let passThrough = false;
5514
+ if (opts.bounds !== void 0) {
5515
+ [lo, hi] = opts.bounds;
5516
+ } else if (PHYSICS_BOUNDS.has(col)) {
5517
+ const b = PHYSICS_BOUNDS.get(col);
5518
+ if (b === void 0) {
5519
+ throw new Error(`PHYSICS_BOUNDS.get(${col}) unexpectedly undefined`);
5520
+ }
5521
+ [lo, hi] = b;
5522
+ } else {
5523
+ if (!Number.isFinite(std) || std <= 0) {
5524
+ throw new RangeError(
5525
+ `clipOutliers: std must be > 0 for the sigma fallback (got ${std}); pass bounds=[lo, hi] or use a physics-default column`
5526
+ );
5527
+ }
5528
+ const vals = [];
5529
+ for (const r of rows) {
5530
+ const v = r?.[col];
5531
+ if (typeof v === "number" && Number.isFinite(v)) vals.push(v);
5532
+ }
5533
+ if (vals.length < 2) {
5534
+ passThrough = true;
5535
+ lo = Number.NEGATIVE_INFINITY;
5536
+ hi = Number.POSITIVE_INFINITY;
5537
+ } else {
5538
+ const mu = vals.reduce((a, b) => a + b, 0) / vals.length;
5539
+ const sumSq = vals.reduce((a, b) => a + (b - mu) ** 2, 0);
5540
+ const sigma = Math.sqrt(sumSq / (vals.length - 1));
5541
+ if (sigma === 0 || !Number.isFinite(sigma)) {
5542
+ passThrough = true;
5543
+ lo = Number.NEGATIVE_INFINITY;
5544
+ hi = Number.POSITIVE_INFINITY;
5545
+ } else {
5546
+ lo = mu - std * sigma;
5547
+ hi = mu + std * sigma;
5548
+ }
5549
+ }
5550
+ }
5551
+ const out = [];
5552
+ for (const r of rows) {
5553
+ const v = r?.[col];
5554
+ let clipped;
5555
+ if (typeof v === "number" && Number.isFinite(v)) {
5556
+ clipped = passThrough ? v : Math.min(Math.max(v, lo), hi);
5557
+ } else {
5558
+ clipped = null;
5559
+ }
5560
+ out.push({ ...r, [key]: clipped });
5561
+ }
5562
+ return out;
5563
+ }
5564
+ function crosscheckIemGhcnh(iemRows, ghcnhRows, opts = {}) {
5565
+ const tolC = opts.tolC ?? 2;
5566
+ if (iemRows.length === 0 || ghcnhRows.length === 0) return [];
5567
+ for (const r of iemRows) {
5568
+ if (typeof r?.station !== "string" || typeof r?.eventTime !== "string") {
5569
+ throw new Error(
5570
+ "crosscheckIemGhcnh: iem rows must carry 'station' (string) and 'eventTime' (string) keys"
5571
+ );
5572
+ }
5573
+ }
5574
+ for (const r of ghcnhRows) {
5575
+ if (typeof r?.station !== "string" || typeof r?.eventTime !== "string") {
5576
+ throw new Error(
5577
+ "crosscheckIemGhcnh: ghcnh rows must carry 'station' (string) and 'eventTime' (string) keys"
5578
+ );
5579
+ }
5580
+ }
5581
+ const iemMap = /* @__PURE__ */ new Map();
5582
+ for (const r of iemRows) {
5583
+ const key = `${r.station}|${r.eventTime}`;
5584
+ iemMap.set(key, r);
5585
+ }
5586
+ const out = [];
5587
+ for (const g of ghcnhRows) {
5588
+ const key = `${g.station}|${g.eventTime}`;
5589
+ const i = iemMap.get(key);
5590
+ if (i === void 0) continue;
5591
+ const iT = typeof i.temp_c === "number" && Number.isFinite(i.temp_c) ? i.temp_c : null;
5592
+ const gT = typeof g.temp_c === "number" && Number.isFinite(g.temp_c) ? g.temp_c : null;
5593
+ if (iT === null || gT === null) continue;
5594
+ const delta = Math.abs(iT - gT);
5595
+ if (delta > tolC) {
5596
+ out.push({
5597
+ station: g.station,
5598
+ eventTime: g.eventTime,
5599
+ tempCIem: iT,
5600
+ tempCGhcnh: gT,
5601
+ deltaC: delta
5602
+ });
5603
+ }
5604
+ }
5605
+ return out;
5606
+ }
5607
+
3986
5608
  // src/index.ts
5609
+ var Preprocessing = preprocessing_exports;
3987
5610
  var version3 = "0.0.0";
3988
5611
  export {
3989
5612
  LiveStreamError,
3990
5613
  MODE2_SOURCES,
3991
5614
  NoLiveDataError,
3992
5615
  POLITE_FLOORS_S,
5616
+ Preprocessing,
3993
5617
  SELECTOR_NAMES,
3994
5618
  SOURCE_ALIASES,
3995
5619
  SOURCE_IDENTITY_TAGS,
@@ -3998,6 +5622,7 @@ export {
3998
5622
  assertSourceIdentity,
3999
5623
  buildOverrideWarning,
4000
5624
  src_exports as core,
5625
+ dailyExtremes,
4001
5626
  discover,
4002
5627
  helloCore,
4003
5628
  helloMarkets,
@@ -4006,6 +5631,8 @@ export {
4006
5631
  isMode2Source,
4007
5632
  latest,
4008
5633
  src_exports3 as markets,
5634
+ obs,
5635
+ preprocessing_exports as preprocessing,
4009
5636
  research,
4010
5637
  researchBySource,
4011
5638
  resolveCity,