@bgord/tools 0.14.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/age.vo.d.ts +10 -6
- package/dist/age.vo.js +22 -14
- package/dist/api-key.vo.d.ts +3 -0
- package/dist/api-key.vo.js +7 -1
- package/dist/clock-format.service.d.ts +9 -0
- package/dist/clock-format.service.js +10 -0
- package/dist/clock.vo.d.ts +7 -14
- package/dist/clock.vo.js +25 -34
- package/dist/height.vo.d.ts +7 -7
- package/dist/height.vo.js +6 -6
- package/dist/hour-format.service.d.ts +10 -0
- package/dist/hour-format.service.js +19 -0
- package/dist/hour.vo.d.ts +8 -17
- package/dist/hour.vo.js +22 -39
- package/dist/iban-mask.service.js +6 -3
- package/dist/iban.vo.d.ts +6 -4
- package/dist/iban.vo.js +7 -5
- package/dist/image.vo.d.ts +8 -2
- package/dist/image.vo.js +14 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/language.vo.d.ts +4 -1
- package/dist/language.vo.js +5 -3
- package/dist/mean.service.d.ts +4 -2
- package/dist/mean.service.js +9 -5
- package/dist/min-max-scaler.service.d.ts +22 -14
- package/dist/min-max-scaler.service.js +16 -15
- package/dist/minute.vo.d.ts +5 -6
- package/dist/minute.vo.js +14 -14
- package/dist/money.vo.d.ts +13 -2
- package/dist/money.vo.js +18 -13
- package/dist/outlier-detector.service.d.ts +2 -1
- package/dist/outlier-detector.service.js +5 -3
- package/dist/percentage.service.d.ts +3 -2
- package/dist/percentage.service.js +3 -2
- package/dist/population-standard-deviation.service.d.ts +3 -2
- package/dist/population-standard-deviation.service.js +5 -4
- package/dist/random.service.d.ts +6 -0
- package/dist/random.service.js +12 -6
- package/dist/rounding.adapter.d.ts +16 -0
- package/dist/{rounding.service.js → rounding.adapter.js} +8 -7
- package/dist/rounding.port.d.ts +3 -0
- package/dist/rounding.port.js +1 -0
- package/dist/simple-linear-regression.service.d.ts +11 -4
- package/dist/simple-linear-regression.service.js +39 -30
- package/dist/size.vo.js +1 -1
- package/dist/stopwatch.service.d.ts +1 -1
- package/dist/stopwatch.service.js +3 -4
- package/dist/sum.service.d.ts +2 -1
- package/dist/sum.service.js +11 -0
- package/dist/time.service.js +1 -1
- package/dist/timestamp.vo.d.ts +3 -0
- package/dist/timestamp.vo.js +7 -1
- package/dist/timezone.vo.d.ts +3 -0
- package/dist/timezone.vo.js +4 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/visually-unambiguous-characters-generator.service.js +0 -1
- package/dist/weight.vo.d.ts +4 -4
- package/dist/weight.vo.js +1 -1
- package/dist/z-score.service.d.ts +3 -2
- package/dist/z-score.service.js +3 -2
- package/package.json +2 -2
- package/readme.md +4 -2
- package/src/age.vo.ts +24 -14
- package/src/api-key.vo.ts +9 -1
- package/src/clock-format.service.ts +15 -0
- package/src/clock.vo.ts +24 -43
- package/src/height.vo.ts +12 -11
- package/src/hour-format.service.ts +21 -0
- package/src/hour.vo.ts +24 -47
- package/src/iban-mask.service.ts +8 -3
- package/src/iban.vo.ts +10 -8
- package/src/image.vo.ts +19 -4
- package/src/index.ts +2 -2
- package/src/language.vo.ts +7 -3
- package/src/mean.service.ts +13 -5
- package/src/min-max-scaler.service.ts +39 -24
- package/src/minute.vo.ts +18 -15
- package/src/money.vo.ts +26 -23
- package/src/outlier-detector.service.ts +6 -4
- package/src/percentage.service.ts +6 -7
- package/src/population-standard-deviation.service.ts +8 -5
- package/src/random.service.ts +16 -8
- package/src/relative-date.vo.ts +0 -1
- package/src/rounding.adapter.ts +33 -0
- package/src/rounding.port.ts +3 -0
- package/src/simple-linear-regression.service.ts +41 -31
- package/src/size.vo.ts +1 -1
- package/src/stopwatch.service.ts +4 -6
- package/src/sum.service.ts +15 -1
- package/src/time.service.ts +1 -1
- package/src/timestamp.vo.ts +9 -1
- package/src/timezone.vo.ts +15 -15
- package/src/visually-unambiguous-characters-generator.service.ts +0 -1
- package/src/weight.vo.ts +5 -4
- package/src/z-score.service.ts +6 -3
- package/dist/dates-of-the-week.vo.d.ts +0 -9
- package/dist/dates-of-the-week.vo.js +0 -10
- package/dist/rounding.service.d.ts +0 -17
- package/src/dates-of-the-week.vo.ts +0 -9
- package/src/rounding.service.ts +0 -31
package/src/image.vo.ts
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
export
|
|
3
|
+
export const ImageWidthError = { error: "invalid.image.width" } as const;
|
|
4
|
+
export const ImageHeightError = { error: "invalid.image.height" } as const;
|
|
5
5
|
|
|
6
|
-
export const
|
|
7
|
-
|
|
6
|
+
export const ImageWidth = z
|
|
7
|
+
.number(ImageWidthError)
|
|
8
|
+
.int(ImageWidthError)
|
|
9
|
+
.positive(ImageWidthError)
|
|
10
|
+
.max(10_000, ImageWidthError)
|
|
11
|
+
.brand("image-width");
|
|
12
|
+
|
|
13
|
+
export type ImageWidthType = z.infer<typeof ImageWidth>;
|
|
14
|
+
|
|
15
|
+
export const ImageHeight = z
|
|
16
|
+
.number(ImageHeightError)
|
|
17
|
+
.int(ImageHeightError)
|
|
18
|
+
.positive(ImageHeightError)
|
|
19
|
+
.max(10_000, ImageHeightError)
|
|
20
|
+
.brand("image-height");
|
|
21
|
+
|
|
22
|
+
export type ImageHeightType = z.infer<typeof ImageHeight>;
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,6 @@ export * from "./clock.vo";
|
|
|
5
5
|
export * from "./date-calculator.service";
|
|
6
6
|
export * from "./date-formatter.service";
|
|
7
7
|
export * from "./date-range.vo";
|
|
8
|
-
export * from "./dates-of-the-week.vo";
|
|
9
8
|
export * from "./day.vo";
|
|
10
9
|
export * from "./day-iso-id.vo";
|
|
11
10
|
export * from "./directory-path-absolute.vo";
|
|
@@ -50,7 +49,8 @@ export * from "./rate-limiter.service";
|
|
|
50
49
|
export * from "./relative-date.vo";
|
|
51
50
|
export * from "./reordering.service";
|
|
52
51
|
export * from "./revision.vo";
|
|
53
|
-
export * from "./rounding.
|
|
52
|
+
export * from "./rounding.adapter";
|
|
53
|
+
export * from "./rounding.port";
|
|
54
54
|
export * from "./simple-linear-regression.service";
|
|
55
55
|
export * from "./size.vo";
|
|
56
56
|
export * from "./stepper.service";
|
package/src/language.vo.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
2
|
|
|
3
|
+
export const LanguageError = { error: "invalid.language" } as const;
|
|
4
|
+
|
|
3
5
|
export const Language = z
|
|
4
|
-
.string()
|
|
5
|
-
.length(2)
|
|
6
|
-
.regex(/^[a-z]{2}$/,
|
|
6
|
+
.string(LanguageError)
|
|
7
|
+
.length(2, LanguageError)
|
|
8
|
+
.regex(/^[a-z]{2}$/, LanguageError)
|
|
9
|
+
.brand("Language");
|
|
10
|
+
|
|
7
11
|
export type LanguageType = z.infer<typeof Language>;
|
package/src/mean.service.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RoundToDecimal } from "./rounding.adapter";
|
|
2
|
+
import type { RoundingPort } from "./rounding.port";
|
|
2
3
|
import { Sum } from "./sum.service";
|
|
3
4
|
|
|
5
|
+
export const MeanEmptyValuesError = "mean.values.empty" as const;
|
|
6
|
+
|
|
4
7
|
export class Mean {
|
|
5
|
-
static
|
|
6
|
-
|
|
8
|
+
private static readonly DEFAULT_ROUNDING: RoundingPort = new RoundToDecimal(2);
|
|
9
|
+
|
|
10
|
+
static calculate(values: number[], rounding?: RoundingPort): number {
|
|
11
|
+
if (values.length === 0) throw new Error(MeanEmptyValuesError);
|
|
12
|
+
|
|
13
|
+
const sum = Sum.of(values);
|
|
14
|
+
const mean = sum / values.length;
|
|
7
15
|
|
|
8
|
-
const
|
|
16
|
+
const chosen = rounding ?? Mean.DEFAULT_ROUNDING;
|
|
9
17
|
|
|
10
|
-
return
|
|
18
|
+
return chosen.round(mean);
|
|
11
19
|
}
|
|
12
20
|
}
|
|
@@ -1,50 +1,66 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RoundToDecimal } from "./rounding.adapter";
|
|
2
|
+
import type { RoundingPort } from "./rounding.port";
|
|
2
3
|
|
|
3
4
|
type MinMaxScalerValueType = number;
|
|
5
|
+
|
|
4
6
|
type MinMaxScalerConfigType = {
|
|
5
7
|
min: MinMaxScalerValueType;
|
|
6
8
|
max: MinMaxScalerValueType;
|
|
7
9
|
bound?: { lower: MinMaxScalerValueType; upper: MinMaxScalerValueType };
|
|
8
|
-
rounding?:
|
|
10
|
+
rounding?: RoundingPort;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const MinMaxInvalidMinMaxError = "minmax.invalid.minmax" as const;
|
|
14
|
+
export const MinMaxInvalidBoundError = "minmax.invalid.bound" as const;
|
|
15
|
+
export const MinMaxValueOutOfRangeError = "minmax.value.out.of.range" as const;
|
|
16
|
+
export const MinMaxScaledOutOfBoundsError = "minmax.scaled.out.of.bounds" as const;
|
|
17
|
+
export const MinMaxEmptyArrayError = "minmax.empty.array" as const;
|
|
18
|
+
|
|
19
|
+
type ScaleResult = {
|
|
20
|
+
original: MinMaxScalerValueType;
|
|
21
|
+
scaled: MinMaxScalerValueType;
|
|
22
|
+
isMin: boolean;
|
|
23
|
+
isMax: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type DescaleResult = {
|
|
27
|
+
original: MinMaxScalerValueType;
|
|
28
|
+
scaled: MinMaxScalerValueType;
|
|
29
|
+
isLowerBound: boolean;
|
|
30
|
+
isUpperBound: boolean;
|
|
9
31
|
};
|
|
10
32
|
|
|
11
33
|
export class MinMaxScaler {
|
|
34
|
+
private static readonly DEFAULT_ROUNDING: RoundingPort = new RoundToDecimal(2);
|
|
35
|
+
|
|
12
36
|
private readonly min: MinMaxScalerValueType;
|
|
13
37
|
private readonly max: MinMaxScalerValueType;
|
|
14
38
|
private readonly lower: MinMaxScalerValueType;
|
|
15
39
|
private readonly upper: MinMaxScalerValueType;
|
|
16
|
-
|
|
17
|
-
private readonly rounding: RoundingStrategy;
|
|
40
|
+
private readonly rounding: RoundingPort;
|
|
18
41
|
|
|
19
42
|
constructor(config: MinMaxScalerConfigType) {
|
|
20
|
-
const rounding = config.rounding ?? new RoundToDecimal(2);
|
|
21
|
-
|
|
22
43
|
const lower = config.bound?.lower ?? 0;
|
|
23
44
|
const upper = config.bound?.upper ?? 1;
|
|
24
45
|
|
|
25
|
-
if (config.max - config.min < 0) throw new Error(
|
|
26
|
-
if (upper - lower <= 0) throw new Error(
|
|
27
|
-
|
|
28
|
-
this.rounding = rounding;
|
|
46
|
+
if (config.max - config.min < 0) throw new Error(MinMaxInvalidMinMaxError);
|
|
47
|
+
if (upper - lower <= 0) throw new Error(MinMaxInvalidBoundError);
|
|
29
48
|
|
|
49
|
+
this.rounding = config.rounding ?? MinMaxScaler.DEFAULT_ROUNDING;
|
|
30
50
|
this.min = config.min;
|
|
31
51
|
this.max = config.max;
|
|
32
52
|
this.lower = lower;
|
|
33
53
|
this.upper = upper;
|
|
34
54
|
}
|
|
35
55
|
|
|
36
|
-
scale(value: MinMaxScalerValueType) {
|
|
56
|
+
scale(value: MinMaxScalerValueType): ScaleResult {
|
|
37
57
|
const { min, max, lower, upper } = this;
|
|
38
58
|
|
|
39
|
-
if (value < min || value > max) throw new Error(
|
|
59
|
+
if (value < min || value > max) throw new Error(MinMaxValueOutOfRangeError);
|
|
40
60
|
|
|
41
|
-
if (min === max)
|
|
42
|
-
return {
|
|
43
|
-
|
|
44
|
-
scaled: (lower + upper) / 2,
|
|
45
|
-
isMin: value === min,
|
|
46
|
-
isMax: value === max,
|
|
47
|
-
};
|
|
61
|
+
if (min === max) {
|
|
62
|
+
return { original: value, scaled: (lower + upper) / 2, isMin: value === min, isMax: value === max };
|
|
63
|
+
}
|
|
48
64
|
|
|
49
65
|
const result = ((value - min) / (max - min)) * (upper - lower) + lower;
|
|
50
66
|
|
|
@@ -56,10 +72,10 @@ export class MinMaxScaler {
|
|
|
56
72
|
};
|
|
57
73
|
}
|
|
58
74
|
|
|
59
|
-
descale(scaled: MinMaxScalerValueType) {
|
|
75
|
+
descale(scaled: MinMaxScalerValueType): DescaleResult {
|
|
60
76
|
const { min, max, lower, upper } = this;
|
|
61
77
|
|
|
62
|
-
if (scaled < lower || scaled > upper) throw new Error(
|
|
78
|
+
if (scaled < lower || scaled > upper) throw new Error(MinMaxScaledOutOfBoundsError);
|
|
63
79
|
|
|
64
80
|
const result = ((scaled - lower) / (upper - lower)) * (max - min) + min;
|
|
65
81
|
|
|
@@ -71,9 +87,8 @@ export class MinMaxScaler {
|
|
|
71
87
|
};
|
|
72
88
|
}
|
|
73
89
|
|
|
74
|
-
static getMinMax(values: MinMaxScalerValueType[]) {
|
|
75
|
-
if (values.length === 0) throw new Error(
|
|
76
|
-
|
|
90
|
+
static getMinMax(values: MinMaxScalerValueType[]): { min: number; max: number } {
|
|
91
|
+
if (values.length === 0) throw new Error(MinMaxEmptyArrayError);
|
|
77
92
|
return { min: Math.min(...values), max: Math.max(...values) };
|
|
78
93
|
}
|
|
79
94
|
}
|
package/src/minute.vo.ts
CHANGED
|
@@ -1,42 +1,45 @@
|
|
|
1
1
|
import type { TimestampType } from "./timestamp.vo";
|
|
2
2
|
|
|
3
|
+
export const MinuteValueError = "invalid.minute" as const;
|
|
4
|
+
|
|
3
5
|
export class Minute {
|
|
4
6
|
private readonly value: number;
|
|
5
7
|
|
|
6
8
|
static readonly ZERO = new Minute(0);
|
|
7
|
-
|
|
8
9
|
static readonly MAX = new Minute(59);
|
|
9
10
|
|
|
10
11
|
constructor(candidate: number) {
|
|
11
|
-
if (!Number.isInteger(candidate)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (!Number.isInteger(candidate) || candidate < 0 || candidate >= 60) {
|
|
13
|
+
throw new Error(MinuteValueError);
|
|
14
|
+
}
|
|
15
15
|
this.value = candidate;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
static
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
static fromEpochMs(timestamp: TimestampType): Minute {
|
|
19
|
+
return new Minute(new Date(timestamp).getUTCMinutes());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get(): number {
|
|
23
|
+
return this.value;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
return
|
|
26
|
+
toString(): string {
|
|
27
|
+
return this.value.toString().padStart(2, "0");
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
equals(another: Minute): boolean {
|
|
28
|
-
return this.value === another.
|
|
31
|
+
return this.value === another.value;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
isAfter(another: Minute): boolean {
|
|
32
|
-
return this.value > another.
|
|
35
|
+
return this.value > another.value;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
isBefore(another: Minute): boolean {
|
|
36
|
-
return this.value < another.
|
|
39
|
+
return this.value < another.value;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
static list() {
|
|
40
|
-
return Array.from({ length: 60 }
|
|
42
|
+
static list(): readonly Minute[] {
|
|
43
|
+
return Array.from({ length: 60 }, (_, index) => new Minute(index));
|
|
41
44
|
}
|
|
42
45
|
}
|
package/src/money.vo.ts
CHANGED
|
@@ -1,66 +1,72 @@
|
|
|
1
1
|
import { z } from "zod/v4";
|
|
2
|
-
import {
|
|
2
|
+
import { RoundToNearest } from "./rounding.adapter";
|
|
3
|
+
import type { RoundingPort } from "./rounding.port";
|
|
4
|
+
|
|
5
|
+
export const MoneyAmountInvalidError = { error: "money.amount.invalid" } as const;
|
|
6
|
+
export const MoneyMultiplicationFactorInvalidError = {
|
|
7
|
+
error: "money.multiplication-factor.invalid",
|
|
8
|
+
} as const;
|
|
9
|
+
export const MoneyDivisionFactorInvalidError = { error: "money.division-factor.invalid" } as const;
|
|
10
|
+
export const MoneySubtractLessThanZeroError = "money.subtract.less.than.zero" as const;
|
|
3
11
|
|
|
4
12
|
export const MoneyAmount = z
|
|
5
|
-
.number()
|
|
6
|
-
.int(
|
|
7
|
-
.min(0,
|
|
13
|
+
.number(MoneyAmountInvalidError)
|
|
14
|
+
.int(MoneyAmountInvalidError)
|
|
15
|
+
.min(0, MoneyAmountInvalidError)
|
|
8
16
|
.brand("MoneyAmount");
|
|
9
17
|
|
|
10
18
|
export type MoneyAmountType = z.infer<typeof MoneyAmount>;
|
|
11
19
|
|
|
12
20
|
export const MoneyMultiplicationFactor = z
|
|
13
|
-
.number()
|
|
14
|
-
.min(0,
|
|
21
|
+
.number(MoneyMultiplicationFactorInvalidError)
|
|
22
|
+
.min(0, MoneyMultiplicationFactorInvalidError)
|
|
15
23
|
.brand("MoneyMultiplicationFactor");
|
|
16
24
|
|
|
17
25
|
export type MoneyMultiplicationFactorType = z.infer<typeof MoneyMultiplicationFactor>;
|
|
18
26
|
|
|
19
27
|
export const MoneyDivisionFactor = z
|
|
20
|
-
.number()
|
|
21
|
-
.
|
|
22
|
-
.refine((value) => value !== 0, { message: "money.division-factor.invalid" })
|
|
28
|
+
.number(MoneyDivisionFactorInvalidError)
|
|
29
|
+
.gt(0, MoneyDivisionFactorInvalidError)
|
|
23
30
|
.brand("MoneyDivisionFactor");
|
|
24
31
|
|
|
25
32
|
export type MoneyDivisionFactorType = z.infer<typeof MoneyDivisionFactor>;
|
|
26
33
|
|
|
27
34
|
export class Money {
|
|
28
35
|
private static readonly ZERO = 0;
|
|
36
|
+
private static readonly DEFAULT_ROUNDING: RoundingPort = new RoundToNearest();
|
|
29
37
|
|
|
30
38
|
private readonly amount: MoneyAmountType;
|
|
39
|
+
private readonly rounding: RoundingPort;
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
constructor(value: number = Money.ZERO, rounding?: RoundingStrategy) {
|
|
41
|
+
constructor(value: number = Money.ZERO, rounding?: RoundingPort) {
|
|
35
42
|
this.amount = MoneyAmount.parse(value);
|
|
36
|
-
this.rounding = rounding ??
|
|
43
|
+
this.rounding = rounding ?? Money.DEFAULT_ROUNDING;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
getAmount(): MoneyAmountType {
|
|
40
47
|
return this.amount;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
|
-
add(money: Money) {
|
|
50
|
+
add(money: Money): Money {
|
|
44
51
|
const result = this.rounding.round(this.amount + money.getAmount());
|
|
45
52
|
|
|
46
53
|
return new Money(MoneyAmount.parse(result), this.rounding);
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
multiply(factor: MoneyMultiplicationFactorType) {
|
|
56
|
+
multiply(factor: MoneyMultiplicationFactorType): Money {
|
|
50
57
|
const result = this.rounding.round(this.amount * factor);
|
|
51
58
|
|
|
52
59
|
return new Money(MoneyAmount.parse(result), this.rounding);
|
|
53
60
|
}
|
|
54
61
|
|
|
55
|
-
subtract(money: Money) {
|
|
62
|
+
subtract(money: Money): Money {
|
|
56
63
|
const result = this.rounding.round(this.amount - money.getAmount());
|
|
57
64
|
|
|
58
|
-
if (result < Money.ZERO) throw new Error(
|
|
59
|
-
|
|
65
|
+
if (result < Money.ZERO) throw new Error(MoneySubtractLessThanZeroError);
|
|
60
66
|
return new Money(MoneyAmount.parse(result), this.rounding);
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
divide(factor: MoneyDivisionFactorType) {
|
|
69
|
+
divide(factor: MoneyDivisionFactorType): Money {
|
|
64
70
|
const result = this.rounding.round(this.amount / factor);
|
|
65
71
|
|
|
66
72
|
return new Money(MoneyAmount.parse(result), this.rounding);
|
|
@@ -83,10 +89,7 @@ export class Money {
|
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
format(): string {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const whole = Math.floor(result);
|
|
89
|
-
|
|
92
|
+
const whole = Math.floor(this.amount / 100);
|
|
90
93
|
const fraction = this.amount % 100;
|
|
91
94
|
const fractionFormatted = fraction.toString().padStart(2, "0");
|
|
92
95
|
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { ZScore } from "./z-score.service";
|
|
2
2
|
|
|
3
|
+
export const OutlierDetectorMinValuesError = "outlier.detector.min.values" as const;
|
|
4
|
+
|
|
3
5
|
export class OutlierDetector {
|
|
4
6
|
private readonly zScore: ZScore;
|
|
5
|
-
|
|
6
7
|
private readonly threshold: number;
|
|
7
8
|
|
|
8
9
|
constructor(values: number[], threshold: number) {
|
|
9
|
-
if (values.length < 2) throw new Error(
|
|
10
|
+
if (values.length < 2) throw new Error(OutlierDetectorMinValuesError);
|
|
10
11
|
|
|
11
12
|
this.zScore = new ZScore(values);
|
|
12
13
|
this.threshold = Math.abs(threshold);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
isInlier(value: number): boolean {
|
|
17
|
+
const score = this.zScore.calculate(value);
|
|
18
|
+
return Math.abs(score) <= this.threshold;
|
|
17
19
|
}
|
|
18
20
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RoundToNearest } from "./rounding.adapter";
|
|
2
|
+
import type { RoundingPort } from "./rounding.port";
|
|
3
|
+
|
|
4
|
+
export const PercentageInvalidDenominatorError = "percentage.invalid.denominator" as const;
|
|
2
5
|
|
|
3
6
|
export class Percentage {
|
|
4
|
-
static of(
|
|
5
|
-
|
|
6
|
-
denominator: number,
|
|
7
|
-
rounding: RoundingStrategy = new RoundToNearest(),
|
|
8
|
-
): number {
|
|
9
|
-
if (denominator === 0) throw new Error("Invalid denominator");
|
|
7
|
+
static of(numerator: number, denominator: number, rounding: RoundingPort = new RoundToNearest()): number {
|
|
8
|
+
if (denominator === 0) throw new Error(PercentageInvalidDenominatorError);
|
|
10
9
|
if (numerator === 0) return 0;
|
|
11
10
|
return rounding.round((numerator / denominator) * 100);
|
|
12
11
|
}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { Mean } from "./mean.service";
|
|
2
|
-
import {
|
|
2
|
+
import { RoundToDecimal } from "./rounding.adapter";
|
|
3
|
+
import type { RoundingPort } from "./rounding.port";
|
|
3
4
|
import { Sum } from "./sum.service";
|
|
4
5
|
|
|
6
|
+
export const PopulationStandardDeviationMinValuesError = "population.standard.deviation.min.values" as const;
|
|
7
|
+
|
|
5
8
|
export class PopulationStandardDeviation {
|
|
6
|
-
static calculate(values: number[], rounding:
|
|
7
|
-
if (values.length < 2) throw new Error(
|
|
9
|
+
static calculate(values: number[], rounding: RoundingPort = new RoundToDecimal(2)): number {
|
|
10
|
+
if (values.length < 2) throw new Error(PopulationStandardDeviationMinValuesError);
|
|
8
11
|
|
|
9
12
|
const mean = Mean.calculate(values);
|
|
10
|
-
const
|
|
13
|
+
const count = values.length;
|
|
11
14
|
|
|
12
15
|
const squaredDifferences = values.map((value) => (value - mean) ** 2);
|
|
13
16
|
const sumOfSquaredDifferences = Sum.of(squaredDifferences);
|
|
14
17
|
|
|
15
|
-
const variance = sumOfSquaredDifferences /
|
|
18
|
+
const variance = sumOfSquaredDifferences / count;
|
|
16
19
|
|
|
17
20
|
return rounding.round(Math.sqrt(variance));
|
|
18
21
|
}
|
package/src/random.service.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
type RandomGenerateConfigType = { min: number; max: number };
|
|
2
2
|
|
|
3
|
+
export const RandomMinNotIntegerError = "random.min.not.integer" as const;
|
|
4
|
+
export const RandomMaxNotIntegerError = "random.max.not.integer" as const;
|
|
5
|
+
export const RandomMinEqualsMaxError = "random.min.equals.max" as const;
|
|
6
|
+
export const RandomMinGreaterThanMaxError = "random.min.greater.than.max" as const;
|
|
7
|
+
|
|
3
8
|
export class Random {
|
|
4
|
-
static
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (min
|
|
9
|
+
private static readonly DEFAULT_MIN = 0;
|
|
10
|
+
private static readonly DEFAULT_MAX = 1;
|
|
11
|
+
|
|
12
|
+
static generate(config?: RandomGenerateConfigType): number {
|
|
13
|
+
const min = config ? config.min : Random.DEFAULT_MIN;
|
|
14
|
+
const max = config ? config.max : Random.DEFAULT_MAX;
|
|
15
|
+
|
|
16
|
+
if (!Number.isInteger(min)) throw new Error(RandomMinNotIntegerError);
|
|
17
|
+
if (!Number.isInteger(max)) throw new Error(RandomMaxNotIntegerError);
|
|
18
|
+
if (min === max) throw new Error(RandomMinEqualsMaxError);
|
|
19
|
+
if (min > max) throw new Error(RandomMinGreaterThanMaxError);
|
|
12
20
|
|
|
13
21
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
14
22
|
}
|
package/src/relative-date.vo.ts
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RoundingPort } from "./rounding.port";
|
|
2
|
+
|
|
3
|
+
export class RoundToNearest implements RoundingPort {
|
|
4
|
+
round(value: number): number {
|
|
5
|
+
return Math.round(value);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class RoundUp implements RoundingPort {
|
|
10
|
+
round(value: number): number {
|
|
11
|
+
return Math.ceil(value);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class RoundDown implements RoundingPort {
|
|
16
|
+
round(value: number): number {
|
|
17
|
+
return Math.floor(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const RoundingDecimalsError = "invalid.rounding.decimals" as const;
|
|
22
|
+
|
|
23
|
+
export class RoundToDecimal implements RoundingPort {
|
|
24
|
+
constructor(private readonly decimals: number) {
|
|
25
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 100) {
|
|
26
|
+
throw new Error(RoundingDecimalsError);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
round(value: number): number {
|
|
31
|
+
return Number.parseFloat(value.toFixed(this.decimals));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,56 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { RoundToNearest } from "./rounding.adapter";
|
|
2
|
+
import type { RoundingPort } from "./rounding.port";
|
|
3
3
|
|
|
4
4
|
export type SLRPairType = { x: number; y: number };
|
|
5
5
|
export type SLRParamsType = { a: number; b: number };
|
|
6
6
|
export type SLRPredictionType = number;
|
|
7
7
|
|
|
8
|
+
export const SLRMinPairsError = "slr.min.pairs" as const;
|
|
9
|
+
export const SLRSumXTooBigError = "slr.sum.x.too.big" as const;
|
|
10
|
+
export const SLRSumYTooBigError = "slr.sum.y.too.big" as const;
|
|
11
|
+
export const SLRSumXSquaredTooBigError = "slr.sum.x.squared.too.big" as const;
|
|
12
|
+
export const SLRSumXTimesYTooBigError = "slr.sum.x.times.y.too.big" as const;
|
|
13
|
+
export const SLRModelCreationError = "slr.model.creation" as const;
|
|
14
|
+
|
|
8
15
|
export class SimpleLinearRegression {
|
|
9
|
-
private readonly
|
|
16
|
+
private static readonly DEFAULT_ROUNDING: RoundingPort = new RoundToNearest();
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
rounding?: RoundingStrategy,
|
|
14
|
-
) {
|
|
15
|
-
this.rounding = rounding ?? this.rounding;
|
|
16
|
-
}
|
|
18
|
+
private readonly params: SLRParamsType;
|
|
19
|
+
private readonly rounding: RoundingPort;
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
constructor(params: SLRParamsType, rounding?: RoundingPort) {
|
|
22
|
+
this.params = params;
|
|
23
|
+
this.rounding = rounding ?? SimpleLinearRegression.DEFAULT_ROUNDING;
|
|
24
|
+
}
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
static fromPairs(pairs: SLRPairType[], rounding?: RoundingPort): SimpleLinearRegression {
|
|
27
|
+
const count = pairs.length;
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
const y = pairs.map((pair) => pair.y);
|
|
25
|
-
const xx = x.map((x) => x ** 2);
|
|
26
|
-
const xy = pairs.map((pair) => pair.x * pair.y);
|
|
29
|
+
if (count < 2) throw new Error(SLRMinPairsError);
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
let sumX = 0;
|
|
32
|
+
let sumY = 0;
|
|
33
|
+
let sumXX = 0;
|
|
34
|
+
let sumXY = 0;
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
for (let index = 0; index < count; index++) {
|
|
37
|
+
const pair = pairs[index];
|
|
38
|
+
sumX += pair.x;
|
|
39
|
+
sumY += pair.y;
|
|
40
|
+
sumXX += pair.x * pair.x;
|
|
41
|
+
sumXY += pair.x * pair.y;
|
|
42
|
+
}
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
if (
|
|
44
|
+
if (Math.abs(sumX) >= Number.MAX_SAFE_INTEGER) throw new Error(SLRSumXTooBigError);
|
|
45
|
+
if (Math.abs(sumY) >= Number.MAX_SAFE_INTEGER) throw new Error(SLRSumYTooBigError);
|
|
46
|
+
if (Math.abs(sumXY) >= Number.MAX_SAFE_INTEGER) throw new Error(SLRSumXTimesYTooBigError);
|
|
47
|
+
if (Math.abs(sumXX) >= Number.MAX_SAFE_INTEGER) throw new Error(SLRSumXSquaredTooBigError);
|
|
36
48
|
|
|
37
|
-
const
|
|
38
|
-
if (sXY >= Number.MAX_SAFE_INTEGER) throw new Error("Sum of x times y values is too big");
|
|
49
|
+
const bDenominator = sumXX - sumX ** 2 / count;
|
|
39
50
|
|
|
40
|
-
|
|
41
|
-
if (bDenominator === 0) throw new Error("Unable to create the model");
|
|
51
|
+
if (bDenominator === 0) throw new Error(SLRModelCreationError);
|
|
42
52
|
|
|
43
|
-
const b = (
|
|
44
|
-
const a = (
|
|
53
|
+
const b = (sumXY - (sumX * sumY) / count) / bDenominator;
|
|
54
|
+
const a = (sumY - b * sumX) / count;
|
|
45
55
|
|
|
46
56
|
return new SimpleLinearRegression({ a, b }, rounding);
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
predict(x: SLRPairType["x"],
|
|
50
|
-
const
|
|
59
|
+
predict(x: SLRPairType["x"], rounding?: RoundingPort): SLRPredictionType {
|
|
60
|
+
const chosen = rounding ?? this.rounding;
|
|
51
61
|
const prediction = this.params.b * x + this.params.a;
|
|
52
62
|
|
|
53
|
-
return
|
|
63
|
+
return chosen.round(prediction);
|
|
54
64
|
}
|
|
55
65
|
|
|
56
66
|
inspect(): SimpleLinearRegression["params"] {
|
package/src/size.vo.ts
CHANGED
package/src/stopwatch.service.ts
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
import { Timestamp, type TimestampType } from "./timestamp.vo";
|
|
2
|
-
import type { Falsy } from "./ts-utils";
|
|
3
2
|
|
|
4
3
|
enum StopwatchState {
|
|
5
4
|
started = "started",
|
|
6
5
|
stopped = "stopped",
|
|
7
6
|
}
|
|
8
7
|
|
|
8
|
+
export const StopwatchStateError = "stopwatch.already.stopped" as const;
|
|
9
|
+
|
|
9
10
|
export type StopwatchResultType = { durationMs: TimestampType };
|
|
10
11
|
|
|
11
12
|
export class Stopwatch {
|
|
12
13
|
private state: StopwatchState = StopwatchState.started;
|
|
13
14
|
|
|
14
|
-
private stopMs: Falsy<TimestampType>;
|
|
15
|
-
|
|
16
15
|
constructor(private readonly startMs: TimestampType) {}
|
|
17
16
|
|
|
18
17
|
stop(): StopwatchResultType {
|
|
19
|
-
if (this.state === StopwatchState.stopped) throw new Error(
|
|
18
|
+
if (this.state === StopwatchState.stopped) throw new Error(StopwatchStateError);
|
|
20
19
|
|
|
21
20
|
this.state = StopwatchState.stopped;
|
|
22
|
-
this.stopMs = Timestamp.parse(Date.now());
|
|
23
21
|
|
|
24
|
-
return { durationMs: Timestamp.parse(
|
|
22
|
+
return { durationMs: Timestamp.parse(Date.now() - this.startMs) };
|
|
25
23
|
}
|
|
26
24
|
}
|