@camstack/addon-post-analysis 1.2.213 → 1.2.216
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-DA1sJKXM.js → dist-B_6UZCut.js} +204 -17
- package/dist/{dist-CT2SouNJ.mjs → dist-DWZqNymj.mjs} +204 -17
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CxVox7bu.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BmDXGilB.mjs} +3 -3
- package/dist/pipeline-analytics/{_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-CcE47LPf.mjs → _virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Dgmog5ky.mjs} +1 -1
- package/dist/pipeline-analytics/{hostInit-BSVhT9bi.mjs → hostInit-DMDZJw2T.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +761 -201
- package/dist/pipeline-analytics/index.mjs +753 -193
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as VISIT_MERGE_GAP_MS, $t as object, A as NcRulePatchSchema, Bt as CamProfileSchema, C as NC_ALARM_SYSTEM_EVENT_KINDS, D as NC_TAXONOMY, Dt as readDeviceStateFrom, Et as plateGalleryCapability, F as NcSnoozeSchema, Ft as vectorDimFromBase64, G as SCENE_DEFAULT_UNCOVERED_POLICY, Gt as nodePin, H as RetrainStatusSchema, Ht as createEvent, I as NcSnoozeSuppressedSchema, It as videoclipsCapability, J as TIMELAPSE_DENSE_FLOOR_SEC, Jt as array, K as SCENE_DIVERGED, Kt as sleep$1, L as NcSystemEventKindSchema, Lt as zoneAnalyticsCapability, M as NcRuleTargetSchema, Mt as storageOccupancyCapability, N as NcScheduleSchema, Nt as subKindsOf, O as NcConditionDescriptorSchema, Ot as readTimelapseGeneratedAt, P as NcSnoozeInputSchema, Pt as systemEventFilterApplies, Q as TrackSourceSchema, Qt as number, R as NcTaxonomySchema, Rt as errMsg$1, S as MediaFileKindEnum, St as notificationRulesCapability, T as NC_DEFAULT_SNOOZE_MINUTES, Tt as pipelineAnalyticsCapability, Ut as hydrateSchema, V as RECORDING_EXPORT_MAX_READ_BYTES, Vt as DeviceType, W as SCENE_DEFAULT_ANCHOR_THRESHOLD, Wt as isDeviceScopedCap, X as TimelapseRulePatchSchema, Xt as discriminatedUnion, Y as TimelapseRuleInputSchema, Yt as boolean, Z as TimelapseRuleSchema, Zt as literal, _ as LabelAttributionSchema, _t as isDetectionMacroClass, a as CLUSTER_MODEL_SCOPED_STEPS, at as buildEventKindDescriptor, b as MAX_BIRTH_DECISION_RECORDS, bt as kebabToCamel, ct as defineCustomActions, dt as encodeVectorBase64, en as partialRecord, et as addonWidgetsSourceCapability, f as DeclaredDevices, ft as evaluateSensorEdge, g as FailureCounters, h as FULL_IMAGE_BBOX, ht as failureContributionCapability, i as BirthDecisionRecordSchema, in as EventCategory, it as audioModeOf, j as NcRuleSchema, jt as sceneMonitorCapability, k as NcRuleInputSchema, kt as resolveLocationMode, lt as deriveRecordingMode, m as EVENT_PAD_MS, mt as faceGalleryCapability, n as ArchivedDebugNoteSchema, nn as string, nt as assertTimelapseCadences, o as COCO_TO_MACRO, ot as cosineSimilarity$1, p as EVENT_KIND_BY_CAP, pt as evictionPolicyOfLocation, q as SceneMonitorSchema, qt as _enum, r as BaseDevice, rn as unknown, rt as audioMetricsCapability, s as DEFAULT_EVENT_COLOR, st as customAction, t as AUDIO_MACRO_LABELS, tn as record, tt as alarmPanelCapability, u as DETECTION_MACRO_CLASSES, v as MACRO_LABELS, vt as isScheduleActive, w as NC_CONDITION_CATALOG, wt as pickClusterStepModels, xt as mayWriteToLocation, y as MAX_ARCHIVED_DEBUG_NOTES, yt as isSourceCap, z as OpsLogEntrySchema, zt as BaseAddon } from "../dist-
|
|
1
|
+
import { $ as VISIT_MERGE_GAP_MS, $t as object, A as NcRulePatchSchema, Bt as CamProfileSchema, C as NC_ALARM_SYSTEM_EVENT_KINDS, D as NC_TAXONOMY, Dt as readDeviceStateFrom, Et as plateGalleryCapability, F as NcSnoozeSchema, Ft as vectorDimFromBase64, G as SCENE_DEFAULT_UNCOVERED_POLICY, Gt as nodePin, H as RetrainStatusSchema, Ht as createEvent, I as NcSnoozeSuppressedSchema, It as videoclipsCapability, J as TIMELAPSE_DENSE_FLOOR_SEC, Jt as array, K as SCENE_DIVERGED, Kt as sleep$1, L as NcSystemEventKindSchema, Lt as zoneAnalyticsCapability, M as NcRuleTargetSchema, Mt as storageOccupancyCapability, N as NcScheduleSchema, Nt as subKindsOf, O as NcConditionDescriptorSchema, Ot as readTimelapseGeneratedAt, P as NcSnoozeInputSchema, Pt as systemEventFilterApplies, Q as TrackSourceSchema, Qt as number, R as NcTaxonomySchema, Rt as errMsg$1, S as MediaFileKindEnum, St as notificationRulesCapability, T as NC_DEFAULT_SNOOZE_MINUTES, Tt as pipelineAnalyticsCapability, Ut as hydrateSchema, V as RECORDING_EXPORT_MAX_READ_BYTES, Vt as DeviceType, W as SCENE_DEFAULT_ANCHOR_THRESHOLD, Wt as isDeviceScopedCap, X as TimelapseRulePatchSchema, Xt as discriminatedUnion, Y as TimelapseRuleInputSchema, Yt as boolean, Z as TimelapseRuleSchema, Zt as literal, _ as LabelAttributionSchema, _t as isDetectionMacroClass, a as CLUSTER_MODEL_SCOPED_STEPS, at as buildEventKindDescriptor, b as MAX_BIRTH_DECISION_RECORDS, bt as kebabToCamel, ct as defineCustomActions, dt as encodeVectorBase64, en as partialRecord, et as addonWidgetsSourceCapability, f as DeclaredDevices, ft as evaluateSensorEdge, g as FailureCounters, h as FULL_IMAGE_BBOX, ht as failureContributionCapability, i as BirthDecisionRecordSchema, in as EventCategory, it as audioModeOf, j as NcRuleSchema, jt as sceneMonitorCapability, k as NcRuleInputSchema, kt as resolveLocationMode, lt as deriveRecordingMode, m as EVENT_PAD_MS, mt as faceGalleryCapability, n as ArchivedDebugNoteSchema, nn as string, nt as assertTimelapseCadences, o as COCO_TO_MACRO, ot as cosineSimilarity$1, p as EVENT_KIND_BY_CAP, pt as evictionPolicyOfLocation, q as SceneMonitorSchema, qt as _enum, r as BaseDevice, rn as unknown, rt as audioMetricsCapability, s as DEFAULT_EVENT_COLOR, st as customAction, t as AUDIO_MACRO_LABELS, tn as record, tt as alarmPanelCapability, u as DETECTION_MACRO_CLASSES, v as MACRO_LABELS, vt as isScheduleActive, w as NC_CONDITION_CATALOG, wt as pickClusterStepModels, xt as mayWriteToLocation, y as MAX_ARCHIVED_DEBUG_NOTES, yt as isSourceCap, z as OpsLogEntrySchema, zt as BaseAddon } from "../dist-DWZqNymj.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -6,6 +6,7 @@ import * as path$1 from "node:path";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
8
8
|
import { execFile } from "node:child_process";
|
|
9
|
+
import { startEventLoopStallMonitor } from "@camstack/system/addon-utils";
|
|
9
10
|
import sharp from "sharp";
|
|
10
11
|
import os from "node:os";
|
|
11
12
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
@@ -14913,7 +14914,7 @@ function buildOccupancyStatus(input) {
|
|
|
14913
14914
|
* `this.byId.clear()` happened to sit AFTER the `await` that threw. Nothing
|
|
14914
14915
|
* said so, and hoisting one line would have silently turned every transient
|
|
14915
14916
|
* store error into a hub that stops notifying — the reversal behind the D130
|
|
14916
|
-
* flood and the
|
|
14917
|
+
* flood and the occupancy cold-seed loss. The ledger's contract
|
|
14917
14918
|
* rule 2 (a failed load NEVER clears the mirror, deliberately no knob) makes
|
|
14918
14919
|
* that reversal unexpressible here. `rule-store-load-failure-direction.spec.ts`
|
|
14919
14920
|
* holds the direction and records what was removed to make it red.
|
|
@@ -18130,7 +18131,7 @@ function clockOf$2(atMs) {
|
|
|
18130
18131
|
*
|
|
18131
18132
|
* ## The `.partial()` trap this file exists to avoid
|
|
18132
18133
|
*
|
|
18133
|
-
*
|
|
18134
|
+
* Found in production 2026-08-12: `NcRulePatchSchema` was
|
|
18134
18135
|
* `NcRuleInputSchema.partial()`, and Zod 4 `.partial()` KEEPS the inner
|
|
18135
18136
|
* `.default()`. Parsing `{ name: 'x' }` therefore materialised every defaulted
|
|
18136
18137
|
* key, and `{ ...existing, ...patch }` wrote them over the rule — a camera- and
|
|
@@ -34880,6 +34881,8 @@ var SortTracker = class SortTracker {
|
|
|
34880
34881
|
refusalSampler = new SpawnRefusalSampler();
|
|
34881
34882
|
/** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
|
|
34882
34883
|
spawnRefusalObserver = null;
|
|
34884
|
+
/** Unsampled copy for the frame path — see `setSpawnRefusalSink`. */
|
|
34885
|
+
refusalSink = null;
|
|
34883
34886
|
/** Per-reason counters behind the sampled spawn-beside-coasting diagnostics. */
|
|
34884
34887
|
spawnBesideSampler = new SpawnBesideCoastingSampler();
|
|
34885
34888
|
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
@@ -34992,11 +34995,7 @@ var SortTracker = class SortTracker {
|
|
|
34992
34995
|
}
|
|
34993
34996
|
/** Emit one dropped-detection record, subject to the refusal sampler. */
|
|
34994
34997
|
reportRefusal(reason, det, extra = {}) {
|
|
34995
|
-
const
|
|
34996
|
-
if (!observer) return;
|
|
34997
|
-
const refusalCount = this.refusalSampler.next(reason);
|
|
34998
|
-
if (refusalCount === void 0) return;
|
|
34999
|
-
observer({
|
|
34998
|
+
const base = {
|
|
35000
34999
|
reason,
|
|
35001
35000
|
detClass: det.class,
|
|
35002
35001
|
detScore: det.score,
|
|
@@ -35008,13 +35007,31 @@ var SortTracker = class SortTracker {
|
|
|
35008
35007
|
againstTrackId: extra.against.id,
|
|
35009
35008
|
againstTrackClass: extra.against.class
|
|
35010
35009
|
} : {},
|
|
35011
|
-
refusalCount,
|
|
35012
|
-
sampledEvery: 10,
|
|
35013
35010
|
...extra.droppedTrackId !== void 0 ? { droppedTrackId: extra.droppedTrackId } : {},
|
|
35014
35011
|
...extra.trackLifeMs !== void 0 ? { trackLifeMs: extra.trackLifeMs } : {}
|
|
35012
|
+
};
|
|
35013
|
+
this.refusalSink?.(base);
|
|
35014
|
+
const observer = this.spawnRefusalObserver;
|
|
35015
|
+
if (!observer) return;
|
|
35016
|
+
const refusalCount = this.refusalSampler.next(reason);
|
|
35017
|
+
if (refusalCount === void 0) return;
|
|
35018
|
+
observer({
|
|
35019
|
+
...base,
|
|
35020
|
+
refusalCount,
|
|
35021
|
+
sampledEvery: 10
|
|
35015
35022
|
});
|
|
35016
35023
|
}
|
|
35017
35024
|
/**
|
|
35025
|
+
* Wire the UNSAMPLED refusal sink — the frame path's copy.
|
|
35026
|
+
*
|
|
35027
|
+
* Separate from {@link setSpawnRefusalObserver} because the two want
|
|
35028
|
+
* different things from the same record: the observer wants a readable log
|
|
35029
|
+
* and accepts a sample; this decides whether an EVENT exists.
|
|
35030
|
+
*/
|
|
35031
|
+
setSpawnRefusalSink(sink) {
|
|
35032
|
+
this.refusalSink = sink;
|
|
35033
|
+
}
|
|
35034
|
+
/**
|
|
35018
35035
|
* Report a TRACK that ends having emitted nothing — the last silent branch
|
|
35019
35036
|
* on the detection→position path (D391).
|
|
35020
35037
|
*
|
|
@@ -36108,6 +36125,20 @@ var FrameProcessor = class {
|
|
|
36108
36125
|
/** Sink + sampler for the drops this stage owns (the detection-stage zone
|
|
36109
36126
|
* filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
|
|
36110
36127
|
spawnRefusalObserver = null;
|
|
36128
|
+
/** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
|
|
36129
|
+
spawnRefusalForward = null;
|
|
36130
|
+
/**
|
|
36131
|
+
* The spawn refusals of the frame being processed.
|
|
36132
|
+
*
|
|
36133
|
+
* Filled by the same sink that feeds the diagnostic log, and drained at the
|
|
36134
|
+
* end of `process`. It exists because a refused detection inside a watched
|
|
36135
|
+
* zone still has to become an EVENT: an immediate zone rule asks "is there an
|
|
36136
|
+
* animal in the flowerbed now?", which does not need "is it the same animal
|
|
36137
|
+
* as before?" answered first. See `zone-detection-emits-without-a-track`.
|
|
36138
|
+
*/
|
|
36139
|
+
refusalsThisFrame = [];
|
|
36140
|
+
/** Running count of events this path has produced — the volume measure. */
|
|
36141
|
+
zoneRefusalEventCount = 0;
|
|
36111
36142
|
zoneRefusalSampler = new SpawnRefusalSampler();
|
|
36112
36143
|
/** Optional stationary-object gate (parked-object suppression). Null until
|
|
36113
36144
|
* the addon wires it via {@link setStationaryGate}. */
|
|
@@ -36122,6 +36153,9 @@ var FrameProcessor = class {
|
|
|
36122
36153
|
this.deviceId = deviceId;
|
|
36123
36154
|
this.source = source;
|
|
36124
36155
|
this.tracker = new SortTracker(trackerConfig);
|
|
36156
|
+
this.tracker.setSpawnRefusalSink((record) => {
|
|
36157
|
+
this.refusalsThisFrame.push(record);
|
|
36158
|
+
});
|
|
36125
36159
|
this.stateAnalyzer = new StateAnalyzer(stateConfig);
|
|
36126
36160
|
this.eventEmitter = new DetectionEventEmitter(emitterConfig);
|
|
36127
36161
|
this.zones = [];
|
|
@@ -36228,6 +36262,55 @@ var FrameProcessor = class {
|
|
|
36228
36262
|
* ate it. Sampled like every other refusal: an excluded box is excluded on
|
|
36229
36263
|
* every frame of a passage.
|
|
36230
36264
|
*/
|
|
36265
|
+
/**
|
|
36266
|
+
* Turn this frame's refused spawns into events, but only those inside a zone.
|
|
36267
|
+
*
|
|
36268
|
+
* Drains {@link refusalsThisFrame}. Each surviving record becomes an
|
|
36269
|
+
* `object.detected` event with NO track — carrying the zones it fell in, the
|
|
36270
|
+
* percentage of the box inside each, and the refusal reason so a consumer can
|
|
36271
|
+
* tell it apart from a track whose media went missing.
|
|
36272
|
+
*
|
|
36273
|
+
* `zone-filtered` refusals are excluded on purpose: that reason means the
|
|
36274
|
+
* operator's own detection rules already said the box does not count on this
|
|
36275
|
+
* camera, and re-admitting it here would overrule a decision they made.
|
|
36276
|
+
*/
|
|
36277
|
+
eventsForRefusedDetectionsInZones(timestamp, frame, frameWidth, frameHeight) {
|
|
36278
|
+
const refusals = this.refusalsThisFrame;
|
|
36279
|
+
this.refusalsThisFrame = [];
|
|
36280
|
+
if (refusals.length === 0 || this.zones.length === 0) return [];
|
|
36281
|
+
const out = [];
|
|
36282
|
+
for (const r of refusals) {
|
|
36283
|
+
if (r.reason === "zone-filtered") continue;
|
|
36284
|
+
const { memberships } = this.zoneEngine.splitDetectionZones(r.bbox, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
|
|
36285
|
+
if (memberships.length === 0) continue;
|
|
36286
|
+
this.zoneRefusalEventCount += 1;
|
|
36287
|
+
out.push({
|
|
36288
|
+
id: randomUUID(),
|
|
36289
|
+
kind: "object",
|
|
36290
|
+
deviceId: this.deviceId,
|
|
36291
|
+
timestamp,
|
|
36292
|
+
frameId: frame.frameId,
|
|
36293
|
+
className: r.detClass,
|
|
36294
|
+
confidence: r.detScore,
|
|
36295
|
+
bbox: r.bbox,
|
|
36296
|
+
frameWidth,
|
|
36297
|
+
frameHeight,
|
|
36298
|
+
zones: memberships.map((m) => m.zoneId),
|
|
36299
|
+
zoneOverlaps: memberships.map((m) => ({
|
|
36300
|
+
zoneId: m.zoneId,
|
|
36301
|
+
...m.zoneName === void 0 ? {} : { zoneName: m.zoneName },
|
|
36302
|
+
overlapPct: Math.round(m.overlap * 1e3) / 10
|
|
36303
|
+
})),
|
|
36304
|
+
spawnRefused: r.reason
|
|
36305
|
+
});
|
|
36306
|
+
}
|
|
36307
|
+
return out;
|
|
36308
|
+
}
|
|
36309
|
+
/** How many zone-refusal events this processor has emitted — the measure that
|
|
36310
|
+
* says whether this path is a trickle or a storm. Read by the addon. */
|
|
36311
|
+
zoneRefusalEventsEmitted() {
|
|
36312
|
+
return this.zoneRefusalEventCount;
|
|
36313
|
+
}
|
|
36231
36314
|
reportZoneFiltered(excluded) {
|
|
36232
36315
|
const observer = this.spawnRefusalObserver;
|
|
36233
36316
|
if (!observer || excluded.length === 0) return;
|
|
@@ -36629,9 +36712,11 @@ var FrameProcessor = class {
|
|
|
36629
36712
|
frameHeight
|
|
36630
36713
|
};
|
|
36631
36714
|
};
|
|
36715
|
+
const zoneRefusalEvents = this.eventsForRefusedDetectionsInZones(timestamp, frame, frameWidth, frameHeight);
|
|
36632
36716
|
const withTrackId = rawEvents.filter((e) => e.detection.trackId);
|
|
36633
36717
|
const objectEvents = withTrackId.filter((e) => e.type !== "object.detected").map((e) => toObjectEvent(e));
|
|
36634
36718
|
const appearanceEvents = withTrackId.filter((e) => e.type === "object.detected").map((e) => toObjectEvent(e, "entered"));
|
|
36719
|
+
objectEvents.push(...zoneRefusalEvents);
|
|
36635
36720
|
return {
|
|
36636
36721
|
deviceId: this.deviceId,
|
|
36637
36722
|
timestamp,
|
|
@@ -47245,6 +47330,7 @@ var TrackDetailDispatcher = class {
|
|
|
47245
47330
|
if (!dev) return;
|
|
47246
47331
|
dev.scheduler.onTrackEnded(trackId);
|
|
47247
47332
|
dev.tracks.delete(trackId);
|
|
47333
|
+
dev.parcelEnriched.delete(trackId);
|
|
47248
47334
|
dev.candidateBest.delete(trackId);
|
|
47249
47335
|
if (dev.tracks.size === 0) this.clearTimer(dev);
|
|
47250
47336
|
}
|
|
@@ -47254,6 +47340,7 @@ var TrackDetailDispatcher = class {
|
|
|
47254
47340
|
this.clearTimer(dev);
|
|
47255
47341
|
dev.queue.length = 0;
|
|
47256
47342
|
dev.tracks.clear();
|
|
47343
|
+
dev.parcelEnriched.clear();
|
|
47257
47344
|
dev.candidateBest.clear();
|
|
47258
47345
|
}
|
|
47259
47346
|
this.devices.clear();
|
|
@@ -47264,6 +47351,7 @@ var TrackDetailDispatcher = class {
|
|
|
47264
47351
|
dev = {
|
|
47265
47352
|
scheduler: new DetailScheduler(),
|
|
47266
47353
|
tracks: /* @__PURE__ */ new Map(),
|
|
47354
|
+
parcelEnriched: /* @__PURE__ */ new Map(),
|
|
47267
47355
|
candidateBest: new BestDetectionTracker(CANDIDATE_IMPROVE_POLICY),
|
|
47268
47356
|
queue: [],
|
|
47269
47357
|
coastedFramesIgnored: 0,
|
|
@@ -47380,19 +47468,21 @@ var TrackDetailDispatcher = class {
|
|
|
47380
47468
|
*
|
|
47381
47469
|
* 1. `frameHandle` — cheapest when it hits: no payload moves and the runner's
|
|
47382
47470
|
* leaf children re-cut their ROI at native resolution. Tried first, always.
|
|
47383
|
-
* 2. `frameJpeg` — the
|
|
47384
|
-
*
|
|
47385
|
-
*
|
|
47386
|
-
*
|
|
47387
|
-
*
|
|
47388
|
-
* the
|
|
47471
|
+
* 2. `frameJpeg` — the track's PARKED key frame (D422): the full native frame
|
|
47472
|
+
* the owning node copied at the best instant, retrieved by track and handed
|
|
47473
|
+
* to the runner UNCUT, so the rectangle still comes from the single
|
|
47474
|
+
* derivation (D52). This rung exists because rung 1 is structurally
|
|
47475
|
+
* unreachable for most dispatches — a tick + inference routinely lands
|
|
47476
|
+
* outside the lease — and it cannot miss on age. It runs ONCE per parcel
|
|
47477
|
+
* per step; a dispatch that finds the same parcel already enriched skips
|
|
47478
|
+
* it, so a coasting track does not re-embed the same image every second.
|
|
47389
47479
|
* 3. `cropJpeg` — the pre-cut tile. Last, because the caller decided its
|
|
47390
47480
|
* rectangle and because in practice the same native miss that emptied rung 1
|
|
47391
47481
|
* empties this one too.
|
|
47392
47482
|
*
|
|
47393
|
-
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG
|
|
47394
|
-
* demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47395
|
-
* rung 1 missed — never a frame-rate stream, and never raw pixels.
|
|
47483
|
+
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG retrieved
|
|
47484
|
+
* on demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47485
|
+
* after rung 1 missed — never a frame-rate stream, and never raw pixels.
|
|
47396
47486
|
*
|
|
47397
47487
|
* ONE outcome stops the ladder cold: the runner's typed `not-attached`
|
|
47398
47488
|
* refusal (`detail-refusal.ts`). It names the camera's SESSION state, not
|
|
@@ -47430,34 +47520,39 @@ var TrackDetailDispatcher = class {
|
|
|
47430
47520
|
}
|
|
47431
47521
|
});
|
|
47432
47522
|
}
|
|
47433
|
-
if (this.deps.
|
|
47434
|
-
const
|
|
47435
|
-
if (
|
|
47523
|
+
if (this.deps.captureParkedFrame !== void 0) try {
|
|
47524
|
+
const parked = await this.deps.captureParkedFrame(deviceId, req.trackId);
|
|
47525
|
+
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", {
|
|
47436
47526
|
tags: { deviceId },
|
|
47437
47527
|
meta: {
|
|
47438
47528
|
trackId: req.trackId,
|
|
47439
47529
|
stepId: req.stepId,
|
|
47440
|
-
|
|
47441
|
-
frameHeight: full.height,
|
|
47442
|
-
bboxFrameWidth: frame.frameWidth,
|
|
47443
|
-
bboxFrameHeight: frame.frameHeight
|
|
47530
|
+
parkedTimestamp: parked.timestamp
|
|
47444
47531
|
}
|
|
47445
47532
|
});
|
|
47446
47533
|
else {
|
|
47447
47534
|
const derived = await this.deps.runDetailSubtree({
|
|
47448
47535
|
deviceId,
|
|
47449
|
-
frameJpeg:
|
|
47450
|
-
parent
|
|
47536
|
+
frameJpeg: parked.base64,
|
|
47537
|
+
parent: {
|
|
47538
|
+
bbox: { ...parked.bbox },
|
|
47539
|
+
className: frame.className
|
|
47540
|
+
},
|
|
47451
47541
|
steps
|
|
47452
|
-
},
|
|
47453
|
-
if (derived !== null)
|
|
47542
|
+
}, parked.nodeId);
|
|
47543
|
+
if (derived !== null) {
|
|
47544
|
+
const perStep = dev.parcelEnriched.get(req.trackId) ?? /* @__PURE__ */ new Map();
|
|
47545
|
+
perStep.set(req.stepId, parked.timestamp);
|
|
47546
|
+
dev.parcelEnriched.set(req.trackId, perStep);
|
|
47547
|
+
return derived.details;
|
|
47548
|
+
}
|
|
47454
47549
|
}
|
|
47455
47550
|
} catch (err) {
|
|
47456
47551
|
if (isDetailNotAttachedRefusal(err)) {
|
|
47457
47552
|
this.warnNotAttachedThrottled(deviceId, dev, req);
|
|
47458
47553
|
return null;
|
|
47459
47554
|
}
|
|
47460
|
-
this.deps.logger.debug("detail
|
|
47555
|
+
this.deps.logger.debug("detail parked-frame rung failed", {
|
|
47461
47556
|
tags: { deviceId },
|
|
47462
47557
|
meta: {
|
|
47463
47558
|
trackId: req.trackId,
|
|
@@ -47524,7 +47619,7 @@ var TrackDetailDispatcher = class {
|
|
|
47524
47619
|
trackId: req.trackId,
|
|
47525
47620
|
stepId: req.stepId,
|
|
47526
47621
|
reason: req.reason,
|
|
47527
|
-
|
|
47622
|
+
triedParkedFrame: this.deps.captureParkedFrame !== void 0,
|
|
47528
47623
|
triedCropJpeg: this.deps.captureCropBase64 !== void 0
|
|
47529
47624
|
}
|
|
47530
47625
|
});
|
|
@@ -49486,6 +49581,87 @@ var FaceRecognizer = class {
|
|
|
49486
49581
|
}
|
|
49487
49582
|
}
|
|
49488
49583
|
};
|
|
49584
|
+
/** Read the window. Pure — this is the judgement the module exists to make. */
|
|
49585
|
+
function lagVerdict(report) {
|
|
49586
|
+
const bus = report.busLagMaxMs;
|
|
49587
|
+
if (bus === null) return "unknown";
|
|
49588
|
+
return bus >= 5e3 ? "starved" : "delivered-on-time";
|
|
49589
|
+
}
|
|
49590
|
+
/** Does this window need an operator's eyes? Unknown never counts as alarming
|
|
49591
|
+
* — that is the fabricated verdict D8 forbids. */
|
|
49592
|
+
function isAlarmingLagReport(report) {
|
|
49593
|
+
const bus = report.busLagMaxMs;
|
|
49594
|
+
const capture = report.captureLagMaxMs;
|
|
49595
|
+
return bus !== null && bus >= 5e3 || capture !== null && capture >= 5e3;
|
|
49596
|
+
}
|
|
49597
|
+
/**
|
|
49598
|
+
* Folds per-frame delivery lag into per-device windows.
|
|
49599
|
+
*
|
|
49600
|
+
* Not a logger: it returns the line to write (or null) and the caller decides
|
|
49601
|
+
* level and tags. That keeps the throttle — the part that can wreck the frame
|
|
49602
|
+
* path — testable without a logger fake.
|
|
49603
|
+
*/
|
|
49604
|
+
var FrameLagObserver = class {
|
|
49605
|
+
windows = /* @__PURE__ */ new Map();
|
|
49606
|
+
/**
|
|
49607
|
+
* Fold one inference result into `deviceId`'s window.
|
|
49608
|
+
*
|
|
49609
|
+
* @param now Receipt time, read by the caller once at handler entry.
|
|
49610
|
+
* @param emittedAt Runner-side emit stamp; undefined ⇒ unknown (D8).
|
|
49611
|
+
* @param capturedAt Frame shm-commit stamp; undefined ⇒ unknown.
|
|
49612
|
+
* @returns The closed window when this frame ends one, else null.
|
|
49613
|
+
*/
|
|
49614
|
+
observe(deviceId, now, emittedAt, capturedAt) {
|
|
49615
|
+
const existing = this.windows.get(deviceId);
|
|
49616
|
+
const w = existing ?? {
|
|
49617
|
+
startedAt: now,
|
|
49618
|
+
frames: 0,
|
|
49619
|
+
busMax: null,
|
|
49620
|
+
busMin: null,
|
|
49621
|
+
busUnknown: 0,
|
|
49622
|
+
captureMax: null,
|
|
49623
|
+
captureUnknown: 0
|
|
49624
|
+
};
|
|
49625
|
+
if (existing === void 0) this.windows.set(deviceId, w);
|
|
49626
|
+
w.frames += 1;
|
|
49627
|
+
if (typeof emittedAt === "number") {
|
|
49628
|
+
const busLag = now - emittedAt;
|
|
49629
|
+
if (w.busMax === null || busLag > w.busMax) w.busMax = busLag;
|
|
49630
|
+
if (w.busMin === null || busLag < w.busMin) w.busMin = busLag;
|
|
49631
|
+
} else w.busUnknown += 1;
|
|
49632
|
+
if (typeof capturedAt === "number") {
|
|
49633
|
+
const captureLag = now - capturedAt;
|
|
49634
|
+
if (w.captureMax === null || captureLag > w.captureMax) w.captureMax = captureLag;
|
|
49635
|
+
} else w.captureUnknown += 1;
|
|
49636
|
+
if (existing !== void 0 && now - w.startedAt < 3e4) return null;
|
|
49637
|
+
const report = {
|
|
49638
|
+
deviceId,
|
|
49639
|
+
frames: w.frames,
|
|
49640
|
+
busLagMaxMs: w.busMax,
|
|
49641
|
+
busLagMinMs: w.busMin,
|
|
49642
|
+
busLagUnknownFrames: w.busUnknown,
|
|
49643
|
+
captureLagMaxMs: w.captureMax,
|
|
49644
|
+
captureLagUnknownFrames: w.captureUnknown,
|
|
49645
|
+
windowMs: now - w.startedAt
|
|
49646
|
+
};
|
|
49647
|
+
w.startedAt = now;
|
|
49648
|
+
w.frames = 0;
|
|
49649
|
+
w.busMax = null;
|
|
49650
|
+
w.busMin = null;
|
|
49651
|
+
w.busUnknown = 0;
|
|
49652
|
+
w.captureMax = null;
|
|
49653
|
+
w.captureUnknown = 0;
|
|
49654
|
+
return report;
|
|
49655
|
+
}
|
|
49656
|
+
/** Drop a device's window (unbound camera, shutdown). */
|
|
49657
|
+
forget(deviceId) {
|
|
49658
|
+
this.windows.delete(deviceId);
|
|
49659
|
+
}
|
|
49660
|
+
/** Drop every window. */
|
|
49661
|
+
clear() {
|
|
49662
|
+
this.windows.clear();
|
|
49663
|
+
}
|
|
49664
|
+
};
|
|
49489
49665
|
//#endregion
|
|
49490
49666
|
//#region src/pipeline-analytics/location-aware-media-storage.ts
|
|
49491
49667
|
/**
|
|
@@ -53808,6 +53984,17 @@ async function encodeKeyFrameVariants(native) {
|
|
|
53808
53984
|
})
|
|
53809
53985
|
};
|
|
53810
53986
|
}
|
|
53987
|
+
/**
|
|
53988
|
+
* The web companion from the PARKED key frame's own JPEG (D422): the parcel is
|
|
53989
|
+
* stored verbatim as `keyFrame`, and `keyFrameSmall` is this — the same bytes
|
|
53990
|
+
* downscaled to {@link KEYFRAME_SMALL_MAX_WIDTH}. One decode, one encode,
|
|
53991
|
+
* never a second fetch; `withoutEnlargement` never upscales a sub-960 frame.
|
|
53992
|
+
* The by-handle twin ({@link encodeKeyFrameVariants}) remains for the
|
|
53993
|
+
* summary group shot, which still fetches a native surface by handle.
|
|
53994
|
+
*/
|
|
53995
|
+
async function deriveKeyFrameSmall(keyFrameJpeg) {
|
|
53996
|
+
return sharp(keyFrameJpeg).resize(960, null, { withoutEnlargement: true }).jpeg({ quality: 88 }).toBuffer();
|
|
53997
|
+
}
|
|
53811
53998
|
//#endregion
|
|
53812
53999
|
//#region src/pipeline-analytics/pipeline/key-frame-metrics.ts
|
|
53813
54000
|
var DEFAULT_FLUSH_INTERVAL_MS = 6e4;
|
|
@@ -53833,11 +54020,8 @@ var KeyFrameCaptureLog = class {
|
|
|
53833
54020
|
w.landed += 1;
|
|
53834
54021
|
if (sample.retry) w.retryLanded += 1;
|
|
53835
54022
|
break;
|
|
53836
|
-
case "
|
|
53837
|
-
w.
|
|
53838
|
-
break;
|
|
53839
|
-
case "ram-tier":
|
|
53840
|
-
w.ramTier += 1;
|
|
54023
|
+
case "parcel-missing":
|
|
54024
|
+
w.parcelMissing += 1;
|
|
53841
54025
|
break;
|
|
53842
54026
|
case "error":
|
|
53843
54027
|
w.error += 1;
|
|
@@ -53866,8 +54050,7 @@ var KeyFrameCaptureLog = class {
|
|
|
53866
54050
|
landed: 0,
|
|
53867
54051
|
retryAttempts: 0,
|
|
53868
54052
|
retryLanded: 0,
|
|
53869
|
-
|
|
53870
|
-
ramTier: 0,
|
|
54053
|
+
parcelMissing: 0,
|
|
53871
54054
|
error: 0,
|
|
53872
54055
|
skippedNodeOffline: 0
|
|
53873
54056
|
};
|
|
@@ -53882,8 +54065,7 @@ var KeyFrameCaptureLog = class {
|
|
|
53882
54065
|
landed: w.landed,
|
|
53883
54066
|
retryAttempts: w.retryAttempts,
|
|
53884
54067
|
retryLanded: w.retryLanded,
|
|
53885
|
-
|
|
53886
|
-
ramTier: w.ramTier,
|
|
54068
|
+
parcelMissing: w.parcelMissing,
|
|
53887
54069
|
error: w.error,
|
|
53888
54070
|
skippedNodeOffline: w.skippedNodeOffline
|
|
53889
54071
|
};
|
|
@@ -53892,63 +54074,44 @@ var KeyFrameCaptureLog = class {
|
|
|
53892
54074
|
//#endregion
|
|
53893
54075
|
//#region src/pipeline-analytics/pipeline/key-frame-persist.ts
|
|
53894
54076
|
/**
|
|
53895
|
-
*
|
|
54077
|
+
* Read, derive and store one track's key frame + its web companion.
|
|
53896
54078
|
*
|
|
53897
|
-
*
|
|
53898
|
-
*
|
|
53899
|
-
* outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
54079
|
+
* PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
|
|
54080
|
+
* parks a later frame. The `keyFrame` write lands FIRST and its key is what
|
|
54081
|
+
* the outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
53900
54082
|
* retry that would re-shoot a key frame the rebuild can already read.
|
|
53901
54083
|
*/
|
|
53902
54084
|
async function persistTrackKeyFrame(deps, input) {
|
|
53903
|
-
const { deviceId, trackId,
|
|
54085
|
+
const { deviceId, trackId, retry } = input;
|
|
53904
54086
|
const drop = {
|
|
53905
54087
|
tags: { deviceId },
|
|
53906
54088
|
meta: {
|
|
53907
54089
|
trackId,
|
|
53908
|
-
nodeId: frameHandle.nodeId,
|
|
53909
54090
|
retry
|
|
53910
54091
|
}
|
|
53911
54092
|
};
|
|
53912
54093
|
try {
|
|
53913
|
-
const
|
|
53914
|
-
|
|
53915
|
-
|
|
53916
|
-
|
|
53917
|
-
...drop,
|
|
53918
|
-
meta: {
|
|
53919
|
-
...drop.meta,
|
|
53920
|
-
shmId: frameHandle.shmId
|
|
53921
|
-
}
|
|
53922
|
-
});
|
|
53923
|
-
return { kind: "native-miss" };
|
|
54094
|
+
const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
|
|
54095
|
+
if (parcel === null || parcel.jpeg.byteLength === 0) {
|
|
54096
|
+
deps.logger.error("key-frame parcel missing — nothing to write, retry parks a later frame", { ...drop });
|
|
54097
|
+
return { kind: "parcel-missing" };
|
|
53924
54098
|
}
|
|
53925
|
-
if (!keyFrameAcceptsTier(result.tier)) {
|
|
53926
|
-
deps.logger.warn("keyFrame native miss — RAM tier rejected, will retry while budget remains", {
|
|
53927
|
-
...drop,
|
|
53928
|
-
meta: {
|
|
53929
|
-
...drop.meta,
|
|
53930
|
-
shmId: frameHandle.shmId,
|
|
53931
|
-
tier: result.tier
|
|
53932
|
-
}
|
|
53933
|
-
});
|
|
53934
|
-
return { kind: "ram-tier" };
|
|
53935
|
-
}
|
|
53936
|
-
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
|
|
53937
54099
|
const keyFrameKey = await deps.media.putReplacing({
|
|
53938
54100
|
deviceId,
|
|
53939
54101
|
ownerKind: "track",
|
|
53940
54102
|
ownerId: trackId,
|
|
53941
54103
|
kind: "keyFrame",
|
|
53942
|
-
timestamp:
|
|
53943
|
-
data:
|
|
54104
|
+
timestamp: parcel.timestamp,
|
|
54105
|
+
data: parcel.jpeg
|
|
53944
54106
|
});
|
|
53945
54107
|
try {
|
|
54108
|
+
const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
|
|
53946
54109
|
await deps.media.putReplacing({
|
|
53947
54110
|
deviceId,
|
|
53948
54111
|
ownerKind: "track",
|
|
53949
54112
|
ownerId: trackId,
|
|
53950
54113
|
kind: "keyFrameSmall",
|
|
53951
|
-
timestamp:
|
|
54114
|
+
timestamp: parcel.timestamp,
|
|
53952
54115
|
data: keyFrameSmall
|
|
53953
54116
|
});
|
|
53954
54117
|
} catch (err) {
|
|
@@ -53956,17 +54119,29 @@ async function persistTrackKeyFrame(deps, input) {
|
|
|
53956
54119
|
...drop,
|
|
53957
54120
|
meta: {
|
|
53958
54121
|
...drop.meta,
|
|
54122
|
+
nodeId: parcel.nodeId,
|
|
53959
54123
|
error: errMsg$1(err)
|
|
53960
54124
|
}
|
|
53961
54125
|
});
|
|
53962
54126
|
}
|
|
54127
|
+
deps.logger.debug("key frame written from the parked parcel", {
|
|
54128
|
+
...drop,
|
|
54129
|
+
meta: {
|
|
54130
|
+
...drop.meta,
|
|
54131
|
+
nodeId: parcel.nodeId,
|
|
54132
|
+
width: parcel.width,
|
|
54133
|
+
height: parcel.height,
|
|
54134
|
+
bytes: parcel.jpeg.byteLength,
|
|
54135
|
+
parkedTimestamp: parcel.timestamp
|
|
54136
|
+
}
|
|
54137
|
+
});
|
|
53963
54138
|
return {
|
|
53964
54139
|
kind: "landed",
|
|
53965
54140
|
keyFrameKey
|
|
53966
54141
|
};
|
|
53967
54142
|
} catch (err) {
|
|
53968
54143
|
const error = errMsg$1(err);
|
|
53969
|
-
deps.logger.warn("key-frame
|
|
54144
|
+
deps.logger.warn("key-frame write failed", {
|
|
53970
54145
|
...drop,
|
|
53971
54146
|
meta: {
|
|
53972
54147
|
...drop.meta,
|
|
@@ -54153,6 +54328,61 @@ function createKeyFrameCrop(deps) {
|
|
|
54153
54328
|
};
|
|
54154
54329
|
}
|
|
54155
54330
|
//#endregion
|
|
54331
|
+
//#region src/pipeline-analytics/pipeline/track-park.ts
|
|
54332
|
+
/** The whole frame — what a `keyFrame` park cuts. */
|
|
54333
|
+
var FULL_FRAME = {
|
|
54334
|
+
x: 0,
|
|
54335
|
+
y: 0,
|
|
54336
|
+
w: 1,
|
|
54337
|
+
h: 1
|
|
54338
|
+
};
|
|
54339
|
+
/** The two parcels a best-frame decision orders, in a fixed order. */
|
|
54340
|
+
function buildParkOrders(decision) {
|
|
54341
|
+
const subject = {
|
|
54342
|
+
bbox: { ...decision.bbox },
|
|
54343
|
+
frameWidth: decision.frameWidth,
|
|
54344
|
+
frameHeight: decision.frameHeight
|
|
54345
|
+
};
|
|
54346
|
+
const layout = deliveredWideCentralSquareLayout(decision.bbox, {
|
|
54347
|
+
W: decision.frameWidth,
|
|
54348
|
+
H: decision.frameHeight
|
|
54349
|
+
});
|
|
54350
|
+
const base = {
|
|
54351
|
+
deviceId: decision.deviceId,
|
|
54352
|
+
trackId: decision.trackId,
|
|
54353
|
+
handle: decision.frameHandle,
|
|
54354
|
+
timestamp: decision.timestamp,
|
|
54355
|
+
subject
|
|
54356
|
+
};
|
|
54357
|
+
return [{
|
|
54358
|
+
...base,
|
|
54359
|
+
kind: "keyFrame",
|
|
54360
|
+
bbox: FULL_FRAME,
|
|
54361
|
+
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
54362
|
+
}, {
|
|
54363
|
+
...base,
|
|
54364
|
+
kind: "thumbnail",
|
|
54365
|
+
bbox: layout.fetch,
|
|
54366
|
+
layout
|
|
54367
|
+
}];
|
|
54368
|
+
}
|
|
54369
|
+
/**
|
|
54370
|
+
* The subject's box in the PARCEL's pixel space. The box was measured on the
|
|
54371
|
+
* ≤640 detection raster; the parcel is native-resolution. Both show the same
|
|
54372
|
+
* scene at the same aspect, so a uniform per-axis scale places the box on the
|
|
54373
|
+
* parcel — the runner then derives the crop rectangle from it (D52).
|
|
54374
|
+
*/
|
|
54375
|
+
function scaleSubjectIntoParcel(subject, parcelWidth, parcelHeight) {
|
|
54376
|
+
const sx = parcelWidth / subject.frameWidth;
|
|
54377
|
+
const sy = parcelHeight / subject.frameHeight;
|
|
54378
|
+
return {
|
|
54379
|
+
x: subject.bbox.x * sx,
|
|
54380
|
+
y: subject.bbox.y * sy,
|
|
54381
|
+
w: subject.bbox.w * sx,
|
|
54382
|
+
h: subject.bbox.h * sy
|
|
54383
|
+
};
|
|
54384
|
+
}
|
|
54385
|
+
//#endregion
|
|
54156
54386
|
//#region src/pipeline-analytics/pipeline/native-detection.ts
|
|
54157
54387
|
/**
|
|
54158
54388
|
* Nominal frame size used to denormalize native `[0,1]` boxes when a
|
|
@@ -58683,7 +58913,28 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
58683
58913
|
}
|
|
58684
58914
|
//#endregion
|
|
58685
58915
|
//#region src/pipeline-analytics/summary/summary-store.ts
|
|
58916
|
+
/**
|
|
58917
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
58918
|
+
* write="one row per SUMMARY — a window of co-present tracks on one camera
|
|
58919
|
+
* group. Opened by `openOrAbsorb` when the first member arrives, updated
|
|
58920
|
+
* in place while the window absorbs more, and finalised by `seal` when the
|
|
58921
|
+
* window closes. Per WINDOW, never per frame or per track: the sealed
|
|
58922
|
+
* summary is what a notification rule reads."
|
|
58923
|
+
* retention="age-keyed on `sealedAt`, swept with the rest of the analytics
|
|
58924
|
+
* retention. An unsealed row belongs to a window still open and is never
|
|
58925
|
+
* swept by age — it is closed or cascaded away with its members."
|
|
58926
|
+
*/
|
|
58686
58927
|
var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
|
|
58928
|
+
/**
|
|
58929
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
58930
|
+
* write="one row per (summary, track) membership, written as a window
|
|
58931
|
+
* absorbs a track. The join that lets a sealed summary name its subjects
|
|
58932
|
+
* without re-reading the track store."
|
|
58933
|
+
* retention="cascaded — a member dies with its summary (`delete-track-cascade`
|
|
58934
|
+
* counts them as leaves) or with the track it names. It carries no clock of
|
|
58935
|
+
* its own: an age sweep here could outlive or predecease the summary the
|
|
58936
|
+
* row only means anything inside of."
|
|
58937
|
+
*/
|
|
58687
58938
|
var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
|
|
58688
58939
|
var SUMMARIES_COLUMNS = [
|
|
58689
58940
|
{
|
|
@@ -63511,35 +63762,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63511
63762
|
const getRemoteFrame = async (handle) => requestNativeCrop(handle, FULL_FRAME_BBOX, handle.width, replyToFullFrame);
|
|
63512
63763
|
const fetchNativeFullFrameTiered = async (handle, maxWidth) => requestNativeCrop(handle, FULL_FRAME_BBOX, maxWidth, replyToTieredFullFrame);
|
|
63513
63764
|
const getNativeFullFrameRgb = async (handle, maxWidth) => (await fetchNativeFullFrameTiered(handle, maxWidth))?.frame ?? null;
|
|
63514
|
-
const getFullFrameJpegBase64 = async (handle, maxWidth) => {
|
|
63515
|
-
if (!pipelineRunnerApi?.getNativeCrop) return null;
|
|
63516
|
-
try {
|
|
63517
|
-
const full = await pipelineRunnerApi.getNativeCrop.query({
|
|
63518
|
-
handle,
|
|
63519
|
-
bbox: {
|
|
63520
|
-
x: 0,
|
|
63521
|
-
y: 0,
|
|
63522
|
-
w: 1,
|
|
63523
|
-
h: 1
|
|
63524
|
-
},
|
|
63525
|
-
maxWidth,
|
|
63526
|
-
encodeJpeg: true
|
|
63527
|
-
}, nodePin(handle.nodeId));
|
|
63528
|
-
if (!full?.jpeg || full.width <= 0 || full.height <= 0) return null;
|
|
63529
|
-
return {
|
|
63530
|
-
base64: full.jpeg,
|
|
63531
|
-
width: full.width,
|
|
63532
|
-
height: full.height,
|
|
63533
|
-
tier: full.tier === "ram-fullframe" ? "ram-fullframe" : "native"
|
|
63534
|
-
};
|
|
63535
|
-
} catch (err) {
|
|
63536
|
-
logger.debug("full-frame jpeg fetch failed", { meta: {
|
|
63537
|
-
nodeId: handle.nodeId,
|
|
63538
|
-
error: errMsg$1(err)
|
|
63539
|
-
} });
|
|
63540
|
-
return null;
|
|
63541
|
-
}
|
|
63542
|
-
};
|
|
63543
63765
|
const cropMetricLogger = logger.child("NativeCrop");
|
|
63544
63766
|
let nativeHits = 0;
|
|
63545
63767
|
let nativeFallbacks = 0;
|
|
@@ -63619,7 +63841,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63619
63841
|
getRemoteFrame,
|
|
63620
63842
|
getNativeFullFrameRgb,
|
|
63621
63843
|
getNativeKeyFrameRgb: fetchNativeFullFrameTiered,
|
|
63622
|
-
getFullFrameJpegBase64,
|
|
63623
63844
|
getNativeCropJpeg,
|
|
63624
63845
|
captureCrop: createCaptureCrop({
|
|
63625
63846
|
tryNativeCrop,
|
|
@@ -63730,6 +63951,233 @@ function parseTopologySnapshotNodes(data) {
|
|
|
63730
63951
|
return parsed;
|
|
63731
63952
|
}
|
|
63732
63953
|
//#endregion
|
|
63954
|
+
//#region src/pipeline-analytics/runtime/parked-frame-client.ts
|
|
63955
|
+
/**
|
|
63956
|
+
* Bound on the mirror. A track's record leaves at release; this is the
|
|
63957
|
+
* backstop for a lost close. At the busiest measured hub (~12 sessions,
|
|
63958
|
+
* a few tracks each) 4 096 is an hour of tracks, past which the OLDEST record
|
|
63959
|
+
* is forgotten — its parcel still ages out on the node on its own.
|
|
63960
|
+
*/
|
|
63961
|
+
var MAX_MIRRORED_TRACKS = 4096;
|
|
63962
|
+
/** One "runner has no park method" line per camera per minute. */
|
|
63963
|
+
var SKEW_WARN_THROTTLE_MS = 6e4;
|
|
63964
|
+
function inFlightKey(trackId, kind) {
|
|
63965
|
+
return `${trackId} ${kind}`;
|
|
63966
|
+
}
|
|
63967
|
+
/** Strip the hub-side context off an order: only the wire fields travel. */
|
|
63968
|
+
function toWireInput(order) {
|
|
63969
|
+
return {
|
|
63970
|
+
deviceId: order.deviceId,
|
|
63971
|
+
trackId: order.trackId,
|
|
63972
|
+
kind: order.kind,
|
|
63973
|
+
handle: order.handle,
|
|
63974
|
+
bbox: order.bbox,
|
|
63975
|
+
...order.maxWidth !== void 0 ? { maxWidth: order.maxWidth } : {},
|
|
63976
|
+
timestamp: order.timestamp
|
|
63977
|
+
};
|
|
63978
|
+
}
|
|
63979
|
+
function createParkedFrameClient(deps) {
|
|
63980
|
+
const { api, logger } = deps;
|
|
63981
|
+
const now = deps.now ?? Date.now;
|
|
63982
|
+
/** trackId → kind → what the hub kept beside the parcel. */
|
|
63983
|
+
const mirror = /* @__PURE__ */ new Map();
|
|
63984
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
63985
|
+
const skewWarnedAt = /* @__PURE__ */ new Map();
|
|
63986
|
+
const remember = (order, answer) => {
|
|
63987
|
+
let kinds = mirror.get(order.trackId);
|
|
63988
|
+
if (kinds === void 0) {
|
|
63989
|
+
kinds = /* @__PURE__ */ new Map();
|
|
63990
|
+
mirror.set(order.trackId, kinds);
|
|
63991
|
+
while (mirror.size > MAX_MIRRORED_TRACKS) {
|
|
63992
|
+
const oldest = mirror.keys().next().value;
|
|
63993
|
+
if (oldest === void 0) break;
|
|
63994
|
+
mirror.delete(oldest);
|
|
63995
|
+
}
|
|
63996
|
+
}
|
|
63997
|
+
kinds.set(order.kind, {
|
|
63998
|
+
nodeId: answer.nodeId,
|
|
63999
|
+
deviceId: order.deviceId,
|
|
64000
|
+
kind: order.kind,
|
|
64001
|
+
timestamp: order.timestamp,
|
|
64002
|
+
width: answer.width,
|
|
64003
|
+
height: answer.height,
|
|
64004
|
+
subject: order.subject,
|
|
64005
|
+
...order.layout !== void 0 ? { layout: order.layout } : {}
|
|
64006
|
+
});
|
|
64007
|
+
};
|
|
64008
|
+
const warnSkew = (order) => {
|
|
64009
|
+
const at = skewWarnedAt.get(order.deviceId) ?? 0;
|
|
64010
|
+
if (now() - at < SKEW_WARN_THROTTLE_MS) return;
|
|
64011
|
+
skewWarnedAt.set(order.deviceId, now());
|
|
64012
|
+
logger.warn("track frame park impossible — the runner api has no park method (version skew)", {
|
|
64013
|
+
tags: { deviceId: order.deviceId },
|
|
64014
|
+
meta: {
|
|
64015
|
+
trackId: order.trackId,
|
|
64016
|
+
kind: order.kind,
|
|
64017
|
+
nodeId: order.handle.nodeId
|
|
64018
|
+
}
|
|
64019
|
+
});
|
|
64020
|
+
};
|
|
64021
|
+
const sendPark = async (order) => {
|
|
64022
|
+
if (api === void 0) {
|
|
64023
|
+
warnSkew(order);
|
|
64024
|
+
return false;
|
|
64025
|
+
}
|
|
64026
|
+
if (deps.isNodeOffline?.(order.handle.nodeId) === true) {
|
|
64027
|
+
logger.debug("track frame park skipped — the owning node is offline in the registry", {
|
|
64028
|
+
tags: { deviceId: order.deviceId },
|
|
64029
|
+
meta: {
|
|
64030
|
+
trackId: order.trackId,
|
|
64031
|
+
kind: order.kind,
|
|
64032
|
+
nodeId: order.handle.nodeId
|
|
64033
|
+
}
|
|
64034
|
+
});
|
|
64035
|
+
return false;
|
|
64036
|
+
}
|
|
64037
|
+
const meta = {
|
|
64038
|
+
trackId: order.trackId,
|
|
64039
|
+
kind: order.kind,
|
|
64040
|
+
nodeId: order.handle.nodeId,
|
|
64041
|
+
handle: `${order.handle.shmId}#${order.handle.slot}#${order.handle.seq}`
|
|
64042
|
+
};
|
|
64043
|
+
try {
|
|
64044
|
+
const answer = await api.parkTrackFrame.mutate(toWireInput(order), nodePin(order.handle.nodeId));
|
|
64045
|
+
if (answer.parked) {
|
|
64046
|
+
remember(order, answer);
|
|
64047
|
+
logger.debug("track frame parked", {
|
|
64048
|
+
tags: { deviceId: order.deviceId },
|
|
64049
|
+
meta: {
|
|
64050
|
+
...meta,
|
|
64051
|
+
bytes: answer.bytes,
|
|
64052
|
+
replaced: answer.replaced
|
|
64053
|
+
}
|
|
64054
|
+
});
|
|
64055
|
+
return true;
|
|
64056
|
+
}
|
|
64057
|
+
logger.warn("track frame park refused by the owning node", {
|
|
64058
|
+
tags: { deviceId: order.deviceId },
|
|
64059
|
+
meta: {
|
|
64060
|
+
...meta,
|
|
64061
|
+
reason: answer.reason,
|
|
64062
|
+
handleAgeMs: answer.handleAgeMs,
|
|
64063
|
+
...answer.detail !== void 0 ? { detail: answer.detail } : {}
|
|
64064
|
+
}
|
|
64065
|
+
});
|
|
64066
|
+
return false;
|
|
64067
|
+
} catch (err) {
|
|
64068
|
+
logger.warn("track frame park failed — the order did not reach the node", {
|
|
64069
|
+
tags: { deviceId: order.deviceId },
|
|
64070
|
+
meta: {
|
|
64071
|
+
...meta,
|
|
64072
|
+
error: errMsg$1(err)
|
|
64073
|
+
}
|
|
64074
|
+
});
|
|
64075
|
+
return false;
|
|
64076
|
+
}
|
|
64077
|
+
};
|
|
64078
|
+
const park = (order) => {
|
|
64079
|
+
const key = inFlightKey(order.trackId, order.kind);
|
|
64080
|
+
const pending = inFlight.get(key);
|
|
64081
|
+
if (pending !== void 0) return pending;
|
|
64082
|
+
const run = sendPark(order).finally(() => {
|
|
64083
|
+
if (inFlight.get(key) === run) inFlight.delete(key);
|
|
64084
|
+
});
|
|
64085
|
+
inFlight.set(key, run);
|
|
64086
|
+
return run;
|
|
64087
|
+
};
|
|
64088
|
+
const awaitPark = async (trackId, kind) => {
|
|
64089
|
+
const pending = inFlight.get(inFlightKey(trackId, kind));
|
|
64090
|
+
if (pending !== void 0) await pending;
|
|
64091
|
+
};
|
|
64092
|
+
const info = (trackId, kind) => mirror.get(trackId)?.get(kind);
|
|
64093
|
+
const fetch = async (trackId, kind) => {
|
|
64094
|
+
const known = info(trackId, kind);
|
|
64095
|
+
if (known === void 0 || api === void 0) return null;
|
|
64096
|
+
if (deps.isNodeOffline?.(known.nodeId) === true) {
|
|
64097
|
+
logger.warn("parked frame retrieval skipped — the holding node is offline in the registry", {
|
|
64098
|
+
tags: { deviceId: known.deviceId },
|
|
64099
|
+
meta: {
|
|
64100
|
+
trackId,
|
|
64101
|
+
kind,
|
|
64102
|
+
nodeId: known.nodeId
|
|
64103
|
+
}
|
|
64104
|
+
});
|
|
64105
|
+
return null;
|
|
64106
|
+
}
|
|
64107
|
+
try {
|
|
64108
|
+
const parcel = await api.getParkedTrackFrame.query({
|
|
64109
|
+
deviceId: known.deviceId,
|
|
64110
|
+
trackId,
|
|
64111
|
+
kind
|
|
64112
|
+
}, nodePin(known.nodeId));
|
|
64113
|
+
if (parcel === null) {
|
|
64114
|
+
logger.warn("parked frame gone at retrieval — the node no longer holds it", {
|
|
64115
|
+
tags: { deviceId: known.deviceId },
|
|
64116
|
+
meta: {
|
|
64117
|
+
trackId,
|
|
64118
|
+
kind,
|
|
64119
|
+
nodeId: known.nodeId,
|
|
64120
|
+
parkedTimestamp: known.timestamp
|
|
64121
|
+
}
|
|
64122
|
+
});
|
|
64123
|
+
return null;
|
|
64124
|
+
}
|
|
64125
|
+
return {
|
|
64126
|
+
jpeg: Buffer.from(parcel.jpeg, "base64"),
|
|
64127
|
+
width: parcel.width,
|
|
64128
|
+
height: parcel.height,
|
|
64129
|
+
timestamp: parcel.timestamp,
|
|
64130
|
+
info: known
|
|
64131
|
+
};
|
|
64132
|
+
} catch (err) {
|
|
64133
|
+
logger.warn("parked frame retrieval failed", {
|
|
64134
|
+
tags: { deviceId: known.deviceId },
|
|
64135
|
+
meta: {
|
|
64136
|
+
trackId,
|
|
64137
|
+
kind,
|
|
64138
|
+
nodeId: known.nodeId,
|
|
64139
|
+
error: errMsg$1(err)
|
|
64140
|
+
}
|
|
64141
|
+
});
|
|
64142
|
+
return null;
|
|
64143
|
+
}
|
|
64144
|
+
};
|
|
64145
|
+
const release = (trackId) => {
|
|
64146
|
+
const kinds = mirror.get(trackId);
|
|
64147
|
+
if (kinds === void 0) return;
|
|
64148
|
+
mirror.delete(trackId);
|
|
64149
|
+
if (api === void 0) return;
|
|
64150
|
+
const targets = /* @__PURE__ */ new Map();
|
|
64151
|
+
for (const record of kinds.values()) targets.set(record.nodeId, record.deviceId);
|
|
64152
|
+
for (const [nodeId, deviceId] of targets) api.releaseParkedTrackFrames.mutate({
|
|
64153
|
+
deviceId,
|
|
64154
|
+
trackId
|
|
64155
|
+
}, nodePin(nodeId)).catch((err) => {
|
|
64156
|
+
logger.debug("parked frame release did not reach the node", {
|
|
64157
|
+
tags: { deviceId },
|
|
64158
|
+
meta: {
|
|
64159
|
+
trackId,
|
|
64160
|
+
nodeId,
|
|
64161
|
+
error: errMsg$1(err)
|
|
64162
|
+
}
|
|
64163
|
+
});
|
|
64164
|
+
});
|
|
64165
|
+
};
|
|
64166
|
+
const dispose = () => {
|
|
64167
|
+
mirror.clear();
|
|
64168
|
+
inFlight.clear();
|
|
64169
|
+
skewWarnedAt.clear();
|
|
64170
|
+
};
|
|
64171
|
+
return {
|
|
64172
|
+
park,
|
|
64173
|
+
awaitPark,
|
|
64174
|
+
info,
|
|
64175
|
+
fetch,
|
|
64176
|
+
release,
|
|
64177
|
+
dispose
|
|
64178
|
+
};
|
|
64179
|
+
}
|
|
64180
|
+
//#endregion
|
|
63733
64181
|
//#region src/pipeline-analytics/runtime/widget-manifest.ts
|
|
63734
64182
|
/** The remote-module coordinates shared by every analytics widget. */
|
|
63735
64183
|
var REMOTE_NAME = "addon_pipeline_analytics_widgets";
|
|
@@ -64831,7 +65279,8 @@ function isUniformRgbFrame(data, width, height) {
|
|
|
64831
65279
|
* frame downscaled to {@link SNAPSHOT_MAX_WIDTH} (960) with ONLY the track's own
|
|
64832
65280
|
* box burned in, falling back to the ≤640 detection frame (honest lower res,
|
|
64833
65281
|
* never an upscale) on a native miss. The best `thumbnail` stays a clean,
|
|
64834
|
-
* uncapped native subject crop
|
|
65282
|
+
* uncapped native subject crop — read from the track's PARKED slab, by track
|
|
65283
|
+
* (D422), never by handle.
|
|
64835
65284
|
*
|
|
64836
65285
|
* The detection frame is resolved ONCE per call (blank-frame guard); native full
|
|
64837
65286
|
* frames + subject crops are fetched per target by handle. Frames recycle in
|
|
@@ -65099,10 +65548,10 @@ var EventMediaDispatcher = class {
|
|
|
65099
65548
|
if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
65100
65549
|
let thumbnailWritten = false;
|
|
65101
65550
|
if (sn.bestThumbnail) {
|
|
65102
|
-
const variants = await this.cropSubjectVariants(deviceId, sn.trackId
|
|
65551
|
+
const variants = await this.cropSubjectVariants(deviceId, sn.trackId);
|
|
65103
65552
|
if (variants) {
|
|
65104
|
-
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail",
|
|
65105
|
-
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall",
|
|
65553
|
+
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", variants.timestamp, variants.thumbnail, "native");
|
|
65554
|
+
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", variants.timestamp, variants.thumbnailSmall, "native");
|
|
65106
65555
|
}
|
|
65107
65556
|
}
|
|
65108
65557
|
return {
|
|
@@ -65180,81 +65629,49 @@ var EventMediaDispatcher = class {
|
|
|
65180
65629
|
}
|
|
65181
65630
|
/**
|
|
65182
65631
|
* The best-shot subject crop as its TWO persisted variants (best-crop
|
|
65183
|
-
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22)
|
|
65184
|
-
*
|
|
65185
|
-
*
|
|
65186
|
-
*
|
|
65187
|
-
*
|
|
65188
|
-
* the
|
|
65189
|
-
*
|
|
65190
|
-
*
|
|
65191
|
-
*
|
|
65192
|
-
*
|
|
65193
|
-
*
|
|
65194
|
-
*
|
|
65195
|
-
*
|
|
65196
|
-
*
|
|
65197
|
-
*
|
|
65198
|
-
*
|
|
65199
|
-
*
|
|
65200
|
-
|
|
65201
|
-
|
|
65202
|
-
* upscale of a ≤640 tile (a blurred lie).
|
|
65203
|
-
*/
|
|
65204
|
-
async cropSubjectVariants(deviceId, trackId, frameHandle, fw, fh, bbox) {
|
|
65205
|
-
if (!this.deps.getNativeCropJpeg) {
|
|
65206
|
-
this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
|
|
65207
|
-
tags: { deviceId },
|
|
65208
|
-
meta: {
|
|
65209
|
-
shmId: frameHandle.shmId,
|
|
65210
|
-
reason: "no-native-cap"
|
|
65211
|
-
}
|
|
65212
|
-
});
|
|
65213
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "no-native-cap");
|
|
65214
|
-
return null;
|
|
65215
|
-
}
|
|
65632
|
+
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22), from the
|
|
65633
|
+
* track's PARKED slab (D422). ONE read, by `(deviceId, trackId)`: the
|
|
65634
|
+
* in-frame slab of the 16:9 central-square WINDOW
|
|
65635
|
+
* ({@link wideCentralSquareLayout}) the runner cut at uncapped native
|
|
65636
|
+
* resolution the instant the track's best was decided. It is composed into
|
|
65637
|
+
* the full 16:9 window (lateral letterbox only for the part outside the
|
|
65638
|
+
* frame) → the `thumbnail`. The `thumbnailSmall` is DERIVED by downscaling
|
|
65639
|
+
* that SAME window to the 480 long-side cap — never a second read, never
|
|
65640
|
+
* upscaled ({@link deriveThumbnailSmall} never upscales a smaller window).
|
|
65641
|
+
*
|
|
65642
|
+
* Both variants share the SAME framing + central-square containment
|
|
65643
|
+
* guarantee: wide consumers use the 16:9 image as-is; square consumers
|
|
65644
|
+
* center-crop the middle square (subject always inside it).
|
|
65645
|
+
*
|
|
65646
|
+
* PARCEL-OR-NOTHING: no parcel (or a throw) returns `null` after a loud
|
|
65647
|
+
* `logger.warn`; the caller SKIPS the write and the per-frame retry parks a
|
|
65648
|
+
* later frame. Never a by-handle crop, never a local upscale of a ≤640 tile.
|
|
65649
|
+
*/
|
|
65650
|
+
async cropSubjectVariants(deviceId, trackId) {
|
|
65216
65651
|
try {
|
|
65217
|
-
const
|
|
65218
|
-
|
|
65219
|
-
|
|
65220
|
-
});
|
|
65221
|
-
if (centralSquareContainmentFraction(bbox, {
|
|
65222
|
-
W: fw,
|
|
65223
|
-
H: fh
|
|
65224
|
-
}) < .5) this.deps.logger.info("best-shot delivered via contained square layout (edge subject)", {
|
|
65225
|
-
tags: { deviceId },
|
|
65226
|
-
meta: {
|
|
65227
|
-
bbox,
|
|
65228
|
-
frameW: fw,
|
|
65229
|
-
frameH: fh
|
|
65230
|
-
}
|
|
65231
|
-
});
|
|
65232
|
-
const askedAt = Date.now();
|
|
65233
|
-
const slab = await this.deps.getNativeCropJpeg(frameHandle, layout.fetch, void 0, deviceId);
|
|
65234
|
-
if (!slab) {
|
|
65235
|
-
this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
|
|
65652
|
+
const parked = await this.deps.getParkedSubjectSlab(deviceId, trackId);
|
|
65653
|
+
if (!parked) {
|
|
65654
|
+
this.deps.logger.warn("best-shot parcel missing — no thumbnail this frame, will retry", {
|
|
65236
65655
|
tags: { deviceId },
|
|
65237
65656
|
meta: {
|
|
65238
|
-
|
|
65239
|
-
|
|
65240
|
-
handleNodeId: frameHandle.nodeId,
|
|
65241
|
-
roundTripMs: Date.now() - askedAt,
|
|
65242
|
-
reason: "native-miss"
|
|
65657
|
+
trackId,
|
|
65658
|
+
reason: "parcel-missing"
|
|
65243
65659
|
}
|
|
65244
65660
|
});
|
|
65245
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "
|
|
65661
|
+
this.deps.onSubjectCropMiss?.(deviceId, trackId, "parcel-missing");
|
|
65246
65662
|
return null;
|
|
65247
65663
|
}
|
|
65248
|
-
const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
|
|
65664
|
+
const thumbnail = await composeWideCentralSquareThumbnail(parked.slab, parked.layout);
|
|
65249
65665
|
return {
|
|
65250
65666
|
thumbnail,
|
|
65251
|
-
thumbnailSmall: await deriveThumbnailSmall(thumbnail)
|
|
65667
|
+
thumbnailSmall: await deriveThumbnailSmall(thumbnail),
|
|
65668
|
+
timestamp: parked.timestamp
|
|
65252
65669
|
};
|
|
65253
65670
|
} catch (err) {
|
|
65254
|
-
this.deps.logger.warn("
|
|
65671
|
+
this.deps.logger.warn("best-shot parcel read failed — no thumbnail this frame, will retry", {
|
|
65255
65672
|
tags: { deviceId },
|
|
65256
65673
|
meta: {
|
|
65257
|
-
|
|
65674
|
+
trackId,
|
|
65258
65675
|
reason: "threw",
|
|
65259
65676
|
error: err instanceof Error ? err.message : String(err)
|
|
65260
65677
|
}
|
|
@@ -70545,7 +70962,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70545
70962
|
* the FULL cleanup litany on every teardown path (and cancels the track's
|
|
70546
70963
|
* queued captures via the onClose hook) and `closeDevice` finally clears
|
|
70547
70964
|
* per-track state on device removal (the two documented leaks). */
|
|
70548
|
-
residents = new TrackResidentState({ onClose: (trackId) =>
|
|
70965
|
+
residents = new TrackResidentState({ onClose: (trackId) => {
|
|
70966
|
+
this.captureScheduler.cancelForTrack(trackId);
|
|
70967
|
+
this.parkedFrames?.release(trackId);
|
|
70968
|
+
} });
|
|
70969
|
+
/**
|
|
70970
|
+
* The hub's side of a PARK (D422): orders the owning node to keep a track's
|
|
70971
|
+
* best frame the instant `BestDetectionTracker` accepts it, and answers the
|
|
70972
|
+
* keyFrame writer, the best-shot thumbnail and the detail plane's full-frame
|
|
70973
|
+
* rung by `(deviceId, trackId, kind)` — never by handle. Null until init.
|
|
70974
|
+
*/
|
|
70975
|
+
parkedFrames = null;
|
|
70549
70976
|
/** Defence in depth for the 2026-08-21 `b50e09f7` incident: a row the track
|
|
70550
70977
|
* store has already CLOSED never earns another media target, whatever the
|
|
70551
70978
|
* tracker keeps handing back. Reads the store's in-RAM close record only —
|
|
@@ -70655,6 +71082,22 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70655
71082
|
* a ≤640 RAM fallback (`tier: 'ram-fullframe'`). Null until init completes. */
|
|
70656
71083
|
getNativeKeyFrameRgb = null;
|
|
70657
71084
|
shuttingDown = false;
|
|
71085
|
+
/**
|
|
71086
|
+
* Per-device delivery-lag windows, folded at `handleInferenceResult` entry.
|
|
71087
|
+
* The instrument that tells a late DELIVERY (starved) apart from a slow
|
|
71088
|
+
* `processFrame` (waiting) — see `frame-lag-observer.ts`. Costs one map read
|
|
71089
|
+
* and ~10 numeric ops per frame, and writes one line per device per 30 s.
|
|
71090
|
+
*/
|
|
71091
|
+
frameLag = new FrameLagObserver();
|
|
71092
|
+
/**
|
|
71093
|
+
* Stops the GC-attributing stall monitor armed in {@link onInitialize}.
|
|
71094
|
+
*
|
|
71095
|
+
* The other half of the frame-lag line: that one says whether the events
|
|
71096
|
+
* arrived late, this one says whether THIS loop was blocked while they did.
|
|
71097
|
+
* The 5-minute `[mem]` heartbeat cannot see a 10 s freeze — it fired at
|
|
71098
|
+
* dt = 300020 / 300066 / 299991 ms straight through the 2026-09-09 silences.
|
|
71099
|
+
*/
|
|
71100
|
+
stopStallMonitor = null;
|
|
70658
71101
|
/** True only on the cluster's designated post-processing node. When false the
|
|
70659
71102
|
* addon subscribes to NOTHING — fully inert (no event/media generation). */
|
|
70660
71103
|
isPostProcessingNode = true;
|
|
@@ -70873,6 +71316,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70873
71316
|
super({});
|
|
70874
71317
|
}
|
|
70875
71318
|
async onInitialize() {
|
|
71319
|
+
this.stopStallMonitor = startEventLoopStallMonitor(this.ctx.logger);
|
|
70876
71320
|
const rawApi = this.ctx.api;
|
|
70877
71321
|
if (!rawApi) throw new Error("pipeline-analytics requires ctx.api (device-manager + settings-store)");
|
|
70878
71322
|
const api = censusApi(rawApi, this.readCensus);
|
|
@@ -70923,17 +71367,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70923
71367
|
noteCropOutcome: (deviceId, reason) => this.failureReport.noteEnrichmentCrop(deviceId, reason)
|
|
70924
71368
|
});
|
|
70925
71369
|
this.getNativeKeyFrameRgb = transport.getNativeKeyFrameRgb;
|
|
71370
|
+
this.parkedFrames = createParkedFrameClient({
|
|
71371
|
+
api: api.pipelineRunner,
|
|
71372
|
+
logger: logger.child("ParkedFrames"),
|
|
71373
|
+
isNodeOffline: (nodeId) => this.nodeLiveness.isKnownOffline(nodeId, Date.now())
|
|
71374
|
+
});
|
|
70926
71375
|
this.captureCrop = transport.captureCrop;
|
|
70927
71376
|
this.eventMediaDispatcher = new EventMediaDispatcher({
|
|
70928
71377
|
getRemoteFrame: transport.getRemoteFrame,
|
|
70929
71378
|
getNativeCropJpeg: transport.getNativeCropJpeg,
|
|
70930
71379
|
getNativeFullFrameRgb: transport.getNativeFullFrameRgb,
|
|
71380
|
+
getParkedSubjectSlab: (deviceId, trackId) => this.fetchParkedSubjectSlab(deviceId, trackId),
|
|
70931
71381
|
mediaStore: stores.mediaStore,
|
|
70932
71382
|
logger: logger.child("EventMediaDispatcher"),
|
|
70933
71383
|
onSubjectCropMiss: (deviceId, trackId, reason) => this.residents.notePreviewCaptureMiss(deviceId, trackId, `thumbnail:${reason}`)
|
|
70934
71384
|
});
|
|
70935
71385
|
this.buildRecognizers(api, logger, stores, transport);
|
|
70936
|
-
this.buildDetailDispatcher(api, logger
|
|
71386
|
+
this.buildDetailDispatcher(api, logger);
|
|
70937
71387
|
const capProviders = this.buildAnalyticsProviders(api, logger);
|
|
70938
71388
|
await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
|
|
70939
71389
|
await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
|
|
@@ -71763,7 +72213,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71763
72213
|
* demand via `pipelineRunner.runDetailSubtree`. The dispatcher owns the
|
|
71764
72214
|
* per-device schedulers, concurrency cap, and 1 s tick; result routing +
|
|
71765
72215
|
* the pinned call + crop fallback are injected here. */
|
|
71766
|
-
buildDetailDispatcher(api, logger
|
|
72216
|
+
buildDetailDispatcher(api, logger) {
|
|
71767
72217
|
const runnerApi = api.pipelineRunner;
|
|
71768
72218
|
this.detailDispatcher = new TrackDetailDispatcher({
|
|
71769
72219
|
logger: logger.child("DetailDispatcher"),
|
|
@@ -71773,13 +72223,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71773
72223
|
return runnerApi.runDetailSubtree.mutate(input);
|
|
71774
72224
|
},
|
|
71775
72225
|
routeResults: (deviceId, trackId, details, frame) => this.routeDetailResults(deviceId, trackId, details, frame),
|
|
71776
|
-
|
|
71777
|
-
|
|
71778
|
-
|
|
71779
|
-
return
|
|
71780
|
-
base64:
|
|
71781
|
-
width:
|
|
71782
|
-
height:
|
|
72226
|
+
captureParkedFrame: async (_deviceId, trackId) => {
|
|
72227
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
72228
|
+
if (parcel === null) return null;
|
|
72229
|
+
return {
|
|
72230
|
+
base64: parcel.jpeg.toString("base64"),
|
|
72231
|
+
width: parcel.width,
|
|
72232
|
+
height: parcel.height,
|
|
72233
|
+
bbox: scaleSubjectIntoParcel(parcel.info.subject, parcel.width, parcel.height),
|
|
72234
|
+
nodeId: parcel.info.nodeId,
|
|
72235
|
+
timestamp: parcel.timestamp
|
|
71783
72236
|
};
|
|
71784
72237
|
},
|
|
71785
72238
|
captureCropBase64: async (frame) => {
|
|
@@ -73377,6 +73830,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73377
73830
|
}
|
|
73378
73831
|
async onShutdown() {
|
|
73379
73832
|
this.shuttingDown = true;
|
|
73833
|
+
if (this.stopStallMonitor) {
|
|
73834
|
+
this.stopStallMonitor();
|
|
73835
|
+
this.stopStallMonitor = null;
|
|
73836
|
+
}
|
|
73380
73837
|
this.sceneEngine?.stop();
|
|
73381
73838
|
this.sceneEngine = null;
|
|
73382
73839
|
if (this.sceneReloadTimer !== null) {
|
|
@@ -73417,10 +73874,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73417
73874
|
this.audioMarkers?.flush(Date.now());
|
|
73418
73875
|
this.detailDispatcher?.dispose();
|
|
73419
73876
|
this.detailDispatcher = null;
|
|
73877
|
+
this.parkedFrames?.dispose();
|
|
73420
73878
|
this.overlayState.clear();
|
|
73421
73879
|
this.overlaySynthesisWarnAt.clear();
|
|
73422
73880
|
this.processors.clear();
|
|
73423
73881
|
this.lastActiveTrackIds.clear();
|
|
73882
|
+
this.frameLag.clear();
|
|
73424
73883
|
this.lastFrameDimsByDevice.clear();
|
|
73425
73884
|
this.dropoutSkipsByKey.clear();
|
|
73426
73885
|
this.residents.clearAll();
|
|
@@ -73460,6 +73919,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73460
73919
|
async handleInferenceResult(data) {
|
|
73461
73920
|
if (this.shuttingDown) return;
|
|
73462
73921
|
const { deviceId, frame } = data;
|
|
73922
|
+
const lag = this.frameLag.observe(deviceId, Date.now(), data.emittedAt, data.capturedAt);
|
|
73923
|
+
if (lag !== null) {
|
|
73924
|
+
const verdict = lagVerdict(lag);
|
|
73925
|
+
(isAlarmingLagReport(lag) ? this.ctx.logger.warn.bind(this.ctx.logger) : this.ctx.logger.info.bind(this.ctx.logger))("pipeline-analytics frame lag", {
|
|
73926
|
+
tags: { deviceId },
|
|
73927
|
+
meta: {
|
|
73928
|
+
verdict,
|
|
73929
|
+
frames: lag.frames,
|
|
73930
|
+
windowMs: lag.windowMs,
|
|
73931
|
+
busLagMaxMs: lag.busLagMaxMs,
|
|
73932
|
+
busLagMinMs: lag.busLagMinMs,
|
|
73933
|
+
busLagUnknownFrames: lag.busLagUnknownFrames,
|
|
73934
|
+
captureLagMaxMs: lag.captureLagMaxMs,
|
|
73935
|
+
captureLagUnknownFrames: lag.captureLagUnknownFrames
|
|
73936
|
+
}
|
|
73937
|
+
});
|
|
73938
|
+
}
|
|
73463
73939
|
await this.processFrame(deviceId, frame, "pipeline", data.frameHandle, data.detailSteps);
|
|
73464
73940
|
}
|
|
73465
73941
|
/**
|
|
@@ -74057,7 +74533,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74057
74533
|
let plateCrops = 0;
|
|
74058
74534
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
74059
74535
|
else plateCrops += 1;
|
|
74060
|
-
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
74536
|
+
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight, frameHandle);
|
|
74061
74537
|
this.offerGroupShot(deviceId, result, frameHandle);
|
|
74062
74538
|
for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
74063
74539
|
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
@@ -74176,7 +74652,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74176
74652
|
}).catch(() => {});
|
|
74177
74653
|
}
|
|
74178
74654
|
const keyFrameCaptures = selectKeyFrameCaptures(snapshotTargets);
|
|
74179
|
-
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures
|
|
74655
|
+
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures);
|
|
74180
74656
|
}
|
|
74181
74657
|
if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
|
|
74182
74658
|
deviceId,
|
|
@@ -75106,10 +75582,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75106
75582
|
});
|
|
75107
75583
|
}).catch(() => {});
|
|
75108
75584
|
}
|
|
75109
|
-
|
|
75110
|
-
|
|
75585
|
+
/**
|
|
75586
|
+
* Order the parcels a best-frame decision wants (D422). Skipped entirely at
|
|
75587
|
+
* a node the registry already called offline (D58) — logged on the
|
|
75588
|
+
* transition by `noteCapturesGated`, counted in the key-frame window.
|
|
75589
|
+
* `onlyIfMissing` is the retry rule: a later frame may fill a kind that has
|
|
75590
|
+
* no parcel, never replace one the ranking preferred.
|
|
75591
|
+
*/
|
|
75592
|
+
issueParkOrders(decision, kinds, onlyIfMissing) {
|
|
75593
|
+
const client = this.parkedFrames;
|
|
75594
|
+
if (client === null) return;
|
|
75595
|
+
if (this.nodeLiveness.isKnownOffline(decision.frameHandle.nodeId, Date.now())) {
|
|
75596
|
+
this.noteCapturesGated(decision.deviceId, decision.frameHandle.nodeId);
|
|
75597
|
+
return;
|
|
75598
|
+
}
|
|
75599
|
+
for (const order of buildParkOrders(decision)) {
|
|
75600
|
+
if (!kinds.includes(order.kind)) continue;
|
|
75601
|
+
if (onlyIfMissing && client.info(order.trackId, order.kind) !== void 0) continue;
|
|
75602
|
+
client.park(order);
|
|
75603
|
+
}
|
|
75604
|
+
}
|
|
75605
|
+
/** A parcel by track, after any park in flight for it has landed. */
|
|
75606
|
+
async fetchParkedParcel(trackId, kind) {
|
|
75607
|
+
const client = this.parkedFrames;
|
|
75608
|
+
if (client === null) return null;
|
|
75609
|
+
await client.awaitPark(trackId, kind);
|
|
75610
|
+
return client.fetch(trackId, kind);
|
|
75611
|
+
}
|
|
75612
|
+
/** The best-shot slab for the dispatcher: the parcel plus the layout the hub kept. */
|
|
75613
|
+
async fetchParkedSubjectSlab(_deviceId, trackId) {
|
|
75614
|
+
const parcel = await this.fetchParkedParcel(trackId, "thumbnail");
|
|
75615
|
+
if (parcel === null || parcel.info.layout === void 0) return null;
|
|
75616
|
+
return {
|
|
75617
|
+
slab: parcel.jpeg,
|
|
75618
|
+
layout: parcel.info.layout,
|
|
75619
|
+
timestamp: parcel.timestamp
|
|
75620
|
+
};
|
|
75621
|
+
}
|
|
75622
|
+
async persistKeyFrames(deviceId, timestamp, captures) {
|
|
75111
75623
|
const mediaStore = this.mediaStore;
|
|
75112
|
-
if (
|
|
75624
|
+
if (this.parkedFrames === null || !mediaStore) return;
|
|
75113
75625
|
await Promise.all(captures.map((capture) => this.captureScheduler.request({
|
|
75114
75626
|
deviceId,
|
|
75115
75627
|
trackId: capture.trackId,
|
|
@@ -75119,16 +75631,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75119
75631
|
exec: async () => {
|
|
75120
75632
|
this.residents.noteKeyFrameAttempt(deviceId, capture.trackId, timestamp, capture.retry);
|
|
75121
75633
|
const outcome = await persistTrackKeyFrame({
|
|
75122
|
-
|
|
75123
|
-
|
|
75634
|
+
fetchParkedKeyFrame: async (parcelDeviceId, trackId) => {
|
|
75635
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
75636
|
+
if (parcel === null || parcel.info.deviceId !== parcelDeviceId) return null;
|
|
75637
|
+
return {
|
|
75638
|
+
jpeg: parcel.jpeg,
|
|
75639
|
+
width: parcel.width,
|
|
75640
|
+
height: parcel.height,
|
|
75641
|
+
timestamp: parcel.timestamp,
|
|
75642
|
+
nodeId: parcel.info.nodeId
|
|
75643
|
+
};
|
|
75644
|
+
},
|
|
75645
|
+
deriveSmall: deriveKeyFrameSmall,
|
|
75124
75646
|
media: mediaStore,
|
|
75125
75647
|
logger: this.ctx.logger
|
|
75126
75648
|
}, {
|
|
75127
75649
|
deviceId,
|
|
75128
75650
|
trackId: capture.trackId,
|
|
75129
|
-
timestamp,
|
|
75130
|
-
frameHandle,
|
|
75131
|
-
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
|
|
75132
75651
|
retry: capture.retry
|
|
75133
75652
|
});
|
|
75134
75653
|
if (outcome.kind === "landed") {
|
|
@@ -75208,10 +75727,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75208
75727
|
isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
|
|
75209
75728
|
captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
|
|
75210
75729
|
captureKeyFrame: async (req) => {
|
|
75730
|
+
this.issueParkOrders({
|
|
75731
|
+
deviceId: req.deviceId,
|
|
75732
|
+
trackId: req.trackId,
|
|
75733
|
+
timestamp: req.timestamp,
|
|
75734
|
+
frameHandle: req.frameHandle,
|
|
75735
|
+
bbox: { ...req.bodyBbox },
|
|
75736
|
+
frameWidth: req.frameWidth,
|
|
75737
|
+
frameHeight: req.frameHeight
|
|
75738
|
+
}, ["keyFrame"], true);
|
|
75211
75739
|
await this.persistKeyFrames(req.deviceId, req.timestamp, [{
|
|
75212
75740
|
trackId: req.trackId,
|
|
75213
75741
|
retry: false
|
|
75214
|
-
}]
|
|
75742
|
+
}]);
|
|
75215
75743
|
}
|
|
75216
75744
|
}, input);
|
|
75217
75745
|
}
|
|
@@ -75265,10 +75793,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75265
75793
|
* (coalescing, per-device budget and land-only bookkeeping identical). */
|
|
75266
75794
|
captureSemanticBestShot(request) {
|
|
75267
75795
|
const { deviceId, trackId, timestamp, bbox, frameHandle } = request;
|
|
75796
|
+
this.issueParkOrders({
|
|
75797
|
+
deviceId,
|
|
75798
|
+
trackId,
|
|
75799
|
+
timestamp,
|
|
75800
|
+
frameHandle,
|
|
75801
|
+
bbox: { ...bbox },
|
|
75802
|
+
frameWidth: request.frameWidth,
|
|
75803
|
+
frameHeight: request.frameHeight
|
|
75804
|
+
}, ["keyFrame", "thumbnail"], false);
|
|
75268
75805
|
this.persistKeyFrames(deviceId, timestamp, [{
|
|
75269
75806
|
trackId,
|
|
75270
75807
|
retry: false
|
|
75271
|
-
}]
|
|
75808
|
+
}]);
|
|
75272
75809
|
const dispatcher = this.eventMediaDispatcher;
|
|
75273
75810
|
if (!dispatcher) return;
|
|
75274
75811
|
if (this.captureScheduler.isKeyActive(captureCoalesceKey(deviceId, "thumbnail", trackId))) {
|
|
@@ -75419,7 +75956,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75419
75956
|
}
|
|
75420
75957
|
return retries;
|
|
75421
75958
|
}
|
|
75422
|
-
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
75959
|
+
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight, frameHandle) {
|
|
75423
75960
|
const targets = [];
|
|
75424
75961
|
for (const t of tracked) {
|
|
75425
75962
|
if (t.matchedThisFrame === false) continue;
|
|
@@ -75505,6 +76042,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75505
76042
|
state: this.residents.keyFrameCapture(t.trackId),
|
|
75506
76043
|
now: timestamp
|
|
75507
76044
|
}).kind;
|
|
76045
|
+
if (frameHandle !== void 0 && plausibleBox) {
|
|
76046
|
+
const decision = {
|
|
76047
|
+
deviceId,
|
|
76048
|
+
trackId: t.trackId,
|
|
76049
|
+
timestamp,
|
|
76050
|
+
frameHandle,
|
|
76051
|
+
bbox: {
|
|
76052
|
+
x: t.bbox.x,
|
|
76053
|
+
y: t.bbox.y,
|
|
76054
|
+
w: t.bbox.w,
|
|
76055
|
+
h: t.bbox.h
|
|
76056
|
+
},
|
|
76057
|
+
frameWidth,
|
|
76058
|
+
frameHeight
|
|
76059
|
+
};
|
|
76060
|
+
if (isNewBest) this.issueParkOrders(decision, ["keyFrame", "thumbnail"], false);
|
|
76061
|
+
else {
|
|
76062
|
+
const retryKinds = [];
|
|
76063
|
+
if (keyFrame === "retry") retryKinds.push("keyFrame");
|
|
76064
|
+
if (bestThumbnail) retryKinds.push("thumbnail");
|
|
76065
|
+
if (retryKinds.length > 0) this.issueParkOrders(decision, retryKinds, true);
|
|
76066
|
+
}
|
|
76067
|
+
}
|
|
75508
76068
|
const suppressedBy = (() => {
|
|
75509
76069
|
if (skipParkedThumb) return "parked-thumbnail";
|
|
75510
76070
|
if (!isNewBest || bestThumbnail) return void 0;
|