@camstack/addon-provider-homeassistant 1.1.31 → 1.1.32

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
@@ -7762,6 +7762,160 @@ var EncodeProfileSchema = object({
7762
7762
  */
7763
7763
  outputArgs: array(string()).optional()
7764
7764
  });
7765
+ var COCO_TO_MACRO = {
7766
+ mapping: {
7767
+ person: "person",
7768
+ bicycle: "vehicle",
7769
+ car: "vehicle",
7770
+ motorcycle: "vehicle",
7771
+ airplane: "vehicle",
7772
+ bus: "vehicle",
7773
+ train: "vehicle",
7774
+ truck: "vehicle",
7775
+ boat: "vehicle",
7776
+ bird: "animal",
7777
+ cat: "animal",
7778
+ dog: "animal",
7779
+ horse: "animal",
7780
+ sheep: "animal",
7781
+ cow: "animal",
7782
+ elephant: "animal",
7783
+ bear: "animal",
7784
+ zebra: "animal",
7785
+ giraffe: "animal",
7786
+ suitcase: "package",
7787
+ backpack: "package",
7788
+ handbag: "package"
7789
+ },
7790
+ preserveOriginal: false
7791
+ };
7792
+ var AUDIO_MACRO_LABELS = [
7793
+ {
7794
+ id: "speech",
7795
+ name: "Speech",
7796
+ icon: "🗣️"
7797
+ },
7798
+ {
7799
+ id: "scream",
7800
+ name: "Scream / Shout",
7801
+ icon: "😱"
7802
+ },
7803
+ {
7804
+ id: "crying",
7805
+ name: "Crying / Baby",
7806
+ icon: "😢"
7807
+ },
7808
+ {
7809
+ id: "laughter",
7810
+ name: "Laughter",
7811
+ icon: "😂"
7812
+ },
7813
+ {
7814
+ id: "music",
7815
+ name: "Music",
7816
+ icon: "🎵"
7817
+ },
7818
+ {
7819
+ id: "dog",
7820
+ name: "Dog",
7821
+ icon: "🐕"
7822
+ },
7823
+ {
7824
+ id: "cat",
7825
+ name: "Cat",
7826
+ icon: "🐈"
7827
+ },
7828
+ {
7829
+ id: "bird",
7830
+ name: "Bird",
7831
+ icon: "🐦"
7832
+ },
7833
+ {
7834
+ id: "animal",
7835
+ name: "Animal (other)",
7836
+ icon: "🐾"
7837
+ },
7838
+ {
7839
+ id: "alarm",
7840
+ name: "Alarm / Siren",
7841
+ icon: "🚨"
7842
+ },
7843
+ {
7844
+ id: "doorbell",
7845
+ name: "Doorbell / Knock",
7846
+ icon: "🔔"
7847
+ },
7848
+ {
7849
+ id: "glass_breaking",
7850
+ name: "Glass Breaking",
7851
+ icon: "💥"
7852
+ },
7853
+ {
7854
+ id: "gunshot",
7855
+ name: "Gunshot / Explosion",
7856
+ icon: "💣"
7857
+ },
7858
+ {
7859
+ id: "vehicle",
7860
+ name: "Vehicle",
7861
+ icon: "🚗"
7862
+ },
7863
+ {
7864
+ id: "siren",
7865
+ name: "Emergency Siren",
7866
+ icon: "🚑"
7867
+ },
7868
+ {
7869
+ id: "fire",
7870
+ name: "Fire / Smoke",
7871
+ icon: "🔥"
7872
+ },
7873
+ {
7874
+ id: "water",
7875
+ name: "Water",
7876
+ icon: "💧"
7877
+ },
7878
+ {
7879
+ id: "wind",
7880
+ name: "Wind / Weather",
7881
+ icon: "🌬️"
7882
+ },
7883
+ {
7884
+ id: "door",
7885
+ name: "Door",
7886
+ icon: "🚪"
7887
+ },
7888
+ {
7889
+ id: "footsteps",
7890
+ name: "Footsteps",
7891
+ icon: "👣"
7892
+ },
7893
+ {
7894
+ id: "crowd",
7895
+ name: "Crowd / Chatter",
7896
+ icon: "👥"
7897
+ },
7898
+ {
7899
+ id: "telephone",
7900
+ name: "Telephone",
7901
+ icon: "📞"
7902
+ },
7903
+ {
7904
+ id: "engine",
7905
+ name: "Engine / Motor",
7906
+ icon: "⚙️"
7907
+ },
7908
+ {
7909
+ id: "tools",
7910
+ name: "Tools / Construction",
7911
+ icon: "🔨"
7912
+ },
7913
+ {
7914
+ id: "silence",
7915
+ name: "Silence",
7916
+ icon: "🤫"
7917
+ }
7918
+ ];
7765
7919
  var YAMNET_TO_MACRO = {
7766
7920
  mapping: {
7767
7921
  Speech: "speech",
@@ -8028,6 +8182,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8028
8182
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8029
8183
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8030
8184
  /**
8185
+ * Unified event-kind taxonomy — THE single source of truth for
8186
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8187
+ * icon }`.
8188
+ *
8189
+ * This dictionary folds together what used to be scattered across four
8190
+ * copies:
8191
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8192
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8193
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8194
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8195
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8196
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8197
+ *
8198
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8199
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8200
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8201
+ * color or an icon: they read this dictionary (server descriptors carry the
8202
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8203
+ *
8204
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8205
+ */
8206
+ var TAXONOMY_COLORS = {
8207
+ motion: "#f59e0b",
8208
+ audio: "#06b6d4",
8209
+ person: "#22c55e",
8210
+ vehicle: "#3b82f6",
8211
+ animal: "#f97316",
8212
+ package: "#a855f7",
8213
+ sensor: "#8b5cf6",
8214
+ control: "#10b981",
8215
+ genericDetection: "#64748b"
8216
+ };
8217
+ var DETECTION_SUB_COLORS = {
8218
+ car: "#f59e0b",
8219
+ truck: "#d97706",
8220
+ bus: "#b45309",
8221
+ motorcycle: "#eab308",
8222
+ bicycle: "#ca8a04",
8223
+ airplane: "#60a5fa",
8224
+ boat: "#2563eb",
8225
+ train: "#1d4ed8",
8226
+ bird: "#14b8a6",
8227
+ dog: "#84cc16",
8228
+ cat: "#f97316",
8229
+ horse: "#a16207",
8230
+ sheep: "#a3a3a3",
8231
+ cow: "#78716c",
8232
+ elephant: "#6b7280",
8233
+ bear: "#7c2d12",
8234
+ zebra: "#404040",
8235
+ giraffe: "#d4a373"
8236
+ };
8237
+ function titleCase(id) {
8238
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8239
+ }
8240
+ var entries = /* @__PURE__ */ new Map();
8241
+ function macro(kind, category, color, iconId, label) {
8242
+ entries.set(kind, {
8243
+ kind,
8244
+ parentKind: null,
8245
+ level: "macro",
8246
+ category,
8247
+ color,
8248
+ iconId,
8249
+ labelKey: `eventKind.${kind}`,
8250
+ label
8251
+ });
8252
+ }
8253
+ function sub(kind, parentKind, category, color, iconId, label) {
8254
+ entries.set(kind, {
8255
+ kind,
8256
+ parentKind,
8257
+ level: "sub",
8258
+ category,
8259
+ color,
8260
+ iconId,
8261
+ labelKey: `eventKind.${kind}`,
8262
+ label
8263
+ });
8264
+ }
8265
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8266
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8267
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8268
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8269
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8270
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8271
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8272
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8273
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8274
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8275
+ if (entries.has(cocoClass)) continue;
8276
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8277
+ }
8278
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8279
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8280
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8281
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8282
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8283
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8284
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8285
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8286
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8287
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8288
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8289
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8290
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8291
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8292
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8293
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8294
+ sub("button", "control", "control", "#10b981", "button", "Button");
8295
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8296
+ for (const l of AUDIO_MACRO_LABELS) {
8297
+ const kind = `audio-${l.id}`;
8298
+ if (entries.has(kind)) continue;
8299
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8300
+ }
8301
+ /** The complete taxonomy dictionary, keyed by kind. */
8302
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8303
+ /**
8031
8304
  * Error types for the safe expression engine. Two distinct classes so callers
8032
8305
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8033
8306
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -19297,17 +19570,30 @@ var EventKindCategorySchema = _enum([
19297
19570
  "audio",
19298
19571
  "detection",
19299
19572
  "sensor",
19573
+ "control",
19300
19574
  "custom",
19301
19575
  "package"
19302
19576
  ]);
19577
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19578
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19303
19579
  var EventKindDescriptorSchema = object({
19304
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19580
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19305
19581
  kind: string(),
19582
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19583
+ labelKey: string(),
19584
+ /** English fallback label (kept for clients that don't translate). */
19306
19585
  label: string(),
19307
19586
  /** Hex color for timeline/legend rendering. */
19308
19587
  color: string(),
19588
+ /** Dictionary id → lucide component on the UI side. */
19589
+ iconId: string(),
19590
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19309
19591
  icon: EventKindIconSchema,
19310
19592
  category: EventKindCategorySchema,
19593
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19594
+ parentKind: string().nullable(),
19595
+ /** Derived from `parentKind`, explicit for the client tree. */
19596
+ level: EventKindLevelSchema,
19311
19597
  /** Which cap + device contributes this kind. For built-ins the camera
19312
19598
  * itself; for sensor kinds the LINKED source device. */
19313
19599
  source: object({
@@ -19380,11 +19666,21 @@ var TrackAudioLabelSchema = object({
19380
19666
  firstAt: number(),
19381
19667
  lastAt: number()
19382
19668
  });
19669
+ /**
19670
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19671
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19672
+ * linked sensor/control state change (no positions; carries a snapshot). The
19673
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19674
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19675
+ */
19676
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19383
19677
  var TrackSchema = object({
19384
19678
  trackId: string(),
19385
19679
  deviceId: number(),
19386
19680
  className: string(),
19387
19681
  label: string().optional(),
19682
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19683
+ source: TrackSourceSchema.optional(),
19388
19684
  firstSeen: number(),
19389
19685
  lastSeen: number(),
19390
19686
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19761,6 +20057,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19761
20057
  eventId: string(),
19762
20058
  timestamp: number()
19763
20059
  });
20060
+ /**
20061
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
20062
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
20063
+ * caps into per-camera event-kind descriptors.
20064
+ *
20065
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
20066
+ * is NOT duplicated here — every entry is derived from the single
20067
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
20068
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
20069
+ * control cap means adding one line here (and a taxonomy entry); the anti-
20070
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
20071
+ * eventful cap is missing.
20072
+ */
20073
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
20074
+ var LEGACY_ICON = {
20075
+ motion: "motion",
20076
+ audio: "audio",
20077
+ person: "person",
20078
+ vehicle: "vehicle",
20079
+ animal: "animal",
20080
+ package: "package",
20081
+ door: "door",
20082
+ pir: "pir",
20083
+ smoke: "smoke",
20084
+ water: "water",
20085
+ button: "button",
20086
+ generic: "generic",
20087
+ gas: "smoke",
20088
+ vibration: "generic",
20089
+ tamper: "generic",
20090
+ presence: "person",
20091
+ lock: "generic",
20092
+ siren: "generic",
20093
+ switch: "generic",
20094
+ doorbell: "button"
20095
+ };
20096
+ function legacyIcon(iconId) {
20097
+ return LEGACY_ICON[iconId] ?? "generic";
20098
+ }
20099
+ /**
20100
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
20101
+ * The anti-drift guard cross-checks this against the eventful caps declared
20102
+ * in `packages/types/src/capabilities/*.cap.ts`.
20103
+ */
20104
+ var CAP_TO_KIND = {
20105
+ contact: "contact",
20106
+ motion: "motion-sensor",
20107
+ smoke: "smoke",
20108
+ flood: "flood",
20109
+ gas: "gas",
20110
+ "carbon-monoxide": "carbon-monoxide",
20111
+ vibration: "vibration",
20112
+ tamper: "tamper",
20113
+ presence: "presence",
20114
+ "enum-sensor": "enum-sensor",
20115
+ "event-emitter": "device-event",
20116
+ "lock-control": "lock",
20117
+ switch: "switch",
20118
+ button: "button",
20119
+ doorbell: "doorbell"
20120
+ };
20121
+ function buildDescriptor(capName, kind) {
20122
+ const t = EVENT_TAXONOMY[kind];
20123
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
20124
+ return {
20125
+ ...t,
20126
+ icon: legacyIcon(t.iconId)
20127
+ };
20128
+ }
20129
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19764
20130
  var CameraPipelineConfigSchema = object({
19765
20131
  engine: PipelineEngineChoiceSchema.optional(),
19766
20132
  steps: array(PipelineStepInputSchema).readonly(),
package/dist/addon.mjs CHANGED
@@ -7761,6 +7761,160 @@ var EncodeProfileSchema = object({
7761
7761
  */
7762
7762
  outputArgs: array(string()).optional()
7763
7763
  });
7764
+ var COCO_TO_MACRO = {
7765
+ mapping: {
7766
+ person: "person",
7767
+ bicycle: "vehicle",
7768
+ car: "vehicle",
7769
+ motorcycle: "vehicle",
7770
+ airplane: "vehicle",
7771
+ bus: "vehicle",
7772
+ train: "vehicle",
7773
+ truck: "vehicle",
7774
+ boat: "vehicle",
7775
+ bird: "animal",
7776
+ cat: "animal",
7777
+ dog: "animal",
7778
+ horse: "animal",
7779
+ sheep: "animal",
7780
+ cow: "animal",
7781
+ elephant: "animal",
7782
+ bear: "animal",
7783
+ zebra: "animal",
7784
+ giraffe: "animal",
7785
+ suitcase: "package",
7786
+ backpack: "package",
7787
+ handbag: "package"
7788
+ },
7789
+ preserveOriginal: false
7790
+ };
7791
+ var AUDIO_MACRO_LABELS = [
7792
+ {
7793
+ id: "speech",
7794
+ name: "Speech",
7795
+ icon: "🗣️"
7796
+ },
7797
+ {
7798
+ id: "scream",
7799
+ name: "Scream / Shout",
7800
+ icon: "😱"
7801
+ },
7802
+ {
7803
+ id: "crying",
7804
+ name: "Crying / Baby",
7805
+ icon: "😢"
7806
+ },
7807
+ {
7808
+ id: "laughter",
7809
+ name: "Laughter",
7810
+ icon: "😂"
7811
+ },
7812
+ {
7813
+ id: "music",
7814
+ name: "Music",
7815
+ icon: "🎵"
7816
+ },
7817
+ {
7818
+ id: "dog",
7819
+ name: "Dog",
7820
+ icon: "🐕"
7821
+ },
7822
+ {
7823
+ id: "cat",
7824
+ name: "Cat",
7825
+ icon: "🐈"
7826
+ },
7827
+ {
7828
+ id: "bird",
7829
+ name: "Bird",
7830
+ icon: "🐦"
7831
+ },
7832
+ {
7833
+ id: "animal",
7834
+ name: "Animal (other)",
7835
+ icon: "🐾"
7836
+ },
7837
+ {
7838
+ id: "alarm",
7839
+ name: "Alarm / Siren",
7840
+ icon: "🚨"
7841
+ },
7842
+ {
7843
+ id: "doorbell",
7844
+ name: "Doorbell / Knock",
7845
+ icon: "🔔"
7846
+ },
7847
+ {
7848
+ id: "glass_breaking",
7849
+ name: "Glass Breaking",
7850
+ icon: "💥"
7851
+ },
7852
+ {
7853
+ id: "gunshot",
7854
+ name: "Gunshot / Explosion",
7855
+ icon: "💣"
7856
+ },
7857
+ {
7858
+ id: "vehicle",
7859
+ name: "Vehicle",
7860
+ icon: "🚗"
7861
+ },
7862
+ {
7863
+ id: "siren",
7864
+ name: "Emergency Siren",
7865
+ icon: "🚑"
7866
+ },
7867
+ {
7868
+ id: "fire",
7869
+ name: "Fire / Smoke",
7870
+ icon: "🔥"
7871
+ },
7872
+ {
7873
+ id: "water",
7874
+ name: "Water",
7875
+ icon: "💧"
7876
+ },
7877
+ {
7878
+ id: "wind",
7879
+ name: "Wind / Weather",
7880
+ icon: "🌬️"
7881
+ },
7882
+ {
7883
+ id: "door",
7884
+ name: "Door",
7885
+ icon: "🚪"
7886
+ },
7887
+ {
7888
+ id: "footsteps",
7889
+ name: "Footsteps",
7890
+ icon: "👣"
7891
+ },
7892
+ {
7893
+ id: "crowd",
7894
+ name: "Crowd / Chatter",
7895
+ icon: "👥"
7896
+ },
7897
+ {
7898
+ id: "telephone",
7899
+ name: "Telephone",
7900
+ icon: "📞"
7901
+ },
7902
+ {
7903
+ id: "engine",
7904
+ name: "Engine / Motor",
7905
+ icon: "⚙️"
7906
+ },
7907
+ {
7908
+ id: "tools",
7909
+ name: "Tools / Construction",
7910
+ icon: "🔨"
7911
+ },
7912
+ {
7913
+ id: "silence",
7914
+ name: "Silence",
7915
+ icon: "🤫"
7916
+ }
7917
+ ];
7764
7918
  var YAMNET_TO_MACRO = {
7765
7919
  mapping: {
7766
7920
  Speech: "speech",
@@ -8027,6 +8181,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8027
8181
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8028
8182
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8029
8183
  /**
8184
+ * Unified event-kind taxonomy — THE single source of truth for
8185
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8186
+ * icon }`.
8187
+ *
8188
+ * This dictionary folds together what used to be scattered across four
8189
+ * copies:
8190
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8191
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8192
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8193
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8194
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8195
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8196
+ *
8197
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8198
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8199
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8200
+ * color or an icon: they read this dictionary (server descriptors carry the
8201
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8202
+ *
8203
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8204
+ */
8205
+ var TAXONOMY_COLORS = {
8206
+ motion: "#f59e0b",
8207
+ audio: "#06b6d4",
8208
+ person: "#22c55e",
8209
+ vehicle: "#3b82f6",
8210
+ animal: "#f97316",
8211
+ package: "#a855f7",
8212
+ sensor: "#8b5cf6",
8213
+ control: "#10b981",
8214
+ genericDetection: "#64748b"
8215
+ };
8216
+ var DETECTION_SUB_COLORS = {
8217
+ car: "#f59e0b",
8218
+ truck: "#d97706",
8219
+ bus: "#b45309",
8220
+ motorcycle: "#eab308",
8221
+ bicycle: "#ca8a04",
8222
+ airplane: "#60a5fa",
8223
+ boat: "#2563eb",
8224
+ train: "#1d4ed8",
8225
+ bird: "#14b8a6",
8226
+ dog: "#84cc16",
8227
+ cat: "#f97316",
8228
+ horse: "#a16207",
8229
+ sheep: "#a3a3a3",
8230
+ cow: "#78716c",
8231
+ elephant: "#6b7280",
8232
+ bear: "#7c2d12",
8233
+ zebra: "#404040",
8234
+ giraffe: "#d4a373"
8235
+ };
8236
+ function titleCase(id) {
8237
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8238
+ }
8239
+ var entries = /* @__PURE__ */ new Map();
8240
+ function macro(kind, category, color, iconId, label) {
8241
+ entries.set(kind, {
8242
+ kind,
8243
+ parentKind: null,
8244
+ level: "macro",
8245
+ category,
8246
+ color,
8247
+ iconId,
8248
+ labelKey: `eventKind.${kind}`,
8249
+ label
8250
+ });
8251
+ }
8252
+ function sub(kind, parentKind, category, color, iconId, label) {
8253
+ entries.set(kind, {
8254
+ kind,
8255
+ parentKind,
8256
+ level: "sub",
8257
+ category,
8258
+ color,
8259
+ iconId,
8260
+ labelKey: `eventKind.${kind}`,
8261
+ label
8262
+ });
8263
+ }
8264
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8265
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8266
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8267
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8268
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8269
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8270
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8271
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8272
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8273
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8274
+ if (entries.has(cocoClass)) continue;
8275
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8276
+ }
8277
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8278
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8279
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8280
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8281
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8282
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8283
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8284
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8285
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8286
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8287
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8288
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8289
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8290
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8291
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8292
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8293
+ sub("button", "control", "control", "#10b981", "button", "Button");
8294
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8295
+ for (const l of AUDIO_MACRO_LABELS) {
8296
+ const kind = `audio-${l.id}`;
8297
+ if (entries.has(kind)) continue;
8298
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8299
+ }
8300
+ /** The complete taxonomy dictionary, keyed by kind. */
8301
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8302
+ /**
8030
8303
  * Error types for the safe expression engine. Two distinct classes so callers
8031
8304
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8032
8305
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -19296,17 +19569,30 @@ var EventKindCategorySchema = _enum([
19296
19569
  "audio",
19297
19570
  "detection",
19298
19571
  "sensor",
19572
+ "control",
19299
19573
  "custom",
19300
19574
  "package"
19301
19575
  ]);
19576
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19577
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19302
19578
  var EventKindDescriptorSchema = object({
19303
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19579
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19304
19580
  kind: string(),
19581
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19582
+ labelKey: string(),
19583
+ /** English fallback label (kept for clients that don't translate). */
19305
19584
  label: string(),
19306
19585
  /** Hex color for timeline/legend rendering. */
19307
19586
  color: string(),
19587
+ /** Dictionary id → lucide component on the UI side. */
19588
+ iconId: string(),
19589
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19308
19590
  icon: EventKindIconSchema,
19309
19591
  category: EventKindCategorySchema,
19592
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19593
+ parentKind: string().nullable(),
19594
+ /** Derived from `parentKind`, explicit for the client tree. */
19595
+ level: EventKindLevelSchema,
19310
19596
  /** Which cap + device contributes this kind. For built-ins the camera
19311
19597
  * itself; for sensor kinds the LINKED source device. */
19312
19598
  source: object({
@@ -19379,11 +19665,21 @@ var TrackAudioLabelSchema = object({
19379
19665
  firstAt: number(),
19380
19666
  lastAt: number()
19381
19667
  });
19668
+ /**
19669
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19670
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19671
+ * linked sensor/control state change (no positions; carries a snapshot). The
19672
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19673
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19674
+ */
19675
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19382
19676
  var TrackSchema = object({
19383
19677
  trackId: string(),
19384
19678
  deviceId: number(),
19385
19679
  className: string(),
19386
19680
  label: string().optional(),
19681
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19682
+ source: TrackSourceSchema.optional(),
19387
19683
  firstSeen: number(),
19388
19684
  lastSeen: number(),
19389
19685
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19760,6 +20056,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19760
20056
  eventId: string(),
19761
20057
  timestamp: number()
19762
20058
  });
20059
+ /**
20060
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
20061
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
20062
+ * caps into per-camera event-kind descriptors.
20063
+ *
20064
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
20065
+ * is NOT duplicated here — every entry is derived from the single
20066
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
20067
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
20068
+ * control cap means adding one line here (and a taxonomy entry); the anti-
20069
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
20070
+ * eventful cap is missing.
20071
+ */
20072
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
20073
+ var LEGACY_ICON = {
20074
+ motion: "motion",
20075
+ audio: "audio",
20076
+ person: "person",
20077
+ vehicle: "vehicle",
20078
+ animal: "animal",
20079
+ package: "package",
20080
+ door: "door",
20081
+ pir: "pir",
20082
+ smoke: "smoke",
20083
+ water: "water",
20084
+ button: "button",
20085
+ generic: "generic",
20086
+ gas: "smoke",
20087
+ vibration: "generic",
20088
+ tamper: "generic",
20089
+ presence: "person",
20090
+ lock: "generic",
20091
+ siren: "generic",
20092
+ switch: "generic",
20093
+ doorbell: "button"
20094
+ };
20095
+ function legacyIcon(iconId) {
20096
+ return LEGACY_ICON[iconId] ?? "generic";
20097
+ }
20098
+ /**
20099
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
20100
+ * The anti-drift guard cross-checks this against the eventful caps declared
20101
+ * in `packages/types/src/capabilities/*.cap.ts`.
20102
+ */
20103
+ var CAP_TO_KIND = {
20104
+ contact: "contact",
20105
+ motion: "motion-sensor",
20106
+ smoke: "smoke",
20107
+ flood: "flood",
20108
+ gas: "gas",
20109
+ "carbon-monoxide": "carbon-monoxide",
20110
+ vibration: "vibration",
20111
+ tamper: "tamper",
20112
+ presence: "presence",
20113
+ "enum-sensor": "enum-sensor",
20114
+ "event-emitter": "device-event",
20115
+ "lock-control": "lock",
20116
+ switch: "switch",
20117
+ button: "button",
20118
+ doorbell: "doorbell"
20119
+ };
20120
+ function buildDescriptor(capName, kind) {
20121
+ const t = EVENT_TAXONOMY[kind];
20122
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
20123
+ return {
20124
+ ...t,
20125
+ icon: legacyIcon(t.iconId)
20126
+ };
20127
+ }
20128
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19763
20129
  var CameraPipelineConfigSchema = object({
19764
20130
  engine: PipelineEngineChoiceSchema.optional(),
19765
20131
  steps: array(PipelineStepInputSchema).readonly(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.1.31",
3
+ "version": "1.1.32",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",