@utiliread/primitives 1.0.0-146 → 1.0.0-148
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/index.js +120 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +120 -25
- package/dist/index.mjs.map +1 -1
- package/dist/precision-number.d.ts +10 -0
- package/package.json +9 -6
- package/src/aggregate-id.ts +2 -9
- package/src/breakdown-rule.spec.ts +61 -61
- package/src/breakdown-rule.ts +58 -58
- package/src/csv.spec.ts +42 -42
- package/src/csv.ts +8 -9
- package/src/cursor-navigation-state.spec.ts +194 -194
- package/src/cursor-navigation-state.ts +168 -168
- package/src/index.ts +61 -61
- package/src/measure-id.ts +17 -17
- package/src/measures/duration.ts +11 -11
- package/src/measures/energy.ts +13 -13
- package/src/measures/humidity.ts +3 -3
- package/src/measures/infocode.ts +3 -3
- package/src/measures/mass-flow.ts +19 -19
- package/src/measures/mass.ts +10 -10
- package/src/measures/noise.ts +5 -5
- package/src/measures/power.ts +23 -23
- package/src/measures/pressure.ts +9 -9
- package/src/measures/reactive-energy.ts +7 -7
- package/src/measures/reactive-power.ts +3 -3
- package/src/measures/temperature.ts +9 -9
- package/src/measures/time.ts +3 -3
- package/src/measures/volume-flow.ts +25 -25
- package/src/measures/volume.ts +3 -3
- package/src/medium.spec.ts +12 -12
- package/src/medium.ts +47 -47
- package/src/number-formatter.spec.ts +570 -549
- package/src/number-formatter.ts +126 -118
- package/src/optional.ts +1 -1
- package/src/precision-number.spec.ts +382 -251
- package/src/precision-number.ts +229 -123
- package/src/register-convention.spec.ts +52 -52
- package/src/register-convention.ts +70 -70
- package/src/register-definition.ts +23 -23
- package/src/register-id.ts +288 -288
- package/src/register-library.spec.ts +41 -41
- package/src/register-library.ts +64 -64
- package/src/register-pack.ts +5 -5
- package/src/register-utils.ts +6 -6
- package/src/registers/acoustic-noise.ts +52 -52
- package/src/registers/ambient-temperature.ts +52 -52
- package/src/registers/axioma-infocode.ts +16 -16
- package/src/registers/consumer.ts +17 -17
- package/src/registers/cooling-energy-inlet.ts +23 -23
- package/src/registers/cooling-energy-outlet.ts +23 -23
- package/src/registers/cooling-energy.ts +23 -23
- package/src/registers/cooling.ts +113 -113
- package/src/registers/device-addresses-kamdraft.ts +14 -14
- package/src/registers/device-addresses-msisdn.ts +14 -14
- package/src/registers/device-addresses-tchlr.ts +14 -14
- package/src/registers/device-addresses-wmbus.ts +17 -17
- package/src/registers/device-date-time.ts +9 -9
- package/src/registers/device-date.ts +9 -9
- package/src/registers/device-nominal-diameter.ts +9 -9
- package/src/registers/device-permanent-volume-flow.ts +9 -9
- package/src/registers/device-registers.ts +9 -9
- package/src/registers/device-samples.ts +37 -37
- package/src/registers/device-software-version.ts +8 -8
- package/src/registers/device-time.ts +9 -9
- package/src/registers/device.ts +16 -16
- package/src/registers/diehl-infocode.ts +44 -44
- package/src/registers/electrical-energy.ts +23 -23
- package/src/registers/energy-inlet.ts +23 -23
- package/src/registers/energy-outlet.ts +23 -23
- package/src/registers/error-operating-time.ts +23 -23
- package/src/registers/flow-temperature-aux.ts +52 -52
- package/src/registers/flow-temperature-inlet.ts +52 -52
- package/src/registers/flow-temperature-outlet.ts +52 -52
- package/src/registers/flow-temperature.ts +52 -52
- package/src/registers/heat-energy-inlet.ts +65 -65
- package/src/registers/heat-energy-outlet.ts +65 -65
- package/src/registers/heat-energy.ts +65 -65
- package/src/registers/index.ts +71 -71
- package/src/registers/infocode.ts +70 -70
- package/src/registers/kamstrup-module-type.ts +8 -8
- package/src/registers/kamstrup-multical-infocode.ts +58 -58
- package/src/registers/landis-gyr-infocode.ts +23 -23
- package/src/registers/landis-gyr-mbus-status.ts +23 -23
- package/src/registers/mass-flow-inlet.ts +52 -52
- package/src/registers/mass-flow-outlet.ts +52 -52
- package/src/registers/mass-flow.ts +52 -52
- package/src/registers/mass-inlet.ts +23 -23
- package/src/registers/mass-outlet.ts +23 -23
- package/src/registers/mass.ts +23 -23
- package/src/registers/mbus-format-string-signature.ts +8 -8
- package/src/registers/none.ts +8 -8
- package/src/registers/operating-time.ts +23 -23
- package/src/registers/place.ts +31 -31
- package/src/registers/power-inlet.ts +52 -52
- package/src/registers/power-outlet.ts +52 -52
- package/src/registers/power.ts +52 -52
- package/src/registers/pressure-inlet.ts +52 -52
- package/src/registers/pressure-outlet.ts +52 -52
- package/src/registers/pressure.ts +52 -52
- package/src/registers/radiator-temperature.ts +52 -52
- package/src/registers/reactive-energy.ts +23 -23
- package/src/registers/relative-humidity.ts +8 -8
- package/src/registers/remaining-battery-time.ts +23 -23
- package/src/registers/time-last-exact-overhearing-arrived.ts +19 -19
- package/src/registers/time-last-installation-mounted.ts +19 -19
- package/src/registers/time-last-possible-candidate-overhearing-arrived.ts +21 -21
- package/src/registers/time-last-sampled.ts +19 -19
- package/src/registers/time.ts +45 -45
- package/src/registers/volume-flow-inlet.ts +52 -52
- package/src/registers/volume-flow-outlet.ts +52 -52
- package/src/registers/volume-flow.ts +52 -52
- package/src/registers/volume-inlet.ts +23 -23
- package/src/registers/volume-outlet.ts +23 -23
- package/src/registers/volume-pulse-input-a.ts +23 -23
- package/src/registers/volume-pulse-input-b.ts +23 -23
- package/src/registers/volume.ts +23 -23
- package/src/repository.spec.ts +30 -30
- package/src/repository.ts +26 -26
- package/src/step.spec.ts +37 -37
- package/src/step.ts +45 -45
- package/src/time-or-rule.ts +42 -42
- package/src/unit-id.ts +108 -108
package/src/precision-number.ts
CHANGED
|
@@ -1,123 +1,229 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NumberFormatter,
|
|
3
|
-
invariantDecimalSeparator,
|
|
4
|
-
localeDecimalSeparator,
|
|
5
|
-
} from "./number-formatter";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
1
|
+
import {
|
|
2
|
+
NumberFormatter,
|
|
3
|
+
invariantDecimalSeparator,
|
|
4
|
+
localeDecimalSeparator,
|
|
5
|
+
} from "./number-formatter";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A number with a number of significant digits (`precision`).
|
|
9
|
+
*
|
|
10
|
+
* Convention for zero (which has no significant digits): the precision is the number of decimals.
|
|
11
|
+
* "0" has precision 0, "0.0" has precision 1, "0.00" has precision 2, etc.
|
|
12
|
+
* A precision of 0 is therefore only valid for the value 0 and must NOT be interpreted as "no precision",
|
|
13
|
+
* i.e. use `precision === undefined` and not `!precision` to test for a missing precision.
|
|
14
|
+
*
|
|
15
|
+
* This is the same convention as `PrecisionDouble` in C#.
|
|
16
|
+
*/
|
|
17
|
+
export class PrecisionNumber {
|
|
18
|
+
public value: number;
|
|
19
|
+
public precision: number;
|
|
20
|
+
|
|
21
|
+
public get decimals() {
|
|
22
|
+
return getNoOfDecimalPlaces(this.value, this.precision);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
constructor(value: number, precision?: number) {
|
|
26
|
+
this.value = value;
|
|
27
|
+
this.precision = precision ?? getPrecision(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static parse(input: string) {
|
|
31
|
+
let normalized = input
|
|
32
|
+
.trim()
|
|
33
|
+
.replace(",", ".")
|
|
34
|
+
.replace(localeDecimalSeparator, invariantDecimalSeparator);
|
|
35
|
+
|
|
36
|
+
if (normalized.endsWith(invariantDecimalSeparator)) {
|
|
37
|
+
normalized += "0";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const value = parseFloat(normalized);
|
|
41
|
+
const precision =
|
|
42
|
+
value === 0
|
|
43
|
+
? // The precision of zero is its number of decimals, "0" => 0, "0.0" => 1
|
|
44
|
+
(normalized.split(".", 2)[1]?.length ?? 0)
|
|
45
|
+
: normalized
|
|
46
|
+
.replace(".", "")
|
|
47
|
+
.replace("+", "")
|
|
48
|
+
.replace("-", "")
|
|
49
|
+
.replace(/^0+/, "").length;
|
|
50
|
+
return new PrecisionNumber(value, precision);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
abs() {
|
|
54
|
+
return new PrecisionNumber(Math.abs(this.value), this.precision);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
plus(rhs: PrecisionNumber) {
|
|
58
|
+
const lhs = this;
|
|
59
|
+
|
|
60
|
+
// "0" (without decimals) is the additive identity
|
|
61
|
+
if (isIntegerZero(lhs)) {
|
|
62
|
+
return rhs;
|
|
63
|
+
} else if (isIntegerZero(rhs)) {
|
|
64
|
+
return lhs;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The result should have the same number of decimal places as the minimum of the two inputs
|
|
68
|
+
const value = lhs.value + rhs.value;
|
|
69
|
+
const decimals = Math.min(lhs.decimals, rhs.decimals);
|
|
70
|
+
return fromResult(value, decimals);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
minus(rhs: PrecisionNumber) {
|
|
74
|
+
const lhs = this;
|
|
75
|
+
|
|
76
|
+
// "0" (without decimals) is the additive identity
|
|
77
|
+
if (isIntegerZero(lhs)) {
|
|
78
|
+
return new PrecisionNumber(-rhs.value, rhs.precision);
|
|
79
|
+
} else if (isIntegerZero(rhs)) {
|
|
80
|
+
return lhs;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The result should have the same number of decimal places as the minimum of the two inputs
|
|
84
|
+
const value = lhs.value - rhs.value;
|
|
85
|
+
const decimals = Math.min(lhs.decimals, rhs.decimals);
|
|
86
|
+
return fromResult(value, decimals);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
valueOf() {
|
|
90
|
+
const scale = Math.pow(10, this.decimals);
|
|
91
|
+
const integer = Math.round(this.value * scale);
|
|
92
|
+
const value = integer / scale;
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
toString() {
|
|
97
|
+
return this.valueOf().toString();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
toFixed() {
|
|
101
|
+
return NumberFormatter.toFixed(this.valueOf(), this.precision);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
toPrecision() {
|
|
105
|
+
return NumberFormatter.toPrecision(this.valueOf(), this.precision);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
toLocaleFixed() {
|
|
109
|
+
return NumberFormatter.toLocaleFixed(this.valueOf(), this.precision);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
toLocalePrecision() {
|
|
113
|
+
return NumberFormatter.toLocalePrecision(
|
|
114
|
+
this.valueOf(),
|
|
115
|
+
this.precision,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isIntegerZero(number: PrecisionNumber) {
|
|
121
|
+
return number.value === 0 && number.precision === 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Create the result of an addition or subtraction having `decimals` decimal places.
|
|
126
|
+
* A result that is zero at that number of decimal places (e.g. floating point noise from 0.3 - 0.1 - 0.2) becomes an exact zero.
|
|
127
|
+
*/
|
|
128
|
+
function fromResult(value: number, decimals: number) {
|
|
129
|
+
if (decimals >= 0 && Math.abs(value) * Math.pow(10, decimals) < 0.5) {
|
|
130
|
+
return new PrecisionNumber(0, decimals);
|
|
131
|
+
}
|
|
132
|
+
return new PrecisionNumber(value, getPrecision(value, decimals));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get the precision of `number` having `decimals` decimal places.
|
|
137
|
+
*
|
|
138
|
+
* Number Decimals Precision
|
|
139
|
+
* 0 0 0 ("0")
|
|
140
|
+
* 0 2 2 ("0.00")
|
|
141
|
+
* 0.05 2 1
|
|
142
|
+
* 0.12 2 2
|
|
143
|
+
* 100 1 4
|
|
144
|
+
* 100 0 3
|
|
145
|
+
* 100 -1 2
|
|
146
|
+
* 100 <= -2 1
|
|
147
|
+
*/
|
|
148
|
+
function getPrecision(number: number, decimals?: number) {
|
|
149
|
+
decimals ??= number.toString().split(".", 2)[1]?.length ?? 0;
|
|
150
|
+
|
|
151
|
+
if (number === 0) {
|
|
152
|
+
// The precision of zero is its number of decimals
|
|
153
|
+
return Math.max(decimals, 0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const abs = Math.abs(number);
|
|
157
|
+
if (abs < 1) {
|
|
158
|
+
// Leading zeros after the decimal separator are not significant
|
|
159
|
+
return Math.max(decimals - getNoOfLeadingZeros(abs), 1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Math.max(getNumberOfIntegralDigits(number) + decimals, 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get the number of decimal places of `number` having `precision` significant digits.
|
|
167
|
+
*
|
|
168
|
+
* Number Precision Decimals
|
|
169
|
+
* 0 2 2 ("0.00")
|
|
170
|
+
* 0.099 1 1 (0.1)
|
|
171
|
+
* 0.07 1 2
|
|
172
|
+
* 0.1 2 2
|
|
173
|
+
* 100 1 -2
|
|
174
|
+
* 100 3 0
|
|
175
|
+
* 100 4 1
|
|
176
|
+
*/
|
|
177
|
+
function getNoOfDecimalPlaces(number: number, precision: number) {
|
|
178
|
+
if (number === 0) {
|
|
179
|
+
// The precision of zero is its number of decimals
|
|
180
|
+
return precision;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const abs = Math.abs(number);
|
|
184
|
+
if (abs < 1) {
|
|
185
|
+
// Leading zeros after the decimal separator are not significant
|
|
186
|
+
const leadingZeros = getNoOfLeadingZeros(abs);
|
|
187
|
+
let decimals = precision + leadingZeros;
|
|
188
|
+
|
|
189
|
+
// Rounding may carry into the next order of magnitude, e.g. 0.099 at precision 1 is 0.1
|
|
190
|
+
if (
|
|
191
|
+
leadingZeros > 0 &&
|
|
192
|
+
Math.round(abs * Math.pow(10, decimals)) >= Math.pow(10, precision)
|
|
193
|
+
) {
|
|
194
|
+
decimals--;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return decimals;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return precision - getNumberOfIntegralDigits(number);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get the number of zeros between the decimal separator and the first significant digit of `abs` where 0 < `abs` < 1,
|
|
205
|
+
* e.g. 0.5 => 0, 0.05 => 1, 0.005 => 2
|
|
206
|
+
*/
|
|
207
|
+
function getNoOfLeadingZeros(abs: number) {
|
|
208
|
+
let leadingZeros = 0;
|
|
209
|
+
let scaled = abs * 10;
|
|
210
|
+
while (scaled < 1) {
|
|
211
|
+
scaled *= 10;
|
|
212
|
+
leadingZeros++;
|
|
213
|
+
}
|
|
214
|
+
return leadingZeros;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getNumberOfIntegralDigits(number: number) {
|
|
218
|
+
const abs = Math.abs(number);
|
|
219
|
+
if (abs < 10) return 1;
|
|
220
|
+
if (abs < 100) return 2;
|
|
221
|
+
if (abs < 1000) return 3;
|
|
222
|
+
if (abs < 10000) return 4;
|
|
223
|
+
if (abs < 100000) return 5;
|
|
224
|
+
if (abs < 1000000) return 6;
|
|
225
|
+
if (abs < 10000000) return 7;
|
|
226
|
+
if (abs < 100000000) return 8;
|
|
227
|
+
if (abs < 1000000000) return 9;
|
|
228
|
+
return Math.floor(Math.log10(abs) + 1);
|
|
229
|
+
}
|
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
import { RegisterConvention } from "./register-convention";
|
|
2
|
-
import { expect } from "chai";
|
|
3
|
-
|
|
4
|
-
describe("getAggregate", () => {
|
|
5
|
-
it("instant register is instant", () => {
|
|
6
|
-
const result = RegisterConvention.getAggregate("flowTemperature");
|
|
7
|
-
expect(result).to.equal("instant");
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("avg register is avg", () => {
|
|
11
|
-
const result = RegisterConvention.getAggregate("avgFlowTemperature");
|
|
12
|
-
expect(result).to.equal("avg");
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe("toInstant", () => {
|
|
17
|
-
it("should convert instant", () => {
|
|
18
|
-
const registerId = RegisterConvention.toInstant("flowTemperature");
|
|
19
|
-
expect(registerId).to.equal("flowTemperature");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("should convert aggregate", () => {
|
|
23
|
-
const registerId = RegisterConvention.toInstant("avgFlowTemperature");
|
|
24
|
-
expect(registerId).to.equal("flowTemperature");
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe("toAggregate", () => {
|
|
29
|
-
it("should convert instant to aggregate", () => {
|
|
30
|
-
const registerId = RegisterConvention.toAggregate(
|
|
31
|
-
"flowTemperature",
|
|
32
|
-
"avg",
|
|
33
|
-
);
|
|
34
|
-
expect(registerId).to.equal("avgFlowTemperature");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("should convert aggregate to same aggregate", () => {
|
|
38
|
-
const registerId = RegisterConvention.toAggregate(
|
|
39
|
-
"avgFlowTemperature",
|
|
40
|
-
"avg",
|
|
41
|
-
);
|
|
42
|
-
expect(registerId).to.equal("avgFlowTemperature");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("should convert aggregate to other aggregate", () => {
|
|
46
|
-
const registerId = RegisterConvention.toAggregate(
|
|
47
|
-
"avgFlowTemperature",
|
|
48
|
-
"min",
|
|
49
|
-
);
|
|
50
|
-
expect(registerId).to.equal("minFlowTemperature");
|
|
51
|
-
});
|
|
52
|
-
});
|
|
1
|
+
import { RegisterConvention } from "./register-convention";
|
|
2
|
+
import { expect } from "chai";
|
|
3
|
+
|
|
4
|
+
describe("getAggregate", () => {
|
|
5
|
+
it("instant register is instant", () => {
|
|
6
|
+
const result = RegisterConvention.getAggregate("flowTemperature");
|
|
7
|
+
expect(result).to.equal("instant");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("avg register is avg", () => {
|
|
11
|
+
const result = RegisterConvention.getAggregate("avgFlowTemperature");
|
|
12
|
+
expect(result).to.equal("avg");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("toInstant", () => {
|
|
17
|
+
it("should convert instant", () => {
|
|
18
|
+
const registerId = RegisterConvention.toInstant("flowTemperature");
|
|
19
|
+
expect(registerId).to.equal("flowTemperature");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should convert aggregate", () => {
|
|
23
|
+
const registerId = RegisterConvention.toInstant("avgFlowTemperature");
|
|
24
|
+
expect(registerId).to.equal("flowTemperature");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("toAggregate", () => {
|
|
29
|
+
it("should convert instant to aggregate", () => {
|
|
30
|
+
const registerId = RegisterConvention.toAggregate(
|
|
31
|
+
"flowTemperature",
|
|
32
|
+
"avg",
|
|
33
|
+
);
|
|
34
|
+
expect(registerId).to.equal("avgFlowTemperature");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should convert aggregate to same aggregate", () => {
|
|
38
|
+
const registerId = RegisterConvention.toAggregate(
|
|
39
|
+
"avgFlowTemperature",
|
|
40
|
+
"avg",
|
|
41
|
+
);
|
|
42
|
+
expect(registerId).to.equal("avgFlowTemperature");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should convert aggregate to other aggregate", () => {
|
|
46
|
+
const registerId = RegisterConvention.toAggregate(
|
|
47
|
+
"avgFlowTemperature",
|
|
48
|
+
"min",
|
|
49
|
+
);
|
|
50
|
+
expect(registerId).to.equal("minFlowTemperature");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
import type { RegisterDefinition } from "./register-definition";
|
|
2
|
-
import { AggregateId } from "./aggregate-id";
|
|
3
|
-
import type { RegisterId } from "./register-id";
|
|
4
|
-
import { getRegisterId } from "./register-utils";
|
|
5
|
-
|
|
6
|
-
export const aggregatePrefixes: { [aggregateId in AggregateId]: string } = {
|
|
7
|
-
instant: "",
|
|
8
|
-
diff: "diff",
|
|
9
|
-
min: "min",
|
|
10
|
-
max: "max",
|
|
11
|
-
avg: "avg",
|
|
12
|
-
sum: "sum",
|
|
13
|
-
cnt: "cnt",
|
|
14
|
-
ssq: "ssq",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
class Convention {
|
|
18
|
-
public getAggregate(register: RegisterId | RegisterDefinition) {
|
|
19
|
-
const registerId = getRegisterId(register);
|
|
20
|
-
// Brute force all prefixes - if none of them, then it must be instant.
|
|
21
|
-
for (const aggregateString of Object.keys(aggregatePrefixes)) {
|
|
22
|
-
const aggregate = <AggregateId>aggregateString;
|
|
23
|
-
const prefix = aggregatePrefixes[aggregate];
|
|
24
|
-
if (prefix.length && isPrefix(registerId, prefix)) {
|
|
25
|
-
// Not instant - it was any of the others.
|
|
26
|
-
return aggregate;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return "instant";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
toInstant(register: RegisterId | RegisterDefinition) {
|
|
33
|
-
const registerId = getRegisterId(register);
|
|
34
|
-
for (const prefix of Object.values(aggregatePrefixes)) {
|
|
35
|
-
if (prefix.length && isPrefix(registerId, prefix)) {
|
|
36
|
-
const tail = registerId.substr(prefix.length);
|
|
37
|
-
return <RegisterId>(tail[0].toLowerCase() + tail.substr(1));
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return registerId;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
toAggregate(
|
|
44
|
-
register: RegisterId | RegisterDefinition,
|
|
45
|
-
aggregate: AggregateId,
|
|
46
|
-
) {
|
|
47
|
-
const instantRegisterId = this.toInstant(register);
|
|
48
|
-
if (aggregate === "instant") {
|
|
49
|
-
return instantRegisterId;
|
|
50
|
-
}
|
|
51
|
-
const prefix = aggregatePrefixes[aggregate];
|
|
52
|
-
return <RegisterId>(
|
|
53
|
-
(prefix +
|
|
54
|
-
instantRegisterId[0].toUpperCase() +
|
|
55
|
-
instantRegisterId.substr(1))
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function isPrefix(registerId: RegisterId, prefix: string) {
|
|
61
|
-
return (
|
|
62
|
-
registerId.startsWith(prefix) && isUppercase(registerId[prefix.length])
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function isUppercase(input: string) {
|
|
67
|
-
return input.toUpperCase() === input;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export const RegisterConvention = new Convention();
|
|
1
|
+
import type { RegisterDefinition } from "./register-definition";
|
|
2
|
+
import { AggregateId } from "./aggregate-id";
|
|
3
|
+
import type { RegisterId } from "./register-id";
|
|
4
|
+
import { getRegisterId } from "./register-utils";
|
|
5
|
+
|
|
6
|
+
export const aggregatePrefixes: { [aggregateId in AggregateId]: string } = {
|
|
7
|
+
instant: "",
|
|
8
|
+
diff: "diff",
|
|
9
|
+
min: "min",
|
|
10
|
+
max: "max",
|
|
11
|
+
avg: "avg",
|
|
12
|
+
sum: "sum",
|
|
13
|
+
cnt: "cnt",
|
|
14
|
+
ssq: "ssq",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
class Convention {
|
|
18
|
+
public getAggregate(register: RegisterId | RegisterDefinition) {
|
|
19
|
+
const registerId = getRegisterId(register);
|
|
20
|
+
// Brute force all prefixes - if none of them, then it must be instant.
|
|
21
|
+
for (const aggregateString of Object.keys(aggregatePrefixes)) {
|
|
22
|
+
const aggregate = <AggregateId>aggregateString;
|
|
23
|
+
const prefix = aggregatePrefixes[aggregate];
|
|
24
|
+
if (prefix.length && isPrefix(registerId, prefix)) {
|
|
25
|
+
// Not instant - it was any of the others.
|
|
26
|
+
return aggregate;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return "instant";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toInstant(register: RegisterId | RegisterDefinition) {
|
|
33
|
+
const registerId = getRegisterId(register);
|
|
34
|
+
for (const prefix of Object.values(aggregatePrefixes)) {
|
|
35
|
+
if (prefix.length && isPrefix(registerId, prefix)) {
|
|
36
|
+
const tail = registerId.substr(prefix.length);
|
|
37
|
+
return <RegisterId>(tail[0].toLowerCase() + tail.substr(1));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return registerId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
toAggregate(
|
|
44
|
+
register: RegisterId | RegisterDefinition,
|
|
45
|
+
aggregate: AggregateId,
|
|
46
|
+
) {
|
|
47
|
+
const instantRegisterId = this.toInstant(register);
|
|
48
|
+
if (aggregate === "instant") {
|
|
49
|
+
return instantRegisterId;
|
|
50
|
+
}
|
|
51
|
+
const prefix = aggregatePrefixes[aggregate];
|
|
52
|
+
return <RegisterId>(
|
|
53
|
+
(prefix +
|
|
54
|
+
instantRegisterId[0].toUpperCase() +
|
|
55
|
+
instantRegisterId.substr(1))
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isPrefix(registerId: RegisterId, prefix: string) {
|
|
61
|
+
return (
|
|
62
|
+
registerId.startsWith(prefix) && isUppercase(registerId[prefix.length])
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isUppercase(input: string) {
|
|
67
|
+
return input.toUpperCase() === input;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const RegisterConvention = new Convention();
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import type { AggregateId } from "./aggregate-id";
|
|
2
|
-
import type { MeasureId } from "./measure-id";
|
|
3
|
-
import type { RegisterId } from "./register-id";
|
|
4
|
-
import type { UnitId } from "./unit-id";
|
|
5
|
-
|
|
6
|
-
export interface RegisterDefinition {
|
|
7
|
-
/// The id of the register
|
|
8
|
-
id: RegisterId;
|
|
9
|
-
/// The register measure
|
|
10
|
-
measure: MeasureId;
|
|
11
|
-
/// The aggregate of the register
|
|
12
|
-
aggregate: AggregateId;
|
|
13
|
-
/// The possible register units
|
|
14
|
-
units: ReadonlyArray<UnitId>;
|
|
15
|
-
/// Whether the register can only be resolved by "present value".
|
|
16
|
-
immediate?: true;
|
|
17
|
-
/// An optional concise register name.
|
|
18
|
-
/// Only used for custom registers as locales exist for standard registers.
|
|
19
|
-
name?: string;
|
|
20
|
-
/// An optional register description.
|
|
21
|
-
/// Only used for custom registers as locales exist for standard registers.
|
|
22
|
-
description?: string;
|
|
23
|
-
}
|
|
1
|
+
import type { AggregateId } from "./aggregate-id";
|
|
2
|
+
import type { MeasureId } from "./measure-id";
|
|
3
|
+
import type { RegisterId } from "./register-id";
|
|
4
|
+
import type { UnitId } from "./unit-id";
|
|
5
|
+
|
|
6
|
+
export interface RegisterDefinition {
|
|
7
|
+
/// The id of the register
|
|
8
|
+
id: RegisterId;
|
|
9
|
+
/// The register measure
|
|
10
|
+
measure: MeasureId;
|
|
11
|
+
/// The aggregate of the register
|
|
12
|
+
aggregate: AggregateId;
|
|
13
|
+
/// The possible register units
|
|
14
|
+
units: ReadonlyArray<UnitId>;
|
|
15
|
+
/// Whether the register can only be resolved by "present value".
|
|
16
|
+
immediate?: true;
|
|
17
|
+
/// An optional concise register name.
|
|
18
|
+
/// Only used for custom registers as locales exist for standard registers.
|
|
19
|
+
name?: string;
|
|
20
|
+
/// An optional register description.
|
|
21
|
+
/// Only used for custom registers as locales exist for standard registers.
|
|
22
|
+
description?: string;
|
|
23
|
+
}
|