@camstack/addon-post-analysis 1.2.128 → 1.2.130
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-CRDS5ykk.mjs → dist-CBCs0-eP.mjs} +2425 -2247
- package/dist/{dist-D6O14xf_.js → dist-CDHL-0gR.js} +2425 -2247
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BZm5TUIO.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CMk3S5nA.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-UGfPXz4L.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Doc16Tif.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-CQz_Ev1M.mjs → hostInit-B6sMoPF2.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +273 -32
- package/dist/pipeline-analytics/index.mjs +273 -32
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as customAction, A as NcTaxonomySchema, At as sleep, B as TIMELAPSE_DENSE_FLOOR_SEC, Bt as string, C as NcRuleSchema, Ct as errMsg, D as NcSnoozeSchema, Dt as hydrateSchema, E as NcSnoozeInputSchema, Et as createEvent, Ft as literal, G as VISIT_MERGE_GAP_MS, H as TimelapseRulePatchSchema, Ht as EventCategory, I as SCENE_DEFAULT_ANCHOR_THRESHOLD, It as number, J as assertTimelapseCadences, K as addonWidgetsSourceCapability, L as SCENE_DEFAULT_UNCOVERED_POLICY, Lt as object, Mt as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nt as boolean, O as NcSnoozeSuppressedSchema, Ot as isDeviceScopedCap, P as RetrainStatusSchema, Pt as discriminatedUnion, Q as cosineSimilarity$1, R as SCENE_DIVERGED, Rt as partialRecord, S as NcRulePatchSchema, St as zoneAnalyticsCapability, T as NcScheduleSchema, Tt as DeviceType, U as TimelapseRuleSchema, V as TimelapseRuleInputSchema, Vt as unknown, W as TrackSourceSchema, X as audioModeOf, Y as audioMetricsCapability, Z as buildEventKindDescriptor, _ as NC_DEFAULT_SNOOZE_MINUTES, _t as sceneMonitorCapability, a as DEFAULT_EVENT_COLOR, b as NcConditionDescriptorSchema, bt as vectorDimFromBase64, ct as kebabToCamel, d as EVENT_PAD_MS, dt as pickClusterStepModels, et as defineCustomActions, f as FULL_IMAGE_BBOX, ft as pipelineAnalyticsCapability, g as NC_CONDITION_CATALOG, h as NC_ALARM_SYSTEM_EVENT_KINDS, ht as readTimelapseGeneratedAt, i as CLUSTER_MODEL_SCOPED_STEPS, it as faceGalleryCapability, j as OpsLogEntrySchema, jt as _enum, k as NcSystemEventKindSchema, kt as nodePin, l as DeclaredDevices, lt as notificationRulesCapability, m as MACRO_LABELS, mt as readDeviceStateFrom, n as AnalyticsGroupRecordSchema, ot as isDetectionMacroClass, p as LabelAttributionSchema, pt as plateGalleryCapability, q as alarmPanelCapability, r as BaseDevice, rt as encodeVectorBase64, s as DETECTION_MACRO_CLASSES, st as isScheduleActive, t as AnalyticsGroupDetailSchema, tt as deriveRecordingMode, u as EVENT_KIND_BY_CAP, vt as subKindsOf, w as NcRuleTargetSchema, wt as BaseAddon, x as NcRuleInputSchema, xt as videoclipsCapability, y as NC_TAXONOMY, yt as systemEventFilterApplies, z as SceneMonitorSchema, zt as record } from "../dist-
|
|
1
|
+
import { $ as customAction, A as NcTaxonomySchema, At as sleep, B as TIMELAPSE_DENSE_FLOOR_SEC, Bt as string, C as NcRuleSchema, Ct as errMsg, D as NcSnoozeSchema, Dt as hydrateSchema, E as NcSnoozeInputSchema, Et as createEvent, Ft as literal, G as VISIT_MERGE_GAP_MS, H as TimelapseRulePatchSchema, Ht as EventCategory, I as SCENE_DEFAULT_ANCHOR_THRESHOLD, It as number, J as assertTimelapseCadences, K as addonWidgetsSourceCapability, L as SCENE_DEFAULT_UNCOVERED_POLICY, Lt as object, Mt as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nt as boolean, O as NcSnoozeSuppressedSchema, Ot as isDeviceScopedCap, P as RetrainStatusSchema, Pt as discriminatedUnion, Q as cosineSimilarity$1, R as SCENE_DIVERGED, Rt as partialRecord, S as NcRulePatchSchema, St as zoneAnalyticsCapability, T as NcScheduleSchema, Tt as DeviceType, U as TimelapseRuleSchema, V as TimelapseRuleInputSchema, Vt as unknown, W as TrackSourceSchema, X as audioModeOf, Y as audioMetricsCapability, Z as buildEventKindDescriptor, _ as NC_DEFAULT_SNOOZE_MINUTES, _t as sceneMonitorCapability, a as DEFAULT_EVENT_COLOR, b as NcConditionDescriptorSchema, bt as vectorDimFromBase64, ct as kebabToCamel, d as EVENT_PAD_MS, dt as pickClusterStepModels, et as defineCustomActions, f as FULL_IMAGE_BBOX, ft as pipelineAnalyticsCapability, g as NC_CONDITION_CATALOG, h as NC_ALARM_SYSTEM_EVENT_KINDS, ht as readTimelapseGeneratedAt, i as CLUSTER_MODEL_SCOPED_STEPS, it as faceGalleryCapability, j as OpsLogEntrySchema, jt as _enum, k as NcSystemEventKindSchema, kt as nodePin, l as DeclaredDevices, lt as notificationRulesCapability, m as MACRO_LABELS, mt as readDeviceStateFrom, n as AnalyticsGroupRecordSchema, ot as isDetectionMacroClass, p as LabelAttributionSchema, pt as plateGalleryCapability, q as alarmPanelCapability, r as BaseDevice, rt as encodeVectorBase64, s as DETECTION_MACRO_CLASSES, st as isScheduleActive, t as AnalyticsGroupDetailSchema, tt as deriveRecordingMode, u as EVENT_KIND_BY_CAP, vt as subKindsOf, w as NcRuleTargetSchema, wt as BaseAddon, x as NcRuleInputSchema, xt as videoclipsCapability, y as NC_TAXONOMY, yt as systemEventFilterApplies, z as SceneMonitorSchema, zt as record } from "../dist-CBCs0-eP.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";
|
|
@@ -2439,6 +2439,45 @@ var NcArtifactPlane = class {
|
|
|
2439
2439
|
}
|
|
2440
2440
|
}
|
|
2441
2441
|
/**
|
|
2442
|
+
* Mint a FRESH signed URL for an artefact already sitting on the shelf —
|
|
2443
|
+
* no bytes read, no re-store. The other half of {@link publishIndexed}: that
|
|
2444
|
+
* one stores AND mints once at publish time; this one re-mints on demand for
|
|
2445
|
+
* an id a caller already holds (a history row's `artifactIds`), because the
|
|
2446
|
+
* link `publishIndexed` returned has its own TTL and a caller reaching for
|
|
2447
|
+
* the artefact hours later needs a new one.
|
|
2448
|
+
*
|
|
2449
|
+
* `null` on any of three causes, deliberately indistinguishable to the
|
|
2450
|
+
* caller: no index is wired (nothing to resolve against), the id is unknown
|
|
2451
|
+
* or its artefact has expired past the retained shelf's own age bound, or no
|
|
2452
|
+
* externally-reachable base URL exists for this install. A caller (the Home
|
|
2453
|
+
* Assistant export) must render all three as "no image right now" — never as
|
|
2454
|
+
* a broken link, and never by reusing a URL this call refused to mint.
|
|
2455
|
+
*/
|
|
2456
|
+
async mintUrl(artifactId) {
|
|
2457
|
+
const index = this.deps.index;
|
|
2458
|
+
if (index === void 0) return null;
|
|
2459
|
+
try {
|
|
2460
|
+
const record = await index.resolve(artifactId);
|
|
2461
|
+
if (record === null) return null;
|
|
2462
|
+
if (record.expiresAt <= this.now()) return null;
|
|
2463
|
+
const baseUrl = await this.resolveBaseUrl();
|
|
2464
|
+
if (baseUrl === null) return null;
|
|
2465
|
+
return buildArtifactUrl({
|
|
2466
|
+
baseUrl,
|
|
2467
|
+
routePrefix: this.deps.routePrefix,
|
|
2468
|
+
id: artifactId,
|
|
2469
|
+
secret: this.deps.secret,
|
|
2470
|
+
expMs: this.now() + (this.deps.ttlMs ?? DEFAULT_TTL_MS)
|
|
2471
|
+
});
|
|
2472
|
+
} catch (err) {
|
|
2473
|
+
this.deps.logger.debug("artifact url mint failed", { meta: {
|
|
2474
|
+
artifactId,
|
|
2475
|
+
error: String(err)
|
|
2476
|
+
} });
|
|
2477
|
+
return null;
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2442
2481
|
* Absolutize a path against the SAME base URL an artifact link would use.
|
|
2443
2482
|
*
|
|
2444
2483
|
* For links this plane does not host but a RECIPIENT still has to open — a
|
|
@@ -8925,7 +8964,7 @@ var NcDispatcher = class {
|
|
|
8925
8964
|
return { ok: true };
|
|
8926
8965
|
}
|
|
8927
8966
|
}
|
|
8928
|
-
const notification = await this.buildNotification(entry, target);
|
|
8967
|
+
const { notification, artifactIds } = await this.buildNotification(entry, target);
|
|
8929
8968
|
const verdict = await this.runConfirmGate(entry, notification);
|
|
8930
8969
|
if (verdict !== null && verdict.decision === "suppressed") {
|
|
8931
8970
|
this.deps.logger.info("notification suppressed by AI confirm", {
|
|
@@ -8983,10 +9022,11 @@ var NcDispatcher = class {
|
|
|
8983
9022
|
...dropped.length > 0 ? { dropped } : {}
|
|
8984
9023
|
}
|
|
8985
9024
|
});
|
|
8986
|
-
return
|
|
9025
|
+
return {
|
|
8987
9026
|
ok: true,
|
|
8988
|
-
confirm: verdict
|
|
8989
|
-
|
|
9027
|
+
...verdict !== null ? { confirm: verdict } : {},
|
|
9028
|
+
...artifactIds.length > 0 ? { artifactIds } : {}
|
|
9029
|
+
};
|
|
8990
9030
|
} catch (err) {
|
|
8991
9031
|
return {
|
|
8992
9032
|
ok: false,
|
|
@@ -9297,6 +9337,12 @@ var NcDispatcher = class {
|
|
|
9297
9337
|
});
|
|
9298
9338
|
return input.frozen ?? "";
|
|
9299
9339
|
}
|
|
9340
|
+
/**
|
|
9341
|
+
* Builds the notification AND returns the artefact ids its media resolve
|
|
9342
|
+
* indexed — see {@link withArtifactUrls}. A tuple rather than a field on
|
|
9343
|
+
* `NcNotificationInput`: the ids are internal (outbox provenance), never
|
|
9344
|
+
* part of what `notification-output` sends to a backend.
|
|
9345
|
+
*/
|
|
9300
9346
|
async buildNotification(entry, target) {
|
|
9301
9347
|
const subject = entry.payload.subject;
|
|
9302
9348
|
const systemEvent = subject.systemEvent;
|
|
@@ -9325,7 +9371,8 @@ var NcDispatcher = class {
|
|
|
9325
9371
|
vars: { body: rendered },
|
|
9326
9372
|
frozen: rendered
|
|
9327
9373
|
}) : rendered;
|
|
9328
|
-
const
|
|
9374
|
+
const resolvedMedia = await this.withArtifactUrls(entry, await this.resolveAttachments(entry));
|
|
9375
|
+
const attachments = [...resolvedMedia.attachments, ...frozenArtifactAttachments(entry)];
|
|
9329
9376
|
const params = pickParams(entry.payload.params);
|
|
9330
9377
|
const tapUrl = params.clickUrl === void 0 && this.deps.viewerUrlBase !== void 0 ? defaultTapUrl({
|
|
9331
9378
|
deviceId: systemEvent !== void 0 ? 0 : subject.deviceId,
|
|
@@ -9335,21 +9382,24 @@ var NcDispatcher = class {
|
|
|
9335
9382
|
}) : null;
|
|
9336
9383
|
const actions = await this.mintActions(entry, target);
|
|
9337
9384
|
return {
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9385
|
+
notification: {
|
|
9386
|
+
body,
|
|
9387
|
+
title,
|
|
9388
|
+
priority: clampPriority(paramNumber(entry.payload.params, "priority") ?? entry.payload.priority),
|
|
9389
|
+
...params,
|
|
9390
|
+
...tapUrl !== null ? { clickUrl: tapUrl } : {},
|
|
9391
|
+
tag: entry.payload.group?.key ?? entry.ruleId,
|
|
9392
|
+
...systemEvent !== void 0 ? systemEvent.deviceId !== void 0 ? { deviceId: systemEvent.deviceId } : {} : { deviceId: subject.deviceId },
|
|
9393
|
+
...subject.eventId !== void 0 ? { eventId: subject.eventId } : {},
|
|
9394
|
+
...systemEvent !== void 0 ? { metadata: {
|
|
9395
|
+
systemEventKind: systemEvent.kind,
|
|
9396
|
+
subject: systemEvent.subject,
|
|
9397
|
+
...systemEvent.data
|
|
9398
|
+
} } : {},
|
|
9399
|
+
...attachments.length > 0 ? { attachments } : {},
|
|
9400
|
+
...actions.length > 0 ? { actions } : {}
|
|
9401
|
+
},
|
|
9402
|
+
artifactIds: resolvedMedia.artifactIds
|
|
9353
9403
|
};
|
|
9354
9404
|
}
|
|
9355
9405
|
/**
|
|
@@ -9393,11 +9443,28 @@ var NcDispatcher = class {
|
|
|
9393
9443
|
}
|
|
9394
9444
|
});
|
|
9395
9445
|
}
|
|
9446
|
+
/**
|
|
9447
|
+
* What one delivery's media resolve produced: the attachments ready to ship
|
|
9448
|
+
* (bytes plus a signed url when the plane could mint one) and the ids the
|
|
9449
|
+
* artefact INDEX assigned them, in the same order — still first, then gif,
|
|
9450
|
+
* then clip. The ids never ride on the attachment objects themselves: those
|
|
9451
|
+
* cross to `notification-output` and a notifier backend has no use for an
|
|
9452
|
+
* internal store id, only for `url`/`bytes`.
|
|
9453
|
+
*
|
|
9454
|
+
* `artifactIds` is what lets a delivery's outbox row later answer "which
|
|
9455
|
+
* artefact was this trigger's picture" (`getHistory.artifactIds`) without a
|
|
9456
|
+
* second write path — the outbox writes back exactly the ids this method
|
|
9457
|
+
* handed to a SUCCESSFUL send, never re-deriving them.
|
|
9458
|
+
*/
|
|
9396
9459
|
async withArtifactUrls(entry, attachments) {
|
|
9397
9460
|
const index = this.deps.indexArtifact;
|
|
9398
9461
|
const publish = this.deps.publishArtifact;
|
|
9399
|
-
if (index === void 0 && publish === void 0) return
|
|
9462
|
+
if (index === void 0 && publish === void 0) return {
|
|
9463
|
+
attachments: [...attachments],
|
|
9464
|
+
artifactIds: []
|
|
9465
|
+
};
|
|
9400
9466
|
const out = [];
|
|
9467
|
+
const artifactIds = [];
|
|
9401
9468
|
let posterId = null;
|
|
9402
9469
|
const deviceId = entry.payload.subject.deviceId;
|
|
9403
9470
|
const common = {
|
|
@@ -9420,6 +9487,7 @@ var NcDispatcher = class {
|
|
|
9420
9487
|
artifactId: null
|
|
9421
9488
|
}));
|
|
9422
9489
|
if (kind === "event-still" && pub.artifactId !== null) posterId = pub.artifactId;
|
|
9490
|
+
if (pub.artifactId !== null) artifactIds.push(pub.artifactId);
|
|
9423
9491
|
out.push(pub.url === null ? { ...att } : {
|
|
9424
9492
|
...att,
|
|
9425
9493
|
url: pub.url
|
|
@@ -9432,7 +9500,10 @@ var NcDispatcher = class {
|
|
|
9432
9500
|
url
|
|
9433
9501
|
});
|
|
9434
9502
|
}
|
|
9435
|
-
return
|
|
9503
|
+
return {
|
|
9504
|
+
attachments: out,
|
|
9505
|
+
artifactIds
|
|
9506
|
+
};
|
|
9436
9507
|
}
|
|
9437
9508
|
/** The full attachment list: the policy still (zone-cropped when the rule
|
|
9438
9509
|
* froze zone ids) plus the footage cut from the broker's clip ring when
|
|
@@ -12536,13 +12607,17 @@ var NcOutbox = class {
|
|
|
12536
12607
|
}
|
|
12537
12608
|
const now = this.now();
|
|
12538
12609
|
if (result.ok) {
|
|
12610
|
+
const payloadPatch = {
|
|
12611
|
+
...result.confirm !== void 0 ? { confirmVerdict: result.confirm } : {},
|
|
12612
|
+
...result.artifactIds !== void 0 && result.artifactIds.length > 0 ? { artifactIds: result.artifactIds } : {}
|
|
12613
|
+
};
|
|
12539
12614
|
const sent = {
|
|
12540
12615
|
...entry,
|
|
12541
12616
|
status: "sent",
|
|
12542
12617
|
updatedAt: now,
|
|
12543
|
-
...
|
|
12618
|
+
...Object.keys(payloadPatch).length > 0 ? { payload: {
|
|
12544
12619
|
...entry.payload,
|
|
12545
|
-
|
|
12620
|
+
...payloadPatch
|
|
12546
12621
|
} } : {}
|
|
12547
12622
|
};
|
|
12548
12623
|
this.pending.delete(entry.id);
|
|
@@ -19561,7 +19636,8 @@ function outboxEntryToHistory(texts, entry) {
|
|
|
19561
19636
|
zones: [...s.zones],
|
|
19562
19637
|
timestamp: s.timestamp,
|
|
19563
19638
|
...s.systemEvent !== void 0 ? { systemEvent: historySystemEvent(texts, s.systemEvent) } : {}
|
|
19564
|
-
}
|
|
19639
|
+
},
|
|
19640
|
+
...entry.payload.artifactIds !== void 0 && entry.payload.artifactIds.length > 0 ? { artifactIds: [...entry.payload.artifactIds] } : {}
|
|
19565
19641
|
};
|
|
19566
19642
|
}
|
|
19567
19643
|
/** The watched question in one grep-able token. */
|
|
@@ -21100,6 +21176,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
21100
21176
|
}
|
|
21101
21177
|
return { entries: mapped };
|
|
21102
21178
|
},
|
|
21179
|
+
resolveArtifactUrl: async ({ artifactId }) => ({ url: await this.deps.resolveArtifactUrl?.(artifactId) ?? null }),
|
|
21103
21180
|
listDeviceMutes: async () => ({ mutedDeviceIds: this.deviceMutes.listMuted() }),
|
|
21104
21181
|
setDeviceMuted: async ({ deviceId, muted }) => {
|
|
21105
21182
|
await this.deviceMutes.setMuted(deviceId, muted);
|
|
@@ -27195,6 +27272,18 @@ var FaceGalleryProvider = class {
|
|
|
27195
27272
|
* This module names that state (`model-mismatch`) so the UI can say "this face
|
|
27196
27273
|
* is in an old feature space" instead of showing 0% and letting the operator
|
|
27197
27274
|
* conclude the person is not there.
|
|
27275
|
+
*
|
|
27276
|
+
* ## `buildFaceRescoreReport` stays pure — the CALLER persists
|
|
27277
|
+
*
|
|
27278
|
+
* This function never touches the store; every input is passed in and nothing
|
|
27279
|
+
* is read or written (see its own doc below). The operator asked for this
|
|
27280
|
+
* exact recompute, though, and wants the answer to stick — so the ACTION that
|
|
27281
|
+
* wraps this report (`rescoreFaceForTrack` in `pipeline-analytics/index.ts`)
|
|
27282
|
+
* writes the result onto `Face.suggestedIdentityId` / `suggestedMatchScore`,
|
|
27283
|
+
* through `decideRescorePersistence` / `persistRescoreResult` below. That is
|
|
27284
|
+
* still not a background job — it is the SAME explicitly-requested call,
|
|
27285
|
+
* writing once. A `model-mismatch` report (`comparable: false`) is never
|
|
27286
|
+
* written: see `persistRescoreResult`.
|
|
27198
27287
|
*/
|
|
27199
27288
|
/** The empty report every "cannot score" path returns, so shape never varies. */
|
|
27200
27289
|
function emptyReport(verdict, input, comparable) {
|
|
@@ -27270,6 +27359,52 @@ function buildFaceRescoreReport(input) {
|
|
|
27270
27359
|
...best !== void 0 && second !== void 0 ? { runnerUpGap: best.score - second.score } : {}
|
|
27271
27360
|
};
|
|
27272
27361
|
}
|
|
27362
|
+
/**
|
|
27363
|
+
* What `face.rescoreTrack` should PERSIST for a COMPARABLE report, decided as
|
|
27364
|
+
* its own pure function so the rule is directly testable — separated from the
|
|
27365
|
+
* class that owns the store I/O (`rescoreFaceForTrack` in
|
|
27366
|
+
* `pipeline-analytics/index.ts`), which only wires this answer to
|
|
27367
|
+
* `FaceStore.setSuggestion`.
|
|
27368
|
+
*
|
|
27369
|
+
* `null` covers every verdict OTHER than `auto-assignable` / `suggestion`, and
|
|
27370
|
+
* also an `auto-assignable` / `suggestion` verdict whose best-ranked row is
|
|
27371
|
+
* not itself ELIGIBLE (`identities` is ranked by raw score across every
|
|
27372
|
+
* comparable identity, eligible or not — see `buildFaceRescoreReport`). Every
|
|
27373
|
+
* one of those is a genuine "nothing to suggest right now": never an
|
|
27374
|
+
* assignment, and never the winner of an `ambiguous` verdict — the matcher
|
|
27375
|
+
* refused to pick one, so this function does not pick one either.
|
|
27376
|
+
*
|
|
27377
|
+
* Callers MUST gate on `report.comparable` before calling this — a
|
|
27378
|
+
* `model-mismatch` report has no verdict worth acting on; see
|
|
27379
|
+
* {@link persistRescoreResult}, which does that gating and is the one thing
|
|
27380
|
+
* `rescoreFaceForTrack` actually calls.
|
|
27381
|
+
*/
|
|
27382
|
+
function decideRescorePersistence(report) {
|
|
27383
|
+
if (report.verdict !== "auto-assignable" && report.verdict !== "suggestion") return null;
|
|
27384
|
+
const candidate = report.identities.find((row) => row.eligible);
|
|
27385
|
+
return candidate === void 0 ? null : {
|
|
27386
|
+
identityId: candidate.identityId,
|
|
27387
|
+
score: candidate.score
|
|
27388
|
+
};
|
|
27389
|
+
}
|
|
27390
|
+
/**
|
|
27391
|
+
* Persist `face.rescoreTrack`'s result onto the SUGGESTED tier — the ONE
|
|
27392
|
+
* write this explicitly-requested action makes, and the only place that
|
|
27393
|
+
* decides whether it happens at all.
|
|
27394
|
+
*
|
|
27395
|
+
* A non-comparable report (`model-mismatch`) short-circuits BEFORE
|
|
27396
|
+
* `ports.setSuggestion` is ever called: a cosine across model spaces is
|
|
27397
|
+
* well-formed noise, and persisting it would look exactly like a fresh,
|
|
27398
|
+
* meaningful read to every future reader of the row. This is the single most
|
|
27399
|
+
* load-bearing branch in this file — the regression it guards against writes
|
|
27400
|
+
* garbage that reads as data.
|
|
27401
|
+
*/
|
|
27402
|
+
async function persistRescoreResult(faceId, report, ports) {
|
|
27403
|
+
if (!report.comparable) return "not-comparable";
|
|
27404
|
+
const candidate = decideRescorePersistence(report);
|
|
27405
|
+
await ports.setSuggestion(faceId, candidate);
|
|
27406
|
+
return candidate !== null ? "written" : "cleared";
|
|
27407
|
+
}
|
|
27273
27408
|
//#endregion
|
|
27274
27409
|
//#region src/pipeline-analytics/face-rescore-actions.ts
|
|
27275
27410
|
/**
|
|
@@ -27355,9 +27490,12 @@ var FaceReembedReportSchema = object({
|
|
|
27355
27490
|
});
|
|
27356
27491
|
var faceRescoreActions = defineCustomActions({
|
|
27357
27492
|
/**
|
|
27358
|
-
*
|
|
27359
|
-
*
|
|
27360
|
-
*
|
|
27493
|
+
* Persists its own result onto the SUGGESTED tier (`suggestedIdentityId` /
|
|
27494
|
+
* `suggestedMatchScore`) — never an assignment, never automatic; see
|
|
27495
|
+
* `rescoreFaceForTrack` in `pipeline-analytics/index.ts` and
|
|
27496
|
+
* `persistRescoreResult` in `face-rescore.ts`. `admin` because the report
|
|
27497
|
+
* also names every enrolled person and how well an arbitrary face matches
|
|
27498
|
+
* them — the gallery's shape, which is not a viewer-level fact.
|
|
27361
27499
|
*/
|
|
27362
27500
|
"face.rescoreTrack": customAction(object({
|
|
27363
27501
|
deviceId: number().int(),
|
|
@@ -27375,8 +27513,10 @@ var faceRescoreActions = defineCustomActions({
|
|
|
27375
27513
|
/** The face's current assignment, so the UI can mark "already this person". */
|
|
27376
27514
|
assigned: boolean(),
|
|
27377
27515
|
assignedIdentityId: string().optional(),
|
|
27378
|
-
/** What the track has been SHOWING until now —
|
|
27379
|
-
*
|
|
27516
|
+
/** What the track has been SHOWING until now — read BEFORE this call
|
|
27517
|
+
* writes, so it is whatever a previous track close or a previous
|
|
27518
|
+
* `face.rescoreTrack` last persisted. Returned so the UI can put the stale
|
|
27519
|
+
* number beside the fresh one. */
|
|
27380
27520
|
storedSuggestedIdentityId: string().optional(),
|
|
27381
27521
|
storedSuggestedMatchScore: number().optional(),
|
|
27382
27522
|
verdict: FaceRescoreVerdictSchema,
|
|
@@ -29383,6 +29523,51 @@ var FrameProcessor = class {
|
|
|
29383
29523
|
}
|
|
29384
29524
|
};
|
|
29385
29525
|
//#endregion
|
|
29526
|
+
//#region src/pipeline-analytics/replay/frame-processor-pass.ts
|
|
29527
|
+
/**
|
|
29528
|
+
* Run a FRESH `FrameProcessor` over a fixed, ordered sequence of frames and
|
|
29529
|
+
* return the tracks it produced.
|
|
29530
|
+
*
|
|
29531
|
+
* No `TrackStore`, no bus, no media — a track here is a summary built
|
|
29532
|
+
* ENTIRELY from `FrameProcessor.process()`'s in-memory return value.
|
|
29533
|
+
*/
|
|
29534
|
+
function runFrameProcessorPass(input) {
|
|
29535
|
+
const processor = new FrameProcessor(input.deviceId, {}, {}, {}, input.source);
|
|
29536
|
+
if (input.zones !== void 0) processor.setZones(input.zones);
|
|
29537
|
+
if (input.detectionRules !== void 0) processor.setDetectionRules(input.detectionRules);
|
|
29538
|
+
if (input.zoneMembershipMinOverlap !== void 0) processor.setZoneMembershipMinOverlap(input.zoneMembershipMinOverlap);
|
|
29539
|
+
const accum = /* @__PURE__ */ new Map();
|
|
29540
|
+
for (const { timestamp, frame } of input.frames) {
|
|
29541
|
+
const result = processor.process({
|
|
29542
|
+
timestamp,
|
|
29543
|
+
frame
|
|
29544
|
+
});
|
|
29545
|
+
for (const td of result.tracked) {
|
|
29546
|
+
const existing = accum.get(td.trackId);
|
|
29547
|
+
const matched = td.matchedThisFrame ? 1 : 0;
|
|
29548
|
+
if (existing === void 0) accum.set(td.trackId, {
|
|
29549
|
+
className: td.className,
|
|
29550
|
+
firstSeenMs: timestamp,
|
|
29551
|
+
lastSeenMs: timestamp,
|
|
29552
|
+
bbox: { ...td.bbox },
|
|
29553
|
+
framesMatched: matched
|
|
29554
|
+
});
|
|
29555
|
+
else {
|
|
29556
|
+
existing.className = td.className;
|
|
29557
|
+
existing.lastSeenMs = timestamp;
|
|
29558
|
+
existing.framesMatched += matched;
|
|
29559
|
+
}
|
|
29560
|
+
}
|
|
29561
|
+
}
|
|
29562
|
+
return { tracks: [...accum.values()].map((t) => ({
|
|
29563
|
+
className: t.className,
|
|
29564
|
+
firstSeenMs: t.firstSeenMs,
|
|
29565
|
+
lastSeenMs: t.lastSeenMs,
|
|
29566
|
+
bbox: t.bbox,
|
|
29567
|
+
framesMatched: t.framesMatched
|
|
29568
|
+
})) };
|
|
29569
|
+
}
|
|
29570
|
+
//#endregion
|
|
29386
29571
|
//#region src/pipeline-analytics/pipeline/semantic-detail-bbox.ts
|
|
29387
29572
|
/** Which box the semantic best-shot should crop: a plate/face DETAIL box
|
|
29388
29573
|
* when we have one, otherwise the subject box. */
|
|
@@ -47566,6 +47751,37 @@ var FaceStore = class {
|
|
|
47566
47751
|
}
|
|
47567
47752
|
}
|
|
47568
47753
|
/**
|
|
47754
|
+
* Persist a `face.rescoreTrack` result onto the SUGGESTED tier — the ONE
|
|
47755
|
+
* write that explicitly-requested action makes. `suggestion: null` means
|
|
47756
|
+
* the recompute found no candidate worth offering and CLEARS both columns
|
|
47757
|
+
* (a genuine "the gallery has nothing for this face right now" result, not
|
|
47758
|
+
* a no-op left at whatever they held before). Never touches `assigned` /
|
|
47759
|
+
* `recognizedIdentityId` — a suggestion is never an assignment. The caller
|
|
47760
|
+
* is responsible for never calling this with a non-comparable (`model-
|
|
47761
|
+
* mismatch`) report; this method persists whatever it is given.
|
|
47762
|
+
*/
|
|
47763
|
+
async setSuggestion(faceId, suggestion) {
|
|
47764
|
+
const data = suggestion === null ? {
|
|
47765
|
+
suggestedIdentityId: null,
|
|
47766
|
+
suggestedMatchScore: null
|
|
47767
|
+
} : {
|
|
47768
|
+
suggestedIdentityId: suggestion.identityId,
|
|
47769
|
+
suggestedMatchScore: suggestion.score
|
|
47770
|
+
};
|
|
47771
|
+
try {
|
|
47772
|
+
await this.store.update.mutate({
|
|
47773
|
+
collection: FACES_COLLECTION,
|
|
47774
|
+
id: faceId,
|
|
47775
|
+
data
|
|
47776
|
+
});
|
|
47777
|
+
} catch (err) {
|
|
47778
|
+
this.logger.warn("FaceStore.setSuggestion failed", { meta: {
|
|
47779
|
+
faceId,
|
|
47780
|
+
error: String(err)
|
|
47781
|
+
} });
|
|
47782
|
+
}
|
|
47783
|
+
}
|
|
47784
|
+
/**
|
|
47569
47785
|
* `TrackScopedStore` contract for the retention cascade: delete the buffered
|
|
47570
47786
|
* face rows of the given tracks that are NOT assigned. Reuses the EXACT
|
|
47571
47787
|
* `assigned` exemption that `prune`/`pruneAll` apply (see the
|
|
@@ -59957,6 +60173,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
59957
60173
|
cancelVision: async (requestId) => {
|
|
59958
60174
|
await api.llm.cancel.mutate({ requestId });
|
|
59959
60175
|
},
|
|
60176
|
+
resolveArtifactUrl: async (artifactId) => await this.ncArtifactPlane?.mintUrl(artifactId) ?? null,
|
|
59960
60177
|
mintActionUrl: (input) => this.ncActionMintUrl?.(input) ?? Promise.resolve(null),
|
|
59961
60178
|
readDeviceStates: async (ids) => {
|
|
59962
60179
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -60475,6 +60692,21 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60475
60692
|
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
|
|
60476
60693
|
});
|
|
60477
60694
|
}
|
|
60695
|
+
/**
|
|
60696
|
+
* The FrameProcessor pass of a replay run — see the cap doc on
|
|
60697
|
+
* `runReplayFrameProcessor` for why this is not the `processFrame` method
|
|
60698
|
+
* this addon deliberately does not have.
|
|
60699
|
+
*
|
|
60700
|
+
* `runFrameProcessorPass` is pure w.r.t. this addon's own state: it
|
|
60701
|
+
* constructs a FRESH `FrameProcessor` and does not touch `this.trackStore`,
|
|
60702
|
+
* `this.stationaryRegistry` or any other instance field — unlike
|
|
60703
|
+
* `getOrCreateProcessor` (the production path), which wires both.
|
|
60704
|
+
* `scripts/check-replay-isolation.ts` holds that boundary on the
|
|
60705
|
+
* `replay/` directory this delegates into.
|
|
60706
|
+
*/
|
|
60707
|
+
async runReplayFrameProcessor(input) {
|
|
60708
|
+
return runFrameProcessorPass(input);
|
|
60709
|
+
}
|
|
60478
60710
|
async listRetrainAnnotations(input) {
|
|
60479
60711
|
return this.requireRetrain().listAnnotations(input.trackId);
|
|
60480
60712
|
}
|
|
@@ -65424,6 +65656,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65424
65656
|
* See `face-rescore.ts` for why the number already on the track cannot answer
|
|
65425
65657
|
* that: it was computed during live detection and persisted at track close,
|
|
65426
65658
|
* so every enrolment made since is invisible to it.
|
|
65659
|
+
*
|
|
65660
|
+
* The report's result is PERSISTED (`persistRescoreResult`, below) onto
|
|
65661
|
+
* `Face.suggestedIdentityId` / `suggestedMatchScore` — the SAME columns the
|
|
65662
|
+
* live recognizer's suggestion band writes, so a reader never has to know
|
|
65663
|
+
* whether a suggestion came from track close or from this button. Still
|
|
65664
|
+
* never a background job: this write happens once, only because the
|
|
65665
|
+
* operator asked THIS question right now.
|
|
65427
65666
|
*/
|
|
65428
65667
|
async rescoreFaceForTrack(input) {
|
|
65429
65668
|
const api = this.ctx.api;
|
|
@@ -65466,6 +65705,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65466
65705
|
clusterModelId,
|
|
65467
65706
|
params
|
|
65468
65707
|
});
|
|
65708
|
+
const persisted = await persistRescoreResult(faceId, report, { setSuggestion: (id, suggestion) => faceStore.setSuggestion(id, suggestion) });
|
|
65469
65709
|
this.ctx.logger.info("face rescore", {
|
|
65470
65710
|
tags: { deviceId: input.deviceId },
|
|
65471
65711
|
meta: {
|
|
@@ -65479,7 +65719,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65479
65719
|
best: report.identities[0]?.name ?? null,
|
|
65480
65720
|
bestScore: report.identities[0]?.score ?? null,
|
|
65481
65721
|
storedSuggestion: face.suggestedMatchScore ?? null,
|
|
65482
|
-
reembedded: reembed.succeeded
|
|
65722
|
+
reembedded: reembed.succeeded,
|
|
65723
|
+
persisted
|
|
65483
65724
|
}
|
|
65484
65725
|
});
|
|
65485
65726
|
return {
|
|
@@ -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-CMk3S5nA.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.130",
|
|
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",
|