@camstack/addon-post-analysis 1.2.663 → 1.2.665

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.
Files changed (16) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/{clip-model-registry-CwXJyrLg.mjs → clip-model-registry-DZTzG2p4.mjs} +377 -132
  3. package/dist/{clip-model-registry-Cz8LWYwU.js → clip-model-registry-nn_STiGt.js} +382 -131
  4. package/dist/embedding-encoder/index.js +1 -1
  5. package/dist/embedding-encoder/index.mjs +1 -1
  6. package/dist/pipeline-analytics/_stub.js +2 -2
  7. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-jPBj20ez.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Hy74pMMo.mjs} +2 -2
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BXTAZYaJ.mjs +26 -0
  9. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Br6eWzp_.mjs +26 -0
  10. package/dist/pipeline-analytics/{hostInit-DOdK6KJ5.mjs → hostInit-BAYLsfoZ.mjs} +2 -2
  11. package/dist/pipeline-analytics/index.js +438 -61
  12. package/dist/pipeline-analytics/index.mjs +438 -61
  13. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  14. package/package.json +2 -2
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Be6bZvOS.mjs +0 -26
  16. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-B5Bw-VHt.mjs +0 -26
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
2
2
  __esModule: { value: true },
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
- const require_clip_model_registry = require("../clip-model-registry-Cz8LWYwU.js");
5
+ const require_clip_model_registry = require("../clip-model-registry-nn_STiGt.js");
6
6
  let node_fs = require("node:fs");
7
7
  node_fs = require_clip_model_registry.__toESM(node_fs, 1);
8
8
  let node_path = require("node:path");
@@ -12284,6 +12284,16 @@ var NcStateSummaryTestSchema = require_clip_model_registry.object({
12284
12284
  }))
12285
12285
  });
12286
12286
  //#endregion
12287
+ //#region src/notification-center/template-defaults.ts
12288
+ function servedTemplateDefaults(texts) {
12289
+ return [{
12290
+ id: "appliance-cycle",
12291
+ kind: "sensor",
12292
+ title: texts.raw("appliance.finished.title") ?? "",
12293
+ body: texts.raw("appliance.finished.body") ?? ""
12294
+ }];
12295
+ }
12296
+ //#endregion
12287
12297
  //#region src/notification-center/clock-text.ts
12288
12298
  /**
12289
12299
  * `{{time}}` — hours and minutes in the NOTIFICATION language.
@@ -13682,7 +13692,10 @@ var ncActions = require_clip_model_registry.defineCustomActions({
13682
13692
  catalog: require_clip_model_registry.array(require_clip_model_registry.NcConditionDescriptorSchema),
13683
13693
  taxonomy: require_clip_model_registry.NcTaxonomySchema
13684
13694
  })),
13685
- "nc.getTemplateCatalog": require_clip_model_registry.customAction(require_clip_model_registry.object({}), require_clip_model_registry.object({ vars: require_clip_model_registry.array(require_clip_model_registry.NcTemplateVarDescriptorSchema) })),
13695
+ "nc.getTemplateCatalog": require_clip_model_registry.customAction(require_clip_model_registry.object({}), require_clip_model_registry.object({
13696
+ vars: require_clip_model_registry.array(require_clip_model_registry.NcTemplateVarDescriptorSchema),
13697
+ defaults: require_clip_model_registry.array(require_clip_model_registry.NcTemplateDefaultSchema)
13698
+ })),
13686
13699
  "nc.previewTemplate": require_clip_model_registry.customAction(require_clip_model_registry.NcTemplatePreviewInputSchema, require_clip_model_registry.NcTemplatePreviewSchema, { kind: "mutation" }),
13687
13700
  "nc.createRule": require_clip_model_registry.customAction(require_clip_model_registry.object({ rule: require_clip_model_registry.NcRuleInputSchema }), require_clip_model_registry.object({ rule: require_clip_model_registry.NcRuleSchema }), {
13688
13701
  kind: "mutation",
@@ -14462,7 +14475,10 @@ function makeNcActionHandlers(deps) {
14462
14475
  catalog: [...require_clip_model_registry.NC_CONDITION_CATALOG],
14463
14476
  taxonomy: require_clip_model_registry.NC_TAXONOMY
14464
14477
  }),
14465
- "nc.getTemplateCatalog": async () => ({ vars: require_clip_model_registry.servedTemplateVars() }),
14478
+ "nc.getTemplateCatalog": async () => ({
14479
+ vars: require_clip_model_registry.servedTemplateVars(),
14480
+ defaults: deps.previewTexts === void 0 ? [] : servedTemplateDefaults(deps.previewTexts)
14481
+ }),
14466
14482
  "nc.previewTemplate": async (input) => {
14467
14483
  const texts = deps.previewTexts;
14468
14484
  if (texts === void 0) throw new Error("nc.previewTemplate: text catalog not wired");
@@ -49951,7 +49967,7 @@ async function encodeRgbCropToJpeg(bytes, width, height) {
49951
49967
  * what lets the caller stop base64-ing purely to be understood.
49952
49968
  */
