@camstack/addon-post-analysis 1.2.214 → 1.2.217
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 +703 -201
- package/dist/pipeline-analytics/index.mjs +695 -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
|
*
|
|
@@ -36075,6 +36091,17 @@ function mapObjectStateToTrackState(s) {
|
|
|
36075
36091
|
default: return "new";
|
|
36076
36092
|
}
|
|
36077
36093
|
}
|
|
36094
|
+
/**
|
|
36095
|
+
* How long an identical refusal stays collapsed. A stationary subject is
|
|
36096
|
+
* refused on every analysed frame; at ~2 fps of analysis, 60 s turns a parked
|
|
36097
|
+
* car's 120 lines into one and still reports a subject that comes back later.
|
|
36098
|
+
*/
|
|
36099
|
+
var ZONE_REFUSAL_THROTTLE_MS = 6e4;
|
|
36100
|
+
/** Box quantisation for the collapse key: a subject that moves less than this
|
|
36101
|
+
* is the same sighting for diagnostic purposes. */
|
|
36102
|
+
var ZONE_REFUSAL_BOX_CELL_PX = 64;
|
|
36103
|
+
/** Bound on the collapse map; stale keys are swept when it is exceeded. */
|
|
36104
|
+
var ZONE_REFUSAL_KEY_LIMIT = 512;
|
|
36078
36105
|
var FrameProcessor = class {
|
|
36079
36106
|
deviceId;
|
|
36080
36107
|
/** Detection source this processor handles (`pipeline` | `onboard`).
|
|
@@ -36134,6 +36161,23 @@ var FrameProcessor = class {
|
|
|
36134
36161
|
/** Sink + sampler for the drops this stage owns (the detection-stage zone
|
|
36135
36162
|
* filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
|
|
36136
36163
|
spawnRefusalObserver = null;
|
|
36164
|
+
/** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
|
|
36165
|
+
spawnRefusalForward = null;
|
|
36166
|
+
/**
|
|
36167
|
+
* The spawn refusals of the frame being processed.
|
|
36168
|
+
*
|
|
36169
|
+
* Filled by the same sink that feeds the diagnostic log, and drained at the
|
|
36170
|
+
* end of `process`. It exists because a refused detection inside a watched
|
|
36171
|
+
* zone still has to become an EVENT: an immediate zone rule asks "is there an
|
|
36172
|
+
* animal in the flowerbed now?", which does not need "is it the same animal
|
|
36173
|
+
* as before?" answered first. See `zone-detection-emits-without-a-track`.
|
|
36174
|
+
*/
|
|
36175
|
+
refusalsThisFrame = [];
|
|
36176
|
+
/** Running count of refusals-in-zone SEEN — never the throttled number. */
|
|
36177
|
+
zoneRefusalEventCount = 0;
|
|
36178
|
+
zoneRefusalObserver = null;
|
|
36179
|
+
/** Last report time per collapsed key, for {@link ZONE_REFUSAL_THROTTLE_MS}. */
|
|
36180
|
+
zoneRefusalLastReport = /* @__PURE__ */ new Map();
|
|
36137
36181
|
zoneRefusalSampler = new SpawnRefusalSampler();
|
|
36138
36182
|
/** Optional stationary-object gate (parked-object suppression). Null until
|
|
36139
36183
|
* the addon wires it via {@link setStationaryGate}. */
|
|
@@ -36148,6 +36192,9 @@ var FrameProcessor = class {
|
|
|
36148
36192
|
this.deviceId = deviceId;
|
|
36149
36193
|
this.source = source;
|
|
36150
36194
|
this.tracker = new SortTracker(trackerConfig);
|
|
36195
|
+
this.tracker.setSpawnRefusalSink((record) => {
|
|
36196
|
+
this.refusalsThisFrame.push(record);
|
|
36197
|
+
});
|
|
36151
36198
|
this.stateAnalyzer = new StateAnalyzer(stateConfig);
|
|
36152
36199
|
this.eventEmitter = new DetectionEventEmitter(emitterConfig);
|
|
36153
36200
|
this.zones = [];
|
|
@@ -36231,6 +36278,14 @@ var FrameProcessor = class {
|
|
|
36231
36278
|
this.tracker.setSpawnRefusalObserver(observer);
|
|
36232
36279
|
this.spawnRefusalObserver = observer;
|
|
36233
36280
|
}
|
|
36281
|
+
/**
|
|
36282
|
+
* Where a refused-inside-a-zone detection goes. Diagnostics only: this is
|
|
36283
|
+
* deliberately NOT the event stream, so nothing downstream can alert on a
|
|
36284
|
+
* box the confirmation gate threw away (D426).
|
|
36285
|
+
*/
|
|
36286
|
+
setZoneRefusalObserver(observer) {
|
|
36287
|
+
this.zoneRefusalObserver = observer;
|
|
36288
|
+
}
|
|
36234
36289
|
setBindObserver(observer) {
|
|
36235
36290
|
this.tracker.setBindObserver(observer);
|
|
36236
36291
|
}
|
|
@@ -36254,6 +36309,88 @@ var FrameProcessor = class {
|
|
|
36254
36309
|
* ate it. Sampled like every other refusal: an excluded box is excluded on
|
|
36255
36310
|
* every frame of a passage.
|
|
36256
36311
|
*/
|
|
36312
|
+
/**
|
|
36313
|
+
* Turn this frame's refused spawns into events, but only those inside a zone.
|
|
36314
|
+
*
|
|
36315
|
+
* Drains {@link refusalsThisFrame}. Each surviving record becomes an
|
|
36316
|
+
* `object.detected` event with NO track — carrying the zones it fell in, the
|
|
36317
|
+
* percentage of the box inside each, and the refusal reason so a consumer can
|
|
36318
|
+
* tell it apart from a track whose media went missing.
|
|
36319
|
+
*
|
|
36320
|
+
* `zone-filtered` refusals are excluded on purpose: that reason means the
|
|
36321
|
+
* operator's own detection rules already said the box does not count on this
|
|
36322
|
+
* camera, and re-admitting it here would overrule a decision they made.
|
|
36323
|
+
*/
|
|
36324
|
+
/**
|
|
36325
|
+
* Report every detection the tracker refused that fell INSIDE a watched
|
|
36326
|
+
* zone. Diagnostics only — see the spec
|
|
36327
|
+
* `zone-refusal-is-observed-never-notified` for the ten hours of production
|
|
36328
|
+
* that decided this: 1473 of 1511 such refusals were one stationary parcel
|
|
36329
|
+
* re-offered every frame, and both notifications this path ever produced
|
|
36330
|
+
* were phantoms the confirmation gate had correctly rejected.
|
|
36331
|
+
*
|
|
36332
|
+
* Drains {@link refusalsThisFrame}. `zone-filtered` is skipped: a box the
|
|
36333
|
+
* zone filter already removed was never in a zone.
|
|
36334
|
+
*/
|
|
36335
|
+
reportRefusedDetectionsInZones(timestamp, frameWidth, frameHeight) {
|
|
36336
|
+
const refusals = this.refusalsThisFrame;
|
|
36337
|
+
this.refusalsThisFrame = [];
|
|
36338
|
+
if (refusals.length === 0 || this.zones.length === 0) return;
|
|
36339
|
+
const observer = this.zoneRefusalObserver;
|
|
36340
|
+
if (observer === null) return;
|
|
36341
|
+
for (const r of refusals) {
|
|
36342
|
+
if (r.reason === "zone-filtered") continue;
|
|
36343
|
+
const { memberships } = this.zoneEngine.splitDetectionZones(r.bbox, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
|
|
36344
|
+
if (memberships.length === 0) continue;
|
|
36345
|
+
this.zoneRefusalEventCount += 1;
|
|
36346
|
+
if (!this.shouldReportZoneRefusal(r, memberships, timestamp)) continue;
|
|
36347
|
+
observer({
|
|
36348
|
+
deviceId: this.deviceId,
|
|
36349
|
+
timestamp,
|
|
36350
|
+
className: r.detClass,
|
|
36351
|
+
confidence: r.detScore,
|
|
36352
|
+
bbox: r.bbox,
|
|
36353
|
+
reason: r.reason,
|
|
36354
|
+
zones: memberships.map((m) => ({
|
|
36355
|
+
zoneId: m.zoneId,
|
|
36356
|
+
...m.zoneName === void 0 ? {} : { zoneName: m.zoneName },
|
|
36357
|
+
overlapPct: Math.round(m.overlap * 1e3) / 10
|
|
36358
|
+
})),
|
|
36359
|
+
seenSoFar: this.zoneRefusalEventCount
|
|
36360
|
+
});
|
|
36361
|
+
}
|
|
36362
|
+
}
|
|
36363
|
+
/**
|
|
36364
|
+
* Collapse identical refusals. A refused box is refused on EVERY analysed
|
|
36365
|
+
* frame, so a subject that does not leave — the parcel at the door, the
|
|
36366
|
+
* parked car — is an unbounded emitter. The key includes a coarse box, so a
|
|
36367
|
+
* subject that actually moves keeps reporting while a stationary one does
|
|
36368
|
+
* not.
|
|
36369
|
+
*/
|
|
36370
|
+
shouldReportZoneRefusal(r, memberships, timestamp) {
|
|
36371
|
+
const cell = (v) => Math.round(v / ZONE_REFUSAL_BOX_CELL_PX);
|
|
36372
|
+
const key = [
|
|
36373
|
+
r.detClass,
|
|
36374
|
+
r.reason,
|
|
36375
|
+
memberships.map((m) => m.zoneId).join("+"),
|
|
36376
|
+
cell(r.bbox.x),
|
|
36377
|
+
cell(r.bbox.y),
|
|
36378
|
+
cell(r.bbox.width),
|
|
36379
|
+
cell(r.bbox.height)
|
|
36380
|
+
].join("|");
|
|
36381
|
+
const last = this.zoneRefusalLastReport.get(key);
|
|
36382
|
+
if (last !== void 0 && timestamp - last < ZONE_REFUSAL_THROTTLE_MS) return false;
|
|
36383
|
+
this.zoneRefusalLastReport.set(key, timestamp);
|
|
36384
|
+
if (this.zoneRefusalLastReport.size > ZONE_REFUSAL_KEY_LIMIT) {
|
|
36385
|
+
for (const [k, t] of this.zoneRefusalLastReport) if (timestamp - t >= ZONE_REFUSAL_THROTTLE_MS) this.zoneRefusalLastReport.delete(k);
|
|
36386
|
+
}
|
|
36387
|
+
return true;
|
|
36388
|
+
}
|
|
36389
|
+
/** How many refusals-in-zone this processor has SEEN — the measure that says
|
|
36390
|
+
* whether this path is a trickle or a storm, before any throttling. */
|
|
36391
|
+
zoneRefusalEventsEmitted() {
|
|
36392
|
+
return this.zoneRefusalEventCount;
|
|
36393
|
+
}
|
|
36257
36394
|
reportZoneFiltered(excluded) {
|
|
36258
36395
|
const observer = this.spawnRefusalObserver;
|
|
36259
36396
|
if (!observer || excluded.length === 0) return;
|
|
@@ -36655,6 +36792,7 @@ var FrameProcessor = class {
|
|
|
36655
36792
|
frameHeight
|
|
36656
36793
|
};
|
|
36657
36794
|
};
|
|
36795
|
+
this.reportRefusedDetectionsInZones(timestamp, frameWidth, frameHeight);
|
|
36658
36796
|
const withTrackId = rawEvents.filter((e) => e.detection.trackId);
|
|
36659
36797
|
const objectEvents = withTrackId.filter((e) => e.type !== "object.detected").map((e) => toObjectEvent(e));
|
|
36660
36798
|
const appearanceEvents = withTrackId.filter((e) => e.type === "object.detected").map((e) => toObjectEvent(e, "entered"));
|
|
@@ -47320,6 +47458,7 @@ var TrackDetailDispatcher = class {
|
|
|
47320
47458
|
if (!dev) return;
|
|
47321
47459
|
dev.scheduler.onTrackEnded(trackId);
|
|
47322
47460
|
dev.tracks.delete(trackId);
|
|
47461
|
+
dev.parcelEnriched.delete(trackId);
|
|
47323
47462
|
dev.candidateBest.delete(trackId);
|
|
47324
47463
|
if (dev.tracks.size === 0) this.clearTimer(dev);
|
|
47325
47464
|
}
|
|
@@ -47329,6 +47468,7 @@ var TrackDetailDispatcher = class {
|
|
|
47329
47468
|
this.clearTimer(dev);
|
|
47330
47469
|
dev.queue.length = 0;
|
|
47331
47470
|
dev.tracks.clear();
|
|
47471
|
+
dev.parcelEnriched.clear();
|
|
47332
47472
|
dev.candidateBest.clear();
|
|
47333
47473
|
}
|
|
47334
47474
|
this.devices.clear();
|
|
@@ -47339,6 +47479,7 @@ var TrackDetailDispatcher = class {
|
|
|
47339
47479
|
dev = {
|
|
47340
47480
|
scheduler: new DetailScheduler(),
|
|
47341
47481
|
tracks: /* @__PURE__ */ new Map(),
|
|
47482
|
+
parcelEnriched: /* @__PURE__ */ new Map(),
|
|
47342
47483
|
candidateBest: new BestDetectionTracker(CANDIDATE_IMPROVE_POLICY),
|
|
47343
47484
|
queue: [],
|
|
47344
47485
|
coastedFramesIgnored: 0,
|
|
@@ -47455,19 +47596,21 @@ var TrackDetailDispatcher = class {
|
|
|
47455
47596
|
*
|
|
47456
47597
|
* 1. `frameHandle` — cheapest when it hits: no payload moves and the runner's
|
|
47457
47598
|
* leaf children re-cut their ROI at native resolution. Tried first, always.
|
|
47458
|
-
* 2. `frameJpeg` — the
|
|
47459
|
-
*
|
|
47460
|
-
*
|
|
47461
|
-
*
|
|
47462
|
-
*
|
|
47463
|
-
* the
|
|
47599
|
+
* 2. `frameJpeg` — the track's PARKED key frame (D422): the full native frame
|
|
47600
|
+
* the owning node copied at the best instant, retrieved by track and handed
|
|
47601
|
+
* to the runner UNCUT, so the rectangle still comes from the single
|
|
47602
|
+
* derivation (D52). This rung exists because rung 1 is structurally
|
|
47603
|
+
* unreachable for most dispatches — a tick + inference routinely lands
|
|
47604
|
+
* outside the lease — and it cannot miss on age. It runs ONCE per parcel
|
|
47605
|
+
* per step; a dispatch that finds the same parcel already enriched skips
|
|
47606
|
+
* it, so a coasting track does not re-embed the same image every second.
|
|
47464
47607
|
* 3. `cropJpeg` — the pre-cut tile. Last, because the caller decided its
|
|
47465
47608
|
* rectangle and because in practice the same native miss that emptied rung 1
|
|
47466
47609
|
* empties this one too.
|
|
47467
47610
|
*
|
|
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.
|
|
47611
|
+
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG retrieved
|
|
47612
|
+
* on demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47613
|
+
* after rung 1 missed — never a frame-rate stream, and never raw pixels.
|
|
47471
47614
|
*
|
|
47472
47615
|
* ONE outcome stops the ladder cold: the runner's typed `not-attached`
|
|
47473
47616
|
* refusal (`detail-refusal.ts`). It names the camera's SESSION state, not
|
|
@@ -47505,34 +47648,39 @@ var TrackDetailDispatcher = class {
|
|
|
47505
47648
|
}
|
|
47506
47649
|
});
|
|
47507
47650
|
}
|
|
47508
|
-
if (this.deps.
|
|
47509
|
-
const
|
|
47510
|
-
if (
|
|
47651
|
+
if (this.deps.captureParkedFrame !== void 0) try {
|
|
47652
|
+
const parked = await this.deps.captureParkedFrame(deviceId, req.trackId);
|
|
47653
|
+
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
47654
|
tags: { deviceId },
|
|
47512
47655
|
meta: {
|
|
47513
47656
|
trackId: req.trackId,
|
|
47514
47657
|
stepId: req.stepId,
|
|
47515
|
-
|
|
47516
|
-
frameHeight: full.height,
|
|
47517
|
-
bboxFrameWidth: frame.frameWidth,
|
|
47518
|
-
bboxFrameHeight: frame.frameHeight
|
|
47658
|
+
parkedTimestamp: parked.timestamp
|
|
47519
47659
|
}
|
|
47520
47660
|
});
|
|
47521
47661
|
else {
|
|
47522
47662
|
const derived = await this.deps.runDetailSubtree({
|
|
47523
47663
|
deviceId,
|
|
47524
|
-
frameJpeg:
|
|
47525
|
-
parent
|
|
47664
|
+
frameJpeg: parked.base64,
|
|
47665
|
+
parent: {
|
|
47666
|
+
bbox: { ...parked.bbox },
|
|
47667
|
+
className: frame.className
|
|
47668
|
+
},
|
|
47526
47669
|
steps
|
|
47527
|
-
},
|
|
47528
|
-
if (derived !== null)
|
|
47670
|
+
}, parked.nodeId);
|
|
47671
|
+
if (derived !== null) {
|
|
47672
|
+
const perStep = dev.parcelEnriched.get(req.trackId) ?? /* @__PURE__ */ new Map();
|
|
47673
|
+
perStep.set(req.stepId, parked.timestamp);
|
|
47674
|
+
dev.parcelEnriched.set(req.trackId, perStep);
|
|
47675
|
+
return derived.details;
|
|
47676
|
+
}
|
|
47529
47677
|
}
|
|
47530
47678
|
} catch (err) {
|
|
47531
47679
|
if (isDetailNotAttachedRefusal(err)) {
|
|
47532
47680
|
this.warnNotAttachedThrottled(deviceId, dev, req);
|
|
47533
47681
|
return null;
|
|
47534
47682
|
}
|
|
47535
|
-
this.deps.logger.debug("detail
|
|
47683
|
+
this.deps.logger.debug("detail parked-frame rung failed", {
|
|
47536
47684
|
tags: { deviceId },
|
|
47537
47685
|
meta: {
|
|
47538
47686
|
trackId: req.trackId,
|
|
@@ -47599,7 +47747,7 @@ var TrackDetailDispatcher = class {
|
|
|
47599
47747
|
trackId: req.trackId,
|
|
47600
47748
|
stepId: req.stepId,
|
|
47601
47749
|
reason: req.reason,
|
|
47602
|
-
|
|
47750
|
+
triedParkedFrame: this.deps.captureParkedFrame !== void 0,
|
|
47603
47751
|
triedCropJpeg: this.deps.captureCropBase64 !== void 0
|
|
47604
47752
|
}
|
|
47605
47753
|
});
|
|
@@ -53964,6 +54112,17 @@ async function encodeKeyFrameVariants(native) {
|
|
|
53964
54112
|
})
|
|
53965
54113
|
};
|
|
53966
54114
|
}
|
|
54115
|
+
/**
|
|
54116
|
+
* The web companion from the PARKED key frame's own JPEG (D422): the parcel is
|
|
54117
|
+
* stored verbatim as `keyFrame`, and `keyFrameSmall` is this — the same bytes
|
|
54118
|
+
* downscaled to {@link KEYFRAME_SMALL_MAX_WIDTH}. One decode, one encode,
|
|
54119
|
+
* never a second fetch; `withoutEnlargement` never upscales a sub-960 frame.
|
|
54120
|
+
* The by-handle twin ({@link encodeKeyFrameVariants}) remains for the
|
|
54121
|
+
* summary group shot, which still fetches a native surface by handle.
|
|
54122
|
+
*/
|
|
54123
|
+
async function deriveKeyFrameSmall(keyFrameJpeg) {
|
|
54124
|
+
return (0, sharp.default)(keyFrameJpeg).resize(960, null, { withoutEnlargement: true }).jpeg({ quality: 88 }).toBuffer();
|
|
54125
|
+
}
|
|
53967
54126
|
//#endregion
|
|
53968
54127
|
//#region src/pipeline-analytics/pipeline/key-frame-metrics.ts
|
|
53969
54128
|
var DEFAULT_FLUSH_INTERVAL_MS = 6e4;
|
|
@@ -53989,11 +54148,8 @@ var KeyFrameCaptureLog = class {
|
|
|
53989
54148
|
w.landed += 1;
|
|
53990
54149
|
if (sample.retry) w.retryLanded += 1;
|
|
53991
54150
|
break;
|
|
53992
|
-
case "
|
|
53993
|
-
w.
|
|
53994
|
-
break;
|
|
53995
|
-
case "ram-tier":
|
|
53996
|
-
w.ramTier += 1;
|
|
54151
|
+
case "parcel-missing":
|
|
54152
|
+
w.parcelMissing += 1;
|
|
53997
54153
|
break;
|
|
53998
54154
|
case "error":
|
|
53999
54155
|
w.error += 1;
|
|
@@ -54022,8 +54178,7 @@ var KeyFrameCaptureLog = class {
|
|
|
54022
54178
|
landed: 0,
|
|
54023
54179
|
retryAttempts: 0,
|
|
54024
54180
|
retryLanded: 0,
|
|
54025
|
-
|
|
54026
|
-
ramTier: 0,
|
|
54181
|
+
parcelMissing: 0,
|
|
54027
54182
|
error: 0,
|
|
54028
54183
|
skippedNodeOffline: 0
|
|
54029
54184
|
};
|
|
@@ -54038,8 +54193,7 @@ var KeyFrameCaptureLog = class {
|
|
|
54038
54193
|
landed: w.landed,
|
|
54039
54194
|
retryAttempts: w.retryAttempts,
|
|
54040
54195
|
retryLanded: w.retryLanded,
|
|
54041
|
-
|
|
54042
|
-
ramTier: w.ramTier,
|
|
54196
|
+
parcelMissing: w.parcelMissing,
|
|
54043
54197
|
error: w.error,
|
|
54044
54198
|
skippedNodeOffline: w.skippedNodeOffline
|
|
54045
54199
|
};
|
|
@@ -54048,63 +54202,44 @@ var KeyFrameCaptureLog = class {
|
|
|
54048
54202
|
//#endregion
|
|
54049
54203
|
//#region src/pipeline-analytics/pipeline/key-frame-persist.ts
|
|
54050
54204
|
/**
|
|
54051
|
-
*
|
|
54205
|
+
* Read, derive and store one track's key frame + its web companion.
|
|
54052
54206
|
*
|
|
54053
|
-
*
|
|
54054
|
-
*
|
|
54055
|
-
* outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
54207
|
+
* PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
|
|
54208
|
+
* parks a later frame. The `keyFrame` write lands FIRST and its key is what
|
|
54209
|
+
* the outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
54056
54210
|
* retry that would re-shoot a key frame the rebuild can already read.
|
|
54057
54211
|
*/
|
|
54058
54212
|
async function persistTrackKeyFrame(deps, input) {
|
|
54059
|
-
const { deviceId, trackId,
|
|
54213
|
+
const { deviceId, trackId, retry } = input;
|
|
54060
54214
|
const drop = {
|
|
54061
54215
|
tags: { deviceId },
|
|
54062
54216
|
meta: {
|
|
54063
54217
|
trackId,
|
|
54064
|
-
nodeId: frameHandle.nodeId,
|
|
54065
54218
|
retry
|
|
54066
54219
|
}
|
|
54067
54220
|
};
|
|
54068
54221
|
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" };
|
|
54222
|
+
const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
|
|
54223
|
+
if (parcel === null || parcel.jpeg.byteLength === 0) {
|
|
54224
|
+
deps.logger.error("key-frame parcel missing — nothing to write, retry parks a later frame", { ...drop });
|
|
54225
|
+
return { kind: "parcel-missing" };
|
|
54091
54226
|
}
|
|
54092
|
-
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
|
|
54093
54227
|
const keyFrameKey = await deps.media.putReplacing({
|
|
54094
54228
|
deviceId,
|
|
54095
54229
|
ownerKind: "track",
|
|
54096
54230
|
ownerId: trackId,
|
|
54097
54231
|
kind: "keyFrame",
|
|
54098
|
-
timestamp:
|
|
54099
|
-
data:
|
|
54232
|
+
timestamp: parcel.timestamp,
|
|
54233
|
+
data: parcel.jpeg
|
|
54100
54234
|
});
|
|
54101
54235
|
try {
|
|
54236
|
+
const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
|
|
54102
54237
|
await deps.media.putReplacing({
|
|
54103
54238
|
deviceId,
|
|
54104
54239
|
ownerKind: "track",
|
|
54105
54240
|
ownerId: trackId,
|
|
54106
54241
|
kind: "keyFrameSmall",
|
|
54107
|
-
timestamp:
|
|
54242
|
+
timestamp: parcel.timestamp,
|
|
54108
54243
|
data: keyFrameSmall
|
|
54109
54244
|
});
|
|
54110
54245
|
} catch (err) {
|
|
@@ -54112,17 +54247,29 @@ async function persistTrackKeyFrame(deps, input) {
|
|
|
54112
54247
|
...drop,
|
|
54113
54248
|
meta: {
|
|
54114
54249
|
...drop.meta,
|
|
54250
|
+
nodeId: parcel.nodeId,
|
|
54115
54251
|
error: require_dist.errMsg(err)
|
|
54116
54252
|
}
|
|
54117
54253
|
});
|
|
54118
54254
|
}
|
|
54255
|
+
deps.logger.debug("key frame written from the parked parcel", {
|
|
54256
|
+
...drop,
|
|
54257
|
+
meta: {
|
|
54258
|
+
...drop.meta,
|
|
54259
|
+
nodeId: parcel.nodeId,
|
|
54260
|
+
width: parcel.width,
|
|
54261
|
+
height: parcel.height,
|
|
54262
|
+
bytes: parcel.jpeg.byteLength,
|
|
54263
|
+
parkedTimestamp: parcel.timestamp
|
|
54264
|
+
}
|
|
54265
|
+
});
|
|
54119
54266
|
return {
|
|
54120
54267
|
kind: "landed",
|
|
54121
54268
|
keyFrameKey
|
|
54122
54269
|
};
|
|
54123
54270
|
} catch (err) {
|
|
54124
54271
|
const error = require_dist.errMsg(err);
|
|
54125
|
-
deps.logger.warn("key-frame
|
|
54272
|
+
deps.logger.warn("key-frame write failed", {
|
|
54126
54273
|
...drop,
|
|
54127
54274
|
meta: {
|
|
54128
54275
|
...drop.meta,
|
|
@@ -54309,6 +54456,61 @@ function createKeyFrameCrop(deps) {
|
|
|
54309
54456
|
};
|
|
54310
54457
|
}
|
|
54311
54458
|
//#endregion
|
|
54459
|
+
//#region src/pipeline-analytics/pipeline/track-park.ts
|
|
54460
|
+
/** The whole frame — what a `keyFrame` park cuts. */
|
|
54461
|
+
var FULL_FRAME = {
|
|
54462
|
+
x: 0,
|
|
54463
|
+
y: 0,
|
|
54464
|
+
w: 1,
|
|
54465
|
+
h: 1
|
|
54466
|
+
};
|
|
54467
|
+
/** The two parcels a best-frame decision orders, in a fixed order. */
|
|
54468
|
+
function buildParkOrders(decision) {
|
|
54469
|
+
const subject = {
|
|
54470
|
+
bbox: { ...decision.bbox },
|
|
54471
|
+
frameWidth: decision.frameWidth,
|
|
54472
|
+
frameHeight: decision.frameHeight
|
|
54473
|
+
};
|
|
54474
|
+
const layout = deliveredWideCentralSquareLayout(decision.bbox, {
|
|
54475
|
+
W: decision.frameWidth,
|
|
54476
|
+
H: decision.frameHeight
|
|
54477
|
+
});
|
|
54478
|
+
const base = {
|
|
54479
|
+
deviceId: decision.deviceId,
|
|
54480
|
+
trackId: decision.trackId,
|
|
54481
|
+
handle: decision.frameHandle,
|
|
54482
|
+
timestamp: decision.timestamp,
|
|
54483
|
+
subject
|
|
54484
|
+
};
|
|
54485
|
+
return [{
|
|
54486
|
+
...base,
|
|
54487
|
+
kind: "keyFrame",
|
|
54488
|
+
bbox: FULL_FRAME,
|
|
54489
|
+
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
54490
|
+
}, {
|
|
54491
|
+
...base,
|
|
54492
|
+
kind: "thumbnail",
|
|
54493
|
+
bbox: layout.fetch,
|
|
54494
|
+
layout
|
|
54495
|
+
}];
|
|
54496
|
+
}
|
|
54497
|
+
/**
|
|
54498
|
+
* The subject's box in the PARCEL's pixel space. The box was measured on the
|
|
54499
|
+
* ≤640 detection raster; the parcel is native-resolution. Both show the same
|
|
54500
|
+
* scene at the same aspect, so a uniform per-axis scale places the box on the
|
|
54501
|
+
* parcel — the runner then derives the crop rectangle from it (D52).
|
|
54502
|
+
*/
|
|
54503
|
+
function scaleSubjectIntoParcel(subject, parcelWidth, parcelHeight) {
|
|
54504
|
+
const sx = parcelWidth / subject.frameWidth;
|
|
54505
|
+
const sy = parcelHeight / subject.frameHeight;
|
|
54506
|
+
return {
|
|
54507
|
+
x: subject.bbox.x * sx,
|
|
54508
|
+
y: subject.bbox.y * sy,
|
|
54509
|
+
w: subject.bbox.w * sx,
|
|
54510
|
+
h: subject.bbox.h * sy
|
|
54511
|
+
};
|
|
54512
|
+
}
|
|
54513
|
+
//#endregion
|
|
54312
54514
|
//#region src/pipeline-analytics/pipeline/native-detection.ts
|
|
54313
54515
|
/**
|
|
54314
54516
|
* Nominal frame size used to denormalize native `[0,1]` boxes when a
|
|
@@ -58839,7 +59041,28 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
58839
59041
|
}
|
|
58840
59042
|
//#endregion
|
|
58841
59043
|
//#region src/pipeline-analytics/summary/summary-store.ts
|
|
59044
|
+
/**
|
|
59045
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
59046
|
+
* write="one row per SUMMARY — a window of co-present tracks on one camera
|
|
59047
|
+
* group. Opened by `openOrAbsorb` when the first member arrives, updated
|
|
59048
|
+
* in place while the window absorbs more, and finalised by `seal` when the
|
|
59049
|
+
* window closes. Per WINDOW, never per frame or per track: the sealed
|
|
59050
|
+
* summary is what a notification rule reads."
|
|
59051
|
+
* retention="age-keyed on `sealedAt`, swept with the rest of the analytics
|
|
59052
|
+
* retention. An unsealed row belongs to a window still open and is never
|
|
59053
|
+
* swept by age — it is closed or cascaded away with its members."
|
|
59054
|
+
*/
|
|
58842
59055
|
var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
|
|
59056
|
+
/**
|
|
59057
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
59058
|
+
* write="one row per (summary, track) membership, written as a window
|
|
59059
|
+
* absorbs a track. The join that lets a sealed summary name its subjects
|
|
59060
|
+
* without re-reading the track store."
|
|
59061
|
+
* retention="cascaded — a member dies with its summary (`delete-track-cascade`
|
|
59062
|
+
* counts them as leaves) or with the track it names. It carries no clock of
|
|
59063
|
+
* its own: an age sweep here could outlive or predecease the summary the
|
|
59064
|
+
* row only means anything inside of."
|
|
59065
|
+
*/
|
|
58843
59066
|
var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
|
|
58844
59067
|
var SUMMARIES_COLUMNS = [
|
|
58845
59068
|
{
|
|
@@ -63667,35 +63890,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63667
63890
|
const getRemoteFrame = async (handle) => requestNativeCrop(handle, FULL_FRAME_BBOX, handle.width, replyToFullFrame);
|
|
63668
63891
|
const fetchNativeFullFrameTiered = async (handle, maxWidth) => requestNativeCrop(handle, FULL_FRAME_BBOX, maxWidth, replyToTieredFullFrame);
|
|
63669
63892
|
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
63893
|
const cropMetricLogger = logger.child("NativeCrop");
|
|
63700
63894
|
let nativeHits = 0;
|
|
63701
63895
|
let nativeFallbacks = 0;
|
|
@@ -63775,7 +63969,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63775
63969
|
getRemoteFrame,
|
|
63776
63970
|
getNativeFullFrameRgb,
|
|
63777
63971
|
getNativeKeyFrameRgb: fetchNativeFullFrameTiered,
|
|
63778
|
-
getFullFrameJpegBase64,
|
|
63779
63972
|
getNativeCropJpeg,
|
|
63780
63973
|
captureCrop: createCaptureCrop({
|
|
63781
63974
|
tryNativeCrop,
|
|
@@ -63886,6 +64079,233 @@ function parseTopologySnapshotNodes(data) {
|
|
|
63886
64079
|
return parsed;
|
|
63887
64080
|
}
|
|
63888
64081
|
//#endregion
|
|
64082
|
+
//#region src/pipeline-analytics/runtime/parked-frame-client.ts
|
|
64083
|
+
/**
|
|
64084
|
+
* Bound on the mirror. A track's record leaves at release; this is the
|
|
64085
|
+
* backstop for a lost close. At the busiest measured hub (~12 sessions,
|
|
64086
|
+
* a few tracks each) 4 096 is an hour of tracks, past which the OLDEST record
|
|
64087
|
+
* is forgotten — its parcel still ages out on the node on its own.
|
|
64088
|
+
*/
|
|
64089
|
+
var MAX_MIRRORED_TRACKS = 4096;
|
|
64090
|
+
/** One "runner has no park method" line per camera per minute. */
|
|
64091
|
+
var SKEW_WARN_THROTTLE_MS = 6e4;
|
|
64092
|
+
function inFlightKey(trackId, kind) {
|
|
64093
|
+
return `${trackId} ${kind}`;
|
|
64094
|
+
}
|
|
64095
|
+
/** Strip the hub-side context off an order: only the wire fields travel. */
|
|
64096
|
+
function toWireInput(order) {
|
|
64097
|
+
return {
|
|
64098
|
+
deviceId: order.deviceId,
|
|
64099
|
+
trackId: order.trackId,
|
|
64100
|
+
kind: order.kind,
|
|
64101
|
+
handle: order.handle,
|
|
64102
|
+
bbox: order.bbox,
|
|
64103
|
+
...order.maxWidth !== void 0 ? { maxWidth: order.maxWidth } : {},
|
|
64104
|
+
timestamp: order.timestamp
|
|
64105
|
+
};
|
|
64106
|
+
}
|
|
64107
|
+
function createParkedFrameClient(deps) {
|
|
64108
|
+
const { api, logger } = deps;
|
|
64109
|
+
const now = deps.now ?? Date.now;
|
|
64110
|
+
/** trackId → kind → what the hub kept beside the parcel. */
|
|
64111
|
+
const mirror = /* @__PURE__ */ new Map();
|
|
64112
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
64113
|
+
const skewWarnedAt = /* @__PURE__ */ new Map();
|
|
64114
|
+
const remember = (order, answer) => {
|
|
64115
|
+
let kinds = mirror.get(order.trackId);
|
|
64116
|
+
if (kinds === void 0) {
|
|
64117
|
+
kinds = /* @__PURE__ */ new Map();
|
|
64118
|
+
mirror.set(order.trackId, kinds);
|
|
64119
|
+
while (mirror.size > MAX_MIRRORED_TRACKS) {
|
|
64120
|
+
const oldest = mirror.keys().next().value;
|
|
64121
|
+
if (oldest === void 0) break;
|
|
64122
|
+
mirror.delete(oldest);
|
|
64123
|
+
}
|
|
64124
|
+
}
|
|
64125
|
+
kinds.set(order.kind, {
|
|
64126
|
+
nodeId: answer.nodeId,
|
|
64127
|
+
deviceId: order.deviceId,
|
|
64128
|
+
kind: order.kind,
|
|
64129
|
+
timestamp: order.timestamp,
|
|
64130
|
+
width: answer.width,
|
|
64131
|
+
height: answer.height,
|
|
64132
|
+
subject: order.subject,
|
|
64133
|
+
...order.layout !== void 0 ? { layout: order.layout } : {}
|
|
64134
|
+
});
|
|
64135
|
+
};
|
|
64136
|
+
const warnSkew = (order) => {
|
|
64137
|
+
const at = skewWarnedAt.get(order.deviceId) ?? 0;
|
|
64138
|
+
if (now() - at < SKEW_WARN_THROTTLE_MS) return;
|
|
64139
|
+
skewWarnedAt.set(order.deviceId, now());
|
|
64140
|
+
logger.warn("track frame park impossible — the runner api has no park method (version skew)", {
|
|
64141
|
+
tags: { deviceId: order.deviceId },
|
|
64142
|
+
meta: {
|
|
64143
|
+
trackId: order.trackId,
|
|
64144
|
+
kind: order.kind,
|
|
64145
|
+
nodeId: order.handle.nodeId
|
|
64146
|
+
}
|
|
64147
|
+
});
|
|
64148
|
+
};
|
|
64149
|
+
const sendPark = async (order) => {
|
|
64150
|
+
if (api === void 0) {
|
|
64151
|
+
warnSkew(order);
|
|
64152
|
+
return false;
|
|
64153
|
+
}
|
|
64154
|
+
if (deps.isNodeOffline?.(order.handle.nodeId) === true) {
|
|
64155
|
+
logger.debug("track frame park skipped — the owning node is offline in the registry", {
|
|
64156
|
+
tags: { deviceId: order.deviceId },
|
|
64157
|
+
meta: {
|
|
64158
|
+
trackId: order.trackId,
|
|
64159
|
+
kind: order.kind,
|
|
64160
|
+
nodeId: order.handle.nodeId
|
|
64161
|
+
}
|
|
64162
|
+
});
|
|
64163
|
+
return false;
|
|
64164
|
+
}
|
|
64165
|
+
const meta = {
|
|
64166
|
+
trackId: order.trackId,
|
|
64167
|
+
kind: order.kind,
|
|
64168
|
+
nodeId: order.handle.nodeId,
|
|
64169
|
+
handle: `${order.handle.shmId}#${order.handle.slot}#${order.handle.seq}`
|
|
64170
|
+
};
|
|
64171
|
+
try {
|
|
64172
|
+
const answer = await api.parkTrackFrame.mutate(toWireInput(order), require_dist.nodePin(order.handle.nodeId));
|
|
64173
|
+
if (answer.parked) {
|
|
64174
|
+
remember(order, answer);
|
|
64175
|
+
logger.debug("track frame parked", {
|
|
64176
|
+
tags: { deviceId: order.deviceId },
|
|
64177
|
+
meta: {
|
|
64178
|
+
...meta,
|
|
64179
|
+
bytes: answer.bytes,
|
|
64180
|
+
replaced: answer.replaced
|
|
64181
|
+
}
|
|
64182
|
+
});
|
|
64183
|
+
return true;
|
|
64184
|
+
}
|
|
64185
|
+
logger.warn("track frame park refused by the owning node", {
|
|
64186
|
+
tags: { deviceId: order.deviceId },
|
|
64187
|
+
meta: {
|
|
64188
|
+
...meta,
|
|
64189
|
+
reason: answer.reason,
|
|
64190
|
+
handleAgeMs: answer.handleAgeMs,
|
|
64191
|
+
...answer.detail !== void 0 ? { detail: answer.detail } : {}
|
|
64192
|
+
}
|
|
64193
|
+
});
|
|
64194
|
+
return false;
|
|
64195
|
+
} catch (err) {
|
|
64196
|
+
logger.warn("track frame park failed — the order did not reach the node", {
|
|
64197
|
+
tags: { deviceId: order.deviceId },
|
|
64198
|
+
meta: {
|
|
64199
|
+
...meta,
|
|
64200
|
+
error: require_dist.errMsg(err)
|
|
64201
|
+
}
|
|
64202
|
+
});
|
|
64203
|
+
return false;
|
|
64204
|
+
}
|
|
64205
|
+
};
|
|
64206
|
+
const park = (order) => {
|
|
64207
|
+
const key = inFlightKey(order.trackId, order.kind);
|
|
64208
|
+
const pending = inFlight.get(key);
|
|
64209
|
+
if (pending !== void 0) return pending;
|
|
64210
|
+
const run = sendPark(order).finally(() => {
|
|
64211
|
+
if (inFlight.get(key) === run) inFlight.delete(key);
|
|
64212
|
+
});
|
|
64213
|
+
inFlight.set(key, run);
|
|
64214
|
+
return run;
|
|
64215
|
+
};
|
|
64216
|
+
const awaitPark = async (trackId, kind) => {
|
|
64217
|
+
const pending = inFlight.get(inFlightKey(trackId, kind));
|
|
64218
|
+
if (pending !== void 0) await pending;
|
|
64219
|
+
};
|
|
64220
|
+
const info = (trackId, kind) => mirror.get(trackId)?.get(kind);
|
|
64221
|
+
const fetch = async (trackId, kind) => {
|
|
64222
|
+
const known = info(trackId, kind);
|
|
64223
|
+
if (known === void 0 || api === void 0) return null;
|
|
64224
|
+
if (deps.isNodeOffline?.(known.nodeId) === true) {
|
|
64225
|
+
logger.warn("parked frame retrieval skipped — the holding node is offline in the registry", {
|
|
64226
|
+
tags: { deviceId: known.deviceId },
|
|
64227
|
+
meta: {
|
|
64228
|
+
trackId,
|
|
64229
|
+
kind,
|
|
64230
|
+
nodeId: known.nodeId
|
|
64231
|
+
}
|
|
64232
|
+
});
|
|
64233
|
+
return null;
|
|
64234
|
+
}
|
|
64235
|
+
try {
|
|
64236
|
+
const parcel = await api.getParkedTrackFrame.query({
|
|
64237
|
+
deviceId: known.deviceId,
|
|
64238
|
+
trackId,
|
|
64239
|
+
kind
|
|
64240
|
+
}, require_dist.nodePin(known.nodeId));
|
|
64241
|
+
if (parcel === null) {
|
|
64242
|
+
logger.warn("parked frame gone at retrieval — the node no longer holds it", {
|
|
64243
|
+
tags: { deviceId: known.deviceId },
|
|
64244
|
+
meta: {
|
|
64245
|
+
trackId,
|
|
64246
|
+
kind,
|
|
64247
|
+
nodeId: known.nodeId,
|
|
64248
|
+
parkedTimestamp: known.timestamp
|
|
64249
|
+
}
|
|
64250
|
+
});
|
|
64251
|
+
return null;
|
|
64252
|
+
}
|
|
64253
|
+
return {
|
|
64254
|
+
jpeg: Buffer.from(parcel.jpeg, "base64"),
|
|
64255
|
+
width: parcel.width,
|
|
64256
|
+
height: parcel.height,
|
|
64257
|
+
timestamp: parcel.timestamp,
|
|
64258
|
+
info: known
|
|
64259
|
+
};
|
|
64260
|
+
} catch (err) {
|
|
64261
|
+
logger.warn("parked frame retrieval failed", {
|
|
64262
|
+
tags: { deviceId: known.deviceId },
|
|
64263
|
+
meta: {
|
|
64264
|
+
trackId,
|
|
64265
|
+
kind,
|
|
64266
|
+
nodeId: known.nodeId,
|
|
64267
|
+
error: require_dist.errMsg(err)
|
|
64268
|
+
}
|
|
64269
|
+
});
|
|
64270
|
+
return null;
|
|
64271
|
+
}
|
|
64272
|
+
};
|
|
64273
|
+
const release = (trackId) => {
|
|
64274
|
+
const kinds = mirror.get(trackId);
|
|
64275
|
+
if (kinds === void 0) return;
|
|
64276
|
+
mirror.delete(trackId);
|
|
64277
|
+
if (api === void 0) return;
|
|
64278
|
+
const targets = /* @__PURE__ */ new Map();
|
|
64279
|
+
for (const record of kinds.values()) targets.set(record.nodeId, record.deviceId);
|
|
64280
|
+
for (const [nodeId, deviceId] of targets) api.releaseParkedTrackFrames.mutate({
|
|
64281
|
+
deviceId,
|
|
64282
|
+
trackId
|
|
64283
|
+
}, require_dist.nodePin(nodeId)).catch((err) => {
|
|
64284
|
+
logger.debug("parked frame release did not reach the node", {
|
|
64285
|
+
tags: { deviceId },
|
|
64286
|
+
meta: {
|
|
64287
|
+
trackId,
|
|
64288
|
+
nodeId,
|
|
64289
|
+
error: require_dist.errMsg(err)
|
|
64290
|
+
}
|
|
64291
|
+
});
|
|
64292
|
+
});
|
|
64293
|
+
};
|
|
64294
|
+
const dispose = () => {
|
|
64295
|
+
mirror.clear();
|
|
64296
|
+
inFlight.clear();
|
|
64297
|
+
skewWarnedAt.clear();
|
|
64298
|
+
};
|
|
64299
|
+
return {
|
|
64300
|
+
park,
|
|
64301
|
+
awaitPark,
|
|
64302
|
+
info,
|
|
64303
|
+
fetch,
|
|
64304
|
+
release,
|
|
64305
|
+
dispose
|
|
64306
|
+
};
|
|
64307
|
+
}
|
|
64308
|
+
//#endregion
|
|
63889
64309
|
//#region src/pipeline-analytics/runtime/widget-manifest.ts
|
|
63890
64310
|
/** The remote-module coordinates shared by every analytics widget. */
|
|
63891
64311
|
var REMOTE_NAME = "addon_pipeline_analytics_widgets";
|
|
@@ -64987,7 +65407,8 @@ function isUniformRgbFrame(data, width, height) {
|
|
|
64987
65407
|
* frame downscaled to {@link SNAPSHOT_MAX_WIDTH} (960) with ONLY the track's own
|
|
64988
65408
|
* box burned in, falling back to the ≤640 detection frame (honest lower res,
|
|
64989
65409
|
* never an upscale) on a native miss. The best `thumbnail` stays a clean,
|
|
64990
|
-
* uncapped native subject crop
|
|
65410
|
+
* uncapped native subject crop — read from the track's PARKED slab, by track
|
|
65411
|
+
* (D422), never by handle.
|
|
64991
65412
|
*
|
|
64992
65413
|
* The detection frame is resolved ONCE per call (blank-frame guard); native full
|
|
64993
65414
|
* frames + subject crops are fetched per target by handle. Frames recycle in
|
|
@@ -65255,10 +65676,10 @@ var EventMediaDispatcher = class {
|
|
|
65255
65676
|
if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
65256
65677
|
let thumbnailWritten = false;
|
|
65257
65678
|
if (sn.bestThumbnail) {
|
|
65258
|
-
const variants = await this.cropSubjectVariants(deviceId, sn.trackId
|
|
65679
|
+
const variants = await this.cropSubjectVariants(deviceId, sn.trackId);
|
|
65259
65680
|
if (variants) {
|
|
65260
|
-
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail",
|
|
65261
|
-
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall",
|
|
65681
|
+
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", variants.timestamp, variants.thumbnail, "native");
|
|
65682
|
+
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", variants.timestamp, variants.thumbnailSmall, "native");
|
|
65262
65683
|
}
|
|
65263
65684
|
}
|
|
65264
65685
|
return {
|
|
@@ -65336,81 +65757,49 @@ var EventMediaDispatcher = class {
|
|
|
65336
65757
|
}
|
|
65337
65758
|
/**
|
|
65338
65759
|
* 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
|
-
}
|
|
65760
|
+
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22), from the
|
|
65761
|
+
* track's PARKED slab (D422). ONE read, by `(deviceId, trackId)`: the
|
|
65762
|
+
* in-frame slab of the 16:9 central-square WINDOW
|
|
65763
|
+
* ({@link wideCentralSquareLayout}) the runner cut at uncapped native
|
|
65764
|
+
* resolution the instant the track's best was decided. It is composed into
|
|
65765
|
+
* the full 16:9 window (lateral letterbox only for the part outside the
|
|
65766
|
+
* frame) → the `thumbnail`. The `thumbnailSmall` is DERIVED by downscaling
|
|
65767
|
+
* that SAME window to the 480 long-side cap — never a second read, never
|
|
65768
|
+
* upscaled ({@link deriveThumbnailSmall} never upscales a smaller window).
|
|
65769
|
+
*
|
|
65770
|
+
* Both variants share the SAME framing + central-square containment
|
|
65771
|
+
* guarantee: wide consumers use the 16:9 image as-is; square consumers
|
|
65772
|
+
* center-crop the middle square (subject always inside it).
|
|
65773
|
+
*
|
|
65774
|
+
* PARCEL-OR-NOTHING: no parcel (or a throw) returns `null` after a loud
|
|
65775
|
+
* `logger.warn`; the caller SKIPS the write and the per-frame retry parks a
|
|
65776
|
+
* later frame. Never a by-handle crop, never a local upscale of a ≤640 tile.
|
|
65777
|
+
*/
|
|
65778
|
+
async cropSubjectVariants(deviceId, trackId) {
|
|
65372
65779
|
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", {
|
|
65780
|
+
const parked = await this.deps.getParkedSubjectSlab(deviceId, trackId);
|
|
65781
|
+
if (!parked) {
|
|
65782
|
+
this.deps.logger.warn("best-shot parcel missing — no thumbnail this frame, will retry", {
|
|
65392
65783
|
tags: { deviceId },
|
|
65393
65784
|
meta: {
|
|
65394
|
-
|
|
65395
|
-
|
|
65396
|
-
handleNodeId: frameHandle.nodeId,
|
|
65397
|
-
roundTripMs: Date.now() - askedAt,
|
|
65398
|
-
reason: "native-miss"
|
|
65785
|
+
trackId,
|
|
65786
|
+
reason: "parcel-missing"
|
|
65399
65787
|
}
|
|
65400
65788
|
});
|
|
65401
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "
|
|
65789
|
+
this.deps.onSubjectCropMiss?.(deviceId, trackId, "parcel-missing");
|
|
65402
65790
|
return null;
|
|
65403
65791
|
}
|
|
65404
|
-
const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
|
|
65792
|
+
const thumbnail = await composeWideCentralSquareThumbnail(parked.slab, parked.layout);
|
|
65405
65793
|
return {
|
|
65406
65794
|
thumbnail,
|
|
65407
|
-
thumbnailSmall: await deriveThumbnailSmall(thumbnail)
|
|
65795
|
+
thumbnailSmall: await deriveThumbnailSmall(thumbnail),
|
|
65796
|
+
timestamp: parked.timestamp
|
|
65408
65797
|
};
|
|
65409
65798
|
} catch (err) {
|
|
65410
|
-
this.deps.logger.warn("
|
|
65799
|
+
this.deps.logger.warn("best-shot parcel read failed — no thumbnail this frame, will retry", {
|
|
65411
65800
|
tags: { deviceId },
|
|
65412
65801
|
meta: {
|
|
65413
|
-
|
|
65802
|
+
trackId,
|
|
65414
65803
|
reason: "threw",
|
|
65415
65804
|
error: err instanceof Error ? err.message : String(err)
|
|
65416
65805
|
}
|
|
@@ -70701,7 +71090,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70701
71090
|
* the FULL cleanup litany on every teardown path (and cancels the track's
|
|
70702
71091
|
* queued captures via the onClose hook) and `closeDevice` finally clears
|
|
70703
71092
|
* per-track state on device removal (the two documented leaks). */
|
|
70704
|
-
residents = new TrackResidentState({ onClose: (trackId) =>
|
|
71093
|
+
residents = new TrackResidentState({ onClose: (trackId) => {
|
|
71094
|
+
this.captureScheduler.cancelForTrack(trackId);
|
|
71095
|
+
this.parkedFrames?.release(trackId);
|
|
71096
|
+
} });
|
|
71097
|
+
/**
|
|
71098
|
+
* The hub's side of a PARK (D422): orders the owning node to keep a track's
|
|
71099
|
+
* best frame the instant `BestDetectionTracker` accepts it, and answers the
|
|
71100
|
+
* keyFrame writer, the best-shot thumbnail and the detail plane's full-frame
|
|
71101
|
+
* rung by `(deviceId, trackId, kind)` — never by handle. Null until init.
|
|
71102
|
+
*/
|
|
71103
|
+
parkedFrames = null;
|
|
70705
71104
|
/** Defence in depth for the 2026-08-21 `b50e09f7` incident: a row the track
|
|
70706
71105
|
* store has already CLOSED never earns another media target, whatever the
|
|
70707
71106
|
* tracker keeps handing back. Reads the store's in-RAM close record only —
|
|
@@ -71096,17 +71495,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71096
71495
|
noteCropOutcome: (deviceId, reason) => this.failureReport.noteEnrichmentCrop(deviceId, reason)
|
|
71097
71496
|
});
|
|
71098
71497
|
this.getNativeKeyFrameRgb = transport.getNativeKeyFrameRgb;
|
|
71498
|
+
this.parkedFrames = createParkedFrameClient({
|
|
71499
|
+
api: api.pipelineRunner,
|
|
71500
|
+
logger: logger.child("ParkedFrames"),
|
|
71501
|
+
isNodeOffline: (nodeId) => this.nodeLiveness.isKnownOffline(nodeId, Date.now())
|
|
71502
|
+
});
|
|
71099
71503
|
this.captureCrop = transport.captureCrop;
|
|
71100
71504
|
this.eventMediaDispatcher = new EventMediaDispatcher({
|
|
71101
71505
|
getRemoteFrame: transport.getRemoteFrame,
|
|
71102
71506
|
getNativeCropJpeg: transport.getNativeCropJpeg,
|
|
71103
71507
|
getNativeFullFrameRgb: transport.getNativeFullFrameRgb,
|
|
71508
|
+
getParkedSubjectSlab: (deviceId, trackId) => this.fetchParkedSubjectSlab(deviceId, trackId),
|
|
71104
71509
|
mediaStore: stores.mediaStore,
|
|
71105
71510
|
logger: logger.child("EventMediaDispatcher"),
|
|
71106
71511
|
onSubjectCropMiss: (deviceId, trackId, reason) => this.residents.notePreviewCaptureMiss(deviceId, trackId, `thumbnail:${reason}`)
|
|
71107
71512
|
});
|
|
71108
71513
|
this.buildRecognizers(api, logger, stores, transport);
|
|
71109
|
-
this.buildDetailDispatcher(api, logger
|
|
71514
|
+
this.buildDetailDispatcher(api, logger);
|
|
71110
71515
|
const capProviders = this.buildAnalyticsProviders(api, logger);
|
|
71111
71516
|
await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
|
|
71112
71517
|
await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
|
|
@@ -71936,7 +72341,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71936
72341
|
* demand via `pipelineRunner.runDetailSubtree`. The dispatcher owns the
|
|
71937
72342
|
* per-device schedulers, concurrency cap, and 1 s tick; result routing +
|
|
71938
72343
|
* the pinned call + crop fallback are injected here. */
|
|
71939
|
-
buildDetailDispatcher(api, logger
|
|
72344
|
+
buildDetailDispatcher(api, logger) {
|
|
71940
72345
|
const runnerApi = api.pipelineRunner;
|
|
71941
72346
|
this.detailDispatcher = new TrackDetailDispatcher({
|
|
71942
72347
|
logger: logger.child("DetailDispatcher"),
|
|
@@ -71946,13 +72351,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71946
72351
|
return runnerApi.runDetailSubtree.mutate(input);
|
|
71947
72352
|
},
|
|
71948
72353
|
routeResults: (deviceId, trackId, details, frame) => this.routeDetailResults(deviceId, trackId, details, frame),
|
|
71949
|
-
|
|
71950
|
-
|
|
71951
|
-
|
|
71952
|
-
return
|
|
71953
|
-
base64:
|
|
71954
|
-
width:
|
|
71955
|
-
height:
|
|
72354
|
+
captureParkedFrame: async (_deviceId, trackId) => {
|
|
72355
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
72356
|
+
if (parcel === null) return null;
|
|
72357
|
+
return {
|
|
72358
|
+
base64: parcel.jpeg.toString("base64"),
|
|
72359
|
+
width: parcel.width,
|
|
72360
|
+
height: parcel.height,
|
|
72361
|
+
bbox: scaleSubjectIntoParcel(parcel.info.subject, parcel.width, parcel.height),
|
|
72362
|
+
nodeId: parcel.info.nodeId,
|
|
72363
|
+
timestamp: parcel.timestamp
|
|
71956
72364
|
};
|
|
71957
72365
|
},
|
|
71958
72366
|
captureCropBase64: async (frame) => {
|
|
@@ -73594,6 +74002,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
73594
74002
|
this.audioMarkers?.flush(Date.now());
|
|
73595
74003
|
this.detailDispatcher?.dispose();
|
|
73596
74004
|
this.detailDispatcher = null;
|
|
74005
|
+
this.parkedFrames?.dispose();
|
|
73597
74006
|
this.overlayState.clear();
|
|
73598
74007
|
this.overlaySynthesisWarnAt.clear();
|
|
73599
74008
|
this.processors.clear();
|
|
@@ -74252,7 +74661,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74252
74661
|
let plateCrops = 0;
|
|
74253
74662
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
74254
74663
|
else plateCrops += 1;
|
|
74255
|
-
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
74664
|
+
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight, frameHandle);
|
|
74256
74665
|
this.offerGroupShot(deviceId, result, frameHandle);
|
|
74257
74666
|
for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
74258
74667
|
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
@@ -74371,7 +74780,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
74371
74780
|
}).catch(() => {});
|
|
74372
74781
|
}
|
|
74373
74782
|
const keyFrameCaptures = selectKeyFrameCaptures(snapshotTargets);
|
|
74374
|
-
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures
|
|
74783
|
+
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures);
|
|
74375
74784
|
}
|
|
74376
74785
|
if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
|
|
74377
74786
|
deviceId,
|
|
@@ -75301,10 +75710,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75301
75710
|
});
|
|
75302
75711
|
}).catch(() => {});
|
|
75303
75712
|
}
|
|
75304
|
-
|
|
75305
|
-
|
|
75713
|
+
/**
|
|
75714
|
+
* Order the parcels a best-frame decision wants (D422). Skipped entirely at
|
|
75715
|
+
* a node the registry already called offline (D58) — logged on the
|
|
75716
|
+
* transition by `noteCapturesGated`, counted in the key-frame window.
|
|
75717
|
+
* `onlyIfMissing` is the retry rule: a later frame may fill a kind that has
|
|
75718
|
+
* no parcel, never replace one the ranking preferred.
|
|
75719
|
+
*/
|
|
75720
|
+
issueParkOrders(decision, kinds, onlyIfMissing) {
|
|
75721
|
+
const client = this.parkedFrames;
|
|
75722
|
+
if (client === null) return;
|
|
75723
|
+
if (this.nodeLiveness.isKnownOffline(decision.frameHandle.nodeId, Date.now())) {
|
|
75724
|
+
this.noteCapturesGated(decision.deviceId, decision.frameHandle.nodeId);
|
|
75725
|
+
return;
|
|
75726
|
+
}
|
|
75727
|
+
for (const order of buildParkOrders(decision)) {
|
|
75728
|
+
if (!kinds.includes(order.kind)) continue;
|
|
75729
|
+
if (onlyIfMissing && client.info(order.trackId, order.kind) !== void 0) continue;
|
|
75730
|
+
client.park(order);
|
|
75731
|
+
}
|
|
75732
|
+
}
|
|
75733
|
+
/** A parcel by track, after any park in flight for it has landed. */
|
|
75734
|
+
async fetchParkedParcel(trackId, kind) {
|
|
75735
|
+
const client = this.parkedFrames;
|
|
75736
|
+
if (client === null) return null;
|
|
75737
|
+
await client.awaitPark(trackId, kind);
|
|
75738
|
+
return client.fetch(trackId, kind);
|
|
75739
|
+
}
|
|
75740
|
+
/** The best-shot slab for the dispatcher: the parcel plus the layout the hub kept. */
|
|
75741
|
+
async fetchParkedSubjectSlab(_deviceId, trackId) {
|
|
75742
|
+
const parcel = await this.fetchParkedParcel(trackId, "thumbnail");
|
|
75743
|
+
if (parcel === null || parcel.info.layout === void 0) return null;
|
|
75744
|
+
return {
|
|
75745
|
+
slab: parcel.jpeg,
|
|
75746
|
+
layout: parcel.info.layout,
|
|
75747
|
+
timestamp: parcel.timestamp
|
|
75748
|
+
};
|
|
75749
|
+
}
|
|
75750
|
+
async persistKeyFrames(deviceId, timestamp, captures) {
|
|
75306
75751
|
const mediaStore = this.mediaStore;
|
|
75307
|
-
if (
|
|
75752
|
+
if (this.parkedFrames === null || !mediaStore) return;
|
|
75308
75753
|
await Promise.all(captures.map((capture) => this.captureScheduler.request({
|
|
75309
75754
|
deviceId,
|
|
75310
75755
|
trackId: capture.trackId,
|
|
@@ -75314,16 +75759,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75314
75759
|
exec: async () => {
|
|
75315
75760
|
this.residents.noteKeyFrameAttempt(deviceId, capture.trackId, timestamp, capture.retry);
|
|
75316
75761
|
const outcome = await persistTrackKeyFrame({
|
|
75317
|
-
|
|
75318
|
-
|
|
75762
|
+
fetchParkedKeyFrame: async (parcelDeviceId, trackId) => {
|
|
75763
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
75764
|
+
if (parcel === null || parcel.info.deviceId !== parcelDeviceId) return null;
|
|
75765
|
+
return {
|
|
75766
|
+
jpeg: parcel.jpeg,
|
|
75767
|
+
width: parcel.width,
|
|
75768
|
+
height: parcel.height,
|
|
75769
|
+
timestamp: parcel.timestamp,
|
|
75770
|
+
nodeId: parcel.info.nodeId
|
|
75771
|
+
};
|
|
75772
|
+
},
|
|
75773
|
+
deriveSmall: deriveKeyFrameSmall,
|
|
75319
75774
|
media: mediaStore,
|
|
75320
75775
|
logger: this.ctx.logger
|
|
75321
75776
|
}, {
|
|
75322
75777
|
deviceId,
|
|
75323
75778
|
trackId: capture.trackId,
|
|
75324
|
-
timestamp,
|
|
75325
|
-
frameHandle,
|
|
75326
|
-
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
|
|
75327
75779
|
retry: capture.retry
|
|
75328
75780
|
});
|
|
75329
75781
|
if (outcome.kind === "landed") {
|
|
@@ -75403,10 +75855,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75403
75855
|
isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
|
|
75404
75856
|
captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
|
|
75405
75857
|
captureKeyFrame: async (req) => {
|
|
75858
|
+
this.issueParkOrders({
|
|
75859
|
+
deviceId: req.deviceId,
|
|
75860
|
+
trackId: req.trackId,
|
|
75861
|
+
timestamp: req.timestamp,
|
|
75862
|
+
frameHandle: req.frameHandle,
|
|
75863
|
+
bbox: { ...req.bodyBbox },
|
|
75864
|
+
frameWidth: req.frameWidth,
|
|
75865
|
+
frameHeight: req.frameHeight
|
|
75866
|
+
}, ["keyFrame"], true);
|
|
75406
75867
|
await this.persistKeyFrames(req.deviceId, req.timestamp, [{
|
|
75407
75868
|
trackId: req.trackId,
|
|
75408
75869
|
retry: false
|
|
75409
|
-
}]
|
|
75870
|
+
}]);
|
|
75410
75871
|
}
|
|
75411
75872
|
}, input);
|
|
75412
75873
|
}
|
|
@@ -75460,10 +75921,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75460
75921
|
* (coalescing, per-device budget and land-only bookkeeping identical). */
|
|
75461
75922
|
captureSemanticBestShot(request) {
|
|
75462
75923
|
const { deviceId, trackId, timestamp, bbox, frameHandle } = request;
|
|
75924
|
+
this.issueParkOrders({
|
|
75925
|
+
deviceId,
|
|
75926
|
+
trackId,
|
|
75927
|
+
timestamp,
|
|
75928
|
+
frameHandle,
|
|
75929
|
+
bbox: { ...bbox },
|
|
75930
|
+
frameWidth: request.frameWidth,
|
|
75931
|
+
frameHeight: request.frameHeight
|
|
75932
|
+
}, ["keyFrame", "thumbnail"], false);
|
|
75463
75933
|
this.persistKeyFrames(deviceId, timestamp, [{
|
|
75464
75934
|
trackId,
|
|
75465
75935
|
retry: false
|
|
75466
|
-
}]
|
|
75936
|
+
}]);
|
|
75467
75937
|
const dispatcher = this.eventMediaDispatcher;
|
|
75468
75938
|
if (!dispatcher) return;
|
|
75469
75939
|
if (this.captureScheduler.isKeyActive(captureCoalesceKey(deviceId, "thumbnail", trackId))) {
|
|
@@ -75614,7 +76084,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75614
76084
|
}
|
|
75615
76085
|
return retries;
|
|
75616
76086
|
}
|
|
75617
|
-
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
76087
|
+
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight, frameHandle) {
|
|
75618
76088
|
const targets = [];
|
|
75619
76089
|
for (const t of tracked) {
|
|
75620
76090
|
if (t.matchedThisFrame === false) continue;
|
|
@@ -75700,6 +76170,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
75700
76170
|
state: this.residents.keyFrameCapture(t.trackId),
|
|
75701
76171
|
now: timestamp
|
|
75702
76172
|
}).kind;
|
|
76173
|
+
if (frameHandle !== void 0 && plausibleBox) {
|
|
76174
|
+
const decision = {
|
|
76175
|
+
deviceId,
|
|
76176
|
+
trackId: t.trackId,
|
|
76177
|
+
timestamp,
|
|
76178
|
+
frameHandle,
|
|
76179
|
+
bbox: {
|
|
76180
|
+
x: t.bbox.x,
|
|
76181
|
+
y: t.bbox.y,
|
|
76182
|
+
w: t.bbox.w,
|
|
76183
|
+
h: t.bbox.h
|
|
76184
|
+
},
|
|
76185
|
+
frameWidth,
|
|
76186
|
+
frameHeight
|
|
76187
|
+
};
|
|
76188
|
+
if (isNewBest) this.issueParkOrders(decision, ["keyFrame", "thumbnail"], false);
|
|
76189
|
+
else {
|
|
76190
|
+
const retryKinds = [];
|
|
76191
|
+
if (keyFrame === "retry") retryKinds.push("keyFrame");
|
|
76192
|
+
if (bestThumbnail) retryKinds.push("thumbnail");
|
|
76193
|
+
if (retryKinds.length > 0) this.issueParkOrders(decision, retryKinds, true);
|
|
76194
|
+
}
|
|
76195
|
+
}
|
|
75703
76196
|
const suppressedBy = (() => {
|
|
75704
76197
|
if (skipParkedThumb) return "parked-thumbnail";
|
|
75705
76198
|
if (!isNewBest || bestThumbnail) return void 0;
|
|
@@ -77115,6 +77608,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
77115
77608
|
}
|
|
77116
77609
|
});
|
|
77117
77610
|
});
|
|
77611
|
+
p.setZoneRefusalObserver((observation) => {
|
|
77612
|
+
this.ctx.logger.info("detection refused inside a watched zone — not notified", {
|
|
77613
|
+
tags: { deviceId },
|
|
77614
|
+
meta: {
|
|
77615
|
+
source,
|
|
77616
|
+
...observation
|
|
77617
|
+
}
|
|
77618
|
+
});
|
|
77619
|
+
});
|
|
77118
77620
|
p.setSpawnBesideCoastingObserver((record) => {
|
|
77119
77621
|
this.ctx.logger.info("tracker: new id minted beside a live coasting track", {
|
|
77120
77622
|
tags: { deviceId },
|