@camstack/addon-post-analysis 1.2.93 → 1.2.95
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-DYoWeRRt.js → dist-BdojoP7i.js} +8 -0
- package/dist/{dist-3QRyOemL.mjs → dist-C-_TL-I1.mjs} +8 -0
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-C4-ljPpt.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CeFPqm3h.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-BoUejl7E.mjs → hostInit-BExsGl4h.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +235 -143
- package/dist/pipeline-analytics/index.mjs +235 -143
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -21525,6 +21525,14 @@ var ClipSchema = object({
|
|
|
21525
21525
|
endMs: number()
|
|
21526
21526
|
}),
|
|
21527
21527
|
/**
|
|
21528
|
+
* Distinct object classes attached to this visit (`person`, `car`, …),
|
|
21529
|
+
* dominant first, capped at {@link MAX_CLIP_LABELS}. The ribbon renders these
|
|
21530
|
+
* instead of the bare kind — "Object" tells the operator nothing a colour bar
|
|
21531
|
+
* did not. Absent (never empty) when the visit attached no classified object
|
|
21532
|
+
* event, so a motion/audio-only visit keeps its kind label.
|
|
21533
|
+
*/
|
|
21534
|
+
labels: array(string()).max(3).optional(),
|
|
21535
|
+
/**
|
|
21528
21536
|
* Lazy thumbnail URL, never inlined.
|
|
21529
21537
|
*
|
|
21530
21538
|
* Recording-derived clips (events-mode keep-window, and the prepared
|
|
@@ -21494,6 +21494,14 @@ var ClipSchema = object({
|
|
|
21494
21494
|
endMs: number()
|
|
21495
21495
|
}),
|
|
21496
21496
|
/**
|
|
21497
|
+
* Distinct object classes attached to this visit (`person`, `car`, …),
|
|
21498
|
+
* dominant first, capped at {@link MAX_CLIP_LABELS}. The ribbon renders these
|
|
21499
|
+
* instead of the bare kind — "Object" tells the operator nothing a colour bar
|
|
21500
|
+
* did not. Absent (never empty) when the visit attached no classified object
|
|
21501
|
+
* event, so a motion/audio-only visit keeps its kind label.
|
|
21502
|
+
*/
|
|
21503
|
+
labels: array(string()).max(3).optional(),
|
|
21504
|
+
/**
|
|
21497
21505
|
* Lazy thumbnail URL, never inlined.
|
|
21498
21506
|
*
|
|
21499
21507
|
* Recording-derived clips (events-mode keep-window, and the prepared
|
|
@@ -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-BdojoP7i.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_fs$1 = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
node_fs = require_dist.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { O as PoolMemoryWatchdog, Q as embeddingEncoderCapability, dt as resolvePoolMemoryPolicy, ot as parseProcStatus, tt as hfModelUrl, yt as BaseAddon } from "../dist-
|
|
1
|
+
import { O as PoolMemoryWatchdog, Q as embeddingEncoderCapability, dt as resolvePoolMemoryPolicy, ot as parseProcStatus, tt as hfModelUrl, yt as BaseAddon } from "../dist-C-_TL-I1.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import "node:fs";
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.24",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.88",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.60",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.88",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.24",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.60",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -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-BdojoP7i.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -6405,6 +6405,16 @@ var NcDeviceStateCache = class {
|
|
|
6405
6405
|
}
|
|
6406
6406
|
};
|
|
6407
6407
|
//#endregion
|
|
6408
|
+
//#region src/notification-center/default-tap-url.ts
|
|
6409
|
+
/** The viewer's hosted base path under the hub. */
|
|
6410
|
+
var VIEWER_BASE_PATH = "/viewer/camstack";
|
|
6411
|
+
function defaultTapUrl(input) {
|
|
6412
|
+
if (input.systemEvent === true) return null;
|
|
6413
|
+
if (input.baseUrl === null || input.baseUrl.length === 0) return null;
|
|
6414
|
+
if (!Number.isInteger(input.deviceId) || input.deviceId <= 0) return null;
|
|
6415
|
+
return `${input.baseUrl.replace(/\/+$/, "")}${VIEWER_BASE_PATH}/camera/${String(input.deviceId)}?at=${String(input.timestampMs)}`;
|
|
6416
|
+
}
|
|
6417
|
+
//#endregion
|
|
6408
6418
|
//#region src/shared/frame/box-drawer.ts
|
|
6409
6419
|
var DEFAULT_COLOR = require_dist.DEFAULT_EVENT_COLOR;
|
|
6410
6420
|
var DEFAULT_QUALITY = 80;
|
|
@@ -9262,12 +9272,19 @@ var NcDispatcher = class {
|
|
|
9262
9272
|
}) : rendered;
|
|
9263
9273
|
const attachments = [...await this.withArtifactUrls(entry, await this.resolveAttachments(entry)), ...frozenArtifactAttachments(entry)];
|
|
9264
9274
|
const params = pickParams(entry.payload.params);
|
|
9275
|
+
const tapUrl = params.clickUrl === void 0 && this.deps.viewerUrlBase !== void 0 ? defaultTapUrl({
|
|
9276
|
+
deviceId: systemEvent !== void 0 ? 0 : subject.deviceId,
|
|
9277
|
+
timestampMs: subject.timestamp,
|
|
9278
|
+
baseUrl: await this.deps.viewerUrlBase().catch(() => null),
|
|
9279
|
+
systemEvent: systemEvent !== void 0
|
|
9280
|
+
}) : null;
|
|
9265
9281
|
const actions = await this.mintActions(entry, target);
|
|
9266
9282
|
return {
|
|
9267
9283
|
body,
|
|
9268
9284
|
title,
|
|
9269
9285
|
priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
|
|
9270
9286
|
...params,
|
|
9287
|
+
...tapUrl !== null ? { clickUrl: tapUrl } : {},
|
|
9271
9288
|
tag: entry.payload.group?.key ?? entry.ruleId,
|
|
9272
9289
|
...systemEvent !== void 0 ? systemEvent.deviceId !== void 0 ? { deviceId: systemEvent.deviceId } : {} : { deviceId: subject.deviceId },
|
|
9273
9290
|
...subject.eventId !== void 0 ? { eventId: subject.eventId } : {},
|
|
@@ -19148,7 +19165,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19148
19165
|
confirmGate: (input) => confirmGate.judge(input),
|
|
19149
19166
|
confirmStats: () => confirmGate.stats()
|
|
19150
19167
|
} : {},
|
|
19151
|
-
releaseCooldown: (input) => this.releaseCooldown(input)
|
|
19168
|
+
releaseCooldown: (input) => this.releaseCooldown(input),
|
|
19169
|
+
...deps.dispatcher.viewerUrlBase !== void 0 ? { viewerUrlBase: deps.dispatcher.viewerUrlBase } : {}
|
|
19152
19170
|
});
|
|
19153
19171
|
this.outbox = new NcOutbox({
|
|
19154
19172
|
store: deps.store,
|
|
@@ -24856,6 +24874,188 @@ function clusterByEmbedding(items, threshold = .5) {
|
|
|
24856
24874
|
})).toSorted((a, b) => b.size - a.size || b.cohesion - a.cohesion);
|
|
24857
24875
|
}
|
|
24858
24876
|
//#endregion
|
|
24877
|
+
//#region src/pipeline-analytics/pipeline/face-matcher.ts
|
|
24878
|
+
/**
|
|
24879
|
+
* Count enrolled samples per identity for probes of a matching model+dimension.
|
|
24880
|
+
* Only identities meeting `minIdentitySamples` are eligible auto-match targets.
|
|
24881
|
+
*/
|
|
24882
|
+
function eligibleIdentities(gallery, probeModelId, probeDim, minIdentitySamples) {
|
|
24883
|
+
const counts = /* @__PURE__ */ new Map();
|
|
24884
|
+
for (const s of gallery) {
|
|
24885
|
+
if (s.modelId !== probeModelId) continue;
|
|
24886
|
+
if (s.embedding.length !== probeDim) continue;
|
|
24887
|
+
counts.set(s.identityId, (counts.get(s.identityId) ?? 0) + 1);
|
|
24888
|
+
}
|
|
24889
|
+
const eligible = /* @__PURE__ */ new Set();
|
|
24890
|
+
for (const [id, count] of counts) if (count >= minIdentitySamples) eligible.add(id);
|
|
24891
|
+
return eligible;
|
|
24892
|
+
}
|
|
24893
|
+
/**
|
|
24894
|
+
* Match a probe embedding against the gallery. Only samples with the same
|
|
24895
|
+
* `modelId` AND the same dimension are compared (model-version safety). Returns
|
|
24896
|
+
* the best identity when its score ≥ threshold and it beats the best OTHER
|
|
24897
|
+
* identity by ≥ margin; otherwise null.
|
|
24898
|
+
*/
|
|
24899
|
+
function matchEmbedding(probe, gallery, opts) {
|
|
24900
|
+
const probeVec = new Float32Array(probe.embedding);
|
|
24901
|
+
const eligible = eligibleIdentities(gallery, probe.modelId, probe.embedding.length, opts.minIdentitySamples ?? 1);
|
|
24902
|
+
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
24903
|
+
for (const s of gallery) {
|
|
24904
|
+
if (s.modelId !== probe.modelId) continue;
|
|
24905
|
+
if (s.embedding.length !== probe.embedding.length) continue;
|
|
24906
|
+
if (!eligible.has(s.identityId)) continue;
|
|
24907
|
+
const score = require_dist.cosineSimilarity(probeVec, new Float32Array(s.embedding));
|
|
24908
|
+
const prev = bestByIdentity.get(s.identityId);
|
|
24909
|
+
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
24910
|
+
}
|
|
24911
|
+
if (bestByIdentity.size === 0) return null;
|
|
24912
|
+
let bestId = null;
|
|
24913
|
+
let bestScore = -Infinity;
|
|
24914
|
+
let secondScore = -Infinity;
|
|
24915
|
+
for (const [id, score] of bestByIdentity) if (score > bestScore) {
|
|
24916
|
+
secondScore = bestScore;
|
|
24917
|
+
bestScore = score;
|
|
24918
|
+
bestId = id;
|
|
24919
|
+
} else if (score > secondScore) secondScore = score;
|
|
24920
|
+
if (bestId === null || bestScore < opts.threshold) return null;
|
|
24921
|
+
if (secondScore > -Infinity && bestScore - secondScore < opts.margin) return null;
|
|
24922
|
+
return {
|
|
24923
|
+
identityId: bestId,
|
|
24924
|
+
score: bestScore
|
|
24925
|
+
};
|
|
24926
|
+
}
|
|
24927
|
+
/**
|
|
24928
|
+
* Assign at most one identity per track AND at most one track per identity for
|
|
24929
|
+
* a single frame. Greedy by score: compute every candidate's full ranked match
|
|
24930
|
+
* list, then repeatedly take the globally-highest (track, identity) pair whose
|
|
24931
|
+
* track and identity are both still free, until none qualify. A losing track
|
|
24932
|
+
* falls back to its next-best free identity ≥ threshold. Ties resolve by input order.
|
|
24933
|
+
*/
|
|
24934
|
+
/**
|
|
24935
|
+
* Enrollment bound: the maximum cosine an embedding may score against a sample
|
|
24936
|
+
* of a DIFFERENT identity and still be enrolled. Genuine ArcFace cross-person
|
|
24937
|
+
* cosines sit under ~0.4; the low-information attractor (IR, blur, backs of
|
|
24938
|
+
* heads) sits at 0.95+. Measured 2026-08-18: 22 of 50 live samples were
|
|
24939
|
+
* enrolled from that attractor, with inter-identity cosines up to 0.988 — each
|
|
24940
|
+
* one a magnet that "recognises" every degraded probe as its owner.
|
|
24941
|
+
*/
|
|
24942
|
+
var ENROLLMENT_MAX_FOREIGN_COSINE = .9;
|
|
24943
|
+
/**
|
|
24944
|
+
* Gate an embedding BEFORE it becomes a gallery sample: condemned when its
|
|
24945
|
+
* best cosine against any sample of ANOTHER identity (same model + dimension)
|
|
24946
|
+
* reaches {@link ENROLLMENT_MAX_FOREIGN_COSINE}. Null = safe to enroll.
|
|
24947
|
+
* Closeness to the TARGET identity's own samples is expected and never
|
|
24948
|
+
* condemns.
|
|
24949
|
+
*/
|
|
24950
|
+
function enrollmentPromiscuity(embedding, modelId, targetIdentityId, gallery, bound = ENROLLMENT_MAX_FOREIGN_COSINE) {
|
|
24951
|
+
const probeVec = new Float32Array(embedding);
|
|
24952
|
+
let worst = null;
|
|
24953
|
+
for (const s of gallery) {
|
|
24954
|
+
if (s.identityId === targetIdentityId) continue;
|
|
24955
|
+
if (s.modelId !== modelId) continue;
|
|
24956
|
+
if (s.embedding.length !== embedding.length) continue;
|
|
24957
|
+
const cosine = require_dist.cosineSimilarity(probeVec, new Float32Array(s.embedding));
|
|
24958
|
+
if (cosine >= bound && (worst === null || cosine > worst.cosine)) worst = {
|
|
24959
|
+
identityId: s.identityId,
|
|
24960
|
+
cosine
|
|
24961
|
+
};
|
|
24962
|
+
}
|
|
24963
|
+
return worst;
|
|
24964
|
+
}
|
|
24965
|
+
function assignUniquePerFrame(candidates, gallery, opts) {
|
|
24966
|
+
const pairs = [];
|
|
24967
|
+
candidates.forEach((c, trackIdx) => {
|
|
24968
|
+
const probeVec = new Float32Array(c.embedding);
|
|
24969
|
+
const eligible = eligibleIdentities(gallery, c.modelId, c.embedding.length, opts.minIdentitySamples ?? 1);
|
|
24970
|
+
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
24971
|
+
for (const s of gallery) {
|
|
24972
|
+
if (s.modelId !== c.modelId) continue;
|
|
24973
|
+
if (s.embedding.length !== c.embedding.length) continue;
|
|
24974
|
+
if (!eligible.has(s.identityId)) continue;
|
|
24975
|
+
const score = require_dist.cosineSimilarity(probeVec, new Float32Array(s.embedding));
|
|
24976
|
+
const prev = bestByIdentity.get(s.identityId);
|
|
24977
|
+
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
24978
|
+
}
|
|
24979
|
+
let bestId = null;
|
|
24980
|
+
let bestScore = -1;
|
|
24981
|
+
let secondScore = -1;
|
|
24982
|
+
for (const [identityId, score] of bestByIdentity) if (score > bestScore) {
|
|
24983
|
+
if (bestId !== null) secondScore = bestScore;
|
|
24984
|
+
bestId = identityId;
|
|
24985
|
+
bestScore = score;
|
|
24986
|
+
} else if (score > secondScore) secondScore = score;
|
|
24987
|
+
const ambiguous = secondScore >= 0 && bestScore - secondScore < opts.margin;
|
|
24988
|
+
if (bestId !== null && !ambiguous) {
|
|
24989
|
+
for (const [identityId, score] of bestByIdentity) if (score >= opts.threshold) pairs.push({
|
|
24990
|
+
trackIdx,
|
|
24991
|
+
identityId,
|
|
24992
|
+
score
|
|
24993
|
+
});
|
|
24994
|
+
}
|
|
24995
|
+
});
|
|
24996
|
+
pairs.sort((a, b) => b.score - a.score);
|
|
24997
|
+
const takenTracks = /* @__PURE__ */ new Set();
|
|
24998
|
+
const takenIdentities = /* @__PURE__ */ new Set();
|
|
24999
|
+
const result = /* @__PURE__ */ new Map();
|
|
25000
|
+
for (const p of pairs) {
|
|
25001
|
+
if (takenTracks.has(p.trackIdx) || takenIdentities.has(p.identityId)) continue;
|
|
25002
|
+
takenTracks.add(p.trackIdx);
|
|
25003
|
+
takenIdentities.add(p.identityId);
|
|
25004
|
+
result.set(candidates[p.trackIdx].trackId, {
|
|
25005
|
+
identityId: p.identityId,
|
|
25006
|
+
score: p.score
|
|
25007
|
+
});
|
|
25008
|
+
}
|
|
25009
|
+
return result;
|
|
25010
|
+
}
|
|
25011
|
+
var EMPTY_AGGREGATE = Object.freeze({
|
|
25012
|
+
assignedIdentityId: null,
|
|
25013
|
+
confirmations: Object.freeze({})
|
|
25014
|
+
});
|
|
25015
|
+
/**
|
|
25016
|
+
* Fold one frame's match into a track's aggregate. A null match leaves state
|
|
25017
|
+
* untouched. An identity is assigned once its cumulative confirmation count
|
|
25018
|
+
* reaches `confirmFrames`; once assigned, a different identity must independently
|
|
25019
|
+
* reach `confirmFrames` to switch. `changed` is true only on a (re)assignment to
|
|
25020
|
+
* a NEW identity, so callers fire the label update exactly once per transition.
|
|
25021
|
+
*
|
|
25022
|
+
* On each (re)assignment the confirmations record is reset to contain only the
|
|
25023
|
+
* newly-assigned identity's count, so any challenger must independently re-reach
|
|
25024
|
+
* `confirmFrames` from scratch after any assignment.
|
|
25025
|
+
*/
|
|
25026
|
+
function updateTrackAggregate(prev, match, opts) {
|
|
25027
|
+
if (match === null) return {
|
|
25028
|
+
state: prev,
|
|
25029
|
+
changed: false
|
|
25030
|
+
};
|
|
25031
|
+
const count = (prev.confirmations[match.identityId] ?? 0) + 1;
|
|
25032
|
+
const confirmations = {
|
|
25033
|
+
...prev.confirmations,
|
|
25034
|
+
[match.identityId]: count
|
|
25035
|
+
};
|
|
25036
|
+
if (count < opts.confirmFrames) return {
|
|
25037
|
+
state: {
|
|
25038
|
+
...prev,
|
|
25039
|
+
confirmations
|
|
25040
|
+
},
|
|
25041
|
+
changed: false
|
|
25042
|
+
};
|
|
25043
|
+
if (prev.assignedIdentityId === match.identityId) return {
|
|
25044
|
+
state: {
|
|
25045
|
+
...prev,
|
|
25046
|
+
confirmations
|
|
25047
|
+
},
|
|
25048
|
+
changed: false
|
|
25049
|
+
};
|
|
25050
|
+
return {
|
|
25051
|
+
state: {
|
|
25052
|
+
assignedIdentityId: match.identityId,
|
|
25053
|
+
confirmations: { [match.identityId]: count }
|
|
25054
|
+
},
|
|
25055
|
+
changed: true
|
|
25056
|
+
};
|
|
25057
|
+
}
|
|
25058
|
+
//#endregion
|
|
24859
25059
|
//#region src/pipeline-analytics/face-gallery-provider.ts
|
|
24860
25060
|
/** Attribution of a face identity NAMED BY THE OPERATOR in the gallery. */
|
|
24861
25061
|
var OPERATOR_FACE_STEP_ID = "operator:face-gallery";
|
|
@@ -25102,6 +25302,19 @@ var FaceGalleryProvider = class {
|
|
|
25102
25302
|
}
|
|
25103
25303
|
const currentFace = await this.faceStore.get(faceId);
|
|
25104
25304
|
if (!currentFace) throw new Error(`FaceGalleryProvider.assignFace: face disappeared after pre-cleanup: ${faceId}`);
|
|
25305
|
+
const verdict = enrollmentPromiscuity(currentFace.embedding, MODEL_ID, identityId, await this.identityStore.loadGallery());
|
|
25306
|
+
if (verdict !== null) {
|
|
25307
|
+
this.logger.warn("assignFace refused: embedding too close to a FOREIGN identity sample", {
|
|
25308
|
+
tags: { deviceId: currentFace.deviceId },
|
|
25309
|
+
meta: {
|
|
25310
|
+
faceId,
|
|
25311
|
+
identityId,
|
|
25312
|
+
foreignIdentityId: verdict.identityId,
|
|
25313
|
+
cosine: verdict.cosine
|
|
25314
|
+
}
|
|
25315
|
+
});
|
|
25316
|
+
throw new Error(`FaceGalleryProvider.assignFace: embedding is ambiguous — cosine ${verdict.cosine.toFixed(3)} with a sample of another identity (${verdict.identityId}); pick a clearer crop`);
|
|
25317
|
+
}
|
|
25105
25318
|
let newKey;
|
|
25106
25319
|
if (currentFace.mediaKey) {
|
|
25107
25320
|
newKey = await this.mediaStore.reown({
|
|
@@ -30047,6 +30260,22 @@ function stillInstantMs(ranges) {
|
|
|
30047
30260
|
if (best === void 0) return void 0;
|
|
30048
30261
|
return Math.floor((best.startMs + best.endMs) / 2);
|
|
30049
30262
|
}
|
|
30263
|
+
/**
|
|
30264
|
+
* The distinct object classes attached to a visit, most frequent first,
|
|
30265
|
+
* capped at {@link MAX_CLIP_LABELS}. Empty when nothing classified overlapped
|
|
30266
|
+
* — the caller then omits the field so a motion/audio-only visit keeps its
|
|
30267
|
+
* kind label instead of carrying an empty array every consumer length-checks.
|
|
30268
|
+
*/
|
|
30269
|
+
function dominantLabels(attached) {
|
|
30270
|
+
const counts = /* @__PURE__ */ new Map();
|
|
30271
|
+
for (const a of attached) {
|
|
30272
|
+
if (a.kind !== "object") continue;
|
|
30273
|
+
const name = a.event.className;
|
|
30274
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
30275
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
30276
|
+
}
|
|
30277
|
+
return [...counts.entries()].toSorted((x, y) => y[1] - x[1]).slice(0, 3).map(([name]) => name);
|
|
30278
|
+
}
|
|
30050
30279
|
function visitsToClips(visits, events, pad, vouched) {
|
|
30051
30280
|
const tagged = [
|
|
30052
30281
|
...events.object.map((event) => ({
|
|
@@ -30067,6 +30296,7 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
30067
30296
|
const kind = dominantKind(attached);
|
|
30068
30297
|
const withThumb = attached.find((a) => a.kind === kind && a.event.mediaUrl && vouched.has(a.event.id)) ?? attached.find((a) => a.event.mediaUrl && vouched.has(a.event.id));
|
|
30069
30298
|
const stillAtMs = stillInstantMs(visit.ranges);
|
|
30299
|
+
const labels = dominantLabels(attached);
|
|
30070
30300
|
return {
|
|
30071
30301
|
id: `analytics:${visit.startMs}:${visit.endMs}:${VISIT_EVENT_ID}`,
|
|
30072
30302
|
source: "analytics",
|
|
@@ -30079,7 +30309,8 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
30079
30309
|
...stillAtMs !== void 0 ? { stillAtMs } : {},
|
|
30080
30310
|
eventIds: attached.slice(0, 24).map((a) => a.event.id),
|
|
30081
30311
|
eventCount: attached.length,
|
|
30082
|
-
holes: [...visit.holes]
|
|
30312
|
+
holes: [...visit.holes],
|
|
30313
|
+
...labels.length > 0 ? { labels } : {}
|
|
30083
30314
|
};
|
|
30084
30315
|
}).toSorted((a, b) => b.timeRange.startMs - a.timeRange.startMs);
|
|
30085
30316
|
}
|
|
@@ -33534,146 +33765,6 @@ async function resolveDefaultEventMedia(deps) {
|
|
|
33534
33765
|
return pickTrackFallbackMedia(await deps.listTrackMedia(trackId)) ?? null;
|
|
33535
33766
|
}
|
|
33536
33767
|
//#endregion
|
|
33537
|
-
//#region src/pipeline-analytics/pipeline/face-matcher.ts
|
|
33538
|
-
/**
|
|
33539
|
-
* Count enrolled samples per identity for probes of a matching model+dimension.
|
|
33540
|
-
* Only identities meeting `minIdentitySamples` are eligible auto-match targets.
|
|
33541
|
-
*/
|
|
33542
|
-
function eligibleIdentities(gallery, probeModelId, probeDim, minIdentitySamples) {
|
|
33543
|
-
const counts = /* @__PURE__ */ new Map();
|
|
33544
|
-
for (const s of gallery) {
|
|
33545
|
-
if (s.modelId !== probeModelId) continue;
|
|
33546
|
-
if (s.embedding.length !== probeDim) continue;
|
|
33547
|
-
counts.set(s.identityId, (counts.get(s.identityId) ?? 0) + 1);
|
|
33548
|
-
}
|
|
33549
|
-
const eligible = /* @__PURE__ */ new Set();
|
|
33550
|
-
for (const [id, count] of counts) if (count >= minIdentitySamples) eligible.add(id);
|
|
33551
|
-
return eligible;
|
|
33552
|
-
}
|
|
33553
|
-
/**
|
|
33554
|
-
* Match a probe embedding against the gallery. Only samples with the same
|
|
33555
|
-
* `modelId` AND the same dimension are compared (model-version safety). Returns
|
|
33556
|
-
* the best identity when its score ≥ threshold and it beats the best OTHER
|
|
33557
|
-
* identity by ≥ margin; otherwise null.
|
|
33558
|
-
*/
|
|
33559
|
-
function matchEmbedding(probe, gallery, opts) {
|
|
33560
|
-
const probeVec = new Float32Array(probe.embedding);
|
|
33561
|
-
const eligible = eligibleIdentities(gallery, probe.modelId, probe.embedding.length, opts.minIdentitySamples ?? 1);
|
|
33562
|
-
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
33563
|
-
for (const s of gallery) {
|
|
33564
|
-
if (s.modelId !== probe.modelId) continue;
|
|
33565
|
-
if (s.embedding.length !== probe.embedding.length) continue;
|
|
33566
|
-
if (!eligible.has(s.identityId)) continue;
|
|
33567
|
-
const score = require_dist.cosineSimilarity(probeVec, new Float32Array(s.embedding));
|
|
33568
|
-
const prev = bestByIdentity.get(s.identityId);
|
|
33569
|
-
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
33570
|
-
}
|
|
33571
|
-
if (bestByIdentity.size === 0) return null;
|
|
33572
|
-
let bestId = null;
|
|
33573
|
-
let bestScore = -Infinity;
|
|
33574
|
-
let secondScore = -Infinity;
|
|
33575
|
-
for (const [id, score] of bestByIdentity) if (score > bestScore) {
|
|
33576
|
-
secondScore = bestScore;
|
|
33577
|
-
bestScore = score;
|
|
33578
|
-
bestId = id;
|
|
33579
|
-
} else if (score > secondScore) secondScore = score;
|
|
33580
|
-
if (bestId === null || bestScore < opts.threshold) return null;
|
|
33581
|
-
if (secondScore > -Infinity && bestScore - secondScore < opts.margin) return null;
|
|
33582
|
-
return {
|
|
33583
|
-
identityId: bestId,
|
|
33584
|
-
score: bestScore
|
|
33585
|
-
};
|
|
33586
|
-
}
|
|
33587
|
-
/**
|
|
33588
|
-
* Assign at most one identity per track AND at most one track per identity for
|
|
33589
|
-
* a single frame. Greedy by score: compute every candidate's full ranked match
|
|
33590
|
-
* list, then repeatedly take the globally-highest (track, identity) pair whose
|
|
33591
|
-
* track and identity are both still free, until none qualify. A losing track
|
|
33592
|
-
* falls back to its next-best free identity ≥ threshold. Ties resolve by input order.
|
|
33593
|
-
*/
|
|
33594
|
-
function assignUniquePerFrame(candidates, gallery, opts) {
|
|
33595
|
-
const pairs = [];
|
|
33596
|
-
candidates.forEach((c, trackIdx) => {
|
|
33597
|
-
const probeVec = new Float32Array(c.embedding);
|
|
33598
|
-
const eligible = eligibleIdentities(gallery, c.modelId, c.embedding.length, opts.minIdentitySamples ?? 1);
|
|
33599
|
-
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
33600
|
-
for (const s of gallery) {
|
|
33601
|
-
if (s.modelId !== c.modelId) continue;
|
|
33602
|
-
if (s.embedding.length !== c.embedding.length) continue;
|
|
33603
|
-
if (!eligible.has(s.identityId)) continue;
|
|
33604
|
-
const score = require_dist.cosineSimilarity(probeVec, new Float32Array(s.embedding));
|
|
33605
|
-
const prev = bestByIdentity.get(s.identityId);
|
|
33606
|
-
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
33607
|
-
}
|
|
33608
|
-
for (const [identityId, score] of bestByIdentity) if (score >= opts.threshold) pairs.push({
|
|
33609
|
-
trackIdx,
|
|
33610
|
-
identityId,
|
|
33611
|
-
score
|
|
33612
|
-
});
|
|
33613
|
-
});
|
|
33614
|
-
pairs.sort((a, b) => b.score - a.score);
|
|
33615
|
-
const takenTracks = /* @__PURE__ */ new Set();
|
|
33616
|
-
const takenIdentities = /* @__PURE__ */ new Set();
|
|
33617
|
-
const result = /* @__PURE__ */ new Map();
|
|
33618
|
-
for (const p of pairs) {
|
|
33619
|
-
if (takenTracks.has(p.trackIdx) || takenIdentities.has(p.identityId)) continue;
|
|
33620
|
-
takenTracks.add(p.trackIdx);
|
|
33621
|
-
takenIdentities.add(p.identityId);
|
|
33622
|
-
result.set(candidates[p.trackIdx].trackId, {
|
|
33623
|
-
identityId: p.identityId,
|
|
33624
|
-
score: p.score
|
|
33625
|
-
});
|
|
33626
|
-
}
|
|
33627
|
-
return result;
|
|
33628
|
-
}
|
|
33629
|
-
var EMPTY_AGGREGATE = Object.freeze({
|
|
33630
|
-
assignedIdentityId: null,
|
|
33631
|
-
confirmations: Object.freeze({})
|
|
33632
|
-
});
|
|
33633
|
-
/**
|
|
33634
|
-
* Fold one frame's match into a track's aggregate. A null match leaves state
|
|
33635
|
-
* untouched. An identity is assigned once its cumulative confirmation count
|
|
33636
|
-
* reaches `confirmFrames`; once assigned, a different identity must independently
|
|
33637
|
-
* reach `confirmFrames` to switch. `changed` is true only on a (re)assignment to
|
|
33638
|
-
* a NEW identity, so callers fire the label update exactly once per transition.
|
|
33639
|
-
*
|
|
33640
|
-
* On each (re)assignment the confirmations record is reset to contain only the
|
|
33641
|
-
* newly-assigned identity's count, so any challenger must independently re-reach
|
|
33642
|
-
* `confirmFrames` from scratch after any assignment.
|
|
33643
|
-
*/
|
|
33644
|
-
function updateTrackAggregate(prev, match, opts) {
|
|
33645
|
-
if (match === null) return {
|
|
33646
|
-
state: prev,
|
|
33647
|
-
changed: false
|
|
33648
|
-
};
|
|
33649
|
-
const count = (prev.confirmations[match.identityId] ?? 0) + 1;
|
|
33650
|
-
const confirmations = {
|
|
33651
|
-
...prev.confirmations,
|
|
33652
|
-
[match.identityId]: count
|
|
33653
|
-
};
|
|
33654
|
-
if (count < opts.confirmFrames) return {
|
|
33655
|
-
state: {
|
|
33656
|
-
...prev,
|
|
33657
|
-
confirmations
|
|
33658
|
-
},
|
|
33659
|
-
changed: false
|
|
33660
|
-
};
|
|
33661
|
-
if (prev.assignedIdentityId === match.identityId) return {
|
|
33662
|
-
state: {
|
|
33663
|
-
...prev,
|
|
33664
|
-
confirmations
|
|
33665
|
-
},
|
|
33666
|
-
changed: false
|
|
33667
|
-
};
|
|
33668
|
-
return {
|
|
33669
|
-
state: {
|
|
33670
|
-
assignedIdentityId: match.identityId,
|
|
33671
|
-
confirmations: { [match.identityId]: count }
|
|
33672
|
-
},
|
|
33673
|
-
changed: true
|
|
33674
|
-
};
|
|
33675
|
-
}
|
|
33676
|
-
//#endregion
|
|
33677
33768
|
//#region src/pipeline-analytics/face-recognizer.ts
|
|
33678
33769
|
/** Attribution of an identity resolved by the in-process ArcFace matcher. */
|
|
33679
33770
|
var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
|
|
@@ -52378,6 +52469,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
52378
52469
|
},
|
|
52379
52470
|
renderEventMedia: (req) => this.renderEventMedia(req),
|
|
52380
52471
|
publishArtifact: async (bytes, mime) => await this.ncArtifactPlane?.publish(bytes, mime) ?? null,
|
|
52472
|
+
viewerUrlBase: async () => pickArtifactBaseUrl(await this.ncEndpoints()),
|
|
52381
52473
|
indexArtifact: async (bytes, mime, meta) => await this.ncArtifactPlane?.publishIndexed(bytes, mime, meta) ?? {
|
|
52382
52474
|
url: null,
|
|
52383
52475
|
artifactId: null
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as encodeVectorBase64, A as RetrainStatusSchema, At as literal, B as TrackSourceSchema, C as NcSnoozeSchema, Ct as isDeviceScopedCap, D as OpsLogEntrySchema, Dt as array, E as NcTaxonomySchema, Et as _enum, F as SceneMonitorSchema, Ft as string, G as audioMetricsCapability, H as addonWidgetsSourceCapability, I as TIMELAPSE_DENSE_FLOOR_SEC, It as unknown, J as cosineSimilarity$1, K as audioModeOf, L as TimelapseRuleInputSchema, Lt as EventCategory, M as SCENE_DEFAULT_ANCHOR_THRESHOLD, Mt as object, N as SCENE_DEFAULT_UNCOVERED_POLICY, Nt as partialRecord, Ot as boolean, P as SCENE_DIVERGED, Pt as record, R as TimelapseRulePatchSchema, S as NcSnoozeInputSchema, St as hydrateSchema, T as NcSystemEventKindSchema, Tt as sleep, U as alarmPanelCapability, V as VISIT_MERGE_GAP_MS, W as assertTimelapseCadences, X as defineCustomActions, Y as customAction, Z as deriveRecordingMode, _ as NcRuleInputSchema, _t as zoneAnalyticsCapability, at as notificationRulesCapability, b as NcRuleTargetSchema, bt as DeviceType, c as EVENT_PAD_MS, ct as plateGalleryCapability, d as NC_ALARM_SYSTEM_EVENT_KINDS, et as faceGalleryCapability, f as NC_CONDITION_CATALOG, ft as sceneMonitorCapability, g as NcConditionDescriptorSchema, gt as videoclipsCapability, h as NC_TAXONOMY, ht as vectorDimFromBase64, i as DETECTION_MACRO_CLASSES, it as kebabToCamel, jt as number, k as RECORDING_EXPORT_MAX_READ_BYTES, kt as discriminatedUnion, l as LabelAttributionSchema, lt as readDeviceStateFrom, mt as systemEventFilterApplies, n as DEFAULT_EVENT_COLOR, nt as isDetectionMacroClass, o as DeclaredDevices, p as NC_DEFAULT_SNOOZE_MINUTES, pt as subKindsOf, q as buildEventKindDescriptor, rt as isScheduleActive, s as EVENT_KIND_BY_CAP, st as pipelineAnalyticsCapability, t as BaseDevice, u as MACRO_LABELS, ut as readTimelapseGeneratedAt, v as NcRulePatchSchema, vt as errMsg, w as NcSnoozeSuppressedSchema, wt as nodePin, x as NcScheduleSchema, xt as createEvent, y as NcRuleSchema, yt as BaseAddon, z as TimelapseRuleSchema } from "../dist-
|
|
1
|
+
import { $ as encodeVectorBase64, A as RetrainStatusSchema, At as literal, B as TrackSourceSchema, C as NcSnoozeSchema, Ct as isDeviceScopedCap, D as OpsLogEntrySchema, Dt as array, E as NcTaxonomySchema, Et as _enum, F as SceneMonitorSchema, Ft as string, G as audioMetricsCapability, H as addonWidgetsSourceCapability, I as TIMELAPSE_DENSE_FLOOR_SEC, It as unknown, J as cosineSimilarity$1, K as audioModeOf, L as TimelapseRuleInputSchema, Lt as EventCategory, M as SCENE_DEFAULT_ANCHOR_THRESHOLD, Mt as object, N as SCENE_DEFAULT_UNCOVERED_POLICY, Nt as partialRecord, Ot as boolean, P as SCENE_DIVERGED, Pt as record, R as TimelapseRulePatchSchema, S as NcSnoozeInputSchema, St as hydrateSchema, T as NcSystemEventKindSchema, Tt as sleep, U as alarmPanelCapability, V as VISIT_MERGE_GAP_MS, W as assertTimelapseCadences, X as defineCustomActions, Y as customAction, Z as deriveRecordingMode, _ as NcRuleInputSchema, _t as zoneAnalyticsCapability, at as notificationRulesCapability, b as NcRuleTargetSchema, bt as DeviceType, c as EVENT_PAD_MS, ct as plateGalleryCapability, d as NC_ALARM_SYSTEM_EVENT_KINDS, et as faceGalleryCapability, f as NC_CONDITION_CATALOG, ft as sceneMonitorCapability, g as NcConditionDescriptorSchema, gt as videoclipsCapability, h as NC_TAXONOMY, ht as vectorDimFromBase64, i as DETECTION_MACRO_CLASSES, it as kebabToCamel, jt as number, k as RECORDING_EXPORT_MAX_READ_BYTES, kt as discriminatedUnion, l as LabelAttributionSchema, lt as readDeviceStateFrom, mt as systemEventFilterApplies, n as DEFAULT_EVENT_COLOR, nt as isDetectionMacroClass, o as DeclaredDevices, p as NC_DEFAULT_SNOOZE_MINUTES, pt as subKindsOf, q as buildEventKindDescriptor, rt as isScheduleActive, s as EVENT_KIND_BY_CAP, st as pipelineAnalyticsCapability, t as BaseDevice, u as MACRO_LABELS, ut as readTimelapseGeneratedAt, v as NcRulePatchSchema, vt as errMsg, w as NcSnoozeSuppressedSchema, wt as nodePin, x as NcScheduleSchema, xt as createEvent, y as NcRuleSchema, yt as BaseAddon, z as TimelapseRuleSchema } from "../dist-C-_TL-I1.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";
|
|
@@ -6398,6 +6398,16 @@ var NcDeviceStateCache = class {
|
|
|
6398
6398
|
}
|
|
6399
6399
|
};
|
|
6400
6400
|
//#endregion
|
|
6401
|
+
//#region src/notification-center/default-tap-url.ts
|
|
6402
|
+
/** The viewer's hosted base path under the hub. */
|
|
6403
|
+
var VIEWER_BASE_PATH = "/viewer/camstack";
|
|
6404
|
+
function defaultTapUrl(input) {
|
|
6405
|
+
if (input.systemEvent === true) return null;
|
|
6406
|
+
if (input.baseUrl === null || input.baseUrl.length === 0) return null;
|
|
6407
|
+
if (!Number.isInteger(input.deviceId) || input.deviceId <= 0) return null;
|
|
6408
|
+
return `${input.baseUrl.replace(/\/+$/, "")}${VIEWER_BASE_PATH}/camera/${String(input.deviceId)}?at=${String(input.timestampMs)}`;
|
|
6409
|
+
}
|
|
6410
|
+
//#endregion
|
|
6401
6411
|
//#region src/shared/frame/box-drawer.ts
|
|
6402
6412
|
var DEFAULT_COLOR = DEFAULT_EVENT_COLOR;
|
|
6403
6413
|
var DEFAULT_QUALITY = 80;
|
|
@@ -9255,12 +9265,19 @@ var NcDispatcher = class {
|
|
|
9255
9265
|
}) : rendered;
|
|
9256
9266
|
const attachments = [...await this.withArtifactUrls(entry, await this.resolveAttachments(entry)), ...frozenArtifactAttachments(entry)];
|
|
9257
9267
|
const params = pickParams(entry.payload.params);
|
|
9268
|
+
const tapUrl = params.clickUrl === void 0 && this.deps.viewerUrlBase !== void 0 ? defaultTapUrl({
|
|
9269
|
+
deviceId: systemEvent !== void 0 ? 0 : subject.deviceId,
|
|
9270
|
+
timestampMs: subject.timestamp,
|
|
9271
|
+
baseUrl: await this.deps.viewerUrlBase().catch(() => null),
|
|
9272
|
+
systemEvent: systemEvent !== void 0
|
|
9273
|
+
}) : null;
|
|
9258
9274
|
const actions = await this.mintActions(entry, target);
|
|
9259
9275
|
return {
|
|
9260
9276
|
body,
|
|
9261
9277
|
title,
|
|
9262
9278
|
priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
|
|
9263
9279
|
...params,
|
|
9280
|
+
...tapUrl !== null ? { clickUrl: tapUrl } : {},
|
|
9264
9281
|
tag: entry.payload.group?.key ?? entry.ruleId,
|
|
9265
9282
|
...systemEvent !== void 0 ? systemEvent.deviceId !== void 0 ? { deviceId: systemEvent.deviceId } : {} : { deviceId: subject.deviceId },
|
|
9266
9283
|
...subject.eventId !== void 0 ? { eventId: subject.eventId } : {},
|
|
@@ -19126,7 +19143,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19126
19143
|
confirmGate: (input) => confirmGate.judge(input),
|
|
19127
19144
|
confirmStats: () => confirmGate.stats()
|
|
19128
19145
|
} : {},
|
|
19129
|
-
releaseCooldown: (input) => this.releaseCooldown(input)
|
|
19146
|
+
releaseCooldown: (input) => this.releaseCooldown(input),
|
|
19147
|
+
...deps.dispatcher.viewerUrlBase !== void 0 ? { viewerUrlBase: deps.dispatcher.viewerUrlBase } : {}
|
|
19130
19148
|
});
|
|
19131
19149
|
this.outbox = new NcOutbox({
|
|
19132
19150
|
store: deps.store,
|
|
@@ -24834,6 +24852,188 @@ function clusterByEmbedding(items, threshold = .5) {
|
|
|
24834
24852
|
})).toSorted((a, b) => b.size - a.size || b.cohesion - a.cohesion);
|
|
24835
24853
|
}
|
|
24836
24854
|
//#endregion
|
|
24855
|
+
//#region src/pipeline-analytics/pipeline/face-matcher.ts
|
|
24856
|
+
/**
|
|
24857
|
+
* Count enrolled samples per identity for probes of a matching model+dimension.
|
|
24858
|
+
* Only identities meeting `minIdentitySamples` are eligible auto-match targets.
|
|
24859
|
+
*/
|
|
24860
|
+
function eligibleIdentities(gallery, probeModelId, probeDim, minIdentitySamples) {
|
|
24861
|
+
const counts = /* @__PURE__ */ new Map();
|
|
24862
|
+
for (const s of gallery) {
|
|
24863
|
+
if (s.modelId !== probeModelId) continue;
|
|
24864
|
+
if (s.embedding.length !== probeDim) continue;
|
|
24865
|
+
counts.set(s.identityId, (counts.get(s.identityId) ?? 0) + 1);
|
|
24866
|
+
}
|
|
24867
|
+
const eligible = /* @__PURE__ */ new Set();
|
|
24868
|
+
for (const [id, count] of counts) if (count >= minIdentitySamples) eligible.add(id);
|
|
24869
|
+
return eligible;
|
|
24870
|
+
}
|
|
24871
|
+
/**
|
|
24872
|
+
* Match a probe embedding against the gallery. Only samples with the same
|
|
24873
|
+
* `modelId` AND the same dimension are compared (model-version safety). Returns
|
|
24874
|
+
* the best identity when its score ≥ threshold and it beats the best OTHER
|
|
24875
|
+
* identity by ≥ margin; otherwise null.
|
|
24876
|
+
*/
|
|
24877
|
+
function matchEmbedding(probe, gallery, opts) {
|
|
24878
|
+
const probeVec = new Float32Array(probe.embedding);
|
|
24879
|
+
const eligible = eligibleIdentities(gallery, probe.modelId, probe.embedding.length, opts.minIdentitySamples ?? 1);
|
|
24880
|
+
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
24881
|
+
for (const s of gallery) {
|
|
24882
|
+
if (s.modelId !== probe.modelId) continue;
|
|
24883
|
+
if (s.embedding.length !== probe.embedding.length) continue;
|
|
24884
|
+
if (!eligible.has(s.identityId)) continue;
|
|
24885
|
+
const score = cosineSimilarity$1(probeVec, new Float32Array(s.embedding));
|
|
24886
|
+
const prev = bestByIdentity.get(s.identityId);
|
|
24887
|
+
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
24888
|
+
}
|
|
24889
|
+
if (bestByIdentity.size === 0) return null;
|
|
24890
|
+
let bestId = null;
|
|
24891
|
+
let bestScore = -Infinity;
|
|
24892
|
+
let secondScore = -Infinity;
|
|
24893
|
+
for (const [id, score] of bestByIdentity) if (score > bestScore) {
|
|
24894
|
+
secondScore = bestScore;
|
|
24895
|
+
bestScore = score;
|
|
24896
|
+
bestId = id;
|
|
24897
|
+
} else if (score > secondScore) secondScore = score;
|
|
24898
|
+
if (bestId === null || bestScore < opts.threshold) return null;
|
|
24899
|
+
if (secondScore > -Infinity && bestScore - secondScore < opts.margin) return null;
|
|
24900
|
+
return {
|
|
24901
|
+
identityId: bestId,
|
|
24902
|
+
score: bestScore
|
|
24903
|
+
};
|
|
24904
|
+
}
|
|
24905
|
+
/**
|
|
24906
|
+
* Assign at most one identity per track AND at most one track per identity for
|
|
24907
|
+
* a single frame. Greedy by score: compute every candidate's full ranked match
|
|
24908
|
+
* list, then repeatedly take the globally-highest (track, identity) pair whose
|
|
24909
|
+
* track and identity are both still free, until none qualify. A losing track
|
|
24910
|
+
* falls back to its next-best free identity ≥ threshold. Ties resolve by input order.
|
|
24911
|
+
*/
|
|
24912
|
+
/**
|
|
24913
|
+
* Enrollment bound: the maximum cosine an embedding may score against a sample
|
|
24914
|
+
* of a DIFFERENT identity and still be enrolled. Genuine ArcFace cross-person
|
|
24915
|
+
* cosines sit under ~0.4; the low-information attractor (IR, blur, backs of
|
|
24916
|
+
* heads) sits at 0.95+. Measured 2026-08-18: 22 of 50 live samples were
|
|
24917
|
+
* enrolled from that attractor, with inter-identity cosines up to 0.988 — each
|
|
24918
|
+
* one a magnet that "recognises" every degraded probe as its owner.
|
|
24919
|
+
*/
|
|
24920
|
+
var ENROLLMENT_MAX_FOREIGN_COSINE = .9;
|
|
24921
|
+
/**
|
|
24922
|
+
* Gate an embedding BEFORE it becomes a gallery sample: condemned when its
|
|
24923
|
+
* best cosine against any sample of ANOTHER identity (same model + dimension)
|
|
24924
|
+
* reaches {@link ENROLLMENT_MAX_FOREIGN_COSINE}. Null = safe to enroll.
|
|
24925
|
+
* Closeness to the TARGET identity's own samples is expected and never
|
|
24926
|
+
* condemns.
|
|
24927
|
+
*/
|
|
24928
|
+
function enrollmentPromiscuity(embedding, modelId, targetIdentityId, gallery, bound = ENROLLMENT_MAX_FOREIGN_COSINE) {
|
|
24929
|
+
const probeVec = new Float32Array(embedding);
|
|
24930
|
+
let worst = null;
|
|
24931
|
+
for (const s of gallery) {
|
|
24932
|
+
if (s.identityId === targetIdentityId) continue;
|
|
24933
|
+
if (s.modelId !== modelId) continue;
|
|
24934
|
+
if (s.embedding.length !== embedding.length) continue;
|
|
24935
|
+
const cosine = cosineSimilarity$1(probeVec, new Float32Array(s.embedding));
|
|
24936
|
+
if (cosine >= bound && (worst === null || cosine > worst.cosine)) worst = {
|
|
24937
|
+
identityId: s.identityId,
|
|
24938
|
+
cosine
|
|
24939
|
+
};
|
|
24940
|
+
}
|
|
24941
|
+
return worst;
|
|
24942
|
+
}
|
|
24943
|
+
function assignUniquePerFrame(candidates, gallery, opts) {
|
|
24944
|
+
const pairs = [];
|
|
24945
|
+
candidates.forEach((c, trackIdx) => {
|
|
24946
|
+
const probeVec = new Float32Array(c.embedding);
|
|
24947
|
+
const eligible = eligibleIdentities(gallery, c.modelId, c.embedding.length, opts.minIdentitySamples ?? 1);
|
|
24948
|
+
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
24949
|
+
for (const s of gallery) {
|
|
24950
|
+
if (s.modelId !== c.modelId) continue;
|
|
24951
|
+
if (s.embedding.length !== c.embedding.length) continue;
|
|
24952
|
+
if (!eligible.has(s.identityId)) continue;
|
|
24953
|
+
const score = cosineSimilarity$1(probeVec, new Float32Array(s.embedding));
|
|
24954
|
+
const prev = bestByIdentity.get(s.identityId);
|
|
24955
|
+
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
24956
|
+
}
|
|
24957
|
+
let bestId = null;
|
|
24958
|
+
let bestScore = -1;
|
|
24959
|
+
let secondScore = -1;
|
|
24960
|
+
for (const [identityId, score] of bestByIdentity) if (score > bestScore) {
|
|
24961
|
+
if (bestId !== null) secondScore = bestScore;
|
|
24962
|
+
bestId = identityId;
|
|
24963
|
+
bestScore = score;
|
|
24964
|
+
} else if (score > secondScore) secondScore = score;
|
|
24965
|
+
const ambiguous = secondScore >= 0 && bestScore - secondScore < opts.margin;
|
|
24966
|
+
if (bestId !== null && !ambiguous) {
|
|
24967
|
+
for (const [identityId, score] of bestByIdentity) if (score >= opts.threshold) pairs.push({
|
|
24968
|
+
trackIdx,
|
|
24969
|
+
identityId,
|
|
24970
|
+
score
|
|
24971
|
+
});
|
|
24972
|
+
}
|
|
24973
|
+
});
|
|
24974
|
+
pairs.sort((a, b) => b.score - a.score);
|
|
24975
|
+
const takenTracks = /* @__PURE__ */ new Set();
|
|
24976
|
+
const takenIdentities = /* @__PURE__ */ new Set();
|
|
24977
|
+
const result = /* @__PURE__ */ new Map();
|
|
24978
|
+
for (const p of pairs) {
|
|
24979
|
+
if (takenTracks.has(p.trackIdx) || takenIdentities.has(p.identityId)) continue;
|
|
24980
|
+
takenTracks.add(p.trackIdx);
|
|
24981
|
+
takenIdentities.add(p.identityId);
|
|
24982
|
+
result.set(candidates[p.trackIdx].trackId, {
|
|
24983
|
+
identityId: p.identityId,
|
|
24984
|
+
score: p.score
|
|
24985
|
+
});
|
|
24986
|
+
}
|
|
24987
|
+
return result;
|
|
24988
|
+
}
|
|
24989
|
+
var EMPTY_AGGREGATE = Object.freeze({
|
|
24990
|
+
assignedIdentityId: null,
|
|
24991
|
+
confirmations: Object.freeze({})
|
|
24992
|
+
});
|
|
24993
|
+
/**
|
|
24994
|
+
* Fold one frame's match into a track's aggregate. A null match leaves state
|
|
24995
|
+
* untouched. An identity is assigned once its cumulative confirmation count
|
|
24996
|
+
* reaches `confirmFrames`; once assigned, a different identity must independently
|
|
24997
|
+
* reach `confirmFrames` to switch. `changed` is true only on a (re)assignment to
|
|
24998
|
+
* a NEW identity, so callers fire the label update exactly once per transition.
|
|
24999
|
+
*
|
|
25000
|
+
* On each (re)assignment the confirmations record is reset to contain only the
|
|
25001
|
+
* newly-assigned identity's count, so any challenger must independently re-reach
|
|
25002
|
+
* `confirmFrames` from scratch after any assignment.
|
|
25003
|
+
*/
|
|
25004
|
+
function updateTrackAggregate(prev, match, opts) {
|
|
25005
|
+
if (match === null) return {
|
|
25006
|
+
state: prev,
|
|
25007
|
+
changed: false
|
|
25008
|
+
};
|
|
25009
|
+
const count = (prev.confirmations[match.identityId] ?? 0) + 1;
|
|
25010
|
+
const confirmations = {
|
|
25011
|
+
...prev.confirmations,
|
|
25012
|
+
[match.identityId]: count
|
|
25013
|
+
};
|
|
25014
|
+
if (count < opts.confirmFrames) return {
|
|
25015
|
+
state: {
|
|
25016
|
+
...prev,
|
|
25017
|
+
confirmations
|
|
25018
|
+
},
|
|
25019
|
+
changed: false
|
|
25020
|
+
};
|
|
25021
|
+
if (prev.assignedIdentityId === match.identityId) return {
|
|
25022
|
+
state: {
|
|
25023
|
+
...prev,
|
|
25024
|
+
confirmations
|
|
25025
|
+
},
|
|
25026
|
+
changed: false
|
|
25027
|
+
};
|
|
25028
|
+
return {
|
|
25029
|
+
state: {
|
|
25030
|
+
assignedIdentityId: match.identityId,
|
|
25031
|
+
confirmations: { [match.identityId]: count }
|
|
25032
|
+
},
|
|
25033
|
+
changed: true
|
|
25034
|
+
};
|
|
25035
|
+
}
|
|
25036
|
+
//#endregion
|
|
24837
25037
|
//#region src/pipeline-analytics/face-gallery-provider.ts
|
|
24838
25038
|
/** Attribution of a face identity NAMED BY THE OPERATOR in the gallery. */
|
|
24839
25039
|
var OPERATOR_FACE_STEP_ID = "operator:face-gallery";
|
|
@@ -25080,6 +25280,19 @@ var FaceGalleryProvider = class {
|
|
|
25080
25280
|
}
|
|
25081
25281
|
const currentFace = await this.faceStore.get(faceId);
|
|
25082
25282
|
if (!currentFace) throw new Error(`FaceGalleryProvider.assignFace: face disappeared after pre-cleanup: ${faceId}`);
|
|
25283
|
+
const verdict = enrollmentPromiscuity(currentFace.embedding, MODEL_ID, identityId, await this.identityStore.loadGallery());
|
|
25284
|
+
if (verdict !== null) {
|
|
25285
|
+
this.logger.warn("assignFace refused: embedding too close to a FOREIGN identity sample", {
|
|
25286
|
+
tags: { deviceId: currentFace.deviceId },
|
|
25287
|
+
meta: {
|
|
25288
|
+
faceId,
|
|
25289
|
+
identityId,
|
|
25290
|
+
foreignIdentityId: verdict.identityId,
|
|
25291
|
+
cosine: verdict.cosine
|
|
25292
|
+
}
|
|
25293
|
+
});
|
|
25294
|
+
throw new Error(`FaceGalleryProvider.assignFace: embedding is ambiguous — cosine ${verdict.cosine.toFixed(3)} with a sample of another identity (${verdict.identityId}); pick a clearer crop`);
|
|
25295
|
+
}
|
|
25083
25296
|
let newKey;
|
|
25084
25297
|
if (currentFace.mediaKey) {
|
|
25085
25298
|
newKey = await this.mediaStore.reown({
|
|
@@ -29988,6 +30201,22 @@ function stillInstantMs(ranges) {
|
|
|
29988
30201
|
if (best === void 0) return void 0;
|
|
29989
30202
|
return Math.floor((best.startMs + best.endMs) / 2);
|
|
29990
30203
|
}
|
|
30204
|
+
/**
|
|
30205
|
+
* The distinct object classes attached to a visit, most frequent first,
|
|
30206
|
+
* capped at {@link MAX_CLIP_LABELS}. Empty when nothing classified overlapped
|
|
30207
|
+
* — the caller then omits the field so a motion/audio-only visit keeps its
|
|
30208
|
+
* kind label instead of carrying an empty array every consumer length-checks.
|
|
30209
|
+
*/
|
|
30210
|
+
function dominantLabels(attached) {
|
|
30211
|
+
const counts = /* @__PURE__ */ new Map();
|
|
30212
|
+
for (const a of attached) {
|
|
30213
|
+
if (a.kind !== "object") continue;
|
|
30214
|
+
const name = a.event.className;
|
|
30215
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
30216
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
30217
|
+
}
|
|
30218
|
+
return [...counts.entries()].toSorted((x, y) => y[1] - x[1]).slice(0, 3).map(([name]) => name);
|
|
30219
|
+
}
|
|
29991
30220
|
function visitsToClips(visits, events, pad, vouched) {
|
|
29992
30221
|
const tagged = [
|
|
29993
30222
|
...events.object.map((event) => ({
|
|
@@ -30008,6 +30237,7 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
30008
30237
|
const kind = dominantKind(attached);
|
|
30009
30238
|
const withThumb = attached.find((a) => a.kind === kind && a.event.mediaUrl && vouched.has(a.event.id)) ?? attached.find((a) => a.event.mediaUrl && vouched.has(a.event.id));
|
|
30010
30239
|
const stillAtMs = stillInstantMs(visit.ranges);
|
|
30240
|
+
const labels = dominantLabels(attached);
|
|
30011
30241
|
return {
|
|
30012
30242
|
id: `analytics:${visit.startMs}:${visit.endMs}:${VISIT_EVENT_ID}`,
|
|
30013
30243
|
source: "analytics",
|
|
@@ -30020,7 +30250,8 @@ function visitsToClips(visits, events, pad, vouched) {
|
|
|
30020
30250
|
...stillAtMs !== void 0 ? { stillAtMs } : {},
|
|
30021
30251
|
eventIds: attached.slice(0, 24).map((a) => a.event.id),
|
|
30022
30252
|
eventCount: attached.length,
|
|
30023
|
-
holes: [...visit.holes]
|
|
30253
|
+
holes: [...visit.holes],
|
|
30254
|
+
...labels.length > 0 ? { labels } : {}
|
|
30024
30255
|
};
|
|
30025
30256
|
}).toSorted((a, b) => b.timeRange.startMs - a.timeRange.startMs);
|
|
30026
30257
|
}
|
|
@@ -33474,146 +33705,6 @@ async function resolveDefaultEventMedia(deps) {
|
|
|
33474
33705
|
return pickTrackFallbackMedia(await deps.listTrackMedia(trackId)) ?? null;
|
|
33475
33706
|
}
|
|
33476
33707
|
//#endregion
|
|
33477
|
-
//#region src/pipeline-analytics/pipeline/face-matcher.ts
|
|
33478
|
-
/**
|
|
33479
|
-
* Count enrolled samples per identity for probes of a matching model+dimension.
|
|
33480
|
-
* Only identities meeting `minIdentitySamples` are eligible auto-match targets.
|
|
33481
|
-
*/
|
|
33482
|
-
function eligibleIdentities(gallery, probeModelId, probeDim, minIdentitySamples) {
|
|
33483
|
-
const counts = /* @__PURE__ */ new Map();
|
|
33484
|
-
for (const s of gallery) {
|
|
33485
|
-
if (s.modelId !== probeModelId) continue;
|
|
33486
|
-
if (s.embedding.length !== probeDim) continue;
|
|
33487
|
-
counts.set(s.identityId, (counts.get(s.identityId) ?? 0) + 1);
|
|
33488
|
-
}
|
|
33489
|
-
const eligible = /* @__PURE__ */ new Set();
|
|
33490
|
-
for (const [id, count] of counts) if (count >= minIdentitySamples) eligible.add(id);
|
|
33491
|
-
return eligible;
|
|
33492
|
-
}
|
|
33493
|
-
/**
|
|
33494
|
-
* Match a probe embedding against the gallery. Only samples with the same
|
|
33495
|
-
* `modelId` AND the same dimension are compared (model-version safety). Returns
|
|
33496
|
-
* the best identity when its score ≥ threshold and it beats the best OTHER
|
|
33497
|
-
* identity by ≥ margin; otherwise null.
|
|
33498
|
-
*/
|
|
33499
|
-
function matchEmbedding(probe, gallery, opts) {
|
|
33500
|
-
const probeVec = new Float32Array(probe.embedding);
|
|
33501
|
-
const eligible = eligibleIdentities(gallery, probe.modelId, probe.embedding.length, opts.minIdentitySamples ?? 1);
|
|
33502
|
-
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
33503
|
-
for (const s of gallery) {
|
|
33504
|
-
if (s.modelId !== probe.modelId) continue;
|
|
33505
|
-
if (s.embedding.length !== probe.embedding.length) continue;
|
|
33506
|
-
if (!eligible.has(s.identityId)) continue;
|
|
33507
|
-
const score = cosineSimilarity$1(probeVec, new Float32Array(s.embedding));
|
|
33508
|
-
const prev = bestByIdentity.get(s.identityId);
|
|
33509
|
-
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
33510
|
-
}
|
|
33511
|
-
if (bestByIdentity.size === 0) return null;
|
|
33512
|
-
let bestId = null;
|
|
33513
|
-
let bestScore = -Infinity;
|
|
33514
|
-
let secondScore = -Infinity;
|
|
33515
|
-
for (const [id, score] of bestByIdentity) if (score > bestScore) {
|
|
33516
|
-
secondScore = bestScore;
|
|
33517
|
-
bestScore = score;
|
|
33518
|
-
bestId = id;
|
|
33519
|
-
} else if (score > secondScore) secondScore = score;
|
|
33520
|
-
if (bestId === null || bestScore < opts.threshold) return null;
|
|
33521
|
-
if (secondScore > -Infinity && bestScore - secondScore < opts.margin) return null;
|
|
33522
|
-
return {
|
|
33523
|
-
identityId: bestId,
|
|
33524
|
-
score: bestScore
|
|
33525
|
-
};
|
|
33526
|
-
}
|
|
33527
|
-
/**
|
|
33528
|
-
* Assign at most one identity per track AND at most one track per identity for
|
|
33529
|
-
* a single frame. Greedy by score: compute every candidate's full ranked match
|
|
33530
|
-
* list, then repeatedly take the globally-highest (track, identity) pair whose
|
|
33531
|
-
* track and identity are both still free, until none qualify. A losing track
|
|
33532
|
-
* falls back to its next-best free identity ≥ threshold. Ties resolve by input order.
|
|
33533
|
-
*/
|
|
33534
|
-
function assignUniquePerFrame(candidates, gallery, opts) {
|
|
33535
|
-
const pairs = [];
|
|
33536
|
-
candidates.forEach((c, trackIdx) => {
|
|
33537
|
-
const probeVec = new Float32Array(c.embedding);
|
|
33538
|
-
const eligible = eligibleIdentities(gallery, c.modelId, c.embedding.length, opts.minIdentitySamples ?? 1);
|
|
33539
|
-
const bestByIdentity = /* @__PURE__ */ new Map();
|
|
33540
|
-
for (const s of gallery) {
|
|
33541
|
-
if (s.modelId !== c.modelId) continue;
|
|
33542
|
-
if (s.embedding.length !== c.embedding.length) continue;
|
|
33543
|
-
if (!eligible.has(s.identityId)) continue;
|
|
33544
|
-
const score = cosineSimilarity$1(probeVec, new Float32Array(s.embedding));
|
|
33545
|
-
const prev = bestByIdentity.get(s.identityId);
|
|
33546
|
-
if (prev === void 0 || score > prev) bestByIdentity.set(s.identityId, score);
|
|
33547
|
-
}
|
|
33548
|
-
for (const [identityId, score] of bestByIdentity) if (score >= opts.threshold) pairs.push({
|
|
33549
|
-
trackIdx,
|
|
33550
|
-
identityId,
|
|
33551
|
-
score
|
|
33552
|
-
});
|
|
33553
|
-
});
|
|
33554
|
-
pairs.sort((a, b) => b.score - a.score);
|
|
33555
|
-
const takenTracks = /* @__PURE__ */ new Set();
|
|
33556
|
-
const takenIdentities = /* @__PURE__ */ new Set();
|
|
33557
|
-
const result = /* @__PURE__ */ new Map();
|
|
33558
|
-
for (const p of pairs) {
|
|
33559
|
-
if (takenTracks.has(p.trackIdx) || takenIdentities.has(p.identityId)) continue;
|
|
33560
|
-
takenTracks.add(p.trackIdx);
|
|
33561
|
-
takenIdentities.add(p.identityId);
|
|
33562
|
-
result.set(candidates[p.trackIdx].trackId, {
|
|
33563
|
-
identityId: p.identityId,
|
|
33564
|
-
score: p.score
|
|
33565
|
-
});
|
|
33566
|
-
}
|
|
33567
|
-
return result;
|
|
33568
|
-
}
|
|
33569
|
-
var EMPTY_AGGREGATE = Object.freeze({
|
|
33570
|
-
assignedIdentityId: null,
|
|
33571
|
-
confirmations: Object.freeze({})
|
|
33572
|
-
});
|
|
33573
|
-
/**
|
|
33574
|
-
* Fold one frame's match into a track's aggregate. A null match leaves state
|
|
33575
|
-
* untouched. An identity is assigned once its cumulative confirmation count
|
|
33576
|
-
* reaches `confirmFrames`; once assigned, a different identity must independently
|
|
33577
|
-
* reach `confirmFrames` to switch. `changed` is true only on a (re)assignment to
|
|
33578
|
-
* a NEW identity, so callers fire the label update exactly once per transition.
|
|
33579
|
-
*
|
|
33580
|
-
* On each (re)assignment the confirmations record is reset to contain only the
|
|
33581
|
-
* newly-assigned identity's count, so any challenger must independently re-reach
|
|
33582
|
-
* `confirmFrames` from scratch after any assignment.
|
|
33583
|
-
*/
|
|
33584
|
-
function updateTrackAggregate(prev, match, opts) {
|
|
33585
|
-
if (match === null) return {
|
|
33586
|
-
state: prev,
|
|
33587
|
-
changed: false
|
|
33588
|
-
};
|
|
33589
|
-
const count = (prev.confirmations[match.identityId] ?? 0) + 1;
|
|
33590
|
-
const confirmations = {
|
|
33591
|
-
...prev.confirmations,
|
|
33592
|
-
[match.identityId]: count
|
|
33593
|
-
};
|
|
33594
|
-
if (count < opts.confirmFrames) return {
|
|
33595
|
-
state: {
|
|
33596
|
-
...prev,
|
|
33597
|
-
confirmations
|
|
33598
|
-
},
|
|
33599
|
-
changed: false
|
|
33600
|
-
};
|
|
33601
|
-
if (prev.assignedIdentityId === match.identityId) return {
|
|
33602
|
-
state: {
|
|
33603
|
-
...prev,
|
|
33604
|
-
confirmations
|
|
33605
|
-
},
|
|
33606
|
-
changed: false
|
|
33607
|
-
};
|
|
33608
|
-
return {
|
|
33609
|
-
state: {
|
|
33610
|
-
assignedIdentityId: match.identityId,
|
|
33611
|
-
confirmations: { [match.identityId]: count }
|
|
33612
|
-
},
|
|
33613
|
-
changed: true
|
|
33614
|
-
};
|
|
33615
|
-
}
|
|
33616
|
-
//#endregion
|
|
33617
33708
|
//#region src/pipeline-analytics/face-recognizer.ts
|
|
33618
33709
|
/** Attribution of an identity resolved by the in-process ArcFace matcher. */
|
|
33619
33710
|
var FACE_RECOGNIZER_STEP_ID = "face-recognizer";
|
|
@@ -52318,6 +52409,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
52318
52409
|
},
|
|
52319
52410
|
renderEventMedia: (req) => this.renderEventMedia(req),
|
|
52320
52411
|
publishArtifact: async (bytes, mime) => await this.ncArtifactPlane?.publish(bytes, mime) ?? null,
|
|
52412
|
+
viewerUrlBase: async () => pickArtifactBaseUrl(await this.ncEndpoints()),
|
|
52321
52413
|
indexArtifact: async (bytes, mime, meta) => await this.ncArtifactPlane?.publishIndexed(bytes, mime, meta) ?? {
|
|
52322
52414
|
url: null,
|
|
52323
52415
|
artifactId: null
|
|
@@ -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-CeFPqm3h.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.95",
|
|
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",
|