@camstack/addon-post-analysis 1.2.213 → 1.2.216
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-DA1sJKXM.js → dist-B_6UZCut.js} +204 -17
- package/dist/{dist-CT2SouNJ.mjs → dist-DWZqNymj.mjs} +204 -17
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CxVox7bu.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BmDXGilB.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CcE47LPf.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dgmog5ky.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-BSVhT9bi.mjs → hostInit-DMDZJw2T.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +761 -201
- package/dist/pipeline-analytics/index.mjs +753 -193
- 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-B_6UZCut.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -10,6 +10,7 @@ let node_path$1 = require_dist.__toESM(node_path, 1);
|
|
|
10
10
|
node_path = require_dist.__toESM(node_path);
|
|
11
11
|
let node_crypto = require("node:crypto");
|
|
12
12
|
let node_child_process = require("node:child_process");
|
|
13
|
+
let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
|
|
13
14
|
let sharp = require("sharp");
|
|
14
15
|
sharp = require_dist.__toESM(sharp);
|
|
15
16
|
let node_os = require("node:os");
|
|
@@ -3223,8 +3224,8 @@ async function raceTimeout(call, timeoutMs) {
|
|
|
3223
3224
|
* detail the encoder can see.
|
|
3224
3225
|
*/
|
|
3225
3226
|
async function downscaleJpeg(bytes, maxPx) {
|
|
3226
|
-
const { default: sharp$
|
|
3227
|
-
const out = await sharp$
|
|
3227
|
+
const { default: sharp$16 } = await import("sharp");
|
|
3228
|
+
const out = await sharp$16(Buffer.from(bytes)).resize({
|
|
3228
3229
|
width: maxPx,
|
|
3229
3230
|
height: maxPx,
|
|
3230
3231
|
fit: "inside",
|
|
@@ -11740,8 +11741,8 @@ var NcDispatcher = class {
|
|
|
11740
11741
|
try {
|
|
11741
11742
|
const points = (await this.deps.getZonePolygons?.(deviceId, zoneIds) ?? []).flat();
|
|
11742
11743
|
if (points.length === 0) return null;
|
|
11743
|
-
const { default: sharp$
|
|
11744
|
-
const img = sharp$
|
|
11744
|
+
const { default: sharp$15 } = await import("sharp");
|
|
11745
|
+
const img = sharp$15(Buffer.from(jpeg));
|
|
11745
11746
|
const meta = await img.metadata();
|
|
11746
11747
|
const W = meta.width ?? 0;
|
|
11747
11748
|
const H = meta.height ?? 0;
|
|
@@ -14920,7 +14921,7 @@ function buildOccupancyStatus(input) {
|
|
|
14920
14921
|
* `this.byId.clear()` happened to sit AFTER the `await` that threw. Nothing
|
|
14921
14922
|
* said so, and hoisting one line would have silently turned every transient
|
|
14922
14923
|
* store error into a hub that stops notifying — the reversal behind the D130
|
|
14923
|
-
* flood and the
|
|
14924
|
+
* flood and the occupancy cold-seed loss. The ledger's contract
|
|
14924
14925
|
* rule 2 (a failed load NEVER clears the mirror, deliberately no knob) makes
|
|
14925
14926
|
* that reversal unexpressible here. `rule-store-load-failure-direction.spec.ts`
|
|
14926
14927
|
* holds the direction and records what was removed to make it red.
|
|
@@ -18137,7 +18138,7 @@ function clockOf$2(atMs) {
|
|
|
18137
18138
|
*
|
|
18138
18139
|
* ## The `.partial()` trap this file exists to avoid
|
|
18139
18140
|
*
|
|
18140
|
-
*
|
|
18141
|
+
* Found in production 2026-08-12: `NcRulePatchSchema` was
|
|
18141
18142
|
* `NcRuleInputSchema.partial()`, and Zod 4 `.partial()` KEEPS the inner
|
|
18142
18143
|
* `.default()`. Parsing `{ name: 'x' }` therefore materialised every defaulted
|
|
18143
18144
|
* key, and `{ ...existing, ...patch }` wrote them over the rule — a camera- and
|
|
@@ -19032,8 +19033,8 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
|
|
|
19032
19033
|
];
|
|
19033
19034
|
if (bbox === void 0) return files;
|
|
19034
19035
|
try {
|
|
19035
|
-
const { default: sharp$
|
|
19036
|
-
const meta = await sharp$
|
|
19036
|
+
const { default: sharp$14 } = await import("sharp");
|
|
19037
|
+
const meta = await sharp$14(Buffer.from(jpeg)).metadata();
|
|
19037
19038
|
const W = meta.width ?? 0;
|
|
19038
19039
|
const H = meta.height ?? 0;
|
|
19039
19040
|
if (W === 0 || H === 0) return files;
|
|
@@ -19041,7 +19042,7 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
|
|
|
19041
19042
|
const top = Math.max(0, Math.min(H - 1, Math.floor(bbox.y * H)));
|
|
19042
19043
|
const width = Math.max(1, Math.min(W - left, Math.ceil(bbox.w * W)));
|
|
19043
19044
|
const height = Math.max(1, Math.min(H - top, Math.ceil(bbox.h * H)));
|
|
19044
|
-
const cropBuf = await sharp$
|
|
19045
|
+
const cropBuf = await sharp$14(Buffer.from(jpeg)).extract({
|
|
19045
19046
|
left,
|
|
19046
19047
|
top,
|
|
19047
19048
|
width,
|
|
@@ -19063,7 +19064,7 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
|
|
|
19063
19064
|
});
|
|
19064
19065
|
const stroke = Math.max(2, Math.round(Math.min(W, H) / 240));
|
|
19065
19066
|
const overlay = Buffer.from(`<svg width="${W}" height="${H}"><rect x="${left}" y="${top}" width="${width}" height="${height}" fill="none" stroke="#22d3ee" stroke-width="${stroke}"/></svg>`);
|
|
19066
|
-
const boxedBuf = await sharp$
|
|
19067
|
+
const boxedBuf = await sharp$14(Buffer.from(jpeg)).composite([{
|
|
19067
19068
|
input: overlay,
|
|
19068
19069
|
top: 0,
|
|
19069
19070
|
left: 0
|
|
@@ -34905,6 +34906,8 @@ var SortTracker = class SortTracker {
|
|
|
34905
34906
|
refusalSampler = new SpawnRefusalSampler();
|
|
34906
34907
|
/** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
|
|
34907
34908
|
spawnRefusalObserver = null;
|
|
34909
|
+
/** Unsampled copy for the frame path — see `setSpawnRefusalSink`. */
|
|
34910
|
+
refusalSink = null;
|
|
34908
34911
|
/** Per-reason counters behind the sampled spawn-beside-coasting diagnostics. */
|
|
34909
34912
|
spawnBesideSampler = new SpawnBesideCoastingSampler();
|
|
34910
34913
|
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
@@ -35017,11 +35020,7 @@ var SortTracker = class SortTracker {
|
|
|
35017
35020
|
}
|
|
35018
35021
|
/** Emit one dropped-detection record, subject to the refusal sampler. */
|
|
35019
35022
|
reportRefusal(reason, det, extra = {}) {
|
|
35020
|
-
const
|
|
35021
|
-
if (!observer) return;
|
|
35022
|
-
const refusalCount = this.refusalSampler.next(reason);
|
|
35023
|
-
if (refusalCount === void 0) return;
|
|
35024
|
-
observer({
|
|
35023
|
+
const base = {
|
|
35025
35024
|
reason,
|
|
35026
35025
|
detClass: det.class,
|
|
35027
35026
|
detScore: det.score,
|
|
@@ -35033,13 +35032,31 @@ var SortTracker = class SortTracker {
|
|
|
35033
35032
|
againstTrackId: extra.against.id,
|
|
35034
35033
|
againstTrackClass: extra.against.class
|
|
35035
35034
|
} : {},
|
|
35036
|
-
refusalCount,
|
|
35037
|
-
sampledEvery: 10,
|
|
35038
35035
|
...extra.droppedTrackId !== void 0 ? { droppedTrackId: extra.droppedTrackId } : {},
|
|
35039
35036
|
...extra.trackLifeMs !== void 0 ? { trackLifeMs: extra.trackLifeMs } : {}
|
|
35037
|
+
};
|
|
35038
|
+
this.refusalSink?.(base);
|
|
35039
|
+
const observer = this.spawnRefusalObserver;
|
|
35040
|
+
if (!observer) return;
|
|
35041
|
+
const refusalCount = this.refusalSampler.next(reason);
|
|
35042
|
+
if (refusalCount === void 0) return;
|
|
35043
|
+
observer({
|
|
35044
|
+
...base,
|
|
35045
|
+
refusalCount,
|
|
35046
|
+
sampledEvery: 10
|
|
35040
35047
|
});
|
|
35041
35048
|
}
|
|
35042
35049
|
/**
|
|
35050
|
+
* Wire the UNSAMPLED refusal sink — the frame path's copy.
|
|
35051
|
+
*
|
|
35052
|
+
* Separate from {@link setSpawnRefusalObserver} because the two want
|
|
35053
|
+
* different things from the same record: the observer wants a readable log
|
|
35054
|
+
* and accepts a sample; this decides whether an EVENT exists.
|
|
35055
|
+
*/
|
|
35056
|
+
setSpawnRefusalSink(sink) {
|
|
35057
|
+
this.refusalSink = sink;
|
|
35058
|
+
}
|
|
35059
|
+
/**
|
|
35043
35060
|
* Report a TRACK that ends having emitted nothing — the last silent branch
|
|
35044
35061
|
* on the detection→position path (D391).
|
|
35045
35062
|
*
|
|
@@ -36133,6 +36150,20 @@ var FrameProcessor = class {
|
|
|
36133
36150
|
/** Sink + sampler for the drops this stage owns (the detection-stage zone
|
|
36134
36151
|
* filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
|
|
36135
36152
|
spawnRefusalObserver = null;
|
|
36153
|
+
/** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
|
|
36154
|
+
spawnRefusalForward = null;
|
|
36155
|
+
/**
|
|
36156
|
+
* The spawn refusals of the frame being processed.
|
|
36157
|
+
*
|
|
36158
|
+
* Filled by the same sink that feeds the diagnostic log, and drained at the
|
|
36159
|
+
* end of `process`. It exists because a refused detection inside a watched
|
|
36160
|
+
* zone still has to become an EVENT: an immediate zone rule asks "is there an
|
|
36161
|
+
* animal in the flowerbed now?", which does not need "is it the same animal
|
|
36162
|
+
* as before?" answered first. See `zone-detection-emits-without-a-track`.
|
|
36163
|
+
*/
|
|
36164
|
+
refusalsThisFrame = [];
|
|
36165
|
+
/** Running count of events this path has produced — the volume measure. */
|
|
36166
|
+
zoneRefusalEventCount = 0;
|
|
36136
36167
|
zoneRefusalSampler = new SpawnRefusalSampler();
|
|
36137
36168
|
/** Optional stationary-object gate (parked-object suppression). Null until
|
|
36138
36169
|
* the addon wires it via {@link setStationaryGate}. */
|
|
@@ -36147,6 +36178,9 @@ var FrameProcessor = class {
|
|
|
36147
36178
|
this.deviceId = deviceId;
|
|
36148
36179
|
this.source = source;
|
|
36149
36180
|
this.tracker = new SortTracker(trackerConfig);
|
|
36181
|
+
this.tracker.setSpawnRefusalSink((record) => {
|
|
36182
|
+
this.refusalsThisFrame.push(record);
|
|
36183
|
+
});
|
|
36150
36184
|
this.stateAnalyzer = new StateAnalyzer(stateConfig);
|
|
36151
36185
|
this.eventEmitter = new DetectionEventEmitter(emitterConfig);
|
|
36152
36186
|
this.zones = [];
|
|
@@ -36253,6 +36287,55 @@ var FrameProcessor = class {
|
|
|
36253
36287
|
* ate it. Sampled like every other refusal: an excluded box is excluded on
|
|
36254
36288
|
* every frame of a passage.
|
|
36255
36289
|
*/
|
|
36290
|
+
/**
|
|
36291
|
+
* Turn this frame's refused spawns into events, but only those inside a zone.
|
|
36292
|
+
*
|
|
36293
|
+
* Drains {@link refusalsThisFrame}. Each surviving record becomes an
|
|
36294
|
+
* `object.detected` event with NO track — carrying the zones it fell in, the
|
|
36295
|
+
* percentage of the box inside each, and the refusal reason so a consumer can
|
|
36296
|
+
* tell it apart from a track whose media went missing.
|
|
36297
|
+
*
|
|
36298
|
+
* `zone-filtered` refusals are excluded on purpose: that reason means the
|
|
36299
|
+
* operator's own detection rules already said the box does not count on this
|
|
36300
|
+
* camera, and re-admitting it here would overrule a decision they made.
|
|
36301
|
+
*/
|
|
36302
|
+
eventsForRefusedDetectionsInZones(timestamp, frame, frameWidth, frameHeight) {
|
|
36303
|
+
const refusals = this.refusalsThisFrame;
|
|
36304
|
+
this.refusalsThisFrame = [];
|
|
36305
|
+
if (refusals.length === 0 || this.zones.length === 0) return [];
|
|
36306
|
+
const out = [];
|
|
36307
|
+
for (const r of refusals) {
|
|
36308
|
+
if (r.reason === "zone-filtered") continue;
|
|
36309
|
+
const { memberships } = this.zoneEngine.splitDetectionZones(r.bbox, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
|
|
36310
|
+
if (memberships.length === 0) continue;
|
|
36311
|
+
this.zoneRefusalEventCount += 1;
|
|
36312
|
+
out.push({
|
|
36313
|
+
id: (0, node_crypto.randomUUID)(),
|
|
36314
|
+
kind: "object",
|
|
36315
|
+
deviceId: this.deviceId,
|
|
36316
|
+
timestamp,
|
|
36317
|
+
frameId: frame.frameId,
|
|
36318
|
+
className: r.detClass,
|
|
36319
|
+
confidence: r.detScore,
|
|
36320
|
+
bbox: r.bbox,
|
|
36321
|
+
frameWidth,
|
|
36322
|
+
frameHeight,
|
|
36323
|
+
zones: memberships.map((m) => m.zoneId),
|
|
36324
|
+
zoneOverlaps: memberships.map((m) => ({
|
|
36325
|
+
zoneId: m.zoneId,
|
|
36326
|
+
...m.zoneName === void 0 ? {} : { zoneName: m.zoneName },
|
|
36327
|
+
overlapPct: Math.round(m.overlap * 1e3) / 10
|
|
36328
|
+
})),
|
|
36329
|
+
spawnRefused: r.reason
|
|
36330
|
+
});
|
|
36331
|
+
}
|
|
36332
|
+
return out;
|
|
36333
|
+
}
|
|
36334
|
+
/** How many zone-refusal events this processor has emitted — the measure that
|
|
36335
|
+
* says whether this path is a trickle or a storm. Read by the addon. */
|
|
36336
|
+
zoneRefusalEventsEmitted() {
|
|
36337
|
+
return this.zoneRefusalEventCount;
|
|
36338
|
+
}
|
|
36256
36339
|
reportZoneFiltered(excluded) {
|
|
36257
36340
|
const observer = this.spawnRefusalObserver;
|
|
36258
36341
|
if (!observer || excluded.length === 0) return;
|
|
@@ -36654,9 +36737,11 @@ var FrameProcessor = class {
|
|
|
36654
36737
|
frameHeight
|
|
36655
36738
|
};
|
|
36656
36739
|
};
|
|
36740
|
+
const zoneRefusalEvents = this.eventsForRefusedDetectionsInZones(timestamp, frame, frameWidth, frameHeight);
|
|
36657
36741
|
const withTrackId = rawEvents.filter((e) => e.detection.trackId);
|
|
36658
36742
|
const objectEvents = withTrackId.filter((e) => e.type !== "object.detected").map((e) => toObjectEvent(e));
|
|
36659
36743
|
const appearanceEvents = withTrackId.filter((e) => e.type === "object.detected").map((e) => toObjectEvent(e, "entered"));
|
|
36744
|
+
objectEvents.push(...zoneRefusalEvents);
|
|
36660
36745
|
return {
|
|
36661
36746
|
deviceId: this.deviceId,
|
|
36662
36747
|
timestamp,
|
|
@@ -47319,6 +47404,7 @@ var TrackDetailDispatcher = class {
|
|
|
47319
47404
|
if (!dev) return;
|
|
47320
47405
|
dev.scheduler.onTrackEnded(trackId);
|
|
47321
47406
|
dev.tracks.delete(trackId);
|
|
47407
|
+
dev.parcelEnriched.delete(trackId);
|
|
47322
47408
|
dev.candidateBest.delete(trackId);
|
|
47323
47409
|
if (dev.tracks.size === 0) this.clearTimer(dev);
|
|
47324
47410
|
}
|
|
@@ -47328,6 +47414,7 @@ var TrackDetailDispatcher = class {
|
|
|
47328
47414
|
this.clearTimer(dev);
|
|
47329
47415
|
dev.queue.length = 0;
|
|
47330
47416
|
dev.tracks.clear();
|
|
47417
|
+
dev.parcelEnriched.clear();
|
|
47331
47418
|
dev.candidateBest.clear();
|
|
47332
47419
|
}
|
|
47333
47420
|
this.devices.clear();
|
|
@@ -47338,6 +47425,7 @@ var TrackDetailDispatcher = class {
|
|
|
47338
47425
|
dev = {
|
|
47339
47426
|
scheduler: new DetailScheduler(),
|
|
47340
47427
|
tracks: /* @__PURE__ */ new Map(),
|
|
47428
|
+
parcelEnriched: /* @__PURE__ */ new Map(),
|
|
47341
47429
|
candidateBest: new BestDetectionTracker(CANDIDATE_IMPROVE_POLICY),
|
|
47342
47430
|
queue: [],
|
|
47343
47431
|
coastedFramesIgnored: 0,
|
|
@@ -47454,19 +47542,21 @@ var TrackDetailDispatcher = class {
|
|
|
47454
47542
|
*
|
|
47455
47543
|
* 1. `frameHandle` — cheapest when it hits: no payload moves and the runner's
|
|
47456
47544
|
* leaf children re-cut their ROI at native resolution. Tried first, always.
|
|
47457
|
-
* 2. `frameJpeg` — the
|
|
47458
|
-
*
|
|
47459
|
-
*
|
|
47460
|
-
*
|
|
47461
|
-
*
|
|
47462
|
-
* the
|
|
47545
|
+
* 2. `frameJpeg` — the track's PARKED key frame (D422): the full native frame
|
|
47546
|
+
* the owning node copied at the best instant, retrieved by track and handed
|
|
47547
|
+
* to the runner UNCUT, so the rectangle still comes from the single
|
|
47548
|
+
* derivation (D52). This rung exists because rung 1 is structurally
|
|
47549
|
+
* unreachable for most dispatches — a tick + inference routinely lands
|
|
47550
|
+
* outside the lease — and it cannot miss on age. It runs ONCE per parcel
|
|
47551
|
+
* per step; a dispatch that finds the same parcel already enriched skips
|
|
47552
|
+
* it, so a coasting track does not re-embed the same image every second.
|
|
47463
47553
|
* 3. `cropJpeg` — the pre-cut tile. Last, because the caller decided its
|
|
47464
47554
|
* rectangle and because in practice the same native miss that emptied rung 1
|
|
47465
47555
|
* empties this one too.
|
|
47466
47556
|
*
|
|
47467
|
-
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG
|
|
47468
|
-
* demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47469
|
-
* rung 1 missed — never a frame-rate stream, and never raw pixels.
|
|
47557
|
+
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG retrieved
|
|
47558
|
+
* on demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47559
|
+
* after rung 1 missed — never a frame-rate stream, and never raw pixels.
|
|
47470
47560
|
*
|
|
47471
47561
|
* ONE outcome stops the ladder cold: the runner's typed `not-attached`
|
|
47472
47562
|
* refusal (`detail-refusal.ts`). It names the camera's SESSION state, not
|
|
@@ -47504,34 +47594,39 @@ var TrackDetailDispatcher = class {
|
|
|
47504
47594
|
}
|
|
47505
47595
|
});
|
|
47506
47596
|
}
|
|
47507
|
-
if (this.deps.
|
|
47508
|
-
const
|
|
47509
|
-
if (
|
|
47597
|
+
if (this.deps.captureParkedFrame !== void 0) try {
|
|
47598
|
+
const parked = await this.deps.captureParkedFrame(deviceId, req.trackId);
|
|
47599
|
+
if (parked !== null) if (dev.parcelEnriched.get(req.trackId)?.get(req.stepId) === parked.timestamp) this.deps.logger.debug("detail parcel already enriched for this step — skipping", {
|
|
47510
47600
|
tags: { deviceId },
|
|
47511
47601
|
meta: {
|
|
47512
47602
|
trackId: req.trackId,
|
|
47513
47603
|
stepId: req.stepId,
|
|
47514
|
-
|
|
47515
|
-
frameHeight: full.height,
|
|
47516
|
-
bboxFrameWidth: frame.frameWidth,
|
|
47517
|
-
bboxFrameHeight: frame.frameHeight
|
|
47604
|
+
parkedTimestamp: parked.timestamp
|
|
47518
47605
|
}
|
|
47519
47606
|
});
|
|
47520
47607
|
else {
|
|
47521
47608
|
const derived = await this.deps.runDetailSubtree({
|
|
47522
47609
|
deviceId,
|
|
47523
|
-
frameJpeg:
|
|
47524
|
-
parent
|
|
47610
|
+
frameJpeg: parked.base64,
|
|
47611
|
+
parent: {
|
|
47612
|
+
bbox: { ...parked.bbox },
|
|
47613
|
+
className: frame.className
|
|
47614
|
+
},
|
|
47525
47615
|
steps
|
|
47526
|
-
},
|
|
47527
|
-
if (derived !== null)
|
|
47616
|
+
}, parked.nodeId);
|
|
47617
|
+
if (derived !== null) {
|
|
47618
|
+
const perStep = dev.parcelEnriched.get(req.trackId) ?? /* @__PURE__ */ new Map();
|
|
47619
|
+
perStep.set(req.stepId, parked.timestamp);
|
|
47620
|
+
dev.parcelEnriched.set(req.trackId, perStep);
|
|
47621
|
+
return derived.details;
|
|
47622
|
+
}
|
|
47528
47623
|
}
|
|
47529
47624
|
} catch (err) {
|
|
47530
47625
|
if (isDetailNotAttachedRefusal(err)) {
|
|
47531
47626
|
this.warnNotAttachedThrottled(deviceId, dev, req);
|
|
47532
47627
|
return null;
|
|
47533
47628
|
}
|
|
47534
|
-
this.deps.logger.debug("detail
|
|
47629
|
+
this.deps.logger.debug("detail parked-frame rung failed", {
|
|
47535
47630
|
tags: { deviceId },
|
|
47536
47631
|
meta: {
|
|
47537
47632
|
trackId: req.trackId,
|
|
@@ -47598,7 +47693,7 @@ var TrackDetailDispatcher = class {
|
|
|
47598
47693
|
trackId: req.trackId,
|
|
47599
47694
|
stepId: req.stepId,
|
|
47600
47695
|
reason: req.reason,
|
|
47601
|
-
|
|
47696
|
+
triedParkedFrame: this.deps.captureParkedFrame !== void 0,
|
|
47602
47697
|
triedCropJpeg: this.deps.captureCropBase64 !== void 0
|
|
47603
47698
|
}
|
|
47604
47699
|
});
|
|
@@ -49560,6 +49655,87 @@ var FaceRecognizer = class {
|
|
|
49560
49655
|
}
|
|
49561
49656
|
}
|
|
49562
49657
|
};
|
|
49658
|
+
/** Read the window. Pure — this is the judgement the module exists to make. */
|
|
49659
|
+
function lagVerdict(report) {
|
|
49660
|
+
const bus = report.busLagMaxMs;
|
|
49661
|
+
if (bus === null) return "unknown";
|
|
49662
|
+
return bus >= 5e3 ? "starved" : "delivered-on-time";
|
|
49663
|
+
}
|
|
49664
|
+
/** Does this window need an operator's eyes? Unknown never counts as alarming
|
|
49665
|
+
* — that is the fabricated verdict D8 forbids. */
|
|
49666
|
+
function isAlarmingLagReport(report) {
|
|
49667
|
+
const bus = report.busLagMaxMs;
|
|
49668
|
+
const capture = report.captureLagMaxMs;
|
|
49669
|
+
return bus !== null && bus >= 5e3 || capture !== null && capture >= 5e3;
|
|
49670
|
+
}
|
|
49671
|
+
/**
|
|
49672
|
+
* Folds per-frame delivery lag into per-device windows.
|
|
49673
|
+
*
|
|
49674
|
+
* Not a logger: it returns the line to write (or null) and the caller decides
|
|
49675
|
+
* level and tags. That keeps the throttle — the part that can wreck the frame
|
|
49676
|
+
* path — testable without a logger fake.
|
|
49677
|
+
*/
|
|
49678
|
+
var FrameLagObserver = class {
|
|
49679
|
+
windows = /* @__PURE__ */ new Map();
|
|
49680
|
+
/**
|
|
49681
|
+
* Fold one inference result into `deviceId`'s window.
|
|
49682
|
+
*
|
|
49683
|
+
* @param now Receipt time, read by the caller once at handler entry.
|
|
49684
|
+
* @param emittedAt Runner-side emit stamp; undefined ⇒ unknown (D8).
|
|
49685
|
+
* @param capturedAt Frame shm-commit stamp; undefined ⇒ unknown.
|
|
49686
|
+
* @returns The closed window when this frame ends one, else null.
|
|
49687
|
+
*/
|
|
49688
|
+
observe(deviceId, now, emittedAt, capturedAt) {
|
|
49689
|
+
const existing = this.windows.get(deviceId);
|
|
49690
|
+
const w = existing ?? {
|
|
49691
|
+
startedAt: now,
|
|
49692
|
+
frames: 0,
|
|
49693
|
+
busMax: null,
|
|
49694
|
+
busMin: null,
|
|
49695
|
+
busUnknown: 0,
|
|
49696
|
+
captureMax: null,
|
|
49697
|
+
captureUnknown: 0
|
|
49698
|
+
};
|
|
49699
|
+
if (existing === void 0) this.windows.set(deviceId, w);
|
|
49700
|
+
w.frames += 1;
|
|
49701
|
+
if (typeof emittedAt === "number") {
|
|
49702
|
+
const busLag = now - emittedAt;
|
|
49703
|
+
if (w.busMax === null || busLag > w.busMax) w.busMax = busLag;
|
|
49704
|
+
if (w.busMin === null || busLag < w.busMin) w.busMin = busLag;
|
|
49705
|
+
} else w.busUnknown += 1;
|
|
49706
|
+
if (typeof capturedAt === "number") {
|
|
49707
|
+
const captureLag = now - capturedAt;
|
|
49708
|
+
if (w.captureMax === null || captureLag > w.captureMax) w.captureMax = captureLag;
|
|
49709
|
+
} else w.captureUnknown += 1;
|
|
49710
|
+
if (existing !== void 0 && now - w.startedAt < 3e4) return null;
|
|
49711
|
+
const report = {
|
|
49712
|
+
deviceId,
|
|
49713
|
+
frames: w.frames,
|
|
49714
|
+
busLagMaxMs: w.busMax,
|
|
49715
|
+
busLagMinMs: w.busMin,
|
|
49716
|
+
busLagUnknownFrames: w.busUnknown,
|
|
49717
|
+
captureLagMaxMs: w.captureMax,
|
|
49718
|
+
captureLagUnknownFrames: w.captureUnknown,
|
|
49719
|
+
windowMs: now - w.startedAt
|
|
49720
|
+
};
|
|
49721
|
+
w.startedAt = now;
|
|
49722
|
+
w.frames = 0;
|
|
49723
|
+
w.busMax = null;
|
|
49724
|
+
w.busMin = null;
|
|
49725
|
+
w.busUnknown = 0;
|
|
49726
|
+
w.captureMax = null;
|
|
49727
|
+
w.captureUnknown = 0;
|
|
49728
|
+
return report;
|
|
49729
|
+
}
|
|
49730
|
+
/** Drop a device's window (unbound camera, shutdown). */
|
|
49731
|
+
forget(deviceId) {
|
|
49732
|
+
this.windows.delete(deviceId);
|
|
49733
|
+
}
|
|
49734
|
+
/** Drop every window. */
|
|
49735
|
+
clear() {
|
|
49736
|
+
this.windows.clear();
|
|
49737
|
+
}
|
|
49738
|
+
};
|
|
49563
49739
|
//#endregion
|
|
49564
49740
|
//#region src/pipeline-analytics/location-aware-media-storage.ts
|
|
49565
49741
|
/**
|
|
@@ -53882,6 +54058,17 @@ async function encodeKeyFrameVariants(native) {
|
|
|
53882
54058
|
})
|
|
53883
54059
|
};
|
|
53884
54060
|
}
|
|
54061
|
+
/**
|
|
54062
|
+
* The web companion from the PARKED key frame's own JPEG (D422): the parcel is
|
|
54063
|
+
* stored verbatim as `keyFrame`, and `keyFrameSmall` is this — the same bytes
|
|
54064
|
+
* downscaled to {@link KEYFRAME_SMALL_MAX_WIDTH}. One decode, one encode,
|
|
54065
|
+
* never a second fetch; `withoutEnlargement` never upscales a sub-960 frame.
|
|
54066
|
+
* The by-handle twin ({@link encodeKeyFrameVariants}) remains for the
|
|
54067
|
+
* summary group shot, which still fetches a native surface by handle.
|
|
54068
|
+
*/
|
|
54069
|
+
async function deriveKeyFrameSmall(keyFrameJpeg) {
|
|
54070
|
+
return (0, sharp.default)(keyFrameJpeg).resize(960, null, { withoutEnlargement: true }).jpeg({ quality: 88 }).toBuffer();
|
|
54071
|
+
}
|
|
53885
54072
|
//#endregion
|
|
53886
54073
|
//#region src/pipeline-analytics/pipeline/key-frame-metrics.ts
|
|
53887
54074
|
var DEFAULT_FLUSH_INTERVAL_MS = 6e4;
|
|
@@ -53907,11 +54094,8 @@ var KeyFrameCaptureLog = class {
|
|
|
53907
54094
|
w.landed += 1;
|
|
53908
54095
|
if (sample.retry) w.retryLanded += 1;
|
|
53909
54096
|
break;
|
|
53910
|
-
case "
|
|
53911
|
-
w.
|
|
53912
|
-
break;
|
|
53913
|
-
case "ram-tier":
|
|
53914
|
-
w.ramTier += 1;
|
|
54097
|
+
case "parcel-missing":
|
|
54098
|
+
w.parcelMissing += 1;
|
|
53915
54099
|
break;
|
|
53916
54100
|
case "error":
|
|
53917
54101
|
w.error += 1;
|
|
@@ -53940,8 +54124,7 @@ var KeyFrameCaptureLog = class {
|
|
|
53940
54124
|
landed: 0,
|
|
53941
54125
|
retryAttempts: 0,
|
|
53942
54126
|
retryLanded: 0,
|
|
53943
|
-
|
|
53944
|
-
ramTier: 0,
|
|
54127
|
+
parcelMissing: 0,
|
|
53945
54128
|
error: 0,
|
|
53946
54129
|
skippedNodeOffline: 0
|
|
53947
54130
|
};
|
|
@@ -53956,8 +54139,7 @@ var KeyFrameCaptureLog = class {
|
|
|
53956
54139
|
landed: w.landed,
|
|
53957
54140
|
retryAttempts: w.retryAttempts,
|
|
53958
54141
|
retryLanded: w.retryLanded,
|
|
53959
|
-
|
|
53960
|
-
ramTier: w.ramTier,
|
|
54142
|
+
parcelMissing: w.parcelMissing,
|
|
53961
54143
|
error: w.error,
|
|
53962
54144
|
skippedNodeOffline: w.skippedNodeOffline
|
|
53963
54145
|
};
|
|
@@ -53966,63 +54148,44 @@ var KeyFrameCaptureLog = class {
|
|
|
53966
54148
|
//#endregion
|
|
53967
54149
|
//#region src/pipeline-analytics/pipeline/key-frame-persist.ts
|
|
53968
54150
|
/**
|
|
53969
|
-
*
|
|
54151
|
+
* Read, derive and store one track's key frame + its web companion.
|
|
53970
54152
|
*
|
|
53971
|
-
*
|
|
53972
|
-
*
|
|
53973
|
-
* outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
54153
|
+
* PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
|
|
54154
|
+
* parks a later frame. The `keyFrame` write lands FIRST and its key is what
|
|
54155
|
+
* the outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
53974
54156
|
* retry that would re-shoot a key frame the rebuild can already read.
|
|
53975
54157
|
*/
|
|
53976
54158
|
async function persistTrackKeyFrame(deps, input) {
|
|
53977
|
-
const { deviceId, trackId,
|
|
54159
|
+
const { deviceId, trackId, retry } = input;
|
|
53978
54160
|
const drop = {
|
|
53979
54161
|
tags: { deviceId },
|
|
53980
54162
|
meta: {
|
|
53981
54163
|
trackId,
|
|
53982
|
-
nodeId: frameHandle.nodeId,
|
|
53983
54164
|
retry
|
|
53984
54165
|
}
|
|
53985
54166
|
};
|
|
53986
54167
|
try {
|
|
53987
|
-
const
|
|
53988
|
-
|
|
53989
|
-
|
|
53990
|
-
|
|
53991
|
-
...drop,
|
|
53992
|
-
meta: {
|
|
53993
|
-
...drop.meta,
|
|
53994
|
-
shmId: frameHandle.shmId
|
|
53995
|
-
}
|
|
53996
|
-
});
|
|
53997
|
-
return { kind: "native-miss" };
|
|
54168
|
+
const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
|
|
54169
|
+
if (parcel === null || parcel.jpeg.byteLength === 0) {
|
|
54170
|
+
deps.logger.error("key-frame parcel missing — nothing to write, retry parks a later frame", { ...drop });
|
|
54171
|
+
return { kind: "parcel-missing" };
|
|
53998
54172
|
}
|
|
53999
|
-
if (!keyFrameAcceptsTier(result.tier)) {
|
|
54000
|
-
deps.logger.warn("keyFrame native miss — RAM tier rejected, will retry while budget remains", {
|
|
54001
|
-
...drop,
|
|
54002
|
-
meta: {
|
|
54003
|
-
...drop.meta,
|
|
54004
|
-
shmId: frameHandle.shmId,
|
|
54005
|
-
tier: result.tier
|
|
54006
|
-
}
|
|
54007
|
-
});
|
|
54008
|
-
return { kind: "ram-tier" };
|
|
54009
|
-
}
|
|
54010
|
-
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
|
|
54011
54173
|
const keyFrameKey = await deps.media.putReplacing({
|
|
54012
54174
|
deviceId,
|
|
54013
54175
|
ownerKind: "track",
|
|
54014
54176
|
ownerId: trackId,
|
|
54015
54177
|
kind: "keyFrame",
|
|
54016
|
-
timestamp:
|
|
54017
|
-
data:
|
|
54178
|
+
timestamp: parcel.timestamp,
|
|
54179
|
+
data: parcel.jpeg
|
|
54018
54180
|
});
|
|
54019
54181
|
try {
|
|
54182
|
+
const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
|
|
54020
54183
|
await deps.media.putReplacing({
|
|
54021
54184
|
deviceId,
|
|
54022
54185
|
ownerKind: "track",
|
|
54023
54186
|
ownerId: trackId,
|
|
54024
54187
|
kind: "keyFrameSmall",
|
|
54025
|
-
timestamp:
|
|
54188
|
+
timestamp: parcel.timestamp,
|
|
54026
54189
|
data: keyFrameSmall
|
|
54027
54190
|
});
|
|
54028
54191
|
} catch (err) {
|
|
@@ -54030,17 +54193,29 @@ async function persistTrackKeyFrame(deps, input) {
|
|
|
54030
54193
|
...drop,
|
|
54031
54194
|
meta: {
|
|
54032
54195
|
...drop.meta,
|
|
54196
|
+
nodeId: parcel.nodeId,
|
|
54033
54197
|
error: require_dist.errMsg(err)
|
|
54034
54198
|
}
|
|
54035
54199
|
});
|
|
54036
54200
|
}
|
|
54201
|
+
deps.logger.debug("key frame written from the parked parcel", {
|
|
54202
|
+
...drop,
|
|
54203
|
+
meta: {
|
|
54204
|
+
...drop.meta,
|
|
54205
|
+
nodeId: parcel.nodeId,
|
|
54206
|
+
width: parcel.width,
|
|
54207
|
+
height: parcel.height,
|
|
54208
|
+
bytes: parcel.jpeg.byteLength,
|
|
54209
|
+
parkedTimestamp: parcel.timestamp
|
|
54210
|
+
}
|
|
54211
|
+
});
|
|
54037
54212
|
return {
|
|
54038
54213
|
kind: "landed",
|
|
54039
54214
|
keyFrameKey
|
|
54040
54215
|
};
|
|
54041
54216
|
} catch (err) {
|
|
54042
54217
|
const error = require_dist.errMsg(err);
|
|
54043
|
-
deps.logger.warn("key-frame
|
|
54218
|
+
deps.logger.warn("key-frame write failed", {
|
|
54044
54219
|
...drop,
|
|
54045
54220
|
meta: {
|
|
54046
54221
|
...drop.meta,
|
|
@@ -54227,6 +54402,61 @@ function createKeyFrameCrop(deps) {
|
|
|
54227
54402
|
};
|
|
54228
54403
|
}
|
|
54229
54404
|
//#endregion
|
|
54405
|
+
//#region src/pipeline-analytics/pipeline/track-park.ts
|
|
54406
|
+
/** The whole frame — what a `keyFrame` park cuts. */
|
|
54407
|
+
var FULL_FRAME = {
|
|
54408
|
+
x: 0,
|
|
54409
|
+
y: 0,
|
|
54410
|
+
w: 1,
|
|
54411
|
+
h: 1
|
|
54412
|
+
};
|
|
54413
|
+
/** The two parcels a best-frame decision orders, in a fixed order. */
|
|
54414
|
+
function buildParkOrders(decision) {
|
|
54415
|
+
const subject = {
|
|
54416
|
+
bbox: { ...decision.bbox },
|
|
54417
|
+
frameWidth: decision.frameWidth,
|
|
54418
|
+
frameHeight: decision.frameHeight
|
|
54419
|
+
};
|
|
54420
|
+
const layout = deliveredWideCentralSquareLayout(decision.bbox, {
|
|
54421
|
+
W: decision.frameWidth,
|
|
54422
|
+
H: decision.frameHeight
|
|
54423
|
+
});
|
|
54424
|
+
const base = {
|
|
54425
|
+
deviceId: decision.deviceId,
|
|
54426
|
+
trackId: decision.trackId,
|
|
54427
|
+
handle: decision.frameHandle,
|
|
54428
|
+
timestamp: decision.timestamp,
|
|
54429
|
+
subject
|
|
54430
|
+
};
|
|
54431
|
+
return [{
|
|
54432
|
+
...base,
|
|
54433
|
+
kind: "keyFrame",
|
|
54434
|
+
bbox: FULL_FRAME,
|
|
54435
|
+
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
54436
|
+
}, {
|
|
54437
|
+
...base,
|
|
54438
|
+
kind: "thumbnail",
|
|
54439
|
+
bbox: layout.fetch,
|
|
54440
|
+
layout
|
|
54441
|
+
}];
|
|
54442
|
+
}
|
|
54443
|
+
/**
|
|
54444
|
+
* The subject's box in the PARCEL's pixel space. The box was measured on the
|
|
54445
|
+
* ≤640 detection raster; the parcel is native-resolution. Both show the same
|
|
54446
|
+
* scene at the same aspect, so a uniform per-axis scale places the box on the
|
|
54447
|
+
* parcel — the runner then derives the crop rectangle from it (D52).
|
|
54448
|
+
*/
|
|
54449
|
+
function scaleSubjectIntoParcel(subject, parcelWidth, parcelHeight) {
|
|
54450
|
+
const sx = parcelWidth / subject.frameWidth;
|
|
54451
|
+
const sy = parcelHeight / subject.frameHeight;
|
|
54452
|
+
return {
|
|
54453
|
+
x: subject.bbox.x * sx,
|
|
54454
|
+
y: subject.bbox.y * sy,
|
|
54455
|
+
w: subject.bbox.w * sx,
|
|
54456
|
+
h: subject.bbox.h * sy
|
|
54457
|
+
};
|
|
54458
|
+
}
|
|
54459
|
+
//#endregion
|
|
54230
54460
|
//#region src/pipeline-analytics/pipeline/native-detection.ts
|
|
54231
54461
|
/**
|
|
54232
54462
|
* Nominal frame size used to denormalize native `[0,1]` boxes when a
|
|
@@ -58757,7 +58987,28 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
58757
58987
|
}
|
|
58758
58988
|
//#endregion
|
|
58759
58989
|
//#region src/pipeline-analytics/summary/summary-store.ts
|
|
58990
|
+
/**
|
|
58991
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
58992
|
+
* write="one row per SUMMARY — a window of co-present tracks on one camera
|
|
58993
|
+
* group. Opened by `openOrAbsorb` when the first member arrives, updated
|
|
58994
|
+
* in place while the window absorbs more, and finalised by `seal` when the
|
|
58995
|
+
* window closes. Per WINDOW, never per frame or per track: the sealed
|
|
58996
|
+
* summary is what a notification rule reads."
|
|
58997
|
+
* retention="age-keyed on `sealedAt`, swept with the rest of the analytics
|
|
58998
|
+
* retention. An unsealed row belongs to a window still open and is never
|
|
58999
|
+
* swept by age — it is closed or cascaded away with its members."
|
|
59000
|
+
*/
|
|
58760
59001
|
var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
|
|
59002
|
+
/**
|
|
59003
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
59004
|
+
* write="one row per (summary, track) membership, written as a window
|
|
59005
|
+
* absorbs a track. The join that lets a sealed summary name its subjects
|
|
59006
|
+
* without re-reading the track store."
|
|
59007
|
+
* retention="cascaded — a member dies with its summary (`delete-track-cascade`
|
|
59008
|
+
* counts them as leaves) or with the track it names. It carries no clock of
|
|
59009
|
+
* its own: an age sweep here could outlive or predecease the summary the
|
|
59010
|
+
* row only means anything inside of."
|
|
59011
|
+
*/
|
|
58761
59012
|
var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
|
|
58762
59013
|
var SUMMARIES_COLUMNS = [
|
|
58763
59014
|
{
|
|
@@ -63585,35 +63836,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63585
63836
|
const getRemoteFrame = async (handle) => requestNativeCrop(handle, FULL_FRAME_BBOX, handle.width, replyToFullFrame);
|
|
63586
63837
|
const fetchNativeFullFrameTiered = async (handle, maxWidth) => requestNativeCrop(handle, FULL_FRAME_BBOX, maxWidth, replyToTieredFullFrame);
|
|
63587
63838
|
const getNativeFullFrameRgb = async (handle, maxWidth) => (await fetchNativeFullFrameTiered(handle, maxWidth))?.frame ?? null;
|
|
63588
|
-
const getFullFrameJpegBase64 = async (handle, maxWidth) => {
|
|
63589
|
-
if (!pipelineRunnerApi?.getNativeCrop) return null;
|
|
63590
|
-
try {
|
|
63591
|
-
const full = await pipelineRunnerApi.getNativeCrop.query({
|
|
63592
|
-
handle,
|
|
63593
|
-
bbox: {
|
|
63594
|
-
x: 0,
|
|
63595
|
-
y: 0,
|
|
63596
|
-
w: 1,
|
|
63597
|
-
h: 1
|
|
63598
|
-
},
|
|
63599
|
-
maxWidth,
|
|
63600
|
-
encodeJpeg: true
|
|
63601
|
-
}, require_dist.nodePin(handle.nodeId));
|
|
63602
|
-
if (!full?.jpeg || full.width <= 0 || full.height <= 0) return null;
|
|
63603
|
-
return {
|
|
63604
|
-
base64: full.jpeg,
|
|
63605
|
-
width: full.width,
|
|
63606
|
-
height: full.height,
|
|
63607
|
-
tier: full.tier === "ram-fullframe" ? "ram-fullframe" : "native"
|
|
63608
|
-
};
|
|
63609
|
-
} catch (err) {
|
|
63610
|
-
logger.debug("full-frame jpeg fetch failed", { meta: {
|
|
63611
|
-
nodeId: handle.nodeId,
|
|
63612
|
-
error: require_dist.errMsg(err)
|
|
63613
|
-
} });
|
|
63614
|
-
return null;
|
|
63615
|
-
}
|
|
63616
|
-
};
|
|
63617
63839
|
const cropMetricLogger = logger.child("NativeCrop");
|
|
63618
63840
|
let nativeHits = 0;
|
|
63619
63841
|
let nativeFallbacks = 0;
|
|
@@ -63693,7 +63915,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63693
63915
|
getRemoteFrame,
|
|
63694
63916
|
getNativeFullFrameRgb,
|
|
63695
63917
|
getNativeKeyFrameRgb: fetchNativeFullFrameTiered,
|
|
63696
|
-
getFullFrameJpegBase64,
|
|
63697
63918
|
getNativeCropJpeg,
|
|
63698
63919
|
captureCrop: createCaptureCrop({
|
|
63699
63920
|
tryNativeCrop,
|
|
@@ -63804,6 +64025,233 @@ function parseTopologySnapshotNodes(data) {
|
|
|
63804
64025
|
return parsed;
|
|
63805
64026
|
}
|
|
63806
64027
|
//#endregion
|
|
64028
|
+
//#region src/pipeline-analytics/runtime/parked-frame-client.ts
|
|
64029
|
+
/**
|
|
64030
|
+
* Bound on the mirror. A track's record leaves at release; this is the
|
|
64031
|
+
* backstop for a lost close. At the busiest measured hub (~12 sessions,
|
|
64032
|
+
* a few tracks each) 4 096 is an hour of tracks, past which the OLDEST record
|
|
64033
|
+
* is forgotten — its parcel still ages out on the node on its own.
|
|
64034
|
+
*/
|
|
64035
|
+
var MAX_MIRRORED_TRACKS = 4096;
|
|
64036
|
+
/** One "runner has no park method" line per camera per minute. */
|
|
64037
|
+
var SKEW_WARN_THROTTLE_MS = 6e4;
|
|
64038
|
+
function inFlightKey(trackId, kind) {
|
|
64039
|
+
return `${trackId} ${kind}`;
|
|
64040
|
+
}
|
|
64041
|
+
/** Strip the hub-side context off an order: only the wire fields travel. */
|
|
64042
|
+
function toWireInput(order) {
|
|
64043
|
+
return {
|
|
64044
|
+
deviceId: order.deviceId,
|
|
64045
|
+
trackId: order.trackId,
|
|
64046
|
+
kind: order.kind,
|
|
64047
|
+
handle: order.handle,
|
|
64048
|
+
bbox: order.bbox,
|
|
64049
|
+
...order.maxWidth !== void 0 ? { maxWidth: order.maxWidth } : {},
|
|
64050
|
+
timestamp: order.timestamp
|
|
64051
|
+
};
|
|
64052
|
+
}
|
|
64053
|
+
function createParkedFrameClient(deps) {
|
|
64054
|
+
const { api, logger } = deps;
|
|
64055
|
+
const now = deps.now ?? Date.now;
|
|
64056
|
+
/** trackId → kind → what the hub kept beside the parcel. */
|
|
64057
|
+
const mirror = /* @__PURE__ */ new Map();
|
|
64058
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
64059
|
+
const skewWarnedAt = /* @__PURE__ */ new Map();
|
|
64060
|
+
const remember = (order, answer) => {
|
|
64061
|
+
let kinds = mirror.get(order.trackId);
|
|
64062
|
+
if (kinds === void 0) {
|
|
64063
|
+
kinds = /* @__PURE__ */ new Map();
|
|
64064
|
+
mirror.set(order.trackId, kinds);
|
|
64065
|
+
while (mirror.size > MAX_MIRRORED_TRACKS) {
|
|
64066
|
+
const oldest = mirror.keys().next().value;
|
|
64067
|
+
if (oldest === void 0) break;
|
|
64068
|
+
mirror.delete(oldest);
|
|
64069
|
+
}
|
|
64070
|
+
}
|
|
64071
|
+
kinds.set(order.kind, {
|
|
64072
|
+
nodeId: answer.nodeId,
|
|
64073
|
+
deviceId: order.deviceId,
|
|
64074
|
+
kind: order.kind,
|
|
64075
|
+
timestamp: order.timestamp,
|
|
64076
|
+
width: answer.width,
|
|
64077
|
+
height: answer.height,
|
|
64078
|
+
subject: order.subject,
|
|
64079
|
+
...order.layout !== void 0 ? { layout: order.layout } : {}
|
|
64080
|
+
});
|
|
64081
|
+
};
|
|
64082
|
+
const warnSkew = (order) => {
|
|
64083
|
+
const at = skewWarnedAt.get(order.deviceId) ?? 0;
|
|
64084
|
+
if (now() - at < SKEW_WARN_THROTTLE_MS) return;
|
|
64085
|
+
skewWarnedAt.set(order.deviceId, now());
|
|
64086
|
+
logger.warn("track frame park impossible — the runner api has no park method (version skew)", {
|
|
64087
|
+
tags: { deviceId: order.deviceId },
|
|
64088
|
+
meta: {
|
|
64089
|
+
trackId: order.trackId,
|
|
64090
|
+
kind: order.kind,
|
|
64091
|
+
nodeId: order.handle.nodeId
|
|
64092
|
+
}
|
|
64093
|
+
});
|
|
64094
|
+
};
|
|
64095
|
+
const sendPark = async (order) => {
|
|
64096
|
+
if (api === void 0) {
|
|
64097
|
+
warnSkew(order);
|
|
64098
|
+
return false;
|
|
64099
|
+
}
|
|
64100
|
+
if (deps.isNodeOffline?.(order.handle.nodeId) === true) {
|
|
64101
|
+
logger.debug("track frame park skipped — the owning node is offline in the registry", {
|
|
64102
|
+
tags: { deviceId: order.deviceId },
|
|
64103
|
+
meta: {
|
|
64104
|
+
trackId: order.trackId,
|
|
64105
|
+
kind: order.kind,
|
|
64106
|
+
nodeId: order.handle.nodeId
|
|
64107
|
+
}
|
|
64108
|
+
});
|
|
64109
|
+
return false;
|
|
64110
|
+
}
|
|
64111
|
+
const meta = {
|
|
64112
|
+
trackId: order.trackId,
|
|
64113
|
+
kind: order.kind,
|
|
64114
|
+
nodeId: order.handle.nodeId,
|
|
64115
|
+
handle: `${order.handle.shmId}#${order.handle.slot}#${order.handle.seq}`
|
|
64116
|
+
};
|
|
64117
|
+
try {
|
|
64118
|
+
const answer = await api.parkTrackFrame.mutate(toWireInput(order), require_dist.nodePin(order.handle.nodeId));
|
|
64119
|
+
if (answer.parked) {
|
|
64120
|
+
remember(order, answer);
|
|
64121
|
+
logger.debug("track frame parked", {
|
|
64122
|
+
tags: { deviceId: order.deviceId },
|
|
64123
|
+
meta: {
|
|
64124
|
+
...meta,
|
|
64125
|
+
bytes: answer.bytes,
|
|
64126
|
+
replaced: answer.replaced
|
|
64127
|
+
}
|
|
64128
|
+
});
|
|
64129
|
+
return true;
|
|
64130
|
+
}
|
|
64131
|
+
logger.warn("track frame park refused by the owning node", {
|
|
64132
|
+
tags: { deviceId: order.deviceId },
|
|
64133
|
+
meta: {
|
|
64134
|
+
...meta,
|
|
64135
|
+
reason: answer.reason,
|
|
64136
|
+
handleAgeMs: answer.handleAgeMs,
|
|
64137
|
+
...answer.detail !== void 0 ? { detail: answer.detail } : {}
|
|
64138
|
+
}
|
|
64139
|
+
});
|
|
64140
|
+
return false;
|
|
64141
|
+
} catch (err) {
|
|
64142
|
+
logger.warn("track frame park failed — the order did not reach the node", {
|
|
64143
|
+
tags: { deviceId: order.deviceId },
|
|
64144
|
+
meta: {
|
|
64145
|
+
...meta,
|
|
64146
|
+
error: require_dist.errMsg(err)
|
|
64147
|
+
}
|
|
64148
|
+
});
|
|
64149
|
+
return false;
|
|
64150
|
+
}
|
|
64151
|
+
};
|
|
64152
|
+
const park = (order) => {
|
|
64153
|
+
const key = inFlightKey(order.trackId, order.kind);
|
|
64154
|
+
const pending = inFlight.get(key);
|
|
64155
|
+
if (pending !== void 0) return pending;
|
|
64156
|
+
const run = sendPark(order).finally(() => {
|
|
64157
|
+
if (inFlight.get(key) === run) inFlight.delete(key);
|
|
64158
|
+
});
|
|
64159
|
+
inFlight.set(key, run);
|
|
64160
|
+
return run;
|
|
64161
|
+
};
|
|
64162
|
+
const awaitPark = async (trackId, kind) => {
|
|
64163
|
+
const pending = inFlight.get(inFlightKey(trackId, kind));
|
|
64164
|
+
if (pending !== void 0) await pending;
|
|
64165
|
+
};
|
|
64166
|
+
const info = (trackId, kind) => mirror.get(trackId)?.get(kind);
|
|
64167
|
+
const fetch = async (trackId, kind) => {
|
|
64168
|
+
const known = info(trackId, kind);
|
|
64169
|
+
if (known === void 0 || api === void 0) return null;
|
|
64170
|
+
if (deps.isNodeOffline?.(known.nodeId) === true) {
|
|
64171
|
+
logger.warn("parked frame retrieval skipped — the holding node is offline in the registry", {
|
|
64172
|
+
tags: { deviceId: known.deviceId },
|
|
64173
|
+
meta: {
|
|
64174
|
+
trackId,
|
|
64175
|
+
kind,
|
|
64176
|
+
nodeId: known.nodeId
|
|
64177
|
+
}
|
|
64178
|
+
});
|
|
64179
|
+
return null;
|
|
64180
|
+
}
|
|
64181
|
+
try {
|
|
64182
|
+
const parcel = await api.getParkedTrackFrame.query({
|
|
64183
|
+
deviceId: known.deviceId,
|
|
64184
|
+
trackId,
|
|
64185
|
+
kind
|
|
64186
|
+
}, require_dist.nodePin(known.nodeId));
|
|
64187
|
+
if (parcel === null) {
|
|
64188
|
+
logger.warn("parked frame gone at retrieval — the node no longer holds it", {
|
|
64189
|
+
tags: { deviceId: known.deviceId },
|
|
64190
|
+
meta: {
|
|
64191
|
+
trackId,
|
|
64192
|
+
kind,
|
|
64193
|
+
nodeId: known.nodeId,
|
|
64194
|
+
parkedTimestamp: known.timestamp
|
|
64195
|
+
}
|
|
64196
|
+
});
|
|
64197
|
+
return null;
|
|
64198
|
+
}
|
|
64199
|
+
return {
|
|
64200
|
+
jpeg: Buffer.from(parcel.jpeg, "base64"),
|
|
64201
|
+
width: parcel.width,
|
|
64202
|
+
height: parcel.height,
|
|
64203
|
+
timestamp: parcel.timestamp,
|
|
64204
|
+
info: known
|
|
64205
|
+
};
|
|
64206
|
+
} catch (err) {
|
|
64207
|
+
logger.warn("parked frame retrieval failed", {
|
|
64208
|
+
tags: { deviceId: known.deviceId },
|
|
64209
|
+
meta: {
|
|
64210
|
+
trackId,
|
|
64211
|
+
kind,
|
|
64212
|
+
nodeId: known.nodeId,
|
|
64213
|
+
error: require_dist.errMsg(err)
|
|
64214
|
+
}
|
|
64215
|
+
});
|
|
64216
|
+
return null;
|
|
64217
|
+
}
|
|
64218
|
+
};
|
|
64219
|
+
const release = (trackId) => {
|
|
64220
|
+
const kinds = mirror.get(trackId);
|
|
64221
|
+
if (kinds === void 0) return;
|
|
64222
|
+
mirror.delete(trackId);
|
|
64223
|
+
if (api === void 0) return;
|
|
64224
|
+
const targets = /* @__PURE__ */ new Map();
|
|
64225
|
+
for (const record of kinds.values()) targets.set(record.nodeId, record.deviceId);
|
|
64226
|
+
for (const [nodeId, deviceId] of targets) api.releaseParkedTrackFrames.mutate({
|
|
64227
|
+
deviceId,
|
|
64228
|
+
trackId
|
|
64229
|
+
}, require_dist.nodePin(nodeId)).catch((err) => {
|
|
64230
|
+
logger.debug("parked frame release did not reach the node", {
|
|
64231
|
+
tags: { deviceId },
|
|
64232
|
+
meta: {
|
|
64233
|
+
trackId,
|
|
64234
|
+
nodeId,
|
|
64235
|
+
error: require_dist.errMsg(err)
|
|
64236
|
+
}
|
|
64237
|
+
});
|
|
64238
|
+
});
|
|
64239
|
+
};
|
|
64240
|
+
const dispose = () => {
|
|
64241
|
+
mirror.clear();
|
|
64242
|
+
inFlight.clear();
|
|
64243
|
+
skewWarnedAt.clear();
|
|
64244
|
+
};
|
|
64245
|
+
return {
|
|
64246
|
+
park,
|
|
64247
|
+
awaitPark,
|
|
64248
|
+
info,
|
|
64249
|
+
fetch,
|
|
64250
|
+
release,
|
|
64251
|
+
dispose
|
|
64252
|
+
};
|
|
64253
|
+
}
|
|
64254
|
+
//#endregion
|
|
63807
64255
|
//#region src/pipeline-analytics/runtime/widget-manifest.ts
|
|
63808
64256
|
/** The remote-module coordinates shared by every analytics widget. */
|
|
63809
64257
|
var REMOTE_NAME = "addon_pipeline_analytics_widgets";
|
|
@@ -64905,7 +65353,8 @@ function isUniformRgbFrame(data, width, height) {
|
|
|
64905
65353
|
* frame downscaled to {@link SNAPSHOT_MAX_WIDTH} (960) with ONLY the track's own
|
|
64906
65354
|
* box burned in, falling back to the ≤640 detection frame (honest lower res,
|
|
64907
65355
|
* never an upscale) on a native miss. The best `thumbnail` stays a clean,
|
|
64908
|
-
* uncapped native subject crop
|
|
65356
|
+
* uncapped native subject crop — read from the track's PARKED slab, by track
|
|
65357
|
+
* (D422), never by handle.
|
|
64909
65358
|
*
|
|
64910
65359
|
* The detection frame is resolved ONCE per call (blank-frame guard); native full
|
|
64911
65360
|
* frames + subject crops are fetched per target by handle. Frames recycle in
|
|
@@ -65173,10 +65622,10 @@ var EventMediaDispatcher = class {
|
|
|
65173
65622
|
if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
65174
65623
|
let thumbnailWritten = false;
|
|
65175
65624
|
if (sn.bestThumbnail) {
|
|
65176
|
-
const variants = await this.cropSubjectVariants(deviceId, sn.trackId
|
|
65625
|
+
const variants = await this.cropSubjectVariants(deviceId, sn.trackId);
|
|
65177
65626
|
if (variants) {
|
|
65178
|
-
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail",
|
|
65179
|
-
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall",
|
|
65627
|
+
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", variants.timestamp, variants.thumbnail, "native");
|
|
65628
|
+
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", variants.timestamp, variants.thumbnailSmall, "native");
|
|
65180
65629
|
}
|
|
65181
65630
|
}
|
|
65182
65631
|
return {
|
|
@@ -65254,81 +65703,49 @@ var EventMediaDispatcher = class {
|
|
|
65254
65703
|
}
|
|
65255
65704
|
/**
|
|
65256
65705
|
* The best-shot subject crop as its TWO persisted variants (best-crop
|
|
65257
|
-
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22)
|
|
65258
|
-
*
|
|
65259
|
-
*
|
|
65260
|
-
*
|
|
65261
|
-
*
|
|
65262
|
-
* the
|
|
65263
|
-
*
|
|
65264
|
-
*
|
|
65265
|
-
*
|
|
65266
|
-
*
|
|
65267
|
-
*
|
|
65268
|
-
*
|
|
65269
|
-
*
|
|
65270
|
-
*
|
|
65271
|
-
*
|
|
65272
|
-
*
|
|
65273
|
-
*
|
|
65274
|
-
|
|
65275
|
-
|
|
65276
|
-
* upscale of a ≤640 tile (a blurred lie).
|
|
65277
|
-
*/
|
|
65278
|
-
async cropSubjectVariants(deviceId, trackId, frameHandle, fw, fh, bbox) {
|
|
65279
|
-
if (!this.deps.getNativeCropJpeg) {
|
|
65280
|
-
this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
|
|
65281
|
-
tags: { deviceId },
|
|
65282
|
-
meta: {
|
|
65283
|
-
shmId: frameHandle.shmId,
|
|
65284
|
-
reason: "no-native-cap"
|
|
65285
|
-
}
|
|
65286
|
-
});
|
|
65287
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "no-native-cap");
|
|
65288
|
-
return null;
|
|
65289
|
-
}
|
|
65706
|
+
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22), from the
|
|
65707
|
+
* track's PARKED slab (D422). ONE read, by `(deviceId, trackId)`: the
|
|
65708
|
+
* in-frame slab of the 16:9 central-square WINDOW
|
|
65709
|
+
* ({@link wideCentralSquareLayout}) the runner cut at uncapped native
|
|
65710
|
+
* resolution the instant the track's best was decided. It is composed into
|
|
65711
|
+
* the full 16:9 window (lateral letterbox only for the part outside the
|
|
65712
|
+
* frame) → the `thumbnail`. The `thumbnailSmall` is DERIVED by downscaling
|
|
65713
|
+
* that SAME window to the 480 long-side cap — never a second read, never
|
|
65714
|
+
* upscaled ({@link deriveThumbnailSmall} never upscales a smaller window).
|
|
65715
|
+
*
|
|
65716
|
+
* Both variants share the SAME framing + central-square containment
|
|
65717
|
+
* guarantee: wide consumers use the 16:9 image as-is; square consumers
|
|
65718
|
+
* center-crop the middle square (subject always inside it).
|
|
65719
|
+
*
|
|
65720
|
+
* PARCEL-OR-NOTHING: no parcel (or a throw) returns `null` after a loud
|
|
65721
|
+
* `logger.warn`; the caller SKIPS the write and the per-frame retry parks a
|
|
65722
|
+
* later frame. Never a by-handle crop, never a local upscale of a ≤640 tile.
|
|
65723
|
+
*/
|
|
65724
|
+
async cropSubjectVariants(deviceId, trackId) {
|
|
65290
65725
|
try {
|
|
65291
|
-
const
|
|
65292
|
-
|
|
65293
|
-
|
|
65294
|
-
});
|
|
65295
|
-
if (centralSquareContainmentFraction(bbox, {
|
|
65296
|
-
W: fw,
|
|
65297
|
-
H: fh
|
|
65298
|
-
}) < .5) this.deps.logger.info("best-shot delivered via contained square layout (edge subject)", {
|
|
65299
|
-
tags: { deviceId },
|
|
65300
|
-
meta: {
|
|
65301
|
-
bbox,
|
|
65302
|
-
frameW: fw,
|
|
65303
|
-
frameH: fh
|
|
65304
|
-
}
|
|
65305
|
-
});
|
|
65306
|
-
const askedAt = Date.now();
|
|
65307
|
-
const slab = await this.deps.getNativeCropJpeg(frameHandle, layout.fetch, void 0, deviceId);
|
|
65308
|
-
if (!slab) {
|
|
65309
|
-
this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
|
|
65726
|
+
const parked = await this.deps.getParkedSubjectSlab(deviceId, trackId);
|
|
65727
|
+
if (!parked) {
|
|
65728
|
+
this.deps.logger.warn("best-shot parcel missing — no thumbnail this frame, will retry", {
|
|
65310
65729
|
tags: { deviceId },
|
|
65311
65730
|
meta: {
|
|
65312
|
-
|
|
65313
|
-
|
|
65314
|
-
handleNodeId: frameHandle.nodeId,
|
|
65315
|
-
roundTripMs: Date.now() - askedAt,
|
|
65316
|
-
reason: "native-miss"
|
|
65731
|
+
trackId,
|
|
65732
|
+
reason: "parcel-missing"
|
|
65317
65733
|
}
|
|
65318
65734
|
});
|
|
65319
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "
|
|
65735
|
+
this.deps.onSubjectCropMiss?.(deviceId, trackId, "parcel-missing");
|
|
65320
65736
|
return null;
|
|
65321
65737
|
}
|
|
65322
|
-
const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
|
|
65738
|
+
const thumbnail = await composeWideCentralSquareThumbnail(parked.slab, parked.layout);
|
|
65323
65739
|
return {
|
|
65324
65740
|
thumbnail,
|
|
65325
|
-
thumbnailSmall: await deriveThumbnailSmall(thumbnail)
|
|
65741
|
+
thumbnailSmall: await deriveThumbnailSmall(thumbnail),
|
|
65742
|
+
timestamp: parked.timestamp
|
|
65326
65743
|
};
|
|
65327
65744
|
} catch (err) {
|
|
65328
|
-
this.deps.logger.warn("
|
|
65745
|
+
this.deps.logger.warn("best-shot parcel read failed — no thumbnail this frame, will retry", {
|
|
65329
65746
|
tags: { deviceId },
|
|
65330
65747
|
meta: {
|
|
65331
|
-
|
|
65748
|
+
trackId,
|
|
65332
65749
|
reason: "threw",
|
|
65333
65750
|
error: err instanceof Error ? err.message : String(err)
|
|
65334
65751
|
}
|
|
@@ -70619,7 +71036,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70619
71036
|
* the FULL cleanup litany on every teardown path (and cancels the track's
|
|
70620
71037
|
* queued captures via the onClose hook) and `closeDevice` finally clears
|
|
70621
71038
|
* per-track state on device removal (the two documented leaks). */
|
|
70622
|
-
residents = new TrackResidentState({ onClose: (trackId) =>
|
|
71039
|
+
residents = new TrackResidentState({ onClose: (trackId) => {
|
|
71040
|
+
this.captureScheduler.cancelForTrack(trackId);
|
|
71041
|
+
this.parkedFrames?.release(trackId);
|
|
71042
|
+
} });
|
|
71043
|
+
/**
|
|
71044
|
+
* The hub's side of a PARK (D422): orders the owning node to keep a track's
|
|
71045
|
+
* best frame the instant `BestDetectionTracker` accepts it, and answers the
|
|
71046
|
+
* keyFrame writer, the best-shot thumbnail and the detail plane's full-frame
|
|
71047
|
+
* rung by `(deviceId, trackId, kind)` — never by handle. Null until init.
|
|
71048
|
+
*/
|
|
71049
|
+
parkedFrames = null;
|
|
70623
71050
|
/** Defence in depth for the 2026-08-21 `b50e09f7` incident: a row the track
|
|
70624
71051
|
* store has already CLOSED never earns another media target, whatever the
|
|
70625
71052
|
* tracker keeps handing back. Reads the store's in-RAM close record only —
|
|
@@ -70729,6 +71156,22 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70729
71156
|
* a ≤640 RAM fallback (`tier: 'ram-fullframe'`). Null until init completes. */
|
|
70730
71157
|
getNativeKeyFrameRgb = null;
|
|
70731
71158
|
shuttingDown = false;
|
|
71159
|
+
/**
|
|
71160
|
+
* Per-device delivery-lag windows, folded at `handleInferenceResult` entry.
|
|
71161
|
+
* The instrument that tells a late DELIVERY (starved) apart from a slow
|
|
71162
|
+
* `processFrame` (waiting) — see `frame-lag-observer.ts`. Costs one map read
|
|
71163
|
+
* and ~10 numeric ops per frame, and writes one line per device per 30 s.
|
|
71164
|
+
*/
|
|
71165
|
+
frameLag = new FrameLagObserver();
|
|
71166
|
+
/**
|
|
71167
|
+
* Stops the GC-attributing stall monitor armed in {@link onInitialize}.
|
|
71168
|
+
*
|
|
71169
|
+
* The other half of the frame-lag line: that one says whether the events
|
|
71170
|
+
* arrived late, this one says whether THIS loop was blocked while they did.
|
|
71171
|
+
* The 5-minute `[mem]` heartbeat cannot see a 10 s freeze — it fired at
|
|
71172
|
+
* dt = 300020 / 300066 / 299991 ms straight through the 2026-09-09 silences.
|
|
71173
|
+
*/
|
|
71174
|
+
stopStallMonitor = null;
|
|
70732
71175
|
/** True only on the cluster's designated post-processing node. When false the
|
|
70733
71176
|
* addon subscribes to NOTHING — fully inert (no event/media generation). */
|
|
70734
71177
|
isPostProcessingNode = true;
|
|
@@ -70947,6 +71390,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70947
71390
|
super({});
|
|
70948
71391
|
}
|
|
70949
71392
|
async onInitialize() {
|
|
71393
|
+
this.stopStallMonitor = (0, _camstack_system_addon_utils.startEventLoopStallMonitor)(this.ctx.logger);
|
|
70950
71394
|
const rawApi = this.ctx.api;
|
|
70951
71395
|
if (!rawApi) throw new Error("pipeline-analytics requires ctx.api (device-manager + settings-store)");
|
|
70952
71396
|
const api = censusApi(rawApi, this.readCensus);
|
|
@@ -70997,17 +71441,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70997
71441
|
noteCropOutcome: (deviceId, reason) => this.failureReport.noteEnrichmentCrop(deviceId, reason)
|
|
70998
71442
|
});
|
|
70999
71443
|
this.getNativeKeyFrameRgb = transport.getNativeKeyFrameRgb;
|
|
71444
|
+
this.parkedFrames = createParkedFrameClient({
|
|
71445
|
+
api: api.pipelineRunner,
|
|
71446
|
+
logger: logger.child("ParkedFrames"),
|
|
71447
|
+
isNodeOffline: (nodeId) => this.nodeLiveness.isKnownOffline(nodeId, Date.now())
|
|
71448
|
+
});
|
|
71000
71449
|
this.captureCrop = transport.captureCrop;
|
|
71001
71450
|
this.eventMediaDispatcher = new EventMediaDispatcher({
|
|
71002
71451
|
getRemoteFrame: transport.getRemoteFrame,
|
|
71003
71452
|
getNativeCropJpeg: transport.getNativeCropJpeg,
|
|
71004
71453
|
getNativeFullFrameRgb: transport.getNativeFullFrameRgb,
|
|
71454
|
+
getParkedSubjectSlab: (deviceId, trackId) => this.fetchParkedSubjectSlab(deviceId, trackId),
|
|
71005
71455
|
mediaStore: stores.mediaStore,
|
|
71006
71456
|
logger: logger.child("EventMediaDispatcher"),
|
|
71007
71457
|
onSubjectCropMiss: (deviceId, trackId, reason) => this.residents.notePreviewCaptureMiss(deviceId, trackId, `thumbnail:${reason}`)
|
|
71008
71458
|
});
|
|
71009
71459
|
this.buildRecognizers(api, logger, stores, transport);
|
|
71010
|
-
this.buildDetailDispatcher(api, logger
|
|
71460
|
+
this.buildDetailDispatcher(api, logger);
|
|
71011
71461
|
const capProviders = this.buildAnalyticsProviders(api, logger);
|
|
71012
71462
|
await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
|
|
71013
71463
|
await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
|
|
@@ -71837,7 +72287,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71837
72287
|
* demand via `pipelineRunner.runDetailSubtree`. The dispatcher owns the
|
|
71838
72288
|
* per-device schedulers, concurrency cap, and 1 s tick; result routing +
|
|
71839
72289
|
* the pinned call + crop fallback are injected here. */
|
|
71840
|
-
buildDetailDispatcher(api, logger
|
|
72290
|
+
buildDetailDispatcher(api, logger) {
|
|
71841
72291
|
const runnerApi = api.pipelineRunner;
|
|
71842
72292
|
this.detailDispatcher = new TrackDetailDispatcher({
|
|
71843
72293
|
logger: logger.child("DetailDispatcher"),
|
|
@@ -71847,13 +72297,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71847
72297
|
return runnerApi.runDetailSubtree.mutate(input);
|
|
71848
72298
|
},
|
|
71849
72299
|
routeResults: (deviceId, trackId, details, frame) => this.routeDetailResults(deviceId, trackId, details, frame),
|
|
71850
|
-
|
|
71851
|
-
|
|
71852
|
-
|
|
71853
|
-
return
|
|
71854
|
-
base64:
|
|
71855
|
-
width:
|
|
71856
|
-
height:
|
|
72300
|
+
captureParkedFrame: async (_deviceId, trackId) => {
|
|
72301
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
72302
|
+
if (parcel === null) return null;
|
|
72303
|
+
return {
|
|
72304
|
+
base64: parcel.jpeg.toString("base64"),
|
|
72305
|
+
width: parcel.width,
|
|
72306
|
+
height: parcel.height,
|
|
72307
|
+
bbox: scaleSubjectIntoParcel(parcel.info.subject, parcel.width, parcel.height),
|
|
72308
|
+
nodeId: parcel.info.nodeId,
|
|
72309
|
+
timestamp: parcel.timestamp
|
|
71857
72310
|
};
|
|
71858
72311
|
},
|
|
71859
72312
|
captureCropBase64: async (frame) => {
|
|
@@ -73451,6 +73904,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73451
73904
|
}
|
|
73452
73905
|
async onShutdown() {
|
|
73453
73906
|
this.shuttingDown = true;
|
|
73907
|
+
if (this.stopStallMonitor) {
|
|
73908
|
+
this.stopStallMonitor();
|
|
73909
|
+
this.stopStallMonitor = null;
|
|
73910
|
+
}
|
|
73454
73911
|
this.sceneEngine?.stop();
|
|
73455
73912
|
this.sceneEngine = null;
|
|
73456
73913
|
if (this.sceneReloadTimer !== null) {
|
|
@@ -73491,10 +73948,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73491
73948
|
this.audioMarkers?.flush(Date.now());
|
|
73492
73949
|
this.detailDispatcher?.dispose();
|
|
73493
73950
|
this.detailDispatcher = null;
|
|
73951
|
+
this.parkedFrames?.dispose();
|
|
73494
73952
|
this.overlayState.clear();
|
|
73495
73953
|
this.overlaySynthesisWarnAt.clear();
|
|
73496
73954
|
this.processors.clear();
|
|
73497
73955
|
this.lastActiveTrackIds.clear();
|
|
73956
|
+
this.frameLag.clear();
|
|
73498
73957
|
this.lastFrameDimsByDevice.clear();
|
|
73499
73958
|
this.dropoutSkipsByKey.clear();
|
|
73500
73959
|
this.residents.clearAll();
|
|
@@ -73534,6 +73993,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73534
73993
|
async handleInferenceResult(data) {
|
|
73535
73994
|
if (this.shuttingDown) return;
|
|
73536
73995
|
const { deviceId, frame } = data;
|
|
73996
|
+
const lag = this.frameLag.observe(deviceId, Date.now(), data.emittedAt, data.capturedAt);
|
|
73997
|
+
if (lag !== null) {
|
|
73998
|
+
const verdict = lagVerdict(lag);
|
|
73999
|
+
(isAlarmingLagReport(lag) ? this.ctx.logger.warn.bind(this.ctx.logger) : this.ctx.logger.info.bind(this.ctx.logger))("pipeline-analytics frame lag", {
|
|
74000
|
+
tags: { deviceId },
|
|
74001
|
+
meta: {
|
|
74002
|
+
verdict,
|
|
74003
|
+
frames: lag.frames,
|
|
74004
|
+
windowMs: lag.windowMs,
|
|
74005
|
+
busLagMaxMs: lag.busLagMaxMs,
|
|
74006
|
+
busLagMinMs: lag.busLagMinMs,
|
|
74007
|
+
busLagUnknownFrames: lag.busLagUnknownFrames,
|
|
74008
|
+
captureLagMaxMs: lag.captureLagMaxMs,
|
|
74009
|
+
captureLagUnknownFrames: lag.captureLagUnknownFrames
|
|
74010
|
+
}
|
|
74011
|
+
});
|
|
74012
|
+
}
|
|
73537
74013
|
await this.processFrame(deviceId, frame, "pipeline", data.frameHandle, data.detailSteps);
|
|
73538
74014
|
}
|
|
73539
74015
|
/**
|
|
@@ -74131,7 +74607,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74131
74607
|
let plateCrops = 0;
|
|
74132
74608
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
74133
74609
|
else plateCrops += 1;
|
|
74134
|
-
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
74610
|
+
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight, frameHandle);
|
|
74135
74611
|
this.offerGroupShot(deviceId, result, frameHandle);
|
|
74136
74612
|
for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
74137
74613
|
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
@@ -74250,7 +74726,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74250
74726
|
}).catch(() => {});
|
|
74251
74727
|
}
|
|
74252
74728
|
const keyFrameCaptures = selectKeyFrameCaptures(snapshotTargets);
|
|
74253
|
-
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures
|
|
74729
|
+
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures);
|
|
74254
74730
|
}
|
|
74255
74731
|
if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
|
|
74256
74732
|
deviceId,
|
|
@@ -75180,10 +75656,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75180
75656
|
});
|
|
75181
75657
|
}).catch(() => {});
|
|
75182
75658
|
}
|
|
75183
|
-
|
|
75184
|
-
|
|
75659
|
+
/**
|
|
75660
|
+
* Order the parcels a best-frame decision wants (D422). Skipped entirely at
|
|
75661
|
+
* a node the registry already called offline (D58) — logged on the
|
|
75662
|
+
* transition by `noteCapturesGated`, counted in the key-frame window.
|
|
75663
|
+
* `onlyIfMissing` is the retry rule: a later frame may fill a kind that has
|
|
75664
|
+
* no parcel, never replace one the ranking preferred.
|
|
75665
|
+
*/
|
|
75666
|
+
issueParkOrders(decision, kinds, onlyIfMissing) {
|
|
75667
|
+
const client = this.parkedFrames;
|
|
75668
|
+
if (client === null) return;
|
|
75669
|
+
if (this.nodeLiveness.isKnownOffline(decision.frameHandle.nodeId, Date.now())) {
|
|
75670
|
+
this.noteCapturesGated(decision.deviceId, decision.frameHandle.nodeId);
|
|
75671
|
+
return;
|
|
75672
|
+
}
|
|
75673
|
+
for (const order of buildParkOrders(decision)) {
|
|
75674
|
+
if (!kinds.includes(order.kind)) continue;
|
|
75675
|
+
if (onlyIfMissing && client.info(order.trackId, order.kind) !== void 0) continue;
|
|
75676
|
+
client.park(order);
|
|
75677
|
+
}
|
|
75678
|
+
}
|
|
75679
|
+
/** A parcel by track, after any park in flight for it has landed. */
|
|
75680
|
+
async fetchParkedParcel(trackId, kind) {
|
|
75681
|
+
const client = this.parkedFrames;
|
|
75682
|
+
if (client === null) return null;
|
|
75683
|
+
await client.awaitPark(trackId, kind);
|
|
75684
|
+
return client.fetch(trackId, kind);
|
|
75685
|
+
}
|
|
75686
|
+
/** The best-shot slab for the dispatcher: the parcel plus the layout the hub kept. */
|
|
75687
|
+
async fetchParkedSubjectSlab(_deviceId, trackId) {
|
|
75688
|
+
const parcel = await this.fetchParkedParcel(trackId, "thumbnail");
|
|
75689
|
+
if (parcel === null || parcel.info.layout === void 0) return null;
|
|
75690
|
+
return {
|
|
75691
|
+
slab: parcel.jpeg,
|
|
75692
|
+
layout: parcel.info.layout,
|
|
75693
|
+
timestamp: parcel.timestamp
|
|
75694
|
+
};
|
|
75695
|
+
}
|
|
75696
|
+
async persistKeyFrames(deviceId, timestamp, captures) {
|
|
75185
75697
|
const mediaStore = this.mediaStore;
|
|
75186
|
-
if (
|
|
75698
|
+
if (this.parkedFrames === null || !mediaStore) return;
|
|
75187
75699
|
await Promise.all(captures.map((capture) => this.captureScheduler.request({
|
|
75188
75700
|
deviceId,
|
|
75189
75701
|
trackId: capture.trackId,
|
|
@@ -75193,16 +75705,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75193
75705
|
exec: async () => {
|
|
75194
75706
|
this.residents.noteKeyFrameAttempt(deviceId, capture.trackId, timestamp, capture.retry);
|
|
75195
75707
|
const outcome = await persistTrackKeyFrame({
|
|
75196
|
-
|
|
75197
|
-
|
|
75708
|
+
fetchParkedKeyFrame: async (parcelDeviceId, trackId) => {
|
|
75709
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
75710
|
+
if (parcel === null || parcel.info.deviceId !== parcelDeviceId) return null;
|
|
75711
|
+
return {
|
|
75712
|
+
jpeg: parcel.jpeg,
|
|
75713
|
+
width: parcel.width,
|
|
75714
|
+
height: parcel.height,
|
|
75715
|
+
timestamp: parcel.timestamp,
|
|
75716
|
+
nodeId: parcel.info.nodeId
|
|
75717
|
+
};
|
|
75718
|
+
},
|
|
75719
|
+
deriveSmall: deriveKeyFrameSmall,
|
|
75198
75720
|
media: mediaStore,
|
|
75199
75721
|
logger: this.ctx.logger
|
|
75200
75722
|
}, {
|
|
75201
75723
|
deviceId,
|
|
75202
75724
|
trackId: capture.trackId,
|
|
75203
|
-
timestamp,
|
|
75204
|
-
frameHandle,
|
|
75205
|
-
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
|
|
75206
75725
|
retry: capture.retry
|
|
75207
75726
|
});
|
|
75208
75727
|
if (outcome.kind === "landed") {
|
|
@@ -75282,10 +75801,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75282
75801
|
isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
|
|
75283
75802
|
captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
|
|
75284
75803
|
captureKeyFrame: async (req) => {
|
|
75804
|
+
this.issueParkOrders({
|
|
75805
|
+
deviceId: req.deviceId,
|
|
75806
|
+
trackId: req.trackId,
|
|
75807
|
+
timestamp: req.timestamp,
|
|
75808
|
+
frameHandle: req.frameHandle,
|
|
75809
|
+
bbox: { ...req.bodyBbox },
|
|
75810
|
+
frameWidth: req.frameWidth,
|
|
75811
|
+
frameHeight: req.frameHeight
|
|
75812
|
+
}, ["keyFrame"], true);
|
|
75285
75813
|
await this.persistKeyFrames(req.deviceId, req.timestamp, [{
|
|
75286
75814
|
trackId: req.trackId,
|
|
75287
75815
|
retry: false
|
|
75288
|
-
}]
|
|
75816
|
+
}]);
|
|
75289
75817
|
}
|
|
75290
75818
|
}, input);
|
|
75291
75819
|
}
|
|
@@ -75339,10 +75867,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75339
75867
|
* (coalescing, per-device budget and land-only bookkeeping identical). */
|
|
75340
75868
|
captureSemanticBestShot(request) {
|
|
75341
75869
|
const { deviceId, trackId, timestamp, bbox, frameHandle } = request;
|
|
75870
|
+
this.issueParkOrders({
|
|
75871
|
+
deviceId,
|
|
75872
|
+
trackId,
|
|
75873
|
+
timestamp,
|
|
75874
|
+
frameHandle,
|
|
75875
|
+
bbox: { ...bbox },
|
|
75876
|
+
frameWidth: request.frameWidth,
|
|
75877
|
+
frameHeight: request.frameHeight
|
|
75878
|
+
}, ["keyFrame", "thumbnail"], false);
|
|
75342
75879
|
this.persistKeyFrames(deviceId, timestamp, [{
|
|
75343
75880
|
trackId,
|
|
75344
75881
|
retry: false
|
|
75345
|
-
}]
|
|
75882
|
+
}]);
|
|
75346
75883
|
const dispatcher = this.eventMediaDispatcher;
|
|
75347
75884
|
if (!dispatcher) return;
|
|
75348
75885
|
if (this.captureScheduler.isKeyActive(captureCoalesceKey(deviceId, "thumbnail", trackId))) {
|
|
@@ -75493,7 +76030,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75493
76030
|
}
|
|
75494
76031
|
return retries;
|
|
75495
76032
|
}
|
|
75496
|
-
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
76033
|
+
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight, frameHandle) {
|
|
75497
76034
|
const targets = [];
|
|
75498
76035
|
for (const t of tracked) {
|
|
75499
76036
|
if (t.matchedThisFrame === false) continue;
|
|
@@ -75579,6 +76116,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75579
76116
|
state: this.residents.keyFrameCapture(t.trackId),
|
|
75580
76117
|
now: timestamp
|
|
75581
76118
|
}).kind;
|
|
76119
|
+
if (frameHandle !== void 0 && plausibleBox) {
|
|
76120
|
+
const decision = {
|
|
76121
|
+
deviceId,
|
|
76122
|
+
trackId: t.trackId,
|
|
76123
|
+
timestamp,
|
|
76124
|
+
frameHandle,
|
|
76125
|
+
bbox: {
|
|
76126
|
+
x: t.bbox.x,
|
|
76127
|
+
y: t.bbox.y,
|
|
76128
|
+
w: t.bbox.w,
|
|
76129
|
+
h: t.bbox.h
|
|
76130
|
+
},
|
|
76131
|
+
frameWidth,
|
|
76132
|
+
frameHeight
|
|
76133
|
+
};
|
|
76134
|
+
if (isNewBest) this.issueParkOrders(decision, ["keyFrame", "thumbnail"], false);
|
|
76135
|
+
else {
|
|
76136
|
+
const retryKinds = [];
|
|
76137
|
+
if (keyFrame === "retry") retryKinds.push("keyFrame");
|
|
76138
|
+
if (bestThumbnail) retryKinds.push("thumbnail");
|
|
76139
|
+
if (retryKinds.length > 0) this.issueParkOrders(decision, retryKinds, true);
|
|
76140
|
+
}
|
|
76141
|
+
}
|
|
75582
76142
|
const suppressedBy = (() => {
|
|
75583
76143
|
if (skipParkedThumb) return "parked-thumbnail";
|
|
75584
76144
|
if (!isNewBest || bestThumbnail) return void 0;
|