49953
49969
  async function decodeJpegToRgb(jpeg) {
49954
- const { data, info } = await (0, sharp.default)(typeof jpeg === "string" ? Buffer.from(jpeg, "base64") : jpeg).raw().toBuffer({ resolveWithObject: true });
49970
+ const { data, info } = await (0, sharp.default)(typeof jpeg === "string" ? Buffer.from(jpeg, "base64") : jpeg).removeAlpha().toColourspace("srgb").raw().toBuffer({ resolveWithObject: true });
49955
49971
  return {
49956
49972
  bytes: data,
49957
49973
  width: info.width,
@@ -53037,6 +53053,42 @@ function collectPackageZoneIds(rules) {
53037
53053
  }
53038
53054
  return ids;
53039
53055
  }
53056
+ /**
53057
+ * How much the package zone box grows on every side when asking whether motion
53058
+ * reached it. Mirrors the D144 dilation of the pipeline's burst trigger
53059
+ * (`PACKAGE_ZONE_DILATION` in addon-pipeline, not importable from here): about
53060
+ * one adult's width beside a doormat, the place a courier stands.
53061
+ */
53062
+ var PACKAGE_ZONE_MOTION_DILATION = .5;
53063
+ /**
53064
+ * A motion region covering more than this fraction of the frame is a light
53065
+ * change or the weather, not a person: it never counts as a deliverer.
53066
+ */
53067
+ var PACKAGE_ZONE_MOTION_MAX_REGION_FRAC = .8;
53068
+ /**
53069
+ * Did analyzer motion reach the dilated package zone with a person-sized
53070
+ * region? Unknown geometry answers false: this stamp only ever BACKS the open
53071
+ * of a candidate, so failing closed keeps the light-wedge guard intact.
53072
+ */
53073
+ function delivererMotionNearPackageZone(input) {
53074
+ const { zoneRect, frameWidth, frameHeight } = input;
53075
+ if (zoneRect === null || frameWidth <= 0 || frameHeight <= 0) return false;
53076
+ const dx = zoneRect.w * PACKAGE_ZONE_MOTION_DILATION;
53077
+ const dy = zoneRect.h * PACKAGE_ZONE_MOTION_DILATION;
53078
+ const left = Math.max(0, zoneRect.x - dx);
53079
+ const top = Math.max(0, zoneRect.y - dy);
53080
+ const right = Math.min(1, zoneRect.x + zoneRect.w + dx);
53081
+ const bottom = Math.min(1, zoneRect.y + zoneRect.h + dy);
53082
+ for (const region of input.regions) {
53083
+ const x = region.bbox.x / frameWidth;
53084
+ const y = region.bbox.y / frameHeight;
53085
+ const w = region.bbox.w / frameWidth;
53086
+ const h = region.bbox.h / frameHeight;
53087
+ if (w * h > PACKAGE_ZONE_MOTION_MAX_REGION_FRAC) continue;
53088
+ if (x < right && left < x + w && y < bottom && top < y + h) return true;
53089
+ }
53090
+ return false;
53091
+ }
53040
53092
  //#endregion
53041
53093
  //#region src/pipeline-analytics/pipeline/package/package-collector-mirror.ts
53042
53094
  /**
@@ -53982,6 +54034,15 @@ var PACKAGE_STILLNESS_TTL_MS = 5 * 6e4;
53982
54034
  */
53983
54035
  var PACKAGE_STILLNESS_ASSOC_IOU = .3;
53984
54036
  /**
54037
+ * Lowest score of a below-threshold box that may CONTINUE an open candidate
54038
+ * (D665: a box under the floor may continue, never open). Observed on a real
54039
+ * parcel after the day to IR flip: 0.35 to 0.50.
54040
+ */
54041
+ var PACKAGE_STILLNESS_CONTINUATION_MIN_SCORE = .3;
54042
+ /** Above-floor sightings a candidate needs before a sub-floor box may count:
54043
+ * one confident look plus a doormat-shaped reading is not a parcel. */
54044
+ var PACKAGE_CONTINUATION_MIN_REAL_HITS = 2;
54045
+ /**
53985
54046
  * Two absence reads closer than this are ONE look, not two. A settle burst
53986
54047
  * runs the package step 5 s apart and a poll 45 s apart, so a real second
53987
54048
  * look is always further away; two frames of one moment (a re-dispatch, a
@@ -54031,6 +54092,7 @@ var PackageStillnessTracker = class {
54031
54092
  const next = [...this.byDevice.get(input.deviceId) ?? []];
54032
54093
  const consumed = /* @__PURE__ */ new Set();
54033
54094
  const promoted = [];
54095
+ const continuedLog = [];
54034
54096
  for (const s of input.sightings) {
54035
54097
  const index = this.bestMatch(next, s, consumed);
54036
54098
  if (index < 0) {
@@ -54043,17 +54105,33 @@ var PackageStillnessTracker = class {
54043
54105
  if (prev === void 0) continue;
54044
54106
  consumed.add(prev.id);
54045
54107
  const updated = confirm(prev, input, s);
54046
- const minHits = input.minHits ?? this.config.minHits;
54047
- if (!(updated.promotedAt === null && updated.activityBackedAtOpen && updated.hits >= minHits && input.timestamp - updated.firstSeenAt >= input.minDwellMs)) {
54048
- next[index] = updated;
54049
- continue;
54050
- }
54051
- const parked = {
54052
- ...updated,
54053
- promotedAt: input.timestamp
54108
+ const parked = this.promoteWhenReady(updated, input);
54109
+ next[index] = parked ?? updated;
54110
+ if (parked !== null) promoted.push(toEntry(parked));
54111
+ }
54112
+ if (input.looked === true && input.continuationBoxes !== void 0) for (let i = 0; i < next.length; i += 1) {
54113
+ const c = next[i];
54114
+ if (c === void 0 || c.promotedAt !== null || consumed.has(c.id)) continue;
54115
+ if (c.realHits < PACKAGE_CONTINUATION_MIN_REAL_HITS) continue;
54116
+ const box = this.continuationFor(c, input.continuationBoxes);
54117
+ if (box === null) continue;
54118
+ consumed.add(c.id);
54119
+ const continued = {
54120
+ ...c,
54121
+ lastSeenAt: input.timestamp,
54122
+ hits: c.hits + 1,
54123
+ absenceReads: [],
54124
+ lastLook: "present",
54125
+ earlyUnbackedReported: false
54054
54126
  };
54055
- next[index] = parked;
54056
- promoted.push(toEntry(parked));
54127
+ continuedLog.push({
54128
+ id: c.id,
54129
+ hits: continued.hits,
54130
+ score: box.score
54131
+ });
54132
+ const parked = this.promoteWhenReady(continued, input);
54133
+ next[i] = parked ?? continued;
54134
+ if (parked !== null) promoted.push(toEntry(parked));
54057
54135
  }
54058
54136
  const present = input.rawPackageBoxes !== void 0 ? withRawPresence(next, consumed, input.rawPackageBoxes, input.timestamp, this.config.assocIou) : next;
54059
54137
  const looked = input.looked === true && input.rawPackageBoxes !== void 0 ? withLook(present, consumed, input, input.rawPackageBoxes, this.config.assocIou) : present;
@@ -54089,7 +54167,8 @@ var PackageStillnessTracker = class {
54089
54167
  ...input.lastActivityAt !== void 0 ? { lastActivityAt: input.lastActivityAt } : {}
54090
54168
  }),
54091
54169
  earlyDepartureIds: [...early],
54092
- earlyEvidenceUnbacked: unbacked
54170
+ earlyEvidenceUnbacked: unbacked,
54171
+ continued: continuedLog
54093
54172
  };
54094
54173
  }
54095
54174
  /** Why complete early evidence may NOT conclude, or null when it may. */
@@ -54116,7 +54195,8 @@ var PackageStillnessTracker = class {
54116
54195
  ...input.lastActivityAt !== void 0 ? { lastActivityAt: input.lastActivityAt } : {}
54117
54196
  }),
