@camstack/addon-post-analysis 1.2.199 → 1.2.201

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.
@@ -22106,8 +22106,25 @@ var occupancyRecheckFramesField = {
22106
22106
  * (analyzer attaches detected `regions[]`; onboard does not — the
22107
22107
  * camera typically only reports a binary signal plus an optional
22108
22108
  * channel/AI class which lives in dedicated event channels).
22109
- */
22110
- var MotionSourceEnum = _enum(["onboard", "analyzer"]);
22109
+ *
22110
+ * - `onboard` — the camera's firmware said something moved.
22111
+ * - `analyzer` — this runner's frame-diff said so, and attaches `regions[]`.
22112
+ * - `device-activity` — the DEVICE said it is doing its job: the
22113
+ * `recording-signal` LEVEL the same device raises for the recorder
22114
+ * ([D380](../../../../docs/decisions/adr-0380-a-device-decided-recording-is-a-mode-with-no-schedule-seeded-once.md)),
22115
+ * republished as a motion source. It attaches **nothing** — no regions, no
22116
+ * class: the only fact it carries is that the device is active, and a robot
22117
+ * vacuum that is itself the moving object has no region worth sending. It is
22118
+ * a LEVEL, so unlike `onboard` it has a real falling edge, and unlike
22119
+ * `analyzer` it must not open the frame-diff side-channel — the runner's
22120
+ * `handleOnboardMotionAnalyzer` gate is `source === 'onboard'` and stays that
22121
+ * way ([D392](../../../../docs/decisions/adr-0392-a-device-that-says-it-is-working-is-a-motion-source-of-its-own.md)).
22122
+ */
22123
+ var MotionSourceEnum = _enum([
22124
+ "onboard",
22125
+ "analyzer",
22126
+ "device-activity"
22127
+ ]);
22111
22128
  /**
22112
22129
  * List of motion sources active on a camera. Empty array is valid:
22113
22130
  * "no source" — happens for battery cams without firmware motion when
@@ -22137,8 +22137,25 @@ var occupancyRecheckFramesField = {
22137
22137
  * (analyzer attaches detected `regions[]`; onboard does not — the
22138
22138
  * camera typically only reports a binary signal plus an optional
22139
22139
  * channel/AI class which lives in dedicated event channels).
22140
- */
22141
- var MotionSourceEnum = _enum(["onboard", "analyzer"]);
22140
+ *
22141
+ * - `onboard` — the camera's firmware said something moved.
22142
+ * - `analyzer` — this runner's frame-diff said so, and attaches `regions[]`.
22143
+ * - `device-activity` — the DEVICE said it is doing its job: the
22144
+ * `recording-signal` LEVEL the same device raises for the recorder
22145
+ * ([D380](../../../../docs/decisions/adr-0380-a-device-decided-recording-is-a-mode-with-no-schedule-seeded-once.md)),
22146
+ * republished as a motion source. It attaches **nothing** — no regions, no
22147
+ * class: the only fact it carries is that the device is active, and a robot
22148
+ * vacuum that is itself the moving object has no region worth sending. It is
22149
+ * a LEVEL, so unlike `onboard` it has a real falling edge, and unlike
22150
+ * `analyzer` it must not open the frame-diff side-channel — the runner's
22151
+ * `handleOnboardMotionAnalyzer` gate is `source === 'onboard'` and stays that
22152
+ * way ([D392](../../../../docs/decisions/adr-0392-a-device-that-says-it-is-working-is-a-motion-source-of-its-own.md)).
22153
+ */
22154
+ var MotionSourceEnum = _enum([
22155
+ "onboard",
22156
+ "analyzer",
22157
+ "device-activity"
22158
+ ]);
22142
22159
  /**
22143
22160
  * List of motion sources active on a camera. Empty array is valid:
22144
22161
  * "no source" — happens for battery cams without firmware motion when
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-fmXXxa9D.js");
5
+ const require_dist = require("../dist-DbgfbjZC.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs);
8
8
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { F as PoolMemoryWatchdog, Pt as BaseAddon, Tt as resolvePoolMemoryPolicy, at as embeddingEncoderCapability, dt as hfModelUrl, vt as parseProcStatus } from "../dist-BrszC4uJ.mjs";
1
+ import { F as PoolMemoryWatchdog, Pt as BaseAddon, Tt as resolvePoolMemoryPolicy, at as embeddingEncoderCapability, dt as hfModelUrl, vt as parseProcStatus } from "../dist-1xB8qj2h.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.83",
6
+ version: "1.2.84",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_pipeline_analytics_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.163",
21
+ version: "1.2.164",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_pipeline_analytics_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.131",
36
+ version: "1.2.132",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_pipeline_analytics_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.163",
39
+ version: "1.2.164",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.2.83",
48
+ version: "1.2.84",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.131",
84
+ version: "1.2.132",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_dist = require("../dist-fmXXxa9D.js");
5
+ const require_dist = require("../dist-DbgfbjZC.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_dist.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -34061,6 +34061,22 @@ function pairRiders(detections) {
34061
34061
  }
34062
34062
  return pairs;
34063
34063
  }
34064
+ /** Per-(stage, gate) counter + sampling decision. One instance per tracker, so
34065
+ * the counts are per camera and per detection source. */
34066
+ var LooseBindSampler = class {
34067
+ counts = /* @__PURE__ */ new Map();
34068
+ /** Increment the (stage, gate) counter and answer with the running total when
34069
+ * this decision should be reported, or `undefined` when it is sampled out. */
34070
+ next(stage, gate) {
34071
+ const key = `${stage}|${gate}`;
34072
+ const count = (this.counts.get(key) ?? 0) + 1;
34073
+ this.counts.set(key, count);
34074
+ if (count === 1 || count % 50 === 0) return count;
34075
+ }
34076
+ reset() {
34077
+ this.counts.clear();
34078
+ }
34079
+ };
34064
34080
  //#endregion
34065
34081
  //#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
34066
34082
  /**
@@ -34118,22 +34134,6 @@ function growthAdjacentMatch(track, det, ctx) {
34118
34134
  const dc = bboxCentroid(det);
34119
34135
  return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.hypot(track.w, track.h);
34120
34136
  }
34121
- /** Per-(stage, gate) counter + sampling decision. One instance per tracker, so
34122
- * the counts are per camera and per detection source. */
34123
- var LooseBindSampler = class {
34124
- counts = /* @__PURE__ */ new Map();
34125
- /** Increment the (stage, gate) counter and answer with the running total when
34126
- * this decision should be reported, or `undefined` when it is sampled out. */
34127
- next(stage, gate) {
34128
- const key = `${stage}|${gate}`;
34129
- const count = (this.counts.get(key) ?? 0) + 1;
34130
- this.counts.set(key, count);
34131
- if (count === 1 || count % 50 === 0) return count;
34132
- }
34133
- reset() {
34134
- this.counts.clear();
34135
- }
34136
- };
34137
34137
  /** Per-reason counter + sampling decision. One instance per tracker, so the
34138
34138
  * counts are per camera and per detection source. */
34139
34139
  var SpawnRefusalSampler = class {
@@ -34277,7 +34277,35 @@ var SortTracker = class SortTracker {
34277
34277
  againstTrackClass: extra.against.class
34278
34278
  } : {},
34279
34279
  refusalCount,
34280
- sampledEvery: 10
34280
+ sampledEvery: 10,
34281
+ ...extra.droppedTrackId !== void 0 ? { droppedTrackId: extra.droppedTrackId } : {},
34282
+ ...extra.trackLifeMs !== void 0 ? { trackLifeMs: extra.trackLifeMs } : {}
34283
+ });
34284
+ }
34285
+ /**
34286
+ * Report a TRACK that ends having emitted nothing — the last silent branch
34287
+ * on the detection→position path (D391).
34288
+ *
34289
+ * The subject is the track, not one box, so the record carries the track's
34290
+ * own last class/score/bbox: those ARE the detection that fed it last, and
34291
+ * they are what an operator matches against the recovery line that produced
34292
+ * it. Reported once (`lossReported`) and only where the track can no longer
34293
+ * come back — a graveyarded track may still resurrect, and a line about work
34294
+ * that was not lost is worse than no line.
34295
+ */
34296
+ reportTrackLoss(track) {
34297
+ if (track.emitted || track.lossReported) return;
34298
+ track.lossReported = true;
34299
+ this.reportRefusal("unconfirmed-spawn-lost", {
34300
+ class: track.class,
34301
+ originalClass: track.originalClass,
34302
+ score: track.score,
34303
+ bbox: track.bbox
34304
+ }, {
34305
+ threshold: this.config.minHits,
34306
+ measured: track.hits,
34307
+ droppedTrackId: track.id,
34308
+ trackLifeMs: track.lastSeen - track.firstSeen
34281
34309
  });
34282
34310
  }
