@camstack/addon-post-analysis 1.2.145 → 1.2.147
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/{dist-C8RL0Hzy.js → dist-1xYuXmDS.js} +131 -12
- package/dist/{dist-3FJkk7Mx.mjs → dist-4eP-xmjY.mjs} +131 -12
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Bb5cXFSY.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B8VSQDMs.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DC69_fX2.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-C7KsMvFR.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-Btr3CZPD.mjs → hostInit-Dt9TGWyM.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +189 -126
- package/dist/pipeline-analytics/index.mjs +189 -126
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as buildEventKindDescriptor, A as NcSnoozeSuppressedSchema, At as hydrateSchema, B as SCENE_DIVERGED, Bt as object, C as NcRuleInputSchema, Ct as vectorDimFromBase64, D as NcScheduleSchema, Dt as BaseAddon, E as NcRuleTargetSchema, Et as errMsg, F as RECORDING_EXPORT_MAX_READ_BYTES, Ft as array, G as TimelapseRuleSchema, Gt as EventCategory, H as TIMELAPSE_DENSE_FLOOR_SEC, Ht as record, I as RetrainStatusSchema, It as boolean, J as addonWidgetsSourceCapability, K as TrackSourceSchema, Lt as discriminatedUnion, M as NcTaxonomySchema, Mt as nodePin, N as OpsLogEntrySchema, Nt as sleep, O as NcSnoozeInputSchema, Ot as DeviceType, Pt as _enum, Q as audioModeOf, R as SCENE_DEFAULT_ANCHOR_THRESHOLD, Rt as literal, S as NcConditionDescriptorSchema, St as systemEventFilterApplies, T as NcRuleSchema, Tt as zoneAnalyticsCapability, U as TimelapseRuleInputSchema, Ut as string, V as SceneMonitorSchema, Vt as partialRecord, W as TimelapseRulePatchSchema, Wt as unknown, X as assertTimelapseCadences, Y as alarmPanelCapability, Z as audioMetricsCapability, _ as NC_ALARM_SYSTEM_EVENT_KINDS, _t as readDeviceStateFrom, a as COCO_TO_MACRO, at as encodeVectorBase64, bt as sceneMonitorCapability, c as DETECTION_MACRO_CLASSES, d as EVENT_KIND_BY_CAP, dt as kebabToCamel, et as cosineSimilarity$1, f as EVENT_PAD_MS, ft as notificationRulesCapability, g as MACRO_LABELS, gt as plateGalleryCapability, h as LabelAttributionSchema, ht as pipelineAnalyticsCapability, i as CLUSTER_MODEL_SCOPED_STEPS, j as NcSystemEventKindSchema, jt as isDeviceScopedCap, k as NcSnoozeSchema, kt as createEvent, lt as isDetectionMacroClass, m as FailureCounters, mt as pickClusterStepModels, n as AnalyticsGroupRecordSchema, nt as defineCustomActions, o as DEFAULT_EVENT_COLOR, ot as faceGalleryCapability, p as FULL_IMAGE_BBOX, q as VISIT_MERGE_GAP_MS, r as BaseDevice, rt as deriveRecordingMode, st as failureContributionCapability, t as AnalyticsGroupDetailSchema, tt as customAction, u as DeclaredDevices, ut as isScheduleActive, v as NC_CONDITION_CATALOG, vt as readTimelapseGeneratedAt, w as NcRulePatchSchema, wt as videoclipsCapability, x as NC_TAXONOMY, xt as subKindsOf, y as NC_DEFAULT_SNOOZE_MINUTES, z as SCENE_DEFAULT_UNCOVERED_POLICY, zt as number } from "../dist-
|
|
1
|
+
import { $ as buildEventKindDescriptor, A as NcSnoozeSuppressedSchema, At as hydrateSchema, B as SCENE_DIVERGED, Bt as object, C as NcRuleInputSchema, Ct as vectorDimFromBase64, D as NcScheduleSchema, Dt as BaseAddon, E as NcRuleTargetSchema, Et as errMsg, F as RECORDING_EXPORT_MAX_READ_BYTES, Ft as array, G as TimelapseRuleSchema, Gt as EventCategory, H as TIMELAPSE_DENSE_FLOOR_SEC, Ht as record, I as RetrainStatusSchema, It as boolean, J as addonWidgetsSourceCapability, K as TrackSourceSchema, Lt as discriminatedUnion, M as NcTaxonomySchema, Mt as nodePin, N as OpsLogEntrySchema, Nt as sleep, O as NcSnoozeInputSchema, Ot as DeviceType, Pt as _enum, Q as audioModeOf, R as SCENE_DEFAULT_ANCHOR_THRESHOLD, Rt as literal, S as NcConditionDescriptorSchema, St as systemEventFilterApplies, T as NcRuleSchema, Tt as zoneAnalyticsCapability, U as TimelapseRuleInputSchema, Ut as string, V as SceneMonitorSchema, Vt as partialRecord, W as TimelapseRulePatchSchema, Wt as unknown, X as assertTimelapseCadences, Y as alarmPanelCapability, Z as audioMetricsCapability, _ as NC_ALARM_SYSTEM_EVENT_KINDS, _t as readDeviceStateFrom, a as COCO_TO_MACRO, at as encodeVectorBase64, bt as sceneMonitorCapability, c as DETECTION_MACRO_CLASSES, d as EVENT_KIND_BY_CAP, dt as kebabToCamel, et as cosineSimilarity$1, f as EVENT_PAD_MS, ft as notificationRulesCapability, g as MACRO_LABELS, gt as plateGalleryCapability, h as LabelAttributionSchema, ht as pipelineAnalyticsCapability, i as CLUSTER_MODEL_SCOPED_STEPS, j as NcSystemEventKindSchema, jt as isDeviceScopedCap, k as NcSnoozeSchema, kt as createEvent, lt as isDetectionMacroClass, m as FailureCounters, mt as pickClusterStepModels, n as AnalyticsGroupRecordSchema, nt as defineCustomActions, o as DEFAULT_EVENT_COLOR, ot as faceGalleryCapability, p as FULL_IMAGE_BBOX, q as VISIT_MERGE_GAP_MS, r as BaseDevice, rt as deriveRecordingMode, st as failureContributionCapability, t as AnalyticsGroupDetailSchema, tt as customAction, u as DeclaredDevices, ut as isScheduleActive, v as NC_CONDITION_CATALOG, vt as readTimelapseGeneratedAt, w as NcRulePatchSchema, wt as videoclipsCapability, x as NC_TAXONOMY, xt as subKindsOf, y as NC_DEFAULT_SNOOZE_MINUTES, z as SCENE_DEFAULT_UNCOVERED_POLICY, zt as number } from "../dist-4eP-xmjY.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -40150,9 +40150,6 @@ function readJpegSize(data) {
|
|
|
40150
40150
|
* table or the disk it consumes."
|
|
40151
40151
|
*/
|
|
40152
40152
|
var RETRAIN_FRAMES_COLLECTION = "pipeline-analytics:retrain-frames";
|
|
40153
|
-
/** Storage location for retrain copies — the same default media root the event
|
|
40154
|
-
* media lives on, under a prefix nothing else enumerates. */
|
|
40155
|
-
var RETRAIN_MEDIA_LOCATION = "eventMedia";
|
|
40156
40153
|
/** Path prefix of every retrain copy. Never `<deviceId>/events/…`: the orphan
|
|
40157
40154
|
* audit resolves ownership from INDEX rows, but a future filesystem-side tool
|
|
40158
40155
|
* must be able to tell the two corpora apart by path alone. */
|
|
@@ -40245,6 +40242,7 @@ var RetrainFrameStore = class {
|
|
|
40245
40242
|
readSource;
|
|
40246
40243
|
now;
|
|
40247
40244
|
writeGate;
|
|
40245
|
+
resolveEventMediaLocation;
|
|
40248
40246
|
constructor(deps) {
|
|
40249
40247
|
this.store = deps.store;
|
|
40250
40248
|
this.storage = deps.storage;
|
|
@@ -40252,6 +40250,29 @@ var RetrainFrameStore = class {
|
|
|
40252
40250
|
this.readSource = deps.readSource;
|
|
40253
40251
|
this.now = deps.now;
|
|
40254
40252
|
this.writeGate = deps.writeGate;
|
|
40253
|
+
this.resolveEventMediaLocation = deps.resolveEventMediaLocation;
|
|
40254
|
+
}
|
|
40255
|
+
/**
|
|
40256
|
+
* The location a NEW copy is written to, and the id its row NAMES.
|
|
40257
|
+
*
|
|
40258
|
+
* `null` = "no concrete location was resolved", which persists as the legacy
|
|
40259
|
+
* NULL row. A resolver that throws must not refuse the copy — losing an
|
|
40260
|
+
* operator's selection is worse than an unstamped row the seal can fix — but
|
|
40261
|
+
* it says so, because that branch re-creates the exact population a
|
|
40262
|
+
* non-blocking migration is gated on.
|
|
40263
|
+
*/
|
|
40264
|
+
async resolveWriteLocation(deviceId) {
|
|
40265
|
+
if (!this.resolveEventMediaLocation) return null;
|
|
40266
|
+
try {
|
|
40267
|
+
const resolved = await this.resolveEventMediaLocation(deviceId);
|
|
40268
|
+
return resolved === "eventMedia" ? null : resolved;
|
|
40269
|
+
} catch (err) {
|
|
40270
|
+
this.logger.warn("retrain copy could not resolve its eventMedia location — the row is written UNSTAMPED and a storage repoint would re-aim it at a disk that does not hold it", {
|
|
40271
|
+
tags: { deviceId },
|
|
40272
|
+
meta: { error: String(err) }
|
|
40273
|
+
});
|
|
40274
|
+
return null;
|
|
40275
|
+
}
|
|
40255
40276
|
}
|
|
40256
40277
|
static async declare(store) {
|
|
40257
40278
|
await store.declareCollection.mutate({
|
|
@@ -40410,6 +40431,7 @@ var RetrainFrameStore = class {
|
|
|
40410
40431
|
};
|
|
40411
40432
|
}
|
|
40412
40433
|
const path = framePath(input.deviceId, input.trackId, frameId);
|
|
40434
|
+
const locationId = await this.resolveWriteLocation(input.deviceId);
|
|
40413
40435
|
const frame = {
|
|
40414
40436
|
frameId,
|
|
40415
40437
|
deviceId: input.deviceId,
|
|
@@ -40420,11 +40442,11 @@ var RetrainFrameStore = class {
|
|
|
40420
40442
|
width: size.width,
|
|
40421
40443
|
height: size.height,
|
|
40422
40444
|
copiedAt: this.now(),
|
|
40423
|
-
locationId
|
|
40445
|
+
locationId
|
|
40424
40446
|
};
|
|
40425
40447
|
const persist = async () => {
|
|
40426
40448
|
await this.storage.write({
|
|
40427
|
-
location:
|
|
40449
|
+
location: locationId ?? "eventMedia",
|
|
40428
40450
|
relativePath: path,
|
|
40429
40451
|
data: bytes
|
|
40430
40452
|
});
|
|
@@ -40441,7 +40463,7 @@ var RetrainFrameStore = class {
|
|
|
40441
40463
|
width: frame.width,
|
|
40442
40464
|
height: frame.height,
|
|
40443
40465
|
copiedAt: frame.copiedAt,
|
|
40444
|
-
locationId:
|
|
40466
|
+
locationId: frame.locationId
|
|
40445
40467
|
}
|
|
40446
40468
|
});
|
|
40447
40469
|
};
|
|
@@ -43439,6 +43461,13 @@ function pickWritableMediaLocation(locations, currentDefaultId) {
|
|
|
43439
43461
|
//#region src/pipeline-analytics/media-relocate-engine.ts
|
|
43440
43462
|
var PAGE_SIZE = 200;
|
|
43441
43463
|
var DEFAULT_THROTTLE_MBPS = 40;
|
|
43464
|
+
/** What a pass touches, reported on the job so an ops-log row says which of the
|
|
43465
|
+
* three passes ran without anyone reading this file. */
|
|
43466
|
+
function entitiesFor(mode) {
|
|
43467
|
+
if (mode === "seal") return ["mediaRows", "retrainFrameRows"];
|
|
43468
|
+
if (mode === "gallery") return ["galleryMedia"];
|
|
43469
|
+
return ["media", "retrainFrames"];
|
|
43470
|
+
}
|
|
43442
43471
|
function snapshot(j) {
|
|
43443
43472
|
return {
|
|
43444
43473
|
jobId: j.jobId,
|
|
@@ -43446,7 +43475,7 @@ function snapshot(j) {
|
|
|
43446
43475
|
fromLocationId: j.fromLocationId,
|
|
43447
43476
|
toLocationId: j.toLocationId,
|
|
43448
43477
|
deviceId: j.deviceId,
|
|
43449
|
-
entities: [
|
|
43478
|
+
entities: [...entitiesFor(j.mode)],
|
|
43450
43479
|
filesMoved: j.filesMoved,
|
|
43451
43480
|
bytesMoved: j.bytesMoved,
|
|
43452
43481
|
filesTotal: null,
|
|
@@ -43455,6 +43484,22 @@ function snapshot(j) {
|
|
|
43455
43484
|
error: j.error
|
|
43456
43485
|
};
|
|
43457
43486
|
}
|
|
43487
|
+
/**
|
|
43488
|
+
* True when the row names NO location — the population `seal` exists for.
|
|
43489
|
+
*
|
|
43490
|
+
* Deliberately NOT `mediaRowLocation(data) === DEFAULT_MEDIA_LOCATION`: that
|
|
43491
|
+
* helper collapses "unstamped" and "stamped with the bare type ref" into one
|
|
43492
|
+
* answer, and only the first is a row whose meaning changes under a repoint.
|
|
43493
|
+
*/
|
|
43494
|
+
function rowIsUnstamped(data) {
|
|
43495
|
+
const id = data["locationId"];
|
|
43496
|
+
return !(typeof id === "string" && id.length > 0);
|
|
43497
|
+
}
|
|
43498
|
+
/** True for the enrolled-gallery rows `move` skips and `gallery` selects. */
|
|
43499
|
+
function rowIsGallery(data) {
|
|
43500
|
+
const ownerKind = String(data["ownerKind"] ?? "");
|
|
43501
|
+
return isMediaOwnerKind(ownerKind) && isRetentionExemptOwnerKind(ownerKind);
|
|
43502
|
+
}
|
|
43458
43503
|
var MediaRelocateEngine = class {
|
|
43459
43504
|
deps;
|
|
43460
43505
|
jobs = /* @__PURE__ */ new Map();
|
|
@@ -43470,11 +43515,34 @@ var MediaRelocateEngine = class {
|
|
|
43470
43515
|
job.cancelRequested = true;
|
|
43471
43516
|
return true;
|
|
43472
43517
|
}
|
|
43518
|
+
/**
|
|
43519
|
+
* How many rows still carry no `locationId`, per collection.
|
|
43520
|
+
*
|
|
43521
|
+
* Read-only, and the ONLY number the migration planner's non-blocking gate
|
|
43522
|
+
* is allowed to trust: it is counted here, against the rows themselves,
|
|
43523
|
+
* rather than inferred from "a seal job reported done" — a seal that failed
|
|
43524
|
+
* some rows still reports a terminal state.
|
|
43525
|
+
*/
|
|
43526
|
+
async countUnstamped() {
|
|
43527
|
+
const media = await this.countUnstampedIn(MEDIA_COLLECTION, "timestamp");
|
|
43528
|
+
const retrainFrames = await this.countUnstampedIn(RETRAIN_FRAMES_COLLECTION, "copiedAt");
|
|
43529
|
+
return {
|
|
43530
|
+
media,
|
|
43531
|
+
retrainFrames,
|
|
43532
|
+
total: media + retrainFrames
|
|
43533
|
+
};
|
|
43534
|
+
}
|
|
43535
|
+
async countUnstampedIn(collection, orderField) {
|
|
43536
|
+
let unstamped = 0;
|
|
43537
|
+
for await (const row of this.pageRows(collection, orderField)) if (rowIsUnstamped(row.data)) unstamped++;
|
|
43538
|
+
return unstamped;
|
|
43539
|
+
}
|
|
43473
43540
|
start(input) {
|
|
43474
43541
|
for (const j of this.jobs.values()) if (j.state === "running") throw new Error(`a media relocation is already running (${j.jobId})`);
|
|
43475
43542
|
const job = {
|
|
43476
43543
|
jobId: this.deps.newId(),
|
|
43477
43544
|
state: "running",
|
|
43545
|
+
mode: input.mode ?? "move",
|
|
43478
43546
|
fromLocationId: "*",
|
|
43479
43547
|
toLocationId: input.toLocationId,
|
|
43480
43548
|
deviceId: null,
|
|
@@ -43489,130 +43557,104 @@ var MediaRelocateEngine = class {
|
|
|
43489
43557
|
this.run(job, input.throttleMbps ?? DEFAULT_THROTTLE_MBPS);
|
|
43490
43558
|
return job.jobId;
|
|
43491
43559
|
}
|
|
43560
|
+
/**
|
|
43561
|
+
* Page one collection oldest-first, yielding every row exactly once.
|
|
43562
|
+
*
|
|
43563
|
+
* The cursor is `(orderField, offset-within-ties)` rather than a plain
|
|
43564
|
+
* offset: 201 rows sharing one timestamp is a real shape here (a burst of
|
|
43565
|
+
* snapshots for one track), and a plain `offset` walk over a collection being
|
|
43566
|
+
* mutated underneath is how rows get skipped. Rows only ever gain a
|
|
43567
|
+
* `locationId` during a pass — never a new order key — so appending inserts
|
|
43568
|
+
* land after the cursor and are picked up, and nothing is missed.
|
|
43569
|
+
*/
|
|
43570
|
+
async *pageRows(collection, orderField) {
|
|
43571
|
+
let cursor = 0;
|
|
43572
|
+
let offsetAtCursor = 0;
|
|
43573
|
+
for (;;) {
|
|
43574
|
+
const rows = await this.deps.store.query.query({
|
|
43575
|
+
collection,
|
|
43576
|
+
filter: {
|
|
43577
|
+
whereBetween: { [orderField]: [cursor, Number.MAX_SAFE_INTEGER] },
|
|
43578
|
+
orderBy: {
|
|
43579
|
+
field: orderField,
|
|
43580
|
+
direction: "asc"
|
|
43581
|
+
},
|
|
43582
|
+
limit: PAGE_SIZE,
|
|
43583
|
+
offset: offsetAtCursor
|
|
43584
|
+
}
|
|
43585
|
+
});
|
|
43586
|
+
if (rows.length === 0) return;
|
|
43587
|
+
for (const row of rows) yield row;
|
|
43588
|
+
const last = rows[rows.length - 1];
|
|
43589
|
+
if (last === void 0) return;
|
|
43590
|
+
const lastKey = Number(last.data[orderField] ?? cursor);
|
|
43591
|
+
if (lastKey === cursor) offsetAtCursor += rows.length;
|
|
43592
|
+
else {
|
|
43593
|
+
cursor = lastKey;
|
|
43594
|
+
offsetAtCursor = rows.filter((row) => Number(row.data[orderField]) === lastKey).length;
|
|
43595
|
+
}
|
|
43596
|
+
if (rows.length < PAGE_SIZE) return;
|
|
43597
|
+
}
|
|
43598
|
+
}
|
|
43599
|
+
/** Does this pass act on this row at all? */
|
|
43600
|
+
selects(job, data, isRetrain) {
|
|
43601
|
+
if (job.mode === "seal") return rowIsUnstamped(data);
|
|
43602
|
+
if (mediaRowLocation(data) === job.toLocationId) return false;
|
|
43603
|
+
if (job.mode === "gallery") return !isRetrain && rowIsGallery(data);
|
|
43604
|
+
return isRetrain || !rowIsGallery(data);
|
|
43605
|
+
}
|
|
43492
43606
|
async run(job, throttleMbps) {
|
|
43493
43607
|
const sleep = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
43494
43608
|
const bytesPerMs = throttleMbps * 1024 * 1024 / 1e3;
|
|
43495
43609
|
try {
|
|
43496
43610
|
await this.deps.resolveTargetRoot(job.toLocationId);
|
|
43497
43611
|
let failedRows = 0;
|
|
43498
|
-
|
|
43499
|
-
|
|
43500
|
-
|
|
43612
|
+
/** Selected rows that name no blob path. Counted and reported ONCE per
|
|
43613
|
+
* pass rather than per row: a row with no path names no bytes anywhere,
|
|
43614
|
+
* so there is nothing a repoint can orphan — but a pass that steps over
|
|
43615
|
+
* rows in silence reads as "there were none", which is how a migration
|
|
43616
|
+
* gets believed. */
|
|
43617
|
+
let pathlessRows = 0;
|
|
43618
|
+
const collections = job.mode === "gallery" ? [{
|
|
43619
|
+
name: MEDIA_COLLECTION,
|
|
43620
|
+
order: "timestamp"
|
|
43621
|
+
}] : [{
|
|
43622
|
+
name: MEDIA_COLLECTION,
|
|
43623
|
+
order: "timestamp"
|
|
43624
|
+
}, {
|
|
43625
|
+
name: RETRAIN_FRAMES_COLLECTION,
|
|
43626
|
+
order: "copiedAt"
|
|
43627
|
+
}];
|
|
43628
|
+
for (const collection of collections) {
|
|
43501
43629
|
if (job.cancelRequested) break;
|
|
43502
|
-
const
|
|
43503
|
-
|
|
43504
|
-
filter: {
|
|
43505
|
-
whereBetween: { timestamp: [cursor, Number.MAX_SAFE_INTEGER] },
|
|
43506
|
-
orderBy: {
|
|
43507
|
-
field: "timestamp",
|
|
43508
|
-
direction: "asc"
|
|
43509
|
-
},
|
|
43510
|
-
limit: PAGE_SIZE,
|
|
43511
|
-
offset: offsetAtCursor
|
|
43512
|
-
}
|
|
43513
|
-
});
|
|
43514
|
-
if (rows.length === 0) break;
|
|
43515
|
-
for (const row of rows) {
|
|
43630
|
+
const isRetrain = collection.name === RETRAIN_FRAMES_COLLECTION;
|
|
43631
|
+
for await (const row of this.pageRows(collection.name, collection.order)) {
|
|
43516
43632
|
if (job.cancelRequested) break;
|
|
43517
43633
|
const data = row.data;
|
|
43518
|
-
|
|
43519
|
-
if (
|
|
43520
|
-
|
|
43521
|
-
|
|
43522
|
-
|
|
43523
|
-
|
|
43524
|
-
|
|
43525
|
-
|
|
43526
|
-
|
|
43527
|
-
collection: MEDIA_COLLECTION,
|
|
43634
|
+
if (!this.selects(job, data, isRetrain)) continue;
|
|
43635
|
+
if (job.mode === "seal") {
|
|
43636
|
+
try {
|
|
43637
|
+
await this.stamp(collection.name, row.id, data, job.toLocationId);
|
|
43638
|
+
job.filesMoved++;
|
|
43639
|
+
} catch (err) {
|
|
43640
|
+
failedRows++;
|
|
43641
|
+
this.deps.logger.debug("event-media seal row failed", { meta: {
|
|
43642
|
+
collection: collection.name,
|
|
43528
43643
|
key: row.id,
|
|
43529
|
-
|
|
43530
|
-
|
|
43531
|
-
locationId: job.toLocationId
|
|
43532
|
-
}
|
|
43533
|
-
});
|
|
43534
|
-
continue;
|
|
43644
|
+
error: String(err)
|
|
43645
|
+
} });
|
|
43535
43646
|
}
|
|
43536
|
-
|
|
43537
|
-
location: from,
|
|
43538
|
-
relativePath
|
|
43539
|
-
});
|
|
43540
|
-
await this.deps.storage.write({
|
|
43541
|
-
location: job.toLocationId,
|
|
43542
|
-
relativePath,
|
|
43543
|
-
data: bytes
|
|
43544
|
-
});
|
|
43545
|
-
if ((await this.deps.storage.read({
|
|
43546
|
-
location: job.toLocationId,
|
|
43547
|
-
relativePath
|
|
43548
|
-
})).length !== bytes.length) throw new Error(`relocated media size mismatch for ${relativePath}`);
|
|
43549
|
-
await this.deps.store.set.mutate({
|
|
43550
|
-
collection: MEDIA_COLLECTION,
|
|
43551
|
-
key: row.id,
|
|
43552
|
-
value: {
|
|
43553
|
-
...data,
|
|
43554
|
-
locationId: job.toLocationId
|
|
43555
|
-
}
|
|
43556
|
-
});
|
|
43557
|
-
await this.deps.storage.delete({
|
|
43558
|
-
location: from,
|
|
43559
|
-
relativePath
|
|
43560
|
-
});
|
|
43561
|
-
job.filesMoved++;
|
|
43562
|
-
job.bytesMoved += bytes.length;
|
|
43563
|
-
await sleep(bytes.length / bytesPerMs);
|
|
43564
|
-
} catch (err) {
|
|
43565
|
-
failedRows++;
|
|
43566
|
-
this.deps.logger.debug("media relocate row failed", { meta: {
|
|
43567
|
-
key: row.id,
|
|
43568
|
-
error: String(err)
|
|
43569
|
-
} });
|
|
43570
|
-
}
|
|
43571
|
-
}
|
|
43572
|
-
const last = rows[rows.length - 1];
|
|
43573
|
-
const lastTs = Number(last.data["timestamp"] ?? cursor);
|
|
43574
|
-
if (lastTs === cursor) offsetAtCursor += rows.length;
|
|
43575
|
-
else {
|
|
43576
|
-
cursor = lastTs;
|
|
43577
|
-
offsetAtCursor = rows.filter((row) => Number(row.data["timestamp"]) === lastTs).length;
|
|
43578
|
-
}
|
|
43579
|
-
if (rows.length < PAGE_SIZE) break;
|
|
43580
|
-
}
|
|
43581
|
-
let retrainOffset = 0;
|
|
43582
|
-
for (;;) {
|
|
43583
|
-
if (job.cancelRequested) break;
|
|
43584
|
-
const rows = await this.deps.store.query.query({
|
|
43585
|
-
collection: RETRAIN_FRAMES_COLLECTION,
|
|
43586
|
-
filter: {
|
|
43587
|
-
orderBy: {
|
|
43588
|
-
field: "copiedAt",
|
|
43589
|
-
direction: "asc"
|
|
43590
|
-
},
|
|
43591
|
-
limit: PAGE_SIZE,
|
|
43592
|
-
offset: retrainOffset
|
|
43647
|
+
continue;
|
|
43593
43648
|
}
|
|
43594
|
-
});
|
|
43595
|
-
if (rows.length === 0) break;
|
|
43596
|
-
for (const row of rows) {
|
|
43597
|
-
if (job.cancelRequested) break;
|
|
43598
|
-
const data = row.data;
|
|
43599
43649
|
const from = mediaRowLocation(data);
|
|
43600
|
-
if (from === job.toLocationId) continue;
|
|
43601
43650
|
const relativePath = String(data["path"] ?? "");
|
|
43602
43651
|
if (relativePath.length === 0) {
|
|
43603
|
-
|
|
43652
|
+
pathlessRows++;
|
|
43604
43653
|
continue;
|
|
43605
43654
|
}
|
|
43606
43655
|
try {
|
|
43607
43656
|
if (await this.samePhysicalLocation(from, job.toLocationId)) {
|
|
43608
|
-
await this.
|
|
43609
|
-
collection: RETRAIN_FRAMES_COLLECTION,
|
|
43610
|
-
key: row.id,
|
|
43611
|
-
value: {
|
|
43612
|
-
...data,
|
|
43613
|
-
locationId: job.toLocationId
|
|
43614
|
-
}
|
|
43615
|
-
});
|
|
43657
|
+
await this.stamp(collection.name, row.id, data, job.toLocationId);
|
|
43616
43658
|
continue;
|
|
43617
43659
|
}
|
|
43618
43660
|
const bytes = await this.deps.storage.read({
|
|
@@ -43627,15 +43669,8 @@ var MediaRelocateEngine = class {
|
|
|
43627
43669
|
if ((await this.deps.storage.read({
|
|
43628
43670
|
location: job.toLocationId,
|
|
43629
43671
|
relativePath
|
|
43630
|
-
})).length !== bytes.length) throw new Error(`relocated
|
|
43631
|
-
await this.
|
|
43632
|
-
collection: RETRAIN_FRAMES_COLLECTION,
|
|
43633
|
-
key: row.id,
|
|
43634
|
-
value: {
|
|
43635
|
-
...data,
|
|
43636
|
-
locationId: job.toLocationId
|
|
43637
|
-
}
|
|
43638
|
-
});
|
|
43672
|
+
})).length !== bytes.length) throw new Error(`relocated media size mismatch for ${relativePath}`);
|
|
43673
|
+
await this.stamp(collection.name, row.id, data, job.toLocationId);
|
|
43639
43674
|
await this.deps.storage.delete({
|
|
43640
43675
|
location: from,
|
|
43641
43676
|
relativePath
|
|
@@ -43645,22 +43680,27 @@ var MediaRelocateEngine = class {
|
|
|
43645
43680
|
await sleep(bytes.length / bytesPerMs);
|
|
43646
43681
|
} catch (err) {
|
|
43647
43682
|
failedRows++;
|
|
43648
|
-
this.deps.logger.debug("
|
|
43683
|
+
this.deps.logger.debug("media relocate row failed", { meta: {
|
|
43684
|
+
collection: collection.name,
|
|
43649
43685
|
key: row.id,
|
|
43650
43686
|
error: String(err)
|
|
43651
43687
|
} });
|
|
43652
43688
|
}
|
|
43653
43689
|
}
|
|
43654
|
-
retrainOffset += rows.length;
|
|
43655
|
-
if (rows.length < PAGE_SIZE) break;
|
|
43656
43690
|
}
|
|
43657
|
-
if (
|
|
43691
|
+
if (pathlessRows > 0) this.deps.logger.warn("media relocate stepped over rows that name no blob path", { meta: {
|
|
43692
|
+
jobId: job.jobId,
|
|
43693
|
+
mode: job.mode,
|
|
43694
|
+
rows: pathlessRows
|
|
43695
|
+
} });
|
|
43696
|
+
if (failedRows > 0) throw new Error(`event-media ${job.mode} left ${failedRows} row(s) unmoved`);
|
|
43658
43697
|
job.state = job.cancelRequested ? "cancelled" : "done";
|
|
43659
43698
|
} catch (err) {
|
|
43660
43699
|
job.state = "failed";
|
|
43661
43700
|
job.error = err instanceof Error ? err.message : String(err);
|
|
43662
43701
|
this.deps.logger.warn("media relocate job failed", { meta: {
|
|
43663
43702
|
jobId: job.jobId,
|
|
43703
|
+
mode: job.mode,
|
|
43664
43704
|
error: job.error
|
|
43665
43705
|
} });
|
|
43666
43706
|
} finally {
|
|
@@ -43668,6 +43708,16 @@ var MediaRelocateEngine = class {
|
|
|
43668
43708
|
this.deps.onFinished?.(snapshot(job));
|
|
43669
43709
|
}
|
|
43670
43710
|
}
|
|
43711
|
+
async stamp(collection, key, data, locationId) {
|
|
43712
|
+
await this.deps.store.set.mutate({
|
|
43713
|
+
collection,
|
|
43714
|
+
key,
|
|
43715
|
+
value: {
|
|
43716
|
+
...data,
|
|
43717
|
+
locationId
|
|
43718
|
+
}
|
|
43719
|
+
});
|
|
43720
|
+
}
|
|
43671
43721
|
async samePhysicalLocation(from, to) {
|
|
43672
43722
|
if (from === to) return true;
|
|
43673
43723
|
try {
|
|
@@ -62235,6 +62285,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
62235
62285
|
logger: logger.child("RetrainFrames"),
|
|
62236
62286
|
now: () => Date.now(),
|
|
62237
62287
|
writeGate: this.mediaWriteGate,
|
|
62288
|
+
resolveEventMediaLocation: (deviceId) => this.resolveEventMediaWriteLocation(api, logger, deviceId),
|
|
62238
62289
|
readSource: async (mediaKey) => {
|
|
62239
62290
|
const file = await mediaStore.getByKey(mediaKey);
|
|
62240
62291
|
return file === null ? null : Buffer.from(file.base64, "base64");
|
|
@@ -68911,10 +68962,22 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68911
68962
|
const jobId = this.mediaRelocate.start(input);
|
|
68912
68963
|
this.ctx.logger.info("pipeline-analytics: media relocate job enqueued", { meta: {
|
|
68913
68964
|
jobId,
|
|
68965
|
+
mode: input.mode ?? "move",
|
|
68914
68966
|
to: input.toLocationId
|
|
68915
68967
|
} });
|
|
68916
68968
|
return { jobId };
|
|
68917
68969
|
}
|
|
68970
|
+
/**
|
|
68971
|
+
* How many media / retrain rows still carry no `locationId`.
|
|
68972
|
+
*
|
|
68973
|
+
* Counted against the rows, never inferred from a seal job's terminal state:
|
|
68974
|
+
* the migration planner refuses a non-blocking `eventMedia` cutover on this
|
|
68975
|
+
* number, and a seal that failed some rows still finishes.
|
|
68976
|
+
*/
|
|
68977
|
+
async countUnstampedEventMedia() {
|
|
68978
|
+
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
68979
|
+
return this.mediaRelocate.countUnstamped();
|
|
68980
|
+
}
|
|
68918
68981
|
/** Every relocate job this addon knows about, newest first (in RAM: the
|
|
68919
68982
|
* move is resumable, so a lost list costs nothing but the display). */
|
|
68920
68983
|
async listRelocateMediaJobs() {
|
|
@@ -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-B8VSQDMs.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.147",
|
|
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",
|