@camstack/addon-post-analysis 1.2.53 → 1.2.55
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-DGdY1GSf.js → dist-B1CMW3_t.js} +4506 -4267
- package/dist/{dist-Da97q0x6.mjs → dist-D9Bm8RyD.mjs} +4506 -4267
- package/dist/embedding-encoder/index.js +5 -2
- package/dist/embedding-encoder/index.mjs +5 -3
- package/dist/{node-CpXZkQ6V.js → node-cjH3qw1E.js} +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-tCAuhv5m.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BuCI6peK.mjs} +2 -2
- package/dist/pipeline-analytics/{hostInit-CyLkuQ7j.mjs → hostInit-3GBLWJ6R.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +257 -58
- package/dist/pipeline-analytics/index.mjs +256 -57
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-B1CMW3_t.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
|
-
|
|
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-
|
|
1
|
+
import { A as embeddingEncoderCapability, G as BaseAddon, N as hfModelUrl } from "../dist-D9Bm8RyD.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
|
-
|
|
12
|
-
|
|
13
|
+
promisify(brotliCompress);
|
|
14
|
+
promisify(gzip);
|
|
13
15
|
function isNonEmptyFile(filePath) {
|
|
14
16
|
return fs.existsSync(filePath) && fs.statSync(filePath).size > 0;
|
|
15
17
|
}
|
|
@@ -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.51",
|
|
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.36",
|
|
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.51",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
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.36",
|
|
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-B1CMW3_t.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
|
|
8079
|
-
|
|
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
|
-
|
|
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
|
|
@@ -12405,23 +12452,73 @@ async function makeSquareThumb(bytes, size) {
|
|
|
12405
12452
|
}).jpeg({ quality: THUMB_QUALITY }).toBuffer();
|
|
12406
12453
|
}
|
|
12407
12454
|
//#endregion
|
|
12455
|
+
//#region src/pipeline-analytics/zone-name-stamp.ts
|
|
12456
|
+
/**
|
|
12457
|
+
* Copy each track with `zoneNames` stamped on.
|
|
12458
|
+
*
|
|
12459
|
+
* Three silences, all deliberate, all of them "say nothing" rather than "say
|
|
12460
|
+
* something wrong":
|
|
12461
|
+
*
|
|
12462
|
+
* - a track that visited no zone gets NO field (an empty array on every row of
|
|
12463
|
+
* a 1000-row page is pure wire cost for no information);
|
|
12464
|
+
* - a zone id with no entry in the catalogue — a zone deleted since the track
|
|
12465
|
+
* was written — is DROPPED. `zoneNames` is therefore not positionally
|
|
12466
|
+
* aligned with `zonesVisited` and can be shorter;
|
|
12467
|
+
* - a device whose catalogue could not be read at all leaves the track exactly
|
|
12468
|
+
* as it arrived. A zones cap that is momentarily down must not look like a
|
|
12469
|
+
* track that was never in a zone.
|
|
12470
|
+
*/
|
|
12471
|
+
function stampZoneNames(tracks, lookup) {
|
|
12472
|
+
return tracks.map((track) => {
|
|
12473
|
+
if (track.zonesVisited.length === 0) return track;
|
|
12474
|
+
const catalogue = lookup(track.deviceId);
|
|
12475
|
+
if (catalogue === null) return track;
|
|
12476
|
+
const names = [];
|
|
12477
|
+
for (const id of track.zonesVisited) {
|
|
12478
|
+
const name = catalogue.get(id);
|
|
12479
|
+
if (name !== void 0 && name.length > 0 && !names.includes(name)) names.push(name);
|
|
12480
|
+
}
|
|
12481
|
+
if (names.length === 0) return track;
|
|
12482
|
+
return {
|
|
12483
|
+
...track,
|
|
12484
|
+
zoneNames: names
|
|
12485
|
+
};
|
|
12486
|
+
});
|
|
12487
|
+
}
|
|
12488
|
+
//#endregion
|
|
12408
12489
|
//#region src/pipeline-analytics/analytics-query-facade.ts
|
|
12409
12490
|
var AnalyticsQueryFacade = class {
|
|
12410
12491
|
deps;
|
|
12411
12492
|
constructor(deps) {
|
|
12412
12493
|
this.deps = deps;
|
|
12413
12494
|
}
|
|
12495
|
+
/**
|
|
12496
|
+
* Stamp the human `zoneNames` onto a batch of tracks.
|
|
12497
|
+
*
|
|
12498
|
+
* ONE place, on the read path, for every track surface — `zonesVisited` is
|
|
12499
|
+
* ids and a client cannot resolve them without a per-camera fan-out. A hub
|
|
12500
|
+
* with no catalogue reader configured returns the tracks untouched.
|
|
12501
|
+
*/
|
|
12502
|
+
async withZoneNames(tracks) {
|
|
12503
|
+
const cache = this.deps.zoneCatalogue?.() ?? null;
|
|
12504
|
+
if (cache === null || tracks.length === 0) return tracks;
|
|
12505
|
+
const zoned = tracks.filter((t) => t.zonesVisited.length > 0);
|
|
12506
|
+
if (zoned.length === 0) return tracks;
|
|
12507
|
+
await cache.ensure(zoned.map((t) => t.deviceId));
|
|
12508
|
+
return stampZoneNames(tracks, (deviceId) => cache.lookup(deviceId));
|
|
12509
|
+
}
|
|
12414
12510
|
async getActiveTracks(input) {
|
|
12415
|
-
return this.deps.trackStore()?.getActive(input.deviceId) ?? [];
|
|
12511
|
+
return this.withZoneNames(this.deps.trackStore()?.getActive(input.deviceId) ?? []);
|
|
12416
12512
|
}
|
|
12417
12513
|
async getTrack(input) {
|
|
12418
12514
|
const store = this.deps.trackStore();
|
|
12419
|
-
const
|
|
12420
|
-
if (
|
|
12421
|
-
|
|
12515
|
+
const track = store?.getActiveByTrack(input.trackId) ?? await store?.getPersistedByTrackId(input.trackId) ?? null;
|
|
12516
|
+
if (track === null) return null;
|
|
12517
|
+
const [named] = await this.withZoneNames([track]);
|
|
12518
|
+
return named ?? track;
|
|
12422
12519
|
}
|
|
12423
12520
|
async listTracks(input) {
|
|
12424
|
-
return this.deps.trackStore()?.queryHistorical(input) ?? [];
|
|
12521
|
+
return this.withZoneNames(await this.deps.trackStore()?.queryHistorical(input) ?? []);
|
|
12425
12522
|
}
|
|
12426
12523
|
/**
|
|
12427
12524
|
* Batched cluster-wide track listing — one merged (`lastSeen` DESC,
|
|
@@ -12431,10 +12528,14 @@ var AnalyticsQueryFacade = class {
|
|
|
12431
12528
|
* indexed page + k-way merge and the cursor encoding.
|
|
12432
12529
|
*/
|
|
12433
12530
|
async listRecentTracks(input) {
|
|
12434
|
-
|
|
12531
|
+
const page = await this.deps.trackStore()?.queryRecent(input) ?? {
|
|
12435
12532
|
tracks: [],
|
|
12436
12533
|
nextCursor: null
|
|
12437
12534
|
};
|
|
12535
|
+
return {
|
|
12536
|
+
...page,
|
|
12537
|
+
tracks: await this.withZoneNames(page.tracks)
|
|
12538
|
+
};
|
|
12438
12539
|
}
|
|
12439
12540
|
/** Per-camera sensor-event history (state changes of linked devices). */
|
|
12440
12541
|
async getSensorEvents(input) {
|
|
@@ -17106,6 +17207,72 @@ var RetrainService = class {
|
|
|
17106
17207
|
}
|
|
17107
17208
|
};
|
|
17108
17209
|
//#endregion
|
|
17210
|
+
//#region src/pipeline-analytics/zone-catalogue-cache.ts
|
|
17211
|
+
/**
|
|
17212
|
+
* zone-catalogue-cache — the per-device zone id→name dictionary the track read
|
|
17213
|
+
* path stamps `zoneNames` from.
|
|
17214
|
+
*
|
|
17215
|
+
* The `zones` capability is per-device, so resolving names on a merged
|
|
17216
|
+
* `listRecentTracks` page would otherwise be one cap call per camera per page —
|
|
17217
|
+
* the exact fan-out that surface exists to remove. One read per camera per TTL
|
|
17218
|
+
* is the whole cost, against a catalogue that changes only when an operator
|
|
17219
|
+
* draws a polygon.
|
|
17220
|
+
*
|
|
17221
|
+
* The failure rule is the important part: a refresh that throws KEEPS the last
|
|
17222
|
+
* known catalogue. Dropping it would make every zoned track look unzoned for
|
|
17223
|
+
* the next minute, which reads as "zones stopped working" rather than "the
|
|
17224
|
+
* zones cap blipped".
|
|
17225
|
+
*/
|
|
17226
|
+
/** How long a device's zone catalogue is trusted before a re-read. A zone
|
|
17227
|
+
* rename should reach the feed without a restart; a minute is invisible to an
|
|
17228
|
+
* operator and reduces the read to ~16 cheap calls/min on this fleet. */
|
|
17229
|
+
var ZONE_CATALOGUE_TTL_MS = 6e4;
|
|
17230
|
+
var ZoneCatalogueCache = class {
|
|
17231
|
+
deps;
|
|
17232
|
+
entries = /* @__PURE__ */ new Map();
|
|
17233
|
+
/** In-flight reads, so a burst of pages for the same camera makes one call. */
|
|
17234
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
17235
|
+
constructor(deps) {
|
|
17236
|
+
this.deps = deps;
|
|
17237
|
+
}
|
|
17238
|
+
/** Make sure every device has a catalogue no older than the TTL. */
|
|
17239
|
+
async ensure(deviceIds) {
|
|
17240
|
+
const stale = [...new Set(deviceIds)].filter((id) => this.isStale(id));
|
|
17241
|
+
await Promise.all(stale.map((id) => this.refresh(id)));
|
|
17242
|
+
}
|
|
17243
|
+
/** The device's zone id→name map, or null when it was never readable. */
|
|
17244
|
+
lookup(deviceId) {
|
|
17245
|
+
return this.entries.get(deviceId)?.names ?? null;
|
|
17246
|
+
}
|
|
17247
|
+
isStale(deviceId) {
|
|
17248
|
+
const held = this.entries.get(deviceId);
|
|
17249
|
+
if (held === void 0) return true;
|
|
17250
|
+
return this.deps.now() - held.readAtMs >= ZONE_CATALOGUE_TTL_MS;
|
|
17251
|
+
}
|
|
17252
|
+
async refresh(deviceId) {
|
|
17253
|
+
const running = this.inFlight.get(deviceId);
|
|
17254
|
+
if (running !== void 0) return running;
|
|
17255
|
+
const task = this.read(deviceId).finally(() => {
|
|
17256
|
+
this.inFlight.delete(deviceId);
|
|
17257
|
+
});
|
|
17258
|
+
this.inFlight.set(deviceId, task);
|
|
17259
|
+
return task;
|
|
17260
|
+
}
|
|
17261
|
+
async read(deviceId) {
|
|
17262
|
+
try {
|
|
17263
|
+
const zones = await this.deps.fetchZones(deviceId);
|
|
17264
|
+
const names = /* @__PURE__ */ new Map();
|
|
17265
|
+
for (const zone of zones) if (zone.id.length > 0 && zone.name.length > 0) names.set(zone.id, zone.name);
|
|
17266
|
+
this.entries.set(deviceId, {
|
|
17267
|
+
names,
|
|
17268
|
+
readAtMs: this.deps.now()
|
|
17269
|
+
});
|
|
17270
|
+
} catch (error) {
|
|
17271
|
+
this.deps.onError(deviceId, error);
|
|
17272
|
+
}
|
|
17273
|
+
}
|
|
17274
|
+
};
|
|
17275
|
+
//#endregion
|
|
17109
17276
|
//#region src/pipeline-analytics/media-capture-log.ts
|
|
17110
17277
|
var DEFAULT_FLUSH_INTERVAL_MS$1 = 6e4;
|
|
17111
17278
|
var MediaCaptureLogAggregator = class {
|
|
@@ -20124,6 +20291,21 @@ var PlateRecognizer = class {
|
|
|
20124
20291
|
}
|
|
20125
20292
|
};
|
|
20126
20293
|
//#endregion
|
|
20294
|
+
//#region src/pipeline-analytics/read-slice.ts
|
|
20295
|
+
/**
|
|
20296
|
+
* The loaded slice, or `undefined` when it has never loaded and the one
|
|
20297
|
+
* permitted refresh also failed to produce it.
|
|
20298
|
+
*
|
|
20299
|
+
* A failing refresh is swallowed deliberately: every caller degrades to "no
|
|
20300
|
+
* zones / no rules", and a zone read must never take down the frame that asked.
|
|
20301
|
+
*/
|
|
20302
|
+
async function readSliceOnce(handle) {
|
|
20303
|
+
const loaded = handle.value;
|
|
20304
|
+
if (loaded !== void 0) return loaded;
|
|
20305
|
+
await handle.refresh().catch(() => void 0);
|
|
20306
|
+
return handle.value;
|
|
20307
|
+
}
|
|
20308
|
+
//#endregion
|
|
20127
20309
|
//#region src/pipeline-analytics/rebuild-detail-request.ts
|
|
20128
20310
|
/** The one step a rebuild runs. */
|
|
20129
20311
|
var REBUILD_DETAIL_STEP_ID = "clip-embedding";
|
|
@@ -30944,6 +31126,27 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
30944
31126
|
* bodies live in `analytics-query-facade.ts`; the class keeps one-line
|
|
30945
31127
|
* delegates because the pipeline-analytics cap registers `this` as its
|
|
30946
31128
|
* provider. Deps are lazy getters — the stores are late-bound in boot. */
|
|
31129
|
+
/**
|
|
31130
|
+
* Zone id→name dictionary for the track read path.
|
|
31131
|
+
*
|
|
31132
|
+
* `zonesVisited` persists ids; every operator-facing surface needs the name.
|
|
31133
|
+
* Cached per device (see `ZoneCatalogueCache`) so a merged 1000-row page
|
|
31134
|
+
* costs at most one `listZones` per camera per minute rather than one per
|
|
31135
|
+
* page.
|
|
31136
|
+
*/
|
|
31137
|
+
zoneCatalogue = new ZoneCatalogueCache({
|
|
31138
|
+
fetchZones: (deviceId) => this.ctx.api.zones.listZones.query({ deviceId }),
|
|
31139
|
+
now: () => Date.now(),
|
|
31140
|
+
onError: (deviceId, error) => {
|
|
31141
|
+
this.ctx.logger.warn("zone catalogue read failed — zone names unresolved for this device", {
|
|
31142
|
+
tags: { deviceId },
|
|
31143
|
+
meta: {
|
|
31144
|
+
deviceId,
|
|
31145
|
+
error: require_dist.errMsg(error)
|
|
31146
|
+
}
|
|
31147
|
+
});
|
|
31148
|
+
}
|
|
31149
|
+
});
|
|
30947
31150
|
queryFacade = new AnalyticsQueryFacade({
|
|
30948
31151
|
trackStore: () => this.trackStore,
|
|
30949
31152
|
eventStore: () => this.eventStore,
|
|
@@ -30951,7 +31154,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
30951
31154
|
sensorEventStore: () => this.sensorEventStore,
|
|
30952
31155
|
opsLog: () => this.eventsOpsLog,
|
|
30953
31156
|
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
30954
|
-
logger: () => this.ctx.logger
|
|
31157
|
+
logger: () => this.ctx.logger,
|
|
31158
|
+
zoneCatalogue: () => this.zoneCatalogue
|
|
30955
31159
|
});
|
|
30956
31160
|
/** The shared crop extractor (native-res first, detection-frame fallback),
|
|
30957
31161
|
* captured in the constructor so `processFrame` can crop object thumbnails in
|
|
@@ -31305,7 +31509,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
31305
31509
|
let storage = this.ctx.kernel.storage;
|
|
31306
31510
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
31307
31511
|
if (mediaRoot) {
|
|
31308
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-
|
|
31512
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-cjH3qw1E.js"));
|
|
31309
31513
|
storage = new FilesystemStorageProvider(mediaRoot, { eventMedia: mediaRoot });
|
|
31310
31514
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
31311
31515
|
}
|
|
@@ -32316,6 +32520,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
32316
32520
|
eventStore: stores.eventStore,
|
|
32317
32521
|
logger: this.ctx.logger,
|
|
32318
32522
|
refreshGallery: () => this.faceRecognizer?.refreshGallery(),
|
|
32523
|
+
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
32319
32524
|
emitFaceGalleryChanged: (payload) => this.emitFaceGalleryChanged(payload)
|
|
32320
32525
|
});
|
|
32321
32526
|
const plateGallery = new PlateGalleryProvider({
|
|
@@ -33327,10 +33532,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
33327
33532
|
async resolveDevicePackageRules(deviceId) {
|
|
33328
33533
|
const proxy = await this.ensureProxy(deviceId);
|
|
33329
33534
|
if (!proxy) return [];
|
|
33330
|
-
|
|
33331
|
-
if (cached && cached.length > 0) return cached;
|
|
33332
|
-
await proxy.state.zoneRules.refresh().catch(() => void 0);
|
|
33333
|
-
return proxy.state.zoneRules.value?.package ?? [];
|
|
33535
|
+
return (await readSliceOnce(proxy.state.zoneRules))?.package ?? [];
|
|
33334
33536
|
}
|
|
33335
33537
|
/**
|
|
33336
33538
|
* Route one track's `runDetailSubtree` results (two-plane detail dispatch)
|
|
@@ -34638,10 +34840,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
34638
34840
|
async resolveDeviceZones(deviceId) {
|
|
34639
34841
|
const proxy = await this.ensureProxy(deviceId);
|
|
34640
34842
|
if (!proxy) return [];
|
|
34641
|
-
|
|
34642
|
-
if (cached && cached.length > 0) return cached;
|
|
34643
|
-
await proxy.state.zones.refresh().catch(() => void 0);
|
|
34644
|
-
return proxy.state.zones.value?.zones ?? [];
|
|
34843
|
+
return (await readSliceOnce(proxy.state.zones))?.zones ?? [];
|
|
34645
34844
|
}
|
|
34646
34845
|
releaseProxy(deviceId) {
|
|
34647
34846
|
const unsubs = this.proxyUnsubs.get(deviceId);
|