@camstack/addon-post-analysis 1.2.53 → 1.2.54

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.
@@ -20953,7 +20953,29 @@ var FaceInfoSchema = object({
20953
20953
  recognizedIdentityId: string().optional(),
20954
20954
  identityName: string().optional(),
20955
20955
  assigned: boolean(),
20956
+ /**
20957
+ * The crop, inline, base64.
20958
+ *
20959
+ * **Prefer {@link cropUrl}.** At the 500 rows the Faces view asks for this
20960
+ * field alone is ~2.87 MiB, re-sent in full on every operator assign and
20961
+ * every 30 s poll, base64-inflated over the msgpack socket and held in the
20962
+ * query heap. It stays for callers that have not migrated; `includeCrops:
20963
+ * false` turns it off once they have.
20964
+ */
20956
20965
  base64: string().optional(),
20966
+ /**
20967
+ * Same crop, as a data-plane URL for `<img src>` — the move the admin
20968
+ * snapshot surfaces made on 2026-08-08.
20969
+ *
20970
+ * Served by the `event-media` plane, which resolves a raw MediaStore key and
20971
+ * is `access: 'authenticated'`: a bare `<img>` carries the `camstack_session`
20972
+ * cookie, so no header plumbing is needed. The bytes then ride the browser's
20973
+ * HTTP cache with an ETag and `immutable`, instead of the WebSocket.
20974
+ *
20975
+ * Absent when the face has no stored crop, or when the addon has no data
20976
+ * plane — callers fall back to {@link base64}.
20977
+ */
20978
+ cropUrl: string().optional(),
20957
20979
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
20958
20980
  * view draw the box over the native `keyFrameMediaKey` frame. Absent on
20959
20981
  * legacy rows written before design B. */
@@ -21029,7 +21051,23 @@ var faceGalleryCapability = {
21029
21051
  }),
21030
21052
  listRecentFaces: method(object({
21031
21053
  limit: number().int().positive().optional(),
21032
- filter: FaceFilterEnum.optional()
21054
+ filter: FaceFilterEnum.optional(),
21055
+ /**
21056
+ * Inline the base64 crop on every row. Default `true` — the existing
21057
+ * behaviour, kept so no caller breaks.
21058
+ *
21059
+ * Set `false` once the caller renders {@link FaceInfo.cropUrl}: that
21060
+ * drops ~2.87 MiB per 500-row page to a few KiB of metadata and lets
21061
+ * the browser cache the images.
21062
+ *
21063
+ * **This is an INPUT field, so it does not reach the addon until the
21064
+ * next train.** The hub router validates cap inputs against its own
21065
+ * compiled Zod, which strips a key it does not know — verified today
21066
+ * on the OUTPUT side, where an additive field DOES arrive immediately
21067
+ * (`Track.hasFace`). Until the train ships, sending `false` is
21068
+ * harmless and simply keeps the crops inline.
21069
+ */
21070
+ includeCrops: boolean().optional()
21033
21071
  }).optional(), array(FaceInfoSchema).readonly()),
21034
21072
  getFaceByTrack: method(object({
21035
21073
  deviceId: number().int(),
@@ -20931,7 +20931,29 @@ var FaceInfoSchema = object({
20931
20931
  recognizedIdentityId: string().optional(),
20932
20932
  identityName: string().optional(),
20933
20933
  assigned: boolean(),
20934
+ /**
20935
+ * The crop, inline, base64.
20936
+ *
20937
+ * **Prefer {@link cropUrl}.** At the 500 rows the Faces view asks for this
20938
+ * field alone is ~2.87 MiB, re-sent in full on every operator assign and
20939
+ * every 30 s poll, base64-inflated over the msgpack socket and held in the
20940
+ * query heap. It stays for callers that have not migrated; `includeCrops:
20941
+ * false` turns it off once they have.
20942
+ */
20934
20943
  base64: string().optional(),
20944
+ /**
20945
+ * Same crop, as a data-plane URL for `<img src>` — the move the admin
20946
+ * snapshot surfaces made on 2026-08-08.
20947
+ *
20948
+ * Served by the `event-media` plane, which resolves a raw MediaStore key and
20949
+ * is `access: 'authenticated'`: a bare `<img>` carries the `camstack_session`
20950
+ * cookie, so no header plumbing is needed. The bytes then ride the browser's
20951
+ * HTTP cache with an ETag and `immutable`, instead of the WebSocket.
20952
+ *
20953
+ * Absent when the face has no stored crop, or when the addon has no data
20954
+ * plane — callers fall back to {@link base64}.
20955
+ */
20956
+ cropUrl: string().optional(),
20935
20957
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
20936
20958
  * view draw the box over the native `keyFrameMediaKey` frame. Absent on
20937
20959
  * legacy rows written before design B. */
@@ -21007,7 +21029,23 @@ var faceGalleryCapability = {
21007
21029
  }),
21008
21030
  listRecentFaces: method(object({
21009
21031
  limit: number().int().positive().optional(),
21010
- filter: FaceFilterEnum.optional()
21032
+ filter: FaceFilterEnum.optional(),
21033
+ /**
21034
+ * Inline the base64 crop on every row. Default `true` — the existing
21035
+ * behaviour, kept so no caller breaks.
21036
+ *
21037
+ * Set `false` once the caller renders {@link FaceInfo.cropUrl}: that
21038
+ * drops ~2.87 MiB per 500-row page to a few KiB of metadata and lets
21039
+ * the browser cache the images.
21040
+ *
21041
+ * **This is an INPUT field, so it does not reach the addon until the
21042
+ * next train.** The hub router validates cap inputs against its own
21043
+ * compiled Zod, which strips a key it does not know — verified today
21044
+ * on the OUTPUT side, where an additive field DOES arrive immediately
21045
+ * (`Track.hasFace`). Until the train ships, sending `false` is
21046
+ * harmless and simply keeps the crops inline.
21047
+ */
21048
+ includeCrops: boolean().optional()
21011
21049
  }).optional(), array(FaceInfoSchema).readonly()),
21012
21050
  getFaceByTrack: method(object({
21013
21051
  deviceId: number().int(),
@@ -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-DGdY1GSf.js");
5
+ const require_dist = require("../dist-BJ65Xxa1.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);
@@ -10,8 +10,11 @@ let node_path = require("node:path");
10
10
  let node_path$1 = require_dist.__toESM(node_path, 1);
11
11
  node_path = require_dist.__toESM(node_path);
12
12
  require("node:crypto");
13
+ let node_util = require("node:util");
14
+ let node_zlib = require("node:zlib");
13
15
  let node_child_process = require("node:child_process");
14
- //#region ../system/dist/model-download-service-D8B-4ktF.mjs
16
+ (0, node_util.promisify)(node_zlib.brotliCompress);
17
+ (0, node_util.promisify)(node_zlib.gzip);
15
18
  function isNonEmptyFile(filePath) {
16
19
  return node_fs$1.existsSync(filePath) && node_fs$1.statSync(filePath).size > 0;
17
20
  }
@@ -1,15 +1,17 @@
1
- import { A as embeddingEncoderCapability, G as BaseAddon, N as hfModelUrl } from "../dist-Da97q0x6.mjs";
1
+ import { A as embeddingEncoderCapability, G as BaseAddon, N as hfModelUrl } from "../dist-BVOgkikr.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import "node:fs";
5
5
  import * as path$1 from "node:path";
6
6
  import "node:path";
7
7
  import "node:crypto";
8
+ import { promisify } from "node:util";
9
+ import { brotliCompress, gzip } from "node:zlib";
8
10
  import { spawn } from "node:child_process";
9
11
  //#region \0rolldown/runtime.js
10
12
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
11
- //#endregion
12
- //#region ../system/dist/model-download-service-D8B-4ktF.mjs
13
+ promisify(brotliCompress);
14
+ promisify(gzip);
13
15
  function isNonEmptyFile(filePath) {
14
16
  return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
15
17
  }
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-DGdY1GSf.js");
1
+ const require_dist = require("./dist-BJ65Xxa1.js");
2
2
  let node_fs = require("node:fs");
3
3
  node_fs = require_dist.__toESM(node_fs, 1);
4
4
  let node_path = require("node:path");
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.47",
21
+ version: "1.2.48",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.47",
39
+ version: "1.2.48",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  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-DGdY1GSf.js");
5
+ const require_dist = require("../dist-BJ65Xxa1.js");
6
6
  let node_fs = require("node:fs");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_dist.__toESM(node_path);
@@ -6771,6 +6771,40 @@ function makeNcActionHandlers(deps) {
6771
6771
  }
6772
6772
  };
6773
6773
  }
6774
+ //#endregion
6775
+ //#region src/pipeline-analytics/data-plane-access.ts
6776
+ /**
6777
+ * Prefix literals for the export planes, mirrored here so the policy reads as
6778
+ * a table without importing three handler modules into the addon's boot path.
6779
+ * `data-plane-access.spec.ts` asserts they still match the constants their
6780
+ * handlers register with — a mirror nothing checks is a lie waiting to happen.
6781
+ */
6782
+ var TRAINING_EXPORT_PREFIX_REF = "training-export";
6783
+ var RETRAIN_EXPORT_PREFIX_REF = "retrain-export";
6784
+ var DEBUG_EXPORT_PREFIX_REF = "debug-export";
6785
+ /**
6786
+ * Every data plane this addon serves, and the level the HUB enforces before it
6787
+ * proxies. Exhaustive by test: a new plane that forgets to appear here fails
6788
+ * `data-plane-access.spec.ts`.
6789
+ */
6790
+ var PIPELINE_ANALYTICS_PLANE_ACCESS = {
6791
+ /** One stored crop/thumbnail by an unguessable id the caller already holds. */
6792
+ "event-media": "authenticated",
6793
+ /** Self-authenticating single-use action token (D47). */
6794
+ "nc-action": "public",
6795
+ /** Self-authenticating single-use artifact token (D47). */
6796
+ "nc-artifact": "public",
6797
+ /** Every frame behind a marked selection — a corpus, not an item. */
6798
+ [TRAINING_EXPORT_PREFIX_REF]: "admin",
6799
+ [RETRAIN_EXPORT_PREFIX_REF]: "admin",
6800
+ /** Diagnostic bundle: logs, config and frames. */
6801
+ [DEBUG_EXPORT_PREFIX_REF]: "admin"
6802
+ };
6803
+ /** The declared level for a plane prefix. Typed, so a prefix with no declared
6804
+ * policy is a compile error rather than a silent default. */
6805
+ function planeAccess(prefix) {
6806
+ return PIPELINE_ANALYTICS_PLANE_ACCESS[prefix];
6807
+ }
6774
6808
  var MAX_EXPORT_BYTES = 256 * 1024 * 1024;
6775
6809
  var DebugFindingSchema = require_dist.object({
6776
6810
  code: require_dist.string(),
@@ -7129,40 +7163,6 @@ function parseDebugDeviceIds(url) {
7129
7163
  return out;
7130
7164
  }
7131
7165
  //#endregion
7132
- //#region src/pipeline-analytics/data-plane-access.ts
7133
- /**
7134
- * Prefix literals for the export planes, mirrored here so the policy reads as
7135
- * a table without importing three handler modules into the addon's boot path.
7136
- * `data-plane-access.spec.ts` asserts they still match the constants their
7137
- * handlers register with — a mirror nothing checks is a lie waiting to happen.
7138
- */
7139
- var TRAINING_EXPORT_PREFIX_REF = "training-export";
7140
- var RETRAIN_EXPORT_PREFIX_REF = "retrain-export";
7141
- var DEBUG_EXPORT_PREFIX_REF = "debug-export";
7142
- /**
7143
- * Every data plane this addon serves, and the level the HUB enforces before it
7144
- * proxies. Exhaustive by test: a new plane that forgets to appear here fails
7145
- * `data-plane-access.spec.ts`.
7146
- */
7147
- var PIPELINE_ANALYTICS_PLANE_ACCESS = {
7148
- /** One stored crop/thumbnail by an unguessable id the caller already holds. */
7149
- "event-media": "authenticated",
7150
- /** Self-authenticating single-use action token (D47). */
7151
- "nc-action": "public",
7152
- /** Self-authenticating single-use artifact token (D47). */
7153
- "nc-artifact": "public",
7154
- /** Every frame behind a marked selection — a corpus, not an item. */
7155
- [TRAINING_EXPORT_PREFIX_REF]: "admin",
7156
- [RETRAIN_EXPORT_PREFIX_REF]: "admin",
7157
- /** Diagnostic bundle: logs, config and frames. */
7158
- [DEBUG_EXPORT_PREFIX_REF]: "admin"
7159
- };
7160
- /** The declared level for a plane prefix. Typed, so a prefix with no declared
7161
- * policy is a compile error rather than a silent default. */
7162
- function planeAccess(prefix) {
7163
- return PIPELINE_ANALYTICS_PLANE_ACCESS[prefix];
7164
- }
7165
- //#endregion
7166
7166
  //#region src/pipeline-analytics/debug/debug-export-handler.ts
7167
7167
  /**
7168
7168
  * Pacing. The export is a BACKGROUND job sharing a thread with live decode, so
@@ -7689,6 +7689,25 @@ var embeddingActions = require_dist.defineCustomActions({
7689
7689
  "embedding.wipe": require_dist.customAction(NoInputSchema, WipeResultSchema, { auth: "admin" })
7690
7690
  });
7691
7691
  //#endregion
7692
+ //#region src/pipeline-analytics/map-with-concurrency.ts
7693
+ /**
7694
+ * Apply `mapper` to every item with at most `limit` in flight at once.
7695
+ *
7696
+ * A rejecting mapper rejects the whole call (same contract as `Promise.all`);
7697
+ * callers that must not fail a page for one bad row catch inside the mapper.
7698
+ */
7699
+ async function mapWithConcurrency(items, limit, mapper) {
7700
+ const results = new Array(items.length);
7701
+ if (items.length === 0) return results;
7702
+ const effectiveLimit = Math.max(1, Math.min(Math.trunc(limit), items.length));
7703
+ const entries = items.entries();
7704
+ const worker = async () => {
7705
+ for (const [index, item] of entries) results[index] = await mapper(item, index);
7706
+ };
7707
+ await Promise.all(Array.from({ length: effectiveLimit }, () => worker()));
7708
+ return results;
7709
+ }
7710
+ //#endregion
7692
7711
  //#region src/pipeline-analytics/pipeline/key-event-scoring.ts
7693
7712
  /**
7694
7713
  * key-event-scoring — the SINGLE deterministic "importance" scorer for a
@@ -7960,6 +7979,16 @@ function clusterByEmbedding(items, threshold = .5) {
7960
7979
  var OPERATOR_FACE_STEP_ID = "operator:face-gallery";
7961
7980
  /** Embedding model id used when enrolling face crops as identity samples. */
7962
7981
  var MODEL_ID = "arcface-r100";
7982
+ /**
7983
+ * How many face crops `listRecentFaces` resolves at once.
7984
+ *
7985
+ * Each crop costs a store round-trip plus a disk read, so the page is I/O-bound
7986
+ * and the serial form dominated the operator's assign latency. 16 recovers
7987
+ * nearly all of the win while leaving the UDS channel — shared with the live
7988
+ * detection path — far from saturated; unbounded would trade a slow page for a
7989
+ * stalled pipeline.
7990
+ */
7991
+ var FACE_CROP_FETCH_CONCURRENCY = 16;
7963
7992
  var FaceGalleryProvider = class {
7964
7993
  identityStore;
7965
7994
  faceStore;
@@ -7968,6 +7997,7 @@ var FaceGalleryProvider = class {
7968
7997
  eventStore;
7969
7998
  logger;
7970
7999
  refreshGallery;
8000
+ eventMediaBaseUrl;
7971
8001
  emitFaceGalleryChanged;
7972
8002
  constructor(deps) {
7973
8003
  this.identityStore = deps.identityStore;
@@ -7977,6 +8007,7 @@ var FaceGalleryProvider = class {
7977
8007
  this.eventStore = deps.eventStore;
7978
8008
  this.logger = deps.logger;
7979
8009
  this.refreshGallery = deps.refreshGallery;
8010
+ this.eventMediaBaseUrl = deps.eventMediaBaseUrl;
7980
8011
  this.emitFaceGalleryChanged = deps.emitFaceGalleryChanged;
7981
8012
  }
7982
8013
  /** Best-effort emit — never allowed to fail the mutation it follows. */
@@ -8068,6 +8099,21 @@ var FaceGalleryProvider = class {
8068
8099
  const media = await this.mediaStore.listByOwner("face", face.id);
8069
8100
  return media.length > 0 ? media[0].base64 : void 0;
8070
8101
  }
8102
+ /**
8103
+ * The crop as a data-plane URL, or undefined when there is no stored key or
8104
+ * no plane to serve it.
8105
+ *
8106
+ * The `event-media` plane resolves a raw MediaStore key (any id containing
8107
+ * `:` goes to `getByKey`), so a face crop needs NO new plane and no new
8108
+ * access decision — the same `authenticated` level already serves the track
8109
+ * thumbnails the same operator is looking at.
8110
+ */
8111
+ cropUrlFor(face) {
8112
+ if (face.mediaKey === void 0 || face.mediaKey === "") return void 0;
8113
+ const base = this.eventMediaBaseUrl();
8114
+ if (base === null) return void 0;
8115
+ return `${base}/${encodeURIComponent(face.mediaKey)}`;
8116
+ }
8071
8117
  async listRecentFaces(input) {
8072
8118
  const faces = await this.faceStore.listAllRecentFaces({
8073
8119
  limit: input?.limit,
@@ -8075,11 +8121,12 @@ var FaceGalleryProvider = class {
8075
8121
  });
8076
8122
  const identities = await this.identityStore.listIdentities();
8077
8123
  const nameMap = new Map(identities.map((i) => [i.id, i.name]));
8078
- const result = [];
8079
- for (const face of faces) {
8080
- const base64 = await this.faceCropBase64(face);
8124
+ const includeCrops = input?.includeCrops ?? true;
8125
+ return mapWithConcurrency(faces, FACE_CROP_FETCH_CONCURRENCY, async (face) => {
8126
+ const base64 = includeCrops ? await this.faceCropBase64(face) : void 0;
8127
+ const cropUrl = this.cropUrlFor(face);
8081
8128
  const identityName = face.recognizedIdentityId ? nameMap.get(face.recognizedIdentityId) : void 0;
8082
- result.push({
8129
+ return {
8083
8130
  faceId: face.id,
8084
8131
  deviceId: face.deviceId,
8085
8132
  trackId: face.trackId,
@@ -8088,15 +8135,15 @@ var FaceGalleryProvider = class {
8088
8135
  ...identityName !== void 0 ? { identityName } : {},
8089
8136
  assigned: face.assigned,
8090
8137
  ...base64 !== void 0 ? { base64 } : {},
8138
+ ...cropUrl !== void 0 ? { cropUrl } : {},
8091
8139
  ...face.faceBbox !== void 0 ? { faceBbox: face.faceBbox } : {},
8092
8140
  ...face.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: face.keyFrameMediaKey } : {},
8093
8141
  ...face.bestMatchScore != null ? { bestMatchScore: face.bestMatchScore } : {},
8094
8142
  ...face.nativeFaceShortSidePx != null ? { nativeFaceShortSidePx: face.nativeFaceShortSidePx } : {},
8095
8143
  ...face.suggestedIdentityId != null ? { suggestedIdentityId: face.suggestedIdentityId } : {},
8096
8144
  ...face.suggestedMatchScore != null ? { suggestedMatchScore: face.suggestedMatchScore } : {}
8097
- });
8098
- }
8099
- return result;
8145
+ };
8146
+ });
8100
8147
  }
8101
8148
  /**
8102
8149
  * Get a face by deviceId + trackId. Returns null if not found or if
@@ -20124,6 +20171,21 @@ var PlateRecognizer = class {
20124
20171
  }
20125
20172
  };
20126
20173
  //#endregion
20174
+ //#region src/pipeline-analytics/read-slice.ts
20175
+ /**
20176
+ * The loaded slice, or `undefined` when it has never loaded and the one
20177
+ * permitted refresh also failed to produce it.
20178
+ *
20179
+ * A failing refresh is swallowed deliberately: every caller degrades to "no
20180
+ * zones / no rules", and a zone read must never take down the frame that asked.
20181
+ */
20182
+ async function readSliceOnce(handle) {
20183
+ const loaded = handle.value;
20184
+ if (loaded !== void 0) return loaded;
20185
+ await handle.refresh().catch(() => void 0);
20186
+ return handle.value;
20187
+ }
20188
+ //#endregion
20127
20189
  //#region src/pipeline-analytics/rebuild-detail-request.ts
20128
20190
  /** The one step a rebuild runs. */
20129
20191
  var REBUILD_DETAIL_STEP_ID = "clip-embedding";
@@ -31305,7 +31367,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
31305
31367
  let storage = this.ctx.kernel.storage;
31306
31368
  const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
31307
31369
  if (mediaRoot) {
31308
- const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-CpXZkQ6V.js"));
31370
+ const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-DTrkngzv.js"));
31309
31371
  storage = new FilesystemStorageProvider(mediaRoot, { eventMedia: mediaRoot });
31310
31372
  logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
31311
31373
  }
@@ -32316,6 +32378,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
32316
32378
  eventStore: stores.eventStore,
32317
32379
  logger: this.ctx.logger,
32318
32380
  refreshGallery: () => this.faceRecognizer?.refreshGallery(),
32381
+ eventMediaBaseUrl: () => this.eventMediaBaseUrl,
32319
32382
  emitFaceGalleryChanged: (payload) => this.emitFaceGalleryChanged(payload)
32320
32383
  });
32321
32384
  const plateGallery = new PlateGalleryProvider({
@@ -33327,10 +33390,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
33327
33390
  async resolveDevicePackageRules(deviceId) {
33328
33391
  const proxy = await this.ensureProxy(deviceId);
33329
33392
  if (!proxy) return [];
33330
- const cached = proxy.state.zoneRules.value?.package;
33331
- if (cached && cached.length > 0) return cached;
33332
- await proxy.state.zoneRules.refresh().catch(() => void 0);
33333
- return proxy.state.zoneRules.value?.package ?? [];
33393
+ return (await readSliceOnce(proxy.state.zoneRules))?.package ?? [];
33334
33394
  }
33335
33395
  /**
33336
33396
  * Route one track's `runDetailSubtree` results (two-plane detail dispatch)
@@ -34638,10 +34698,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
34638
34698
  async resolveDeviceZones(deviceId) {
34639
34699
  const proxy = await this.ensureProxy(deviceId);
34640
34700
  if (!proxy) return [];
34641
- const cached = proxy.state.zones.value?.zones;
34642
- if (cached && cached.length > 0) return cached;
34643
- await proxy.state.zones.refresh().catch(() => void 0);
34644
- return proxy.state.zones.value?.zones ?? [];
34701
+ return (await readSliceOnce(proxy.state.zones))?.zones ?? [];
34645
34702
  }
34646
34703
  releaseProxy(deviceId) {
34647
34704
  const unsubs = this.proxyUnsubs.get(deviceId);
@@ -1,4 +1,4 @@
1
- import { $ as array, B as subKindsOf, C as addonWidgetsSourceCapability, D as cosineSimilarity, E as buildEventKindDescriptor, F as kebabToCamel, G as BaseAddon, H as videoclipsCapability, I as notificationRulesCapability, J as hydrateSchema, K as DeviceType, L as pipelineAnalyticsCapability, M as faceGalleryCapability, O as customAction, P as isScheduleActive, Q as _enum, R as plateGalleryCapability, S as TrackSourceSchema, T as audioMetricsCapability, U as zoneAnalyticsCapability, V as vectorDimFromBase64, W as errMsg, X as nodePin, Y as isDeviceScopedCap, Z as sleep, _ as NcTaxonomySchema, a as EVENT_PAD_MS, at as string, b as TimelapseRuleInputSchema, c as NC_CONDITION_CATALOG, d as NcRuleInputSchema, et as boolean, f as NcRulePatchSchema, g as NcSnoozeSuppressedSchema, h as NcSnoozeSchema, i as EVENT_KIND_BY_CAP, it as record, j as encodeVectorBase64, k as defineCustomActions, l as NC_TAXONOMY, m as NcSnoozeInputSchema, n as DEFAULT_EVENT_COLOR, nt as number, o as LabelAttributionSchema, ot as unknown, p as NcRuleSchema, q as createEvent, r as DeclaredDevices, rt as object, s as MACRO_LABELS, st as EventCategory, t as BaseDevice, tt as literal, u as NcConditionDescriptorSchema, v as OpsLogEntrySchema, w as alarmPanelCapability, x as TimelapseRuleSchema, y as RetrainStatusSchema, z as readDeviceStateFrom } from "../dist-Da97q0x6.mjs";
1
+ import { $ as array, B as subKindsOf, C as addonWidgetsSourceCapability, D as cosineSimilarity, E as buildEventKindDescriptor, F as kebabToCamel, G as BaseAddon, H as videoclipsCapability, I as notificationRulesCapability, J as hydrateSchema, K as DeviceType, L as pipelineAnalyticsCapability, M as faceGalleryCapability, O as customAction, P as isScheduleActive, Q as _enum, R as plateGalleryCapability, S as TrackSourceSchema, T as audioMetricsCapability, U as zoneAnalyticsCapability, V as vectorDimFromBase64, W as errMsg, X as nodePin, Y as isDeviceScopedCap, Z as sleep, _ as NcTaxonomySchema, a as EVENT_PAD_MS, at as string, b as TimelapseRuleInputSchema, c as NC_CONDITION_CATALOG, d as NcRuleInputSchema, et as boolean, f as NcRulePatchSchema, g as NcSnoozeSuppressedSchema, h as NcSnoozeSchema, i as EVENT_KIND_BY_CAP, it as record, j as encodeVectorBase64, k as defineCustomActions, l as NC_TAXONOMY, m as NcSnoozeInputSchema, n as DEFAULT_EVENT_COLOR, nt as number, o as LabelAttributionSchema, ot as unknown, p as NcRuleSchema, q as createEvent, r as DeclaredDevices, rt as object, s as MACRO_LABELS, st as EventCategory, t as BaseDevice, tt as literal, u as NcConditionDescriptorSchema, v as OpsLogEntrySchema, w as alarmPanelCapability, x as TimelapseRuleSchema, y as RetrainStatusSchema, z as readDeviceStateFrom } from "../dist-BVOgkikr.mjs";
2
2
  import { promises } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
@@ -6757,6 +6757,40 @@ function makeNcActionHandlers(deps) {
6757
6757
  }
6758
6758
  };
6759
6759
  }
6760
+ //#endregion
6761
+ //#region src/pipeline-analytics/data-plane-access.ts
6762
+ /**
6763
+ * Prefix literals for the export planes, mirrored here so the policy reads as
6764
+ * a table without importing three handler modules into the addon's boot path.
6765
+ * `data-plane-access.spec.ts` asserts they still match the constants their
6766
+ * handlers register with — a mirror nothing checks is a lie waiting to happen.
6767
+ */
6768
+ var TRAINING_EXPORT_PREFIX_REF = "training-export";
6769
+ var RETRAIN_EXPORT_PREFIX_REF = "retrain-export";
6770
+ var DEBUG_EXPORT_PREFIX_REF = "debug-export";
6771
+ /**
6772
+ * Every data plane this addon serves, and the level the HUB enforces before it
6773
+ * proxies. Exhaustive by test: a new plane that forgets to appear here fails
6774
+ * `data-plane-access.spec.ts`.
6775
+ */
6776
+ var PIPELINE_ANALYTICS_PLANE_ACCESS = {
6777
+ /** One stored crop/thumbnail by an unguessable id the caller already holds. */
6778
+ "event-media": "authenticated",
6779
+ /** Self-authenticating single-use action token (D47). */
6780
+ "nc-action": "public",
6781
+ /** Self-authenticating single-use artifact token (D47). */
6782
+ "nc-artifact": "public",
6783
+ /** Every frame behind a marked selection — a corpus, not an item. */
6784
+ [TRAINING_EXPORT_PREFIX_REF]: "admin",
6785
+ [RETRAIN_EXPORT_PREFIX_REF]: "admin",
6786
+ /** Diagnostic bundle: logs, config and frames. */
6787
+ [DEBUG_EXPORT_PREFIX_REF]: "admin"
6788
+ };
6789
+ /** The declared level for a plane prefix. Typed, so a prefix with no declared
6790
+ * policy is a compile error rather than a silent default. */
6791
+ function planeAccess(prefix) {
6792
+ return PIPELINE_ANALYTICS_PLANE_ACCESS[prefix];
6793
+ }
6760
6794
  var MAX_EXPORT_BYTES = 256 * 1024 * 1024;
6761
6795
  var DebugFindingSchema = object({
6762
6796
  code: string(),
@@ -7115,40 +7149,6 @@ function parseDebugDeviceIds(url) {
7115
7149
  return out;
7116
7150
  }
7117
7151
  //#endregion
7118
- //#region src/pipeline-analytics/data-plane-access.ts
7119
- /**
7120
- * Prefix literals for the export planes, mirrored here so the policy reads as
7121
- * a table without importing three handler modules into the addon's boot path.
7122
- * `data-plane-access.spec.ts` asserts they still match the constants their
7123
- * handlers register with — a mirror nothing checks is a lie waiting to happen.
7124
- */
7125
- var TRAINING_EXPORT_PREFIX_REF = "training-export";
7126
- var RETRAIN_EXPORT_PREFIX_REF = "retrain-export";
7127
- var DEBUG_EXPORT_PREFIX_REF = "debug-export";
7128
- /**
7129
- * Every data plane this addon serves, and the level the HUB enforces before it
7130
- * proxies. Exhaustive by test: a new plane that forgets to appear here fails
7131
- * `data-plane-access.spec.ts`.
7132
- */
7133
- var PIPELINE_ANALYTICS_PLANE_ACCESS = {
7134
- /** One stored crop/thumbnail by an unguessable id the caller already holds. */
7135
- "event-media": "authenticated",
7136
- /** Self-authenticating single-use action token (D47). */
7137
- "nc-action": "public",
7138
- /** Self-authenticating single-use artifact token (D47). */
7139
- "nc-artifact": "public",
7140
- /** Every frame behind a marked selection — a corpus, not an item. */
7141
- [TRAINING_EXPORT_PREFIX_REF]: "admin",
7142
- [RETRAIN_EXPORT_PREFIX_REF]: "admin",
7143
- /** Diagnostic bundle: logs, config and frames. */
7144
- [DEBUG_EXPORT_PREFIX_REF]: "admin"
7145
- };
7146
- /** The declared level for a plane prefix. Typed, so a prefix with no declared
7147
- * policy is a compile error rather than a silent default. */
7148
- function planeAccess(prefix) {
7149
- return PIPELINE_ANALYTICS_PLANE_ACCESS[prefix];
7150
- }
7151
- //#endregion
7152
7152
  //#region src/pipeline-analytics/debug/debug-export-handler.ts
7153
7153
  /**
7154
7154
  * Pacing. The export is a BACKGROUND job sharing a thread with live decode, so
@@ -7675,6 +7675,25 @@ var embeddingActions = defineCustomActions({
7675
7675
  "embedding.wipe": customAction(NoInputSchema, WipeResultSchema, { auth: "admin" })
7676
7676
  });
7677
7677
  //#endregion
7678
+ //#region src/pipeline-analytics/map-with-concurrency.ts
7679
+ /**
7680
+ * Apply `mapper` to every item with at most `limit` in flight at once.
7681
+ *
7682
+ * A rejecting mapper rejects the whole call (same contract as `Promise.all`);
7683
+ * callers that must not fail a page for one bad row catch inside the mapper.
7684
+ */
7685
+ async function mapWithConcurrency(items, limit, mapper) {
7686
+ const results = new Array(items.length);
7687
+ if (items.length === 0) return results;
7688
+ const effectiveLimit = Math.max(1, Math.min(Math.trunc(limit), items.length));
7689
+ const entries = items.entries();
7690
+ const worker = async () => {
7691
+ for (const [index, item] of entries) results[index] = await mapper(item, index);
7692
+ };
7693
+ await Promise.all(Array.from({ length: effectiveLimit }, () => worker()));
7694
+ return results;
7695
+ }
7696
+ //#endregion
7678
7697
  //#region src/pipeline-analytics/pipeline/key-event-scoring.ts
7679
7698
  /**
7680
7699
  * key-event-scoring — the SINGLE deterministic "importance" scorer for a
@@ -7946,6 +7965,16 @@ function clusterByEmbedding(items, threshold = .5) {
7946
7965
  var OPERATOR_FACE_STEP_ID = "operator:face-gallery";
7947
7966
  /** Embedding model id used when enrolling face crops as identity samples. */
7948
7967
  var MODEL_ID = "arcface-r100";
7968
+ /**
7969
+ * How many face crops `listRecentFaces` resolves at once.
7970
+ *
7971
+ * Each crop costs a store round-trip plus a disk read, so the page is I/O-bound
7972
+ * and the serial form dominated the operator's assign latency. 16 recovers
7973
+ * nearly all of the win while leaving the UDS channel — shared with the live
7974
+ * detection path — far from saturated; unbounded would trade a slow page for a
7975
+ * stalled pipeline.
7976
+ */
7977
+ var FACE_CROP_FETCH_CONCURRENCY = 16;
7949
7978
  var FaceGalleryProvider = class {
7950
7979
  identityStore;
7951
7980
  faceStore;
@@ -7954,6 +7983,7 @@ var FaceGalleryProvider = class {
7954
7983
  eventStore;
7955
7984
  logger;
7956
7985
  refreshGallery;
7986
+ eventMediaBaseUrl;
7957
7987
  emitFaceGalleryChanged;
7958
7988
  constructor(deps) {
7959
7989
  this.identityStore = deps.identityStore;
@@ -7963,6 +7993,7 @@ var FaceGalleryProvider = class {
7963
7993
  this.eventStore = deps.eventStore;
7964
7994
  this.logger = deps.logger;
7965
7995
  this.refreshGallery = deps.refreshGallery;
7996
+ this.eventMediaBaseUrl = deps.eventMediaBaseUrl;
7966
7997
  this.emitFaceGalleryChanged = deps.emitFaceGalleryChanged;
7967
7998
  }
7968
7999
  /** Best-effort emit — never allowed to fail the mutation it follows. */
@@ -8054,6 +8085,21 @@ var FaceGalleryProvider = class {
8054
8085
  const media = await this.mediaStore.listByOwner("face", face.id);
8055
8086
  return media.length > 0 ? media[0].base64 : void 0;
8056
8087
  }
8088
+ /**
8089
+ * The crop as a data-plane URL, or undefined when there is no stored key or
8090
+ * no plane to serve it.
8091
+ *
8092
+ * The `event-media` plane resolves a raw MediaStore key (any id containing
8093
+ * `:` goes to `getByKey`), so a face crop needs NO new plane and no new
8094
+ * access decision — the same `authenticated` level already serves the track
8095
+ * thumbnails the same operator is looking at.
8096
+ */
8097
+ cropUrlFor(face) {
8098
+ if (face.mediaKey === void 0 || face.mediaKey === "") return void 0;
8099
+ const base = this.eventMediaBaseUrl();
8100
+ if (base === null) return void 0;
8101
+ return `${base}/${encodeURIComponent(face.mediaKey)}`;
8102
+ }
8057
8103
  async listRecentFaces(input) {
8058
8104
  const faces = await this.faceStore.listAllRecentFaces({
8059
8105
  limit: input?.limit,
@@ -8061,11 +8107,12 @@ var FaceGalleryProvider = class {
8061
8107
  });
8062
8108
  const identities = await this.identityStore.listIdentities();
8063
8109
  const nameMap = new Map(identities.map((i) => [i.id, i.name]));
8064
- const result = [];
8065
- for (const face of faces) {
8066
- const base64 = await this.faceCropBase64(face);
8110
+ const includeCrops = input?.includeCrops ?? true;
8111
+ return mapWithConcurrency(faces, FACE_CROP_FETCH_CONCURRENCY, async (face) => {
8112
+ const base64 = includeCrops ? await this.faceCropBase64(face) : void 0;
8113
+ const cropUrl = this.cropUrlFor(face);
8067
8114
  const identityName = face.recognizedIdentityId ? nameMap.get(face.recognizedIdentityId) : void 0;
8068
- result.push({
8115
+ return {
8069
8116
  faceId: face.id,
8070
8117
  deviceId: face.deviceId,
8071
8118
  trackId: face.trackId,
@@ -8074,15 +8121,15 @@ var FaceGalleryProvider = class {
8074
8121
  ...identityName !== void 0 ? { identityName } : {},
8075
8122
  assigned: face.assigned,
8076
8123
  ...base64 !== void 0 ? { base64 } : {},
8124
+ ...cropUrl !== void 0 ? { cropUrl } : {},
8077
8125
  ...face.faceBbox !== void 0 ? { faceBbox: face.faceBbox } : {},
8078
8126
  ...face.keyFrameMediaKey !== void 0 ? { keyFrameMediaKey: face.keyFrameMediaKey } : {},
8079
8127
  ...face.bestMatchScore != null ? { bestMatchScore: face.bestMatchScore } : {},
8080
8128
  ...face.nativeFaceShortSidePx != null ? { nativeFaceShortSidePx: face.nativeFaceShortSidePx } : {},
8081
8129
  ...face.suggestedIdentityId != null ? { suggestedIdentityId: face.suggestedIdentityId } : {},
8082
8130
  ...face.suggestedMatchScore != null ? { suggestedMatchScore: face.suggestedMatchScore } : {}
8083
- });
8084
- }
8085
- return result;
8131
+ };
8132
+ });
8086
8133
  }
8087
8134
  /**
8088
8135
  * Get a face by deviceId + trackId. Returns null if not found or if
@@ -20110,6 +20157,21 @@ var PlateRecognizer = class {
20110
20157
  }
20111
20158
  };
20112
20159
  //#endregion
20160
+ //#region src/pipeline-analytics/read-slice.ts
20161
+ /**
20162
+ * The loaded slice, or `undefined` when it has never loaded and the one
20163
+ * permitted refresh also failed to produce it.
20164
+ *
20165
+ * A failing refresh is swallowed deliberately: every caller degrades to "no
20166
+ * zones / no rules", and a zone read must never take down the frame that asked.
20167
+ */
20168
+ async function readSliceOnce(handle) {
20169
+ const loaded = handle.value;
20170
+ if (loaded !== void 0) return loaded;
20171
+ await handle.refresh().catch(() => void 0);
20172
+ return handle.value;
20173
+ }
20174
+ //#endregion
20113
20175
  //#region src/pipeline-analytics/rebuild-detail-request.ts
20114
20176
  /** The one step a rebuild runs. */
20115
20177
  var REBUILD_DETAIL_STEP_ID = "clip-embedding";
@@ -32302,6 +32364,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
32302
32364
  eventStore: stores.eventStore,
32303
32365
  logger: this.ctx.logger,
32304
32366
  refreshGallery: () => this.faceRecognizer?.refreshGallery(),
32367
+ eventMediaBaseUrl: () => this.eventMediaBaseUrl,
32305
32368
  emitFaceGalleryChanged: (payload) => this.emitFaceGalleryChanged(payload)
32306
32369
  });
32307
32370
  const plateGallery = new PlateGalleryProvider({
@@ -33313,10 +33376,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
33313
33376
  async resolveDevicePackageRules(deviceId) {
33314
33377
  const proxy = await this.ensureProxy(deviceId);
33315
33378
  if (!proxy) return [];
33316
- const cached = proxy.state.zoneRules.value?.package;
33317
- if (cached && cached.length > 0) return cached;
33318
- await proxy.state.zoneRules.refresh().catch(() => void 0);
33319
- return proxy.state.zoneRules.value?.package ?? [];
33379
+ return (await readSliceOnce(proxy.state.zoneRules))?.package ?? [];
33320
33380
  }
33321
33381
  /**
33322
33382
  * Route one track's `runDetailSubtree` results (two-plane detail dispatch)
@@ -34624,10 +34684,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
34624
34684
  async resolveDeviceZones(deviceId) {
34625
34685
  const proxy = await this.ensureProxy(deviceId);
34626
34686
  if (!proxy) return [];
34627
- const cached = proxy.state.zones.value?.zones;
34628
- if (cached && cached.length > 0) return cached;
34629
- await proxy.state.zones.refresh().catch(() => void 0);
34630
- return proxy.state.zones.value?.zones ?? [];
34687
+ return (await readSliceOnce(proxy.state.zones))?.zones ?? [];
34631
34688
  }
34632
34689
  releaseProxy(deviceId) {
34633
34690
  const unsubs = this.proxyUnsubs.get(deviceId);
@@ -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-tCAuhv5m.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-uW2bbQBi.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.53",
3
+ "version": "1.2.54",
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",