@camstack/addon-post-analysis 1.2.664 → 1.2.666
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/{clip-model-registry-TeNUAAPP.js → clip-model-registry-BIhYJRFt.js} +324 -132
- package/dist/{clip-model-registry-Ch9Sz-Qb.mjs → clip-model-registry-Uc_C1x81.mjs} +319 -133
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CXG7YPYq.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-FkeZoNXj.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CnYZAdvw.mjs +26 -0
- 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
- package/dist/pipeline-analytics/{hostInit-BZ_D3_sF.mjs → hostInit-BBhgVoNl.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +430 -46
- package/dist/pipeline-analytics/index.mjs +430 -46
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CIt_RDlm.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-4TTlyJ66.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-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-BIhYJRFt.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");
|
|
@@ -7354,6 +7354,18 @@ function sensorEdgeOf(kind, value) {
|
|
|
7354
7354
|
if (HALTED.has(opening.state)) return "neither";
|
|
7355
7355
|
if (OPENING.has(opening.state)) return "opening";
|
|
7356
7356
|
}
|
|
7357
|
+
/**
|
|
7358
|
+
* The WORD a sensor change is told with — the state the device moved to, read off
|
|
7359
|
+
* the same slice the edge is: `unlocked`, `open`, `closed`… for a lock, cover, contact
|
|
7360
|
+
* or switch, else `raised` / `cleared` for a boolean sensor (water, smoke…). Undefined
|
|
7361
|
+
* when the slice cannot say: a sentence never invents a state (D393).
|
|
7362
|
+
*/
|
|
7363
|
+
function sensorStateWordOf(kind, value, raised) {
|
|
7364
|
+
const opening = alarmOpeningFromSensorEvent(kind, value);
|
|
7365
|
+
if (opening.kind === "reading") return opening.state;
|
|
7366
|
+
if (raised === void 0) return void 0;
|
|
7367
|
+
return raised ? "raised" : "cleared";
|
|
7368
|
+
}
|
|
7357
7369
|
//#endregion
|
|
7358
7370
|
//#region src/notification-center/audio-rule-matcher.ts
|
|
7359
7371
|
/**
|
|
@@ -7861,6 +7873,7 @@ function subjectFromSensorEvent(ev, markerTrackId, linked) {
|
|
|
7861
7873
|
const eventType = readSensorEventType(ev.value);
|
|
7862
7874
|
const sensorEdge = sensorEdgeOf(ev.kind, ev.value);
|
|
7863
7875
|
const sensorRaised = require_clip_model_registry.sensorRaisedOf(ev.kind, ev.value);
|
|
7876
|
+
const sensorState = sensorStateWordOf(ev.kind, ev.value, sensorRaised);
|
|
7864
7877
|
return {
|
|
7865
7878
|
kind: "device-event",
|
|
7866
7879
|
recordId: String(ev.id),
|
|
@@ -7875,6 +7888,7 @@ function subjectFromSensorEvent(ev, markerTrackId, linked) {
|
|
|
7875
7888
|
...eventType !== void 0 ? { eventType } : {},
|
|
7876
7889
|
...sensorEdge !== void 0 ? { sensorEdge } : {},
|
|
7877
7890
|
...sensorRaised !== void 0 ? { sensorRaised } : {},
|
|
7891
|
+
...sensorState !== void 0 ? { sensorState } : {},
|
|
7878
7892
|
...linked !== void 0 ? {
|
|
7879
7893
|
linkedSensorId: ev.sourceDeviceId,
|
|
7880
7894
|
linkedCameraIds: [...linked.linkedCameraIds],
|
|
@@ -9213,6 +9227,17 @@ var en_default = {
|
|
|
9213
9227
|
"alarm.combined.title": "Alarm: {{deviceName}}",
|
|
9214
9228
|
"alarm.intrusion.title": "Intrusion — {{locations}}",
|
|
9215
9229
|
"alarm.intrusion.body": "Intrusion: {{locations}}",
|
|
9230
|
+
"alarm.intrusion.body.withChanges": "Intrusion: {{locations}} — {{changes}}",
|
|
9231
|
+
"alarm.change.lock.unlocked": "{{device}} unlocked",
|
|
9232
|
+
"alarm.change.lock.unlocking": "{{device}} unlocking",
|
|
9233
|
+
"alarm.change.lock.jammed": "{{device}} jammed",
|
|
9234
|
+
"alarm.change.lock.door-open": "{{device}} open",
|
|
9235
|
+
"alarm.change.open": "{{device}} open",
|
|
9236
|
+
"alarm.change.opening": "{{device}} opening",
|
|
9237
|
+
"alarm.change.closed": "{{device}} closed",
|
|
9238
|
+
"alarm.change.on": "{{device}} on",
|
|
9239
|
+
"alarm.change.raised": "{{device}} triggered",
|
|
9240
|
+
"alarm.change.cleared": "{{device}} cleared",
|
|
9216
9241
|
"system.alarm-arm-refused.title.home": "Arming home refused",
|
|
9217
9242
|
"system.alarm-arm-refused.title.away": "Arming away refused",
|
|
9218
9243
|
"system.alarm-arm-refused.title.night": "Arming night refused",
|
|
@@ -9630,6 +9655,17 @@ var it_default = {
|
|
|
9630
9655
|
"alarm.combined.title": "Allarme: {{deviceName}}",
|
|
9631
9656
|
"alarm.intrusion.title": "Intrusione — {{locations}}",
|
|
9632
9657
|
"alarm.intrusion.body": "Intrusione: {{locations}}",
|
|
9658
|
+
"alarm.intrusion.body.withChanges": "Intrusione: {{locations}} — {{changes}}",
|
|
9659
|
+
"alarm.change.lock.unlocked": "{{device}} aperta",
|
|
9660
|
+
"alarm.change.lock.unlocking": "{{device}} in apertura",
|
|
9661
|
+
"alarm.change.lock.jammed": "{{device}} bloccata",
|
|
9662
|
+
"alarm.change.lock.door-open": "{{device}} aperta",
|
|
9663
|
+
"alarm.change.open": "{{device}} aperto",
|
|
9664
|
+
"alarm.change.opening": "{{device}} in apertura",
|
|
9665
|
+
"alarm.change.closed": "{{device}} chiuso",
|
|
9666
|
+
"alarm.change.on": "{{device}} acceso",
|
|
9667
|
+
"alarm.change.raised": "{{device}} attivato",
|
|
9668
|
+
"alarm.change.cleared": "{{device}} ripristinato",
|
|
9633
9669
|
"system.alarm-arm-refused.title.home": "Inserimento Casa rifiutato",
|
|
9634
9670
|
"system.alarm-arm-refused.title.away": "Inserimento Fuori rifiutato",
|
|
9635
9671
|
"system.alarm-arm-refused.title.night": "Inserimento Notte rifiutato",
|
|
@@ -12284,6 +12320,16 @@ var NcStateSummaryTestSchema = require_clip_model_registry.object({
|
|
|
12284
12320
|
}))
|
|
12285
12321
|
});
|
|
12286
12322
|
//#endregion
|
|
12323
|
+
//#region src/notification-center/template-defaults.ts
|
|
12324
|
+
function servedTemplateDefaults(texts) {
|
|
12325
|
+
return [{
|
|
12326
|
+
id: "appliance-cycle",
|
|
12327
|
+
kind: "sensor",
|
|
12328
|
+
title: texts.raw("appliance.finished.title") ?? "",
|
|
12329
|
+
body: texts.raw("appliance.finished.body") ?? ""
|
|
12330
|
+
}];
|
|
12331
|
+
}
|
|
12332
|
+
//#endregion
|
|
12287
12333
|
//#region src/notification-center/clock-text.ts
|
|
12288
12334
|
/**
|
|
12289
12335
|
* `{{time}}` — hours and minutes in the NOTIFICATION language.
|
|
@@ -13682,7 +13728,10 @@ var ncActions = require_clip_model_registry.defineCustomActions({
|
|
|
13682
13728
|
catalog: require_clip_model_registry.array(require_clip_model_registry.NcConditionDescriptorSchema),
|
|
13683
13729
|
taxonomy: require_clip_model_registry.NcTaxonomySchema
|
|
13684
13730
|
})),
|
|
13685
|
-
"nc.getTemplateCatalog": require_clip_model_registry.customAction(require_clip_model_registry.object({}), require_clip_model_registry.object({
|
|
13731
|
+
"nc.getTemplateCatalog": require_clip_model_registry.customAction(require_clip_model_registry.object({}), require_clip_model_registry.object({
|
|
13732
|
+
vars: require_clip_model_registry.array(require_clip_model_registry.NcTemplateVarDescriptorSchema),
|
|
13733
|
+
defaults: require_clip_model_registry.array(require_clip_model_registry.NcTemplateDefaultSchema)
|
|
13734
|
+
})),
|
|
13686
13735
|
"nc.previewTemplate": require_clip_model_registry.customAction(require_clip_model_registry.NcTemplatePreviewInputSchema, require_clip_model_registry.NcTemplatePreviewSchema, { kind: "mutation" }),
|
|
13687
13736
|
"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
13737
|
kind: "mutation",
|
|
@@ -14462,7 +14511,10 @@ function makeNcActionHandlers(deps) {
|
|
|
14462
14511
|
catalog: [...require_clip_model_registry.NC_CONDITION_CATALOG],
|
|
14463
14512
|
taxonomy: require_clip_model_registry.NC_TAXONOMY
|
|
14464
14513
|
}),
|
|
14465
|
-
"nc.getTemplateCatalog": async () => ({
|
|
14514
|
+
"nc.getTemplateCatalog": async () => ({
|
|
14515
|
+
vars: require_clip_model_registry.servedTemplateVars(),
|
|
14516
|
+
defaults: deps.previewTexts === void 0 ? [] : servedTemplateDefaults(deps.previewTexts)
|
|
14517
|
+
}),
|
|
14466
14518
|
"nc.previewTemplate": async (input) => {
|
|
14467
14519
|
const texts = deps.previewTexts;
|
|
14468
14520
|
if (texts === void 0) throw new Error("nc.previewTemplate: text catalog not wired");
|
|
@@ -49951,7 +50003,7 @@ async function encodeRgbCropToJpeg(bytes, width, height) {
|
|
|
49951
50003
|
* what lets the caller stop base64-ing purely to be understood.
|
|
49952
50004
|
*/
|
|
49953
50005
|
async function decodeJpegToRgb(jpeg) {
|
|
49954
|
-
const { data, info } = await (0, sharp.default)(typeof jpeg === "string" ? Buffer.from(jpeg, "base64") : jpeg).raw().toBuffer({ resolveWithObject: true });
|
|
50006
|
+
const { data, info } = await (0, sharp.default)(typeof jpeg === "string" ? Buffer.from(jpeg, "base64") : jpeg).removeAlpha().toColourspace("srgb").raw().toBuffer({ resolveWithObject: true });
|
|
49955
50007
|
return {
|
|
49956
50008
|
bytes: data,
|
|
49957
50009
|
width: info.width,
|
|
@@ -53037,6 +53089,42 @@ function collectPackageZoneIds(rules) {
|
|
|
53037
53089
|
}
|
|
53038
53090
|
return ids;
|
|
53039
53091
|
}
|
|
53092
|
+
/**
|
|
53093
|
+
* How much the package zone box grows on every side when asking whether motion
|
|
53094
|
+
* reached it. Mirrors the D144 dilation of the pipeline's burst trigger
|
|
53095
|
+
* (`PACKAGE_ZONE_DILATION` in addon-pipeline, not importable from here): about
|
|
53096
|
+
* one adult's width beside a doormat, the place a courier stands.
|
|
53097
|
+
*/
|
|
53098
|
+
var PACKAGE_ZONE_MOTION_DILATION = .5;
|
|
53099
|
+
/**
|
|
53100
|
+
* A motion region covering more than this fraction of the frame is a light
|
|
53101
|
+
* change or the weather, not a person: it never counts as a deliverer.
|
|
53102
|
+
*/
|
|
53103
|
+
var PACKAGE_ZONE_MOTION_MAX_REGION_FRAC = .8;
|
|
53104
|
+
/**
|
|
53105
|
+
* Did analyzer motion reach the dilated package zone with a person-sized
|
|
53106
|
+
* region? Unknown geometry answers false: this stamp only ever BACKS the open
|
|
53107
|
+
* of a candidate, so failing closed keeps the light-wedge guard intact.
|
|
53108
|
+
*/
|
|
53109
|
+
function delivererMotionNearPackageZone(input) {
|
|
53110
|
+
const { zoneRect, frameWidth, frameHeight } = input;
|
|
53111
|
+
if (zoneRect === null || frameWidth <= 0 || frameHeight <= 0) return false;
|
|
53112
|
+
const dx = zoneRect.w * PACKAGE_ZONE_MOTION_DILATION;
|
|
53113
|
+
const dy = zoneRect.h * PACKAGE_ZONE_MOTION_DILATION;
|
|
53114
|
+
const left = Math.max(0, zoneRect.x - dx);
|
|
53115
|
+
const top = Math.max(0, zoneRect.y - dy);
|
|
53116
|
+
const right = Math.min(1, zoneRect.x + zoneRect.w + dx);
|
|
53117
|
+
const bottom = Math.min(1, zoneRect.y + zoneRect.h + dy);
|
|
53118
|
+
for (const region of input.regions) {
|
|
53119
|
+
const x = region.bbox.x / frameWidth;
|
|
53120
|
+
const y = region.bbox.y / frameHeight;
|
|
53121
|
+
const w = region.bbox.w / frameWidth;
|
|
53122
|
+
const h = region.bbox.h / frameHeight;
|
|
53123
|
+
if (w * h > PACKAGE_ZONE_MOTION_MAX_REGION_FRAC) continue;
|
|
53124
|
+
if (x < right && left < x + w && y < bottom && top < y + h) return true;
|
|
53125
|
+
}
|
|
53126
|
+
return false;
|
|
53127
|
+
}
|
|
53040
53128
|
//#endregion
|
|
53041
53129
|
//#region src/pipeline-analytics/pipeline/package/package-collector-mirror.ts
|
|
53042
53130
|
/**
|
|
@@ -53982,6 +54070,15 @@ var PACKAGE_STILLNESS_TTL_MS = 5 * 6e4;
|
|
|
53982
54070
|
*/
|
|
53983
54071
|
var PACKAGE_STILLNESS_ASSOC_IOU = .3;
|
|
53984
54072
|
/**
|
|
54073
|
+
* Lowest score of a below-threshold box that may CONTINUE an open candidate
|
|
54074
|
+
* (D665: a box under the floor may continue, never open). Observed on a real
|
|
54075
|
+
* parcel after the day to IR flip: 0.35 to 0.50.
|
|
54076
|
+
*/
|
|
54077
|
+
var PACKAGE_STILLNESS_CONTINUATION_MIN_SCORE = .3;
|
|
54078
|
+
/** Above-floor sightings a candidate needs before a sub-floor box may count:
|
|
54079
|
+
* one confident look plus a doormat-shaped reading is not a parcel. */
|
|
54080
|
+
var PACKAGE_CONTINUATION_MIN_REAL_HITS = 2;
|
|
54081
|
+
/**
|
|
53985
54082
|
* Two absence reads closer than this are ONE look, not two. A settle burst
|
|
53986
54083
|
* runs the package step 5 s apart and a poll 45 s apart, so a real second
|
|
53987
54084
|
* look is always further away; two frames of one moment (a re-dispatch, a
|
|
@@ -54031,6 +54128,7 @@ var PackageStillnessTracker = class {
|
|
|
54031
54128
|
const next = [...this.byDevice.get(input.deviceId) ?? []];
|
|
54032
54129
|
const consumed = /* @__PURE__ */ new Set();
|
|
54033
54130
|
const promoted = [];
|
|
54131
|
+
const continuedLog = [];
|
|
54034
54132
|
for (const s of input.sightings) {
|
|
54035
54133
|
const index = this.bestMatch(next, s, consumed);
|
|
54036
54134
|
if (index < 0) {
|
|
@@ -54043,17 +54141,33 @@ var PackageStillnessTracker = class {
|
|
|
54043
54141
|
if (prev === void 0) continue;
|
|
54044
54142
|
consumed.add(prev.id);
|
|
54045
54143
|
const updated = confirm(prev, input, s);
|
|
54046
|
-
const
|
|
54047
|
-
|
|
54048
|
-
|
|
54049
|
-
|
|
54050
|
-
|
|
54051
|
-
const
|
|
54052
|
-
|
|
54053
|
-
|
|
54144
|
+
const parked = this.promoteWhenReady(updated, input);
|
|
54145
|
+
next[index] = parked ?? updated;
|
|
54146
|
+
if (parked !== null) promoted.push(toEntry(parked));
|
|
54147
|
+
}
|
|
54148
|
+
if (input.looked === true && input.continuationBoxes !== void 0) for (let i = 0; i < next.length; i += 1) {
|
|
54149
|
+
const c = next[i];
|
|
54150
|
+
if (c === void 0 || c.promotedAt !== null || consumed.has(c.id)) continue;
|
|
54151
|
+
if (c.realHits < PACKAGE_CONTINUATION_MIN_REAL_HITS) continue;
|
|
54152
|
+
const box = this.continuationFor(c, input.continuationBoxes);
|
|
54153
|
+
if (box === null) continue;
|
|
54154
|
+
consumed.add(c.id);
|
|
54155
|
+
const continued = {
|
|
54156
|
+
...c,
|
|
54157
|
+
lastSeenAt: input.timestamp,
|
|
54158
|
+
hits: c.hits + 1,
|
|
54159
|
+
absenceReads: [],
|
|
54160
|
+
lastLook: "present",
|
|
54161
|
+
earlyUnbackedReported: false
|
|
54054
54162
|
};
|
|
54055
|
-
|
|
54056
|
-
|
|
54163
|
+
continuedLog.push({
|
|
54164
|
+
id: c.id,
|
|
54165
|
+
hits: continued.hits,
|
|
54166
|
+
score: box.score
|
|
54167
|
+
});
|
|
54168
|
+
const parked = this.promoteWhenReady(continued, input);
|
|
54169
|
+
next[i] = parked ?? continued;
|
|
54170
|
+
if (parked !== null) promoted.push(toEntry(parked));
|
|
54057
54171
|
}
|
|
54058
54172
|
const present = input.rawPackageBoxes !== void 0 ? withRawPresence(next, consumed, input.rawPackageBoxes, input.timestamp, this.config.assocIou) : next;
|
|
54059
54173
|
const looked = input.looked === true && input.rawPackageBoxes !== void 0 ? withLook(present, consumed, input, input.rawPackageBoxes, this.config.assocIou) : present;
|
|
@@ -54089,7 +54203,8 @@ var PackageStillnessTracker = class {
|
|
|
54089
54203
|
...input.lastActivityAt !== void 0 ? { lastActivityAt: input.lastActivityAt } : {}
|
|
54090
54204
|
}),
|
|
54091
54205
|
earlyDepartureIds: [...early],
|
|
54092
|
-
earlyEvidenceUnbacked: unbacked
|
|
54206
|
+
earlyEvidenceUnbacked: unbacked,
|
|
54207
|
+
continued: continuedLog
|
|
54093
54208
|
};
|
|
54094
54209
|
}
|
|
54095
54210
|
/** Why complete early evidence may NOT conclude, or null when it may. */
|
|
@@ -54116,7 +54231,8 @@ var PackageStillnessTracker = class {
|
|
|
54116
54231
|
...input.lastActivityAt !== void 0 ? { lastActivityAt: input.lastActivityAt } : {}
|
|
54117
54232
|
}),
|
|
54118
54233
|
earlyDepartureIds: [],
|
|
54119
|
-
earlyEvidenceUnbacked: []
|
|
54234
|
+
earlyEvidenceUnbacked: [],
|
|
54235
|
+
continued: []
|
|
54120
54236
|
};
|
|
54121
54237
|
}
|
|
54122
54238
|
/** The devices holding a candidate — what a sweep has to visit. */
|
|
@@ -54180,6 +54296,43 @@ var PackageStillnessTracker = class {
|
|
|
54180
54296
|
/** Best same-class candidate over the association floor, ignoring any already
|
|
54181
54297
|
* consumed by an earlier sighting in THIS frame (two boxes never collapse
|
|
54182
54298
|
* into one parcel). */
|
|
54299
|
+
continuationFor(c, boxes) {
|
|
54300
|
+
let best = null;
|
|
54301
|
+
let bestIou = this.config.assocIou;
|
|
54302
|
+
for (const b of boxes) {
|
|
54303
|
+
if (b.score < PACKAGE_STILLNESS_CONTINUATION_MIN_SCORE) continue;
|
|
54304
|
+
const overlap = iou$2(c.bbox, b.bbox);
|
|
54305
|
+
if (overlap >= bestIou) {
|
|
54306
|
+
bestIou = overlap;
|
|
54307
|
+
best = b;
|
|
54308
|
+
}
|
|
54309
|
+
}
|
|
54310
|
+
return best;
|
|
54311
|
+
}
|
|
54312
|
+
promoteWhenReady(candidate, input) {
|
|
54313
|
+
const updated = this.backedLate(candidate, input);
|
|
54314
|
+
const minHits = input.minHits ?? this.config.minHits;
|
|
54315
|
+
return updated.promotedAt === null && updated.activityBackedAtOpen && updated.hits >= minHits && input.timestamp - updated.firstSeenAt >= input.minDwellMs ? {
|
|
54316
|
+
...updated,
|
|
54317
|
+
promotedAt: input.timestamp
|
|
54318
|
+
} : null;
|
|
54319
|
+
}
|
|
54320
|
+
/**
|
|
54321
|
+
* Zone motion learned AFTER the open still backs the candidate when its stamp
|
|
54322
|
+
* lies in the window around the FIRST sighting (the same slack a pick-up
|
|
54323
|
+
* gets): the backing stays anchored at the open, a later passer-by never
|
|
54324
|
+
* validates scenery (D1003).
|
|
54325
|
+
*/
|
|
54326
|
+
backedLate(c, input) {
|
|
54327
|
+
if (c.activityBackedAtOpen || c.promotedAt !== null) return c;
|
|
54328
|
+
const at = input.lastDelivererMotionAt;
|
|
54329
|
+
const windowMs = input.activityWindowMs ?? 0;
|
|
54330
|
+
if (at === void 0 || windowMs <= 0) return c;
|
|
54331
|
+
return at >= c.firstSeenAt - windowMs && at <= c.firstSeenAt + this.config.roundGapMs ? {
|
|
54332
|
+
...c,
|
|
54333
|
+
activityBackedAtOpen: true
|
|
54334
|
+
} : c;
|
|
54335
|
+
}
|
|
54183
54336
|
bestMatch(candidates, s, consumed) {
|
|
54184
54337
|
let bestIndex = -1;
|
|
54185
54338
|
let bestIou = this.config.assocIou;
|
|
@@ -54196,7 +54349,7 @@ var PackageStillnessTracker = class {
|
|
|
54196
54349
|
}
|
|
54197
54350
|
openCandidate(input, s) {
|
|
54198
54351
|
const windowMs = input.activityWindowMs ?? 0;
|
|
54199
|
-
const activityBackedAtOpen = windowMs <= 0 || input.lastActivityAt !== void 0 && input.timestamp - input.lastActivityAt <= windowMs;
|
|
54352
|
+
const activityBackedAtOpen = windowMs <= 0 || input.lastActivityAt !== void 0 && input.timestamp - input.lastActivityAt <= windowMs || input.lastDelivererMotionAt !== void 0 && input.timestamp - input.lastDelivererMotionAt <= windowMs;
|
|
54200
54353
|
return {
|
|
54201
54354
|
id: this.newId(),
|
|
54202
54355
|
deviceId: input.deviceId,
|
|
@@ -54207,6 +54360,7 @@ var PackageStillnessTracker = class {
|
|
|
54207
54360
|
firstSeenAt: input.timestamp,
|
|
54208
54361
|
lastSeenAt: input.timestamp,
|
|
54209
54362
|
hits: 1,
|
|
54363
|
+
realHits: 1,
|
|
54210
54364
|
promotedAt: null,
|
|
54211
54365
|
absenceReads: [],
|
|
54212
54366
|
lastLook: null,
|
|
@@ -54229,6 +54383,7 @@ function confirm(prev, input, s) {
|
|
|
54229
54383
|
frameHeight: input.frameHeight,
|
|
54230
54384
|
lastSeenAt: input.timestamp,
|
|
54231
54385
|
hits: prev.hits + 1,
|
|
54386
|
+
realHits: prev.realHits + 1,
|
|
54232
54387
|
absenceReads: [],
|
|
54233
54388
|
lastLook: "present",
|
|
54234
54389
|
earlyUnbackedReported: false,
|
|
@@ -54765,6 +54920,9 @@ var FrameState = class {
|
|
|
54765
54920
|
* which would let the streetlight wedge vouch for its own delivery.
|
|
54766
54921
|
*/
|
|
54767
54922
|
lastNonPackageActivityAt = /* @__PURE__ */ new Map();
|
|
54923
|
+
/** Last analyzer motion near a package drop-off zone, per device (D1003).
|
|
54924
|
+
* Backs the OPEN of a candidate only; never a pick-up witness. */
|
|
54925
|
+
lastPackageZoneMotionAt = /* @__PURE__ */ new Map();
|
|
54768
54926
|
/**
|
|
54769
54927
|
* WHO was in the drop-off zone, and when (D404). The pick-up is CONCLUDED
|
|
54770
54928
|
* minutes after the fact, so the collector must be remembered while it is
|
|
@@ -58124,6 +58282,7 @@ function nextPackageLookDropEpisodes(previous, drops) {
|
|
|
58124
58282
|
//#region src/pipeline-analytics/pipeline/package/package-look-evidence.ts
|
|
58125
58283
|
function packageLookEvidence(input) {
|
|
58126
58284
|
const raw = [];
|
|
58285
|
+
const continuation = [];
|
|
58127
58286
|
for (const d of input.frame.detections) {
|
|
58128
58287
|
if (d.kind !== "first-level" || !input.packageClasses.has(d.macroClass)) continue;
|
|
58129
58288
|
raw.push({
|
|
@@ -58136,11 +58295,16 @@ function packageLookEvidence(input) {
|
|
|
58136
58295
|
for (const d of input.frame.discarded ?? []) {
|
|
58137
58296
|
if (!input.packageClasses.has(d.macroClass)) continue;
|
|
58138
58297
|
const [x1, y1, x2, y2] = d.bbox;
|
|
58139
|
-
|
|
58298
|
+
const box = {
|
|
58140
58299
|
x: x1,
|
|
58141
58300
|
y: y1,
|
|
58142
58301
|
w: x2 - x1,
|
|
58143
58302
|
h: y2 - y1
|
|
58303
|
+
};
|
|
58304
|
+
raw.push(box);
|
|
58305
|
+
if (d.reason === "below-threshold") continuation.push({
|
|
58306
|
+
bbox: box,
|
|
58307
|
+
score: d.score
|
|
58144
58308
|
});
|
|
58145
58309
|
}
|
|
58146
58310
|
return {
|
|
@@ -58152,6 +58316,7 @@ function packageLookEvidence(input) {
|
|
|
58152
58316
|
h: t.bbox.h
|
|
58153
58317
|
})),
|
|
58154
58318
|
rawPackageBoxes: raw,
|
|
58319
|
+
continuationBoxes: continuation,
|
|
58155
58320
|
earlyPickup: input.earlyPickupEnabled
|
|
58156
58321
|
};
|
|
58157
58322
|
}
|
|
@@ -58339,6 +58504,13 @@ function clamp01(value) {
|
|
|
58339
58504
|
}
|
|
58340
58505
|
//#endregion
|
|
58341
58506
|
//#region src/pipeline-analytics/pipeline/package/package-scene-diff.ts
|
|
58507
|
+
/**
|
|
58508
|
+
* Zone-crop scene difference for a packages rule (D931).
|
|
58509
|
+
*
|
|
58510
|
+
* A uniform brightness shift is removed by subtracting the median luma delta,
|
|
58511
|
+
* so a shadow that lifts the whole crop leaves no blob. What remains is a
|
|
58512
|
+
* connected region. The package model is not consulted here.
|
|
58513
|
+
*/
|
|
58342
58514
|
/** Residual, after the median shift, that counts as a changed pixel. */
|
|
58343
58515
|
var PACKAGE_SCENE_DIFF_LUMA_THRESHOLD = 28;
|
|
58344
58516
|
/** A changed region this large, relative to the zone, is the zone itself. */
|
|
@@ -58353,12 +58525,26 @@ var PACKAGE_SCENE_DIFF_SETTLE_MS = 3e3;
|
|
|
58353
58525
|
var PACKAGE_SCENE_DIFF_LOOK_GAP_MS = 2e3;
|
|
58354
58526
|
/** Confidence stamped on a scene-diff sighting the model did not box. */
|
|
58355
58527
|
var PACKAGE_SCENE_DIFF_CONFIDENCE = .5;
|
|
58528
|
+
/** True when the crop was shot in infrared (no colour). */
|
|
58529
|
+
function isIrCrop(crop) {
|
|
58530
|
+
return crop.chromaSpread < IR_CHROMA_THRESHOLD;
|
|
58531
|
+
}
|
|
58356
58532
|
function diffZoneLuma(input) {
|
|
58357
58533
|
const { before, after } = input;
|
|
58358
58534
|
if (before.width !== after.width || before.height !== after.height || before.luma.length !== after.luma.length) return {
|
|
58359
58535
|
blobs: [],
|
|
58360
58536
|
rejections: [{ reason: "crop-mismatch" }]
|
|
58361
58537
|
};
|
|
58538
|
+
if (isIrCrop(before) !== isIrCrop(after)) return {
|
|
58539
|
+
blobs: [],
|
|
58540
|
+
rejections: [{
|
|
58541
|
+
reason: "lighting-mode-changed",
|
|
58542
|
+
chroma: {
|
|
58543
|
+
before: before.chromaSpread,
|
|
58544
|
+
after: after.chromaSpread
|
|
58545
|
+
}
|
|
58546
|
+
}]
|
|
58547
|
+
};
|
|
58362
58548
|
const median = medianDelta(before.luma, after.luma);
|
|
58363
58549
|
const mask = residualMask(before.luma, after.luma, median);
|
|
58364
58550
|
const zonePx = before.width * before.height;
|
|
@@ -58573,6 +58759,7 @@ function okCrop(rgb, width, height, jpeg) {
|
|
|
58573
58759
|
width,
|
|
58574
58760
|
height,
|
|
58575
58761
|
luma: lumaFromRgb(rgb, width, height),
|
|
58762
|
+
chromaSpread: chromaSpreadFromRgb(rgb, width, height),
|
|
58576
58763
|
jpeg: new Uint8Array(jpeg)
|
|
58577
58764
|
};
|
|
58578
58765
|
}
|
|
@@ -58589,6 +58776,46 @@ function lumaFromRgb(bytes, width, height) {
|
|
|
58589
58776
|
}
|
|
58590
58777
|
return luma;
|
|
58591
58778
|
}
|
|
58779
|
+
/** The grid `IR_CHROMA_THRESHOLD` was calibrated on (`chromaSpread`, scene-crop.ts). */
|
|
58780
|
+
var CHROMA_GRID = 32;
|
|
58781
|
+
/**
|
|
58782
|
+
* Mean of max(r,g,b) - min(r,g,b), 0..255, measured on a 32x32 box-averaged
|
|
58783
|
+
* copy of the crop: the same measure and grid as `chromaSpread` in
|
|
58784
|
+
* `scene-crop.ts` (which needs sharp), so `IR_CHROMA_THRESHOLD` means the same
|
|
58785
|
+
* thing. Averaging first matters: sensor noise and JPEG chroma speckle on an IR
|
|
58786
|
+
* frame cancel out, where per-pixel they read as colour.
|
|
58787
|
+
*/
|
|
58788
|
+
function chromaSpreadFromRgb(bytes, width, height) {
|
|
58789
|
+
if (width <= 0 || height <= 0 || bytes.length < width * height * 3) return 0;
|
|
58790
|
+
const gw = Math.min(CHROMA_GRID, width);
|
|
58791
|
+
const gh = Math.min(CHROMA_GRID, height);
|
|
58792
|
+
const sum = new Float64Array(gw * gh * 3);
|
|
58793
|
+
const count = new Float64Array(gw * gh);
|
|
58794
|
+
for (let y = 0; y < height; y++) {
|
|
58795
|
+
const cy = Math.min(gh - 1, Math.floor(y * gh / height));
|
|
58796
|
+
for (let x = 0; x < width; x++) {
|
|
58797
|
+
const cx = Math.min(gw - 1, Math.floor(x * gw / width));
|
|
58798
|
+
const cell = cy * gw + cx;
|
|
58799
|
+
const o = (y * width + x) * 3;
|
|
58800
|
+
sum[cell * 3] = (sum[cell * 3] ?? 0) + (bytes[o] ?? 0);
|
|
58801
|
+
sum[cell * 3 + 1] = (sum[cell * 3 + 1] ?? 0) + (bytes[o + 1] ?? 0);
|
|
58802
|
+
sum[cell * 3 + 2] = (sum[cell * 3 + 2] ?? 0) + (bytes[o + 2] ?? 0);
|
|
58803
|
+
count[cell] = (count[cell] ?? 0) + 1;
|
|
58804
|
+
}
|
|
58805
|
+
}
|
|
58806
|
+
let total = 0;
|
|
58807
|
+
let cells = 0;
|
|
58808
|
+
for (let cell = 0; cell < gw * gh; cell++) {
|
|
58809
|
+
const n = count[cell] ?? 0;
|
|
58810
|
+
if (n === 0) continue;
|
|
58811
|
+
const r = (sum[cell * 3] ?? 0) / n;
|
|
58812
|
+
const g = (sum[cell * 3 + 1] ?? 0) / n;
|
|
58813
|
+
const b = (sum[cell * 3 + 2] ?? 0) / n;
|
|
58814
|
+
total += Math.max(r, g, b) - Math.min(r, g, b);
|
|
58815
|
+
cells += 1;
|
|
58816
|
+
}
|
|
58817
|
+
return cells === 0 ? 0 : total / cells;
|
|
58818
|
+
}
|
|
58592
58819
|
//#endregion
|
|
58593
58820
|
//#region src/pipeline-analytics/pipeline/package/package-scene-step.ts
|
|
58594
58821
|
/**
|
|
@@ -58688,6 +58915,14 @@ function compareAfterLeave(prev, input) {
|
|
|
58688
58915
|
minAreaFrac: input.minAreaFrac,
|
|
58689
58916
|
occluders: input.occluders
|
|
58690
58917
|
});
|
|
58918
|
+
if (diff.rejections.some((r) => r.reason === "lighting-mode-changed")) return {
|
|
58919
|
+
state: {
|
|
58920
|
+
...rememberClear(prev, input).state,
|
|
58921
|
+
lastDiffAt: input.timestamp
|
|
58922
|
+
},
|
|
58923
|
+
sightings: [],
|
|
58924
|
+
rejections: diff.rejections
|
|
58925
|
+
};
|
|
58691
58926
|
const blocked = diff.rejections.some((r) => r.reason === "overlaps-person" || r.reason === "overlaps-vehicle");
|
|
58692
58927
|
if (diff.blobs.length === 0) {
|
|
58693
58928
|
if (blocked) return {
|
|
@@ -58731,6 +58966,20 @@ function confirmPending(prev, input) {
|
|
|
58731
58966
|
sightings: [],
|
|
58732
58967
|
rejections: []
|
|
58733
58968
|
};
|
|
58969
|
+
if (isIrCrop(prev.after) !== isIrCrop(input.crop)) return {
|
|
58970
|
+
state: {
|
|
58971
|
+
...rememberClear(prev, input).state,
|
|
58972
|
+
lastDiffAt: input.timestamp
|
|
58973
|
+
},
|
|
58974
|
+
sightings: [],
|
|
58975
|
+
rejections: [{
|
|
58976
|
+
reason: "lighting-mode-changed",
|
|
58977
|
+
chroma: {
|
|
58978
|
+
before: prev.after.chromaSpread,
|
|
58979
|
+
after: input.crop.chromaSpread
|
|
58980
|
+
}
|
|
58981
|
+
}]
|
|
58982
|
+
};
|
|
58734
58983
|
const sightings = [];
|
|
58735
58984
|
const rejections = [];
|
|
58736
58985
|
const still = [];
|
|
@@ -58777,7 +59026,7 @@ function judgeBlob(prev, input, blob) {
|
|
|
58777
59026
|
bbox: blob.bbox
|
|
58778
59027
|
} };
|
|
58779
59028
|
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;
|
|
59029
|
+
const present = isIrCrop(before) !== isIrCrop(crop) || meanAbsInBox(before, crop, blob.bbox) >= PACKAGE_SCENE_DIFF_LUMA_THRESHOLD;
|
|
58781
59030
|
if (!stable || !present) return { rejection: {
|
|
58782
59031
|
reason: "unstable",
|
|
58783
59032
|
bbox: blob.bbox,
|
|
@@ -58945,7 +59194,8 @@ var PackageSceneCoordinator = class {
|
|
|
58945
59194
|
const crop = {
|
|
58946
59195
|
width: fetched.width,
|
|
58947
59196
|
height: fetched.height,
|
|
58948
|
-
luma: fetched.luma
|
|
59197
|
+
luma: fetched.luma,
|
|
59198
|
+
chromaSpread: fetched.chromaSpread
|
|
58949
59199
|
};
|
|
58950
59200
|
const stepped = stepPackageScene({
|
|
58951
59201
|
deviceId,
|
|
@@ -59011,6 +59261,10 @@ var PackageSceneCoordinator = class {
|
|
|
59011
59261
|
tags: { deviceId },
|
|
59012
59262
|
meta: {
|
|
59013
59263
|
reason: rejection.reason,
|
|
59264
|
+
...rejection.chroma !== void 0 ? {
|
|
59265
|
+
chromaBefore: Number(rejection.chroma.before.toFixed(2)),
|
|
59266
|
+
chromaAfter: Number(rejection.chroma.after.toFixed(2))
|
|
59267
|
+
} : {},
|
|
59014
59268
|
...rejection.areaFrac !== void 0 ? { areaFrac: Number(rejection.areaFrac.toFixed(5)) } : {}
|
|
59015
59269
|
}
|
|
59016
59270
|
});
|
|
@@ -59119,14 +59373,8 @@ function planPackageSceneFrame(deviceId, input) {
|
|
|
59119
59373
|
frameWidth: input.frameWidth,
|
|
59120
59374
|
frameHeight: input.frameHeight
|
|
59121
59375
|
});
|
|
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
59376
|
return {
|
|
59129
|
-
sightings:
|
|
59377
|
+
sightings: input.sightings,
|
|
59130
59378
|
note: {
|
|
59131
59379
|
deviceId,
|
|
59132
59380
|
timestamp: input.timestamp,
|
|
@@ -59169,15 +59417,6 @@ function occludersOf(tracked) {
|
|
|
59169
59417
|
}
|
|
59170
59418
|
return out;
|
|
59171
59419
|
}
|
|
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
59420
|
//#endregion
|
|
59182
59421
|
//#region src/pipeline-analytics/pipeline/package/package-llm-confirm.ts
|
|
59183
59422
|
/**
|
|
@@ -71157,6 +71396,7 @@ Object.freeze({
|
|
|
71157
71396
|
"addon-pages-source": ["listPages"],
|
|
71158
71397
|
"addon-routes": ["getRoutes"],
|
|
71159
71398
|
"addon-widgets-source": ["listWidgets"],
|
|
71399
|
+
"board-key-source": ["listSources"],
|
|
71160
71400
|
"broker": ["list", "listProviders"],
|
|
71161
71401
|
"calendar-source": ["listCalendars", "listEvents"],
|
|
71162
71402
|
"custom-model-registry": ["listModels"],
|
|
@@ -71287,8 +71527,8 @@ Object.freeze({
|
|
|
71287
71527
|
* ## Caps with no boolean level pass through untouched
|
|
71288
71528
|
*
|
|
71289
71529
|
* `EVENT_KIND_BY_CAP` covers sixteen caps; `SOURCE_CAP_ACTIVE_FIELD` covers the
|
|
71290
|
-
* ten that have a boolean whose edge means something. `button`, `presence
|
|
71291
|
-
*
|
|
71530
|
+
* ten that have a boolean whose edge means something. `button`, `presence`
|
|
71531
|
+
* and `event-emitter` are NOT levels (`enum-sensor` is a stampless word level, see `UNSTAMPED_WORD_FIELD`) — their ARRIVAL is the
|
|
71292
71532
|
* event, and three presses in a row are three markers. Gating them would
|
|
71293
71533
|
* re-ship the failure of 2026-08-08, when camera 101's press produced a row, a
|
|
71294
71534
|
* ring, a matched rule and no marker. (`doorbell` is not a level either, but
|
|
@@ -71414,6 +71654,15 @@ var WORD_LEVEL_FIELD = {
|
|
|
71414
71654
|
* running for an hour at boot did not just start).
|
|
71415
71655
|
*/
|
|
71416
71656
|
var BOOLEAN_LEVEL_FIELD = { "running-state": "running" };
|
|
71657
|
+
/**
|
|
71658
|
+
* Caps whose level is a WORD with NO change stamp: cap → field. `enum-sensor`
|
|
71659
|
+
* carries `{ value, lastFetchedAt }`, and `lastFetchedAt` moves on every
|
|
71660
|
+
* provider refresh, so it says nothing about when the word changed. Gated by
|
|
71661
|
+
* the word alone: the first sighting seeds, only a different word passes, and
|
|
71662
|
+
* a null / missing value is dropped and never remembered (planning#396: such
|
|
71663
|
+
* slices passed as `changed` many times a minute and could match an NC rule).
|
|
71664
|
+
*/
|
|
71665
|
+
var UNSTAMPED_WORD_FIELD = { "enum-sensor": "value" };
|
|
71417
71666
|
/** The word a word-level cap rests in — its `false`, for the first-sighting rule. */
|
|
71418
71667
|
var WORD_LEVEL_INACTIVE = {
|
|
71419
71668
|
cover: "closed",
|
|
@@ -71466,6 +71715,8 @@ var SensorTransitionGate = class {
|
|
|
71466
71715
|
*/
|
|
71467
71716
|
observe(deviceId, capName, slice) {
|
|
71468
71717
|
if (slice["lastChangedAt"] === 0) return this.drop(deviceId, capName, "cold-slice");
|
|
71718
|
+
const unstampedField = UNSTAMPED_WORD_FIELD[capName];
|
|
71719
|
+
if (unstampedField !== void 0) return this.observeUnstampedWord(deviceId, capName, slice, unstampedField);
|
|
71469
71720
|
const wordField = WORD_LEVEL_FIELD[capName];
|
|
71470
71721
|
if (wordField !== void 0) return this.observeWord(deviceId, capName, slice, wordField);
|
|
71471
71722
|
const levelField = BOOLEAN_LEVEL_FIELD[capName];
|
|
@@ -71536,11 +71787,25 @@ var SensorTransitionGate = class {
|
|
|
71536
71787
|
observeWord(deviceId, capName, slice, wordField) {
|
|
71537
71788
|
const raw = slice[wordField];
|
|
71538
71789
|
if (typeof raw !== "string" || raw.length === 0) return this.drop(deviceId, capName, "non-word-value");
|
|
71790
|
+
const stamp = slice["lastChangedAt"];
|
|
71791
|
+
if (typeof stamp !== "number" || !Number.isFinite(stamp) || stamp <= 0) return this.drop(deviceId, capName, "cold-slice");
|
|
71539
71792
|
const key = `${deviceId}:${capName}`;
|
|
71540
71793
|
const prior = this.lastWords.get(key);
|
|
71541
71794
|
this.lastWords.set(key, raw);
|
|
71542
71795
|
if (prior === void 0) return this.firstWordSighting(deviceId, capName, slice, raw);
|
|
71543
71796
|
if (prior === raw) return this.drop(deviceId, capName, "no-change");
|
|
71797
|
+
if (stamp < this.startedAtMs) return this.drop(deviceId, capName, "baseline-seeded-stale-active");
|
|
71798
|
+
return { changed: true };
|
|
71799
|
+
}
|
|
71800
|
+
/** A word-level cap with no change stamp: only a DIFFERENT word is a transition. */
|
|
71801
|
+
observeUnstampedWord(deviceId, capName, slice, field) {
|
|
71802
|
+
const raw = slice[field];
|
|
71803
|
+
if (typeof raw !== "string" || raw.length === 0) return this.drop(deviceId, capName, "non-word-value");
|
|
71804
|
+
const key = `${deviceId}:${capName}`;
|
|
71805
|
+
const prior = this.lastWords.get(key);
|
|
71806
|
+
this.lastWords.set(key, raw);
|
|
71807
|
+
if (prior === void 0) return this.drop(deviceId, capName, "baseline-seeded-inactive");
|
|
71808
|
+
if (prior === raw) return this.drop(deviceId, capName, "no-change");
|
|
71544
71809
|
return { changed: true };
|
|
71545
71810
|
}
|
|
71546
71811
|
/**
|
|
@@ -72685,7 +72950,7 @@ function buildGlobalSettingsSchema(cap) {
|
|
|
72685
72950
|
type: "slider",
|
|
72686
72951
|
key: "packageDropActivityWindowSec",
|
|
72687
72952
|
label: "Deliverer window",
|
|
72688
|
-
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.",
|
|
72953
|
+
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.",
|
|
72689
72954
|
min: 0,
|
|
72690
72955
|
max: 1800,
|
|
72691
72956
|
step: 30,
|
|
@@ -81616,11 +81881,11 @@ function unique(values) {
|
|
|
81616
81881
|
* therefore keeps the alarm's own sentence, which is also what every row
|
|
81617
81882
|
* enqueued before this deploy carries.
|
|
81618
81883
|
*/
|
|
81619
|
-
function combinedAlarmText(kind, source) {
|
|
81884
|
+
function combinedAlarmText(kind, source, followers = []) {
|
|
81620
81885
|
if (kind !== "alarm-triggered" || source === void 0) return null;
|
|
81621
81886
|
return {
|
|
81622
81887
|
titleKey: source.className !== void 0 ? "alarm.combined.title" : "system.alarm-triggered.title",
|
|
81623
|
-
bodyKey: "alarm.intrusion.body"
|
|
81888
|
+
bodyKey: [source, ...followers].some((ref) => ref.sensorChange !== void 0) ? "alarm.intrusion.body.withChanges" : "alarm.intrusion.body"
|
|
81624
81889
|
};
|
|
81625
81890
|
}
|
|
81626
81891
|
/**
|
|
@@ -81635,7 +81900,7 @@ function combinedAlarmText(kind, source) {
|
|
|
81635
81900
|
*/
|
|
81636
81901
|
function alarmTextKeys(transition, body) {
|
|
81637
81902
|
const { kind, source } = transition;
|
|
81638
|
-
const combined = combinedAlarmText(kind, source);
|
|
81903
|
+
const combined = combinedAlarmText(kind, source, transition.followers);
|
|
81639
81904
|
if (combined !== null) return combined;
|
|
81640
81905
|
const baseTitle = transition.mode !== void 0 ? `system.${kind}.title.${transition.mode}` : `system.${kind}.title`;
|
|
81641
81906
|
const titleKey = body?.titleKeySuffix !== void 0 ? `${baseTitle}.${body.titleKeySuffix}` : baseTitle;
|
|
@@ -87205,6 +87470,13 @@ var NC_STILL_WAIT_MAX_AGE_MS = 3e4;
|
|
|
87205
87470
|
function canStillGainMedia(entry) {
|
|
87206
87471
|
return entry.recordKind === "object-event";
|
|
87207
87472
|
}
|
|
87473
|
+
/** The variables that carry a finished cycle's run summary. */
|
|
87474
|
+
var CYCLE_SUMMARY_VARS = [
|
|
87475
|
+
"duration",
|
|
87476
|
+
"energy",
|
|
87477
|
+
"afterDuration",
|
|
87478
|
+
"usedEnergy"
|
|
87479
|
+
];
|
|
87208
87480
|
var NcDispatcher = class {
|
|
87209
87481
|
deps;
|
|
87210
87482
|
targetCache = null;
|
|
@@ -88108,6 +88380,7 @@ var NcDispatcher = class {
|
|
|
88108
88380
|
}
|
|
88109
88381
|
/** Rules already warned about a deprecated variable in this process — once each. */
|
|
88110
88382
|
deprecatedVarWarned = /* @__PURE__ */ new Set();
|
|
88383
|
+
cycleSummaryWarned = /* @__PURE__ */ new Set();
|
|
88111
88384
|
/**
|
|
88112
88385
|
* A rule template written by an older client may still say `{{camera}}`. It
|
|
88113
88386
|
* renders (the alias), and the rule is named once so the operator can edit it.
|
|
@@ -88127,6 +88400,27 @@ var NcDispatcher = class {
|
|
|
88127
88400
|
});
|
|
88128
88401
|
}
|
|
88129
88402
|
/**
|
|
88403
|
+
* A FINISHED-cycle row whose own template places none of the four cycle
|
|
88404
|
+
* variables delivers no run summary (D391: accepted, produced less). Said
|
|
88405
|
+
* once per rule; the operator's text is never touched.
|
|
88406
|
+
*/
|
|
88407
|
+
warnCycleSummaryDropped(entry) {
|
|
88408
|
+
const deviceId = entry.payload.subject.applianceCycle?.deviceId;
|
|
88409
|
+
const template = entry.payload.template;
|
|
88410
|
+
if (deviceId === void 0 || template === void 0) return;
|
|
88411
|
+
if (this.cycleSummaryWarned.has(entry.ruleId)) return;
|
|
88412
|
+
const used = new Set([...templatePlaceholders(template.title ?? ""), ...templatePlaceholders(template.body ?? "")]);
|
|
88413
|
+
if (CYCLE_SUMMARY_VARS.some((name) => used.has(name))) return;
|
|
88414
|
+
this.cycleSummaryWarned.add(entry.ruleId);
|
|
88415
|
+
this.deps.logger.warn("appliance cycle finished but the rule template places no run summary — the message carries no duration or energy", {
|
|
88416
|
+
tags: {
|
|
88417
|
+
deviceId,
|
|
88418
|
+
ruleId: entry.ruleId
|
|
88419
|
+
},
|
|
88420
|
+
meta: { placeholders: [...CYCLE_SUMMARY_VARS] }
|
|
88421
|
+
});
|
|
88422
|
+
}
|
|
88423
|
+
/**
|
|
88130
88424
|
* The facts of a finished appliance cycle; unknown on every other row and on
|
|
88131
88425
|
* any failure (a part that cannot be read is omitted, never a blocked send).
|
|
88132
88426
|
*/
|
|
@@ -88171,6 +88465,7 @@ var NcDispatcher = class {
|
|
|
88171
88465
|
const cycleFacts = await this.applianceCycleFacts(entry);
|
|
88172
88466
|
const vars = buildTemplateVars(this.texts, entry, deviceName, zoneLabels, void 0, cycleFacts);
|
|
88173
88467
|
this.warnDeprecatedVars(entry);
|
|
88468
|
+
this.warnCycleSummaryDropped(entry);
|
|
88174
88469
|
const systemCount = systemEvent?.textCount !== void 0 ? { count: systemEvent.textCount } : {};
|
|
88175
88470
|
const title = renderRowTitle(this.texts, entry, vars);
|
|
88176
88471
|
const authoredBody = entry.payload.template?.body;
|
|
@@ -89429,10 +89724,31 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone, cycle
|
|
|
89429
89724
|
latestVersion: typeof systemEvent?.data["latestVersion"] === "string" ? systemEvent.data["latestVersion"] : "",
|
|
89430
89725
|
...systemEvent?.deviceId !== void 0 ? { deviceName: String(systemEvent.deviceId) } : {},
|
|
89431
89726
|
...systemEvent?.nodeId !== void 0 && (systemEvent.kind === "node-online" || systemEvent.kind === "node-offline") ? { nodeName: systemEvent.nodeId } : {},
|
|
89727
|
+
...systemEvent?.alarmSource !== void 0 ? { changes: alarmChangesText(texts, systemEvent.alarmSource, systemEvent.alarmFollowers) } : {},
|
|
89432
89728
|
...systemEventParamsForTemplate(systemEvent?.textParams)
|
|
89433
89729
|
};
|
|
89434
89730
|
}
|
|
89435
89731
|
/**
|
|
89732
|
+
* `{{changes}}` of the combined alarm body: what each sensor of the incident did, in
|
|
89733
|
+
* arrival order and one phrase per device — "Laundry lock unlocked". The phrase is a
|
|
89734
|
+
* catalog key per kind and state (`alarm.change.<kind>.<state>`), then per state
|
|
89735
|
+
* (`alarm.change.<state>`), then the bare name: a state nobody worded never drops the device.
|
|
89736
|
+
*/
|
|
89737
|
+
function alarmChangesText(texts, source, followers) {
|
|
89738
|
+
const phrases = [];
|
|
89739
|
+
for (const ref of [source, ...followers ?? []]) {
|
|
89740
|
+
const change = ref.sensorChange;
|
|
89741
|
+
if (change === void 0) continue;
|
|
89742
|
+
const key = [`alarm.change.${change.kind}.${change.state}`, `alarm.change.${change.state}`].find((candidate) => texts.has(candidate));
|
|
89743
|
+
const phrase = key === void 0 ? change.name : texts.text({
|
|
89744
|
+
key,
|
|
89745
|
+
vars: { device: change.name }
|
|
89746
|
+
});
|
|
89747
|
+
if (!phrases.includes(phrase)) phrases.push(phrase);
|
|
89748
|
+
}
|
|
89749
|
+
return phrases.join(", ");
|
|
89750
|
+
}
|
|
89751
|
+
/**
|
|
89436
89752
|
* Which catalog key supplies the DEFAULT body for a row.
|
|
89437
89753
|
*
|
|
89438
89754
|
* A system event names its own at intake (the variant selection — profiled or
|
|
@@ -102153,7 +102469,10 @@ function buildProvider(host) {
|
|
|
102153
102469
|
catalog: [...require_clip_model_registry.NC_CONDITION_CATALOG],
|
|
102154
102470
|
taxonomy: require_clip_model_registry.NC_TAXONOMY
|
|
102155
102471
|
}),
|
|
102156
|
-
getTemplateCatalog: async () => ({
|
|
102472
|
+
getTemplateCatalog: async () => ({
|
|
102473
|
+
vars: require_clip_model_registry.servedTemplateVars(),
|
|
102474
|
+
defaults: servedTemplateDefaults(host.texts)
|
|
102475
|
+
}),
|
|
102157
102476
|
previewTemplate: async (input) => previewTemplate(host.texts, input),
|
|
102158
102477
|
getHistory: async ({ filter }) => {
|
|
102159
102478
|
const entries = await host.outbox.queryHistory({
|
|
@@ -105715,7 +106034,7 @@ function groupedAlarmPhaseKey(ruleId, trackId) {
|
|
|
105715
106034
|
* combined alarm notification would then arrive with no picture, which is the
|
|
105716
106035
|
* one thing it exists to fix.
|
|
105717
106036
|
*/
|
|
105718
|
-
function fireRefFor(rule, subject, kind) {
|
|
106037
|
+
function fireRefFor(rule, subject, kind, sensorName) {
|
|
105719
106038
|
const hasEventMedia = kind === "object-event" || kind === "package-event";
|
|
105720
106039
|
const className = subject.classNames[0];
|
|
105721
106040
|
const linkedOwners = subject.trackId === void 0 && subject.linkedStills !== void 0 ? linkedMediaOwners(subject.linkedStills, linkedCameraMode(rule.media)) : void 0;
|
|
@@ -105725,6 +106044,11 @@ function fireRefFor(rule, subject, kind) {
|
|
|
105725
106044
|
ruleName: rule.name,
|
|
105726
106045
|
deviceId: subject.deviceId,
|
|
105727
106046
|
...subject.sourceDeviceId !== void 0 ? { sourceDeviceId: subject.sourceDeviceId } : {},
|
|
106047
|
+
...subject.sensorKind !== void 0 && subject.sensorState !== void 0 && sensorName !== void 0 ? { sensorChange: {
|
|
106048
|
+
name: sensorName,
|
|
106049
|
+
kind: subject.sensorKind,
|
|
106050
|
+
state: subject.sensorState
|
|
106051
|
+
} } : {},
|
|
105728
106052
|
recordId: subject.recordId,
|
|
105729
106053
|
...trackId !== void 0 ? { trackId } : {},
|
|
105730
106054
|
...linkedOwners !== void 0 && linkedOwners.extraStills.length > 0 ? { extraStills: linkedOwners.extraStills.map((x) => ({ ...x })) } : {},
|
|
@@ -105736,6 +106060,12 @@ function fireRefFor(rule, subject, kind) {
|
|
|
105736
106060
|
...subject.confidence !== void 0 ? { confidence: subject.confidence } : {}
|
|
105737
106061
|
};
|
|
105738
106062
|
}
|
|
106063
|
+
/** The directory name of the sensor a match was about; absent for a camera subject. */
|
|
106064
|
+
function sensorNameOf(host, subject) {
|
|
106065
|
+
if (subject.source !== "sensor") return void 0;
|
|
106066
|
+
const name = host.deviceDirectory.get(subject.sourceDeviceId ?? subject.deviceId)?.name.trim();
|
|
106067
|
+
return name !== void 0 && name.length > 0 ? name : void 0;
|
|
106068
|
+
}
|
|
105739
106069
|
/**
|
|
105740
106070
|
* Run this rule's onTrigger / onTriggerOff bindings for this match, and —
|
|
105741
106071
|
* when the rule is gated on `armed_*` — auto-trigger the CamStack
|
|
@@ -105757,7 +106087,7 @@ function runRuleActions(host, rule, subject, kind, controls) {
|
|
|
105757
106087
|
deviceId: subject.deviceId,
|
|
105758
106088
|
bindings,
|
|
105759
106089
|
lookup: (id) => host.sequences.get(id),
|
|
105760
|
-
origin: fireRefFor(rule, subject, kind)
|
|
106090
|
+
origin: fireRefFor(rule, subject, kind, sensorNameOf(host, subject))
|
|
105761
106091
|
});
|
|
105762
106092
|
return phase;
|
|
105763
106093
|
}
|
|
@@ -105781,7 +106111,7 @@ function autoTriggerAlarmPanel(host, rule, subject, kind) {
|
|
|
105781
106111
|
});
|
|
105782
106112
|
return NO_ALARM_PHASE;
|
|
105783
106113
|
}
|
|
105784
|
-
const origin = fireRefFor(rule, subject, kind);
|
|
106114
|
+
const origin = fireRefFor(rule, subject, kind, sensorNameOf(host, subject));
|
|
105785
106115
|
const phase = panel.classifyRuleMatch(origin);
|
|
105786
106116
|
try {
|
|
105787
106117
|
panel.triggerFromRule(origin);
|
|
@@ -125090,6 +125420,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
125090
125420
|
this.packageDropDetector?.forgetDevice(data.deviceId);
|
|
125091
125421
|
this.packageScene?.forget(data.deviceId);
|
|
125092
125422
|
this.frameState.lastNonPackageActivityAt.delete(data.deviceId);
|
|
125423
|
+
this.frameState.lastPackageZoneMotionAt.delete(data.deviceId);
|
|
125093
125424
|
this.frameState.packageCollectors.forgetDevice(data.deviceId);
|
|
125094
125425
|
this.frameState.packageOversizeLogAt.delete(data.deviceId);
|
|
125095
125426
|
this.frameState.packageLookFrames.delete(data.deviceId);
|
|
@@ -125114,6 +125445,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
125114
125445
|
this.packageDropDetector?.forgetDevice(deviceId);
|
|
125115
125446
|
this.packageScene?.forget(deviceId);
|
|
125116
125447
|
this.frameState.lastNonPackageActivityAt.delete(deviceId);
|
|
125448
|
+
this.frameState.lastPackageZoneMotionAt.delete(deviceId);
|
|
125117
125449
|
this.frameState.packageCollectors.forgetDevice(deviceId);
|
|
125118
125450
|
this.frameState.packageOversizeLogAt.delete(deviceId);
|
|
125119
125451
|
this.frameState.packageLookFrames.delete(deviceId);
|
|
@@ -127686,6 +128018,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
127686
128018
|
detected,
|
|
127687
128019
|
atMs: timestamp
|
|
127688
128020
|
});
|
|
128021
|
+
if (detected) await this.stampPackageZoneMotion(deviceId, payload, timestamp);
|
|
127689
128022
|
if (detected) this.eventStore?.noteMotionSample(deviceId, payload.regionCount, timestamp);
|
|
127690
128023
|
await this.motionEpisodes?.observe(deviceId, "analyzer", detected, timestamp, {
|
|
127691
128024
|
regionCount: payload.regionCount,
|
|
@@ -127704,6 +128037,28 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
127704
128037
|
});
|
|
127705
128038
|
}
|
|
127706
128039
|
/**
|
|
128040
|
+
* Stamp analyzer motion that reached a package drop-off zone (D1003): the
|
|
128041
|
+
* courier who never became an L1 track still backs the OPEN of a candidate.
|
|
128042
|
+
* Onboard motion carries no regions or dimensions and never stamps.
|
|
128043
|
+
*/
|
|
128044
|
+
async stampPackageZoneMotion(deviceId, payload, timestamp) {
|
|
128045
|
+
const proxy = await this.ensureProxy(deviceId);
|
|
128046
|
+
const packageZoneIds = collectPackageZoneIds(proxy?.state.zoneRules.value?.package ?? []);
|
|
128047
|
+
if (packageZoneIds.size === 0) return;
|
|
128048
|
+
const zoneRect = packageZoneCropRect({
|
|
128049
|
+
zones: proxy?.state.zones.value?.zones ?? [],
|
|
128050
|
+
packageZoneIds,
|
|
128051
|
+
frameWidth: payload.frameWidth,
|
|
128052
|
+
frameHeight: payload.frameHeight
|
|
128053
|
+
});
|
|
128054
|
+
if (delivererMotionNearPackageZone({
|
|
128055
|
+
regions: payload.regions,
|
|
128056
|
+
frameWidth: payload.frameWidth,
|
|
128057
|
+
frameHeight: payload.frameHeight,
|
|
128058
|
+
zoneRect
|
|
128059
|
+
})) this.frameState.lastPackageZoneMotionAt.set(deviceId, timestamp);
|
|
128060
|
+
}
|
|
128061
|
+
/**
|
|
127707
128062
|
* Persist firmware-driven (onboard / device-activity) motion events into
|
|
127708
128063
|
* the SAME episode tracker as {@link handleMotionAnalysis}, keyed per
|
|
127709
128064
|
* `(deviceId, source)` so the two paths can never share — or fight over —
|
|
@@ -128036,6 +128391,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
128036
128391
|
encodeJpeg: request.encodeJpeg
|
|
128037
128392
|
}, request.pin);
|
|
128038
128393
|
}
|
|
128394
|
+
zoneMotionStamp(deviceId) {
|
|
128395
|
+
const at = this.frameState.lastPackageZoneMotionAt.get(deviceId);
|
|
128396
|
+
return at === void 0 ? {} : { lastDelivererMotionAt: at };
|
|
128397
|
+
}
|
|
128039
128398
|
deliverPackageSceneSightings(delivery) {
|
|
128040
128399
|
const settings = this.packageDropSettingsFromCache(delivery.deviceId);
|
|
128041
128400
|
const lastActivityAt = this.frameState.lastNonPackageActivityAt.get(delivery.deviceId);
|
|
@@ -128049,6 +128408,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
128049
128408
|
minDwellMs: settings.packageDropDwellSec * 1e3,
|
|
128050
128409
|
activityWindowMs: settings.packageDropActivityWindowSec * 1e3,
|
|
128051
128410
|
...lastActivityAt !== void 0 ? { lastActivityAt } : {},
|
|
128411
|
+
...this.zoneMotionStamp(delivery.deviceId),
|
|
128052
128412
|
looked: false
|
|
128053
128413
|
});
|
|
128054
128414
|
this.bridgePackageStillness({
|
|
@@ -128234,6 +128594,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
128234
128594
|
minDwellMs: input.settings.packageDropDwellSec * 1e3,
|
|
128235
128595
|
activityWindowMs: input.settings.packageDropActivityWindowSec * 1e3,
|
|
128236
128596
|
...lastActivityAt !== void 0 ? { lastActivityAt } : {},
|
|
128597
|
+
...this.zoneMotionStamp(input.deviceId),
|
|
128237
128598
|
...lookEvidence
|
|
128238
128599
|
});
|
|
128239
128600
|
this.bridgePackageStillness({
|
|
@@ -128429,6 +128790,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
128429
128790
|
});
|
|
128430
128791
|
}).catch(() => void 0);
|
|
128431
128792
|
}
|
|
128793
|
+
for (const c of outcome.continued) this.ctx.logger.info("package candidate continued by a sub-floor box", {
|
|
128794
|
+
tags,
|
|
128795
|
+
meta: {
|
|
128796
|
+
entryId: c.id,
|
|
128797
|
+
hits: c.hits,
|
|
128798
|
+
score: c.score,
|
|
128799
|
+
via: input.via
|
|
128800
|
+
}
|
|
128801
|
+
});
|
|
128432
128802
|
for (const u of outcome.earlyEvidenceUnbacked) this.ctx.logger.info("early pick-up evidence complete — nobody around, left to the TTL", {
|
|
128433
128803
|
tags,
|
|
128434
128804
|
meta: {
|
|
@@ -128456,6 +128826,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
128456
128826
|
observedDwellMs,
|
|
128457
128827
|
requiredDwellMs,
|
|
128458
128828
|
activityBackedAtOpen: ex.activityBackedAtOpen,
|
|
128829
|
+
lastZoneMotionAt: this.frameState.lastPackageZoneMotionAt.get(input.deviceId) ?? null,
|
|
128459
128830
|
reasons
|
|
128460
128831
|
}
|
|
128461
128832
|
});
|
|
@@ -129229,6 +129600,19 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
129229
129600
|
}
|
|
129230
129601
|
const verdict = this.sensorTransitions.observe(data.deviceId, data.capName, { ...slice });
|
|
129231
129602
|
this.flushSensorTransitionDrops();
|
|
129603
|
+
if (verdict.changed) {
|
|
129604
|
+
const entries = Object.entries(slice);
|
|
129605
|
+
const field = (key) => entries.find(([k]) => k === key)?.[1];
|
|
129606
|
+
this.ctx.logger.info("sensor slice passed the transition gate", {
|
|
129607
|
+
tags: { deviceId: data.deviceId },
|
|
129608
|
+
meta: {
|
|
129609
|
+
capName: data.capName,
|
|
129610
|
+
state: field("state") ?? field("value") ?? field("entryOpen") ?? field("active") ?? null,
|
|
129611
|
+
lastChangedAt: field("lastChangedAt") ?? null,
|
|
129612
|
+
verdict: "changed"
|
|
129613
|
+
}
|
|
129614
|
+
});
|
|
129615
|
+
}
|
|
129232
129616
|
return verdict.changed;
|
|
129233
129617
|
}
|
|
129234
129618
|
/**
|