@mostlyrightmd/core 0.1.0-rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/discovery/index.cjs +1646 -0
  4. package/dist/discovery/index.cjs.map +1 -0
  5. package/dist/discovery/index.d.cts +313 -0
  6. package/dist/discovery/index.d.ts +313 -0
  7. package/dist/discovery/index.mjs +1609 -0
  8. package/dist/discovery/index.mjs.map +1 -0
  9. package/dist/formats/index.cjs +498 -0
  10. package/dist/formats/index.cjs.map +1 -0
  11. package/dist/formats/index.d.cts +97 -0
  12. package/dist/formats/index.d.ts +97 -0
  13. package/dist/formats/index.mjs +465 -0
  14. package/dist/formats/index.mjs.map +1 -0
  15. package/dist/index.cjs +1624 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.cts +559 -0
  18. package/dist/index.d.ts +559 -0
  19. package/dist/index.global.js +1582 -0
  20. package/dist/index.global.js.map +1 -0
  21. package/dist/index.mjs +1557 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/internal/bounds.cjs +125 -0
  24. package/dist/internal/bounds.cjs.map +1 -0
  25. package/dist/internal/bounds.d.cts +36 -0
  26. package/dist/internal/bounds.d.ts +36 -0
  27. package/dist/internal/bounds.mjs +81 -0
  28. package/dist/internal/bounds.mjs.map +1 -0
  29. package/dist/internal/cache/fs.cjs +217 -0
  30. package/dist/internal/cache/fs.cjs.map +1 -0
  31. package/dist/internal/cache/fs.d.cts +57 -0
  32. package/dist/internal/cache/fs.d.ts +57 -0
  33. package/dist/internal/cache/fs.mjs +179 -0
  34. package/dist/internal/cache/fs.mjs.map +1 -0
  35. package/dist/internal/cache/index.browser.cjs +1184 -0
  36. package/dist/internal/cache/index.browser.cjs.map +1 -0
  37. package/dist/internal/cache/index.browser.d.cts +20 -0
  38. package/dist/internal/cache/index.browser.d.ts +20 -0
  39. package/dist/internal/cache/index.browser.mjs +36 -0
  40. package/dist/internal/cache/index.browser.mjs.map +1 -0
  41. package/dist/internal/cache/index.cjs +1389 -0
  42. package/dist/internal/cache/index.cjs.map +1 -0
  43. package/dist/internal/cache/index.d.cts +16 -0
  44. package/dist/internal/cache/index.d.ts +16 -0
  45. package/dist/internal/cache/index.mjs +40 -0
  46. package/dist/internal/cache/index.mjs.map +1 -0
  47. package/dist/internal/chunk-PKJXHY27.mjs +1137 -0
  48. package/dist/internal/chunk-PKJXHY27.mjs.map +1 -0
  49. package/dist/internal/convert.cjs +161 -0
  50. package/dist/internal/convert.cjs.map +1 -0
  51. package/dist/internal/convert.d.cts +44 -0
  52. package/dist/internal/convert.d.ts +44 -0
  53. package/dist/internal/convert.mjs +117 -0
  54. package/dist/internal/convert.mjs.map +1 -0
  55. package/dist/internal/fs-O6XR4WWW.mjs +183 -0
  56. package/dist/internal/fs-O6XR4WWW.mjs.map +1 -0
  57. package/dist/internal/keys-B7C8C88N.d.cts +191 -0
  58. package/dist/internal/keys-B7C8C88N.d.ts +191 -0
  59. package/dist/internal/merge/index.cjs +75 -0
  60. package/dist/internal/merge/index.cjs.map +1 -0
  61. package/dist/internal/merge/index.d.cts +74 -0
  62. package/dist/internal/merge/index.d.ts +74 -0
  63. package/dist/internal/merge/index.mjs +46 -0
  64. package/dist/internal/merge/index.mjs.map +1 -0
  65. package/dist/internal/pairs.cjs +328 -0
  66. package/dist/internal/pairs.cjs.map +1 -0
  67. package/dist/internal/pairs.d.cts +105 -0
  68. package/dist/internal/pairs.d.ts +105 -0
  69. package/dist/internal/pairs.mjs +298 -0
  70. package/dist/internal/pairs.mjs.map +1 -0
  71. package/dist/qc/index.cjs +247 -0
  72. package/dist/qc/index.cjs.map +1 -0
  73. package/dist/qc/index.d.cts +140 -0
  74. package/dist/qc/index.d.ts +140 -0
  75. package/dist/qc/index.mjs +212 -0
  76. package/dist/qc/index.mjs.map +1 -0
  77. package/dist/temporal/index.cjs +504 -0
  78. package/dist/temporal/index.cjs.map +1 -0
  79. package/dist/temporal/index.d.cts +121 -0
  80. package/dist/temporal/index.d.ts +121 -0
  81. package/dist/temporal/index.mjs +474 -0
  82. package/dist/temporal/index.mjs.map +1 -0
  83. package/dist/transforms/index.cjs +399 -0
  84. package/dist/transforms/index.cjs.map +1 -0
  85. package/dist/transforms/index.d.cts +193 -0
  86. package/dist/transforms/index.d.ts +193 -0
  87. package/dist/transforms/index.mjs +362 -0
  88. package/dist/transforms/index.mjs.map +1 -0
  89. package/dist/validator.cjs +1870 -0
  90. package/dist/validator.cjs.map +1 -0
  91. package/dist/validator.d.cts +30 -0
  92. package/dist/validator.d.ts +30 -0
  93. package/dist/validator.mjs +1843 -0
  94. package/dist/validator.mjs.map +1 -0
  95. package/package.json +115 -0
