@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
|
@@ -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";
|
|
@@ -14914,7 +14914,7 @@ function buildOccupancyStatus(input) {
|
|
|
14914
14914
|
* `this.byId.clear()` happened to sit AFTER the `await` that threw. Nothing
|
|
14915
14915
|
* said so, and hoisting one line would have silently turned every transient
|
|
14916
14916
|
* store error into a hub that stops notifying — the reversal behind the D130
|
|
14917
|
-
* flood and the
|
|
14917
|
+
* flood and the occupancy cold-seed loss. The ledger's contract
|
|
14918
14918
|
* rule 2 (a failed load NEVER clears the mirror, deliberately no knob) makes
|
|
14919
14919
|
* that reversal unexpressible here. `rule-store-load-failure-direction.spec.ts`
|
|
14920
14920
|
* holds the direction and records what was removed to make it red.
|
|
@@ -18131,7 +18131,7 @@ function clockOf$2(atMs) {
|
|
|
18131
18131
|
*
|
|
18132
18132
|
* ## The `.partial()` trap this file exists to avoid
|
|
18133
18133
|
*
|
|
18134
|
-
*
|
|
18134
|
+
* Found in production 2026-08-12: `NcRulePatchSchema` was
|
|
18135
18135
|
* `NcRuleInputSchema.partial()`, and Zod 4 `.partial()` KEEPS the inner
|
|
18136
18136
|
* `.default()`. Parsing `{ name: 'x' }` therefore materialised every defaulted
|
|
18137
18137
|
* key, and `{ ...existing, ...patch }` wrote them over the rule — a camera- and
|
|
@@ -34881,6 +34881,8 @@ var SortTracker = class SortTracker {
|
|
|
34881
34881
|
refusalSampler = new SpawnRefusalSampler();
|
|
34882
34882
|
/** Sink for dropped-detection diagnostics — see {@link setSpawnRefusalObserver}. */
|
|
34883
34883
|
spawnRefusalObserver = null;
|
|
34884
|
+
/** Unsampled copy for the frame path — see `setSpawnRefusalSink`. */
|
|
34885
|
+
refusalSink = null;
|
|
34884
34886
|
/** Per-reason counters behind the sampled spawn-beside-coasting diagnostics. */
|
|
34885
34887
|
spawnBesideSampler = new SpawnBesideCoastingSampler();
|
|
34886
34888
|
/** Sink for fragmentation diagnostics — see {@link setSpawnBesideCoastingObserver}. */
|
|
@@ -34993,11 +34995,7 @@ var SortTracker = class SortTracker {
|
|
|
34993
34995
|
}
|
|
34994
34996
|
/** Emit one dropped-detection record, subject to the refusal sampler. */
|
|
34995
34997
|
reportRefusal(reason, det, extra = {}) {
|
|
34996
|
-
const
|
|
34997
|
-
if (!observer) return;
|
|
34998
|
-
const refusalCount = this.refusalSampler.next(reason);
|
|
34999
|
-
if (refusalCount === void 0) return;
|
|
35000
|
-
observer({
|
|
34998
|
+
const base = {
|
|
35001
34999
|
reason,
|
|
35002
35000
|
detClass: det.class,
|
|
35003
35001
|
detScore: det.score,
|
|
@@ -35009,13 +35007,31 @@ var SortTracker = class SortTracker {
|
|
|
35009
35007
|
againstTrackId: extra.against.id,
|
|
35010
35008
|
againstTrackClass: extra.against.class
|
|
35011
35009
|
} : {},
|
|
35012
|
-
refusalCount,
|
|
35013
|
-
sampledEvery: 10,
|
|
35014
35010
|
...extra.droppedTrackId !== void 0 ? { droppedTrackId: extra.droppedTrackId } : {},
|
|
35015
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
|
|
35016
35022
|
});
|
|
35017
35023
|
}
|
|
35018
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
|
+
/**
|
|
35019
35035
|
* Report a TRACK that ends having emitted nothing — the last silent branch
|
|
35020
35036
|
* on the detection→position path (D391).
|
|
35021
35037
|
*
|
|
@@ -36050,6 +36066,17 @@ function mapObjectStateToTrackState(s) {
|
|
|
36050
36066
|
default: return "new";
|
|
36051
36067
|
}
|
|
36052
36068
|
}
|
|
36069
|
+
/**
|
|
36070
|
+
* How long an identical refusal stays collapsed. A stationary subject is
|
|
36071
|
+
* refused on every analysed frame; at ~2 fps of analysis, 60 s turns a parked
|
|
36072
|
+
* car's 120 lines into one and still reports a subject that comes back later.
|
|
36073
|
+
*/
|
|
36074
|
+
var ZONE_REFUSAL_THROTTLE_MS = 6e4;
|
|
36075
|
+
/** Box quantisation for the collapse key: a subject that moves less than this
|
|
36076
|
+
* is the same sighting for diagnostic purposes. */
|
|
36077
|
+
var ZONE_REFUSAL_BOX_CELL_PX = 64;
|
|
36078
|
+
/** Bound on the collapse map; stale keys are swept when it is exceeded. */
|
|
36079
|
+
var ZONE_REFUSAL_KEY_LIMIT = 512;
|
|
36053
36080
|
var FrameProcessor = class {
|
|
36054
36081
|
deviceId;
|
|
36055
36082
|
/** Detection source this processor handles (`pipeline` | `onboard`).
|
|
@@ -36109,6 +36136,23 @@ var FrameProcessor = class {
|
|
|
36109
36136
|
/** Sink + sampler for the drops this stage owns (the detection-stage zone
|
|
36110
36137
|
* filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
|
|
36111
36138
|
spawnRefusalObserver = null;
|
|
36139
|
+
/** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
|
|
36140
|
+
spawnRefusalForward = null;
|
|
36141
|
+
/**
|
|
36142
|
+
* The spawn refusals of the frame being processed.
|
|
36143
|
+
*
|
|
36144
|
+
* Filled by the same sink that feeds the diagnostic log, and drained at the
|
|
36145
|
+
* end of `process`. It exists because a refused detection inside a watched
|
|
36146
|
+
* zone still has to become an EVENT: an immediate zone rule asks "is there an
|
|
36147
|
+
* animal in the flowerbed now?", which does not need "is it the same animal
|
|
36148
|
+
* as before?" answered first. See `zone-detection-emits-without-a-track`.
|
|
36149
|
+
*/
|
|
36150
|
+
refusalsThisFrame = [];
|
|
36151
|
+
/** Running count of refusals-in-zone SEEN — never the throttled number. */
|
|
36152
|
+
zoneRefusalEventCount = 0;
|
|
36153
|
+
zoneRefusalObserver = null;
|
|
36154
|
+
/** Last report time per collapsed key, for {@link ZONE_REFUSAL_THROTTLE_MS}. */
|
|
36155
|
+
zoneRefusalLastReport = /* @__PURE__ */ new Map();
|
|
36112
36156
|
zoneRefusalSampler = new SpawnRefusalSampler();
|
|
36113
36157
|
/** Optional stationary-object gate (parked-object suppression). Null until
|
|
36114
36158
|
* the addon wires it via {@link setStationaryGate}. */
|
|
@@ -36123,6 +36167,9 @@ var FrameProcessor = class {
|
|
|
36123
36167
|
this.deviceId = deviceId;
|
|
36124
36168
|
this.source = source;
|
|
36125
36169
|
this.tracker = new SortTracker(trackerConfig);
|
|
36170
|
+
this.tracker.setSpawnRefusalSink((record) => {
|
|
36171
|
+
this.refusalsThisFrame.push(record);
|
|
36172
|
+
});
|
|
36126
36173
|
this.stateAnalyzer = new StateAnalyzer(stateConfig);
|
|
36127
36174
|
this.eventEmitter = new DetectionEventEmitter(emitterConfig);
|
|
36128
36175
|
this.zones = [];
|
|
@@ -36206,6 +36253,14 @@ var FrameProcessor = class {
|
|
|
36206
36253
|
this.tracker.setSpawnRefusalObserver(observer);
|
|
36207
36254
|
this.spawnRefusalObserver = observer;
|
|
36208
36255
|
}
|
|
36256
|
+
/**
|
|
36257
|
+
* Where a refused-inside-a-zone detection goes. Diagnostics only: this is
|
|
36258
|
+
* deliberately NOT the event stream, so nothing downstream can alert on a
|
|
36259
|
+
* box the confirmation gate threw away (D426).
|
|
36260
|
+
*/
|
|
36261
|
+
setZoneRefusalObserver(observer) {
|
|
36262
|
+
this.zoneRefusalObserver = observer;
|
|
36263
|
+
}
|
|
36209
36264
|
setBindObserver(observer) {
|
|
36210
36265
|
this.tracker.setBindObserver(observer);
|
|
36211
36266
|
}
|
|
@@ -36229,6 +36284,88 @@ var FrameProcessor = class {
|
|
|
36229
36284
|
* ate it. Sampled like every other refusal: an excluded box is excluded on
|
|
36230
36285
|
* every frame of a passage.
|
|
36231
36286
|
*/
|
|
36287
|
+
/**
|
|
36288
|
+
* Turn this frame's refused spawns into events, but only those inside a zone.
|
|
36289
|
+
*
|
|
36290
|
+
* Drains {@link refusalsThisFrame}. Each surviving record becomes an
|
|
36291
|
+
* `object.detected` event with NO track — carrying the zones it fell in, the
|
|
36292
|
+
* percentage of the box inside each, and the refusal reason so a consumer can
|
|
36293
|
+
* tell it apart from a track whose media went missing.
|
|
36294
|
+
*
|
|
36295
|
+
* `zone-filtered` refusals are excluded on purpose: that reason means the
|
|
36296
|
+
* operator's own detection rules already said the box does not count on this
|
|
36297
|
+
* camera, and re-admitting it here would overrule a decision they made.
|
|
36298
|
+
*/
|
|
36299
|
+
/**
|
|
36300
|
+
* Report every detection the tracker refused that fell INSIDE a watched
|
|
36301
|
+
* zone. Diagnostics only — see the spec
|
|
36302
|
+
* `zone-refusal-is-observed-never-notified` for the ten hours of production
|
|
36303
|
+
* that decided this: 1473 of 1511 such refusals were one stationary parcel
|
|
36304
|
+
* re-offered every frame, and both notifications this path ever produced
|
|
36305
|
+
* were phantoms the confirmation gate had correctly rejected.
|
|
36306
|
+
*
|
|
36307
|
+
* Drains {@link refusalsThisFrame}. `zone-filtered` is skipped: a box the
|
|
36308
|
+
* zone filter already removed was never in a zone.
|
|
36309
|
+
*/
|
|
36310
|
+
reportRefusedDetectionsInZones(timestamp, frameWidth, frameHeight) {
|
|
36311
|
+
const refusals = this.refusalsThisFrame;
|
|
36312
|
+
this.refusalsThisFrame = [];
|
|
36313
|
+
if (refusals.length === 0 || this.zones.length === 0) return;
|
|
36314
|
+
const observer = this.zoneRefusalObserver;
|
|
36315
|
+
if (observer === null) return;
|
|
36316
|
+
for (const r of refusals) {
|
|
36317
|
+
if (r.reason === "zone-filtered") continue;
|
|
36318
|
+
const { memberships } = this.zoneEngine.splitDetectionZones(r.bbox, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
|
|
36319
|
+
if (memberships.length === 0) continue;
|
|
36320
|
+
this.zoneRefusalEventCount += 1;
|
|
36321
|
+
if (!this.shouldReportZoneRefusal(r, memberships, timestamp)) continue;
|
|
36322
|
+
observer({
|
|
36323
|
+
deviceId: this.deviceId,
|
|
36324
|
+
timestamp,
|
|
36325
|
+
className: r.detClass,
|
|
36326
|
+
confidence: r.detScore,
|
|
36327
|
+
bbox: r.bbox,
|
|
36328
|
+
reason: r.reason,
|
|
36329
|
+
zones: memberships.map((m) => ({
|
|
36330
|
+
zoneId: m.zoneId,
|
|
36331
|
+
...m.zoneName === void 0 ? {} : { zoneName: m.zoneName },
|
|
36332
|
+
overlapPct: Math.round(m.overlap * 1e3) / 10
|
|
36333
|
+
})),
|
|
36334
|
+
seenSoFar: this.zoneRefusalEventCount
|
|
36335
|
+
});
|
|
36336
|
+
}
|
|
36337
|
+
}
|
|
36338
|
+
/**
|
|
36339
|
+
* Collapse identical refusals. A refused box is refused on EVERY analysed
|
|
36340
|
+
* frame, so a subject that does not leave — the parcel at the door, the
|
|
36341
|
+
* parked car — is an unbounded emitter. The key includes a coarse box, so a
|
|
36342
|
+
* subject that actually moves keeps reporting while a stationary one does
|
|
36343
|
+
* not.
|
|
36344
|
+
*/
|
|
36345
|
+
shouldReportZoneRefusal(r, memberships, timestamp) {
|
|
36346
|
+
const cell = (v) => Math.round(v / ZONE_REFUSAL_BOX_CELL_PX);
|
|
36347
|
+
const key = [
|
|
36348
|
+
r.detClass,
|
|
36349
|
+
r.reason,
|
|
36350
|
+
memberships.map((m) => m.zoneId).join("+"),
|
|
36351
|
+
cell(r.bbox.x),
|
|
36352
|
+
cell(r.bbox.y),
|
|
36353
|
+
cell(r.bbox.width),
|
|
36354
|
+
cell(r.bbox.height)
|
|
36355
|
+
].join("|");
|
|
36356
|
+
const last = this.zoneRefusalLastReport.get(key);
|
|
36357
|
+
if (last !== void 0 && timestamp - last < ZONE_REFUSAL_THROTTLE_MS) return false;
|
|
36358
|
+
this.zoneRefusalLastReport.set(key, timestamp);
|
|
36359
|
+
if (this.zoneRefusalLastReport.size > ZONE_REFUSAL_KEY_LIMIT) {
|
|
36360
|
+
for (const [k, t] of this.zoneRefusalLastReport) if (timestamp - t >= ZONE_REFUSAL_THROTTLE_MS) this.zoneRefusalLastReport.delete(k);
|
|
36361
|
+
}
|
|
36362
|
+
return true;
|
|
36363
|
+
}
|
|
36364
|
+
/** How many refusals-in-zone this processor has SEEN — the measure that says
|
|
36365
|
+
* whether this path is a trickle or a storm, before any throttling. */
|
|
36366
|
+
zoneRefusalEventsEmitted() {
|
|
36367
|
+
return this.zoneRefusalEventCount;
|
|
36368
|
+
}
|
|
36232
36369
|
reportZoneFiltered(excluded) {
|
|
36233
36370
|
const observer = this.spawnRefusalObserver;
|
|
36234
36371
|
if (!observer || excluded.length === 0) return;
|
|
@@ -36630,6 +36767,7 @@ var FrameProcessor = class {
|
|
|
36630
36767
|
frameHeight
|
|
36631
36768
|
};
|
|
36632
36769
|
};
|
|
36770
|
+
this.reportRefusedDetectionsInZones(timestamp, frameWidth, frameHeight);
|
|
36633
36771
|
const withTrackId = rawEvents.filter((e) => e.detection.trackId);
|
|
36634
36772
|
const objectEvents = withTrackId.filter((e) => e.type !== "object.detected").map((e) => toObjectEvent(e));
|
|
36635
36773
|
const appearanceEvents = withTrackId.filter((e) => e.type === "object.detected").map((e) => toObjectEvent(e, "entered"));
|
|
@@ -47246,6 +47384,7 @@ var TrackDetailDispatcher = class {
|
|
|
47246
47384
|
if (!dev) return;
|
|
47247
47385
|
dev.scheduler.onTrackEnded(trackId);
|
|
47248
47386
|
dev.tracks.delete(trackId);
|
|
47387
|
+
dev.parcelEnriched.delete(trackId);
|
|
47249
47388
|
dev.candidateBest.delete(trackId);
|
|
47250
47389
|
if (dev.tracks.size === 0) this.clearTimer(dev);
|
|
47251
47390
|
}
|
|
@@ -47255,6 +47394,7 @@ var TrackDetailDispatcher = class {
|
|
|
47255
47394
|
this.clearTimer(dev);
|
|
47256
47395
|
dev.queue.length = 0;
|
|
47257
47396
|
dev.tracks.clear();
|
|
47397
|
+
dev.parcelEnriched.clear();
|
|
47258
47398
|
dev.candidateBest.clear();
|
|
47259
47399
|
}
|
|
47260
47400
|
this.devices.clear();
|
|
@@ -47265,6 +47405,7 @@ var TrackDetailDispatcher = class {
|
|
|
47265
47405
|
dev = {
|
|
47266
47406
|
scheduler: new DetailScheduler(),
|
|
47267
47407
|
tracks: /* @__PURE__ */ new Map(),
|
|
47408
|
+
parcelEnriched: /* @__PURE__ */ new Map(),
|
|
47268
47409
|
candidateBest: new BestDetectionTracker(CANDIDATE_IMPROVE_POLICY),
|
|
47269
47410
|
queue: [],
|
|
47270
47411
|
coastedFramesIgnored: 0,
|
|
@@ -47381,19 +47522,21 @@ var TrackDetailDispatcher = class {
|
|
|
47381
47522
|
*
|
|
47382
47523
|
* 1. `frameHandle` — cheapest when it hits: no payload moves and the runner's
|
|
47383
47524
|
* leaf children re-cut their ROI at native resolution. Tried first, always.
|
|
47384
|
-
* 2. `frameJpeg` — the
|
|
47385
|
-
*
|
|
47386
|
-
*
|
|
47387
|
-
*
|
|
47388
|
-
*
|
|
47389
|
-
* the
|
|
47525
|
+
* 2. `frameJpeg` — the track's PARKED key frame (D422): the full native frame
|
|
47526
|
+
* the owning node copied at the best instant, retrieved by track and handed
|
|
47527
|
+
* to the runner UNCUT, so the rectangle still comes from the single
|
|
47528
|
+
* derivation (D52). This rung exists because rung 1 is structurally
|
|
47529
|
+
* unreachable for most dispatches — a tick + inference routinely lands
|
|
47530
|
+
* outside the lease — and it cannot miss on age. It runs ONCE per parcel
|
|
47531
|
+
* per step; a dispatch that finds the same parcel already enriched skips
|
|
47532
|
+
* it, so a coasting track does not re-embed the same image every second.
|
|
47390
47533
|
* 3. `cropJpeg` — the pre-cut tile. Last, because the caller decided its
|
|
47391
47534
|
* rectangle and because in practice the same native miss that emptied rung 1
|
|
47392
47535
|
* empties this one too.
|
|
47393
47536
|
*
|
|
47394
|
-
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG
|
|
47395
|
-
* demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47396
|
-
* rung 1 missed — never a frame-rate stream, and never raw pixels.
|
|
47537
|
+
* On D9/D18: the payload that crosses in rung 2 is a COMPRESSED JPEG retrieved
|
|
47538
|
+
* on demand, per TRACK per dispatch (≤2 in flight per device, ~1 Hz) and only
|
|
47539
|
+
* after rung 1 missed — never a frame-rate stream, and never raw pixels.
|
|
47397
47540
|
*
|
|
47398
47541
|
* ONE outcome stops the ladder cold: the runner's typed `not-attached`
|
|
47399
47542
|
* refusal (`detail-refusal.ts`). It names the camera's SESSION state, not
|
|
@@ -47431,34 +47574,39 @@ var TrackDetailDispatcher = class {
|
|
|
47431
47574
|
}
|
|
47432
47575
|
});
|
|
47433
47576
|
}
|
|
47434
|
-
if (this.deps.
|
|
47435
|
-
const
|
|
47436
|
-
if (
|
|
47577
|
+
if (this.deps.captureParkedFrame !== void 0) try {
|
|
47578
|
+
const parked = await this.deps.captureParkedFrame(deviceId, req.trackId);
|
|
47579
|
+
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", {
|
|
47437
47580
|
tags: { deviceId },
|
|
47438
47581
|
meta: {
|
|
47439
47582
|
trackId: req.trackId,
|
|
47440
47583
|
stepId: req.stepId,
|
|
47441
|
-
|
|
47442
|
-
frameHeight: full.height,
|
|
47443
|
-
bboxFrameWidth: frame.frameWidth,
|
|
47444
|
-
bboxFrameHeight: frame.frameHeight
|
|
47584
|
+
parkedTimestamp: parked.timestamp
|
|
47445
47585
|
}
|
|
47446
47586
|
});
|
|
47447
47587
|
else {
|
|
47448
47588
|
const derived = await this.deps.runDetailSubtree({
|
|
47449
47589
|
deviceId,
|
|
47450
|
-
frameJpeg:
|
|
47451
|
-
parent
|
|
47590
|
+
frameJpeg: parked.base64,
|
|
47591
|
+
parent: {
|
|
47592
|
+
bbox: { ...parked.bbox },
|
|
47593
|
+
className: frame.className
|
|
47594
|
+
},
|
|
47452
47595
|
steps
|
|
47453
|
-
},
|
|
47454
|
-
if (derived !== null)
|
|
47596
|
+
}, parked.nodeId);
|
|
47597
|
+
if (derived !== null) {
|
|
47598
|
+
const perStep = dev.parcelEnriched.get(req.trackId) ?? /* @__PURE__ */ new Map();
|
|
47599
|
+
perStep.set(req.stepId, parked.timestamp);
|
|
47600
|
+
dev.parcelEnriched.set(req.trackId, perStep);
|
|
47601
|
+
return derived.details;
|
|
47602
|
+
}
|
|
47455
47603
|
}
|
|
47456
47604
|
} catch (err) {
|
|
47457
47605
|
if (isDetailNotAttachedRefusal(err)) {
|
|
47458
47606
|
this.warnNotAttachedThrottled(deviceId, dev, req);
|
|
47459
47607
|
return null;
|
|
47460
47608
|
}
|
|
47461
|
-
this.deps.logger.debug("detail
|
|
47609
|
+
this.deps.logger.debug("detail parked-frame rung failed", {
|
|
47462
47610
|
tags: { deviceId },
|
|
47463
47611
|
meta: {
|
|
47464
47612
|
trackId: req.trackId,
|
|
@@ -47525,7 +47673,7 @@ var TrackDetailDispatcher = class {
|
|
|
47525
47673
|
trackId: req.trackId,
|
|
47526
47674
|
stepId: req.stepId,
|
|
47527
47675
|
reason: req.reason,
|
|
47528
|
-
|
|
47676
|
+
triedParkedFrame: this.deps.captureParkedFrame !== void 0,
|
|
47529
47677
|
triedCropJpeg: this.deps.captureCropBase64 !== void 0
|
|
47530
47678
|
}
|
|
47531
47679
|
});
|
|
@@ -53890,6 +54038,17 @@ async function encodeKeyFrameVariants(native) {
|
|
|
53890
54038
|
})
|
|
53891
54039
|
};
|
|
53892
54040
|
}
|
|
54041
|
+
/**
|
|
54042
|
+
* The web companion from the PARKED key frame's own JPEG (D422): the parcel is
|
|
54043
|
+
* stored verbatim as `keyFrame`, and `keyFrameSmall` is this — the same bytes
|
|
54044
|
+
* downscaled to {@link KEYFRAME_SMALL_MAX_WIDTH}. One decode, one encode,
|
|
54045
|
+
* never a second fetch; `withoutEnlargement` never upscales a sub-960 frame.
|
|
54046
|
+
* The by-handle twin ({@link encodeKeyFrameVariants}) remains for the
|
|
54047
|
+
* summary group shot, which still fetches a native surface by handle.
|
|
54048
|
+
*/
|
|
54049
|
+
async function deriveKeyFrameSmall(keyFrameJpeg) {
|
|
54050
|
+
return sharp(keyFrameJpeg).resize(960, null, { withoutEnlargement: true }).jpeg({ quality: 88 }).toBuffer();
|
|
54051
|
+
}
|
|
53893
54052
|
//#endregion
|
|
53894
54053
|
//#region src/pipeline-analytics/pipeline/key-frame-metrics.ts
|
|
53895
54054
|
var DEFAULT_FLUSH_INTERVAL_MS = 6e4;
|
|
@@ -53915,11 +54074,8 @@ var KeyFrameCaptureLog = class {
|
|
|
53915
54074
|
w.landed += 1;
|
|
53916
54075
|
if (sample.retry) w.retryLanded += 1;
|
|
53917
54076
|
break;
|
|
53918
|
-
case "
|
|
53919
|
-
w.
|
|
53920
|
-
break;
|
|
53921
|
-
case "ram-tier":
|
|
53922
|
-
w.ramTier += 1;
|
|
54077
|
+
case "parcel-missing":
|
|
54078
|
+
w.parcelMissing += 1;
|
|
53923
54079
|
break;
|
|
53924
54080
|
case "error":
|
|
53925
54081
|
w.error += 1;
|
|
@@ -53948,8 +54104,7 @@ var KeyFrameCaptureLog = class {
|
|
|
53948
54104
|
landed: 0,
|
|
53949
54105
|
retryAttempts: 0,
|
|
53950
54106
|
retryLanded: 0,
|
|
53951
|
-
|
|
53952
|
-
ramTier: 0,
|
|
54107
|
+
parcelMissing: 0,
|
|
53953
54108
|
error: 0,
|
|
53954
54109
|
skippedNodeOffline: 0
|
|
53955
54110
|
};
|
|
@@ -53964,8 +54119,7 @@ var KeyFrameCaptureLog = class {
|
|
|
53964
54119
|
landed: w.landed,
|
|
53965
54120
|
retryAttempts: w.retryAttempts,
|
|
53966
54121
|
retryLanded: w.retryLanded,
|
|
53967
|
-
|
|
53968
|
-
ramTier: w.ramTier,
|
|
54122
|
+
parcelMissing: w.parcelMissing,
|
|
53969
54123
|
error: w.error,
|
|
53970
54124
|
skippedNodeOffline: w.skippedNodeOffline
|
|
53971
54125
|
};
|
|
@@ -53974,63 +54128,44 @@ var KeyFrameCaptureLog = class {
|
|
|
53974
54128
|
//#endregion
|
|
53975
54129
|
//#region src/pipeline-analytics/pipeline/key-frame-persist.ts
|
|
53976
54130
|
/**
|
|
53977
|
-
*
|
|
54131
|
+
* Read, derive and store one track's key frame + its web companion.
|
|
53978
54132
|
*
|
|
53979
|
-
*
|
|
53980
|
-
*
|
|
53981
|
-
* outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
54133
|
+
* PARCEL-OR-NOTHING: no parcel writes nothing and the caller's bounded retry
|
|
54134
|
+
* parks a later frame. The `keyFrame` write lands FIRST and its key is what
|
|
54135
|
+
* the outcome carries — a `keyFrameSmall` failure afterwards must not re-arm a
|
|
53982
54136
|
* retry that would re-shoot a key frame the rebuild can already read.
|
|
53983
54137
|
*/
|
|
53984
54138
|
async function persistTrackKeyFrame(deps, input) {
|
|
53985
|
-
const { deviceId, trackId,
|
|
54139
|
+
const { deviceId, trackId, retry } = input;
|
|
53986
54140
|
const drop = {
|
|
53987
54141
|
tags: { deviceId },
|
|
53988
54142
|
meta: {
|
|
53989
54143
|
trackId,
|
|
53990
|
-
nodeId: frameHandle.nodeId,
|
|
53991
54144
|
retry
|
|
53992
54145
|
}
|
|
53993
54146
|
};
|
|
53994
54147
|
try {
|
|
53995
|
-
const
|
|
53996
|
-
|
|
53997
|
-
|
|
53998
|
-
|
|
53999
|
-
...drop,
|
|
54000
|
-
meta: {
|
|
54001
|
-
...drop.meta,
|
|
54002
|
-
shmId: frameHandle.shmId
|
|
54003
|
-
}
|
|
54004
|
-
});
|
|
54005
|
-
return { kind: "native-miss" };
|
|
54006
|
-
}
|
|
54007
|
-
if (!keyFrameAcceptsTier(result.tier)) {
|
|
54008
|
-
deps.logger.warn("keyFrame native miss — RAM tier rejected, will retry while budget remains", {
|
|
54009
|
-
...drop,
|
|
54010
|
-
meta: {
|
|
54011
|
-
...drop.meta,
|
|
54012
|
-
shmId: frameHandle.shmId,
|
|
54013
|
-
tier: result.tier
|
|
54014
|
-
}
|
|
54015
|
-
});
|
|
54016
|
-
return { kind: "ram-tier" };
|
|
54148
|
+
const parcel = await deps.fetchParkedKeyFrame(deviceId, trackId);
|
|
54149
|
+
if (parcel === null || parcel.jpeg.byteLength === 0) {
|
|
54150
|
+
deps.logger.error("key-frame parcel missing — nothing to write, retry parks a later frame", { ...drop });
|
|
54151
|
+
return { kind: "parcel-missing" };
|
|
54017
54152
|
}
|
|
54018
|
-
const { keyFrame, keyFrameSmall } = await deps.encodeVariants(native);
|
|
54019
54153
|
const keyFrameKey = await deps.media.putReplacing({
|
|
54020
54154
|
deviceId,
|
|
54021
54155
|
ownerKind: "track",
|
|
54022
54156
|
ownerId: trackId,
|
|
54023
54157
|
kind: "keyFrame",
|
|
54024
|
-
timestamp:
|
|
54025
|
-
data:
|
|
54158
|
+
timestamp: parcel.timestamp,
|
|
54159
|
+
data: parcel.jpeg
|
|
54026
54160
|
});
|
|
54027
54161
|
try {
|
|
54162
|
+
const keyFrameSmall = await deps.deriveSmall(parcel.jpeg);
|
|
54028
54163
|
await deps.media.putReplacing({
|
|
54029
54164
|
deviceId,
|
|
54030
54165
|
ownerKind: "track",
|
|
54031
54166
|
ownerId: trackId,
|
|
54032
54167
|
kind: "keyFrameSmall",
|
|
54033
|
-
timestamp:
|
|
54168
|
+
timestamp: parcel.timestamp,
|
|
54034
54169
|
data: keyFrameSmall
|
|
54035
54170
|
});
|
|
54036
54171
|
} catch (err) {
|
|
@@ -54038,17 +54173,29 @@ async function persistTrackKeyFrame(deps, input) {
|
|
|
54038
54173
|
...drop,
|
|
54039
54174
|
meta: {
|
|
54040
54175
|
...drop.meta,
|
|
54176
|
+
nodeId: parcel.nodeId,
|
|
54041
54177
|
error: errMsg$1(err)
|
|
54042
54178
|
}
|
|
54043
54179
|
});
|
|
54044
54180
|
}
|
|
54181
|
+
deps.logger.debug("key frame written from the parked parcel", {
|
|
54182
|
+
...drop,
|
|
54183
|
+
meta: {
|
|
54184
|
+
...drop.meta,
|
|
54185
|
+
nodeId: parcel.nodeId,
|
|
54186
|
+
width: parcel.width,
|
|
54187
|
+
height: parcel.height,
|
|
54188
|
+
bytes: parcel.jpeg.byteLength,
|
|
54189
|
+
parkedTimestamp: parcel.timestamp
|
|
54190
|
+
}
|
|
54191
|
+
});
|
|
54045
54192
|
return {
|
|
54046
54193
|
kind: "landed",
|
|
54047
54194
|
keyFrameKey
|
|
54048
54195
|
};
|
|
54049
54196
|
} catch (err) {
|
|
54050
54197
|
const error = errMsg$1(err);
|
|
54051
|
-
deps.logger.warn("key-frame
|
|
54198
|
+
deps.logger.warn("key-frame write failed", {
|
|
54052
54199
|
...drop,
|
|
54053
54200
|
meta: {
|
|
54054
54201
|
...drop.meta,
|
|
@@ -54235,6 +54382,61 @@ function createKeyFrameCrop(deps) {
|
|
|
54235
54382
|
};
|
|
54236
54383
|
}
|
|
54237
54384
|
//#endregion
|
|
54385
|
+
//#region src/pipeline-analytics/pipeline/track-park.ts
|
|
54386
|
+
/** The whole frame — what a `keyFrame` park cuts. */
|
|
54387
|
+
var FULL_FRAME = {
|
|
54388
|
+
x: 0,
|
|
54389
|
+
y: 0,
|
|
54390
|
+
w: 1,
|
|
54391
|
+
h: 1
|
|
54392
|
+
};
|
|
54393
|
+
/** The two parcels a best-frame decision orders, in a fixed order. */
|
|
54394
|
+
function buildParkOrders(decision) {
|
|
54395
|
+
const subject = {
|
|
54396
|
+
bbox: { ...decision.bbox },
|
|
54397
|
+
frameWidth: decision.frameWidth,
|
|
54398
|
+
frameHeight: decision.frameHeight
|
|
54399
|
+
};
|
|
54400
|
+
const layout = deliveredWideCentralSquareLayout(decision.bbox, {
|
|
54401
|
+
W: decision.frameWidth,
|
|
54402
|
+
H: decision.frameHeight
|
|
54403
|
+
});
|
|
54404
|
+
const base = {
|
|
54405
|
+
deviceId: decision.deviceId,
|
|
54406
|
+
trackId: decision.trackId,
|
|
54407
|
+
handle: decision.frameHandle,
|
|
54408
|
+
timestamp: decision.timestamp,
|
|
54409
|
+
subject
|
|
54410
|
+
};
|
|
54411
|
+
return [{
|
|
54412
|
+
...base,
|
|
54413
|
+
kind: "keyFrame",
|
|
54414
|
+
bbox: FULL_FRAME,
|
|
54415
|
+
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH
|
|
54416
|
+
}, {
|
|
54417
|
+
...base,
|
|
54418
|
+
kind: "thumbnail",
|
|
54419
|
+
bbox: layout.fetch,
|
|
54420
|
+
layout
|
|
54421
|
+
}];
|
|
54422
|
+
}
|
|
54423
|
+
/**
|
|
54424
|
+
* The subject's box in the PARCEL's pixel space. The box was measured on the
|
|
54425
|
+
* ≤640 detection raster; the parcel is native-resolution. Both show the same
|
|
54426
|
+
* scene at the same aspect, so a uniform per-axis scale places the box on the
|
|
54427
|
+
* parcel — the runner then derives the crop rectangle from it (D52).
|
|
54428
|
+
*/
|
|
54429
|
+
function scaleSubjectIntoParcel(subject, parcelWidth, parcelHeight) {
|
|
54430
|
+
const sx = parcelWidth / subject.frameWidth;
|
|
54431
|
+
const sy = parcelHeight / subject.frameHeight;
|
|
54432
|
+
return {
|
|
54433
|
+
x: subject.bbox.x * sx,
|
|
54434
|
+
y: subject.bbox.y * sy,
|
|
54435
|
+
w: subject.bbox.w * sx,
|
|
54436
|
+
h: subject.bbox.h * sy
|
|
54437
|
+
};
|
|
54438
|
+
}
|
|
54439
|
+
//#endregion
|
|
54238
54440
|
//#region src/pipeline-analytics/pipeline/native-detection.ts
|
|
54239
54441
|
/**
|
|
54240
54442
|
* Nominal frame size used to denormalize native `[0,1]` boxes when a
|
|
@@ -58765,7 +58967,28 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
58765
58967
|
}
|
|
58766
58968
|
//#endregion
|
|
58767
58969
|
//#region src/pipeline-analytics/summary/summary-store.ts
|
|
58970
|
+
/**
|
|
58971
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
58972
|
+
* write="one row per SUMMARY — a window of co-present tracks on one camera
|
|
58973
|
+
* group. Opened by `openOrAbsorb` when the first member arrives, updated
|
|
58974
|
+
* in place while the window absorbs more, and finalised by `seal` when the
|
|
58975
|
+
* window closes. Per WINDOW, never per frame or per track: the sealed
|
|
58976
|
+
* summary is what a notification rule reads."
|
|
58977
|
+
* retention="age-keyed on `sealedAt`, swept with the rest of the analytics
|
|
58978
|
+
* retention. An unsealed row belongs to a window still open and is never
|
|
58979
|
+
* swept by age — it is closed or cascaded away with its members."
|
|
58980
|
+
*/
|
|
58768
58981
|
var SUMMARIES_COLLECTION = "pipeline-analytics:summaries";
|
|
58982
|
+
/**
|
|
58983
|
+
* @durable class=ledger owner=pipeline-analytics
|
|
58984
|
+
* write="one row per (summary, track) membership, written as a window
|
|
58985
|
+
* absorbs a track. The join that lets a sealed summary name its subjects
|
|
58986
|
+
* without re-reading the track store."
|
|
58987
|
+
* retention="cascaded — a member dies with its summary (`delete-track-cascade`
|
|
58988
|
+
* counts them as leaves) or with the track it names. It carries no clock of
|
|
58989
|
+
* its own: an age sweep here could outlive or predecease the summary the
|
|
58990
|
+
* row only means anything inside of."
|
|
58991
|
+
*/
|
|
58769
58992
|
var SUMMARY_MEMBERS_COLLECTION = "pipeline-analytics:summary-members";
|
|
58770
58993
|
var SUMMARIES_COLUMNS = [
|
|
58771
58994
|
{
|
|
@@ -63593,35 +63816,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63593
63816
|
const getRemoteFrame = async (handle) => requestNativeCrop(handle, FULL_FRAME_BBOX, handle.width, replyToFullFrame);
|
|
63594
63817
|
const fetchNativeFullFrameTiered = async (handle, maxWidth) => requestNativeCrop(handle, FULL_FRAME_BBOX, maxWidth, replyToTieredFullFrame);
|
|
63595
63818
|
const getNativeFullFrameRgb = async (handle, maxWidth) => (await fetchNativeFullFrameTiered(handle, maxWidth))?.frame ?? null;
|
|
63596
|
-
const getFullFrameJpegBase64 = async (handle, maxWidth) => {
|
|
63597
|
-
if (!pipelineRunnerApi?.getNativeCrop) return null;
|
|
63598
|
-
try {
|
|
63599
|
-
const full = await pipelineRunnerApi.getNativeCrop.query({
|
|
63600
|
-
handle,
|
|
63601
|
-
bbox: {
|
|
63602
|
-
x: 0,
|
|
63603
|
-
y: 0,
|
|
63604
|
-
w: 1,
|
|
63605
|
-
h: 1
|
|
63606
|
-
},
|
|
63607
|
-
maxWidth,
|
|
63608
|
-
encodeJpeg: true
|
|
63609
|
-
}, nodePin(handle.nodeId));
|
|
63610
|
-
if (!full?.jpeg || full.width <= 0 || full.height <= 0) return null;
|
|
63611
|
-
return {
|
|
63612
|
-
base64: full.jpeg,
|
|
63613
|
-
width: full.width,
|
|
63614
|
-
height: full.height,
|
|
63615
|
-
tier: full.tier === "ram-fullframe" ? "ram-fullframe" : "native"
|
|
63616
|
-
};
|
|
63617
|
-
} catch (err) {
|
|
63618
|
-
logger.debug("full-frame jpeg fetch failed", { meta: {
|
|
63619
|
-
nodeId: handle.nodeId,
|
|
63620
|
-
error: errMsg$1(err)
|
|
63621
|
-
} });
|
|
63622
|
-
return null;
|
|
63623
|
-
}
|
|
63624
|
-
};
|
|
63625
63819
|
const cropMetricLogger = logger.child("NativeCrop");
|
|
63626
63820
|
let nativeHits = 0;
|
|
63627
63821
|
let nativeFallbacks = 0;
|
|
@@ -63701,7 +63895,6 @@ function createNativeFrameTransport(deps) {
|
|
|
63701
63895
|
getRemoteFrame,
|
|
63702
63896
|
getNativeFullFrameRgb,
|
|
63703
63897
|
getNativeKeyFrameRgb: fetchNativeFullFrameTiered,
|
|
63704
|
-
getFullFrameJpegBase64,
|
|
63705
63898
|
getNativeCropJpeg,
|
|
63706
63899
|
captureCrop: createCaptureCrop({
|
|
63707
63900
|
tryNativeCrop,
|
|
@@ -63812,6 +64005,233 @@ function parseTopologySnapshotNodes(data) {
|
|
|
63812
64005
|
return parsed;
|
|
63813
64006
|
}
|
|
63814
64007
|
//#endregion
|
|
64008
|
+
//#region src/pipeline-analytics/runtime/parked-frame-client.ts
|
|
64009
|
+
/**
|
|
64010
|
+
* Bound on the mirror. A track's record leaves at release; this is the
|
|
64011
|
+
* backstop for a lost close. At the busiest measured hub (~12 sessions,
|
|
64012
|
+
* a few tracks each) 4 096 is an hour of tracks, past which the OLDEST record
|
|
64013
|
+
* is forgotten — its parcel still ages out on the node on its own.
|
|
64014
|
+
*/
|
|
64015
|
+
var MAX_MIRRORED_TRACKS = 4096;
|
|
64016
|
+
/** One "runner has no park method" line per camera per minute. */
|
|
64017
|
+
var SKEW_WARN_THROTTLE_MS = 6e4;
|
|
64018
|
+
function inFlightKey(trackId, kind) {
|
|
64019
|
+
return `${trackId} ${kind}`;
|
|
64020
|
+
}
|
|
64021
|
+
/** Strip the hub-side context off an order: only the wire fields travel. */
|
|
64022
|
+
function toWireInput(order) {
|
|
64023
|
+
return {
|
|
64024
|
+
deviceId: order.deviceId,
|
|
64025
|
+
trackId: order.trackId,
|
|
64026
|
+
kind: order.kind,
|
|
64027
|
+
handle: order.handle,
|
|
64028
|
+
bbox: order.bbox,
|
|
64029
|
+
...order.maxWidth !== void 0 ? { maxWidth: order.maxWidth } : {},
|
|
64030
|
+
timestamp: order.timestamp
|
|
64031
|
+
};
|
|
64032
|
+
}
|
|
64033
|
+
function createParkedFrameClient(deps) {
|
|
64034
|
+
const { api, logger } = deps;
|
|
64035
|
+
const now = deps.now ?? Date.now;
|
|
64036
|
+
/** trackId → kind → what the hub kept beside the parcel. */
|
|
64037
|
+
const mirror = /* @__PURE__ */ new Map();
|
|
64038
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
64039
|
+
const skewWarnedAt = /* @__PURE__ */ new Map();
|
|
64040
|
+
const remember = (order, answer) => {
|
|
64041
|
+
let kinds = mirror.get(order.trackId);
|
|
64042
|
+
if (kinds === void 0) {
|
|
64043
|
+
kinds = /* @__PURE__ */ new Map();
|
|
64044
|
+
mirror.set(order.trackId, kinds);
|
|
64045
|
+
while (mirror.size > MAX_MIRRORED_TRACKS) {
|
|
64046
|
+
const oldest = mirror.keys().next().value;
|
|
64047
|
+
if (oldest === void 0) break;
|
|
64048
|
+
mirror.delete(oldest);
|
|
64049
|
+
}
|
|
64050
|
+
}
|
|
64051
|
+
kinds.set(order.kind, {
|
|
64052
|
+
nodeId: answer.nodeId,
|
|
64053
|
+
deviceId: order.deviceId,
|
|
64054
|
+
kind: order.kind,
|
|
64055
|
+
timestamp: order.timestamp,
|
|
64056
|
+
width: answer.width,
|
|
64057
|
+
height: answer.height,
|
|
64058
|
+
subject: order.subject,
|
|
64059
|
+
...order.layout !== void 0 ? { layout: order.layout } : {}
|
|
64060
|
+
});
|
|
64061
|
+
};
|
|
64062
|
+
const warnSkew = (order) => {
|
|
64063
|
+
const at = skewWarnedAt.get(order.deviceId) ?? 0;
|
|
64064
|
+
if (now() - at < SKEW_WARN_THROTTLE_MS) return;
|
|
64065
|
+
skewWarnedAt.set(order.deviceId, now());
|
|
64066
|
+
logger.warn("track frame park impossible — the runner api has no park method (version skew)", {
|
|
64067
|
+
tags: { deviceId: order.deviceId },
|
|
64068
|
+
meta: {
|
|
64069
|
+
trackId: order.trackId,
|
|
64070
|
+
kind: order.kind,
|
|
64071
|
+
nodeId: order.handle.nodeId
|
|
64072
|
+
}
|
|
64073
|
+
});
|
|
64074
|
+
};
|
|
64075
|
+
const sendPark = async (order) => {
|
|
64076
|
+
if (api === void 0) {
|
|
64077
|
+
warnSkew(order);
|
|
64078
|
+
return false;
|
|
64079
|
+
}
|
|
64080
|
+
if (deps.isNodeOffline?.(order.handle.nodeId) === true) {
|
|
64081
|
+
logger.debug("track frame park skipped — the owning node is offline in the registry", {
|
|
64082
|
+
tags: { deviceId: order.deviceId },
|
|
64083
|
+
meta: {
|
|
64084
|
+
trackId: order.trackId,
|
|
64085
|
+
kind: order.kind,
|
|
64086
|
+
nodeId: order.handle.nodeId
|
|
64087
|
+
}
|
|
64088
|
+
});
|
|
64089
|
+
return false;
|
|
64090
|
+
}
|
|
64091
|
+
const meta = {
|
|
64092
|
+
trackId: order.trackId,
|
|
64093
|
+
kind: order.kind,
|
|
64094
|
+
nodeId: order.handle.nodeId,
|
|
64095
|
+
handle: `${order.handle.shmId}#${order.handle.slot}#${order.handle.seq}`
|
|
64096
|
+
};
|
|
64097
|
+
try {
|
|
64098
|
+
const answer = await api.parkTrackFrame.mutate(toWireInput(order), nodePin(order.handle.nodeId));
|
|
64099
|
+
if (answer.parked) {
|
|
64100
|
+
remember(order, answer);
|
|
64101
|
+
logger.debug("track frame parked", {
|
|
64102
|
+
tags: { deviceId: order.deviceId },
|
|
64103
|
+
meta: {
|
|
64104
|
+
...meta,
|
|
64105
|
+
bytes: answer.bytes,
|
|
64106
|
+
replaced: answer.replaced
|
|
64107
|
+
}
|
|
64108
|
+
});
|
|
64109
|
+
return true;
|
|
64110
|
+
}
|
|
64111
|
+
logger.warn("track frame park refused by the owning node", {
|
|
64112
|
+
tags: { deviceId: order.deviceId },
|
|
64113
|
+
meta: {
|
|
64114
|
+
...meta,
|
|
64115
|
+
reason: answer.reason,
|
|
64116
|
+
handleAgeMs: answer.handleAgeMs,
|
|
64117
|
+
...answer.detail !== void 0 ? { detail: answer.detail } : {}
|
|
64118
|
+
}
|
|
64119
|
+
});
|
|
64120
|
+
return false;
|
|
64121
|
+
} catch (err) {
|
|
64122
|
+
logger.warn("track frame park failed — the order did not reach the node", {
|
|
64123
|
+
tags: { deviceId: order.deviceId },
|
|
64124
|
+
meta: {
|
|
64125
|
+
...meta,
|
|
64126
|
+
error: errMsg$1(err)
|
|
64127
|
+
}
|
|
64128
|
+
});
|
|
64129
|
+
return false;
|
|
64130
|
+
}
|
|
64131
|
+
};
|
|
64132
|
+
const park = (order) => {
|
|
64133
|
+
const key = inFlightKey(order.trackId, order.kind);
|
|
64134
|
+
const pending = inFlight.get(key);
|
|
64135
|
+
if (pending !== void 0) return pending;
|
|
64136
|
+
const run = sendPark(order).finally(() => {
|
|
64137
|
+
if (inFlight.get(key) === run) inFlight.delete(key);
|
|
64138
|
+
});
|
|
64139
|
+
inFlight.set(key, run);
|
|
64140
|
+
return run;
|
|
64141
|
+
};
|
|
64142
|
+
const awaitPark = async (trackId, kind) => {
|
|
64143
|
+
const pending = inFlight.get(inFlightKey(trackId, kind));
|
|
64144
|
+
if (pending !== void 0) await pending;
|
|
64145
|
+
};
|
|
64146
|
+
const info = (trackId, kind) => mirror.get(trackId)?.get(kind);
|
|
64147
|
+
const fetch = async (trackId, kind) => {
|
|
64148
|
+
const known = info(trackId, kind);
|
|
64149
|
+
if (known === void 0 || api === void 0) return null;
|
|
64150
|
+
if (deps.isNodeOffline?.(known.nodeId) === true) {
|
|
64151
|
+
logger.warn("parked frame retrieval skipped — the holding node is offline in the registry", {
|
|
64152
|
+
tags: { deviceId: known.deviceId },
|
|
64153
|
+
meta: {
|
|
64154
|
+
trackId,
|
|
64155
|
+
kind,
|
|
64156
|
+
nodeId: known.nodeId
|
|
64157
|
+
}
|
|
64158
|
+
});
|
|
64159
|
+
return null;
|
|
64160
|
+
}
|
|
64161
|
+
try {
|
|
64162
|
+
const parcel = await api.getParkedTrackFrame.query({
|
|
64163
|
+
deviceId: known.deviceId,
|
|
64164
|
+
trackId,
|
|
64165
|
+
kind
|
|
64166
|
+
}, nodePin(known.nodeId));
|
|
64167
|
+
if (parcel === null) {
|
|
64168
|
+
logger.warn("parked frame gone at retrieval — the node no longer holds it", {
|
|
64169
|
+
tags: { deviceId: known.deviceId },
|
|
64170
|
+
meta: {
|
|
64171
|
+
trackId,
|
|
64172
|
+
kind,
|
|
64173
|
+
nodeId: known.nodeId,
|
|
64174
|
+
parkedTimestamp: known.timestamp
|
|
64175
|
+
}
|
|
64176
|
+
});
|
|
64177
|
+
return null;
|
|
64178
|
+
}
|
|
64179
|
+
return {
|
|
64180
|
+
jpeg: Buffer.from(parcel.jpeg, "base64"),
|
|
64181
|
+
width: parcel.width,
|
|
64182
|
+
height: parcel.height,
|
|
64183
|
+
timestamp: parcel.timestamp,
|
|
64184
|
+
info: known
|
|
64185
|
+
};
|
|
64186
|
+
} catch (err) {
|
|
64187
|
+
logger.warn("parked frame retrieval failed", {
|
|
64188
|
+
tags: { deviceId: known.deviceId },
|
|
64189
|
+
meta: {
|
|
64190
|
+
trackId,
|
|
64191
|
+
kind,
|
|
64192
|
+
nodeId: known.nodeId,
|
|
64193
|
+
error: errMsg$1(err)
|
|
64194
|
+
}
|
|
64195
|
+
});
|
|
64196
|
+
return null;
|
|
64197
|
+
}
|
|
64198
|
+
};
|
|
64199
|
+
const release = (trackId) => {
|
|
64200
|
+
const kinds = mirror.get(trackId);
|
|
64201
|
+
if (kinds === void 0) return;
|
|
64202
|
+
mirror.delete(trackId);
|
|
64203
|
+
if (api === void 0) return;
|
|
64204
|
+
const targets = /* @__PURE__ */ new Map();
|
|
64205
|
+
for (const record of kinds.values()) targets.set(record.nodeId, record.deviceId);
|
|
64206
|
+
for (const [nodeId, deviceId] of targets) api.releaseParkedTrackFrames.mutate({
|
|
64207
|
+
deviceId,
|
|
64208
|
+
trackId
|
|
64209
|
+
}, nodePin(nodeId)).catch((err) => {
|
|
64210
|
+
logger.debug("parked frame release did not reach the node", {
|
|
64211
|
+
tags: { deviceId },
|
|
64212
|
+
meta: {
|
|
64213
|
+
trackId,
|
|
64214
|
+
nodeId,
|
|
64215
|
+
error: errMsg$1(err)
|
|
64216
|
+
}
|
|
64217
|
+
});
|
|
64218
|
+
});
|
|
64219
|
+
};
|
|
64220
|
+
const dispose = () => {
|
|
64221
|
+
mirror.clear();
|
|
64222
|
+
inFlight.clear();
|
|
64223
|
+
skewWarnedAt.clear();
|
|
64224
|
+
};
|
|
64225
|
+
return {
|
|
64226
|
+
park,
|
|
64227
|
+
awaitPark,
|
|
64228
|
+
info,
|
|
64229
|
+
fetch,
|
|
64230
|
+
release,
|
|
64231
|
+
dispose
|
|
64232
|
+
};
|
|
64233
|
+
}
|
|
64234
|
+
//#endregion
|
|
63815
64235
|
//#region src/pipeline-analytics/runtime/widget-manifest.ts
|
|
63816
64236
|
/** The remote-module coordinates shared by every analytics widget. */
|
|
63817
64237
|
var REMOTE_NAME = "addon_pipeline_analytics_widgets";
|
|
@@ -64913,7 +65333,8 @@ function isUniformRgbFrame(data, width, height) {
|
|
|
64913
65333
|
* frame downscaled to {@link SNAPSHOT_MAX_WIDTH} (960) with ONLY the track's own
|
|
64914
65334
|
* box burned in, falling back to the ≤640 detection frame (honest lower res,
|
|
64915
65335
|
* never an upscale) on a native miss. The best `thumbnail` stays a clean,
|
|
64916
|
-
* uncapped native subject crop
|
|
65336
|
+
* uncapped native subject crop — read from the track's PARKED slab, by track
|
|
65337
|
+
* (D422), never by handle.
|
|
64917
65338
|
*
|
|
64918
65339
|
* The detection frame is resolved ONCE per call (blank-frame guard); native full
|
|
64919
65340
|
* frames + subject crops are fetched per target by handle. Frames recycle in
|
|
@@ -65181,10 +65602,10 @@ var EventMediaDispatcher = class {
|
|
|
65181
65602
|
if (sn.rollingLastFrame && boxed) lastFrameWritten = await this.replaceKind(deviceId, sn.trackId, "lastFrame", sn.timestamp, boxed);
|
|
65182
65603
|
let thumbnailWritten = false;
|
|
65183
65604
|
if (sn.bestThumbnail) {
|
|
65184
|
-
const variants = await this.cropSubjectVariants(deviceId, sn.trackId
|
|
65605
|
+
const variants = await this.cropSubjectVariants(deviceId, sn.trackId);
|
|
65185
65606
|
if (variants) {
|
|
65186
|
-
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail",
|
|
65187
|
-
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall",
|
|
65607
|
+
thumbnailWritten = await this.replaceKind(deviceId, sn.trackId, "thumbnail", variants.timestamp, variants.thumbnail, "native");
|
|
65608
|
+
await this.replaceKind(deviceId, sn.trackId, "thumbnailSmall", variants.timestamp, variants.thumbnailSmall, "native");
|
|
65188
65609
|
}
|
|
65189
65610
|
}
|
|
65190
65611
|
return {
|
|
@@ -65262,81 +65683,49 @@ var EventMediaDispatcher = class {
|
|
|
65262
65683
|
}
|
|
65263
65684
|
/**
|
|
65264
65685
|
* The best-shot subject crop as its TWO persisted variants (best-crop
|
|
65265
|
-
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22)
|
|
65266
|
-
*
|
|
65267
|
-
*
|
|
65268
|
-
*
|
|
65269
|
-
*
|
|
65270
|
-
* the
|
|
65271
|
-
*
|
|
65272
|
-
*
|
|
65273
|
-
*
|
|
65274
|
-
*
|
|
65275
|
-
*
|
|
65276
|
-
*
|
|
65277
|
-
*
|
|
65278
|
-
*
|
|
65279
|
-
*
|
|
65280
|
-
*
|
|
65281
|
-
*
|
|
65282
|
-
|
|
65283
|
-
|
|
65284
|
-
* upscale of a ≤640 tile (a blurred lie).
|
|
65285
|
-
*/
|
|
65286
|
-
async cropSubjectVariants(deviceId, trackId, frameHandle, fw, fh, bbox) {
|
|
65287
|
-
if (!this.deps.getNativeCropJpeg) {
|
|
65288
|
-
this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
|
|
65289
|
-
tags: { deviceId },
|
|
65290
|
-
meta: {
|
|
65291
|
-
shmId: frameHandle.shmId,
|
|
65292
|
-
reason: "no-native-cap"
|
|
65293
|
-
}
|
|
65294
|
-
});
|
|
65295
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "no-native-cap");
|
|
65296
|
-
return null;
|
|
65297
|
-
}
|
|
65686
|
+
* fast-load, 2026-07-21; 16:9 central-square reframe, 2026-07-22), from the
|
|
65687
|
+
* track's PARKED slab (D422). ONE read, by `(deviceId, trackId)`: the
|
|
65688
|
+
* in-frame slab of the 16:9 central-square WINDOW
|
|
65689
|
+
* ({@link wideCentralSquareLayout}) the runner cut at uncapped native
|
|
65690
|
+
* resolution the instant the track's best was decided. It is composed into
|
|
65691
|
+
* the full 16:9 window (lateral letterbox only for the part outside the
|
|
65692
|
+
* frame) → the `thumbnail`. The `thumbnailSmall` is DERIVED by downscaling
|
|
65693
|
+
* that SAME window to the 480 long-side cap — never a second read, never
|
|
65694
|
+
* upscaled ({@link deriveThumbnailSmall} never upscales a smaller window).
|
|
65695
|
+
*
|
|
65696
|
+
* Both variants share the SAME framing + central-square containment
|
|
65697
|
+
* guarantee: wide consumers use the 16:9 image as-is; square consumers
|
|
65698
|
+
* center-crop the middle square (subject always inside it).
|
|
65699
|
+
*
|
|
65700
|
+
* PARCEL-OR-NOTHING: no parcel (or a throw) returns `null` after a loud
|
|
65701
|
+
* `logger.warn`; the caller SKIPS the write and the per-frame retry parks a
|
|
65702
|
+
* later frame. Never a by-handle crop, never a local upscale of a ≤640 tile.
|
|
65703
|
+
*/
|
|
65704
|
+
async cropSubjectVariants(deviceId, trackId) {
|
|
65298
65705
|
try {
|
|
65299
|
-
const
|
|
65300
|
-
|
|
65301
|
-
|
|
65302
|
-
});
|
|
65303
|
-
if (centralSquareContainmentFraction(bbox, {
|
|
65304
|
-
W: fw,
|
|
65305
|
-
H: fh
|
|
65306
|
-
}) < .5) this.deps.logger.info("best-shot delivered via contained square layout (edge subject)", {
|
|
65307
|
-
tags: { deviceId },
|
|
65308
|
-
meta: {
|
|
65309
|
-
bbox,
|
|
65310
|
-
frameW: fw,
|
|
65311
|
-
frameH: fh
|
|
65312
|
-
}
|
|
65313
|
-
});
|
|
65314
|
-
const askedAt = Date.now();
|
|
65315
|
-
const slab = await this.deps.getNativeCropJpeg(frameHandle, layout.fetch, void 0, deviceId);
|
|
65316
|
-
if (!slab) {
|
|
65317
|
-
this.deps.logger.warn("native subject crop miss — will retry, no upscale", {
|
|
65706
|
+
const parked = await this.deps.getParkedSubjectSlab(deviceId, trackId);
|
|
65707
|
+
if (!parked) {
|
|
65708
|
+
this.deps.logger.warn("best-shot parcel missing — no thumbnail this frame, will retry", {
|
|
65318
65709
|
tags: { deviceId },
|
|
65319
65710
|
meta: {
|
|
65320
|
-
|
|
65321
|
-
|
|
65322
|
-
handleNodeId: frameHandle.nodeId,
|
|
65323
|
-
roundTripMs: Date.now() - askedAt,
|
|
65324
|
-
reason: "native-miss"
|
|
65711
|
+
trackId,
|
|
65712
|
+
reason: "parcel-missing"
|
|
65325
65713
|
}
|
|
65326
65714
|
});
|
|
65327
|
-
this.deps.onSubjectCropMiss?.(deviceId, trackId, "
|
|
65715
|
+
this.deps.onSubjectCropMiss?.(deviceId, trackId, "parcel-missing");
|
|
65328
65716
|
return null;
|
|
65329
65717
|
}
|
|
65330
|
-
const thumbnail = await composeWideCentralSquareThumbnail(slab, layout);
|
|
65718
|
+
const thumbnail = await composeWideCentralSquareThumbnail(parked.slab, parked.layout);
|
|
65331
65719
|
return {
|
|
65332
65720
|
thumbnail,
|
|
65333
|
-
thumbnailSmall: await deriveThumbnailSmall(thumbnail)
|
|
65721
|
+
thumbnailSmall: await deriveThumbnailSmall(thumbnail),
|
|
65722
|
+
timestamp: parked.timestamp
|
|
65334
65723
|
};
|
|
65335
65724
|
} catch (err) {
|
|
65336
|
-
this.deps.logger.warn("
|
|
65725
|
+
this.deps.logger.warn("best-shot parcel read failed — no thumbnail this frame, will retry", {
|
|
65337
65726
|
tags: { deviceId },
|
|
65338
65727
|
meta: {
|
|
65339
|
-
|
|
65728
|
+
trackId,
|
|
65340
65729
|
reason: "threw",
|
|
65341
65730
|
error: err instanceof Error ? err.message : String(err)
|
|
65342
65731
|
}
|
|
@@ -70627,7 +71016,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70627
71016
|
* the FULL cleanup litany on every teardown path (and cancels the track's
|
|
70628
71017
|
* queued captures via the onClose hook) and `closeDevice` finally clears
|
|
70629
71018
|
* per-track state on device removal (the two documented leaks). */
|
|
70630
|
-
residents = new TrackResidentState({ onClose: (trackId) =>
|
|
71019
|
+
residents = new TrackResidentState({ onClose: (trackId) => {
|
|
71020
|
+
this.captureScheduler.cancelForTrack(trackId);
|
|
71021
|
+
this.parkedFrames?.release(trackId);
|
|
71022
|
+
} });
|
|
71023
|
+
/**
|
|
71024
|
+
* The hub's side of a PARK (D422): orders the owning node to keep a track's
|
|
71025
|
+
* best frame the instant `BestDetectionTracker` accepts it, and answers the
|
|
71026
|
+
* keyFrame writer, the best-shot thumbnail and the detail plane's full-frame
|
|
71027
|
+
* rung by `(deviceId, trackId, kind)` — never by handle. Null until init.
|
|
71028
|
+
*/
|
|
71029
|
+
parkedFrames = null;
|
|
70631
71030
|
/** Defence in depth for the 2026-08-21 `b50e09f7` incident: a row the track
|
|
70632
71031
|
* store has already CLOSED never earns another media target, whatever the
|
|
70633
71032
|
* tracker keeps handing back. Reads the store's in-RAM close record only —
|
|
@@ -71022,17 +71421,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71022
71421
|
noteCropOutcome: (deviceId, reason) => this.failureReport.noteEnrichmentCrop(deviceId, reason)
|
|
71023
71422
|
});
|
|
71024
71423
|
this.getNativeKeyFrameRgb = transport.getNativeKeyFrameRgb;
|
|
71424
|
+
this.parkedFrames = createParkedFrameClient({
|
|
71425
|
+
api: api.pipelineRunner,
|
|
71426
|
+
logger: logger.child("ParkedFrames"),
|
|
71427
|
+
isNodeOffline: (nodeId) => this.nodeLiveness.isKnownOffline(nodeId, Date.now())
|
|
71428
|
+
});
|
|
71025
71429
|
this.captureCrop = transport.captureCrop;
|
|
71026
71430
|
this.eventMediaDispatcher = new EventMediaDispatcher({
|
|
71027
71431
|
getRemoteFrame: transport.getRemoteFrame,
|
|
71028
71432
|
getNativeCropJpeg: transport.getNativeCropJpeg,
|
|
71029
71433
|
getNativeFullFrameRgb: transport.getNativeFullFrameRgb,
|
|
71434
|
+
getParkedSubjectSlab: (deviceId, trackId) => this.fetchParkedSubjectSlab(deviceId, trackId),
|
|
71030
71435
|
mediaStore: stores.mediaStore,
|
|
71031
71436
|
logger: logger.child("EventMediaDispatcher"),
|
|
71032
71437
|
onSubjectCropMiss: (deviceId, trackId, reason) => this.residents.notePreviewCaptureMiss(deviceId, trackId, `thumbnail:${reason}`)
|
|
71033
71438
|
});
|
|
71034
71439
|
this.buildRecognizers(api, logger, stores, transport);
|
|
71035
|
-
this.buildDetailDispatcher(api, logger
|
|
71440
|
+
this.buildDetailDispatcher(api, logger);
|
|
71036
71441
|
const capProviders = this.buildAnalyticsProviders(api, logger);
|
|
71037
71442
|
await step("buildNotificationCenter", () => this.buildNotificationCenter(api, logger, stores));
|
|
71038
71443
|
await step("serveEventMediaDataPlane", () => this.serveEventMediaDataPlane());
|
|
@@ -71862,7 +72267,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71862
72267
|
* demand via `pipelineRunner.runDetailSubtree`. The dispatcher owns the
|
|
71863
72268
|
* per-device schedulers, concurrency cap, and 1 s tick; result routing +
|
|
71864
72269
|
* the pinned call + crop fallback are injected here. */
|
|
71865
|
-
buildDetailDispatcher(api, logger
|
|
72270
|
+
buildDetailDispatcher(api, logger) {
|
|
71866
72271
|
const runnerApi = api.pipelineRunner;
|
|
71867
72272
|
this.detailDispatcher = new TrackDetailDispatcher({
|
|
71868
72273
|
logger: logger.child("DetailDispatcher"),
|
|
@@ -71872,13 +72277,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71872
72277
|
return runnerApi.runDetailSubtree.mutate(input);
|
|
71873
72278
|
},
|
|
71874
72279
|
routeResults: (deviceId, trackId, details, frame) => this.routeDetailResults(deviceId, trackId, details, frame),
|
|
71875
|
-
|
|
71876
|
-
|
|
71877
|
-
|
|
71878
|
-
return
|
|
71879
|
-
base64:
|
|
71880
|
-
width:
|
|
71881
|
-
height:
|
|
72280
|
+
captureParkedFrame: async (_deviceId, trackId) => {
|
|
72281
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
72282
|
+
if (parcel === null) return null;
|
|
72283
|
+
return {
|
|
72284
|
+
base64: parcel.jpeg.toString("base64"),
|
|
72285
|
+
width: parcel.width,
|
|
72286
|
+
height: parcel.height,
|
|
72287
|
+
bbox: scaleSubjectIntoParcel(parcel.info.subject, parcel.width, parcel.height),
|
|
72288
|
+
nodeId: parcel.info.nodeId,
|
|
72289
|
+
timestamp: parcel.timestamp
|
|
71882
72290
|
};
|
|
71883
72291
|
},
|
|
71884
72292
|
captureCropBase64: async (frame) => {
|
|
@@ -73520,6 +73928,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
73520
73928
|
this.audioMarkers?.flush(Date.now());
|
|
73521
73929
|
this.detailDispatcher?.dispose();
|
|
73522
73930
|
this.detailDispatcher = null;
|
|
73931
|
+
this.parkedFrames?.dispose();
|
|
73523
73932
|
this.overlayState.clear();
|
|
73524
73933
|
this.overlaySynthesisWarnAt.clear();
|
|
73525
73934
|
this.processors.clear();
|
|
@@ -74178,7 +74587,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74178
74587
|
let plateCrops = 0;
|
|
74179
74588
|
for (const crops of childCropsByEvent.values()) for (const c of crops) if (c.kind === "faceCrop") faceCrops += 1;
|
|
74180
74589
|
else plateCrops += 1;
|
|
74181
|
-
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight);
|
|
74590
|
+
const snapshotTargets = this.buildSnapshotTargets(deviceId, result.tracked, result.timestamp, mediaSettings, result.frameWidth, result.frameHeight, frameHandle);
|
|
74182
74591
|
this.offerGroupShot(deviceId, result, frameHandle);
|
|
74183
74592
|
for (const retry of this.collectFirstFrameRetries(deviceId, result.tracked, result.timestamp, firstFrameTargets)) firstFrameTargets.push(retry);
|
|
74184
74593
|
const targetedThisFrame = new Set([...firstFrameTargets.map((t) => t.trackId), ...snapshotTargets.map((s) => s.trackId)]);
|
|
@@ -74297,7 +74706,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
74297
74706
|
}).catch(() => {});
|
|
74298
74707
|
}
|
|
74299
74708
|
const keyFrameCaptures = selectKeyFrameCaptures(snapshotTargets);
|
|
74300
|
-
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures
|
|
74709
|
+
if (keyFrameCaptures.length > 0) this.persistKeyFrames(deviceId, result.timestamp, keyFrameCaptures);
|
|
74301
74710
|
}
|
|
74302
74711
|
if (this.faceRecognizer && faceGloballyEnabled && faceSettings) this.faceRecognizer.processFrame({
|
|
74303
74712
|
deviceId,
|
|
@@ -75227,10 +75636,46 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75227
75636
|
});
|
|
75228
75637
|
}).catch(() => {});
|
|
75229
75638
|
}
|
|
75230
|
-
|
|
75231
|
-
|
|
75639
|
+
/**
|
|
75640
|
+
* Order the parcels a best-frame decision wants (D422). Skipped entirely at
|
|
75641
|
+
* a node the registry already called offline (D58) — logged on the
|
|
75642
|
+
* transition by `noteCapturesGated`, counted in the key-frame window.
|
|
75643
|
+
* `onlyIfMissing` is the retry rule: a later frame may fill a kind that has
|
|
75644
|
+
* no parcel, never replace one the ranking preferred.
|
|
75645
|
+
*/
|
|
75646
|
+
issueParkOrders(decision, kinds, onlyIfMissing) {
|
|
75647
|
+
const client = this.parkedFrames;
|
|
75648
|
+
if (client === null) return;
|
|
75649
|
+
if (this.nodeLiveness.isKnownOffline(decision.frameHandle.nodeId, Date.now())) {
|
|
75650
|
+
this.noteCapturesGated(decision.deviceId, decision.frameHandle.nodeId);
|
|
75651
|
+
return;
|
|
75652
|
+
}
|
|
75653
|
+
for (const order of buildParkOrders(decision)) {
|
|
75654
|
+
if (!kinds.includes(order.kind)) continue;
|
|
75655
|
+
if (onlyIfMissing && client.info(order.trackId, order.kind) !== void 0) continue;
|
|
75656
|
+
client.park(order);
|
|
75657
|
+
}
|
|
75658
|
+
}
|
|
75659
|
+
/** A parcel by track, after any park in flight for it has landed. */
|
|
75660
|
+
async fetchParkedParcel(trackId, kind) {
|
|
75661
|
+
const client = this.parkedFrames;
|
|
75662
|
+
if (client === null) return null;
|
|
75663
|
+
await client.awaitPark(trackId, kind);
|
|
75664
|
+
return client.fetch(trackId, kind);
|
|
75665
|
+
}
|
|
75666
|
+
/** The best-shot slab for the dispatcher: the parcel plus the layout the hub kept. */
|
|
75667
|
+
async fetchParkedSubjectSlab(_deviceId, trackId) {
|
|
75668
|
+
const parcel = await this.fetchParkedParcel(trackId, "thumbnail");
|
|
75669
|
+
if (parcel === null || parcel.info.layout === void 0) return null;
|
|
75670
|
+
return {
|
|
75671
|
+
slab: parcel.jpeg,
|
|
75672
|
+
layout: parcel.info.layout,
|
|
75673
|
+
timestamp: parcel.timestamp
|
|
75674
|
+
};
|
|
75675
|
+
}
|
|
75676
|
+
async persistKeyFrames(deviceId, timestamp, captures) {
|
|
75232
75677
|
const mediaStore = this.mediaStore;
|
|
75233
|
-
if (
|
|
75678
|
+
if (this.parkedFrames === null || !mediaStore) return;
|
|
75234
75679
|
await Promise.all(captures.map((capture) => this.captureScheduler.request({
|
|
75235
75680
|
deviceId,
|
|
75236
75681
|
trackId: capture.trackId,
|
|
@@ -75240,16 +75685,23 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75240
75685
|
exec: async () => {
|
|
75241
75686
|
this.residents.noteKeyFrameAttempt(deviceId, capture.trackId, timestamp, capture.retry);
|
|
75242
75687
|
const outcome = await persistTrackKeyFrame({
|
|
75243
|
-
|
|
75244
|
-
|
|
75688
|
+
fetchParkedKeyFrame: async (parcelDeviceId, trackId) => {
|
|
75689
|
+
const parcel = await this.fetchParkedParcel(trackId, "keyFrame");
|
|
75690
|
+
if (parcel === null || parcel.info.deviceId !== parcelDeviceId) return null;
|
|
75691
|
+
return {
|
|
75692
|
+
jpeg: parcel.jpeg,
|
|
75693
|
+
width: parcel.width,
|
|
75694
|
+
height: parcel.height,
|
|
75695
|
+
timestamp: parcel.timestamp,
|
|
75696
|
+
nodeId: parcel.info.nodeId
|
|
75697
|
+
};
|
|
75698
|
+
},
|
|
75699
|
+
deriveSmall: deriveKeyFrameSmall,
|
|
75245
75700
|
media: mediaStore,
|
|
75246
75701
|
logger: this.ctx.logger
|
|
75247
75702
|
}, {
|
|
75248
75703
|
deviceId,
|
|
75249
75704
|
trackId: capture.trackId,
|
|
75250
|
-
timestamp,
|
|
75251
|
-
frameHandle,
|
|
75252
|
-
maxWidth: KEYFRAME_NATIVE_MAX_WIDTH,
|
|
75253
75705
|
retry: capture.retry
|
|
75254
75706
|
});
|
|
75255
75707
|
if (outcome.kind === "landed") {
|
|
@@ -75329,10 +75781,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75329
75781
|
isKeyFrameLanded: (trackId) => this.residents.keyFrameCapture(trackId).landed,
|
|
75330
75782
|
captureBodyThumbnail: (req) => this.captureHierarchyBodyThumbnail(req),
|
|
75331
75783
|
captureKeyFrame: async (req) => {
|
|
75784
|
+
this.issueParkOrders({
|
|
75785
|
+
deviceId: req.deviceId,
|
|
75786
|
+
trackId: req.trackId,
|
|
75787
|
+
timestamp: req.timestamp,
|
|
75788
|
+
frameHandle: req.frameHandle,
|
|
75789
|
+
bbox: { ...req.bodyBbox },
|
|
75790
|
+
frameWidth: req.frameWidth,
|
|
75791
|
+
frameHeight: req.frameHeight
|
|
75792
|
+
}, ["keyFrame"], true);
|
|
75332
75793
|
await this.persistKeyFrames(req.deviceId, req.timestamp, [{
|
|
75333
75794
|
trackId: req.trackId,
|
|
75334
75795
|
retry: false
|
|
75335
|
-
}]
|
|
75796
|
+
}]);
|
|
75336
75797
|
}
|
|
75337
75798
|
}, input);
|
|
75338
75799
|
}
|
|
@@ -75386,10 +75847,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75386
75847
|
* (coalescing, per-device budget and land-only bookkeeping identical). */
|
|
75387
75848
|
captureSemanticBestShot(request) {
|
|
75388
75849
|
const { deviceId, trackId, timestamp, bbox, frameHandle } = request;
|
|
75850
|
+
this.issueParkOrders({
|
|
75851
|
+
deviceId,
|
|
75852
|
+
trackId,
|
|
75853
|
+
timestamp,
|
|
75854
|
+
frameHandle,
|
|
75855
|
+
bbox: { ...bbox },
|
|
75856
|
+
frameWidth: request.frameWidth,
|
|
75857
|
+
frameHeight: request.frameHeight
|
|
75858
|
+
}, ["keyFrame", "thumbnail"], false);
|
|
75389
75859
|
this.persistKeyFrames(deviceId, timestamp, [{
|
|
75390
75860
|
trackId,
|
|
75391
75861
|
retry: false
|
|
75392
|
-
}]
|
|
75862
|
+
}]);
|
|
75393
75863
|
const dispatcher = this.eventMediaDispatcher;
|
|
75394
75864
|
if (!dispatcher) return;
|
|
75395
75865
|
if (this.captureScheduler.isKeyActive(captureCoalesceKey(deviceId, "thumbnail", trackId))) {
|
|
@@ -75540,7 +76010,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75540
76010
|
}
|
|
75541
76011
|
return retries;
|
|
75542
76012
|
}
|
|
75543
|
-
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight) {
|
|
76013
|
+
buildSnapshotTargets(deviceId, tracked, timestamp, media, frameWidth, frameHeight, frameHandle) {
|
|
75544
76014
|
const targets = [];
|
|
75545
76015
|
for (const t of tracked) {
|
|
75546
76016
|
if (t.matchedThisFrame === false) continue;
|
|
@@ -75626,6 +76096,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
75626
76096
|
state: this.residents.keyFrameCapture(t.trackId),
|
|
75627
76097
|
now: timestamp
|
|
75628
76098
|
}).kind;
|
|
76099
|
+
if (frameHandle !== void 0 && plausibleBox) {
|
|
76100
|
+
const decision = {
|
|
76101
|
+
deviceId,
|
|
76102
|
+
trackId: t.trackId,
|
|
76103
|
+
timestamp,
|
|
76104
|
+
frameHandle,
|
|
76105
|
+
bbox: {
|
|
76106
|
+
x: t.bbox.x,
|
|
76107
|
+
y: t.bbox.y,
|
|
76108
|
+
w: t.bbox.w,
|
|
76109
|
+
h: t.bbox.h
|
|
76110
|
+
},
|
|
76111
|
+
frameWidth,
|
|
76112
|
+
frameHeight
|
|
76113
|
+
};
|
|
76114
|
+
if (isNewBest) this.issueParkOrders(decision, ["keyFrame", "thumbnail"], false);
|
|
76115
|
+
else {
|
|
76116
|
+
const retryKinds = [];
|
|
76117
|
+
if (keyFrame === "retry") retryKinds.push("keyFrame");
|
|
76118
|
+
if (bestThumbnail) retryKinds.push("thumbnail");
|
|
76119
|
+
if (retryKinds.length > 0) this.issueParkOrders(decision, retryKinds, true);
|
|
76120
|
+
}
|
|
76121
|
+
}
|
|
75629
76122
|
const suppressedBy = (() => {
|
|
75630
76123
|
if (skipParkedThumb) return "parked-thumbnail";
|
|
75631
76124
|
if (!isNewBest || bestThumbnail) return void 0;
|
|
@@ -77041,6 +77534,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
77041
77534
|
}
|
|
77042
77535
|
});
|
|
77043
77536
|
});
|
|
77537
|
+
p.setZoneRefusalObserver((observation) => {
|
|
77538
|
+
this.ctx.logger.info("detection refused inside a watched zone — not notified", {
|
|
77539
|
+
tags: { deviceId },
|
|
77540
|
+
meta: {
|
|
77541
|
+
source,
|
|
77542
|
+
...observation
|
|
77543
|
+
}
|
|
77544
|
+
});
|
|
77545
|
+
});
|
|
77044
77546
|
p.setSpawnBesideCoastingObserver((record) => {
|
|
77045
77547
|
this.ctx.logger.info("tracker: new id minted beside a live coasting track", {
|
|
77046
77548
|
tags: { deviceId },
|