mostlyright 1.5.1 → 1.5.2

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.
@@ -2917,6 +2917,11 @@ var mostlyright = (() => {
2917
2917
  Number.parseInt(parts[2], 10)
2918
2918
  ];
2919
2919
  }
2920
+ function addOneDay(iso) {
2921
+ const [y, m, d] = splitIso(iso);
2922
+ const next = new Date(Date.UTC(y, m - 1, d + 1));
2923
+ return next.toISOString().slice(0, 10);
2924
+ }
2920
2925
  async function downloadIemAsos(stationCode, start, end, opts = {}) {
2921
2926
  const reportType = opts.reportType ?? 3;
2922
2927
  if (!VALID_REPORT_TYPES.has(reportType)) {
@@ -2926,10 +2931,9 @@ var mostlyright = (() => {
2926
2931
  if (start > end) {
2927
2932
  return [];
2928
2933
  }
2929
- const normalizedStart = `${start.slice(0, 4)}-01-01`;
2930
- const chunks = yearlyChunksExclusiveEnd(normalizedStart, end);
2934
+ const chunks = opts.exactStart ? [[start, addOneDay(end)]] : yearlyChunksExclusiveEnd(`${start.slice(0, 4)}-01-01`, end);
2931
2935
  const politenessMs = opts.politenessMs ?? IEM_POLITE_DELAY_MS;
2932
- const { reportType: _rtDrop, politenessMs: _pmDrop, ...fetchOpts } = opts;
2936
+ const { reportType: _rtDrop, politenessMs: _pmDrop, exactStart: _esDrop, ...fetchOpts } = opts;
2933
2937
  const out = [];
2934
2938
  for (const [chunkStart, chunkEnd] of chunks) {
2935
2939
  const url = buildIemUrl(stationCode, chunkStart, chunkEnd, reportType);
@@ -4075,6 +4079,26 @@ var mostlyright = (() => {
4075
4079
  var SUPPORTED_MODELS = /* @__PURE__ */ new Set(["nbe", "gfs", "lav", "met", "ecm"]);
4076
4080
  var KT_TO_MS2 = 0.5144444;
4077
4081
  var NBE_CYCLE_CUTOVER = Date.UTC(2026, 5 - 1, 5, 0, 0, 0);
4082
+ var MOS_FETCH_CONCURRENCY = 8;
4083
+ async function mapWithConcurrency(items, limit, fn) {
4084
+ const results = new Array(items.length);
4085
+ let cursor = 0;
4086
+ let failed = false;
4087
+ async function worker() {
4088
+ while (cursor < items.length && !failed) {
4089
+ const index = cursor++;
4090
+ try {
4091
+ results[index] = await fn(items[index], index);
4092
+ } catch (err) {
4093
+ failed = true;
4094
+ throw err;
4095
+ }
4096
+ }
4097
+ }
4098
+ const poolSize = Math.min(limit, items.length);
4099
+ await Promise.all(Array.from({ length: poolSize }, () => worker()));
4100
+ return results;
4101
+ }
4078
4102
  function runtimeHoursFor(model, fromDt, toDt) {
4079
4103
  if (model !== "nbe") return [0, 6, 12, 18];
4080
4104
  const fromMs = fromDt.getTime();
@@ -4158,31 +4182,37 @@ var mostlyright = (() => {
4158
4182
  const toDt = parseIsoDate2(toDate, true);
4159
4183
  const hours = runtimeHoursFor(model, fromDt, toDt);
4160
4184
  const retrievedAt = (/* @__PURE__ */ new Date()).toISOString();
4161
- const rows = [];
4162
4185
  const dayMs = 864e5;
4186
+ const urls = [];
4163
4187
  for (let day = fromDt.getTime(); day <= toDt.getTime(); day += dayMs) {
4164
4188
  for (const h of hours) {
4165
4189
  const rt = new Date(day);
4166
4190
  rt.setUTCHours(h, 0, 0, 0);
4167
4191
  if (rt < fromDt || rt > toDt) continue;
4168
- const url = `${IEM_MOS_URL}?station=${encodeURIComponent(
4169
- station
4170
- )}&model=${encodeURIComponent(model.toUpperCase())}&runtime=${encodeURIComponent(
4171
- rt.toISOString()
4172
- )}`;
4173
- const resp = await fetchFn(url);
4174
- if (resp.status === 404) continue;
4175
- if (!resp.ok) {
4176
- throw new Error(`iemMosForecasts: HTTP ${resp.status} on ${url}`);
4177
- }
4178
- const payload = await resp.json();
4179
- for (const raw of payload.data ?? []) {
4180
- const projected = parseRow(raw, station, model, retrievedAt);
4181
- if (projected !== null) rows.push(projected);
4182
- }
4192
+ urls.push(
4193
+ `${IEM_MOS_URL}?station=${encodeURIComponent(
4194
+ station
4195
+ )}&model=${encodeURIComponent(model.toUpperCase())}&runtime=${encodeURIComponent(
4196
+ rt.toISOString()
4197
+ )}`
4198
+ );
4183
4199
  }
4184
4200
  }
4185
- return rows;
4201
+ const perCycle = await mapWithConcurrency(urls, MOS_FETCH_CONCURRENCY, async (url) => {
4202
+ const resp = await fetchFn(url);
4203
+ if (resp.status === 404) return [];
4204
+ if (!resp.ok) {
4205
+ throw new Error(`iemMosForecasts: HTTP ${resp.status} on ${url}`);
4206
+ }
4207
+ const payload = await resp.json();
4208
+ const out = [];
4209
+ for (const raw of payload.data ?? []) {
4210
+ const projected = parseRow(raw, station, model, retrievedAt);
4211
+ if (projected !== null) out.push(projected);
4212
+ }
4213
+ return out;
4214
+ });
4215
+ return perCycle.flat();
4186
4216
  }
4187
4217
 
4188
4218
  // ../weather/src/forecasts/nwp-stub.ts
@@ -4461,6 +4491,15 @@ var mostlyright = (() => {
4461
4491
  const n = typeof value === "number" ? value : Number(value);
4462
4492
  return Number.isFinite(n) ? n : null;
4463
4493
  }
4494
+ function maybeInt(value) {
4495
+ const n = maybeNumber2(value);
4496
+ if (n === null) return null;
4497
+ const floor = Math.floor(n);
4498
+ const diff = n - floor;
4499
+ if (diff < 0.5) return floor;
4500
+ if (diff > 0.5) return floor + 1;
4501
+ return floor % 2 === 0 ? floor : floor + 1;
4502
+ }
4464
4503
  function pickHourlyValue(hourly, key, isPreviousRuns, idx) {
4465
4504
  const arr = isPreviousRuns ? hourly[`${key}_previous_day1`] ?? hourly[key] : hourly[key];
4466
4505
  if (!Array.isArray(arr) || idx >= arr.length) return null;
@@ -4556,20 +4595,20 @@ var mostlyright = (() => {
4556
4595
  dewPointC: maybeNumber2(pickHourlyValue(h, "dew_point_2m", isPrev, i)),
4557
4596
  apparentTempC: maybeNumber2(pickHourlyValue(h, "apparent_temperature", isPrev, i)),
4558
4597
  windSpeedMs: maybeNumber2(pickHourlyValue(h, "wind_speed_10m", isPrev, i)),
4559
- windDirDeg: maybeNumber2(pickHourlyValue(h, "wind_direction_10m", isPrev, i)),
4598
+ windDirDeg: maybeInt(pickHourlyValue(h, "wind_direction_10m", isPrev, i)),
4560
4599
  windGustsMs: maybeNumber2(pickHourlyValue(h, "wind_gusts_10m", isPrev, i)),
4561
4600
  precipProbability: popPct === null ? null : popPct / 100,
4562
4601
  precipitationMm: maybeNumber2(pickHourlyValue(h, "precipitation", isPrev, i)),
4563
- cloudCoverPct: maybeNumber2(pickHourlyValue(h, "cloud_cover", isPrev, i)),
4602
+ cloudCoverPct: maybeInt(pickHourlyValue(h, "cloud_cover", isPrev, i)),
4564
4603
  surfacePressureHpa: maybeNumber2(pickHourlyValue(h, "surface_pressure", isPrev, i)),
4565
4604
  pressureMslHpa: maybeNumber2(pickHourlyValue(h, "pressure_msl", isPrev, i)),
4566
4605
  shortwaveRadiationWm2: maybeNumber2(pickHourlyValue(h, "shortwave_radiation", isPrev, i)),
4567
4606
  directRadiationWm2: maybeNumber2(pickHourlyValue(h, "direct_radiation", isPrev, i)),
4568
4607
  capeJkg: maybeNumber2(pickHourlyValue(h, "cape", isPrev, i)),
4569
- freezingLevelM: maybeNumber2(pickHourlyValue(h, "freezing_level_height", isPrev, i)),
4608
+ freezingLevelM: maybeInt(pickHourlyValue(h, "freezing_level_height", isPrev, i)),
4570
4609
  snowDepthM: maybeNumber2(pickHourlyValue(h, "snow_depth", isPrev, i)),
4571
- visibilityM: maybeNumber2(pickHourlyValue(h, "visibility", isPrev, i)),
4572
- weatherCode: maybeNumber2(pickHourlyValue(h, "weather_code", isPrev, i)),
4610
+ visibilityM: maybeInt(pickHourlyValue(h, "visibility", isPrev, i)),
4611
+ weatherCode: maybeInt(pickHourlyValue(h, "weather_code", isPrev, i)),
4573
4612
  source,
4574
4613
  retrievedAt
4575
4614
  });
@@ -6496,7 +6535,8 @@ var mostlyright = (() => {
6496
6535
  if (resolvedStrategy === "exact_window") {
6497
6536
  const chunks = await downloadIemAsos(station, fromDate, toDate, {
6498
6537
  reportType: 3,
6499
- politenessMs: 1e3
6538
+ politenessMs: 1e3,
6539
+ exactStart: true
6500
6540
  });
6501
6541
  for (const chunk of chunks) {
6502
6542
  const rows = parseIemCsv(chunk.csv, { observationTypeOverride: "METAR" });