@@ -0,0 +1,399 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/transforms/index.ts
21
+ var transforms_exports = {};
22
+ __export(transforms_exports, {
23
+ PHYSICS_BOUNDS: () => PHYSICS_BOUNDS,
24
+ ROLLING_FNS: () => ROLLING_FNS,
25
+ calendarFeatures: () => calendarFeatures,
26
+ clipOutliers: () => clipOutliers,
27
+ diff: () => diff,
28
+ diff2: () => diff2,
29
+ heatIndex: () => heatIndex,
30
+ lag: () => lag,
31
+ rolling: () => rolling,
32
+ spread: () => spread,
33
+ windChill: () => windChill
34
+ });
35
+ module.exports = __toCommonJS(transforms_exports);
36
+
37
+ // src/transforms/lag.ts
38
+ function lag(rows, col, n = 1) {
39
+ if (!Number.isInteger(n) || n < 1) {
40
+ throw new RangeError(`lag: n must be a positive integer; got ${n}`);
41
+ }
42
+ const key = `${col}_lag_${n}`;
43
+ const out = [];
44
+ for (let i = 0; i < rows.length; i++) {
45
+ let v = null;
46
+ if (i >= n) {
47
+ const src = rows[i - n]?.[col];
48
+ if (typeof src === "number" && Number.isFinite(src)) {
49
+ v = src;
50
+ }
51
+ }
52
+ out.push({ ...rows[i], [key]: v });
53
+ }
54
+ return out;
55
+ }
56
+
57
+ // src/transforms/diff.ts
58
+ function diff(rows, col, n = 1) {
59
+ if (!Number.isInteger(n) || n < 1) {
60
+ throw new RangeError(`diff: n must be a positive integer; got ${n}`);
61
+ }
62
+ const key = `${col}_diff_${n}`;
63
+ const out = [];
64
+ for (let i = 0; i < rows.length; i++) {
65
+ let v = null;
66
+ if (i >= n) {
67
+ const a = rows[i]?.[col];
68
+ const b = rows[i - n]?.[col];
69
+ if (typeof a === "number" && Number.isFinite(a) && typeof b === "number" && Number.isFinite(b)) {
70
+ v = a - b;
71
+ }
72
+ }
73
+ out.push({ ...rows[i], [key]: v });
74
+ }
75
+ return out;
76
+ }
77
+ function diff2(rows, col) {
78
+ const first = new Array(rows.length).fill(null);
79
+ for (let i = 1; i < rows.length; i++) {
80
+ const a = rows[i]?.[col];
81
+ const b = rows[i - 1]?.[col];
82
+ if (typeof a === "number" && Number.isFinite(a) && typeof b === "number" && Number.isFinite(b)) {
83
+ first[i] = a - b;
84
+ }
85
+ }
86
+ const key = `${col}_diff2`;
87
+ const out = [];
88
+ for (let i = 0; i < rows.length; i++) {
89
+ let v = null;
90
+ if (i >= 2) {
91
+ const a = first[i];
92
+ const b = first[i - 1];
93
+ if (a != null && b != null) {
94
+ v = a - b;
95
+ }
96
+ }
97
+ out.push({ ...rows[i], [key]: v });
98
+ }
99
+ return out;
100
+ }
101
+
102
+ // src/transforms/rolling.ts
103
+ var ROLLING_FNS = ["mean", "median", "min", "max", "std", "count"];
104
+ function isRollingFn(v) {
105
+ return typeof v === "string" && ROLLING_FNS.includes(v);
106
+ }
107
+ function aggregate(vals, fn) {
108
+ if (vals.length === 0) {
109
+ return fn === "count" ? 0 : null;
110
+ }
111
+ if (fn === "count") {
112
+ return vals.length;
113
+ }
114
+ if (fn === "mean") {
115
+ let sum2 = 0;
116
+ for (const v of vals) sum2 += v;
117
+ return sum2 / vals.length;
118
+ }
119
+ if (fn === "min") {
120
+ let m = vals[0];
121
+ for (let i = 1; i < vals.length; i++) {
122
+ const x = vals[i];
123
+ if (x < m) m = x;
124
+ }
125
+ return m;
126
+ }
127
+ if (fn === "max") {
128
+ let m = vals[0];
129
+ for (let i = 1; i < vals.length; i++) {
130
+ const x = vals[i];
131
+ if (x > m) m = x;
132
+ }
133
+ return m;
134
+ }
135
+ if (fn === "median") {
136
+ const sorted = [...vals].sort((a, b) => a - b);
137
+ const mid = Math.floor(sorted.length / 2);
138
+ if (sorted.length % 2 === 0) {
139
+ const lo = sorted[mid - 1];
140
+ const hi = sorted[mid];
141
+ if (lo === void 0 || hi === void 0) return null;
142
+ return (lo + hi) / 2;
143
+ }
144
+ return sorted[mid] ?? null;
145
+ }
146
+ if (vals.length < 2) return null;
147
+ let sum = 0;
148
+ for (const v of vals) sum += v;
149
+ const mean = sum / vals.length;
150
+ let sumSq = 0;
151
+ for (const v of vals) sumSq += (v - mean) ** 2;
152
+ return Math.sqrt(sumSq / (vals.length - 1));
153
+ }
154
+ function rolling(rows, col, window, fn = "mean") {
155
+ if (!Number.isInteger(window) || window < 1) {
156
+ throw new RangeError(`rolling: window must be a positive integer; got ${window}`);
157
+ }
158
+ if (!isRollingFn(fn)) {
159
+ throw new RangeError(
160
+ `rolling: fn must be one of ${JSON.stringify(ROLLING_FNS)}; got '${String(fn)}'`
161
+ );
162
+ }
163
+ const key = `${col}_rolling_${window}_${fn}`;
164
+ const out = [];
165
+ for (let i = 0; i < rows.length; i++) {
166
+ const start = Math.max(0, i - window + 1);
167
+ const slice = [];
168
+ for (let j = start; j <= i; j++) {
169
+ const v = rows[j]?.[col];
170
+ if (typeof v === "number" && Number.isFinite(v)) {
171
+ slice.push(v);
172
+ }
173
+ }
174
+ const agg = aggregate(slice, fn);
175
+ out.push({ ...rows[i], [key]: agg });
176
+ }
177
+ return out;
178
+ }
179
+
180
+ // src/transforms/calendar.ts
181
+ function calendarFeatures(rows, dateCol, tz) {
182
+ let formatter = null;
183
+ if (tz !== void 0) {
184
+ try {
185
+ formatter = new Intl.DateTimeFormat("en-US", {
186
+ timeZone: tz,
187
+ year: "numeric",
188
+ month: "2-digit",
189
+ day: "2-digit",
190
+ hour: "2-digit",
191
+ minute: "2-digit",
192
+ second: "2-digit",
193
+ hour12: false,
194
+ weekday: "short"
195
+ });
196
+ } catch (e) {
197
+ throw new RangeError(`calendarFeatures: invalid IANA timezone '${tz}': ${String(e)}`);
198
+ }
199
+ }
200
+ const TAU = 2 * Math.PI;
201
+ const NULLS = {
202
+ month_sin: null,
203
+ month_cos: null,
204
+ dow_sin: null,
205
+ dow_cos: null,
206
+ hour_sin: null,
207
+ hour_cos: null,
208
+ day_of_year_sin: null,
209
+ day_of_year_cos: null
210
+ };
211
+ const WEEKDAY_INDEX = {
212
+ Mon: 0,
213
+ Tue: 1,
214
+ Wed: 2,
215
+ Thu: 3,
216
+ Fri: 4,
217
+ Sat: 5,
218
+ Sun: 6
219
+ };
220
+ const out = [];
221
+ for (const r of rows) {
222
+ const raw = r[dateCol];
223
+ let d = null;
224
+ if (raw instanceof Date) {
225
+ d = Number.isFinite(raw.getTime()) ? raw : null;
226
+ } else if (typeof raw === "string") {
227
+ const parsed = new Date(raw);
228
+ d = Number.isFinite(parsed.getTime()) ? parsed : null;
229
+ } else if (typeof raw === "number" && Number.isFinite(raw)) {
230
+ d = new Date(raw);
231
+ }
232
+ if (d === null) {
233
+ out.push({ ...r, ...NULLS });
234
+ continue;
235
+ }
236
+ let month;
237
+ let dow;
238
+ let hour;
239
+ let doy;
240
+ if (formatter !== null) {
241
+ const parts = formatter.formatToParts(d);
242
+ const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
243
+ const y = Number.parseInt(get("year"), 10);
244
+ month = Number.parseInt(get("month"), 10);
245
+ const dom = Number.parseInt(get("day"), 10);
246
+ hour = Number.parseInt(get("hour"), 10);
247
+ dow = WEEKDAY_INDEX[get("weekday")] ?? 0;
248
+ doy = Math.floor((Date.UTC(y, month - 1, dom) - Date.UTC(y, 0, 1)) / 864e5) + 1;
249
+ } else {
250
+ month = d.getUTCMonth() + 1;
251
+ dow = (d.getUTCDay() + 6) % 7;
252
+ hour = d.getUTCHours();
253
+ const yStart = Date.UTC(d.getUTCFullYear(), 0, 1);
254
+ doy = Math.floor((d.getTime() - yStart) / 864e5) + 1;
255
+ }
256
+ const derived = {
257
+ month_sin: Math.sin(TAU * month / 12),
258
+ month_cos: Math.cos(TAU * month / 12),
259
+ dow_sin: Math.sin(TAU * dow / 7),
260
+ dow_cos: Math.cos(TAU * dow / 7),
261
+ hour_sin: Math.sin(TAU * hour / 24),
262
+ hour_cos: Math.cos(TAU * hour / 24),
263
+ day_of_year_sin: Math.sin(TAU * doy / 365),
264
+ day_of_year_cos: Math.cos(TAU * doy / 365)
265
+ };
266
+ out.push({ ...r, ...derived });
267
+ }
268
+ return out;
269
+ }
270
+
271
+ // src/transforms/cross.ts
272
+ function spread(rows, colA, colB) {
273
+ const key = `${colA}_minus_${colB}`;
274
+ const out = [];
275
+ for (const r of rows) {
276
+ const a = r?.[colA];
277
+ const b = r?.[colB];
278
+ const v = typeof a === "number" && Number.isFinite(a) && typeof b === "number" && Number.isFinite(b) ? a - b : null;
279
+ out.push({ ...r, [key]: v });
280
+ }
281
+ return out;
282
+ }
283
+
284
+ // src/transforms/weather.ts
285
+ function windChill(tempF, windMph) {
286
+ if (tempF === null || tempF === void 0 || windMph === null || windMph === void 0) {
287
+ return null;
288
+ }
289
+ if (typeof tempF !== "number" || !Number.isFinite(tempF)) return null;
290
+ if (typeof windMph !== "number" || !Number.isFinite(windMph)) return null;
291
+ if (tempF > 50 || windMph <= 3) return tempF;
292
+ const v016 = windMph ** 0.16;
293
+ return 35.74 + 0.6215 * tempF - 35.75 * v016 + 0.4275 * tempF * v016;
294
+ }
295
+ function heatIndex(tempF, rhPct) {
296
+ if (tempF === null || tempF === void 0 || rhPct === null || rhPct === void 0) {
297
+ return null;
298
+ }
299
+ if (typeof tempF !== "number" || !Number.isFinite(tempF)) return null;
300
+ if (typeof rhPct !== "number" || !Number.isFinite(rhPct)) return null;
301
+ if (tempF < 80) return tempF;
302
+ const t = tempF;
303
+ const h = rhPct;
304
+ const simple = 0.5 * (t + 61 + (t - 68) * 1.2 + h * 0.094);
305
+ if ((simple + t) / 2 < 80) return simple;
306
+ let hi = -42.379 + 2.04901523 * t + 10.14333127 * h - 0.22475541 * t * h - 683783e-8 * t * t - 0.05481717 * h * h + 122874e-8 * t * t * h + 85282e-8 * t * h * h - 199e-8 * t * t * h * h;
307
+ if (h < 13 && t >= 80 && t <= 112) {
308
+ hi -= (13 - h) / 4 * Math.sqrt((17 - Math.abs(t - 95)) / 17);
309
+ } else if (h > 85 && t >= 80 && t <= 87) {
310
+ hi += (h - 85) / 10 * ((87 - t) / 5);
311
+ }
312
+ return hi;
313
+ }
314
+
315
+ // src/transforms/clip.ts
316
+ var PHYSICS_BOUNDS = /* @__PURE__ */ new Map([
317
+ ["temp_c", [-89, 57]],
318
+ ["dew_point_c", [-89, 35]],
319
+ ["dewpoint_c", [-89, 35]],
320
+ ["wind_speed_ms", [0, 100]],
321
+ ["wind_speed_kt", [0, 200]],
322
+ ["wind_dir_deg", [0, 360]],
323
+ ["wind_dir_degrees", [0, 360]],
324
+ ["slp_hpa", [870, 1085]],
325
+ ["sea_level_pressure_mb", [870, 1085]],
326
+ ["relative_humidity_pct_2m", [0, 100]],
327
+ ["precip_mm_1h", [0, 305]]
328
+ ]);
329
+ function clipOutliers(rows, col, opts = {}) {
330
+ const std = opts.std ?? 3;
331
+ const key = `${col}_clipped`;
332
+ let lo;
333
+ let hi;
334
+ let passThrough = false;
335
+ if (opts.bounds !== void 0) {
336
+ [lo, hi] = opts.bounds;
337
+ } else if (PHYSICS_BOUNDS.has(col)) {
338
+ const b = PHYSICS_BOUNDS.get(col);
339
+ if (b === void 0) {
340
+ throw new Error(`PHYSICS_BOUNDS.get(${col}) unexpectedly undefined`);
341
+ }
342
+ [lo, hi] = b;
343
+ } else {
344
+ if (!Number.isFinite(std) || std <= 0) {
345
+ throw new RangeError(
346
+ `clipOutliers: std must be > 0 for the sigma fallback (got ${std}); pass bounds=[lo, hi] or use a physics-default column`
347
+ );
348
+ }
349
+ const vals = [];
350
+ for (const r of rows) {
351
+ const v = r?.[col];
352
+ if (typeof v === "number" && Number.isFinite(v)) vals.push(v);
353
+ }
354
+ if (vals.length < 2) {
355
+ passThrough = true;
356
+ lo = Number.NEGATIVE_INFINITY;
357
+ hi = Number.POSITIVE_INFINITY;
358
+ } else {
359
+ const mu = vals.reduce((a, b) => a + b, 0) / vals.length;
360
+ const sumSq = vals.reduce((a, b) => a + (b - mu) ** 2, 0);
361
+ const sigma = Math.sqrt(sumSq / (vals.length - 1));
362
+ if (sigma === 0 || !Number.isFinite(sigma)) {
363
+ passThrough = true;
364
+ lo = Number.NEGATIVE_INFINITY;
365
+ hi = Number.POSITIVE_INFINITY;
366
+ } else {
367
+ lo = mu - std * sigma;
368
+ hi = mu + std * sigma;
369
+ }
370
+ }
371
+ }
372
+ const out = [];
373
+ for (const r of rows) {
374
+ const v = r?.[col];
375
+ let clipped;
376
+ if (typeof v === "number" && Number.isFinite(v)) {
377
+ clipped = passThrough ? v : Math.min(Math.max(v, lo), hi);
378
+ } else {
379
+ clipped = null;
380
+ }
381
+ out.push({ ...r, [key]: clipped });
382
+ }
383
+ return out;
384
+ }
385
+ // Annotate the CommonJS export names for ESM import in node:
386
+ 0 && (module.exports = {
387
+ PHYSICS_BOUNDS,
388
+ ROLLING_FNS,
389
+ calendarFeatures,
390
+ clipOutliers,
391
+ diff,
392
+ diff2,
393
+ heatIndex,
394
+ lag,
395
+ rolling,
396
+ spread,
397
+ windChill
398
+ });
399
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/transforms/index.ts","../../src/transforms/lag.ts","../../src/transforms/diff.ts","../../src/transforms/rolling.ts","../../src/transforms/calendar.ts","../../src/transforms/cross.ts","../../src/transforms/weather.ts","../../src/transforms/clip.ts"],"sourcesContent":["// Barrel for @mostlyrightmd/core/transforms — TS-W4 Plan 02 + 03 + 04.\n//\n// Pure functions porting Python `mostlyright.transforms` (lag/diff/diff2/\n// rolling/calendar_features/spread/wind_chill/heat_index) plus\n// `mostlyright.preprocessing.clip_outliers`. Lives at the subpath, NOT the\n// root barrel, to keep the @mostlyrightmd/core main bundle under its 25 KB\n// size-limit gate (TS-BUNDLE-01). Same pattern as temporal / formats /\n// validator — see iter-4 H8 lesson in `packages-ts/core/src/index.ts`.\n//\n// Consumers import with:\n//\n// import {\n// lag, diff, diff2, rolling, calendarFeatures,\n// spread, windChill, heatIndex, clipOutliers, PHYSICS_BOUNDS,\n// } from \"@mostlyrightmd/core/transforms\";\n// import type { RollingFn, ClipOutliersOptions } from \"@mostlyrightmd/core/transforms\";\n\nexport { lag } from \"./lag.js\";\nexport { diff, diff2 } from \"./diff.js\";\nexport { ROLLING_FNS, type RollingFn, rolling } from \"./rolling.js\";\nexport { calendarFeatures } from \"./calendar.js\";\nexport { spread } from \"./cross.js\";\nexport { windChill, heatIndex } from \"./weather.js\";\nexport { clipOutliers, PHYSICS_BOUNDS, type ClipOutliersOptions } from \"./clip.js\";\n","// TS-W4 Plan 02 Task 1 — lag transform.\n//\n// Pure row→row port of Python `mostlyright.transforms.lag` (packages/core/src/\n// mostlyright/transforms.py:43-45). TS does NOT have pandas Series; we operate\n// on a `ReadonlyArray<Row>` and add a derived column `{col}_lag_{n}` to each\n// output row. Source rows are NOT mutated.\n//\n// Numeric coercion is STRICT: only `typeof v === 'number' && Number.isFinite(v)`\n// passes through. Strings like `'3.5'` are NOT auto-parsed — upstream parsers\n// (the AWC / IEM / CLI adapters) are responsible for coercion before any\n// transform runs. This avoids silent type confusion in the data layer.\n\n/**\n * Shift a column by `n` rows.\n *\n * Mirrors Python `pd.Series.shift(periods=n)` semantics: at output index `i`,\n * the derived column carries `rows[i-n][col]` if available, else `null`.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param col column name to lag\n * @param n positive integer; rows at index `< n` get `null` in the derived column\n * @returns new array of rows, each carrying `{col}_lag_{n}` derived column\n * @throws RangeError if `n < 1` or `!Number.isInteger(n)`\n */\nexport function lag<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n col: string,\n n = 1,\n): ReadonlyArray<Row & Record<string, number | null>> {\n if (!Number.isInteger(n) || n < 1) {\n throw new RangeError(`lag: n must be a positive integer; got ${n}`);\n }\n const key = `${col}_lag_${n}`;\n const out: Array<Row & Record<string, number | null>> = [];\n for (let i = 0; i < rows.length; i++) {\n let v: number | null = null;\n if (i >= n) {\n const src = rows[i - n]?.[col];\n if (typeof src === \"number\" && Number.isFinite(src)) {\n v = src;\n }\n }\n out.push({ ...(rows[i] as Row), [key]: v } as Row & Record<string, number | null>);\n }\n return out;\n}\n","// TS-W4 Plan 02 Task 1 — diff + diff2 transforms.\n//\n// Pure row→row ports of Python `mostlyright.transforms.diff` and `diff2`\n// (packages/core/src/mostlyright/transforms.py:48-55). The TS port operates\n// on `ReadonlyArray<Row>` and adds a single derived column per call:\n// - diff → `{col}_diff_{n}`\n// - diff2 → `{col}_diff2` (NOT `{col}_diff_1` + `{col}_diff2`)\n//\n// Numeric coercion is STRICT: only finite numbers pass through; strings,\n// null, undefined, NaN, and ±Infinity all produce `null` in the derived\n// column (matches Python `pd.NA`/`None` behavior on serialization).\n\n/**\n * First (or nth-order) discrete difference of a column.\n *\n * At output index `i`, the derived column carries\n * `rows[i][col] - rows[i-n][col]` if both are finite numbers, else `null`.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param col column name to difference\n * @param n positive integer step (default 1)\n * @returns new array of rows, each carrying `{col}_diff_{n}` derived column\n * @throws RangeError if `n < 1` or `!Number.isInteger(n)`\n */\nexport function diff<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n col: string,\n n = 1,\n): ReadonlyArray<Row & Record<string, number | null>> {\n if (!Number.isInteger(n) || n < 1) {\n throw new RangeError(`diff: n must be a positive integer; got ${n}`);\n }\n const key = `${col}_diff_${n}`;\n const out: Array<Row & Record<string, number | null>> = [];\n for (let i = 0; i < rows.length; i++) {\n let v: number | null = null;\n if (i >= n) {\n const a = rows[i]?.[col];\n const b = rows[i - n]?.[col];\n if (\n typeof a === \"number\" &&\n Number.isFinite(a) &&\n typeof b === \"number\" &&\n Number.isFinite(b)\n ) {\n v = a - b;\n }\n }\n out.push({ ...(rows[i] as Row), [key]: v } as Row & Record<string, number | null>);\n }\n return out;\n}\n\n/**\n * Second discrete difference of a column.\n *\n * Equivalent to `diff(diff(col))`. The first two output rows carry `null`\n * (no prior diff available). Mirrors Python `df[column].diff().diff()` which\n * returns a single Series — so the TS output carries ONLY `{col}_diff2`,\n * NOT the intermediate `{col}_diff_1` from the first pass.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param col column name to second-difference\n * @returns new array of rows, each carrying `{col}_diff2` derived column\n */\nexport function diff2<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n col: string,\n): ReadonlyArray<Row & Record<string, number | null>> {\n // Pass 1: compute first-differences into a parallel numeric array.\n // Doing it this way (rather than chaining `diff(diff(...))`) lets us\n // drop the intermediate `{col}_diff_1` column cleanly — the output\n // carries only `{col}_diff2`, matching Python's single-Series return.\n const first: Array<number | null> = new Array(rows.length).fill(null);\n for (let i = 1; i < rows.length; i++) {\n const a = rows[i]?.[col];\n const b = rows[i - 1]?.[col];\n if (\n typeof a === \"number\" &&\n Number.isFinite(a) &&\n typeof b === \"number\" &&\n Number.isFinite(b)\n ) {\n first[i] = a - b;\n }\n }\n // Pass 2: second-differences from `first`.\n const key = `${col}_diff2`;\n const out: Array<Row & Record<string, number | null>> = [];\n for (let i = 0; i < rows.length; i++) {\n let v: number | null = null;\n if (i >= 2) {\n const a = first[i];\n const b = first[i - 1];\n if (a != null && b != null) {\n v = a - b;\n }\n }\n out.push({ ...(rows[i] as Row), [key]: v } as Row & Record<string, number | null>);\n }\n return out;\n}\n","// TS-W4 Plan 02 Task 2 — rolling reduction transform.\n//\n// Pure row→row port of Python `mostlyright.transforms.rolling`\n// (packages/core/src/mostlyright/transforms.py:58-68) which uses\n// `df[col].rolling(window=window, min_periods=1)` and `getattr(rolling, fn)()`.\n//\n// Key semantics (load-bearing for Wave 5 QCEngine and Python parity):\n// - min_periods=1: every row gets a value as long as the window contains\n// at least one non-null finite number. No leading nulls from \"warmup\"\n// — pandas defaults to `min_periods=window` for `.rolling(window)`\n// but `transforms.py` explicitly overrides to 1.\n// - std uses Bessel's correction (denominator n-1) — matches pandas\n// default `.std(ddof=1)`. Needs ≥ 2 non-null values; else null.\n// - Derived column name: `{col}_rolling_{window}_{fn}` — exact format.\n// - `fn` is a const-union (NOT enum) so the type narrows in callers.\n\n/** The set of reducer names accepted by `rolling`. Ordering is API surface. */\nexport const ROLLING_FNS = [\"mean\", \"median\", \"min\", \"max\", \"std\", \"count\"] as const;\n\n/** Union of the six reducer-name string literals. */\nexport type RollingFn = (typeof ROLLING_FNS)[number];\n\nfunction isRollingFn(v: unknown): v is RollingFn {\n return typeof v === \"string\" && (ROLLING_FNS as readonly string[]).includes(v);\n}\n\n/** Aggregate a numeric window. Empty window → null (or 0 for `count`). */\nfunction aggregate(vals: ReadonlyArray<number>, fn: RollingFn): number | null {\n if (vals.length === 0) {\n return fn === \"count\" ? 0 : null;\n }\n if (fn === \"count\") {\n return vals.length;\n }\n if (fn === \"mean\") {\n let sum = 0;\n for (const v of vals) sum += v;\n return sum / vals.length;\n }\n if (fn === \"min\") {\n let m = vals[0] as number;\n for (let i = 1; i < vals.length; i++) {\n const x = vals[i] as number;\n if (x < m) m = x;\n }\n return m;\n }\n if (fn === \"max\") {\n let m = vals[0] as number;\n for (let i = 1; i < vals.length; i++) {\n const x = vals[i] as number;\n if (x > m) m = x;\n }\n return m;\n }\n if (fn === \"median\") {\n const sorted = [...vals].sort((a, b) => a - b);\n const mid = Math.floor(sorted.length / 2);\n if (sorted.length % 2 === 0) {\n const lo = sorted[mid - 1];\n const hi = sorted[mid];\n // mid >= 1 here because length is even ≥ 2; both defined.\n if (lo === undefined || hi === undefined) return null;\n return (lo + hi) / 2;\n }\n return sorted[mid] ?? null;\n }\n // std — sample stdev with Bessel's correction (n-1 denominator).\n if (vals.length < 2) return null;\n let sum = 0;\n for (const v of vals) sum += v;\n const mean = sum / vals.length;\n let sumSq = 0;\n for (const v of vals) sumSq += (v - mean) ** 2;\n return Math.sqrt(sumSq / (vals.length - 1));\n}\n\n/**\n * Windowed reduction over a numeric column.\n *\n * At each output row `i`, the window covers\n * `rows[max(0, i-window+1) .. i]` (inclusive both ends), so the first\n * `window-1` rows compute against a partial (still-filling) window —\n * `min_periods=1` semantics from Python.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param col column name to reduce over\n * @param window positive integer window size (≥ 1)\n * @param fn one of `'mean' | 'median' | 'min' | 'max' | 'std' | 'count'`\n * @returns new array of rows, each carrying `{col}_rolling_{window}_{fn}`\n * @throws RangeError if `window < 1`, non-integer, or `fn` is not a `RollingFn`\n */\nexport function rolling<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n col: string,\n window: number,\n fn: RollingFn = \"mean\",\n): ReadonlyArray<Row & Record<string, number | null>> {\n if (!Number.isInteger(window) || window < 1) {\n throw new RangeError(`rolling: window must be a positive integer; got ${window}`);\n }\n if (!isRollingFn(fn)) {\n throw new RangeError(\n `rolling: fn must be one of ${JSON.stringify(ROLLING_FNS)}; got '${String(fn)}'`,\n );\n }\n const key = `${col}_rolling_${window}_${fn}`;\n const out: Array<Row & Record<string, number | null>> = [];\n for (let i = 0; i < rows.length; i++) {\n const start = Math.max(0, i - window + 1);\n const slice: number[] = [];\n for (let j = start; j <= i; j++) {\n const v = rows[j]?.[col];\n if (typeof v === \"number\" && Number.isFinite(v)) {\n slice.push(v);\n }\n }\n const agg = aggregate(slice, fn);\n out.push({ ...(rows[i] as Row), [key]: agg } as Row & Record<string, number | null>);\n }\n return out;\n}\n","// TS-W4 Plan 03 Task 1 — calendarFeatures transform.\n//\n// Pure row→row port of Python `mostlyright.transforms.calendar_features`\n// (packages/core/src/mostlyright/transforms.py:71-100). Adds 8 cyclical-pair\n// columns (month_sin/cos, dow_sin/cos, hour_sin/cos, day_of_year_sin/cos)\n// to each output row; source rows NOT mutated.\n//\n// TZ awareness (the load-bearing detail): when the caller passes `tz` (an\n// IANA name like 'America/New_York'), month/dow/hour/dayOfYear come from\n// the LOCAL clock in that tz — extracted via `Intl.DateTimeFormat`\n// (browser + Node built-in; NO luxon/date-fns dep, keeping the bundle\n// under the TS-BUNDLE-01 25 KB gate).\n//\n// When `tz` is omitted, UTC parts of the parsed date are used\n// (`getUTCMonth`, `getUTCDay`, etc.).\n//\n// Cyclical formulas (verbatim from Python `transforms.py:90-99`):\n// month_sin = sin(2π·month/12), month ∈ [1, 12]\n// dow_sin = sin(2π·dow/7), dow ∈ [0, 6] (Mon=0..Sun=6 ISO order)\n// hour_sin = sin(2π·hour/24), hour ∈ [0, 23]\n// day_of_year_sin = sin(2π·doy/365.0) — note 365.0 (NOT 365.25, NOT 366)\n//\n// Day-of-week ordering: ISO Monday-first (Mon=0..Sun=6), matching Python's\n// `pd.Series.dt.dayofweek`. JS's native `Date.prototype.getDay()` uses\n// Sunday=0..Saturday=6, so we convert with `(jsDay + 6) % 7`.\n//\n// Null handling: rows whose `dateCol` value is null/undefined/non-parseable\n// produce `null` (NOT NaN) for all 8 derived columns. Matches Python's\n// returning NaT/NaN without raising.\n\n/**\n * Add 8 cyclical-pair calendar features to each row.\n *\n * Pairs (sin²+cos² ≈ 1, so a model sees wraparound — Dec→Jan is 1 month\n * apart, not 11):\n *\n * - `month_sin` / `month_cos` (period 12)\n * - `dow_sin` / `dow_cos` (period 7, ISO Mon=0..Sun=6)\n * - `hour_sin` / `hour_cos` (period 24)\n * - `day_of_year_sin` / `day_of_year_cos` (period 365.0, NOT 365.25)\n *\n * TZ handling: when `tz` is an IANA zone name like 'America/New_York',\n * month/dow/hour/dayOfYear come from the LOCAL clock in that tz (via\n * `Intl.DateTimeFormat`). When `tz` is omitted, UTC parts are used.\n *\n * Invalid `tz` throws `RangeError` BEFORE any row processing (fail-fast).\n *\n * @param rows input rows (NOT mutated)\n * @param dateCol column name containing a date — accepted as ISO string,\n * `Date` instance, or finite epoch-ms number\n * @param tz optional IANA timezone name (validated upfront)\n * @returns new array of rows, each carrying 8 new derived columns\n * (each value is a `number` from sin/cos, or `null` when\n * the row's date is non-parseable)\n * @throws RangeError if `tz` is provided but not a valid IANA zone\n */\nexport function calendarFeatures<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n dateCol: string,\n tz?: string,\n): ReadonlyArray<Row & Record<string, number | null>> {\n // Fail-fast tz validation: construct a formatter once, before any row work.\n // Invalid IANA names throw RangeError from the Intl constructor; we\n // re-throw with a clearer message that includes the offending string.\n let formatter: Intl.DateTimeFormat | null = null;\n if (tz !== undefined) {\n try {\n formatter = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: tz,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false,\n weekday: \"short\",\n });\n } catch (e) {\n throw new RangeError(`calendarFeatures: invalid IANA timezone '${tz}': ${String(e)}`);\n }\n }\n\n const TAU = 2 * Math.PI;\n // Sentinel object for the null-date case: all 8 derived columns are null\n // (NOT NaN; matches Python's None/NaT for unparseable dates).\n const NULLS = {\n month_sin: null,\n month_cos: null,\n dow_sin: null,\n dow_cos: null,\n hour_sin: null,\n hour_cos: null,\n day_of_year_sin: null,\n day_of_year_cos: null,\n } as const;\n\n // Map Intl's `weekday: 'short'` (en-US: Mon, Tue, ..., Sun) to the ISO\n // Monday-first ordering used by Python's `dt.dayofweek` (Mon=0..Sun=6).\n const WEEKDAY_INDEX: Readonly<Record<string, number>> = {\n Mon: 0,\n Tue: 1,\n Wed: 2,\n Thu: 3,\n Fri: 4,\n Sat: 5,\n Sun: 6,\n };\n\n const out: Array<Row & Record<string, number | null>> = [];\n for (const r of rows) {\n const raw = r[dateCol];\n\n // Parse `raw` into a valid Date, or `null` if the value isn't a usable date.\n let d: Date | null = null;\n if (raw instanceof Date) {\n d = Number.isFinite(raw.getTime()) ? raw : null;\n } else if (typeof raw === \"string\") {\n const parsed = new Date(raw);\n d = Number.isFinite(parsed.getTime()) ? parsed : null;\n } else if (typeof raw === \"number\" && Number.isFinite(raw)) {\n d = new Date(raw);\n }\n\n if (d === null) {\n out.push({ ...r, ...NULLS } as Row & Record<string, number | null>);\n continue;\n }\n\n let month: number; // 1..12\n let dow: number; // 0..6 (Mon=0..Sun=6 ISO)\n let hour: number; // 0..23\n let doy: number; // 1..366\n\n if (formatter !== null) {\n // TZ branch: use Intl.DateTimeFormat.formatToParts to pluck the\n // LOCAL-tz year/month/day/hour/weekday for `d`. This is the\n // browser+Node native pathway for tz-aware calendar extraction —\n // no luxon/date-fns dep required.\n const parts = formatter.formatToParts(d);\n const get = (t: string): string => parts.find((p) => p.type === t)?.value ?? \"\";\n const y = Number.parseInt(get(\"year\"), 10);\n month = Number.parseInt(get(\"month\"), 10);\n const dom = Number.parseInt(get(\"day\"), 10);\n hour = Number.parseInt(get(\"hour\"), 10);\n // `weekday: 'short'` returns 'Mon', 'Tue', ..., 'Sun' in en-US.\n // Map to ISO Mon=0..Sun=6. `?? 0` is unreachable in practice (formatter\n // always emits a weekday part), but kept defensive.\n dow = WEEKDAY_INDEX[get(\"weekday\")] ?? 0;\n // Day-of-year from the tz-LOCAL (y, month, dom) tuple: number of full\n // days between Jan 1 of that year and the local date, +1. Computing\n // via `Date.UTC` differences sidesteps DST: both anchors share the\n // same UTC offset (they're treated as UTC for arithmetic only).\n doy = Math.floor((Date.UTC(y, month - 1, dom) - Date.UTC(y, 0, 1)) / 86400000) + 1;\n } else {\n // UTC branch: pull straight from the Date's UTC accessors.\n month = d.getUTCMonth() + 1;\n // JS getUTCDay: Sun=0..Sat=6. Convert to ISO Mon=0..Sun=6.\n dow = (d.getUTCDay() + 6) % 7;\n hour = d.getUTCHours();\n const yStart = Date.UTC(d.getUTCFullYear(), 0, 1);\n doy = Math.floor((d.getTime() - yStart) / 86400000) + 1;\n }\n\n const derived = {\n month_sin: Math.sin((TAU * month) / 12),\n month_cos: Math.cos((TAU * month) / 12),\n dow_sin: Math.sin((TAU * dow) / 7),\n dow_cos: Math.cos((TAU * dow) / 7),\n hour_sin: Math.sin((TAU * hour) / 24),\n hour_cos: Math.cos((TAU * hour) / 24),\n day_of_year_sin: Math.sin((TAU * doy) / 365.0),\n day_of_year_cos: Math.cos((TAU * doy) / 365.0),\n };\n\n out.push({ ...r, ...derived } as Row & Record<string, number | null>);\n }\n return out;\n}\n","// TS-W4 Plan 04 Task 1 — spread (pairwise cross-feature).\n//\n// Pure row→row port of Python `mostlyright.transforms.spread` (packages/core/\n// src/mostlyright/transforms.py:103-105). TS does NOT have pandas Series; we\n// operate on a `ReadonlyArray<Row>` and add a derived column\n// `{colA}_minus_{colB}` to each output row. Source rows are NOT mutated.\n//\n// Numeric coercion is STRICT: only `typeof v === 'number' && Number.isFinite(v)`\n// passes through. Strings like `'10'` are NOT auto-parsed — upstream parsers\n// are responsible for coercion before any transform runs. Matches Wave 2/3\n// strictness (lag/diff/rolling/calendar).\n\n/**\n * Pairwise difference between two numeric columns.\n *\n * Mirrors Python `transforms.spread(df, col_a, col_b)`. Derived column name\n * is exactly `{colA}_minus_{colB}`. Value at index i is `rows[i][colA] -\n * rows[i][colB]` when both are finite numbers; otherwise `null`.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param colA minuend column\n * @param colB subtrahend column\n * @returns new array of rows, each carrying `{colA}_minus_{colB}` column\n */\nexport function spread<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n colA: string,\n colB: string,\n): ReadonlyArray<Row & Record<string, number | null>> {\n const key = `${colA}_minus_${colB}`;\n const out: Array<Row & Record<string, number | null>> = [];\n for (const r of rows) {\n const a = r?.[colA];\n const b = r?.[colB];\n const v =\n typeof a === \"number\" && Number.isFinite(a) && typeof b === \"number\" && Number.isFinite(b)\n ? a - b\n : null;\n out.push({ ...(r as Row), [key]: v } as Row & Record<string, number | null>);\n }\n return out;\n}\n","// TS-W4 Plan 04 Task 1 — NWS weather cross-features (wind chill + heat index).\n//\n// Pure scalar ports of Python `mostlyright.transforms.wind_chill` and\n// `heat_index` at packages/core/src/mostlyright/transforms.py:108-147.\n//\n// **PARITY-NOTE (out-of-domain return value):**\n// Python returns `temp_f` UNCHANGED when outside the valid domain\n// (transforms.py:114 for wind_chill, transforms.py:126 for heat_index).\n// The REQUIREMENTS.md text says \"out-of-domain → null\" but that is incorrect\n// vs the canonical Python source. We honor Python source and return tempF\n// unchanged in those branches. `null` is reserved for null / undefined /\n// non-finite inputs only.\n\n/**\n * NWS wind-chill formula (°F). Domain: `tempF ≤ 50 AND windMph > 3`.\n *\n * Mirrors Python `transforms.wind_chill` at transforms.py:108-116. The\n * formula is the 2001 NWS standard:\n *\n * wc = 35.74 + 0.6215 * T - 35.75 * V^0.16 + 0.4275 * T * V^0.16\n *\n * where T is air temperature in °F and V is wind speed in mph.\n *\n * **Out-of-domain (NOT null — Python parity):** when the domain bounds are\n * not satisfied, returns `tempF` unchanged — physically \"wind chill equals\n * air temperature when wind is calm or air is warm\". Null is reserved for\n * null / undefined / non-finite inputs.\n *\n * Reference: https://www.weather.gov/safety/cold-wind-chill-chart\n * Sanity: `windChill(20, 15) ≈ 6 °F` matches the NWS chart.\n */\nexport function windChill(\n tempF: number | null | undefined,\n windMph: number | null | undefined,\n): number | null {\n if (tempF === null || tempF === undefined || windMph === null || windMph === undefined) {\n return null;\n }\n if (typeof tempF !== \"number\" || !Number.isFinite(tempF)) return null;\n if (typeof windMph !== \"number\" || !Number.isFinite(windMph)) return null;\n if (tempF > 50.0 || windMph <= 3.0) return tempF; // out-of-domain → tempF (Python parity)\n const v016 = windMph ** 0.16;\n return 35.74 + 0.6215 * tempF - 35.75 * v016 + 0.4275 * tempF * v016;\n}\n\n/**\n * NWS heat index (°F) using the Rothfusz regression. Domain: `tempF ≥ 80`.\n *\n * Mirrors Python `transforms.heat_index` at transforms.py:119-147. Includes:\n *\n * 1. A simple approximation `simple = 0.5*(T + 61 + (T-68)*1.2 + RH*0.094)`\n * used when `(simple + T)/2 < 80` (low-effective-temperature fast path).\n * 2. The Rothfusz 9-term polynomial:\n * hi = -42.379 + 2.04901523*T + 10.14333127*RH - 0.22475541*T*RH\n * - 0.00683783*T² - 0.05481717*RH² + 0.00122874*T²*RH\n * + 0.00085282*T*RH² - 0.00000199*T²*RH²\n * 3. A dry-air adjustment when `RH < 13 && 80 ≤ T ≤ 112`:\n * hi -= ((13 - RH) / 4) * sqrt((17 - |T - 95|) / 17)\n * 4. A humid-air adjustment when `RH > 85 && 80 ≤ T ≤ 87`:\n * hi += ((RH - 85) / 10) * ((87 - T) / 5)\n *\n * **Out-of-domain (NOT null — Python parity):** when `tempF < 80`, returns\n * `tempF` unchanged. Null is reserved for null / undefined / non-finite\n * inputs.\n *\n * Reference: https://www.wpc.ncep.noaa.gov/html/heatindex.shtml\n * Sanity: `heatIndex(90, 70) ≈ 106 °F` matches the NWS Rothfusz table.\n */\nexport function heatIndex(\n tempF: number | null | undefined,\n rhPct: number | null | undefined,\n): number | null {\n if (tempF === null || tempF === undefined || rhPct === null || rhPct === undefined) {\n return null;\n }\n if (typeof tempF !== \"number\" || !Number.isFinite(tempF)) return null;\n if (typeof rhPct !== \"number\" || !Number.isFinite(rhPct)) return null;\n if (tempF < 80.0) return tempF; // out-of-domain → tempF (Python parity)\n\n const t = tempF;\n const h = rhPct;\n const simple = 0.5 * (t + 61.0 + (t - 68.0) * 1.2 + h * 0.094);\n if ((simple + t) / 2.0 < 80.0) return simple;\n\n let hi =\n -42.379 +\n 2.04901523 * t +\n 10.14333127 * h -\n 0.22475541 * t * h -\n 0.00683783 * t * t -\n 0.05481717 * h * h +\n 0.00122874 * t * t * h +\n 0.00085282 * t * h * h -\n 0.00000199 * t * t * h * h;\n\n if (h < 13.0 && t >= 80.0 && t <= 112.0) {\n hi -= ((13.0 - h) / 4.0) * Math.sqrt((17.0 - Math.abs(t - 95.0)) / 17.0);\n } else if (h > 85.0 && t >= 80.0 && t <= 87.0) {\n hi += ((h - 85.0) / 10.0) * ((87.0 - t) / 5.0);\n }\n return hi;\n}\n","// TS-W4 Plan 04 Task 2 — clipOutliers (winsorize) + PHYSICS_BOUNDS.\n//\n// Pure row→row port of Python `mostlyright.preprocessing.clip_outliers` at\n// packages/core/src/mostlyright/preprocessing.py:49-91. The v0.1.0 canonical\n// surface (supersedes the older `transforms.clip_outliers`).\n//\n// Decision tree (mirrors Python preprocessing.py:75-91):\n// 1. opts.bounds set → clip to explicit [lo, hi]\n// 2. PHYSICS_BOUNDS.has(col) → clip to physics defaults\n// 3. else → sigma fallback (mu ± std*sigma)\n//\n// Phase 3.5 review-iter HIGH fixes (preserved here):\n// - Architect iter-1 HIGH: std<=0 in the sigma branch silently collapses\n// every row to the mean. Python raises ValueError; we throw RangeError.\n// - Sigma=0 pass-through: when all values are identical, sample sigma is\n// zero and the clamp [mu, mu] would collapse the column. Pass values\n// through unchanged instead (a TS-side improvement on top of Python).\n//\n// Numeric coercion is STRICT: only `typeof v === 'number' && Number.isFinite(v)`\n// passes through. Strings like '5' do NOT auto-parse. Matches Wave 2/3/04-task1.\n\n/**\n * Physics-based clipping defaults for canonical observation columns.\n *\n * Mirrors Python `mostlyright.preprocessing.PHYSICS_BOUNDS` (preprocessing.py:34-46).\n * Values are `[min, max]` tuples in canonical units (°C for temp, m/s and kt\n * for wind, hPa for pressure, percent for humidity, mm for precip).\n *\n * Both `dew_point_c`/`dewpoint_c` and `wind_dir_deg`/`wind_dir_degrees` are\n * aliased to support legacy + canonical column names.\n */\nexport const PHYSICS_BOUNDS: ReadonlyMap<string, readonly [number, number]> = new Map([\n [\"temp_c\", [-89.0, 57.0] as const],\n [\"dew_point_c\", [-89.0, 35.0] as const],\n [\"dewpoint_c\", [-89.0, 35.0] as const],\n [\"wind_speed_ms\", [0.0, 100.0] as const],\n [\"wind_speed_kt\", [0.0, 200.0] as const],\n [\"wind_dir_deg\", [0.0, 360.0] as const],\n [\"wind_dir_degrees\", [0.0, 360.0] as const],\n [\"slp_hpa\", [870.0, 1085.0] as const],\n [\"sea_level_pressure_mb\", [870.0, 1085.0] as const],\n [\"relative_humidity_pct_2m\", [0.0, 100.0] as const],\n [\"precip_mm_1h\", [0.0, 305.0] as const],\n]);\n\nexport interface ClipOutliersOptions {\n /** Explicit `[lo, hi]` range. Overrides PHYSICS_BOUNDS and sigma fallback. */\n bounds?: readonly [number, number];\n /** Sigma multiplier for the fallback branch. Default 3.0. Must be > 0. */\n std?: number;\n}\n\n/**\n * Winsorize a numeric column.\n *\n * Mirrors Python `mostlyright.preprocessing.clip_outliers`. Returns rows with\n * a derived `{col}_clipped` column; the source `col` is preserved unchanged.\n *\n * Decision tree:\n * - `opts.bounds` set → clip to explicit `[lo, hi]`\n * - `PHYSICS_BOUNDS.has(col)` → clip to physics defaults\n * - else → sigma fallback (`mu ± std*sigma`)\n *\n * **Phase 3.5 review-iter fixes:**\n * - Throws `RangeError` if `std ≤ 0` in the sigma fallback (matches Python\n * `ValueError` at preprocessing.py:84-88; silent dataset corruption\n * otherwise).\n * - Sigma=0 pass-through: when all values are identical, sample sigma is\n * zero and the clamp `[mu, mu]` would collapse the column. Pass values\n * through unchanged instead.\n *\n * @param rows input rows (NOT mutated; pure function)\n * @param col column to clip\n * @param opts optional bounds / std overrides; defaults: PHYSICS_BOUNDS or sigma=3\n * @returns new array of rows, each carrying `{col}_clipped`\n * @throws RangeError if sigma fallback would use `std <= 0` or non-finite std\n */\nexport function clipOutliers<Row extends Record<string, unknown>>(\n rows: ReadonlyArray<Row>,\n col: string,\n opts: ClipOutliersOptions = {},\n): ReadonlyArray<Row & Record<string, number | null>> {\n const std = opts.std ?? 3.0;\n const key = `${col}_clipped`;\n\n // Determine clip range. `passThrough` short-circuits to \"copy value unchanged\"\n // for the sigma=0 / n<2 edge cases (Phase 3.5 review-iter HIGH fix).\n let lo: number;\n let hi: number;\n let passThrough = false;\n\n if (opts.bounds !== undefined) {\n [lo, hi] = opts.bounds;\n } else if (PHYSICS_BOUNDS.has(col)) {\n const b = PHYSICS_BOUNDS.get(col);\n if (b === undefined) {\n // Unreachable (we just checked has()), but the narrowing requires it.\n throw new Error(`PHYSICS_BOUNDS.get(${col}) unexpectedly undefined`);\n }\n [lo, hi] = b;\n } else {\n // Sigma fallback. Architect iter-1 HIGH: std<=0 collapses to mu.\n if (!Number.isFinite(std) || std <= 0) {\n throw new RangeError(\n `clipOutliers: std must be > 0 for the sigma fallback (got ${std}); pass bounds=[lo, hi] or use a physics-default column`,\n );\n }\n // Compute mu + sigma over non-null finite values.\n const vals: number[] = [];\n for (const r of rows) {\n const v = r?.[col];\n if (typeof v === \"number\" && Number.isFinite(v)) vals.push(v);\n }\n if (vals.length < 2) {\n // Not enough values to compute sample sigma → pass-through.\n passThrough = true;\n lo = Number.NEGATIVE_INFINITY;\n hi = Number.POSITIVE_INFINITY;\n } else {\n const mu = vals.reduce((a, b) => a + b, 0) / vals.length;\n const sumSq = vals.reduce((a, b) => a + (b - mu) ** 2, 0);\n const sigma = Math.sqrt(sumSq / (vals.length - 1)); // sample stdev (Bessel n-1)\n if (sigma === 0 || !Number.isFinite(sigma)) {\n // Phase 3.5 review-iter HIGH: pass values through unchanged\n // instead of collapsing to [mu, mu] (NOT NaN, NOT mu).\n passThrough = true;\n lo = Number.NEGATIVE_INFINITY;\n hi = Number.POSITIVE_INFINITY;\n } else {\n lo = mu - std * sigma;\n hi = mu + std * sigma;\n }\n }\n }\n\n const out: Array<Row & Record<string, number | null>> = [];\n for (const r of rows) {\n const v = r?.[col];\n let clipped: number | null;\n if (typeof v === \"number\" && Number.isFinite(v)) {\n clipped = passThrough ? v : Math.min(Math.max(v, lo), hi);\n } else {\n clipped = null;\n }\n out.push({ ...(r as Row), [key]: clipped } as Row & Record<string, number | null>);\n }\n return out;\n}\n"],"mappings":"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","names":["sum"]}