@camstack/types 1.2.71 → 1.2.73

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.
@@ -45,8 +45,16 @@ export declare const NC_AUDIO_DB_STEP = 3;
45
45
  * an absent `dbThreshold` means "any level" and stays absent until asked for,
46
46
  * and a stepper seeded at the -96 floor would need thirty taps to reach a
47
47
  * threshold that can actually fire.
48
+ *
49
+ * **It was -30 and -30 is a trap.** dBFS is negative-going, so a HIGHER number
50
+ * is a NARROWER filter, and the sounds this hub is actually asked about are not
51
+ * loud: measured on the live installation, real crying sits between -55 and
52
+ * -26 dBFS. A floor at -30 therefore rejects most of the very sound the
53
+ * operator turned the filter on for, and the rule looks broken rather than
54
+ * mis-seeded. The offered floor is the QUIET end of that measured band; the
55
+ * editor says which way the number runs rather than leaving it to be inferred.
48
56
  */
49
- export declare const NC_AUDIO_DB_OFFERED = -30;
57
+ export declare const NC_AUDIO_DB_OFFERED = -55;
50
58
  export declare const NC_AUDIO_HIT_PERCENT_MIN = 1;
51
59
  export declare const NC_AUDIO_HIT_PERCENT_MAX = 100;
52
60
  export declare const NC_AUDIO_SAMPLING_MIN_SEC = 1;
@@ -55,6 +63,34 @@ export declare const NC_AUDIO_SAMPLING_MAX_SEC = 300;
55
63
  export declare const NC_AUDIO_DEFAULTS: NcAudioCondition;
56
64
  /** What an editor SHOWS for an unset condition — without authoring it. */
57
65
  export declare function audioOrDefaults(value: NcAudioCondition | undefined): NcAudioCondition;
66
+ /**
67
+ * The two EXCLUSIVE ways an audio rule works (operator decision, 2026-08-14 —
68
+ * see `docs/decisions/D157-audio-rule-label-mode.md`).
69
+ *
70
+ * - `label` — the rule NAMES SOUNDS. It fires on the FIRST frame the
71
+ * classifier labels with one of them (above the analyzer's own per-device
72
+ * confidence floor). No window, no hit percentage: the rule's cooldown is
73
+ * the only brake.
74
+ * - `level` — the rule NAMES A LEVEL. The sampling window is the whole point:
75
+ * `hitPercent`% of the samples over `samplingSeconds` must clear
76
+ * `dbThreshold`.
77
+ */
78
+ export type NcAudioMode = 'label' | 'level';
79
+ /**
80
+ * Which mode this condition is in — the ONE place that question is answered.
81
+ *
82
+ * The mode is NOT a stored field, deliberately. It is which filter the rule
83
+ * carries, so every rule authored before the modes existed migrates for free
84
+ * and there is no second switch that can disagree with the first (the failure
85
+ * this repo has shipped twice). `null` = neither filter, which the engine
86
+ * refuses (see {@link audioIsFailClosed}).
87
+ *
88
+ * A LEGACY rule carrying BOTH resolves to `label`: it is the mode that fires,
89
+ * and the alternative is silently keeping a window the operator can no longer
90
+ * see in the editor. Nothing new can reach this branch — {@link patchAudio}
91
+ * clears the other filter on every write.
92
+ */
93
+ export declare function audioModeOf(value: NcAudioCondition | undefined): NcAudioMode | null;
58
94
  /**
59
95
  * True when the condition names NEITHER a level floor NOR any label. The engine
60
96
  * refuses such a rule (every sample is a hit, so the window fires on silence) —
@@ -68,8 +104,12 @@ export declare function audioIsFailClosed(value: NcAudioCondition | undefined):
68
104
  /**
69
105
  * A patch over the composite condition. A key present with `undefined` CLEARS
70
106
  * the (optional) filter; an absent key leaves it alone.
107
+ *
108
+ * `mode` is the explicit switch an editor's mode picker sends. It is not
109
+ * stored — {@link patchAudio} turns it into which filter survives.
71
110
  */
72
111
  export interface NcAudioPatch {
112
+ readonly mode?: NcAudioMode;
73
113
  readonly labels?: readonly string[] | undefined;
74
114
  readonly dbThreshold?: number | undefined;
75
115
  readonly hitPercent?: number;
@@ -80,6 +120,18 @@ export interface NcAudioPatch {
80
120
  * The result is always in-bounds for `NcAudioConditionSchema`, and a cleared
81
121
  * filter is OMITTED rather than written as an explicit `undefined` (which
82
122
  * survives a merge patch as a deliberate erase and reads as authored).
123
+ *
124
+ * **The two modes are exclusive here, structurally.** Writing labels drops any
125
+ * stored `dbThreshold`; writing a `dbThreshold` drops the labels. The schema
126
+ * cannot say "exactly one of" without becoming a ZodEffects the cap path would
127
+ * have to special-case, so the exclusivity lives in the ONE function every
128
+ * editor writes through — which also means a legacy both-filters rule is
129
+ * normalized the first time it is touched, onto the mode {@link audioModeOf}
130
+ * already reads it as.
131
+ *
132
+ * Switching to `level` with nothing stored seeds {@link NC_AUDIO_DB_OFFERED}:
133
+ * a level rule with no floor is fail-closed, and handing the operator a mode
134
+ * that cannot fire is the trap this whole change exists to remove.
83
135
  */
84
136
  export declare function patchAudio(current: NcAudioCondition | undefined, patch: NcAudioPatch): NcAudioCondition;
85
137
  /**
@@ -3273,6 +3273,7 @@ function createDeviceProxy(api, binding, opts) {
3273
3273
  captureReference: (input) => dispatch("scene-monitor", "sceneMonitor", "captureReference", "mutation", input),
3274
3274
  deleteReference: (input) => dispatch("scene-monitor", "sceneMonitor", "deleteReference", "mutation", input),
3275
3275
  recheckNow: (input) => dispatch("scene-monitor", "sceneMonitor", "recheckNow", "mutation", input),
3276
+ resetScene: (input) => dispatch("scene-monitor", "sceneMonitor", "resetScene", "mutation", input),
3276
3277
  getStatus: (input) => dispatch("scene-monitor", "sceneMonitor", "getStatus", "query", input)
3277
3278
  },
3278
3279
  scriptRunner: {
@@ -3273,6 +3273,7 @@ function createDeviceProxy(api, binding, opts) {
3273
3273
  captureReference: (input) => dispatch("scene-monitor", "sceneMonitor", "captureReference", "mutation", input),
3274
3274
  deleteReference: (input) => dispatch("scene-monitor", "sceneMonitor", "deleteReference", "mutation", input),
3275
3275
  recheckNow: (input) => dispatch("scene-monitor", "sceneMonitor", "recheckNow", "mutation", input),
3276
+ resetScene: (input) => dispatch("scene-monitor", "sceneMonitor", "resetScene", "mutation", input),
3276
3277
  getStatus: (input) => dispatch("scene-monitor", "sceneMonitor", "getStatus", "query", input)
3277
3278
  },
3278
3279
  scriptRunner: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.71",
3
+ "version": "1.2.73",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",