54118
54197
  earlyDepartureIds: [],
54119
- earlyEvidenceUnbacked: []
54198
+ earlyEvidenceUnbacked: [],
54199
+ continued: []
54120
54200
  };
54121
54201
  }
54122
54202
  /** The devices holding a candidate — what a sweep has to visit. */
@@ -54180,6 +54260,43 @@ var PackageStillnessTracker = class {
54180
54260
  /** Best same-class candidate over the association floor, ignoring any already
54181
54261
  * consumed by an earlier sighting in THIS frame (two boxes never collapse
54182
54262
  * into one parcel). */
54263
+ continuationFor(c, boxes) {
54264
+ let best = null;
54265
+ let bestIou = this.config.assocIou;
54266
+ for (const b of boxes) {
54267
+ if (b.score < PACKAGE_STILLNESS_CONTINUATION_MIN_SCORE) continue;
54268
+ const overlap = iou$2(c.bbox, b.bbox);
54269
+ if (overlap >= bestIou) {
54270
+ bestIou = overlap;
54271
+ best = b;
54272
+ }
54273
+ }
54274
+ return best;
54275
+ }
54276
+ promoteWhenReady(candidate, input) {
54277
+ const updated = this.backedLate(candidate, input);
54278
+ const minHits = input.minHits ?? this.config.minHits;
54279
+ return updated.promotedAt === null && updated.activityBackedAtOpen && updated.hits >= minHits && input.timestamp - updated.firstSeenAt >= input.minDwellMs ? {
54280
+ ...updated,
54281
+ promotedAt: input.timestamp
54282
+ } : null;
54283
+ }
54284
+ /**
54285
+ * Zone motion learned AFTER the open still backs the candidate when its stamp
54286
+ * lies in the window around the FIRST sighting (the same slack a pick-up
54287
+ * gets): the backing stays anchored at the open, a later passer-by never
54288
+ * validates scenery (D1003).
54289
+ */
54290
+ backedLate(c, input) {
54291
+ if (c.activityBackedAtOpen || c.promotedAt !== null) return c;
54292
+ const at = input.lastDelivererMotionAt;
54293
+ const windowMs = input.activityWindowMs ?? 0;
54294
+ if (at === void 0 || windowMs <= 0) return c;
54295
+ return at >= c.firstSeenAt - windowMs && at <= c.firstSeenAt + this.config.roundGapMs ? {
54296
+ ...c,
54297
+ activityBackedAtOpen: true
54298
+ } : c;
54299
+ }
54183
54300
  bestMatch(candidates, s, consumed) {
54184
54301
  let bestIndex = -1;
54185
54302
  let bestIou = this.config.assocIou;
@@ -54196,7 +54313,7 @@ var PackageStillnessTracker = class {
54196
54313
  }
54197
54314
  openCandidate(input, s) {
54198
54315
  const windowMs = input.activityWindowMs ?? 0;
54199
- const activityBackedAtOpen = windowMs <= 0 || input.lastActivityAt !== void 0 && input.timestamp - input.lastActivityAt <= windowMs;
54316
+ const activityBackedAtOpen = windowMs <= 0 || input.lastActivityAt !== void 0 && input.timestamp - input.lastActivityAt <= windowMs || input.lastDelivererMotionAt !== void 0 && input.timestamp - input.lastDelivererMotionAt <= windowMs;
54200
54317
  return {
54201
54318
  id: this.newId(),
54202
54319
  deviceId: input.deviceId,
@@ -54207,6 +54324,7 @@ var PackageStillnessTracker = class {
54207
54324
  firstSeenAt: input.timestamp,
54208
54325
  lastSeenAt: input.timestamp,
54209
54326
  hits: 1,
54327
+ realHits: 1,
54210
54328
  promotedAt: null,
54211
54329
  absenceReads: [],
54212
54330
  lastLook: null,
@@ -54229,6 +54347,7 @@ function confirm(prev, input, s) {
54229
54347
  frameHeight: input.frameHeight,
54230
54348
  lastSeenAt: input.timestamp,
54231
54349
  hits: prev.hits + 1,
54350
+ realHits: prev.realHits + 1,
54232
54351
  absenceReads: [],
54233
54352
  lastLook: "present",
54234
54353
  earlyUnbackedReported: false,
@@ -54765,6 +54884,9 @@ var FrameState = class {
54765
54884
  * which would let the streetlight wedge vouch for its own delivery.
54766
54885
  */
54767
54886
  lastNonPackageActivityAt = /* @__PURE__ */ new Map();
54887
+ /** Last analyzer motion near a package drop-off zone, per device (D1003).
54888
+ * Backs the OPEN of a candidate only; never a pick-up witness. */
54889
+ lastPackageZoneMotionAt = /* @__PURE__ */ new Map();
54768
54890
  /**
54769
54891
  * WHO was in the drop-off zone, and when (D404). The pick-up is CONCLUDED
54770
54892
  * minutes after the fact, so the collector must be remembered while it is
@@ -58124,6 +58246,7 @@ function nextPackageLookDropEpisodes(previous, drops) {
58124
58246
  //#region src/pipeline-analytics/pipeline/package/package-look-evidence.ts
58125
58247
  function packageLookEvidence(input) {
58126
58248
  const raw = [];
58249
+ const continuation = [];
58127
58250
  for (const d of input.frame.detections) {
58128
58251
  if (d.kind !== "first-level" || !input.packageClasses.has(d.macroClass)) continue;
58129
58252
  raw.push({
@@ -58136,11 +58259,16 @@ function packageLookEvidence(input) {
58136
58259
  for (const d of input.frame.discarded ?? []) {
58137
58260
  if (!input.packageClasses.has(d.macroClass)) continue;
58138
58261
  const [x1, y1, x2, y2] = d.bbox;
58139
- raw.push({
58262
+ const box = {
58140
58263
  x: x1,
58141
58264
  y: y1,
58142
58265
  w: x2 - x1,
58143
58266
  h: y2 - y1
58267
+ };
58268
+ raw.push(box);
58269
+ if (d.reason === "below-threshold") continuation.push({
58270
+ bbox: box,
58271
+ score: d.score
58144
58272
  });
58145
58273
  }
58146
58274
  return {
@@ -58152,6 +58280,7 @@ function packageLookEvidence(input) {
58152
58280
  h: t.bbox.h
58153
58281
  })),
58154
58282
  rawPackageBoxes: raw,
58283
+ continuationBoxes: continuation,
58155
58284
  earlyPickup: input.earlyPickupEnabled
58156
58285
  };
58157
58286
  }
@@ -58339,6 +58468,13 @@ function clamp01(value) {
58339
58468
  }
58340
58469
  //#endregion
58341
58470
  //#region src/pipeline-analytics/pipeline/package/package-scene-diff.ts
58471
+ /**
58472
+ * Zone-crop scene difference for a packages rule (D931).
58473
+ *
58474
+ * A uniform brightness shift is removed by subtracting the median luma delta,
58475
+ * so a shadow that lifts the whole crop leaves no blob. What remains is a
58476
+ * connected region. The package model is not consulted here.
58477
+ */
58342
58478
  /** Residual, after the median shift, that counts as a changed pixel. */
58343
58479
  var PACKAGE_SCENE_DIFF_LUMA_THRESHOLD = 28;
58344
58480
  /** A changed region this large, relative to the zone, is the zone itself. */
@@ -58353,12 +58489,26 @@ var PACKAGE_SCENE_DIFF_SETTLE_MS = 3e3;
58353
58489
  var PACKAGE_SCENE_DIFF_LOOK_GAP_MS = 2e3;
58354
58490
  /** Confidence stamped on a scene-diff sighting the model did not box. */
58355
58491
  var PACKAGE_SCENE_DIFF_CONFIDENCE = .5;
58492
+ /** True when the crop was shot in infrared (no colour). */
58493
+ function isIrCrop(crop) {
58494
+ return crop.chromaSpread < IR_CHROMA_THRESHOLD;
58495
+ }
58356
58496
  function diffZoneLuma(input) {
58357
58497
  const { before, after } = input;
58358
58498
  if (before.width !== after.width || before.height !== after.height || before.luma.length !== after.luma.length) return {
58359
58499
  blobs: [],
58360
58500
  rejections: [{ reason: "crop-mismatch" }]
58361
58501
  };
58502
+ if (isIrCrop(before) !== isIrCrop(after)) return {
58503
+ blobs: [],
58504
+ rejections: [{
58505
+ reason: "lighting-mode-changed",
58506
+ chroma: {
58507
+ before: before.chromaSpread,
58508
+ after: after.chromaSpread
58509
+ }
58510
+ }]
58511
+ };
58362
58512
  const median = medianDelta(before.luma, after.luma);
58363
58513
  const mask = residualMask(before.luma, after.luma, median);
58364
58514
  const zonePx = before.width * before.height;
@@ -58573,6 +58723,7 @@ function okCrop(rgb, width, height, jpeg) {
58573
58723
  width,
58574
58724
  height,
58575
58725
  luma: lumaFromRgb(rgb, width, height),
58726
+ chromaSpread: chromaSpreadFromRgb(rgb, width, height),
58576
58727
  jpeg: new Uint8Array(jpeg)
58577
58728
  };
58578
58729
  }
@@ -58589,6 +58740,46 @@ function lumaFromRgb(bytes, width, height) {
58589
58740
  }
58590
58741
  return luma;
58591
58742
  }
58743
+ /** The grid `IR_CHROMA_THRESHOLD` was calibrated on (`chromaSpread`, scene-crop.ts). */
58744
+ var CHROMA_GRID = 32;
58745
+ /**
58746
+ * Mean of max(r,g,b) - min(r,g,b), 0..255, measured on a 32x32 box-averaged
58747
+ * copy of the crop: the same measure and grid as `chromaSpread` in
58748
+ * `scene-crop.ts` (which needs sharp), so `IR_CHROMA_THRESHOLD` means the same
58749
+ * thing. Averaging first matters: sensor noise and JPEG chroma speckle on an IR
58750
+ * frame cancel out, where per-pixel they read as colour.
58751
+ */
58752
+ function chromaSpreadFromRgb(bytes, width, height) {
58753
+ if (width <= 0 || height <= 0 || bytes.length < width * height * 3) return 0;
58754
+ const gw = Math.min(CHROMA_GRID, width);
58755
+ const gh = Math.min(CHROMA_GRID, height);
58756
+ const sum = new Float64Array(gw * gh * 3);
58757
+ const count = new Float64Array(gw * gh);
58758
+ for (let y = 0; y < height; y++) {
58759
+ const cy = Math.min(gh - 1, Math.floor(y * gh / height));
58760
+ for (let x = 0; x < width; x++) {
58761
+ const cx = Math.min(gw - 1, Math.floor(x * gw / width));
58762
+ const cell = cy * gw + cx;
58763
+ const o = (y * width + x) * 3;
58764
+ sum[cell * 3] = (sum[cell * 3] ?? 0) + (bytes[o] ?? 0);
58765
+ sum[cell * 3 + 1] = (sum[cell * 3 + 1] ?? 0) + (bytes[o + 1] ?? 0);
58766
+ sum[cell * 3 + 2] = (sum[cell * 3 + 2] ?? 0) + (bytes[o + 2] ?? 0);
58767
+ count[cell] = (count[cell] ?? 0) + 1;
58768
+ }
58769
+ }
58770
+ let total = 0;
58771
+ let cells = 0;
58772
+ for (let cell = 0; cell < gw * gh; cell++) {
58773
+ const n = count[cell] ?? 0;
58774
+ if (n === 0) continue;
58775
+ const r = (sum[cell * 3] ?? 0) / n;
58776
+ const g = (sum[cell * 3 + 1] ?? 0) / n;
58777
+ const b = (sum[cell * 3 + 2] ?? 0) / n;
58778
+ total += Math.max(r, g, b) - Math.min(r, g, b);
58779
+ cells += 1;
58780
+ }
58781
+ return cells === 0 ? 0 : total / cells;
58782
+ }
58592
58783
  //#endregion
58593
58784
  //#region src/pipeline-analytics/pipeline/package/package-scene-step.ts
58594
58785
  /**
@@ -58688,6 +58879,14 @@ function compareAfterLeave(prev, input) {
58688
58879
  minAreaFrac: input.minAreaFrac,
58689
58880
  occluders: input.occluders
58690
58881
  });
58882
+ if (diff.rejections.some((r) => r.reason === "lighting-mode-changed")) return {
58883
+ state: {
58884
+ ...rememberClear(prev, input).state,
58885
+ lastDiffAt: input.timestamp
58886
+ },
58887
+ sightings: [],
58888
+ rejections: diff.rejections
58889
+ };
58691
58890
  const blocked = diff.rejections.some((r) => r.reason === "overlaps-person" || r.reason === "overlaps-vehicle");
58692
58891
  if (diff.blobs.length === 0) {
58693
58892
  if (blocked) return {
@@ -58731,6 +58930,20 @@ function confirmPending(prev, input) {
58731
58930
  sightings: [],
58732
58931
  rejections: []
58733
58932
  };
58933
+ if (isIrCrop(prev.after) !== isIrCrop(input.crop)) return {
58934
+ state: {
58935
+ ...rememberClear(prev, input).state,
58936
+ lastDiffAt: input.timestamp
58937
+ },
58938
+ sightings: [],
58939
+ rejections: [{
58940
+ reason: "lighting-mode-changed",
58941
+ chroma: {
58942
+ before: prev.after.chromaSpread,
58943
+ after: input.crop.chromaSpread
58944
+ }
58945
+ }]
58946
+ };
58734
58947
  const sightings = [];
58735
58948
  const rejections = [];
58736
58949
  const still = [];
@@ -58777,7 +58990,7 @@ function judgeBlob(prev, input, blob) {
58777
58990
  bbox: blob.bbox
58778
58991
  } };
58779
58992
  const stable = meanAbsInBox(after, crop, blob.bbox) <= PACKAGE_SCENE_DIFF_STABLE_MEAN;
58780
- const present = meanAbsInBox(before, crop, blob.bbox) >= PACKAGE_SCENE_DIFF_LUMA_THRESHOLD;
58993
+ const present = isIrCrop(before) !== isIrCrop(crop) || meanAbsInBox(before, crop, blob.bbox) >= PACKAGE_SCENE_DIFF_LUMA_THRESHOLD;
58781
58994
  if (!stable || !present) return { rejection: {
58782
58995
  reason: "unstable",
58783
58996
  bbox: blob.bbox,
@@ -58945,7 +59158,8 @@ var PackageSceneCoordinator = class {
58945
59158
  const crop = {
58946
59159
  width: fetched.width,
58947
59160
  height: fetched.height,
58948
- luma: fetched.luma
59161
+ luma: fetched.luma,
59162
+ chromaSpread: fetched.chromaSpread
58949
59163
  };
58950
59164
  const stepped = stepPackageScene({
58951
59165
  deviceId,
@@ -59011,6 +59225,10 @@ var PackageSceneCoordinator = class {
59011
59225
  tags: { deviceId },
59012
59226
  meta: {
59013
59227
  reason: rejection.reason,
59228
+ ...rejection.chroma !== void 0 ? {
59229
+ chromaBefore: Number(rejection.chroma.before.toFixed(2)),
59230
+ chromaAfter: Number(rejection.chroma.after.toFixed(2))
59231
+ } : {},
59014
59232
  ...rejection.areaFrac !== void 0 ? { areaFrac: Number(rejection.areaFrac.toFixed(5)) } : {}
59015
59233
  }
59016
59234
  });
@@ -59119,14 +59337,8 @@ function planPackageSceneFrame(deviceId, input) {
59119
59337
  frameWidth: input.frameWidth,
59120
59338
  frameHeight: input.frameHeight
59121
59339
  });
59122
- const sceneRect = sceneZones.size === 0 ? null : packageZoneCropRect({
59123
- zones: input.zones,
59124
- packageZoneIds: sceneZones,
59125
- frameWidth: input.frameWidth,
59126
- frameHeight: input.frameHeight
59127
- });
59128
59340
  return {
59129
- sightings: sceneRect === null ? input.sightings : withoutSceneCenters(input, sceneRect),
59341
+ sightings: input.sightings,
59130
59342
  note: {
59131
59343
  deviceId,
59132
59344
  timestamp: input.timestamp,
@@ -59169,15 +59381,6 @@ function occludersOf(tracked) {
59169
59381
  }
59170
59382
  return out;
59171
59383
  }
59172
- function withoutSceneCenters(input, rect) {
59173
- if (input.frameWidth <= 0 || input.frameHeight <= 0) return input.sightings;
59174
- return input.sightings.filter((sighting) => !centerInside(sighting.bbox, rect, input));
59175
- }
59176
- function centerInside(bbox, rect, input) {
59177
- const cx = (bbox.x + bbox.w / 2) / input.frameWidth;
59178
- const cy = (bbox.y + bbox.h / 2) / input.frameHeight;
59179
- return cx >= rect.x && cx < rect.x + rect.w && cy >= rect.y && cy < rect.y + rect.h;
59180
- }
59181
59384
  //#endregion
59182
59385
  //#region src/pipeline-analytics/pipeline/package/package-llm-confirm.ts
59183
59386
  /**
@@ -59338,56 +59541,143 @@ function sweepOne(deps, deviceId) {
59338
59541
  }
59339
59542
  //#endregion
59340
59543
  //#region src/pipeline-analytics/pipeline/package/package-still-capture.ts
59341
- /** A held look frame older than this is not the parcel's picture any more. */
59342
- var PACKAGE_LOOK_FRAME_MAX_AGE_MS = 15e3;
59544
+ /**
59545
+ * The runner's native lease on a look frame (holdFrames 8) is released about
59546
+ * 100 ms after inference, so a handle older than this answers `native-miss` or
59547
+ * `preview-only` — promotion runs seconds to minutes after the look (D60).
59548
+ */
59549
+ var PACKAGE_NATIVE_HANDLE_MAX_AGE_MS = 1e3;
59550
+ /** Total time a promotion may spend landing its still before it ships without (D60). */
59551
+ var PACKAGE_STILL_BOUND_MS = 6e3;
59552
+ /** One live-source attempt never takes longer than this (a sleeping camera). */
59553
+ var PACKAGE_STILL_ATTEMPT_TIMEOUT_MS = 3e3;
59554
+ /** Pause between two live attempts. */
59555
+ var PACKAGE_STILL_RETRY_PAUSE_MS = 750;
59556
+ var PACKAGE_STILL_LIVE_ATTEMPTS = 3;
59557
+ var realPause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
59343
59558
  async function jpegOf(reply) {
59344
59559
  if (reply.tier === "ram-fullframe") return "preview-only";
59345
59560
  if (reply.jpegBytes !== void 0 && reply.jpegBytes.byteLength > 0) return reply.jpegBytes;
59346
59561
  if (reply.bytes !== void 0 && reply.bytes.length === reply.width * reply.height * 3) return encodeRgbCropToJpeg(reply.bytes, reply.width, reply.height);
59347
59562
  return "empty-frame";
59348
59563
  }
59564
+ /** One live read, cut off after `limitMs` so a hung camera cannot hold the delivery. */
59565
+ async function readLive(source, limitMs) {
59566
+ let timer;
59567
+ const timeout = new Promise((resolve) => {
59568
+ timer = setTimeout(() => resolve(null), limitMs);
59569
+ });
59570
+ try {
59571
+ return await Promise.race([source(), timeout]);
59572
+ } finally {
59573
+ if (timer !== void 0) clearTimeout(timer);
59574
+ }
59575
+ }
59576
+ /** Retry the live source under {@link PACKAGE_STILL_BOUND_MS} until a frame lands (D60). */
59577
+ async function liveStill(input, source) {
59578
+ const now = input.now ?? Date.now;
59579
+ const pause = input.pause ?? realPause;
59580
+ const deadline = now() + PACKAGE_STILL_BOUND_MS;
59581
+ let reason = "live-unavailable";
59582
+ let attempts = 0;
59583
+ while (attempts < PACKAGE_STILL_LIVE_ATTEMPTS) {
59584
+ const remaining = deadline - now();
59585
+ if (remaining <= 0) return {
59586
+ jpeg: null,
59587
+ reason: "bound-exhausted",
59588
+ attempts
59589
+ };
59590
+ attempts += 1;
59591
+ try {
59592
+ const jpeg = await readLive(source, Math.min(PACKAGE_STILL_ATTEMPT_TIMEOUT_MS, remaining));
59593
+ if (jpeg !== null && jpeg.byteLength > 0) return {
59594
+ jpeg,
59595
+ reason,
59596
+ attempts
59597
+ };
59598
+ reason = jpeg === null ? "live-unavailable" : "empty-frame";
59599
+ } catch {
59600
+ reason = "live-unavailable";
59601
+ }
59602
+ if (attempts < PACKAGE_STILL_LIVE_ATTEMPTS) await pause(PACKAGE_STILL_RETRY_PAUSE_MS);
59603
+ }
59604
+ return {
59605
+ jpeg: null,
59606
+ reason,
59607
+ attempts
59608
+ };
59609
+ }
59349
59610
  /**
59350
59611
  * The entry carrying its still's `keyFrameMediaId`, or the entry unchanged when
59351
59612
  * it already had one or none could be stored (said, with the reason).
59613
+ *
59614
+ * Ladder: the native handle while its lease can still be alive, then a frame
59615
+ * that exists now (retried under a bound). Every landed still is logged next to
59616
+ * every miss (D400).
59352
59617
  */
59353
59618
  async function withParcelStill(input) {
59354
59619
  const { entry, logger } = input;
59355
59620
  if (entry.keyFrameMediaId !== void 0) return entry;
59356
- const miss = (reason, detail) => {
59621
+ const tags = { deviceId: entry.deviceId };
59622
+ const miss = (reason, extra = {}) => {
59357
59623
  logger.warn("package still NOT stored — the delivery ships without a picture of the parcel", {
59358
- tags: { deviceId: entry.deviceId },
59624
+ tags,
59359
59625
  meta: {
59360
59626
  entryId: entry.id,
59361
59627
  reason,
59362
- ...detail !== void 0 ? { error: detail } : {}
59628
+ ...extra
59363
59629
  }
59364
59630
  });
59365
59631
  return entry;
59366
59632
  };
59367
- if (input.handle === null) return miss("no-frame-handle");
59368
59633
  if (input.writer === null) return miss("no-media-store");
59634
+ const now = input.now ?? Date.now;
59635
+ let jpeg = null;
59636
+ let source = "native";
59637
+ let stamp = input.timestamp;
59638
+ let nativeMiss = "no-frame-handle";
59639
+ let liveReason = null;
59640
+ let attempts = 0;
59369
59641
  try {
59370
- const reply = await input.query({
59371
- handle: input.handle,
59372
- bbox: {
59373
- x: 0,
59374
- y: 0,
59375
- w: entry.frameWidth,
59376
- h: entry.frameHeight
59377
- },
59378
- acceptJpegBytes: true,
59379
- encodeJpeg: true,
59380
- pin: require_clip_model_registry.nodePin(input.handle.nodeId)
59642
+ if (input.handle !== null) {
59643
+ const reply = await input.query({
59644
+ handle: input.handle,
59645
+ bbox: {
59646
+ x: 0,
59647
+ y: 0,
59648
+ w: entry.frameWidth,
59649
+ h: entry.frameHeight
59650
+ },
59651
+ acceptJpegBytes: true,
59652
+ encodeJpeg: true,
59653
+ pin: require_clip_model_registry.nodePin(input.handle.nodeId)
59654
+ });
59655
+ if (reply === null) nativeMiss = "native-miss";
59656
+ else {
59657
+ const out = await jpegOf(reply);
59658
+ if (typeof out === "string") nativeMiss = out;
59659
+ else jpeg = out;
59660
+ }
59661
+ }
59662
+ if (jpeg === null && input.live !== null) {
59663
+ const live = await liveStill(input, input.live);
59664
+ attempts = live.attempts;
59665
+ if (live.jpeg !== null) {
59666
+ jpeg = live.jpeg;
59667
+ source = "live";
59668
+ stamp = now();
59669
+ } else liveReason = live.reason;
59670
+ }
59671
+ if (jpeg === null) return miss(liveReason ?? nativeMiss, {
59672
+ ...input.handle !== null ? { nativeMiss } : {},
59673
+ ...liveReason !== null ? { liveAttempts: attempts } : {}
59381
59674
  });
59382
- if (reply === null) return miss("native-miss");
59383
- const jpeg = await jpegOf(reply);
59384
- if (typeof jpeg === "string") return miss(jpeg);
59385
59675
  const keyFrameMediaId = await input.writer.putReplacing({
59386
59676
  deviceId: entry.deviceId,
59387
59677
  ownerKind: "track",
59388
59678
  ownerId: entry.id,
59389
59679
  kind: "keyFrame",
59390
- timestamp: input.timestamp,
59680
+ timestamp: stamp,
59391
59681
  data: Buffer.from(jpeg),
59392
59682
  subjectBox: {
59393
59683
  x: entry.bbox.x,
@@ -59398,12 +59688,20 @@ async function withParcelStill(input) {
59398
59688
  frameHeight: entry.frameHeight
59399
59689
  }
59400
59690
  });
59691
+ logger.info("package still stored", {
59692
+ tags,
59693
+ meta: {
59694
+ entryId: entry.id,
59695
+ mediaId: keyFrameMediaId,
59696
+ source
59697
+ }
59698
+ });
59401
59699
  return {
59402
59700
  ...entry,
59403
59701
  keyFrameMediaId
59404
59702
  };
59405
59703
  } catch (err) {
59406
- return miss("write-failed", require_clip_model_registry.errMsg(err));
59704
+ return miss("write-failed", { error: require_clip_model_registry.errMsg(err) });
59407
59705
  }
59408
59706
  }
59409
59707
  /**
@@ -71062,6 +71360,7 @@ Object.freeze({
71062
71360
  "addon-pages-source": ["listPages"],
71063
71361
  "addon-routes": ["getRoutes"],
71064
71362
  "addon-widgets-source": ["listWidgets"],
71363
+ "board-key-source": ["listSources"],
71065
71364
  "broker": ["list", "listProviders"],
71066
71365
  "calendar-source": ["listCalendars", "listEvents"],
71067
71366
  "custom-model-registry": ["listModels"],
@@ -72590,7 +72889,7 @@ function buildGlobalSettingsSchema(cap) {
72590
72889
  type: "slider",
72591
72890
  key: "packageDropActivityWindowSec",
72592
72891
  label: "Deliverer window",
72593
- description: "A delivery only counts if a person or vehicle was in the configured package drop-off zone within this many seconds before the package appeared — and a pick-up only with somebody in that zone when it vanished. Motion elsewhere on the camera does not count. 0 disables the requirement.",
72892
+ description: "A delivery only counts if a person or vehicle was in the configured package drop-off zone within this many seconds before the package appeared — and a pick-up only with somebody in that zone when it vanished. Person-sized motion beside the drop-off zone also counts when nobody was tracked. Motion elsewhere on the camera does not count. 0 disables the requirement.",
72594
72893
  min: 0,
72595
72894
  max: 1800,
72596
72895
  step: 30,
@@ -87110,6 +87409,13 @@ var NC_STILL_WAIT_MAX_AGE_MS = 3e4;
87110
87409
  function canStillGainMedia(entry) {
87111
87410
  return entry.recordKind === "object-event";
87112
87411
  }
87412
+ /** The variables that carry a finished cycle's run summary. */
87413
+ var CYCLE_SUMMARY_VARS = [
87414
+ "duration",
87415
+ "energy",
87416
+ "afterDuration",
87417
+ "usedEnergy"
87418
+ ];
87113
87419
  var NcDispatcher = class {
87114
87420
  deps;
87115
87421
  targetCache = null;
@@ -88013,6 +88319,7 @@ var NcDispatcher = class {
88013
88319
  }
88014
88320
  /** Rules already warned about a deprecated variable in this process — once each. */
88015
88321
  deprecatedVarWarned = /* @__PURE__ */ new Set();
88322
+ cycleSummaryWarned = /* @__PURE__ */ new Set();
88016
88323
  /**
88017
88324
  * A rule template written by an older client may still say `{{camera}}`. It
88018
88325
  * renders (the alias), and the rule is named once so the operator can edit it.
@@ -88032,6 +88339,27 @@ var NcDispatcher = class {
88032
88339
  });
88033
88340
  }
88034
88341
  /**
88342
+ * A FINISHED-cycle row whose own template places none of the four cycle
88343
+ * variables delivers no run summary (D391: accepted, produced less). Said
88344
+ * once per rule; the operator's text is never touched.
88345
+ */
88346
+ warnCycleSummaryDropped(entry) {
88347
+ const deviceId = entry.payload.subject.applianceCycle?.deviceId;
88348
+ const template = entry.payload.template;
88349
+ if (deviceId === void 0 || template === void 0) return;
88350
+ if (this.cycleSummaryWarned.has(entry.ruleId)) return;
88351
+ const used = new Set([...templatePlaceholders(template.title ?? ""), ...templatePlaceholders(template.body ?? "")]);
88352
+ if (CYCLE_SUMMARY_VARS.some((name) => used.has(name))) return;
88353
+ this.cycleSummaryWarned.add(entry.ruleId);
88354
+ this.deps.logger.warn("appliance cycle finished but the rule template places no run summary — the message carries no duration or energy", {
88355
+ tags: {
88356
+ deviceId,
88357
+ ruleId: entry.ruleId
88358
+ },
88359
+ meta: { placeholders: [...CYCLE_SUMMARY_VARS] }
88360
+ });
88361
+ }
88362
+ /**
88035
88363
  * The facts of a finished appliance cycle; unknown on every other row and on
88036
88364
  * any failure (a part that cannot be read is omitted, never a blocked send).
88037
88365
  */
@@ -88076,6 +88404,7 @@ var NcDispatcher = class {
88076
88404
  const cycleFacts = await this.applianceCycleFacts(entry);
88077
88405
  const vars = buildTemplateVars(this.texts, entry, deviceName, zoneLabels, void 0, cycleFacts);
88078
88406
  this.warnDeprecatedVars(entry);
88407
+ this.warnCycleSummaryDropped(entry);
88079
88408
  const systemCount = systemEvent?.textCount !== void 0 ? { count: systemEvent.textCount } : {};
88080
88409
  const title = renderRowTitle(this.texts, entry, vars);
88081
88410
  const authoredBody = entry.payload.template?.body;
@@ -102058,7 +102387,10 @@ function buildProvider(host) {
102058
102387
  catalog: [...require_clip_model_registry.NC_CONDITION_CATALOG],
102059
102388
  taxonomy: require_clip_model_registry.NC_TAXONOMY
102060
102389
  }),
102061
- getTemplateCatalog: async () => ({ vars: require_clip_model_registry.servedTemplateVars() }),
102390
+ getTemplateCatalog: async () => ({
102391
+ vars: require_clip_model_registry.servedTemplateVars(),
102392
+ defaults: servedTemplateDefaults(host.texts)
102393
+ }),
102062
102394
  previewTemplate: async (input) => previewTemplate(host.texts, input),
102063
102395
  getHistory: async ({ filter }) => {
102064
102396
  const entries = await host.outbox.queryHistory({
@@ -124995,6 +125327,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
124995
125327
  this.packageDropDetector?.forgetDevice(data.deviceId);
124996
125328
  this.packageScene?.forget(data.deviceId);
124997
125329
  this.frameState.lastNonPackageActivityAt.delete(data.deviceId);
125330
+ this.frameState.lastPackageZoneMotionAt.delete(data.deviceId);
124998
125331
  this.frameState.packageCollectors.forgetDevice(data.deviceId);
124999
125332
  this.frameState.packageOversizeLogAt.delete(data.deviceId);
125000
125333
  this.frameState.packageLookFrames.delete(data.deviceId);
@@ -125019,6 +125352,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
125019
125352
  this.packageDropDetector?.forgetDevice(deviceId);
125020
125353
  this.packageScene?.forget(deviceId);
125021
125354
  this.frameState.lastNonPackageActivityAt.delete(deviceId);
125355
+ this.frameState.lastPackageZoneMotionAt.delete(deviceId);
125022
125356
  this.frameState.packageCollectors.forgetDevice(deviceId);
125023
125357
  this.frameState.packageOversizeLogAt.delete(deviceId);
125024
125358
  this.frameState.packageLookFrames.delete(deviceId);
@@ -127591,6 +127925,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
127591
127925
  detected,
127592
127926
  atMs: timestamp
127593
127927
  });
127928
+ if (detected) await this.stampPackageZoneMotion(deviceId, payload, timestamp);
127594
127929
  if (detected) this.eventStore?.noteMotionSample(deviceId, payload.regionCount, timestamp);
127595
127930
  await this.motionEpisodes?.observe(deviceId, "analyzer", detected, timestamp, {
127596
127931
  regionCount: payload.regionCount,
@@ -127609,6 +127944,28 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
127609
127944
  });
127610
127945
  }
127611
127946
  /**
127947
+ * Stamp analyzer motion that reached a package drop-off zone (D1003): the
127948
+ * courier who never became an L1 track still backs the OPEN of a candidate.
127949
+ * Onboard motion carries no regions or dimensions and never stamps.
127950
+ */
127951
+ async stampPackageZoneMotion(deviceId, payload, timestamp) {
127952
+ const proxy = await this.ensureProxy(deviceId);
127953
+ const packageZoneIds = collectPackageZoneIds(proxy?.state.zoneRules.value?.package ?? []);
127954
+ if (packageZoneIds.size === 0) return;
127955
+ const zoneRect = packageZoneCropRect({
127956
+ zones: proxy?.state.zones.value?.zones ?? [],
127957
+ packageZoneIds,
127958
+ frameWidth: payload.frameWidth,
127959
+ frameHeight: payload.frameHeight
127960
+ });
127961
+ if (delivererMotionNearPackageZone({
127962
+ regions: payload.regions,
127963
+ frameWidth: payload.frameWidth,
127964
+ frameHeight: payload.frameHeight,
127965
+ zoneRect
127966
+ })) this.frameState.lastPackageZoneMotionAt.set(deviceId, timestamp);
127967
+ }
127968
+ /**
127612
127969
  * Persist firmware-driven (onboard / device-activity) motion events into
127613
127970
  * the SAME episode tracker as {@link handleMotionAnalysis}, keyed per
127614
127971
  * `(deviceId, source)` so the two paths can never share — or fight over —
@@ -127941,6 +128298,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
127941
128298
  encodeJpeg: request.encodeJpeg
127942
128299
  }, request.pin);
127943
128300
  }
128301
+ zoneMotionStamp(deviceId) {
128302
+ const at = this.frameState.lastPackageZoneMotionAt.get(deviceId);
128303
+ return at === void 0 ? {} : { lastDelivererMotionAt: at };
128304
+ }
127944
128305
  deliverPackageSceneSightings(delivery) {
127945
128306
  const settings = this.packageDropSettingsFromCache(delivery.deviceId);
127946
128307
  const lastActivityAt = this.frameState.lastNonPackageActivityAt.get(delivery.deviceId);
@@ -127954,6 +128315,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
127954
128315
  minDwellMs: settings.packageDropDwellSec * 1e3,
127955
128316
  activityWindowMs: settings.packageDropActivityWindowSec * 1e3,
127956
128317
  ...lastActivityAt !== void 0 ? { lastActivityAt } : {},
128318
+ ...this.zoneMotionStamp(delivery.deviceId),
127957
128319
  looked: false
127958
128320
  });
127959
128321
  this.bridgePackageStillness({
@@ -128139,6 +128501,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
128139
128501
  minDwellMs: input.settings.packageDropDwellSec * 1e3,
128140
128502
  activityWindowMs: input.settings.packageDropActivityWindowSec * 1e3,
128141
128503
  ...lastActivityAt !== void 0 ? { lastActivityAt } : {},
128504
+ ...this.zoneMotionStamp(input.deviceId),
128142
128505
  ...lookEvidence
128143
128506
  });
128144
128507
  this.bridgePackageStillness({
@@ -128244,11 +128607,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
128244
128607
  const lookFrame = this.frameState.packageLookFrames.get(input.deviceId);
128245
128608
  promoteWithStill({
128246
128609
  entry,
128247
- handle: lookFrame !== void 0 && input.timestamp - lookFrame.at <= PACKAGE_LOOK_FRAME_MAX_AGE_MS ? lookFrame.handle : null,
128610
+ handle: lookFrame !== void 0 && input.timestamp - lookFrame.at <= PACKAGE_NATIVE_HANDLE_MAX_AGE_MS ? lookFrame.handle : null,
128248
128611
  timestamp: lookFrame?.at ?? input.timestamp,
128249
128612
  query: (request) => this.queryPackageSceneCrop(request),
128250
128613
  writer: this.mediaStore,
128251
128614
  logger: this.ctx.logger,
128615
+ live: async () => {
128616
+ const snap = await this.ctx.api.snapshot.getSnapshot.query({ deviceId: input.deviceId });
128617
+ return snap === null ? null : Uint8Array.from(Buffer.from(snap.base64, "base64"));
128618
+ },
128252
128619
  detector: this.packageDropDetector
128253
128620
  }).catch((err) => {
128254
128621
  this.ctx.logger.warn("package promotion failed", {
@@ -128330,6 +128697,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
128330
128697
  });
128331
128698
  }).catch(() => void 0);
128332
128699
  }
128700
+ for (const c of outcome.continued) this.ctx.logger.info("package candidate continued by a sub-floor box", {
128701
+ tags,
128702
+ meta: {
128703
+ entryId: c.id,
128704
+ hits: c.hits,
128705
+ score: c.score,
128706
+ via: input.via
128707
+ }
128708
+ });
128333
128709
  for (const u of outcome.earlyEvidenceUnbacked) this.ctx.logger.info("early pick-up evidence complete — nobody around, left to the TTL", {
128334
128710
  tags,
128335
128711
  meta: {
@@ -128357,6 +128733,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
128357
128733
  observedDwellMs,
128358
128734
  requiredDwellMs,
128359
128735
  activityBackedAtOpen: ex.activityBackedAtOpen,
128736
+ lastZoneMotionAt: this.frameState.lastPackageZoneMotionAt.get(input.deviceId) ?? null,
128360
128737
  reasons
128361
128738
  }
128362
128739
  });