@camstack/addon-notifiers 1.1.29 → 1.1.30

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/addon.js CHANGED
@@ -7748,6 +7748,160 @@ var EncodeProfileSchema = object({
7748
7748
  */
7749
7749
  outputArgs: array(string()).optional()
7750
7750
  });
7751
+ var COCO_TO_MACRO = {
7752
+ mapping: {
7753
+ person: "person",
7754
+ bicycle: "vehicle",
7755
+ car: "vehicle",
7756
+ motorcycle: "vehicle",
7757
+ airplane: "vehicle",
7758
+ bus: "vehicle",
7759
+ train: "vehicle",
7760
+ truck: "vehicle",
7761
+ boat: "vehicle",
7762
+ bird: "animal",
7763
+ cat: "animal",
7764
+ dog: "animal",
7765
+ horse: "animal",
7766
+ sheep: "animal",
7767
+ cow: "animal",
7768
+ elephant: "animal",
7769
+ bear: "animal",
7770
+ zebra: "animal",
7771
+ giraffe: "animal",
7772
+ suitcase: "package",
7773
+ backpack: "package",
7774
+ handbag: "package"
7775
+ },
7776
+ preserveOriginal: false
7777
+ };
7778
+ var AUDIO_MACRO_LABELS = [
7779
+ {
7780
+ id: "speech",
7781
+ name: "Speech",
7782
+ icon: "🗣️"
7783
+ },
7784
+ {
7785
+ id: "scream",
7786
+ name: "Scream / Shout",
7787
+ icon: "😱"
7788
+ },
7789
+ {
7790
+ id: "crying",
7791
+ name: "Crying / Baby",
7792
+ icon: "😢"
7793
+ },
7794
+ {
7795
+ id: "laughter",
7796
+ name: "Laughter",
7797
+ icon: "😂"
7798
+ },
7799
+ {
7800
+ id: "music",
7801
+ name: "Music",
7802
+ icon: "🎵"
7803
+ },
7804
+ {
7805
+ id: "dog",
7806
+ name: "Dog",
7807
+ icon: "🐕"
7808
+ },
7809
+ {
7810
+ id: "cat",
7811
+ name: "Cat",
7812
+ icon: "🐈"
7813
+ },
7814
+ {
7815
+ id: "bird",
7816
+ name: "Bird",
7817
+ icon: "🐦"
7818
+ },
7819
+ {
7820
+ id: "animal",
7821
+ name: "Animal (other)",
7822
+ icon: "🐾"
7823
+ },
7824
+ {
7825
+ id: "alarm",
7826
+ name: "Alarm / Siren",
7827
+ icon: "🚨"
7828
+ },
7829
+ {
7830
+ id: "doorbell",
7831
+ name: "Doorbell / Knock",
7832
+ icon: "🔔"
7833
+ },
7834
+ {
7835
+ id: "glass_breaking",
7836
+ name: "Glass Breaking",
7837
+ icon: "💥"
7838
+ },
7839
+ {
7840
+ id: "gunshot",
7841
+ name: "Gunshot / Explosion",
7842
+ icon: "💣"
7843
+ },
7844
+ {
7845
+ id: "vehicle",
7846
+ name: "Vehicle",
7847
+ icon: "🚗"
7848
+ },
7849
+ {
7850
+ id: "siren",
7851
+ name: "Emergency Siren",
7852
+ icon: "🚑"
7853
+ },
7854
+ {
7855
+ id: "fire",
7856
+ name: "Fire / Smoke",
7857
+ icon: "🔥"
7858
+ },
7859
+ {
7860
+ id: "water",
7861
+ name: "Water",
7862
+ icon: "💧"
7863
+ },
7864
+ {
7865
+ id: "wind",
7866
+ name: "Wind / Weather",
7867
+ icon: "🌬️"
7868
+ },
7869
+ {
7870
+ id: "door",
7871
+ name: "Door",
7872
+ icon: "🚪"
7873
+ },
7874
+ {
7875
+ id: "footsteps",
7876
+ name: "Footsteps",
7877
+ icon: "👣"
7878
+ },
7879
+ {
7880
+ id: "crowd",
7881
+ name: "Crowd / Chatter",
7882
+ icon: "👥"
7883
+ },
7884
+ {
7885
+ id: "telephone",
7886
+ name: "Telephone",
7887
+ icon: "📞"
7888
+ },
7889
+ {
7890
+ id: "engine",
7891
+ name: "Engine / Motor",
7892
+ icon: "⚙️"
7893
+ },
7894
+ {
7895
+ id: "tools",
7896
+ name: "Tools / Construction",
7897
+ icon: "🔨"
7898
+ },
7899
+ {
7900
+ id: "silence",
7901
+ name: "Silence",
7902
+ icon: "🤫"
7903
+ }
7904
+ ];
7751
7905
  var YAMNET_TO_MACRO = {
7752
7906
  mapping: {
7753
7907
  Speech: "speech",
@@ -8014,6 +8168,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8014
8168
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8015
8169
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8016
8170
  /**
8171
+ * Unified event-kind taxonomy — THE single source of truth for
8172
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8173
+ * icon }`.
8174
+ *
8175
+ * This dictionary folds together what used to be scattered across four
8176
+ * copies:
8177
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8178
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8179
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8180
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8181
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8182
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8183
+ *
8184
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8185
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8186
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8187
+ * color or an icon: they read this dictionary (server descriptors carry the
8188
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8189
+ *
8190
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8191
+ */
8192
+ var TAXONOMY_COLORS = {
8193
+ motion: "#f59e0b",
8194
+ audio: "#06b6d4",
8195
+ person: "#22c55e",
8196
+ vehicle: "#3b82f6",
8197
+ animal: "#f97316",
8198
+ package: "#a855f7",
8199
+ sensor: "#8b5cf6",
8200
+ control: "#10b981",
8201
+ genericDetection: "#64748b"
8202
+ };
8203
+ var DETECTION_SUB_COLORS = {
8204
+ car: "#f59e0b",
8205
+ truck: "#d97706",
8206
+ bus: "#b45309",
8207
+ motorcycle: "#eab308",
8208
+ bicycle: "#ca8a04",
8209
+ airplane: "#60a5fa",
8210
+ boat: "#2563eb",
8211
+ train: "#1d4ed8",
8212
+ bird: "#14b8a6",
8213
+ dog: "#84cc16",
8214
+ cat: "#f97316",
8215
+ horse: "#a16207",
8216
+ sheep: "#a3a3a3",
8217
+ cow: "#78716c",
8218
+ elephant: "#6b7280",
8219
+ bear: "#7c2d12",
8220
+ zebra: "#404040",
8221
+ giraffe: "#d4a373"
8222
+ };
8223
+ function titleCase(id) {
8224
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8225
+ }
8226
+ var entries = /* @__PURE__ */ new Map();
8227
+ function macro(kind, category, color, iconId, label) {
8228
+ entries.set(kind, {
8229
+ kind,
8230
+ parentKind: null,
8231
+ level: "macro",
8232
+ category,
8233
+ color,
8234
+ iconId,
8235
+ labelKey: `eventKind.${kind}`,
8236
+ label
8237
+ });
8238
+ }
8239
+ function sub(kind, parentKind, category, color, iconId, label) {
8240
+ entries.set(kind, {
8241
+ kind,
8242
+ parentKind,
8243
+ level: "sub",
8244
+ category,
8245
+ color,
8246
+ iconId,
8247
+ labelKey: `eventKind.${kind}`,
8248
+ label
8249
+ });
8250
+ }
8251
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8252
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8253
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8254
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8255
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8256
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8257
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8258
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8259
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8260
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8261
+ if (entries.has(cocoClass)) continue;
8262
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8263
+ }
8264
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8265
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8266
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8267
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8268
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8269
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8270
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8271
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8272
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8273
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8274
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8275
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8276
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8277
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8278
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8279
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8280
+ sub("button", "control", "control", "#10b981", "button", "Button");
8281
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8282
+ for (const l of AUDIO_MACRO_LABELS) {
8283
+ const kind = `audio-${l.id}`;
8284
+ if (entries.has(kind)) continue;
8285
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8286
+ }
8287
+ /** The complete taxonomy dictionary, keyed by kind. */
8288
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8289
+ /**
8017
8290
  * Error types for the safe expression engine. Two distinct classes so callers
8018
8291
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8019
8292
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -16189,17 +16462,30 @@ var EventKindCategorySchema = _enum([
16189
16462
  "audio",
16190
16463
  "detection",
16191
16464
  "sensor",
16465
+ "control",
16192
16466
  "custom",
16193
16467
  "package"
16194
16468
  ]);
16469
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16470
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16195
16471
  var EventKindDescriptorSchema = object({
16196
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16472
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16197
16473
  kind: string(),
16474
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16475
+ labelKey: string(),
16476
+ /** English fallback label (kept for clients that don't translate). */
16198
16477
  label: string(),
16199
16478
  /** Hex color for timeline/legend rendering. */
16200
16479
  color: string(),
16480
+ /** Dictionary id → lucide component on the UI side. */
16481
+ iconId: string(),
16482
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16201
16483
  icon: EventKindIconSchema,
16202
16484
  category: EventKindCategorySchema,
16485
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16486
+ parentKind: string().nullable(),
16487
+ /** Derived from `parentKind`, explicit for the client tree. */
16488
+ level: EventKindLevelSchema,
16203
16489
  /** Which cap + device contributes this kind. For built-ins the camera
16204
16490
  * itself; for sensor kinds the LINKED source device. */
16205
16491
  source: object({
@@ -16272,11 +16558,21 @@ var TrackAudioLabelSchema = object({
16272
16558
  firstAt: number(),
16273
16559
  lastAt: number()
16274
16560
  });
16561
+ /**
16562
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16563
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16564
+ * linked sensor/control state change (no positions; carries a snapshot). The
16565
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16566
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16567
+ */
16568
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16275
16569
  var TrackSchema = object({
16276
16570
  trackId: string(),
16277
16571
  deviceId: number(),
16278
16572
  className: string(),
16279
16573
  label: string().optional(),
16574
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16575
+ source: TrackSourceSchema.optional(),
16280
16576
  firstSeen: number(),
16281
16577
  lastSeen: number(),
16282
16578
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16653,6 +16949,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16653
16949
  eventId: string(),
16654
16950
  timestamp: number()
16655
16951
  });
16952
+ /**
16953
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16954
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16955
+ * caps into per-camera event-kind descriptors.
16956
+ *
16957
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16958
+ * is NOT duplicated here — every entry is derived from the single
16959
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16960
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16961
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16962
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16963
+ * eventful cap is missing.
16964
+ */
16965
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16966
+ var LEGACY_ICON = {
16967
+ motion: "motion",
16968
+ audio: "audio",
16969
+ person: "person",
16970
+ vehicle: "vehicle",
16971
+ animal: "animal",
16972
+ package: "package",
16973
+ door: "door",
16974
+ pir: "pir",
16975
+ smoke: "smoke",
16976
+ water: "water",
16977
+ button: "button",
16978
+ generic: "generic",
16979
+ gas: "smoke",
16980
+ vibration: "generic",
16981
+ tamper: "generic",
16982
+ presence: "person",
16983
+ lock: "generic",
16984
+ siren: "generic",
16985
+ switch: "generic",
16986
+ doorbell: "button"
16987
+ };
16988
+ function legacyIcon(iconId) {
16989
+ return LEGACY_ICON[iconId] ?? "generic";
16990
+ }
16991
+ /**
16992
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16993
+ * The anti-drift guard cross-checks this against the eventful caps declared
16994
+ * in `packages/types/src/capabilities/*.cap.ts`.
16995
+ */
16996
+ var CAP_TO_KIND = {
16997
+ contact: "contact",
16998
+ motion: "motion-sensor",
16999
+ smoke: "smoke",
17000
+ flood: "flood",
17001
+ gas: "gas",
17002
+ "carbon-monoxide": "carbon-monoxide",
17003
+ vibration: "vibration",
17004
+ tamper: "tamper",
17005
+ presence: "presence",
17006
+ "enum-sensor": "enum-sensor",
17007
+ "event-emitter": "device-event",
17008
+ "lock-control": "lock",
17009
+ switch: "switch",
17010
+ button: "button",
17011
+ doorbell: "doorbell"
17012
+ };
17013
+ function buildDescriptor(capName, kind) {
17014
+ const t = EVENT_TAXONOMY[kind];
17015
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
17016
+ return {
17017
+ ...t,
17018
+ icon: legacyIcon(t.iconId)
17019
+ };
17020
+ }
17021
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16656
17022
  var CameraPipelineConfigSchema = object({
16657
17023
  engine: PipelineEngineChoiceSchema.optional(),
16658
17024
  steps: array(PipelineStepInputSchema).readonly(),
package/dist/addon.mjs CHANGED
@@ -7744,6 +7744,160 @@ var EncodeProfileSchema = object({
7744
7744
  */
7745
7745
  outputArgs: array(string()).optional()
7746
7746
  });
7747
+ var COCO_TO_MACRO = {
7748
+ mapping: {
7749
+ person: "person",
7750
+ bicycle: "vehicle",
7751
+ car: "vehicle",
7752
+ motorcycle: "vehicle",
7753
+ airplane: "vehicle",
7754
+ bus: "vehicle",
7755
+ train: "vehicle",
7756
+ truck: "vehicle",
7757
+ boat: "vehicle",
7758
+ bird: "animal",
7759
+ cat: "animal",
7760
+ dog: "animal",
7761
+ horse: "animal",
7762
+ sheep: "animal",
7763
+ cow: "animal",
7764
+ elephant: "animal",
7765
+ bear: "animal",
7766
+ zebra: "animal",
7767
+ giraffe: "animal",
7768
+ suitcase: "package",
7769
+ backpack: "package",
7770
+ handbag: "package"
7771
+ },
7772
+ preserveOriginal: false
7773
+ };
7774
+ var AUDIO_MACRO_LABELS = [
7775
+ {
7776
+ id: "speech",
7777
+ name: "Speech",
7778
+ icon: "🗣️"
7779
+ },
7780
+ {
7781
+ id: "scream",
7782
+ name: "Scream / Shout",
7783
+ icon: "😱"
7784
+ },
7785
+ {
7786
+ id: "crying",
7787
+ name: "Crying / Baby",
7788
+ icon: "😢"
7789
+ },
7790
+ {
7791
+ id: "laughter",
7792
+ name: "Laughter",
7793
+ icon: "😂"
7794
+ },
7795
+ {
7796
+ id: "music",
7797
+ name: "Music",
7798
+ icon: "🎵"
7799
+ },
7800
+ {
7801
+ id: "dog",
7802
+ name: "Dog",
7803
+ icon: "🐕"
7804
+ },
7805
+ {
7806
+ id: "cat",
7807
+ name: "Cat",
7808
+ icon: "🐈"
7809
+ },
7810
+ {
7811
+ id: "bird",
7812
+ name: "Bird",
7813
+ icon: "🐦"
7814
+ },
7815
+ {
7816
+ id: "animal",
7817
+ name: "Animal (other)",
7818
+ icon: "🐾"
7819
+ },
7820
+ {
7821
+ id: "alarm",
7822
+ name: "Alarm / Siren",
7823
+ icon: "🚨"
7824
+ },
7825
+ {
7826
+ id: "doorbell",
7827
+ name: "Doorbell / Knock",
7828
+ icon: "🔔"
7829
+ },
7830
+ {
7831
+ id: "glass_breaking",
7832
+ name: "Glass Breaking",
7833
+ icon: "💥"
7834
+ },
7835
+ {
7836
+ id: "gunshot",
7837
+ name: "Gunshot / Explosion",
7838
+ icon: "💣"
7839
+ },
7840
+ {
7841
+ id: "vehicle",
7842
+ name: "Vehicle",
7843
+ icon: "🚗"
7844
+ },
7845
+ {
7846
+ id: "siren",
7847
+ name: "Emergency Siren",
7848
+ icon: "🚑"
7849
+ },
7850
+ {
7851
+ id: "fire",
7852
+ name: "Fire / Smoke",
7853
+ icon: "🔥"
7854
+ },
7855
+ {
7856
+ id: "water",
7857
+ name: "Water",
7858
+ icon: "💧"
7859
+ },
7860
+ {
7861
+ id: "wind",
7862
+ name: "Wind / Weather",
7863
+ icon: "🌬️"
7864
+ },
7865
+ {
7866
+ id: "door",
7867
+ name: "Door",
7868
+ icon: "🚪"
7869
+ },
7870
+ {
7871
+ id: "footsteps",
7872
+ name: "Footsteps",
7873
+ icon: "👣"
7874
+ },
7875
+ {
7876
+ id: "crowd",
7877
+ name: "Crowd / Chatter",
7878
+ icon: "👥"
7879
+ },
7880
+ {
7881
+ id: "telephone",
7882
+ name: "Telephone",
7883
+ icon: "📞"
7884
+ },
7885
+ {
7886
+ id: "engine",
7887
+ name: "Engine / Motor",
7888
+ icon: "⚙️"
7889
+ },
7890
+ {
7891
+ id: "tools",
7892
+ name: "Tools / Construction",
7893
+ icon: "🔨"
7894
+ },
7895
+ {
7896
+ id: "silence",
7897
+ name: "Silence",
7898
+ icon: "🤫"
7899
+ }
7900
+ ];
7747
7901
  var YAMNET_TO_MACRO = {
7748
7902
  mapping: {
7749
7903
  Speech: "speech",
@@ -8010,6 +8164,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8010
8164
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8011
8165
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8012
8166
  /**
8167
+ * Unified event-kind taxonomy — THE single source of truth for
8168
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8169
+ * icon }`.
8170
+ *
8171
+ * This dictionary folds together what used to be scattered across four
8172
+ * copies:
8173
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8174
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8175
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8176
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8177
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8178
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8179
+ *
8180
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8181
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8182
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8183
+ * color or an icon: they read this dictionary (server descriptors carry the
8184
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8185
+ *
8186
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8187
+ */
8188
+ var TAXONOMY_COLORS = {
8189
+ motion: "#f59e0b",
8190
+ audio: "#06b6d4",
8191
+ person: "#22c55e",
8192
+ vehicle: "#3b82f6",
8193
+ animal: "#f97316",
8194
+ package: "#a855f7",
8195
+ sensor: "#8b5cf6",
8196
+ control: "#10b981",
8197
+ genericDetection: "#64748b"
8198
+ };
8199
+ var DETECTION_SUB_COLORS = {
8200
+ car: "#f59e0b",
8201
+ truck: "#d97706",
8202
+ bus: "#b45309",
8203
+ motorcycle: "#eab308",
8204
+ bicycle: "#ca8a04",
8205
+ airplane: "#60a5fa",
8206
+ boat: "#2563eb",
8207
+ train: "#1d4ed8",
8208
+ bird: "#14b8a6",
8209
+ dog: "#84cc16",
8210
+ cat: "#f97316",
8211
+ horse: "#a16207",
8212
+ sheep: "#a3a3a3",
8213
+ cow: "#78716c",
8214
+ elephant: "#6b7280",
8215
+ bear: "#7c2d12",
8216
+ zebra: "#404040",
8217
+ giraffe: "#d4a373"
8218
+ };
8219
+ function titleCase(id) {
8220
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8221
+ }
8222
+ var entries = /* @__PURE__ */ new Map();
8223
+ function macro(kind, category, color, iconId, label) {
8224
+ entries.set(kind, {
8225
+ kind,
8226
+ parentKind: null,
8227
+ level: "macro",
8228
+ category,
8229
+ color,
8230
+ iconId,
8231
+ labelKey: `eventKind.${kind}`,
8232
+ label
8233
+ });
8234
+ }
8235
+ function sub(kind, parentKind, category, color, iconId, label) {
8236
+ entries.set(kind, {
8237
+ kind,
8238
+ parentKind,
8239
+ level: "sub",
8240
+ category,
8241
+ color,
8242
+ iconId,
8243
+ labelKey: `eventKind.${kind}`,
8244
+ label
8245
+ });
8246
+ }
8247
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8248
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8249
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8250
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8251
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8252
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8253
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8254
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8255
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8256
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8257
+ if (entries.has(cocoClass)) continue;
8258
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8259
+ }
8260
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8261
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8262
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8263
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8264
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8265
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8266
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8267
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8268
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8269
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8270
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8271
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8272
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8273
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8274
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8275
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8276
+ sub("button", "control", "control", "#10b981", "button", "Button");
8277
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8278
+ for (const l of AUDIO_MACRO_LABELS) {
8279
+ const kind = `audio-${l.id}`;
8280
+ if (entries.has(kind)) continue;
8281
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8282
+ }
8283
+ /** The complete taxonomy dictionary, keyed by kind. */
8284
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8285
+ /**
8013
8286
  * Error types for the safe expression engine. Two distinct classes so callers
8014
8287
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8015
8288
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -16185,17 +16458,30 @@ var EventKindCategorySchema = _enum([
16185
16458
  "audio",
16186
16459
  "detection",
16187
16460
  "sensor",
16461
+ "control",
16188
16462
  "custom",
16189
16463
  "package"
16190
16464
  ]);
16465
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16466
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16191
16467
  var EventKindDescriptorSchema = object({
16192
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16468
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16193
16469
  kind: string(),
16470
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16471
+ labelKey: string(),
16472
+ /** English fallback label (kept for clients that don't translate). */
16194
16473
  label: string(),
16195
16474
  /** Hex color for timeline/legend rendering. */
16196
16475
  color: string(),
16476
+ /** Dictionary id → lucide component on the UI side. */
16477
+ iconId: string(),
16478
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16197
16479
  icon: EventKindIconSchema,
16198
16480
  category: EventKindCategorySchema,
16481
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16482
+ parentKind: string().nullable(),
16483
+ /** Derived from `parentKind`, explicit for the client tree. */
16484
+ level: EventKindLevelSchema,
16199
16485
  /** Which cap + device contributes this kind. For built-ins the camera
16200
16486
  * itself; for sensor kinds the LINKED source device. */
16201
16487
  source: object({
@@ -16268,11 +16554,21 @@ var TrackAudioLabelSchema = object({
16268
16554
  firstAt: number(),
16269
16555
  lastAt: number()
16270
16556
  });
16557
+ /**
16558
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16559
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16560
+ * linked sensor/control state change (no positions; carries a snapshot). The
16561
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16562
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16563
+ */
16564
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16271
16565
  var TrackSchema = object({
16272
16566
  trackId: string(),
16273
16567
  deviceId: number(),
16274
16568
  className: string(),
16275
16569
  label: string().optional(),
16570
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16571
+ source: TrackSourceSchema.optional(),
16276
16572
  firstSeen: number(),
16277
16573
  lastSeen: number(),
16278
16574
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16649,6 +16945,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16649
16945
  eventId: string(),
16650
16946
  timestamp: number()
16651
16947
  });
16948
+ /**
16949
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16950
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16951
+ * caps into per-camera event-kind descriptors.
16952
+ *
16953
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16954
+ * is NOT duplicated here — every entry is derived from the single
16955
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16956
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16957
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16958
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16959
+ * eventful cap is missing.
16960
+ */
16961
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16962
+ var LEGACY_ICON = {
16963
+ motion: "motion",
16964
+ audio: "audio",
16965
+ person: "person",
16966
+ vehicle: "vehicle",
16967
+ animal: "animal",
16968
+ package: "package",
16969
+ door: "door",
16970
+ pir: "pir",
16971
+ smoke: "smoke",
16972
+ water: "water",
16973
+ button: "button",
16974
+ generic: "generic",
16975
+ gas: "smoke",
16976
+ vibration: "generic",
16977
+ tamper: "generic",
16978
+ presence: "person",
16979
+ lock: "generic",
16980
+ siren: "generic",
16981
+ switch: "generic",
16982
+ doorbell: "button"
16983
+ };
16984
+ function legacyIcon(iconId) {
16985
+ return LEGACY_ICON[iconId] ?? "generic";
16986
+ }
16987
+ /**
16988
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16989
+ * The anti-drift guard cross-checks this against the eventful caps declared
16990
+ * in `packages/types/src/capabilities/*.cap.ts`.
16991
+ */
16992
+ var CAP_TO_KIND = {
16993
+ contact: "contact",
16994
+ motion: "motion-sensor",
16995
+ smoke: "smoke",
16996
+ flood: "flood",
16997
+ gas: "gas",
16998
+ "carbon-monoxide": "carbon-monoxide",
16999
+ vibration: "vibration",
17000
+ tamper: "tamper",
17001
+ presence: "presence",
17002
+ "enum-sensor": "enum-sensor",
17003
+ "event-emitter": "device-event",
17004
+ "lock-control": "lock",
17005
+ switch: "switch",
17006
+ button: "button",
17007
+ doorbell: "doorbell"
17008
+ };
17009
+ function buildDescriptor(capName, kind) {
17010
+ const t = EVENT_TAXONOMY[kind];
17011
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
17012
+ return {
17013
+ ...t,
17014
+ icon: legacyIcon(t.iconId)
17015
+ };
17016
+ }
17017
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16652
17018
  var CameraPipelineConfigSchema = object({
16653
17019
  engine: PipelineEngineChoiceSchema.optional(),
16654
17020
  steps: array(PipelineStepInputSchema).readonly(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-notifiers",
3
- "version": "1.1.29",
3
+ "version": "1.1.30",
4
4
  "description": "System notifiers addon for CamStack — a `notification-output` collection provider hosting per-kind notifier adapters (ntfy, pushover, gotify, telegram, discord, webhook, zentik).",
5
5
  "keywords": [
6
6
  "camstack",