34283
34311
  /** Emit one salvage decision, subject to {@link LOOSE_BIND_LOG_SAMPLE_EVERY}.
@@ -34472,7 +34500,22 @@ var SortTracker = class SortTracker {
34472
34500
  }
34473
34501
  const streak = (this.dupStreaks.get(key) ?? 0) + 1;
34474
34502
  if (streak >= this.config.dedupMergeFrames) {
34475
- dropIds.add(SortTracker.duplicateLoser(a, b).id);
34503
+ const loser = SortTracker.duplicateLoser(a, b);
34504
+ const winner = loser === a ? b : a;
34505
+ dropIds.add(loser.id);
34506
+ this.reportRefusal("merged-duplicate", {
34507
+ class: loser.class,
34508
+ originalClass: loser.originalClass,
34509
+ score: loser.score,
34510
+ bbox: loser.bbox
34511
+ }, {
34512
+ threshold: this.config.dedupMergeIou,
34513
+ measured: iou$3(a.bbox, b.bbox),
34514
+ against: winner,
34515
+ droppedTrackId: loser.id,
34516
+ trackLifeMs: loser.lastSeen - loser.firstSeen
34517
+ });
34518
+ loser.lossReported = true;
34476
34519
  this.dupStreaks.delete(key);
34477
34520
  } else this.dupStreaks.set(key, streak);
34478
34521
  }
@@ -34654,6 +34697,7 @@ var SortTracker = class SortTracker {
34654
34697
  track.lost = true;
34655
34698
  track.lostAt = timestamp;
34656
34699
  track.resurrectable = false;
34700
+ this.reportTrackLoss(track);
34657
34701
  this.lostTracks.push(track);
34658
34702
  } else alive.push(track);
34659
34703
  this.tracks = alive;
@@ -34665,11 +34709,15 @@ var SortTracker = class SortTracker {
34665
34709
  track.lost = true;
34666
34710
  track.lostAt = track.lastSeen;
34667
34711
  track.resurrectable = false;
34712
+ this.reportTrackLoss(track);
34668
34713
  this.lostTracks.push(track);
34669
34714
  } else alive.push(track);
34670
34715
  this.tracks = alive;
34671
34716
  }
34672
- this.lostTracks = this.lostTracks.filter((t) => timestamp - t.lostAt <= this.config.resurrectionWindowMs);
34717
+ const stillResurrectable = [];
34718
+ for (const t of this.lostTracks) if (timestamp - t.lostAt <= this.config.resurrectionWindowMs) stillResurrectable.push(t);
34719
+ else this.reportTrackLoss(t);
34720
+ this.lostTracks = stillResurrectable;
34673
34721
  const used = /* @__PURE__ */ new Set();
