@camstack/addon-post-analysis 1.2.99 → 1.2.101
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/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Caz0R4UZ.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D8HLGYbH.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-By6c47y7.mjs → hostInit-Br0v_V7W.mjs} +1 -1
- package/dist/pipeline-analytics/index.js +375 -135
- package/dist/pipeline-analytics/index.mjs +375 -135
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -8304,23 +8304,23 @@ var en_default = {
|
|
|
8304
8304
|
},
|
|
8305
8305
|
"class.other": "{{name}}",
|
|
8306
8306
|
"group.others.person": {
|
|
8307
|
-
"one": "
|
|
8307
|
+
"one": "{{count}} other person",
|
|
8308
8308
|
"other": "{{count}} other people"
|
|
8309
8309
|
},
|
|
8310
8310
|
"group.others.vehicle": {
|
|
8311
|
-
"one": "
|
|
8311
|
+
"one": "{{count}} other vehicle",
|
|
8312
8312
|
"other": "{{count}} other vehicles"
|
|
8313
8313
|
},
|
|
8314
8314
|
"group.others.animal": {
|
|
8315
|
-
"one": "
|
|
8315
|
+
"one": "{{count}} other animal",
|
|
8316
8316
|
"other": "{{count}} other animals"
|
|
8317
8317
|
},
|
|
8318
8318
|
"group.others.package": {
|
|
8319
|
-
"one": "
|
|
8319
|
+
"one": "{{count}} other package",
|
|
8320
8320
|
"other": "{{count}} other packages"
|
|
8321
8321
|
},
|
|
8322
8322
|
"group.others.other": {
|
|
8323
|
-
"one": "
|
|
8323
|
+
"one": "{{count}} other {{classNoun}}",
|
|
8324
8324
|
"other": "{{count}} other {{classNoun}}"
|
|
8325
8325
|
},
|
|
8326
8326
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
@@ -8518,23 +8518,23 @@ var it_default = {
|
|
|
8518
8518
|
},
|
|
8519
8519
|
"class.other": "{{name}}",
|
|
8520
8520
|
"group.others.person": {
|
|
8521
|
-
"one": "
|
|
8521
|
+
"one": "{{count}} altra persona",
|
|
8522
8522
|
"other": "{{count}} altre persone"
|
|
8523
8523
|
},
|
|
8524
8524
|
"group.others.vehicle": {
|
|
8525
|
-
"one": "
|
|
8525
|
+
"one": "{{count}} altro veicolo",
|
|
8526
8526
|
"other": "{{count}} altri veicoli"
|
|
8527
8527
|
},
|
|
8528
8528
|
"group.others.animal": {
|
|
8529
|
-
"one": "
|
|
8529
|
+
"one": "{{count}} altro animale",
|
|
8530
8530
|
"other": "{{count}} altri animali"
|
|
8531
8531
|
},
|
|
8532
8532
|
"group.others.package": {
|
|
8533
|
-
"one": "
|
|
8533
|
+
"one": "{{count}} altro pacco",
|
|
8534
8534
|
"other": "{{count}} altri pacchi"
|
|
8535
8535
|
},
|
|
8536
8536
|
"group.others.other": {
|
|
8537
|
-
"one": "
|
|
8537
|
+
"one": "{{count}} altro {{classNoun}}",
|
|
8538
8538
|
"other": "{{count}} altri {{classNoun}}"
|
|
8539
8539
|
},
|
|
8540
8540
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
@@ -31058,8 +31058,14 @@ function reportUnvouchedClips(deps, deviceId, clips) {
|
|
|
31058
31058
|
* @durable class=config owner=pipeline-analytics
|
|
31059
31059
|
* write="a signed-in viewer user saves a named snapshot of their local stores"
|
|
31060
31060
|
* retention="none — a row goes only when its owner deletes it"
|
|
31061
|
+
*
|
|
31062
|
+
* Renamed from the bare `viewer-settings-snapshots` on 2026-08-19: an addon
|
|
31063
|
+
* collection carries its owner's prefix, and the bare name sat in the same flat
|
|
31064
|
+
* namespace as the kernel's `users` / `system-settings`. Rows already written
|
|
31065
|
+
* under the old name are adopted at boot by
|
|
31066
|
+
* `viewer-settings-legacy-adoption.ts`.
|
|
31061
31067
|
*/
|
|
31062
|
-
var VIEWER_SETTINGS_SNAPSHOTS_COLLECTION = "viewer-settings-snapshots";
|
|
31068
|
+
var VIEWER_SETTINGS_SNAPSHOTS_COLLECTION = "pipeline-analytics:viewer-settings-snapshots";
|
|
31063
31069
|
var COLUMNS = [
|
|
31064
31070
|
{
|
|
31065
31071
|
name: "id",
|
|
@@ -31159,7 +31165,7 @@ var ViewerSettingsSnapshotStore = class {
|
|
|
31159
31165
|
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31160
31166
|
columns: [...COLUMNS],
|
|
31161
31167
|
indexes: [{
|
|
31162
|
-
name: "
|
|
31168
|
+
name: "idx_pa_viewer_settings_owner",
|
|
31163
31169
|
columns: ["ownerUserId"]
|
|
31164
31170
|
}]
|
|
31165
31171
|
});
|
|
@@ -31258,6 +31264,183 @@ function makeViewerSettingsActionHandlers(store) {
|
|
|
31258
31264
|
}
|
|
31259
31265
|
};
|
|
31260
31266
|
}
|
|
31267
|
+
//#endregion
|
|
31268
|
+
//#region src/pipeline-analytics/viewer-settings-legacy-adoption.ts
|
|
31269
|
+
/**
|
|
31270
|
+
* @durable class=config owner=pipeline-analytics
|
|
31271
|
+
* write="NOTHING writes it any more — it is the pre-2026-08-19 bare name of
|
|
31272
|
+
* `pipeline-analytics:viewer-settings-snapshots`, declared here only so its
|
|
31273
|
+
* surviving rows can be READ once and moved"
|
|
31274
|
+
* retention="drained, not pruned: a row goes as soon as the successor is
|
|
31275
|
+
* confirmed to hold the same id. The table is empty and inert once every
|
|
31276
|
+
* hub has booted this addon; it is then dropped through the kernel's
|
|
31277
|
+
* `RETIRED_COLLECTIONS` and this module deleted."
|
|
31278
|
+
*
|
|
31279
|
+
* Bare, and therefore a namespacing violation — hence the single
|
|
31280
|
+
* `ALLOWED_BARE` entry in `scripts/check-collection-namespacing.ts`, which
|
|
31281
|
+
* names this file and the condition for removing both.
|
|
31282
|
+
*/
|
|
31283
|
+
var LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION = "viewer-settings-snapshots";
|
|
31284
|
+
/**
|
|
31285
|
+
* The ORIGINAL column list, byte-for-byte.
|
|
31286
|
+
*
|
|
31287
|
+
* Do not "tidy" this to reference the successor's constant: they are allowed to
|
|
31288
|
+
* diverge, and the day they do, a shared constant would silently rebuild the
|
|
31289
|
+
* legacy table and destroy the rows.
|
|
31290
|
+
*/
|
|
31291
|
+
var LEGACY_COLUMNS = [
|
|
31292
|
+
{
|
|
31293
|
+
name: "id",
|
|
31294
|
+
type: "TEXT",
|
|
31295
|
+
primaryKey: true,
|
|
31296
|
+
notNull: true
|
|
31297
|
+
},
|
|
31298
|
+
{
|
|
31299
|
+
name: "name",
|
|
31300
|
+
type: "TEXT",
|
|
31301
|
+
notNull: true
|
|
31302
|
+
},
|
|
31303
|
+
{
|
|
31304
|
+
name: "ownerUserId",
|
|
31305
|
+
type: "TEXT",
|
|
31306
|
+
notNull: true
|
|
31307
|
+
},
|
|
31308
|
+
{
|
|
31309
|
+
name: "ownerUsername",
|
|
31310
|
+
type: "TEXT",
|
|
31311
|
+
notNull: true
|
|
31312
|
+
},
|
|
31313
|
+
{
|
|
31314
|
+
name: "createdAt",
|
|
31315
|
+
type: "INTEGER",
|
|
31316
|
+
notNull: true
|
|
31317
|
+
},
|
|
31318
|
+
{
|
|
31319
|
+
name: "updatedAt",
|
|
31320
|
+
type: "INTEGER",
|
|
31321
|
+
notNull: true
|
|
31322
|
+
},
|
|
31323
|
+
{
|
|
31324
|
+
name: "blob",
|
|
31325
|
+
type: "JSON",
|
|
31326
|
+
notNull: true
|
|
31327
|
+
}
|
|
31328
|
+
];
|
|
31329
|
+
/** Bounded read: this table held 1 row on the reference hub. */
|
|
31330
|
+
var LEGACY_READ_LIMIT = 500;
|
|
31331
|
+
function errText(err) {
|
|
31332
|
+
return err instanceof Error ? err.message : String(err);
|
|
31333
|
+
}
|
|
31334
|
+
function asRows(raw) {
|
|
31335
|
+
if (!Array.isArray(raw)) return [];
|
|
31336
|
+
const out = [];
|
|
31337
|
+
for (const item of raw) {
|
|
31338
|
+
if (typeof item !== "object" || item === null) continue;
|
|
31339
|
+
const id = Reflect.get(item, "id");
|
|
31340
|
+
const data = Reflect.get(item, "data");
|
|
31341
|
+
if (typeof id !== "string" || typeof data !== "object" || data === null) continue;
|
|
31342
|
+
out.push({
|
|
31343
|
+
id,
|
|
31344
|
+
data
|
|
31345
|
+
});
|
|
31346
|
+
}
|
|
31347
|
+
return out;
|
|
31348
|
+
}
|
|
31349
|
+
/**
|
|
31350
|
+
* Move every legacy row into the successor and clear the legacy table.
|
|
31351
|
+
*
|
|
31352
|
+
* Returns the number of legacy rows RETIRED — the completion metric, not the
|
|
31353
|
+
* number written. A row copied but not yet verified counts as zero and is
|
|
31354
|
+
* retried on the next boot, which is the safe direction.
|
|
31355
|
+
*/
|
|
31356
|
+
async function adoptLegacyViewerSettingsSnapshots(deps) {
|
|
31357
|
+
await deps.store.declareCollection.mutate({
|
|
31358
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31359
|
+
columns: LEGACY_COLUMNS
|
|
31360
|
+
});
|
|
31361
|
+
let legacy;
|
|
31362
|
+
try {
|
|
31363
|
+
legacy = asRows(await deps.store.query.query({
|
|
31364
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31365
|
+
filter: { limit: LEGACY_READ_LIMIT }
|
|
31366
|
+
}));
|
|
31367
|
+
} catch (err) {
|
|
31368
|
+
deps.logger.warn("viewer-settings legacy adoption skipped — legacy read failed", { meta: {
|
|
31369
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31370
|
+
error: errText(err)
|
|
31371
|
+
} });
|
|
31372
|
+
return 0;
|
|
31373
|
+
}
|
|
31374
|
+
if (legacy.length === 0) return 0;
|
|
31375
|
+
let existing;
|
|
31376
|
+
try {
|
|
31377
|
+
existing = new Set(asRows(await deps.store.query.query({
|
|
31378
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31379
|
+
filter: { limit: LEGACY_READ_LIMIT }
|
|
31380
|
+
})).map((r) => r.id));
|
|
31381
|
+
} catch (err) {
|
|
31382
|
+
deps.logger.warn("viewer-settings legacy adoption skipped — successor read failed", { meta: {
|
|
31383
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31384
|
+
error: errText(err)
|
|
31385
|
+
} });
|
|
31386
|
+
return 0;
|
|
31387
|
+
}
|
|
31388
|
+
let written = 0;
|
|
31389
|
+
for (const row of legacy) {
|
|
31390
|
+
if (existing.has(row.id)) continue;
|
|
31391
|
+
try {
|
|
31392
|
+
await deps.store.set.mutate({
|
|
31393
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31394
|
+
key: row.id,
|
|
31395
|
+
value: row.data
|
|
31396
|
+
});
|
|
31397
|
+
written += 1;
|
|
31398
|
+
} catch (err) {
|
|
31399
|
+
deps.logger.warn("viewer-settings legacy adoption — one row failed to write", { meta: {
|
|
31400
|
+
snapshotId: row.id,
|
|
31401
|
+
error: errText(err)
|
|
31402
|
+
} });
|
|
31403
|
+
}
|
|
31404
|
+
}
|
|
31405
|
+
let landed;
|
|
31406
|
+
try {
|
|
31407
|
+
landed = new Set(asRows(await deps.store.query.query({
|
|
31408
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31409
|
+
filter: { limit: LEGACY_READ_LIMIT }
|
|
31410
|
+
})).map((r) => r.id));
|
|
31411
|
+
} catch (err) {
|
|
31412
|
+
deps.logger.warn("viewer-settings legacy adoption — verification read failed, legacy rows kept", { meta: {
|
|
31413
|
+
written,
|
|
31414
|
+
error: errText(err)
|
|
31415
|
+
} });
|
|
31416
|
+
return 0;
|
|
31417
|
+
}
|
|
31418
|
+
let retired = 0;
|
|
31419
|
+
for (const row of legacy) {
|
|
31420
|
+
if (!landed.has(row.id)) continue;
|
|
31421
|
+
try {
|
|
31422
|
+
await deps.store.delete.mutate({
|
|
31423
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31424
|
+
key: row.id
|
|
31425
|
+
});
|
|
31426
|
+
retired += 1;
|
|
31427
|
+
} catch (err) {
|
|
31428
|
+
deps.logger.warn("viewer-settings legacy adoption — one legacy row failed to delete", { meta: {
|
|
31429
|
+
snapshotId: row.id,
|
|
31430
|
+
error: errText(err)
|
|
31431
|
+
} });
|
|
31432
|
+
}
|
|
31433
|
+
}
|
|
31434
|
+
deps.logger.warn("viewer-settings snapshots adopted from the legacy bare collection", { meta: {
|
|
31435
|
+
from: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31436
|
+
to: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31437
|
+
found: legacy.length,
|
|
31438
|
+
written,
|
|
31439
|
+
retired,
|
|
31440
|
+
kept: legacy.length - retired
|
|
31441
|
+
} });
|
|
31442
|
+
return retired;
|
|
31443
|
+
}
|
|
31261
31444
|
/**
|
|
31262
31445
|
* The VIDEO's playback rate — 4×, the same as {@link NC_GIF_SPEED}.
|
|
31263
31446
|
*
|
|
@@ -40020,6 +40203,129 @@ function buildRecordedStillPorts(input) {
|
|
|
40020
40203
|
})
|
|
40021
40204
|
};
|
|
40022
40205
|
}
|
|
40206
|
+
/** Where the completion marker lives. */
|
|
40207
|
+
var MIGRATION_MARKER_COLLECTION = "pipeline-analytics:migrations";
|
|
40208
|
+
var MIGRATION_MARKER_KEY = "label-tier-4g";
|
|
40209
|
+
/**
|
|
40210
|
+
* The decision for ONE row, exposed so the rule is testable without a store.
|
|
40211
|
+
*
|
|
40212
|
+
* `null` = leave the row alone.
|
|
40213
|
+
*/
|
|
40214
|
+
function decideRowMigration(data, now) {
|
|
40215
|
+
const label = data["label"];
|
|
40216
|
+
if (typeof label !== "string" || label.length === 0) return null;
|
|
40217
|
+
if (typeof data["subLabel"] === "string" && data["subLabel"].length > 0) return null;
|
|
40218
|
+
if (data["labelMeta"] !== null && data["labelMeta"] !== void 0) return null;
|
|
40219
|
+
if (isMacroClass(label)) return {
|
|
40220
|
+
label: null,
|
|
40221
|
+
labelScore: null,
|
|
40222
|
+
labelMeta: null
|
|
40223
|
+
};
|
|
40224
|
+
return {
|
|
40225
|
+
label: null,
|
|
40226
|
+
labelScore: null,
|
|
40227
|
+
labelMeta: null,
|
|
40228
|
+
subLabel: label,
|
|
40229
|
+
subLabelScore: null,
|
|
40230
|
+
subLabelMeta: {
|
|
40231
|
+
stepId: MIGRATION_STEP_ID,
|
|
40232
|
+
decidedAt: now
|
|
40233
|
+
}
|
|
40234
|
+
};
|
|
40235
|
+
}
|
|
40236
|
+
/** Migrate one collection, paged and bounded. */
|
|
40237
|
+
async function migrateCollectionLabelTiers(deps, collection) {
|
|
40238
|
+
let scanned = 0;
|
|
40239
|
+
let demoted = 0;
|
|
40240
|
+
let cleared = 0;
|
|
40241
|
+
let skipped = 0;
|
|
40242
|
+
let offset = 0;
|
|
40243
|
+
let truncated = false;
|
|
40244
|
+
for (;;) {
|
|
40245
|
+
if (scanned >= 2e5) {
|
|
40246
|
+
truncated = true;
|
|
40247
|
+
break;
|
|
40248
|
+
}
|
|
40249
|
+
const rows = await deps.store.query.query({
|
|
40250
|
+
collection,
|
|
40251
|
+
filter: {
|
|
40252
|
+
limit: 500,
|
|
40253
|
+
offset
|
|
40254
|
+
}
|
|
40255
|
+
});
|
|
40256
|
+
if (rows.length === 0) break;
|
|
40257
|
+
for (const row of rows) {
|
|
40258
|
+
scanned++;
|
|
40259
|
+
const patch = decideRowMigration(row.data, deps.now());
|
|
40260
|
+
if (patch === null) {
|
|
40261
|
+
skipped++;
|
|
40262
|
+
continue;
|
|
40263
|
+
}
|
|
40264
|
+
try {
|
|
40265
|
+
await deps.store.update.mutate({
|
|
40266
|
+
collection,
|
|
40267
|
+
id: row.id,
|
|
40268
|
+
data: patch
|
|
40269
|
+
});
|
|
40270
|
+
if (patch["subLabel"] === void 0) cleared++;
|
|
40271
|
+
else demoted++;
|
|
40272
|
+
} catch (err) {
|
|
40273
|
+
deps.logger.warn("label tier migration row failed", { meta: {
|
|
40274
|
+
collection,
|
|
40275
|
+
id: row.id,
|
|
40276
|
+
error: String(err)
|
|
40277
|
+
} });
|
|
40278
|
+
skipped++;
|
|
40279
|
+
}
|
|
40280
|
+
}
|
|
40281
|
+
if (rows.length < 500) break;
|
|
40282
|
+
offset += rows.length;
|
|
40283
|
+
}
|
|
40284
|
+
return {
|
|
40285
|
+
collection,
|
|
40286
|
+
scanned,
|
|
40287
|
+
demoted,
|
|
40288
|
+
cleared,
|
|
40289
|
+
skipped,
|
|
40290
|
+
truncated
|
|
40291
|
+
};
|
|
40292
|
+
}
|
|
40293
|
+
/**
|
|
40294
|
+
* Run the migration across the collections that carry the tiered columns, once.
|
|
40295
|
+
*
|
|
40296
|
+
* The marker short-circuits a completed migration; it is written only when
|
|
40297
|
+
* NEITHER collection was truncated, so an interrupted pass resumes on the next
|
|
40298
|
+
* boot instead of declaring itself done.
|
|
40299
|
+
*/
|
|
40300
|
+
async function runLabelTierMigration(deps, collections) {
|
|
40301
|
+
try {
|
|
40302
|
+
const marker = await deps.store.get.query({
|
|
40303
|
+
collection: MIGRATION_MARKER_COLLECTION,
|
|
40304
|
+
key: MIGRATION_MARKER_KEY
|
|
40305
|
+
});
|
|
40306
|
+
if (marker !== null && marker !== void 0) return [];
|
|
40307
|
+
} catch {}
|
|
40308
|
+
const stats = [];
|
|
40309
|
+
for (const collection of collections) stats.push(await migrateCollectionLabelTiers(deps, collection));
|
|
40310
|
+
if (stats.every((s) => !s.truncated)) try {
|
|
40311
|
+
await deps.store.set.mutate({
|
|
40312
|
+
collection: MIGRATION_MARKER_COLLECTION,
|
|
40313
|
+
key: MIGRATION_MARKER_KEY,
|
|
40314
|
+
value: { completedAt: deps.now() }
|
|
40315
|
+
});
|
|
40316
|
+
} catch (err) {
|
|
40317
|
+
deps.logger.warn("label tier migration marker failed", { meta: { error: String(err) } });
|
|
40318
|
+
}
|
|
40319
|
+
for (const s of stats) deps.logger.info("label tier migration", { meta: {
|
|
40320
|
+
collection: s.collection,
|
|
40321
|
+
scanned: s.scanned,
|
|
40322
|
+
demoted: s.demoted,
|
|
40323
|
+
cleared: s.cleared,
|
|
40324
|
+
skipped: s.skipped,
|
|
40325
|
+
truncated: s.truncated
|
|
40326
|
+
} });
|
|
40327
|
+
return stats;
|
|
40328
|
+
}
|
|
40023
40329
|
//#endregion
|
|
40024
40330
|
//#region src/durable/lts-aggregator.ts
|
|
40025
40331
|
/** Wall-clock bucket width. Five minutes, matching HA's statistics tier. */
|
|
@@ -45211,6 +45517,55 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
45211
45517
|
why: "a five-minute statistics row summarises an INTERVAL, not an entity — there is no owner it can be orphaned from. Its bound is a ROW CAP per (subject, series, scope) enforced by the aggregator itself (105,120 rows ≈ one year), deliberately not an age sweep: D61 collapsed ten disagreeing age clocks into one, and an LTS series has no clock but its own."
|
|
45212
45518
|
}
|
|
45213
45519
|
},
|
|
45520
|
+
{
|
|
45521
|
+
collection: RETRAIN_ANNOTATIONS_COLLECTION,
|
|
45522
|
+
classification: {
|
|
45523
|
+
kind: "never-orphaned",
|
|
45524
|
+
why: "the ground truth an operator DREW. It declares `trackId` and must be classified by hand for that reason: the id is provenance, not ownership. The dataset addresses COPIES (D81), which is precisely what makes a 'trained' track evictable again — so a track ageing out leaves the annotations standing. Rows go only through `deleteForFrame`, when the operator deselects the frame. Deleting one because its track expired would destroy labelling work no sweep can recreate."
|
|
45525
|
+
}
|
|
45526
|
+
},
|
|
45527
|
+
{
|
|
45528
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
45529
|
+
classification: {
|
|
45530
|
+
kind: "never-orphaned",
|
|
45531
|
+
why: "the frame COPIES the dataset owns outright, under a `retrain` prefix no owner cascade and no age sweep walks (D81). Also declares `trackId` as provenance. Rows go only through `deleteFrame`, after their annotations, on operator deselection. NOTE: nothing caps this table or the disk it consumes — that is a deliberate decision, not an oversight, and it is the one collection here whose bound is an operator's habit."
|
|
45532
|
+
}
|
|
45533
|
+
},
|
|
45534
|
+
{
|
|
45535
|
+
collection: SCENE_COLLECTION,
|
|
45536
|
+
classification: {
|
|
45537
|
+
kind: "never-orphaned",
|
|
45538
|
+
why: "operator intent — one row per (deviceId, monitorId), created and destroyed by scene CRUD or by a device-removal sweep. Nothing retention does can orphan it, and no age clock applies: a scene the operator configured and never revisits is not stale, it is configured."
|
|
45539
|
+
}
|
|
45540
|
+
},
|
|
45541
|
+
{
|
|
45542
|
+
collection: SCENE_LATCH_COLLECTION,
|
|
45543
|
+
classification: {
|
|
45544
|
+
kind: "never-orphaned",
|
|
45545
|
+
why: "it IS owned — by the scene set — but the owner-cleanup is self-contained: `pruneExcept(the scene set)` runs on every scene reload, so a latch whose scene is gone is collected there and never by retention. Deliberately NOT `owner-indirection`: that kind puts a collection into `auditableCollections()`, and the orphan audit resolves exactly one indirection, that of `pipeline-analytics:media`."
|
|
45546
|
+
}
|
|
45547
|
+
},
|
|
45548
|
+
{
|
|
45549
|
+
collection: MIGRATION_MARKER_COLLECTION,
|
|
45550
|
+
classification: {
|
|
45551
|
+
kind: "never-orphaned",
|
|
45552
|
+
why: "a one-row completion marker for the label-tier 4g pass. It summarises a one-time event, so there is no entity it can be orphaned from — and its PRESENCE is what stops the migration running again, so deleting it is not cleanup, it is a re-run over every track, face and object-event row."
|
|
45553
|
+
}
|
|
45554
|
+
},
|
|
45555
|
+
{
|
|
45556
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
45557
|
+
classification: {
|
|
45558
|
+
kind: "never-orphaned",
|
|
45559
|
+
why: "a hub user's named snapshot of their own viewer layout. It is not analytics data — it happens to live in this addon because the actions ride `addons.custom`. Owned by the user who saved it and deleted only by them; no track, device or event can orphan it, and no age applies."
|
|
45560
|
+
}
|
|
45561
|
+
},
|
|
45562
|
+
{
|
|
45563
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
45564
|
+
classification: {
|
|
45565
|
+
kind: "never-orphaned",
|
|
45566
|
+
why: "the drained predecessor of the row above, declared only so its rows can be read once and moved (`viewer-settings-legacy-adoption.ts`). Retention must never touch it: its rows go one way only, to the successor, gated on a re-read that confirms they landed. Delete this entry together with that module."
|
|
45567
|
+
}
|
|
45568
|
+
},
|
|
45214
45569
|
{
|
|
45215
45570
|
collection: STATIONARY_COLLECTION,
|
|
45216
45571
|
classification: {
|
|
@@ -49890,129 +50245,6 @@ function evaluatePeriodicSnapshot(input) {
|
|
|
49890
50245
|
movedFraction
|
|
49891
50246
|
};
|
|
49892
50247
|
}
|
|
49893
|
-
/** Where the completion marker lives. */
|
|
49894
|
-
var MIGRATION_MARKER_COLLECTION = "pipeline-analytics:migrations";
|
|
49895
|
-
var MIGRATION_MARKER_KEY = "label-tier-4g";
|
|
49896
|
-
/**
|
|
49897
|
-
* The decision for ONE row, exposed so the rule is testable without a store.
|
|
49898
|
-
*
|
|
49899
|
-
* `null` = leave the row alone.
|
|
49900
|
-
*/
|
|
49901
|
-
function decideRowMigration(data, now) {
|
|
49902
|
-
const label = data["label"];
|
|
49903
|
-
if (typeof label !== "string" || label.length === 0) return null;
|
|
49904
|
-
if (typeof data["subLabel"] === "string" && data["subLabel"].length > 0) return null;
|
|
49905
|
-
if (data["labelMeta"] !== null && data["labelMeta"] !== void 0) return null;
|
|
49906
|
-
if (isMacroClass(label)) return {
|
|
49907
|
-
label: null,
|
|
49908
|
-
labelScore: null,
|
|
49909
|
-
labelMeta: null
|
|
49910
|
-
};
|
|
49911
|
-
return {
|
|
49912
|
-
label: null,
|
|
49913
|
-
labelScore: null,
|
|
49914
|
-
labelMeta: null,
|
|
49915
|
-
subLabel: label,
|
|
49916
|
-
subLabelScore: null,
|
|
49917
|
-
subLabelMeta: {
|
|
49918
|
-
stepId: MIGRATION_STEP_ID,
|
|
49919
|
-
decidedAt: now
|
|
49920
|
-
}
|
|
49921
|
-
};
|
|
49922
|
-
}
|
|
49923
|
-
/** Migrate one collection, paged and bounded. */
|
|
49924
|
-
async function migrateCollectionLabelTiers(deps, collection) {
|
|
49925
|
-
let scanned = 0;
|
|
49926
|
-
let demoted = 0;
|
|
49927
|
-
let cleared = 0;
|
|
49928
|
-
let skipped = 0;
|
|
49929
|
-
let offset = 0;
|
|
49930
|
-
let truncated = false;
|
|
49931
|
-
for (;;) {
|
|
49932
|
-
if (scanned >= 2e5) {
|
|
49933
|
-
truncated = true;
|
|
49934
|
-
break;
|
|
49935
|
-
}
|
|
49936
|
-
const rows = await deps.store.query.query({
|
|
49937
|
-
collection,
|
|
49938
|
-
filter: {
|
|
49939
|
-
limit: 500,
|
|
49940
|
-
offset
|
|
49941
|
-
}
|
|
49942
|
-
});
|
|
49943
|
-
if (rows.length === 0) break;
|
|
49944
|
-
for (const row of rows) {
|
|
49945
|
-
scanned++;
|
|
49946
|
-
const patch = decideRowMigration(row.data, deps.now());
|
|
49947
|
-
if (patch === null) {
|
|
49948
|
-
skipped++;
|
|
49949
|
-
continue;
|
|
49950
|
-
}
|
|
49951
|
-
try {
|
|
49952
|
-
await deps.store.update.mutate({
|
|
49953
|
-
collection,
|
|
49954
|
-
id: row.id,
|
|
49955
|
-
data: patch
|
|
49956
|
-
});
|
|
49957
|
-
if (patch["subLabel"] === void 0) cleared++;
|
|
49958
|
-
else demoted++;
|
|
49959
|
-
} catch (err) {
|
|
49960
|
-
deps.logger.warn("label tier migration row failed", { meta: {
|
|
49961
|
-
collection,
|
|
49962
|
-
id: row.id,
|
|
49963
|
-
error: String(err)
|
|
49964
|
-
} });
|
|
49965
|
-
skipped++;
|
|
49966
|
-
}
|
|
49967
|
-
}
|
|
49968
|
-
if (rows.length < 500) break;
|
|
49969
|
-
offset += rows.length;
|
|
49970
|
-
}
|
|
49971
|
-
return {
|
|
49972
|
-
collection,
|
|
49973
|
-
scanned,
|
|
49974
|
-
demoted,
|
|
49975
|
-
cleared,
|
|
49976
|
-
skipped,
|
|
49977
|
-
truncated
|
|
49978
|
-
};
|
|
49979
|
-
}
|
|
49980
|
-
/**
|
|
49981
|
-
* Run the migration across the collections that carry the tiered columns, once.
|
|
49982
|
-
*
|
|
49983
|
-
* The marker short-circuits a completed migration; it is written only when
|
|
49984
|
-
* NEITHER collection was truncated, so an interrupted pass resumes on the next
|
|
49985
|
-
* boot instead of declaring itself done.
|
|
49986
|
-
*/
|
|
49987
|
-
async function runLabelTierMigration(deps, collections) {
|
|
49988
|
-
try {
|
|
49989
|
-
const marker = await deps.store.get.query({
|
|
49990
|
-
collection: MIGRATION_MARKER_COLLECTION,
|
|
49991
|
-
key: MIGRATION_MARKER_KEY
|
|
49992
|
-
});
|
|
49993
|
-
if (marker !== null && marker !== void 0) return [];
|
|
49994
|
-
} catch {}
|
|
49995
|
-
const stats = [];
|
|
49996
|
-
for (const collection of collections) stats.push(await migrateCollectionLabelTiers(deps, collection));
|
|
49997
|
-
if (stats.every((s) => !s.truncated)) try {
|
|
49998
|
-
await deps.store.set.mutate({
|
|
49999
|
-
collection: MIGRATION_MARKER_COLLECTION,
|
|
50000
|
-
key: MIGRATION_MARKER_KEY,
|
|
50001
|
-
value: { completedAt: deps.now() }
|
|
50002
|
-
});
|
|
50003
|
-
} catch (err) {
|
|
50004
|
-
deps.logger.warn("label tier migration marker failed", { meta: { error: String(err) } });
|
|
50005
|
-
}
|
|
50006
|
-
for (const s of stats) deps.logger.info("label tier migration", { meta: {
|
|
50007
|
-
collection: s.collection,
|
|
50008
|
-
scanned: s.scanned,
|
|
50009
|
-
demoted: s.demoted,
|
|
50010
|
-
cleared: s.cleared,
|
|
50011
|
-
skipped: s.skipped,
|
|
50012
|
-
truncated: s.truncated
|
|
50013
|
-
} });
|
|
50014
|
-
return stats;
|
|
50015
|
-
}
|
|
50016
50248
|
//#endregion
|
|
50017
50249
|
//#region src/pipeline-analytics/store/object-embedding-store.ts
|
|
50018
50250
|
/**
|
|
@@ -52424,6 +52656,14 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
52424
52656
|
ViewerSettingsSnapshotStore.declare(api.settingsStore)
|
|
52425
52657
|
]);
|
|
52426
52658
|
this.viewerSettingsSnapshots = new ViewerSettingsSnapshotStore(api.settingsStore);
|
|
52659
|
+
try {
|
|
52660
|
+
await adoptLegacyViewerSettingsSnapshots({
|
|
52661
|
+
store: api.settingsStore,
|
|
52662
|
+
logger: this.ctx.logger.child("viewer-settings")
|
|
52663
|
+
});
|
|
52664
|
+
} catch (err) {
|
|
52665
|
+
this.ctx.logger.warn("viewer-settings legacy adoption threw", { meta: { error: require_dist.errMsg(err) } });
|
|
52666
|
+
}
|
|
52427
52667
|
runLabelTierMigration({
|
|
52428
52668
|
store: api.settingsStore,
|
|
52429
52669
|
logger: this.ctx.logger,
|
|
@@ -8297,23 +8297,23 @@ var en_default = {
|
|
|
8297
8297
|
},
|
|
8298
8298
|
"class.other": "{{name}}",
|
|
8299
8299
|
"group.others.person": {
|
|
8300
|
-
"one": "
|
|
8300
|
+
"one": "{{count}} other person",
|
|
8301
8301
|
"other": "{{count}} other people"
|
|
8302
8302
|
},
|
|
8303
8303
|
"group.others.vehicle": {
|
|
8304
|
-
"one": "
|
|
8304
|
+
"one": "{{count}} other vehicle",
|
|
8305
8305
|
"other": "{{count}} other vehicles"
|
|
8306
8306
|
},
|
|
8307
8307
|
"group.others.animal": {
|
|
8308
|
-
"one": "
|
|
8308
|
+
"one": "{{count}} other animal",
|
|
8309
8309
|
"other": "{{count}} other animals"
|
|
8310
8310
|
},
|
|
8311
8311
|
"group.others.package": {
|
|
8312
|
-
"one": "
|
|
8312
|
+
"one": "{{count}} other package",
|
|
8313
8313
|
"other": "{{count}} other packages"
|
|
8314
8314
|
},
|
|
8315
8315
|
"group.others.other": {
|
|
8316
|
-
"one": "
|
|
8316
|
+
"one": "{{count}} other {{classNoun}}",
|
|
8317
8317
|
"other": "{{count}} other {{classNoun}}"
|
|
8318
8318
|
},
|
|
8319
8319
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
@@ -8511,23 +8511,23 @@ var it_default = {
|
|
|
8511
8511
|
},
|
|
8512
8512
|
"class.other": "{{name}}",
|
|
8513
8513
|
"group.others.person": {
|
|
8514
|
-
"one": "
|
|
8514
|
+
"one": "{{count}} altra persona",
|
|
8515
8515
|
"other": "{{count}} altre persone"
|
|
8516
8516
|
},
|
|
8517
8517
|
"group.others.vehicle": {
|
|
8518
|
-
"one": "
|
|
8518
|
+
"one": "{{count}} altro veicolo",
|
|
8519
8519
|
"other": "{{count}} altri veicoli"
|
|
8520
8520
|
},
|
|
8521
8521
|
"group.others.animal": {
|
|
8522
|
-
"one": "
|
|
8522
|
+
"one": "{{count}} altro animale",
|
|
8523
8523
|
"other": "{{count}} altri animali"
|
|
8524
8524
|
},
|
|
8525
8525
|
"group.others.package": {
|
|
8526
|
-
"one": "
|
|
8526
|
+
"one": "{{count}} altro pacco",
|
|
8527
8527
|
"other": "{{count}} altri pacchi"
|
|
8528
8528
|
},
|
|
8529
8529
|
"group.others.other": {
|
|
8530
|
-
"one": "
|
|
8530
|
+
"one": "{{count}} altro {{classNoun}}",
|
|
8531
8531
|
"other": "{{count}} altri {{classNoun}}"
|
|
8532
8532
|
},
|
|
8533
8533
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
@@ -30999,8 +30999,14 @@ function reportUnvouchedClips(deps, deviceId, clips) {
|
|
|
30999
30999
|
* @durable class=config owner=pipeline-analytics
|
|
31000
31000
|
* write="a signed-in viewer user saves a named snapshot of their local stores"
|
|
31001
31001
|
* retention="none — a row goes only when its owner deletes it"
|
|
31002
|
+
*
|
|
31003
|
+
* Renamed from the bare `viewer-settings-snapshots` on 2026-08-19: an addon
|
|
31004
|
+
* collection carries its owner's prefix, and the bare name sat in the same flat
|
|
31005
|
+
* namespace as the kernel's `users` / `system-settings`. Rows already written
|
|
31006
|
+
* under the old name are adopted at boot by
|
|
31007
|
+
* `viewer-settings-legacy-adoption.ts`.
|
|
31002
31008
|
*/
|
|
31003
|
-
var VIEWER_SETTINGS_SNAPSHOTS_COLLECTION = "viewer-settings-snapshots";
|
|
31009
|
+
var VIEWER_SETTINGS_SNAPSHOTS_COLLECTION = "pipeline-analytics:viewer-settings-snapshots";
|
|
31004
31010
|
var COLUMNS = [
|
|
31005
31011
|
{
|
|
31006
31012
|
name: "id",
|
|
@@ -31099,7 +31105,7 @@ var ViewerSettingsSnapshotStore = class {
|
|
|
31099
31105
|
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31100
31106
|
columns: [...COLUMNS],
|
|
31101
31107
|
indexes: [{
|
|
31102
|
-
name: "
|
|
31108
|
+
name: "idx_pa_viewer_settings_owner",
|
|
31103
31109
|
columns: ["ownerUserId"]
|
|
31104
31110
|
}]
|
|
31105
31111
|
});
|
|
@@ -31198,6 +31204,183 @@ function makeViewerSettingsActionHandlers(store) {
|
|
|
31198
31204
|
}
|
|
31199
31205
|
};
|
|
31200
31206
|
}
|
|
31207
|
+
//#endregion
|
|
31208
|
+
//#region src/pipeline-analytics/viewer-settings-legacy-adoption.ts
|
|
31209
|
+
/**
|
|
31210
|
+
* @durable class=config owner=pipeline-analytics
|
|
31211
|
+
* write="NOTHING writes it any more — it is the pre-2026-08-19 bare name of
|
|
31212
|
+
* `pipeline-analytics:viewer-settings-snapshots`, declared here only so its
|
|
31213
|
+
* surviving rows can be READ once and moved"
|
|
31214
|
+
* retention="drained, not pruned: a row goes as soon as the successor is
|
|
31215
|
+
* confirmed to hold the same id. The table is empty and inert once every
|
|
31216
|
+
* hub has booted this addon; it is then dropped through the kernel's
|
|
31217
|
+
* `RETIRED_COLLECTIONS` and this module deleted."
|
|
31218
|
+
*
|
|
31219
|
+
* Bare, and therefore a namespacing violation — hence the single
|
|
31220
|
+
* `ALLOWED_BARE` entry in `scripts/check-collection-namespacing.ts`, which
|
|
31221
|
+
* names this file and the condition for removing both.
|
|
31222
|
+
*/
|
|
31223
|
+
var LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION = "viewer-settings-snapshots";
|
|
31224
|
+
/**
|
|
31225
|
+
* The ORIGINAL column list, byte-for-byte.
|
|
31226
|
+
*
|
|
31227
|
+
* Do not "tidy" this to reference the successor's constant: they are allowed to
|
|
31228
|
+
* diverge, and the day they do, a shared constant would silently rebuild the
|
|
31229
|
+
* legacy table and destroy the rows.
|
|
31230
|
+
*/
|
|
31231
|
+
var LEGACY_COLUMNS = [
|
|
31232
|
+
{
|
|
31233
|
+
name: "id",
|
|
31234
|
+
type: "TEXT",
|
|
31235
|
+
primaryKey: true,
|
|
31236
|
+
notNull: true
|
|
31237
|
+
},
|
|
31238
|
+
{
|
|
31239
|
+
name: "name",
|
|
31240
|
+
type: "TEXT",
|
|
31241
|
+
notNull: true
|
|
31242
|
+
},
|
|
31243
|
+
{
|
|
31244
|
+
name: "ownerUserId",
|
|
31245
|
+
type: "TEXT",
|
|
31246
|
+
notNull: true
|
|
31247
|
+
},
|
|
31248
|
+
{
|
|
31249
|
+
name: "ownerUsername",
|
|
31250
|
+
type: "TEXT",
|
|
31251
|
+
notNull: true
|
|
31252
|
+
},
|
|
31253
|
+
{
|
|
31254
|
+
name: "createdAt",
|
|
31255
|
+
type: "INTEGER",
|
|
31256
|
+
notNull: true
|
|
31257
|
+
},
|
|
31258
|
+
{
|
|
31259
|
+
name: "updatedAt",
|
|
31260
|
+
type: "INTEGER",
|
|
31261
|
+
notNull: true
|
|
31262
|
+
},
|
|
31263
|
+
{
|
|
31264
|
+
name: "blob",
|
|
31265
|
+
type: "JSON",
|
|
31266
|
+
notNull: true
|
|
31267
|
+
}
|
|
31268
|
+
];
|
|
31269
|
+
/** Bounded read: this table held 1 row on the reference hub. */
|
|
31270
|
+
var LEGACY_READ_LIMIT = 500;
|
|
31271
|
+
function errText(err) {
|
|
31272
|
+
return err instanceof Error ? err.message : String(err);
|
|
31273
|
+
}
|
|
31274
|
+
function asRows(raw) {
|
|
31275
|
+
if (!Array.isArray(raw)) return [];
|
|
31276
|
+
const out = [];
|
|
31277
|
+
for (const item of raw) {
|
|
31278
|
+
if (typeof item !== "object" || item === null) continue;
|
|
31279
|
+
const id = Reflect.get(item, "id");
|
|
31280
|
+
const data = Reflect.get(item, "data");
|
|
31281
|
+
if (typeof id !== "string" || typeof data !== "object" || data === null) continue;
|
|
31282
|
+
out.push({
|
|
31283
|
+
id,
|
|
31284
|
+
data
|
|
31285
|
+
});
|
|
31286
|
+
}
|
|
31287
|
+
return out;
|
|
31288
|
+
}
|
|
31289
|
+
/**
|
|
31290
|
+
* Move every legacy row into the successor and clear the legacy table.
|
|
31291
|
+
*
|
|
31292
|
+
* Returns the number of legacy rows RETIRED — the completion metric, not the
|
|
31293
|
+
* number written. A row copied but not yet verified counts as zero and is
|
|
31294
|
+
* retried on the next boot, which is the safe direction.
|
|
31295
|
+
*/
|
|
31296
|
+
async function adoptLegacyViewerSettingsSnapshots(deps) {
|
|
31297
|
+
await deps.store.declareCollection.mutate({
|
|
31298
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31299
|
+
columns: LEGACY_COLUMNS
|
|
31300
|
+
});
|
|
31301
|
+
let legacy;
|
|
31302
|
+
try {
|
|
31303
|
+
legacy = asRows(await deps.store.query.query({
|
|
31304
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31305
|
+
filter: { limit: LEGACY_READ_LIMIT }
|
|
31306
|
+
}));
|
|
31307
|
+
} catch (err) {
|
|
31308
|
+
deps.logger.warn("viewer-settings legacy adoption skipped — legacy read failed", { meta: {
|
|
31309
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31310
|
+
error: errText(err)
|
|
31311
|
+
} });
|
|
31312
|
+
return 0;
|
|
31313
|
+
}
|
|
31314
|
+
if (legacy.length === 0) return 0;
|
|
31315
|
+
let existing;
|
|
31316
|
+
try {
|
|
31317
|
+
existing = new Set(asRows(await deps.store.query.query({
|
|
31318
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31319
|
+
filter: { limit: LEGACY_READ_LIMIT }
|
|
31320
|
+
})).map((r) => r.id));
|
|
31321
|
+
} catch (err) {
|
|
31322
|
+
deps.logger.warn("viewer-settings legacy adoption skipped — successor read failed", { meta: {
|
|
31323
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31324
|
+
error: errText(err)
|
|
31325
|
+
} });
|
|
31326
|
+
return 0;
|
|
31327
|
+
}
|
|
31328
|
+
let written = 0;
|
|
31329
|
+
for (const row of legacy) {
|
|
31330
|
+
if (existing.has(row.id)) continue;
|
|
31331
|
+
try {
|
|
31332
|
+
await deps.store.set.mutate({
|
|
31333
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31334
|
+
key: row.id,
|
|
31335
|
+
value: row.data
|
|
31336
|
+
});
|
|
31337
|
+
written += 1;
|
|
31338
|
+
} catch (err) {
|
|
31339
|
+
deps.logger.warn("viewer-settings legacy adoption — one row failed to write", { meta: {
|
|
31340
|
+
snapshotId: row.id,
|
|
31341
|
+
error: errText(err)
|
|
31342
|
+
} });
|
|
31343
|
+
}
|
|
31344
|
+
}
|
|
31345
|
+
let landed;
|
|
31346
|
+
try {
|
|
31347
|
+
landed = new Set(asRows(await deps.store.query.query({
|
|
31348
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31349
|
+
filter: { limit: LEGACY_READ_LIMIT }
|
|
31350
|
+
})).map((r) => r.id));
|
|
31351
|
+
} catch (err) {
|
|
31352
|
+
deps.logger.warn("viewer-settings legacy adoption — verification read failed, legacy rows kept", { meta: {
|
|
31353
|
+
written,
|
|
31354
|
+
error: errText(err)
|
|
31355
|
+
} });
|
|
31356
|
+
return 0;
|
|
31357
|
+
}
|
|
31358
|
+
let retired = 0;
|
|
31359
|
+
for (const row of legacy) {
|
|
31360
|
+
if (!landed.has(row.id)) continue;
|
|
31361
|
+
try {
|
|
31362
|
+
await deps.store.delete.mutate({
|
|
31363
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31364
|
+
key: row.id
|
|
31365
|
+
});
|
|
31366
|
+
retired += 1;
|
|
31367
|
+
} catch (err) {
|
|
31368
|
+
deps.logger.warn("viewer-settings legacy adoption — one legacy row failed to delete", { meta: {
|
|
31369
|
+
snapshotId: row.id,
|
|
31370
|
+
error: errText(err)
|
|
31371
|
+
} });
|
|
31372
|
+
}
|
|
31373
|
+
}
|
|
31374
|
+
deps.logger.warn("viewer-settings snapshots adopted from the legacy bare collection", { meta: {
|
|
31375
|
+
from: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31376
|
+
to: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
31377
|
+
found: legacy.length,
|
|
31378
|
+
written,
|
|
31379
|
+
retired,
|
|
31380
|
+
kept: legacy.length - retired
|
|
31381
|
+
} });
|
|
31382
|
+
return retired;
|
|
31383
|
+
}
|
|
31201
31384
|
/**
|
|
31202
31385
|
* The VIDEO's playback rate — 4×, the same as {@link NC_GIF_SPEED}.
|
|
31203
31386
|
*
|
|
@@ -39960,6 +40143,129 @@ function buildRecordedStillPorts(input) {
|
|
|
39960
40143
|
})
|
|
39961
40144
|
};
|
|
39962
40145
|
}
|
|
40146
|
+
/** Where the completion marker lives. */
|
|
40147
|
+
var MIGRATION_MARKER_COLLECTION = "pipeline-analytics:migrations";
|
|
40148
|
+
var MIGRATION_MARKER_KEY = "label-tier-4g";
|
|
40149
|
+
/**
|
|
40150
|
+
* The decision for ONE row, exposed so the rule is testable without a store.
|
|
40151
|
+
*
|
|
40152
|
+
* `null` = leave the row alone.
|
|
40153
|
+
*/
|
|
40154
|
+
function decideRowMigration(data, now) {
|
|
40155
|
+
const label = data["label"];
|
|
40156
|
+
if (typeof label !== "string" || label.length === 0) return null;
|
|
40157
|
+
if (typeof data["subLabel"] === "string" && data["subLabel"].length > 0) return null;
|
|
40158
|
+
if (data["labelMeta"] !== null && data["labelMeta"] !== void 0) return null;
|
|
40159
|
+
if (isMacroClass(label)) return {
|
|
40160
|
+
label: null,
|
|
40161
|
+
labelScore: null,
|
|
40162
|
+
labelMeta: null
|
|
40163
|
+
};
|
|
40164
|
+
return {
|
|
40165
|
+
label: null,
|
|
40166
|
+
labelScore: null,
|
|
40167
|
+
labelMeta: null,
|
|
40168
|
+
subLabel: label,
|
|
40169
|
+
subLabelScore: null,
|
|
40170
|
+
subLabelMeta: {
|
|
40171
|
+
stepId: MIGRATION_STEP_ID,
|
|
40172
|
+
decidedAt: now
|
|
40173
|
+
}
|
|
40174
|
+
};
|
|
40175
|
+
}
|
|
40176
|
+
/** Migrate one collection, paged and bounded. */
|
|
40177
|
+
async function migrateCollectionLabelTiers(deps, collection) {
|
|
40178
|
+
let scanned = 0;
|
|
40179
|
+
let demoted = 0;
|
|
40180
|
+
let cleared = 0;
|
|
40181
|
+
let skipped = 0;
|
|
40182
|
+
let offset = 0;
|
|
40183
|
+
let truncated = false;
|
|
40184
|
+
for (;;) {
|
|
40185
|
+
if (scanned >= 2e5) {
|
|
40186
|
+
truncated = true;
|
|
40187
|
+
break;
|
|
40188
|
+
}
|
|
40189
|
+
const rows = await deps.store.query.query({
|
|
40190
|
+
collection,
|
|
40191
|
+
filter: {
|
|
40192
|
+
limit: 500,
|
|
40193
|
+
offset
|
|
40194
|
+
}
|
|
40195
|
+
});
|
|
40196
|
+
if (rows.length === 0) break;
|
|
40197
|
+
for (const row of rows) {
|
|
40198
|
+
scanned++;
|
|
40199
|
+
const patch = decideRowMigration(row.data, deps.now());
|
|
40200
|
+
if (patch === null) {
|
|
40201
|
+
skipped++;
|
|
40202
|
+
continue;
|
|
40203
|
+
}
|
|
40204
|
+
try {
|
|
40205
|
+
await deps.store.update.mutate({
|
|
40206
|
+
collection,
|
|
40207
|
+
id: row.id,
|
|
40208
|
+
data: patch
|
|
40209
|
+
});
|
|
40210
|
+
if (patch["subLabel"] === void 0) cleared++;
|
|
40211
|
+
else demoted++;
|
|
40212
|
+
} catch (err) {
|
|
40213
|
+
deps.logger.warn("label tier migration row failed", { meta: {
|
|
40214
|
+
collection,
|
|
40215
|
+
id: row.id,
|
|
40216
|
+
error: String(err)
|
|
40217
|
+
} });
|
|
40218
|
+
skipped++;
|
|
40219
|
+
}
|
|
40220
|
+
}
|
|
40221
|
+
if (rows.length < 500) break;
|
|
40222
|
+
offset += rows.length;
|
|
40223
|
+
}
|
|
40224
|
+
return {
|
|
40225
|
+
collection,
|
|
40226
|
+
scanned,
|
|
40227
|
+
demoted,
|
|
40228
|
+
cleared,
|
|
40229
|
+
skipped,
|
|
40230
|
+
truncated
|
|
40231
|
+
};
|
|
40232
|
+
}
|
|
40233
|
+
/**
|
|
40234
|
+
* Run the migration across the collections that carry the tiered columns, once.
|
|
40235
|
+
*
|
|
40236
|
+
* The marker short-circuits a completed migration; it is written only when
|
|
40237
|
+
* NEITHER collection was truncated, so an interrupted pass resumes on the next
|
|
40238
|
+
* boot instead of declaring itself done.
|
|
40239
|
+
*/
|
|
40240
|
+
async function runLabelTierMigration(deps, collections) {
|
|
40241
|
+
try {
|
|
40242
|
+
const marker = await deps.store.get.query({
|
|
40243
|
+
collection: MIGRATION_MARKER_COLLECTION,
|
|
40244
|
+
key: MIGRATION_MARKER_KEY
|
|
40245
|
+
});
|
|
40246
|
+
if (marker !== null && marker !== void 0) return [];
|
|
40247
|
+
} catch {}
|
|
40248
|
+
const stats = [];
|
|
40249
|
+
for (const collection of collections) stats.push(await migrateCollectionLabelTiers(deps, collection));
|
|
40250
|
+
if (stats.every((s) => !s.truncated)) try {
|
|
40251
|
+
await deps.store.set.mutate({
|
|
40252
|
+
collection: MIGRATION_MARKER_COLLECTION,
|
|
40253
|
+
key: MIGRATION_MARKER_KEY,
|
|
40254
|
+
value: { completedAt: deps.now() }
|
|
40255
|
+
});
|
|
40256
|
+
} catch (err) {
|
|
40257
|
+
deps.logger.warn("label tier migration marker failed", { meta: { error: String(err) } });
|
|
40258
|
+
}
|
|
40259
|
+
for (const s of stats) deps.logger.info("label tier migration", { meta: {
|
|
40260
|
+
collection: s.collection,
|
|
40261
|
+
scanned: s.scanned,
|
|
40262
|
+
demoted: s.demoted,
|
|
40263
|
+
cleared: s.cleared,
|
|
40264
|
+
skipped: s.skipped,
|
|
40265
|
+
truncated: s.truncated
|
|
40266
|
+
} });
|
|
40267
|
+
return stats;
|
|
40268
|
+
}
|
|
39963
40269
|
//#endregion
|
|
39964
40270
|
//#region src/durable/lts-aggregator.ts
|
|
39965
40271
|
/** Wall-clock bucket width. Five minutes, matching HA's statistics tier. */
|
|
@@ -45151,6 +45457,55 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
45151
45457
|
why: "a five-minute statistics row summarises an INTERVAL, not an entity — there is no owner it can be orphaned from. Its bound is a ROW CAP per (subject, series, scope) enforced by the aggregator itself (105,120 rows ≈ one year), deliberately not an age sweep: D61 collapsed ten disagreeing age clocks into one, and an LTS series has no clock but its own."
|
|
45152
45458
|
}
|
|
45153
45459
|
},
|
|
45460
|
+
{
|
|
45461
|
+
collection: RETRAIN_ANNOTATIONS_COLLECTION,
|
|
45462
|
+
classification: {
|
|
45463
|
+
kind: "never-orphaned",
|
|
45464
|
+
why: "the ground truth an operator DREW. It declares `trackId` and must be classified by hand for that reason: the id is provenance, not ownership. The dataset addresses COPIES (D81), which is precisely what makes a 'trained' track evictable again — so a track ageing out leaves the annotations standing. Rows go only through `deleteForFrame`, when the operator deselects the frame. Deleting one because its track expired would destroy labelling work no sweep can recreate."
|
|
45465
|
+
}
|
|
45466
|
+
},
|
|
45467
|
+
{
|
|
45468
|
+
collection: RETRAIN_FRAMES_COLLECTION,
|
|
45469
|
+
classification: {
|
|
45470
|
+
kind: "never-orphaned",
|
|
45471
|
+
why: "the frame COPIES the dataset owns outright, under a `retrain` prefix no owner cascade and no age sweep walks (D81). Also declares `trackId` as provenance. Rows go only through `deleteFrame`, after their annotations, on operator deselection. NOTE: nothing caps this table or the disk it consumes — that is a deliberate decision, not an oversight, and it is the one collection here whose bound is an operator's habit."
|
|
45472
|
+
}
|
|
45473
|
+
},
|
|
45474
|
+
{
|
|
45475
|
+
collection: SCENE_COLLECTION,
|
|
45476
|
+
classification: {
|
|
45477
|
+
kind: "never-orphaned",
|
|
45478
|
+
why: "operator intent — one row per (deviceId, monitorId), created and destroyed by scene CRUD or by a device-removal sweep. Nothing retention does can orphan it, and no age clock applies: a scene the operator configured and never revisits is not stale, it is configured."
|
|
45479
|
+
}
|
|
45480
|
+
},
|
|
45481
|
+
{
|
|
45482
|
+
collection: SCENE_LATCH_COLLECTION,
|
|
45483
|
+
classification: {
|
|
45484
|
+
kind: "never-orphaned",
|
|
45485
|
+
why: "it IS owned — by the scene set — but the owner-cleanup is self-contained: `pruneExcept(the scene set)` runs on every scene reload, so a latch whose scene is gone is collected there and never by retention. Deliberately NOT `owner-indirection`: that kind puts a collection into `auditableCollections()`, and the orphan audit resolves exactly one indirection, that of `pipeline-analytics:media`."
|
|
45486
|
+
}
|
|
45487
|
+
},
|
|
45488
|
+
{
|
|
45489
|
+
collection: MIGRATION_MARKER_COLLECTION,
|
|
45490
|
+
classification: {
|
|
45491
|
+
kind: "never-orphaned",
|
|
45492
|
+
why: "a one-row completion marker for the label-tier 4g pass. It summarises a one-time event, so there is no entity it can be orphaned from — and its PRESENCE is what stops the migration running again, so deleting it is not cleanup, it is a re-run over every track, face and object-event row."
|
|
45493
|
+
}
|
|
45494
|
+
},
|
|
45495
|
+
{
|
|
45496
|
+
collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
45497
|
+
classification: {
|
|
45498
|
+
kind: "never-orphaned",
|
|
45499
|
+
why: "a hub user's named snapshot of their own viewer layout. It is not analytics data — it happens to live in this addon because the actions ride `addons.custom`. Owned by the user who saved it and deleted only by them; no track, device or event can orphan it, and no age applies."
|
|
45500
|
+
}
|
|
45501
|
+
},
|
|
45502
|
+
{
|
|
45503
|
+
collection: LEGACY_VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
|
|
45504
|
+
classification: {
|
|
45505
|
+
kind: "never-orphaned",
|
|
45506
|
+
why: "the drained predecessor of the row above, declared only so its rows can be read once and moved (`viewer-settings-legacy-adoption.ts`). Retention must never touch it: its rows go one way only, to the successor, gated on a re-read that confirms they landed. Delete this entry together with that module."
|
|
45507
|
+
}
|
|
45508
|
+
},
|
|
45154
45509
|
{
|
|
45155
45510
|
collection: STATIONARY_COLLECTION,
|
|
45156
45511
|
classification: {
|
|
@@ -49830,129 +50185,6 @@ function evaluatePeriodicSnapshot(input) {
|
|
|
49830
50185
|
movedFraction
|
|
49831
50186
|
};
|
|
49832
50187
|
}
|
|
49833
|
-
/** Where the completion marker lives. */
|
|
49834
|
-
var MIGRATION_MARKER_COLLECTION = "pipeline-analytics:migrations";
|
|
49835
|
-
var MIGRATION_MARKER_KEY = "label-tier-4g";
|
|
49836
|
-
/**
|
|
49837
|
-
* The decision for ONE row, exposed so the rule is testable without a store.
|
|
49838
|
-
*
|
|
49839
|
-
* `null` = leave the row alone.
|
|
49840
|
-
*/
|
|
49841
|
-
function decideRowMigration(data, now) {
|
|
49842
|
-
const label = data["label"];
|
|
49843
|
-
if (typeof label !== "string" || label.length === 0) return null;
|
|
49844
|
-
if (typeof data["subLabel"] === "string" && data["subLabel"].length > 0) return null;
|
|
49845
|
-
if (data["labelMeta"] !== null && data["labelMeta"] !== void 0) return null;
|
|
49846
|
-
if (isMacroClass(label)) return {
|
|
49847
|
-
label: null,
|
|
49848
|
-
labelScore: null,
|
|
49849
|
-
labelMeta: null
|
|
49850
|
-
};
|
|
49851
|
-
return {
|
|
49852
|
-
label: null,
|
|
49853
|
-
labelScore: null,
|
|
49854
|
-
labelMeta: null,
|
|
49855
|
-
subLabel: label,
|
|
49856
|
-
subLabelScore: null,
|
|
49857
|
-
subLabelMeta: {
|
|
49858
|
-
stepId: MIGRATION_STEP_ID,
|
|
49859
|
-
decidedAt: now
|
|
49860
|
-
}
|
|
49861
|
-
};
|
|
49862
|
-
}
|
|
49863
|
-
/** Migrate one collection, paged and bounded. */
|
|
49864
|
-
async function migrateCollectionLabelTiers(deps, collection) {
|
|
49865
|
-
let scanned = 0;
|
|
49866
|
-
let demoted = 0;
|
|
49867
|
-
let cleared = 0;
|
|
49868
|
-
let skipped = 0;
|
|
49869
|
-
let offset = 0;
|
|
49870
|
-
let truncated = false;
|
|
49871
|
-
for (;;) {
|
|
49872
|
-
if (scanned >= 2e5) {
|
|
49873
|
-
truncated = true;
|
|
49874
|
-
break;
|
|
49875
|
-
}
|
|
49876
|
-
const rows = await deps.store.query.query({
|
|
49877
|
-
collection,
|
|
49878
|
-
filter: {
|
|
49879
|
-
limit: 500,
|
|
49880
|
-
offset
|
|
49881
|
-
}
|
|
49882
|
-
});
|
|
49883
|
-
if (rows.length === 0) break;
|
|
49884
|
-
for (const row of rows) {
|
|
49885
|
-
scanned++;
|
|
49886
|
-
const patch = decideRowMigration(row.data, deps.now());
|
|
49887
|
-
if (patch === null) {
|
|
49888
|
-
skipped++;
|
|
49889
|
-
continue;
|
|
49890
|
-
}
|
|
49891
|
-
try {
|
|
49892
|
-
await deps.store.update.mutate({
|
|
49893
|
-
collection,
|
|
49894
|
-
id: row.id,
|
|
49895
|
-
data: patch
|
|
49896
|
-
});
|
|
49897
|
-
if (patch["subLabel"] === void 0) cleared++;
|
|
49898
|
-
else demoted++;
|
|
49899
|
-
} catch (err) {
|
|
49900
|
-
deps.logger.warn("label tier migration row failed", { meta: {
|
|
49901
|
-
collection,
|
|
49902
|
-
id: row.id,
|
|
49903
|
-
error: String(err)
|
|
49904
|
-
} });
|
|
49905
|
-
skipped++;
|
|
49906
|
-
}
|
|
49907
|
-
}
|
|
49908
|
-
if (rows.length < 500) break;
|
|
49909
|
-
offset += rows.length;
|
|
49910
|
-
}
|
|
49911
|
-
return {
|
|
49912
|
-
collection,
|
|
49913
|
-
scanned,
|
|
49914
|
-
demoted,
|
|
49915
|
-
cleared,
|
|
49916
|
-
skipped,
|
|
49917
|
-
truncated
|
|
49918
|
-
};
|
|
49919
|
-
}
|
|
49920
|
-
/**
|
|
49921
|
-
* Run the migration across the collections that carry the tiered columns, once.
|
|
49922
|
-
*
|
|
49923
|
-
* The marker short-circuits a completed migration; it is written only when
|
|
49924
|
-
* NEITHER collection was truncated, so an interrupted pass resumes on the next
|
|
49925
|
-
* boot instead of declaring itself done.
|
|
49926
|
-
*/
|
|
49927
|
-
async function runLabelTierMigration(deps, collections) {
|
|
49928
|
-
try {
|
|
49929
|
-
const marker = await deps.store.get.query({
|
|
49930
|
-
collection: MIGRATION_MARKER_COLLECTION,
|
|
49931
|
-
key: MIGRATION_MARKER_KEY
|
|
49932
|
-
});
|
|
49933
|
-
if (marker !== null && marker !== void 0) return [];
|
|
49934
|
-
} catch {}
|
|
49935
|
-
const stats = [];
|
|
49936
|
-
for (const collection of collections) stats.push(await migrateCollectionLabelTiers(deps, collection));
|
|
49937
|
-
if (stats.every((s) => !s.truncated)) try {
|
|
49938
|
-
await deps.store.set.mutate({
|
|
49939
|
-
collection: MIGRATION_MARKER_COLLECTION,
|
|
49940
|
-
key: MIGRATION_MARKER_KEY,
|
|
49941
|
-
value: { completedAt: deps.now() }
|
|
49942
|
-
});
|
|
49943
|
-
} catch (err) {
|
|
49944
|
-
deps.logger.warn("label tier migration marker failed", { meta: { error: String(err) } });
|
|
49945
|
-
}
|
|
49946
|
-
for (const s of stats) deps.logger.info("label tier migration", { meta: {
|
|
49947
|
-
collection: s.collection,
|
|
49948
|
-
scanned: s.scanned,
|
|
49949
|
-
demoted: s.demoted,
|
|
49950
|
-
cleared: s.cleared,
|
|
49951
|
-
skipped: s.skipped,
|
|
49952
|
-
truncated: s.truncated
|
|
49953
|
-
} });
|
|
49954
|
-
return stats;
|
|
49955
|
-
}
|
|
49956
50188
|
//#endregion
|
|
49957
50189
|
//#region src/pipeline-analytics/store/object-embedding-store.ts
|
|
49958
50190
|
/**
|
|
@@ -52364,6 +52596,14 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
52364
52596
|
ViewerSettingsSnapshotStore.declare(api.settingsStore)
|
|
52365
52597
|
]);
|
|
52366
52598
|
this.viewerSettingsSnapshots = new ViewerSettingsSnapshotStore(api.settingsStore);
|
|
52599
|
+
try {
|
|
52600
|
+
await adoptLegacyViewerSettingsSnapshots({
|
|
52601
|
+
store: api.settingsStore,
|
|
52602
|
+
logger: this.ctx.logger.child("viewer-settings")
|
|
52603
|
+
});
|
|
52604
|
+
} catch (err) {
|
|
52605
|
+
this.ctx.logger.warn("viewer-settings legacy adoption threw", { meta: { error: errMsg(err) } });
|
|
52606
|
+
}
|
|
52367
52607
|
runLabelTierMigration({
|
|
52368
52608
|
store: api.settingsStore,
|
|
52369
52609
|
logger: this.ctx.logger,
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D8HLGYbH.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.101",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|