@camstack/addon-post-analysis 1.2.36 → 1.2.38
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-B-2HqwZV.mjs → dist-BdBTDQxw.mjs} +25 -1
- package/dist/{dist-cXcI2z-g.js → dist-DQvvZ5_E.js} +25 -1
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-DmmNcR6B.js → node-CJcK55RY.js} +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CyRodtV2.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DrKQwfNe.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-D2GwK89q.mjs → hostInit-DyfCBQE8.mjs} +1 -1
- package/dist/pipeline-analytics/index.js +190 -395
- package/dist/pipeline-analytics/index.mjs +189 -394
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-DQvvZ5_E.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_dist.__toESM(node_path);
|
|
@@ -14030,41 +14030,6 @@ function shouldSkipDropoutFrame(activeTrackCount, detectionCount, consecutiveSki
|
|
|
14030
14030
|
return consecutiveSkips < cfg.maxSkipFrames;
|
|
14031
14031
|
}
|
|
14032
14032
|
//#endregion
|
|
14033
|
-
//#region src/pipeline-analytics/pipeline/embedding-search.ts
|
|
14034
|
-
/**
|
|
14035
|
-
* embedding-search — CLIP text→embedding cosine ranking helper.
|
|
14036
|
-
*
|
|
14037
|
-
* `rankByText` mirrors `face-matcher.matchEmbedding` with the identity-grouping
|
|
14038
|
-
* removed: it compares a single query vector against all rows, enforces the
|
|
14039
|
-
* same-modelId/dim gate, and returns the top-N rows above `minScore` sorted
|
|
14040
|
-
* descending by cosine similarity.
|
|
14041
|
-
*/
|
|
14042
|
-
/**
|
|
14043
|
-
* Brute-force cosine rank of `rows` against `queryVec`.
|
|
14044
|
-
*
|
|
14045
|
-
* Gate: same-modelId (when `opts.modelId` is set) AND same dimension as
|
|
14046
|
-
* `queryVec.length`. Rows that fail either gate are skipped.
|
|
14047
|
-
* Returns at most `opts.limit` rows with `score >= opts.minScore`,
|
|
14048
|
-
* ordered descending by score. Immutable — does not modify inputs.
|
|
14049
|
-
*/
|
|
14050
|
-
function rankByText(queryVec, rows, opts) {
|
|
14051
|
-
const queryDim = queryVec.length;
|
|
14052
|
-
const scored = [];
|
|
14053
|
-
for (const row of rows) {
|
|
14054
|
-
if (opts.modelId !== void 0 && row.modelId !== opts.modelId) continue;
|
|
14055
|
-
if (row.dim !== queryDim) continue;
|
|
14056
|
-
if (row.embedding.length !== queryDim) continue;
|
|
14057
|
-
const score = require_dist.cosineSimilarity(queryVec, new Float32Array(row.embedding));
|
|
14058
|
-
if (score < opts.minScore) continue;
|
|
14059
|
-
scored.push({
|
|
14060
|
-
row,
|
|
14061
|
-
score
|
|
14062
|
-
});
|
|
14063
|
-
}
|
|
14064
|
-
scored.sort((a, b) => b.score - a.score);
|
|
14065
|
-
return scored.slice(0, opts.limit);
|
|
14066
|
-
}
|
|
14067
|
-
//#endregion
|
|
14068
14033
|
//#region src/pipeline-analytics/pipeline/event-child-crops.ts
|
|
14069
14034
|
/**
|
|
14070
14035
|
* Compute Intersection-over-Union between two (x,y,w,h) bounding boxes.
|
|
@@ -20291,50 +20256,35 @@ var IdentityStore = class {
|
|
|
20291
20256
|
}
|
|
20292
20257
|
};
|
|
20293
20258
|
//#endregion
|
|
20294
|
-
//#region src/pipeline-analytics/store/object-embedding-
|
|
20259
|
+
//#region src/pipeline-analytics/store/object-embedding-store.ts
|
|
20295
20260
|
/**
|
|
20296
|
-
*
|
|
20297
|
-
*
|
|
20298
|
-
* ## Why a mirror and not a swap
|
|
20261
|
+
* ObjectEmbeddingStore — per-track CLIP image embeddings, in the `vector-store`
|
|
20262
|
+
* capability.
|
|
20299
20263
|
*
|
|
20300
|
-
*
|
|
20301
|
-
* fast enough to use. Flipping it onto a vector index that has not been
|
|
20302
|
-
* backfilled would return an empty search on a hub with 8,455 embeddings — a
|
|
20303
|
-
* regression an operator would notice before any benefit landed.
|
|
20264
|
+
* ## What this used to be
|
|
20304
20265
|
*
|
|
20305
|
-
*
|
|
20306
|
-
*
|
|
20307
|
-
*
|
|
20308
|
-
*
|
|
20309
|
-
*
|
|
20266
|
+
* Until 2026-08-06 this owned a `pipeline-analytics:object-embeddings` settings
|
|
20267
|
+
* collection whose `embedding` column was a JSON array of 512 floats — measured
|
|
20268
|
+
* at ~11.7 KB per row on the live hub and `JSON.parse`d 5,000 times on every
|
|
20269
|
+
* semantic search. That collection is gone. The vector store is now the only
|
|
20270
|
+
* home for these vectors: base64 Float32LE (2,732 bytes for the same 512 dims),
|
|
20271
|
+
* with `deviceId` / `timestamp` / `className` / `modelId` promoted to indexed
|
|
20272
|
+
* columns so prefilters are pushed into SQL rather than applied after loading.
|
|
20310
20273
|
*
|
|
20311
|
-
*
|
|
20312
|
-
*
|
|
20313
|
-
*
|
|
20274
|
+
* The dual-write mirror and the boot backfill that carried the transition were
|
|
20275
|
+
* deleted along with the collection: the index converged (8,467 = 8,467, delta
|
|
20276
|
+
* 0) and a migration aid left running is just a second writer nobody reads.
|
|
20314
20277
|
*
|
|
20315
|
-
* ##
|
|
20278
|
+
* ## What did NOT change
|
|
20316
20279
|
*
|
|
20317
|
-
* One
|
|
20318
|
-
*
|
|
20319
|
-
*
|
|
20320
|
-
*
|
|
20280
|
+
* One row per track, keyed by `trackId`, holding the track's best-confidence
|
|
20281
|
+
* detection. The confidence gate lives here rather than in the caller because
|
|
20282
|
+
* the in-memory `TrackBestSelector` resets on restart and this is the durable
|
|
20283
|
+
* half — see `best-selection-policies.ts`.
|
|
20321
20284
|
*/
|
|
20322
|
-
/** The
|
|
20285
|
+
/** The vector-store index this store owns. */
|
|
20323
20286
|
var OBJECT_CLIP_INDEX = "object-clip";
|
|
20324
|
-
|
|
20325
|
-
function metadataFor(row) {
|
|
20326
|
-
return {
|
|
20327
|
-
deviceId: row.deviceId,
|
|
20328
|
-
timestamp: row.timestamp,
|
|
20329
|
-
className: row.className,
|
|
20330
|
-
modelId: row.modelId,
|
|
20331
|
-
trackId: row.trackId,
|
|
20332
|
-
confidence: row.confidence,
|
|
20333
|
-
...row.mediaKey !== void 0 ? { mediaKey: row.mediaKey } : {},
|
|
20334
|
-
...row.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: row.keyFrameMediaKey } : {}
|
|
20335
|
-
};
|
|
20336
|
-
}
|
|
20337
|
-
var ObjectEmbeddingMirror = class {
|
|
20287
|
+
var ObjectEmbeddingStore = class {
|
|
20338
20288
|
vectorStore;
|
|
20339
20289
|
logger;
|
|
20340
20290
|
declaredDim = null;
|
|
@@ -20343,11 +20293,13 @@ var ObjectEmbeddingMirror = class {
|
|
|
20343
20293
|
this.logger = deps.logger;
|
|
20344
20294
|
}
|
|
20345
20295
|
/**
|
|
20346
|
-
* Declare the index lazily, on the first vector
|
|
20347
|
-
*
|
|
20296
|
+
* Declare the index lazily, on the first vector — its dimension comes from
|
|
20297
|
+
* the active encoder and is not known at construction.
|
|
20348
20298
|
*
|
|
20349
|
-
* Returns false when
|
|
20350
|
-
*
|
|
20299
|
+
* Returns false when declaration failed, and the caller must then skip the
|
|
20300
|
+
* write: the provider rejects an upsert into an index it has never seen. A
|
|
20301
|
+
* dim change against a populated index is REFUSED rather than wiping it, so
|
|
20302
|
+
* this failing is the signal that the encoder model changed.
|
|
20351
20303
|
*/
|
|
20352
20304
|
async ensureIndex(dim) {
|
|
20353
20305
|
if (this.declaredDim === dim) return true;
|
|
@@ -20360,7 +20312,7 @@ var ObjectEmbeddingMirror = class {
|
|
|
20360
20312
|
this.declaredDim = dim;
|
|
20361
20313
|
return true;
|
|
20362
20314
|
} catch (err) {
|
|
20363
|
-
this.logger.warn("
|
|
20315
|
+
this.logger.warn("object-embedding index declaration failed — writes disabled for this dim", { meta: {
|
|
20364
20316
|
index: OBJECT_CLIP_INDEX,
|
|
20365
20317
|
dim,
|
|
20366
20318
|
error: String(err)
|
|
@@ -20368,232 +20320,34 @@ var ObjectEmbeddingMirror = class {
|
|
|
20368
20320
|
return false;
|
|
20369
20321
|
}
|
|
20370
20322
|
}
|
|
20371
|
-
/**
|
|
20372
|
-
|
|
20373
|
-
|
|
20374
|
-
|
|
20323
|
+
/**
|
|
20324
|
+
* Upsert the best-confidence embedding for a track. At most ONE row per
|
|
20325
|
+
* track: a write only replaces the stored row when its confidence is
|
|
20326
|
+
* strictly higher, keeping the track's best crop over its lifetime.
|
|
20327
|
+
*/
|
|
20328
|
+
async upsertIfBetter(input) {
|
|
20329
|
+
if (input.embedding.length === 0) return;
|
|
20330
|
+
if (!await this.ensureIndex(input.embedding.length)) return;
|
|
20375
20331
|
try {
|
|
20332
|
+
const stored = (await this.vectorStore.getByIds.query({
|
|
20333
|
+
index: OBJECT_CLIP_INDEX,
|
|
20334
|
+
ids: [input.trackId]
|
|
20335
|
+
})).items[0]?.metadata["confidence"];
|
|
20336
|
+
if (typeof stored === "number" && input.confidence <= stored) return;
|
|
20376
20337
|
if ((await this.vectorStore.upsert.mutate({
|
|
20377
20338
|
index: "object-clip",
|
|
20378
20339
|
items: [{
|
|
20379
|
-
id:
|
|
20380
|
-
vector: require_dist.encodeVectorBase64(
|
|
20381
|
-
metadata: metadataFor(
|
|
20340
|
+
id: input.trackId,
|
|
20341
|
+
vector: require_dist.encodeVectorBase64(input.embedding),
|
|
20342
|
+
metadata: metadataFor(input)
|
|
20382
20343
|
}]
|
|
20383
|
-
})).rejected > 0) this.logger.warn("
|
|
20384
|
-
tags: { deviceId:
|
|
20385
|
-
meta: {
|
|
20386
|
-
trackId: row.trackId,
|
|
20387
|
-
dim: row.embedding.length
|
|
20388
|
-
}
|
|
20389
|
-
});
|
|
20390
|
-
} catch (err) {
|
|
20391
|
-
this.logger.warn("vector-store mirror failed", {
|
|
20392
|
-
tags: { deviceId: row.deviceId },
|
|
20344
|
+
})).rejected > 0) this.logger.warn("object-embedding upsert rejected", {
|
|
20345
|
+
tags: { deviceId: input.deviceId },
|
|
20393
20346
|
meta: {
|
|
20394
|
-
trackId:
|
|
20395
|
-
|
|
20347
|
+
trackId: input.trackId,
|
|
20348
|
+
dim: input.embedding.length
|
|
20396
20349
|
}
|
|
20397
20350
|
});
|
|
20398
|
-
}
|
|
20399
|
-
}
|
|
20400
|
-
/** Mirror a page of embeddings — the backfill path. */
|
|
20401
|
-
async mirrorMany(rows) {
|
|
20402
|
-
const usable = rows.filter((r) => r.embedding.length > 0);
|
|
20403
|
-
const first = usable[0];
|
|
20404
|
-
if (first === void 0) return { upserted: 0 };
|
|
20405
|
-
if (!await this.ensureIndex(first.embedding.length)) return { upserted: 0 };
|
|
20406
|
-
const items = usable.filter((r) => r.embedding.length === first.embedding.length).map((r) => ({
|
|
20407
|
-
id: r.trackId,
|
|
20408
|
-
vector: require_dist.encodeVectorBase64(r.embedding),
|
|
20409
|
-
metadata: metadataFor(r)
|
|
20410
|
-
}));
|
|
20411
|
-
const skipped = usable.length - items.length;
|
|
20412
|
-
if (skipped > 0) this.logger.warn("backfill skipped embeddings of a different dimension", { meta: {
|
|
20413
|
-
skipped,
|
|
20414
|
-
expectedDim: first.embedding.length
|
|
20415
|
-
} });
|
|
20416
|
-
try {
|
|
20417
|
-
return { upserted: (await this.vectorStore.upsert.mutate({
|
|
20418
|
-
index: OBJECT_CLIP_INDEX,
|
|
20419
|
-
items
|
|
20420
|
-
})).upserted };
|
|
20421
|
-
} catch (err) {
|
|
20422
|
-
this.logger.warn("vector-store backfill page failed", { meta: {
|
|
20423
|
-
count: items.length,
|
|
20424
|
-
error: String(err)
|
|
20425
|
-
} });
|
|
20426
|
-
return { upserted: 0 };
|
|
20427
|
-
}
|
|
20428
|
-
}
|
|
20429
|
-
/** Keep the index in step with the legacy cascade delete. */
|
|
20430
|
-
async remove(trackIds) {
|
|
20431
|
-
if (trackIds.length === 0) return;
|
|
20432
|
-
try {
|
|
20433
|
-
await this.vectorStore.deleteByIds.mutate({
|
|
20434
|
-
index: OBJECT_CLIP_INDEX,
|
|
20435
|
-
ids: [...trackIds]
|
|
20436
|
-
});
|
|
20437
|
-
} catch (err) {
|
|
20438
|
-
this.logger.warn("vector-store mirror delete failed", { meta: {
|
|
20439
|
-
count: trackIds.length,
|
|
20440
|
-
error: String(err)
|
|
20441
|
-
} });
|
|
20442
|
-
}
|
|
20443
|
-
}
|
|
20444
|
-
/**
|
|
20445
|
-
* How far the mirror has got, for the decision to flip the read path.
|
|
20446
|
-
* Returns null when the index is not reachable or not yet declared.
|
|
20447
|
-
*/
|
|
20448
|
-
async count() {
|
|
20449
|
-
try {
|
|
20450
|
-
return (await this.vectorStore.stats.query({ index: OBJECT_CLIP_INDEX })).count;
|
|
20451
|
-
} catch {
|
|
20452
|
-
return null;
|
|
20453
|
-
}
|
|
20454
|
-
}
|
|
20455
|
-
};
|
|
20456
|
-
//#endregion
|
|
20457
|
-
//#region src/pipeline-analytics/store/object-embedding-store.ts
|
|
20458
|
-
var OBJECT_EMBEDDINGS_COLLECTION = "pipeline-analytics:object-embeddings";
|
|
20459
|
-
var OBJECT_EMBEDDING_COLUMNS = [
|
|
20460
|
-
{
|
|
20461
|
-
name: "id",
|
|
20462
|
-
type: "TEXT",
|
|
20463
|
-
primaryKey: true,
|
|
20464
|
-
notNull: true
|
|
20465
|
-
},
|
|
20466
|
-
{
|
|
20467
|
-
name: "deviceId",
|
|
20468
|
-
type: "INTEGER",
|
|
20469
|
-
notNull: true
|
|
20470
|
-
},
|
|
20471
|
-
{
|
|
20472
|
-
name: "trackId",
|
|
20473
|
-
type: "TEXT",
|
|
20474
|
-
notNull: true
|
|
20475
|
-
},
|
|
20476
|
-
{
|
|
20477
|
-
name: "timestamp",
|
|
20478
|
-
type: "INTEGER",
|
|
20479
|
-
notNull: true
|
|
20480
|
-
},
|
|
20481
|
-
{
|
|
20482
|
-
name: "className",
|
|
20483
|
-
type: "TEXT",
|
|
20484
|
-
notNull: true
|
|
20485
|
-
},
|
|
20486
|
-
{
|
|
20487
|
-
name: "embedding",
|
|
20488
|
-
type: "JSON",
|
|
20489
|
-
notNull: true
|
|
20490
|
-
},
|
|
20491
|
-
{
|
|
20492
|
-
name: "modelId",
|
|
20493
|
-
type: "TEXT",
|
|
20494
|
-
notNull: true
|
|
20495
|
-
},
|
|
20496
|
-
{
|
|
20497
|
-
name: "dim",
|
|
20498
|
-
type: "INTEGER",
|
|
20499
|
-
notNull: true
|
|
20500
|
-
},
|
|
20501
|
-
{
|
|
20502
|
-
name: "confidence",
|
|
20503
|
-
type: "REAL",
|
|
20504
|
-
notNull: true
|
|
20505
|
-
},
|
|
20506
|
-
{
|
|
20507
|
-
name: "mediaKey",
|
|
20508
|
-
type: "TEXT"
|
|
20509
|
-
},
|
|
20510
|
-
{
|
|
20511
|
-
name: "keyFrameMediaKey",
|
|
20512
|
-
type: "TEXT"
|
|
20513
|
-
}
|
|
20514
|
-
];
|
|
20515
|
-
/**
|
|
20516
|
-
* Ceiling on the CANDIDATE pool a semantic search may load.
|
|
20517
|
-
*
|
|
20518
|
-
* `query()` was called with no limit at all, so a default search loaded the
|
|
20519
|
-
* entire collection — 86.5 MB across 7,375 rows on a live hub, every time,
|
|
20520
|
-
* before ranking anything.
|
|
20521
|
-
*
|
|
20522
|
-
* It cannot simply be the caller's result limit: search RANKS the pool and then
|
|
20523
|
-
* takes the top N, so limiting to N up front would return N arbitrary rows
|
|
20524
|
-
* instead of the N best. This bounds the pool well above any result count, and
|
|
20525
|
-
* `query()` orders by `timestamp` DESC whenever a limit applies so "the pool"
|
|
20526
|
-
* means the most recent rows rather than whatever the page order happened to
|
|
20527
|
-
* be. The caller logs when the cap is reached — a silently truncated search
|
|
20528
|
-
* reads as "nothing older matched".
|
|
20529
|
-
*/
|
|
20530
|
-
var EMBEDDING_SEARCH_CANDIDATE_CAP = 5e3;
|
|
20531
|
-
var ObjectEmbeddingStore = class {
|
|
20532
|
-
store;
|
|
20533
|
-
logger;
|
|
20534
|
-
mirror;
|
|
20535
|
-
constructor(deps) {
|
|
20536
|
-
this.store = deps.store;
|
|
20537
|
-
this.logger = deps.logger;
|
|
20538
|
-
this.mirror = deps.mirror ?? null;
|
|
20539
|
-
}
|
|
20540
|
-
static async declare(store) {
|
|
20541
|
-
await store.declareCollection.mutate({
|
|
20542
|
-
collection: OBJECT_EMBEDDINGS_COLLECTION,
|
|
20543
|
-
columns: [...OBJECT_EMBEDDING_COLUMNS],
|
|
20544
|
-
indexes: [{
|
|
20545
|
-
name: "idx_obj_emb_device_ts",
|
|
20546
|
-
columns: ["deviceId", "timestamp"]
|
|
20547
|
-
}, {
|
|
20548
|
-
name: "idx_obj_emb_track",
|
|
20549
|
-
columns: ["trackId"]
|
|
20550
|
-
}]
|
|
20551
|
-
});
|
|
20552
|
-
}
|
|
20553
|
-
/**
|
|
20554
|
-
* Upsert the best-confidence CLIP embedding for a track. The id is the
|
|
20555
|
-
* trackId so there is at most ONE row per track. An upsert only replaces
|
|
20556
|
-
* the row when the new confidence is strictly higher than the stored value,
|
|
20557
|
-
* keeping the best-crop embedding for the track over its lifetime.
|
|
20558
|
-
*/
|
|
20559
|
-
async upsertIfBetter(input) {
|
|
20560
|
-
const existing = await this.store.get.query({
|
|
20561
|
-
collection: OBJECT_EMBEDDINGS_COLLECTION,
|
|
20562
|
-
key: input.trackId
|
|
20563
|
-
});
|
|
20564
|
-
if (existing !== void 0 && existing !== null) {
|
|
20565
|
-
const storedConf = existing.confidence;
|
|
20566
|
-
if (typeof storedConf === "number" && input.confidence <= storedConf) return;
|
|
20567
|
-
}
|
|
20568
|
-
const record = {
|
|
20569
|
-
deviceId: input.deviceId,
|
|
20570
|
-
trackId: input.trackId,
|
|
20571
|
-
timestamp: input.timestamp,
|
|
20572
|
-
className: input.className,
|
|
20573
|
-
embedding: input.embedding,
|
|
20574
|
-
modelId: input.modelId,
|
|
20575
|
-
dim: input.embedding.length,
|
|
20576
|
-
confidence: input.confidence,
|
|
20577
|
-
...input.mediaKey !== void 0 ? { mediaKey: input.mediaKey } : {},
|
|
20578
|
-
...input.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: input.keyFrameMediaKey } : {}
|
|
20579
|
-
};
|
|
20580
|
-
try {
|
|
20581
|
-
await this.store.set.mutate({
|
|
20582
|
-
collection: OBJECT_EMBEDDINGS_COLLECTION,
|
|
20583
|
-
key: input.trackId,
|
|
20584
|
-
value: record
|
|
20585
|
-
});
|
|
20586
|
-
await this.mirror?.mirror({
|
|
20587
|
-
trackId: input.trackId,
|
|
20588
|
-
deviceId: input.deviceId,
|
|
20589
|
-
timestamp: input.timestamp,
|
|
20590
|
-
className: input.className,
|
|
20591
|
-
embedding: input.embedding,
|
|
20592
|
-
modelId: input.modelId,
|
|
20593
|
-
confidence: input.confidence,
|
|
20594
|
-
...input.mediaKey !== void 0 ? { mediaKey: input.mediaKey } : {},
|
|
20595
|
-
...input.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: input.keyFrameMediaKey } : {}
|
|
20596
|
-
});
|
|
20597
20351
|
} catch (err) {
|
|
20598
20352
|
this.logger.warn("ObjectEmbeddingStore.upsertIfBetter failed", {
|
|
20599
20353
|
tags: { deviceId: input.deviceId },
|
|
@@ -20605,93 +20359,141 @@ var ObjectEmbeddingStore = class {
|
|
|
20605
20359
|
}
|
|
20606
20360
|
}
|
|
20607
20361
|
/**
|
|
20608
|
-
*
|
|
20609
|
-
*
|
|
20610
|
-
|
|
20611
|
-
|
|
20612
|
-
|
|
20613
|
-
|
|
20614
|
-
|
|
20615
|
-
|
|
20616
|
-
|
|
20617
|
-
if (input.
|
|
20618
|
-
if (input.
|
|
20619
|
-
|
|
20620
|
-
|
|
20621
|
-
|
|
20622
|
-
|
|
20362
|
+
* Rank stored embeddings against a query vector.
|
|
20363
|
+
*
|
|
20364
|
+
* Ranking happens in the provider, over compact rows, with the prefilters
|
|
20365
|
+
* pushed into SQL. This used to load every candidate into this process and
|
|
20366
|
+
* cosine them in JS behind a 5,000-row cap.
|
|
20367
|
+
*/
|
|
20368
|
+
async searchByVector(input) {
|
|
20369
|
+
const equals = {};
|
|
20370
|
+
if (input.deviceId !== void 0) equals["deviceId"] = input.deviceId;
|
|
20371
|
+
if (input.className !== void 0) equals["className"] = input.className;
|
|
20372
|
+
if (input.modelId !== void 0) equals["modelId"] = input.modelId;
|
|
20373
|
+
const between = {};
|
|
20374
|
+
if (input.since !== void 0 || input.until !== void 0) between["timestamp"] = [input.since ?? 0, input.until ?? Date.now()];
|
|
20375
|
+
const filter = {
|
|
20376
|
+
...Object.keys(equals).length > 0 ? { equals } : {},
|
|
20377
|
+
...Object.keys(between).length > 0 ? { between } : {}
|
|
20378
|
+
};
|
|
20379
|
+
try {
|
|
20380
|
+
const result = await this.vectorStore.query.query({
|
|
20381
|
+
index: OBJECT_CLIP_INDEX,
|
|
20382
|
+
vector: require_dist.encodeVectorBase64(input.vector),
|
|
20383
|
+
topK: input.limit,
|
|
20384
|
+
...input.minScore !== void 0 ? { minScore: input.minScore } : {},
|
|
20385
|
+
...Object.keys(filter).length > 0 ? { filter } : {}
|
|
20386
|
+
});
|
|
20387
|
+
const hits = [];
|
|
20388
|
+
for (const match of result.matches) {
|
|
20389
|
+
const row = rowFromMetadata(match.id, match.metadata);
|
|
20390
|
+
if (row === null) continue;
|
|
20391
|
+
hits.push({
|
|
20392
|
+
row,
|
|
20393
|
+
score: match.score
|
|
20394
|
+
});
|
|
20395
|
+
}
|
|
20396
|
+
return {
|
|
20397
|
+
hits,
|
|
20398
|
+
scanned: result.scanned,
|
|
20399
|
+
truncated: result.truncated
|
|
20623
20400
|
};
|
|
20624
|
-
}
|
|
20625
|
-
|
|
20626
|
-
|
|
20627
|
-
|
|
20628
|
-
|
|
20629
|
-
const data = r.data;
|
|
20401
|
+
} catch (err) {
|
|
20402
|
+
this.logger.warn("ObjectEmbeddingStore.searchByVector failed", {
|
|
20403
|
+
...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {},
|
|
20404
|
+
meta: { error: String(err) }
|
|
20405
|
+
});
|
|
20630
20406
|
return {
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
keyFrameMediaKey: data.keyFrameMediaKey ?? void 0
|
|
20407
|
+
hits: [],
|
|
20408
|
+
scanned: 0,
|
|
20409
|
+
truncated: false
|
|
20635
20410
|
};
|
|
20636
|
-
}
|
|
20411
|
+
}
|
|
20637
20412
|
}
|
|
20638
20413
|
/**
|
|
20639
|
-
* Retention sweep:
|
|
20640
|
-
*
|
|
20641
|
-
*
|
|
20414
|
+
* Retention sweep: drop every embedding older than `cutoffMs`, in one
|
|
20415
|
+
* filtered delete rather than the page-and-delete loop the collection needed.
|
|
20416
|
+
* Returns the number removed.
|
|
20642
20417
|
*/
|
|
20643
20418
|
async pruneAll(cutoffMs) {
|
|
20644
|
-
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
for (const row of rows) {
|
|
20656
|
-
const id = typeof row.id === "string" ? row.id : void 0;
|
|
20657
|
-
if (!id) continue;
|
|
20658
|
-
try {
|
|
20659
|
-
await this.store.delete.mutate({
|
|
20660
|
-
collection: OBJECT_EMBEDDINGS_COLLECTION,
|
|
20661
|
-
key: id
|
|
20662
|
-
});
|
|
20663
|
-
ids.push(id);
|
|
20664
|
-
deletedInPage++;
|
|
20665
|
-
} catch {}
|
|
20666
|
-
}
|
|
20667
|
-
if (deletedInPage === 0) break;
|
|
20419
|
+
try {
|
|
20420
|
+
return (await this.vectorStore.deleteByFilter.mutate({
|
|
20421
|
+
index: OBJECT_CLIP_INDEX,
|
|
20422
|
+
filter: { between: { timestamp: [0, cutoffMs] } }
|
|
20423
|
+
})).deleted;
|
|
20424
|
+
} catch (err) {
|
|
20425
|
+
this.logger.warn("ObjectEmbeddingStore.pruneAll failed", { meta: {
|
|
20426
|
+
cutoffMs,
|
|
20427
|
+
error: String(err)
|
|
20428
|
+
} });
|
|
20429
|
+
return 0;
|
|
20668
20430
|
}
|
|
20669
|
-
await this.mirror?.remove(ids);
|
|
20670
|
-
return ids;
|
|
20671
20431
|
}
|
|
20672
20432
|
/**
|
|
20673
20433
|
* `TrackScopedStore` contract for the retention cascade: delete the per-track
|
|
20674
|
-
* CLIP search vector for the given tracks.
|
|
20675
|
-
*
|
|
20676
|
-
* the identity MATCHING embeddings (ArcFace) live in a SEPARATE durable store
|
|
20434
|
+
* CLIP search vector for the given tracks. Pure EPHEMERAL track state — the
|
|
20435
|
+
* identity MATCHING embeddings (ArcFace) live in a SEPARATE durable store
|
|
20677
20436
|
* (`identity-samples`) and are never touched here, so pruning this never
|
|
20678
|
-
* breaks recognition (design §2/§4).
|
|
20437
|
+
* breaks recognition (design §2/§4).
|
|
20679
20438
|
*/
|
|
20680
20439
|
async deleteByTracks(trackIds) {
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
await this.
|
|
20684
|
-
|
|
20685
|
-
|
|
20440
|
+
if (trackIds.length === 0) return;
|
|
20441
|
+
try {
|
|
20442
|
+
await this.vectorStore.deleteByIds.mutate({
|
|
20443
|
+
index: OBJECT_CLIP_INDEX,
|
|
20444
|
+
ids: [...trackIds]
|
|
20686
20445
|
});
|
|
20687
20446
|
} catch (err) {
|
|
20688
20447
|
this.logger.warn("ObjectEmbeddingStore.deleteByTracks failed", { meta: {
|
|
20689
|
-
|
|
20448
|
+
count: trackIds.length,
|
|
20690
20449
|
error: String(err)
|
|
20691
20450
|
} });
|
|
20692
20451
|
}
|
|
20693
20452
|
}
|
|
20694
20453
|
};
|
|
20454
|
+
/** Build the vector-store metadata for one embedding. */
|
|
20455
|
+
function metadataFor(input) {
|
|
20456
|
+
return {
|
|
20457
|
+
deviceId: input.deviceId,
|
|
20458
|
+
timestamp: input.timestamp,
|
|
20459
|
+
className: input.className,
|
|
20460
|
+
modelId: input.modelId,
|
|
20461
|
+
trackId: input.trackId,
|
|
20462
|
+
confidence: input.confidence,
|
|
20463
|
+
...input.mediaKey !== void 0 ? { mediaKey: input.mediaKey } : {},
|
|
20464
|
+
...input.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: input.keyFrameMediaKey } : {}
|
|
20465
|
+
};
|
|
20466
|
+
}
|
|
20467
|
+
/**
|
|
20468
|
+
* Rebuild a stored row from vector-store metadata.
|
|
20469
|
+
*
|
|
20470
|
+
* Returns null when the row lacks its identity fields rather than filling them
|
|
20471
|
+
* with defaults: a hit whose `deviceId` silently became 0 would be joined to
|
|
20472
|
+
* the wrong camera's events.
|
|
20473
|
+
*/
|
|
20474
|
+
function rowFromMetadata(id, metadata) {
|
|
20475
|
+
const deviceId = metadata["deviceId"];
|
|
20476
|
+
const timestamp = metadata["timestamp"];
|
|
20477
|
+
const trackId = metadata["trackId"] ?? id;
|
|
20478
|
+
if (typeof deviceId !== "number" || typeof timestamp !== "number") return null;
|
|
20479
|
+
if (typeof trackId !== "string") return null;
|
|
20480
|
+
const className = metadata["className"];
|
|
20481
|
+
const modelId = metadata["modelId"];
|
|
20482
|
+
const confidence = metadata["confidence"];
|
|
20483
|
+
const mediaKey = metadata["mediaKey"];
|
|
20484
|
+
const keyFrameMediaKey = metadata["keyFrameMediaKey"];
|
|
20485
|
+
return {
|
|
20486
|
+
id,
|
|
20487
|
+
trackId,
|
|
20488
|
+
deviceId,
|
|
20489
|
+
timestamp,
|
|
20490
|
+
className: typeof className === "string" ? className : "unknown",
|
|
20491
|
+
modelId: typeof modelId === "string" ? modelId : "unknown",
|
|
20492
|
+
confidence: typeof confidence === "number" ? confidence : 0,
|
|
20493
|
+
...typeof mediaKey === "string" ? { mediaKey } : {},
|
|
20494
|
+
...typeof keyFrameMediaKey === "string" ? { keyFrameMediaKey } : {}
|
|
20495
|
+
};
|
|
20496
|
+
}
|
|
20695
20497
|
//#endregion
|
|
20696
20498
|
//#region src/pipeline-analytics/store/ops-log-store.ts
|
|
20697
20499
|
/**
|
|
@@ -23686,7 +23488,6 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
23686
23488
|
await FaceStore.declare(api.settingsStore);
|
|
23687
23489
|
await PlateStore.declare(api.settingsStore);
|
|
23688
23490
|
await VehicleStore.declare(api.settingsStore);
|
|
23689
|
-
await ObjectEmbeddingStore.declare(api.settingsStore);
|
|
23690
23491
|
await StationaryObjectRegistry.declare(api.settingsStore);
|
|
23691
23492
|
await OpsLogStore.declare(api.settingsStore);
|
|
23692
23493
|
await NotificationCenter.declare(api.settingsStore);
|
|
@@ -23701,7 +23502,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
23701
23502
|
let storage = this.ctx.kernel.storage;
|
|
23702
23503
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
23703
23504
|
if (mediaRoot) {
|
|
23704
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-
|
|
23505
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-CJcK55RY.js"));
|
|
23705
23506
|
storage = new FilesystemStorageProvider(mediaRoot, { eventMedia: mediaRoot });
|
|
23706
23507
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
23707
23508
|
}
|
|
@@ -23945,14 +23746,10 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
23945
23746
|
});
|
|
23946
23747
|
this.plateRecognizer.refreshGallery();
|
|
23947
23748
|
const vectorStore = api.vectorStore;
|
|
23948
|
-
|
|
23749
|
+
if (vectorStore === void 0) logger.warn("vector-store capability unavailable — CLIP semantic search is disabled", { meta: { provider: "sqlite-settings (builtin)" } });
|
|
23750
|
+
else this.objectEmbeddingStore = new ObjectEmbeddingStore({
|
|
23949
23751
|
vectorStore,
|
|
23950
|
-
logger: logger.child("
|
|
23951
|
-
}) : void 0;
|
|
23952
|
-
this.objectEmbeddingStore = new ObjectEmbeddingStore({
|
|
23953
|
-
store: api.settingsStore,
|
|
23954
|
-
logger: logger.child("ObjectEmbeddingStore"),
|
|
23955
|
-
...mirror !== void 0 ? { mirror } : {}
|
|
23752
|
+
logger: logger.child("ObjectEmbeddingStore")
|
|
23956
23753
|
});
|
|
23957
23754
|
}
|
|
23958
23755
|
/** Two-plane detail dispatcher: the per-frame executor runs ONLY the root
|
|
@@ -26165,8 +25962,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
26165
25962
|
this.ctx.logger.debug("plate buffer prune failed", { meta: { error: String(err) } });
|
|
26166
25963
|
}
|
|
26167
25964
|
if (this.objectEmbeddingStore && Number.isFinite(minObjectCutoffMs)) try {
|
|
26168
|
-
const
|
|
26169
|
-
if (
|
|
25965
|
+
const deletedEmbeddings = await this.objectEmbeddingStore.pruneAll(minObjectCutoffMs);
|
|
25966
|
+
if (deletedEmbeddings > 0) this.ctx.logger.info("object embedding retention prune", { meta: { deleted: deletedEmbeddings } });
|
|
26170
25967
|
} catch (err) {
|
|
26171
25968
|
this.ctx.logger.debug("object embedding prune failed", { meta: { error: String(err) } });
|
|
26172
25969
|
}
|
|
@@ -26707,9 +26504,9 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
26707
26504
|
*
|
|
26708
26505
|
* Flow:
|
|
26709
26506
|
* 1. Call `embedding-encoder.encodeText` to embed `text` → `queryVec`.
|
|
26710
|
-
* 2.
|
|
26711
|
-
*
|
|
26712
|
-
*
|
|
26507
|
+
* 2. Resolve the active encoder's modelId (same-model gate).
|
|
26508
|
+
* 3. Rank IN THE VECTOR STORE — prefilters pushed into SQL, cosine over
|
|
26509
|
+
* compact rows in the provider.
|
|
26713
26510
|
* 4. Fetch the corresponding ObjectEvents by trackId (via getObjectEvents
|
|
26714
26511
|
* with each winner's deviceId + trackId filter) and join score.
|
|
26715
26512
|
* 5. Return at most `limit` ScoredObjectEvents sorted descending by score.
|
|
@@ -26725,39 +26522,37 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
26725
26522
|
});
|
|
26726
26523
|
if (!encodeResult) return [];
|
|
26727
26524
|
const queryVec = new Float32Array(encodeResult.embedding);
|
|
26728
|
-
const
|
|
26729
|
-
|
|
26730
|
-
|
|
26731
|
-
until: input.until,
|
|
26732
|
-
className: input.classFilter,
|
|
26733
|
-
limit: EMBEDDING_SEARCH_CANDIDATE_CAP
|
|
26525
|
+
const encoderInfo = await api.embeddingEncoder.getInfo.query().catch((err) => {
|
|
26526
|
+
this.ctx.logger.warn("searchObjectEvents: getInfo failed, proceeding without modelId gate", { meta: { error: String(err) } });
|
|
26527
|
+
return null;
|
|
26734
26528
|
});
|
|
26735
|
-
|
|
26529
|
+
const search = await store.searchByVector({
|
|
26530
|
+
vector: queryVec,
|
|
26531
|
+
limit: input.limit,
|
|
26532
|
+
minScore: input.minScore,
|
|
26533
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
26534
|
+
...input.since !== void 0 ? { since: input.since } : {},
|
|
26535
|
+
...input.until !== void 0 ? { until: input.until } : {},
|
|
26536
|
+
...input.classFilter !== void 0 ? { className: input.classFilter } : {},
|
|
26537
|
+
...encoderInfo !== null ? { modelId: encoderInfo.modelId } : {}
|
|
26538
|
+
});
|
|
26539
|
+
if (search.truncated) this.ctx.logger.warn("searchObjectEvents: candidate pool truncated", {
|
|
26736
26540
|
meta: {
|
|
26737
|
-
|
|
26541
|
+
scanned: search.scanned,
|
|
26738
26542
|
since: input.since,
|
|
26739
26543
|
until: input.until,
|
|
26740
26544
|
className: input.classFilter
|
|
26741
26545
|
},
|
|
26742
|
-
|
|
26546
|
+
...input.deviceId !== void 0 ? { tags: { deviceId: input.deviceId } } : {}
|
|
26743
26547
|
});
|
|
26744
|
-
|
|
26548
|
+
const ranked = search.hits;
|
|
26549
|
+
if (ranked.length === 0) return [];
|
|
26745
26550
|
const embeddingMediaKeyByTrackId = /* @__PURE__ */ new Map();
|
|
26746
26551
|
const keyFrameKeyByTrackId = /* @__PURE__ */ new Map();
|
|
26747
|
-
for (const row of
|
|
26552
|
+
for (const { row } of ranked) {
|
|
26748
26553
|
if (row.mediaKey !== void 0) embeddingMediaKeyByTrackId.set(row.trackId, row.mediaKey);
|
|
26749
26554
|
if (row.keyFrameMediaKey !== void 0) keyFrameKeyByTrackId.set(row.trackId, row.keyFrameMediaKey);
|
|
26750
26555
|
}
|
|
26751
|
-
const encoderInfo = await api.embeddingEncoder.getInfo.query().catch((err) => {
|
|
26752
|
-
this.ctx.logger.warn("searchObjectEvents: getInfo failed, proceeding without modelId gate", { meta: { error: String(err) } });
|
|
26753
|
-
return null;
|
|
26754
|
-
});
|
|
26755
|
-
const ranked = rankByText(queryVec, embeddingRows, {
|
|
26756
|
-
minScore: input.minScore,
|
|
26757
|
-
limit: input.limit,
|
|
26758
|
-
...encoderInfo !== null ? { modelId: encoderInfo.modelId } : {}
|
|
26759
|
-
});
|
|
26760
|
-
if (ranked.length === 0) return [];
|
|
26761
26556
|
const scoreByTrackId = /* @__PURE__ */ new Map();
|
|
26762
26557
|
for (const { row, score } of ranked) scoreByTrackId.set(row.trackId, score);
|
|
26763
26558
|
const events = await (this.eventStore?.queryObjectByTrackIds([...scoreByTrackId.keys()]) ?? Promise.resolve([]));
|