@camstack/addon-post-analysis 1.2.95 → 1.2.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{dist-BdojoP7i.js → dist-BQsMSX2A.js} +29 -3
- package/dist/{dist-C-_TL-I1.mjs → dist-ecY_-nDx.mjs} +29 -3
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CeFPqm3h.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BdX09HCw.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-BExsGl4h.mjs → hostInit-DULHUyr1.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +187 -89
- package/dist/pipeline-analytics/index.mjs +187 -89
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -27629,9 +27629,21 @@ var RecordingLocationUsageSchema = object({
|
|
|
27629
27629
|
* previously rendered as two identical "disks" with a nonsensical used
|
|
27630
27630
|
* split — hydrate attribution across aliases is arbitrary by nature. */
|
|
27631
27631
|
locationIds: array(string()).optional(),
|
|
27632
|
-
/**
|
|
27632
|
+
/**
|
|
27633
|
+
* Filesystem root of the physical volume this row accounts — the only
|
|
27634
|
+
* operator-legible identity when a location has no `displayName` yet, and
|
|
27635
|
+
* what a "which disk is full" alert has to name. OPTIONAL for the same
|
|
27636
|
+
* train-skew reason as `oldestMs`: a recorder that predates the field omits
|
|
27637
|
+
* it, and this schema must keep validating that older provider's payload.
|
|
27638
|
+
*/
|
|
27639
|
+
root: string().optional(),
|
|
27640
|
+
/** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
|
|
27641
|
+
* the footage's SHARE of the volume, not how full the volume is — the disk
|
|
27642
|
+
* also holds everything that is not recordings. Volume fill is
|
|
27643
|
+
* `(totalBytes - availableBytes) / totalBytes`. */
|
|
27633
27644
|
usedBytes: number(),
|
|
27634
|
-
/** Free bytes on the location's volume; null when capacity is unknown
|
|
27645
|
+
/** Free bytes on the location's volume; null when capacity is unknown
|
|
27646
|
+
* (remote/unstattable — the recorder logs the statfs failure with the root). */
|
|
27635
27647
|
availableBytes: number().nullable(),
|
|
27636
27648
|
/** Total bytes of the location's volume; null when unknown. */
|
|
27637
27649
|
totalBytes: number().nullable()
|
|
@@ -27643,7 +27655,15 @@ var RecordingStorageUsageSchema = object({
|
|
|
27643
27655
|
nodeId: string(),
|
|
27644
27656
|
totalUsedBytes: number(),
|
|
27645
27657
|
devices: array(RecordingDeviceUsageSchema),
|
|
27646
|
-
|
|
27658
|
+
/**
|
|
27659
|
+
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
27660
|
+
* of the payload without it: the hub serves this method through a BAKED
|
|
27661
|
+
* router, so a hub whose framework train predates a change to this array
|
|
27662
|
+
* strips it out of an answer a newer recorder happily produced. A UI that
|
|
27663
|
+
* dereferences it unconditionally crashes the whole Storage page over a
|
|
27664
|
+
* missing disk bar.
|
|
27665
|
+
*/
|
|
27666
|
+
locations: array(RecordingLocationUsageSchema).optional()
|
|
27647
27667
|
});
|
|
27648
27668
|
/**
|
|
27649
27669
|
* The OPERATOR-ARMED half of multi-location recordings (D116).
|
|
@@ -38505,6 +38525,11 @@ Object.freeze({
|
|
|
38505
38525
|
"network-access": "ingress",
|
|
38506
38526
|
"smtp-provider": "email"
|
|
38507
38527
|
});
|
|
38528
|
+
/** Schema defaults — an untouched sub-field must author exactly these. */
|
|
38529
|
+
var NC_AUDIO_DEFAULTS = {
|
|
38530
|
+
hitPercent: 60,
|
|
38531
|
+
samplingSeconds: 10
|
|
38532
|
+
};
|
|
38508
38533
|
/**
|
|
38509
38534
|
* Which mode this condition is in — the ONE place that question is answered.
|
|
38510
38535
|
*
|
|
@@ -38527,6 +38552,7 @@ function audioModeOf(value) {
|
|
|
38527
38552
|
}
|
|
38528
38553
|
new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
|
|
38529
38554
|
new Set(["devices", "classes"]);
|
|
38555
|
+
NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
38530
38556
|
var WEEKDAY_TO_DAY = {
|
|
38531
38557
|
Sun: 0,
|
|
38532
38558
|
Mon: 1,
|
|
@@ -27598,9 +27598,21 @@ var RecordingLocationUsageSchema = object({
|
|
|
27598
27598
|
* previously rendered as two identical "disks" with a nonsensical used
|
|
27599
27599
|
* split — hydrate attribution across aliases is arbitrary by nature. */
|
|
27600
27600
|
locationIds: array(string()).optional(),
|
|
27601
|
-
/**
|
|
27601
|
+
/**
|
|
27602
|
+
* Filesystem root of the physical volume this row accounts — the only
|
|
27603
|
+
* operator-legible identity when a location has no `displayName` yet, and
|
|
27604
|
+
* what a "which disk is full" alert has to name. OPTIONAL for the same
|
|
27605
|
+
* train-skew reason as `oldestMs`: a recorder that predates the field omits
|
|
27606
|
+
* it, and this schema must keep validating that older provider's payload.
|
|
27607
|
+
*/
|
|
27608
|
+
root: string().optional(),
|
|
27609
|
+
/** Bytes of recordings stored on this PHYSICAL volume (all aliases). This is
|
|
27610
|
+
* the footage's SHARE of the volume, not how full the volume is — the disk
|
|
27611
|
+
* also holds everything that is not recordings. Volume fill is
|
|
27612
|
+
* `(totalBytes - availableBytes) / totalBytes`. */
|
|
27602
27613
|
usedBytes: number(),
|
|
27603
|
-
/** Free bytes on the location's volume; null when capacity is unknown
|
|
27614
|
+
/** Free bytes on the location's volume; null when capacity is unknown
|
|
27615
|
+
* (remote/unstattable — the recorder logs the statfs failure with the root). */
|
|
27604
27616
|
availableBytes: number().nullable(),
|
|
27605
27617
|
/** Total bytes of the location's volume; null when unknown. */
|
|
27606
27618
|
totalBytes: number().nullable()
|
|
@@ -27612,7 +27624,15 @@ var RecordingStorageUsageSchema = object({
|
|
|
27612
27624
|
nodeId: string(),
|
|
27613
27625
|
totalUsedBytes: number(),
|
|
27614
27626
|
devices: array(RecordingDeviceUsageSchema),
|
|
27615
|
-
|
|
27627
|
+
/**
|
|
27628
|
+
* Per-physical-volume capacity. OPTIONAL, and consumers must render the rest
|
|
27629
|
+
* of the payload without it: the hub serves this method through a BAKED
|
|
27630
|
+
* router, so a hub whose framework train predates a change to this array
|
|
27631
|
+
* strips it out of an answer a newer recorder happily produced. A UI that
|
|
27632
|
+
* dereferences it unconditionally crashes the whole Storage page over a
|
|
27633
|
+
* missing disk bar.
|
|
27634
|
+
*/
|
|
27635
|
+
locations: array(RecordingLocationUsageSchema).optional()
|
|
27616
27636
|
});
|
|
27617
27637
|
/**
|
|
27618
27638
|
* The OPERATOR-ARMED half of multi-location recordings (D116).
|
|
@@ -38474,6 +38494,11 @@ Object.freeze({
|
|
|
38474
38494
|
"network-access": "ingress",
|
|
38475
38495
|
"smtp-provider": "email"
|
|
38476
38496
|
});
|
|
38497
|
+
/** Schema defaults — an untouched sub-field must author exactly these. */
|
|
38498
|
+
var NC_AUDIO_DEFAULTS = {
|
|
38499
|
+
hitPercent: 60,
|
|
38500
|
+
samplingSeconds: 10
|
|
38501
|
+
};
|
|
38477
38502
|
/**
|
|
38478
38503
|
* Which mode this condition is in — the ONE place that question is answered.
|
|
38479
38504
|
*
|
|
@@ -38496,6 +38521,7 @@ function audioModeOf(value) {
|
|
|
38496
38521
|
}
|
|
38497
38522
|
new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
|
|
38498
38523
|
new Set(["devices", "classes"]);
|
|
38524
|
+
NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
|
|
38499
38525
|
var WEEKDAY_TO_DAY = {
|
|
38500
38526
|
Sun: 0,
|
|
38501
38527
|
Mon: 1,
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BQsMSX2A.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_fs$1 = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
node_fs = require_dist.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { O as PoolMemoryWatchdog, Q as embeddingEncoderCapability, dt as resolvePoolMemoryPolicy, ot as parseProcStatus, tt as hfModelUrl, yt as BaseAddon } from "../dist-
|
|
1
|
+
import { O as PoolMemoryWatchdog, Q as embeddingEncoderCapability, dt as resolvePoolMemoryPolicy, ot as parseProcStatus, tt as hfModelUrl, yt as BaseAddon } from "../dist-ecY_-nDx.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import "node:fs";
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.25",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.89",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.61",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.89",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.25",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.61",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BQsMSX2A.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -5562,24 +5562,39 @@ async function downscaleFullFrameJpeg(jpeg, maxWidth = 640, maxHeight = 360) {
|
|
|
5562
5562
|
}).jpeg({ quality: FULL_FRAME_QUALITY }).toBuffer();
|
|
5563
5563
|
}
|
|
5564
5564
|
//#endregion
|
|
5565
|
-
//#region src/notification-center/
|
|
5565
|
+
//#region src/notification-center/still-shelf.ts
|
|
5566
5566
|
/**
|
|
5567
|
-
* The
|
|
5567
|
+
* The still shelf — the PHOTOGRAPH a notification carries when nothing it can
|
|
5568
|
+
* name owns a frame.
|
|
5568
5569
|
*
|
|
5569
|
-
* ## Why a
|
|
5570
|
+
* ## Why a trigger needs one at all
|
|
5570
5571
|
*
|
|
5571
|
-
* The attachment ladder resolves media by OWNER, and
|
|
5572
|
-
*
|
|
5573
|
-
*
|
|
5572
|
+
* The attachment ladder resolves media by OWNER, and most triggers have one: an
|
|
5573
|
+
* object or package event owns its crops, a closed track owns its best shot, a
|
|
5574
|
+
* doorbell press owns the marker track the same press projected
|
|
5574
5575
|
* (`sensor-marker-projector.ts`), an occupancy edge names one of the objects it
|
|
5575
|
-
* counted (`chooseOccupancyMediaOwner`).
|
|
5576
|
-
*
|
|
5577
|
-
*
|
|
5578
|
-
*
|
|
5576
|
+
* counted (`chooseOccupancyMediaOwner`). Two triggers own nothing, and for the
|
|
5577
|
+
* same reason in both cases — the subject is an ABSENCE:
|
|
5578
|
+
*
|
|
5579
|
+
* - an **audio** match: nothing was boxed, nothing was tracked, and —
|
|
5580
|
+
* deliberately — nothing is persisted at all. A confirmed window is a claim
|
|
5581
|
+
* about sound that has already stopped, and `event-intake.ts` states why
|
|
5582
|
+
* replaying it later would be wrong.
|
|
5583
|
+
* - an **occupancy** edge whose scope is EMPTY — "posto libero". The vehicle
|
|
5584
|
+
* whose departure IS the news has left, so `chooseOccupancyMediaOwner` names
|
|
5585
|
+
* nobody and the ladder logs `no still could be resolved … owners=[]`.
|
|
5579
5586
|
*
|
|
5580
5587
|
* So the only honest picture is a PHOTOGRAPH of the camera taken at the moment
|
|
5581
|
-
* of the
|
|
5582
|
-
*
|
|
5588
|
+
* of the trigger. Not of the sound, not of the object that left — of what the
|
|
5589
|
+
* camera can see now that it happened. For a freed parking space that is
|
|
5590
|
+
* exactly the answer the operator wants: the space, empty.
|
|
5591
|
+
*
|
|
5592
|
+
* **One shelf, not two.** The mechanism is identical down to the reuse window,
|
|
5593
|
+
* and the only thing that differs between the two triggers is the GATE deciding
|
|
5594
|
+
* that a photograph is owed at all — which belongs at the trigger site, where
|
|
5595
|
+
* the rules are, and not here. A twin module would be a second derivation of
|
|
5596
|
+
* one thing. The trigger rides along as {@link NcStillTrigger} only so the logs
|
|
5597
|
+
* can say which absence they are about.
|
|
5583
5598
|
*
|
|
5584
5599
|
* ## Three properties, and each one is a decision
|
|
5585
5600
|
*
|
|
@@ -5587,27 +5602,30 @@ async function downscaleFullFrameJpeg(jpeg, maxWidth = 640, maxHeight = 360) {
|
|
|
5587
5602
|
* TTL that covers the outbox's whole retry horizon — and nowhere else. The
|
|
5588
5603
|
* alternative was the doorbell's: materialise a synthetic marker track through
|
|
5589
5604
|
* `SyntheticTrackMaterializer` and let the notification name it. That would put
|
|
5590
|
-
* a durable Track on the camera's timeline for every confirmed window
|
|
5591
|
-
* the digest's `listTracks`, retention, and the audio-
|
|
5592
|
-
* operator ceilings (`audio-marker-projector.ts` exists
|
|
5593
|
-
* many audio markers a camera may emit). A notification
|
|
5594
|
-
* timeline history as a side effect of wanting a picture.
|
|
5595
|
-
*
|
|
5596
|
-
* **The capture STARTS immediately and is never awaited.**
|
|
5597
|
-
* — a scream is over before a snapshot round-trip completes
|
|
5598
|
-
*
|
|
5599
|
-
*
|
|
5600
|
-
*
|
|
5601
|
-
*
|
|
5602
|
-
*
|
|
5603
|
-
*
|
|
5604
|
-
*
|
|
5605
|
+
* a durable Track on the camera's timeline for every confirmed window and every
|
|
5606
|
+
* emptied zone, feeding the digest's `listTracks`, retention, and the audio-
|
|
5607
|
+
* marker feature's own operator ceilings (`audio-marker-projector.ts` exists
|
|
5608
|
+
* precisely to bound how many audio markers a camera may emit). A notification
|
|
5609
|
+
* must not manufacture timeline history as a side effect of wanting a picture.
|
|
5610
|
+
*
|
|
5611
|
+
* **The capture STARTS immediately and is never awaited.** The subject is
|
|
5612
|
+
* transient — a scream is over before a snapshot round-trip completes, and a
|
|
5613
|
+
* freed space is about to be taken by the next car — so the fetch is kicked off
|
|
5614
|
+
* at the trigger, before the rule evaluation runs, and the owner id is minted
|
|
5615
|
+
* synchronously so the outbox row can name it. The bytes land while the row
|
|
5616
|
+
* waits in the queue, and the dispatcher's existing bounded still-wait (or the
|
|
5617
|
+
* pause its own footage render already costs) picks them up. A camera that
|
|
5618
|
+
* never answers costs the picture and never the notification.
|
|
5619
|
+
*
|
|
5620
|
+
* **Two triggers seconds apart share ONE capture.** A barking dog confirms
|
|
5605
5621
|
* repeatedly and a label-mode rule has no re-arm timer at all (D157) — the
|
|
5606
5622
|
* rule's cooldown is its only brake, and the cooldown is applied AFTER this.
|
|
5607
5623
|
* Without a reuse window this would photograph a camera at whatever rate the
|
|
5608
|
-
* sound happens to occur. Each
|
|
5624
|
+
* sound happens to occur. Each trigger still gets its OWN owner id, so two
|
|
5609
5625
|
* outbox rows are never mistaken for one subject; they merely point at the same
|
|
5610
|
-
* frame, which is the truth — the scene did not change in ten seconds.
|
|
5626
|
+
* frame, which is the truth — the scene did not change in ten seconds. The
|
|
5627
|
+
* window is per CAMERA and trigger-agnostic for the same reason: a sound and an
|
|
5628
|
+
* emptied zone ten seconds apart are two claims about one scene.
|
|
5611
5629
|
*
|
|
5612
5630
|
* Nothing here is silent: a capture that lands and a camera that refuses each
|
|
5613
5631
|
* emit one line carrying `tags: { deviceId }`, because "why did 617 get a photo
|
|
@@ -5615,13 +5633,13 @@ async function downscaleFullFrameJpeg(jpeg, maxWidth = 640, maxHeight = 360) {
|
|
|
5615
5633
|
*/
|
|
5616
5634
|
/**
|
|
5617
5635
|
* The owner-id namespace. It is what routes a lookup here instead of to the
|
|
5618
|
-
* media store, and it is the reason the dispatcher needs no audio
|
|
5619
|
-
* `getMediaForOwner` answers for
|
|
5636
|
+
* media store, and it is the reason the dispatcher needs no audio or occupancy
|
|
5637
|
+
* branch — `getMediaForOwner` answers for all of them under one signature.
|
|
5620
5638
|
*/
|
|
5621
|
-
var
|
|
5639
|
+
var NC_STILL_SHELF_PREFIX = "nc-still:";
|
|
5622
5640
|
/** True for an owner id this shelf minted. */
|
|
5623
|
-
function
|
|
5624
|
-
return id.startsWith(
|
|
5641
|
+
function isStillShelfId(id) {
|
|
5642
|
+
return id.startsWith(NC_STILL_SHELF_PREFIX);
|
|
5625
5643
|
}
|
|
5626
5644
|
/**
|
|
5627
5645
|
* How long the bytes are held.
|
|
@@ -5631,16 +5649,16 @@ function isAudioStillId(id) {
|
|
|
5631
5649
|
* ships text-only, which is the correct degradation for a photograph of a scene
|
|
5632
5650
|
* that is a quarter of an hour stale anyway.
|
|
5633
5651
|
*/
|
|
5634
|
-
var
|
|
5652
|
+
var NC_STILL_SHELF_TTL_MS = 15 * 6e4;
|
|
5635
5653
|
/** A hanging snapshot cap must not pin a capture slot forever. */
|
|
5636
5654
|
var SNAPSHOT_TIMEOUT_MS = 8e3;
|
|
5637
5655
|
/**
|
|
5638
|
-
* Hard bound on held captures. Reached only if every camera on the hub
|
|
5639
|
-
*
|
|
5656
|
+
* Hard bound on held captures. Reached only if every camera on the hub triggers
|
|
5657
|
+
* one inside a single TTL; the oldest is dropped first, which costs a
|
|
5640
5658
|
* fifteen-minute-old picture nobody is waiting for.
|
|
5641
5659
|
*/
|
|
5642
5660
|
var MAX_CAPTURES = 64;
|
|
5643
|
-
var
|
|
5661
|
+
var NcStillShelf = class {
|
|
5644
5662
|
deps;
|
|
5645
5663
|
/** captureId → the photograph. */
|
|
5646
5664
|
captures = /* @__PURE__ */ new Map();
|
|
@@ -5652,14 +5670,14 @@ var NcAudioStillShelf = class {
|
|
|
5652
5670
|
this.deps = deps;
|
|
5653
5671
|
}
|
|
5654
5672
|
/**
|
|
5655
|
-
* Photograph `deviceId` for a
|
|
5656
|
-
* subject should name. SYNCHRONOUS by contract: the caller is on the
|
|
5657
|
-
*
|
|
5673
|
+
* Photograph `deviceId` for a trigger at `atMs`, and return the owner id the
|
|
5674
|
+
* subject should name. SYNCHRONOUS by contract: the caller is on the trigger
|
|
5675
|
+
* path and the outbox row is built from what this returns.
|
|
5658
5676
|
*/
|
|
5659
|
-
capture(deviceId, atMs) {
|
|
5677
|
+
capture(deviceId, atMs, trigger) {
|
|
5660
5678
|
const now = this.deps.now();
|
|
5661
5679
|
this.prune(now);
|
|
5662
|
-
const ownerId = `${
|
|
5680
|
+
const ownerId = `${NC_STILL_SHELF_PREFIX}${(0, node_crypto.randomUUID)()}`;
|
|
5663
5681
|
const recent = this.newest.get(deviceId);
|
|
5664
5682
|
if (recent !== void 0 && now - recent.at < 1e4 && this.captures.has(recent.captureId)) {
|
|
5665
5683
|
this.owners.set(ownerId, recent.captureId);
|
|
@@ -5668,8 +5686,9 @@ var NcAudioStillShelf = class {
|
|
|
5668
5686
|
const captureId = (0, node_crypto.randomUUID)();
|
|
5669
5687
|
this.captures.set(captureId, {
|
|
5670
5688
|
deviceId,
|
|
5689
|
+
trigger,
|
|
5671
5690
|
startedAt: now,
|
|
5672
|
-
expiresAt: now +
|
|
5691
|
+
expiresAt: now + NC_STILL_SHELF_TTL_MS,
|
|
5673
5692
|
files: []
|
|
5674
5693
|
});
|
|
5675
5694
|
this.newest.set(deviceId, {
|
|
@@ -5677,7 +5696,7 @@ var NcAudioStillShelf = class {
|
|
|
5677
5696
|
at: now
|
|
5678
5697
|
});
|
|
5679
5698
|
this.owners.set(ownerId, captureId);
|
|
5680
|
-
this.fetch(captureId, deviceId, atMs);
|
|
5699
|
+
this.fetch(captureId, deviceId, atMs, trigger);
|
|
5681
5700
|
return ownerId;
|
|
5682
5701
|
}
|
|
5683
5702
|
/**
|
|
@@ -5713,11 +5732,11 @@ var NcAudioStillShelf = class {
|
|
|
5713
5732
|
this.owners.clear();
|
|
5714
5733
|
this.newest.clear();
|
|
5715
5734
|
}
|
|
5716
|
-
async fetch(captureId, deviceId, atMs) {
|
|
5735
|
+
async fetch(captureId, deviceId, atMs, trigger) {
|
|
5717
5736
|
try {
|
|
5718
5737
|
const shot = await withTimeout$3(this.deps.getSnapshot(deviceId), SNAPSHOT_TIMEOUT_MS);
|
|
5719
5738
|
if (shot === null || shot.base64.length === 0) {
|
|
5720
|
-
this.reportMiss(deviceId, "the camera returned no snapshot");
|
|
5739
|
+
this.reportMiss(deviceId, trigger, "the camera returned no snapshot");
|
|
5721
5740
|
return;
|
|
5722
5741
|
}
|
|
5723
5742
|
const raw = Buffer.from(shot.base64, "base64");
|
|
@@ -5729,17 +5748,18 @@ var NcAudioStillShelf = class {
|
|
|
5729
5748
|
if (held === void 0) return;
|
|
5730
5749
|
this.captures.set(captureId, {
|
|
5731
5750
|
...held,
|
|
5732
|
-
files:
|
|
5751
|
+
files: stillShelfMedia(data, atMs)
|
|
5733
5752
|
});
|
|
5734
|
-
this.deps.logger.info(
|
|
5753
|
+
this.deps.logger.info(`${trigger} still captured`, {
|
|
5735
5754
|
tags: { deviceId },
|
|
5736
5755
|
meta: {
|
|
5756
|
+
trigger,
|
|
5737
5757
|
bytes: data.byteLength,
|
|
5738
5758
|
tookMs: this.deps.now() - held.startedAt
|
|
5739
5759
|
}
|
|
5740
5760
|
});
|
|
5741
5761
|
} catch (err) {
|
|
5742
|
-
this.reportMiss(deviceId, err instanceof Error ? err.message : String(err));
|
|
5762
|
+
this.reportMiss(deviceId, trigger, err instanceof Error ? err.message : String(err));
|
|
5743
5763
|
}
|
|
5744
5764
|
}
|
|
5745
5765
|
/**
|
|
@@ -5747,10 +5767,13 @@ var NcAudioStillShelf = class {
|
|
|
5747
5767
|
* on a notification they DID receive, so it is a warn and it carries the
|
|
5748
5768
|
* camera — the only key the question is ever asked with.
|
|
5749
5769
|
*/
|
|
5750
|
-
reportMiss(deviceId, reason) {
|
|
5751
|
-
this.deps.logger.warn(
|
|
5770
|
+
reportMiss(deviceId, trigger, reason) {
|
|
5771
|
+
this.deps.logger.warn(`the camera did not answer the ${trigger} still — this notification ships text-only`, {
|
|
5752
5772
|
tags: { deviceId },
|
|
5753
|
-
meta: {
|
|
5773
|
+
meta: {
|
|
5774
|
+
trigger,
|
|
5775
|
+
reason
|
|
5776
|
+
}
|
|
5754
5777
|
});
|
|
5755
5778
|
}
|
|
5756
5779
|
};
|
|
@@ -5765,10 +5788,10 @@ var NcAudioStillShelf = class {
|
|
|
5765
5788
|
* so. Inventing a centre crop would answer a different question from the one
|
|
5766
5789
|
* the operator asked.
|
|
5767
5790
|
*/
|
|
5768
|
-
function
|
|
5791
|
+
function stillShelfMedia(data, timestamp) {
|
|
5769
5792
|
const base64 = data.toString("base64");
|
|
5770
5793
|
const file = (kind) => ({
|
|
5771
|
-
key: `${
|
|
5794
|
+
key: `${NC_STILL_SHELF_PREFIX}${kind}`,
|
|
5772
5795
|
kind,
|
|
5773
5796
|
base64,
|
|
5774
5797
|
sizeBytes: data.byteLength,
|
|
@@ -7434,7 +7457,7 @@ function subjectFromSensorEvent(ev, markerTrackId) {
|
|
|
7434
7457
|
* (see the {@link evaluateRule} audio gate + the matchers below).
|
|
7435
7458
|
*
|
|
7436
7459
|
* `stillOwnerId` is the audio still shelf's owner id — the photograph of the
|
|
7437
|
-
* camera taken at the match (`
|
|
7460
|
+
* camera taken at the match (`still-shelf.ts`). It rides `trackId` because that
|
|
7438
7461
|
* is the field the attachment ladder reads, exactly as a doorbell press rides
|
|
7439
7462
|
* its marker track's id; nothing else about the subject is track-scoped, and
|
|
7440
7463
|
* `buildEntries` keys an audio row on its record id, never on this. Absent when
|
|
@@ -7526,10 +7549,18 @@ function audioSubjectFor(hit, spec) {
|
|
|
7526
7549
|
* chosen at commit (`chooseOccupancyMediaOwner`) — rides on `trackId`, and that
|
|
7527
7550
|
* is the whole of what gives an occupancy notification a picture: the ordinary
|
|
7528
7551
|
* media-owner ladder resolves it, exactly as it resolves a doorbell's marker
|
|
7529
|
-
* track.
|
|
7530
|
-
*
|
|
7531
|
-
|
|
7532
|
-
|
|
7552
|
+
* track.
|
|
7553
|
+
*
|
|
7554
|
+
* `stillOwnerId` is the SECOND source of that picture and the only one an
|
|
7555
|
+
* emptied scope can have: with no member left to name — "posto libero", where
|
|
7556
|
+
* the object whose departure is the news has gone — the trigger site photographs
|
|
7557
|
+
* the camera and holds the bytes on the still shelf (`still-shelf.ts`), exactly
|
|
7558
|
+
* as an audio match does. The member track always WINS: it is a picture of the
|
|
7559
|
+
* counted object, and a photograph is only ever the stand-in for not having one.
|
|
7560
|
+
* Neither present (no snapshot cap on this node) ⇒ text-only, as before.
|
|
7561
|
+
*/
|
|
7562
|
+
function subjectFromOccupancyEvent(edge, stillOwnerId) {
|
|
7563
|
+
const mediaOwner = edge.mediaTrackId ?? stillOwnerId;
|
|
7533
7564
|
return {
|
|
7534
7565
|
kind: "occupancy-event",
|
|
7535
7566
|
recordId: `occ:${edge.deviceId}:${edge.zoneId ?? "@frame"}:${edge.className ?? "@all"}:t${edge.threshold}:${edge.occupied ? "occ" : "free"}:${edge.timestamp}`,
|
|
@@ -7537,17 +7568,28 @@ function subjectFromOccupancyEvent(edge) {
|
|
|
7537
7568
|
timestamp: edge.timestamp,
|
|
7538
7569
|
classNames: [edge.className ?? "occupancy"],
|
|
7539
7570
|
zones: [],
|
|
7540
|
-
...
|
|
7571
|
+
...mediaOwner !== void 0 ? { trackId: mediaOwner } : {},
|
|
7541
7572
|
source: "pipeline",
|
|
7542
|
-
occupancy:
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7573
|
+
occupancy: occupancySubjectFromEdge(edge)
|
|
7574
|
+
};
|
|
7575
|
+
}
|
|
7576
|
+
/**
|
|
7577
|
+
* The scope + level half of an occupancy subject, ALONE.
|
|
7578
|
+
*
|
|
7579
|
+
* Split out for one reason: the still-shelf gate must ask `matchesOccupancy`
|
|
7580
|
+
* whether a rule would fire on an edge BEFORE the subject that would carry the
|
|
7581
|
+
* answer exists (the owner id it mints is an input to that subject). Sharing
|
|
7582
|
+
* this keeps the gate reading the same numbers the verdict will.
|
|
7583
|
+
*/
|
|
7584
|
+
function occupancySubjectFromEdge(edge) {
|
|
7585
|
+
return {
|
|
7586
|
+
...edge.zoneId !== void 0 ? { zoneId: edge.zoneId } : {},
|
|
7587
|
+
...edge.zoneName !== void 0 ? { zoneName: edge.zoneName } : {},
|
|
7588
|
+
...edge.className !== void 0 ? { className: edge.className } : {},
|
|
7589
|
+
count: edge.count,
|
|
7590
|
+
previousCount: edge.previousCount,
|
|
7591
|
+
occupied: edge.occupied,
|
|
7592
|
+
threshold: edge.threshold
|
|
7551
7593
|
};
|
|
7552
7594
|
}
|
|
7553
7595
|
/** Build the subject for a `package-event` evaluation (a persisted `package`
|
|
@@ -7674,6 +7716,13 @@ function matchesAudio(cond, s) {
|
|
|
7674
7716
|
}
|
|
7675
7717
|
return false;
|
|
7676
7718
|
}
|
|
7719
|
+
/**
|
|
7720
|
+
* Exported for ONE caller: the still-shelf gate in the centre, which must know
|
|
7721
|
+
* — before the outbox row exists — whether any enabled rule would fire on THIS
|
|
7722
|
+
* edge and wants a picture for it. Sharing the predicate is what keeps that gate
|
|
7723
|
+
* from becoming a second, drifting opinion about which edge belongs to which
|
|
7724
|
+
* rule. It is pure and reads nothing.
|
|
7725
|
+
*/
|
|
7677
7726
|
function matchesOccupancy(occ, s) {
|
|
7678
7727
|
if ((occ.zoneId ?? void 0) !== (s.zoneId ?? void 0)) return false;
|
|
7679
7728
|
if ((occ.className ?? void 0) !== (s.className ?? void 0)) return false;
|
|
@@ -9881,7 +9930,7 @@ function incomingFromSensorEvent(event, markerTrackId) {
|
|
|
9881
9930
|
* Audio-event persist (classified episode).
|
|
9882
9931
|
*
|
|
9883
9932
|
* `stillOwnerId` is the audio still shelf's owner id for the photograph taken
|
|
9884
|
-
* at this episode (`
|
|
9933
|
+
* at this episode (`still-shelf.ts`). Absent when no rule that could fire asked
|
|
9885
9934
|
* for a picture — the same shape `incomingFromSensorEvent` carries its marker.
|
|
9886
9935
|
*/
|
|
9887
9936
|
function incomingFromAudioEvent(event, stillOwnerId) {
|
|
@@ -9962,10 +10011,16 @@ function audioHitMeta(hit) {
|
|
|
9962
10011
|
samples: window.samples
|
|
9963
10012
|
};
|
|
9964
10013
|
}
|
|
9965
|
-
/**
|
|
9966
|
-
|
|
10014
|
+
/**
|
|
10015
|
+
* A committed ZoneAnalytics occupancy edge.
|
|
10016
|
+
*
|
|
10017
|
+
* `stillOwnerId` — see {@link subjectFromOccupancyEvent}. An edge whose scope is
|
|
10018
|
+
* empty owns no member track, so the only picture it can carry is a photograph
|
|
10019
|
+
* of its camera taken at the edge, held on the still shelf under this id.
|
|
10020
|
+
*/
|
|
10021
|
+
function incomingFromOccupancyEdge(edge, stillOwnerId) {
|
|
9967
10022
|
return {
|
|
9968
|
-
subject: subjectFromOccupancyEvent(edge),
|
|
10023
|
+
subject: subjectFromOccupancyEvent(edge, stillOwnerId),
|
|
9969
10024
|
kind: "occupancy-event",
|
|
9970
10025
|
origin: "pipeline",
|
|
9971
10026
|
log: () => ({
|
|
@@ -16094,7 +16149,7 @@ function isSyntheticId(id) {
|
|
|
16094
16149
|
* - and, since 2026-08-15, a `trackId` for both AUDIO kinds: a sound owns no
|
|
16095
16150
|
* frame, so production photographs the camera at the match and holds the
|
|
16096
16151
|
* bytes on the still shelf under an owner id the subject names
|
|
16097
|
-
* (`
|
|
16152
|
+
* (`still-shelf.ts`). The tester's RAM-held frame stands in for exactly that
|
|
16098
16153
|
* photograph, and — unlike every other kind here — it is taken the same way
|
|
16099
16154
|
* production takes it, at the same moment, from the same cap.
|
|
16100
16155
|
*
|
|
@@ -18761,11 +18816,12 @@ var NotificationCenter = class NotificationCenter {
|
|
|
18761
18816
|
*/
|
|
18762
18817
|
syntheticMedia = /* @__PURE__ */ new Map();
|
|
18763
18818
|
/**
|
|
18764
|
-
* The photograph a
|
|
18765
|
-
*
|
|
18766
|
-
*
|
|
18819
|
+
* The photograph a notification carries when the subject it is about is an
|
|
18820
|
+
* ABSENCE — a sound that owns no frame, an emptied occupancy scope that owns
|
|
18821
|
+
* no member. See `still-shelf.ts`. Null when this wiring has no snapshot cap,
|
|
18822
|
+
* which leaves those notifications text-only rather than broken.
|
|
18767
18823
|
*/
|
|
18768
|
-
|
|
18824
|
+
stills;
|
|
18769
18825
|
/** Per-device rate limit for the "matched NO rule" report — see `reportNoMatch`. */
|
|
18770
18826
|
lastNoMatchReportAt = /* @__PURE__ */ new Map();
|
|
18771
18827
|
noMatchSuppressed = /* @__PURE__ */ new Map();
|
|
@@ -19062,9 +19118,9 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19062
19118
|
this.logger = deps.logger;
|
|
19063
19119
|
this.now = deps.now ?? (() => Date.now());
|
|
19064
19120
|
const getSnapshot = deps.getSnapshot;
|
|
19065
|
-
this.
|
|
19121
|
+
this.stills = getSnapshot !== void 0 ? new NcStillShelf({
|
|
19066
19122
|
getSnapshot,
|
|
19067
|
-
logger: this.logger.child("
|
|
19123
|
+
logger: this.logger.child("still-shelf"),
|
|
19068
19124
|
now: this.now
|
|
19069
19125
|
}) : null;
|
|
19070
19126
|
this.rules = new NcRuleStore({
|
|
@@ -19154,8 +19210,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19154
19210
|
getMediaForOwner: async (ownerKind, ownerId) => {
|
|
19155
19211
|
const held = isSyntheticId(ownerId) ? this.syntheticMedia.get(ownerId) : void 0;
|
|
19156
19212
|
if (held !== void 0) return held.files;
|
|
19157
|
-
const
|
|
19158
|
-
if (
|
|
19213
|
+
const shelved = isStillShelfId(ownerId) ? this.stills?.get(ownerId) : void 0;
|
|
19214
|
+
if (shelved !== void 0) return shelved;
|
|
19159
19215
|
return deps.dispatcher.getMediaForOwner(ownerKind, ownerId);
|
|
19160
19216
|
},
|
|
19161
19217
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
@@ -19361,7 +19417,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19361
19417
|
}
|
|
19362
19418
|
this.timelapseScheduler?.stop();
|
|
19363
19419
|
this.summaryProducer?.stop();
|
|
19364
|
-
this.
|
|
19420
|
+
this.stills?.clear();
|
|
19365
19421
|
this.addonUpdates.dispose();
|
|
19366
19422
|
this.livenessHoldoff.dispose();
|
|
19367
19423
|
this.evaluationActive = false;
|
|
@@ -19713,14 +19769,14 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19713
19769
|
* direction that DESTROYS the media.
|
|
19714
19770
|
*/
|
|
19715
19771
|
audioStillOwner(deviceId, atMs, mode) {
|
|
19716
|
-
const shelf = this.
|
|
19772
|
+
const shelf = this.stills;
|
|
19717
19773
|
if (shelf === null || !this.evaluationActive) return void 0;
|
|
19718
19774
|
for (const rule of this.rules.listEnabled("immediate")) {
|
|
19719
19775
|
if (rule.media.attach === "none") continue;
|
|
19720
19776
|
if (!(mode === "condition" ? rule.conditions.audio !== void 0 : rule.conditions.audio === void 0 && referencesAudioClass(rule.conditions.classes))) continue;
|
|
19721
19777
|
const devices = rule.conditions.devices;
|
|
19722
19778
|
if (devices !== void 0 && devices.length > 0 && !devices.includes(deviceId)) continue;
|
|
19723
|
-
return shelf.capture(deviceId, atMs);
|
|
19779
|
+
return shelf.capture(deviceId, atMs, "audio");
|
|
19724
19780
|
}
|
|
19725
19781
|
}
|
|
19726
19782
|
/**
|
|
@@ -19779,7 +19835,48 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19779
19835
|
* Public so a test / a future out-of-band edge source can drive it directly.
|
|
19780
19836
|
*/
|
|
19781
19837
|
onOccupancyEdge(edge) {
|
|
19782
|
-
this.consumeEvent(incomingFromOccupancyEdge(edge));
|
|
19838
|
+
this.consumeEvent(incomingFromOccupancyEdge(edge, this.occupancyStillOwner(edge)));
|
|
19839
|
+
}
|
|
19840
|
+
/**
|
|
19841
|
+
* Start photographing this edge's camera NOW and return the owner id its
|
|
19842
|
+
* subject should name — or `undefined` when nothing would use it.
|
|
19843
|
+
*
|
|
19844
|
+
* **Only an edge that owns NOBODY.** `chooseOccupancyMediaOwner` names a
|
|
19845
|
+
* member track whenever the scope holds one, and that picture is of the
|
|
19846
|
+
* counted object itself; a photograph is strictly the stand-in for not having
|
|
19847
|
+
* one. The case this exists for is "posto libero" (camera 617): the vehicle
|
|
19848
|
+
* whose departure IS the notification has left, so the scope is empty, the
|
|
19849
|
+
* ladder resolves nothing and the delivery logged `no still could be resolved
|
|
19850
|
+
* … owners=[]`. A became-free edge has no owner BY CONSTRUCTION, and a
|
|
19851
|
+
* became-occupied edge can lack one too (every member parked, i.e.
|
|
19852
|
+
* `stationary:`) — so the gate is the ABSENCE itself, not the polarity. That
|
|
19853
|
+
* is the honest predicate: photograph exactly the edges whose media cannot be
|
|
19854
|
+
* resolved, and no others.
|
|
19855
|
+
*
|
|
19856
|
+
* **The capture is not awaited**, for the audio shelf's reason and one of its
|
|
19857
|
+
* own: a freed space is about to be taken by the next car, and a still fetched
|
|
19858
|
+
* after the outbox drained would show it occupied again.
|
|
19859
|
+
*
|
|
19860
|
+
* **The gate is the RULE's media policy AND the engine's own verdict.** The
|
|
19861
|
+
* watcher commits both polarities of every watched key, so a hub with only a
|
|
19862
|
+
* became-occupied rule still produces free edges — photographing those would
|
|
19863
|
+
* be a snapshot no notification can ever carry. `matchesOccupancy` is the same
|
|
19864
|
+
* predicate `evaluateRule` will apply, shared rather than restated. The rule
|
|
19865
|
+
* cache is an in-memory mirror, so this is not a fallible read (D49).
|
|
19866
|
+
*/
|
|
19867
|
+
occupancyStillOwner(edge) {
|
|
19868
|
+
const shelf = this.stills;
|
|
19869
|
+
if (shelf === null || !this.evaluationActive) return void 0;
|
|
19870
|
+
if (edge.mediaTrackId !== void 0) return void 0;
|
|
19871
|
+
const subject = occupancySubjectFromEdge(edge);
|
|
19872
|
+
for (const rule of this.rules.listEnabled("device-event")) {
|
|
19873
|
+
if (rule.media.attach === "none") continue;
|
|
19874
|
+
const occ = rule.conditions.occupancy;
|
|
19875
|
+
if (occ === void 0 || !matchesOccupancy(occ, subject)) continue;
|
|
19876
|
+
const devices = rule.conditions.devices;
|
|
19877
|
+
if (devices !== void 0 && devices.length > 0 && !devices.includes(edge.deviceId)) continue;
|
|
19878
|
+
return shelf.capture(edge.deviceId, edge.timestamp, "occupancy");
|
|
19879
|
+
}
|
|
19783
19880
|
}
|
|
19784
19881
|
/**
|
|
19785
19882
|
* Feed ONE audio inference frame into the sampling-window matcher.
|
|
@@ -20746,7 +20843,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20746
20843
|
kind,
|
|
20747
20844
|
ruleId: rule.id,
|
|
20748
20845
|
rule: rule.name,
|
|
20749
|
-
snapshotWired: this.
|
|
20846
|
+
snapshotWired: this.stills !== null
|
|
20750
20847
|
}
|
|
20751
20848
|
});
|
|
20752
20849
|
}
|
|
@@ -20911,6 +21008,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20911
21008
|
if (subject.kind === "audio-window" || subject.kind === "audio-event") return { outcome: "disabled" };
|
|
20912
21009
|
const trackId = subject.trackId;
|
|
20913
21010
|
if (trackId === void 0) return { outcome: "disabled" };
|
|
21011
|
+
if (isStillShelfId(trackId)) return { outcome: "disabled" };
|
|
20914
21012
|
const member = {
|
|
20915
21013
|
trackId,
|
|
20916
21014
|
className: subject.classNames[0] ?? "event",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as encodeVectorBase64, A as RetrainStatusSchema, At as literal, B as TrackSourceSchema, C as NcSnoozeSchema, Ct as isDeviceScopedCap, D as OpsLogEntrySchema, Dt as array, E as NcTaxonomySchema, Et as _enum, F as SceneMonitorSchema, Ft as string, G as audioMetricsCapability, H as addonWidgetsSourceCapability, I as TIMELAPSE_DENSE_FLOOR_SEC, It as unknown, J as cosineSimilarity$1, K as audioModeOf, L as TimelapseRuleInputSchema, Lt as EventCategory, M as SCENE_DEFAULT_ANCHOR_THRESHOLD, Mt as object, N as SCENE_DEFAULT_UNCOVERED_POLICY, Nt as partialRecord, Ot as boolean, P as SCENE_DIVERGED, Pt as record, R as TimelapseRulePatchSchema, S as NcSnoozeInputSchema, St as hydrateSchema, T as NcSystemEventKindSchema, Tt as sleep, U as alarmPanelCapability, V as VISIT_MERGE_GAP_MS, W as assertTimelapseCadences, X as defineCustomActions, Y as customAction, Z as deriveRecordingMode, _ as NcRuleInputSchema, _t as zoneAnalyticsCapability, at as notificationRulesCapability, b as NcRuleTargetSchema, bt as DeviceType, c as EVENT_PAD_MS, ct as plateGalleryCapability, d as NC_ALARM_SYSTEM_EVENT_KINDS, et as faceGalleryCapability, f as NC_CONDITION_CATALOG, ft as sceneMonitorCapability, g as NcConditionDescriptorSchema, gt as videoclipsCapability, h as NC_TAXONOMY, ht as vectorDimFromBase64, i as DETECTION_MACRO_CLASSES, it as kebabToCamel, jt as number, k as RECORDING_EXPORT_MAX_READ_BYTES, kt as discriminatedUnion, l as LabelAttributionSchema, lt as readDeviceStateFrom, mt as systemEventFilterApplies, n as DEFAULT_EVENT_COLOR, nt as isDetectionMacroClass, o as DeclaredDevices, p as NC_DEFAULT_SNOOZE_MINUTES, pt as subKindsOf, q as buildEventKindDescriptor, rt as isScheduleActive, s as EVENT_KIND_BY_CAP, st as pipelineAnalyticsCapability, t as BaseDevice, u as MACRO_LABELS, ut as readTimelapseGeneratedAt, v as NcRulePatchSchema, vt as errMsg, w as NcSnoozeSuppressedSchema, wt as nodePin, x as NcScheduleSchema, xt as createEvent, y as NcRuleSchema, yt as BaseAddon, z as TimelapseRuleSchema } from "../dist-
|
|
1
|
+
import { $ as encodeVectorBase64, A as RetrainStatusSchema, At as literal, B as TrackSourceSchema, C as NcSnoozeSchema, Ct as isDeviceScopedCap, D as OpsLogEntrySchema, Dt as array, E as NcTaxonomySchema, Et as _enum, F as SceneMonitorSchema, Ft as string, G as audioMetricsCapability, H as addonWidgetsSourceCapability, I as TIMELAPSE_DENSE_FLOOR_SEC, It as unknown, J as cosineSimilarity$1, K as audioModeOf, L as TimelapseRuleInputSchema, Lt as EventCategory, M as SCENE_DEFAULT_ANCHOR_THRESHOLD, Mt as object, N as SCENE_DEFAULT_UNCOVERED_POLICY, Nt as partialRecord, Ot as boolean, P as SCENE_DIVERGED, Pt as record, R as TimelapseRulePatchSchema, S as NcSnoozeInputSchema, St as hydrateSchema, T as NcSystemEventKindSchema, Tt as sleep, U as alarmPanelCapability, V as VISIT_MERGE_GAP_MS, W as assertTimelapseCadences, X as defineCustomActions, Y as customAction, Z as deriveRecordingMode, _ as NcRuleInputSchema, _t as zoneAnalyticsCapability, at as notificationRulesCapability, b as NcRuleTargetSchema, bt as DeviceType, c as EVENT_PAD_MS, ct as plateGalleryCapability, d as NC_ALARM_SYSTEM_EVENT_KINDS, et as faceGalleryCapability, f as NC_CONDITION_CATALOG, ft as sceneMonitorCapability, g as NcConditionDescriptorSchema, gt as videoclipsCapability, h as NC_TAXONOMY, ht as vectorDimFromBase64, i as DETECTION_MACRO_CLASSES, it as kebabToCamel, jt as number, k as RECORDING_EXPORT_MAX_READ_BYTES, kt as discriminatedUnion, l as LabelAttributionSchema, lt as readDeviceStateFrom, mt as systemEventFilterApplies, n as DEFAULT_EVENT_COLOR, nt as isDetectionMacroClass, o as DeclaredDevices, p as NC_DEFAULT_SNOOZE_MINUTES, pt as subKindsOf, q as buildEventKindDescriptor, rt as isScheduleActive, s as EVENT_KIND_BY_CAP, st as pipelineAnalyticsCapability, t as BaseDevice, u as MACRO_LABELS, ut as readTimelapseGeneratedAt, v as NcRulePatchSchema, vt as errMsg, w as NcSnoozeSuppressedSchema, wt as nodePin, x as NcScheduleSchema, xt as createEvent, y as NcRuleSchema, yt as BaseAddon, z as TimelapseRuleSchema } from "../dist-ecY_-nDx.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -5555,24 +5555,39 @@ async function downscaleFullFrameJpeg(jpeg, maxWidth = 640, maxHeight = 360) {
|
|
|
5555
5555
|
}).jpeg({ quality: FULL_FRAME_QUALITY }).toBuffer();
|
|
5556
5556
|
}
|
|
5557
5557
|
//#endregion
|
|
5558
|
-
//#region src/notification-center/
|
|
5558
|
+
//#region src/notification-center/still-shelf.ts
|
|
5559
5559
|
/**
|
|
5560
|
-
* The
|
|
5560
|
+
* The still shelf — the PHOTOGRAPH a notification carries when nothing it can
|
|
5561
|
+
* name owns a frame.
|
|
5561
5562
|
*
|
|
5562
|
-
* ## Why a
|
|
5563
|
+
* ## Why a trigger needs one at all
|
|
5563
5564
|
*
|
|
5564
|
-
* The attachment ladder resolves media by OWNER, and
|
|
5565
|
-
*
|
|
5566
|
-
*
|
|
5565
|
+
* The attachment ladder resolves media by OWNER, and most triggers have one: an
|
|
5566
|
+
* object or package event owns its crops, a closed track owns its best shot, a
|
|
5567
|
+
* doorbell press owns the marker track the same press projected
|
|
5567
5568
|
* (`sensor-marker-projector.ts`), an occupancy edge names one of the objects it
|
|
5568
|
-
* counted (`chooseOccupancyMediaOwner`).
|
|
5569
|
-
*
|
|
5570
|
-
*
|
|
5571
|
-
*
|
|
5569
|
+
* counted (`chooseOccupancyMediaOwner`). Two triggers own nothing, and for the
|
|
5570
|
+
* same reason in both cases — the subject is an ABSENCE:
|
|
5571
|
+
*
|
|
5572
|
+
* - an **audio** match: nothing was boxed, nothing was tracked, and —
|
|
5573
|
+
* deliberately — nothing is persisted at all. A confirmed window is a claim
|
|
5574
|
+
* about sound that has already stopped, and `event-intake.ts` states why
|
|
5575
|
+
* replaying it later would be wrong.
|
|
5576
|
+
* - an **occupancy** edge whose scope is EMPTY — "posto libero". The vehicle
|
|
5577
|
+
* whose departure IS the news has left, so `chooseOccupancyMediaOwner` names
|
|
5578
|
+
* nobody and the ladder logs `no still could be resolved … owners=[]`.
|
|
5572
5579
|
*
|
|
5573
5580
|
* So the only honest picture is a PHOTOGRAPH of the camera taken at the moment
|
|
5574
|
-
* of the
|
|
5575
|
-
*
|
|
5581
|
+
* of the trigger. Not of the sound, not of the object that left — of what the
|
|
5582
|
+
* camera can see now that it happened. For a freed parking space that is
|
|
5583
|
+
* exactly the answer the operator wants: the space, empty.
|
|
5584
|
+
*
|
|
5585
|
+
* **One shelf, not two.** The mechanism is identical down to the reuse window,
|
|
5586
|
+
* and the only thing that differs between the two triggers is the GATE deciding
|
|
5587
|
+
* that a photograph is owed at all — which belongs at the trigger site, where
|
|
5588
|
+
* the rules are, and not here. A twin module would be a second derivation of
|
|
5589
|
+
* one thing. The trigger rides along as {@link NcStillTrigger} only so the logs
|
|
5590
|
+
* can say which absence they are about.
|
|
5576
5591
|
*
|
|
5577
5592
|
* ## Three properties, and each one is a decision
|
|
5578
5593
|
*
|
|
@@ -5580,27 +5595,30 @@ async function downscaleFullFrameJpeg(jpeg, maxWidth = 640, maxHeight = 360) {
|
|
|
5580
5595
|
* TTL that covers the outbox's whole retry horizon — and nowhere else. The
|
|
5581
5596
|
* alternative was the doorbell's: materialise a synthetic marker track through
|
|
5582
5597
|
* `SyntheticTrackMaterializer` and let the notification name it. That would put
|
|
5583
|
-
* a durable Track on the camera's timeline for every confirmed window
|
|
5584
|
-
* the digest's `listTracks`, retention, and the audio-
|
|
5585
|
-
* operator ceilings (`audio-marker-projector.ts` exists
|
|
5586
|
-
* many audio markers a camera may emit). A notification
|
|
5587
|
-
* timeline history as a side effect of wanting a picture.
|
|
5588
|
-
*
|
|
5589
|
-
* **The capture STARTS immediately and is never awaited.**
|
|
5590
|
-
* — a scream is over before a snapshot round-trip completes
|
|
5591
|
-
*
|
|
5592
|
-
*
|
|
5593
|
-
*
|
|
5594
|
-
*
|
|
5595
|
-
*
|
|
5596
|
-
*
|
|
5597
|
-
*
|
|
5598
|
+
* a durable Track on the camera's timeline for every confirmed window and every
|
|
5599
|
+
* emptied zone, feeding the digest's `listTracks`, retention, and the audio-
|
|
5600
|
+
* marker feature's own operator ceilings (`audio-marker-projector.ts` exists
|
|
5601
|
+
* precisely to bound how many audio markers a camera may emit). A notification
|
|
5602
|
+
* must not manufacture timeline history as a side effect of wanting a picture.
|
|
5603
|
+
*
|
|
5604
|
+
* **The capture STARTS immediately and is never awaited.** The subject is
|
|
5605
|
+
* transient — a scream is over before a snapshot round-trip completes, and a
|
|
5606
|
+
* freed space is about to be taken by the next car — so the fetch is kicked off
|
|
5607
|
+
* at the trigger, before the rule evaluation runs, and the owner id is minted
|
|
5608
|
+
* synchronously so the outbox row can name it. The bytes land while the row
|
|
5609
|
+
* waits in the queue, and the dispatcher's existing bounded still-wait (or the
|
|
5610
|
+
* pause its own footage render already costs) picks them up. A camera that
|
|
5611
|
+
* never answers costs the picture and never the notification.
|
|
5612
|
+
*
|
|
5613
|
+
* **Two triggers seconds apart share ONE capture.** A barking dog confirms
|
|
5598
5614
|
* repeatedly and a label-mode rule has no re-arm timer at all (D157) — the
|
|
5599
5615
|
* rule's cooldown is its only brake, and the cooldown is applied AFTER this.
|
|
5600
5616
|
* Without a reuse window this would photograph a camera at whatever rate the
|
|
5601
|
-
* sound happens to occur. Each
|
|
5617
|
+
* sound happens to occur. Each trigger still gets its OWN owner id, so two
|
|
5602
5618
|
* outbox rows are never mistaken for one subject; they merely point at the same
|
|
5603
|
-
* frame, which is the truth — the scene did not change in ten seconds.
|
|
5619
|
+
* frame, which is the truth — the scene did not change in ten seconds. The
|
|
5620
|
+
* window is per CAMERA and trigger-agnostic for the same reason: a sound and an
|
|
5621
|
+
* emptied zone ten seconds apart are two claims about one scene.
|
|
5604
5622
|
*
|
|
5605
5623
|
* Nothing here is silent: a capture that lands and a camera that refuses each
|
|
5606
5624
|
* emit one line carrying `tags: { deviceId }`, because "why did 617 get a photo
|
|
@@ -5608,13 +5626,13 @@ async function downscaleFullFrameJpeg(jpeg, maxWidth = 640, maxHeight = 360) {
|
|
|
5608
5626
|
*/
|
|
5609
5627
|
/**
|
|
5610
5628
|
* The owner-id namespace. It is what routes a lookup here instead of to the
|
|
5611
|
-
* media store, and it is the reason the dispatcher needs no audio
|
|
5612
|
-
* `getMediaForOwner` answers for
|
|
5629
|
+
* media store, and it is the reason the dispatcher needs no audio or occupancy
|
|
5630
|
+
* branch — `getMediaForOwner` answers for all of them under one signature.
|
|
5613
5631
|
*/
|
|
5614
|
-
var
|
|
5632
|
+
var NC_STILL_SHELF_PREFIX = "nc-still:";
|
|
5615
5633
|
/** True for an owner id this shelf minted. */
|
|
5616
|
-
function
|
|
5617
|
-
return id.startsWith(
|
|
5634
|
+
function isStillShelfId(id) {
|
|
5635
|
+
return id.startsWith(NC_STILL_SHELF_PREFIX);
|
|
5618
5636
|
}
|
|
5619
5637
|
/**
|
|
5620
5638
|
* How long the bytes are held.
|
|
@@ -5624,16 +5642,16 @@ function isAudioStillId(id) {
|
|
|
5624
5642
|
* ships text-only, which is the correct degradation for a photograph of a scene
|
|
5625
5643
|
* that is a quarter of an hour stale anyway.
|
|
5626
5644
|
*/
|
|
5627
|
-
var
|
|
5645
|
+
var NC_STILL_SHELF_TTL_MS = 15 * 6e4;
|
|
5628
5646
|
/** A hanging snapshot cap must not pin a capture slot forever. */
|
|
5629
5647
|
var SNAPSHOT_TIMEOUT_MS = 8e3;
|
|
5630
5648
|
/**
|
|
5631
|
-
* Hard bound on held captures. Reached only if every camera on the hub
|
|
5632
|
-
*
|
|
5649
|
+
* Hard bound on held captures. Reached only if every camera on the hub triggers
|
|
5650
|
+
* one inside a single TTL; the oldest is dropped first, which costs a
|
|
5633
5651
|
* fifteen-minute-old picture nobody is waiting for.
|
|
5634
5652
|
*/
|
|
5635
5653
|
var MAX_CAPTURES = 64;
|
|
5636
|
-
var
|
|
5654
|
+
var NcStillShelf = class {
|
|
5637
5655
|
deps;
|
|
5638
5656
|
/** captureId → the photograph. */
|
|
5639
5657
|
captures = /* @__PURE__ */ new Map();
|
|
@@ -5645,14 +5663,14 @@ var NcAudioStillShelf = class {
|
|
|
5645
5663
|
this.deps = deps;
|
|
5646
5664
|
}
|
|
5647
5665
|
/**
|
|
5648
|
-
* Photograph `deviceId` for a
|
|
5649
|
-
* subject should name. SYNCHRONOUS by contract: the caller is on the
|
|
5650
|
-
*
|
|
5666
|
+
* Photograph `deviceId` for a trigger at `atMs`, and return the owner id the
|
|
5667
|
+
* subject should name. SYNCHRONOUS by contract: the caller is on the trigger
|
|
5668
|
+
* path and the outbox row is built from what this returns.
|
|
5651
5669
|
*/
|
|
5652
|
-
capture(deviceId, atMs) {
|
|
5670
|
+
capture(deviceId, atMs, trigger) {
|
|
5653
5671
|
const now = this.deps.now();
|
|
5654
5672
|
this.prune(now);
|
|
5655
|
-
const ownerId = `${
|
|
5673
|
+
const ownerId = `${NC_STILL_SHELF_PREFIX}${randomUUID()}`;
|
|
5656
5674
|
const recent = this.newest.get(deviceId);
|
|
5657
5675
|
if (recent !== void 0 && now - recent.at < 1e4 && this.captures.has(recent.captureId)) {
|
|
5658
5676
|
this.owners.set(ownerId, recent.captureId);
|
|
@@ -5661,8 +5679,9 @@ var NcAudioStillShelf = class {
|
|
|
5661
5679
|
const captureId = randomUUID();
|
|
5662
5680
|
this.captures.set(captureId, {
|
|
5663
5681
|
deviceId,
|
|
5682
|
+
trigger,
|
|
5664
5683
|
startedAt: now,
|
|
5665
|
-
expiresAt: now +
|
|
5684
|
+
expiresAt: now + NC_STILL_SHELF_TTL_MS,
|
|
5666
5685
|
files: []
|
|
5667
5686
|
});
|
|
5668
5687
|
this.newest.set(deviceId, {
|
|
@@ -5670,7 +5689,7 @@ var NcAudioStillShelf = class {
|
|
|
5670
5689
|
at: now
|
|
5671
5690
|
});
|
|
5672
5691
|
this.owners.set(ownerId, captureId);
|
|
5673
|
-
this.fetch(captureId, deviceId, atMs);
|
|
5692
|
+
this.fetch(captureId, deviceId, atMs, trigger);
|
|
5674
5693
|
return ownerId;
|
|
5675
5694
|
}
|
|
5676
5695
|
/**
|
|
@@ -5706,11 +5725,11 @@ var NcAudioStillShelf = class {
|
|
|
5706
5725
|
this.owners.clear();
|
|
5707
5726
|
this.newest.clear();
|
|
5708
5727
|
}
|
|
5709
|
-
async fetch(captureId, deviceId, atMs) {
|
|
5728
|
+
async fetch(captureId, deviceId, atMs, trigger) {
|
|
5710
5729
|
try {
|
|
5711
5730
|
const shot = await withTimeout$3(this.deps.getSnapshot(deviceId), SNAPSHOT_TIMEOUT_MS);
|
|
5712
5731
|
if (shot === null || shot.base64.length === 0) {
|
|
5713
|
-
this.reportMiss(deviceId, "the camera returned no snapshot");
|
|
5732
|
+
this.reportMiss(deviceId, trigger, "the camera returned no snapshot");
|
|
5714
5733
|
return;
|
|
5715
5734
|
}
|
|
5716
5735
|
const raw = Buffer.from(shot.base64, "base64");
|
|
@@ -5722,17 +5741,18 @@ var NcAudioStillShelf = class {
|
|
|
5722
5741
|
if (held === void 0) return;
|
|
5723
5742
|
this.captures.set(captureId, {
|
|
5724
5743
|
...held,
|
|
5725
|
-
files:
|
|
5744
|
+
files: stillShelfMedia(data, atMs)
|
|
5726
5745
|
});
|
|
5727
|
-
this.deps.logger.info(
|
|
5746
|
+
this.deps.logger.info(`${trigger} still captured`, {
|
|
5728
5747
|
tags: { deviceId },
|
|
5729
5748
|
meta: {
|
|
5749
|
+
trigger,
|
|
5730
5750
|
bytes: data.byteLength,
|
|
5731
5751
|
tookMs: this.deps.now() - held.startedAt
|
|
5732
5752
|
}
|
|
5733
5753
|
});
|
|
5734
5754
|
} catch (err) {
|
|
5735
|
-
this.reportMiss(deviceId, err instanceof Error ? err.message : String(err));
|
|
5755
|
+
this.reportMiss(deviceId, trigger, err instanceof Error ? err.message : String(err));
|
|
5736
5756
|
}
|
|
5737
5757
|
}
|
|
5738
5758
|
/**
|
|
@@ -5740,10 +5760,13 @@ var NcAudioStillShelf = class {
|
|
|
5740
5760
|
* on a notification they DID receive, so it is a warn and it carries the
|
|
5741
5761
|
* camera — the only key the question is ever asked with.
|
|
5742
5762
|
*/
|
|
5743
|
-
reportMiss(deviceId, reason) {
|
|
5744
|
-
this.deps.logger.warn(
|
|
5763
|
+
reportMiss(deviceId, trigger, reason) {
|
|
5764
|
+
this.deps.logger.warn(`the camera did not answer the ${trigger} still — this notification ships text-only`, {
|
|
5745
5765
|
tags: { deviceId },
|
|
5746
|
-
meta: {
|
|
5766
|
+
meta: {
|
|
5767
|
+
trigger,
|
|
5768
|
+
reason
|
|
5769
|
+
}
|
|
5747
5770
|
});
|
|
5748
5771
|
}
|
|
5749
5772
|
};
|
|
@@ -5758,10 +5781,10 @@ var NcAudioStillShelf = class {
|
|
|
5758
5781
|
* so. Inventing a centre crop would answer a different question from the one
|
|
5759
5782
|
* the operator asked.
|
|
5760
5783
|
*/
|
|
5761
|
-
function
|
|
5784
|
+
function stillShelfMedia(data, timestamp) {
|
|
5762
5785
|
const base64 = data.toString("base64");
|
|
5763
5786
|
const file = (kind) => ({
|
|
5764
|
-
key: `${
|
|
5787
|
+
key: `${NC_STILL_SHELF_PREFIX}${kind}`,
|
|
5765
5788
|
kind,
|
|
5766
5789
|
base64,
|
|
5767
5790
|
sizeBytes: data.byteLength,
|
|
@@ -7427,7 +7450,7 @@ function subjectFromSensorEvent(ev, markerTrackId) {
|
|
|
7427
7450
|
* (see the {@link evaluateRule} audio gate + the matchers below).
|
|
7428
7451
|
*
|
|
7429
7452
|
* `stillOwnerId` is the audio still shelf's owner id — the photograph of the
|
|
7430
|
-
* camera taken at the match (`
|
|
7453
|
+
* camera taken at the match (`still-shelf.ts`). It rides `trackId` because that
|
|
7431
7454
|
* is the field the attachment ladder reads, exactly as a doorbell press rides
|
|
7432
7455
|
* its marker track's id; nothing else about the subject is track-scoped, and
|
|
7433
7456
|
* `buildEntries` keys an audio row on its record id, never on this. Absent when
|
|
@@ -7519,10 +7542,18 @@ function audioSubjectFor(hit, spec) {
|
|
|
7519
7542
|
* chosen at commit (`chooseOccupancyMediaOwner`) — rides on `trackId`, and that
|
|
7520
7543
|
* is the whole of what gives an occupancy notification a picture: the ordinary
|
|
7521
7544
|
* media-owner ladder resolves it, exactly as it resolves a doorbell's marker
|
|
7522
|
-
* track.
|
|
7523
|
-
*
|
|
7524
|
-
|
|
7525
|
-
|
|
7545
|
+
* track.
|
|
7546
|
+
*
|
|
7547
|
+
* `stillOwnerId` is the SECOND source of that picture and the only one an
|
|
7548
|
+
* emptied scope can have: with no member left to name — "posto libero", where
|
|
7549
|
+
* the object whose departure is the news has gone — the trigger site photographs
|
|
7550
|
+
* the camera and holds the bytes on the still shelf (`still-shelf.ts`), exactly
|
|
7551
|
+
* as an audio match does. The member track always WINS: it is a picture of the
|
|
7552
|
+
* counted object, and a photograph is only ever the stand-in for not having one.
|
|
7553
|
+
* Neither present (no snapshot cap on this node) ⇒ text-only, as before.
|
|
7554
|
+
*/
|
|
7555
|
+
function subjectFromOccupancyEvent(edge, stillOwnerId) {
|
|
7556
|
+
const mediaOwner = edge.mediaTrackId ?? stillOwnerId;
|
|
7526
7557
|
return {
|
|
7527
7558
|
kind: "occupancy-event",
|
|
7528
7559
|
recordId: `occ:${edge.deviceId}:${edge.zoneId ?? "@frame"}:${edge.className ?? "@all"}:t${edge.threshold}:${edge.occupied ? "occ" : "free"}:${edge.timestamp}`,
|
|
@@ -7530,17 +7561,28 @@ function subjectFromOccupancyEvent(edge) {
|
|
|
7530
7561
|
timestamp: edge.timestamp,
|
|
7531
7562
|
classNames: [edge.className ?? "occupancy"],
|
|
7532
7563
|
zones: [],
|
|
7533
|
-
...
|
|
7564
|
+
...mediaOwner !== void 0 ? { trackId: mediaOwner } : {},
|
|
7534
7565
|
source: "pipeline",
|
|
7535
|
-
occupancy:
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7566
|
+
occupancy: occupancySubjectFromEdge(edge)
|
|
7567
|
+
};
|
|
7568
|
+
}
|
|
7569
|
+
/**
|
|
7570
|
+
* The scope + level half of an occupancy subject, ALONE.
|
|
7571
|
+
*
|
|
7572
|
+
* Split out for one reason: the still-shelf gate must ask `matchesOccupancy`
|
|
7573
|
+
* whether a rule would fire on an edge BEFORE the subject that would carry the
|
|
7574
|
+
* answer exists (the owner id it mints is an input to that subject). Sharing
|
|
7575
|
+
* this keeps the gate reading the same numbers the verdict will.
|
|
7576
|
+
*/
|
|
7577
|
+
function occupancySubjectFromEdge(edge) {
|
|
7578
|
+
return {
|
|
7579
|
+
...edge.zoneId !== void 0 ? { zoneId: edge.zoneId } : {},
|
|
7580
|
+
...edge.zoneName !== void 0 ? { zoneName: edge.zoneName } : {},
|
|
7581
|
+
...edge.className !== void 0 ? { className: edge.className } : {},
|
|
7582
|
+
count: edge.count,
|
|
7583
|
+
previousCount: edge.previousCount,
|
|
7584
|
+
occupied: edge.occupied,
|
|
7585
|
+
threshold: edge.threshold
|
|
7544
7586
|
};
|
|
7545
7587
|
}
|
|
7546
7588
|
/** Build the subject for a `package-event` evaluation (a persisted `package`
|
|
@@ -7667,6 +7709,13 @@ function matchesAudio(cond, s) {
|
|
|
7667
7709
|
}
|
|
7668
7710
|
return false;
|
|
7669
7711
|
}
|
|
7712
|
+
/**
|
|
7713
|
+
* Exported for ONE caller: the still-shelf gate in the centre, which must know
|
|
7714
|
+
* — before the outbox row exists — whether any enabled rule would fire on THIS
|
|
7715
|
+
* edge and wants a picture for it. Sharing the predicate is what keeps that gate
|
|
7716
|
+
* from becoming a second, drifting opinion about which edge belongs to which
|
|
7717
|
+
* rule. It is pure and reads nothing.
|
|
7718
|
+
*/
|
|
7670
7719
|
function matchesOccupancy(occ, s) {
|
|
7671
7720
|
if ((occ.zoneId ?? void 0) !== (s.zoneId ?? void 0)) return false;
|
|
7672
7721
|
if ((occ.className ?? void 0) !== (s.className ?? void 0)) return false;
|
|
@@ -9874,7 +9923,7 @@ function incomingFromSensorEvent(event, markerTrackId) {
|
|
|
9874
9923
|
* Audio-event persist (classified episode).
|
|
9875
9924
|
*
|
|
9876
9925
|
* `stillOwnerId` is the audio still shelf's owner id for the photograph taken
|
|
9877
|
-
* at this episode (`
|
|
9926
|
+
* at this episode (`still-shelf.ts`). Absent when no rule that could fire asked
|
|
9878
9927
|
* for a picture — the same shape `incomingFromSensorEvent` carries its marker.
|
|
9879
9928
|
*/
|
|
9880
9929
|
function incomingFromAudioEvent(event, stillOwnerId) {
|
|
@@ -9955,10 +10004,16 @@ function audioHitMeta(hit) {
|
|
|
9955
10004
|
samples: window.samples
|
|
9956
10005
|
};
|
|
9957
10006
|
}
|
|
9958
|
-
/**
|
|
9959
|
-
|
|
10007
|
+
/**
|
|
10008
|
+
* A committed ZoneAnalytics occupancy edge.
|
|
10009
|
+
*
|
|
10010
|
+
* `stillOwnerId` — see {@link subjectFromOccupancyEvent}. An edge whose scope is
|
|
10011
|
+
* empty owns no member track, so the only picture it can carry is a photograph
|
|
10012
|
+
* of its camera taken at the edge, held on the still shelf under this id.
|
|
10013
|
+
*/
|
|
10014
|
+
function incomingFromOccupancyEdge(edge, stillOwnerId) {
|
|
9960
10015
|
return {
|
|
9961
|
-
subject: subjectFromOccupancyEvent(edge),
|
|
10016
|
+
subject: subjectFromOccupancyEvent(edge, stillOwnerId),
|
|
9962
10017
|
kind: "occupancy-event",
|
|
9963
10018
|
origin: "pipeline",
|
|
9964
10019
|
log: () => ({
|
|
@@ -16072,7 +16127,7 @@ function isSyntheticId(id) {
|
|
|
16072
16127
|
* - and, since 2026-08-15, a `trackId` for both AUDIO kinds: a sound owns no
|
|
16073
16128
|
* frame, so production photographs the camera at the match and holds the
|
|
16074
16129
|
* bytes on the still shelf under an owner id the subject names
|
|
16075
|
-
* (`
|
|
16130
|
+
* (`still-shelf.ts`). The tester's RAM-held frame stands in for exactly that
|
|
16076
16131
|
* photograph, and — unlike every other kind here — it is taken the same way
|
|
16077
16132
|
* production takes it, at the same moment, from the same cap.
|
|
16078
16133
|
*
|
|
@@ -18739,11 +18794,12 @@ var NotificationCenter = class NotificationCenter {
|
|
|
18739
18794
|
*/
|
|
18740
18795
|
syntheticMedia = /* @__PURE__ */ new Map();
|
|
18741
18796
|
/**
|
|
18742
|
-
* The photograph a
|
|
18743
|
-
*
|
|
18744
|
-
*
|
|
18797
|
+
* The photograph a notification carries when the subject it is about is an
|
|
18798
|
+
* ABSENCE — a sound that owns no frame, an emptied occupancy scope that owns
|
|
18799
|
+
* no member. See `still-shelf.ts`. Null when this wiring has no snapshot cap,
|
|
18800
|
+
* which leaves those notifications text-only rather than broken.
|
|
18745
18801
|
*/
|
|
18746
|
-
|
|
18802
|
+
stills;
|
|
18747
18803
|
/** Per-device rate limit for the "matched NO rule" report — see `reportNoMatch`. */
|
|
18748
18804
|
lastNoMatchReportAt = /* @__PURE__ */ new Map();
|
|
18749
18805
|
noMatchSuppressed = /* @__PURE__ */ new Map();
|
|
@@ -19040,9 +19096,9 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19040
19096
|
this.logger = deps.logger;
|
|
19041
19097
|
this.now = deps.now ?? (() => Date.now());
|
|
19042
19098
|
const getSnapshot = deps.getSnapshot;
|
|
19043
|
-
this.
|
|
19099
|
+
this.stills = getSnapshot !== void 0 ? new NcStillShelf({
|
|
19044
19100
|
getSnapshot,
|
|
19045
|
-
logger: this.logger.child("
|
|
19101
|
+
logger: this.logger.child("still-shelf"),
|
|
19046
19102
|
now: this.now
|
|
19047
19103
|
}) : null;
|
|
19048
19104
|
this.rules = new NcRuleStore({
|
|
@@ -19132,8 +19188,8 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19132
19188
|
getMediaForOwner: async (ownerKind, ownerId) => {
|
|
19133
19189
|
const held = isSyntheticId(ownerId) ? this.syntheticMedia.get(ownerId) : void 0;
|
|
19134
19190
|
if (held !== void 0) return held.files;
|
|
19135
|
-
const
|
|
19136
|
-
if (
|
|
19191
|
+
const shelved = isStillShelfId(ownerId) ? this.stills?.get(ownerId) : void 0;
|
|
19192
|
+
if (shelved !== void 0) return shelved;
|
|
19137
19193
|
return deps.dispatcher.getMediaForOwner(ownerKind, ownerId);
|
|
19138
19194
|
},
|
|
19139
19195
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
@@ -19339,7 +19395,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19339
19395
|
}
|
|
19340
19396
|
this.timelapseScheduler?.stop();
|
|
19341
19397
|
this.summaryProducer?.stop();
|
|
19342
|
-
this.
|
|
19398
|
+
this.stills?.clear();
|
|
19343
19399
|
this.addonUpdates.dispose();
|
|
19344
19400
|
this.livenessHoldoff.dispose();
|
|
19345
19401
|
this.evaluationActive = false;
|
|
@@ -19691,14 +19747,14 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19691
19747
|
* direction that DESTROYS the media.
|
|
19692
19748
|
*/
|
|
19693
19749
|
audioStillOwner(deviceId, atMs, mode) {
|
|
19694
|
-
const shelf = this.
|
|
19750
|
+
const shelf = this.stills;
|
|
19695
19751
|
if (shelf === null || !this.evaluationActive) return void 0;
|
|
19696
19752
|
for (const rule of this.rules.listEnabled("immediate")) {
|
|
19697
19753
|
if (rule.media.attach === "none") continue;
|
|
19698
19754
|
if (!(mode === "condition" ? rule.conditions.audio !== void 0 : rule.conditions.audio === void 0 && referencesAudioClass(rule.conditions.classes))) continue;
|
|
19699
19755
|
const devices = rule.conditions.devices;
|
|
19700
19756
|
if (devices !== void 0 && devices.length > 0 && !devices.includes(deviceId)) continue;
|
|
19701
|
-
return shelf.capture(deviceId, atMs);
|
|
19757
|
+
return shelf.capture(deviceId, atMs, "audio");
|
|
19702
19758
|
}
|
|
19703
19759
|
}
|
|
19704
19760
|
/**
|
|
@@ -19757,7 +19813,48 @@ var NotificationCenter = class NotificationCenter {
|
|
|
19757
19813
|
* Public so a test / a future out-of-band edge source can drive it directly.
|
|
19758
19814
|
*/
|
|
19759
19815
|
onOccupancyEdge(edge) {
|
|
19760
|
-
this.consumeEvent(incomingFromOccupancyEdge(edge));
|
|
19816
|
+
this.consumeEvent(incomingFromOccupancyEdge(edge, this.occupancyStillOwner(edge)));
|
|
19817
|
+
}
|
|
19818
|
+
/**
|
|
19819
|
+
* Start photographing this edge's camera NOW and return the owner id its
|
|
19820
|
+
* subject should name — or `undefined` when nothing would use it.
|
|
19821
|
+
*
|
|
19822
|
+
* **Only an edge that owns NOBODY.** `chooseOccupancyMediaOwner` names a
|
|
19823
|
+
* member track whenever the scope holds one, and that picture is of the
|
|
19824
|
+
* counted object itself; a photograph is strictly the stand-in for not having
|
|
19825
|
+
* one. The case this exists for is "posto libero" (camera 617): the vehicle
|
|
19826
|
+
* whose departure IS the notification has left, so the scope is empty, the
|
|
19827
|
+
* ladder resolves nothing and the delivery logged `no still could be resolved
|
|
19828
|
+
* … owners=[]`. A became-free edge has no owner BY CONSTRUCTION, and a
|
|
19829
|
+
* became-occupied edge can lack one too (every member parked, i.e.
|
|
19830
|
+
* `stationary:`) — so the gate is the ABSENCE itself, not the polarity. That
|
|
19831
|
+
* is the honest predicate: photograph exactly the edges whose media cannot be
|
|
19832
|
+
* resolved, and no others.
|
|
19833
|
+
*
|
|
19834
|
+
* **The capture is not awaited**, for the audio shelf's reason and one of its
|
|
19835
|
+
* own: a freed space is about to be taken by the next car, and a still fetched
|
|
19836
|
+
* after the outbox drained would show it occupied again.
|
|
19837
|
+
*
|
|
19838
|
+
* **The gate is the RULE's media policy AND the engine's own verdict.** The
|
|
19839
|
+
* watcher commits both polarities of every watched key, so a hub with only a
|
|
19840
|
+
* became-occupied rule still produces free edges — photographing those would
|
|
19841
|
+
* be a snapshot no notification can ever carry. `matchesOccupancy` is the same
|
|
19842
|
+
* predicate `evaluateRule` will apply, shared rather than restated. The rule
|
|
19843
|
+
* cache is an in-memory mirror, so this is not a fallible read (D49).
|
|
19844
|
+
*/
|
|
19845
|
+
occupancyStillOwner(edge) {
|
|
19846
|
+
const shelf = this.stills;
|
|
19847
|
+
if (shelf === null || !this.evaluationActive) return void 0;
|
|
19848
|
+
if (edge.mediaTrackId !== void 0) return void 0;
|
|
19849
|
+
const subject = occupancySubjectFromEdge(edge);
|
|
19850
|
+
for (const rule of this.rules.listEnabled("device-event")) {
|
|
19851
|
+
if (rule.media.attach === "none") continue;
|
|
19852
|
+
const occ = rule.conditions.occupancy;
|
|
19853
|
+
if (occ === void 0 || !matchesOccupancy(occ, subject)) continue;
|
|
19854
|
+
const devices = rule.conditions.devices;
|
|
19855
|
+
if (devices !== void 0 && devices.length > 0 && !devices.includes(edge.deviceId)) continue;
|
|
19856
|
+
return shelf.capture(edge.deviceId, edge.timestamp, "occupancy");
|
|
19857
|
+
}
|
|
19761
19858
|
}
|
|
19762
19859
|
/**
|
|
19763
19860
|
* Feed ONE audio inference frame into the sampling-window matcher.
|
|
@@ -20724,7 +20821,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20724
20821
|
kind,
|
|
20725
20822
|
ruleId: rule.id,
|
|
20726
20823
|
rule: rule.name,
|
|
20727
|
-
snapshotWired: this.
|
|
20824
|
+
snapshotWired: this.stills !== null
|
|
20728
20825
|
}
|
|
20729
20826
|
});
|
|
20730
20827
|
}
|
|
@@ -20889,6 +20986,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
20889
20986
|
if (subject.kind === "audio-window" || subject.kind === "audio-event") return { outcome: "disabled" };
|
|
20890
20987
|
const trackId = subject.trackId;
|
|
20891
20988
|
if (trackId === void 0) return { outcome: "disabled" };
|
|
20989
|
+
if (isStillShelfId(trackId)) return { outcome: "disabled" };
|
|
20892
20990
|
const member = {
|
|
20893
20991
|
trackId,
|
|
20894
20992
|
className: subject.classNames[0] ?? "event",
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BdX09HCw.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.96",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|