@camstack/addon-post-analysis 1.2.217 → 1.2.218
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-DWZqNymj.mjs → dist-BqkhuC85.mjs} +41 -4
- package/dist/{dist-B_6UZCut.js → dist-CbQHenLE.js} +41 -4
- 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-BmDXGilB.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-JPlVJb-8.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-DMDZJw2T.mjs → hostInit-CqdkxAhm.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +155 -12
- package/dist/pipeline-analytics/index.mjs +155 -12
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -21091,7 +21091,17 @@ var EventDensityBucketSchema = object({
|
|
|
21091
21091
|
bucketStart: number(),
|
|
21092
21092
|
motion: number().int(),
|
|
21093
21093
|
object: number().int(),
|
|
21094
|
-
audio: number().int()
|
|
21094
|
+
audio: number().int(),
|
|
21095
|
+
/**
|
|
21096
|
+
* Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
|
|
21097
|
+
* as opposed to `audio`, which is their COUNT (D431).
|
|
21098
|
+
*
|
|
21099
|
+
* ABSENT when the bucket holds no audio event. Absence means "nothing was
|
|
21100
|
+
* measured", which is not the same claim as silence, and a lane that cannot
|
|
21101
|
+
* tell them apart paints a quiet hour at full scale — which is exactly what
|
|
21102
|
+
* the count-on-a-level-meter did.
|
|
21103
|
+
*/
|
|
21104
|
+
audioDbfs: number().optional()
|
|
21095
21105
|
});
|
|
21096
21106
|
/**
|
|
21097
21107
|
* One camera's row in a `getEventDensityBatch` answer.
|
|
@@ -31838,7 +31848,23 @@ var RecordingRangeSchema = object({
|
|
|
31838
31848
|
});
|
|
31839
31849
|
var RecordingAvailabilitySchema = object({
|
|
31840
31850
|
deviceId: number(),
|
|
31841
|
-
ranges: array(RecordingRangeSchema)
|
|
31851
|
+
ranges: array(RecordingRangeSchema),
|
|
31852
|
+
/**
|
|
31853
|
+
* Every profile this camera has footage in — not only the one `ranges`
|
|
31854
|
+
* describes (D433).
|
|
31855
|
+
*
|
|
31856
|
+
* `ranges` answers for ONE profile by design: the timeline is a single bar,
|
|
31857
|
+
* and enumerating all of them triples the directory reads for a bar that
|
|
31858
|
+
* would look identical. But the answer used to say nothing about that, so a
|
|
31859
|
+
* caller asking "what can I export?" read the single preferred profile as
|
|
31860
|
+
* the only one that exists — on this deployment every camera records `high`
|
|
31861
|
+
* AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
|
|
31862
|
+
* UI could not see it.
|
|
31863
|
+
*
|
|
31864
|
+
* Cheap: it is the same shallow directory read the profile CHOICE already
|
|
31865
|
+
* makes. Ask for a specific profile's ranges with the `profile` input.
|
|
31866
|
+
*/
|
|
31867
|
+
profilesWithFootage: array(string())
|
|
31842
31868
|
});
|
|
31843
31869
|
var RecordingDaysSchema = object({
|
|
31844
31870
|
deviceId: number(),
|
|
@@ -31864,7 +31890,11 @@ var RecordingDaysSchema = object({
|
|
|
31864
31890
|
var RecordingAvailabilityForDeviceSchema = object({
|
|
31865
31891
|
deviceId: number(),
|
|
31866
31892
|
read: _enum(["read", "unreadable"]),
|
|
31867
|
-
ranges: array(RecordingRangeSchema).readonly()
|
|
31893
|
+
ranges: array(RecordingRangeSchema).readonly(),
|
|
31894
|
+
/** Same field, same meaning, as the singular answer (D433). A row that
|
|
31895
|
+
* dropped it would tell a grid caller the archive holds one profile.
|
|
31896
|
+
* Empty on an `'unreadable'` row: nobody looked. */
|
|
31897
|
+
profilesWithFootage: array(string()).readonly()
|
|
31868
31898
|
});
|
|
31869
31899
|
/**
|
|
31870
31900
|
* One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
|
|
@@ -32154,7 +32184,14 @@ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
|
|
|
32154
32184
|
method(object({
|
|
32155
32185
|
deviceId: number(),
|
|
32156
32186
|
fromMs: number(),
|
|
32157
|
-
toMs: number()
|
|
32187
|
+
toMs: number(),
|
|
32188
|
+
/**
|
|
32189
|
+
* Answer for THIS profile instead of the preferred one (D433). Absent
|
|
32190
|
+
* keeps the timeline's behaviour — one bar, one profile, one set of
|
|
32191
|
+
* reads. `profilesWithFootage` on the answer says what may be asked
|
|
32192
|
+
* for.
|
|
32193
|
+
*/
|
|
32194
|
+
profile: string().optional()
|
|
32158
32195
|
}), RecordingAvailabilitySchema, {
|
|
32159
32196
|
kind: "query",
|
|
32160
32197
|
auth: "protected"
|
|
@@ -21122,7 +21122,17 @@ var EventDensityBucketSchema = object({
|
|
|
21122
21122
|
bucketStart: number(),
|
|
21123
21123
|
motion: number().int(),
|
|
21124
21124
|
object: number().int(),
|
|
21125
|
-
audio: number().int()
|
|
21125
|
+
audio: number().int(),
|
|
21126
|
+
/**
|
|
21127
|
+
* Energy-weighted mean dBFS of the audio events in this bucket — the LEVEL,
|
|
21128
|
+
* as opposed to `audio`, which is their COUNT (D431).
|
|
21129
|
+
*
|
|
21130
|
+
* ABSENT when the bucket holds no audio event. Absence means "nothing was
|
|
21131
|
+
* measured", which is not the same claim as silence, and a lane that cannot
|
|
21132
|
+
* tell them apart paints a quiet hour at full scale — which is exactly what
|
|
21133
|
+
* the count-on-a-level-meter did.
|
|
21134
|
+
*/
|
|
21135
|
+
audioDbfs: number().optional()
|
|
21126
21136
|
});
|
|
21127
21137
|
/**
|
|
21128
21138
|
* One camera's row in a `getEventDensityBatch` answer.
|
|
@@ -31869,7 +31879,23 @@ var RecordingRangeSchema = object({
|
|
|
31869
31879
|
});
|
|
31870
31880
|
var RecordingAvailabilitySchema = object({
|
|
31871
31881
|
deviceId: number(),
|
|
31872
|
-
ranges: array(RecordingRangeSchema)
|
|
31882
|
+
ranges: array(RecordingRangeSchema),
|
|
31883
|
+
/**
|
|
31884
|
+
* Every profile this camera has footage in — not only the one `ranges`
|
|
31885
|
+
* describes (D433).
|
|
31886
|
+
*
|
|
31887
|
+
* `ranges` answers for ONE profile by design: the timeline is a single bar,
|
|
31888
|
+
* and enumerating all of them triples the directory reads for a bar that
|
|
31889
|
+
* would look identical. But the answer used to say nothing about that, so a
|
|
31890
|
+
* caller asking "what can I export?" read the single preferred profile as
|
|
31891
|
+
* the only one that exists — on this deployment every camera records `high`
|
|
31892
|
+
* AND `low`, the low tree holding 14 GB for camera 615 alone, and the export
|
|
31893
|
+
* UI could not see it.
|
|
31894
|
+
*
|
|
31895
|
+
* Cheap: it is the same shallow directory read the profile CHOICE already
|
|
31896
|
+
* makes. Ask for a specific profile's ranges with the `profile` input.
|
|
31897
|
+
*/
|
|
31898
|
+
profilesWithFootage: array(string())
|
|
31873
31899
|
});
|
|
31874
31900
|
var RecordingDaysSchema = object({
|
|
31875
31901
|
deviceId: number(),
|
|
@@ -31895,7 +31921,11 @@ var RecordingDaysSchema = object({
|
|
|
31895
31921
|
var RecordingAvailabilityForDeviceSchema = object({
|
|
31896
31922
|
deviceId: number(),
|
|
31897
31923
|
read: _enum(["read", "unreadable"]),
|
|
31898
|
-
ranges: array(RecordingRangeSchema).readonly()
|
|
31924
|
+
ranges: array(RecordingRangeSchema).readonly(),
|
|
31925
|
+
/** Same field, same meaning, as the singular answer (D433). A row that
|
|
31926
|
+
* dropped it would tell a grid caller the archive holds one profile.
|
|
31927
|
+
* Empty on an `'unreadable'` row: nobody looked. */
|
|
31928
|
+
profilesWithFootage: array(string()).readonly()
|
|
31899
31929
|
});
|
|
31900
31930
|
/**
|
|
31901
31931
|
* One camera's row in a `getDaysWithRecordingsBatch` answer. Same rule as
|
|
@@ -32185,7 +32215,14 @@ var ReadWindowBytesResultSchema = discriminatedUnion("kind", [object({
|
|
|
32185
32215
|
method(object({
|
|
32186
32216
|
deviceId: number(),
|
|
32187
32217
|
fromMs: number(),
|
|
32188
|
-
toMs: number()
|
|
32218
|
+
toMs: number(),
|
|
32219
|
+
/**
|
|
32220
|
+
* Answer for THIS profile instead of the preferred one (D433). Absent
|
|
32221
|
+
* keeps the timeline's behaviour — one bar, one profile, one set of
|
|
32222
|
+
* reads. `profilesWithFootage` on the answer says what may be asked
|
|
32223
|
+
* for.
|
|
32224
|
+
*/
|
|
32225
|
+
profile: string().optional()
|
|
32189
32226
|
}), RecordingAvailabilitySchema, {
|
|
32190
32227
|
kind: "query",
|
|
32191
32228
|
auth: "protected"
|
|
@@ -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-CbQHenLE.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { At as resolvePoolMemoryPolicy, B as PoolMemoryWatchdog, Ct as parseProcStatus, gt as hfModelUrl, ut as embeddingEncoderCapability, zt as BaseAddon } from "../dist-
|
|
1
|
+
import { At as resolvePoolMemoryPolicy, B as PoolMemoryWatchdog, Ct as parseProcStatus, gt as hfModelUrl, ut as embeddingEncoderCapability, zt as BaseAddon } from "../dist-BqkhuC85.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.96",
|
|
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.176",
|
|
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.144",
|
|
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.176",
|
|
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.96",
|
|
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.144",
|
|
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-CbQHenLE.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");
|
|
@@ -20092,11 +20092,19 @@ var TimelapseRenderer = class {
|
|
|
20092
20092
|
async render(request) {
|
|
20093
20093
|
const { deviceId, window } = request;
|
|
20094
20094
|
const tags = { deviceId };
|
|
20095
|
-
const
|
|
20095
|
+
const first = await this.ports.getAvailability({
|
|
20096
20096
|
deviceId,
|
|
20097
20097
|
fromMs: window.startMs,
|
|
20098
20098
|
toMs: window.endMs
|
|
20099
20099
|
});
|
|
20100
|
+
const cheapest = [...first.profilesWithFootage].sort((a, b) => profileCost(a) - profileCost(b))[0];
|
|
20101
|
+
const describes = first.ranges[0]?.profile;
|
|
20102
|
+
const availability = cheapest !== void 0 && cheapest !== describes ? (await this.ports.getAvailability({
|
|
20103
|
+
deviceId,
|
|
20104
|
+
fromMs: window.startMs,
|
|
20105
|
+
toMs: window.endMs,
|
|
20106
|
+
profile: cheapest
|
|
20107
|
+
})).ranges : first.ranges;
|
|
20100
20108
|
const chosen = pickProfile(availability, window);
|
|
20101
20109
|
if (chosen === null || chosen.coverage.coveredMs === 0) {
|
|
20102
20110
|
this.ports.logger.warn("timelapse skipped: no footage covers the window", {
|
|
@@ -36162,7 +36170,6 @@ var FrameProcessor = class {
|
|
|
36162
36170
|
* filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
|
|
36163
36171
|
spawnRefusalObserver = null;
|
|
36164
36172
|
/** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
|
|
36165
|
-
spawnRefusalForward = null;
|
|
36166
36173
|
/**
|
|
36167
36174
|
* The spawn refusals of the frame being processed.
|
|
36168
36175
|
*
|
|
@@ -36176,6 +36183,10 @@ var FrameProcessor = class {
|
|
|
36176
36183
|
/** Running count of refusals-in-zone SEEN — never the throttled number. */
|
|
36177
36184
|
zoneRefusalEventCount = 0;
|
|
36178
36185
|
zoneRefusalObserver = null;
|
|
36186
|
+
/** Track ids whose BIRTH zone history has already been folded in — the fold
|
|
36187
|
+
* happens once, on the frame a track first becomes visible. */
|
|
36188
|
+
birthZonesSeeded = /* @__PURE__ */ new Set();
|
|
36189
|
+
birthZoneTrailObserver = null;
|
|
36179
36190
|
/** Last report time per collapsed key, for {@link ZONE_REFUSAL_THROTTLE_MS}. */
|
|
36180
36191
|
zoneRefusalLastReport = /* @__PURE__ */ new Map();
|
|
36181
36192
|
zoneRefusalSampler = new SpawnRefusalSampler();
|
|
@@ -36286,6 +36297,10 @@ var FrameProcessor = class {
|
|
|
36286
36297
|
setZoneRefusalObserver(observer) {
|
|
36287
36298
|
this.zoneRefusalObserver = observer;
|
|
36288
36299
|
}
|
|
36300
|
+
/** Where "this track was born already carrying a zone" is reported. */
|
|
36301
|
+
setBirthZoneTrailObserver(observer) {
|
|
36302
|
+
this.birthZoneTrailObserver = observer;
|
|
36303
|
+
}
|
|
36289
36304
|
setBindObserver(observer) {
|
|
36290
36305
|
this.tracker.setBindObserver(observer);
|
|
36291
36306
|
}
|
|
@@ -36375,8 +36390,8 @@ var FrameProcessor = class {
|
|
|
36375
36390
|
memberships.map((m) => m.zoneId).join("+"),
|
|
36376
36391
|
cell(r.bbox.x),
|
|
36377
36392
|
cell(r.bbox.y),
|
|
36378
|
-
cell(r.bbox.
|
|
36379
|
-
cell(r.bbox.
|
|
36393
|
+
cell(r.bbox.w),
|
|
36394
|
+
cell(r.bbox.h)
|
|
36380
36395
|
].join("|");
|
|
36381
36396
|
const last = this.zoneRefusalLastReport.get(key);
|
|
36382
36397
|
if (last !== void 0 && timestamp - last < ZONE_REFUSAL_THROTTLE_MS) return false;
|
|
@@ -36621,6 +36636,32 @@ var FrameProcessor = class {
|
|
|
36621
36636
|
if (memberships.length > 0) overlapsByTrack.set(td.trackId, memberships);
|
|
36622
36637
|
if (belowBar.length > 0) rejectedByTrack.set(td.trackId, belowBar);
|
|
36623
36638
|
}
|
|
36639
|
+
for (const td of trackedDetections) {
|
|
36640
|
+
if (this.birthZonesSeeded.has(td.trackId)) continue;
|
|
36641
|
+
this.birthZonesSeeded.add(td.trackId);
|
|
36642
|
+
const trail = td.path;
|
|
36643
|
+
if (trail.length <= 1) continue;
|
|
36644
|
+
const atBirth = zonesByTrack.get(td.trackId) ?? [];
|
|
36645
|
+
const zones = new Set(atBirth);
|
|
36646
|
+
const bornWith = zones.size;
|
|
36647
|
+
for (let i = 0; i < trail.length - 1; i++) {
|
|
36648
|
+
const box = trail[i];
|
|
36649
|
+
if (box === void 0) continue;
|
|
36650
|
+
const { memberships } = this.zoneEngine.splitDetectionZones(box, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
|
|
36651
|
+
for (const m of memberships) zones.add(m.zoneId);
|
|
36652
|
+
}
|
|
36653
|
+
if (zones.size === bornWith) continue;
|
|
36654
|
+
const inherited = [...zones].filter((z) => !atBirth.includes(z));
|
|
36655
|
+
zonesByTrack.set(td.trackId, [...zones]);
|
|
36656
|
+
this.birthZoneTrailObserver?.({
|
|
36657
|
+
deviceId: this.deviceId,
|
|
36658
|
+
trackId: td.trackId,
|
|
36659
|
+
className: td.class,
|
|
36660
|
+
measurementsBeforeBirth: trail.length - 1,
|
|
36661
|
+
zonesAtBirth: [...atBirth],
|
|
36662
|
+
inheritedZones: inherited
|
|
36663
|
+
});
|
|
36664
|
+
}
|
|
36624
36665
|
this.lastZoneOverlaps = overlapsByTrack;
|
|
36625
36666
|
this.lastZoneRejections = rejectedByTrack;
|
|
36626
36667
|
for (const td of trackedDetections) this.lastDetectionByTrack.set(td.trackId, td);
|
|
@@ -36693,6 +36734,7 @@ var FrameProcessor = class {
|
|
|
36693
36734
|
for (const state of objectStates) {
|
|
36694
36735
|
if (state.state !== "leaving") continue;
|
|
36695
36736
|
this.lastZonesByTrack.delete(state.trackId);
|
|
36737
|
+
this.birthZonesSeeded.delete(state.trackId);
|
|
36696
36738
|
this.lastDetectionByTrack.delete(state.trackId);
|
|
36697
36739
|
this.reportedRefusalByTrack.delete(state.trackId);
|
|
36698
36740
|
}
|
|
@@ -39620,6 +39662,62 @@ function tieredLabelColumnData(patch) {
|
|
|
39620
39662
|
};
|
|
39621
39663
|
}
|
|
39622
39664
|
//#endregion
|
|
39665
|
+
//#region src/pipeline-analytics/store/audio-level-density.ts
|
|
39666
|
+
/**
|
|
39667
|
+
* The energy-weighted mean of a set of dBFS values, in dBFS.
|
|
39668
|
+
*
|
|
39669
|
+
* dBFS is logarithmic, so an arithmetic mean of dB numbers is the mean of
|
|
39670
|
+
* nothing physical — it under-weights the loud moment that is the reason to
|
|
39671
|
+
* look at a bar at all. Averaging POWER and converting back is what a meter is
|
|
39672
|
+
* expected to show, and it is the same arithmetic as an RMS over the interval.
|
|
39673
|
+
*
|
|
39674
|
+
* `undefined` for an empty set, and for a set with no finite sample: a bucket
|
|
39675
|
+
* nobody measured must not be reported as silence, and −∞ dBFS is not a number
|
|
39676
|
+
* a bar can paint.
|
|
39677
|
+
*/
|
|
39678
|
+
function energyWeightedMeanDbfs(samples) {
|
|
39679
|
+
let power = 0;
|
|
39680
|
+
let n = 0;
|
|
39681
|
+
for (const dbfs of samples) {
|
|
39682
|
+
if (Number.isNaN(dbfs)) continue;
|
|
39683
|
+
if (dbfs === Number.NEGATIVE_INFINITY) {
|
|
39684
|
+
n += 1;
|
|
39685
|
+
continue;
|
|
39686
|
+
}
|
|
39687
|
+
if (!Number.isFinite(dbfs)) continue;
|
|
39688
|
+
power += 10 ** (dbfs / 10);
|
|
39689
|
+
n += 1;
|
|
39690
|
+
}
|
|
39691
|
+
if (n === 0) return void 0;
|
|
39692
|
+
const mean = power / n;
|
|
39693
|
+
if (mean <= 0) return void 0;
|
|
39694
|
+
return 10 * Math.log10(mean);
|
|
39695
|
+
}
|
|
39696
|
+
/**
|
|
39697
|
+
* Energy-weighted mean dBFS per bucket index, for the buckets that HAVE audio.
|
|
39698
|
+
*
|
|
39699
|
+
* A bucket with no events is absent from the map rather than present at zero:
|
|
39700
|
+
* "nothing was heard" and "nothing was measured" are different claims, and the
|
|
39701
|
+
* lane has to be able to draw nothing.
|
|
39702
|
+
*/
|
|
39703
|
+
function audioDbfsByBucket(events, since, bucketMs) {
|
|
39704
|
+
const out = /* @__PURE__ */ new Map();
|
|
39705
|
+
if (bucketMs <= 0) return out;
|
|
39706
|
+
const byBucket = /* @__PURE__ */ new Map();
|
|
39707
|
+
for (const ev of events) {
|
|
39708
|
+
const idx = Math.floor((ev.timestamp - since) / bucketMs);
|
|
39709
|
+
if (idx < 0) continue;
|
|
39710
|
+
const bin = byBucket.get(idx);
|
|
39711
|
+
if (bin === void 0) byBucket.set(idx, [ev.dbfs]);
|
|
39712
|
+
else bin.push(ev.dbfs);
|
|
39713
|
+
}
|
|
39714
|
+
for (const [idx, samples] of byBucket) {
|
|
39715
|
+
const mean = energyWeightedMeanDbfs(samples);
|
|
39716
|
+
if (mean !== void 0) out.set(idx, mean);
|
|
39717
|
+
}
|
|
39718
|
+
return out;
|
|
39719
|
+
}
|
|
39720
|
+
//#endregion
|
|
39623
39721
|
//#region src/pipeline-analytics/store/event-store.ts
|
|
39624
39722
|
/**
|
|
39625
39723
|
* @durable class=ledger owner=pipeline-analytics
|
|
@@ -40458,10 +40556,11 @@ var EventStore = class {
|
|
|
40458
40556
|
});
|
|
40459
40557
|
return new Map(rows.map((r) => [r.bucket, r.count]));
|
|
40460
40558
|
};
|
|
40461
|
-
const [motionMap, objectMap, audioMap] = await Promise.all([
|
|
40559
|
+
const [motionMap, objectMap, audioMap, audioDbfsMap] = await Promise.all([
|
|
40462
40560
|
run(MOTION_EVENTS_COLLECTION),
|
|
40463
40561
|
run(OBJECT_EVENTS_COLLECTION),
|
|
40464
|
-
run(AUDIO_EVENTS_COLLECTION)
|
|
40562
|
+
run(AUDIO_EVENTS_COLLECTION),
|
|
40563
|
+
this.audioDbfsPerBucket(q)
|
|
40465
40564
|
]);
|
|
40466
40565
|
const buckets = Math.max(0, Math.ceil((q.until - q.since) / q.bucketMs));
|
|
40467
40566
|
const out = [];
|
|
@@ -40470,16 +40569,46 @@ var EventStore = class {
|
|
|
40470
40569
|
const o = objectMap.get(i) ?? 0;
|
|
40471
40570
|
const a = audioMap.get(i) ?? 0;
|
|
40472
40571
|
if (m + o + a === 0) continue;
|
|
40572
|
+
const dbfs = audioDbfsMap.get(i);
|
|
40473
40573
|
out.push({
|
|
40474
40574
|
bucketStart: q.since + i * q.bucketMs,
|
|
40475
40575
|
motion: m,
|
|
40476
40576
|
object: o,
|
|
40477
|
-
audio: a
|
|
40577
|
+
audio: a,
|
|
40578
|
+
...dbfs !== void 0 ? { audioDbfs: dbfs } : {}
|
|
40478
40579
|
});
|
|
40479
40580
|
}
|
|
40480
40581
|
return out;
|
|
40481
40582
|
}
|
|
40482
40583
|
/**
|
|
40584
|
+
* Energy-weighted mean dBFS per bucket index, for the buckets that have
|
|
40585
|
+
* audio. Reads the window's audio events once, `slim`, and folds them —
|
|
40586
|
+
* `histogram` can only count, and a level is not a count.
|
|
40587
|
+
*
|
|
40588
|
+
* A read that fails yields an EMPTY map, so the lane draws no level rather
|
|
40589
|
+
* than a wrong one: the density counts beside it are unaffected, and a
|
|
40590
|
+
* missing bar is the honest rendering of "not measured" (D431).
|
|
40591
|
+
*/
|
|
40592
|
+
async audioDbfsPerBucket(q) {
|
|
40593
|
+
try {
|
|
40594
|
+
return audioDbfsByBucket((await this.queryAudio({
|
|
40595
|
+
deviceId: q.deviceId,
|
|
40596
|
+
since: q.since,
|
|
40597
|
+
until: q.until,
|
|
40598
|
+
projection: "slim"
|
|
40599
|
+
})).map((e) => ({
|
|
40600
|
+
timestamp: e.timestamp,
|
|
40601
|
+
dbfs: e.dbfs
|
|
40602
|
+
})), q.since, q.bucketMs);
|
|
40603
|
+
} catch (err) {
|
|
40604
|
+
this.logger.warn("density: audio level aggregation failed — the lane will draw no level", {
|
|
40605
|
+
tags: { deviceId: q.deviceId },
|
|
40606
|
+
meta: { error: String(err) }
|
|
40607
|
+
});
|
|
40608
|
+
return /* @__PURE__ */ new Map();
|
|
40609
|
+
}
|
|
40610
|
+
}
|
|
40611
|
+
/**
|
|
40483
40612
|
* Age-evict the TRACKLESS event kinds — motion and audio.
|
|
40484
40613
|
*
|
|
40485
40614
|
* **Object events are not here, and that is the point.** An object event is a
|
|
@@ -72816,12 +72945,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
72816
72945
|
*/
|
|
72817
72946
|
buildTimelapsePorts(api, stores) {
|
|
72818
72947
|
return {
|
|
72819
|
-
getAvailability: async ({ deviceId, fromMs, toMs }) => {
|
|
72820
|
-
|
|
72948
|
+
getAvailability: async ({ deviceId, fromMs, toMs, profile }) => {
|
|
72949
|
+
const availability = await api.recording.getAvailability.query({
|
|
72821
72950
|
deviceId,
|
|
72822
72951
|
fromMs,
|
|
72823
|
-
toMs
|
|
72824
|
-
|
|
72952
|
+
toMs,
|
|
72953
|
+
...profile !== void 0 ? { profile } : {}
|
|
72954
|
+
});
|
|
72955
|
+
return {
|
|
72956
|
+
ranges: availability.ranges,
|
|
72957
|
+
profilesWithFootage: availability.profilesWithFootage
|
|
72958
|
+
};
|
|
72825
72959
|
},
|
|
72826
72960
|
listTracks: async ({ deviceId, since, until }) => {
|
|
72827
72961
|
return (await stores.trackStore.queryHistorical({
|
|
@@ -77608,6 +77742,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
77608
77742
|
}
|
|
77609
77743
|
});
|
|
77610
77744
|
});
|
|
77745
|
+
p.setBirthZoneTrailObserver((trail) => {
|
|
77746
|
+
this.ctx.logger.info("track born carrying zones it crossed before it was a track", {
|
|
77747
|
+
tags: { deviceId },
|
|
77748
|
+
meta: {
|
|
77749
|
+
source,
|
|
77750
|
+
...trail
|
|
77751
|
+
}
|
|
77752
|
+
});
|
|
77753
|
+
});
|
|
77611
77754
|
p.setZoneRefusalObserver((observation) => {
|
|
77612
77755
|
this.ctx.logger.info("detection refused inside a watched zone — not notified", {
|
|
77613
77756
|
tags: { deviceId },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as VISIT_MERGE_GAP_MS, $t as object, A as NcRulePatchSchema, Bt as CamProfileSchema, C as NC_ALARM_SYSTEM_EVENT_KINDS, D as NC_TAXONOMY, Dt as readDeviceStateFrom, Et as plateGalleryCapability, F as NcSnoozeSchema, Ft as vectorDimFromBase64, G as SCENE_DEFAULT_UNCOVERED_POLICY, Gt as nodePin, H as RetrainStatusSchema, Ht as createEvent, I as NcSnoozeSuppressedSchema, It as videoclipsCapability, J as TIMELAPSE_DENSE_FLOOR_SEC, Jt as array, K as SCENE_DIVERGED, Kt as sleep$1, L as NcSystemEventKindSchema, Lt as zoneAnalyticsCapability, M as NcRuleTargetSchema, Mt as storageOccupancyCapability, N as NcScheduleSchema, Nt as subKindsOf, O as NcConditionDescriptorSchema, Ot as readTimelapseGeneratedAt, P as NcSnoozeInputSchema, Pt as systemEventFilterApplies, Q as TrackSourceSchema, Qt as number, R as NcTaxonomySchema, Rt as errMsg$1, S as MediaFileKindEnum, St as notificationRulesCapability, T as NC_DEFAULT_SNOOZE_MINUTES, Tt as pipelineAnalyticsCapability, Ut as hydrateSchema, V as RECORDING_EXPORT_MAX_READ_BYTES, Vt as DeviceType, W as SCENE_DEFAULT_ANCHOR_THRESHOLD, Wt as isDeviceScopedCap, X as TimelapseRulePatchSchema, Xt as discriminatedUnion, Y as TimelapseRuleInputSchema, Yt as boolean, Z as TimelapseRuleSchema, Zt as literal, _ as LabelAttributionSchema, _t as isDetectionMacroClass, a as CLUSTER_MODEL_SCOPED_STEPS, at as buildEventKindDescriptor, b as MAX_BIRTH_DECISION_RECORDS, bt as kebabToCamel, ct as defineCustomActions, dt as encodeVectorBase64, en as partialRecord, et as addonWidgetsSourceCapability, f as DeclaredDevices, ft as evaluateSensorEdge, g as FailureCounters, h as FULL_IMAGE_BBOX, ht as failureContributionCapability, i as BirthDecisionRecordSchema, in as EventCategory, it as audioModeOf, j as NcRuleSchema, jt as sceneMonitorCapability, k as NcRuleInputSchema, kt as resolveLocationMode, lt as deriveRecordingMode, m as EVENT_PAD_MS, mt as faceGalleryCapability, n as ArchivedDebugNoteSchema, nn as string, nt as assertTimelapseCadences, o as COCO_TO_MACRO, ot as cosineSimilarity$1, p as EVENT_KIND_BY_CAP, pt as evictionPolicyOfLocation, q as SceneMonitorSchema, qt as _enum, r as BaseDevice, rn as unknown, rt as audioMetricsCapability, s as DEFAULT_EVENT_COLOR, st as customAction, t as AUDIO_MACRO_LABELS, tn as record, tt as alarmPanelCapability, u as DETECTION_MACRO_CLASSES, v as MACRO_LABELS, vt as isScheduleActive, w as NC_CONDITION_CATALOG, wt as pickClusterStepModels, xt as mayWriteToLocation, y as MAX_ARCHIVED_DEBUG_NOTES, yt as isSourceCap, z as OpsLogEntrySchema, zt as BaseAddon } from "../dist-
|
|
1
|
+
import { $ as VISIT_MERGE_GAP_MS, $t as object, A as NcRulePatchSchema, Bt as CamProfileSchema, C as NC_ALARM_SYSTEM_EVENT_KINDS, D as NC_TAXONOMY, Dt as readDeviceStateFrom, Et as plateGalleryCapability, F as NcSnoozeSchema, Ft as vectorDimFromBase64, G as SCENE_DEFAULT_UNCOVERED_POLICY, Gt as nodePin, H as RetrainStatusSchema, Ht as createEvent, I as NcSnoozeSuppressedSchema, It as videoclipsCapability, J as TIMELAPSE_DENSE_FLOOR_SEC, Jt as array, K as SCENE_DIVERGED, Kt as sleep$1, L as NcSystemEventKindSchema, Lt as zoneAnalyticsCapability, M as NcRuleTargetSchema, Mt as storageOccupancyCapability, N as NcScheduleSchema, Nt as subKindsOf, O as NcConditionDescriptorSchema, Ot as readTimelapseGeneratedAt, P as NcSnoozeInputSchema, Pt as systemEventFilterApplies, Q as TrackSourceSchema, Qt as number, R as NcTaxonomySchema, Rt as errMsg$1, S as MediaFileKindEnum, St as notificationRulesCapability, T as NC_DEFAULT_SNOOZE_MINUTES, Tt as pipelineAnalyticsCapability, Ut as hydrateSchema, V as RECORDING_EXPORT_MAX_READ_BYTES, Vt as DeviceType, W as SCENE_DEFAULT_ANCHOR_THRESHOLD, Wt as isDeviceScopedCap, X as TimelapseRulePatchSchema, Xt as discriminatedUnion, Y as TimelapseRuleInputSchema, Yt as boolean, Z as TimelapseRuleSchema, Zt as literal, _ as LabelAttributionSchema, _t as isDetectionMacroClass, a as CLUSTER_MODEL_SCOPED_STEPS, at as buildEventKindDescriptor, b as MAX_BIRTH_DECISION_RECORDS, bt as kebabToCamel, ct as defineCustomActions, dt as encodeVectorBase64, en as partialRecord, et as addonWidgetsSourceCapability, f as DeclaredDevices, ft as evaluateSensorEdge, g as FailureCounters, h as FULL_IMAGE_BBOX, ht as failureContributionCapability, i as BirthDecisionRecordSchema, in as EventCategory, it as audioModeOf, j as NcRuleSchema, jt as sceneMonitorCapability, k as NcRuleInputSchema, kt as resolveLocationMode, lt as deriveRecordingMode, m as EVENT_PAD_MS, mt as faceGalleryCapability, n as ArchivedDebugNoteSchema, nn as string, nt as assertTimelapseCadences, o as COCO_TO_MACRO, ot as cosineSimilarity$1, p as EVENT_KIND_BY_CAP, pt as evictionPolicyOfLocation, q as SceneMonitorSchema, qt as _enum, r as BaseDevice, rn as unknown, rt as audioMetricsCapability, s as DEFAULT_EVENT_COLOR, st as customAction, t as AUDIO_MACRO_LABELS, tn as record, tt as alarmPanelCapability, u as DETECTION_MACRO_CLASSES, v as MACRO_LABELS, vt as isScheduleActive, w as NC_CONDITION_CATALOG, wt as pickClusterStepModels, xt as mayWriteToLocation, y as MAX_ARCHIVED_DEBUG_NOTES, yt as isSourceCap, z as OpsLogEntrySchema, zt as BaseAddon } from "../dist-BqkhuC85.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";
|
|
@@ -20070,11 +20070,19 @@ var TimelapseRenderer = class {
|
|
|
20070
20070
|
async render(request) {
|
|
20071
20071
|
const { deviceId, window } = request;
|
|
20072
20072
|
const tags = { deviceId };
|
|
20073
|
-
const
|
|
20073
|
+
const first = await this.ports.getAvailability({
|
|
20074
20074
|
deviceId,
|
|
20075
20075
|
fromMs: window.startMs,
|
|
20076
20076
|
toMs: window.endMs
|
|
20077
20077
|
});
|
|
20078
|
+
const cheapest = [...first.profilesWithFootage].sort((a, b) => profileCost(a) - profileCost(b))[0];
|
|
20079
|
+
const describes = first.ranges[0]?.profile;
|
|
20080
|
+
const availability = cheapest !== void 0 && cheapest !== describes ? (await this.ports.getAvailability({
|
|
20081
|
+
deviceId,
|
|
20082
|
+
fromMs: window.startMs,
|
|
20083
|
+
toMs: window.endMs,
|
|
20084
|
+
profile: cheapest
|
|
20085
|
+
})).ranges : first.ranges;
|
|
20078
20086
|
const chosen = pickProfile(availability, window);
|
|
20079
20087
|
if (chosen === null || chosen.coverage.coveredMs === 0) {
|
|
20080
20088
|
this.ports.logger.warn("timelapse skipped: no footage covers the window", {
|
|
@@ -36137,7 +36145,6 @@ var FrameProcessor = class {
|
|
|
36137
36145
|
* filter). Shares the tracker's vocabulary — see {@link SpawnRefusalReason}. */
|
|
36138
36146
|
spawnRefusalObserver = null;
|
|
36139
36147
|
/** Where a collected refusal is forwarded for LOGGING, when anyone asked. */
|
|
36140
|
-
spawnRefusalForward = null;
|
|
36141
36148
|
/**
|
|
36142
36149
|
* The spawn refusals of the frame being processed.
|
|
36143
36150
|
*
|
|
@@ -36151,6 +36158,10 @@ var FrameProcessor = class {
|
|
|
36151
36158
|
/** Running count of refusals-in-zone SEEN — never the throttled number. */
|
|
36152
36159
|
zoneRefusalEventCount = 0;
|
|
36153
36160
|
zoneRefusalObserver = null;
|
|
36161
|
+
/** Track ids whose BIRTH zone history has already been folded in — the fold
|
|
36162
|
+
* happens once, on the frame a track first becomes visible. */
|
|
36163
|
+
birthZonesSeeded = /* @__PURE__ */ new Set();
|
|
36164
|
+
birthZoneTrailObserver = null;
|
|
36154
36165
|
/** Last report time per collapsed key, for {@link ZONE_REFUSAL_THROTTLE_MS}. */
|
|
36155
36166
|
zoneRefusalLastReport = /* @__PURE__ */ new Map();
|
|
36156
36167
|
zoneRefusalSampler = new SpawnRefusalSampler();
|
|
@@ -36261,6 +36272,10 @@ var FrameProcessor = class {
|
|
|
36261
36272
|
setZoneRefusalObserver(observer) {
|
|
36262
36273
|
this.zoneRefusalObserver = observer;
|
|
36263
36274
|
}
|
|
36275
|
+
/** Where "this track was born already carrying a zone" is reported. */
|
|
36276
|
+
setBirthZoneTrailObserver(observer) {
|
|
36277
|
+
this.birthZoneTrailObserver = observer;
|
|
36278
|
+
}
|
|
36264
36279
|
setBindObserver(observer) {
|
|
36265
36280
|
this.tracker.setBindObserver(observer);
|
|
36266
36281
|
}
|
|
@@ -36350,8 +36365,8 @@ var FrameProcessor = class {
|
|
|
36350
36365
|
memberships.map((m) => m.zoneId).join("+"),
|
|
36351
36366
|
cell(r.bbox.x),
|
|
36352
36367
|
cell(r.bbox.y),
|
|
36353
|
-
cell(r.bbox.
|
|
36354
|
-
cell(r.bbox.
|
|
36368
|
+
cell(r.bbox.w),
|
|
36369
|
+
cell(r.bbox.h)
|
|
36355
36370
|
].join("|");
|
|
36356
36371
|
const last = this.zoneRefusalLastReport.get(key);
|
|
36357
36372
|
if (last !== void 0 && timestamp - last < ZONE_REFUSAL_THROTTLE_MS) return false;
|
|
@@ -36596,6 +36611,32 @@ var FrameProcessor = class {
|
|
|
36596
36611
|
if (memberships.length > 0) overlapsByTrack.set(td.trackId, memberships);
|
|
36597
36612
|
if (belowBar.length > 0) rejectedByTrack.set(td.trackId, belowBar);
|
|
36598
36613
|
}
|
|
36614
|
+
for (const td of trackedDetections) {
|
|
36615
|
+
if (this.birthZonesSeeded.has(td.trackId)) continue;
|
|
36616
|
+
this.birthZonesSeeded.add(td.trackId);
|
|
36617
|
+
const trail = td.path;
|
|
36618
|
+
if (trail.length <= 1) continue;
|
|
36619
|
+
const atBirth = zonesByTrack.get(td.trackId) ?? [];
|
|
36620
|
+
const zones = new Set(atBirth);
|
|
36621
|
+
const bornWith = zones.size;
|
|
36622
|
+
for (let i = 0; i < trail.length - 1; i++) {
|
|
36623
|
+
const box = trail[i];
|
|
36624
|
+
if (box === void 0) continue;
|
|
36625
|
+
const { memberships } = this.zoneEngine.splitDetectionZones(box, this.zones, frameWidth, frameHeight, void 0, void 0, void 0, this.zoneMembershipMinOverlap);
|
|
36626
|
+
for (const m of memberships) zones.add(m.zoneId);
|
|
36627
|
+
}
|
|
36628
|
+
if (zones.size === bornWith) continue;
|
|
36629
|
+
const inherited = [...zones].filter((z) => !atBirth.includes(z));
|
|
36630
|
+
zonesByTrack.set(td.trackId, [...zones]);
|
|
36631
|
+
this.birthZoneTrailObserver?.({
|
|
36632
|
+
deviceId: this.deviceId,
|
|
36633
|
+
trackId: td.trackId,
|
|
36634
|
+
className: td.class,
|
|
36635
|
+
measurementsBeforeBirth: trail.length - 1,
|
|
36636
|
+
zonesAtBirth: [...atBirth],
|
|
36637
|
+
inheritedZones: inherited
|
|
36638
|
+
});
|
|
36639
|
+
}
|
|
36599
36640
|
this.lastZoneOverlaps = overlapsByTrack;
|
|
36600
36641
|
this.lastZoneRejections = rejectedByTrack;
|
|
36601
36642
|
for (const td of trackedDetections) this.lastDetectionByTrack.set(td.trackId, td);
|
|
@@ -36668,6 +36709,7 @@ var FrameProcessor = class {
|
|
|
36668
36709
|
for (const state of objectStates) {
|
|
36669
36710
|
if (state.state !== "leaving") continue;
|
|
36670
36711
|
this.lastZonesByTrack.delete(state.trackId);
|
|
36712
|
+
this.birthZonesSeeded.delete(state.trackId);
|
|
36671
36713
|
this.lastDetectionByTrack.delete(state.trackId);
|
|
36672
36714
|
this.reportedRefusalByTrack.delete(state.trackId);
|
|
36673
36715
|
}
|
|
@@ -39595,6 +39637,62 @@ function tieredLabelColumnData(patch) {
|
|
|
39595
39637
|
};
|
|
39596
39638
|
}
|
|
39597
39639
|
//#endregion
|
|
39640
|
+
//#region src/pipeline-analytics/store/audio-level-density.ts
|
|
39641
|
+
/**
|
|
39642
|
+
* The energy-weighted mean of a set of dBFS values, in dBFS.
|
|
39643
|
+
*
|
|
39644
|
+
* dBFS is logarithmic, so an arithmetic mean of dB numbers is the mean of
|
|
39645
|
+
* nothing physical — it under-weights the loud moment that is the reason to
|
|
39646
|
+
* look at a bar at all. Averaging POWER and converting back is what a meter is
|
|
39647
|
+
* expected to show, and it is the same arithmetic as an RMS over the interval.
|
|
39648
|
+
*
|
|
39649
|
+
* `undefined` for an empty set, and for a set with no finite sample: a bucket
|
|
39650
|
+
* nobody measured must not be reported as silence, and −∞ dBFS is not a number
|
|
39651
|
+
* a bar can paint.
|
|
39652
|
+
*/
|
|
39653
|
+
function energyWeightedMeanDbfs(samples) {
|
|
39654
|
+
let power = 0;
|
|
39655
|
+
let n = 0;
|
|
39656
|
+
for (const dbfs of samples) {
|
|
39657
|
+
if (Number.isNaN(dbfs)) continue;
|
|
39658
|
+
if (dbfs === Number.NEGATIVE_INFINITY) {
|
|
39659
|
+
n += 1;
|
|
39660
|
+
continue;
|
|
39661
|
+
}
|
|
39662
|
+
if (!Number.isFinite(dbfs)) continue;
|
|
39663
|
+
power += 10 ** (dbfs / 10);
|
|
39664
|
+
n += 1;
|
|
39665
|
+
}
|
|
39666
|
+
if (n === 0) return void 0;
|
|
39667
|
+
const mean = power / n;
|
|
39668
|
+
if (mean <= 0) return void 0;
|
|
39669
|
+
return 10 * Math.log10(mean);
|
|
39670
|
+
}
|
|
39671
|
+
/**
|
|
39672
|
+
* Energy-weighted mean dBFS per bucket index, for the buckets that HAVE audio.
|
|
39673
|
+
*
|
|
39674
|
+
* A bucket with no events is absent from the map rather than present at zero:
|
|
39675
|
+
* "nothing was heard" and "nothing was measured" are different claims, and the
|
|
39676
|
+
* lane has to be able to draw nothing.
|
|
39677
|
+
*/
|
|
39678
|
+
function audioDbfsByBucket(events, since, bucketMs) {
|
|
39679
|
+
const out = /* @__PURE__ */ new Map();
|
|
39680
|
+
if (bucketMs <= 0) return out;
|
|
39681
|
+
const byBucket = /* @__PURE__ */ new Map();
|
|
39682
|
+
for (const ev of events) {
|
|
39683
|
+
const idx = Math.floor((ev.timestamp - since) / bucketMs);
|
|
39684
|
+
if (idx < 0) continue;
|
|
39685
|
+
const bin = byBucket.get(idx);
|
|
39686
|
+
if (bin === void 0) byBucket.set(idx, [ev.dbfs]);
|
|
39687
|
+
else bin.push(ev.dbfs);
|
|
39688
|
+
}
|
|
39689
|
+
for (const [idx, samples] of byBucket) {
|
|
39690
|
+
const mean = energyWeightedMeanDbfs(samples);
|
|
39691
|
+
if (mean !== void 0) out.set(idx, mean);
|
|
39692
|
+
}
|
|
39693
|
+
return out;
|
|
39694
|
+
}
|
|
39695
|
+
//#endregion
|
|
39598
39696
|
//#region src/pipeline-analytics/store/event-store.ts
|
|
39599
39697
|
/**
|
|
39600
39698
|
* @durable class=ledger owner=pipeline-analytics
|
|
@@ -40433,10 +40531,11 @@ var EventStore = class {
|
|
|
40433
40531
|
});
|
|
40434
40532
|
return new Map(rows.map((r) => [r.bucket, r.count]));
|
|
40435
40533
|
};
|
|
40436
|
-
const [motionMap, objectMap, audioMap] = await Promise.all([
|
|
40534
|
+
const [motionMap, objectMap, audioMap, audioDbfsMap] = await Promise.all([
|
|
40437
40535
|
run(MOTION_EVENTS_COLLECTION),
|
|
40438
40536
|
run(OBJECT_EVENTS_COLLECTION),
|
|
40439
|
-
run(AUDIO_EVENTS_COLLECTION)
|
|
40537
|
+
run(AUDIO_EVENTS_COLLECTION),
|
|
40538
|
+
this.audioDbfsPerBucket(q)
|
|
40440
40539
|
]);
|
|
40441
40540
|
const buckets = Math.max(0, Math.ceil((q.until - q.since) / q.bucketMs));
|
|
40442
40541
|
const out = [];
|
|
@@ -40445,16 +40544,46 @@ var EventStore = class {
|
|
|
40445
40544
|
const o = objectMap.get(i) ?? 0;
|
|
40446
40545
|
const a = audioMap.get(i) ?? 0;
|
|
40447
40546
|
if (m + o + a === 0) continue;
|
|
40547
|
+
const dbfs = audioDbfsMap.get(i);
|
|
40448
40548
|
out.push({
|
|
40449
40549
|
bucketStart: q.since + i * q.bucketMs,
|
|
40450
40550
|
motion: m,
|
|
40451
40551
|
object: o,
|
|
40452
|
-
audio: a
|
|
40552
|
+
audio: a,
|
|
40553
|
+
...dbfs !== void 0 ? { audioDbfs: dbfs } : {}
|
|
40453
40554
|
});
|
|
40454
40555
|
}
|
|
40455
40556
|
return out;
|
|
40456
40557
|
}
|
|
40457
40558
|
/**
|
|
40559
|
+
* Energy-weighted mean dBFS per bucket index, for the buckets that have
|
|
40560
|
+
* audio. Reads the window's audio events once, `slim`, and folds them —
|
|
40561
|
+
* `histogram` can only count, and a level is not a count.
|
|
40562
|
+
*
|
|
40563
|
+
* A read that fails yields an EMPTY map, so the lane draws no level rather
|
|
40564
|
+
* than a wrong one: the density counts beside it are unaffected, and a
|
|
40565
|
+
* missing bar is the honest rendering of "not measured" (D431).
|
|
40566
|
+
*/
|
|
40567
|
+
async audioDbfsPerBucket(q) {
|
|
40568
|
+
try {
|
|
40569
|
+
return audioDbfsByBucket((await this.queryAudio({
|
|
40570
|
+
deviceId: q.deviceId,
|
|
40571
|
+
since: q.since,
|
|
40572
|
+
until: q.until,
|
|
40573
|
+
projection: "slim"
|
|
40574
|
+
})).map((e) => ({
|
|
40575
|
+
timestamp: e.timestamp,
|
|
40576
|
+
dbfs: e.dbfs
|
|
40577
|
+
})), q.since, q.bucketMs);
|
|
40578
|
+
} catch (err) {
|
|
40579
|
+
this.logger.warn("density: audio level aggregation failed — the lane will draw no level", {
|
|
40580
|
+
tags: { deviceId: q.deviceId },
|
|
40581
|
+
meta: { error: String(err) }
|
|
40582
|
+
});
|
|
40583
|
+
return /* @__PURE__ */ new Map();
|
|
40584
|
+
}
|
|
40585
|
+
}
|
|
40586
|
+
/**
|
|
40458
40587
|
* Age-evict the TRACKLESS event kinds — motion and audio.
|
|
40459
40588
|
*
|
|
40460
40589
|
* **Object events are not here, and that is the point.** An object event is a
|
|
@@ -72742,12 +72871,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
72742
72871
|
*/
|
|
72743
72872
|
buildTimelapsePorts(api, stores) {
|
|
72744
72873
|
return {
|
|
72745
|
-
getAvailability: async ({ deviceId, fromMs, toMs }) => {
|
|
72746
|
-
|
|
72874
|
+
getAvailability: async ({ deviceId, fromMs, toMs, profile }) => {
|
|
72875
|
+
const availability = await api.recording.getAvailability.query({
|
|
72747
72876
|
deviceId,
|
|
72748
72877
|
fromMs,
|
|
72749
|
-
toMs
|
|
72750
|
-
|
|
72878
|
+
toMs,
|
|
72879
|
+
...profile !== void 0 ? { profile } : {}
|
|
72880
|
+
});
|
|
72881
|
+
return {
|
|
72882
|
+
ranges: availability.ranges,
|
|
72883
|
+
profilesWithFootage: availability.profilesWithFootage
|
|
72884
|
+
};
|
|
72751
72885
|
},
|
|
72752
72886
|
listTracks: async ({ deviceId, since, until }) => {
|
|
72753
72887
|
return (await stores.trackStore.queryHistorical({
|
|
@@ -77534,6 +77668,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
77534
77668
|
}
|
|
77535
77669
|
});
|
|
77536
77670
|
});
|
|
77671
|
+
p.setBirthZoneTrailObserver((trail) => {
|
|
77672
|
+
this.ctx.logger.info("track born carrying zones it crossed before it was a track", {
|
|
77673
|
+
tags: { deviceId },
|
|
77674
|
+
meta: {
|
|
77675
|
+
source,
|
|
77676
|
+
...trail
|
|
77677
|
+
}
|
|
77678
|
+
});
|
|
77679
|
+
});
|
|
77537
77680
|
p.setZoneRefusalObserver((observation) => {
|
|
77538
77681
|
this.ctx.logger.info("detection refused inside a watched zone — not notified", {
|
|
77539
77682
|
tags: { deviceId },
|
|
@@ -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-JPlVJb-8.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.218",
|
|
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",
|