@gzl10/ts-helpers 4.2.1
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.
- package/CHANGELOG.md +320 -0
- package/README.md +233 -0
- package/USAGE-GUIDE.md +800 -0
- package/dist/browser/async.js +15 -0
- package/dist/browser/async.js.map +1 -0
- package/dist/browser/chunk-4O7ZPIJN.js +383 -0
- package/dist/browser/chunk-4O7ZPIJN.js.map +1 -0
- package/dist/browser/chunk-75XNTC34.js +60 -0
- package/dist/browser/chunk-75XNTC34.js.map +1 -0
- package/dist/browser/chunk-C3D7YZVE.js +299 -0
- package/dist/browser/chunk-C3D7YZVE.js.map +1 -0
- package/dist/browser/chunk-CZL6C2EI.js +452 -0
- package/dist/browser/chunk-CZL6C2EI.js.map +1 -0
- package/dist/browser/chunk-D4FZFIVA.js +240 -0
- package/dist/browser/chunk-D4FZFIVA.js.map +1 -0
- package/dist/browser/chunk-IL7NG7IC.js +72 -0
- package/dist/browser/chunk-IL7NG7IC.js.map +1 -0
- package/dist/browser/chunk-NSBPE2FW.js +17 -0
- package/dist/browser/chunk-NSBPE2FW.js.map +1 -0
- package/dist/browser/chunk-SLQVNPTH.js +27 -0
- package/dist/browser/chunk-SLQVNPTH.js.map +1 -0
- package/dist/browser/chunk-WG7ILCUB.js +195 -0
- package/dist/browser/chunk-WG7ILCUB.js.map +1 -0
- package/dist/browser/chunk-WJA4JDMZ.js +278 -0
- package/dist/browser/chunk-WJA4JDMZ.js.map +1 -0
- package/dist/browser/chunk-ZFVYLUTT.js +65 -0
- package/dist/browser/chunk-ZFVYLUTT.js.map +1 -0
- package/dist/browser/chunk-ZYTSVMTI.js +263 -0
- package/dist/browser/chunk-ZYTSVMTI.js.map +1 -0
- package/dist/browser/dates.js +78 -0
- package/dist/browser/dates.js.map +1 -0
- package/dist/browser/environment-detection.js +21 -0
- package/dist/browser/environment-detection.js.map +1 -0
- package/dist/browser/environment.js +34 -0
- package/dist/browser/environment.js.map +1 -0
- package/dist/browser/errors.js +18 -0
- package/dist/browser/errors.js.map +1 -0
- package/dist/browser/index.js +412 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/math.js +51 -0
- package/dist/browser/math.js.map +1 -0
- package/dist/browser/number.js +10 -0
- package/dist/browser/number.js.map +1 -0
- package/dist/browser/objects.js +31 -0
- package/dist/browser/objects.js.map +1 -0
- package/dist/browser/strings.js +80 -0
- package/dist/browser/strings.js.map +1 -0
- package/dist/browser/validation-core.js +54 -0
- package/dist/browser/validation-core.js.map +1 -0
- package/dist/browser/validation-crypto.js +28 -0
- package/dist/browser/validation-crypto.js.map +1 -0
- package/dist/browser/validators.js +98 -0
- package/dist/browser/validators.js.map +1 -0
- package/dist/cjs/async.js +86 -0
- package/dist/cjs/async.js.map +1 -0
- package/dist/cjs/dates.js +285 -0
- package/dist/cjs/dates.js.map +1 -0
- package/dist/cjs/environment-detection.js +84 -0
- package/dist/cjs/environment-detection.js.map +1 -0
- package/dist/cjs/environment.js +261 -0
- package/dist/cjs/environment.js.map +1 -0
- package/dist/cjs/errors.js +80 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.js +2035 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/math.js +388 -0
- package/dist/cjs/math.js.map +1 -0
- package/dist/cjs/number.js +37 -0
- package/dist/cjs/number.js.map +1 -0
- package/dist/cjs/objects.js +249 -0
- package/dist/cjs/objects.js.map +1 -0
- package/dist/cjs/strings.js +253 -0
- package/dist/cjs/strings.js.map +1 -0
- package/dist/cjs/validation.js +450 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/esm/async.js +15 -0
- package/dist/esm/async.js.map +1 -0
- package/dist/esm/chunk-4O7ZPIJN.js +383 -0
- package/dist/esm/chunk-4O7ZPIJN.js.map +1 -0
- package/dist/esm/chunk-75XNTC34.js +60 -0
- package/dist/esm/chunk-75XNTC34.js.map +1 -0
- package/dist/esm/chunk-BDOBKBKA.js +72 -0
- package/dist/esm/chunk-BDOBKBKA.js.map +1 -0
- package/dist/esm/chunk-C3D7YZVE.js +299 -0
- package/dist/esm/chunk-C3D7YZVE.js.map +1 -0
- package/dist/esm/chunk-CZL6C2EI.js +452 -0
- package/dist/esm/chunk-CZL6C2EI.js.map +1 -0
- package/dist/esm/chunk-EBLSTOEC.js +263 -0
- package/dist/esm/chunk-EBLSTOEC.js.map +1 -0
- package/dist/esm/chunk-NSBPE2FW.js +17 -0
- package/dist/esm/chunk-NSBPE2FW.js.map +1 -0
- package/dist/esm/chunk-SLQVNPTH.js +27 -0
- package/dist/esm/chunk-SLQVNPTH.js.map +1 -0
- package/dist/esm/chunk-WG7ILCUB.js +195 -0
- package/dist/esm/chunk-WG7ILCUB.js.map +1 -0
- package/dist/esm/chunk-WJA4JDMZ.js +278 -0
- package/dist/esm/chunk-WJA4JDMZ.js.map +1 -0
- package/dist/esm/chunk-ZFVYLUTT.js +65 -0
- package/dist/esm/chunk-ZFVYLUTT.js.map +1 -0
- package/dist/esm/dates.js +78 -0
- package/dist/esm/dates.js.map +1 -0
- package/dist/esm/environment-detection.js +21 -0
- package/dist/esm/environment-detection.js.map +1 -0
- package/dist/esm/environment.js +34 -0
- package/dist/esm/environment.js.map +1 -0
- package/dist/esm/errors.js +18 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.js +380 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/math.js +51 -0
- package/dist/esm/math.js.map +1 -0
- package/dist/esm/number.js +10 -0
- package/dist/esm/number.js.map +1 -0
- package/dist/esm/objects.js +31 -0
- package/dist/esm/objects.js.map +1 -0
- package/dist/esm/strings.js +80 -0
- package/dist/esm/strings.js.map +1 -0
- package/dist/esm/validation.js +54 -0
- package/dist/esm/validation.js.map +1 -0
- package/dist/node/async.js +93 -0
- package/dist/node/async.js.map +1 -0
- package/dist/node/csv.js +102 -0
- package/dist/node/csv.js.map +1 -0
- package/dist/node/data.js +880 -0
- package/dist/node/data.js.map +1 -0
- package/dist/node/dates.js +324 -0
- package/dist/node/dates.js.map +1 -0
- package/dist/node/environment.js +278 -0
- package/dist/node/environment.js.map +1 -0
- package/dist/node/errors.js +89 -0
- package/dist/node/errors.js.map +1 -0
- package/dist/node/index.js +3151 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/json.js +107 -0
- package/dist/node/json.js.map +1 -0
- package/dist/node/math.js +413 -0
- package/dist/node/math.js.map +1 -0
- package/dist/node/number.js +42 -0
- package/dist/node/number.js.map +1 -0
- package/dist/node/objects.js +264 -0
- package/dist/node/objects.js.map +1 -0
- package/dist/node/strings.js +293 -0
- package/dist/node/strings.js.map +1 -0
- package/dist/node/tree.js +89 -0
- package/dist/node/tree.js.map +1 -0
- package/dist/node/validation-core.js +477 -0
- package/dist/node/validation-core.js.map +1 -0
- package/dist/node/validation-crypto.js +179 -0
- package/dist/node/validation-crypto.js.map +1 -0
- package/dist/node/validation.js +677 -0
- package/dist/node/validation.js.map +1 -0
- package/dist/node/validators.js +123 -0
- package/dist/node/validators.js.map +1 -0
- package/dist/node-esm/async.js +15 -0
- package/dist/node-esm/async.js.map +1 -0
- package/dist/node-esm/chunk-3YOF7NPT.js +299 -0
- package/dist/node-esm/chunk-3YOF7NPT.js.map +1 -0
- package/dist/node-esm/chunk-64TBXJQS.js +263 -0
- package/dist/node-esm/chunk-64TBXJQS.js.map +1 -0
- package/dist/node-esm/chunk-75XNTC34.js +60 -0
- package/dist/node-esm/chunk-75XNTC34.js.map +1 -0
- package/dist/node-esm/chunk-C4PKXIPB.js +278 -0
- package/dist/node-esm/chunk-C4PKXIPB.js.map +1 -0
- package/dist/node-esm/chunk-CMDFZME3.js +452 -0
- package/dist/node-esm/chunk-CMDFZME3.js.map +1 -0
- package/dist/node-esm/chunk-DZZPUYMP.js +74 -0
- package/dist/node-esm/chunk-DZZPUYMP.js.map +1 -0
- package/dist/node-esm/chunk-HTSEHRHI.js +195 -0
- package/dist/node-esm/chunk-HTSEHRHI.js.map +1 -0
- package/dist/node-esm/chunk-JCAUVOPH.js +27 -0
- package/dist/node-esm/chunk-JCAUVOPH.js.map +1 -0
- package/dist/node-esm/chunk-KBHE3K2F.js +505 -0
- package/dist/node-esm/chunk-KBHE3K2F.js.map +1 -0
- package/dist/node-esm/chunk-LYTET5NX.js +65 -0
- package/dist/node-esm/chunk-LYTET5NX.js.map +1 -0
- package/dist/node-esm/chunk-PZ5AY32C.js +10 -0
- package/dist/node-esm/chunk-PZ5AY32C.js.map +1 -0
- package/dist/node-esm/chunk-UKGXL2QO.js +383 -0
- package/dist/node-esm/chunk-UKGXL2QO.js.map +1 -0
- package/dist/node-esm/chunk-XAEYT23H.js +164 -0
- package/dist/node-esm/chunk-XAEYT23H.js.map +1 -0
- package/dist/node-esm/csv.js +63 -0
- package/dist/node-esm/csv.js.map +1 -0
- package/dist/node-esm/data.js +32 -0
- package/dist/node-esm/data.js.map +1 -0
- package/dist/node-esm/dates.js +78 -0
- package/dist/node-esm/dates.js.map +1 -0
- package/dist/node-esm/environment.js +34 -0
- package/dist/node-esm/environment.js.map +1 -0
- package/dist/node-esm/errors.js +18 -0
- package/dist/node-esm/errors.js.map +1 -0
- package/dist/node-esm/index.js +426 -0
- package/dist/node-esm/index.js.map +1 -0
- package/dist/node-esm/json.js +68 -0
- package/dist/node-esm/json.js.map +1 -0
- package/dist/node-esm/math.js +51 -0
- package/dist/node-esm/math.js.map +1 -0
- package/dist/node-esm/number.js +10 -0
- package/dist/node-esm/number.js.map +1 -0
- package/dist/node-esm/objects.js +31 -0
- package/dist/node-esm/objects.js.map +1 -0
- package/dist/node-esm/strings.js +80 -0
- package/dist/node-esm/strings.js.map +1 -0
- package/dist/node-esm/tree.js +8 -0
- package/dist/node-esm/tree.js.map +1 -0
- package/dist/node-esm/validation-core.js +54 -0
- package/dist/node-esm/validation-core.js.map +1 -0
- package/dist/node-esm/validation-crypto.js +26 -0
- package/dist/node-esm/validation-crypto.js.map +1 -0
- package/dist/node-esm/validation.js +606 -0
- package/dist/node-esm/validation.js.map +1 -0
- package/dist/node-esm/validators.js +98 -0
- package/dist/node-esm/validators.js.map +1 -0
- package/dist/types/async-C8gvbSG-.d.ts +453 -0
- package/dist/types/async.d.ts +1 -0
- package/dist/types/csv.d.ts +226 -0
- package/dist/types/data.d.ts +1561 -0
- package/dist/types/dates-hTiE0Z11.d.ts +298 -0
- package/dist/types/dates.d.ts +1 -0
- package/dist/types/environment-B8eLS7KT.d.ts +420 -0
- package/dist/types/environment-detection.d.ts +102 -0
- package/dist/types/environment.d.ts +1 -0
- package/dist/types/errors.d.ts +147 -0
- package/dist/types/index.d.ts +211 -0
- package/dist/types/json.d.ts +284 -0
- package/dist/types/math-BQ9Lwdp7.d.ts +2060 -0
- package/dist/types/math.d.ts +1 -0
- package/dist/types/number-CYnQfLWj.d.ts +44 -0
- package/dist/types/number.d.ts +1 -0
- package/dist/types/objects-BohS8GCS.d.ts +1185 -0
- package/dist/types/objects.d.ts +1 -0
- package/dist/types/strings-CiqRPYLL.d.ts +1349 -0
- package/dist/types/strings.d.ts +1 -0
- package/dist/types/tree.d.ts +284 -0
- package/dist/types/validation-core-DfHF8rCG.d.ts +238 -0
- package/dist/types/validation-crypto-browser.d.ts +56 -0
- package/dist/types/validation-crypto-node.d.ts +31 -0
- package/dist/types/validation.d.ts +1 -0
- package/dist/types/validators.d.ts +216 -0
- package/package.json +253 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleOperation,
|
|
3
|
+
runBatch,
|
|
4
|
+
sleep,
|
|
5
|
+
wait
|
|
6
|
+
} from "./chunk-ZFVYLUTT.js";
|
|
7
|
+
import "./chunk-75XNTC34.js";
|
|
8
|
+
import "./chunk-NSBPE2FW.js";
|
|
9
|
+
export {
|
|
10
|
+
handleOperation,
|
|
11
|
+
runBatch,
|
|
12
|
+
sleep,
|
|
13
|
+
wait
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import {
|
|
2
|
+
roundToDecimals
|
|
3
|
+
} from "./chunk-SLQVNPTH.js";
|
|
4
|
+
import {
|
|
5
|
+
__export
|
|
6
|
+
} from "./chunk-NSBPE2FW.js";
|
|
7
|
+
|
|
8
|
+
// src/math.ts
|
|
9
|
+
var math_exports = {};
|
|
10
|
+
__export(math_exports, {
|
|
11
|
+
calculateAggregations: () => calculateAggregations,
|
|
12
|
+
calculateAnnuityPayment: () => calculateAnnuityPayment,
|
|
13
|
+
calculateCorrelation: () => calculateCorrelation,
|
|
14
|
+
calculateEuclideanDistance: () => calculateEuclideanDistance,
|
|
15
|
+
calculateFutureValue: () => calculateFutureValue,
|
|
16
|
+
calculateHistogram: () => calculateHistogram,
|
|
17
|
+
calculateIQR: () => calculateIQR,
|
|
18
|
+
calculateIRR: () => calculateIRR,
|
|
19
|
+
calculateManhattanDistance: () => calculateManhattanDistance,
|
|
20
|
+
calculateMedian: () => calculateMedian,
|
|
21
|
+
calculateMode: () => calculateMode,
|
|
22
|
+
calculateNPV: () => calculateNPV,
|
|
23
|
+
calculatePercentile: () => calculatePercentile,
|
|
24
|
+
calculatePresentValue: () => calculatePresentValue,
|
|
25
|
+
calculateQuartiles: () => calculateQuartiles,
|
|
26
|
+
calculateStandardDeviation: () => calculateStandardDeviation,
|
|
27
|
+
calculateTrendSlope: () => calculateTrendSlope,
|
|
28
|
+
calculateVariance: () => calculateVariance,
|
|
29
|
+
detectOutliers: () => detectOutliers,
|
|
30
|
+
normalizeToRange: () => normalizeToRange,
|
|
31
|
+
scaleToRange: () => scaleToRange,
|
|
32
|
+
simpleKMeans: () => simpleKMeans
|
|
33
|
+
});
|
|
34
|
+
import * as lodash from "lodash";
|
|
35
|
+
var { map, round, sumBy, get, meanBy, maxBy, some, minBy, uniq, size, sum, zipObject, groupBy } = lodash;
|
|
36
|
+
var calculateAggregations = (operations, pgroupBy, inData, appendOperationToField = true) => {
|
|
37
|
+
let data = [];
|
|
38
|
+
if (pgroupBy?.length) {
|
|
39
|
+
const groupedData = groupBy(
|
|
40
|
+
inData,
|
|
41
|
+
(item) => pgroupBy.map((field) => get(item, field)).join("$|marca|@")
|
|
42
|
+
);
|
|
43
|
+
data = map(groupedData, (items, groupKey) => {
|
|
44
|
+
const obj = zipObject(pgroupBy, groupKey.split("$|marca|@"));
|
|
45
|
+
for (const operation in operations) {
|
|
46
|
+
const fields = operations[operation];
|
|
47
|
+
fields.forEach((field) => {
|
|
48
|
+
const resultField = appendOperationToField ? field + operation : field;
|
|
49
|
+
switch (operation) {
|
|
50
|
+
case "$count":
|
|
51
|
+
obj[resultField] = items.length;
|
|
52
|
+
break;
|
|
53
|
+
case "$sum":
|
|
54
|
+
obj[resultField] = sumBy(items, (item) => get(item, field));
|
|
55
|
+
break;
|
|
56
|
+
case "$avg":
|
|
57
|
+
obj[resultField] = round(
|
|
58
|
+
meanBy(items, (item) => get(item, field)),
|
|
59
|
+
1
|
|
60
|
+
);
|
|
61
|
+
break;
|
|
62
|
+
case "$max": {
|
|
63
|
+
const maxItem = maxBy(items, (item) => get(item, field));
|
|
64
|
+
obj[resultField] = maxItem ? get(maxItem, field) : void 0;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "$min": {
|
|
68
|
+
const minItem = minBy(items, (item) => get(item, field));
|
|
69
|
+
obj[resultField] = minItem ? get(minItem, field) : void 0;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "$exist":
|
|
73
|
+
obj[resultField] = some(items, (item) => get(item, field)) ? 1 : 0;
|
|
74
|
+
break;
|
|
75
|
+
case "$trend":
|
|
76
|
+
obj[resultField] = calculateTrendSlope(
|
|
77
|
+
items.map((item) => get(item, field)),
|
|
78
|
+
items.map((_item, index) => index + 1)
|
|
79
|
+
);
|
|
80
|
+
break;
|
|
81
|
+
case "$countUniq":
|
|
82
|
+
obj[resultField] = uniq(items.map((item) => get(item, field))).length;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return obj;
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
const results = {};
|
|
91
|
+
for (const operation in operations) {
|
|
92
|
+
const fields = operations[operation];
|
|
93
|
+
fields.forEach((field) => {
|
|
94
|
+
const resultField = appendOperationToField ? field + operation : field;
|
|
95
|
+
switch (operation) {
|
|
96
|
+
case "$count":
|
|
97
|
+
results[resultField] = inData.length;
|
|
98
|
+
break;
|
|
99
|
+
case "$sum":
|
|
100
|
+
results[resultField] = sumBy(inData, field);
|
|
101
|
+
break;
|
|
102
|
+
case "$avg":
|
|
103
|
+
results[resultField] = round(meanBy(inData, field), 1);
|
|
104
|
+
break;
|
|
105
|
+
case "$max":
|
|
106
|
+
if (Array.isArray(inData)) {
|
|
107
|
+
const maxItem = maxBy(inData, field);
|
|
108
|
+
results[resultField] = maxItem ? maxItem[field] : void 0;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "$min":
|
|
112
|
+
if (Array.isArray(inData)) {
|
|
113
|
+
const minItem = minBy(inData, field);
|
|
114
|
+
results[resultField] = minItem ? minItem[field] : void 0;
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
case "$exist":
|
|
118
|
+
results[resultField] = some(inData, { [field]: true }) ? 1 : 0;
|
|
119
|
+
break;
|
|
120
|
+
case "$trend":
|
|
121
|
+
results[resultField] = calculateTrendSlope(
|
|
122
|
+
inData.map((item) => get(item, field)),
|
|
123
|
+
inData.map((_item, index) => index + 1)
|
|
124
|
+
);
|
|
125
|
+
break;
|
|
126
|
+
case "$countUniq":
|
|
127
|
+
results[resultField] = uniq(inData.map((item) => get(item, field))).length;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
data = results;
|
|
133
|
+
}
|
|
134
|
+
return data;
|
|
135
|
+
};
|
|
136
|
+
function calculateTrendSlope(y, x) {
|
|
137
|
+
const n = size(y);
|
|
138
|
+
const sum_x = sum(x);
|
|
139
|
+
const sum_y = sum(y);
|
|
140
|
+
const sum_x2 = sum(map(x, (val) => val * val));
|
|
141
|
+
const sum_xy = sum(map(x, (val, i) => val * y[i]));
|
|
142
|
+
const denominator = n * sum_x2 - sum_x * sum_x;
|
|
143
|
+
if (denominator === 0) {
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
const slope = (n * sum_xy - sum_x * sum_y) / denominator;
|
|
147
|
+
return roundToDecimals(slope, 1);
|
|
148
|
+
}
|
|
149
|
+
var calculateMedian = (values) => {
|
|
150
|
+
if (!values.length) return 0;
|
|
151
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
152
|
+
const middle = Math.floor(sorted.length / 2);
|
|
153
|
+
if (sorted.length % 2 === 0) {
|
|
154
|
+
return (sorted[middle - 1] + sorted[middle]) / 2;
|
|
155
|
+
}
|
|
156
|
+
return sorted[middle];
|
|
157
|
+
};
|
|
158
|
+
var calculateMode = (values) => {
|
|
159
|
+
if (!values.length) return null;
|
|
160
|
+
const frequency = {};
|
|
161
|
+
let maxFreq = 0;
|
|
162
|
+
let mode = null;
|
|
163
|
+
let hasMultipleModes = false;
|
|
164
|
+
for (const value of values) {
|
|
165
|
+
frequency[value] = (frequency[value] || 0) + 1;
|
|
166
|
+
if (frequency[value] > maxFreq) {
|
|
167
|
+
maxFreq = frequency[value];
|
|
168
|
+
mode = value;
|
|
169
|
+
hasMultipleModes = false;
|
|
170
|
+
} else if (frequency[value] === maxFreq && value !== mode) {
|
|
171
|
+
hasMultipleModes = true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return hasMultipleModes ? null : mode;
|
|
175
|
+
};
|
|
176
|
+
var calculateStandardDeviation = (values, sample = false) => {
|
|
177
|
+
if (!values.length) return 0;
|
|
178
|
+
const mean = values.reduce((sum2, val) => sum2 + val, 0) / values.length;
|
|
179
|
+
const squaredDiffs = values.map((val) => Math.pow(val - mean, 2));
|
|
180
|
+
const variance = squaredDiffs.reduce((sum2, val) => sum2 + val, 0) / (sample ? values.length - 1 : values.length);
|
|
181
|
+
return Math.sqrt(variance);
|
|
182
|
+
};
|
|
183
|
+
var calculateVariance = (values, sample = false) => {
|
|
184
|
+
if (!values.length) return 0;
|
|
185
|
+
const mean = values.reduce((sum2, val) => sum2 + val, 0) / values.length;
|
|
186
|
+
const squaredDiffs = values.map((val) => Math.pow(val - mean, 2));
|
|
187
|
+
return squaredDiffs.reduce((sum2, val) => sum2 + val, 0) / (sample ? values.length - 1 : values.length);
|
|
188
|
+
};
|
|
189
|
+
var calculatePercentile = (values, percentile) => {
|
|
190
|
+
if (!values.length) return 0;
|
|
191
|
+
if (percentile < 0 || percentile > 100) throw new Error("Percentile must be between 0 and 100");
|
|
192
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
193
|
+
const index = percentile / 100 * (sorted.length - 1);
|
|
194
|
+
if (Math.floor(index) === index) {
|
|
195
|
+
return sorted[index];
|
|
196
|
+
}
|
|
197
|
+
const lower = sorted[Math.floor(index)];
|
|
198
|
+
const upper = sorted[Math.ceil(index)];
|
|
199
|
+
const weight = index - Math.floor(index);
|
|
200
|
+
return lower * (1 - weight) + upper * weight;
|
|
201
|
+
};
|
|
202
|
+
var calculateQuartiles = (values) => {
|
|
203
|
+
return {
|
|
204
|
+
Q1: calculatePercentile(values, 25),
|
|
205
|
+
Q2: calculatePercentile(values, 50),
|
|
206
|
+
// median
|
|
207
|
+
Q3: calculatePercentile(values, 75)
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
var calculateIQR = (values) => {
|
|
211
|
+
const quartiles = calculateQuartiles(values);
|
|
212
|
+
return quartiles.Q3 - quartiles.Q1;
|
|
213
|
+
};
|
|
214
|
+
var detectOutliers = (values, multiplier = 1.5) => {
|
|
215
|
+
const quartiles = calculateQuartiles(values);
|
|
216
|
+
const iqr = quartiles.Q3 - quartiles.Q1;
|
|
217
|
+
const lowerBound = quartiles.Q1 - multiplier * iqr;
|
|
218
|
+
const upperBound = quartiles.Q3 + multiplier * iqr;
|
|
219
|
+
return values.filter((value) => value < lowerBound || value > upperBound);
|
|
220
|
+
};
|
|
221
|
+
var calculateCorrelation = (x, y) => {
|
|
222
|
+
if (x.length !== y.length || x.length === 0) return 0;
|
|
223
|
+
const n = x.length;
|
|
224
|
+
const sumX = sum(x);
|
|
225
|
+
const sumY = sum(y);
|
|
226
|
+
const sumXY = sum(map(x, (val, i) => val * y[i]));
|
|
227
|
+
const sumX2 = sum(map(x, (val) => val * val));
|
|
228
|
+
const sumY2 = sum(map(y, (val) => val * val));
|
|
229
|
+
const numerator = n * sumXY - sumX * sumY;
|
|
230
|
+
const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
|
|
231
|
+
if (denominator === 0) return 0;
|
|
232
|
+
return numerator / denominator;
|
|
233
|
+
};
|
|
234
|
+
var calculateNPV = (cashFlows, discountRate) => {
|
|
235
|
+
return cashFlows.reduce((npv, cashFlow, index) => {
|
|
236
|
+
const discountFactor = index === 0 ? 1 : Math.pow(1 + discountRate, index);
|
|
237
|
+
return npv + cashFlow / discountFactor;
|
|
238
|
+
}, 0);
|
|
239
|
+
};
|
|
240
|
+
var calculateIRR = (cashFlows, initialGuess = 0.1, maxIterations = 100, tolerance = 1e-6) => {
|
|
241
|
+
let rate = initialGuess;
|
|
242
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
243
|
+
const npv = calculateNPV(cashFlows, rate);
|
|
244
|
+
const npvDerivative = cashFlows.reduce((sum2, cashFlow, index) => {
|
|
245
|
+
return sum2 - index * cashFlow / Math.pow(1 + rate, index + 1);
|
|
246
|
+
}, 0);
|
|
247
|
+
if (Math.abs(npv) < tolerance) return rate;
|
|
248
|
+
if (Math.abs(npvDerivative) < tolerance) break;
|
|
249
|
+
rate = rate - npv / npvDerivative;
|
|
250
|
+
}
|
|
251
|
+
return rate;
|
|
252
|
+
};
|
|
253
|
+
var calculateFutureValue = (presentValue, interestRate, periods) => {
|
|
254
|
+
return presentValue * Math.pow(1 + interestRate, periods);
|
|
255
|
+
};
|
|
256
|
+
var calculatePresentValue = (futureValue, interestRate, periods) => {
|
|
257
|
+
return futureValue / Math.pow(1 + interestRate, periods);
|
|
258
|
+
};
|
|
259
|
+
var calculateAnnuityPayment = (presentValue, interestRate, periods) => {
|
|
260
|
+
if (interestRate === 0) return presentValue / periods;
|
|
261
|
+
return presentValue * (interestRate * Math.pow(1 + interestRate, periods)) / (Math.pow(1 + interestRate, periods) - 1);
|
|
262
|
+
};
|
|
263
|
+
var normalizeToRange = (values) => {
|
|
264
|
+
if (!values.length) return [];
|
|
265
|
+
const min = Math.min(...values);
|
|
266
|
+
const max = Math.max(...values);
|
|
267
|
+
const range = max - min;
|
|
268
|
+
if (range === 0) return values.map(() => 0);
|
|
269
|
+
return values.map((value) => (value - min) / range);
|
|
270
|
+
};
|
|
271
|
+
var scaleToRange = (values, minRange, maxRange) => {
|
|
272
|
+
const normalized = normalizeToRange(values);
|
|
273
|
+
const scale = maxRange - minRange;
|
|
274
|
+
return normalized.map((value) => minRange + value * scale);
|
|
275
|
+
};
|
|
276
|
+
var calculateHistogram = (values, bins) => {
|
|
277
|
+
if (!values.length || bins <= 0) return [];
|
|
278
|
+
const min = Math.min(...values);
|
|
279
|
+
const max = Math.max(...values);
|
|
280
|
+
const binWidth = (max - min) / bins;
|
|
281
|
+
const histogram = [];
|
|
282
|
+
for (let i = 0; i < bins; i++) {
|
|
283
|
+
const rangeStart = min + i * binWidth;
|
|
284
|
+
const rangeEnd = i === bins - 1 ? max : rangeStart + binWidth;
|
|
285
|
+
const count = values.filter(
|
|
286
|
+
(value) => value >= rangeStart && (i === bins - 1 ? value <= rangeEnd : value < rangeEnd)
|
|
287
|
+
).length;
|
|
288
|
+
histogram.push({
|
|
289
|
+
range: [roundToDecimals(rangeStart, 2), roundToDecimals(rangeEnd, 2)],
|
|
290
|
+
count
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
return histogram;
|
|
294
|
+
};
|
|
295
|
+
var calculateEuclideanDistance = (point1, point2) => {
|
|
296
|
+
if (point1.length !== point2.length) {
|
|
297
|
+
throw new Error("Points must have the same number of dimensions");
|
|
298
|
+
}
|
|
299
|
+
const squaredDiffs = point1.map((val, index) => Math.pow(val - point2[index], 2));
|
|
300
|
+
return Math.sqrt(sum(squaredDiffs));
|
|
301
|
+
};
|
|
302
|
+
var calculateManhattanDistance = (point1, point2) => {
|
|
303
|
+
if (point1.length !== point2.length) {
|
|
304
|
+
throw new Error("Points must have the same number of dimensions");
|
|
305
|
+
}
|
|
306
|
+
return sum(point1.map((val, index) => Math.abs(val - point2[index])));
|
|
307
|
+
};
|
|
308
|
+
var simpleKMeans = (points, k, maxIterations = 100) => {
|
|
309
|
+
if (points.length === 0 || k <= 0) {
|
|
310
|
+
return { centroids: [], clusters: [] };
|
|
311
|
+
}
|
|
312
|
+
const dimensions = points[0].length;
|
|
313
|
+
let centroids = [];
|
|
314
|
+
for (let i = 0; i < k; i++) {
|
|
315
|
+
const randomPoint = points[Math.floor(Math.random() * points.length)];
|
|
316
|
+
centroids.push([...randomPoint]);
|
|
317
|
+
}
|
|
318
|
+
const clusters = new Array(points.length);
|
|
319
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
320
|
+
let hasChanged = false;
|
|
321
|
+
for (let i = 0; i < points.length; i++) {
|
|
322
|
+
let minDistance = Infinity;
|
|
323
|
+
let closestCentroid = 0;
|
|
324
|
+
for (let j = 0; j < centroids.length; j++) {
|
|
325
|
+
const distance = calculateEuclideanDistance(points[i], centroids[j]);
|
|
326
|
+
if (distance < minDistance) {
|
|
327
|
+
minDistance = distance;
|
|
328
|
+
closestCentroid = j;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (clusters[i] !== closestCentroid) {
|
|
332
|
+
hasChanged = true;
|
|
333
|
+
clusters[i] = closestCentroid;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const newCentroids = new Array(k).fill(0).map(() => new Array(dimensions).fill(0));
|
|
337
|
+
const clusterCounts = new Array(k).fill(0);
|
|
338
|
+
for (let i = 0; i < points.length; i++) {
|
|
339
|
+
const cluster = clusters[i];
|
|
340
|
+
clusterCounts[cluster]++;
|
|
341
|
+
for (let dim = 0; dim < dimensions; dim++) {
|
|
342
|
+
newCentroids[cluster][dim] += points[i][dim];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
for (let i = 0; i < k; i++) {
|
|
346
|
+
if (clusterCounts[i] > 0) {
|
|
347
|
+
for (let dim = 0; dim < dimensions; dim++) {
|
|
348
|
+
newCentroids[i][dim] /= clusterCounts[i];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
centroids = newCentroids;
|
|
353
|
+
if (!hasChanged) break;
|
|
354
|
+
}
|
|
355
|
+
return { centroids, clusters };
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export {
|
|
359
|
+
calculateAggregations,
|
|
360
|
+
calculateTrendSlope,
|
|
361
|
+
calculateMedian,
|
|
362
|
+
calculateMode,
|
|
363
|
+
calculateStandardDeviation,
|
|
364
|
+
calculateVariance,
|
|
365
|
+
calculatePercentile,
|
|
366
|
+
calculateQuartiles,
|
|
367
|
+
calculateIQR,
|
|
368
|
+
detectOutliers,
|
|
369
|
+
calculateCorrelation,
|
|
370
|
+
calculateNPV,
|
|
371
|
+
calculateIRR,
|
|
372
|
+
calculateFutureValue,
|
|
373
|
+
calculatePresentValue,
|
|
374
|
+
calculateAnnuityPayment,
|
|
375
|
+
normalizeToRange,
|
|
376
|
+
scaleToRange,
|
|
377
|
+
calculateHistogram,
|
|
378
|
+
calculateEuclideanDistance,
|
|
379
|
+
calculateManhattanDistance,
|
|
380
|
+
simpleKMeans,
|
|
381
|
+
math_exports
|
|
382
|
+
};
|
|
383
|
+
//# sourceMappingURL=chunk-4O7ZPIJN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/math.ts"],"sourcesContent":["/**\n * Mathematical calculations, statistics, and financial utilities\n * Consolidated from specialized/math module\n */\n\nimport * as lodash from 'lodash'\nconst { map, round, sumBy, get, meanBy, maxBy, some, minBy, uniq, size, sum, zipObject, groupBy } =\n lodash\nimport { roundToDecimals } from './number'\n\n/**\n * Mathematical operations configuration for data aggregation\n * @template T - The type of objects being processed\n * @example\n * ```ts\n * // Simple operations configuration\n * const ops: IMathOp = {\n * $sum: ['amount', 'quantity'],\n * $avg: ['price', 'rating'],\n * $count: ['name'],\n * $max: ['sales'],\n * $min: ['cost']\n * }\n *\n * // For sales data\n * const salesOps: IMathOp<SalesRecord> = {\n * $sum: ['revenue', 'units'],\n * $avg: ['unitPrice'],\n * $countUniq: ['customerId']\n * }\n * ```\n */\nexport interface IMathOp<T = any> {\n /** Calculate average of numeric fields */\n $avg?: (keyof T extends infer K\n ? K extends keyof T\n ? T[K] extends number\n ? K\n : never\n : never\n : never)[]\n /** Count occurrences of string fields */\n $count?: (keyof T extends infer K\n ? K extends keyof T\n ? T[K] extends string\n ? K\n : never\n : never\n : never)[]\n /** Count unique values in fields */\n $countUniq?: (keyof T)[]\n /** Check if field exists (1/0) */\n $exist?: (keyof T)[]\n /** Find maximum value in numeric fields */\n $max?: (keyof T extends infer K\n ? K extends keyof T\n ? T[K] extends number\n ? K\n : never\n : never\n : never)[]\n /** Find minimum value in numeric fields */\n $min?: (keyof T extends infer K\n ? K extends keyof T\n ? T[K] extends number\n ? K\n : never\n : never\n : never)[]\n /** Sum numeric fields */\n $sum?: (keyof T extends infer K\n ? K extends keyof T\n ? T[K] extends number\n ? K\n : never\n : never\n : never)[]\n /** Calculate trend slope for numeric fields */\n $trend?: (keyof T extends infer K\n ? K extends keyof T\n ? T[K] extends number\n ? K\n : never\n : never\n : never)[]\n}\n\n/**\n * Calculates mathematical operations on an array of objects with optional grouping\n * Supports statistical operations like sum, average, count, etc. with grouping capability\n * @param operations - Mathematical operations to perform on data fields\n * @param pgroupBy - Array of field names to group by (empty array for no grouping)\n * @param inData - Input data array to process\n * @param appendOperationToField - Whether to append operation name to result field names\n * @returns Aggregated data with calculated values\n * @example\n * ```ts\n * // Sales data aggregation\n * const sales = [\n * { region: 'North', product: 'Laptop', amount: 1000, quantity: 2 },\n * { region: 'North', product: 'Mouse', amount: 50, quantity: 5 },\n * { region: 'South', product: 'Laptop', amount: 1200, quantity: 3 }\n * ]\n *\n * // Group by region and calculate totals\n * const regionTotals = calculateAggregations(\n * { $sum: ['amount', 'quantity'], $avg: ['amount'] },\n * ['region'],\n * sales\n * )\n * // Result: [\n * // { region: 'North', amount$sum: 1050, quantity$sum: 7, amount$avg: 525 },\n * // { region: 'South', amount$sum: 1200, quantity$sum: 3, amount$avg: 1200 }\n * // ]\n *\n * // Overall totals (no grouping)\n * const totals = calculateAggregations(\n * { $sum: ['amount'], $count: ['product'], $countUniq: ['region'] },\n * [],\n * sales,\n * false\n * )\n * // Result: { amount: 2250, product: 3, region: 2 }\n * ```\n */\nexport const calculateAggregations = (\n operations: IMathOp,\n pgroupBy: string[],\n inData: any[],\n appendOperationToField = true\n) => {\n let data: any = []\n if (pgroupBy?.length) {\n const groupedData = groupBy(inData, item =>\n pgroupBy.map(field => get(item, field)).join('$|marca|@')\n )\n data = map(groupedData, (items, groupKey) => {\n const obj: any = zipObject(pgroupBy, groupKey.split('$|marca|@'))\n for (const operation in operations) {\n const fields = operations[operation]\n fields.forEach(field => {\n const resultField = appendOperationToField ? field + operation : field\n switch (operation) {\n case '$count':\n obj[resultField] = items.length\n break\n case '$sum':\n obj[resultField] = sumBy(items, item => get(item, field))\n break\n case '$avg':\n obj[resultField] = round(\n meanBy(items, item => get(item, field)),\n 1\n )\n break\n case '$max': {\n const maxItem = maxBy(items, item => get(item, field))\n obj[resultField] = maxItem ? get(maxItem, field) : undefined\n break\n }\n case '$min': {\n const minItem = minBy(items, item => get(item, field))\n obj[resultField] = minItem ? get(minItem, field) : undefined\n break\n }\n case '$exist':\n obj[resultField] = some(items, item => get(item, field)) ? 1 : 0\n break\n case '$trend':\n obj[resultField] = calculateTrendSlope(\n items.map(item => get(item, field)),\n items.map((_item, index) => index + 1)\n )\n break\n case '$countUniq':\n obj[resultField] = uniq(items.map(item => get(item, field))).length\n break\n }\n })\n }\n return obj\n })\n } else {\n const results: { [key: string]: number } = {}\n for (const operation in operations) {\n const fields = operations[operation]\n fields.forEach(field => {\n const resultField = appendOperationToField ? field + operation : field\n switch (operation) {\n case '$count':\n results[resultField] = inData.length\n break\n case '$sum':\n results[resultField] = sumBy(inData, field)\n break\n case '$avg':\n results[resultField] = round(meanBy(inData, field), 1)\n break\n case '$max':\n if (Array.isArray(inData)) {\n const maxItem = maxBy(inData, field)\n results[resultField] = maxItem ? maxItem[field] : undefined\n }\n break\n case '$min':\n if (Array.isArray(inData)) {\n const minItem = minBy(inData, field)\n results[resultField] = minItem ? minItem[field] : undefined\n }\n break\n case '$exist':\n results[resultField] = some(inData, { [field]: true }) ? 1 : 0\n break\n case '$trend':\n results[resultField] = calculateTrendSlope(\n inData.map(item => get(item, field)),\n inData.map((_item, index) => index + 1)\n )\n break\n case '$countUniq':\n results[resultField] = uniq(inData.map(item => get(item, field))).length\n break\n }\n })\n }\n data = results\n }\n return data\n}\n\n/**\n * Calculates the slope of a linear trend line using least squares regression\n *\n * Computes the \"rise over run\" coefficient that best fits the data points.\n * Uses the least squares method to find the line y = mx + b, returning only m (slope).\n *\n * Slope interpretation:\n * - **Positive slope**: Y increases as X increases (upward trend)\n * - **Negative slope**: Y decreases as X increases (downward trend)\n * - **Zero slope**: No trend, flat line\n * - **Magnitude**: How steep the trend is (larger = steeper)\n *\n * Algorithm (Least Squares):\n * 1. Calculate Σx, Σy, Σx², Σxy\n * 2. Compute slope: m = (n·Σxy - Σx·Σy) / (n·Σx² - (Σx)²)\n * 3. Handle division by zero (constant x values)\n * 4. Round to 1 decimal place\n *\n * @param y - Dependent variable values (response variable)\n * @param x - Independent variable values (predictor variable)\n * @returns Slope value rounded to 1 decimal (0 if denominator is zero)\n *\n * @example\n * ```typescript\n * // Basic trend slope calculation\n * const sales = [100, 120, 140, 160, 180]\n * const months = [1, 2, 3, 4, 5]\n * calculateTrendSlope(sales, months) // 20.0 (sales increase 20 units/month)\n *\n * const declining = [100, 90, 80, 70, 60]\n * calculateTrendSlope(declining, months) // -10.0 (decreasing 10 units/month)\n *\n * const flat = [100, 100, 100, 100, 100]\n * calculateTrendSlope(flat, months) // 0.0 (no trend)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Sales forecasting and trend analysis\n * interface MonthlySales {\n * month: number\n * revenue: number\n * }\n *\n * function analyzeSalesTrend(data: MonthlySales[]): void {\n * const months = data.map(d => d.month)\n * const revenue = data.map(d => d.revenue)\n * const slope = calculateTrendSlope(revenue, months)\n *\n * console.log(`📈 Sales Trend Analysis:`)\n * console.log(` Slope: ${slope} per month`)\n *\n * if (slope > 0) {\n * const annualGrowth = slope * 12\n * console.log(`✅ Growing trend: $${annualGrowth.toLocaleString()}/year projected`)\n * } else if (slope < 0) {\n * console.log(`⚠️ Declining trend: ${slope} per month`)\n * console.log(`Action: Investigate causes and intervene`)\n * } else {\n * console.log(`➡️ Flat trend: No significant growth or decline`)\n * }\n * }\n *\n * const salesData = [\n * { month: 1, revenue: 50000 },\n * { month: 2, revenue: 52000 },\n * { month: 3, revenue: 54000 },\n * { month: 4, revenue: 56000 }\n * ]\n * analyzeSalesTrend(salesData)\n * // Slope: 2000 per month\n * // ✅ Growing: $24,000/year projected\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: User engagement trending\n * function trackUserEngagement(dailyActiveUsers: number[]): string {\n * const days = Array.from({ length: dailyActiveUsers.length }, (_, i) => i + 1)\n * const slope = calculateTrendSlope(dailyActiveUsers, days)\n *\n * const avgUsers = dailyActiveUsers.reduce((a, b) => a + b) / dailyActiveUsers.length\n * const changePercent = ((slope * 30) / avgUsers) * 100\n *\n * if (changePercent > 5) {\n * return `🚀 Strong growth: ${changePercent.toFixed(1)}% monthly increase`\n * } else if (changePercent < -5) {\n * return `📉 Declining: ${changePercent.toFixed(1)}% monthly decrease (ALERT!)`\n * } else {\n * return `➡️ Stable: ${changePercent.toFixed(1)}% monthly change`\n * }\n * }\n *\n * const dau = [1000, 1020, 1050, 1080, 1100, 1120, 1150]\n * trackUserEngagement(dau)\n * // \"🚀 Strong growth: 12.8% monthly increase\"\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Server response time degradation detection\n * function monitorPerformanceDegradation(\n * responseTimes: number[],\n * timestamps: number[]\n * ): void {\n * const slope = calculateTrendSlope(responseTimes, timestamps)\n *\n * console.log(`⏱️ Performance Trend: ${slope}ms per time unit`)\n *\n * if (slope > 5) {\n * console.log('🚨 ALERT: Response times degrading')\n * console.log(`Degradation rate: ${slope}ms per unit`)\n * console.log('Action: Investigate server load, memory leaks, database queries')\n * } else if (slope < -5) {\n * console.log('✅ Performance improving')\n * } else {\n * console.log('➡️ Performance stable')\n * }\n * }\n *\n * const times = [120, 125, 135, 150, 170, 200]\n * const hours = [1, 2, 3, 4, 5, 6]\n * monitorPerformanceDegradation(times, hours)\n * // 🚨 ALERT: Response times degrading (16ms/hour)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Stock price momentum indicator\n * function calculatePriceMomentum(prices: number[], days: number[]): string {\n * const slope = calculateTrendSlope(prices, days)\n * const avgPrice = prices.reduce((a, b) => a + b) / prices.length\n * const momentum = (slope / avgPrice) * 100\n *\n * if (momentum > 1) {\n * return `🟢 BULLISH (${momentum.toFixed(2)}% daily momentum)`\n * } else if (momentum < -1) {\n * return `🔴 BEARISH (${momentum.toFixed(2)}% daily momentum)`\n * } else {\n * return `🟡 NEUTRAL (${momentum.toFixed(2)}% daily momentum)`\n * }\n * }\n *\n * const stockPrices = [100, 102, 105, 108, 112, 115]\n * const tradingDays = [1, 2, 3, 4, 5, 6]\n * calculatePriceMomentum(stockPrices, tradingDays)\n * // \"🟢 BULLISH (2.86% daily momentum)\"\n * ```\n *\n * @example\n * ```typescript\n * // Edge cases\n * calculateTrendSlope([1, 2, 3], [1, 2, 3]) // 1.0 (perfect positive correlation)\n * calculateTrendSlope([3, 2, 1], [1, 2, 3]) // -1.0 (perfect negative)\n * calculateTrendSlope([5, 5, 5], [1, 2, 3]) // 0.0 (flat line)\n * calculateTrendSlope([1, 2, 3], [1, 1, 1]) // 0.0 (constant X, div by zero)\n * calculateTrendSlope([10, 20], [1, 2]) // 10.0 (two points)\n * ```\n *\n * @see {@link calculateCorrelation} for correlation strength (not just slope)\n * @see {@link calculateAverage} for mean calculation\n */\nexport function calculateTrendSlope(y: number[], x: number[]) {\n const n = size(y)\n const sum_x = sum(x)\n const sum_y = sum(y)\n const sum_x2 = sum(map(x, val => val * val))\n const sum_xy = sum(map(x, (val, i) => val * y[i]))\n\n const denominator = n * sum_x2 - sum_x * sum_x\n if (denominator === 0) {\n return 0\n }\n\n const slope = (n * sum_xy - sum_x * sum_y) / denominator\n return roundToDecimals(slope, 1)\n}\n\n// =============================================================================\n// ADVANCED STATISTICAL FUNCTIONS\n// =============================================================================\n\n/**\n * Calculates the median of an array of numbers\n *\n * The median is the middle value in a sorted dataset. More robust than mean for\n * skewed distributions or datasets with outliers. For even-length arrays, returns\n * the average of the two middle values.\n *\n * Algorithm:\n * 1. Sort array in ascending order\n * 2. If odd length: return middle value\n * 3. If even length: return average of two middle values\n * 4. Empty array returns 0\n *\n * Use cases: Income analysis, response times, test scores, real estate prices\n *\n * @param values - Array of numbers to calculate median from (empty array returns 0)\n * @returns Median value, or 0 if array is empty\n *\n * @example\n * ```typescript\n * // Odd number of values\n * calculateMedian([1, 2, 3, 4, 5]) // 3 (middle value)\n * calculateMedian([10, 5, 15]) // 10 (after sorting: [5, 10, 15])\n *\n * // Even number of values\n * calculateMedian([1, 2, 3, 4]) // 2.5 (average of 2 and 3)\n * calculateMedian([10, 20, 30, 40]) // 25 (average of 20 and 30)\n *\n * // Edge cases\n * calculateMedian([]) // 0\n * calculateMedian([42]) // 42\n * calculateMedian([5, 5, 5]) // 5\n *\n * // Handles negative numbers\n * calculateMedian([-10, -5, 0, 5, 10]) // 0\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Analyze salary data (median better than mean for skewed data)\n * const salaries = [30000, 35000, 40000, 45000, 50000, 250000]\n *\n * const mean = salaries.reduce((a, b) => a + b) / salaries.length\n * const median = calculateMedian(salaries)\n *\n * console.log(`Mean: $${mean}`) // $75,000 (skewed by outlier)\n * console.log(`Median: $${median}`) // $42,500 (more representative)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Website response time analysis\n * const responseTimes = [120, 150, 180, 200, 220, 5000] // milliseconds\n *\n * const medianResponseTime = calculateMedian(responseTimes)\n * console.log(`Median response: ${medianResponseTime}ms`) // 190ms\n * // More useful than mean (946ms) which is skewed by one slow request\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Grade distribution analysis\n * const grades = [65, 70, 75, 80, 85, 90, 95]\n *\n * const median = calculateMedian(grades)\n * const Q1 = calculatePercentile(grades, 25)\n * const Q3 = calculatePercentile(grades, 75)\n *\n * console.log(`Median grade: ${median}`) // 80\n * console.log(`25th percentile: ${Q1}`) // 70\n * console.log(`75th percentile: ${Q3}`) // 90\n * ```\n *\n * @see {@link calculateAverage} for arithmetic mean calculation\n * @see {@link calculatePercentile} for other percentiles (median is 50th percentile)\n * @see {@link calculateQuartiles} for Q1, Q2 (median), Q3\n * @see {@link calculateMode} for most frequent value\n */\nexport const calculateMedian = (values: number[]): number => {\n if (!values.length) return 0\n\n const sorted = [...values].sort((a, b) => a - b)\n const middle = Math.floor(sorted.length / 2)\n\n if (sorted.length % 2 === 0) {\n return (sorted[middle - 1] + sorted[middle]) / 2\n }\n\n return sorted[middle]\n}\n\n/**\n * Calculates the mode (most frequent value) in an array of numbers\n *\n * The mode is the value that appears most frequently in a dataset. Returns null\n * if the array is empty or if there are multiple modes (multimodal distribution).\n *\n * Algorithm:\n * 1. Count frequency of each value using hash map\n * 2. Track maximum frequency and corresponding value\n * 3. Detect multimodal distributions (multiple values with same max frequency)\n * 4. Return single mode or null if multimodal/empty\n *\n * Behavior:\n * - Empty array → null\n * - Single mode → returns that value\n * - Multiple modes (tie) → null\n * - All unique values → null (no mode)\n *\n * @param values - Array of numbers to analyze\n * @returns The mode (most frequent value) or null if multimodal/empty\n *\n * @example\n * ```typescript\n * // Basic mode calculation\n * calculateMode([1, 2, 2, 3, 3, 3, 4]) // 3 (appears 3 times)\n * calculateMode([5, 5, 5, 1, 2, 3]) // 5 (most frequent)\n * calculateMode([10, 20, 30]) // null (all unique, no mode)\n * calculateMode([1, 1, 2, 2, 3]) // null (multimodal: 1 and 2 tie)\n * calculateMode([]) // null (empty array)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Find most common response time in API logs\n * const responseTimes = [\n * 150, 200, 150, 300, 150, // 150ms appears most\n * 200, 400, 150, 200, 150\n * ]\n * const commonResponseTime = calculateMode(responseTimes)\n * // 150 (appears 5 times)\n *\n * if (commonResponseTime !== null) {\n * console.log(`Most common response time: ${commonResponseTime}ms`)\n * console.log('💡 Optimize for this latency profile')\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Detect most frequent error code in logs\n * function analyzeErrorCodes(errorLogs: { code: number }[]): void {\n * const codes = errorLogs.map(log => log.code)\n * const mostFrequentCode = calculateMode(codes)\n *\n * if (mostFrequentCode !== null) {\n * console.log(`🚨 Most frequent error: ${mostFrequentCode}`)\n * console.log('Priority fix needed for this error type')\n * } else {\n * console.log('✅ No dominant error pattern (good distribution)')\n * }\n * }\n *\n * // Example usage\n * const errors = [\n * { code: 404 }, { code: 500 }, { code: 404 },\n * { code: 404 }, { code: 503 }, { code: 404 }\n * ]\n * analyzeErrorCodes(errors)\n * // 🚨 Most frequent error: 404\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Find most popular product rating\n * interface ProductReview {\n * productId: string\n * rating: number // 1-5 stars\n * userId: string\n * }\n *\n * function getMostCommonRating(reviews: ProductReview[]): string {\n * const ratings = reviews.map(r => r.rating)\n * const mode = calculateMode(ratings)\n *\n * if (mode === null) {\n * return 'Ratings are evenly distributed'\n * }\n *\n * const sentiment = mode >= 4 ? '😊 Positive' : mode <= 2 ? '😞 Negative' : '😐 Neutral'\n * return `Most common rating: ${mode}⭐ (${sentiment})`\n * }\n *\n * const reviews = [\n * { productId: 'A1', rating: 5, userId: 'U1' },\n * { productId: 'A1', rating: 5, userId: 'U2' },\n * { productId: 'A1', rating: 4, userId: 'U3' },\n * { productId: 'A1', rating: 5, userId: 'U4' }\n * ]\n *\n * getMostCommonRating(reviews)\n * // \"Most common rating: 5⭐ (😊 Positive)\"\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Detect most frequent user action in analytics\n * const userActions = [\n * 'click', 'scroll', 'click', 'click',\n * 'scroll', 'click', 'submit', 'click'\n * ].map(action => {\n * const actionMap = { click: 1, scroll: 2, submit: 3 }\n * return actionMap[action as keyof typeof actionMap]\n * })\n *\n * const dominantAction = calculateMode(userActions)\n * const actionNames = { 1: 'click', 2: 'scroll', 3: 'submit' }\n *\n * if (dominantAction !== null) {\n * console.log(`Most frequent action: ${actionNames[dominantAction as keyof typeof actionNames]}`)\n * // \"Most frequent action: click\"\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Edge cases\n * calculateMode([42]) // 42 (single value)\n * calculateMode([1, 1]) // 1 (all same)\n * calculateMode([1, 2, 3, 4, 5]) // null (all unique)\n * calculateMode([1, 1, 2, 2]) // null (bimodal)\n * calculateMode([1, 1, 1, 2, 2, 2]) // null (bimodal tie)\n * calculateMode([NaN, NaN, 1]) // NaN (NaN counted as value)\n * calculateMode([Infinity, Infinity]) // Infinity\n * ```\n *\n * @see {@link calculateMedian} for middle value (resistant to outliers)\n * @see {@link calculateAverage} for mean value\n * @see {@link calculateStandardDeviation} for data spread measurement\n */\nexport const calculateMode = (values: number[]): number | null => {\n if (!values.length) return null\n\n const frequency: { [key: number]: number } = {}\n let maxFreq = 0\n let mode: number | null = null\n let hasMultipleModes = false\n\n for (const value of values) {\n frequency[value] = (frequency[value] || 0) + 1\n\n if (frequency[value] > maxFreq) {\n maxFreq = frequency[value]\n mode = value\n hasMultipleModes = false\n } else if (frequency[value] === maxFreq && value !== mode) {\n hasMultipleModes = true\n }\n }\n\n return hasMultipleModes ? null : mode\n}\n\n/**\n * Calculates the standard deviation of an array of numbers\n *\n * Measures the amount of variation or dispersion in a dataset. Low standard deviation\n * means values are close to the mean; high standard deviation means values are spread out.\n *\n * Algorithm:\n * 1. Calculate mean (average)\n * 2. Calculate squared differences from mean\n * 3. Calculate variance (average of squared differences)\n * 4. Return square root of variance\n *\n * Formula:\n * - Population: σ = √(Σ(x - μ)² / N)\n * - Sample: s = √(Σ(x - x̄)² / (N - 1))\n *\n * @param values - Array of numbers (empty array returns 0)\n * @param sample - If true, uses sample standard deviation (N-1). If false, uses population (N). Default: false\n * @returns Standard deviation, or 0 if array is empty\n *\n * @example\n * ```typescript\n * // Low standard deviation (values close together)\n * calculateStandardDeviation([10, 11, 12, 13, 14])\n * // ~1.41 (values tightly clustered)\n *\n * // High standard deviation (values spread out)\n * calculateStandardDeviation([1, 5, 20, 50, 100])\n * // ~35.96 (values widely dispersed)\n *\n * // Population vs Sample\n * const data = [2, 4, 6, 8, 10]\n * calculateStandardDeviation(data, false) // 2.83 (population)\n * calculateStandardDeviation(data, true) // 3.16 (sample, Bessel's correction)\n *\n * // Edge cases\n * calculateStandardDeviation([]) // 0\n * calculateStandardDeviation([5]) // 0\n * calculateStandardDeviation([5, 5, 5]) // 0 (no variation)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Quality control - manufacturing consistency\n * const widgetWeights = [98.5, 99.2, 100.1, 99.8, 100.3, 98.9]\n * const targetWeight = 100\n *\n * const mean = widgetWeights.reduce((a, b) => a + b) / widgetWeights.length\n * const stdDev = calculateStandardDeviation(widgetWeights, true)\n *\n * console.log(`Mean weight: ${mean.toFixed(2)}g`)\n * console.log(`Std deviation: ${stdDev.toFixed(2)}g`)\n * // Low std dev = consistent manufacturing\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Investment volatility analysis\n * const monthlyReturns = [2.3, -1.5, 4.2, 0.8, -2.1, 3.5, 1.2] // %\n *\n * const avgReturn = monthlyReturns.reduce((a, b) => a + b) / monthlyReturns.length\n * const volatility = calculateStandardDeviation(monthlyReturns, true)\n *\n * console.log(`Average return: ${avgReturn.toFixed(2)}%`)\n * console.log(`Volatility (std dev): ${volatility.toFixed(2)}%`)\n * // High volatility = riskier investment\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Student performance analysis\n * const testScores = [75, 82, 68, 90, 78, 85, 72, 88, 80, 77]\n *\n * const mean = testScores.reduce((a, b) => a + b) / testScores.length\n * const stdDev = calculateStandardDeviation(testScores, true)\n *\n * // Identify outliers (beyond 2 standard deviations)\n * const outliers = testScores.filter(score =>\n * Math.abs(score - mean) > 2 * stdDev\n * )\n *\n * console.log(`Mean: ${mean.toFixed(1)}`)\n * console.log(`Std Dev: ${stdDev.toFixed(1)}`)\n * console.log(`Outliers:`, outliers)\n * ```\n *\n * @see {@link calculateVariance} for variance (σ²)\n * @see {@link detectOutliers} for IQR-based outlier detection\n * @see {@link calculateMedian} for central tendency (robust to outliers)\n */\nexport const calculateStandardDeviation = (values: number[], sample = false): number => {\n if (!values.length) return 0\n\n const mean = values.reduce((sum, val) => sum + val, 0) / values.length\n const squaredDiffs = values.map(val => Math.pow(val - mean, 2))\n const variance =\n squaredDiffs.reduce((sum, val) => sum + val, 0) / (sample ? values.length - 1 : values.length)\n\n return Math.sqrt(variance)\n}\n\n/**\n * Calculates the variance of an array of numbers\n *\n * Variance measures how spread out numbers are from their mean (average).\n * It's the average of squared differences from the mean. Higher variance = more spread.\n *\n * Algorithm:\n * 1. Calculate mean (average) of all values\n * 2. Compute squared difference from mean for each value: (value - mean)²\n * 3. Sum all squared differences\n * 4. Divide by N (population) or N-1 (sample) depending on `sample` flag\n *\n * Population vs Sample variance:\n * - **Population (sample=false)**: Use when you have ALL data (divide by N)\n * - **Sample (sample=true)**: Use when you have a subset (divide by N-1, Bessel's correction)\n *\n * @param values - Array of numbers to analyze\n * @param sample - If true, uses sample variance (N-1). If false, uses population variance (N). Default: false\n * @returns Variance value (0 if empty array)\n *\n * @example\n * ```typescript\n * // Basic variance calculation\n * calculateVariance([1, 2, 3, 4, 5]) // 2 (population variance)\n * calculateVariance([1, 2, 3, 4, 5], true) // 2.5 (sample variance)\n * calculateVariance([10, 10, 10, 10]) // 0 (no variance, all same)\n * calculateVariance([1, 100]) // 2450.5 (high variance)\n * calculateVariance([]) // 0 (empty array)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Analyze server response time consistency\n * const responseTimes = [120, 150, 130, 145, 125, 140, 135] // milliseconds\n * const variance = calculateVariance(responseTimes)\n * const stdDev = Math.sqrt(variance)\n *\n * console.log(`Variance: ${variance.toFixed(2)}ms²`)\n * console.log(`Std Dev: ${stdDev.toFixed(2)}ms`)\n *\n * if (stdDev < 20) {\n * console.log('✅ Consistent performance')\n * } else {\n * console.log('⚠️ High variability - investigate outliers')\n * }\n * // Variance: 110.20ms²\n * // Std Dev: 10.50ms\n * // ✅ Consistent performance\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Quality control in manufacturing\n * interface Measurement {\n * batchId: string\n * weight: number // grams\n * }\n *\n * function checkBatchQuality(measurements: Measurement[]): boolean {\n * const weights = measurements.map(m => m.weight)\n * const variance = calculateVariance(weights)\n * const maxAcceptableVariance = 5 // Quality threshold\n *\n * if (variance <= maxAcceptableVariance) {\n * console.log('✅ Batch passes quality control')\n * console.log(`Variance: ${variance.toFixed(2)}g² (within tolerance)`)\n * return true\n * } else {\n * console.log('❌ Batch REJECTED - excessive variance')\n * console.log(`Variance: ${variance.toFixed(2)}g² (exceeds ${maxAcceptableVariance}g²)`)\n * return false\n * }\n * }\n *\n * const batch = [\n * { batchId: 'B001', weight: 100.2 },\n * { batchId: 'B001', weight: 100.5 },\n * { batchId: 'B001', weight: 99.8 },\n * { batchId: 'B001', weight: 100.1 }\n * ]\n *\n * checkBatchQuality(batch)\n * // ✅ Batch passes quality control\n * // Variance: 0.09g² (within tolerance)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Stock price volatility analysis\n * function analyzeStockVolatility(prices: number[]): string {\n * const variance = calculateVariance(prices, true) // Sample variance\n * const stdDev = Math.sqrt(variance)\n * const mean = prices.reduce((sum, p) => sum + p, 0) / prices.length\n * const coefficientOfVariation = (stdDev / mean) * 100\n *\n * if (coefficientOfVariation < 5) {\n * return `Low volatility (${coefficientOfVariation.toFixed(1)}% CV) - Stable stock`\n * } else if (coefficientOfVariation < 15) {\n * return `Moderate volatility (${coefficientOfVariation.toFixed(1)}% CV) - Normal fluctuation`\n * } else {\n * return `High volatility (${coefficientOfVariation.toFixed(1)}% CV) - Risky investment`\n * }\n * }\n *\n * const stockPrices = [100, 102, 98, 101, 99, 103, 97] // Daily closing prices\n * analyzeStockVolatility(stockPrices)\n * // \"Moderate volatility (2.1% CV) - Normal fluctuation\"\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: A/B test result significance\n * function compareVariability(groupA: number[], groupB: number[]): void {\n * const varA = calculateVariance(groupA, true)\n * const varB = calculateVariance(groupB, true)\n * const ratio = varA / varB\n *\n * console.log(`Group A variance: ${varA.toFixed(2)}`)\n * console.log(`Group B variance: ${varB.toFixed(2)}`)\n * console.log(`Variance ratio: ${ratio.toFixed(2)}`)\n *\n * if (ratio > 2 || ratio < 0.5) {\n * console.log('⚠️ Groups have significantly different variability')\n * console.log('Consider using Welch\\'s t-test instead of Student\\'s t-test')\n * } else {\n * console.log('✅ Groups have similar variability')\n * }\n * }\n *\n * const conversionRatesA = [0.12, 0.15, 0.13, 0.14, 0.12]\n * const conversionRatesB = [0.18, 0.22, 0.19, 0.21, 0.20]\n *\n * compareVariability(conversionRatesA, conversionRatesB)\n * // Group A variance: 0.00013\n * // Group B variance: 0.00025\n * // Variance ratio: 0.52\n * // ✅ Groups have similar variability\n * ```\n *\n * @example\n * ```typescript\n * // Edge cases\n * calculateVariance([5]) // 0 (single value)\n * calculateVariance([5], true) // NaN (sample size 1, division by 0)\n * calculateVariance([1, 1, 1]) // 0 (no variation)\n * calculateVariance([0, 0, 0]) // 0 (all zeros)\n * calculateVariance([-5, 0, 5]) // 16.67 (symmetric around 0)\n * calculateVariance([1e10, 1e10 + 1]) // 0.25 (large numbers)\n * ```\n *\n * @see {@link calculateStandardDeviation} for square root of variance (same units as data)\n * @see {@link calculateIQR} for robust spread measure (resistant to outliers)\n * @see {@link detectOutliers} for finding extreme values\n */\nexport const calculateVariance = (values: number[], sample = false): number => {\n if (!values.length) return 0\n\n const mean = values.reduce((sum, val) => sum + val, 0) / values.length\n const squaredDiffs = values.map(val => Math.pow(val - mean, 2))\n\n return (\n squaredDiffs.reduce((sum, val) => sum + val, 0) / (sample ? values.length - 1 : values.length)\n )\n}\n\n/**\n * Calculates a specific percentile from an array of numbers\n *\n * Percentiles indicate the value below which a given percentage of observations fall.\n * For example, the 75th percentile is the value below which 75% of the data lies.\n *\n * Algorithm:\n * 1. Sort values in ascending order\n * 2. Calculate position: (percentile / 100) × (n - 1)\n * 3. If position is integer, return value at that index\n * 4. Otherwise, interpolate between lower and upper values using linear interpolation\n *\n * Common percentiles:\n * - 25th (Q1): First quartile\n * - 50th (Q2): Median\n * - 75th (Q3): Third quartile\n * - 90th: Top 10% threshold\n * - 95th: Top 5% threshold\n * - 99th: Top 1% threshold\n *\n * @param values - Array of numbers to analyze\n * @param percentile - Percentile to calculate (0-100)\n * @returns Value at the specified percentile (0 if empty array)\n * @throws {Error} If percentile is not between 0 and 100\n *\n * @example\n * ```typescript\n * // Basic percentile calculation\n * const scores = [55, 60, 65, 70, 75, 80, 85, 90, 95, 100]\n *\n * calculatePercentile(scores, 25) // 66.25 (Q1 - 25% below this)\n * calculatePercentile(scores, 50) // 77.5 (Median - 50% below this)\n * calculatePercentile(scores, 75) // 88.75 (Q3 - 75% below this)\n * calculatePercentile(scores, 90) // 95.5 (90% below this)\n * calculatePercentile(scores, 100) // 100 (Maximum)\n * calculatePercentile(scores, 0) // 55 (Minimum)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: API response time SLA monitoring\n * const responseTimes = [\n * 120, 150, 180, 200, 250, 300, 350, 400, 450, 500,\n * 600, 700, 800, 1000, 1200\n * ]\n *\n * const p50 = calculatePercentile(responseTimes, 50) // Median\n * const p95 = calculatePercentile(responseTimes, 95) // 95th percentile\n * const p99 = calculatePercentile(responseTimes, 99) // 99th percentile\n *\n * console.log(`📊 Response Time SLA:`)\n * console.log(` 50th (median): ${p50}ms`)\n * console.log(` 95th: ${p95}ms`)\n * console.log(` 99th: ${p99}ms`)\n *\n * if (p95 < 500) {\n * console.log('✅ SLA met: 95% of requests under 500ms')\n * } else {\n * console.log(`⚠️ SLA breach: p95=${p95}ms exceeds 500ms target`)\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Grade distribution analysis\n * function analyzeExamResults(scores: number[]): void {\n * const p10 = calculatePercentile(scores, 10)\n * const p25 = calculatePercentile(scores, 25)\n * const p50 = calculatePercentile(scores, 50)\n * const p75 = calculatePercentile(scores, 75)\n * const p90 = calculatePercentile(scores, 90)\n *\n * console.log('📈 Exam Score Distribution:')\n * console.log(` Bottom 10%: ≤ ${p10.toFixed(1)}`)\n * console.log(` Q1 (25th): ${p25.toFixed(1)}`)\n * console.log(` Median (50th): ${p50.toFixed(1)}`)\n * console.log(` Q3 (75th): ${p75.toFixed(1)}`)\n * console.log(` Top 10%: ≥ ${p90.toFixed(1)}`)\n *\n * // Identify struggling students\n * if (p25 < 60) {\n * console.log('⚠️ Warning: 25% of students scored below 60')\n * console.log('Recommendation: Provide additional support')\n * }\n * }\n *\n * const examScores = [45, 52, 58, 63, 67, 72, 75, 78, 82, 85, 88, 92, 95]\n * analyzeExamResults(examScores)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Server load capacity planning\n * interface ServerMetrics {\n * timestamp: Date\n * cpuUsage: number // percentage\n * }\n *\n * function determineCapacityThreshold(metrics: ServerMetrics[]): number {\n * const cpuUsages = metrics.map(m => m.cpuUsage)\n * const p95 = calculatePercentile(cpuUsages, 95)\n *\n * console.log(`Current p95 CPU usage: ${p95.toFixed(1)}%`)\n *\n * if (p95 > 80) {\n * console.log('🚨 CRITICAL: Scale up immediately')\n * return 100 // Emergency threshold\n * } else if (p95 > 60) {\n * console.log('⚠️ WARNING: Plan scaling in next 24h')\n * return 80\n * } else {\n * console.log('✅ Healthy capacity')\n * return 70\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Income inequality analysis (Gini coefficient context)\n * const householdIncomes = [\n * 25000, 30000, 35000, 40000, 45000, 50000, 55000,\n * 60000, 70000, 80000, 90000, 120000, 150000, 200000\n * ]\n *\n * const p10 = calculatePercentile(householdIncomes, 10)\n * const p50 = calculatePercentile(householdIncomes, 50)\n * const p90 = calculatePercentile(householdIncomes, 90)\n *\n * const p90p10Ratio = p90 / p10\n *\n * console.log(`Income distribution:`)\n * console.log(` Bottom 10%: ≤$${p10.toLocaleString()}`)\n * console.log(` Median: $${p50.toLocaleString()}`)\n * console.log(` Top 10%: ≥$${p90.toLocaleString()}`)\n * console.log(` P90/P10 ratio: ${p90p10Ratio.toFixed(2)}x`)\n *\n * if (p90p10Ratio > 5) {\n * console.log('⚠️ High income inequality detected')\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Edge cases\n * calculatePercentile([10], 50) // 10 (single value)\n * calculatePercentile([1, 2, 3, 4], 0) // 1 (minimum)\n * calculatePercentile([1, 2, 3, 4], 100) // 4 (maximum)\n * calculatePercentile([], 50) // 0 (empty array)\n * calculatePercentile([5, 5, 5], 75) // 5 (all same values)\n *\n * // Error cases\n * calculatePercentile([1, 2, 3], -1) // throws Error\n * calculatePercentile([1, 2, 3], 101) // throws Error\n * ```\n *\n * @see {@link calculateQuartiles} for Q1, Q2, Q3 in one call\n * @see {@link calculateIQR} for interquartile range (Q3 - Q1)\n * @see {@link calculateMedian} for 50th percentile specifically\n * @see {@link detectOutliers} for finding extreme values using IQR method\n */\nexport const calculatePercentile = (values: number[], percentile: number): number => {\n if (!values.length) return 0\n if (percentile < 0 || percentile > 100) throw new Error('Percentile must be between 0 and 100')\n\n const sorted = [...values].sort((a, b) => a - b)\n const index = (percentile / 100) * (sorted.length - 1)\n\n if (Math.floor(index) === index) {\n return sorted[index]\n }\n\n const lower = sorted[Math.floor(index)]\n const upper = sorted[Math.ceil(index)]\n const weight = index - Math.floor(index)\n\n return lower * (1 - weight) + upper * weight\n}\n\n/**\n * Calculates quartiles (Q1, Q2, Q3) of an array of numbers\n *\n * Quartiles divide a sorted dataset into four equal parts. Returns the three cut points:\n * - **Q1 (25th percentile)**: 25% of data below, 75% above\n * - **Q2 (50th percentile)**: Median - 50% below, 50% above\n * - **Q3 (75th percentile)**: 75% below, 25% above\n *\n * Useful for:\n * - Box plot visualization\n * - Outlier detection (IQR method)\n * - Understanding data spread and skewness\n * - Five-number summary (min, Q1, Q2, Q3, max)\n *\n * @param values - Array of numbers to analyze\n * @returns Object with Q1, Q2 (median), Q3 values\n *\n * @example\n * ```typescript\n * // Basic quartiles\n * const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n * calculateQuartiles(data)\n * // { Q1: 3.25, Q2: 5.5, Q3: 7.75 }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Salary distribution analysis\n * const salaries = [\n * 30000, 35000, 40000, 45000, 50000, 55000, 60000,\n * 65000, 70000, 80000, 90000, 120000\n * ]\n *\n * const { Q1, Q2, Q3 } = calculateQuartiles(salaries)\n *\n * console.log(`📊 Salary Distribution:`)\n * console.log(` Q1 (25th): $${Q1.toLocaleString()}`)\n * console.log(` Q2 (Median): $${Q2.toLocaleString()}`)\n * console.log(` Q3 (75th): $${Q3.toLocaleString()}`)\n * console.log(` IQR: $${(Q3 - Q1).toLocaleString()}`)\n * // Q1: $43,750, Q2: $57,500, Q3: $72,500, IQR: $28,750\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Performance monitoring box plot\n * function createBoxPlot(responseTimes: number[]): void {\n * const { Q1, Q2, Q3 } = calculateQuartiles(responseTimes)\n * const min = Math.min(...responseTimes)\n * const max = Math.max(...responseTimes)\n * const iqr = Q3 - Q1\n *\n * console.log('📦 Response Time Box Plot:')\n * console.log(` Min: ${min}ms`)\n * console.log(` Q1: ${Q1}ms ├───┐`)\n * console.log(` Q2: ${Q2}ms │ █ │ (median)`)\n * console.log(` Q3: ${Q3}ms └───┤`)\n * console.log(` Max: ${max}ms`)\n * console.log(` IQR: ${iqr}ms (middle 50%)`)\n * }\n *\n * const times = [120, 150, 180, 200, 220, 250, 300, 350, 400]\n * createBoxPlot(times)\n * ```\n *\n * @see {@link calculatePercentile} for calculating any percentile\n * @see {@link calculateIQR} for interquartile range (Q3 - Q1)\n * @see {@link detectOutliers} for outlier detection using quartiles\n * @see {@link calculateMedian} for Q2 specifically\n */\nexport const calculateQuartiles = (values: number[]): { Q1: number; Q2: number; Q3: number } => {\n return {\n Q1: calculatePercentile(values, 25),\n Q2: calculatePercentile(values, 50), // median\n Q3: calculatePercentile(values, 75),\n }\n}\n\n/**\n * Calculates the interquartile range (IQR) of an array of numbers\n *\n * IQR is the range of the middle 50% of data, calculated as Q3 - Q1.\n * It's a robust measure of statistical dispersion, resistant to outliers.\n *\n * IQR interpretation:\n * - **Small IQR**: Data tightly clustered around median (low variability)\n * - **Large IQR**: Data widely spread (high variability)\n * - **IQR = 0**: All middle 50% values identical\n *\n * Key uses:\n * - Outlier detection (values outside Q1 - 1.5×IQR to Q3 + 1.5×IQR)\n * - Box plot whiskers calculation\n * - Robust alternative to standard deviation (less affected by extremes)\n *\n * @param values - Array of numbers to analyze\n * @returns Interquartile range (Q3 - Q1)\n *\n * @example\n * ```typescript\n * // Basic IQR calculation\n * const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n * calculateIQR(data) // 4.5 (middle 50% spans 4.5 units)\n *\n * const tightData = [10, 10.5, 11, 11.5, 12]\n * calculateIQR(tightData) // 1 (low variability)\n *\n * const spreadData = [10, 20, 30, 40, 50, 60, 70, 80, 90]\n * calculateIQR(spreadData) // 40 (high variability)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Compare consistency between two groups\n * const teamA_responseTimes = [100, 110, 120, 130, 140, 150]\n * const teamB_responseTimes = [80, 120, 130, 140, 180, 250]\n *\n * const iqrA = calculateIQR(teamA_responseTimes) // 30ms\n * const iqrB = calculateIQR(teamB_responseTimes) // 60ms\n *\n * console.log(`Team A IQR: ${iqrA}ms (consistent)`)\n * console.log(`Team B IQR: ${iqrB}ms (inconsistent)`)\n *\n * if (iqrB > iqrA * 1.5) {\n * console.log('⚠️ Team B shows high variability - investigate')\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Quality control tolerance check\n * function checkProductQuality(measurements: number[]): boolean {\n * const iqr = calculateIQR(measurements)\n * const maxAllowedIQR = 5 // mm tolerance\n *\n * console.log(`IQR: ${iqr.toFixed(2)}mm`)\n *\n * if (iqr <= maxAllowedIQR) {\n * console.log('✅ Manufacturing process within tolerance')\n * return true\n * } else {\n * console.log(`❌ IQR ${iqr}mm exceeds ${maxAllowedIQR}mm tolerance`)\n * console.log('Action: Recalibrate machinery')\n * return false\n * }\n * }\n *\n * const partLengths = [99.8, 100.0, 100.1, 100.2, 100.3]\n * checkProductQuality(partLengths)\n * ```\n *\n * @see {@link calculateQuartiles} for Q1, Q2, Q3 values\n * @see {@link detectOutliers} for IQR-based outlier detection\n * @see {@link calculateStandardDeviation} for parametric dispersion measure\n */\nexport const calculateIQR = (values: number[]): number => {\n const quartiles = calculateQuartiles(values)\n return quartiles.Q3 - quartiles.Q1\n}\n\n/**\n * Detects outliers using the IQR (Interquartile Range) method\n *\n * Identifies values that fall outside the \"normal\" range defined by quartiles.\n * Uses Tukey's fences method, commonly used in box plots.\n *\n * Algorithm:\n * 1. Calculate Q1, Q3, and IQR (Q3 - Q1)\n * 2. Compute lower fence: Q1 - (multiplier × IQR)\n * 3. Compute upper fence: Q3 + (multiplier × IQR)\n * 4. Values outside fences are outliers\n *\n * Common multipliers:\n * - **1.5** (default): Standard outliers (Tukey's rule)\n * - **3.0**: Extreme outliers only (more conservative)\n *\n * @param values - Array of numbers to analyze\n * @param multiplier - IQR multiplier for fence calculation (default: 1.5)\n * @returns Array of outlier values\n *\n * @example\n * ```typescript\n * // Basic outlier detection\n * const data = [10, 12, 14, 15, 16, 18, 20, 22, 100]\n * detectOutliers(data) // [100] (extreme high value)\n *\n * const normal = [10, 12, 14, 15, 16, 18, 20, 22, 24]\n * detectOutliers(normal) // [] (no outliers)\n *\n * const withExtremes = [1, 10, 12, 14, 15, 16, 18, 20, 100]\n * detectOutliers(withExtremes) // [1, 100] (both low and high outliers)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Detect anomalous response times\n * function analyzeResponseTimes(times: number[]): void {\n * const outliers = detectOutliers(times)\n *\n * if (outliers.length === 0) {\n * console.log('✅ No anomalous response times detected')\n * return\n * }\n *\n * const { Q1, Q3 } = calculateQuartiles(times)\n * const iqr = Q3 - Q1\n *\n * console.log(`⚠️ Found ${outliers.length} outliers:`)\n * outliers.forEach(outlier => {\n * const severity = outlier > Q3 + 3 * iqr ? 'EXTREME' : 'MODERATE'\n * console.log(` - ${outlier}ms (${severity})`)\n * })\n *\n * console.log('Recommendation: Investigate these requests')\n * }\n *\n * const apiTimes = [100, 120, 130, 140, 150, 160, 180, 200, 5000]\n * analyzeResponseTimes(apiTimes)\n * // ⚠️ Found 1 outliers:\n * // - 5000ms (EXTREME)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Fraud detection in transaction amounts\n * interface Transaction {\n * id: string\n * amount: number\n * userId: string\n * }\n *\n * function detectSuspiciousTransactions(\n * transactions: Transaction[]\n * ): Transaction[] {\n * const amounts = transactions.map(t => t.amount)\n * const outlierAmounts = detectOutliers(amounts, 3) // More conservative\n *\n * const suspicious = transactions.filter(t =>\n * outlierAmounts.includes(t.amount)\n * )\n *\n * if (suspicious.length > 0) {\n * console.log(`🚨 ${suspicious.length} suspicious transactions:`)\n * suspicious.forEach(t => {\n * console.log(` ID: ${t.id}, Amount: $${t.amount}, User: ${t.userId}`)\n * })\n * console.log('Action: Flag for manual review')\n * }\n *\n * return suspicious\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Sensor data cleaning\n * function cleanSensorReadings(readings: number[]): number[] {\n * const outliers = detectOutliers(readings)\n * const cleaned = readings.filter(r => !outliers.includes(r))\n *\n * console.log(`Original readings: ${readings.length}`)\n * console.log(`Outliers removed: ${outliers.length}`)\n * console.log(`Cleaned readings: ${cleaned.length}`)\n *\n * if (outliers.length > readings.length * 0.1) {\n * console.log('⚠️ Warning: >10% outliers - sensor malfunction?')\n * }\n *\n * return cleaned\n * }\n *\n * const tempReadings = [20.1, 20.5, 20.8, 21.0, 85.0, 20.9, 21.2]\n * const clean = cleanSensorReadings(tempReadings)\n * // Original: 7, Outliers: 1 (85.0°C), Cleaned: 6\n * ```\n *\n * @example\n * ```typescript\n * // Comparing multipliers\n * const data = [1, 5, 10, 12, 14, 15, 16, 18, 20, 25, 100]\n *\n * detectOutliers(data, 1.5) // [1, 100] (standard)\n * detectOutliers(data, 3.0) // [100] (extreme only)\n * ```\n *\n * @see {@link calculateIQR} for IQR calculation\n * @see {@link calculateQuartiles} for quartile values\n * @see {@link calculateStandardDeviation} for parametric outlier detection (z-score method)\n */\nexport const detectOutliers = (values: number[], multiplier = 1.5): number[] => {\n const quartiles = calculateQuartiles(values)\n const iqr = quartiles.Q3 - quartiles.Q1\n const lowerBound = quartiles.Q1 - multiplier * iqr\n const upperBound = quartiles.Q3 + multiplier * iqr\n\n return values.filter(value => value < lowerBound || value > upperBound)\n}\n\n/**\n * Calculates Pearson correlation coefficient between two arrays of numbers\n *\n * Measures linear correlation between two variables. Returns value between -1 and +1:\n * - +1: Perfect positive correlation (as X increases, Y increases proportionally)\n * - 0: No linear correlation\n * - -1: Perfect negative correlation (as X increases, Y decreases proportionally)\n *\n * Formula (Pearson's r):\n * r = [n∑(xy) - ∑x∑y] / √{[n∑x² - (∑x)²][n∑y² - (∑y)²]}\n *\n * Use cases: A/B testing, feature selection, risk analysis, data validation\n *\n * ⚠️ NOTE: Measures LINEAR correlation only. May miss non-linear relationships.\n *\n * @param x - First array of numbers (must match length of y)\n * @param y - Second array of numbers (must match length of x)\n * @returns Correlation coefficient (-1 to +1), or 0 if arrays empty/mismatched/no variance\n *\n * @example\n * ```typescript\n * // Perfect positive correlation\n * calculateCorrelation([1, 2, 3, 4, 5], [2, 4, 6, 8, 10])\n * // 1.0 (perfect linear relationship: y = 2x)\n *\n * // Perfect negative correlation\n * calculateCorrelation([1, 2, 3, 4, 5], [10, 8, 6, 4, 2])\n * // -1.0 (perfect inverse relationship)\n *\n * // No correlation\n * calculateCorrelation([1, 2, 3, 4, 5], [5, 3, 5, 3, 5])\n * // ~0 (no linear relationship)\n *\n * // Moderate positive correlation\n * calculateCorrelation([1, 2, 3, 4, 5], [2, 3, 5, 7, 9])\n * // ~0.98 (strong positive correlation)\n *\n * // Edge cases\n * calculateCorrelation([], []) // 0\n * calculateCorrelation([1, 2], [1]) // 0 (length mismatch)\n * calculateCorrelation([5, 5, 5], [1, 2, 3]) // 0 (no variance in x)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Analyze relationship between ad spend and sales\n * const adSpend = [1000, 1500, 2000, 2500, 3000, 3500] // $ thousands\n * const sales = [20, 28, 35, 42, 48, 55] // $ thousands\n *\n * const correlation = calculateCorrelation(adSpend, sales)\n * console.log(`Correlation: ${correlation.toFixed(3)}`)\n * // 0.998 (strong positive correlation - more ad spend → more sales)\n *\n * if (correlation > 0.7) {\n * console.log('✅ Strong relationship: Ad spend drives sales')\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Website traffic vs conversion rate analysis\n * const dailyVisitors = [1200, 1500, 1800, 2000, 2200, 2500]\n * const conversionRate = [3.2, 3.0, 2.9, 2.7, 2.6, 2.4]\n *\n * const correlation = calculateCorrelation(dailyVisitors, conversionRate)\n * console.log(`Correlation: ${correlation.toFixed(3)}`)\n * // -0.995 (strong negative correlation - more traffic → lower conversion %)\n * // Suggests traffic quality decreasing or server overload\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Feature selection for ML model\n * interface DataPoint {\n * temperature: number\n * humidity: number\n * windSpeed: number\n * rainfall: number\n * }\n *\n * const weatherData: DataPoint[] = [\n * { temperature: 25, humidity: 60, windSpeed: 10, rainfall: 0 },\n * { temperature: 30, humidity: 50, windSpeed: 15, rainfall: 2 },\n * { temperature: 20, humidity: 80, windSpeed: 5, rainfall: 10 },\n * // ... more data\n * ]\n *\n * // Find which features correlate with rainfall\n * const temp = weatherData.map(d => d.temperature)\n * const humidity = weatherData.map(d => d.humidity)\n * const wind = weatherData.map(d => d.windSpeed)\n * const rain = weatherData.map(d => d.rainfall)\n *\n * const correlations = {\n * temperature: calculateCorrelation(temp, rain),\n * humidity: calculateCorrelation(humidity, rain),\n * windSpeed: calculateCorrelation(wind, rain)\n * }\n *\n * console.log('Rainfall correlations:', correlations)\n * // { temperature: -0.45, humidity: 0.82, windSpeed: -0.23 }\n * // Humidity strongly predicts rainfall\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Portfolio diversification analysis\n * const stock1Returns = [2.3, -1.5, 4.2, 0.8, -2.1, 3.5]\n * const stock2Returns = [1.8, 2.1, -0.5, 1.2, 3.4, -1.8]\n *\n * const correlation = calculateCorrelation(stock1Returns, stock2Returns)\n * console.log(`Portfolio correlation: ${correlation.toFixed(3)}`)\n *\n * if (Math.abs(correlation) < 0.3) {\n * console.log('✅ Good diversification: Stocks move independently')\n * } else if (correlation > 0.7) {\n * console.log('⚠️ High correlation: Portfolio not diversified')\n * }\n * ```\n *\n * @see {@link calculateStandardDeviation} for measuring spread\n * @see {@link calculateTrendSlope} for linear regression slope\n * @see {@link https://en.wikipedia.org/wiki/Pearson_correlation_coefficient Pearson Correlation}\n */\nexport const calculateCorrelation = (x: number[], y: number[]): number => {\n if (x.length !== y.length || x.length === 0) return 0\n\n const n = x.length\n const sumX = sum(x)\n const sumY = sum(y)\n const sumXY = sum(map(x, (val, i) => val * y[i]))\n const sumX2 = sum(map(x, val => val * val))\n const sumY2 = sum(map(y, val => val * val))\n\n const numerator = n * sumXY - sumX * sumY\n const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY))\n\n if (denominator === 0) return 0\n\n return numerator / denominator\n}\n\n// =============================================================================\n// FINANCIAL FUNCTIONS\n// =============================================================================\n\n/**\n * Calculates Net Present Value (NPV) of cash flows using discounted cash flow method\n *\n * Evaluates investment profitability by discounting future cash flows to present value.\n * Formula: NPV = CF₀ + CF₁/(1+r) + CF₂/(1+r)² + ... + CFₙ/(1+r)ⁿ\n *\n * Decision rules:\n * - NPV > 0: Investment profitable, accept project\n * - NPV = 0: Break-even, neutral decision\n * - NPV < 0: Investment unprofitable, reject project\n *\n * @param cashFlows - Array of cash flows [initial investment (negative), period 1, period 2, ...]\n * @param discountRate - Discount rate as decimal (e.g., 0.1 for 10% WACC/hurdle rate)\n * @returns Net Present Value in currency units\n *\n * @example\n * ```typescript\n * // Investment: -10000€ initial, then +3000€/year for 4 years\n * const npv = calculateNPV([-10000, 3000, 3000, 3000, 3000], 0.10)\n * console.log(npv) // 1509.67€ (profitable investment)\n *\n * // Real-world: Software project ROI analysis\n * const projectCashFlows = [\n * -50000, // Year 0: Development cost\n * 15000, // Year 1: Revenue\n * 20000, // Year 2: Revenue\n * 25000, // Year 3: Revenue\n * 30000 // Year 4: Revenue\n * ]\n * const wacc = 0.12 // 12% weighted average cost of capital\n * const projectNPV = calculateNPV(projectCashFlows, wacc)\n * if (projectNPV > 0) {\n * console.log(`✅ Project approved. NPV: ${projectNPV.toFixed(2)}€`)\n * }\n * ```\n *\n * @see {@link calculateIRR} for internal rate of return\n * @see {@link https://en.wikipedia.org/wiki/Net_present_value NPV Formula}\n */\nexport const calculateNPV = (cashFlows: number[], discountRate: number): number => {\n return cashFlows.reduce((npv, cashFlow, index) => {\n // Initial cash flow (usually investment) is not discounted\n // Subsequent cash flows are discounted by their period\n const discountFactor = index === 0 ? 1 : Math.pow(1 + discountRate, index)\n return npv + cashFlow / discountFactor\n }, 0)\n}\n\n/**\n * Calculates Internal Rate of Return (IRR) using Newton-Raphson iterative method\n *\n * Finds discount rate that makes NPV = 0. IRR represents the annualized effective return rate.\n * Uses Newton-Raphson numerical method with configurable tolerance and iterations.\n *\n * Decision rules:\n * - IRR > Required Rate: Accept project (returns exceed cost of capital)\n * - IRR < Required Rate: Reject project (insufficient returns)\n *\n * @param cashFlows - Array of cash flows [initial investment, period 1, period 2, ...]\n * @param initialGuess - Starting guess for IRR (default: 0.1 = 10%)\n * @param maxIterations - Maximum iterations for convergence (default: 100)\n * @param tolerance - Convergence tolerance (default: 1e-6)\n * @returns Internal Rate of Return as decimal (e.g., 0.15 = 15% annual return)\n *\n * @example\n * ```typescript\n * // Investment: -1000€, returns +400€, +500€, +300€\n * const irr = calculateIRR([-1000, 400, 500, 300])\n * console.log(`${(irr * 100).toFixed(2)}%`) // ~16.17% annual return\n *\n * // Real-world: Compare to required return rate\n * const cashFlows = [-50000, 15000, 20000, 25000, 30000]\n * const irr = calculateIRR(cashFlows)\n * const requiredRate = 0.12 // 12% hurdle rate\n *\n * if (irr > requiredRate) {\n * console.log(`✅ IRR ${(irr*100).toFixed(1)}% exceeds ${(requiredRate*100)}% requirement`)\n * } else {\n * console.log(`❌ IRR ${(irr*100).toFixed(1)}% below ${(requiredRate*100)}% requirement`)\n * }\n * ```\n *\n * @see {@link calculateNPV} for net present value calculation\n * @see {@link https://en.wikipedia.org/wiki/Internal_rate_of_return IRR Formula}\n */\nexport const calculateIRR = (\n cashFlows: number[],\n initialGuess = 0.1,\n maxIterations = 100,\n tolerance = 1e-6\n): number => {\n let rate = initialGuess\n\n for (let i = 0; i < maxIterations; i++) {\n const npv = calculateNPV(cashFlows, rate)\n const npvDerivative = cashFlows.reduce((sum, cashFlow, index) => {\n return sum - (index * cashFlow) / Math.pow(1 + rate, index + 1)\n }, 0)\n\n if (Math.abs(npv) < tolerance) return rate\n if (Math.abs(npvDerivative) < tolerance) break\n\n rate = rate - npv / npvDerivative\n }\n\n return rate\n}\n\n/**\n * Calculates future value (FV) with compound interest\n *\n * Formula: FV = PV × (1 + r)ⁿ\n *\n * @param presentValue - Initial investment amount\n * @param interestRate - Interest rate per period as decimal (e.g., 0.05 for 5%)\n * @param periods - Number of compounding periods\n * @returns Future value after n periods\n *\n * @example\n * ```typescript\n * // 1000€ at 5% annual interest for 10 years\n * const fv = calculateFutureValue(1000, 0.05, 10) // 1628.89€\n *\n * // Retirement planning: 10000€ invested for 30 years at 7%\n * const retirement = calculateFutureValue(10000, 0.07, 30) // 76,122.55€\n * ```\n *\n * @see {@link calculatePresentValue}\n */\nexport const calculateFutureValue = (\n presentValue: number,\n interestRate: number,\n periods: number\n): number => {\n return presentValue * Math.pow(1 + interestRate, periods)\n}\n\n/**\n * Calculates present value (PV) of future amount\n *\n * Formula: PV = FV / (1 + r)ⁿ\n *\n * @param futureValue - Future amount to discount\n * @param interestRate - Discount rate as decimal (e.g., 0.05 for 5%)\n * @param periods - Number of periods\n * @returns Present value today\n *\n * @example\n * ```typescript\n * // What's 10000€ in 5 years worth today at 6% discount?\n * const pv = calculatePresentValue(10000, 0.06, 5) // 7,472.58€\n * ```\n *\n * @see {@link calculateFutureValue}\n */\nexport const calculatePresentValue = (\n futureValue: number,\n interestRate: number,\n periods: number\n): number => {\n return futureValue / Math.pow(1 + interestRate, periods)\n}\n\n/**\n * Calculates periodic payment for annuity (loans, mortgages)\n *\n * Formula: PMT = PV × [r(1+r)ⁿ] / [(1+r)ⁿ - 1]\n *\n * @param presentValue - Loan/mortgage principal amount\n * @param interestRate - Interest rate per period as decimal\n * @param periods - Total number of payment periods\n * @returns Periodic payment amount\n *\n * @example\n * ```typescript\n * // 200000€ mortgage at 3% annual for 30 years (360 months)\n * const monthlyRate = 0.03 / 12 // 0.0025\n * const payment = calculateAnnuityPayment(200000, monthlyRate, 360) // 843.21€/month\n *\n * // Car loan: 25000€ at 5% for 5 years\n * const carPayment = calculateAnnuityPayment(25000, 0.05/12, 60) // 471.78€/month\n * ```\n */\nexport const calculateAnnuityPayment = (\n presentValue: number,\n interestRate: number,\n periods: number\n): number => {\n if (interestRate === 0) return presentValue / periods\n\n return (\n (presentValue * (interestRate * Math.pow(1 + interestRate, periods))) /\n (Math.pow(1 + interestRate, periods) - 1)\n )\n}\n\n// =============================================================================\n// DATA VISUALIZATION UTILITIES\n// =============================================================================\n\n/**\n * Normalizes an array of numbers to [0, 1] range using min-max normalization\n *\n * Transforms values to a common scale where:\n * - Minimum value → 0\n * - Maximum value → 1\n * - Other values → proportional between 0 and 1\n *\n * Formula: normalized = (value - min) / (max - min)\n *\n * Use cases:\n * - Machine learning feature scaling\n * - Comparing datasets with different scales\n * - Data visualization (heatmaps, gradients)\n * - Neural network input preprocessing\n *\n * @param values - Array of numbers to normalize\n * @returns Array normalized to [0, 1] range (empty array returns [], all equal returns all 0s)\n *\n * @example\n * ```typescript\n * // Basic normalization\n * normalizeToRange([10, 20, 30, 40, 50])\n * // [0, 0.25, 0.5, 0.75, 1]\n *\n * normalizeToRange([100, 200, 150])\n * // [0, 1, 0.5]\n *\n * normalizeToRange([5, 5, 5])\n * // [0, 0, 0] (all equal, no range)\n *\n * normalizeToRange([])\n * // [] (empty array)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Student grade normalization for comparison\n * interface StudentScore {\n * name: string\n * rawScore: number\n * }\n *\n * function normalizeScores(students: StudentScore[]): void {\n * const rawScores = students.map(s => s.rawScore)\n * const normalized = normalizeToRange(rawScores)\n *\n * students.forEach((student, i) => {\n * const normalizedScore = (normalized[i] * 100).toFixed(1)\n * console.log(`${student.name}: ${student.rawScore} → ${normalizedScore}%`)\n * })\n * }\n *\n * normalizeScores([\n * { name: 'Alice', rawScore: 85 },\n * { name: 'Bob', rawScore: 72 },\n * { name: 'Charlie', rawScore: 95 }\n * ])\n * // Alice: 85 → 56.5%\n * // Bob: 72 → 0.0%\n * // Charlie: 95 → 100.0%\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Sensor data visualization (temperature heatmap)\n * function generateHeatmapColors(temperatures: number[]): string[] {\n * const normalized = normalizeToRange(temperatures)\n *\n * return normalized.map(value => {\n * const intensity = Math.round(value * 255)\n * return `rgb(${intensity}, 0, ${255 - intensity})`\n * })\n * }\n *\n * const temps = [18, 22, 25, 30, 35]\n * generateHeatmapColors(temps)\n * // ['rgb(0, 0, 255)', 'rgb(60, 0, 195)', ...]\n * // Blue (cold) → Red (hot)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: ML feature scaling for neural network\n * interface HouseData {\n * sqft: number // 500-5000\n * price: number // 100000-1000000\n * bedrooms: number // 1-6\n * }\n *\n * function prepareMLFeatures(houses: HouseData[]): number[][] {\n * const sqfts = normalizeToRange(houses.map(h => h.sqft))\n * const prices = normalizeToRange(houses.map(h => h.price))\n * const bedrooms = normalizeToRange(houses.map(h => h.bedrooms))\n *\n * return sqfts.map((_, i) => [sqfts[i], prices[i], bedrooms[i]])\n * }\n *\n * // All features now in [0, 1] range for training\n * ```\n *\n * @see {@link scaleToRange} for normalizing to custom [min, max] range\n * @see {@link calculateStandardDeviation} for z-score normalization alternative\n */\nexport const normalizeToRange = (values: number[]): number[] => {\n if (!values.length) return []\n\n const min = Math.min(...values)\n const max = Math.max(...values)\n const range = max - min\n\n if (range === 0) return values.map(() => 0)\n\n return values.map(value => (value - min) / range)\n}\n\n/**\n * Scales an array of numbers to a custom [min, max] range\n *\n * Transforms values to specified range while preserving proportions.\n * Uses min-max normalization internally, then scales to target range.\n *\n * Formula:\n * 1. Normalize to [0, 1]: norm = (value - min) / (max - min)\n * 2. Scale to [minRange, maxRange]: scaled = minRange + norm × (maxRange - minRange)\n *\n * Use cases:\n * - Image pixel values (0-255)\n * - Audio amplitude (-1 to 1)\n * - Progress bars (0-100%)\n * - Rating systems (1-5 stars)\n *\n * @param values - Array of numbers to scale\n * @param minRange - Target minimum value\n * @param maxRange - Target maximum value\n * @returns Array scaled to [minRange, maxRange]\n *\n * @example\n * ```typescript\n * // Basic scaling\n * scaleToRange([10, 20, 30, 40, 50], 0, 100)\n * // [0, 25, 50, 75, 100]\n *\n * scaleToRange([1, 2, 3, 4, 5], 1, 5)\n * // [1, 2, 3, 4, 5] (already in range)\n *\n * scaleToRange([0, 0.5, 1], 0, 255)\n * // [0, 127.5, 255] (grayscale pixel values)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Convert arbitrary scores to 1-5 star rating\n * function convertToStarRating(scores: number[]): number[] {\n * const stars = scaleToRange(scores, 1, 5)\n * return stars.map(s => Math.round(s * 2) / 2) // Round to 0.5\n * }\n *\n * const reviewScores = [45, 67, 89, 92, 100]\n * convertToStarRating(reviewScores)\n * // [1, 2.5, 4, 4.5, 5]\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Progress bar percentage\n * function calculateProgress(completedTasks: number[]): number[] {\n * return scaleToRange(completedTasks, 0, 100).map(p => Math.round(p))\n * }\n *\n * const tasksCompleted = [2, 5, 8, 10]\n * calculateProgress(tasksCompleted)\n * // [0, 38, 75, 100] (percentage progress)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Audio normalization to [-1, 1] range\n * function normalizeAudioSamples(samples: number[]): Float32Array {\n * const normalized = scaleToRange(samples, -1, 1)\n * return new Float32Array(normalized)\n * }\n *\n * const audioData = [0, 1000, 2000, 3000, 4000]\n * normalizeAudioSamples(audioData)\n * // Float32Array [-1, -0.5, 0, 0.5, 1]\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Sensor calibration to voltage range\n * interface SensorReading {\n * timestamp: Date\n * rawValue: number\n * }\n *\n * function calibrateSensor(readings: SensorReading[]): number[] {\n * const rawValues = readings.map(r => r.rawValue)\n * // Calibrate to 0-5V range\n * return scaleToRange(rawValues, 0, 5)\n * }\n *\n * const sensorData = [\n * { timestamp: new Date(), rawValue: 100 },\n * { timestamp: new Date(), rawValue: 300 },\n * { timestamp: new Date(), rawValue: 500 }\n * ]\n * calibrateSensor(sensorData)\n * // [0, 2.5, 5] volts\n * ```\n *\n * @see {@link normalizeToRange} for [0, 1] normalization\n */\nexport const scaleToRange = (values: number[], minRange: number, maxRange: number): number[] => {\n const normalized = normalizeToRange(values)\n const scale = maxRange - minRange\n\n return normalized.map(value => minRange + value * scale)\n}\n\n/**\n * Calculates histogram by dividing data into equal-width bins\n *\n * Groups values into intervals (bins) and counts frequency in each bin.\n * Useful for understanding data distribution, finding patterns, and visualization.\n *\n * Algorithm:\n * 1. Find min/max values\n * 2. Calculate bin width: (max - min) / bins\n * 3. Create bins with ranges [start, end)\n * 4. Count values in each bin\n * 5. Last bin includes max value (closed interval)\n *\n * @param values - Array of numbers to analyze\n * @param bins - Number of bins/intervals to divide data into\n * @returns Array of objects with {range: [min, max], count: frequency}\n *\n * @example\n * ```typescript\n * // Basic histogram\n * const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n * calculateHistogram(data, 5)\n * // [\n * // { range: [1, 2.8], count: 2 },\n * // { range: [2.8, 4.6], count: 2 },\n * // { range: [4.6, 6.4], count: 2 },\n * // { range: [6.4, 8.2], count: 2 },\n * // { range: [8.2, 10], count: 2 }\n * // ]\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Response time distribution analysis\n * function analyzeResponseTimes(times: number[]): void {\n * const histogram = calculateHistogram(times, 5)\n *\n * console.log('📊 Response Time Distribution:')\n * histogram.forEach(({ range, count }) => {\n * const bar = '█'.repeat(count)\n * console.log(`${range[0]}-${range[1]}ms: ${bar} (${count})`)\n * })\n *\n * // Identify slow requests\n * const slowBin = histogram[histogram.length - 1]\n * if (slowBin.count > histogram.length * 0.1) {\n * console.log(`⚠️ Warning: ${slowBin.count} requests in slowest bin`)\n * }\n * }\n *\n * const responseTimes = [\n * 120, 150, 180, 200, 220,\n * 250, 300, 350, 400, 5000\n * ]\n * analyzeResponseTimes(responseTimes)\n * // 120-1096ms: ████████ (8)\n * // ...\n * // 4024-5000ms: █ (1)\n * // ⚠️ Warning: 1 requests in slowest bin\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Salary distribution for HR analysis\n * interface Employee {\n * name: string\n * salary: number\n * }\n *\n * function analyzeSalaryDistribution(employees: Employee[]): void {\n * const salaries = employees.map(e => e.salary)\n * const histogram = calculateHistogram(salaries, 4)\n *\n * console.log('💰 Salary Distribution:')\n * histogram.forEach(({ range, count }, i) => {\n * const percentage = ((count / employees.length) * 100).toFixed(1)\n * console.log(`Band ${i + 1}: $${range[0].toLocaleString()}-$${range[1].toLocaleString()}`)\n * console.log(` Employees: ${count} (${percentage}%)`)\n * })\n * }\n *\n * const employees = [\n * { name: 'Alice', salary: 50000 },\n * { name: 'Bob', salary: 60000 },\n * { name: 'Charlie', salary: 75000 },\n * { name: 'David', salary: 90000 },\n * { name: 'Eve', salary: 120000 }\n * ]\n * analyzeSalaryDistribution(employees)\n * // Band 1: $50,000-$67,500 (2 employees - 40%)\n * // Band 2: $67,500-$85,000 (1 employee - 20%)\n * // ...\n * ```\n *\n * @see {@link calculatePercentile} for quantile analysis\n * @see {@link calculateQuartiles} for quartile-based binning\n */\nexport const calculateHistogram = (\n values: number[],\n bins: number\n): Array<{ range: [number, number]; count: number }> => {\n if (!values.length || bins <= 0) return []\n\n const min = Math.min(...values)\n const max = Math.max(...values)\n const binWidth = (max - min) / bins\n\n const histogram: Array<{ range: [number, number]; count: number }> = []\n\n for (let i = 0; i < bins; i++) {\n const rangeStart = min + i * binWidth\n const rangeEnd = i === bins - 1 ? max : rangeStart + binWidth\n\n const count = values.filter(\n value => value >= rangeStart && (i === bins - 1 ? value <= rangeEnd : value < rangeEnd)\n ).length\n\n histogram.push({\n range: [roundToDecimals(rangeStart, 2), roundToDecimals(rangeEnd, 2)],\n count,\n })\n }\n\n return histogram\n}\n\n// =============================================================================\n// BASIC MACHINE LEARNING UTILITIES\n// =============================================================================\n\n/**\n * Calculates Euclidean distance (straight-line distance) between two n-dimensional points\n *\n * Euclidean distance is the \"ordinary\" straight-line distance between two points.\n * Formula: √Σ(p1ᵢ - p2ᵢ)² for all dimensions\n *\n * Use cases:\n * - Machine learning (K-NN, K-Means clustering)\n * - Similarity measurement\n * - Computer vision (object detection)\n * - Geospatial calculations (2D/3D coordinates)\n *\n * @param point1 - First n-dimensional point [x, y, z, ...]\n * @param point2 - Second n-dimensional point [x, y, z, ...]\n * @returns Euclidean distance between the two points\n * @throws {Error} If points have different dimensions\n *\n * @example\n * ```typescript\n * // 2D distance (x, y coordinates)\n * calculateEuclideanDistance([0, 0], [3, 4]) // 5 (3-4-5 triangle)\n * calculateEuclideanDistance([1, 2], [4, 6]) // 5\n *\n * // 3D distance\n * calculateEuclideanDistance([0, 0, 0], [1, 1, 1]) // ~1.73 (√3)\n *\n * // High-dimensional (ML features)\n * calculateEuclideanDistance([1, 2, 3, 4], [2, 3, 4, 5]) // 2\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: K-Nearest Neighbors classification\n * interface User {\n * age: number\n * income: number\n * }\n *\n * function findNearestUsers(target: User, candidates: User[], k: number): User[] {\n * const distances = candidates.map(candidate => ({\n * user: candidate,\n * distance: calculateEuclideanDistance(\n * [target.age, target.income],\n * [candidate.age, candidate.income]\n * )\n * }))\n *\n * return distances\n * .sort((a, b) => a.distance - b.distance)\n * .slice(0, k)\n * .map(d => d.user)\n * }\n *\n * const target = { age: 30, income: 50000 }\n * const users = [\n * { age: 28, income: 48000 },\n * { age: 45, income: 80000 },\n * { age: 32, income: 52000 }\n * ]\n * findNearestUsers(target, users, 2)\n * // Returns 2 closest users by age/income\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Product recommendation similarity\n * interface Product {\n * price: number\n * rating: number\n * reviews: number\n * }\n *\n * function findSimilarProducts(\n * current: Product,\n * catalog: Product[]\n * ): Product[] {\n * const similarities = catalog.map(product => {\n * const distance = calculateEuclideanDistance(\n * [current.price, current.rating * 20, Math.log(current.reviews)],\n * [product.price, product.rating * 20, Math.log(product.reviews)]\n * )\n * return { product, similarity: 1 / (1 + distance) }\n * })\n *\n * return similarities\n * .sort((a, b) => b.similarity - a.similarity)\n * .slice(0, 5)\n * .map(s => s.product)\n * }\n * ```\n *\n * @see {@link calculateManhattanDistance} for taxicab distance (sum of absolute differences)\n * @see {@link simpleKMeans} for K-Means clustering using Euclidean distance\n */\nexport const calculateEuclideanDistance = (point1: number[], point2: number[]): number => {\n if (point1.length !== point2.length) {\n throw new Error('Points must have the same number of dimensions')\n }\n\n const squaredDiffs = point1.map((val, index) => Math.pow(val - point2[index], 2))\n return Math.sqrt(sum(squaredDiffs))\n}\n\n/**\n * Calculates Manhattan distance (taxicab/city block distance) between two n-dimensional points\n *\n * Manhattan distance is the sum of absolute differences in each dimension.\n * Formula: Σ|p1ᵢ - p2ᵢ| for all dimensions\n *\n * Named after Manhattan grid layout where you can only travel along streets (not diagonally).\n * Often more appropriate than Euclidean for grid-based movement or high-dimensional data.\n *\n * Use cases:\n * - Grid-based pathfinding (chess, robots)\n * - High-dimensional ML (less affected by curse of dimensionality)\n * - City distance calculations (street blocks)\n * - Outlier detection in sparse data\n *\n * @param point1 - First n-dimensional point [x, y, z, ...]\n * @param point2 - Second n-dimensional point [x, y, z, ...]\n * @returns Manhattan distance between the two points\n * @throws {Error} If points have different dimensions\n *\n * @example\n * ```typescript\n * // 2D Manhattan distance\n * calculateManhattanDistance([0, 0], [3, 4]) // 7 (3 + 4)\n * calculateManhattanDistance([1, 2], [4, 6]) // 7 (3 + 4)\n *\n * // Compare with Euclidean\n * calculateEuclideanDistance([0, 0], [3, 4]) // 5\n * calculateManhattanDistance([0, 0], [3, 4]) // 7\n *\n * // 3D distance\n * calculateManhattanDistance([0, 0, 0], [1, 1, 1]) // 3 (1+1+1)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: City block distance (taxi routing)\n * interface Location {\n * avenue: number // East-West\n * street: number // North-South\n * }\n *\n * function calculateTaxiFare(from: Location, to: Location): number {\n * const blocks = calculateManhattanDistance(\n * [from.avenue, from.street],\n * [to.avenue, to.street]\n * )\n * const baseFare = 3.50\n * const perBlock = 0.70\n * return baseFare + blocks * perBlock\n * }\n *\n * const start = { avenue: 5, street: 10 }\n * const end = { avenue: 12, street: 20 }\n * calculateTaxiFare(start, end)\n * // Blocks: 17, Fare: $15.40\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Chess king moves (Chebyshev) vs rook moves (Manhattan)\n * function calculateRookMoves(from: [number, number], to: [number, number]): number {\n * // Rook can move straight, so Manhattan distance = moves needed\n * return calculateManhattanDistance(from, to)\n * }\n *\n * calculateRookMoves([0, 0], [3, 4]) // 7 moves minimum\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: High-dimensional text similarity\n * function compareDocuments(doc1Vector: number[], doc2Vector: number[]): number {\n * // Manhattan often works better than Euclidean for sparse high-dim data\n * return calculateManhattanDistance(doc1Vector, doc2Vector)\n * }\n *\n * const doc1 = [0, 3, 0, 5, 0, 2] // Word frequency vector\n * const doc2 = [1, 2, 0, 4, 0, 3]\n * compareDocuments(doc1, doc2) // 5 (difference in word frequencies)\n * ```\n *\n * @see {@link calculateEuclideanDistance} for straight-line distance\n * @see {@link simpleKMeans} for clustering algorithm\n */\nexport const calculateManhattanDistance = (point1: number[], point2: number[]): number => {\n if (point1.length !== point2.length) {\n throw new Error('Points must have the same number of dimensions')\n }\n\n return sum(point1.map((val, index) => Math.abs(val - point2[index])))\n}\n\n/**\n * Implements simple K-Means clustering algorithm\n *\n * Groups data points into K clusters by minimizing within-cluster variance.\n * Uses Euclidean distance and iterative centroid updates until convergence.\n *\n * Algorithm:\n * 1. Initialize K centroids randomly from data points\n * 2. Assign each point to nearest centroid (Euclidean distance)\n * 3. Recalculate centroids as mean of assigned points\n * 4. Repeat steps 2-3 until convergence or max iterations\n *\n * Limitations:\n * - Requires knowing K in advance\n * - Sensitive to initial centroid placement (random)\n * - Assumes spherical clusters\n * - Can get stuck in local minima\n *\n * @param points - Array of n-dimensional points to cluster\n * @param k - Number of clusters to create\n * @param maxIterations - Maximum iterations before stopping (default: 100)\n * @returns Object with {centroids: K cluster centers, clusters: array mapping point index → cluster ID}\n *\n * @example\n * ```typescript\n * // Basic 2D clustering\n * const points = [\n * [1, 1], [2, 1], [1, 2], // Cluster 1 (bottom-left)\n * [8, 8], [9, 8], [8, 9] // Cluster 2 (top-right)\n * ]\n *\n * const { centroids, clusters } = simpleKMeans(points, 2)\n * console.log(centroids) // [[1.33, 1.33], [8.33, 8.33]]\n * console.log(clusters) // [0, 0, 0, 1, 1, 1]\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Customer segmentation\n * interface Customer {\n * age: number\n * income: number\n * spendingScore: number\n * }\n *\n * function segmentCustomers(customers: Customer[], segments: number) {\n * const points = customers.map(c => [c.age, c.income / 1000, c.spendingScore])\n * const { centroids, clusters } = simpleKMeans(points, segments)\n *\n * const segmented = customers.map((customer, i) => ({\n * ...customer,\n * segment: clusters[i]\n * }))\n *\n * console.log('📊 Customer Segments:')\n * centroids.forEach((centroid, i) => {\n * const count = clusters.filter(c => c === i).length\n * console.log(`Segment ${i + 1}: ${count} customers`)\n * console.log(` Avg age: ${centroid[0].toFixed(1)}`)\n * console.log(` Avg income: $${(centroid[1] * 1000).toLocaleString()}`)\n * console.log(` Avg spending: ${centroid[2].toFixed(1)}/100`)\n * })\n *\n * return segmented\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Image color quantization (reduce colors)\n * function quantizeColors(pixels: [number, number, number][], colors: number) {\n * // Each pixel is [R, G, B]\n * const { centroids, clusters } = simpleKMeans(pixels, colors)\n *\n * // Replace each pixel with its cluster centroid color\n * return pixels.map((_, i) => {\n * const cluster = clusters[i]\n * return centroids[cluster].map(Math.round) as [number, number, number]\n * })\n * }\n *\n * // Reduce 16M colors to 8 dominant colors\n * const pixels: [number, number, number][] = [\n * [255, 100, 50], [250, 110, 55], // Similar reds\n * [50, 200, 100], [55, 210, 105] // Similar greens\n * ]\n * quantizeColors(pixels, 2)\n * // Groups similar colors into 2 clusters\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Anomaly detection in server metrics\n * interface ServerMetrics {\n * cpuUsage: number // 0-100%\n * memoryUsage: number // 0-100%\n * responseTime: number // ms\n * }\n *\n * function detectAnomalies(metrics: ServerMetrics[]): number[] {\n * const points = metrics.map(m => [\n * m.cpuUsage,\n * m.memoryUsage,\n * m.responseTime / 10 // Scale to similar range\n * ])\n *\n * const { centroids, clusters } = simpleKMeans(points, 3)\n *\n * // Find cluster with highest avg response time\n * const clusterAvgs = centroids.map(c => c[2] * 10)\n * const anomalyCluster = clusterAvgs.indexOf(Math.max(...clusterAvgs))\n *\n * // Return indices of anomalous metrics\n * return clusters\n * .map((cluster, i) => cluster === anomalyCluster ? i : -1)\n * .filter(i => i !== -1)\n * }\n * ```\n *\n * @see {@link calculateEuclideanDistance} for distance metric used\n * @see {@link normalizeToRange} for feature scaling before clustering\n */\nexport const simpleKMeans = (\n points: number[][],\n k: number,\n maxIterations = 100\n): { centroids: number[][]; clusters: number[] } => {\n if (points.length === 0 || k <= 0) {\n return { centroids: [], clusters: [] }\n }\n\n const dimensions = points[0].length\n\n // Initialize centroids randomly\n let centroids: number[][] = []\n for (let i = 0; i < k; i++) {\n const randomPoint = points[Math.floor(Math.random() * points.length)]\n centroids.push([...randomPoint])\n }\n\n const clusters: number[] = new Array(points.length)\n\n for (let iteration = 0; iteration < maxIterations; iteration++) {\n let hasChanged = false\n\n // Assign points to clusters\n for (let i = 0; i < points.length; i++) {\n let minDistance = Infinity\n let closestCentroid = 0\n\n for (let j = 0; j < centroids.length; j++) {\n const distance = calculateEuclideanDistance(points[i], centroids[j])\n if (distance < minDistance) {\n minDistance = distance\n closestCentroid = j\n }\n }\n\n if (clusters[i] !== closestCentroid) {\n hasChanged = true\n clusters[i] = closestCentroid\n }\n }\n\n // Update centroids\n const newCentroids: number[][] = new Array(k).fill(0).map(() => new Array(dimensions).fill(0))\n const clusterCounts: number[] = new Array(k).fill(0)\n\n for (let i = 0; i < points.length; i++) {\n const cluster = clusters[i]\n clusterCounts[cluster]++\n\n for (let dim = 0; dim < dimensions; dim++) {\n newCentroids[cluster][dim] += points[i][dim]\n }\n }\n\n for (let i = 0; i < k; i++) {\n if (clusterCounts[i] > 0) {\n for (let dim = 0; dim < dimensions; dim++) {\n newCentroids[i][dim] /= clusterCounts[i]\n }\n }\n }\n\n centroids = newCentroids\n\n if (!hasChanged) break\n }\n\n return { centroids, clusters }\n}\n"],"mappings":";;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,YAAY,YAAY;AACxB,IAAM,EAAE,KAAK,OAAO,OAAO,KAAK,QAAQ,OAAO,MAAM,OAAO,MAAM,MAAM,KAAK,WAAW,QAAQ,IAC9F;AAsHK,IAAM,wBAAwB,CACnC,YACA,UACA,QACA,yBAAyB,SACtB;AACH,MAAI,OAAY,CAAC;AACjB,MAAI,UAAU,QAAQ;AACpB,UAAM,cAAc;AAAA,MAAQ;AAAA,MAAQ,UAClC,SAAS,IAAI,WAAS,IAAI,MAAM,KAAK,CAAC,EAAE,KAAK,WAAW;AAAA,IAC1D;AACA,WAAO,IAAI,aAAa,CAAC,OAAO,aAAa;AAC3C,YAAM,MAAW,UAAU,UAAU,SAAS,MAAM,WAAW,CAAC;AAChE,iBAAW,aAAa,YAAY;AAClC,cAAM,SAAS,WAAW,SAAS;AACnC,eAAO,QAAQ,WAAS;AACtB,gBAAM,cAAc,yBAAyB,QAAQ,YAAY;AACjE,kBAAQ,WAAW;AAAA,YACjB,KAAK;AACH,kBAAI,WAAW,IAAI,MAAM;AACzB;AAAA,YACF,KAAK;AACH,kBAAI,WAAW,IAAI,MAAM,OAAO,UAAQ,IAAI,MAAM,KAAK,CAAC;AACxD;AAAA,YACF,KAAK;AACH,kBAAI,WAAW,IAAI;AAAA,gBACjB,OAAO,OAAO,UAAQ,IAAI,MAAM,KAAK,CAAC;AAAA,gBACtC;AAAA,cACF;AACA;AAAA,YACF,KAAK,QAAQ;AACX,oBAAM,UAAU,MAAM,OAAO,UAAQ,IAAI,MAAM,KAAK,CAAC;AACrD,kBAAI,WAAW,IAAI,UAAU,IAAI,SAAS,KAAK,IAAI;AACnD;AAAA,YACF;AAAA,YACA,KAAK,QAAQ;AACX,oBAAM,UAAU,MAAM,OAAO,UAAQ,IAAI,MAAM,KAAK,CAAC;AACrD,kBAAI,WAAW,IAAI,UAAU,IAAI,SAAS,KAAK,IAAI;AACnD;AAAA,YACF;AAAA,YACA,KAAK;AACH,kBAAI,WAAW,IAAI,KAAK,OAAO,UAAQ,IAAI,MAAM,KAAK,CAAC,IAAI,IAAI;AAC/D;AAAA,YACF,KAAK;AACH,kBAAI,WAAW,IAAI;AAAA,gBACjB,MAAM,IAAI,UAAQ,IAAI,MAAM,KAAK,CAAC;AAAA,gBAClC,MAAM,IAAI,CAAC,OAAO,UAAU,QAAQ,CAAC;AAAA,cACvC;AACA;AAAA,YACF,KAAK;AACH,kBAAI,WAAW,IAAI,KAAK,MAAM,IAAI,UAAQ,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE;AAC7D;AAAA,UACJ;AAAA,QACF,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH,OAAO;AACL,UAAM,UAAqC,CAAC;AAC5C,eAAW,aAAa,YAAY;AAClC,YAAM,SAAS,WAAW,SAAS;AACnC,aAAO,QAAQ,WAAS;AACtB,cAAM,cAAc,yBAAyB,QAAQ,YAAY;AACjE,gBAAQ,WAAW;AAAA,UACjB,KAAK;AACH,oBAAQ,WAAW,IAAI,OAAO;AAC9B;AAAA,UACF,KAAK;AACH,oBAAQ,WAAW,IAAI,MAAM,QAAQ,KAAK;AAC1C;AAAA,UACF,KAAK;AACH,oBAAQ,WAAW,IAAI,MAAM,OAAO,QAAQ,KAAK,GAAG,CAAC;AACrD;AAAA,UACF,KAAK;AACH,gBAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,oBAAM,UAAU,MAAM,QAAQ,KAAK;AACnC,sBAAQ,WAAW,IAAI,UAAU,QAAQ,KAAK,IAAI;AAAA,YACpD;AACA;AAAA,UACF,KAAK;AACH,gBAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,oBAAM,UAAU,MAAM,QAAQ,KAAK;AACnC,sBAAQ,WAAW,IAAI,UAAU,QAAQ,KAAK,IAAI;AAAA,YACpD;AACA;AAAA,UACF,KAAK;AACH,oBAAQ,WAAW,IAAI,KAAK,QAAQ,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,IAAI;AAC7D;AAAA,UACF,KAAK;AACH,oBAAQ,WAAW,IAAI;AAAA,cACrB,OAAO,IAAI,UAAQ,IAAI,MAAM,KAAK,CAAC;AAAA,cACnC,OAAO,IAAI,CAAC,OAAO,UAAU,QAAQ,CAAC;AAAA,YACxC;AACA;AAAA,UACF,KAAK;AACH,oBAAQ,WAAW,IAAI,KAAK,OAAO,IAAI,UAAQ,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE;AAClE;AAAA,QACJ;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAoKO,SAAS,oBAAoB,GAAa,GAAa;AAC5D,QAAM,IAAI,KAAK,CAAC;AAChB,QAAM,QAAQ,IAAI,CAAC;AACnB,QAAM,QAAQ,IAAI,CAAC;AACnB,QAAM,SAAS,IAAI,IAAI,GAAG,SAAO,MAAM,GAAG,CAAC;AAC3C,QAAM,SAAS,IAAI,IAAI,GAAG,CAAC,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC;AAEjD,QAAM,cAAc,IAAI,SAAS,QAAQ;AACzC,MAAI,gBAAgB,GAAG;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,IAAI,SAAS,QAAQ,SAAS;AAC7C,SAAO,gBAAgB,OAAO,CAAC;AACjC;AAoFO,IAAM,kBAAkB,CAAC,WAA6B;AAC3D,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC/C,QAAM,SAAS,KAAK,MAAM,OAAO,SAAS,CAAC;AAE3C,MAAI,OAAO,SAAS,MAAM,GAAG;AAC3B,YAAQ,OAAO,SAAS,CAAC,IAAI,OAAO,MAAM,KAAK;AAAA,EACjD;AAEA,SAAO,OAAO,MAAM;AACtB;AA6IO,IAAM,gBAAgB,CAAC,WAAoC;AAChE,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,YAAuC,CAAC;AAC9C,MAAI,UAAU;AACd,MAAI,OAAsB;AAC1B,MAAI,mBAAmB;AAEvB,aAAW,SAAS,QAAQ;AAC1B,cAAU,KAAK,KAAK,UAAU,KAAK,KAAK,KAAK;AAE7C,QAAI,UAAU,KAAK,IAAI,SAAS;AAC9B,gBAAU,UAAU,KAAK;AACzB,aAAO;AACP,yBAAmB;AAAA,IACrB,WAAW,UAAU,KAAK,MAAM,WAAW,UAAU,MAAM;AACzD,yBAAmB;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,mBAAmB,OAAO;AACnC;AA4FO,IAAM,6BAA6B,CAAC,QAAkB,SAAS,UAAkB;AACtF,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,OAAO,OAAO,OAAO,CAACA,MAAK,QAAQA,OAAM,KAAK,CAAC,IAAI,OAAO;AAChE,QAAM,eAAe,OAAO,IAAI,SAAO,KAAK,IAAI,MAAM,MAAM,CAAC,CAAC;AAC9D,QAAM,WACJ,aAAa,OAAO,CAACA,MAAK,QAAQA,OAAM,KAAK,CAAC,KAAK,SAAS,OAAO,SAAS,IAAI,OAAO;AAEzF,SAAO,KAAK,KAAK,QAAQ;AAC3B;AA4JO,IAAM,oBAAoB,CAAC,QAAkB,SAAS,UAAkB;AAC7E,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,QAAM,OAAO,OAAO,OAAO,CAACA,MAAK,QAAQA,OAAM,KAAK,CAAC,IAAI,OAAO;AAChE,QAAM,eAAe,OAAO,IAAI,SAAO,KAAK,IAAI,MAAM,MAAM,CAAC,CAAC;AAE9D,SACE,aAAa,OAAO,CAACA,MAAK,QAAQA,OAAM,KAAK,CAAC,KAAK,SAAS,OAAO,SAAS,IAAI,OAAO;AAE3F;AAmKO,IAAM,sBAAsB,CAAC,QAAkB,eAA+B;AACnF,MAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,MAAI,aAAa,KAAK,aAAa,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAE9F,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC/C,QAAM,QAAS,aAAa,OAAQ,OAAO,SAAS;AAEpD,MAAI,KAAK,MAAM,KAAK,MAAM,OAAO;AAC/B,WAAO,OAAO,KAAK;AAAA,EACrB;AAEA,QAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,CAAC;AACtC,QAAM,QAAQ,OAAO,KAAK,KAAK,KAAK,CAAC;AACrC,QAAM,SAAS,QAAQ,KAAK,MAAM,KAAK;AAEvC,SAAO,SAAS,IAAI,UAAU,QAAQ;AACxC;AAwEO,IAAM,qBAAqB,CAAC,WAA6D;AAC9F,SAAO;AAAA,IACL,IAAI,oBAAoB,QAAQ,EAAE;AAAA,IAClC,IAAI,oBAAoB,QAAQ,EAAE;AAAA;AAAA,IAClC,IAAI,oBAAoB,QAAQ,EAAE;AAAA,EACpC;AACF;AA8EO,IAAM,eAAe,CAAC,WAA6B;AACxD,QAAM,YAAY,mBAAmB,MAAM;AAC3C,SAAO,UAAU,KAAK,UAAU;AAClC;AAmIO,IAAM,iBAAiB,CAAC,QAAkB,aAAa,QAAkB;AAC9E,QAAM,YAAY,mBAAmB,MAAM;AAC3C,QAAM,MAAM,UAAU,KAAK,UAAU;AACrC,QAAM,aAAa,UAAU,KAAK,aAAa;AAC/C,QAAM,aAAa,UAAU,KAAK,aAAa;AAE/C,SAAO,OAAO,OAAO,WAAS,QAAQ,cAAc,QAAQ,UAAU;AACxE;AA8HO,IAAM,uBAAuB,CAAC,GAAa,MAAwB;AACxE,MAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAG,QAAO;AAEpD,QAAM,IAAI,EAAE;AACZ,QAAM,OAAO,IAAI,CAAC;AAClB,QAAM,OAAO,IAAI,CAAC;AAClB,QAAM,QAAQ,IAAI,IAAI,GAAG,CAAC,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC;AAChD,QAAM,QAAQ,IAAI,IAAI,GAAG,SAAO,MAAM,GAAG,CAAC;AAC1C,QAAM,QAAQ,IAAI,IAAI,GAAG,SAAO,MAAM,GAAG,CAAC;AAE1C,QAAM,YAAY,IAAI,QAAQ,OAAO;AACrC,QAAM,cAAc,KAAK,MAAM,IAAI,QAAQ,OAAO,SAAS,IAAI,QAAQ,OAAO,KAAK;AAEnF,MAAI,gBAAgB,EAAG,QAAO;AAE9B,SAAO,YAAY;AACrB;AA6CO,IAAM,eAAe,CAAC,WAAqB,iBAAiC;AACjF,SAAO,UAAU,OAAO,CAAC,KAAK,UAAU,UAAU;AAGhD,UAAM,iBAAiB,UAAU,IAAI,IAAI,KAAK,IAAI,IAAI,cAAc,KAAK;AACzE,WAAO,MAAM,WAAW;AAAA,EAC1B,GAAG,CAAC;AACN;AAuCO,IAAM,eAAe,CAC1B,WACA,eAAe,KACf,gBAAgB,KAChB,YAAY,SACD;AACX,MAAI,OAAO;AAEX,WAAS,IAAI,GAAG,IAAI,eAAe,KAAK;AACtC,UAAM,MAAM,aAAa,WAAW,IAAI;AACxC,UAAM,gBAAgB,UAAU,OAAO,CAACA,MAAK,UAAU,UAAU;AAC/D,aAAOA,OAAO,QAAQ,WAAY,KAAK,IAAI,IAAI,MAAM,QAAQ,CAAC;AAAA,IAChE,GAAG,CAAC;AAEJ,QAAI,KAAK,IAAI,GAAG,IAAI,UAAW,QAAO;AACtC,QAAI,KAAK,IAAI,aAAa,IAAI,UAAW;AAEzC,WAAO,OAAO,MAAM;AAAA,EACtB;AAEA,SAAO;AACT;AAuBO,IAAM,uBAAuB,CAClC,cACA,cACA,YACW;AACX,SAAO,eAAe,KAAK,IAAI,IAAI,cAAc,OAAO;AAC1D;AAoBO,IAAM,wBAAwB,CACnC,aACA,cACA,YACW;AACX,SAAO,cAAc,KAAK,IAAI,IAAI,cAAc,OAAO;AACzD;AAsBO,IAAM,0BAA0B,CACrC,cACA,cACA,YACW;AACX,MAAI,iBAAiB,EAAG,QAAO,eAAe;AAE9C,SACG,gBAAgB,eAAe,KAAK,IAAI,IAAI,cAAc,OAAO,MACjE,KAAK,IAAI,IAAI,cAAc,OAAO,IAAI;AAE3C;AA8GO,IAAM,mBAAmB,CAAC,WAA+B;AAC9D,MAAI,CAAC,OAAO,OAAQ,QAAO,CAAC;AAE5B,QAAM,MAAM,KAAK,IAAI,GAAG,MAAM;AAC9B,QAAM,MAAM,KAAK,IAAI,GAAG,MAAM;AAC9B,QAAM,QAAQ,MAAM;AAEpB,MAAI,UAAU,EAAG,QAAO,OAAO,IAAI,MAAM,CAAC;AAE1C,SAAO,OAAO,IAAI,YAAU,QAAQ,OAAO,KAAK;AAClD;AAmGO,IAAM,eAAe,CAAC,QAAkB,UAAkB,aAA+B;AAC9F,QAAM,aAAa,iBAAiB,MAAM;AAC1C,QAAM,QAAQ,WAAW;AAEzB,SAAO,WAAW,IAAI,WAAS,WAAW,QAAQ,KAAK;AACzD;AAmGO,IAAM,qBAAqB,CAChC,QACA,SACsD;AACtD,MAAI,CAAC,OAAO,UAAU,QAAQ,EAAG,QAAO,CAAC;AAEzC,QAAM,MAAM,KAAK,IAAI,GAAG,MAAM;AAC9B,QAAM,MAAM,KAAK,IAAI,GAAG,MAAM;AAC9B,QAAM,YAAY,MAAM,OAAO;AAE/B,QAAM,YAA+D,CAAC;AAEtE,WAAS,IAAI,GAAG,IAAI,MAAM,KAAK;AAC7B,UAAM,aAAa,MAAM,IAAI;AAC7B,UAAM,WAAW,MAAM,OAAO,IAAI,MAAM,aAAa;AAErD,UAAM,QAAQ,OAAO;AAAA,MACnB,WAAS,SAAS,eAAe,MAAM,OAAO,IAAI,SAAS,WAAW,QAAQ;AAAA,IAChF,EAAE;AAEF,cAAU,KAAK;AAAA,MACb,OAAO,CAAC,gBAAgB,YAAY,CAAC,GAAG,gBAAgB,UAAU,CAAC,CAAC;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAoGO,IAAM,6BAA6B,CAAC,QAAkB,WAA6B;AACxF,MAAI,OAAO,WAAW,OAAO,QAAQ;AACnC,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AAEA,QAAM,eAAe,OAAO,IAAI,CAAC,KAAK,UAAU,KAAK,IAAI,MAAM,OAAO,KAAK,GAAG,CAAC,CAAC;AAChF,SAAO,KAAK,KAAK,IAAI,YAAY,CAAC;AACpC;AAuFO,IAAM,6BAA6B,CAAC,QAAkB,WAA6B;AACxF,MAAI,OAAO,WAAW,OAAO,QAAQ;AACnC,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AAEA,SAAO,IAAI,OAAO,IAAI,CAAC,KAAK,UAAU,KAAK,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC,CAAC;AACtE;AA4HO,IAAM,eAAe,CAC1B,QACA,GACA,gBAAgB,QACkC;AAClD,MAAI,OAAO,WAAW,KAAK,KAAK,GAAG;AACjC,WAAO,EAAE,WAAW,CAAC,GAAG,UAAU,CAAC,EAAE;AAAA,EACvC;AAEA,QAAM,aAAa,OAAO,CAAC,EAAE;AAG7B,MAAI,YAAwB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,cAAc,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,OAAO,MAAM,CAAC;AACpE,cAAU,KAAK,CAAC,GAAG,WAAW,CAAC;AAAA,EACjC;AAEA,QAAM,WAAqB,IAAI,MAAM,OAAO,MAAM;AAElD,WAAS,YAAY,GAAG,YAAY,eAAe,aAAa;AAC9D,QAAI,aAAa;AAGjB,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAI,cAAc;AAClB,UAAI,kBAAkB;AAEtB,eAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,cAAM,WAAW,2BAA2B,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC;AACnE,YAAI,WAAW,aAAa;AAC1B,wBAAc;AACd,4BAAkB;AAAA,QACpB;AAAA,MACF;AAEA,UAAI,SAAS,CAAC,MAAM,iBAAiB;AACnC,qBAAa;AACb,iBAAS,CAAC,IAAI;AAAA,MAChB;AAAA,IACF;AAGA,UAAM,eAA2B,IAAI,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,IAAI,MAAM,IAAI,MAAM,UAAU,EAAE,KAAK,CAAC,CAAC;AAC7F,UAAM,gBAA0B,IAAI,MAAM,CAAC,EAAE,KAAK,CAAC;AAEnD,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,UAAU,SAAS,CAAC;AAC1B,oBAAc,OAAO;AAErB,eAAS,MAAM,GAAG,MAAM,YAAY,OAAO;AACzC,qBAAa,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,EAAE,GAAG;AAAA,MAC7C;AAAA,IACF;AAEA,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAI,cAAc,CAAC,IAAI,GAAG;AACxB,iBAAS,MAAM,GAAG,MAAM,YAAY,OAAO;AACzC,uBAAa,CAAC,EAAE,GAAG,KAAK,cAAc,CAAC;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AAEA,gBAAY;AAEZ,QAAI,CAAC,WAAY;AAAA,EACnB;AAEA,SAAO,EAAE,WAAW,SAAS;AAC/B;","names":["sum"]}
|