@camstack/addon-pipeline 1.2.195 → 1.2.196

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.
@@ -5156,12 +5156,6 @@ var PipelineRunner = class {
5156
5156
  return null;
5157
5157
  }
5158
5158
  };
5159
- /**
5160
- * How much of a region must lie OUTSIDE every full-frame detection for the
5161
- * pass to run on it. A person's own blob (dilated, with shadow) is covered
5162
- * 70–90% by the person box; a person-and-dog blob is covered ~55% by it.
5163
- */
5164
- var RECOVERY_MIN_UNCOVERED_FRACTION = .35;
5165
5159
  function area(r) {
5166
5160
  return Math.max(0, r.w) * Math.max(0, r.h);
5167
5161
  }
@@ -5209,25 +5203,53 @@ function cropsForRegion(region, native) {
5209
5203
  return [squareAround(alongX ? centreX - quarter : centreX, alongX ? centreY : centreY - quarter, side, native), squareAround(alongX ? centreX + quarter : centreX, alongX ? centreY : centreY + quarter, side, native)];
5210
5204
  }
5211
5205
  /**
5212
- * The crops the second pass runs on this frame — possibly none.
5206
+ * The ONE place the two region gates are decided. `planRecoveryCrops` filters
5207
+ * on it and {@link countRecoverySkips} counts it, so the report can never
5208
+ * describe a gate the planner does not actually apply.
5209
+ */
5210
+ function classifyRegion(region, uncovered) {
5211
+ if (area(region) > .25) return "too-large";
5212
+ if (uncovered < .35) return "covered";
5213
+ return "eligible";
5214
+ }
5215
+ /**
5216
+ * WHY A FRAME PLANNED NO CROP, AS A NUMBER (D378).
5213
5217
  *
5214
- * A region qualifies when it is small (`RECOVERY_MAX_REGION_FRAME_FRACTION`)
5215
- * and mostly uncovered by the full-frame boxes
5216
- * (`RECOVERY_MIN_UNCOVERED_FRACTION`). Qualifying regions are served largest
5217
- * uncovered area first under `RECOVERY_MAX_CROPS_PER_FRAME`; a crop whose
5218
- * centre already lies inside a planned crop is folded into it. A frame that
5219
- * cannot hold the smallest crop yields nothing: it is already seen at scale.
5218
+ * The pass report counts the passes that RAN, so "the second pass stops running
5219
+ * when a person is beside the dog" — a plausible reading of
5220
+ * {@link RECOVERY_MAX_REGION_FRAME_FRACTION} and
5221
+ * {@link RECOVERY_MIN_UNCOVERED_FRACTION}, and a live hypothesis on camera 615
5222
+ * — could not be confirmed or killed from the logs at all. This classifies the
5223
+ * regions a frame offered, against the same predicate the planner filters on.
5220
5224
  */
5225
+ function countRecoverySkips(input) {
5226
+ let tooLarge = 0;
5227
+ let covered = 0;
5228
+ let eligible = 0;
5229
+ for (const region of input.regions) switch (classifyRegion(region, uncoveredFraction(region, input.detections))) {
5230
+ case "too-large":
5231
+ tooLarge += 1;
5232
+ break;
5233
+ case "covered":
5234
+ covered += 1;
5235
+ break;
5236
+ case "eligible":
5237
+ eligible += 1;
5238
+ break;
5239
+ }
5240
+ return {
5241
+ tooLarge,
5242
+ covered,
5243
+ eligible
5244
+ };
5245
+ }
5221
5246
  function planRecoveryCrops(input) {
5222
5247
  const { native } = input;
5223
5248
  if (Math.min(native.width, native.height) < 640) return [];
5224
5249
  const ranked = input.regions.map((region) => ({
5225
5250
  region,
5226
5251
  uncovered: uncoveredFraction(region, input.detections)
5227
- })).filter(({ region, uncovered }) => {
5228
- if (area(region) > .25) return false;
5229
- return uncovered >= RECOVERY_MIN_UNCOVERED_FRACTION;
5230
- }).toSorted((a, b) => area(b.region) * b.uncovered - area(a.region) * a.uncovered);
5252
+ })).filter(({ region, uncovered }) => classifyRegion(region, uncovered) === "eligible").toSorted((a, b) => area(b.region) * b.uncovered - area(a.region) * a.uncovered);
5231
5253
  const planned = [];
