@uns-kit/cli 2.0.13 → 2.0.15
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uns-kit/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.15",
|
|
4
4
|
"description": "Command line scaffolding tool for UNS applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"azure-devops-node-api": "^15.1.1",
|
|
29
|
-
"@uns-kit/core": "2.0.
|
|
29
|
+
"@uns-kit/core": "2.0.15"
|
|
30
30
|
},
|
|
31
31
|
"unsKitPackages": {
|
|
32
|
-
"@uns-kit/core": "2.0.
|
|
33
|
-
"@uns-kit/api": "2.0.
|
|
34
|
-
"@uns-kit/cron": "2.0.
|
|
35
|
-
"@uns-kit/temporal": "2.0.
|
|
32
|
+
"@uns-kit/core": "2.0.15",
|
|
33
|
+
"@uns-kit/api": "2.0.14",
|
|
34
|
+
"@uns-kit/cron": "2.0.15",
|
|
35
|
+
"@uns-kit/temporal": "2.0.15"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* pnpm exec tsx src/examples/table-window-load-test.ts
|
|
15
15
|
*
|
|
16
16
|
* Optional args:
|
|
17
|
-
* --run=all|1|2|3
|
|
17
|
+
* --run=all|1|2|3|4
|
|
18
18
|
* --mode=window_replace|dedup|append
|
|
19
19
|
* --bucketMinutes=30
|
|
20
20
|
* --windowHours=2
|
|
@@ -43,8 +43,9 @@ import {
|
|
|
43
43
|
import { resolveGeneratedAsset } from "../uns/uns-assets.js";
|
|
44
44
|
import { GeneratedPhysicalMeasurements } from "../uns/uns-measurements.generated.js";
|
|
45
45
|
|
|
46
|
-
type RunSelection = "all" | "1" | "2" | "3";
|
|
46
|
+
type RunSelection = "all" | "1" | "2" | "3" | "4";
|
|
47
47
|
type IngestMode = "append" | "dedup" | "window_replace";
|
|
48
|
+
type IterationIndex = 1 | 2 | 3 | 4;
|
|
48
49
|
|
|
49
50
|
type CliArgs = {
|
|
50
51
|
run: RunSelection;
|
|
@@ -100,7 +101,7 @@ function parseArgs(argv: string[]): Partial<CliArgs> {
|
|
|
100
101
|
const pause = map.get("pause");
|
|
101
102
|
|
|
102
103
|
const parsed: Partial<CliArgs> = {};
|
|
103
|
-
if (run && (run === "all" || run === "1" || run === "2" || run === "3")) parsed.run = run;
|
|
104
|
+
if (run && (run === "all" || run === "1" || run === "2" || run === "3" || run === "4")) parsed.run = run;
|
|
104
105
|
if (mode && (mode === "append" || mode === "dedup" || mode === "window_replace")) parsed.mode = mode;
|
|
105
106
|
if (bucketMinutes) parsed.bucketMinutes = Number(bucketMinutes);
|
|
106
107
|
if (windowHours) parsed.windowHours = Number(windowHours);
|
|
@@ -128,16 +129,24 @@ function sleep(ms: number): Promise<void> {
|
|
|
128
129
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
function buildIterations(
|
|
132
|
+
function buildIterations(
|
|
133
|
+
anchorEnd: Date,
|
|
134
|
+
windowHours: number,
|
|
135
|
+
stepHours: number,
|
|
136
|
+
): Array<{ idx: IterationIndex; startMs: number; endMs: number }> {
|
|
132
137
|
const stepMs = stepHours * 60 * 60 * 1000;
|
|
133
138
|
const windowMs = windowHours * 60 * 60 * 1000;
|
|
134
139
|
const end3 = anchorEnd.getTime();
|
|
135
140
|
const end2 = end3 - stepMs;
|
|
136
141
|
const end1 = end3 - 2 * stepMs;
|
|
142
|
+
const start1 = end1 - windowMs;
|
|
137
143
|
return [
|
|
138
|
-
{ idx: 1, startMs:
|
|
144
|
+
{ idx: 1, startMs: start1, endMs: end1 },
|
|
139
145
|
{ idx: 2, startMs: end2 - windowMs, endMs: end2 },
|
|
140
146
|
{ idx: 3, startMs: end3 - windowMs, endMs: end3 },
|
|
147
|
+
// Iteration 4: refresh the full timeframe from the earliest bucket of iteration 1
|
|
148
|
+
// to the latest bucket of iteration 3, with a simulated delete of one bucket.
|
|
149
|
+
{ idx: 4, startMs: start1, endMs: end3 },
|
|
141
150
|
];
|
|
142
151
|
}
|
|
143
152
|
|
|
@@ -160,7 +169,13 @@ function simulateScrapKg(producedKg: number, bucketIndex: number, iteration: num
|
|
|
160
169
|
return Number(value.toFixed(3));
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
function isMissingBucket(
|
|
172
|
+
function isMissingBucket(
|
|
173
|
+
iteration: Exclude<IterationIndex, 4>,
|
|
174
|
+
bucketStartMs: number,
|
|
175
|
+
windowStartMs: number,
|
|
176
|
+
windowEndMs: number,
|
|
177
|
+
bucketMs: number,
|
|
178
|
+
): boolean {
|
|
164
179
|
const oneHourMs = 60 * 60 * 1000;
|
|
165
180
|
const bucketIndex = Math.floor(bucketStartMs / bucketMs);
|
|
166
181
|
const inLastHour = bucketStartMs >= windowEndMs - oneHourMs;
|
|
@@ -251,18 +266,40 @@ async function main() {
|
|
|
251
266
|
const bucketMs = cli.bucketMinutes * 60 * 1000;
|
|
252
267
|
const iterations = buildIterations(cli.anchorEnd, cli.windowHours, cli.stepHours);
|
|
253
268
|
|
|
254
|
-
const selected = cli.run === "all" ? new Set([1, 2, 3]) : new Set([Number(cli.run)]);
|
|
269
|
+
const selected = cli.run === "all" ? new Set<IterationIndex>([1, 2, 3, 4]) : new Set<IterationIndex>([Number(cli.run) as IterationIndex]);
|
|
255
270
|
const selectedIterations = iterations.filter((it) => selected.has(it.idx));
|
|
256
271
|
const firstIteration = selectedIterations[0]?.idx ?? 1;
|
|
257
272
|
logger.info(
|
|
258
273
|
`Publishing iterations=${Array.from(selected).join(",")} mode=${cli.mode} bucket=${cli.bucketMinutes}min window=${cli.windowHours}h step=${cli.stepHours}h anchorEnd=${cli.anchorEnd.toISOString()} dataGroup=${cli.dataGroup} pause=${shouldPause}`,
|
|
259
274
|
);
|
|
275
|
+
if (cli.mode === "window_replace") {
|
|
276
|
+
logger.info(
|
|
277
|
+
"NOTE: mode=window_replace marks missing buckets in [windowStart, windowEnd) as deleted=true (soft delete) and refreshes published buckets (deleted=false).",
|
|
278
|
+
);
|
|
279
|
+
} else if (cli.mode === "dedup") {
|
|
280
|
+
logger.info(
|
|
281
|
+
"NOTE: mode=dedup upserts by keys; missing buckets are NOT removed unless you publish tombstones (deleted=true) for those buckets.",
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const fullRefresh = iterations.find((it) => it.idx === 4);
|
|
286
|
+
const deleteBucketStartMs =
|
|
287
|
+
fullRefresh ? fullRefresh.startMs + bucketMs : iterations[0]!.startMs + bucketMs;
|
|
260
288
|
|
|
261
289
|
for (const it of iterations) {
|
|
262
290
|
if (!selected.has(it.idx)) continue;
|
|
263
291
|
|
|
264
292
|
const windowStartIso = UnsPacket.formatToISO8601(new Date(it.startMs)) as ISO8601;
|
|
265
293
|
const windowEndIso = UnsPacket.formatToISO8601(new Date(it.endMs)) as ISO8601;
|
|
294
|
+
if (it.idx === 4) {
|
|
295
|
+
const deleteBucketIso = UnsPacket.formatToISO8601(new Date(deleteBucketStartMs)) as ISO8601;
|
|
296
|
+
logger.info(
|
|
297
|
+
`Iteration 4 plan: refresh full timeframe and simulate one deleted bucket at time='${deleteBucketIso}'. ` +
|
|
298
|
+
(cli.mode === "window_replace"
|
|
299
|
+
? "Delete is simulated by OMITTING the bucket from the snapshot (archiver soft-deletes it: deleted=true)."
|
|
300
|
+
: "Delete is simulated by publishing a tombstone (deleted=true) for that bucket."),
|
|
301
|
+
);
|
|
302
|
+
}
|
|
266
303
|
|
|
267
304
|
if (shouldPause && it.idx !== firstIteration) {
|
|
268
305
|
await rl.question(
|
|
@@ -287,13 +324,47 @@ async function main() {
|
|
|
287
324
|
let published = 0;
|
|
288
325
|
let skipped = 0;
|
|
289
326
|
let tombstones = 0;
|
|
327
|
+
let simulatedDeletes = 0;
|
|
290
328
|
|
|
291
329
|
for (let startMs = it.startMs; startMs < it.endMs; startMs += bucketMs) {
|
|
292
330
|
const endMs = startMs + bucketMs;
|
|
293
331
|
const bucketStartIso = UnsPacket.formatToISO8601(new Date(startMs)) as ISO8601;
|
|
294
332
|
const bucketEndIso = UnsPacket.formatToISO8601(new Date(endMs)) as ISO8601;
|
|
295
333
|
|
|
296
|
-
const
|
|
334
|
+
const isIteration4 = it.idx === 4;
|
|
335
|
+
const missing = !isIteration4 && isMissingBucket(it.idx as Exclude<IterationIndex, 4>, startMs, it.startMs, it.endMs, bucketMs);
|
|
336
|
+
|
|
337
|
+
if (isIteration4 && startMs === deleteBucketStartMs) {
|
|
338
|
+
// Simulate delete of one bucket.
|
|
339
|
+
if (cli.mode === "window_replace") {
|
|
340
|
+
// For window_replace, omit the bucket from the snapshot (it disappears after window delete).
|
|
341
|
+
simulatedDeletes++;
|
|
342
|
+
skipped++;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const bucketIndex = Math.floor(startMs / bucketMs);
|
|
347
|
+
const producedKg = simulateProducedKg(bucketIndex, it.idx);
|
|
348
|
+
const scrapKg = simulateScrapKg(producedKg, bucketIndex, it.idx);
|
|
349
|
+
attrs.push({
|
|
350
|
+
attribute,
|
|
351
|
+
table: {
|
|
352
|
+
dataGroup: cli.dataGroup,
|
|
353
|
+
time: bucketStartIso,
|
|
354
|
+
intervalStart: bucketStartIso,
|
|
355
|
+
intervalEnd: bucketEndIso,
|
|
356
|
+
windowStart: windowStartIso,
|
|
357
|
+
windowEnd: windowEndIso,
|
|
358
|
+
deleted: true,
|
|
359
|
+
columns: buildColumns(producedKg, scrapKg),
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
simulatedDeletes++;
|
|
363
|
+
tombstones++;
|
|
364
|
+
published++;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
297
368
|
if (missing) {
|
|
298
369
|
if (it.idx === 3 && cli.mode === "dedup") {
|
|
299
370
|
const bucketIndex = Math.floor(startMs / bucketMs);
|
|
@@ -339,7 +410,7 @@ async function main() {
|
|
|
339
410
|
}
|
|
340
411
|
|
|
341
412
|
logger.info(
|
|
342
|
-
`Iteration ${it.idx}: window=[${windowStartIso}, ${windowEndIso}) publish=${published} skipped=${skipped} tombstones=${tombstones}`,
|
|
413
|
+
`Iteration ${it.idx}: window=[${windowStartIso}, ${windowEndIso}) publish=${published} skipped=${skipped} tombstones=${tombstones} deletes=${simulatedDeletes}`,
|
|
343
414
|
);
|
|
344
415
|
|
|
345
416
|
await mqttOutput.publishMqttMessage(
|