34674
34722
  const matchedTracks = /* @__PURE__ */ new Set();
34675
34723
  const matched = /* @__PURE__ */ new Map();
@@ -34867,12 +34915,15 @@ var SortTracker = class SortTracker {
34867
34915
  lost: false,
34868
34916
  lostAt: 0,
34869
34917
  resurrectable: true,
34870
- classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]])
34918
+ classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]]),
34919
+ emitted: false,
34920
+ lossReported: false
34871
34921
  });
34872
34922
  }
34873
34923
  this.tracks = surviving;
34874
34924
  this.resolveDuplicates();
34875
34925
  return this.tracks.filter((t) => this.isConfirmedForEmit(t)).map((t) => {
34926
+ t.emitted = true;
34876
34927
  const scene = resurrectedScene.get(t);
34877
34928
  return {
34878
34929
  class: this.resolveReportedClass(t),
@@ -35240,6 +35291,10 @@ var FrameProcessor = class {
35240
35291
  */
35241
35292
  reportedRefusalByTrack = /* @__PURE__ */ new Map();
35242
35293
  zoneEngine = new ZoneEngine();
35294
+ /** Sink + sampler for the drops this stage owns (the detection-stage zone
35295
+ * filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
35296
+ spawnRefusalObserver = null;
35297
+ zoneRefusalSampler = new SpawnRefusalSampler();
35243
35298
  /** Optional stationary-object gate (parked-object suppression). Null until
35244
35299
  * the addon wires it via {@link setStationaryGate}. */
35245
35300
  stationaryGate = null;
@@ -35334,6 +35389,7 @@ var FrameProcessor = class {
35334
35389
  */
35335
35390
  setSpawnRefusalObserver(observer) {
35336
35391
  this.tracker.setSpawnRefusalObserver(observer);
35392
+ this.spawnRefusalObserver = observer;
35337
35393
  }
35338
35394
  setBindObserver(observer) {
35339
35395
  this.tracker.setBindObserver(observer);
@@ -35343,6 +35399,32 @@ var FrameProcessor = class {
35343
35399
  dropTrack(trackId) {
35344
35400
  this.tracker.dropTrack(trackId);
35345
35401
  }
35402
+ /**
35403
+ * Say which detections a detection-stage zone rule excluded (D391).
35404
+ *
35405
+ * `filterDetections` has always ANSWERED this — it returns `excluded` — and
35406
+ * this stage dropped the answer on the floor, so a camera whose subject the
35407
+ * rules exclude was indistinguishable in the log from a camera whose tracker
35408
+ * ate it. Sampled like every other refusal: an excluded box is excluded on
35409
+ * every frame of a passage.
35410
+ */
35411
+ reportZoneFiltered(excluded) {
35412
+ const observer = this.spawnRefusalObserver;
35413
+ if (!observer || excluded.length === 0) return;
35414
+ for (const fd of excluded) {
35415
+ const refusalCount = this.zoneRefusalSampler.next("zone-filtered");
35416
+ if (refusalCount === void 0) continue;
35417
+ observer({
35418
+ reason: "zone-filtered",
35419
+ detClass: fd.detection.class,
35420
+ detScore: fd.detection.score,
35421
+ bbox: fd.detection.bbox,
35422
+ ...fd.detection.nativeRecovery !== void 0 ? { nativeRecoveryScore: fd.detection.nativeRecovery.score } : {},
35423
+ refusalCount,
35424
+ sampledEvery: 10
35425
+ });
35426
+ }
35427
+ }
35346
35428
  process(input) {
35347
35429
  const { timestamp, frame } = input;
35348
35430
  const frameWidth = frame.width;
@@ -35428,7 +35510,8 @@ var FrameProcessor = class {
35428
35510
  }
35429
35511
  });
35430
35512
  }
35431
- const { passed } = this.zoneEngine.filterDetections(flatDetections, this.zones, this.detectionRules, frameWidth, frameHeight, (fd) => fd.detection.class);
35513
+ const { passed, excluded } = this.zoneEngine.filterDetections(flatDetections, this.zones, this.detectionRules, frameWidth, frameHeight, (fd) => fd.detection.class);
35514
+ this.reportZoneFiltered(excluded);
35432
35515
  let filteredDetections = passed.map((fd) => fd.detection);
35433
35516
  const riderPairs = pairRiders(filteredDetections.map((d, i) => ({
35434
35517
  id: String(i),
@@ -74338,7 +74421,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
74338
74421
  });
74339
74422
  });
74340
74423
  p.setSpawnRefusalObserver((record) => {
74341
- this.ctx.logger.info("tracker: detection dropped — no track was born from it", {
74424
+ this.ctx.logger.info("tracker: detection dropped — nothing downstream received it", {
74342
74425
  tags: { deviceId },
74343
74426
  meta: {
74344
74427
  source,
@@ -1,4 +1,4 @@
1
- import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-BrszC4uJ.mjs";
1
+ import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-1xB8qj2h.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";
@@ -34036,6 +34036,22 @@ function pairRiders(detections) {
34036
34036
  }
34037
34037
  return pairs;
34038
34038
  }
34039
+ /** Per-(stage, gate) counter + sampling decision. One instance per tracker, so
34040
+ * the counts are per camera and per detection source. */
34041
+ var LooseBindSampler = class {
34042
+ counts = /* @__PURE__ */ new Map();
34043
+ /** Increment the (stage, gate) counter and answer with the running total when
34044
+ * this decision should be reported, or `undefined` when it is sampled out. */
34045
+ next(stage, gate) {
34046
+ const key = `${stage}|${gate}`;
34047
+ const count = (this.counts.get(key) ?? 0) + 1;
34048
+ this.counts.set(key, count);
34049
+ if (count === 1 || count % 50 === 0) return count;
34050
+ }
34051
+ reset() {
34052
+ this.counts.clear();
34053
+ }
34054
+ };
34039
34055
  //#endregion
34040
34056
  //#region src/pipeline-analytics/pipeline/tracker/rescue-growth.ts
34041
34057
  /**
@@ -34093,22 +34109,6 @@ function growthAdjacentMatch(track, det, ctx) {
34093
34109
  const dc = bboxCentroid(det);
34094
34110
  return Math.hypot(tc.x - dc.x, tc.y - dc.y) <= GROWTH_CENTROID_FACTOR * Math.hypot(track.w, track.h);
34095
34111
  }
34096
- /** Per-(stage, gate) counter + sampling decision. One instance per tracker, so
34097
- * the counts are per camera and per detection source. */
34098
- var LooseBindSampler = class {
34099
- counts = /* @__PURE__ */ new Map();
34100
- /** Increment the (stage, gate) counter and answer with the running total when
34101
- * this decision should be reported, or `undefined` when it is sampled out. */
34102
- next(stage, gate) {
34103
- const key = `${stage}|${gate}`;
34104
- const count = (this.counts.get(key) ?? 0) + 1;
34105
- this.counts.set(key, count);
34106
- if (count === 1 || count % 50 === 0) return count;
34107
- }
34108
- reset() {
34109
- this.counts.clear();
34110
- }
34111
- };
34112
34112
  /** Per-reason counter + sampling decision. One instance per tracker, so the
34113
34113
  * counts are per camera and per detection source. */
34114
34114
  var SpawnRefusalSampler = class {
@@ -34252,7 +34252,35 @@ var SortTracker = class SortTracker {
34252
34252
  againstTrackClass: extra.against.class
34253
34253
  } : {},
34254
34254
  refusalCount,
34255
- sampledEvery: 10
34255
+ sampledEvery: 10,
34256
+ ...extra.droppedTrackId !== void 0 ? { droppedTrackId: extra.droppedTrackId } : {},
34257
+ ...extra.trackLifeMs !== void 0 ? { trackLifeMs: extra.trackLifeMs } : {}
34258
+ });
34259
+ }
34260
+ /**
34261
+ * Report a TRACK that ends having emitted nothing — the last silent branch
34262
+ * on the detection→position path (D391).
34263
+ *
34264
+ * The subject is the track, not one box, so the record carries the track's
34265
+ * own last class/score/bbox: those ARE the detection that fed it last, and
34266
+ * they are what an operator matches against the recovery line that produced
34267
+ * it. Reported once (`lossReported`) and only where the track can no longer
34268
+ * come back — a graveyarded track may still resurrect, and a line about work
34269
+ * that was not lost is worse than no line.
34270
+ */
34271
+ reportTrackLoss(track) {
34272
+ if (track.emitted || track.lossReported) return;
34273
+ track.lossReported = true;
34274
+ this.reportRefusal("unconfirmed-spawn-lost", {
34275
+ class: track.class,
34276
+ originalClass: track.originalClass,
34277
+ score: track.score,
34278
+ bbox: track.bbox
34279
+ }, {
34280
+ threshold: this.config.minHits,
34281
+ measured: track.hits,
34282
+ droppedTrackId: track.id,
34283
+ trackLifeMs: track.lastSeen - track.firstSeen
34256
34284
  });
34257
34285
  }
34258
34286
  /** Emit one salvage decision, subject to {@link LOOSE_BIND_LOG_SAMPLE_EVERY}.
@@ -34447,7 +34475,22 @@ var SortTracker = class SortTracker {
34447
34475
  }
34448
34476
  const streak = (this.dupStreaks.get(key) ?? 0) + 1;
34449
34477
  if (streak >= this.config.dedupMergeFrames) {
34450
- dropIds.add(SortTracker.duplicateLoser(a, b).id);
34478
+ const loser = SortTracker.duplicateLoser(a, b);
34479
+ const winner = loser === a ? b : a;
34480
+ dropIds.add(loser.id);
34481
+ this.reportRefusal("merged-duplicate", {
34482
+ class: loser.class,
34483
+ originalClass: loser.originalClass,
34484
+ score: loser.score,
34485
+ bbox: loser.bbox
34486
+ }, {
34487
+ threshold: this.config.dedupMergeIou,
34488
+ measured: iou$3(a.bbox, b.bbox),
34489
+ against: winner,
34490
+ droppedTrackId: loser.id,
34491
+ trackLifeMs: loser.lastSeen - loser.firstSeen
34492
+ });
34493
+ loser.lossReported = true;
34451
34494
  this.dupStreaks.delete(key);
34452
34495
  } else this.dupStreaks.set(key, streak);
34453
34496
  }
@@ -34629,6 +34672,7 @@ var SortTracker = class SortTracker {
34629
34672
  track.lost = true;
34630
34673
  track.lostAt = timestamp;
34631
34674
  track.resurrectable = false;
34675
+ this.reportTrackLoss(track);
34632
34676
  this.lostTracks.push(track);
34633
34677
  } else alive.push(track);
34634
34678
  this.tracks = alive;
@@ -34640,11 +34684,15 @@ var SortTracker = class SortTracker {
34640
34684
  track.lost = true;
34641
34685
  track.lostAt = track.lastSeen;
34642
34686
  track.resurrectable = false;
34687
+ this.reportTrackLoss(track);
34643
34688
  this.lostTracks.push(track);
34644
34689
  } else alive.push(track);
34645
34690
  this.tracks = alive;
34646
34691
  }
34647
- this.lostTracks = this.lostTracks.filter((t) => timestamp - t.lostAt <= this.config.resurrectionWindowMs);
34692
+ const stillResurrectable = [];
34693
+ for (const t of this.lostTracks) if (timestamp - t.lostAt <= this.config.resurrectionWindowMs) stillResurrectable.push(t);
34694
+ else this.reportTrackLoss(t);
34695
+ this.lostTracks = stillResurrectable;
34648
34696
  const used = /* @__PURE__ */ new Set();
34649
34697
  const matchedTracks = /* @__PURE__ */ new Set();
34650
34698
  const matched = /* @__PURE__ */ new Map();
@@ -34842,12 +34890,15 @@ var SortTracker = class SortTracker {
34842
34890
  lost: false,
34843
34891
  lostAt: 0,
34844
34892
  resurrectable: true,
34845
- classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]])
34893
+ classVotes: new Map([[det.class, det.score > 0 ? det.score : Number.EPSILON]]),
34894
+ emitted: false,
34895
+ lossReported: false
34846
34896
  });
34847
34897
  }
34848
34898
  this.tracks = surviving;
34849
34899
  this.resolveDuplicates();
34850
34900
  return this.tracks.filter((t) => this.isConfirmedForEmit(t)).map((t) => {
34901
+ t.emitted = true;
34851
34902
  const scene = resurrectedScene.get(t);
34852
34903
  return {
34853
34904
  class: this.resolveReportedClass(t),
@@ -35215,6 +35266,10 @@ var FrameProcessor = class {
35215
35266
  */
35216
35267
  reportedRefusalByTrack = /* @__PURE__ */ new Map();
35217
35268
  zoneEngine = new ZoneEngine();
35269
+ /** Sink + sampler for the drops this stage owns (the detection-stage zone
35270
+ * filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
35271
+ spawnRefusalObserver = null;
35272
+ zoneRefusalSampler = new SpawnRefusalSampler();
35218
35273
  /** Optional stationary-object gate (parked-object suppression). Null until
35219
35274
  * the addon wires it via {@link setStationaryGate}. */
35220
35275
  stationaryGate = null;
@@ -35309,6 +35364,7 @@ var FrameProcessor = class {
35309
35364
  */
35310
35365
  setSpawnRefusalObserver(observer) {
35311
35366
  this.tracker.setSpawnRefusalObserver(observer);
35367
+ this.spawnRefusalObserver = observer;
35312
35368
  }
35313
35369
  setBindObserver(observer) {
35314
35370
  this.tracker.setBindObserver(observer);
@@ -35318,6 +35374,32 @@ var FrameProcessor = class {
35318
35374
  dropTrack(trackId) {
35319
35375
  this.tracker.dropTrack(trackId);
35320
35376
  }
35377
+ /**
35378
+ * Say which detections a detection-stage zone rule excluded (D391).
35379
+ *
35380
+ * `filterDetections` has always ANSWERED this — it returns `excluded` — and
35381
+ * this stage dropped the answer on the floor, so a camera whose subject the
35382
+ * rules exclude was indistinguishable in the log from a camera whose tracker
35383
+ * ate it. Sampled like every other refusal: an excluded box is excluded on
35384
+ * every frame of a passage.
35385
+ */
35386
+ reportZoneFiltered(excluded) {
35387
+ const observer = this.spawnRefusalObserver;
35388
+ if (!observer || excluded.length === 0) return;
35389
+ for (const fd of excluded) {
35390
+ const refusalCount = this.zoneRefusalSampler.next("zone-filtered");
35391
+ if (refusalCount === void 0) continue;
35392
+ observer({
35393
+ reason: "zone-filtered",
35394
+ detClass: fd.detection.class,
35395
+ detScore: fd.detection.score,
35396
+ bbox: fd.detection.bbox,
35397
+ ...fd.detection.nativeRecovery !== void 0 ? { nativeRecoveryScore: fd.detection.nativeRecovery.score } : {},
35398
+ refusalCount,
35399
+ sampledEvery: 10
35400
+ });
35401
+ }
35402
+ }
35321
35403
  process(input) {
35322
35404
  const { timestamp, frame } = input;
35323
35405
  const frameWidth = frame.width;
@@ -35403,7 +35485,8 @@ var FrameProcessor = class {
35403
35485
  }
35404
35486
  });
35405
35487
  }
35406
- const { passed } = this.zoneEngine.filterDetections(flatDetections, this.zones, this.detectionRules, frameWidth, frameHeight, (fd) => fd.detection.class);
35488
+ const { passed, excluded } = this.zoneEngine.filterDetections(flatDetections, this.zones, this.detectionRules, frameWidth, frameHeight, (fd) => fd.detection.class);
35489
+ this.reportZoneFiltered(excluded);
35407
35490
  let filteredDetections = passed.map((fd) => fd.detection);
35408
35491
  const riderPairs = pairRiders(filteredDetections.map((d, i) => ({
35409
35492
  id: String(i),
@@ -74264,7 +74347,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
74264
74347
  });
74265
74348
  });
74266
74349
  p.setSpawnRefusalObserver((record) => {
74267
- this.ctx.logger.info("tracker: detection dropped — no track was born from it", {
74350
+ this.ctx.logger.info("tracker: detection dropped — nothing downstream received it", {
74268
74351
  tags: { deviceId },
74269
74352
  meta: {
74270
74353
  source,
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CTKbm1Jm.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DwFi68p5.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-post-analysis",
3
- "version": "1.2.199",
3
+ "version": "1.2.201",
4
4
  "description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
5
5
  "keywords": [
6
6
  "camstack",