@camstack/addon-post-analysis 1.2.128 → 1.2.129

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.
@@ -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-CRDS5ykk.mjs";
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-CGhP6T_c.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";
@@ -27195,6 +27195,18 @@ var FaceGalleryProvider = class {
27195
27195
  * This module names that state (`model-mismatch`) so the UI can say "this face
27196
27196
  * is in an old feature space" instead of showing 0% and letting the operator
27197
27197
  * conclude the person is not there.
27198
+ *
27199
+ * ## `buildFaceRescoreReport` stays pure — the CALLER persists
27200
+ *
27201
+ * This function never touches the store; every input is passed in and nothing
27202
+ * is read or written (see its own doc below). The operator asked for this
27203
+ * exact recompute, though, and wants the answer to stick — so the ACTION that
27204
+ * wraps this report (`rescoreFaceForTrack` in `pipeline-analytics/index.ts`)
27205
+ * writes the result onto `Face.suggestedIdentityId` / `suggestedMatchScore`,
27206
+ * through `decideRescorePersistence` / `persistRescoreResult` below. That is
27207
+ * still not a background job — it is the SAME explicitly-requested call,
27208
+ * writing once. A `model-mismatch` report (`comparable: false`) is never
27209
+ * written: see `persistRescoreResult`.
27198
27210
  */
27199
27211
  /** The empty report every "cannot score" path returns, so shape never varies. */
27200
27212
  function emptyReport(verdict, input, comparable) {
@@ -27270,6 +27282,52 @@ function buildFaceRescoreReport(input) {
27270
27282
  ...best !== void 0 && second !== void 0 ? { runnerUpGap: best.score - second.score } : {}
27271
27283
  };
27272
27284
  }
27285
+ /**
27286
+ * What `face.rescoreTrack` should PERSIST for a COMPARABLE report, decided as
27287
+ * its own pure function so the rule is directly testable — separated from the
27288
+ * class that owns the store I/O (`rescoreFaceForTrack` in
27289
+ * `pipeline-analytics/index.ts`), which only wires this answer to
27290
+ * `FaceStore.setSuggestion`.
27291
+ *
27292
+ * `null` covers every verdict OTHER than `auto-assignable` / `suggestion`, and
27293
+ * also an `auto-assignable` / `suggestion` verdict whose best-ranked row is
27294
+ * not itself ELIGIBLE (`identities` is ranked by raw score across every
27295
+ * comparable identity, eligible or not — see `buildFaceRescoreReport`). Every
27296
+ * one of those is a genuine "nothing to suggest right now": never an
27297
+ * assignment, and never the winner of an `ambiguous` verdict — the matcher
27298
+ * refused to pick one, so this function does not pick one either.
27299
+ *
27300
+ * Callers MUST gate on `report.comparable` before calling this — a
27301
+ * `model-mismatch` report has no verdict worth acting on; see
27302
+ * {@link persistRescoreResult}, which does that gating and is the one thing
27303
+ * `rescoreFaceForTrack` actually calls.
27304
+ */
27305
+ function decideRescorePersistence(report) {
27306
+ if (report.verdict !== "auto-assignable" && report.verdict !== "suggestion") return null;
27307
+ const candidate = report.identities.find((row) => row.eligible);
27308
+ return candidate === void 0 ? null : {
27309
+ identityId: candidate.identityId,
27310
+ score: candidate.score
27311
+ };
27312
+ }
27313
+ /**
27314
+ * Persist `face.rescoreTrack`'s result onto the SUGGESTED tier — the ONE
27315
+ * write this explicitly-requested action makes, and the only place that
27316
+ * decides whether it happens at all.
27317
+ *
27318
+ * A non-comparable report (`model-mismatch`) short-circuits BEFORE
27319
+ * `ports.setSuggestion` is ever called: a cosine across model spaces is
27320
+ * well-formed noise, and persisting it would look exactly like a fresh,
27321
+ * meaningful read to every future reader of the row. This is the single most
27322
+ * load-bearing branch in this file — the regression it guards against writes
27323
+ * garbage that reads as data.
27324
+ */
27325
+ async function persistRescoreResult(faceId, report, ports) {
27326
+ if (!report.comparable) return "not-comparable";
27327
+ const candidate = decideRescorePersistence(report);
27328
+ await ports.setSuggestion(faceId, candidate);
27329
+ return candidate !== null ? "written" : "cleared";
27330
+ }
27273
27331
  //#endregion
27274
27332
  //#region src/pipeline-analytics/face-rescore-actions.ts
27275
27333
  /**
@@ -27355,9 +27413,12 @@ var FaceReembedReportSchema = object({
27355
27413
  });
27356
27414
  var faceRescoreActions = defineCustomActions({
27357
27415
  /**
27358
- * Read-only by default. `admin` because the report names every enrolled
27359
- * person and how well an arbitrary face matches them — the gallery's shape,
27360
- * which is not a viewer-level fact.
27416
+ * Persists its own result onto the SUGGESTED tier (`suggestedIdentityId` /
27417
+ * `suggestedMatchScore`) — never an assignment, never automatic; see
27418
+ * `rescoreFaceForTrack` in `pipeline-analytics/index.ts` and
27419
+ * `persistRescoreResult` in `face-rescore.ts`. `admin` because the report
27420
+ * also names every enrolled person and how well an arbitrary face matches
27421
+ * them — the gallery's shape, which is not a viewer-level fact.
27361
27422
  */
27362
27423
  "face.rescoreTrack": customAction(object({
27363
27424
  deviceId: number().int(),
@@ -27375,8 +27436,10 @@ var faceRescoreActions = defineCustomActions({
27375
27436
  /** The face's current assignment, so the UI can mark "already this person". */
27376
27437
  assigned: boolean(),
27377
27438
  assignedIdentityId: string().optional(),
27378
- /** What the track has been SHOWING until now — persisted at detection time.
27379
- * Returned so the UI can put the stale number beside the fresh one. */
27439
+ /** What the track has been SHOWING until now — read BEFORE this call
27440
+ * writes, so it is whatever a previous track close or a previous
27441
+ * `face.rescoreTrack` last persisted. Returned so the UI can put the stale
27442
+ * number beside the fresh one. */
27380
27443
  storedSuggestedIdentityId: string().optional(),
27381
27444
  storedSuggestedMatchScore: number().optional(),
27382
27445
  verdict: FaceRescoreVerdictSchema,
@@ -29383,6 +29446,51 @@ var FrameProcessor = class {
29383
29446
  }
29384
29447
  };
29385
29448
  //#endregion
29449
+ //#region src/pipeline-analytics/replay/frame-processor-pass.ts
29450
+ /**
29451
+ * Run a FRESH `FrameProcessor` over a fixed, ordered sequence of frames and
29452
+ * return the tracks it produced.
29453
+ *
29454
+ * No `TrackStore`, no bus, no media — a track here is a summary built
29455
+ * ENTIRELY from `FrameProcessor.process()`'s in-memory return value.
29456
+ */
29457
+ function runFrameProcessorPass(input) {
29458
+ const processor = new FrameProcessor(input.deviceId, {}, {}, {}, input.source);
29459
+ if (input.zones !== void 0) processor.setZones(input.zones);
29460
+ if (input.detectionRules !== void 0) processor.setDetectionRules(input.detectionRules);
29461
+ if (input.zoneMembershipMinOverlap !== void 0) processor.setZoneMembershipMinOverlap(input.zoneMembershipMinOverlap);
29462
+ const accum = /* @__PURE__ */ new Map();
29463
+ for (const { timestamp, frame } of input.frames) {
29464
+ const result = processor.process({
29465
+ timestamp,
29466
+ frame
29467
+ });
29468
+ for (const td of result.tracked) {
29469
+ const existing = accum.get(td.trackId);
29470
+ const matched = td.matchedThisFrame ? 1 : 0;
29471
+ if (existing === void 0) accum.set(td.trackId, {
29472
+ className: td.className,
29473
+ firstSeenMs: timestamp,
29474
+ lastSeenMs: timestamp,
29475
+ bbox: { ...td.bbox },
29476
+ framesMatched: matched
29477
+ });
29478
+ else {
29479
+ existing.className = td.className;
29480
+ existing.lastSeenMs = timestamp;
29481
+ existing.framesMatched += matched;
29482
+ }
29483
+ }
29484
+ }
29485
+ return { tracks: [...accum.values()].map((t) => ({
29486
+ className: t.className,
29487
+ firstSeenMs: t.firstSeenMs,
29488
+ lastSeenMs: t.lastSeenMs,
29489
+ bbox: t.bbox,
29490
+ framesMatched: t.framesMatched
29491
+ })) };
29492
+ }
29493
+ //#endregion
29386
29494
  //#region src/pipeline-analytics/pipeline/semantic-detail-bbox.ts
29387
29495
  /** Which box the semantic best-shot should crop: a plate/face DETAIL box
29388
29496
  * when we have one, otherwise the subject box. */
@@ -47566,6 +47674,37 @@ var FaceStore = class {
47566
47674
  }
47567
47675
  }
47568
47676
  /**
47677
+ * Persist a `face.rescoreTrack` result onto the SUGGESTED tier — the ONE
47678
+ * write that explicitly-requested action makes. `suggestion: null` means
47679
+ * the recompute found no candidate worth offering and CLEARS both columns
47680
+ * (a genuine "the gallery has nothing for this face right now" result, not
47681
+ * a no-op left at whatever they held before). Never touches `assigned` /
47682
+ * `recognizedIdentityId` — a suggestion is never an assignment. The caller
47683
+ * is responsible for never calling this with a non-comparable (`model-
47684
+ * mismatch`) report; this method persists whatever it is given.
47685
+ */
47686
+ async setSuggestion(faceId, suggestion) {
47687
+ const data = suggestion === null ? {
47688
+ suggestedIdentityId: null,
47689
+ suggestedMatchScore: null
47690
+ } : {
47691
+ suggestedIdentityId: suggestion.identityId,
47692
+ suggestedMatchScore: suggestion.score
47693
+ };
47694
+ try {
47695
+ await this.store.update.mutate({
47696
+ collection: FACES_COLLECTION,
47697
+ id: faceId,
47698
+ data
47699
+ });
47700
+ } catch (err) {
47701
+ this.logger.warn("FaceStore.setSuggestion failed", { meta: {
47702
+ faceId,
47703
+ error: String(err)
47704
+ } });
47705
+ }
47706
+ }
47707
+ /**
47569
47708
  * `TrackScopedStore` contract for the retention cascade: delete the buffered
47570
47709
  * face rows of the given tracks that are NOT assigned. Reuses the EXACT
47571
47710
  * `assigned` exemption that `prune`/`pruneAll` apply (see the
@@ -60475,6 +60614,21 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
60475
60614
  ...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {}
60476
60615
  });
60477
60616
  }
60617
+ /**
60618
+ * The FrameProcessor pass of a replay run — see the cap doc on
60619
+ * `runReplayFrameProcessor` for why this is not the `processFrame` method
60620
+ * this addon deliberately does not have.
60621
+ *
60622
+ * `runFrameProcessorPass` is pure w.r.t. this addon's own state: it
60623
+ * constructs a FRESH `FrameProcessor` and does not touch `this.trackStore`,
60624
+ * `this.stationaryRegistry` or any other instance field — unlike
60625
+ * `getOrCreateProcessor` (the production path), which wires both.
60626
+ * `scripts/check-replay-isolation.ts` holds that boundary on the
60627
+ * `replay/` directory this delegates into.
60628
+ */
60629
+ async runReplayFrameProcessor(input) {
60630
+ return runFrameProcessorPass(input);
60631
+ }
60478
60632
  async listRetrainAnnotations(input) {
60479
60633
  return this.requireRetrain().listAnnotations(input.trackId);
60480
60634
  }
@@ -65424,6 +65578,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
65424
65578
  * See `face-rescore.ts` for why the number already on the track cannot answer
65425
65579
  * that: it was computed during live detection and persisted at track close,
65426
65580
  * so every enrolment made since is invisible to it.
65581
+ *
65582
+ * The report's result is PERSISTED (`persistRescoreResult`, below) onto
65583
+ * `Face.suggestedIdentityId` / `suggestedMatchScore` — the SAME columns the
65584
+ * live recognizer's suggestion band writes, so a reader never has to know
65585
+ * whether a suggestion came from track close or from this button. Still
65586
+ * never a background job: this write happens once, only because the
65587
+ * operator asked THIS question right now.
65427
65588
  */
65428
65589
  async rescoreFaceForTrack(input) {
65429
65590
  const api = this.ctx.api;
@@ -65466,6 +65627,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
65466
65627
  clusterModelId,
65467
65628
  params
65468
65629
  });
65630
+ const persisted = await persistRescoreResult(faceId, report, { setSuggestion: (id, suggestion) => faceStore.setSuggestion(id, suggestion) });
65469
65631
  this.ctx.logger.info("face rescore", {
65470
65632
  tags: { deviceId: input.deviceId },
65471
65633
  meta: {
@@ -65479,7 +65641,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
65479
65641
  best: report.identities[0]?.name ?? null,
65480
65642
  bestScore: report.identities[0]?.score ?? null,
65481
65643
  storedSuggestion: face.suggestedMatchScore ?? null,
65482
- reembedded: reembed.succeeded
65644
+ reembedded: reembed.succeeded,
65645
+ persisted
65483
65646
  }
65484
65647
  });
65485
65648
  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-BZm5TUIO.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D4G4SLMY.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.128",
3
+ "version": "1.2.129",
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",