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.
@@ -962,7 +962,7 @@ var mostlyright = (() => {
962
962
  }
963
963
  return { _repr_only: true, value: String(value) };
964
964
  }
965
- var TradewindsError, SourceUnavailableError, SchemaValidationError, SourceMismatchError, LeakageError, TemporalDriftError, PayloadTooLargeError, DeferredMarketError, PolymarketEventError, TherminalError, NotFoundError, RateLimitError, ValidationError, AuthenticationError, ForbiddenError, ServerError, LiveStreamError, NoLiveDataError;
965
+ var TradewindsError, SourceUnavailableError, DATA_AVAILABILITY_REASONS, DataAvailabilityError, SchemaValidationError, SourceMismatchError, LeakageError, TemporalDriftError, PayloadTooLargeError, DeferredMarketError, PolymarketEventError, TherminalError, NotFoundError, RateLimitError, ValidationError, AuthenticationError, ForbiddenError, ServerError, LiveStreamError, NoLiveDataError;
966
966
  var init_exceptions = __esm({
967
967
  "../core/src/exceptions/index.ts"() {
968
968
  "use strict";
@@ -1026,6 +1026,40 @@ var mostlyright = (() => {
1026
1026
  };
1027
1027
  }
1028
1028
  };
1029
+ DATA_AVAILABILITY_REASONS = [
1030
+ "model_unavailable",
1031
+ "out_of_window",
1032
+ "cache_miss",
1033
+ "source_404",
1034
+ "source_5xx",
1035
+ "rate_limited"
1036
+ ];
1037
+ DataAvailabilityError = class extends TradewindsError {
1038
+ static defaultErrorCode = "DATA_AVAILABILITY";
1039
+ reason;
1040
+ hint;
1041
+ constructor(options) {
1042
+ if (!DATA_AVAILABILITY_REASONS.includes(options.reason)) {
1043
+ throw new RangeError(
1044
+ `DataAvailabilityError: unknown reason "${String(options.reason)}". Valid reasons: ${DATA_AVAILABILITY_REASONS.join(", ")}`
1045
+ );
1046
+ }
1047
+ if (typeof options.hint !== "string" || options.hint.length === 0) {
1048
+ throw new TypeError("DataAvailabilityError: hint is required and must be a non-empty string");
1049
+ }
1050
+ const message = `[${options.reason}] ${options.hint}`;
1051
+ super(message, options);
1052
+ this.reason = options.reason;
1053
+ this.hint = options.hint;
1054
+ }
1055
+ payload() {
1056
+ return {
1057
+ ...super.payload(),
1058
+ reason: this.reason,
1059
+ hint: this.hint
1060
+ };
1061
+ }
1062
+ };
1029
1063
  SchemaValidationError = class extends TradewindsError {
1030
1064
  static defaultErrorCode = "SCHEMA_VALIDATION_FAILED";
1031
1065
  schemaId;
@@ -1212,7 +1246,7 @@ var mostlyright = (() => {
1212
1246
  NoLiveDataError = class extends LiveStreamError {
1213
1247
  static defaultErrorCode = "NO_LIVE_DATA";
1214
1248
  station;
1215
- constructor(message = "", options) {
1249
+ constructor(message, options) {
1216
1250
  super(message, { ...options, source: options.source });
1217
1251
  this.station = options.station;
1218
1252
  }
@@ -1628,6 +1662,8 @@ var mostlyright = (() => {
1628
1662
  __export(src_exports, {
1629
1663
  AuthenticationError: () => AuthenticationError,
1630
1664
  CLIMATE_REPORT_TYPE_PRIORITY: () => CLIMATE_REPORT_TYPE_PRIORITY,
1665
+ DATA_AVAILABILITY_REASONS: () => DATA_AVAILABILITY_REASONS,
1666
+ DataAvailabilityError: () => DataAvailabilityError,
1631
1667
  DeferredMarketError: () => DeferredMarketError,
1632
1668
  ForbiddenError: () => ForbiddenError,
1633
1669
  LIVE_V1_POLICY: () => LIVE_V1_POLICY,
@@ -2322,8 +2358,8 @@ var mostlyright = (() => {
2322
2358
  const key = header[c];
2323
2359
  row[key] = (cells[c] ?? "").trim();
2324
2360
  }
2325
- const obs = iemToObservation(row, opts);
2326
- if (obs !== null) out.push(obs);
2361
+ const obs2 = iemToObservation(row, opts);
2362
+ if (obs2 !== null) out.push(obs2);
2327
2363
  }
2328
2364
  return out;
2329
2365
  }
@@ -2348,6 +2384,7 @@ var mostlyright = (() => {
2348
2384
  MODE2_SOURCES: () => MODE2_SOURCES,
2349
2385
  NoLiveDataError: () => NoLiveDataError,
2350
2386
  POLITE_FLOORS_S: () => POLITE_FLOORS_S,
2387
+ Preprocessing: () => Preprocessing,
2351
2388
  SELECTOR_NAMES: () => SELECTOR_NAMES,
2352
2389
  SOURCE_ALIASES: () => SOURCE_ALIASES,
2353
2390
  SOURCE_IDENTITY_TAGS: () => SOURCE_IDENTITY_TAGS,
@@ -2356,6 +2393,7 @@ var mostlyright = (() => {
2356
2393
  assertSourceIdentity: () => assertSourceIdentity,
2357
2394
  buildOverrideWarning: () => buildOverrideWarning,
2358
2395
  core: () => src_exports,
2396
+ dailyExtremes: () => dailyExtremes,
2359
2397
  discover: () => discover,
2360
2398
  helloCore: () => helloCore,
2361
2399
  helloMarkets: () => helloMarkets,
@@ -2364,6 +2402,8 @@ var mostlyright = (() => {
2364
2402
  isMode2Source: () => isMode2Source,
2365
2403
  latest: () => latest,
2366
2404
  markets: () => src_exports3,
2405
+ obs: () => obs,
2406
+ preprocessing: () => preprocessing_exports,
2367
2407
  research: () => research,
2368
2408
  researchBySource: () => researchBySource,
2369
2409
  resolveCity: () => resolveCity,
@@ -2401,6 +2441,7 @@ var mostlyright = (() => {
2401
2441
  SUPPORTED_SOURCES: () => SUPPORTED_SOURCES,
2402
2442
  awcToObservation: () => awcToObservation,
2403
2443
  buildIemUrl: () => buildIemUrl,
2444
+ dailyExtremes: () => dailyExtremes,
2404
2445
  downloadCli: () => downloadCli,
2405
2446
  downloadCliRange: () => downloadCliRange,
2406
2447
  downloadGhcnh: () => downloadGhcnh,
@@ -2419,12 +2460,14 @@ var mostlyright = (() => {
2419
2460
  latest: () => latest,
2420
2461
  mapCloudCover: () => mapCloudCover,
2421
2462
  mergeClimate: () => mergeClimate,
2463
+ obs: () => obs,
2422
2464
  parseAwcVisibility: () => parseAwcVisibility,
2423
2465
  parseCliRecord: () => parseCliRecord,
2424
2466
  parseCliResponse: () => parseCliResponse,
2425
2467
  parseGhcnhPsv: () => parseGhcnhPsv,
2426
2468
  parseGhcnhRow: () => parseGhcnhRow,
2427
2469
  parseIemCsv: () => parseIemCsv,
2470
+ resolveAutoStrategy: () => resolveAutoStrategy,
2428
2471
  sourceTag: () => sourceTag,
2429
2472
  stream: () => stream,
2430
2473
  validatePollSeconds: () => validatePollSeconds,
@@ -2626,10 +2669,10 @@ var mostlyright = (() => {
2626
2669
  if (!product) return "preliminary";
2627
2670
  const issued = parseProductTimestamp(product);
2628
2671
  if (issued === null) return "preliminary";
2629
- const obs = parseObservationDate(observationDate);
2630
- if (obs === null) return "preliminary";
2672
+ const obs2 = parseObservationDate(observationDate);
2673
+ if (obs2 === null) return "preliminary";
2631
2674
  const issuedDayUtc = Date.UTC(issued.getUTCFullYear(), issued.getUTCMonth(), issued.getUTCDate());
2632
- const obsDayUtc = obs.getTime();
2675
+ const obsDayUtc = obs2.getTime();
2633
2676
  const deltaDays = Math.round((issuedDayUtc - obsDayUtc) / 864e5);
2634
2677
  if (deltaDays <= 0) return "preliminary";
2635
2678
  if (deltaDays === 1) {
@@ -3005,8 +3048,8 @@ var mostlyright = (() => {
3005
3048
  const key = header[c];
3006
3049
  row[key] = c < cells.length ? cells[c] : "";
3007
3050
  }
3008
- const obs = parseGhcnhRow(row);
3009
- if (obs !== null) out.push(obs);
3051
+ const obs2 = parseGhcnhRow(row);
3052
+ if (obs2 !== null) out.push(obs2);
3010
3053
  }
3011
3054
  return out;
3012
3055
  }
@@ -3075,8 +3118,8 @@ var mostlyright = (() => {
3075
3118
  if (s.length === 3) return `K${s}`;
3076
3119
  return s;
3077
3120
  }
3078
- function asLiveObservation(obs, tag) {
3079
- return { ...obs, source: tag };
3121
+ function asLiveObservation(obs2, tag) {
3122
+ return { ...obs2, source: tag };
3080
3123
  }
3081
3124
  async function fetchAwcLatest(station) {
3082
3125
  const icao = normalizeStation(station);
@@ -3084,9 +3127,9 @@ var mostlyright = (() => {
3084
3127
  const tag = sourceTag("awc");
3085
3128
  const rows = [];
3086
3129
  for (const m of raw) {
3087
- const obs = awcToObservation(m);
3088
- if (obs !== null) {
3089
- rows.push(asLiveObservation(obs, tag));
3130
+ const obs2 = awcToObservation(m);
3131
+ if (obs2 !== null) {
3132
+ rows.push(asLiveObservation(obs2, tag));
3090
3133
  }
3091
3134
  }
3092
3135
  return rows;
@@ -3142,8 +3185,8 @@ var mostlyright = (() => {
3142
3185
  const response = await fetchWithRetry2(url);
3143
3186
  const csv = await response.text();
3144
3187
  const override = reportType === 3 ? "METAR" : "SPECI";
3145
- const obs = parseIemCsv2(csv, { observationTypeOverride: override });
3146
- for (const row of obs) {
3188
+ const obs2 = parseIemCsv2(csv, { observationTypeOverride: override });
3189
+ for (const row of obs2) {
3147
3190
  rows.push(asLiveObservation(row, tag));
3148
3191
  }
3149
3192
  }
@@ -3355,14 +3398,1394 @@ var mostlyright = (() => {
3355
3398
  }
3356
3399
  }
3357
3400
  }
3358
- return rows;
3401
+ return rows;
3402
+ }
3403
+
3404
+ // ../weather/src/forecasts/nwp-stub.ts
3405
+ init_src();
3406
+ var IEM_MOS_COVERED_STATIONS = /* @__PURE__ */ new Set([
3407
+ "KNYC",
3408
+ "KLAX",
3409
+ "KORD",
3410
+ "KMIA",
3411
+ "KDEN",
3412
+ "KSEA",
3413
+ "KATL"
3414
+ ]);
3415
+ function buildHint(station, model) {
3416
+ const hasMosCoverage = IEM_MOS_COVERED_STATIONS.has(station.toUpperCase());
3417
+ 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.`;
3418
+ 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.`;
3419
+ }
3420
+ async function forecastNwp(station, model, _opts = {}) {
3421
+ throw new DataAvailabilityError({
3422
+ reason: "model_unavailable",
3423
+ source: "nwp-stub",
3424
+ hint: buildHint(station, model)
3425
+ });
3426
+ }
3427
+
3428
+ // ../weather/src/dailyExtremes.ts
3429
+ init_src();
3430
+
3431
+ // ../core/dist/discovery/index.mjs
3432
+ var STATIONS2 = [
3433
+ {
3434
+ code: "EDDB",
3435
+ country: "DE",
3436
+ ghcnh_id: null,
3437
+ icao: "EDDB",
3438
+ latitude: 52.3667,
3439
+ longitude: 13.5033,
3440
+ name: "Berlin Brandenburg",
3441
+ tz: "Europe/Berlin"
3442
+ },
3443
+ {
3444
+ code: "EDDF",
3445
+ country: "DE",
3446
+ ghcnh_id: null,
3447
+ icao: "EDDF",
3448
+ latitude: 50.0379,
3449
+ longitude: 8.5622,
3450
+ name: "Frankfurt am Main",
3451
+ tz: "Europe/Berlin"
3452
+ },
3453
+ {
3454
+ code: "EDDM",
3455
+ country: "DE",
3456
+ ghcnh_id: null,
3457
+ icao: "EDDM",
3458
+ latitude: 48.3538,
3459
+ longitude: 11.7861,
3460
+ name: "Munich Franz Josef Strauss",
3461
+ tz: "Europe/Berlin"
3462
+ },
3463
+ {
3464
+ code: "EFHK",
3465
+ country: "FI",
3466
+ ghcnh_id: null,
3467
+ icao: "EFHK",
3468
+ latitude: 60.3172,
3469
+ longitude: 24.9633,
3470
+ name: "Helsinki-Vantaa",
3471
+ tz: "Europe/Helsinki"
3472
+ },
3473
+ {
3474
+ code: "EGKK",
3475
+ country: "GB",
3476
+ ghcnh_id: null,
3477
+ icao: "EGKK",
3478
+ latitude: 51.1481,
3479
+ longitude: -0.1903,
3480
+ name: "London Gatwick",
3481
+ tz: "Europe/London"
3482
+ },
3483
+ {
3484
+ code: "EGLL",
3485
+ country: "GB",
3486
+ ghcnh_id: null,
3487
+ icao: "EGLL",
3488
+ latitude: 51.4706,
3489
+ longitude: -0.4619,
3490
+ name: "London Heathrow",
3491
+ tz: "Europe/London"
3492
+ },
3493
+ {
3494
+ code: "EHAM",
3495
+ country: "NL",
3496
+ ghcnh_id: null,
3497
+ icao: "EHAM",
3498
+ latitude: 52.3086,
3499
+ longitude: 4.7639,
3500
+ name: "Amsterdam Schiphol",
3501
+ tz: "Europe/Amsterdam"
3502
+ },
3503
+ {
3504
+ code: "EKCH",
3505
+ country: "DK",
3506
+ ghcnh_id: null,
3507
+ icao: "EKCH",
3508
+ latitude: 55.6181,
3509
+ longitude: 12.6561,
3510
+ name: "Copenhagen Kastrup",
3511
+ tz: "Europe/Copenhagen"
3512
+ },
3513
+ {
3514
+ code: "EPWA",
3515
+ country: "PL",
3516
+ ghcnh_id: null,
3517
+ icao: "EPWA",
3518
+ latitude: 52.1657,
3519
+ longitude: 20.9671,
3520
+ name: "Warsaw Chopin",
3521
+ tz: "Europe/Warsaw"
3522
+ },
3523
+ {
3524
+ code: "ESSA",
3525
+ country: "SE",
3526
+ ghcnh_id: null,
3527
+ icao: "ESSA",
3528
+ latitude: 59.6519,
3529
+ longitude: 17.9186,
3530
+ name: "Stockholm Arlanda",
3531
+ tz: "Europe/Stockholm"
3532
+ },
3533
+ {
3534
+ code: "ATL",
3535
+ country: "US",
3536
+ ghcnh_id: "USW00013874",
3537
+ icao: "KATL",
3538
+ latitude: 33.6407,
3539
+ longitude: -84.4277,
3540
+ name: "Hartsfield-Jackson Atlanta International",
3541
+ tz: "America/New_York"
3542
+ },
3543
+ {
3544
+ code: "AUS",
3545
+ country: "US",
3546
+ ghcnh_id: "USW00013904",
3547
+ icao: "KAUS",
3548
+ latitude: 30.1975,
3549
+ longitude: -97.6664,
3550
+ name: "Austin-Bergstrom International",
3551
+ tz: "America/Chicago"
3552
+ },
3553
+ {
3554
+ code: "BOS",
3555
+ country: "US",
3556
+ ghcnh_id: "USW00014739",
3557
+ icao: "KBOS",
3558
+ latitude: 42.3656,
3559
+ longitude: -71.0096,
3560
+ name: "Boston Logan International",
3561
+ tz: "America/New_York"
3562
+ },
3563
+ {
3564
+ code: "DCA",
3565
+ country: "US",
3566
+ ghcnh_id: "USW00013743",
3567
+ icao: "KDCA",
3568
+ latitude: 38.8512,
3569
+ longitude: -77.0402,
3570
+ name: "Washington Reagan National",
3571
+ tz: "America/New_York"
3572
+ },
3573
+ {
3574
+ code: "DEN",
3575
+ country: "US",
3576
+ ghcnh_id: "USW00003017",
3577
+ icao: "KDEN",
3578
+ latitude: 39.8561,
3579
+ longitude: -104.6737,
3580
+ name: "Denver International",
3581
+ tz: "America/Denver"
3582
+ },
3583
+ {
3584
+ code: "DFW",
3585
+ country: "US",
3586
+ ghcnh_id: "USW00003927",
3587
+ icao: "KDFW",
3588
+ latitude: 32.8998,
3589
+ longitude: -97.0403,
3590
+ name: "Dallas-Fort Worth International",
3591
+ tz: "America/Chicago"
3592
+ },
3593
+ {
3594
+ code: "HOU",
3595
+ country: "US",
3596
+ ghcnh_id: "USW00012918",
3597
+ icao: "KHOU",
3598
+ latitude: 29.6454,
3599
+ longitude: -95.2789,
3600
+ name: "Houston Hobby",
3601
+ tz: "America/Chicago"
3602
+ },
3603
+ {
3604
+ code: "LAS",
3605
+ country: "US",
3606
+ ghcnh_id: "USW00023169",
3607
+ icao: "KLAS",
3608
+ latitude: 36.084,
3609
+ longitude: -115.1537,
3610
+ name: "Harry Reid (McCarran) International",
3611
+ tz: "America/Los_Angeles"
3612
+ },
3613
+ {
3614
+ code: "LAX",
3615
+ country: "US",
3616
+ ghcnh_id: "USW00023174",
3617
+ icao: "KLAX",
3618
+ latitude: 33.9425,
3619
+ longitude: -118.4081,
3620
+ name: "Los Angeles International",
3621
+ tz: "America/Los_Angeles"
3622
+ },
3623
+ {
3624
+ code: "MDW",
3625
+ country: "US",
3626
+ ghcnh_id: "USW00014819",
3627
+ icao: "KMDW",
3628
+ latitude: 41.7868,
3629
+ longitude: -87.7522,
3630
+ name: "Chicago Midway International",
3631
+ tz: "America/Chicago"
3632
+ },
3633
+ {
3634
+ code: "MIA",
3635
+ country: "US",
3636
+ ghcnh_id: "USW00012839",
3637
+ icao: "KMIA",
3638
+ latitude: 25.7959,
3639
+ longitude: -80.287,
3640
+ name: "Miami International",
3641
+ tz: "America/New_York"
3642
+ },
3643
+ {
3644
+ code: "MSP",
3645
+ country: "US",
3646
+ ghcnh_id: "USW00014922",
3647
+ icao: "KMSP",
3648
+ latitude: 44.8848,
3649
+ longitude: -93.2223,
3650
+ name: "Minneapolis-St Paul International",
3651
+ tz: "America/Chicago"
3652
+ },
3653
+ {
3654
+ code: "MSY",
3655
+ country: "US",
3656
+ ghcnh_id: "USW00012916",
3657
+ icao: "KMSY",
3658
+ latitude: 29.9934,
3659
+ longitude: -90.258,
3660
+ name: "New Orleans Louis Armstrong International",
3661
+ tz: "America/Chicago"
3662
+ },
3663
+ {
3664
+ code: "NYC",
3665
+ country: "US",
3666
+ ghcnh_id: "USW00094728",
3667
+ icao: "KNYC",
3668
+ latitude: 40.7789,
3669
+ longitude: -73.9692,
3670
+ name: "Central Park, New York",
3671
+ tz: "America/New_York"
3672
+ },
3673
+ {
3674
+ code: "OKC",
3675
+ country: "US",
3676
+ ghcnh_id: "USW00013967",
3677
+ icao: "KOKC",
3678
+ latitude: 35.3931,
3679
+ longitude: -97.6007,
3680
+ name: "Oklahoma City Will Rogers World",
3681
+ tz: "America/Chicago"
3682
+ },
3683
+ {
3684
+ code: "PHL",
3685
+ country: "US",
3686
+ ghcnh_id: "USW00013739",
3687
+ icao: "KPHL",
3688
+ latitude: 39.8721,
3689
+ longitude: -75.2411,
3690
+ name: "Philadelphia International",
3691
+ tz: "America/New_York"
3692
+ },
3693
+ {
3694
+ code: "PHX",
3695
+ country: "US",
3696
+ ghcnh_id: "USW00023183",
3697
+ icao: "KPHX",
3698
+ latitude: 33.4373,
3699
+ longitude: -112.0078,
3700
+ name: "Phoenix Sky Harbor International",
3701
+ tz: "America/Phoenix"
3702
+ },
3703
+ {
3704
+ code: "SAT",
3705
+ country: "US",
3706
+ ghcnh_id: "USW00012921",
3707
+ icao: "KSAT",
3708
+ latitude: 29.5337,
3709
+ longitude: -98.4698,
3710
+ name: "San Antonio International",
3711
+ tz: "America/Chicago"
3712
+ },
3713
+ {
3714
+ code: "SEA",
3715
+ country: "US",
3716
+ ghcnh_id: "USW00024233",
3717
+ icao: "KSEA",
3718
+ latitude: 47.4502,
3719
+ longitude: -122.3088,
3720
+ name: "Seattle-Tacoma International",
3721
+ tz: "America/Los_Angeles"
3722
+ },
3723
+ {
3724
+ code: "SFO",
3725
+ country: "US",
3726
+ ghcnh_id: "USW00023234",
3727
+ icao: "KSFO",
3728
+ latitude: 37.6213,
3729
+ longitude: -122.379,
3730
+ name: "San Francisco International",
3731
+ tz: "America/Los_Angeles"
3732
+ },
3733
+ {
3734
+ code: "LEBL",
3735
+ country: "ES",
3736
+ ghcnh_id: null,
3737
+ icao: "LEBL",
3738
+ latitude: 41.2974,
3739
+ longitude: 2.0833,
3740
+ name: "Barcelona El Prat",
3741
+ tz: "Europe/Madrid"
3742
+ },
3743
+ {
3744
+ code: "LEMD",
3745
+ country: "ES",
3746
+ ghcnh_id: null,
3747
+ icao: "LEMD",
3748
+ latitude: 40.4719,
3749
+ longitude: -3.5626,
3750
+ name: "Madrid Barajas",
3751
+ tz: "Europe/Madrid"
3752
+ },
3753
+ {
3754
+ code: "LFPB",
3755
+ country: "FR",
3756
+ ghcnh_id: null,
3757
+ icao: "LFPB",
3758
+ latitude: 48.9694,
3759
+ longitude: 2.4414,
3760
+ name: "Paris Le Bourget",
3761
+ tz: "Europe/Paris"
3762
+ },
3763
+ {
3764
+ code: "LFPG",
3765
+ country: "FR",
3766
+ ghcnh_id: null,
3767
+ icao: "LFPG",
3768
+ latitude: 49.0097,
3769
+ longitude: 2.5479,
3770
+ name: "Paris Charles de Gaulle",
3771
+ tz: "Europe/Paris"
3772
+ },
3773
+ {
3774
+ code: "LFPO",
3775
+ country: "FR",
3776
+ ghcnh_id: null,
3777
+ icao: "LFPO",
3778
+ latitude: 48.7233,
3779
+ longitude: 2.3794,
3780
+ name: "Paris Orly",
3781
+ tz: "Europe/Paris"
3782
+ },
3783
+ {
3784
+ code: "LIMC",
3785
+ country: "IT",
3786
+ ghcnh_id: null,
3787
+ icao: "LIMC",
3788
+ latitude: 45.6306,
3789
+ longitude: 8.7281,
3790
+ name: "Milan Malpensa",
3791
+ tz: "Europe/Rome"
3792
+ },
3793
+ {
3794
+ code: "LIRF",
3795
+ country: "IT",
3796
+ ghcnh_id: null,
3797
+ icao: "LIRF",
3798
+ latitude: 41.8003,
3799
+ longitude: 12.2389,
3800
+ name: "Rome Fiumicino",
3801
+ tz: "Europe/Rome"
3802
+ },
3803
+ {
3804
+ code: "LOWW",
3805
+ country: "AT",
3806
+ ghcnh_id: null,
3807
+ icao: "LOWW",
3808
+ latitude: 48.1103,
3809
+ longitude: 16.5697,
3810
+ name: "Vienna International",
3811
+ tz: "Europe/Vienna"
3812
+ },
3813
+ {
3814
+ code: "LSZH",
3815
+ country: "CH",
3816
+ ghcnh_id: null,
3817
+ icao: "LSZH",
3818
+ latitude: 47.4647,
3819
+ longitude: 8.5492,
3820
+ name: "Zurich",
3821
+ tz: "Europe/Zurich"
3822
+ },
3823
+ {
3824
+ code: "NZAA",
3825
+ country: "NZ",
3826
+ ghcnh_id: null,
3827
+ icao: "NZAA",
3828
+ latitude: -37.0081,
3829
+ longitude: 174.7917,
3830
+ name: "Auckland",
3831
+ tz: "Pacific/Auckland"
3832
+ },
3833
+ {
3834
+ code: "NZWN",
3835
+ country: "NZ",
3836
+ ghcnh_id: null,
3837
+ icao: "NZWN",
3838
+ latitude: -41.3272,
3839
+ longitude: 174.8053,
3840
+ name: "Wellington",
3841
+ tz: "Pacific/Auckland"
3842
+ },
3843
+ {
3844
+ code: "OERK",
3845
+ country: "SA",
3846
+ ghcnh_id: null,
3847
+ icao: "OERK",
3848
+ latitude: 24.9576,
3849
+ longitude: 46.6988,
3850
+ name: "Riyadh King Khalid International",
3851
+ tz: "Asia/Riyadh"
3852
+ },
3853
+ {
3854
+ code: "OMDB",
3855
+ country: "AE",
3856
+ ghcnh_id: null,
3857
+ icao: "OMDB",
3858
+ latitude: 25.2532,
3859
+ longitude: 55.3657,
3860
+ name: "Dubai International",
3861
+ tz: "Asia/Dubai"
3862
+ },
3863
+ {
3864
+ code: "OTHH",
3865
+ country: "QA",
3866
+ ghcnh_id: null,
3867
+ icao: "OTHH",
3868
+ latitude: 25.2731,
3869
+ longitude: 51.608,
3870
+ name: "Doha Hamad International",
3871
+ tz: "Asia/Qatar"
3872
+ },
3873
+ {
3874
+ code: "RCTP",
3875
+ country: "TW",
3876
+ ghcnh_id: null,
3877
+ icao: "RCTP",
3878
+ latitude: 25.0777,
3879
+ longitude: 121.2328,
3880
+ name: "Taipei Taoyuan",
3881
+ tz: "Asia/Taipei"
3882
+ },
3883
+ {
3884
+ code: "RJAA",
3885
+ country: "JP",
3886
+ ghcnh_id: null,
3887
+ icao: "RJAA",
3888
+ latitude: 35.7647,
3889
+ longitude: 140.3864,
3890
+ name: "Tokyo Narita",
3891
+ tz: "Asia/Tokyo"
3892
+ },
3893
+ {
3894
+ code: "RJTT",
3895
+ country: "JP",
3896
+ ghcnh_id: null,
3897
+ icao: "RJTT",
3898
+ latitude: 35.5522,
3899
+ longitude: 139.78,
3900
+ name: "Tokyo Haneda",
3901
+ tz: "Asia/Tokyo"
3902
+ },
3903
+ {
3904
+ code: "RKSI",
3905
+ country: "KR",
3906
+ ghcnh_id: null,
3907
+ icao: "RKSI",
3908
+ latitude: 37.4691,
3909
+ longitude: 126.4505,
3910
+ name: "Seoul Incheon",
3911
+ tz: "Asia/Seoul"
3912
+ },
3913
+ {
3914
+ code: "SAEZ",
3915
+ country: "AR",
3916
+ ghcnh_id: null,
3917
+ icao: "SAEZ",
3918
+ latitude: -34.8222,
3919
+ longitude: -58.5358,
3920
+ name: "Buenos Aires Ezeiza",
3921
+ tz: "America/Argentina/Buenos_Aires"
3922
+ },
3923
+ {
3924
+ code: "SBGR",
3925
+ country: "BR",
3926
+ ghcnh_id: null,
3927
+ icao: "SBGR",
3928
+ latitude: -23.4356,
3929
+ longitude: -46.4731,
3930
+ name: "S\xE3o Paulo Guarulhos",
3931
+ tz: "America/Sao_Paulo"
3932
+ },
3933
+ {
3934
+ code: "UUEE",
3935
+ country: "RU",
3936
+ ghcnh_id: null,
3937
+ icao: "UUEE",
3938
+ latitude: 55.9728,
3939
+ longitude: 37.4147,
3940
+ name: "Moscow Sheremetyevo",
3941
+ tz: "Europe/Moscow"
3942
+ },
3943
+ {
3944
+ code: "VABB",
3945
+ country: "IN",
3946
+ ghcnh_id: null,
3947
+ icao: "VABB",
3948
+ latitude: 19.0887,
3949
+ longitude: 72.8679,
3950
+ name: "Mumbai Chhatrapati Shivaji",
3951
+ tz: "Asia/Kolkata"
3952
+ },
3953
+ {
3954
+ code: "VHHH",
3955
+ country: "HK",
3956
+ ghcnh_id: null,
3957
+ icao: "VHHH",
3958
+ latitude: 22.308,
3959
+ longitude: 113.9185,
3960
+ name: "Hong Kong International",
3961
+ tz: "Asia/Hong_Kong"
3962
+ },
3963
+ {
3964
+ code: "VIDP",
3965
+ country: "IN",
3966
+ ghcnh_id: null,
3967
+ icao: "VIDP",
3968
+ latitude: 28.5562,
3969
+ longitude: 77.1,
3970
+ name: "Delhi Indira Gandhi",
3971
+ tz: "Asia/Kolkata"
3972
+ },
3973
+ {
3974
+ code: "VTBS",
3975
+ country: "TH",
3976
+ ghcnh_id: null,
3977
+ icao: "VTBS",
3978
+ latitude: 13.69,
3979
+ longitude: 100.7501,
3980
+ name: "Bangkok Suvarnabhumi",
3981
+ tz: "Asia/Bangkok"
3982
+ },
3983
+ {
3984
+ code: "WSSS",
3985
+ country: "SG",
3986
+ ghcnh_id: null,
3987
+ icao: "WSSS",
3988
+ latitude: 1.3644,
3989
+ longitude: 103.9915,
3990
+ name: "Singapore Changi",
3991
+ tz: "Asia/Singapore"
3992
+ },
3993
+ {
3994
+ code: "YBBN",
3995
+ country: "AU",
3996
+ ghcnh_id: null,
3997
+ icao: "YBBN",
3998
+ latitude: -27.3842,
3999
+ longitude: 153.1175,
4000
+ name: "Brisbane",
4001
+ tz: "Australia/Brisbane"
4002
+ },
4003
+ {
4004
+ code: "YMML",
4005
+ country: "AU",
4006
+ ghcnh_id: null,
4007
+ icao: "YMML",
4008
+ latitude: -37.6733,
4009
+ longitude: 144.8433,
4010
+ name: "Melbourne Tullamarine",
4011
+ tz: "Australia/Melbourne"
4012
+ },
4013
+ {
4014
+ code: "YSSY",
4015
+ country: "AU",
4016
+ ghcnh_id: null,
4017
+ icao: "YSSY",
4018
+ latitude: -33.9461,
4019
+ longitude: 151.1772,
4020
+ name: "Sydney Kingsford Smith",
4021
+ tz: "Australia/Sydney"
4022
+ },
4023
+ {
4024
+ code: "ZBAA",
4025
+ country: "CN",
4026
+ ghcnh_id: null,
4027
+ icao: "ZBAA",
4028
+ latitude: 40.0801,
4029
+ longitude: 116.5846,
4030
+ name: "Beijing Capital",
4031
+ tz: "Asia/Shanghai"
4032
+ },
4033
+ {
4034
+ code: "ZSPD",
4035
+ country: "CN",
4036
+ ghcnh_id: null,
4037
+ icao: "ZSPD",
4038
+ latitude: 31.1443,
4039
+ longitude: 121.8083,
4040
+ name: "Shanghai Pudong",
4041
+ tz: "Asia/Shanghai"
4042
+ }
4043
+ ];
4044
+ var STATION_BY_CODE2 = /* @__PURE__ */ new Map([
4045
+ ["ATL", STATIONS2[10]],
4046
+ ["AUS", STATIONS2[11]],
4047
+ ["BOS", STATIONS2[12]],
4048
+ ["DCA", STATIONS2[13]],
4049
+ ["DEN", STATIONS2[14]],
4050
+ ["DFW", STATIONS2[15]],
4051
+ ["EDDB", STATIONS2[0]],
4052
+ ["EDDF", STATIONS2[1]],
4053
+ ["EDDM", STATIONS2[2]],
4054
+ ["EFHK", STATIONS2[3]],
4055
+ ["EGKK", STATIONS2[4]],
4056
+ ["EGLL", STATIONS2[5]],
4057
+ ["EHAM", STATIONS2[6]],
4058
+ ["EKCH", STATIONS2[7]],
4059
+ ["EPWA", STATIONS2[8]],
4060
+ ["ESSA", STATIONS2[9]],
4061
+ ["HOU", STATIONS2[16]],
4062
+ ["LAS", STATIONS2[17]],
4063
+ ["LAX", STATIONS2[18]],
4064
+ ["LEBL", STATIONS2[30]],
4065
+ ["LEMD", STATIONS2[31]],
4066
+ ["LFPB", STATIONS2[32]],
4067
+ ["LFPG", STATIONS2[33]],
4068
+ ["LFPO", STATIONS2[34]],
4069
+ ["LIMC", STATIONS2[35]],
4070
+ ["LIRF", STATIONS2[36]],
4071
+ ["LOWW", STATIONS2[37]],
4072
+ ["LSZH", STATIONS2[38]],
4073
+ ["MDW", STATIONS2[19]],
4074
+ ["MIA", STATIONS2[20]],
4075
+ ["MSP", STATIONS2[21]],
4076
+ ["MSY", STATIONS2[22]],
4077
+ ["NYC", STATIONS2[23]],
4078
+ ["NZAA", STATIONS2[39]],
4079
+ ["NZWN", STATIONS2[40]],
4080
+ ["OERK", STATIONS2[41]],
4081
+ ["OKC", STATIONS2[24]],
4082
+ ["OMDB", STATIONS2[42]],
4083
+ ["OTHH", STATIONS2[43]],
4084
+ ["PHL", STATIONS2[25]],
4085
+ ["PHX", STATIONS2[26]],
4086
+ ["RCTP", STATIONS2[44]],
4087
+ ["RJAA", STATIONS2[45]],
4088
+ ["RJTT", STATIONS2[46]],
4089
+ ["RKSI", STATIONS2[47]],
4090
+ ["SAEZ", STATIONS2[48]],
4091
+ ["SAT", STATIONS2[27]],
4092
+ ["SBGR", STATIONS2[49]],
4093
+ ["SEA", STATIONS2[28]],
4094
+ ["SFO", STATIONS2[29]],
4095
+ ["UUEE", STATIONS2[50]],
4096
+ ["VABB", STATIONS2[51]],
4097
+ ["VHHH", STATIONS2[52]],
4098
+ ["VIDP", STATIONS2[53]],
4099
+ ["VTBS", STATIONS2[54]],
4100
+ ["WSSS", STATIONS2[55]],
4101
+ ["YBBN", STATIONS2[56]],
4102
+ ["YMML", STATIONS2[57]],
4103
+ ["YSSY", STATIONS2[58]],
4104
+ ["ZBAA", STATIONS2[59]],
4105
+ ["ZSPD", STATIONS2[60]]
4106
+ ]);
4107
+ var STATION_BY_ICAO2 = /* @__PURE__ */ new Map([
4108
+ ["EDDB", STATIONS2[0]],
4109
+ ["EDDF", STATIONS2[1]],
4110
+ ["EDDM", STATIONS2[2]],
4111
+ ["EFHK", STATIONS2[3]],
4112
+ ["EGKK", STATIONS2[4]],
4113
+ ["EGLL", STATIONS2[5]],
4114
+ ["EHAM", STATIONS2[6]],
4115
+ ["EKCH", STATIONS2[7]],
4116
+ ["EPWA", STATIONS2[8]],
4117
+ ["ESSA", STATIONS2[9]],
4118
+ ["KATL", STATIONS2[10]],
4119
+ ["KAUS", STATIONS2[11]],
4120
+ ["KBOS", STATIONS2[12]],
4121
+ ["KDCA", STATIONS2[13]],
4122
+ ["KDEN", STATIONS2[14]],
4123
+ ["KDFW", STATIONS2[15]],
4124
+ ["KHOU", STATIONS2[16]],
4125
+ ["KLAS", STATIONS2[17]],
4126
+ ["KLAX", STATIONS2[18]],
4127
+ ["KMDW", STATIONS2[19]],
4128
+ ["KMIA", STATIONS2[20]],
4129
+ ["KMSP", STATIONS2[21]],
4130
+ ["KMSY", STATIONS2[22]],
4131
+ ["KNYC", STATIONS2[23]],
4132
+ ["KOKC", STATIONS2[24]],
4133
+ ["KPHL", STATIONS2[25]],
4134
+ ["KPHX", STATIONS2[26]],
4135
+ ["KSAT", STATIONS2[27]],
4136
+ ["KSEA", STATIONS2[28]],
4137
+ ["KSFO", STATIONS2[29]],
4138
+ ["LEBL", STATIONS2[30]],
4139
+ ["LEMD", STATIONS2[31]],
4140
+ ["LFPB", STATIONS2[32]],
4141
+ ["LFPG", STATIONS2[33]],
4142
+ ["LFPO", STATIONS2[34]],
4143
+ ["LIMC", STATIONS2[35]],
4144
+ ["LIRF", STATIONS2[36]],
4145
+ ["LOWW", STATIONS2[37]],
4146
+ ["LSZH", STATIONS2[38]],
4147
+ ["NZAA", STATIONS2[39]],
4148
+ ["NZWN", STATIONS2[40]],
4149
+ ["OERK", STATIONS2[41]],
4150
+ ["OMDB", STATIONS2[42]],
4151
+ ["OTHH", STATIONS2[43]],
4152
+ ["RCTP", STATIONS2[44]],
4153
+ ["RJAA", STATIONS2[45]],
4154
+ ["RJTT", STATIONS2[46]],
4155
+ ["RKSI", STATIONS2[47]],
4156
+ ["SAEZ", STATIONS2[48]],
4157
+ ["SBGR", STATIONS2[49]],
4158
+ ["UUEE", STATIONS2[50]],
4159
+ ["VABB", STATIONS2[51]],
4160
+ ["VHHH", STATIONS2[52]],
4161
+ ["VIDP", STATIONS2[53]],
4162
+ ["VTBS", STATIONS2[54]],
4163
+ ["WSSS", STATIONS2[55]],
4164
+ ["YBBN", STATIONS2[56]],
4165
+ ["YMML", STATIONS2[57]],
4166
+ ["YSSY", STATIONS2[58]],
4167
+ ["ZBAA", STATIONS2[59]],
4168
+ ["ZSPD", STATIONS2[60]]
4169
+ ]);
4170
+ var LOW_COVERAGE_THRESHOLD = 12;
4171
+ var PARTS_CACHE = /* @__PURE__ */ new Map();
4172
+ function getDateFormatter(tz) {
4173
+ let f = PARTS_CACHE.get(tz);
4174
+ if (f === void 0) {
4175
+ f = new Intl.DateTimeFormat("en-US", {
4176
+ timeZone: tz,
4177
+ year: "numeric",
4178
+ month: "2-digit",
4179
+ day: "2-digit"
4180
+ });
4181
+ PARTS_CACHE.set(tz, f);
4182
+ }
4183
+ return f;
4184
+ }
4185
+ function localDateFor(instant, tz) {
4186
+ const parts = getDateFormatter(tz).formatToParts(instant);
4187
+ let y = "";
4188
+ let m = "";
4189
+ let d = "";
4190
+ for (const p of parts) {
4191
+ if (p.type === "year") y = p.value;
4192
+ else if (p.type === "month") m = p.value;
4193
+ else if (p.type === "day") d = p.value;
4194
+ }
4195
+ return `${y}-${m}-${d}`;
4196
+ }
4197
+ function parseInstant(observed) {
4198
+ if (observed === void 0 || observed === null || observed.length === 0) {
4199
+ return null;
4200
+ }
4201
+ const ms = Date.parse(observed);
4202
+ if (Number.isNaN(ms)) return null;
4203
+ return new Date(ms);
4204
+ }
4205
+ function roundHalfUp(value, places) {
4206
+ if (!Number.isFinite(value)) return value;
4207
+ const scale = 10 ** places;
4208
+ const sign = value < 0 ? -1 : 1;
4209
+ const abs = Math.abs(value);
4210
+ const rounded = Math.floor(abs * scale + 0.5 + abs * 1e-12) / scale;
4211
+ return sign * rounded;
4212
+ }
4213
+ function cToF(c) {
4214
+ return c * 1.8 + 32;
4215
+ }
4216
+ function internationalDailyExtremes(rows, opts) {
4217
+ const tz = opts.stationTz;
4218
+ if (typeof tz !== "string" || tz.length === 0) {
4219
+ throw new RangeError("internationalDailyExtremes: stationTz is required (non-empty string)");
4220
+ }
4221
+ const precision = opts.precision ?? 0;
4222
+ const minObs = opts.minObs ?? LOW_COVERAGE_THRESHOLD;
4223
+ try {
4224
+ getDateFormatter(tz);
4225
+ } catch (e) {
4226
+ throw new RangeError(
4227
+ `internationalDailyExtremes: invalid stationTz ${JSON.stringify(tz)}: ${e.message}`
4228
+ );
4229
+ }
4230
+ const byLocalDate = /* @__PURE__ */ new Map();
4231
+ for (const row of rows) {
4232
+ const instant = parseInstant(row.observed_at);
4233
+ if (instant === null) continue;
4234
+ const localDate = localDateFor(instant, tz);
4235
+ let bucket = byLocalDate.get(localDate);
4236
+ if (bucket === void 0) {
4237
+ bucket = { temps: [], precipMm: 0 };
4238
+ byLocalDate.set(localDate, bucket);
4239
+ }
4240
+ const t = row.temp_c;
4241
+ if (typeof t === "number" && Number.isFinite(t)) {
4242
+ bucket.temps.push({ value: t, source: row.source ?? null });
4243
+ }
4244
+ const p = row.precip_mm_1h;
4245
+ if (typeof p === "number" && Number.isFinite(p)) {
4246
+ bucket.precipMm += p;
4247
+ }
4248
+ }
4249
+ const out = [];
4250
+ const sortedDates = [...byLocalDate.keys()].sort();
4251
+ for (const localDate of sortedDates) {
4252
+ const bucket = byLocalDate.get(localDate);
4253
+ if (bucket === void 0) continue;
4254
+ const nObs = bucket.temps.length;
4255
+ let tempMinC = null;
4256
+ let tempMaxC = null;
4257
+ let tempMeanC = null;
4258
+ let sourceTmin = null;
4259
+ let sourceTmax = null;
4260
+ if (nObs > 0 && nObs >= minObs) {
4261
+ let minIdx = 0;
4262
+ let maxIdx = 0;
4263
+ let sum = 0;
4264
+ for (let i = 0; i < bucket.temps.length; i += 1) {
4265
+ const v = bucket.temps[i];
4266
+ sum += v.value;
4267
+ const minRow2 = bucket.temps[minIdx];
4268
+ const maxRow2 = bucket.temps[maxIdx];
4269
+ if (v.value < minRow2.value) minIdx = i;
4270
+ if (v.value > maxRow2.value) maxIdx = i;
4271
+ }
4272
+ const mean = sum / nObs;
4273
+ const minRow = bucket.temps[minIdx];
4274
+ const maxRow = bucket.temps[maxIdx];
4275
+ tempMinC = roundHalfUp(minRow.value, precision);
4276
+ tempMaxC = roundHalfUp(maxRow.value, precision);
4277
+ tempMeanC = roundHalfUp(mean, precision);
4278
+ sourceTmin = minRow.source;
4279
+ sourceTmax = maxRow.source;
4280
+ }
4281
+ out.push(
4282
+ Object.freeze({
4283
+ localDate,
4284
+ nObs,
4285
+ tempMinC,
4286
+ tempMaxC,
4287
+ tempMeanC,
4288
+ tempMinF: tempMinC === null ? null : roundHalfUp(cToF(tempMinC), precision),
4289
+ tempMaxF: tempMaxC === null ? null : roundHalfUp(cToF(tempMaxC), precision),
4290
+ precipMm: roundHalfUp(bucket.precipMm, 4),
4291
+ sourceTmin,
4292
+ sourceTmax
4293
+ })
4294
+ );
4295
+ }
4296
+ return out;
4297
+ }
4298
+ var BUILT_IN_SCHEMAS = Object.freeze([
4299
+ {
4300
+ id: "schema.observation.v1",
4301
+ title: "schema.observation.v1",
4302
+ columnCount: 20,
4303
+ columns: [
4304
+ { name: "dew_point_c", description: "units: celsius \u2014 bounded", nullable: true },
4305
+ { name: "event_time", description: "observation valid time", nullable: false },
4306
+ {
4307
+ name: "metar_raw",
4308
+ description: "raw METAR text if source has it; null for AWC JSON (structured-only)",
4309
+ nullable: true
4310
+ },
4311
+ {
4312
+ name: "observation_type",
4313
+ description: "METAR | SPECI; defaults METAR when source can't distinguish (e.g. AWC JSON)",
4314
+ nullable: false
4315
+ },
4316
+ {
4317
+ name: "precip_mm_1h",
4318
+ description: "units: mm \u2014 hourly precip (METAR p01i, converted from inches)",
4319
+ nullable: true
4320
+ },
4321
+ {
4322
+ name: "sky_base_1_m",
4323
+ description: "units: meters \u2014 first cloud layer base height (converted from feet)",
4324
+ nullable: true
4325
+ },
4326
+ { name: "sky_base_2_m", description: "units: meters", nullable: true },
4327
+ { name: "sky_base_3_m", description: "units: meters", nullable: true },
4328
+ { name: "sky_base_4_m", description: "units: meters", nullable: true },
4329
+ { name: "sky_cover_1", description: "first cloud layer cover code", nullable: true },
4330
+ { name: "sky_cover_2", description: "second layer; null if not present", nullable: true },
4331
+ { name: "sky_cover_3", description: "third layer; null if not present", nullable: true },
4332
+ { name: "sky_cover_4", description: "fourth layer; null if not present", nullable: true },
4333
+ {
4334
+ name: "slp_hpa",
4335
+ description: "units: hPa \u2014 sea-level pressure (canonical aviation unit, not converted across modes)",
4336
+ nullable: true
4337
+ },
4338
+ { name: "station", description: "ICAO/ASOS station ID (e.g. KORD)", nullable: false },
4339
+ {
4340
+ name: "temp_c",
4341
+ description: "units: celsius \u2014 bounded TEMP_MIN_C..TEMP_MAX_C",
4342
+ nullable: true
4343
+ },
4344
+ {
4345
+ name: "visibility_m",
4346
+ description: "units: meters \u2014 converted from statute miles",
4347
+ nullable: true
4348
+ },
4349
+ {
4350
+ name: "wind_dir_deg",
4351
+ description: "units: degrees \u2014 0-360, bounded",
4352
+ nullable: true
4353
+ },
4354
+ { name: "wind_gust_ms", description: "units: m/s \u2014 converted from kt", nullable: true },
4355
+ { name: "wind_speed_ms", description: "units: m/s \u2014 converted from kt", nullable: true }
4356
+ ]
4357
+ },
4358
+ {
4359
+ id: "schema.forecast.iem_mos.v1",
4360
+ title: "schema.forecast.iem_mos.v1",
4361
+ columnCount: 11,
4362
+ columns: [
4363
+ { name: "dew_point_c", description: "units: celsius", nullable: true },
4364
+ {
4365
+ name: "forecast_hour",
4366
+ description: "units: hours \u2014 (valid_at - issued_at).total_seconds() / 3600",
4367
+ nullable: false
4368
+ },
4369
+ {
4370
+ name: "issued_at",
4371
+ description: "model run time (from source `runtime` field)",
4372
+ nullable: false
4373
+ },
4374
+ { name: "model", description: "e.g. NBE, GFS, LAV, MET", nullable: false },
4375
+ {
4376
+ name: "precip_probability",
4377
+ description: "units: probability \u2014 bounded [0, 1]",
4378
+ nullable: true
4379
+ },
4380
+ {
4381
+ name: "sky_cover_pct",
4382
+ description: "units: percent \u2014 bounded [0, 100]",
4383
+ nullable: true
4384
+ },
4385
+ { name: "station", description: "", nullable: false },
4386
+ { name: "temp_c", description: "units: celsius", nullable: true },
4387
+ {
4388
+ name: "valid_at",
4389
+ description: "forecast target time (from source `ftime`)",
4390
+ nullable: false
4391
+ },
4392
+ { name: "wind_dir_deg", description: "units: degrees", nullable: true },
4393
+ { name: "wind_speed_ms", description: "units: m/s", nullable: true }
4394
+ ]
4395
+ },
4396
+ {
4397
+ id: "schema.settlement.cli.v1",
4398
+ title: "schema.settlement.cli.v1",
4399
+ columnCount: 12,
4400
+ columns: [
4401
+ {
4402
+ name: "cli_data_quality",
4403
+ 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.",
4404
+ nullable: false
4405
+ },
4406
+ {
4407
+ name: "event_time",
4408
+ description: "00:00 local time on observation_date converted to UTC; for sort/join only",
4409
+ nullable: false
4410
+ },
4411
+ {
4412
+ name: "observation_date",
4413
+ description: "local climate day per NWS convention (no timezone applied to the date itself)",
4414
+ nullable: false
4415
+ },
4416
+ { name: "precipitation_in", description: "units: inches", nullable: true },
4417
+ {
4418
+ name: "product_release_time",
4419
+ description: "parsed from CLI product header (_climate.py::_parse_product_timestamp)",
4420
+ nullable: false
4421
+ },
4422
+ {
4423
+ name: "report_type",
4424
+ description: "preliminary | final | correction; dedup priority preliminary < final < correction",
4425
+ nullable: false
4426
+ },
4427
+ {
4428
+ name: "settlement_finality",
4429
+ description: "provisional | final | superseded. Kalshi NHIGH/NLOW settlement contractually requires 'final'; 'provisional' values are kept for early-look research only.",
4430
+ nullable: false
4431
+ },
4432
+ { name: "snowfall_in", description: "units: inches", nullable: true },
4433
+ { name: "station", description: "ICAO/ASOS station ID", nullable: false },
4434
+ {
4435
+ name: "station_tz",
4436
+ description: "IANA timezone for the station (e.g. America/Chicago for KORD). Required for local-climate-day semantics; see \xA7U.",
4437
+ nullable: false
4438
+ },
4439
+ {
4440
+ name: "temp_max_F",
4441
+ description: "units: fahrenheit \u2014 daily high (uppercase F for consistency with obs imperial mode)",
4442
+ nullable: true
4443
+ },
4444
+ { name: "temp_min_F", description: "units: fahrenheit \u2014 daily low", nullable: true }
4445
+ ]
4446
+ },
4447
+ {
4448
+ id: "schema.observation_ledger.v1",
4449
+ title: "schema.observation_ledger.v1",
4450
+ columnCount: 15,
4451
+ columns: [
4452
+ { name: "as_of_time", description: "", nullable: true },
4453
+ { name: "dewpoint_c", description: "units: celsius", nullable: true },
4454
+ { name: "ingestion_id", description: "", nullable: true },
4455
+ { name: "observation_kind", description: "", nullable: true },
4456
+ {
4457
+ name: "observation_quality",
4458
+ 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.",
4459
+ nullable: true
4460
+ },
4461
+ { name: "observation_type", description: "", nullable: false },
4462
+ { name: "observed_at", description: "", nullable: false },
4463
+ { name: "parser_name", description: "", nullable: true },
4464
+ { name: "parser_version", description: "", nullable: true },
4465
+ { name: "provenance", description: "", nullable: true },
4466
+ { name: "qc_status", description: "", nullable: true },
4467
+ {
4468
+ name: "source",
4469
+ description: "ncei reserved per D-2.1-09; never written in v0.1.0.",
4470
+ nullable: false
4471
+ },
4472
+ { name: "source_received_at", description: "", nullable: true },
4473
+ { name: "station_code", description: "", nullable: false },
4474
+ { name: "temp_c", description: "units: celsius", nullable: true }
4475
+ ]
4476
+ },
4477
+ {
4478
+ id: "schema.observation_qc.v1",
4479
+ title: "schema.observation_qc.v1",
4480
+ columnCount: 13,
4481
+ columns: [
4482
+ { name: "as_of_time", description: "", nullable: true },
4483
+ {
4484
+ name: "detector_metadata",
4485
+ description: "JSON-serialized detector payload; shape per qc_system.",
4486
+ nullable: true
4487
+ },
4488
+ {
4489
+ name: "field",
4490
+ description: "Observation column the rule evaluated (e.g. temp_c).",
4491
+ nullable: false
4492
+ },
4493
+ { name: "flag", description: "", nullable: false },
4494
+ { name: "ingestion_id", description: "", nullable: true },
4495
+ { name: "observation_kind", description: "", nullable: true },
4496
+ { name: "observed_at", description: "", nullable: false },
4497
+ { name: "parser_name", description: "", nullable: true },
4498
+ { name: "qc_system", description: "", nullable: false },
4499
+ { name: "qc_version", description: "", nullable: false },
4500
+ { name: "rule_id", description: "", nullable: false },
4501
+ { name: "source", description: "", nullable: false },
4502
+ { name: "station_code", description: "", nullable: false }
4503
+ ]
4504
+ }
4505
+ ]);
4506
+ function deepFreezeSchema(info) {
4507
+ const frozenCols = Object.freeze(info.columns.map((c) => Object.freeze({ ...c })));
4508
+ return Object.freeze({ ...info, columns: frozenCols });
4509
+ }
4510
+ var REGISTRY = new Map(
4511
+ BUILT_IN_SCHEMAS.map((info) => [info.id, deepFreezeSchema(info)])
4512
+ );
4513
+ var FEATURE_NAMES = Object.freeze([
4514
+ "calendarFeatures",
4515
+ "clipOutliers",
4516
+ "diff",
4517
+ "diff2",
4518
+ "heatIndex",
4519
+ "lag",
4520
+ "rolling",
4521
+ "spread",
4522
+ "windChill"
4523
+ ]);
4524
+
4525
+ // ../weather/src/dailyExtremes.ts
4526
+ init_iem_asos();
4527
+ init_awc();
4528
+ init_iem();
4529
+ var LOW_COVERAGE_THRESHOLD2 = 12;
4530
+ function addUtcDays(iso, days) {
4531
+ const [yStr, mStr, dStr] = iso.split("-");
4532
+ const dt = new Date(Date.UTC(Number(yStr), Number(mStr) - 1, Number(dStr)));
4533
+ dt.setUTCDate(dt.getUTCDate() + days);
4534
+ const yyyy = dt.getUTCFullYear();
4535
+ const mm = String(dt.getUTCMonth() + 1).padStart(2, "0");
4536
+ const dd = String(dt.getUTCDate()).padStart(2, "0");
4537
+ return `${yyyy}-${mm}-${dd}`;
4538
+ }
4539
+ function lookupStation(icao) {
4540
+ const upper = icao.toUpperCase();
4541
+ for (const s of STATIONS) {
4542
+ if (s.icao === upper) {
4543
+ return { tz: s.tz, isUs: s.country === "US" };
4544
+ }
4545
+ }
4546
+ throw new Error(`dailyExtremes: station "${icao}" not in registry \u2014 check STATIONS catalog`);
4547
+ }
4548
+ function cToF2(c) {
4549
+ if (c === null) return null;
4550
+ return c * (9 / 5) + 32;
4551
+ }
4552
+ function roundHalfUp2(value, decimals) {
4553
+ const m = 10 ** decimals;
4554
+ return Math.round(value * m) / m;
4555
+ }
4556
+ async function fetchIemAsosObservations(station, fromDate, toDate) {
4557
+ const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);
4558
+ const toYear = Number.parseInt(toDate.slice(0, 4), 10);
4559
+ const out = [];
4560
+ for (let year = fromYear; year <= toYear; year++) {
4561
+ const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {
4562
+ reportType: 3,
4563
+ politenessMs: 1e3
4564
+ });
4565
+ for (const chunk of chunks) {
4566
+ const parsed = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
4567
+ for (const row of parsed) {
4568
+ const obsDate = row.observed_at.slice(0, 10);
4569
+ if (obsDate >= fromDate && obsDate <= toDate) {
4570
+ const precipInches = row.precip_1hr_inches ?? null;
4571
+ out.push({
4572
+ observed_at: row.observed_at,
4573
+ temp_c: row.temp_c ?? null,
4574
+ precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,
4575
+ source: row.source
4576
+ });
4577
+ }
4578
+ }
4579
+ }
4580
+ }
4581
+ return out;
4582
+ }
4583
+ async function fetchAwcObservations(station, fromDate, toDate) {
4584
+ const raw = await fetchAwcMetars([station]);
4585
+ const out = [];
4586
+ for (const r of raw) {
4587
+ const obs2 = awcToObservation(r);
4588
+ if (obs2 === null) continue;
4589
+ const obsDate = obs2.observed_at.slice(0, 10);
4590
+ if (obsDate >= fromDate && obsDate <= toDate) {
4591
+ const precipInches = obs2.precip_1hr_inches ?? null;
4592
+ out.push({
4593
+ observed_at: obs2.observed_at,
4594
+ temp_c: obs2.temp_c ?? null,
4595
+ precip_mm_1h: precipInches !== null ? precipInches * 25.4 : null,
4596
+ source: obs2.source
4597
+ });
4598
+ }
4599
+ }
4600
+ return out;
4601
+ }
4602
+ async function fetchForMode(station, fromDate, toDate, mode) {
4603
+ switch (mode) {
4604
+ case "iem_only":
4605
+ return fetchIemAsosObservations(station, fromDate, toDate);
4606
+ case "awc_only":
4607
+ return fetchAwcObservations(station, fromDate, toDate);
4608
+ case "live_v1": {
4609
+ const [iem, awc] = await Promise.all([
4610
+ fetchIemAsosObservations(station, fromDate, toDate),
4611
+ fetchAwcObservations(station, fromDate, toDate).catch(() => [])
4612
+ ]);
4613
+ return [...iem, ...awc];
4614
+ }
4615
+ default: {
4616
+ const _exhaustive = mode;
4617
+ throw new TypeError(`dailyExtremes: unknown merge mode "${String(_exhaustive)}"`);
4618
+ }
4619
+ }
4620
+ }
4621
+ function projectRow(station, d, isUs) {
4622
+ const lowCoverage = d.nObs < LOW_COVERAGE_THRESHOLD2;
4623
+ const decimals = isUs ? 0 : 1;
4624
+ const precipIn = d.precipMm !== null && d.precipMm !== void 0 ? roundHalfUp2(d.precipMm / 25.4, 2) : null;
4625
+ if (lowCoverage) {
4626
+ return {
4627
+ date: d.localDate,
4628
+ station,
4629
+ tmin_f: null,
4630
+ tmax_f: null,
4631
+ tmean_f: null,
4632
+ precip_in: precipIn,
4633
+ low_coverage: true,
4634
+ n_obs: d.nObs
4635
+ };
4636
+ }
4637
+ return {
4638
+ date: d.localDate,
4639
+ station,
4640
+ tmin_f: d.tempMinF !== null ? roundHalfUp2(d.tempMinF, decimals) : null,
4641
+ tmax_f: d.tempMaxF !== null ? roundHalfUp2(d.tempMaxF, decimals) : null,
4642
+ tmean_f: d.tempMeanC !== null ? roundHalfUp2(cToF2(d.tempMeanC), decimals) : null,
4643
+ precip_in: precipIn,
4644
+ low_coverage: false,
4645
+ n_obs: d.nObs
4646
+ };
4647
+ }
4648
+ async function dailyExtremes(station, fromDate, toDate, opts = {}) {
4649
+ const { tz, isUs } = lookupStation(station);
4650
+ const merge = opts.merge ?? "live_v1";
4651
+ const fetchFrom = addUtcDays(fromDate, -1);
4652
+ const fetchTo = addUtcDays(toDate, 1);
4653
+ const rows = await fetchForMode(station, fetchFrom, fetchTo, merge);
4654
+ const extremes = internationalDailyExtremes(rows, {
4655
+ stationTz: tz,
4656
+ precision: isUs ? 1 : 0
4657
+ });
4658
+ return extremes.filter((d) => d.localDate >= fromDate && d.localDate <= toDate).map((d) => projectRow(station.toUpperCase(), d, isUs));
3359
4659
  }
3360
4660
 
3361
- // ../weather/src/forecasts/nwp-stub.ts
3362
- async function forecastNwp(_station, _model, _opts = {}) {
3363
- throw new Error(
3364
- "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."
4661
+ // ../weather/src/obs.ts
4662
+ init_src();
4663
+ init_iem_asos();
4664
+ init_awc();
4665
+ init_iem();
4666
+ function daysBetween(fromDate, toDate) {
4667
+ const from = Date.UTC(
4668
+ Number.parseInt(fromDate.slice(0, 4), 10),
4669
+ Number.parseInt(fromDate.slice(5, 7), 10) - 1,
4670
+ Number.parseInt(fromDate.slice(8, 10), 10)
3365
4671
  );
4672
+ const to = Date.UTC(
4673
+ Number.parseInt(toDate.slice(0, 4), 10),
4674
+ Number.parseInt(toDate.slice(5, 7), 10) - 1,
4675
+ Number.parseInt(toDate.slice(8, 10), 10)
4676
+ );
4677
+ return Math.round((to - from) / (24 * 60 * 60 * 1e3)) + 1;
4678
+ }
4679
+ function resolveAutoStrategy(fromDate, toDate) {
4680
+ return daysBetween(fromDate, toDate) <= 7 ? "exact_window" : "warm_cache";
4681
+ }
4682
+ function inchesToMm(inches) {
4683
+ if (inches === null || inches === void 0) return null;
4684
+ return inches * 25.4;
4685
+ }
4686
+ function mbToInhg(mb) {
4687
+ if (mb === null || mb === void 0) return null;
4688
+ return mb * 0.029529983071445;
4689
+ }
4690
+ function fromObservation(o) {
4691
+ const tempC = o.temp_c ?? null;
4692
+ const dewC = o.dewpoint_c ?? null;
4693
+ return {
4694
+ station: o.station_code,
4695
+ observed_at: o.observed_at,
4696
+ source: o.source,
4697
+ temp_c: tempC,
4698
+ temp_f: tempC !== null ? tempC * (9 / 5) + 32 : null,
4699
+ dewpoint_c: dewC,
4700
+ dewpoint_f: dewC !== null ? dewC * (9 / 5) + 32 : null,
4701
+ wind_speed_kts: o.wind_speed_kt ?? null,
4702
+ wind_direction_deg: o.wind_dir_degrees ?? null,
4703
+ pressure_inhg: mbToInhg(o.sea_level_pressure_mb),
4704
+ precip_mm_1h: inchesToMm(o.precip_1hr_inches),
4705
+ raw_metar: o.raw_metar ?? null
4706
+ };
4707
+ }
4708
+ async function fetchIemForWindow(station, fromDate, toDate, resolvedStrategy) {
4709
+ const out = [];
4710
+ if (resolvedStrategy === "exact_window") {
4711
+ const chunks = await downloadIemAsos(station, fromDate, toDate, {
4712
+ reportType: 3,
4713
+ politenessMs: 1e3
4714
+ });
4715
+ for (const chunk of chunks) {
4716
+ const rows = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
4717
+ for (const r of rows) {
4718
+ const d = r.observed_at.slice(0, 10);
4719
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(r));
4720
+ }
4721
+ }
4722
+ return out;
4723
+ }
4724
+ const fromYear = Number.parseInt(fromDate.slice(0, 4), 10);
4725
+ const toYear = Number.parseInt(toDate.slice(0, 4), 10);
4726
+ for (let year = fromYear; year <= toYear; year++) {
4727
+ const chunks = await downloadIemAsos(station, `${year}-01-01`, `${year}-12-31`, {
4728
+ reportType: 3,
4729
+ politenessMs: 1e3
4730
+ });
4731
+ for (const chunk of chunks) {
4732
+ const rows = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });
4733
+ for (const r of rows) {
4734
+ const d = r.observed_at.slice(0, 10);
4735
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(r));
4736
+ }
4737
+ }
4738
+ }
4739
+ return out;
4740
+ }
4741
+ async function fetchAwcForWindow(station, fromDate, toDate) {
4742
+ const raw = await fetchAwcMetars([station]);
4743
+ const out = [];
4744
+ for (const r of raw) {
4745
+ const obs2 = awcToObservation(r);
4746
+ if (obs2 === null) continue;
4747
+ const d = obs2.observed_at.slice(0, 10);
4748
+ if (d >= fromDate && d <= toDate) out.push(fromObservation(obs2));
4749
+ }
4750
+ return out;
4751
+ }
4752
+ async function fetchByStrategy(station, fromDate, toDate, resolvedStrategy, source) {
4753
+ const wantsIem = source === null || source === void 0 || source === "iem";
4754
+ const wantsAwc = source === null || source === void 0 || source === "awc";
4755
+ const tasks = [];
4756
+ if (wantsIem) tasks.push(fetchIemForWindow(station, fromDate, toDate, resolvedStrategy));
4757
+ if (wantsAwc) tasks.push(fetchAwcForWindow(station, fromDate, toDate).catch(() => []));
4758
+ const results = await Promise.all(tasks);
4759
+ return results.flat();
4760
+ }
4761
+ async function obs(station, fromDate, toDate, opts = {}) {
4762
+ const strategy = opts.strategy ?? "auto";
4763
+ const source = opts.source ?? null;
4764
+ if (source === "ghcnh") {
4765
+ throw new DataAvailabilityError({
4766
+ reason: "model_unavailable",
4767
+ source: "obs.ghcnh",
4768
+ 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."
4769
+ });
4770
+ }
4771
+ if (strategy === "hosted") {
4772
+ throw new DataAvailabilityError({
4773
+ reason: "model_unavailable",
4774
+ source: "obs-hosted-stub",
4775
+ 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"
4776
+ });
4777
+ }
4778
+ let resolved;
4779
+ if (strategy === "auto") {
4780
+ resolved = resolveAutoStrategy(fromDate, toDate);
4781
+ } else if (strategy === "exact_window" || strategy === "warm_cache") {
4782
+ resolved = strategy;
4783
+ } else {
4784
+ throw new TypeError(
4785
+ `obs: unknown strategy "${String(strategy)}" \u2014 expected one of: auto, exact_window, warm_cache, hosted`
4786
+ );
4787
+ }
4788
+ return fetchByStrategy(station, fromDate, toDate, resolved, source);
3366
4789
  }
3367
4790
 
3368
4791
  // ../weather/src/index.ts
@@ -4057,10 +5480,11 @@ var mostlyright = (() => {
4057
5480
  }
4058
5481
  }));
4059
5482
 
4060
- // ../core/dist/internal/chunk-PKJXHY27.mjs
5483
+ // ../core/dist/internal/chunk-IPC4XUYW.mjs
4061
5484
  function lockKeyFor(key) {
4062
5485
  return `mostlyright:cache:lock:${key}`;
4063
5486
  }
5487
+ var CACHE_SCHEMA_VERSION = "v2-phase18-integer-f";
4064
5488
  var MemoryStore = class {
4065
5489
  #entries = /* @__PURE__ */ new Map();
4066
5490
  #chain = /* @__PURE__ */ new Map();
@@ -4207,7 +5631,71 @@ var mostlyright = (() => {
4207
5631
  return next;
4208
5632
  }
4209
5633
  };
4210
- var STATIONS2 = [
5634
+ var VERSION_FIELD = "_cache_schema_version";
5635
+ function isVersionedEntry(v) {
5636
+ if (v === null || typeof v !== "object") return false;
5637
+ if (!(VERSION_FIELD in v)) return false;
5638
+ return typeof v[VERSION_FIELD] === "string";
5639
+ }
5640
+ function hasListKeys(s) {
5641
+ return typeof s.listKeys === "function";
5642
+ }
5643
+ var VersionedCacheStore = class {
5644
+ #inner;
5645
+ #version;
5646
+ constructor(inner, version5) {
5647
+ if (typeof version5 !== "string" || version5.length === 0) {
5648
+ throw new TypeError("versionedCacheStore: version must be a non-empty string");
5649
+ }
5650
+ this.#inner = inner;
5651
+ this.#version = version5;
5652
+ }
5653
+ /**
5654
+ * Test/diagnostics seam: return the underlying store so tests can assert
5655
+ * which concrete backend `defaultCacheStore()` selected. NOT a production
5656
+ * API — production code MUST use the wrapped store so version
5657
+ * invalidation fires on stale reads.
5658
+ *
5659
+ * @internal
5660
+ */
5661
+ __peekInner() {
5662
+ return this.#inner;
5663
+ }
5664
+ async get(key) {
5665
+ const raw = await this.#inner.get(key);
5666
+ if (raw === null) return null;
5667
+ if (!isVersionedEntry(raw)) {
5668
+ return null;
5669
+ }
5670
+ if (raw._cache_schema_version !== this.#version) {
5671
+ return null;
5672
+ }
5673
+ return raw.value;
5674
+ }
5675
+ async set(key, value, opts) {
5676
+ const wrapped = {
5677
+ value,
5678
+ [VERSION_FIELD]: this.#version
5679
+ };
5680
+ await this.#inner.set(key, wrapped, opts);
5681
+ }
5682
+ async delete(key) {
5683
+ await this.#inner.delete(key);
5684
+ }
5685
+ async withLock(key, fn) {
5686
+ return this.#inner.withLock(key, fn);
5687
+ }
5688
+ async listKeys(prefix) {
5689
+ if (hasListKeys(this.#inner)) {
5690
+ return this.#inner.listKeys(prefix);
5691
+ }
5692
+ return Object.freeze([]);
5693
+ }
5694
+ };
5695
+ function versionedCacheStore(inner, version5) {
5696
+ return new VersionedCacheStore(inner, version5);
5697
+ }
5698
+ var STATIONS3 = [
4211
5699
  {
4212
5700
  code: "EDDB",
4213
5701
  country: "DE",
@@ -4819,131 +6307,131 @@ var mostlyright = (() => {
4819
6307
  tz: "Asia/Shanghai"
4820
6308
  }
4821
6309
  ];
4822
- var STATION_BY_CODE2 = /* @__PURE__ */ new Map([
4823
- ["ATL", STATIONS2[10]],
4824
- ["AUS", STATIONS2[11]],
4825
- ["BOS", STATIONS2[12]],
4826
- ["DCA", STATIONS2[13]],
4827
- ["DEN", STATIONS2[14]],
4828
- ["DFW", STATIONS2[15]],
4829
- ["EDDB", STATIONS2[0]],
4830
- ["EDDF", STATIONS2[1]],
4831
- ["EDDM", STATIONS2[2]],
4832
- ["EFHK", STATIONS2[3]],
4833
- ["EGKK", STATIONS2[4]],
4834
- ["EGLL", STATIONS2[5]],
4835
- ["EHAM", STATIONS2[6]],
4836
- ["EKCH", STATIONS2[7]],
4837
- ["EPWA", STATIONS2[8]],
4838
- ["ESSA", STATIONS2[9]],
4839
- ["HOU", STATIONS2[16]],
4840
- ["LAS", STATIONS2[17]],
4841
- ["LAX", STATIONS2[18]],
4842
- ["LEBL", STATIONS2[30]],
4843
- ["LEMD", STATIONS2[31]],
4844
- ["LFPB", STATIONS2[32]],
4845
- ["LFPG", STATIONS2[33]],
4846
- ["LFPO", STATIONS2[34]],
4847
- ["LIMC", STATIONS2[35]],
4848
- ["LIRF", STATIONS2[36]],
4849
- ["LOWW", STATIONS2[37]],
4850
- ["LSZH", STATIONS2[38]],
4851
- ["MDW", STATIONS2[19]],
4852
- ["MIA", STATIONS2[20]],
4853
- ["MSP", STATIONS2[21]],
4854
- ["MSY", STATIONS2[22]],
4855
- ["NYC", STATIONS2[23]],
4856
- ["NZAA", STATIONS2[39]],
4857
- ["NZWN", STATIONS2[40]],
4858
- ["OERK", STATIONS2[41]],
4859
- ["OKC", STATIONS2[24]],
4860
- ["OMDB", STATIONS2[42]],
4861
- ["OTHH", STATIONS2[43]],
4862
- ["PHL", STATIONS2[25]],
4863
- ["PHX", STATIONS2[26]],
4864
- ["RCTP", STATIONS2[44]],
4865
- ["RJAA", STATIONS2[45]],
4866
- ["RJTT", STATIONS2[46]],
4867
- ["RKSI", STATIONS2[47]],
4868
- ["SAEZ", STATIONS2[48]],
4869
- ["SAT", STATIONS2[27]],
4870
- ["SBGR", STATIONS2[49]],
4871
- ["SEA", STATIONS2[28]],
4872
- ["SFO", STATIONS2[29]],
4873
- ["UUEE", STATIONS2[50]],
4874
- ["VABB", STATIONS2[51]],
4875
- ["VHHH", STATIONS2[52]],
4876
- ["VIDP", STATIONS2[53]],
4877
- ["VTBS", STATIONS2[54]],
4878
- ["WSSS", STATIONS2[55]],
4879
- ["YBBN", STATIONS2[56]],
4880
- ["YMML", STATIONS2[57]],
4881
- ["YSSY", STATIONS2[58]],
4882
- ["ZBAA", STATIONS2[59]],
4883
- ["ZSPD", STATIONS2[60]]
6310
+ var STATION_BY_CODE3 = /* @__PURE__ */ new Map([
6311
+ ["ATL", STATIONS3[10]],
6312
+ ["AUS", STATIONS3[11]],
6313
+ ["BOS", STATIONS3[12]],
6314
+ ["DCA", STATIONS3[13]],
6315
+ ["DEN", STATIONS3[14]],
6316
+ ["DFW", STATIONS3[15]],
6317
+ ["EDDB", STATIONS3[0]],
6318
+ ["EDDF", STATIONS3[1]],
6319
+ ["EDDM", STATIONS3[2]],
6320
+ ["EFHK", STATIONS3[3]],
6321
+ ["EGKK", STATIONS3[4]],
6322
+ ["EGLL", STATIONS3[5]],
6323
+ ["EHAM", STATIONS3[6]],
6324
+ ["EKCH", STATIONS3[7]],
6325
+ ["EPWA", STATIONS3[8]],
6326
+ ["ESSA", STATIONS3[9]],
6327
+ ["HOU", STATIONS3[16]],
6328
+ ["LAS", STATIONS3[17]],
6329
+ ["LAX", STATIONS3[18]],
6330
+ ["LEBL", STATIONS3[30]],
6331
+ ["LEMD", STATIONS3[31]],
6332
+ ["LFPB", STATIONS3[32]],
6333
+ ["LFPG", STATIONS3[33]],
6334
+ ["LFPO", STATIONS3[34]],
6335
+ ["LIMC", STATIONS3[35]],
6336
+ ["LIRF", STATIONS3[36]],
6337
+ ["LOWW", STATIONS3[37]],
6338
+ ["LSZH", STATIONS3[38]],
6339
+ ["MDW", STATIONS3[19]],
6340
+ ["MIA", STATIONS3[20]],
6341
+ ["MSP", STATIONS3[21]],
6342
+ ["MSY", STATIONS3[22]],
6343
+ ["NYC", STATIONS3[23]],
6344
+ ["NZAA", STATIONS3[39]],
6345
+ ["NZWN", STATIONS3[40]],
6346
+ ["OERK", STATIONS3[41]],
6347
+ ["OKC", STATIONS3[24]],
6348
+ ["OMDB", STATIONS3[42]],
6349
+ ["OTHH", STATIONS3[43]],
6350
+ ["PHL", STATIONS3[25]],
6351
+ ["PHX", STATIONS3[26]],
6352
+ ["RCTP", STATIONS3[44]],
6353
+ ["RJAA", STATIONS3[45]],
6354
+ ["RJTT", STATIONS3[46]],
6355
+ ["RKSI", STATIONS3[47]],
6356
+ ["SAEZ", STATIONS3[48]],
6357
+ ["SAT", STATIONS3[27]],
6358
+ ["SBGR", STATIONS3[49]],
6359
+ ["SEA", STATIONS3[28]],
6360
+ ["SFO", STATIONS3[29]],
6361
+ ["UUEE", STATIONS3[50]],
6362
+ ["VABB", STATIONS3[51]],
6363
+ ["VHHH", STATIONS3[52]],
6364
+ ["VIDP", STATIONS3[53]],
6365
+ ["VTBS", STATIONS3[54]],
6366
+ ["WSSS", STATIONS3[55]],
6367
+ ["YBBN", STATIONS3[56]],
6368
+ ["YMML", STATIONS3[57]],
6369
+ ["YSSY", STATIONS3[58]],
6370
+ ["ZBAA", STATIONS3[59]],
6371
+ ["ZSPD", STATIONS3[60]]
4884
6372
  ]);
4885
- var STATION_BY_ICAO2 = /* @__PURE__ */ new Map([
4886
- ["EDDB", STATIONS2[0]],
4887
- ["EDDF", STATIONS2[1]],
4888
- ["EDDM", STATIONS2[2]],
4889
- ["EFHK", STATIONS2[3]],
4890
- ["EGKK", STATIONS2[4]],
4891
- ["EGLL", STATIONS2[5]],
4892
- ["EHAM", STATIONS2[6]],
4893
- ["EKCH", STATIONS2[7]],
4894
- ["EPWA", STATIONS2[8]],
4895
- ["ESSA", STATIONS2[9]],
4896
- ["KATL", STATIONS2[10]],
4897
- ["KAUS", STATIONS2[11]],
4898
- ["KBOS", STATIONS2[12]],
4899
- ["KDCA", STATIONS2[13]],
4900
- ["KDEN", STATIONS2[14]],
4901
- ["KDFW", STATIONS2[15]],
4902
- ["KHOU", STATIONS2[16]],
4903
- ["KLAS", STATIONS2[17]],
4904
- ["KLAX", STATIONS2[18]],
4905
- ["KMDW", STATIONS2[19]],
4906
- ["KMIA", STATIONS2[20]],
4907
- ["KMSP", STATIONS2[21]],
4908
- ["KMSY", STATIONS2[22]],
4909
- ["KNYC", STATIONS2[23]],
4910
- ["KOKC", STATIONS2[24]],
4911
- ["KPHL", STATIONS2[25]],
4912
- ["KPHX", STATIONS2[26]],
4913
- ["KSAT", STATIONS2[27]],
4914
- ["KSEA", STATIONS2[28]],
4915
- ["KSFO", STATIONS2[29]],
4916
- ["LEBL", STATIONS2[30]],
4917
- ["LEMD", STATIONS2[31]],
4918
- ["LFPB", STATIONS2[32]],
4919
- ["LFPG", STATIONS2[33]],
4920
- ["LFPO", STATIONS2[34]],
4921
- ["LIMC", STATIONS2[35]],
4922
- ["LIRF", STATIONS2[36]],
4923
- ["LOWW", STATIONS2[37]],
4924
- ["LSZH", STATIONS2[38]],
4925
- ["NZAA", STATIONS2[39]],
4926
- ["NZWN", STATIONS2[40]],
4927
- ["OERK", STATIONS2[41]],
4928
- ["OMDB", STATIONS2[42]],
4929
- ["OTHH", STATIONS2[43]],
4930
- ["RCTP", STATIONS2[44]],
4931
- ["RJAA", STATIONS2[45]],
4932
- ["RJTT", STATIONS2[46]],
4933
- ["RKSI", STATIONS2[47]],
4934
- ["SAEZ", STATIONS2[48]],
4935
- ["SBGR", STATIONS2[49]],
4936
- ["UUEE", STATIONS2[50]],
4937
- ["VABB", STATIONS2[51]],
4938
- ["VHHH", STATIONS2[52]],
4939
- ["VIDP", STATIONS2[53]],
4940
- ["VTBS", STATIONS2[54]],
4941
- ["WSSS", STATIONS2[55]],
4942
- ["YBBN", STATIONS2[56]],
4943
- ["YMML", STATIONS2[57]],
4944
- ["YSSY", STATIONS2[58]],
4945
- ["ZBAA", STATIONS2[59]],
4946
- ["ZSPD", STATIONS2[60]]
6373
+ var STATION_BY_ICAO3 = /* @__PURE__ */ new Map([
6374
+ ["EDDB", STATIONS3[0]],
6375
+ ["EDDF", STATIONS3[1]],
6376
+ ["EDDM", STATIONS3[2]],
6377
+ ["EFHK", STATIONS3[3]],
6378
+ ["EGKK", STATIONS3[4]],
6379
+ ["EGLL", STATIONS3[5]],
6380
+ ["EHAM", STATIONS3[6]],
6381
+ ["EKCH", STATIONS3[7]],
6382
+ ["EPWA", STATIONS3[8]],
6383
+ ["ESSA", STATIONS3[9]],
6384
+ ["KATL", STATIONS3[10]],
6385
+ ["KAUS", STATIONS3[11]],
6386
+ ["KBOS", STATIONS3[12]],
6387
+ ["KDCA", STATIONS3[13]],
6388
+ ["KDEN", STATIONS3[14]],
6389
+ ["KDFW", STATIONS3[15]],
6390
+ ["KHOU", STATIONS3[16]],
6391
+ ["KLAS", STATIONS3[17]],
6392
+ ["KLAX", STATIONS3[18]],
6393
+ ["KMDW", STATIONS3[19]],
6394
+ ["KMIA", STATIONS3[20]],
6395
+ ["KMSP", STATIONS3[21]],
6396
+ ["KMSY", STATIONS3[22]],
6397
+ ["KNYC", STATIONS3[23]],
6398
+ ["KOKC", STATIONS3[24]],
6399
+ ["KPHL", STATIONS3[25]],
6400
+ ["KPHX", STATIONS3[26]],
6401
+ ["KSAT", STATIONS3[27]],
6402
+ ["KSEA", STATIONS3[28]],
6403
+ ["KSFO", STATIONS3[29]],
6404
+ ["LEBL", STATIONS3[30]],
6405
+ ["LEMD", STATIONS3[31]],
6406
+ ["LFPB", STATIONS3[32]],
6407
+ ["LFPG", STATIONS3[33]],
6408
+ ["LFPO", STATIONS3[34]],
6409
+ ["LIMC", STATIONS3[35]],
6410
+ ["LIRF", STATIONS3[36]],
6411
+ ["LOWW", STATIONS3[37]],
6412
+ ["LSZH", STATIONS3[38]],
6413
+ ["NZAA", STATIONS3[39]],
6414
+ ["NZWN", STATIONS3[40]],
6415
+ ["OERK", STATIONS3[41]],
6416
+ ["OMDB", STATIONS3[42]],
6417
+ ["OTHH", STATIONS3[43]],
6418
+ ["RCTP", STATIONS3[44]],
6419
+ ["RJAA", STATIONS3[45]],
6420
+ ["RJTT", STATIONS3[46]],
6421
+ ["RKSI", STATIONS3[47]],
6422
+ ["SAEZ", STATIONS3[48]],
6423
+ ["SBGR", STATIONS3[49]],
6424
+ ["UUEE", STATIONS3[50]],
6425
+ ["VABB", STATIONS3[51]],
6426
+ ["VHHH", STATIONS3[52]],
6427
+ ["VIDP", STATIONS3[53]],
6428
+ ["VTBS", STATIONS3[54]],
6429
+ ["WSSS", STATIONS3[55]],
6430
+ ["YBBN", STATIONS3[56]],
6431
+ ["YMML", STATIONS3[57]],
6432
+ ["YSSY", STATIONS3[58]],
6433
+ ["ZBAA", STATIONS3[59]],
6434
+ ["ZSPD", STATIONS3[60]]
4947
6435
  ]);
4948
6436
  var _STATION_TZ2 = Object.freeze({
4949
6437
  // Eastern (UTC-5 standard / UTC-4 DST)
@@ -5090,13 +6578,13 @@ var mostlyright = (() => {
5090
6578
  }
5091
6579
  function _lstOffsetHoursFor(station) {
5092
6580
  const upper = station.trim().toUpperCase();
5093
- const byCode = STATION_BY_CODE2.get(upper);
6581
+ const byCode = STATION_BY_CODE3.get(upper);
5094
6582
  if (byCode !== void 0) return _lstOffsetHours2(byCode.tz);
5095
- const byIcao = STATION_BY_ICAO2.get(upper);
6583
+ const byIcao = STATION_BY_ICAO3.get(upper);
5096
6584
  if (byIcao !== void 0) return _lstOffsetHours2(byIcao.tz);
5097
6585
  if (upper.length === 4 && upper.startsWith("K")) {
5098
6586
  const stripped = upper.slice(1);
5099
- const retry = STATION_BY_CODE2.get(stripped);
6587
+ const retry = STATION_BY_CODE3.get(stripped);
5100
6588
  if (retry !== void 0) return _lstOffsetHours2(retry.tz);
5101
6589
  }
5102
6590
  throw new RangeError(`unknown station: ${JSON.stringify(station)}`);
@@ -5168,8 +6656,8 @@ var mostlyright = (() => {
5168
6656
 
5169
6657
  // ../core/dist/internal/cache/index.browser.mjs
5170
6658
  async function defaultCacheStore() {
5171
- if (typeof indexedDB !== "undefined") return new IndexedDBStore();
5172
- return new MemoryStore();
6659
+ const inner = typeof indexedDB !== "undefined" ? new IndexedDBStore() : new MemoryStore();
6660
+ return versionedCacheStore(inner, CACHE_SCHEMA_VERSION);
5173
6661
  }
5174
6662
 
5175
6663
  // ../core/dist/internal/pairs.mjs
@@ -5355,9 +6843,9 @@ var mostlyright = (() => {
5355
6843
  );
5356
6844
  return new Date(marketCloseAsUtcMs - offsetHours * 36e5);
5357
6845
  }
5358
- function collectNonNull(obs, key) {
6846
+ function collectNonNull(obs2, key) {
5359
6847
  const out = [];
5360
- for (const o of obs) {
6848
+ for (const o of obs2) {
5361
6849
  const v = o[key];
5362
6850
  if (typeof v === "number" && Number.isFinite(v)) out.push(v);
5363
6851
  }
@@ -5452,13 +6940,68 @@ var mostlyright = (() => {
5452
6940
  function buildPairs(station, dates, observationsByDate, climateByDate, opts = {}) {
5453
6941
  const out = [];
5454
6942
  for (const date of dates) {
5455
- const obs = observationsByDate[date] ?? [];
6943
+ const obs2 = observationsByDate[date] ?? [];
5456
6944
  const climate = climateByDate[date] ?? null;
5457
- out.push(buildPairsRow(date, station, obs, climate, opts));
6945
+ out.push(buildPairsRow(date, station, obs2, climate, opts));
5458
6946
  }
5459
6947
  return Object.freeze(out);
5460
6948
  }
5461
6949
 
6950
+ // src/research.types.ts
6951
+ var KNOWN_RESEARCH_OPTION_KEYS = /* @__PURE__ */ new Set([
6952
+ // Pre-Phase-21 fetcher controls.
6953
+ "signal",
6954
+ "awcHours",
6955
+ "iemPolitenessMs",
6956
+ "ghcnhPolitenessMs",
6957
+ "cliPolitenessMs",
6958
+ "now",
6959
+ "cache",
6960
+ // Pre-Phase-21 selectors.
6961
+ "city",
6962
+ "contract",
6963
+ "contracts",
6964
+ "stationOverride",
6965
+ "sources",
6966
+ "source",
6967
+ "includeTrades",
6968
+ "onWarning",
6969
+ // Phase 21 21-01: Python-parity composable kwargs.
6970
+ "include_forecast",
6971
+ "forecast_model",
6972
+ "forecast_models",
6973
+ "qc",
6974
+ "tz_override",
6975
+ "backend",
6976
+ "return_type"
6977
+ ]);
6978
+ function validateResearchKwargs(opts) {
6979
+ const present = (v) => v !== void 0 && v !== null;
6980
+ for (const key of Object.keys(opts)) {
6981
+ if (!KNOWN_RESEARCH_OPTION_KEYS.has(key)) {
6982
+ throw new TypeError(
6983
+ `research(): unknown option key ${JSON.stringify(key)}. Valid keys: ${[...KNOWN_RESEARCH_OPTION_KEYS].sort().join(", ")}`
6984
+ );
6985
+ }
6986
+ }
6987
+ if (present(opts.sources) && present(opts.source)) {
6988
+ throw new TypeError(
6989
+ "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"
6990
+ );
6991
+ }
6992
+ if (present(opts.forecast_model) && present(opts.forecast_models)) {
6993
+ throw new TypeError(
6994
+ "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"
6995
+ );
6996
+ }
6997
+ const wantsForecast = present(opts.forecast_model) || present(opts.forecast_models);
6998
+ if (wantsForecast && opts.include_forecast !== true) {
6999
+ throw new TypeError(
7000
+ "research(): forecast_model=/forecast_models= require include_forecast=true; the model filter is otherwise silently ignored"
7001
+ );
7002
+ }
7003
+ }
7004
+
5462
7005
  // src/research.ts
5463
7006
  var AWC_MAX_HOURS2 = 168;
5464
7007
  async function resolveCache(opts) {
@@ -5726,9 +7269,9 @@ var mostlyright = (() => {
5726
7269
  }
5727
7270
  }
5728
7271
  }
5729
- for (const obs of monthRows) {
5730
- const obsDate = obs.observed_at.slice(0, 10);
5731
- if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
7272
+ for (const obs2 of monthRows) {
7273
+ const obsDate = obs2.observed_at.slice(0, 10);
7274
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs2);
5732
7275
  }
5733
7276
  }
5734
7277
  return acc;
@@ -5799,14 +7342,22 @@ var mostlyright = (() => {
5799
7342
  }
5800
7343
  }
5801
7344
  }
5802
- for (const obs of monthRows) {
5803
- const obsDate = obs.observed_at.slice(0, 10);
5804
- if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs);
7345
+ for (const obs2 of monthRows) {
7346
+ const obsDate = obs2.observed_at.slice(0, 10);
7347
+ if (obsDate >= fromDate && obsDate <= extendedTo) acc.push(obs2);
5805
7348
  }
5806
7349
  }
5807
7350
  return acc;
5808
7351
  }
5809
7352
  async function research(station, fromDate, toDate, opts = {}) {
7353
+ validateResearchKwargs(opts);
7354
+ if (opts.backend === "polars") {
7355
+ throw new DataAvailabilityError({
7356
+ reason: "model_unavailable",
7357
+ hint: 'polars backend not available in TypeScript SDK; use Python (mostlyrightmd) for backend="polars". TS returns plain object arrays.',
7358
+ source: "research.backend"
7359
+ });
7360
+ }
5810
7361
  const hasCity = typeof opts.city === "string" && opts.city.length > 0;
5811
7362
  const hasContract = typeof opts.contract === "string" && opts.contract.length > 0;
5812
7363
  const hasContracts = Array.isArray(opts.contracts) && opts.contracts.length > 0;
@@ -5882,8 +7433,8 @@ var mostlyright = (() => {
5882
7433
  if (opts.signal !== void 0) awcOpts.signal = opts.signal;
5883
7434
  const awcRaw = await fetchAwcMetars([resolved.icao], awcOpts);
5884
7435
  for (const m of awcRaw) {
5885
- const obs = awcToObservation(m);
5886
- if (obs !== null) awcRows.push(obs);
7436
+ const obs2 = awcToObservation(m);
7437
+ if (obs2 !== null) awcRows.push(obs2);
5887
7438
  }
5888
7439
  }
5889
7440
  const iemRows = await fetchIemAsosWithCache(
@@ -5914,8 +7465,8 @@ var mostlyright = (() => {
5914
7465
  const observationsByDate = {};
5915
7466
  const dateLo = dates[0] ?? "";
5916
7467
  const dateHi = dates[dates.length - 1] ?? "";
5917
- for (const obs of merged) {
5918
- const settleDate = observedSettlementDate(obs.observed_at, resolved.code);
7468
+ for (const obs2 of merged) {
7469
+ const settleDate = observedSettlementDate(obs2.observed_at, resolved.code);
5919
7470
  if (settleDate === null) continue;
5920
7471
  if (settleDate < dateLo || settleDate > dateHi) continue;
5921
7472
  let bucket = observationsByDate[settleDate];
@@ -5923,7 +7474,7 @@ var mostlyright = (() => {
5923
7474
  bucket = [];
5924
7475
  observationsByDate[settleDate] = bucket;
5925
7476
  }
5926
- bucket.push(obs);
7477
+ bucket.push(obs2);
5927
7478
  }
5928
7479
  const climateByDate = {};
5929
7480
  for (const cli of mergedClimate) {
@@ -6058,8 +7609,8 @@ var mostlyright = (() => {
6058
7609
  const raw = await fetchAwcMetars([resolved.icao], awcOpts);
6059
7610
  const parsed = [];
6060
7611
  for (const m of raw) {
6061
- const obs = awcToObservation(m);
6062
- if (obs !== null) parsed.push(obs);
7612
+ const obs2 = awcToObservation(m);
7613
+ if (obs2 !== null) parsed.push(obs2);
6063
7614
  }
6064
7615
  rows = parsed.filter((r) => {
6065
7616
  const d = r.observed_at.slice(0, 10);
@@ -6351,6 +7902,129 @@ var mostlyright = (() => {
6351
7902
 
6352
7903
  // src/index.ts
6353
7904
  init_src();
7905
+
7906
+ // ../core/dist/preprocessing/index.mjs
7907
+ var preprocessing_exports = {};
7908
+ __export(preprocessing_exports, {
7909
+ PHYSICS_BOUNDS: () => PHYSICS_BOUNDS,
7910
+ clipOutliers: () => clipOutliers,
7911
+ iemCrosscheck: () => crosscheckIemGhcnh
7912
+ });
7913
+ var PHYSICS_BOUNDS = /* @__PURE__ */ new Map([
7914
+ ["temp_c", [-89, 57]],
7915
+ ["dew_point_c", [-89, 35]],
7916
+ ["dewpoint_c", [-89, 35]],
7917
+ ["wind_speed_ms", [0, 100]],
7918
+ ["wind_speed_kt", [0, 200]],
7919
+ ["wind_dir_deg", [0, 360]],
7920
+ ["wind_dir_degrees", [0, 360]],
7921
+ ["slp_hpa", [870, 1085]],
7922
+ ["sea_level_pressure_mb", [870, 1085]],
7923
+ ["relative_humidity_pct_2m", [0, 100]],
7924
+ ["precip_mm_1h", [0, 305]]
7925
+ ]);
7926
+ function clipOutliers(rows, col, opts = {}) {
7927
+ const std = opts.std ?? 3;
7928
+ const key = `${col}_clipped`;
7929
+ let lo;
7930
+ let hi;
7931
+ let passThrough = false;
7932
+ if (opts.bounds !== void 0) {
7933
+ [lo, hi] = opts.bounds;
7934
+ } else if (PHYSICS_BOUNDS.has(col)) {
7935
+ const b = PHYSICS_BOUNDS.get(col);
7936
+ if (b === void 0) {
7937
+ throw new Error(`PHYSICS_BOUNDS.get(${col}) unexpectedly undefined`);
7938
+ }
7939
+ [lo, hi] = b;
7940
+ } else {
7941
+ if (!Number.isFinite(std) || std <= 0) {
7942
+ throw new RangeError(
7943
+ `clipOutliers: std must be > 0 for the sigma fallback (got ${std}); pass bounds=[lo, hi] or use a physics-default column`
7944
+ );
7945
+ }
7946
+ const vals = [];
7947
+ for (const r of rows) {
7948
+ const v = r?.[col];
7949
+ if (typeof v === "number" && Number.isFinite(v)) vals.push(v);
7950
+ }
7951
+ if (vals.length < 2) {
7952
+ passThrough = true;
7953
+ lo = Number.NEGATIVE_INFINITY;
7954
+ hi = Number.POSITIVE_INFINITY;
7955
+ } else {
7956
+ const mu = vals.reduce((a, b) => a + b, 0) / vals.length;
7957
+ const sumSq = vals.reduce((a, b) => a + (b - mu) ** 2, 0);
7958
+ const sigma = Math.sqrt(sumSq / (vals.length - 1));
7959
+ if (sigma === 0 || !Number.isFinite(sigma)) {
7960
+ passThrough = true;
7961
+ lo = Number.NEGATIVE_INFINITY;
7962
+ hi = Number.POSITIVE_INFINITY;
7963
+ } else {
7964
+ lo = mu - std * sigma;
7965
+ hi = mu + std * sigma;
7966
+ }
7967
+ }
7968
+ }
7969
+ const out = [];
7970
+ for (const r of rows) {
7971
+ const v = r?.[col];
7972
+ let clipped;
7973
+ if (typeof v === "number" && Number.isFinite(v)) {
7974
+ clipped = passThrough ? v : Math.min(Math.max(v, lo), hi);
7975
+ } else {
7976
+ clipped = null;
7977
+ }
7978
+ out.push({ ...r, [key]: clipped });
7979
+ }
7980
+ return out;
7981
+ }
7982
+ function crosscheckIemGhcnh(iemRows, ghcnhRows, opts = {}) {
7983
+ const tolC = opts.tolC ?? 2;
7984
+ if (iemRows.length === 0 || ghcnhRows.length === 0) return [];
7985
+ for (const r of iemRows) {
7986
+ if (typeof r?.station !== "string" || typeof r?.eventTime !== "string") {
7987
+ throw new Error(
7988
+ "crosscheckIemGhcnh: iem rows must carry 'station' (string) and 'eventTime' (string) keys"
7989
+ );
7990
+ }
7991
+ }
7992
+ for (const r of ghcnhRows) {
7993
+ if (typeof r?.station !== "string" || typeof r?.eventTime !== "string") {
7994
+ throw new Error(
7995
+ "crosscheckIemGhcnh: ghcnh rows must carry 'station' (string) and 'eventTime' (string) keys"
7996
+ );
7997
+ }
7998
+ }
7999
+ const iemMap = /* @__PURE__ */ new Map();
8000
+ for (const r of iemRows) {
8001
+ const key = `${r.station}|${r.eventTime}`;
8002
+ iemMap.set(key, r);
8003
+ }
8004
+ const out = [];
8005
+ for (const g of ghcnhRows) {
8006
+ const key = `${g.station}|${g.eventTime}`;
8007
+ const i = iemMap.get(key);
8008
+ if (i === void 0) continue;
8009
+ const iT = typeof i.temp_c === "number" && Number.isFinite(i.temp_c) ? i.temp_c : null;
8010
+ const gT = typeof g.temp_c === "number" && Number.isFinite(g.temp_c) ? g.temp_c : null;
8011
+ if (iT === null || gT === null) continue;
8012
+ const delta = Math.abs(iT - gT);
8013
+ if (delta > tolC) {
8014
+ out.push({
8015
+ station: g.station,
8016
+ eventTime: g.eventTime,
8017
+ tempCIem: iT,
8018
+ tempCGhcnh: gT,
8019
+ deltaC: delta
8020
+ });
8021
+ }
8022
+ }
8023
+ return out;
8024
+ }
8025
+
8026
+ // src/index.ts
8027
+ var Preprocessing = preprocessing_exports;
6354
8028
  var version4 = "0.0.0";
6355
8029
  return __toCommonJS(index_exports);
6356
8030
  })();