@camstack/addon-post-analysis 1.2.214 → 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-TUlCd-Zk.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-CN3MIGfb.mjs → hostInit-DMDZJw2T.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +640 -201
- package/dist/pipeline-analytics/index.mjs +632 -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");
|
|
@@ -3224,8 +3224,8 @@ async function raceTimeout(call, timeoutMs) {
|
|
|
3224
3224
|
* detail the encoder can see.
|
|
3225
3225
|
*/
|
|
3226
3226
|
async function downscaleJpeg(bytes, maxPx) {
|
|
3227
|
-
const { default: sharp$
|
|
3228
|
-
const out = await sharp$
|
|
3227
|
+
const { default: sharp$16 } = await import("sharp");
|
|
3228
|
+
const out = await sharp$16(Buffer.from(bytes)).resize({
|
|
3229
3229
|
width: maxPx,
|
|
3230
3230
|
height: maxPx,
|
|
3231
3231
|
fit: "inside",
|
|
@@ -11741,8 +11741,8 @@ var NcDispatcher = class {
|
|
|
11741
11741
|
try {
|
|
11742
11742
|
const points = (await this.deps.getZonePolygons?.(deviceId, zoneIds) ?? []).flat();
|
|
11743
11743
|
if (points.length === 0) return null;
|
|
11744
|
-
const { default: sharp$
|
|
11745
|
-
const img = sharp$
|
|
11744
|
+
const { default: sharp$15 } = await import("sharp");
|
|
11745
|
+
const img = sharp$15(Buffer.from(jpeg));
|
|
11746
11746
|
const meta = await img.metadata();
|
|
11747
11747
|
const W = meta.width ?? 0;
|
|
11748
11748
|
const H = meta.height ?? 0;
|
|
@@ -14921,7 +14921,7 @@ function buildOccupancyStatus(input) {
|
|
|
14921
14921
|
* `this.byId.clear()` happened to sit AFTER the `await` that threw. Nothing
|
|
14922
14922
|
* said so, and hoisting one line would have silently turned every transient
|
|
14923
14923
|
* store error into a hub that stops notifying — the reversal behind the D130
|
|
14924
|
-
* flood and the
|
|
14924
|
+
* flood and the occupancy cold-seed loss. The ledger's contract
|
|
14925
14925
|
* rule 2 (a failed load NEVER clears the mirror, deliberately no knob) makes
|
|
14926
14926
|
* that reversal unexpressible here. `rule-store-load-failure-direction.spec.ts`
|
|
14927
14927
|
* holds the direction and records what was removed to make it red.
|
|
@@ -18138,7 +18138,7 @@ function clockOf$2(atMs) {
|
|
|
18138
18138
|
*
|
|
18139
18139
|
* ## The `.partial()` trap this file exists to avoid
|
|
18140
18140
|
*
|
|
18141
|
-
*
|
|
18141
|
+
* Found in production 2026-08-12: `NcRulePatchSchema` was
|
|
18142
18142
|
* `NcRuleInputSchema.partial()`, and Zod 4 `.partial()` KEEPS the inner
|
|
18143
18143
|
* `.default()`. Parsing `{ name: 'x' }` therefore materialised every defaulted
|
|
18144
18144
|
* key, and `{ ...existing, ...patch }` wrote them over the rule — a camera- and
|
|
@@ -19033,8 +19033,8 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
|
|
|
19033
19033
|
];
|
|
19034
19034
|
if (bbox === void 0) return files;
|
|
19035
19035
|
try {
|
|
19036
|
-
const { default: sharp$
|
|
19037
|
-
const meta = await sharp$
|
|
19036
|
+
const { default: sharp$14 } = await import("sharp");
|
|
19037
|
+
const meta = await sharp$14(Buffer.from(jpeg)).metadata();
|
|
19038
19038
|
const W = meta.width ?? 0;
|
|
19039
19039
|
const H = meta.height ?? 0;
|
|
19040
19040
|
if (W === 0 || H === 0) return files;
|
|
@@ -19042,7 +19042,7 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
|
|
|
19042
19042
|
const top = Math.max(0, Math.min(H - 1, Math.floor(bbox.y * H)));
|
|
19043
19043
|
const width = Math.max(1, Math.min(W - left, Math.ceil(bbox.w * W)));
|
|
19044
19044
|
const height = Math.max(1, Math.min(H - top, Math.ceil(bbox.h * H)));
|
|
19045
|
-
const cropBuf = await sharp$
|
|
19045
|
+
const cropBuf = await sharp$14(Buffer.from(jpeg)).extract({
|
|
19046
19046
|
left,
|
|
19047
19047
|
top,
|
|
19048
19048
|
width,
|
|
@@ -19064,7 +19064,7 @@ async function deriveSyntheticMedia(jpeg, bbox, timestamp) {
|
|
|
19064
19064
|
});
|
|
19065
19065
|
const stroke = Math.max(2, Math.round(Math.min(W, H) / 240));
|
|
19066
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>`);
|
|
19067
|
-
const boxedBuf = await sharp$
|
|
19067
|
+
const boxedBuf = await sharp$14(Buffer.from(jpeg)).composite([{
|
|
19068
19068
|
input: overlay,
|
|
19069
19069
|
top: 0,
|
|
19070
19070
|
left: 0
|
|
@@ -34906,6 +34906,8 @@ var SortTracker = class SortTracker {
|
|
|
34906
34906
|
refusalSampler = new SpawnRefusalSampler();
|
|
34907
34907
|
/** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
|
|
34908
34908
|
spawnRefusalObserver = null;
|
|
34909
|
+
/** Unsampled copy for the frame path — see `setSpawnRefusalSink`. */
|
|
34910
|
+
refusalSink = null;
|
|
34909
34911
|
/** Per-reason counters behind the sampled spawn-beside-coasting diagnostics. */
|
|
34910
34912
|
spawnBesideSampler = new SpawnBesideCoastingSampler();
|
|
34911
34913
|
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
@@ -35018,11 +35020,7 @@ var SortTracker = class SortTracker {
|
|
|
35018
35020
|
}
|
|
35019
35021
|
/** Emit one dropped-detection record, subject to the refusal sampler. */
|
|
35020
35022
|
reportRefusal(reason, det, extra = {}) {
|
|
35021
|
-
const
|
|
35022
|
-
if (!observer) return;
|
|
35023
|
-
const refusalCount = this.refusalSampler.next(reason);
|
|
35024
|
-
if (refusalCount === void 0) return;
|
|
35025
|
-
observer({
|
|
35023
|
+
const base = {
|
|
35026
35024
|
reason,
|
|
35027
35025
|
detClass: det.class,
|
|
35028
35026
|
detScore: det.score,
|
|
@@ -35034,13 +35032,31 @@ var SortTracker = class SortTracker {
|
|
|
35034
35032
|
againstTrackId: extra.against.id,
|
|
35035
35033
|
againstTrackClass: extra.against.class
|
|
35036
35034
|
} : {},
|
|
35037
|
-
refusalCount,
|
|
35038
|
-
sampledEvery: 10,
|
|
35039
35035
|
...extra.droppedTrackId !== void 0 ? { droppedTrackId: extra.droppedTrackId } : {},
|
|
35040
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
|
|
35041
35047
|
});
|
|
35042
35048
|
}
|
|
35043
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
|
+
/**
|
|
35044
35060
|
* Report a TRACK that ends having emitted nothing — the last silent branch
|
|
35045
35061
|
* on the detection→position path (D391).
|
|
35046
35062
|
*
|
|
@@ -36134,6 +36150,20 @@ var FrameProcessor = class {
|
|
|
36134
36150
|
/** Sink + sampler for the drops this stage owns (the detection-stage zone
|
|
36135
36151
|
* filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
|
|
36136
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;
|
|
36137
36167
|
zoneRefusalSampler = new SpawnRefusalSampler();
|
|
36138
36168
|
/** Optional stationary-object gate (parked-object suppression). Null until
|
|
36139
36169
|
* the addon wires it via {@link setStationaryGate}. */
|
|
@@ -36148,6 +36178,9 @@ var FrameProcessor = class {
|
|
|
36148
36178
|
this.deviceId = deviceId;
|
|
36149
36179
|
this.source = source;
|
|
36150
36180
|
this.tracker = new SortTracker(trackerConfig);
|
|
36181
|
+
this.tracker.setSpawnRefusalSink((record) => {
|
|
36182
|
+
this.refusalsThisFrame.push(record);
|
|
36183
|
+
});
|
|
36151
36184
|
this.stateAnalyzer = new StateAnalyzer(stateConfig);
|
|
36152
36185
|
this.eventEmitter = new DetectionEventEmitter(emitterConfig);
|
|
36153
36186
|
this.zones = [];
|
|
@@ -36254,6 +36287,55 @@ var FrameProcessor = class {
|
|
|
36254
36287
|
* ate it. Sampled like every other refusal: an excluded box is excluded on
|
|
36255
36288
|
* every frame of a passage.
|
|
36256
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
|
+
}
|
|
36257
36339
|
reportZoneFiltered(excluded) {
|
|
36258
36340
|
const observer = this.spawnRefusalObserver;
|
|
36259
36341
|
if (!observer || excluded.length === 0) return;
|
|
@@ -36655,9 +36737,11 @@ var FrameProcessor = class {
|
|
|
36655
36737
|
frameHeight
|
|
36656
36738
|
};
|
|
36657
36739
|
};
|
|
36740
|
+
const zoneRefusalEvents = this.eventsForRefusedDetectionsInZones(timestamp, frame, frameWidth, frameHeight);
|
|
36658
36741
|
const withTrackId = rawEvents.filter((e) => e.detection.trackId);
|
|
36659
36742
|
const objectEvents = withTrackId.filter((e) => e.type !== "object.detected").map((e) => toObjectEvent(e));
|
|
36660
36743
|
const appearanceEvents = withTrackId.filter((e) => e.type === "object.detected").map((e) => toObjectEvent(e, "entered"));
|
|
36744
|
+
objectEvents.push(...zoneRefusalEvents);
|
|
36661
36745
|
return {
|
|
36662
36746
|
deviceId: this.deviceId,
|
|
36663
36747
|
timestamp,
|
|
@@ -47320,6 +47404,7 @@ var TrackDetailDispatcher = class {
|
|
|
47320
47404
|
if (!dev) return;
|
|
47321
47405
|
dev.scheduler.onTrackEnded(trackId);
|
|
47322
47406
|
dev.tracks.delete(trackId);
|
|
47407
|
+
dev.parcelEnriched.delete(trackId);
|
|
47323
47408
|
dev.candidateBest.delete(trackId);
|
|
47324
47409
|
if (dev.tracks.size === 0) this.clearTimer(dev);
|
|
47325
47410
|
}
|
|
@@ -47329,6 +47414,7 @@ var TrackDetailDispatcher = class {
|
|
|
47329
47414
|
this.clearTimer(dev);
|
|
47330
47415
|
dev.queue.length = 0;
|
|
47331
47416
|
dev.tracks.clear();
|
|
47417
|
+
dev.parcelEnriched.clear();
|
|
47332
47418
|
dev.candidateBest.clear();
|
|
47333
47419
|
}
|
|
47334
47420
|
this.devices.clear();
|
|
@@ -47339,6 +47425,7 @@ var TrackDetailDispatcher = class {
|
|
|
47339
47425
|
dev = {
|
|
47340
47426
|
scheduler: new DetailScheduler(),
|
|
47341
47427
|
tracks: /* @__PURE__ */ new Map(),
|
|
47428
|
+
parcelEnriched: /* @__PURE__ */ new Map(),
|
|
47342
47429
|
candidateBest: new BestDetectionTracker(CANDIDATE_IMPROVE_POLICY),
|
|
47343
47430
|
queue: [],
|
|
47344
47431
|
coastedFramesIgnored: 0,
|
|
@@ -47455,19 +47542,21 @@ var TrackDetailDispatcher = class {
|
|
|
47455
47542
|
*
|
|
47456
47543
|
* 1. `frameHandle` — cheapest when it hits: no payload moves and the runner's
|
|
47457
47544
|
* leaf children re-cut their ROI at native resolution. Tried first, always.
|
|
47458
|
-
* 2. `frameJpeg` — the
|
|
47459
|
-
*
|
|
47460
|
-
*
|
|
47461
|
-
*
|
|
47462
|
-
*
|
|
47463
|
-
* 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.
|
|
47464
47553
|
* 3. `cropJpeg` — the pre-cut tile. Last, because the caller decided its
|
|
47465
47554
|
* rectangle and because in practice the same native miss that emptied rung 1
|
|
47466
47555
|
* empties this one too.
|
|
47467
47556
|
*
|
|
47468
|
-
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG
|
|
47469
|
-
* demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47470
|
-
* 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.
|
|
47471
47560
|
*
|
|
47472
47561
|
* ONE outcome stops the ladder cold: the runner's typed `not-attached`
|
|
47473
47562
|
* refusal (`detail-refusal.ts`). It names the camera's SESSION state, not
|
|
@@ -47505,34 +47594,39 @@ var TrackDetailDispatcher = class {
|
|
|
47505
47594
|
}
|
|
47506
47595
|
});
|
|
47507
47596
|
}
|
|
47508
|
-
if (this.deps.
|
|
47509
|
-
const
|
|
47510
|
-
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", {
|
|
47511
47600
|
tags: { deviceId },
|
|
47512
47601
|
meta: {
|
|
47513
47602
|
trackId: req.trackId,
|
|
47514
47603
|
stepId: req.stepId,
|
|
47515
|
-
|
|
47516
|
-
frameHeight: full.height,
|
|
47517
|
-
bboxFrameWidth: frame.frameWidth,
|
|
47518
|
-
bboxFrameHeight: frame.frameHeight
|
|
47604
|
+
parkedTimestamp: parked.timestamp
|
|
47519
47605
|
}
|
|
47520
47606
|
});
|
|
47521
47607
|
else {
|
|
47522
47608
|
const derived = await this.deps.runDetailSubtree({
|
|
47523
47609
|
deviceId,
|
|
47524
|
-
frameJpeg:
|
|
47525
|
-
parent
|
|
47610
|
+
frameJpeg: parked.base64,
|
|
47611
|
+
parent: {
|
|
47612
|
+
bbox: { ...parked.bbox },
|
|
47613
|
+
className: frame.className
|
|
47614
|
+
},
|
|
47526
47615
|
steps
|
|
47527
|
-
},
|
|
47528
|
-
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
|
+
}
|
|
47529
47623
|
}
|
|
47530
47624
|
} catch (err) {
|
|
47531
47625
|
if (isDetailNotAttachedRefusal(err)) {
|
|
47532
47626
|
this.warnNotAttachedThrottled(deviceId, dev, req);
|
|
47533
47627
|
return null;
|
|
47534
47628
|
}
|
|
47535
|
-
this.deps.logger.debug("detail
|
|
47629
|
+
this.deps.logger.debug("detail parked-frame rung failed", {
|
|
47536
47630
|
tags: { deviceId },
|
|
47537
47631
|
meta: {
|
|
47538
47632
|
trackId: req.trackId,
|
|
@@ -47599,7 +47693,7 @@ var TrackDetailDispatcher = class {
|
|
|
47599
47693
|
trackId: req.trackId,
|
|
47600
47694
|
stepId: req.stepId,
|
|
47601
47695
|
reason: req.reason,
|
|
47602
|
-
|
|
47696
|
+
triedParkedFrame: this.deps.captureParkedFrame !== void 0,
|
|
47603
47697
|
triedCropJpeg: this.deps.captureCropBase64 !== void 0
|
|
47604
47698
|
}
|
|
47605
47699
|
});
|
|
@@ -53964,6 +54058,17 @@ async function encodeKeyFrameVariants(native) {
|
|
|
53964
54058
|
})
|
|
53965
54059
|
};
|
|
53966
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
|
+
}
|
|
53967
54072
|
//#endregion
|
|
53968
54073
|
//#region src/pipeline-analytics/pipeline/key-frame-metrics.ts
|
|
53969
54074
|
var DEFAULT_FLUSH_INTERVAL_MS = 6e4;
|
|
@@ -53989,11 +54094,8 @@ var KeyFrameCaptureLog = class {
|
|
|
53989
54094
|
w.landed += 1;
|
|
53990
54095
|
if (sample.retry) w.retryLanded += 1;
|
|
53991
54096
|
break;
|
|
53992
|
-
case "
|
|
53993
|
-
w.
|
|
53994
|
-
break;
|
|
53995
|
-
case "ram-tier":
|
|
53996
|
-
w.ramTier += 1;
|
|
54097
|
+
case "parcel-missing":
|
|
54098
|
+
w.parcelMissing += 1;
|
|
53997
54099
|
break;
|
|
53998
54100
|
case "error":
|
|
53999
54101
|
w.error += 1;
|
|
@@ -54022,8 +54124,7 @@ var KeyFrameCaptureLog = class {
|
|
|
54022
54124
|
landed: 0,
|
|
54023
54125
|
retryAttempts: 0,
|
|
54024
54126
|
retryLanded: 0,
|
|
54025
|
-
|
|
54026
|
-
ramTier: 0,
|
|
54127
|
+
parcelMissing: 0,
|
|
54027
54128
|
error: 0,
|
|
54028
54129
|
skippedNodeOffline: 0
|
|
54029
54130
|
};
|
|
@@ -54038,8 +54139,7 @@ var KeyFrameCaptureLog = class {
|
|
|
54038
54139
|
landed: w.landed,
|
|
54039
54140
|
retryAttempts: w.retryAttempts,
|
|
54040
54141
|
retryLanded: w.retryLanded,
|
|
54041
|
-
|
|
54042
|
-
ramTier: w.ramTier,
|
|
54142
|
+
parcelMissing: w.parcelMissing,
|
|
54043
54143
|
error: w.error,
|
|
54044
54144
|
skippedNodeOffline: w.skippedNodeOffline
|
|
54045
54145
|
};
|
|
@@ -54048,63 +54148,44 @@ var KeyFrameCaptureLog = class {
|
|
|
54048
54148
|
//#endregion
|
|
54049
54149
|
//#region src/pipeline-analytics/pipeline/key-frame-persist.ts
|
|
54050
54150
|
/**
|
|
54051
|
-
*
|
|
54151
|
+
* Read, derive and store one track's key frame + its web companion.
|
|
54052
54152
|
*
|
|
54053
|
-
*
|
|
54054
|
-
*
|
|
54055
|
-
* 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
|
|
54056
54156
|
* retry that would re-shoot a key frame the rebuild can already read.
|
|
54057
54157
|
*/
|
|
54058
54158
|
async function persistTrackKeyFrame(deps, input) {
|
|
54059
|
-
const { deviceId, trackId,
|
|
54159
|
+
const { deviceId, trackId, retry } = input;
|
|
54060
54160
|
const drop = {
|
|
54061
54161
|
tags: { deviceId },
|
|
54062
54162
|
meta: {
|
|
54063
54163
|
trackId,
|
|
54064
|
-
nodeId: frameHandle.nodeId,
|
|
54065
54164
|
retry
|
|
54066
54165
|
}
|
|
54067
54166
|
};
|
|
54068
54167
|
try {
|
|
54069
|
-
const
|
|
54070
|
-
|
|
54071
|
-
|
|
54072
|
-
|
|
54073
|
-
...drop,
|
|
54074
|
-
meta: {
|
|
54075
|
-
...drop.meta,
|
|
54076
|
-
shmId: frameHandle.shmId
|
|
54077
|
-
}
|
|
54078
|
-
});
|
|
54079
|
-
return { kind: "native-miss" };
|
|
54080
|
-
}
|
|
54081
|
-
if (!keyFrameAcceptsTier(result.tier)) {
|
|
54082
|
-
deps.logger.warn("keyFrame native miss — RAM tier rejected, will retry while budget remains", {
|
|
54083
|
-
...drop,
|
|
54084
|
-
meta: {
|
|
54085
|
-
...drop.meta,
|
|
54086
|
-
shmId: frameHandle.shmId,
|
|
54087
|
-
tier: result.tier
|
|
54088
|
-
}
|
|
54089
|
-
});
|
|
54090
|
-
return { kind: "ram-tier" };
|
|
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" };
|
|
54091
54172
|
}
|
|
54092
|
-
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
|
|
54093
54173
|
const keyFrameKey = await deps.media.putReplacing({
|
|
54094
54174
|
deviceId,
|
|
54095
54175
|
ownerKind: "track",
|
|
54096
54176
|
ownerId: trackId,
|
|
54097
54177
|
kind: "keyFrame",
|
|
54098
|
-
timestamp:
|
|
54099
|
-
data:
|
|
54178
|
+
timestamp: parcel.timestamp,
|
|
54179
|
+
data: parcel.jpeg
|
|
54100
54180
|
});
|
|
54101
54181
|
try {
|
|
54182
|
+
const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
|
|
54102
54183
|
await deps.media.putReplacing({
|
|
54103
54184
|
deviceId,
|
|
54104
54185
|
ownerKind: "track",
|
|
54105
54186
|
ownerId: trackId,
|
|
54106
54187
|
kind: "keyFrameSmall",
|
|
54107
|
-
timestamp:
|
|
54188
|
+
timestamp: parcel.timestamp,
|
|
54108
54189
|
data: keyFrameSmall
|
|
54109
54190
|
});
|
|
54110
54191
|
} catch (err) {
|
|
@@ -54112,17 +54193,29 @@ async function persistTrackKeyFrame(deps, input) {
|
|
|
54112
54193
|
...drop,
|
|
54113
54194
|
meta: {
|
|
54114
54195
|
...drop.meta,
|
|
54196
|
+
nodeId: parcel.nodeId,
|
|
54115
54197
|
error: require_dist.errMsg(err)
|
|
54116
54198
|
}
|
|
54117
54199
|
});
|
|
54118
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
|
+
});
|
|
54119
54212
|
return {
|
|
54120
54213
|
kind: "landed",
|
|
54121
54214
|
keyFrameKey
|
|
54122
54215
|
};
|
|
54123
54216
|
} catch (err) {
|
|
54124
54217
|
const error = require_dist.errMsg(err);
|
|
54125
|
-
deps.logger.warn("key-frame
|
|
54218
|
+
deps.logger.warn("key-frame write failed", {
|
|
54126
54219
|
...drop,
|
|
54127
54220
|
meta: {
|
|
54128
54221
|
...drop.meta,
|
|
@@ -54309,6 +54402,61 @@ function createKeyFrameCrop(deps) {
|
|
|
54309
54402
|
};
|
|
54310
54403
|
}
|
|
54311
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
|
|
54312
54460
|
//#region src/pipeline-analytics/pipeline/native-detection.ts
|
|
54313
54461
|
/**
|
|
54314
54462
|
* Nominal frame size used to denormalize native `[0,1]` boxes when a
|
|
@@ -58839,7 +58987,28 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
58839
58987
|
}
|
|
58840
58988
|
//#endregion
|
|
58841
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
|
+
*/
|
|
58842
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
|
+
*/
|
|
58843
59012
|
var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
|
|
58844
59013
|
var SUMMARIES_COLUMNS = [
|
|
58845
59014
|
{
|
|
@@ -63667,35 +63836,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63667
63836
|
const getRemoteFrame = async (handle) => requestNativeCrop(handle, FULL_FRAME_BBOX, handle.width, replyToFullFrame);
|
|
63668
63837
|
const fetchNativeFullFrameTiered = async (handle, maxWidth) => requestNativeCrop(handle, FULL_FRAME_BBOX, maxWidth, replyToTieredFullFrame);
|
|
63669
63838
|
const getNativeFullFrameRgb = async (handle, maxWidth) => (await fetchNativeFullFrameTiered(handle, maxWidth))?.frame ?? null;
|
|
63670
|
-
const getFullFrameJpegBase64 = async (handle, maxWidth) => {
|
|
63671
|
-
if (!pipelineRunnerApi?.getNativeCrop) return null;
|
|
63672
|
-
try {
|
|
63673
|
-
const full = await pipelineRunnerApi.getNativeCrop.query({
|
|
63674
|
-
handle,
|
|
63675
|
-
bbox: {
|
|
63676
|
-
x: 0,
|
|
63677
|
-
y: 0,
|
|
63678
|
-
w: 1,
|
|
63679
|
-
h: 1
|
|
63680
|
-
},
|
|
63681
|
-
maxWidth,
|
|
63682
|
-
encodeJpeg: true
|
|
63683
|
-
}, require_dist.nodePin(handle.nodeId));
|
|
63684
|
-
if (!full?.jpeg || full.width <= 0 || full.height <= 0) return null;
|
|
63685
|
-
return {
|
|
63686
|
-
base64: full.jpeg,
|
|
63687
|
-
width: full.width,
|
|
63688
|
-
height: full.height,
|
|
63689
|
-
tier: full.tier === "ram-fullframe" ? "ram-fullframe" : "native"
|
|
63690
|
-
};
|
|
63691
|
-
} catch (err) {
|
|
63692
|
-
logger.debug("full-frame jpeg fetch failed", { meta: {
|
|
63693
|
-
nodeId: handle.nodeId,
|
|
63694
|
-
error: require_dist.errMsg(err)
|
|
63695
|
-
} });
|
|
63696
|
-
return null;
|
|
63697
|
-
}
|
|
63698
|
-
};
|
|
63699
63839
|
const cropMetricLogger = logger.child("NativeCrop");
|
|
63700
63840
|
let nativeHits = 0;
|
|
63701
63841
|
let nativeFallbacks = 0;
|
|
@@ -63775,7 +63915,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63775
63915
|
getRemoteFrame,
|
|
63776
63916
|
getNativeFullFrameRgb,
|
|
63777
63917
|
getNativeKeyFrameRgb: fetchNativeFullFrameTiered,
|
|
63778
|
-
getFullFrameJpegBase64,
|
|
63779
63918
|
getNativeCropJpeg,
|
|
63780
63919
|
captureCrop: createCaptureCrop({
|
|
63781
63920
|
tryNativeCrop,
|
|
@@ -63886,6 +64025,233 @@ function parseTopologySnapshotNodes(data) {
|
|
|
63886
64025
|
return parsed;
|
|
63887
64026
|
}
|
|
63888
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
|
|
63889
64255
|
//#region src/pipeline-analytics/runtime/widget-manifest.ts
|
|
63890
64256
|
/** The remote-module coordinates shared by every analytics widget. */
|
|
63891
64257
|
var REMOTE_NAME = "addon_pipeline_analytics_widgets";
|
|
@@ -64987,7 +65353,8 @@ function isUniformRgbFrame(data, width, height) {
|
|
|
64987
65353
|
* frame downscaled to {@link SNAPSHOT_MAX_WIDTH} (960) with ONLY the track's own
|
|
64988
65354
|
* box burned in, falling back to the ≤640 detection frame (honest lower res,
|
|
64989
65355
|
* never an upscale) on a native miss. The best `thumbnail` stays a clean,
|
|
64990
|
-
* uncapped native subject crop
|
|
65356
|
+
* uncapped native subject crop — read from the track's PARKED slab, by track
|
|
65357
|
+
* (D422), never by handle.
|
|
64991
65358
|
*
|
|
64992
65359
|
* The detection frame is resolved ONCE per call (blank-frame guard); native full
|
|
64993
65360
|
* frames + subject crops are fetched per target by handle. Frames recycle in
|
|
@@ -65255,10 +65622,10 @@ var EventMediaDispatcher = class {
|
|
|
65255
65622
|
if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
65256
65623
|
let thumbnailWritten = false;
|
|
65257
65624
|
if (sn.bestThumbnail) {
|
|
65258
|
-
const variants = await this.cropSubjectVariants(deviceId, sn.trackId
|
|
65625
|
+
const variants = await this.cropSubjectVariants(deviceId, sn.trackId);
|
|
65259
65626
|
if (variants) {
|
|
65260
|
-
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail",
|
|
65261
|
-
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");
|
|
65262
65629
|
}
|
|
65263
65630
|
}
|
|
65264
65631
|
return {
|
|
@@ -65336,81 +65703,49 @@ var EventMediaDispatcher = class {
|
|
|
65336
65703
|
}
|
|
65337
65704
|
/**
|
|
65338
65705
|
* The best-shot subject crop as its TWO persisted variants (best-crop
|
|
65339
|
-
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22)
|
|
65340
|
-
*
|
|
65341
|
-
*
|
|
65342
|
-
*
|
|
65343
|
-
*
|
|
65344
|
-
* the
|
|
65345
|
-
*
|
|
65346
|
-
*
|
|
65347
|
-
*
|
|
65348
|
-
*
|
|
65349
|
-
*
|
|
65350
|
-
*
|
|
65351
|
-
*
|
|
65352
|
-
*
|
|
65353
|
-
*
|
|
65354
|
-
*
|
|
65355
|
-
*
|
|
65356
|
-
|
|
65357
|
-
|
|
65358
|
-
* upscale of a ≤640 tile (a blurred lie).
|
|
65359
|
-
*/
|
|
65360
|
-
async cropSubjectVariants(deviceId, trackId, frameHandle, fw, fh, bbox) {
|
|
65361
|
-
if (!this.deps.getNativeCropJpeg) {
|
|
65362
|
-
this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
|
|
65363
|
-
tags: { deviceId },
|
|
65364
|
-
meta: {
|
|
65365
|
-
shmId: frameHandle.shmId,
|
|
65366
|
-
reason: "no-native-cap"
|
|
65367
|
-
}
|
|
65368
|
-
});
|
|
65369
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "no-native-cap");
|
|
65370
|
-
return null;
|
|
65371
|
-
}
|
|
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) {
|
|
65372
65725
|
try {
|
|
65373
|
-
const
|
|
65374
|
-
|
|
65375
|
-
|
|
65376
|
-
});
|
|
65377
|
-
if (centralSquareContainmentFraction(bbox, {
|
|
65378
|
-
W: fw,
|
|
65379
|
-
H: fh
|
|
65380
|
-
}) < .5) this.deps.logger.info("best-shot delivered via contained square layout (edge subject)", {
|
|
65381
|
-
tags: { deviceId },
|
|
65382
|
-
meta: {
|
|
65383
|
-
bbox,
|
|
65384
|
-
frameW: fw,
|
|
65385
|
-
frameH: fh
|
|
65386
|
-
}
|
|
65387
|
-
});
|
|
65388
|
-
const askedAt = Date.now();
|
|
65389
|
-
const slab = await this.deps.getNativeCropJpeg(frameHandle, layout.fetch, void 0, deviceId);
|
|
65390
|
-
if (!slab) {
|
|
65391
|
-
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", {
|
|
65392
65729
|
tags: { deviceId },
|
|
65393
65730
|
meta: {
|
|
65394
|
-
|
|
65395
|
-
|
|
65396
|
-
handleNodeId: frameHandle.nodeId,
|
|
65397
|
-
roundTripMs: Date.now() - askedAt,
|
|
65398
|
-
reason: "native-miss"
|
|
65731
|
+
trackId,
|
|
65732
|
+
reason: "parcel-missing"
|
|
65399
65733
|
}
|
|
65400
65734
|
});
|
|
65401
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "
|
|
65735
|
+
this.deps.onSubjectCropMiss?.(deviceId, trackId, "parcel-missing");
|
|
65402
65736
|
return null;
|
|
65403
65737
|
}
|
|
65404
|
-
const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
|
|
65738
|
+
const thumbnail = await composeWideCentralSquareThumbnail(parked.slab, parked.layout);
|
|
65405
65739
|
return {
|
|
65406
65740
|
thumbnail,
|
|
65407
|
-
thumbnailSmall: await deriveThumbnailSmall(thumbnail)
|
|
65741
|
+
thumbnailSmall: await deriveThumbnailSmall(thumbnail),
|
|
65742
|
+
timestamp: parked.timestamp
|
|
65408
65743
|
};
|
|
65409
65744
|
} catch (err) {
|
|
65410
|
-
this.deps.logger.warn("
|
|
65745
|
+
this.deps.logger.warn("best-shot parcel read failed — no thumbnail this frame, will retry", {
|
|
65411
65746
|
tags: { deviceId },
|
|
65412
65747
|
meta: {
|
|
65413
|
-
|
|
65748
|
+
trackId,
|
|
65414
65749
|
reason: "threw",
|
|
65415
65750
|
error: err instanceof Error ? err.message : String(err)
|
|
65416
65751
|
}
|
|
@@ -70701,7 +71036,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70701
71036
|
* the FULL cleanup litany on every teardown path (and cancels the track's
|
|
70702
71037
|
* queued captures via the onClose hook) and `closeDevice` finally clears
|
|
70703
71038
|
* per-track state on device removal (the two documented leaks). */
|
|
70704
|
-
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;
|
|
70705
71050
|
/** Defence in depth for the 2026-08-21 `b50e09f7` incident: a row the track
|
|
70706
71051
|
* store has already CLOSED never earns another media target, whatever the
|
|
70707
71052
|
* tracker keeps handing back. Reads the store's in-RAM close record only —
|
|
@@ -71096,17 +71441,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71096
71441
|
noteCropOutcome: (deviceId, reason) => this.failureReport.noteEnrichmentCrop(deviceId, reason)
|
|
71097
71442
|
});
|
|
71098
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
|
+
});
|
|
71099
71449
|
this.captureCrop = transport.captureCrop;
|
|
71100
71450
|
this.eventMediaDispatcher = new EventMediaDispatcher({
|
|
71101
71451
|
getRemoteFrame: transport.getRemoteFrame,
|
|
71102
71452
|
getNativeCropJpeg: transport.getNativeCropJpeg,
|
|
71103
71453
|
getNativeFullFrameRgb: transport.getNativeFullFrameRgb,
|
|
71454
|
+
getParkedSubjectSlab: (deviceId, trackId) => this.fetchParkedSubjectSlab(deviceId, trackId),
|
|
71104
71455
|
mediaStore: stores.mediaStore,
|
|
71105
71456
|
logger: logger.child("EventMediaDispatcher"),
|
|
71106
71457
|
onSubjectCropMiss: (deviceId, trackId, reason) => this.residents.notePreviewCaptureMiss(deviceId, trackId, `thumbnail:${reason}`)
|
|
71107
71458
|
});
|
|
71108
71459
|
this.buildRecognizers(api, logger, stores, transport);
|
|
71109
|
-
this.buildDetailDispatcher(api, logger
|
|
71460
|
+
this.buildDetailDispatcher(api, logger);
|
|
71110
71461
|
const capProviders = this.buildAnalyticsProviders(api, logger);
|
|
71111
71462
|
await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
|
|
71112
71463
|
await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
|
|
@@ -71936,7 +72287,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71936
72287
|
* demand via `pipelineRunner.runDetailSubtree`. The dispatcher owns the
|
|
71937
72288
|
* per-device schedulers, concurrency cap, and 1 s tick; result routing +
|
|
71938
72289
|
* the pinned call + crop fallback are injected here. */
|
|
71939
|
-
buildDetailDispatcher(api, logger
|
|
72290
|
+
buildDetailDispatcher(api, logger) {
|
|
71940
72291
|
const runnerApi = api.pipelineRunner;
|
|
71941
72292
|
this.detailDispatcher = new TrackDetailDispatcher({
|
|
71942
72293
|
logger: logger.child("DetailDispatcher"),
|
|
@@ -71946,13 +72297,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71946
72297
|
return runnerApi.runDetailSubtree.mutate(input);
|
|
71947
72298
|
},
|
|
71948
72299
|
routeResults: (deviceId, trackId, details, frame) => this.routeDetailResults(deviceId, trackId, details, frame),
|
|
71949
|
-
|
|
71950
|
-
|
|
71951
|
-
|
|
71952
|
-
return
|
|
71953
|
-
base64:
|
|
71954
|
-
width:
|
|
71955
|
-
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
|
|
71956
72310
|
};
|
|
71957
72311
|
},
|
|
71958
72312
|
captureCropBase64: async (frame) => {
|
|
@@ -73594,6 +73948,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73594
73948
|
this.audioMarkers?.flush(Date.now());
|
|
73595
73949
|
this.detailDispatcher?.dispose();
|
|
73596
73950
|
this.detailDispatcher = null;
|
|
73951
|
+
this.parkedFrames?.dispose();
|
|
73597
73952
|
this.overlayState.clear();
|
|
73598
73953
|
this.overlaySynthesisWarnAt.clear();
|
|
73599
73954
|
this.processors.clear();
|
|
@@ -74252,7 +74607,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74252
74607
|
let plateCrops = 0;
|
|
74253
74608
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
74254
74609
|
else plateCrops += 1;
|
|
74255
|
-
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);
|
|
74256
74611
|
this.offerGroupShot(deviceId, result, frameHandle);
|
|
74257
74612
|
for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
74258
74613
|
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
@@ -74371,7 +74726,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74371
74726
|
}).catch(() => {});
|
|
74372
74727
|
}
|
|
74373
74728
|
const keyFrameCaptures = selectKeyFrameCaptures(snapshotTargets);
|
|
74374
|
-
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures
|
|
74729
|
+
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures);
|
|
74375
74730
|
}
|
|
74376
74731
|
if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
|
|
74377
74732
|
deviceId,
|
|
@@ -75301,10 +75656,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75301
75656
|
});
|
|
75302
75657
|
}).catch(() => {});
|
|
75303
75658
|
}
|
|
75304
|
-
|
|
75305
|
-
|
|
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) {
|
|
75306
75697
|
const mediaStore = this.mediaStore;
|
|
75307
|
-
if (
|
|
75698
|
+
if (this.parkedFrames === null || !mediaStore) return;
|
|
75308
75699
|
await Promise.all(captures.map((capture) => this.captureScheduler.request({
|
|
75309
75700
|
deviceId,
|
|
75310
75701
|
trackId: capture.trackId,
|
|
@@ -75314,16 +75705,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75314
75705
|
exec: async () => {
|
|
75315
75706
|
this.residents.noteKeyFrameAttempt(deviceId, capture.trackId, timestamp, capture.retry);
|
|
75316
75707
|
const outcome = await persistTrackKeyFrame({
|
|
75317
|
-
|
|
75318
|
-
|
|
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,
|
|
75319
75720
|
media: mediaStore,
|
|
75320
75721
|
logger: this.ctx.logger
|
|
75321
75722
|
}, {
|
|
75322
75723
|
deviceId,
|
|
75323
75724
|
trackId: capture.trackId,
|
|
75324
|
-
timestamp,
|
|
75325
|
-
frameHandle,
|
|
75326
|
-
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
|
|
75327
75725
|
retry: capture.retry
|
|
75328
75726
|
});
|
|
75329
75727
|
if (outcome.kind === "landed") {
|
|
@@ -75403,10 +75801,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75403
75801
|
isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
|
|
75404
75802
|
captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
|
|
75405
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);
|
|
75406
75813
|
await this.persistKeyFrames(req.deviceId, req.timestamp, [{
|
|
75407
75814
|
trackId: req.trackId,
|
|
75408
75815
|
retry: false
|
|
75409
|
-
}]
|
|
75816
|
+
}]);
|
|
75410
75817
|
}
|
|
75411
75818
|
}, input);
|
|
75412
75819
|
}
|
|
@@ -75460,10 +75867,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75460
75867
|
* (coalescing, per-device budget and land-only bookkeeping identical). */
|
|
75461
75868
|
captureSemanticBestShot(request) {
|
|
75462
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);
|
|
75463
75879
|
this.persistKeyFrames(deviceId, timestamp, [{
|
|
75464
75880
|
trackId,
|
|
75465
75881
|
retry: false
|
|
75466
|
-
}]
|
|
75882
|
+
}]);
|
|
75467
75883
|
const dispatcher = this.eventMediaDispatcher;
|
|
75468
75884
|
if (!dispatcher) return;
|
|
75469
75885
|
if (this.captureScheduler.isKeyActive(captureCoalesceKey(deviceId, "thumbnail", trackId))) {
|
|
@@ -75614,7 +76030,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75614
76030
|
}
|
|
75615
76031
|
return retries;
|
|
75616
76032
|
}
|
|
75617
|
-
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
76033
|
+
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight, frameHandle) {
|
|
75618
76034
|
const targets = [];
|
|
75619
76035
|
for (const t of tracked) {
|
|
75620
76036
|
if (t.matchedThisFrame === false) continue;
|
|
@@ -75700,6 +76116,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75700
76116
|
state: this.residents.keyFrameCapture(t.trackId),
|
|
75701
76117
|
now: timestamp
|
|
75702
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
|
+
}
|
|
75703
76142
|
const suppressedBy = (() => {
|
|
75704
76143
|
if (skipParkedThumb) return "parked-thumbnail";
|
|
75705
76144
|
if (!isNewBest || bestThumbnail) return void 0;
|