@camstack/addon-post-analysis 1.2.54 → 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-BJ65Xxa1.js → dist-B1CMW3_t.js} +4467 -4266
- package/dist/{dist-BVOgkikr.mjs → dist-D9Bm8RyD.mjs} +4467 -4266
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/{node-DTrkngzv.js → node-cjH3qw1E.js} +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-uW2bbQBi.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BuCI6peK.mjs} +2 -2
- package/dist/pipeline-analytics/{hostInit-BC7FXncY.mjs → hostInit-3GBLWJ6R.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +151 -9
- package/dist/pipeline-analytics/index.mjs +150 -8
- 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);
|
|
@@ -1,4 +1,4 @@
|
|
|
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";
|
|
@@ -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);
|
|
@@ -12452,23 +12452,73 @@ async function makeSquareThumb(bytes, size) {
|
|
|
12452
12452
|
}).jpeg({ quality: THUMB_QUALITY }).toBuffer();
|
|
12453
12453
|
}
|
|
12454
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
|
|
12455
12489
|
//#region src/pipeline-analytics/analytics-query-facade.ts
|
|
12456
12490
|
var AnalyticsQueryFacade = class {
|
|
12457
12491
|
deps;
|
|
12458
12492
|
constructor(deps) {
|
|
12459
12493
|
this.deps = deps;
|
|
12460
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
|
+
}
|
|
12461
12510
|
async getActiveTracks(input) {
|
|
12462
|
-
return this.deps.trackStore()?.getActive(input.deviceId) ?? [];
|
|
12511
|
+
return this.withZoneNames(this.deps.trackStore()?.getActive(input.deviceId) ?? []);
|
|
12463
12512
|
}
|
|
12464
12513
|
async getTrack(input) {
|
|
12465
12514
|
const store = this.deps.trackStore();
|
|
12466
|
-
const
|
|
12467
|
-
if (
|
|
12468
|
-
|
|
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;
|
|
12469
12519
|
}
|
|
12470
12520
|
async listTracks(input) {
|
|
12471
|
-
return this.deps.trackStore()?.queryHistorical(input) ?? [];
|
|
12521
|
+
return this.withZoneNames(await this.deps.trackStore()?.queryHistorical(input) ?? []);
|
|
12472
12522
|
}
|
|
12473
12523
|
/**
|
|
12474
12524
|
* Batched cluster-wide track listing — one merged (`lastSeen` DESC,
|
|
@@ -12478,10 +12528,14 @@ var AnalyticsQueryFacade = class {
|
|
|
12478
12528
|
* indexed page + k-way merge and the cursor encoding.
|
|
12479
12529
|
*/
|
|
12480
12530
|
async listRecentTracks(input) {
|
|
12481
|
-
|
|
12531
|
+
const page = await this.deps.trackStore()?.queryRecent(input) ?? {
|
|
12482
12532
|
tracks: [],
|
|
12483
12533
|
nextCursor: null
|
|
12484
12534
|
};
|
|
12535
|
+
return {
|
|
12536
|
+
...page,
|
|
12537
|
+
tracks: await this.withZoneNames(page.tracks)
|
|
12538
|
+
};
|
|
12485
12539
|
}
|
|
12486
12540
|
/** Per-camera sensor-event history (state changes of linked devices). */
|
|
12487
12541
|
async getSensorEvents(input) {
|
|
@@ -17153,6 +17207,72 @@ var RetrainService = class {
|
|
|
17153
17207
|
}
|
|
17154
17208
|
};
|
|
17155
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
|
|
17156
17276
|
//#region src/pipeline-analytics/media-capture-log.ts
|
|
17157
17277
|
var DEFAULT_FLUSH_INTERVAL_MS$1 = 6e4;
|
|
17158
17278
|
var MediaCaptureLogAggregator = class {
|
|
@@ -31006,6 +31126,27 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
31006
31126
|
* bodies live in `analytics-query-facade.ts`; the class keeps one-line
|
|
31007
31127
|
* delegates because the pipeline-analytics cap registers `this` as its
|
|
31008
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
|
+
});
|
|
31009
31150
|
queryFacade = new AnalyticsQueryFacade({
|
|
31010
31151
|
trackStore: () => this.trackStore,
|
|
31011
31152
|
eventStore: () => this.eventStore,
|
|
@@ -31013,7 +31154,8 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
31013
31154
|
sensorEventStore: () => this.sensorEventStore,
|
|
31014
31155
|
opsLog: () => this.eventsOpsLog,
|
|
31015
31156
|
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
31016
|
-
logger: () => this.ctx.logger
|
|
31157
|
+
logger: () => this.ctx.logger,
|
|
31158
|
+
zoneCatalogue: () => this.zoneCatalogue
|
|
31017
31159
|
});
|
|
31018
31160
|
/** The shared crop extractor (native-res first, detection-frame fallback),
|
|
31019
31161
|
* captured in the constructor so `processFrame` can crop object thumbnails in
|
|
@@ -31367,7 +31509,7 @@ var PipelineAnalyticsAddon = class extends require_dist.BaseAddon {
|
|
|
31367
31509
|
let storage = this.ctx.kernel.storage;
|
|
31368
31510
|
const mediaRoot = process.env.CAMSTACK_MEDIA_ROOT?.trim();
|
|
31369
31511
|
if (mediaRoot) {
|
|
31370
|
-
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-
|
|
31512
|
+
const { FilesystemStorageProvider } = await Promise.resolve().then(() => require("../node-cjH3qw1E.js"));
|
|
31371
31513
|
storage = new FilesystemStorageProvider(mediaRoot, { eventMedia: mediaRoot });
|
|
31372
31514
|
logger.info("pipeline-analytics: event media rooted at CAMSTACK_MEDIA_ROOT", { meta: { mediaRoot } });
|
|
31373
31515
|
}
|
|
@@ -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";
|
|
@@ -12438,23 +12438,73 @@ async function makeSquareThumb(bytes, size) {
|
|
|
12438
12438
|
}).jpeg({ quality: THUMB_QUALITY }).toBuffer();
|
|
12439
12439
|
}
|
|
12440
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
|
|
12441
12475
|
//#region src/pipeline-analytics/analytics-query-facade.ts
|
|
12442
12476
|
var AnalyticsQueryFacade = class {
|
|
12443
12477
|
deps;
|
|
12444
12478
|
constructor(deps) {
|
|
12445
12479
|
this.deps = deps;
|
|
12446
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
|
+
}
|
|
12447
12496
|
async getActiveTracks(input) {
|
|
12448
|
-
return this.deps.trackStore()?.getActive(input.deviceId) ?? [];
|
|
12497
|
+
return this.withZoneNames(this.deps.trackStore()?.getActive(input.deviceId) ?? []);
|
|
12449
12498
|
}
|
|
12450
12499
|
async getTrack(input) {
|
|
12451
12500
|
const store = this.deps.trackStore();
|
|
12452
|
-
const
|
|
12453
|
-
if (
|
|
12454
|
-
|
|
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;
|
|
12455
12505
|
}
|
|
12456
12506
|
async listTracks(input) {
|
|
12457
|
-
return this.deps.trackStore()?.queryHistorical(input) ?? [];
|
|
12507
|
+
return this.withZoneNames(await this.deps.trackStore()?.queryHistorical(input) ?? []);
|
|
12458
12508
|
}
|
|
12459
12509
|
/**
|
|
12460
12510
|
* Batched cluster-wide track listing — one merged (`lastSeen` DESC,
|
|
@@ -12464,10 +12514,14 @@ var AnalyticsQueryFacade = class {
|
|
|
12464
12514
|
* indexed page + k-way merge and the cursor encoding.
|
|
12465
12515
|
*/
|
|
12466
12516
|
async listRecentTracks(input) {
|
|
12467
|
-
|
|
12517
|
+
const page = await this.deps.trackStore()?.queryRecent(input) ?? {
|
|
12468
12518
|
tracks: [],
|
|
12469
12519
|
nextCursor: null
|
|
12470
12520
|
};
|
|
12521
|
+
return {
|
|
12522
|
+
...page,
|
|
12523
|
+
tracks: await this.withZoneNames(page.tracks)
|
|
12524
|
+
};
|
|
12471
12525
|
}
|
|
12472
12526
|
/** Per-camera sensor-event history (state changes of linked devices). */
|
|
12473
12527
|
async getSensorEvents(input) {
|
|
@@ -17139,6 +17193,72 @@ var RetrainService = class {
|
|
|
17139
17193
|
}
|
|
17140
17194
|
};
|
|
17141
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
|
|
17142
17262
|
//#region src/pipeline-analytics/media-capture-log.ts
|
|
17143
17263
|
var DEFAULT_FLUSH_INTERVAL_MS$1 = 6e4;
|
|
17144
17264
|
var MediaCaptureLogAggregator = class {
|
|
@@ -30992,6 +31112,27 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
30992
31112
|
* bodies live in `analytics-query-facade.ts`; the class keeps one-line
|
|
30993
31113
|
* delegates because the pipeline-analytics cap registers `this` as its
|
|
30994
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
|
+
});
|
|
30995
31136
|
queryFacade = new AnalyticsQueryFacade({
|
|
30996
31137
|
trackStore: () => this.trackStore,
|
|
30997
31138
|
eventStore: () => this.eventStore,
|
|
@@ -30999,7 +31140,8 @@ var PipelineAnalyticsAddon = class extends BaseAddon {
|
|
|
30999
31140
|
sensorEventStore: () => this.sensorEventStore,
|
|
31000
31141
|
opsLog: () => this.eventsOpsLog,
|
|
31001
31142
|
eventMediaBaseUrl: () => this.eventMediaBaseUrl,
|
|
31002
|
-
logger: () => this.ctx.logger
|
|
31143
|
+
logger: () => this.ctx.logger,
|
|
31144
|
+
zoneCatalogue: () => this.zoneCatalogue
|
|
31003
31145
|
});
|
|
31004
31146
|
/** The shared crop extractor (native-res first, detection-frame fallback),
|
|
31005
31147
|
* captured in the constructor so `processFrame` can crop object thumbnails in
|
|
@@ -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",
|