5232
5254
  for (const { region } of ranked) for (const crop of cropsForRegion(region, native)) {
5233
5255
  if (planned.length >= 2) return planned;
@@ -5258,6 +5280,11 @@ function remapCropDetections(detections, crop, view, result, native) {
5258
5280
  if (x <= 2 || y <= 2 || x + width >= view.width - 2 || y + height >= view.height - 2) continue;
5259
5281
  out.push({
5260
5282
  ...detection,
5283
+ nativeRecovery: {
5284
+ score: detection.score,
5285
+ cropSidePx: crop.side,
5286
+ viewPx: view.width
5287
+ },
5261
5288
  bbox: {
5262
5289
  x: (crop.left + x * viewToNative) * nativeToResultX,
5263
5290
  y: (crop.top + y * viewToNative) * nativeToResultY,
@@ -5922,6 +5949,15 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
5922
5949
  /** Per-camera counters since the last recovery report line. */
5923
5950
  recoveryStats = /* @__PURE__ */ new Map();
5924
5951
  /**
5952
+ * Per-camera counters for the frames on which the pass did NOT run, since the
5953
+ * last report line (D378). "The second pass stops running when a person is
5954
+ * beside the dog" was a live hypothesis on camera 615 and the logs could not
5955
+ * answer it: a report that counts only the passes that ran cannot say why the
5956
+ * others did not. `rateLimited` is the cadence cap, `noMotion` an empty or
5957
+ * stale motion mirror, and `tooLarge` / `covered` the two region gates.
5958
+ */
5959
+ recoverySkips = /* @__PURE__ */ new Map();
5960
+ /**
5925
5961
  * Gates the root-plane `package-detection` step to motion-settle edges +
5926
5962
  * a low-cadence safety poll instead of every frame (packages are
5927
5963
  * stationary — per-frame parcel inference is wasted cost). Consulted in
@@ -7645,6 +7681,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
7645
7681
  this.motionRegions.forget(deviceId);
7646
7682
  this.lastRecoveryAt.delete(deviceId);
7647
7683
  this.recoveryStats.delete(deviceId);
7684
+ this.recoverySkips.delete(deviceId);
7648
7685
  const warnPrefix = `${deviceId}:`;
7649
7686
  for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
7650
7687
  this.attached.delete(deviceId);
@@ -8413,9 +8450,15 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8413
8450
  const api = this.ctx?.api;
8414
8451
  if (!api || !frame.frameRef) return result;
8415
8452
  const now = Date.now();
8416
- if (now - (this.lastRecoveryAt.get(deviceId) ?? 0) < 500) return result;
8453
+ if (now - (this.lastRecoveryAt.get(deviceId) ?? 0) < 500) {
8454
+ this.addRecoverySkips(deviceId, { rateLimited: 1 });
8455
+ return result;
8456
+ }
8417
8457
  const regions = this.motionRegions.fresh(deviceId, now);
8418
- if (regions.length === 0) return result;
8458
+ if (regions.length === 0) {
8459
+ this.addRecoverySkips(deviceId, { noMotion: 1 });
8460
+ return result;
8461
+ }
8419
8462
  const step = selectRecoveryStep(steps);
8420
8463
  if (step === null) return result;
8421
8464
  if (result.width <= 0 || result.height <= 0) return result;
@@ -8423,7 +8466,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8423
8466
  width: frame.frameRef.width,
8424
8467
  height: frame.frameRef.height
8425
8468
  };
8426
- const crops = planRecoveryCrops({
8469
+ const planInput = {
8427
8470
  regions,
8428
8471
  detections: result.detections.filter((d) => d.kind === "first-level").map((d) => ({
8429
8472
  x: d.bbox.x / result.width,
@@ -8432,8 +8475,16 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8432
8475
  h: d.bbox.height / result.height
8433
8476
  })),
8434
8477
  native
8435
- });
8436
- if (crops.length === 0) return result;
8478
+ };
8479
+ const crops = planRecoveryCrops(planInput);
8480
+ if (crops.length === 0) {
8481
+ const skipped = countRecoverySkips(planInput);
8482
+ this.addRecoverySkips(deviceId, {
8483
+ tooLarge: skipped.tooLarge,
8484
+ covered: skipped.covered
8485
+ });
8486
+ return result;
8487
+ }
8437
8488
  this.lastRecoveryAt.set(deviceId, now);
8438
8489
  const log = this.ctx.logger.withTags({ deviceId });
8439
8490
  const started = Date.now();
@@ -8473,7 +8524,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8473
8524
  recovered: added,
8474
8525
  ms: Date.now() - started,
8475
8526
  now,
8476
- classes: fused.slice(result.detections.length).map((d) => d.macroClass)
8527
+ classes: fused.slice(result.detections.length).map((d) => `${d.macroClass}:${d.score.toFixed(3)}`)
8477
8528
  });
8478
8529
  return added > 0 ? {
8479
8530
  ...result,
@@ -8522,6 +8573,26 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8522
8573
  lease.release(reason);
8523
8574
  }
8524
8575
  }
8576
+ /**
8577
+ * Fold a frame's non-run into the per-camera skip counters, drained by the
8578
+ * next report line (D378). Counters, never lines: a skip happens several
8579
+ * times a second per camera and a line per skip would be the noisiest thing
8580
+ * in the log.
8581
+ */
8582
+ addRecoverySkips(deviceId, delta) {
8583
+ const current = this.recoverySkips.get(deviceId) ?? {
8584
+ rateLimited: 0,
8585
+ noMotion: 0,
8586
+ tooLarge: 0,
8587
+ covered: 0
8588
+ };
8589
+ this.recoverySkips.set(deviceId, {
8590
+ rateLimited: current.rateLimited + (delta.rateLimited ?? 0),
8591
+ noMotion: current.noMotion + (delta.noMotion ?? 0),
8592
+ tooLarge: current.tooLarge + (delta.tooLarge ?? 0),
8593
+ covered: current.covered + (delta.covered ?? 0)
8594
+ });
8595
+ }
8525
8596
  /** Aggregate the pass per camera and report once per {@link RECOVERY_LOG_INTERVAL_MS}. */
8526
8597
  reportRecovery(deviceId, sample) {
8527
8598
  const stats = this.recoveryStats.get(deviceId) ?? {
@@ -8548,6 +8619,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8548
8619
  }
8549
8620
  });
8550
8621
  if (sample.now - stats.reportedAt >= RECOVERY_LOG_INTERVAL_MS) {
8622
+ const skips = this.recoverySkips.get(deviceId) ?? {
8623
+ rateLimited: 0,
8624
+ noMotion: 0,
8625
+ tooLarge: 0,
8626
+ covered: 0
8627
+ };
8551
8628
  this.ctx.logger.info("small-subject recovery: pass report", {
8552
8629
  tags: { deviceId },
8553
8630
  meta: {
@@ -8555,9 +8632,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends require_dist.BaseAdd
8555
8632
  crops: next.crops,
8556
8633
  recovered: next.recovered,
8557
8634
  avgMsPerPass: next.passes > 0 ? Math.round(next.ms / next.passes) : 0,
8558
- windowMs: sample.now - stats.reportedAt
8635
+ windowMs: sample.now - stats.reportedAt,
8636
+ skipRateLimited: skips.rateLimited,
8637
+ skipNoMotion: skips.noMotion,
8638
+ skipTooLarge: skips.tooLarge,
8639
+ skipCovered: skips.covered
8559
8640
  }
8560
8641
  });
8642
+ this.recoverySkips.delete(deviceId);
8561
8643
  this.recoveryStats.set(deviceId, {
8562
8644
  passes: 0,
8563
8645
  crops: 0,
@@ -5149,12 +5149,6 @@ var PipelineRunner = class {
5149
5149
  return null;
5150
5150
  }
5151
5151
  };
5152
- /**
5153
- * How much of a region must lie OUTSIDE every full-frame detection for the
5154
- * pass to run on it. A person's own blob (dilated, with shadow) is covered
5155
- * 70–90% by the person box; a person-and-dog blob is covered ~55% by it.
5156
- */
5157
- var RECOVERY_MIN_UNCOVERED_FRACTION = .35;
5158
5152
  function area(r) {
5159
5153
  return Math.max(0, r.w) * Math.max(0, r.h);
5160
5154
  }
@@ -5202,25 +5196,53 @@ function cropsForRegion(region, native) {
5202
5196
  return [squareAround(alongX ? centreX - quarter : centreX, alongX ? centreY : centreY - quarter, side, native), squareAround(alongX ? centreX + quarter : centreX, alongX ? centreY : centreY + quarter, side, native)];
5203
5197
  }
5204
5198
  /**
5205
- * The crops the second pass runs on this frame — possibly none.
5199
+ * The ONE place the two region gates are decided. `planRecoveryCrops` filters
5200
+ * on it and {@link countRecoverySkips} counts it, so the report can never
5201
+ * describe a gate the planner does not actually apply.
5202
+ */
5203
+ function classifyRegion(region, uncovered) {
5204
+ if (area(region) > .25) return "too-large";
5205
+ if (uncovered < .35) return "covered";
5206
+ return "eligible";
5207
+ }
5208
+ /**
5209
+ * WHY A FRAME PLANNED NO CROP, AS A NUMBER (D378).
5206
5210
  *
5207
- * A region qualifies when it is small (`RECOVERY_MAX_REGION_FRAME_FRACTION`)
5208
- * and mostly uncovered by the full-frame boxes
5209
- * (`RECOVERY_MIN_UNCOVERED_FRACTION`). Qualifying regions are served largest
5210
- * uncovered area first under `RECOVERY_MAX_CROPS_PER_FRAME`; a crop whose
5211
- * centre already lies inside a planned crop is folded into it. A frame that
5212
- * cannot hold the smallest crop yields nothing: it is already seen at scale.
5211
+ * The pass report counts the passes that RAN, so "the second pass stops running
5212
+ * when a person is beside the dog" — a plausible reading of
5213
+ * {@link RECOVERY_MAX_REGION_FRAME_FRACTION} and
5214
+ * {@link RECOVERY_MIN_UNCOVERED_FRACTION}, and a live hypothesis on camera 615
5215
+ * — could not be confirmed or killed from the logs at all. This classifies the
5216
+ * regions a frame offered, against the same predicate the planner filters on.
5213
5217
  */
5218
+ function countRecoverySkips(input) {
5219
+ let tooLarge = 0;
5220
+ let covered = 0;
5221
+ let eligible = 0;
5222
+ for (const region of input.regions) switch (classifyRegion(region, uncoveredFraction(region, input.detections))) {
5223
+ case "too-large":
5224
+ tooLarge += 1;
5225
+ break;
5226
+ case "covered":
5227
+ covered += 1;
5228
+ break;
5229
+ case "eligible":
5230
+ eligible += 1;
5231
+ break;
5232
+ }
5233
+ return {
5234
+ tooLarge,
5235
+ covered,
5236
+ eligible
5237
+ };
5238
+ }
5214
5239
  function planRecoveryCrops(input) {
5215
5240
  const { native } = input;
5216
5241
  if (Math.min(native.width, native.height) < 640) return [];
5217
5242
  const ranked = input.regions.map((region) => ({
5218
5243
  region,
5219
5244
  uncovered: uncoveredFraction(region, input.detections)
5220
- })).filter(({ region, uncovered }) => {
5221
- if (area(region) > .25) return false;
5222
- return uncovered >= RECOVERY_MIN_UNCOVERED_FRACTION;
5223
- }).toSorted((a, b) => area(b.region) * b.uncovered - area(a.region) * a.uncovered);
5245
+ })).filter(({ region, uncovered }) => classifyRegion(region, uncovered) === "eligible").toSorted((a, b) => area(b.region) * b.uncovered - area(a.region) * a.uncovered);
5224
5246
  const planned = [];
5225
5247
  for (const { region } of ranked) for (const crop of cropsForRegion(region, native)) {
5226
5248
  if (planned.length >= 2) return planned;
@@ -5251,6 +5273,11 @@ function remapCropDetections(detections, crop, view, result, native) {
5251
5273
  if (x <= 2 || y <= 2 || x + width >= view.width - 2 || y + height >= view.height - 2) continue;
5252
5274
  out.push({
5253
5275
  ...detection,
5276
+ nativeRecovery: {
5277
+ score: detection.score,
5278
+ cropSidePx: crop.side,
5279
+ viewPx: view.width
5280
+ },
5254
5281
  bbox: {
5255
5282
  x: (crop.left + x * viewToNative) * nativeToResultX,
5256
5283
  y: (crop.top + y * viewToNative) * nativeToResultY,
@@ -5915,6 +5942,15 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
5915
5942
  /** Per-camera counters since the last recovery report line. */
5916
5943
  recoveryStats = /* @__PURE__ */ new Map();
5917
5944
  /**
5945
+ * Per-camera counters for the frames on which the pass did NOT run, since the
5946
+ * last report line (D378). "The second pass stops running when a person is
5947
+ * beside the dog" was a live hypothesis on camera 615 and the logs could not
5948
+ * answer it: a report that counts only the passes that ran cannot say why the
5949
+ * others did not. `rateLimited` is the cadence cap, `noMotion` an empty or
5950
+ * stale motion mirror, and `tooLarge` / `covered` the two region gates.
5951
+ */
5952
+ recoverySkips = /* @__PURE__ */ new Map();
5953
+ /**
5918
5954
  * Gates the root-plane `package-detection` step to motion-settle edges +
5919
5955
  * a low-cadence safety poll instead of every frame (packages are
5920
5956
  * stationary — per-frame parcel inference is wasted cost). Consulted in
@@ -7638,6 +7674,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
7638
7674
  this.motionRegions.forget(deviceId);
7639
7675
  this.lastRecoveryAt.delete(deviceId);
7640
7676
  this.recoveryStats.delete(deviceId);
7677
+ this.recoverySkips.delete(deviceId);
7641
7678
  const warnPrefix = `${deviceId}:`;
7642
7679
  for (const key of this.warnedNoJumpDevice) if (key.startsWith(warnPrefix)) this.warnedNoJumpDevice.delete(key);
7643
7680
  this.attached.delete(deviceId);
@@ -8406,9 +8443,15 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8406
8443
  const api = this.ctx?.api;
8407
8444
  if (!api || !frame.frameRef) return result;
8408
8445
  const now = Date.now();
8409
- if (now - (this.lastRecoveryAt.get(deviceId) ?? 0) < 500) return result;
8446
+ if (now - (this.lastRecoveryAt.get(deviceId) ?? 0) < 500) {
8447
+ this.addRecoverySkips(deviceId, { rateLimited: 1 });
8448
+ return result;
8449
+ }
8410
8450
  const regions = this.motionRegions.fresh(deviceId, now);
8411
- if (regions.length === 0) return result;
8451
+ if (regions.length === 0) {
8452
+ this.addRecoverySkips(deviceId, { noMotion: 1 });
8453
+ return result;
8454
+ }
8412
8455
  const step = selectRecoveryStep(steps);
8413
8456
  if (step === null) return result;
8414
8457
  if (result.width <= 0 || result.height <= 0) return result;
@@ -8416,7 +8459,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8416
8459
  width: frame.frameRef.width,
8417
8460
  height: frame.frameRef.height
8418
8461
  };
8419
- const crops = planRecoveryCrops({
8462
+ const planInput = {
8420
8463
  regions,
8421
8464
  detections: result.detections.filter((d) => d.kind === "first-level").map((d) => ({
8422
8465
  x: d.bbox.x / result.width,
@@ -8425,8 +8468,16 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8425
8468
  h: d.bbox.height / result.height
8426
8469
  })),
8427
8470
  native
8428
- });
8429
- if (crops.length === 0) return result;
8471
+ };
8472
+ const crops = planRecoveryCrops(planInput);
8473
+ if (crops.length === 0) {
8474
+ const skipped = countRecoverySkips(planInput);
8475
+ this.addRecoverySkips(deviceId, {
8476
+ tooLarge: skipped.tooLarge,
8477
+ covered: skipped.covered
8478
+ });
8479
+ return result;
8480
+ }
8430
8481
  this.lastRecoveryAt.set(deviceId, now);
8431
8482
  const log = this.ctx.logger.withTags({ deviceId });
8432
8483
  const started = Date.now();
@@ -8466,7 +8517,7 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8466
8517
  recovered: added,
8467
8518
  ms: Date.now() - started,
8468
8519
  now,
8469
- classes: fused.slice(result.detections.length).map((d) => d.macroClass)
8520
+ classes: fused.slice(result.detections.length).map((d) => `${d.macroClass}:${d.score.toFixed(3)}`)
8470
8521
  });
8471
8522
  return added > 0 ? {
8472
8523
  ...result,
@@ -8515,6 +8566,26 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8515
8566
  lease.release(reason);
8516
8567
  }
8517
8568
  }
8569
+ /**
8570
+ * Fold a frame's non-run into the per-camera skip counters, drained by the
8571
+ * next report line (D378). Counters, never lines: a skip happens several
8572
+ * times a second per camera and a line per skip would be the noisiest thing
8573
+ * in the log.
8574
+ */
8575
+ addRecoverySkips(deviceId, delta) {
8576
+ const current = this.recoverySkips.get(deviceId) ?? {
8577
+ rateLimited: 0,
8578
+ noMotion: 0,
8579
+ tooLarge: 0,
8580
+ covered: 0
8581
+ };
8582
+ this.recoverySkips.set(deviceId, {
8583
+ rateLimited: current.rateLimited + (delta.rateLimited ?? 0),
8584
+ noMotion: current.noMotion + (delta.noMotion ?? 0),
8585
+ tooLarge: current.tooLarge + (delta.tooLarge ?? 0),
8586
+ covered: current.covered + (delta.covered ?? 0)
8587
+ });
8588
+ }
8518
8589
  /** Aggregate the pass per camera and report once per {@link RECOVERY_LOG_INTERVAL_MS}. */
8519
8590
  reportRecovery(deviceId, sample) {
8520
8591
  const stats = this.recoveryStats.get(deviceId) ?? {
@@ -8541,6 +8612,12 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8541
8612
  }
8542
8613
  });
8543
8614
  if (sample.now - stats.reportedAt >= RECOVERY_LOG_INTERVAL_MS) {
8615
+ const skips = this.recoverySkips.get(deviceId) ?? {
8616
+ rateLimited: 0,
8617
+ noMotion: 0,
8618
+ tooLarge: 0,
8619
+ covered: 0
8620
+ };
8544
8621
  this.ctx.logger.info("small-subject recovery: pass report", {
8545
8622
  tags: { deviceId },
8546
8623
  meta: {
@@ -8548,9 +8625,14 @@ var PipelineRunnerAddon = class PipelineRunnerAddon extends BaseAddon {
8548
8625
  crops: next.crops,
8549
8626
  recovered: next.recovered,
8550
8627
  avgMsPerPass: next.passes > 0 ? Math.round(next.ms / next.passes) : 0,
8551
- windowMs: sample.now - stats.reportedAt
8628
+ windowMs: sample.now - stats.reportedAt,
8629
+ skipRateLimited: skips.rateLimited,
8630
+ skipNoMotion: skips.noMotion,
8631
+ skipTooLarge: skips.tooLarge,
8632
+ skipCovered: skips.covered
8552
8633
  }
8553
8634
  });
8635
+ this.recoverySkips.delete(deviceId);
8554
8636
  this.recoveryStats.set(deviceId, {
8555
8637
  passes: 0,
8556
8638
  crops: 0,
@@ -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.77",
6
+ version: "1.2.78",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_stream_broker_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.157",
21
+ version: "1.2.158",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_stream_broker_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.125",
36
+ version: "1.2.126",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_stream_broker_widgets",
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.157",
39
+ version: "1.2.158",
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.77",
48
+ version: "1.2.78",
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.125",
84
+ version: "1.2.126",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -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_stream_broker_widgets-B17jLw_E.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-BMsB2MVD.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-pipeline",
3
- "version": "1.2.195",
3
+ "version": "1.2.196",
4
4
  "description": "Pipeline bundle — runner, detection, motion, audio + stream broker. Multi-entry npm package shipping pipeline addons under a single bundle.",
5
5
  "keywords": [
6
6
  "camstack",