@camstack/addon-post-analysis 1.2.608 → 1.2.609
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/pipeline-analytics/index.js +126 -17
- package/dist/pipeline-analytics/index.mjs +126 -17
- package/package.json +1 -1
|
@@ -22347,6 +22347,41 @@ function audioDbfsByBucket(events, since, bucketMs) {
|
|
|
22347
22347
|
}
|
|
22348
22348
|
return out;
|
|
22349
22349
|
}
|
|
22350
|
+
/**
|
|
22351
|
+
* PEAK dBFS per bucket index from device-history points (D431 follow-up).
|
|
22352
|
+
*
|
|
22353
|
+
* The bar answers "was it loud here", so a bucket reports the loudest window
|
|
22354
|
+
* in it. A raw point has `max === value`; a rollup row's `max` is the loudest
|
|
22355
|
+
* point it covers, which a mean would flatten. A bucket with no point is
|
|
22356
|
+
* absent — never zero. A rollup row also credits a held value across a quiet
|
|
22357
|
+
* gap, so its max can echo an earlier measured peak.
|
|
22358
|
+
*/
|
|
22359
|
+
function audioPeakByBucket(points, since, bucketMs) {
|
|
22360
|
+
const out = /* @__PURE__ */ new Map();
|
|
22361
|
+
if (bucketMs <= 0) return out;
|
|
22362
|
+
for (const p of points) {
|
|
22363
|
+
if (!Number.isFinite(p.max)) continue;
|
|
22364
|
+
const idx = Math.floor((p.t - since) / bucketMs);
|
|
22365
|
+
if (idx < 0) continue;
|
|
22366
|
+
const prev = out.get(idx);
|
|
22367
|
+
if (prev === void 0 || p.max > prev) out.set(idx, p.max);
|
|
22368
|
+
}
|
|
22369
|
+
return out;
|
|
22370
|
+
}
|
|
22371
|
+
//#endregion
|
|
22372
|
+
//#region src/pipeline-analytics/store/analytics-history-keys.ts
|
|
22373
|
+
/** Declared addon series keys (D850). */
|
|
22374
|
+
var ANALYTICS_HISTORY_BUCKET_SERIES = {
|
|
22375
|
+
motion: "addon:pipeline-analytics/motion",
|
|
22376
|
+
"audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
|
|
22377
|
+
occupancy: "addon:pipeline-analytics/occupancy"
|
|
22378
|
+
};
|
|
22379
|
+
var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
|
|
22380
|
+
/** Map the public cap enum to the device-history key (read path). */
|
|
22381
|
+
function historyKeyForLtsSeries(series) {
|
|
22382
|
+
if (series === "battery") return "cap:battery.percentage";
|
|
22383
|
+
return ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
22384
|
+
}
|
|
22350
22385
|
//#endregion
|
|
22351
22386
|
//#region src/pipeline-analytics/store/event-store.ts
|
|
22352
22387
|
/**
|
|
@@ -22719,6 +22754,24 @@ function eventRowId(logger, operation, deviceId, key) {
|
|
|
22719
22754
|
});
|
|
22720
22755
|
return null;
|
|
22721
22756
|
}
|
|
22757
|
+
var RAW_RETENTION_MS = 7 * 864e5;
|
|
22758
|
+
var FIVE_MIN_RETENTION_MS = 14 * 864e5;
|
|
22759
|
+
var FIVE_MIN_MS = 3e5;
|
|
22760
|
+
var HOUR_MS$2 = 36e5;
|
|
22761
|
+
/** The producer writes one raw point per window of this length. */
|
|
22762
|
+
var AUDIO_LEVEL_POINT_MS = 1e4;
|
|
22763
|
+
var HISTORY_MAX_POINTS = 5e3;
|
|
22764
|
+
/**
|
|
22765
|
+
* Pick the device-history tier for a density window: raw while the buckets are
|
|
22766
|
+
* finer than the 5m rollup, the window is inside raw retention and its points
|
|
22767
|
+
* fit one query; the 5m rollup while inside its retention; else 1h.
|
|
22768
|
+
*/
|
|
22769
|
+
function audioLevelResolution(q, now) {
|
|
22770
|
+
const rawFits = Math.ceil((q.until - q.since) / AUDIO_LEVEL_POINT_MS) <= HISTORY_MAX_POINTS;
|
|
22771
|
+
if (q.bucketMs < FIVE_MIN_MS && rawFits && q.since >= now - RAW_RETENTION_MS) return "raw";
|
|
22772
|
+
if (q.bucketMs < HOUR_MS$2 && q.since >= now - FIVE_MIN_RETENTION_MS) return "5m";
|
|
22773
|
+
return "1h";
|
|
22774
|
+
}
|
|
22722
22775
|
var EventStore = class EventStore {
|
|
22723
22776
|
store;
|
|
22724
22777
|
logger;
|
|
@@ -22726,6 +22779,10 @@ var EventStore = class EventStore {
|
|
|
22726
22779
|
audioLedger;
|
|
22727
22780
|
media;
|
|
22728
22781
|
onMotionSample;
|
|
22782
|
+
audioLevelHistory;
|
|
22783
|
+
nowFn;
|
|
22784
|
+
/** Cameras whose history read already failed once (log once per camera, D315). */
|
|
22785
|
+
levelFallbackLogged = /* @__PURE__ */ new Set();
|
|
22729
22786
|
constructor(deps) {
|
|
22730
22787
|
this.store = deps.store;
|
|
22731
22788
|
this.logger = deps.logger;
|
|
@@ -22734,6 +22791,8 @@ var EventStore = class EventStore {
|
|
|
22734
22791
|
this.audioLedger = deps.audioLedger ?? wireAudioEventLedger(wiring);
|
|
22735
22792
|
this.media = deps.media;
|
|
22736
22793
|
this.onMotionSample = deps.onMotionSample;
|
|
22794
|
+
this.audioLevelHistory = deps.audioLevelHistory;
|
|
22795
|
+
this.nowFn = deps.now ?? (() => Date.now());
|
|
22737
22796
|
}
|
|
22738
22797
|
static async declareTracklessEventCollections(store) {
|
|
22739
22798
|
await store.declareCollection.mutate({
|
|
@@ -23462,8 +23521,8 @@ var EventStore = class EventStore {
|
|
|
23462
23521
|
const m = motionMap.get(i) ?? 0;
|
|
23463
23522
|
const o = objectMap.get(i) ?? 0;
|
|
23464
23523
|
const a = audioMap.get(i) ?? 0;
|
|
23465
|
-
if (m + o + a === 0) continue;
|
|
23466
23524
|
const dbfs = audioDbfsMap.get(i);
|
|
23525
|
+
if (m + o + a === 0 && dbfs === void 0) continue;
|
|
23467
23526
|
out.push({
|
|
23468
23527
|
bucketStart: q.since + i * q.bucketMs,
|
|
23469
23528
|
motion: m,
|
|
@@ -23484,6 +23543,28 @@ var EventStore = class EventStore {
|
|
|
23484
23543
|
* missing bar is the honest rendering of "not measured" (D431).
|
|
23485
23544
|
*/
|
|
23486
23545
|
async audioDbfsPerBucket(q) {
|
|
23546
|
+
const history = this.audioLevelHistory;
|
|
23547
|
+
if (history !== void 0) try {
|
|
23548
|
+
const resolution = audioLevelResolution(q, this.nowFn());
|
|
23549
|
+
const points = (await history({
|
|
23550
|
+
deviceId: q.deviceId,
|
|
23551
|
+
key: ANALYTICS_AUDIO_LEVEL_KEY,
|
|
23552
|
+
scope: "",
|
|
23553
|
+
from: q.since,
|
|
23554
|
+
to: q.until,
|
|
23555
|
+
resolution,
|
|
23556
|
+
maxPoints: HISTORY_MAX_POINTS
|
|
23557
|
+
})).series.flatMap((s) => s.points);
|
|
23558
|
+
if (points.length > 0) return audioPeakByBucket(points, q.since, q.bucketMs);
|
|
23559
|
+
} catch (err) {
|
|
23560
|
+
if (!this.levelFallbackLogged.has(q.deviceId)) {
|
|
23561
|
+
this.levelFallbackLogged.add(q.deviceId);
|
|
23562
|
+
this.logger.warn("density: audio level history unreadable — falling back to audio events", {
|
|
23563
|
+
tags: { deviceId: q.deviceId },
|
|
23564
|
+
meta: { error: String(err) }
|
|
23565
|
+
});
|
|
23566
|
+
}
|
|
23567
|
+
}
|
|
23487
23568
|
try {
|
|
23488
23569
|
return audioDbfsByBucket((await this.queryAudio({
|
|
23489
23570
|
deviceId: q.deviceId,
|
|
@@ -111555,20 +111636,6 @@ var MigrationMarkerStore = class MigrationMarkerStore {
|
|
|
111555
111636
|
};
|
|
111556
111637
|
};
|
|
111557
111638
|
//#endregion
|
|
111558
|
-
//#region src/pipeline-analytics/store/analytics-history-keys.ts
|
|
111559
|
-
/** Declared addon series keys (D850). */
|
|
111560
|
-
var ANALYTICS_HISTORY_BUCKET_SERIES = {
|
|
111561
|
-
motion: "addon:pipeline-analytics/motion",
|
|
111562
|
-
"audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
|
|
111563
|
-
occupancy: "addon:pipeline-analytics/occupancy"
|
|
111564
|
-
};
|
|
111565
|
-
var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
|
|
111566
|
-
/** Map the public cap enum to the device-history key (read path). */
|
|
111567
|
-
function historyKeyForLtsSeries(series) {
|
|
111568
|
-
if (series === "battery") return "cap:battery.percentage";
|
|
111569
|
-
return ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
111570
|
-
}
|
|
111571
|
-
//#endregion
|
|
111572
111639
|
//#region src/pipeline-analytics/store/analytics-history-producer.ts
|
|
111573
111640
|
var BUCKET_SERIES_META = [
|
|
111574
111641
|
{
|
|
@@ -111590,6 +111657,10 @@ var AUDIO_LEVEL_SERIES = {
|
|
|
111590
111657
|
displayName: "Audio level (dBFS)",
|
|
111591
111658
|
unit: "dBFS"
|
|
111592
111659
|
};
|
|
111660
|
+
/** One audio level point per window; equals device-history's 1 point / 10 s record bound. */
|
|
111661
|
+
var AUDIO_LEVEL_WINDOW_MS = 1e4;
|
|
111662
|
+
/** Stored for a measured-silent window (−∞ dBFS cannot be a history value). */
|
|
111663
|
+
var AUDIO_LEVEL_SILENT_DBFS = -120;
|
|
111593
111664
|
function refusalCounts(result) {
|
|
111594
111665
|
const counts = {};
|
|
111595
111666
|
for (const r of result.refused) counts[r.reason] = (counts[r.reason] ?? 0) + r.count;
|
|
@@ -111601,6 +111672,7 @@ var AnalyticsHistoryProducer = class {
|
|
|
111601
111672
|
logger;
|
|
111602
111673
|
nowFn;
|
|
111603
111674
|
declared = false;
|
|
111675
|
+
levelWindows = /* @__PURE__ */ new Map();
|
|
111604
111676
|
constructor(deps) {
|
|
111605
111677
|
this.deviceHistory = deps.deviceHistory;
|
|
111606
111678
|
this.logger = deps.logger;
|
|
@@ -111633,6 +111705,43 @@ var AnalyticsHistoryProducer = class {
|
|
|
111633
111705
|
deviceId
|
|
111634
111706
|
}, scope ?? "", value, at ?? this.nowFn());
|
|
111635
111707
|
}
|
|
111708
|
+
/**
|
|
111709
|
+
* Feed EVERY analysed audio sample. One point per window of
|
|
111710
|
+
* {@link AUDIO_LEVEL_WINDOW_MS} is written, stamped at the window's first
|
|
111711
|
+
* sample, carrying the window's PEAK dBFS: a 2 s spike inside a stable window
|
|
111712
|
+
* is not averaged away, and stable audio still yields a point each window
|
|
111713
|
+
* (continuity over dedup — the level bar draws a continuous line).
|
|
111714
|
+
*
|
|
111715
|
+
* Measured silence (−∞) is stored at {@link AUDIO_LEVEL_SILENT_DBFS}: it is a
|
|
111716
|
+
* measurement, not an absence (D431); NaN carries no claim and is dropped.
|
|
111717
|
+
* A window closes on the first sample past its end, so no timer is needed and
|
|
111718
|
+
* points are always at least a window apart (the history's 1 point / 10 s
|
|
111719
|
+
* bound).
|
|
111720
|
+
*/
|
|
111721
|
+
observeAudioLevel(deviceId, dbfs, at) {
|
|
111722
|
+
if (Number.isNaN(dbfs)) return;
|
|
111723
|
+
const value = Number.isFinite(dbfs) ? dbfs : AUDIO_LEVEL_SILENT_DBFS;
|
|
111724
|
+
const win = this.levelWindows.get(deviceId);
|
|
111725
|
+
if (win === void 0) {
|
|
111726
|
+
this.levelWindows.set(deviceId, {
|
|
111727
|
+
start: at,
|
|
111728
|
+
peak: value
|
|
111729
|
+
});
|
|
111730
|
+
return;
|
|
111731
|
+
}
|
|
111732
|
+
if (at - win.start < AUDIO_LEVEL_WINDOW_MS) {
|
|
111733
|
+
if (value > win.peak) this.levelWindows.set(deviceId, {
|
|
111734
|
+
start: win.start,
|
|
111735
|
+
peak: value
|
|
111736
|
+
});
|
|
111737
|
+
return;
|
|
111738
|
+
}
|
|
111739
|
+
this.levelWindows.set(deviceId, {
|
|
111740
|
+
start: at,
|
|
111741
|
+
peak: value
|
|
111742
|
+
});
|
|
111743
|
+
this.recordAudioLevel(deviceId, win.peak, win.start);
|
|
111744
|
+
}
|
|
111636
111745
|
recordAudioLevel(deviceId, dbfs, at) {
|
|
111637
111746
|
const record = this.deviceHistory.record;
|
|
111638
111747
|
if (record === void 0) return;
|
|
@@ -116595,6 +116704,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
116595
116704
|
const eventStore = new EventStore({
|
|
116596
116705
|
store: api.settingsStore,
|
|
116597
116706
|
logger: logger.child("EventStore"),
|
|
116707
|
+
audioLevelHistory: (input) => api.deviceHistory.query.query(input),
|
|
116598
116708
|
motionLedger: wireMotionEventLedger(eventLedgerWiring),
|
|
116599
116709
|
audioLedger: wireAudioEventLedger(eventLedgerWiring),
|
|
116600
116710
|
onMotionSample: (deviceId, regionCount, at) => lts.note("motion", deviceId, regionCount, at),
|
|
@@ -122070,6 +122180,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
122070
122180
|
score: topClassification.score
|
|
122071
122181
|
} : {}
|
|
122072
122182
|
}, this.audioMarkerConfig(settings));
|
|
122183
|
+
if (level !== void 0) this.lts?.observeAudioLevel(deviceId, level.dbfs, timestamp);
|
|
122073
122184
|
this.notificationCenter?.observeAudio(deviceId, ncAudioSampleFromFrame(frame, timestamp, settings));
|
|
122074
122185
|
const route = classifyAudioFrame(topClassification ? {
|
|
122075
122186
|
className: topClassification.macroClass,
|
|
@@ -122145,8 +122256,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
122145
122256
|
});
|
|
122146
122257
|
this.levelStateByDevice.set(deviceId, res.nextState);
|
|
122147
122258
|
if (!res.emit) return;
|
|
122148
|
-
const dbfs = level?.dbfs ?? Number.NEGATIVE_INFINITY;
|
|
122149
|
-
this.lts?.recordAudioLevel(deviceId, dbfs, timestamp);
|
|
122150
122259
|
}
|
|
122151
122260
|
/**
|
|
122152
122261
|
* Persist motion-analysis bus events into the EPISODE tracker (D475): a
|
|
@@ -22339,6 +22339,41 @@ function audioDbfsByBucket(events, since, bucketMs) {
|
|
|
22339
22339
|
}
|
|
22340
22340
|
return out;
|
|
22341
22341
|
}
|
|
22342
|
+
/**
|
|
22343
|
+
* PEAK dBFS per bucket index from device-history points (D431 follow-up).
|
|
22344
|
+
*
|
|
22345
|
+
* The bar answers "was it loud here", so a bucket reports the loudest window
|
|
22346
|
+
* in it. A raw point has `max === value`; a rollup row's `max` is the loudest
|
|
22347
|
+
* point it covers, which a mean would flatten. A bucket with no point is
|
|
22348
|
+
* absent — never zero. A rollup row also credits a held value across a quiet
|
|
22349
|
+
* gap, so its max can echo an earlier measured peak.
|
|
22350
|
+
*/
|
|
22351
|
+
function audioPeakByBucket(points, since, bucketMs) {
|
|
22352
|
+
const out = /* @__PURE__ */ new Map();
|
|
22353
|
+
if (bucketMs <= 0) return out;
|
|
22354
|
+
for (const p of points) {
|
|
22355
|
+
if (!Number.isFinite(p.max)) continue;
|
|
22356
|
+
const idx = Math.floor((p.t - since) / bucketMs);
|
|
22357
|
+
if (idx < 0) continue;
|
|
22358
|
+
const prev = out.get(idx);
|
|
22359
|
+
if (prev === void 0 || p.max > prev) out.set(idx, p.max);
|
|
22360
|
+
}
|
|
22361
|
+
return out;
|
|
22362
|
+
}
|
|
22363
|
+
//#endregion
|
|
22364
|
+
//#region src/pipeline-analytics/store/analytics-history-keys.ts
|
|
22365
|
+
/** Declared addon series keys (D850). */
|
|
22366
|
+
var ANALYTICS_HISTORY_BUCKET_SERIES = {
|
|
22367
|
+
motion: "addon:pipeline-analytics/motion",
|
|
22368
|
+
"audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
|
|
22369
|
+
occupancy: "addon:pipeline-analytics/occupancy"
|
|
22370
|
+
};
|
|
22371
|
+
var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
|
|
22372
|
+
/** Map the public cap enum to the device-history key (read path). */
|
|
22373
|
+
function historyKeyForLtsSeries(series) {
|
|
22374
|
+
if (series === "battery") return "cap:battery.percentage";
|
|
22375
|
+
return ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
22376
|
+
}
|
|
22342
22377
|
//#endregion
|
|
22343
22378
|
//#region src/pipeline-analytics/store/event-store.ts
|
|
22344
22379
|
/**
|
|
@@ -22711,6 +22746,24 @@ function eventRowId(logger, operation, deviceId, key) {
|
|
|
22711
22746
|
});
|
|
22712
22747
|
return null;
|
|
22713
22748
|
}
|
|
22749
|
+
var RAW_RETENTION_MS = 7 * 864e5;
|
|
22750
|
+
var FIVE_MIN_RETENTION_MS = 14 * 864e5;
|
|
22751
|
+
var FIVE_MIN_MS = 3e5;
|
|
22752
|
+
var HOUR_MS$2 = 36e5;
|
|
22753
|
+
/** The producer writes one raw point per window of this length. */
|
|
22754
|
+
var AUDIO_LEVEL_POINT_MS = 1e4;
|
|
22755
|
+
var HISTORY_MAX_POINTS = 5e3;
|
|
22756
|
+
/**
|
|
22757
|
+
* Pick the device-history tier for a density window: raw while the buckets are
|
|
22758
|
+
* finer than the 5m rollup, the window is inside raw retention and its points
|
|
22759
|
+
* fit one query; the 5m rollup while inside its retention; else 1h.
|
|
22760
|
+
*/
|
|
22761
|
+
function audioLevelResolution(q, now) {
|
|
22762
|
+
const rawFits = Math.ceil((q.until - q.since) / AUDIO_LEVEL_POINT_MS) <= HISTORY_MAX_POINTS;
|
|
22763
|
+
if (q.bucketMs < FIVE_MIN_MS && rawFits && q.since >= now - RAW_RETENTION_MS) return "raw";
|
|
22764
|
+
if (q.bucketMs < HOUR_MS$2 && q.since >= now - FIVE_MIN_RETENTION_MS) return "5m";
|
|
22765
|
+
return "1h";
|
|
22766
|
+
}
|
|
22714
22767
|
var EventStore = class EventStore {
|
|
22715
22768
|
store;
|
|
22716
22769
|
logger;
|
|
@@ -22718,6 +22771,10 @@ var EventStore = class EventStore {
|
|
|
22718
22771
|
audioLedger;
|
|
22719
22772
|
media;
|
|
22720
22773
|
onMotionSample;
|
|
22774
|
+
audioLevelHistory;
|
|
22775
|
+
nowFn;
|
|
22776
|
+
/** Cameras whose history read already failed once (log once per camera, D315). */
|
|
22777
|
+
levelFallbackLogged = /* @__PURE__ */ new Set();
|
|
22721
22778
|
constructor(deps) {
|
|
22722
22779
|
this.store = deps.store;
|
|
22723
22780
|
this.logger = deps.logger;
|
|
@@ -22726,6 +22783,8 @@ var EventStore = class EventStore {
|
|
|
22726
22783
|
this.audioLedger = deps.audioLedger ?? wireAudioEventLedger(wiring);
|
|
22727
22784
|
this.media = deps.media;
|
|
22728
22785
|
this.onMotionSample = deps.onMotionSample;
|
|
22786
|
+
this.audioLevelHistory = deps.audioLevelHistory;
|
|
22787
|
+
this.nowFn = deps.now ?? (() => Date.now());
|
|
22729
22788
|
}
|
|
22730
22789
|
static async declareTracklessEventCollections(store) {
|
|
22731
22790
|
await store.declareCollection.mutate({
|
|
@@ -23454,8 +23513,8 @@ var EventStore = class EventStore {
|
|
|
23454
23513
|
const m = motionMap.get(i) ?? 0;
|
|
23455
23514
|
const o = objectMap.get(i) ?? 0;
|
|
23456
23515
|
const a = audioMap.get(i) ?? 0;
|
|
23457
|
-
if (m + o + a === 0) continue;
|
|
23458
23516
|
const dbfs = audioDbfsMap.get(i);
|
|
23517
|
+
if (m + o + a === 0 && dbfs === void 0) continue;
|
|
23459
23518
|
out.push({
|
|
23460
23519
|
bucketStart: q.since + i * q.bucketMs,
|
|
23461
23520
|
motion: m,
|
|
@@ -23476,6 +23535,28 @@ var EventStore = class EventStore {
|
|
|
23476
23535
|
* missing bar is the honest rendering of "not measured" (D431).
|
|
23477
23536
|
*/
|
|
23478
23537
|
async audioDbfsPerBucket(q) {
|
|
23538
|
+
const history = this.audioLevelHistory;
|
|
23539
|
+
if (history !== void 0) try {
|
|
23540
|
+
const resolution = audioLevelResolution(q, this.nowFn());
|
|
23541
|
+
const points = (await history({
|
|
23542
|
+
deviceId: q.deviceId,
|
|
23543
|
+
key: ANALYTICS_AUDIO_LEVEL_KEY,
|
|
23544
|
+
scope: "",
|
|
23545
|
+
from: q.since,
|
|
23546
|
+
to: q.until,
|
|
23547
|
+
resolution,
|
|
23548
|
+
maxPoints: HISTORY_MAX_POINTS
|
|
23549
|
+
})).series.flatMap((s) => s.points);
|
|
23550
|
+
if (points.length > 0) return audioPeakByBucket(points, q.since, q.bucketMs);
|
|
23551
|
+
} catch (err) {
|
|
23552
|
+
if (!this.levelFallbackLogged.has(q.deviceId)) {
|
|
23553
|
+
this.levelFallbackLogged.add(q.deviceId);
|
|
23554
|
+
this.logger.warn("density: audio level history unreadable — falling back to audio events", {
|
|
23555
|
+
tags: { deviceId: q.deviceId },
|
|
23556
|
+
meta: { error: String(err) }
|
|
23557
|
+
});
|
|
23558
|
+
}
|
|
23559
|
+
}
|
|
23479
23560
|
try {
|
|
23480
23561
|
return audioDbfsByBucket((await this.queryAudio({
|
|
23481
23562
|
deviceId: q.deviceId,
|
|
@@ -111460,20 +111541,6 @@ var MigrationMarkerStore = class MigrationMarkerStore {
|
|
|
111460
111541
|
};
|
|
111461
111542
|
};
|
|
111462
111543
|
//#endregion
|
|
111463
|
-
//#region src/pipeline-analytics/store/analytics-history-keys.ts
|
|
111464
|
-
/** Declared addon series keys (D850). */
|
|
111465
|
-
var ANALYTICS_HISTORY_BUCKET_SERIES = {
|
|
111466
|
-
motion: "addon:pipeline-analytics/motion",
|
|
111467
|
-
"audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
|
|
111468
|
-
occupancy: "addon:pipeline-analytics/occupancy"
|
|
111469
|
-
};
|
|
111470
|
-
var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
|
|
111471
|
-
/** Map the public cap enum to the device-history key (read path). */
|
|
111472
|
-
function historyKeyForLtsSeries(series) {
|
|
111473
|
-
if (series === "battery") return "cap:battery.percentage";
|
|
111474
|
-
return ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
111475
|
-
}
|
|
111476
|
-
//#endregion
|
|
111477
111544
|
//#region src/pipeline-analytics/store/analytics-history-producer.ts
|
|
111478
111545
|
var BUCKET_SERIES_META = [
|
|
111479
111546
|
{
|
|
@@ -111495,6 +111562,10 @@ var AUDIO_LEVEL_SERIES = {
|
|
|
111495
111562
|
displayName: "Audio level (dBFS)",
|
|
111496
111563
|
unit: "dBFS"
|
|
111497
111564
|
};
|
|
111565
|
+
/** One audio level point per window; equals device-history's 1 point / 10 s record bound. */
|
|
111566
|
+
var AUDIO_LEVEL_WINDOW_MS = 1e4;
|
|
111567
|
+
/** Stored for a measured-silent window (−∞ dBFS cannot be a history value). */
|
|
111568
|
+
var AUDIO_LEVEL_SILENT_DBFS = -120;
|
|
111498
111569
|
function refusalCounts(result) {
|
|
111499
111570
|
const counts = {};
|
|
111500
111571
|
for (const r of result.refused) counts[r.reason] = (counts[r.reason] ?? 0) + r.count;
|
|
@@ -111506,6 +111577,7 @@ var AnalyticsHistoryProducer = class {
|
|
|
111506
111577
|
logger;
|
|
111507
111578
|
nowFn;
|
|
111508
111579
|
declared = false;
|
|
111580
|
+
levelWindows = /* @__PURE__ */ new Map();
|
|
111509
111581
|
constructor(deps) {
|
|
111510
111582
|
this.deviceHistory = deps.deviceHistory;
|
|
111511
111583
|
this.logger = deps.logger;
|
|
@@ -111538,6 +111610,43 @@ var AnalyticsHistoryProducer = class {
|
|
|
111538
111610
|
deviceId
|
|
111539
111611
|
}, scope ?? "", value, at ?? this.nowFn());
|
|
111540
111612
|
}
|
|
111613
|
+
/**
|
|
111614
|
+
* Feed EVERY analysed audio sample. One point per window of
|
|
111615
|
+
* {@link AUDIO_LEVEL_WINDOW_MS} is written, stamped at the window's first
|
|
111616
|
+
* sample, carrying the window's PEAK dBFS: a 2 s spike inside a stable window
|
|
111617
|
+
* is not averaged away, and stable audio still yields a point each window
|
|
111618
|
+
* (continuity over dedup — the level bar draws a continuous line).
|
|
111619
|
+
*
|
|
111620
|
+
* Measured silence (−∞) is stored at {@link AUDIO_LEVEL_SILENT_DBFS}: it is a
|
|
111621
|
+
* measurement, not an absence (D431); NaN carries no claim and is dropped.
|
|
111622
|
+
* A window closes on the first sample past its end, so no timer is needed and
|
|
111623
|
+
* points are always at least a window apart (the history's 1 point / 10 s
|
|
111624
|
+
* bound).
|
|
111625
|
+
*/
|
|
111626
|
+
observeAudioLevel(deviceId, dbfs, at) {
|
|
111627
|
+
if (Number.isNaN(dbfs)) return;
|
|
111628
|
+
const value = Number.isFinite(dbfs) ? dbfs : AUDIO_LEVEL_SILENT_DBFS;
|
|
111629
|
+
const win = this.levelWindows.get(deviceId);
|
|
111630
|
+
if (win === void 0) {
|
|
111631
|
+
this.levelWindows.set(deviceId, {
|
|
111632
|
+
start: at,
|
|
111633
|
+
peak: value
|
|
111634
|
+
});
|
|
111635
|
+
return;
|
|
111636
|
+
}
|
|
111637
|
+
if (at - win.start < AUDIO_LEVEL_WINDOW_MS) {
|
|
111638
|
+
if (value > win.peak) this.levelWindows.set(deviceId, {
|
|
111639
|
+
start: win.start,
|
|
111640
|
+
peak: value
|
|
111641
|
+
});
|
|
111642
|
+
return;
|
|
111643
|
+
}
|
|
111644
|
+
this.levelWindows.set(deviceId, {
|
|
111645
|
+
start: at,
|
|
111646
|
+
peak: value
|
|
111647
|
+
});
|
|
111648
|
+
this.recordAudioLevel(deviceId, win.peak, win.start);
|
|
111649
|
+
}
|
|
111541
111650
|
recordAudioLevel(deviceId, dbfs, at) {
|
|
111542
111651
|
const record = this.deviceHistory.record;
|
|
111543
111652
|
if (record === void 0) return;
|
|
@@ -116500,6 +116609,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
116500
116609
|
const eventStore = new EventStore({
|
|
116501
116610
|
store: api.settingsStore,
|
|
116502
116611
|
logger: logger.child("EventStore"),
|
|
116612
|
+
audioLevelHistory: (input) => api.deviceHistory.query.query(input),
|
|
116503
116613
|
motionLedger: wireMotionEventLedger(eventLedgerWiring),
|
|
116504
116614
|
audioLedger: wireAudioEventLedger(eventLedgerWiring),
|
|
116505
116615
|
onMotionSample: (deviceId, regionCount, at) => lts.note("motion", deviceId, regionCount, at),
|
|
@@ -121975,6 +122085,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
121975
122085
|
score: topClassification.score
|
|
121976
122086
|
} : {}
|
|
121977
122087
|
}, this.audioMarkerConfig(settings));
|
|
122088
|
+
if (level !== void 0) this.lts?.observeAudioLevel(deviceId, level.dbfs, timestamp);
|
|
121978
122089
|
this.notificationCenter?.observeAudio(deviceId, ncAudioSampleFromFrame(frame, timestamp, settings));
|
|
121979
122090
|
const route = classifyAudioFrame(topClassification ? {
|
|
121980
122091
|
className: topClassification.macroClass,
|
|
@@ -122050,8 +122161,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
122050
122161
|
});
|
|
122051
122162
|
this.levelStateByDevice.set(deviceId, res.nextState);
|
|
122052
122163
|
if (!res.emit) return;
|
|
122053
|
-
const dbfs = level?.dbfs ?? Number.NEGATIVE_INFINITY;
|
|
122054
|
-
this.lts?.recordAudioLevel(deviceId, dbfs, timestamp);
|
|
122055
122164
|
}
|
|
122056
122165
|
/**
|
|
122057
122166
|
* Persist motion-analysis bus events into the EPISODE tracker (D475): a
|
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.609",
|
|
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",
|