@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
|
@@ -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-
|
|
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-D9Bm8RyD.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
|
|
8065
|
-
|
|
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
|
-
|
|
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
|
|
@@ -12391,23 +12438,73 @@ async function makeSquareThumb(bytes, size) {
|
|
|
12391
12438
|
}).jpeg({ quality: THUMB_QUALITY }).toBuffer();
|
|
12392
12439
|
}
|
|
12393
12440
|
//#endregion
|
|
12441
|
+
//#region src/pipeline-analytics/zone-name-stamp.ts
|
|
12442
|
+
/**
|
|
12443
|
+
* Copy each track with `zoneNames` stamped on.
|
|
12444
|
+
*
|
|
12445
|
+
* Three silences, all deliberate, all of them "say nothing" rather than "say
|
|
12446
|
+
* something wrong":
|
|
12447
|
+
*
|
|
12448
|
+
* - a track that visited no zone gets NO field (an empty array on every row of
|
|
12449
|
+
* a 1000-row page is pure wire cost for no information);
|
|
12450
|
+
* - a zone id with no entry in the catalogue — a zone deleted since the track
|
|
12451
|
+
* was written — is DROPPED. `zoneNames` is therefore not positionally
|
|
12452
|
+
* aligned with `zonesVisited` and can be shorter;
|
|
12453
|
+
* - a device whose catalogue could not be read at all leaves the track exactly
|
|
12454
|
+
* as it arrived. A zones cap that is momentarily down must not look like a
|
|
12455
|
+
* track that was never in a zone.
|
|
12456
|
+
*/
|
|
12457
|
+
function stampZoneNames(tracks, lookup) {
|
|
12458
|
+
return tracks.map((track) => {
|
|
12459
|
+
if (track.zonesVisited.length === 0) return track;
|
|
12460
|
+
const catalogue = lookup(track.deviceId);
|
|
12461
|
+
if (catalogue === null) return track;
|
|
12462
|
+
const names = [];
|
|
12463
|
+
for (const id of track.zonesVisited) {
|
|
12464
|
+
const name = catalogue.get(id);
|
|
12465
|
+
if (name !== void 0 && name.length > 0 && !names.includes(name)) names.push(name);
|
|
12466
|
+
}
|
|
12467
|
+
if (names.length === 0) return track;
|
|
12468
|
+
return {
|
|
12469
|
+
...track,
|
|
12470
|
+
zoneNames: names
|
|
12471
|
+
};
|
|
12472
|
+
});
|
|
12473
|
+
}
|
|
12474
|
+
//#endregion
|
|
12394
12475
|
//#region src/pipeline-analytics/analytics-query-facade.ts
|
|
12395
12476
|
var AnalyticsQueryFacade = class {
|
|
12396
12477
|
deps;
|
|
12397
12478
|
constructor(deps) {
|
|
12398
12479
|
this.deps = deps;
|
|
12399
12480
|
}
|
|
12481
|
+
/**
|
|
12482
|
+
* Stamp the human `zoneNames` onto a batch of tracks.
|
|
12483
|
+
*
|
|
12484
|
+
* ONE place, on the read path, for every track surface — `zonesVisited` is
|
|
12485
|
+
* ids and a client cannot resolve them without a per-camera fan-out. A hub
|
|
12486
|
+
* with no catalogue reader configured returns the tracks untouched.
|
|
12487
|
+
*/
|
|
12488
|
+
async withZoneNames(tracks) {
|
|
12489
|
+
const cache = this.deps.zoneCatalogue?.() ?? null;
|
|
12490
|
+
if (cache === null || tracks.length === 0) return tracks;
|
|
12491
|
+
const zoned = tracks.filter((t) => t.zonesVisited.length > 0);
|
|
12492
|
+
if (zoned.length === 0) return tracks;
|
|
12493
|
+
await cache.ensure(zoned.map((t) => t.deviceId));
|
|
12494
|
+
return stampZoneNames(tracks, (deviceId) => cache.lookup(deviceId));
|
|
12495
|
+
}
|
|
12400
12496
|
async getActiveTracks(input) {
|
|
12401
|
-
return this.deps.trackStore()?.getActive(input.deviceId) ?? [];
|
|
12497
|
+
return this.withZoneNames(this.deps.trackStore()?.getActive(input.deviceId) ?? []);
|
|
12402
12498
|
}
|
|
12403
12499
|
async getTrack(input) {
|
|
12404
12500
|
const store = this.deps.trackStore();
|
|
12405
|
-
const
|
|
12406
|
-
if (
|
|
12407
|
-
|
|
12501
|
+
const track = store?.getActiveByTrack(input.trackId) ?? await store?.getPersistedByTrackId(input.trackId) ?? null;
|
|
12502
|
+
if (track === null) return null;
|
|
12503
|
+
const [named] = await this.withZoneNames([track]);
|
|
12504
|
+
return named ?? track;
|
|
12408
12505
|
}
|
|
12409
12506
|
async listTracks(input) {
|
|
12410
|
-
return this.deps.trackStore()?.queryHistorical(input) ?? [];
|
|
12507
|
+
return this.withZoneNames(await this.deps.trackStore()?.queryHistorical(input) ?? []);
|
|
12411
12508
|
}
|
|
12412
12509
|
/**
|
|
12413
12510
|
* Batched cluster-wide track listing — one merged (`lastSeen` DESC,
|
|
@@ -12417,10 +12514,14 @@ var AnalyticsQueryFacade = class {
|
|
|
12417
12514
|
* indexed page + k-way merge and the cursor encoding.
|
|
12418
12515
|
*/
|
|
12419
12516
|
async listRecentTracks(input) {
|
|
12420
|
-
|
|
12517
|
+
const page = await this.deps.trackStore()?.queryRecent(input) ?? {
|
|
12421
12518
|
tracks: [],
|
|
12422
12519
|
nextCursor: null
|
|
12423
12520
|
};
|
|
12521
|
+
return {
|
|
12522
|
+
...page,
|
|
12523
|
+
tracks: await this.withZoneNames(page.tracks)
|
|
12524
|
+
};
|
|
12424
12525
|
}
|
|
12425
12526
|
/** Per-camera sensor-event history (state changes of linked devices). */
|
|
12426
12527
|
async getSensorEvents(input) {
|
|
@@ -17092,6 +17193,72 @@ var RetrainService = class {
|
|
|
17092
17193
|
}
|
|
17093
17194
|
};
|
|
17094
17195
|
//#endregion
|
|
17196
|
+
//#region src/pipeline-analytics/zone-catalogue-cache.ts
|
|
17197
|
+
/**
|
|
17198
|
+
* zone-catalogue-cache — the per-device zone id→name dictionary the track read
|
|
17199
|
+
* path stamps `zoneNames` from.
|
|
17200
|
+
*
|
|
17201
|
+
* The `zones` capability is per-device, so resolving names on a merged
|
|
17202
|
+
* `listRecentTracks` page would otherwise be one cap call per camera per page —
|
|
17203
|
+
* the exact fan-out that surface exists to remove. One read per camera per TTL
|
|
17204
|
+
* is the whole cost, against a catalogue that changes only when an operator
|
|
17205
|
+
* draws a polygon.
|
|
17206
|
+
*
|
|
17207
|
+
* The failure rule is the important part: a refresh that throws KEEPS the last
|
|
17208
|
+
* known catalogue. Dropping it would make every zoned track look unzoned for
|
|
17209
|
+
* the next minute, which reads as "zones stopped working" rather than "the
|
|
17210
|
+
* zones cap blipped".
|
|
17211
|
+
*/
|
|
17212
|
+
/** How long a device's zone catalogue is trusted before a re-read. A zone
|
|
17213
|
+
* rename should reach the feed without a restart; a minute is invisible to an
|
|
17214
|
+
* operator and reduces the read to ~16 cheap calls/min on this fleet. */
|
|
17215
|
+
var ZONE_CATALOGUE_TTL_MS = 6e4;
|
|
17216
|
+
var ZoneCatalogueCache = class {
|
|
17217
|
+
deps;
|
|
17218
|
+
entries = /* @__PURE__ */ new Map();
|
|
17219
|
+
/** In-flight reads, so a burst of pages for the same camera makes one call. */
|
|
17220
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
17221
|
+
constructor(deps) {
|
|
17222
|
+
this.deps = deps;
|
|
17223
|
+
}
|
|
17224
|
+
/** Make sure every device has a catalogue no older than the TTL. */
|
|
17225
|
+
async ensure(deviceIds) {
|
|
17226
|
+
const stale = [...new Set(deviceIds)].filter((id) => this.isStale(id));
|
|
17227
|
+
await Promise.all(stale.map((id) => this.refresh(id)));
|
|
17228
|
+
}
|
|
17229
|
+
/** The device's zone id→name map, or null when it was never readable. */
|
|
17230
|
+
lookup(deviceId) {
|
|
17231
|
+
return this.entries.get(deviceId)?.names ?? null;
|
|
17232
|
+
}
|
|
17233
|
+
isStale(deviceId) {
|
|
17234
|
+
const held = this.entries.get(deviceId);
|
|
17235
|
+
if (held === void 0) return true;
|
|
17236
|
+
return this.deps.now() - held.readAtMs >= ZONE_CATALOGUE_TTL_MS;
|
|
17237
|
+
}
|
|
17238
|
+
async refresh(deviceId) {
|
|
17239
|
+
const running = this.inFlight.get(deviceId);
|
|
17240
|
+
if (running !== void 0) return running;
|
|
17241
|
+
const task = this.read(deviceId).finally(() => {
|
|
17242
|
+
this.inFlight.delete(deviceId);
|
|
17243
|
+
});
|
|
17244
|
+
this.inFlight.set(deviceId, task);
|
|
17245
|
+
return task;
|
|
17246
|
+
}
|
|
17247
|
+
async read(deviceId) {
|
|
17248
|
+
try {
|
|
17249
|
+
const zones = await this.deps.fetchZones(deviceId);
|
|
17250
|
+
const names = /* @__PURE__ */ new Map();
|
|
17251
|
+
for (const zone of zones) if (zone.id.length > 0 && zone.name.length > 0) names.set(zone.id, zone.name);
|
|
17252
|
+
this.entries.set(deviceId, {
|
|
17253
|
+
names,
|
|
17254
|
+
readAtMs: this.deps.now()
|
|
17255
|
+
});
|
|
17256
|
+
} catch (error) {
|
|
17257
|
+
this.deps.onError(deviceId, error);
|
|
17258
|
+
}
|
|
17259
|
+
}
|
|
17260
|
+
};
|
|
17261
|
+
//#endregion
|
|
17095
17262
|
//#region src/pipeline-analytics/media-capture-log.ts
|
|
17096
17263
|
var DEFAULT_FLUSH_INTERVAL_MS$1 = 6e4;
|
|
17097
17264
|
var MediaCaptureLogAggregator = class {
|
|
@@ -20110,6 +20277,21 @@ var PlateRecognizer = class {
|
|
|
20110
20277
|
}
|
|
20111
20278
|
};
|
|
20112
20279
|
//#endregion
|
|
20280
|
+
//#region src/pipeline-analytics/read-slice.ts
|
|
20281
|
+
/**
|
|
20282
|
+
* The loaded slice, or `undefined` when it has never loaded and the one
|
|
20283
|
+
* permitted refresh also failed to produce it.
|
|
20284
|
+
*
|
|
20285
|
+
* A failing refresh is swallowed deliberately: every caller degrades to "no
|
|
20286
|
+
* zones / no rules", and a zone read must never take down the frame that asked.
|
|
20287
|
+
*/
|
|
20288
|
+
async function readSliceOnce(handle) {
|
|
20289
|
+
const loaded = handle.value;
|
|
20290
|
+
if (loaded !== void 0) return loaded;
|
|
20291
|
+
await handle.refresh().catch(() => void 0);
|
|
20292
|
+
return handle.value;
|
|
20293
|
+
}
|
|
20294
|
+
//#endregion
|
|
20113
20295
|
//#region src/pipeline-analytics/rebuild-detail-request.ts
|
|
20114
20296
|
/** The one step a rebuild runs. */
|
|
20115
20297
|
var REBUILD_DETAIL_STEP_ID = "clip-embedding";
|
|
@@ -30930,6 +31112,27 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
30930
31112
|
* bodies live in `analytics-query-facade.ts`; the class keeps one-line
|
|
30931
31113
|
* delegates because the pipeline-analytics cap registers `this` as its
|
|
30932
31114
|
* provider. Deps are lazy getters — the stores are late-bound in boot. */
|
|
31115
|
+
/**
|
|
31116
|
+
* Zone id→name dictionary for the track read path.
|
|
31117
|
+
*
|
|
31118
|
+
* `zonesVisited` persists ids; every operator-facing surface needs the name.
|
|
31119
|
+
* Cached per device (see `ZoneCatalogueCache`) so a merged 1000-row page
|
|
31120
|
+
* costs at most one `listZones` per camera per minute rather than one per
|
|
31121
|
+
* page.
|
|
31122
|
+
*/
|
|
31123
|
+
zoneCatalogue = new ZoneCatalogueCache({
|
|
31124
|
+
fetchZones: (deviceId) => this.ctx.api.zones.listZones.query({ deviceId }),
|
|
31125
|
+
now: () => Date.now(),
|
|
31126
|
+
onError: (deviceId, error) => {
|
|
31127
|
+
this.ctx.logger.warn("zone catalogue read failed — zone names unresolved for this device", {
|
|
31128
|
+
tags: { deviceId },
|
|
31129
|
+
meta: {
|
|
31130
|
+
deviceId,
|
|
31131
|
+
error: errMsg(error)
|
|
31132
|
+
}
|
|
31133
|
+
});
|
|
31134
|
+
}
|
|
31135
|
+
});
|
|
30933
31136
|
queryFacade = new AnalyticsQueryFacade({
|
|
30934
31137
|
trackStore: () => this.trackStore,
|
|
30935
31138
|
eventStore: () => this.eventStore,
|
|
@@ -30937,7 +31140,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
30937
31140
|
sensorEventStore: () => this.sensorEventStore,
|
|
30938
31141
|
opsLog: () => this.eventsOpsLog,
|
|
30939
31142
|
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
30940
|
-
logger: () => this.ctx.logger
|
|
31143
|
+
logger: () => this.ctx.logger,
|
|
31144
|
+
zoneCatalogue: () => this.zoneCatalogue
|
|
30941
31145
|
});
|
|
30942
31146
|
/** The shared crop extractor (native-res first, detection-frame fallback),
|
|
30943
31147
|
* captured in the constructor so `processFrame` can crop object thumbnails in
|
|
@@ -32302,6 +32506,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
32302
32506
|
eventStore: stores.eventStore,
|
|
32303
32507
|
logger: this.ctx.logger,
|
|
32304
32508
|
refreshGallery: () => this.faceRecognizer?.refreshGallery(),
|
|
32509
|
+
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
32305
32510
|
emitFaceGalleryChanged: (payload) => this.emitFaceGalleryChanged(payload)
|
|
32306
32511
|
});
|
|
32307
32512
|
const plateGallery = new PlateGalleryProvider({
|
|
@@ -33313,10 +33518,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
33313
33518
|
async resolveDevicePackageRules(deviceId) {
|
|
33314
33519
|
const proxy = await this.ensureProxy(deviceId);
|
|
33315
33520
|
if (!proxy) return [];
|
|
33316
|
-
|
|
33317
|
-
if (cached && cached.length > 0) return cached;
|
|
33318
|
-
await proxy.state.zoneRules.refresh().catch(() => void 0);
|
|
33319
|
-
return proxy.state.zoneRules.value?.package ?? [];
|
|
33521
|
+
return (await readSliceOnce(proxy.state.zoneRules))?.package ?? [];
|
|
33320
33522
|
}
|
|
33321
33523
|
/**
|
|
33322
33524
|
* Route one track's `runDetailSubtree` results (two-plane detail dispatch)
|
|
@@ -34624,10 +34826,7 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
34624
34826
|
async resolveDeviceZones(deviceId) {
|
|
34625
34827
|
const proxy = await this.ensureProxy(deviceId);
|
|
34626
34828
|
if (!proxy) return [];
|
|
34627
|
-
|
|
34628
|
-
if (cached && cached.length > 0) return cached;
|
|
34629
|
-
await proxy.state.zones.refresh().catch(() => void 0);
|
|
34630
|
-
return proxy.state.zones.value?.zones ?? [];
|
|
34829
|
+
return (await readSliceOnce(proxy.state.zones))?.zones ?? [];
|
|
34631
34830
|
}
|
|
34632
34831
|
releaseProxy(deviceId) {
|
|
34633
34832
|
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-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BuCI6peK.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.55",
|
|
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",
|