@camstack/addon-post-analysis 1.2.167 → 1.2.168
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-dGS8z89L.js → dist-CftJzAHc.js} +56 -5
- package/dist/{dist-DgOc1kH7.mjs → dist-h9lRw5M-.mjs} +45 -6
- 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-CUasEI3s.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-C-Iu7OUr.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-DnBMykUz.mjs → hostInit-5MA0-GO5.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +177 -15
- package/dist/pipeline-analytics/index.mjs +177 -15
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -13923,6 +13923,35 @@ method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({
|
|
|
13923
13923
|
auth: "admin"
|
|
13924
13924
|
});
|
|
13925
13925
|
/**
|
|
13926
|
+
* Transport bounds for AI capability methods.
|
|
13927
|
+
*
|
|
13928
|
+
* Its own module because `llm.cap.ts` and `llm-runtime.cap.ts` already import
|
|
13929
|
+
* each other: declaring the constant in either one hands the other `undefined`
|
|
13930
|
+
* at module-init time, and an undefined `timeoutMs` silently falls back to the
|
|
13931
|
+
* kernel's 60 s default — the exact failure this constant exists to prevent,
|
|
13932
|
+
* reintroduced invisibly. Found by the guard, not by review.
|
|
13933
|
+
*/
|
|
13934
|
+
/**
|
|
13935
|
+
* Ceiling for a cap method that runs inference or loads a model.
|
|
13936
|
+
*
|
|
13937
|
+
* Deliberately far above anything the AI layer itself may wait for (a summary
|
|
13938
|
+
* asks 180 s by default, a rule may ask 600 s), because the TRANSPORT must
|
|
13939
|
+
* never be the layer that gives up first: when it does, the failure is
|
|
13940
|
+
* reported as a transport error for a model that was still thinking, naming
|
|
13941
|
+
* the wrong layer and hiding the real one.
|
|
13942
|
+
*
|
|
13943
|
+
* Measured 2026-09-03: a 6-image vision request needs 44.8 s warm and ~20 s
|
|
13944
|
+
* more on a cold model load. With no declaration every digest failed at
|
|
13945
|
+
* exactly 60 s and shipped without its text.
|
|
13946
|
+
*
|
|
13947
|
+
* A ceiling, not a budget. The AI layer's own timeouts (the rule's `timeoutMs`,
|
|
13948
|
+
* the profile's `timeoutMs` / `firstTokenTimeoutMs`) remain the things that
|
|
13949
|
+
* decide when to stop, because only they can say WHY.
|
|
13950
|
+
*/
|
|
13951
|
+
var AI_CAP_TRANSPORT_TIMEOUT_MS = 6e5;
|
|
13952
|
+
/** A multi-gigabyte download. An hour is not generous, it is honest. */
|
|
13953
|
+
var AI_MODEL_INSTALL_TIMEOUT_MS = 60 * 6e4;
|
|
13954
|
+
/**
|
|
13926
13955
|
* Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
|
|
13927
13956
|
* surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
|
|
13928
13957
|
* caps stay wire-compatible without a circular cap→cap import.
|
|
@@ -14194,16 +14223,19 @@ method(LlmGenerateBaseInputSchema.extend({
|
|
|
14194
14223
|
timeoutMs: number().int().positive().optional()
|
|
14195
14224
|
}), LlmGenerateResultSchema, {
|
|
14196
14225
|
kind: "mutation",
|
|
14197
|
-
auth: "admin"
|
|
14226
|
+
auth: "admin",
|
|
14227
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14198
14228
|
}), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
|
|
14199
14229
|
kind: "mutation",
|
|
14200
|
-
auth: "admin"
|
|
14230
|
+
auth: "admin",
|
|
14231
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14201
14232
|
}), method(object({}), _void(), {
|
|
14202
14233
|
kind: "mutation",
|
|
14203
14234
|
auth: "admin"
|
|
14204
14235
|
}), method(object({}), LlmRuntimeStatusSchema, { auth: "admin" }), method(object({ model: ManagedModelRefSchema }), _void(), {
|
|
14205
14236
|
kind: "mutation",
|
|
14206
|
-
auth: "admin"
|
|
14237
|
+
auth: "admin",
|
|
14238
|
+
timeoutMs: AI_MODEL_INSTALL_TIMEOUT_MS
|
|
14207
14239
|
}), method(object({ file: string() }), _void(), {
|
|
14208
14240
|
kind: "mutation",
|
|
14209
14241
|
auth: "admin"
|
|
@@ -14371,7 +14403,13 @@ var ProfileRefInputSchema = object({
|
|
|
14371
14403
|
addonId: string(),
|
|
14372
14404
|
profileId: string()
|
|
14373
14405
|
});
|
|
14374
|
-
method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
|
|
14406
|
+
method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
|
|
14407
|
+
kind: "mutation",
|
|
14408
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14409
|
+
}), method(GenerateVisionInputSchema, LlmGenerateResultSchema, {
|
|
14410
|
+
kind: "mutation",
|
|
14411
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14412
|
+
}), method(object({
|
|
14375
14413
|
addonId: string().optional(),
|
|
14376
14414
|
requestId: string()
|
|
14377
14415
|
}), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
|
|
@@ -14382,7 +14420,8 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
|
|
|
14382
14420
|
auth: "admin"
|
|
14383
14421
|
}), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
|
|
14384
14422
|
kind: "mutation",
|
|
14385
|
-
auth: "admin"
|
|
14423
|
+
auth: "admin",
|
|
14424
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14386
14425
|
}), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
|
|
14387
14426
|
selector: LlmDefaultSelectorSchema,
|
|
14388
14427
|
profileId: string().nullable()
|
|
@@ -43395,6 +43434,12 @@ function vectorDimFromBase64(encoded) {
|
|
|
43395
43434
|
return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
|
|
43396
43435
|
}
|
|
43397
43436
|
//#endregion
|
|
43437
|
+
Object.defineProperty(exports, "AUDIO_MACRO_LABELS", {
|
|
43438
|
+
enumerable: true,
|
|
43439
|
+
get: function() {
|
|
43440
|
+
return AUDIO_MACRO_LABELS;
|
|
43441
|
+
}
|
|
43442
|
+
});
|
|
43398
43443
|
Object.defineProperty(exports, "BaseAddon", {
|
|
43399
43444
|
enumerable: true,
|
|
43400
43445
|
get: function() {
|
|
@@ -43419,6 +43464,12 @@ Object.defineProperty(exports, "COCO_TO_MACRO", {
|
|
|
43419
43464
|
return COCO_TO_MACRO;
|
|
43420
43465
|
}
|
|
43421
43466
|
});
|
|
43467
|
+
Object.defineProperty(exports, "CamProfileSchema", {
|
|
43468
|
+
enumerable: true,
|
|
43469
|
+
get: function() {
|
|
43470
|
+
return CamProfileSchema;
|
|
43471
|
+
}
|
|
43472
|
+
});
|
|
43422
43473
|
Object.defineProperty(exports, "DEFAULT_EVENT_COLOR", {
|
|
43423
43474
|
enumerable: true,
|
|
43424
43475
|
get: function() {
|
|
@@ -13892,6 +13892,35 @@ method(_void(), array(string()).readonly(), { auth: "admin" }), method(object({
|
|
|
13892
13892
|
auth: "admin"
|
|
13893
13893
|
});
|
|
13894
13894
|
/**
|
|
13895
|
+
* Transport bounds for AI capability methods.
|
|
13896
|
+
*
|
|
13897
|
+
* Its own module because `llm.cap.ts` and `llm-runtime.cap.ts` already import
|
|
13898
|
+
* each other: declaring the constant in either one hands the other `undefined`
|
|
13899
|
+
* at module-init time, and an undefined `timeoutMs` silently falls back to the
|
|
13900
|
+
* kernel's 60 s default — the exact failure this constant exists to prevent,
|
|
13901
|
+
* reintroduced invisibly. Found by the guard, not by review.
|
|
13902
|
+
*/
|
|
13903
|
+
/**
|
|
13904
|
+
* Ceiling for a cap method that runs inference or loads a model.
|
|
13905
|
+
*
|
|
13906
|
+
* Deliberately far above anything the AI layer itself may wait for (a summary
|
|
13907
|
+
* asks 180 s by default, a rule may ask 600 s), because the TRANSPORT must
|
|
13908
|
+
* never be the layer that gives up first: when it does, the failure is
|
|
13909
|
+
* reported as a transport error for a model that was still thinking, naming
|
|
13910
|
+
* the wrong layer and hiding the real one.
|
|
13911
|
+
*
|
|
13912
|
+
* Measured 2026-09-03: a 6-image vision request needs 44.8 s warm and ~20 s
|
|
13913
|
+
* more on a cold model load. With no declaration every digest failed at
|
|
13914
|
+
* exactly 60 s and shipped without its text.
|
|
13915
|
+
*
|
|
13916
|
+
* A ceiling, not a budget. The AI layer's own timeouts (the rule's `timeoutMs`,
|
|
13917
|
+
* the profile's `timeoutMs` / `firstTokenTimeoutMs`) remain the things that
|
|
13918
|
+
* decide when to stop, because only they can say WHY.
|
|
13919
|
+
*/
|
|
13920
|
+
var AI_CAP_TRANSPORT_TIMEOUT_MS = 6e5;
|
|
13921
|
+
/** A multi-gigabyte download. An hour is not generous, it is honest. */
|
|
13922
|
+
var AI_MODEL_INSTALL_TIMEOUT_MS = 60 * 6e4;
|
|
13923
|
+
/**
|
|
13895
13924
|
* Shared LLM generate contracts — imported by BOTH `llm.cap.ts` (consumer
|
|
13896
13925
|
* surface) and `llm-runtime.cap.ts` (node-side managed executor) so the two
|
|
13897
13926
|
* caps stay wire-compatible without a circular cap→cap import.
|
|
@@ -14163,16 +14192,19 @@ method(LlmGenerateBaseInputSchema.extend({
|
|
|
14163
14192
|
timeoutMs: number().int().positive().optional()
|
|
14164
14193
|
}), LlmGenerateResultSchema, {
|
|
14165
14194
|
kind: "mutation",
|
|
14166
|
-
auth: "admin"
|
|
14195
|
+
auth: "admin",
|
|
14196
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14167
14197
|
}), method(object({ runtime: ManagedRuntimeConfigSchema }), LlmRuntimeStatusSchema, {
|
|
14168
14198
|
kind: "mutation",
|
|
14169
|
-
auth: "admin"
|
|
14199
|
+
auth: "admin",
|
|
14200
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14170
14201
|
}), method(object({}), _void(), {
|
|
14171
14202
|
kind: "mutation",
|
|
14172
14203
|
auth: "admin"
|
|
14173
14204
|
}), method(object({}), LlmRuntimeStatusSchema, { auth: "admin" }), method(object({ model: ManagedModelRefSchema }), _void(), {
|
|
14174
14205
|
kind: "mutation",
|
|
14175
|
-
auth: "admin"
|
|
14206
|
+
auth: "admin",
|
|
14207
|
+
timeoutMs: AI_MODEL_INSTALL_TIMEOUT_MS
|
|
14176
14208
|
}), method(object({ file: string() }), _void(), {
|
|
14177
14209
|
kind: "mutation",
|
|
14178
14210
|
auth: "admin"
|
|
@@ -14340,7 +14372,13 @@ var ProfileRefInputSchema = object({
|
|
|
14340
14372
|
addonId: string(),
|
|
14341
14373
|
profileId: string()
|
|
14342
14374
|
});
|
|
14343
|
-
method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
|
|
14375
|
+
method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
|
|
14376
|
+
kind: "mutation",
|
|
14377
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14378
|
+
}), method(GenerateVisionInputSchema, LlmGenerateResultSchema, {
|
|
14379
|
+
kind: "mutation",
|
|
14380
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14381
|
+
}), method(object({
|
|
14344
14382
|
addonId: string().optional(),
|
|
14345
14383
|
requestId: string()
|
|
14346
14384
|
}), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
|
|
@@ -14351,7 +14389,8 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, { kind: "mutation" }
|
|
|
14351
14389
|
auth: "admin"
|
|
14352
14390
|
}), method(ProfileRefInputSchema, LlmGenerateResultSchema, {
|
|
14353
14391
|
kind: "mutation",
|
|
14354
|
-
auth: "admin"
|
|
14392
|
+
auth: "admin",
|
|
14393
|
+
timeoutMs: AI_CAP_TRANSPORT_TIMEOUT_MS
|
|
14355
14394
|
}), method(ProfileRefInputSchema, array(string())), method(object({}), array(LlmDefaultSchema)), method(object({
|
|
14356
14395
|
selector: LlmDefaultSelectorSchema,
|
|
14357
14396
|
profileId: string().nullable()
|
|
@@ -43364,4 +43403,4 @@ function vectorDimFromBase64(encoded) {
|
|
|
43364
43403
|
return Math.floor(Buffer.from(encoded, "base64").byteLength / 4);
|
|
43365
43404
|
}
|
|
43366
43405
|
//#endregion
|
|
43367
|
-
export {
|
|
43406
|
+
export { audioModeOf as $, NcSnoozeSchema as A, BaseAddon as At, SCENE_DEFAULT_UNCOVERED_POLICY as B, boolean as Bt, NcConditionDescriptorSchema as C, sceneMonitorCapability as Ct, NcRuleTargetSchema as D, videoclipsCapability as Dt, NcRuleSchema as E, vectorDimFromBase64 as Et, PoolMemoryWatchdog as F, isDeviceScopedCap as Ft, TimelapseRulePatchSchema as G, partialRecord as Gt, SceneMonitorSchema as H, literal as Ht, RECORDING_EXPORT_MAX_READ_BYTES as I, nodePin as It, VISIT_MERGE_GAP_MS as J, unknown as Jt, TimelapseRuleSchema as K, record as Kt, RetrainStatusSchema as L, sleep as Lt, NcSystemEventKindSchema as M, DeviceType as Mt, NcTaxonomySchema as N, createEvent as Nt, NcScheduleSchema as O, zoneAnalyticsCapability as Ot, OpsLogEntrySchema as P, hydrateSchema as Pt, audioMetricsCapability as Q, SCENE_CONFIRM_DEFAULT_TIMEOUT_MS as R, _enum as Rt, NC_TAXONOMY as S, resolvePoolMemoryPolicy as St, NcRulePatchSchema as T, systemEventFilterApplies as Tt, TIMELAPSE_DENSE_FLOOR_SEC as U, number as Ut, SCENE_DIVERGED as V, discriminatedUnion as Vt, TimelapseRuleInputSchema as W, object as Wt, alarmPanelCapability as X, addonWidgetsSourceCapability as Y, EventCategory as Yt, assertTimelapseCadences as Z, MediaFileKindEnum as _, pickClusterStepModels as _t, DEFAULT_EVENT_COLOR as a, embeddingEncoderCapability as at, NC_DEFAULT_SNOOZE_MINUTES as b, readDeviceStateFrom as bt, DETECTION_MACRO_CLASSES as c, faceGalleryCapability as ct, EVENT_KIND_BY_CAP as d, isDetectionMacroClass as dt, buildEventKindDescriptor as et, EVENT_PAD_MS as f, isScheduleActive as ft, MACRO_LABELS as g, parseProcStatus as gt, LabelAttributionSchema as h, notificationRulesCapability as ht, COCO_TO_MACRO as i, deriveRecordingMode as it, NcSnoozeSuppressedSchema as j, CamProfileSchema as jt, NcSnoozeInputSchema as k, errMsg as kt, DETECTION_PIPELINE_CAP_NAME as l, failureContributionCapability as lt, FailureCounters as m, kebabToCamel as mt, BaseDevice as n, customAction as nt, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as o, encodeVectorBase64 as ot, FULL_IMAGE_BBOX as p, isSourceCap as pt, TrackSourceSchema as q, string as qt, CLUSTER_MODEL_SCOPED_STEPS as r, defineCustomActions as rt, DEFAULT_TIMELAPSE_PREVIEW_TEXT as s, evaluateSensorEdge as st, AUDIO_MACRO_LABELS as t, cosineSimilarity as tt, DeclaredDevices as u, hfModelUrl as ut, NC_ALARM_SYSTEM_EVENT_KINDS as v, pipelineAnalyticsCapability as vt, NcRuleInputSchema as w, subKindsOf as wt, NC_SNOOZE_MAX_MINUTES as x, readTimelapseGeneratedAt as xt, NC_CONDITION_CATALOG as y, plateGalleryCapability as yt, SCENE_DEFAULT_ANCHOR_THRESHOLD as z, array as zt };
|
|
@@ -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-CftJzAHc.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 {
|
|
1
|
+
import { At as BaseAddon, F as PoolMemoryWatchdog, St as resolvePoolMemoryPolicy, at as embeddingEncoderCapability, gt as parseProcStatus, ut as hfModelUrl } from "../dist-h9lRw5M-.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.58",
|
|
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.138",
|
|
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.104",
|
|
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.138",
|
|
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.58",
|
|
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.104",
|
|
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-CftJzAHc.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");
|
|
@@ -3139,7 +3139,8 @@ var LlmVisionJudge = class {
|
|
|
3139
3139
|
prompt: call.prompt,
|
|
3140
3140
|
images,
|
|
3141
3141
|
jsonSchema: call.jsonSchema,
|
|
3142
|
-
temperature: 0
|
|
3142
|
+
temperature: 0,
|
|
3143
|
+
timeoutMs: call.timeoutMs
|
|
3143
3144
|
}).catch((err) => ({
|
|
3144
3145
|
ok: false,
|
|
3145
3146
|
message: String(err)
|
|
@@ -19705,8 +19706,37 @@ var TimelapseRenderError = class extends Error {
|
|
|
19705
19706
|
this.reason = reason;
|
|
19706
19707
|
}
|
|
19707
19708
|
};
|
|
19708
|
-
/**
|
|
19709
|
-
*
|
|
19709
|
+
/**
|
|
19710
|
+
* Profiles from cheapest to dearest, so the source is chosen by COST.
|
|
19711
|
+
*
|
|
19712
|
+
* `CamProfileSchema` is declared dearest-first; this is that order reversed.
|
|
19713
|
+
* A name the enum does not know sorts after all of them, alphabetically, so the
|
|
19714
|
+
* choice stays deterministic for a fleet with a bespoke profile.
|
|
19715
|
+
*/
|
|
19716
|
+
var PROFILE_BY_COST = [...require_dist.CamProfileSchema.options].reverse();
|
|
19717
|
+
function profileCost(profile) {
|
|
19718
|
+
const i = PROFILE_BY_COST.indexOf(profile);
|
|
19719
|
+
return i === -1 ? PROFILE_BY_COST.length : i;
|
|
19720
|
+
}
|
|
19721
|
+
/**
|
|
19722
|
+
* The CHEAPEST profile that has footage over the window.
|
|
19723
|
+
*
|
|
19724
|
+
* It used to be the profile with the MOST coverage, ties broken by name — and
|
|
19725
|
+
* that is a rule which picks `high` almost every time, because the high band is
|
|
19726
|
+
* the one that records continuously. On 2026-09-03 it did: a test render of a
|
|
19727
|
+
* ten-hour window chose `profile=high` at 97% coverage, which on this hub means
|
|
19728
|
+
* 4K read from a USB spinning disk. Decoding it made the machine stop answering
|
|
19729
|
+
* SSH and its own web UI for half an hour, and it had to be power-cycled.
|
|
19730
|
+
*
|
|
19731
|
+
* A timelapse is watched accelerated. Resolution buys nothing at 12 fps over a
|
|
19732
|
+
* night, and the cost difference between the bands is one to two orders of
|
|
19733
|
+
* magnitude in both bytes read and frames decoded. So there is no trade to
|
|
19734
|
+
* weigh: take the cheapest band that has anything, every time.
|
|
19735
|
+
*
|
|
19736
|
+
* Coverage no longer decides WHICH — only whether a band qualifies at all. A
|
|
19737
|
+
* band with less footage is still the right source; the gaps show as a shorter
|
|
19738
|
+
* video, not as a reason to read twenty times the bytes.
|
|
19739
|
+
*/
|
|
19710
19740
|
function pickProfile(ranges, window) {
|
|
19711
19741
|
const byProfile = /* @__PURE__ */ new Map();
|
|
19712
19742
|
for (const r of ranges) {
|
|
@@ -19714,15 +19744,15 @@ function pickProfile(ranges, window) {
|
|
|
19714
19744
|
if (list === void 0) byProfile.set(r.profile, [r]);
|
|
19715
19745
|
else list.push(r);
|
|
19716
19746
|
}
|
|
19717
|
-
|
|
19718
|
-
for (const [profile, rows] of
|
|
19747
|
+
const ordered = [...byProfile].sort((a, b) => profileCost(a[0]) - profileCost(b[0]) || a[0].localeCompare(b[0]));
|
|
19748
|
+
for (const [profile, rows] of ordered) {
|
|
19719
19749
|
const coverage = footageCoverage(rows, window.startMs, window.endMs);
|
|
19720
|
-
if (
|
|
19750
|
+
if (coverage.coveredMs > 0) return {
|
|
19721
19751
|
profile,
|
|
19722
19752
|
coverage
|
|
19723
19753
|
};
|
|
19724
19754
|
}
|
|
19725
|
-
return
|
|
19755
|
+
return null;
|
|
19726
19756
|
}
|
|
19727
19757
|
/**
|
|
19728
19758
|
* Is `record` the render this request is about to order?
|
|
@@ -43985,6 +44015,36 @@ var AnalyticsQueryFacade = class {
|
|
|
43985
44015
|
//#region src/pipeline-analytics/audio-detection-settings.ts
|
|
43986
44016
|
var AudioDetectionSettingsSchema = require_dist.object({
|
|
43987
44017
|
classificationMinScore: require_dist.number().min(0).max(1).default(.6),
|
|
44018
|
+
/**
|
|
44019
|
+
* Macro classes allowed to produce an audio EVENT on this camera. Empty =
|
|
44020
|
+
* no policy, every class passes — so an upgrade changes nothing until an
|
|
44021
|
+
* operator states a policy.
|
|
44022
|
+
*
|
|
44023
|
+
* This belongs to the event generator, NOT to the classifier: the analyzer
|
|
44024
|
+
* keeps classifying everything it hears, `audioLabels` on the track keeps
|
|
44025
|
+
* carrying the full picture, and only the decision to MATERIALIZE an event
|
|
44026
|
+
* is narrowed. Raising a threshold inside the classifier would instead make
|
|
44027
|
+
* the sound invisible to every consumer at once.
|
|
44028
|
+
*/
|
|
44029
|
+
audioAllowedClasses: require_dist.array(require_dist.string()).default([]),
|
|
44030
|
+
/**
|
|
44031
|
+
* Corroboration window on the EVENT, per class: this many occurrences of
|
|
44032
|
+
* the same sound must land inside `audioConfirmWindowSec` before it becomes
|
|
44033
|
+
* an event. `1` = no window.
|
|
44034
|
+
*
|
|
44035
|
+
* Default 2-in-10s, set by the operator on 2026-09-03 after measuring 26
|
|
44036
|
+
* audio tracks in 24 h: the single-frame classification is the dominant
|
|
44037
|
+
* false positive, and a sound that is real is heard again within ten
|
|
44038
|
+
* seconds. A camera that wants every occurrence sets `1`.
|
|
44039
|
+
*
|
|
44040
|
+
* The Notification Center already has this exact brake for audio RULES
|
|
44041
|
+
* (`confirmHits` / `confirmWindowSec`). This is the same idea one level
|
|
44042
|
+
* earlier, so a camera can stop MAKING the single-frame events instead of
|
|
44043
|
+
* only declining to notify about them.
|
|
44044
|
+
*/
|
|
44045
|
+
audioConfirmHits: require_dist.number().int().min(1).default(2),
|
|
44046
|
+
/** Width of the corroboration window. Ignored when `audioConfirmHits` is 1. */
|
|
44047
|
+
audioConfirmWindowSec: require_dist.number().positive().default(10),
|
|
43988
44048
|
levelDeviationDb: require_dist.number().positive().default(10),
|
|
43989
44049
|
levelWindowSec: require_dist.number().positive().default(10),
|
|
43990
44050
|
levelMaxWaitSec: require_dist.number().positive().default(20),
|
|
@@ -44025,8 +44085,17 @@ var AudioDetectionSettingsSchema = require_dist.object({
|
|
|
44025
44085
|
audioEpisodeOpenHoldSec: require_dist.number().nonnegative().default(1.5),
|
|
44026
44086
|
/** Class must be absent this long before the episode closes. */
|
|
44027
44087
|
audioEpisodeCloseGapSec: require_dist.number().positive().default(12),
|
|
44028
|
-
/** After close, the same class cannot reopen until this elapses.
|
|
44029
|
-
|
|
44088
|
+
/** After close, the same class cannot reopen until this elapses.
|
|
44089
|
+
*
|
|
44090
|
+
* This is the minimum spacing between two SYNTHETIC EVENTS (the episode's
|
|
44091
|
+
* event + timeline track) of the same sound on one camera — NOT a
|
|
44092
|
+
* notification floor. Notification spacing is the rule's own
|
|
44093
|
+
* `throttle.cooldownSec`, which already defaults to 60 s and is keyed
|
|
44094
|
+
* per-device; the two are independent brakes on different objects.
|
|
44095
|
+
*
|
|
44096
|
+
* Raised 30 → 60 s on 2026-09-03 by the operator, alongside the 2-in-10s
|
|
44097
|
+
* corroboration window above. */
|
|
44098
|
+
audioEpisodeCooldownSec: require_dist.number().nonnegative().default(60)
|
|
44030
44099
|
});
|
|
44031
44100
|
var AUDIO_DETECTION_DEFAULTS = AudioDetectionSettingsSchema.parse({});
|
|
44032
44101
|
/**
|
|
@@ -44203,14 +44272,45 @@ function observeAudioEpisode(state, sample, knobs) {
|
|
|
44203
44272
|
};
|
|
44204
44273
|
}
|
|
44205
44274
|
//#endregion
|
|
44275
|
+
//#region src/pipeline-analytics/audio-confirm-window.ts
|
|
44276
|
+
function emptyAudioConfirmState() {
|
|
44277
|
+
return { hitsByClass: /* @__PURE__ */ new Map() };
|
|
44278
|
+
}
|
|
44279
|
+
function observeAudioConfirm(state, input, cfg) {
|
|
44280
|
+
if (cfg.confirmHits <= 1) return {
|
|
44281
|
+
confirmed: true,
|
|
44282
|
+
nextState: state
|
|
44283
|
+
};
|
|
44284
|
+
const cutoff = input.timestampMs - cfg.confirmWindowSec * 1e3;
|
|
44285
|
+
const kept = [...(state.hitsByClass.get(input.className) ?? []).filter((at) => at > cutoff), input.timestampMs];
|
|
44286
|
+
const next = new Map(state.hitsByClass);
|
|
44287
|
+
if (kept.length < cfg.confirmHits) {
|
|
44288
|
+
next.set(input.className, kept);
|
|
44289
|
+
return {
|
|
44290
|
+
confirmed: false,
|
|
44291
|
+
nextState: { hitsByClass: next }
|
|
44292
|
+
};
|
|
44293
|
+
}
|
|
44294
|
+
next.delete(input.className);
|
|
44295
|
+
return {
|
|
44296
|
+
confirmed: true,
|
|
44297
|
+
nextState: { hitsByClass: next }
|
|
44298
|
+
};
|
|
44299
|
+
}
|
|
44300
|
+
//#endregion
|
|
44206
44301
|
//#region src/pipeline-analytics/audio-event-router.ts
|
|
44207
44302
|
/**
|
|
44208
44303
|
* Decide whether an audio frame is a (meaningful) classification event or a
|
|
44209
44304
|
* level event. `silence` is never a classification — it falls to the level
|
|
44210
44305
|
* path so only a genuine loudness peak/heartbeat can surface it.
|
|
44306
|
+
*
|
|
44307
|
+
* A class the operator did not allow takes the SAME route as a class that
|
|
44308
|
+
* failed the score: the level path. Both mean "not a classification event on
|
|
44309
|
+
* this camera", and giving the allowlist its own harder outcome (a silent
|
|
44310
|
+
* drop) would be a second arithmetic for one question.
|
|
44211
44311
|
*/
|
|
44212
44312
|
function classifyAudioFrame(top, cfg) {
|
|
44213
|
-
if (top && top.className !== "silence" && top.score >= cfg.classificationMinScore) return {
|
|
44313
|
+
if ((cfg.allowedClasses.length === 0 || cfg.allowedClasses.includes(top?.className ?? "")) && top && top.className !== "silence" && top.score >= cfg.classificationMinScore) return {
|
|
44214
44314
|
kind: "classification",
|
|
44215
44315
|
className: top.className,
|
|
44216
44316
|
score: top.score
|
|
@@ -63076,6 +63176,36 @@ function buildGlobalSettingsSchema(cap) {
|
|
|
63076
63176
|
step: .05,
|
|
63077
63177
|
default: AUDIO_DETECTION_DEFAULTS.classificationMinScore
|
|
63078
63178
|
},
|
|
63179
|
+
{
|
|
63180
|
+
type: "multiselect",
|
|
63181
|
+
key: "audioAllowedClasses",
|
|
63182
|
+
label: "Event classes",
|
|
63183
|
+
description: "Which sounds may become an event on this camera. Leave empty to allow every class. The classifier is untouched — it keeps hearing and labelling everything; this only narrows what is worth an event and a notification here.",
|
|
63184
|
+
options: require_dist.AUDIO_MACRO_LABELS.filter((l) => l.id !== "silence").map((l) => ({
|
|
63185
|
+
value: l.id,
|
|
63186
|
+
label: l.name
|
|
63187
|
+
})),
|
|
63188
|
+
default: AUDIO_DETECTION_DEFAULTS.audioAllowedClasses
|
|
63189
|
+
},
|
|
63190
|
+
{
|
|
63191
|
+
type: "number",
|
|
63192
|
+
key: "audioConfirmHits",
|
|
63193
|
+
label: "Occurrences to confirm",
|
|
63194
|
+
description: "How many times the same sound must be heard inside the window below before it becomes an event. 1 disables the window — every occurrence is an event.",
|
|
63195
|
+
min: 1,
|
|
63196
|
+
step: 1,
|
|
63197
|
+
default: AUDIO_DETECTION_DEFAULTS.audioConfirmHits
|
|
63198
|
+
},
|
|
63199
|
+
{
|
|
63200
|
+
type: "number",
|
|
63201
|
+
key: "audioConfirmWindowSec",
|
|
63202
|
+
label: "Confirmation window",
|
|
63203
|
+
description: "Width of the corroboration window. Occurrences are counted per class, so two different sounds never confirm each other. Ignored when occurrences is 1.",
|
|
63204
|
+
min: 1,
|
|
63205
|
+
step: 1,
|
|
63206
|
+
default: AUDIO_DETECTION_DEFAULTS.audioConfirmWindowSec,
|
|
63207
|
+
unit: "s"
|
|
63208
|
+
},
|
|
63079
63209
|
{
|
|
63080
63210
|
type: "number",
|
|
63081
63211
|
key: "levelDeviationDb",
|
|
@@ -65922,6 +66052,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
65922
66052
|
lastAudioInsertByDevice = /* @__PURE__ */ new Map();
|
|
65923
66053
|
lastMotionInsertByDevice = /* @__PURE__ */ new Map();
|
|
65924
66054
|
levelStateByDevice = /* @__PURE__ */ new Map();
|
|
66055
|
+
/** Per-device corroboration window for classified audio (see
|
|
66056
|
+
* `audio-confirm-window.ts`). Torn down wherever `levelStateByDevice` is —
|
|
66057
|
+
* a stale window on a removed device would confirm on its first frame back. */
|
|
66058
|
+
audioConfirmByDevice = /* @__PURE__ */ new Map();
|
|
65925
66059
|
/**
|
|
65926
66060
|
* Last ms a spatial track was alive on a device, any plane. Written on the
|
|
65927
66061
|
* frame path (one `Map.set` per processed frame), read O(1) by the audio-
|
|
@@ -67436,7 +67570,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
67436
67570
|
mimeType: i.mimeType
|
|
67437
67571
|
})),
|
|
67438
67572
|
jsonSchema: request.jsonSchema,
|
|
67439
|
-
temperature: request.temperature
|
|
67573
|
+
temperature: request.temperature,
|
|
67574
|
+
...request.timeoutMs !== void 0 ? { timeoutMs: request.timeoutMs } : {}
|
|
67440
67575
|
});
|
|
67441
67576
|
return result.ok ? {
|
|
67442
67577
|
ok: true,
|
|
@@ -68342,6 +68477,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
68342
68477
|
this.overlaySynthesisWarnAt.delete(data.deviceId);
|
|
68343
68478
|
this.forgetDeviceProcessors(data.deviceId);
|
|
68344
68479
|
this.levelStateByDevice.delete(data.deviceId);
|
|
68480
|
+
this.audioConfirmByDevice.delete(data.deviceId);
|
|
68345
68481
|
this.settingsCaches.clearDevice(data.deviceId);
|
|
68346
68482
|
this.rawDeviceBlobMirror.invalidate(data.deviceId);
|
|
68347
68483
|
}
|
|
@@ -68360,6 +68496,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
68360
68496
|
this.overlaySynthesisWarnAt.delete(deviceId);
|
|
68361
68497
|
this.forgetDeviceProcessors(deviceId);
|
|
68362
68498
|
this.levelStateByDevice.delete(deviceId);
|
|
68499
|
+
this.audioConfirmByDevice.delete(deviceId);
|
|
68363
68500
|
this.lastTrackActivityMs.delete(deviceId);
|
|
68364
68501
|
this.motionEventSnapshots?.forgetDevice(deviceId);
|
|
68365
68502
|
this.audioEpisodeByDevice.delete(deviceId);
|
|
@@ -68576,7 +68713,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
68576
68713
|
mimeType: image.mimeType
|
|
68577
68714
|
})),
|
|
68578
68715
|
jsonSchema: i.jsonSchema,
|
|
68579
|
-
temperature: i.temperature
|
|
68716
|
+
temperature: i.temperature,
|
|
68717
|
+
...i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : {}
|
|
68580
68718
|
}),
|
|
68581
68719
|
cancelVision: async (requestId) => {
|
|
68582
68720
|
await api.llm.cancel.mutate({ requestId });
|
|
@@ -68871,6 +69009,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
68871
69009
|
this.nodeLiveness.clear();
|
|
68872
69010
|
this.gatedNodeByDevice.clear();
|
|
68873
69011
|
this.levelStateByDevice.clear();
|
|
69012
|
+
this.audioConfirmByDevice.clear();
|
|
68874
69013
|
this.settingsCaches.clearAll();
|
|
68875
69014
|
this.rawDeviceBlobMirror.invalidateAll();
|
|
68876
69015
|
this.rawGlobalStoreMirror.invalidate();
|
|
@@ -70885,12 +71024,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70885
71024
|
this.notificationCenter?.observeAudio(deviceId, {
|
|
70886
71025
|
timestampMs: timestamp,
|
|
70887
71026
|
...level?.dbfs !== void 0 ? { dbfs: level.dbfs } : {},
|
|
70888
|
-
labels: (frame.detections ?? []).filter((d) => d.score >= settings.classificationMinScore).map((d) => d.macroClass)
|
|
71027
|
+
labels: (frame.detections ?? []).filter((d) => d.score >= settings.classificationMinScore).map((d) => d.macroClass).filter((macroClass) => settings.audioAllowedClasses.length === 0 || settings.audioAllowedClasses.includes(macroClass))
|
|
70889
71028
|
});
|
|
70890
71029
|
const route = classifyAudioFrame(topClassification ? {
|
|
70891
71030
|
className: topClassification.macroClass,
|
|
70892
71031
|
score: topClassification.score
|
|
70893
|
-
} : void 0, {
|
|
71032
|
+
} : void 0, {
|
|
71033
|
+
classificationMinScore: settings.classificationMinScore,
|
|
71034
|
+
allowedClasses: settings.audioAllowedClasses
|
|
71035
|
+
});
|
|
70894
71036
|
if (route.kind === "classification") {
|
|
70895
71037
|
if (!topClassification) {
|
|
70896
71038
|
this.ctx.logger.warn("classifyAudioFrame returned classification but topClassification is null", {
|
|
@@ -70899,6 +71041,26 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
70899
71041
|
});
|
|
70900
71042
|
return;
|
|
70901
71043
|
}
|
|
71044
|
+
const confirm = observeAudioConfirm(this.audioConfirmByDevice.get(deviceId) ?? emptyAudioConfirmState(), {
|
|
71045
|
+
className: route.className,
|
|
71046
|
+
timestampMs: timestamp
|
|
71047
|
+
}, {
|
|
71048
|
+
confirmHits: settings.audioConfirmHits,
|
|
71049
|
+
confirmWindowSec: settings.audioConfirmWindowSec
|
|
71050
|
+
});
|
|
71051
|
+
this.audioConfirmByDevice.set(deviceId, confirm.nextState);
|
|
71052
|
+
if (!confirm.confirmed) {
|
|
71053
|
+
this.ctx.logger.debug("audio event held for corroboration", {
|
|
71054
|
+
tags: { deviceId },
|
|
71055
|
+
meta: {
|
|
71056
|
+
deviceId,
|
|
71057
|
+
className: route.className,
|
|
71058
|
+
confirmHits: settings.audioConfirmHits,
|
|
71059
|
+
confirmWindowSec: settings.audioConfirmWindowSec
|
|
71060
|
+
}
|
|
71061
|
+
});
|
|
71062
|
+
return;
|
|
71063
|
+
}
|
|
70902
71064
|
const classified = {
|
|
70903
71065
|
deviceId,
|
|
70904
71066
|
timestamp,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as audioModeOf, A as NcSnoozeSchema, At as BaseAddon, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as boolean, C as NcConditionDescriptorSchema, Ct as sceneMonitorCapability, D as NcRuleTargetSchema, Dt as videoclipsCapability, E as NcRuleSchema, Et as vectorDimFromBase64, Ft as isDeviceScopedCap, G as TimelapseRulePatchSchema, Gt as partialRecord, H as SceneMonitorSchema, Ht as literal, I as RECORDING_EXPORT_MAX_READ_BYTES, It as nodePin, J as VISIT_MERGE_GAP_MS, Jt as unknown, K as TimelapseRuleSchema, Kt as record, L as RetrainStatusSchema, Lt as sleep$1, M as NcSystemEventKindSchema, Mt as DeviceType, N as NcTaxonomySchema, Nt as createEvent, O as NcScheduleSchema, Ot as zoneAnalyticsCapability, P as OpsLogEntrySchema, Pt as hydrateSchema, Q as audioMetricsCapability, Rt as _enum, S as NC_TAXONOMY, T as NcRulePatchSchema, Tt as systemEventFilterApplies, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as number, V as SCENE_DIVERGED, Vt as discriminatedUnion, W as TimelapseRuleInputSchema, Wt as object, X as alarmPanelCapability, Y as addonWidgetsSourceCapability, Yt as EventCategory, Z as assertTimelapseCadences, _ as MediaFileKindEnum, _t as pickClusterStepModels, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as readDeviceStateFrom, c as DETECTION_MACRO_CLASSES, ct as faceGalleryCapability, d as EVENT_KIND_BY_CAP, dt as isDetectionMacroClass, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isScheduleActive, g as MACRO_LABELS, h as LabelAttributionSchema, ht as notificationRulesCapability, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as CamProfileSchema, k as NcSnoozeInputSchema, kt as errMsg, lt as failureContributionCapability, m as FailureCounters, mt as kebabToCamel, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isSourceCap, q as TrackSourceSchema, qt as string, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, v as NC_ALARM_SYSTEM_EVENT_KINDS, vt as pipelineAnalyticsCapability, w as NcRuleInputSchema, wt as subKindsOf, xt as readTimelapseGeneratedAt, y as NC_CONDITION_CATALOG, yt as plateGalleryCapability, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as array } from "../dist-h9lRw5M-.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";
|
|
@@ -3132,7 +3132,8 @@ var LlmVisionJudge = class {
|
|
|
3132
3132
|
prompt: call.prompt,
|
|
3133
3133
|
images,
|
|
3134
3134
|
jsonSchema: call.jsonSchema,
|
|
3135
|
-
temperature: 0
|
|
3135
|
+
temperature: 0,
|
|
3136
|
+
timeoutMs: call.timeoutMs
|
|
3136
3137
|
}).catch((err) => ({
|
|
3137
3138
|
ok: false,
|
|
3138
3139
|
message: String(err)
|
|
@@ -19683,8 +19684,37 @@ var TimelapseRenderError = class extends Error {
|
|
|
19683
19684
|
this.reason = reason;
|
|
19684
19685
|
}
|
|
19685
19686
|
};
|
|
19686
|
-
/**
|
|
19687
|
-
*
|
|
19687
|
+
/**
|
|
19688
|
+
* Profiles from cheapest to dearest, so the source is chosen by COST.
|
|
19689
|
+
*
|
|
19690
|
+
* `CamProfileSchema` is declared dearest-first; this is that order reversed.
|
|
19691
|
+
* A name the enum does not know sorts after all of them, alphabetically, so the
|
|
19692
|
+
* choice stays deterministic for a fleet with a bespoke profile.
|
|
19693
|
+
*/
|
|
19694
|
+
var PROFILE_BY_COST = [...CamProfileSchema.options].reverse();
|
|
19695
|
+
function profileCost(profile) {
|
|
19696
|
+
const i = PROFILE_BY_COST.indexOf(profile);
|
|
19697
|
+
return i === -1 ? PROFILE_BY_COST.length : i;
|
|
19698
|
+
}
|
|
19699
|
+
/**
|
|
19700
|
+
* The CHEAPEST profile that has footage over the window.
|
|
19701
|
+
*
|
|
19702
|
+
* It used to be the profile with the MOST coverage, ties broken by name — and
|
|
19703
|
+
* that is a rule which picks `high` almost every time, because the high band is
|
|
19704
|
+
* the one that records continuously. On 2026-09-03 it did: a test render of a
|
|
19705
|
+
* ten-hour window chose `profile=high` at 97% coverage, which on this hub means
|
|
19706
|
+
* 4K read from a USB spinning disk. Decoding it made the machine stop answering
|
|
19707
|
+
* SSH and its own web UI for half an hour, and it had to be power-cycled.
|
|
19708
|
+
*
|
|
19709
|
+
* A timelapse is watched accelerated. Resolution buys nothing at 12 fps over a
|
|
19710
|
+
* night, and the cost difference between the bands is one to two orders of
|
|
19711
|
+
* magnitude in both bytes read and frames decoded. So there is no trade to
|
|
19712
|
+
* weigh: take the cheapest band that has anything, every time.
|
|
19713
|
+
*
|
|
19714
|
+
* Coverage no longer decides WHICH — only whether a band qualifies at all. A
|
|
19715
|
+
* band with less footage is still the right source; the gaps show as a shorter
|
|
19716
|
+
* video, not as a reason to read twenty times the bytes.
|
|
19717
|
+
*/
|
|
19688
19718
|
function pickProfile(ranges, window) {
|
|
19689
19719
|
const byProfile = /* @__PURE__ */ new Map();
|
|
19690
19720
|
for (const r of ranges) {
|
|
@@ -19692,15 +19722,15 @@ function pickProfile(ranges, window) {
|
|
|
19692
19722
|
if (list === void 0) byProfile.set(r.profile, [r]);
|
|
19693
19723
|
else list.push(r);
|
|
19694
19724
|
}
|
|
19695
|
-
|
|
19696
|
-
for (const [profile, rows] of
|
|
19725
|
+
const ordered = [...byProfile].sort((a, b) => profileCost(a[0]) - profileCost(b[0]) || a[0].localeCompare(b[0]));
|
|
19726
|
+
for (const [profile, rows] of ordered) {
|
|
19697
19727
|
const coverage = footageCoverage(rows, window.startMs, window.endMs);
|
|
19698
|
-
if (
|
|
19728
|
+
if (coverage.coveredMs > 0) return {
|
|
19699
19729
|
profile,
|
|
19700
19730
|
coverage
|
|
19701
19731
|
};
|
|
19702
19732
|
}
|
|
19703
|
-
return
|
|
19733
|
+
return null;
|
|
19704
19734
|
}
|
|
19705
19735
|
/**
|
|
19706
19736
|
* Is `record` the render this request is about to order?
|
|
@@ -43911,6 +43941,36 @@ var AnalyticsQueryFacade = class {
|
|
|
43911
43941
|
//#region src/pipeline-analytics/audio-detection-settings.ts
|
|
43912
43942
|
var AudioDetectionSettingsSchema = object({
|
|
43913
43943
|
classificationMinScore: number().min(0).max(1).default(.6),
|
|
43944
|
+
/**
|
|
43945
|
+
* Macro classes allowed to produce an audio EVENT on this camera. Empty =
|
|
43946
|
+
* no policy, every class passes — so an upgrade changes nothing until an
|
|
43947
|
+
* operator states a policy.
|
|
43948
|
+
*
|
|
43949
|
+
* This belongs to the event generator, NOT to the classifier: the analyzer
|
|
43950
|
+
* keeps classifying everything it hears, `audioLabels` on the track keeps
|
|
43951
|
+
* carrying the full picture, and only the decision to MATERIALIZE an event
|
|
43952
|
+
* is narrowed. Raising a threshold inside the classifier would instead make
|
|
43953
|
+
* the sound invisible to every consumer at once.
|
|
43954
|
+
*/
|
|
43955
|
+
audioAllowedClasses: array(string()).default([]),
|
|
43956
|
+
/**
|
|
43957
|
+
* Corroboration window on the EVENT, per class: this many occurrences of
|
|
43958
|
+
* the same sound must land inside `audioConfirmWindowSec` before it becomes
|
|
43959
|
+
* an event. `1` = no window.
|
|
43960
|
+
*
|
|
43961
|
+
* Default 2-in-10s, set by the operator on 2026-09-03 after measuring 26
|
|
43962
|
+
* audio tracks in 24 h: the single-frame classification is the dominant
|
|
43963
|
+
* false positive, and a sound that is real is heard again within ten
|
|
43964
|
+
* seconds. A camera that wants every occurrence sets `1`.
|
|
43965
|
+
*
|
|
43966
|
+
* The Notification Center already has this exact brake for audio RULES
|
|
43967
|
+
* (`confirmHits` / `confirmWindowSec`). This is the same idea one level
|
|
43968
|
+
* earlier, so a camera can stop MAKING the single-frame events instead of
|
|
43969
|
+
* only declining to notify about them.
|
|
43970
|
+
*/
|
|
43971
|
+
audioConfirmHits: number().int().min(1).default(2),
|
|
43972
|
+
/** Width of the corroboration window. Ignored when `audioConfirmHits` is 1. */
|
|
43973
|
+
audioConfirmWindowSec: number().positive().default(10),
|
|
43914
43974
|
levelDeviationDb: number().positive().default(10),
|
|
43915
43975
|
levelWindowSec: number().positive().default(10),
|
|
43916
43976
|
levelMaxWaitSec: number().positive().default(20),
|
|
@@ -43951,8 +44011,17 @@ var AudioDetectionSettingsSchema = object({
|
|
|
43951
44011
|
audioEpisodeOpenHoldSec: number().nonnegative().default(1.5),
|
|
43952
44012
|
/** Class must be absent this long before the episode closes. */
|
|
43953
44013
|
audioEpisodeCloseGapSec: number().positive().default(12),
|
|
43954
|
-
/** After close, the same class cannot reopen until this elapses.
|
|
43955
|
-
|
|
44014
|
+
/** After close, the same class cannot reopen until this elapses.
|
|
44015
|
+
*
|
|
44016
|
+
* This is the minimum spacing between two SYNTHETIC EVENTS (the episode's
|
|
44017
|
+
* event + timeline track) of the same sound on one camera — NOT a
|
|
44018
|
+
* notification floor. Notification spacing is the rule's own
|
|
44019
|
+
* `throttle.cooldownSec`, which already defaults to 60 s and is keyed
|
|
44020
|
+
* per-device; the two are independent brakes on different objects.
|
|
44021
|
+
*
|
|
44022
|
+
* Raised 30 → 60 s on 2026-09-03 by the operator, alongside the 2-in-10s
|
|
44023
|
+
* corroboration window above. */
|
|
44024
|
+
audioEpisodeCooldownSec: number().nonnegative().default(60)
|
|
43956
44025
|
});
|
|
43957
44026
|
var AUDIO_DETECTION_DEFAULTS = AudioDetectionSettingsSchema.parse({});
|
|
43958
44027
|
/**
|
|
@@ -44129,14 +44198,45 @@ function observeAudioEpisode(state, sample, knobs) {
|
|
|
44129
44198
|
};
|
|
44130
44199
|
}
|
|
44131
44200
|
//#endregion
|
|
44201
|
+
//#region src/pipeline-analytics/audio-confirm-window.ts
|
|
44202
|
+
function emptyAudioConfirmState() {
|
|
44203
|
+
return { hitsByClass: /* @__PURE__ */ new Map() };
|
|
44204
|
+
}
|
|
44205
|
+
function observeAudioConfirm(state, input, cfg) {
|
|
44206
|
+
if (cfg.confirmHits <= 1) return {
|
|
44207
|
+
confirmed: true,
|
|
44208
|
+
nextState: state
|
|
44209
|
+
};
|
|
44210
|
+
const cutoff = input.timestampMs - cfg.confirmWindowSec * 1e3;
|
|
44211
|
+
const kept = [...(state.hitsByClass.get(input.className) ?? []).filter((at) => at > cutoff), input.timestampMs];
|
|
44212
|
+
const next = new Map(state.hitsByClass);
|
|
44213
|
+
if (kept.length < cfg.confirmHits) {
|
|
44214
|
+
next.set(input.className, kept);
|
|
44215
|
+
return {
|
|
44216
|
+
confirmed: false,
|
|
44217
|
+
nextState: { hitsByClass: next }
|
|
44218
|
+
};
|
|
44219
|
+
}
|
|
44220
|
+
next.delete(input.className);
|
|
44221
|
+
return {
|
|
44222
|
+
confirmed: true,
|
|
44223
|
+
nextState: { hitsByClass: next }
|
|
44224
|
+
};
|
|
44225
|
+
}
|
|
44226
|
+
//#endregion
|
|
44132
44227
|
//#region src/pipeline-analytics/audio-event-router.ts
|
|
44133
44228
|
/**
|
|
44134
44229
|
* Decide whether an audio frame is a (meaningful) classification event or a
|
|
44135
44230
|
* level event. `silence` is never a classification — it falls to the level
|
|
44136
44231
|
* path so only a genuine loudness peak/heartbeat can surface it.
|
|
44232
|
+
*
|
|
44233
|
+
* A class the operator did not allow takes the SAME route as a class that
|
|
44234
|
+
* failed the score: the level path. Both mean "not a classification event on
|
|
44235
|
+
* this camera", and giving the allowlist its own harder outcome (a silent
|
|
44236
|
+
* drop) would be a second arithmetic for one question.
|
|
44137
44237
|
*/
|
|
44138
44238
|
function classifyAudioFrame(top, cfg) {
|
|
44139
|
-
if (top && top.className !== "silence" && top.score >= cfg.classificationMinScore) return {
|
|
44239
|
+
if ((cfg.allowedClasses.length === 0 || cfg.allowedClasses.includes(top?.className ?? "")) && top && top.className !== "silence" && top.score >= cfg.classificationMinScore) return {
|
|
44140
44240
|
kind: "classification",
|
|
44141
44241
|
className: top.className,
|
|
44142
44242
|
score: top.score
|
|
@@ -63002,6 +63102,36 @@ function buildGlobalSettingsSchema(cap) {
|
|
|
63002
63102
|
step: .05,
|
|
63003
63103
|
default: AUDIO_DETECTION_DEFAULTS.classificationMinScore
|
|
63004
63104
|
},
|
|
63105
|
+
{
|
|
63106
|
+
type: "multiselect",
|
|
63107
|
+
key: "audioAllowedClasses",
|
|
63108
|
+
label: "Event classes",
|
|
63109
|
+
description: "Which sounds may become an event on this camera. Leave empty to allow every class. The classifier is untouched — it keeps hearing and labelling everything; this only narrows what is worth an event and a notification here.",
|
|
63110
|
+
options: AUDIO_MACRO_LABELS.filter((l) => l.id !== "silence").map((l) => ({
|
|
63111
|
+
value: l.id,
|
|
63112
|
+
label: l.name
|
|
63113
|
+
})),
|
|
63114
|
+
default: AUDIO_DETECTION_DEFAULTS.audioAllowedClasses
|
|
63115
|
+
},
|
|
63116
|
+
{
|
|
63117
|
+
type: "number",
|
|
63118
|
+
key: "audioConfirmHits",
|
|
63119
|
+
label: "Occurrences to confirm",
|
|
63120
|
+
description: "How many times the same sound must be heard inside the window below before it becomes an event. 1 disables the window — every occurrence is an event.",
|
|
63121
|
+
min: 1,
|
|
63122
|
+
step: 1,
|
|
63123
|
+
default: AUDIO_DETECTION_DEFAULTS.audioConfirmHits
|
|
63124
|
+
},
|
|
63125
|
+
{
|
|
63126
|
+
type: "number",
|
|
63127
|
+
key: "audioConfirmWindowSec",
|
|
63128
|
+
label: "Confirmation window",
|
|
63129
|
+
description: "Width of the corroboration window. Occurrences are counted per class, so two different sounds never confirm each other. Ignored when occurrences is 1.",
|
|
63130
|
+
min: 1,
|
|
63131
|
+
step: 1,
|
|
63132
|
+
default: AUDIO_DETECTION_DEFAULTS.audioConfirmWindowSec,
|
|
63133
|
+
unit: "s"
|
|
63134
|
+
},
|
|
63005
63135
|
{
|
|
63006
63136
|
type: "number",
|
|
63007
63137
|
key: "levelDeviationDb",
|
|
@@ -65848,6 +65978,10 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
65848
65978
|
lastAudioInsertByDevice = /* @__PURE__ */ new Map();
|
|
65849
65979
|
lastMotionInsertByDevice = /* @__PURE__ */ new Map();
|
|
65850
65980
|
levelStateByDevice = /* @__PURE__ */ new Map();
|
|
65981
|
+
/** Per-device corroboration window for classified audio (see
|
|
65982
|
+
* `audio-confirm-window.ts`). Torn down wherever `levelStateByDevice` is —
|
|
65983
|
+
* a stale window on a removed device would confirm on its first frame back. */
|
|
65984
|
+
audioConfirmByDevice = /* @__PURE__ */ new Map();
|
|
65851
65985
|
/**
|
|
65852
65986
|
* Last ms a spatial track was alive on a device, any plane. Written on the
|
|
65853
65987
|
* frame path (one `Map.set` per processed frame), read O(1) by the audio-
|
|
@@ -67362,7 +67496,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
67362
67496
|
mimeType: i.mimeType
|
|
67363
67497
|
})),
|
|
67364
67498
|
jsonSchema: request.jsonSchema,
|
|
67365
|
-
temperature: request.temperature
|
|
67499
|
+
temperature: request.temperature,
|
|
67500
|
+
...request.timeoutMs !== void 0 ? { timeoutMs: request.timeoutMs } : {}
|
|
67366
67501
|
});
|
|
67367
67502
|
return result.ok ? {
|
|
67368
67503
|
ok: true,
|
|
@@ -68268,6 +68403,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68268
68403
|
this.overlaySynthesisWarnAt.delete(data.deviceId);
|
|
68269
68404
|
this.forgetDeviceProcessors(data.deviceId);
|
|
68270
68405
|
this.levelStateByDevice.delete(data.deviceId);
|
|
68406
|
+
this.audioConfirmByDevice.delete(data.deviceId);
|
|
68271
68407
|
this.settingsCaches.clearDevice(data.deviceId);
|
|
68272
68408
|
this.rawDeviceBlobMirror.invalidate(data.deviceId);
|
|
68273
68409
|
}
|
|
@@ -68286,6 +68422,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68286
68422
|
this.overlaySynthesisWarnAt.delete(deviceId);
|
|
68287
68423
|
this.forgetDeviceProcessors(deviceId);
|
|
68288
68424
|
this.levelStateByDevice.delete(deviceId);
|
|
68425
|
+
this.audioConfirmByDevice.delete(deviceId);
|
|
68289
68426
|
this.lastTrackActivityMs.delete(deviceId);
|
|
68290
68427
|
this.motionEventSnapshots?.forgetDevice(deviceId);
|
|
68291
68428
|
this.audioEpisodeByDevice.delete(deviceId);
|
|
@@ -68502,7 +68639,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68502
68639
|
mimeType: image.mimeType
|
|
68503
68640
|
})),
|
|
68504
68641
|
jsonSchema: i.jsonSchema,
|
|
68505
|
-
temperature: i.temperature
|
|
68642
|
+
temperature: i.temperature,
|
|
68643
|
+
...i.timeoutMs !== void 0 ? { timeoutMs: i.timeoutMs } : {}
|
|
68506
68644
|
}),
|
|
68507
68645
|
cancelVision: async (requestId) => {
|
|
68508
68646
|
await api.llm.cancel.mutate({ requestId });
|
|
@@ -68797,6 +68935,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
68797
68935
|
this.nodeLiveness.clear();
|
|
68798
68936
|
this.gatedNodeByDevice.clear();
|
|
68799
68937
|
this.levelStateByDevice.clear();
|
|
68938
|
+
this.audioConfirmByDevice.clear();
|
|
68800
68939
|
this.settingsCaches.clearAll();
|
|
68801
68940
|
this.rawDeviceBlobMirror.invalidateAll();
|
|
68802
68941
|
this.rawGlobalStoreMirror.invalidate();
|
|
@@ -70811,12 +70950,15 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70811
70950
|
this.notificationCenter?.observeAudio(deviceId, {
|
|
70812
70951
|
timestampMs: timestamp,
|
|
70813
70952
|
...level?.dbfs !== void 0 ? { dbfs: level.dbfs } : {},
|
|
70814
|
-
labels: (frame.detections ?? []).filter((d) => d.score >= settings.classificationMinScore).map((d) => d.macroClass)
|
|
70953
|
+
labels: (frame.detections ?? []).filter((d) => d.score >= settings.classificationMinScore).map((d) => d.macroClass).filter((macroClass) => settings.audioAllowedClasses.length === 0 || settings.audioAllowedClasses.includes(macroClass))
|
|
70815
70954
|
});
|
|
70816
70955
|
const route = classifyAudioFrame(topClassification ? {
|
|
70817
70956
|
className: topClassification.macroClass,
|
|
70818
70957
|
score: topClassification.score
|
|
70819
|
-
} : void 0, {
|
|
70958
|
+
} : void 0, {
|
|
70959
|
+
classificationMinScore: settings.classificationMinScore,
|
|
70960
|
+
allowedClasses: settings.audioAllowedClasses
|
|
70961
|
+
});
|
|
70820
70962
|
if (route.kind === "classification") {
|
|
70821
70963
|
if (!topClassification) {
|
|
70822
70964
|
this.ctx.logger.warn("classifyAudioFrame returned classification but topClassification is null", {
|
|
@@ -70825,6 +70967,26 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
70825
70967
|
});
|
|
70826
70968
|
return;
|
|
70827
70969
|
}
|
|
70970
|
+
const confirm = observeAudioConfirm(this.audioConfirmByDevice.get(deviceId) ?? emptyAudioConfirmState(), {
|
|
70971
|
+
className: route.className,
|
|
70972
|
+
timestampMs: timestamp
|
|
70973
|
+
}, {
|
|
70974
|
+
confirmHits: settings.audioConfirmHits,
|
|
70975
|
+
confirmWindowSec: settings.audioConfirmWindowSec
|
|
70976
|
+
});
|
|
70977
|
+
this.audioConfirmByDevice.set(deviceId, confirm.nextState);
|
|
70978
|
+
if (!confirm.confirmed) {
|
|
70979
|
+
this.ctx.logger.debug("audio event held for corroboration", {
|
|
70980
|
+
tags: { deviceId },
|
|
70981
|
+
meta: {
|
|
70982
|
+
deviceId,
|
|
70983
|
+
className: route.className,
|
|
70984
|
+
confirmHits: settings.audioConfirmHits,
|
|
70985
|
+
confirmWindowSec: settings.audioConfirmWindowSec
|
|
70986
|
+
}
|
|
70987
|
+
});
|
|
70988
|
+
return;
|
|
70989
|
+
}
|
|
70828
70990
|
const classified = {
|
|
70829
70991
|
deviceId,
|
|
70830
70992
|
timestamp,
|
|
@@ -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-C-Iu7OUr.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.168",
|
|
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",
|