@camstack/addon-post-analysis 0.1.20 → 0.2.0

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.
Files changed (73) hide show
  1. package/dist/dist-4mTLJ7BJ.mjs +20750 -0
  2. package/dist/dist-CS2K80so.js +20933 -0
  3. package/dist/embedding-encoder/index.js +977 -902
  4. package/dist/embedding-encoder/index.mjs +967 -860
  5. package/dist/enrichment-engine/index.js +834 -833
  6. package/dist/enrichment-engine/index.mjs +828 -832
  7. package/dist/pipeline-analytics/_stub.js +1680 -1396
  8. package/dist/pipeline-analytics/_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DOSUJ-U0.mjs +156 -0
  9. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DJvmVCso.mjs +26 -0
  10. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.js-B3Wx5J80.mjs +26 -0
  11. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.js-C0AuF9av.mjs +26 -0
  12. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-Bm-iyjmq.mjs +26 -0
  13. package/dist/pipeline-analytics/dist-CYZr2fwk.mjs +2726 -0
  14. package/dist/pipeline-analytics/hostInit-BazRS2O7.mjs +129 -0
  15. package/dist/pipeline-analytics/index.js +7112 -3100
  16. package/dist/pipeline-analytics/index.mjs +7105 -3100
  17. package/dist/pipeline-analytics/remoteEntry.js +134 -2973
  18. package/dist/pipeline-analytics/remoteEntry.ssr.js +33 -0
  19. package/dist/pipeline-analytics/virtualExposes-BgYzpJZG.mjs +27 -0
  20. package/dist/pipeline-analytics/virtual_mf-exposes-ssr___mfe_internal__addon_pipeline_analytics_widgets__remoteEntry_js-D7qgWCKX.mjs +10 -0
  21. package/dist/resolve-frame-5lMxmeI1.js +57 -0
  22. package/dist/resolve-frame-CT1T1tWy.mjs +44 -0
  23. package/package.json +15 -6
  24. package/dist/embedding-encoder/index.js.map +0 -1
  25. package/dist/embedding-encoder/index.mjs.map +0 -1
  26. package/dist/enrichment-engine/index.js.map +0 -1
  27. package/dist/enrichment-engine/index.mjs.map +0 -1
  28. package/dist/index-B0RhVv1c.js +0 -17107
  29. package/dist/index-B0RhVv1c.js.map +0 -1
  30. package/dist/index-ot5PeFg_.mjs +0 -17108
  31. package/dist/index-ot5PeFg_.mjs.map +0 -1
  32. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/AudioHistoryChart.d.ts +0 -4
  33. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/AudioMetricsPanel.d.ts +0 -10
  34. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/DetectionHistoryChart.d.ts +0 -4
  35. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/LiveStatsTab.d.ts +0 -5
  36. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/MotionHistoryChart.d.ts +0 -4
  37. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/OccupancyHistoryChart.d.ts +0 -4
  38. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/OccupancyPanel.d.ts +0 -10
  39. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/chart-utils.d.ts +0 -97
  40. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/index.d.ts +0 -29
  41. package/dist/pipeline-analytics/@mf-types/widgets.d.ts +0 -2
  42. package/dist/pipeline-analytics/@mf-types.d.ts +0 -3
  43. package/dist/pipeline-analytics/@mf-types.zip +0 -0
  44. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-lantnv8e.mjs +0 -12
  45. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-BD3oMNGB.mjs +0 -29
  46. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BgOHCakr.mjs +0 -18
  47. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.mjs-DoWbefqS.mjs +0 -104
  48. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_client__loadShare__.mjs-52bfkwC8.mjs +0 -85
  49. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.mjs-CVrnrGED.mjs +0 -62
  50. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-D1qPKjvR.mjs +0 -89
  51. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-B5X50Xa4.mjs +0 -29
  52. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-BsyrX6NO.mjs +0 -36
  53. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs-Dp8hqYOB.mjs +0 -45
  54. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-B10b5k5J.mjs +0 -6
  55. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom_mf_1_client__loadShare__.mjs-BZjEt71l.mjs +0 -34
  56. package/dist/pipeline-analytics/_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DWB3apaJ.mjs +0 -156
  57. package/dist/pipeline-analytics/client-C6xdgLZU.mjs +0 -9836
  58. package/dist/pipeline-analytics/getErrorShape-BPSzUA7W-TlK8ipWe.mjs +0 -211
  59. package/dist/pipeline-analytics/hostInit-3cyL9eyG.mjs +0 -168
  60. package/dist/pipeline-analytics/index-BCTHeI2m.mjs +0 -1641
  61. package/dist/pipeline-analytics/index-BuWLz0GG.mjs +0 -2603
  62. package/dist/pipeline-analytics/index-CIwq-tQL.mjs +0 -725
  63. package/dist/pipeline-analytics/index-CWBMDbou.mjs +0 -435
  64. package/dist/pipeline-analytics/index-CWkKuNLr.mjs +0 -232
  65. package/dist/pipeline-analytics/index-CZhagnlH.mjs +0 -67784
  66. package/dist/pipeline-analytics/index-D883Q5B8.mjs +0 -185
  67. package/dist/pipeline-analytics/index-DtOI1aTU.mjs +0 -18504
  68. package/dist/pipeline-analytics/index-xncRG7-x.mjs +0 -2713
  69. package/dist/pipeline-analytics/index.js.map +0 -1
  70. package/dist/pipeline-analytics/index.mjs.map +0 -1
  71. package/dist/pipeline-analytics/jsx-runtime-DdLhuHmJ.mjs +0 -55
  72. package/dist/pipeline-analytics/schemas-B7L0qZtq.mjs +0 -3599
  73. package/dist/pipeline-analytics/virtualExposes-8FzWTdq3.mjs +0 -42
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../src/enrichment-engine/types.ts","../../src/enrichment-engine/services/crop-extractor.ts","../../src/enrichment-engine/services/resolve-frame.ts","../../src/enrichment-engine/workers/embedding-dispatcher.ts","../../src/_analytics-schemas/persistence-records.ts","../../src/enrichment-engine/services/text-embedding-cache.ts","../../src/enrichment-engine/workers/scene-state-worker.ts","../../src/enrichment-engine/workers/activity-summary.ts","../../src/enrichment-engine/index.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Enrichment Engine — Types\n// ---------------------------------------------------------------------------\n\n// --- Normalized Rectangle (0-1 coordinate space) ---\n\nexport interface NormalizedRect {\n readonly x: number // 0-1, left edge\n readonly y: number // 0-1, top edge\n readonly w: number // 0-1, width\n readonly h: number // 0-1, height\n}\n\n// --- Scene State ---\n\nexport interface SceneStateDefinition {\n readonly id: string\n readonly label: string // e.g. \"open\", \"closed\"\n readonly textPrompts: readonly string[] // zero-shot: [\"an open gate\"]\n readonly referenceEmbeddings: readonly (readonly number[])[] // optional: higher accuracy\n readonly notify: boolean\n readonly severity: 'info' | 'warning' | 'alert'\n}\n\nexport interface SceneMonitor {\n readonly id: string\n readonly label: string // e.g. \"Main Gate\"\n readonly roi: NormalizedRect // region of interest\n readonly enabled: boolean\n readonly states: readonly SceneStateDefinition[]\n}\n\nexport interface SceneMonitorConfig {\n readonly monitors: readonly SceneMonitor[]\n}\n\n// --- Enrichment Config (global) ---\n\nexport interface EnrichmentConfig {\n readonly enabled: boolean\n\n readonly embedding: {\n readonly enabled: boolean\n readonly modelId: string // 'clip-vit-b32' | 'clip-vit-b16' | 'siglip2-b16-256'\n readonly agentId: string | 'local'\n readonly runtime: 'node' | 'python'\n readonly backend: 'cpu' | 'coreml' | 'cuda'\n readonly classes: readonly string[] // [] = all classes\n readonly minConfidence: number\n readonly maxPerSecPerCamera: number\n readonly cropStrategy: 'first' | 'best-confidence' | 'track-end'\n readonly retentionDays: number\n }\n\n readonly sceneMonitor: {\n readonly enabled: boolean\n readonly modelId: string // can differ from embedding model\n readonly pollIntervalSec: number\n readonly hysteresisCount: number\n }\n\n readonly activitySummary: {\n readonly enabled: boolean\n readonly intervalSec: number\n readonly activityThresholds: {\n readonly low: number\n readonly medium: number\n readonly high: number\n }\n }\n}\n\n// --- Events ---\n\nexport interface EmbeddingStoredEvent {\n readonly deviceId: number\n readonly trackId: string\n readonly class: string\n readonly embeddingId: string // reference in vector store\n readonly modelId: string\n readonly embeddingDim: number\n readonly inferenceMs: number\n readonly timestamp: number\n}\n\nexport interface SceneStateChangedEvent {\n readonly deviceId: number\n readonly monitorId: string\n readonly monitorLabel: string\n readonly previousState: string\n readonly currentState: string\n readonly confidence: number\n readonly timestamp: number\n}\n\nexport interface ZoneActivityEntry {\n readonly zoneId: string\n readonly entries: number\n readonly exits: number\n readonly avgDwellMs: number\n}\n\nexport interface StateChangeEntry {\n readonly monitorId: string\n readonly from: string\n readonly to: string\n readonly timestamp: number\n}\n\nexport interface ActivitySummaryEvent {\n readonly deviceId: number\n readonly periodStart: number\n readonly periodEnd: number\n readonly objectCounts: Readonly<Record<string, number>>\n readonly zoneActivity: readonly ZoneActivityEntry[]\n readonly stateChanges: readonly StateChangeEntry[]\n readonly activityLevel: 'none' | 'low' | 'medium' | 'high'\n}\n\n// --- Defaults ---\n\nexport const DEFAULT_ENRICHMENT_CONFIG: EnrichmentConfig = {\n enabled: false,\n\n embedding: {\n enabled: false,\n modelId: 'clip-vit-b32',\n agentId: 'local',\n runtime: 'node',\n backend: 'cpu',\n classes: [],\n minConfidence: 0.5,\n maxPerSecPerCamera: 1,\n cropStrategy: 'first',\n retentionDays: 30,\n },\n\n sceneMonitor: {\n enabled: false,\n modelId: 'clip-vit-b32',\n pollIntervalSec: 10,\n hysteresisCount: 3,\n },\n\n activitySummary: {\n enabled: false,\n intervalSec: 60,\n activityThresholds: {\n low: 2,\n medium: 10,\n high: 30,\n },\n },\n}\n","import sharp from 'sharp'\n\n/**\n * Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.\n * Coordinates are clamped to frame bounds to avoid out-of-range errors.\n */\nexport async function extractCrop(\n frameData: Buffer,\n frameWidth: number,\n frameHeight: number,\n bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },\n): Promise<{ crop: Buffer; width: number; height: number }> {\n // Convert normalized coordinates to pixel values\n const rawLeft = Math.round(bbox.x * frameWidth)\n const rawTop = Math.round(bbox.y * frameHeight)\n const rawWidth = Math.round(bbox.w * frameWidth)\n const rawHeight = Math.round(bbox.h * frameHeight)\n\n // Clamp to frame bounds\n const left = Math.max(0, Math.min(rawLeft, frameWidth - 1))\n const top = Math.max(0, Math.min(rawTop, frameHeight - 1))\n const width = Math.max(1, Math.min(rawWidth, frameWidth - left))\n const height = Math.max(1, Math.min(rawHeight, frameHeight - top))\n\n const crop = await sharp(frameData, {\n raw: { width: frameWidth, height: frameHeight, channels: 3 },\n })\n .extract({ left, top, width, height })\n .jpeg({ quality: 90 })\n .toBuffer()\n\n return { crop, width, height }\n}\n","/**\n * `resolveFrame` — turn a serialisable `FrameHandle` back into pixels.\n *\n * The handle's `nodeId` decides the path: when the consumer is co-located\n * with the producing decoder, the pixels are read directly from the shared\n * memory ring (`readers.read(handle)`, a `null` means the slot was recycled\n * before the consumer got to it — a dropped frame, the correct latest-wins\n * behaviour for live video). Otherwise the caller-supplied\n * `getRemoteFrame(handle)` performs a node-routed `decoder.getFrame` call.\n *\n * Keeping the remote fetch as an injected callback keeps this helper free of\n * any cap-client / tRPC coupling and trivially testable.\n */\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\n\nexport interface ResolveFrameDeps {\n /** Cluster node id of the consumer — used to detect co-location. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the caller to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\n/**\n * Resolve the pixels a `FrameHandle` refers to. Local shm read when the\n * handle's `nodeId` matches `deps.ownNodeId`, else routed via\n * `deps.getRemoteFrame`. Returns `null` when the frame is no longer\n * available (slot recycled locally, or the remote node reports no frame).\n */\nexport async function resolveFrame(\n handle: FrameHandle,\n deps: ResolveFrameDeps,\n): Promise<DecodedFrame | null> {\n if (handle.nodeId === deps.ownNodeId) {\n return deps.readers.read(handle)\n }\n return deps.getRemoteFrame(handle)\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type {\n DecodedFrame,\n FrameHandle,\n IEventBus,\n IScopedLogger,\n TypedSystemEvent,\n} from '@camstack/types'\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig, EmbeddingStoredEvent } from '../types.js'\nimport { extractCrop } from '../services/crop-extractor.js'\nimport { resolveFrame } from '../services/resolve-frame.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface PendingCrop {\n readonly trackId: string\n readonly deviceId: string\n readonly class: string\n readonly confidence: number\n readonly frameData: Buffer\n readonly frameWidth: number\n readonly frameHeight: number\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n readonly receivedAt: number\n}\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly score: number\n readonly trackId?: string\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n }\n readonly objectState?: {\n readonly state: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\nexport interface EmbeddingDispatcherDeps {\n readonly config: EnrichmentConfig['embedding']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n /** Cluster node id of the consumer — used to detect co-location with the decoder. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the engine to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\nexport class EmbeddingDispatcher {\n private readonly config: EnrichmentConfig['embedding']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly logger: IScopedLogger\n private readonly ownNodeId: string\n private readonly readers: FrameRingReaderCache\n private readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n\n private readonly lastEmbedTime = new Map<string, number>()\n private readonly pendingCrops = new Map<string, PendingCrop>()\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private unsubscribe: (() => void) | null = null\n\n private _processedCount = 0\n private _totalInferenceMs = 0\n private _encoderIndex = 0\n\n constructor(deps: EmbeddingDispatcherDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.logger = deps.logger\n this.ownNodeId = deps.ownNodeId\n this.readers = deps.readers\n this.getRemoteFrame = deps.getRemoteFrame\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('EmbeddingDispatcher disabled')\n return\n }\n\n this.unsubscribe = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { void this.handleDetectionResult(event) },\n )\n\n // Flush timer for best-confidence strategy\n if (this.config.cropStrategy === 'best-confidence') {\n this.flushTimer = setInterval(() => { void this.flushPending() }, 1000)\n }\n\n this.logger.info('EmbeddingDispatcher started', { meta: { strategy: this.config.cropStrategy, maxPerSec: this.config.maxPerSecPerCamera } })\n }\n\n async stop(): Promise<void> {\n this.unsubscribe?.()\n this.unsubscribe = null\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n // Flush remaining\n await this.flushPending()\n }\n\n get processedCount(): number { return this._processedCount }\n get avgInferenceMs(): number { return this._processedCount > 0 ? this._totalInferenceMs / this._processedCount : 0 }\n get queueDepth(): number { return this.pendingCrops.size }\n\n private async handleDetectionResult(event: TypedSystemEvent<'detection.result'>): Promise<void> {\n const data = event.data\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const detections = (data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n\n // Resolve the underlying frame once per event via the shm frame plane.\n // The handle's `nodeId` decides the path — local shm read when co-located\n // with the producing decoder, else a node-routed `decoder.getFrame` call.\n const handle = data.frameHandle\n if (!handle) {\n this.logger.debug('skip: no frameHandle on DetectionResult', {\n meta: { deviceId },\n })\n return\n }\n\n let decoded: DecodedFrame | null\n try {\n decoded = await resolveFrame(handle, {\n ownNodeId: this.ownNodeId,\n readers: this.readers,\n getRemoteFrame: this.getRemoteFrame,\n })\n } catch (err) {\n this.logger.debug('skip: resolveFrame threw', {\n meta: { deviceId, shmId: handle.shmId, error: String(err) },\n })\n return\n }\n\n if (!decoded) {\n this.logger.debug('skip: frame recycled before resolve', {\n meta: { deviceId, shmId: handle.shmId },\n })\n return\n }\n\n // Defensive: the live detection stream is decoded as RGB by the pipeline\n // runner. `extractCrop` interprets the buffer as raw RGB — bail if not.\n if (decoded.format !== 'rgb') {\n this.logger.debug('skip: resolved frame is not RGB', {\n meta: { deviceId, format: decoded.format },\n })\n return\n }\n\n const frameData = Buffer.isBuffer(decoded.data) ? decoded.data : Buffer.from(decoded.data)\n const frameWidth = decoded.width\n const frameHeight = decoded.height\n\n for (const det of detections) {\n const detection = det.detection\n if (!detection) continue\n\n // Class filter\n if (this.config.classes.length > 0 && !this.config.classes.includes(detection.class)) continue\n\n // Confidence filter\n if (detection.score < this.config.minConfidence) continue\n\n // Rate limit\n const now = Date.now()\n const minInterval = 1000 / this.config.maxPerSecPerCamera\n const lastTime = this.lastEmbedTime.get(deviceId) ?? 0\n if (now - lastTime < minInterval) continue\n\n const trackId = detection.trackId ?? `${deviceId}-${now}`\n\n const pending: PendingCrop = {\n trackId,\n deviceId,\n class: detection.class,\n confidence: detection.score,\n frameData,\n frameWidth,\n frameHeight,\n bbox: detection.bbox,\n receivedAt: now,\n }\n\n switch (this.config.cropStrategy) {\n case 'first': {\n if (!this.pendingCrops.has(trackId)) {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n }\n break\n }\n case 'best-confidence': {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n break\n }\n case 'track-end': {\n const state = det.objectState?.state\n if (state === 'leaving') {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n } else {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n }\n break\n }\n }\n }\n }\n\n private async flushPending(): Promise<void> {\n const now = Date.now()\n const toFlush: PendingCrop[] = []\n\n for (const [trackId, pending] of this.pendingCrops) {\n if (now - pending.receivedAt > 3000) {\n toFlush.push(pending)\n this.pendingCrops.delete(trackId)\n }\n }\n\n await Promise.all(toFlush.map(p => this.processOne(p)))\n }\n\n private async processOne(pending: PendingCrop): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n const { crop, width, height } = await extractCrop(\n pending.frameData, pending.frameWidth, pending.frameHeight, pending.bbox,\n )\n\n // Round-robin across encoders\n const encoder = this.encoders[this._encoderIndex % this.encoders.length]!\n this._encoderIndex++\n\n const { embedding: _embedding, inferenceMs } = await encoder.encode(crop, width, height)\n const info = encoder.getInfo()\n\n this._processedCount++\n this._totalInferenceMs += inferenceMs\n this.lastEmbedTime.set(pending.deviceId, Date.now())\n this.pendingCrops.delete(pending.trackId)\n\n const embeddingId = `${pending.deviceId}/${pending.trackId}/${Date.now()}`\n\n const payload: EmbeddingStoredEvent = {\n deviceId: Number(pending.deviceId),\n trackId: pending.trackId,\n class: pending.class,\n embeddingId,\n modelId: info.modelId,\n embeddingDim: info.embeddingDim,\n inferenceMs,\n timestamp: Date.now(),\n }\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentEmbeddingStored,\n { type: 'addon', id: 'enrichment-engine' },\n payload,\n ))\n\n this.logger.debug('Embedded track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { class: pending.class, trackId: pending.trackId, inferenceMs: Number(inferenceMs.toFixed(1)) },\n })\n } catch (err) {\n this.logger.warn('Failed to embed track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { trackId: pending.trackId, error: String(err) },\n })\n }\n }\n}\n","/**\n * Zod schemas for analytics-suite persisted record types.\n * Used by persistence services to parse settings-store query results.\n */\nimport { z } from 'zod'\n\n// ── Track Trail ─────────────────────────────────────────────────────\n\nexport const TrackPositionSchema = z.object({\n x: z.number(),\n y: z.number(),\n timestamp: z.number(),\n bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]),\n})\n\nexport const TrackSnapshotSchema = z.object({\n timestamp: z.number(),\n position: TrackPositionSchema,\n thumbnailPath: z.string(),\n})\n\nexport const TrackTrailSchema = z.object({\n trackId: z.string(),\n deviceId: z.string(),\n className: z.string(),\n label: z.string().optional(),\n firstSeen: z.number(),\n lastSeen: z.number(),\n positions: z.array(TrackPositionSchema),\n snapshots: z.array(TrackSnapshotSchema),\n totalDistance: z.number(),\n zonesVisited: z.array(z.string()),\n active: z.boolean(),\n})\n\nexport type TrackTrail = z.infer<typeof TrackTrailSchema>\n\n// ── Scene Monitor ───────────────────────────────────────────────────\n\nexport const SceneMonitorConfigSchema = z.object({\n monitors: z.array(z.object({\n id: z.string(),\n label: z.string(),\n roi: z.object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() }),\n enabled: z.boolean(),\n states: z.array(z.object({\n id: z.string(),\n label: z.string(),\n prompt: z.string().optional(),\n textPrompts: z.array(z.string()).optional(),\n referenceEmbeddings: z.array(z.array(z.number())).optional(),\n threshold: z.number().optional(),\n notify: z.boolean().optional(),\n severity: z.enum(['info', 'warning', 'alert']).optional(),\n })),\n })),\n})\n\nexport type SceneMonitorConfig = z.infer<typeof SceneMonitorConfigSchema>\n\n/** Single scene monitor entry (element of SceneMonitorConfig.monitors). */\nexport type SceneMonitor = SceneMonitorConfig['monitors'][number]\n","/**\n * In-memory cache for text prompt embeddings.\n *\n * Keys follow the convention `${monitorId}:${text}` so that all entries\n * belonging to a specific scene monitor can be invalidated in one call.\n */\nexport class TextEmbeddingCache {\n private readonly cache = new Map<string, Float32Array>()\n\n get(key: string): Float32Array | undefined {\n return this.cache.get(key)\n }\n\n set(key: string, embedding: Float32Array): void {\n this.cache.set(key, embedding)\n }\n\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n /**\n * Deletes all entries whose key starts with `${monitorId}:`.\n * Used when a scene monitor's text prompts are updated.\n */\n invalidateMonitor(monitorId: string): void {\n const prefix = `${monitorId}:`\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key)\n }\n }\n }\n\n clear(): void {\n this.cache.clear()\n }\n\n get size(): number {\n return this.cache.size\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient } from '@camstack/types'\nimport type { EnrichmentConfig, SceneStateChangedEvent } from '../types.js'\nimport { SceneMonitorConfigSchema, type SceneMonitor } from '../../_analytics-schemas/persistence-records.js'\nimport { TextEmbeddingCache } from '../services/text-embedding-cache.js'\nimport { extractCrop } from '../services/crop-extractor.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n encodeText(text: string): Promise<{ embedding: Float32Array }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface StreamBrokerRegistry {\n getSnapshot(deviceId: string): Promise<{ data: Buffer; width: number; height: number } | null>\n}\n\ninterface CameraMonitorState {\n currentState: string | null\n pendingState: string | null\n pendingCount: number\n pendingConfidence: number\n}\n\nexport interface SceneStateWorkerDeps {\n readonly config: EnrichmentConfig['sceneMonitor']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly streamBrokerRegistry: StreamBrokerRegistry\n readonly logger: IScopedLogger\n}\n\nexport class SceneStateWorker {\n private readonly config: EnrichmentConfig['sceneMonitor']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly streamBrokerRegistry: StreamBrokerRegistry\n private readonly logger: IScopedLogger\n\n private readonly textCache = new TextEmbeddingCache()\n private readonly monitorStates = new Map<string, CameraMonitorState>()\n private pollTimer: ReturnType<typeof setInterval> | null = null\n private _activeCount = 0\n\n constructor(deps: SceneStateWorkerDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.streamBrokerRegistry = deps.streamBrokerRegistry\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('SceneStateWorker disabled')\n return\n }\n\n this.pollTimer = setInterval(\n () => { void this.pollAll() },\n this.config.pollIntervalSec * 1000,\n )\n this.logger.info('SceneStateWorker started', { meta: { pollIntervalSec: this.config.pollIntervalSec, hysteresis: this.config.hysteresisCount } })\n }\n\n async stop(): Promise<void> {\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n }\n\n get activeCount(): number { return this._activeCount }\n\n private async pollAll(): Promise<void> {\n // Load all scene monitor configs from settings\n const rawConfigs = await this.store.query.query({ collection: 'device-settings', filter: {\n where: { id: 'enrichment:scene-monitor:%' },\n } })\n const allConfigs = rawConfigs.map(r => ({ id: r.id, data: SceneMonitorConfigSchema.parse(r.data) }))\n\n const deviceMonitors = new Map<string, SceneMonitor[]>()\n for (const record of allConfigs) {\n const deviceId = record.id.replace('enrichment:scene-monitor:', '')\n const config = record.data\n const active = config.monitors.filter(m => m.enabled)\n if (active.length > 0) {\n deviceMonitors.set(deviceId, active)\n }\n }\n\n this._activeCount = [...deviceMonitors.values()].reduce((sum, ms) => sum + ms.length, 0)\n\n // Process each camera (1 snapshot per camera)\n await Promise.all(\n [...deviceMonitors.entries()].map(([deviceId, monitors]) =>\n this.pollCamera(deviceId, monitors),\n ),\n )\n }\n\n private async pollCamera(deviceId: string, monitors: SceneMonitor[]): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n // Capture 1 snapshot\n const snapshot = await this.streamBrokerRegistry.getSnapshot(deviceId)\n if (!snapshot) return\n\n const encoder = this.encoders[0]!\n\n // Process each monitor (crop from same snapshot)\n for (const monitor of monitors) {\n try {\n await this.processMonitor(deviceId, monitor, snapshot, encoder)\n } catch (err) {\n this.logger.warn('SceneState error', { tags: { deviceId: Number(deviceId) }, meta: { monitorLabel: monitor.label, error: String(err) } })\n }\n }\n } catch (err) {\n this.logger.warn('Failed to capture snapshot', { tags: { deviceId: Number(deviceId) }, meta: { error: String(err) } })\n }\n }\n\n private async processMonitor(\n deviceId: string,\n monitor: SceneMonitor,\n snapshot: { data: Buffer; width: number; height: number },\n encoder: EmbeddingEncoder,\n ): Promise<void> {\n // Crop ROI (normalized → pixel coords)\n const bbox = {\n x: monitor.roi.x * snapshot.width,\n y: monitor.roi.y * snapshot.height,\n w: monitor.roi.w * snapshot.width,\n h: monitor.roi.h * snapshot.height,\n }\n const { crop, width, height } = await extractCrop(snapshot.data, snapshot.width, snapshot.height, bbox)\n\n // Encode crop\n const { embedding: imageEmb } = await encoder.encode(crop, width, height)\n\n // Find best matching state\n let bestState: string | null = null\n let bestScore = -1\n\n for (const state of monitor.states) {\n let score = 0\n\n if (state.referenceEmbeddings && state.referenceEmbeddings.length > 0) {\n for (const refEmb of state.referenceEmbeddings) {\n const ref = new Float32Array(refEmb)\n const sim = cosineSimilarity(imageEmb, ref)\n score = Math.max(score, sim)\n }\n } else if (state.textPrompts && state.textPrompts.length > 0) {\n for (const prompt of state.textPrompts) {\n const cacheKey = `${monitor.id}:${state.id}:${prompt}`\n let textEmb = this.textCache.get(cacheKey)\n if (!textEmb) {\n const result = await encoder.encodeText(prompt)\n textEmb = result.embedding\n this.textCache.set(cacheKey, textEmb)\n }\n const sim = cosineSimilarity(imageEmb, textEmb)\n score = Math.max(score, sim)\n }\n }\n\n if (score > bestScore) {\n bestScore = score\n bestState = state.label\n }\n }\n\n if (!bestState) return\n\n // Hysteresis\n const key = `${deviceId}:${monitor.id}`\n let ms = this.monitorStates.get(key)\n if (!ms) {\n ms = { currentState: null, pendingState: null, pendingCount: 0, pendingConfidence: 0 }\n this.monitorStates.set(key, ms)\n }\n\n if (bestState === ms.pendingState) {\n ms.pendingCount++\n ms.pendingConfidence = bestScore\n } else {\n ms.pendingState = bestState\n ms.pendingCount = 1\n ms.pendingConfidence = bestScore\n }\n\n if (ms.pendingCount < this.config.hysteresisCount) return\n if (bestState === ms.currentState) return\n\n // State changed!\n const previousState = ms.currentState ?? 'unknown'\n ms.currentState = bestState\n ms.pendingState = null\n ms.pendingCount = 0\n\n const payload: SceneStateChangedEvent = {\n deviceId: Number(deviceId),\n monitorId: monitor.id,\n monitorLabel: monitor.label,\n previousState,\n currentState: bestState,\n confidence: bestScore,\n timestamp: Date.now(),\n }\n\n const data: Record<string, unknown> = { ...payload }\n this.eventBus.emit({\n id: `scene-state-${deviceId}-${monitor.id}-${Date.now()}`,\n category: EventCategory.EnrichmentSceneStateChanged,\n source: { type: 'device', id: deviceId },\n timestamp: new Date(),\n data,\n })\n\n this.logger.info('Scene state changed', {\n tags: { deviceId: Number(deviceId) },\n meta: { monitorLabel: monitor.label, previousState, currentState: bestState, confidence: Number(bestScore.toFixed(2)) },\n })\n }\n}\n\nfunction cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0\n let normA = 0\n let normB = 0\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!\n normA += a[i]! * a[i]!\n normB += b[i]! * b[i]!\n }\n return dot / (Math.sqrt(normA) * Math.sqrt(normB))\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient, TypedSystemEvent } from '@camstack/types'\nimport type { EnrichmentConfig, ActivitySummaryEvent, StateChangeEntry } from '../types.js'\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly trackId?: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\ninterface TrackInfo {\n class: string\n deviceId: string\n firstSeen: number\n lastSeen: number\n zones: Set<string>\n}\n\ninterface ZoneEventInfo {\n type: 'enter' | 'exit'\n zoneId: string\n trackId: string\n timestamp: number\n}\n\ninterface ActivityBuffer {\n tracks: Map<string, TrackInfo>\n zoneEvents: ZoneEventInfo[]\n stateChanges: StateChangeEntry[]\n}\n\nexport interface ActivitySummaryWorkerDeps {\n readonly config: EnrichmentConfig['activitySummary']\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly logger: IScopedLogger\n}\n\nexport class ActivitySummaryWorker {\n private readonly config: EnrichmentConfig['activitySummary']\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly logger: IScopedLogger\n\n private readonly buffers = new Map<string, ActivityBuffer>()\n private summaryTimer: ReturnType<typeof setInterval> | null = null\n private unsubDetection: (() => void) | null = null\n private unsubSceneState: (() => void) | null = null\n private _lastSummary: ActivitySummaryEvent | null = null\n\n constructor(deps: ActivitySummaryWorkerDeps) {\n this.config = deps.config\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('ActivitySummaryWorker disabled')\n return\n }\n\n this.unsubDetection = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { this.handleDetection(event) },\n )\n\n this.unsubSceneState = this.eventBus.subscribe(\n { category: EventCategory.EnrichmentSceneStateChanged },\n (event) => { this.handleSceneStateChange(event) },\n )\n\n this.summaryTimer = setInterval(\n () => { void this.emitSummaries() },\n this.config.intervalSec * 1000,\n )\n\n this.logger.info('ActivitySummaryWorker started', { meta: { intervalSec: this.config.intervalSec } })\n }\n\n async stop(): Promise<void> {\n this.unsubDetection?.()\n this.unsubSceneState?.()\n if (this.summaryTimer) {\n clearInterval(this.summaryTimer)\n this.summaryTimer = null\n }\n // Emit final summaries\n await this.emitSummaries()\n }\n\n get lastSummary(): ActivitySummaryEvent | null { return this._lastSummary }\n\n private getBuffer(deviceId: string): ActivityBuffer {\n let buf = this.buffers.get(deviceId)\n if (!buf) {\n buf = { tracks: new Map(), zoneEvents: [], stateChanges: [] }\n this.buffers.set(deviceId, buf)\n }\n return buf\n }\n\n private handleDetection(event: TypedSystemEvent<'detection.result'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const results = (event.data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n const buf = this.getBuffer(deviceId)\n const now = Date.now()\n\n for (const det of results) {\n const detection = det.detection\n if (!detection) continue\n\n const trackId = detection.trackId ?? `unknown-${now}`\n const existing = buf.tracks.get(trackId)\n\n if (existing) {\n existing.lastSeen = now\n } else {\n buf.tracks.set(trackId, {\n class: detection.class,\n deviceId,\n firstSeen: now,\n lastSeen: now,\n zones: new Set(),\n })\n }\n\n // Zone events\n const zoneEvents = det.zoneEvents ?? []\n for (const ze of zoneEvents) {\n const track = buf.tracks.get(trackId)\n if (track) track.zones.add(ze.zoneId)\n\n if (ze.type === 'zone-enter' || ze.type === 'zone-exit') {\n buf.zoneEvents.push({\n type: ze.type === 'zone-enter' ? 'enter' : 'exit',\n zoneId: ze.zoneId,\n trackId,\n timestamp: ze.timestamp ?? now,\n })\n }\n }\n }\n }\n\n private handleSceneStateChange(event: TypedSystemEvent<'enrichment.scene.state-changed'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n const data = event.data\n const buf = this.getBuffer(deviceId)\n buf.stateChanges.push({\n monitorId: data.monitorId,\n from: data.previousState,\n to: data.currentState,\n timestamp: data.timestamp,\n })\n }\n\n private async emitSummaries(): Promise<void> {\n const now = Date.now()\n\n for (const [deviceId, buf] of this.buffers) {\n if (buf.tracks.size === 0 && buf.zoneEvents.length === 0 && buf.stateChanges.length === 0) {\n continue\n }\n\n // Object counts by class\n const objectCounts: Record<string, number> = {}\n for (const track of buf.tracks.values()) {\n objectCounts[track.class] = (objectCounts[track.class] ?? 0) + 1\n }\n\n // Zone activity\n const zoneMap = new Map<string, { entries: number; exits: number; dwellTimes: number[] }>()\n for (const ze of buf.zoneEvents) {\n let z = zoneMap.get(ze.zoneId)\n if (!z) {\n z = { entries: 0, exits: 0, dwellTimes: [] }\n zoneMap.set(ze.zoneId, z)\n }\n if (ze.type === 'enter') z.entries++\n else z.exits++\n }\n\n const zoneActivity = [...zoneMap.entries()].map(([zoneId, z]) => ({\n zoneId,\n entries: z.entries,\n exits: z.exits,\n avgDwellMs: z.dwellTimes.length > 0\n ? z.dwellTimes.reduce((a, b) => a + b, 0) / z.dwellTimes.length\n : 0,\n }))\n\n // Activity level\n const totalEvents = buf.tracks.size + buf.zoneEvents.length\n const eventsPerMin = totalEvents / (this.config.intervalSec / 60)\n const activityLevel = eventsPerMin >= this.config.activityThresholds.high ? 'high'\n : eventsPerMin >= this.config.activityThresholds.medium ? 'medium'\n : eventsPerMin >= this.config.activityThresholds.low ? 'low'\n : 'none' as const\n\n const summary: ActivitySummaryEvent = {\n deviceId: Number(deviceId),\n periodStart: now - this.config.intervalSec * 1000,\n periodEnd: now,\n objectCounts,\n zoneActivity,\n stateChanges: [...buf.stateChanges],\n activityLevel,\n }\n\n this._lastSummary = summary\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentActivitySummary,\n { type: 'device', id: deviceId },\n summary,\n ))\n\n // Persist for timeline UI\n try {\n await this.store.insert.mutate({ collection: 'addon-settings', record: {\n id: `${deviceId}:${now}`,\n data: { ...summary },\n } })\n } catch {\n // Best-effort persistence\n }\n\n // Reset buffer\n buf.tracks.clear()\n buf.zoneEvents.length = 0\n buf.stateChanges.length = 0\n }\n }\n}\n","import { BaseAddon, asJsonObject } from '@camstack/types'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\nimport { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig } from './types.js'\nimport { DEFAULT_ENRICHMENT_CONFIG } from './types.js'\nimport { EmbeddingDispatcher } from './workers/embedding-dispatcher.js'\nimport type { EmbeddingDispatcherDeps } from './workers/embedding-dispatcher.js'\nimport { SceneStateWorker } from './workers/scene-state-worker.js'\nimport type { SceneStateWorkerDeps } from './workers/scene-state-worker.js'\nimport { ActivitySummaryWorker } from './workers/activity-summary.js'\n\n/**\n * Extended context shape injected at runtime by the server's capability wiring.\n * Not part of the base AddonContext interface because capabilities are resolved\n * after addon initialization.\n */\n\nexport class EnrichmentEngineAddon extends BaseAddon {\n private embeddingDispatcher: EmbeddingDispatcher | null = null\n private sceneStateWorker: SceneStateWorker | null = null\n private activitySummary: ActivitySummaryWorker | null = null\n /**\n * Shared shm-ring reader cache for downstream frame access. Owned by the\n * engine so the cached segments stay open across worker restarts and close\n * exactly once on shutdown.\n */\n private readers: FrameRingReaderCache | null = null\n private currentFlags = {\n embeddingEnabled: true,\n sceneMonitorEnabled: true,\n activitySummaryEnabled: true,\n }\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<void> {\n const config = await this.loadConfig()\n\n // Encoders will be injected by the server via capability wiring.\n // The server extends AddonContext with a `capabilities` object at runtime.\n // Runtime bridges: capabilities.getCollection/get return unknown because\n // the registry holds heterogeneous providers. The shapes are guaranteed\n // by the capability declaration — see project_cast_elimination_checkpoint.md\n // pattern #7.\n const encoderCollection = this.capabilities?.getCollection?.('embedding-encoder') ?? []\n const streamBrokerRaw = this.capabilities?.get?.('stream-broker')\n\n // Resolve this consumer's cluster node id once. When the runner lives in\n // a sub-broker the localNodeId is hierarchical (`hub/post-analysis`); we\n // strip the suffix so the value matches the `FrameHandle.nodeId` set by\n // the producing decoder (always the cluster-visible parent node).\n const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id\n const ownNodeId = rawNodeId.includes('/') ? rawNodeId.split('/')[0]! : rawNodeId\n\n this.readers = new FrameRingReaderCache(this.ctx.logger.child('shm-readers'))\n\n // Stable arrow held by the engine so it doesn't get re-allocated on every\n // detection event. Routes the cross-node read through the decoder cap with\n // `nodeId: handle.nodeId` per the cap's `nodeIdMode: 'routing'` default.\n // The cap schema infers `data: Uint8Array` (the wire shape); the engine\n // hands the dispatcher a `DecodedFrame` with `Buffer` so downstream\n // `extractCrop` (sharp) gets the Node-native type it expects.\n const getRemoteFrame = async (handle: FrameHandle): Promise<DecodedFrame | null> => {\n const remote = await this.ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })\n if (!remote) return null\n return {\n data: Buffer.from(remote.data),\n width: remote.width,\n height: remote.height,\n format: remote.format,\n timestamp: remote.timestamp,\n }\n }\n\n this.embeddingDispatcher = new EmbeddingDispatcher({\n config: config.embedding,\n encoders: encoderCollection as EmbeddingDispatcherDeps['encoders'],\n eventBus: this.ctx.eventBus,\n logger: this.ctx.logger.child('EmbeddingDispatcher'),\n ownNodeId,\n readers: this.readers,\n getRemoteFrame,\n })\n\n this.sceneStateWorker = new SceneStateWorker({\n config: config.sceneMonitor,\n encoders: encoderCollection as SceneStateWorkerDeps['encoders'],\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n streamBrokerRegistry: (streamBrokerRaw ?? { getSnapshot: async () => null }) as SceneStateWorkerDeps['streamBrokerRegistry'],\n logger: this.ctx.logger.child('SceneStateWorker'),\n })\n\n this.activitySummary = new ActivitySummaryWorker({\n config: config.activitySummary,\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n logger: this.ctx.logger.child('ActivitySummary'),\n })\n\n this.currentFlags = {\n embeddingEnabled: config.embedding.enabled,\n sceneMonitorEnabled: config.sceneMonitor.enabled,\n activitySummaryEnabled: config.activitySummary.enabled,\n }\n\n await this.embeddingDispatcher.start()\n await this.sceneStateWorker.start()\n await this.activitySummary.start()\n\n this.ctx.logger.info('Enrichment engine initialized with 3 workers')\n }\n\n protected async onShutdown(): Promise<void> {\n await this.embeddingDispatcher?.stop()\n await this.sceneStateWorker?.stop()\n await this.activitySummary?.stop()\n this.embeddingDispatcher = null\n this.sceneStateWorker = null\n this.activitySummary = null\n // Engine owns the shm-readers cache — close exactly here, not inside\n // the dispatcher (the cache is shared, double-close would be a bug).\n this.readers?.close()\n this.readers = null\n }\n\n // ── Three-level settings API (Phase 3) ─────────────────────────────\n //\n // Enrichment engine is post-detection infra. The three boolean toggle\n // flags (embedding/scene/activity) live in `getGlobalSettings` until\n // the dedicated post-detection UI exists. The underlying\n // EnrichmentConfig blob (stored opaquely in 'addon-settings' →\n // 'enrichment:global') is a separate concern — the flags below mirror\n // the `.enabled` field of each worker's config.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'enrichment-engine-settings',\n title: 'Enrichment Engine',\n columns: 2,\n fields: [\n {\n type: 'boolean',\n key: 'embeddingEnabled',\n label: 'Embedding Enabled',\n description: 'Compute face/object embeddings from detection crops.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'sceneMonitorEnabled',\n label: 'Scene Monitor Enabled',\n description: 'Run periodic scene state capture for change detection.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'activitySummaryEnabled',\n label: 'Activity Summary Enabled',\n description: 'Aggregate hourly activity summaries per camera.',\n default: true,\n },\n ],\n },\n ],\n })\n }\n\n async updateGlobalSettings(patch: Record<string, unknown>): Promise<void> {\n await this.ctx?.settings?.writeAddonStore(patch)\n const prev = this.currentFlags\n const next = {\n embeddingEnabled: typeof patch['embeddingEnabled'] === 'boolean' ? patch['embeddingEnabled'] : prev.embeddingEnabled,\n sceneMonitorEnabled: typeof patch['sceneMonitorEnabled'] === 'boolean' ? patch['sceneMonitorEnabled'] : prev.sceneMonitorEnabled,\n activitySummaryEnabled: typeof patch['activitySummaryEnabled'] === 'boolean' ? patch['activitySummaryEnabled'] : prev.activitySummaryEnabled,\n }\n this.currentFlags = next\n\n // Toggle workers based on flag changes\n if (prev.embeddingEnabled && !next.embeddingEnabled) {\n this.ctx?.logger.info('Stopping embedding dispatcher (disabled via config)')\n await this.embeddingDispatcher?.stop()\n } else if (!prev.embeddingEnabled && next.embeddingEnabled) {\n this.ctx?.logger.info('Starting embedding dispatcher (enabled via config)')\n await this.embeddingDispatcher?.start()\n }\n\n if (prev.sceneMonitorEnabled && !next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Stopping scene state worker (disabled via config)')\n await this.sceneStateWorker?.stop()\n } else if (!prev.sceneMonitorEnabled && next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Starting scene state worker (enabled via config)')\n await this.sceneStateWorker?.start()\n }\n\n if (prev.activitySummaryEnabled && !next.activitySummaryEnabled) {\n this.ctx?.logger.info('Stopping activity summary worker (disabled via config)')\n await this.activitySummary?.stop()\n } else if (!prev.activitySummaryEnabled && next.activitySummaryEnabled) {\n this.ctx?.logger.info('Starting activity summary worker (enabled via config)')\n await this.activitySummary?.start()\n }\n\n this.ctx?.logger.info('Enrichment engine flags updated', { meta: { flags: this.currentFlags } })\n }\n\n private async loadConfig(): Promise<EnrichmentConfig> {\n try {\n const stored = asJsonObject(await this.ctx.api?.settingsStore.get.query({ collection: 'addon-settings', key: 'enrichment:global' }))\n if (stored) {\n // Shallow structural merge: only keys from DEFAULT_ENRICHMENT_CONFIG\n // are preserved; stored values override their counterparts when\n // present with a matching type.\n const merged: EnrichmentConfig = { ...DEFAULT_ENRICHMENT_CONFIG, ...stored }\n return merged\n }\n } catch {\n // First boot — use defaults\n }\n return DEFAULT_ENRICHMENT_CONFIG\n }\n}\n\n// Default export — kernel addon-loader prefers mod.default to identify the addon class\nexport default EnrichmentEngineAddon\n"],"names":["EventCategory","createEvent","z.object","z.number","z.tuple","z.string","z.array","z.boolean","z.enum","BaseAddon","FrameRingReaderCache","asJsonObject"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/enrichment-engine/types.ts","../../src/enrichment-engine/services/crop-extractor.ts","../../src/enrichment-engine/services/resolve-frame.ts","../../src/enrichment-engine/workers/embedding-dispatcher.ts","../../src/_analytics-schemas/persistence-records.ts","../../src/enrichment-engine/services/text-embedding-cache.ts","../../src/enrichment-engine/workers/scene-state-worker.ts","../../src/enrichment-engine/workers/activity-summary.ts","../../src/enrichment-engine/index.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Enrichment Engine — Types\n// ---------------------------------------------------------------------------\n\n// --- Normalized Rectangle (0-1 coordinate space) ---\n\nexport interface NormalizedRect {\n readonly x: number // 0-1, left edge\n readonly y: number // 0-1, top edge\n readonly w: number // 0-1, width\n readonly h: number // 0-1, height\n}\n\n// --- Scene State ---\n\nexport interface SceneStateDefinition {\n readonly id: string\n readonly label: string // e.g. \"open\", \"closed\"\n readonly textPrompts: readonly string[] // zero-shot: [\"an open gate\"]\n readonly referenceEmbeddings: readonly (readonly number[])[] // optional: higher accuracy\n readonly notify: boolean\n readonly severity: 'info' | 'warning' | 'alert'\n}\n\nexport interface SceneMonitor {\n readonly id: string\n readonly label: string // e.g. \"Main Gate\"\n readonly roi: NormalizedRect // region of interest\n readonly enabled: boolean\n readonly states: readonly SceneStateDefinition[]\n}\n\nexport interface SceneMonitorConfig {\n readonly monitors: readonly SceneMonitor[]\n}\n\n// --- Enrichment Config (global) ---\n\nexport interface EnrichmentConfig {\n readonly enabled: boolean\n\n readonly embedding: {\n readonly enabled: boolean\n readonly modelId: string // 'clip-vit-b32' | 'clip-vit-b16' | 'siglip2-b16-256'\n readonly agentId: string | 'local'\n readonly runtime: 'node' | 'python'\n readonly backend: 'cpu' | 'coreml' | 'cuda'\n readonly classes: readonly string[] // [] = all classes\n readonly minConfidence: number\n readonly maxPerSecPerCamera: number\n readonly cropStrategy: 'first' | 'best-confidence' | 'track-end'\n readonly retentionDays: number\n }\n\n readonly sceneMonitor: {\n readonly enabled: boolean\n readonly modelId: string // can differ from embedding model\n readonly pollIntervalSec: number\n readonly hysteresisCount: number\n }\n\n readonly activitySummary: {\n readonly enabled: boolean\n readonly intervalSec: number\n readonly activityThresholds: {\n readonly low: number\n readonly medium: number\n readonly high: number\n }\n }\n}\n\n// --- Events ---\n\nexport interface EmbeddingStoredEvent {\n readonly deviceId: number\n readonly trackId: string\n readonly class: string\n readonly embeddingId: string // reference in vector store\n readonly modelId: string\n readonly embeddingDim: number\n readonly inferenceMs: number\n readonly timestamp: number\n}\n\nexport interface SceneStateChangedEvent {\n readonly deviceId: number\n readonly monitorId: string\n readonly monitorLabel: string\n readonly previousState: string\n readonly currentState: string\n readonly confidence: number\n readonly timestamp: number\n}\n\nexport interface ZoneActivityEntry {\n readonly zoneId: string\n readonly entries: number\n readonly exits: number\n readonly avgDwellMs: number\n}\n\nexport interface StateChangeEntry {\n readonly monitorId: string\n readonly from: string\n readonly to: string\n readonly timestamp: number\n}\n\nexport interface ActivitySummaryEvent {\n readonly deviceId: number\n readonly periodStart: number\n readonly periodEnd: number\n readonly objectCounts: Readonly<Record<string, number>>\n readonly zoneActivity: readonly ZoneActivityEntry[]\n readonly stateChanges: readonly StateChangeEntry[]\n readonly activityLevel: 'none' | 'low' | 'medium' | 'high'\n}\n\n// --- Defaults ---\n\nexport const DEFAULT_ENRICHMENT_CONFIG: EnrichmentConfig = {\n enabled: false,\n\n embedding: {\n enabled: false,\n modelId: 'clip-vit-b32',\n agentId: 'local',\n runtime: 'node',\n backend: 'cpu',\n classes: [],\n minConfidence: 0.5,\n maxPerSecPerCamera: 1,\n cropStrategy: 'first',\n retentionDays: 30,\n },\n\n sceneMonitor: {\n enabled: false,\n modelId: 'clip-vit-b32',\n pollIntervalSec: 10,\n hysteresisCount: 3,\n },\n\n activitySummary: {\n enabled: false,\n intervalSec: 60,\n activityThresholds: {\n low: 2,\n medium: 10,\n high: 30,\n },\n },\n}\n","import sharp from 'sharp'\n\n/**\n * Extracts a JPEG-encoded crop from a raw frame buffer using a normalized bounding box.\n * Coordinates are clamped to frame bounds to avoid out-of-range errors.\n */\nexport async function extractCrop(\n frameData: Buffer,\n frameWidth: number,\n frameHeight: number,\n bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number },\n): Promise<{ crop: Buffer; width: number; height: number }> {\n // Convert normalized coordinates to pixel values\n const rawLeft = Math.round(bbox.x * frameWidth)\n const rawTop = Math.round(bbox.y * frameHeight)\n const rawWidth = Math.round(bbox.w * frameWidth)\n const rawHeight = Math.round(bbox.h * frameHeight)\n\n // Clamp to frame bounds\n const left = Math.max(0, Math.min(rawLeft, frameWidth - 1))\n const top = Math.max(0, Math.min(rawTop, frameHeight - 1))\n const width = Math.max(1, Math.min(rawWidth, frameWidth - left))\n const height = Math.max(1, Math.min(rawHeight, frameHeight - top))\n\n const crop = await sharp(frameData, {\n raw: { width: frameWidth, height: frameHeight, channels: 3 },\n })\n .extract({ left, top, width, height })\n .jpeg({ quality: 90 })\n .toBuffer()\n\n return { crop, width, height }\n}\n","/**\n * `resolveFrame` — turn a serialisable `FrameHandle` back into pixels.\n *\n * The handle's `nodeId` decides the path: when the consumer is co-located\n * with the producing decoder, the pixels are read directly from the shared\n * memory ring (`readers.read(handle)`, a `null` means the slot was recycled\n * before the consumer got to it — a dropped frame, the correct latest-wins\n * behaviour for live video). Otherwise the caller-supplied\n * `getRemoteFrame(handle)` performs a node-routed `decoder.getFrame` call.\n *\n * Keeping the remote fetch as an injected callback keeps this helper free of\n * any cap-client / tRPC coupling and trivially testable.\n */\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\n\nexport interface ResolveFrameDeps {\n /** Cluster node id of the consumer — used to detect co-location. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the caller to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\n/**\n * Resolve the pixels a `FrameHandle` refers to. Local shm read when the\n * handle's `nodeId` matches `deps.ownNodeId`, else routed via\n * `deps.getRemoteFrame`. Returns `null` when the frame is no longer\n * available (slot recycled locally, or the remote node reports no frame).\n */\nexport async function resolveFrame(\n handle: FrameHandle,\n deps: ResolveFrameDeps,\n): Promise<DecodedFrame | null> {\n if (handle.nodeId === deps.ownNodeId) {\n return deps.readers.read(handle)\n }\n return deps.getRemoteFrame(handle)\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type {\n DecodedFrame,\n FrameHandle,\n IEventBus,\n IScopedLogger,\n TypedSystemEvent,\n} from '@camstack/types'\nimport type { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig, EmbeddingStoredEvent } from '../types.js'\nimport { extractCrop } from '../services/crop-extractor.js'\nimport { resolveFrame } from '../services/resolve-frame.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface PendingCrop {\n readonly trackId: string\n readonly deviceId: string\n readonly class: string\n readonly confidence: number\n readonly frameData: Buffer\n readonly frameWidth: number\n readonly frameHeight: number\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n readonly receivedAt: number\n}\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly score: number\n readonly trackId?: string\n readonly bbox: { readonly x: number; readonly y: number; readonly w: number; readonly h: number }\n }\n readonly objectState?: {\n readonly state: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\nexport interface EmbeddingDispatcherDeps {\n readonly config: EnrichmentConfig['embedding']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly logger: IScopedLogger\n /** Cluster node id of the consumer — used to detect co-location with the decoder. */\n readonly ownNodeId: string\n /** Opens (and caches) shm segments and reads a `FrameHandle` to pixels. */\n readonly readers: FrameRingReaderCache\n /**\n * Cross-node frame fetch. Wired by the engine to\n * `ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })`.\n */\n readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n}\n\nexport class EmbeddingDispatcher {\n private readonly config: EnrichmentConfig['embedding']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly logger: IScopedLogger\n private readonly ownNodeId: string\n private readonly readers: FrameRingReaderCache\n private readonly getRemoteFrame: (handle: FrameHandle) => Promise<DecodedFrame | null>\n\n private readonly lastEmbedTime = new Map<string, number>()\n private readonly pendingCrops = new Map<string, PendingCrop>()\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private unsubscribe: (() => void) | null = null\n\n private _processedCount = 0\n private _totalInferenceMs = 0\n private _encoderIndex = 0\n\n constructor(deps: EmbeddingDispatcherDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.logger = deps.logger\n this.ownNodeId = deps.ownNodeId\n this.readers = deps.readers\n this.getRemoteFrame = deps.getRemoteFrame\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('EmbeddingDispatcher disabled')\n return\n }\n\n this.unsubscribe = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { void this.handleDetectionResult(event) },\n )\n\n // Flush timer for best-confidence strategy\n if (this.config.cropStrategy === 'best-confidence') {\n this.flushTimer = setInterval(() => { void this.flushPending() }, 1000)\n }\n\n this.logger.info('EmbeddingDispatcher started', { meta: { strategy: this.config.cropStrategy, maxPerSec: this.config.maxPerSecPerCamera } })\n }\n\n async stop(): Promise<void> {\n this.unsubscribe?.()\n this.unsubscribe = null\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n // Flush remaining\n await this.flushPending()\n }\n\n get processedCount(): number { return this._processedCount }\n get avgInferenceMs(): number { return this._processedCount > 0 ? this._totalInferenceMs / this._processedCount : 0 }\n get queueDepth(): number { return this.pendingCrops.size }\n\n private async handleDetectionResult(event: TypedSystemEvent<'detection.result'>): Promise<void> {\n const data = event.data\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const detections = (data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n\n // Resolve the underlying frame once per event via the shm frame plane.\n // The handle's `nodeId` decides the path — local shm read when co-located\n // with the producing decoder, else a node-routed `decoder.getFrame` call.\n const handle = data.frameHandle\n if (!handle) {\n this.logger.debug('skip: no frameHandle on DetectionResult', {\n meta: { deviceId },\n })\n return\n }\n\n let decoded: DecodedFrame | null\n try {\n decoded = await resolveFrame(handle, {\n ownNodeId: this.ownNodeId,\n readers: this.readers,\n getRemoteFrame: this.getRemoteFrame,\n })\n } catch (err) {\n this.logger.debug('skip: resolveFrame threw', {\n meta: { deviceId, shmId: handle.shmId, error: String(err) },\n })\n return\n }\n\n if (!decoded) {\n this.logger.debug('skip: frame recycled before resolve', {\n meta: { deviceId, shmId: handle.shmId },\n })\n return\n }\n\n // Defensive: the live detection stream is decoded as RGB by the pipeline\n // runner. `extractCrop` interprets the buffer as raw RGB — bail if not.\n if (decoded.format !== 'rgb') {\n this.logger.debug('skip: resolved frame is not RGB', {\n meta: { deviceId, format: decoded.format },\n })\n return\n }\n\n const frameData = Buffer.isBuffer(decoded.data) ? decoded.data : Buffer.from(decoded.data)\n const frameWidth = decoded.width\n const frameHeight = decoded.height\n\n for (const det of detections) {\n const detection = det.detection\n if (!detection) continue\n\n // Class filter\n if (this.config.classes.length > 0 && !this.config.classes.includes(detection.class)) continue\n\n // Confidence filter\n if (detection.score < this.config.minConfidence) continue\n\n // Rate limit\n const now = Date.now()\n const minInterval = 1000 / this.config.maxPerSecPerCamera\n const lastTime = this.lastEmbedTime.get(deviceId) ?? 0\n if (now - lastTime < minInterval) continue\n\n const trackId = detection.trackId ?? `${deviceId}-${now}`\n\n const pending: PendingCrop = {\n trackId,\n deviceId,\n class: detection.class,\n confidence: detection.score,\n frameData,\n frameWidth,\n frameHeight,\n bbox: detection.bbox,\n receivedAt: now,\n }\n\n switch (this.config.cropStrategy) {\n case 'first': {\n if (!this.pendingCrops.has(trackId)) {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n }\n break\n }\n case 'best-confidence': {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n break\n }\n case 'track-end': {\n const state = det.objectState?.state\n if (state === 'leaving') {\n this.pendingCrops.set(trackId, pending)\n void this.processOne(pending)\n } else {\n const existing = this.pendingCrops.get(trackId)\n if (!existing || pending.confidence > existing.confidence) {\n this.pendingCrops.set(trackId, pending)\n }\n }\n break\n }\n }\n }\n }\n\n private async flushPending(): Promise<void> {\n const now = Date.now()\n const toFlush: PendingCrop[] = []\n\n for (const [trackId, pending] of this.pendingCrops) {\n if (now - pending.receivedAt > 3000) {\n toFlush.push(pending)\n this.pendingCrops.delete(trackId)\n }\n }\n\n await Promise.all(toFlush.map(p => this.processOne(p)))\n }\n\n private async processOne(pending: PendingCrop): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n const { crop, width, height } = await extractCrop(\n pending.frameData, pending.frameWidth, pending.frameHeight, pending.bbox,\n )\n\n // Round-robin across encoders\n const encoder = this.encoders[this._encoderIndex % this.encoders.length]!\n this._encoderIndex++\n\n const { embedding: _embedding, inferenceMs } = await encoder.encode(crop, width, height)\n const info = encoder.getInfo()\n\n this._processedCount++\n this._totalInferenceMs += inferenceMs\n this.lastEmbedTime.set(pending.deviceId, Date.now())\n this.pendingCrops.delete(pending.trackId)\n\n const embeddingId = `${pending.deviceId}/${pending.trackId}/${Date.now()}`\n\n const payload: EmbeddingStoredEvent = {\n deviceId: Number(pending.deviceId),\n trackId: pending.trackId,\n class: pending.class,\n embeddingId,\n modelId: info.modelId,\n embeddingDim: info.embeddingDim,\n inferenceMs,\n timestamp: Date.now(),\n }\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentEmbeddingStored,\n { type: 'addon', id: 'enrichment-engine' },\n payload,\n ))\n\n this.logger.debug('Embedded track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { class: pending.class, trackId: pending.trackId, inferenceMs: Number(inferenceMs.toFixed(1)) },\n })\n } catch (err) {\n this.logger.warn('Failed to embed track', {\n tags: { deviceId: Number(pending.deviceId) },\n meta: { trackId: pending.trackId, error: String(err) },\n })\n }\n }\n}\n","/**\n * Zod schemas for analytics-suite persisted record types.\n * Used by persistence services to parse settings-store query results.\n */\nimport { z } from 'zod'\n\n// ── Track Trail ─────────────────────────────────────────────────────\n\nexport const TrackPositionSchema = z.object({\n x: z.number(),\n y: z.number(),\n timestamp: z.number(),\n bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]),\n})\n\nexport const TrackSnapshotSchema = z.object({\n timestamp: z.number(),\n position: TrackPositionSchema,\n thumbnailPath: z.string(),\n})\n\nexport const TrackTrailSchema = z.object({\n trackId: z.string(),\n deviceId: z.string(),\n className: z.string(),\n label: z.string().optional(),\n firstSeen: z.number(),\n lastSeen: z.number(),\n positions: z.array(TrackPositionSchema),\n snapshots: z.array(TrackSnapshotSchema),\n totalDistance: z.number(),\n zonesVisited: z.array(z.string()),\n active: z.boolean(),\n})\n\nexport type TrackTrail = z.infer<typeof TrackTrailSchema>\n\n// ── Scene Monitor ───────────────────────────────────────────────────\n\nexport const SceneMonitorConfigSchema = z.object({\n monitors: z.array(z.object({\n id: z.string(),\n label: z.string(),\n roi: z.object({ x: z.number(), y: z.number(), w: z.number(), h: z.number() }),\n enabled: z.boolean(),\n states: z.array(z.object({\n id: z.string(),\n label: z.string(),\n prompt: z.string().optional(),\n textPrompts: z.array(z.string()).optional(),\n referenceEmbeddings: z.array(z.array(z.number())).optional(),\n threshold: z.number().optional(),\n notify: z.boolean().optional(),\n severity: z.enum(['info', 'warning', 'alert']).optional(),\n })),\n })),\n})\n\nexport type SceneMonitorConfig = z.infer<typeof SceneMonitorConfigSchema>\n\n/** Single scene monitor entry (element of SceneMonitorConfig.monitors). */\nexport type SceneMonitor = SceneMonitorConfig['monitors'][number]\n","/**\n * In-memory cache for text prompt embeddings.\n *\n * Keys follow the convention `${monitorId}:${text}` so that all entries\n * belonging to a specific scene monitor can be invalidated in one call.\n */\nexport class TextEmbeddingCache {\n private readonly cache = new Map<string, Float32Array>()\n\n get(key: string): Float32Array | undefined {\n return this.cache.get(key)\n }\n\n set(key: string, embedding: Float32Array): void {\n this.cache.set(key, embedding)\n }\n\n has(key: string): boolean {\n return this.cache.has(key)\n }\n\n /**\n * Deletes all entries whose key starts with `${monitorId}:`.\n * Used when a scene monitor's text prompts are updated.\n */\n invalidateMonitor(monitorId: string): void {\n const prefix = `${monitorId}:`\n for (const key of this.cache.keys()) {\n if (key.startsWith(prefix)) {\n this.cache.delete(key)\n }\n }\n }\n\n clear(): void {\n this.cache.clear()\n }\n\n get size(): number {\n return this.cache.size\n }\n}\n","import { EventCategory } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient } from '@camstack/types'\nimport type { EnrichmentConfig, SceneStateChangedEvent } from '../types.js'\nimport { SceneMonitorConfigSchema, type SceneMonitor } from '../../_analytics-schemas/persistence-records.js'\nimport { TextEmbeddingCache } from '../services/text-embedding-cache.js'\nimport { extractCrop } from '../services/crop-extractor.js'\n\ninterface EmbeddingEncoder {\n encode(crop: Buffer, width: number, height: number): Promise<{ embedding: Float32Array; inferenceMs: number }>\n encodeText(text: string): Promise<{ embedding: Float32Array }>\n getInfo(): { modelId: string; embeddingDim: number; ready: boolean }\n}\n\ninterface StreamBrokerRegistry {\n getSnapshot(deviceId: string): Promise<{ data: Buffer; width: number; height: number } | null>\n}\n\ninterface CameraMonitorState {\n currentState: string | null\n pendingState: string | null\n pendingCount: number\n pendingConfidence: number\n}\n\nexport interface SceneStateWorkerDeps {\n readonly config: EnrichmentConfig['sceneMonitor']\n readonly encoders: readonly EmbeddingEncoder[]\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly streamBrokerRegistry: StreamBrokerRegistry\n readonly logger: IScopedLogger\n}\n\nexport class SceneStateWorker {\n private readonly config: EnrichmentConfig['sceneMonitor']\n private readonly encoders: readonly EmbeddingEncoder[]\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly streamBrokerRegistry: StreamBrokerRegistry\n private readonly logger: IScopedLogger\n\n private readonly textCache = new TextEmbeddingCache()\n private readonly monitorStates = new Map<string, CameraMonitorState>()\n private pollTimer: ReturnType<typeof setInterval> | null = null\n private _activeCount = 0\n\n constructor(deps: SceneStateWorkerDeps) {\n this.config = deps.config\n this.encoders = deps.encoders\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.streamBrokerRegistry = deps.streamBrokerRegistry\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('SceneStateWorker disabled')\n return\n }\n\n this.pollTimer = setInterval(\n () => { void this.pollAll() },\n this.config.pollIntervalSec * 1000,\n )\n this.logger.info('SceneStateWorker started', { meta: { pollIntervalSec: this.config.pollIntervalSec, hysteresis: this.config.hysteresisCount } })\n }\n\n async stop(): Promise<void> {\n if (this.pollTimer) {\n clearInterval(this.pollTimer)\n this.pollTimer = null\n }\n }\n\n get activeCount(): number { return this._activeCount }\n\n private async pollAll(): Promise<void> {\n // Load all scene monitor configs from settings\n const rawConfigs = await this.store.query.query({ collection: 'device-settings', filter: {\n where: { id: 'enrichment:scene-monitor:%' },\n } })\n const allConfigs = rawConfigs.map(r => ({ id: r.id, data: SceneMonitorConfigSchema.parse(r.data) }))\n\n const deviceMonitors = new Map<string, SceneMonitor[]>()\n for (const record of allConfigs) {\n const deviceId = record.id.replace('enrichment:scene-monitor:', '')\n const config = record.data\n const active = config.monitors.filter(m => m.enabled)\n if (active.length > 0) {\n deviceMonitors.set(deviceId, active)\n }\n }\n\n this._activeCount = [...deviceMonitors.values()].reduce((sum, ms) => sum + ms.length, 0)\n\n // Process each camera (1 snapshot per camera)\n await Promise.all(\n [...deviceMonitors.entries()].map(([deviceId, monitors]) =>\n this.pollCamera(deviceId, monitors),\n ),\n )\n }\n\n private async pollCamera(deviceId: string, monitors: SceneMonitor[]): Promise<void> {\n if (this.encoders.length === 0) return\n\n try {\n // Capture 1 snapshot\n const snapshot = await this.streamBrokerRegistry.getSnapshot(deviceId)\n if (!snapshot) return\n\n const encoder = this.encoders[0]!\n\n // Process each monitor (crop from same snapshot)\n for (const monitor of monitors) {\n try {\n await this.processMonitor(deviceId, monitor, snapshot, encoder)\n } catch (err) {\n this.logger.warn('SceneState error', { tags: { deviceId: Number(deviceId) }, meta: { monitorLabel: monitor.label, error: String(err) } })\n }\n }\n } catch (err) {\n this.logger.warn('Failed to capture snapshot', { tags: { deviceId: Number(deviceId) }, meta: { error: String(err) } })\n }\n }\n\n private async processMonitor(\n deviceId: string,\n monitor: SceneMonitor,\n snapshot: { data: Buffer; width: number; height: number },\n encoder: EmbeddingEncoder,\n ): Promise<void> {\n // Crop ROI (normalized → pixel coords)\n const bbox = {\n x: monitor.roi.x * snapshot.width,\n y: monitor.roi.y * snapshot.height,\n w: monitor.roi.w * snapshot.width,\n h: monitor.roi.h * snapshot.height,\n }\n const { crop, width, height } = await extractCrop(snapshot.data, snapshot.width, snapshot.height, bbox)\n\n // Encode crop\n const { embedding: imageEmb } = await encoder.encode(crop, width, height)\n\n // Find best matching state\n let bestState: string | null = null\n let bestScore = -1\n\n for (const state of monitor.states) {\n let score = 0\n\n if (state.referenceEmbeddings && state.referenceEmbeddings.length > 0) {\n for (const refEmb of state.referenceEmbeddings) {\n const ref = new Float32Array(refEmb)\n const sim = cosineSimilarity(imageEmb, ref)\n score = Math.max(score, sim)\n }\n } else if (state.textPrompts && state.textPrompts.length > 0) {\n for (const prompt of state.textPrompts) {\n const cacheKey = `${monitor.id}:${state.id}:${prompt}`\n let textEmb = this.textCache.get(cacheKey)\n if (!textEmb) {\n const result = await encoder.encodeText(prompt)\n textEmb = result.embedding\n this.textCache.set(cacheKey, textEmb)\n }\n const sim = cosineSimilarity(imageEmb, textEmb)\n score = Math.max(score, sim)\n }\n }\n\n if (score > bestScore) {\n bestScore = score\n bestState = state.label\n }\n }\n\n if (!bestState) return\n\n // Hysteresis\n const key = `${deviceId}:${monitor.id}`\n let ms = this.monitorStates.get(key)\n if (!ms) {\n ms = { currentState: null, pendingState: null, pendingCount: 0, pendingConfidence: 0 }\n this.monitorStates.set(key, ms)\n }\n\n if (bestState === ms.pendingState) {\n ms.pendingCount++\n ms.pendingConfidence = bestScore\n } else {\n ms.pendingState = bestState\n ms.pendingCount = 1\n ms.pendingConfidence = bestScore\n }\n\n if (ms.pendingCount < this.config.hysteresisCount) return\n if (bestState === ms.currentState) return\n\n // State changed!\n const previousState = ms.currentState ?? 'unknown'\n ms.currentState = bestState\n ms.pendingState = null\n ms.pendingCount = 0\n\n const payload: SceneStateChangedEvent = {\n deviceId: Number(deviceId),\n monitorId: monitor.id,\n monitorLabel: monitor.label,\n previousState,\n currentState: bestState,\n confidence: bestScore,\n timestamp: Date.now(),\n }\n\n const data: Record<string, unknown> = { ...payload }\n this.eventBus.emit({\n id: `scene-state-${deviceId}-${monitor.id}-${Date.now()}`,\n category: EventCategory.EnrichmentSceneStateChanged,\n source: { type: 'device', id: deviceId },\n timestamp: new Date(),\n data,\n })\n\n this.logger.info('Scene state changed', {\n tags: { deviceId: Number(deviceId) },\n meta: { monitorLabel: monitor.label, previousState, currentState: bestState, confidence: Number(bestScore.toFixed(2)) },\n })\n }\n}\n\nfunction cosineSimilarity(a: Float32Array, b: Float32Array): number {\n let dot = 0\n let normA = 0\n let normB = 0\n for (let i = 0; i < a.length; i++) {\n dot += a[i]! * b[i]!\n normA += a[i]! * a[i]!\n normB += b[i]! * b[i]!\n }\n return dot / (Math.sqrt(normA) * Math.sqrt(normB))\n}\n","import { EventCategory, createEvent } from '@camstack/types'\nimport type { IEventBus, IScopedLogger, SettingsStoreClient, TypedSystemEvent } from '@camstack/types'\nimport type { EnrichmentConfig, ActivitySummaryEvent, StateChangeEntry } from '../types.js'\n\n/** Local narrow of a pipeline analysis result item (runtime shape from detection pipeline). */\ninterface PipelineAnalysisItem {\n readonly detection?: {\n readonly class: string\n readonly trackId?: string\n }\n readonly zoneEvents?: readonly { readonly zoneId: string; readonly type: string; readonly timestamp?: number }[]\n}\n\ninterface TrackInfo {\n class: string\n deviceId: string\n firstSeen: number\n lastSeen: number\n zones: Set<string>\n}\n\ninterface ZoneEventInfo {\n type: 'enter' | 'exit'\n zoneId: string\n trackId: string\n timestamp: number\n}\n\ninterface ActivityBuffer {\n tracks: Map<string, TrackInfo>\n zoneEvents: ZoneEventInfo[]\n stateChanges: StateChangeEntry[]\n}\n\nexport interface ActivitySummaryWorkerDeps {\n readonly config: EnrichmentConfig['activitySummary']\n readonly eventBus: IEventBus\n readonly store: SettingsStoreClient\n readonly logger: IScopedLogger\n}\n\nexport class ActivitySummaryWorker {\n private readonly config: EnrichmentConfig['activitySummary']\n private readonly eventBus: IEventBus\n private readonly store: SettingsStoreClient\n private readonly logger: IScopedLogger\n\n private readonly buffers = new Map<string, ActivityBuffer>()\n private summaryTimer: ReturnType<typeof setInterval> | null = null\n private unsubDetection: (() => void) | null = null\n private unsubSceneState: (() => void) | null = null\n private _lastSummary: ActivitySummaryEvent | null = null\n\n constructor(deps: ActivitySummaryWorkerDeps) {\n this.config = deps.config\n this.eventBus = deps.eventBus\n this.store = deps.store\n this.logger = deps.logger\n }\n\n async start(): Promise<void> {\n if (!this.config.enabled) {\n this.logger.info('ActivitySummaryWorker disabled')\n return\n }\n\n this.unsubDetection = this.eventBus.subscribe(\n { category: EventCategory.DetectionResult },\n (event) => { this.handleDetection(event) },\n )\n\n this.unsubSceneState = this.eventBus.subscribe(\n { category: EventCategory.EnrichmentSceneStateChanged },\n (event) => { this.handleSceneStateChange(event) },\n )\n\n this.summaryTimer = setInterval(\n () => { void this.emitSummaries() },\n this.config.intervalSec * 1000,\n )\n\n this.logger.info('ActivitySummaryWorker started', { meta: { intervalSec: this.config.intervalSec } })\n }\n\n async stop(): Promise<void> {\n this.unsubDetection?.()\n this.unsubSceneState?.()\n if (this.summaryTimer) {\n clearInterval(this.summaryTimer)\n this.summaryTimer = null\n }\n // Emit final summaries\n await this.emitSummaries()\n }\n\n get lastSummary(): ActivitySummaryEvent | null { return this._lastSummary }\n\n private getBuffer(deviceId: string): ActivityBuffer {\n let buf = this.buffers.get(deviceId)\n if (!buf) {\n buf = { tracks: new Map(), zoneEvents: [], stateChanges: [] }\n this.buffers.set(deviceId, buf)\n }\n return buf\n }\n\n private handleDetection(event: TypedSystemEvent<'detection.result'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n // analysisResults items are untyped pipeline outputs — narrow to local interface at boundary\n const results = (event.data.analysisResults as readonly PipelineAnalysisItem[]) ?? []\n const buf = this.getBuffer(deviceId)\n const now = Date.now()\n\n for (const det of results) {\n const detection = det.detection\n if (!detection) continue\n\n const trackId = detection.trackId ?? `unknown-${now}`\n const existing = buf.tracks.get(trackId)\n\n if (existing) {\n existing.lastSeen = now\n } else {\n buf.tracks.set(trackId, {\n class: detection.class,\n deviceId,\n firstSeen: now,\n lastSeen: now,\n zones: new Set(),\n })\n }\n\n // Zone events\n const zoneEvents = det.zoneEvents ?? []\n for (const ze of zoneEvents) {\n const track = buf.tracks.get(trackId)\n if (track) track.zones.add(ze.zoneId)\n\n if (ze.type === 'zone-enter' || ze.type === 'zone-exit') {\n buf.zoneEvents.push({\n type: ze.type === 'zone-enter' ? 'enter' : 'exit',\n zoneId: ze.zoneId,\n trackId,\n timestamp: ze.timestamp ?? now,\n })\n }\n }\n }\n }\n\n private handleSceneStateChange(event: TypedSystemEvent<'enrichment.scene.state-changed'>): void {\n const deviceId = event.source.id !== undefined ? String(event.source.id) : ''\n if (!deviceId) return\n\n const data = event.data\n const buf = this.getBuffer(deviceId)\n buf.stateChanges.push({\n monitorId: data.monitorId,\n from: data.previousState,\n to: data.currentState,\n timestamp: data.timestamp,\n })\n }\n\n private async emitSummaries(): Promise<void> {\n const now = Date.now()\n\n for (const [deviceId, buf] of this.buffers) {\n if (buf.tracks.size === 0 && buf.zoneEvents.length === 0 && buf.stateChanges.length === 0) {\n continue\n }\n\n // Object counts by class\n const objectCounts: Record<string, number> = {}\n for (const track of buf.tracks.values()) {\n objectCounts[track.class] = (objectCounts[track.class] ?? 0) + 1\n }\n\n // Zone activity\n const zoneMap = new Map<string, { entries: number; exits: number; dwellTimes: number[] }>()\n for (const ze of buf.zoneEvents) {\n let z = zoneMap.get(ze.zoneId)\n if (!z) {\n z = { entries: 0, exits: 0, dwellTimes: [] }\n zoneMap.set(ze.zoneId, z)\n }\n if (ze.type === 'enter') z.entries++\n else z.exits++\n }\n\n const zoneActivity = [...zoneMap.entries()].map(([zoneId, z]) => ({\n zoneId,\n entries: z.entries,\n exits: z.exits,\n avgDwellMs: z.dwellTimes.length > 0\n ? z.dwellTimes.reduce((a, b) => a + b, 0) / z.dwellTimes.length\n : 0,\n }))\n\n // Activity level\n const totalEvents = buf.tracks.size + buf.zoneEvents.length\n const eventsPerMin = totalEvents / (this.config.intervalSec / 60)\n const activityLevel = eventsPerMin >= this.config.activityThresholds.high ? 'high'\n : eventsPerMin >= this.config.activityThresholds.medium ? 'medium'\n : eventsPerMin >= this.config.activityThresholds.low ? 'low'\n : 'none' as const\n\n const summary: ActivitySummaryEvent = {\n deviceId: Number(deviceId),\n periodStart: now - this.config.intervalSec * 1000,\n periodEnd: now,\n objectCounts,\n zoneActivity,\n stateChanges: [...buf.stateChanges],\n activityLevel,\n }\n\n this._lastSummary = summary\n\n this.eventBus.emit(createEvent(\n EventCategory.EnrichmentActivitySummary,\n { type: 'device', id: deviceId },\n summary,\n ))\n\n // Persist for timeline UI\n try {\n await this.store.insert.mutate({ collection: 'addon-settings', record: {\n id: `${deviceId}:${now}`,\n data: { ...summary },\n } })\n } catch {\n // Best-effort persistence\n }\n\n // Reset buffer\n buf.tracks.clear()\n buf.zoneEvents.length = 0\n buf.stateChanges.length = 0\n }\n }\n}\n","import { BaseAddon, asJsonObject } from '@camstack/types'\nimport type { DecodedFrame, FrameHandle } from '@camstack/types'\nimport { FrameRingReaderCache } from '@camstack/shm-ring'\nimport type { EnrichmentConfig } from './types.js'\nimport { DEFAULT_ENRICHMENT_CONFIG } from './types.js'\nimport { EmbeddingDispatcher } from './workers/embedding-dispatcher.js'\nimport type { EmbeddingDispatcherDeps } from './workers/embedding-dispatcher.js'\nimport { SceneStateWorker } from './workers/scene-state-worker.js'\nimport type { SceneStateWorkerDeps } from './workers/scene-state-worker.js'\nimport { ActivitySummaryWorker } from './workers/activity-summary.js'\n\n/**\n * Extended context shape injected at runtime by the server's capability wiring.\n * Not part of the base AddonContext interface because capabilities are resolved\n * after addon initialization.\n */\n\nexport class EnrichmentEngineAddon extends BaseAddon {\n private embeddingDispatcher: EmbeddingDispatcher | null = null\n private sceneStateWorker: SceneStateWorker | null = null\n private activitySummary: ActivitySummaryWorker | null = null\n /**\n * Shared shm-ring reader cache for downstream frame access. Owned by the\n * engine so the cached segments stay open across worker restarts and close\n * exactly once on shutdown.\n */\n private readers: FrameRingReaderCache | null = null\n private currentFlags = {\n embeddingEnabled: true,\n sceneMonitorEnabled: true,\n activitySummaryEnabled: true,\n }\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<void> {\n const config = await this.loadConfig()\n\n // Encoders will be injected by the server via capability wiring.\n // The server extends AddonContext with a `capabilities` object at runtime.\n // Runtime bridges: capabilities.getCollection/get return unknown because\n // the registry holds heterogeneous providers. The shapes are guaranteed\n // by the capability declaration — see project_cast_elimination_checkpoint.md\n // pattern #7.\n const encoderCollection = this.capabilities?.getCollection?.('embedding-encoder') ?? []\n const streamBrokerRaw = this.capabilities?.get?.('stream-broker')\n\n // Resolve this consumer's cluster node id once. When the runner lives in\n // a sub-broker the localNodeId is hierarchical (`hub/post-analysis`); we\n // strip the suffix so the value matches the `FrameHandle.nodeId` set by\n // the producing decoder (always the cluster-visible parent node).\n const rawNodeId = this.ctx.kernel.localNodeId ?? this.ctx.id\n const ownNodeId = rawNodeId.includes('/') ? rawNodeId.split('/')[0]! : rawNodeId\n\n this.readers = new FrameRingReaderCache(this.ctx.logger.child('shm-readers'))\n\n // Stable arrow held by the engine so it doesn't get re-allocated on every\n // detection event. Routes the cross-node read through the decoder cap with\n // `nodeId: handle.nodeId` per the cap's `nodeIdMode: 'routing'` default.\n // The cap schema infers `data: Uint8Array` (the wire shape); the engine\n // hands the dispatcher a `DecodedFrame` with `Buffer` so downstream\n // `extractCrop` (sharp) gets the Node-native type it expects.\n const getRemoteFrame = async (handle: FrameHandle): Promise<DecodedFrame | null> => {\n const remote = await this.ctx.api.decoder.getFrame.query({ handle, nodeId: handle.nodeId })\n if (!remote) return null\n return {\n data: Buffer.from(remote.data),\n width: remote.width,\n height: remote.height,\n format: remote.format,\n timestamp: remote.timestamp,\n }\n }\n\n this.embeddingDispatcher = new EmbeddingDispatcher({\n config: config.embedding,\n encoders: encoderCollection as EmbeddingDispatcherDeps['encoders'],\n eventBus: this.ctx.eventBus,\n logger: this.ctx.logger.child('EmbeddingDispatcher'),\n ownNodeId,\n readers: this.readers,\n getRemoteFrame,\n })\n\n this.sceneStateWorker = new SceneStateWorker({\n config: config.sceneMonitor,\n encoders: encoderCollection as SceneStateWorkerDeps['encoders'],\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n streamBrokerRegistry: (streamBrokerRaw ?? { getSnapshot: async () => null }) as SceneStateWorkerDeps['streamBrokerRegistry'],\n logger: this.ctx.logger.child('SceneStateWorker'),\n })\n\n this.activitySummary = new ActivitySummaryWorker({\n config: config.activitySummary,\n eventBus: this.ctx.eventBus,\n store: this.ctx.api!.settingsStore,\n logger: this.ctx.logger.child('ActivitySummary'),\n })\n\n this.currentFlags = {\n embeddingEnabled: config.embedding.enabled,\n sceneMonitorEnabled: config.sceneMonitor.enabled,\n activitySummaryEnabled: config.activitySummary.enabled,\n }\n\n await this.embeddingDispatcher.start()\n await this.sceneStateWorker.start()\n await this.activitySummary.start()\n\n this.ctx.logger.info('Enrichment engine initialized with 3 workers')\n }\n\n protected async onShutdown(): Promise<void> {\n await this.embeddingDispatcher?.stop()\n await this.sceneStateWorker?.stop()\n await this.activitySummary?.stop()\n this.embeddingDispatcher = null\n this.sceneStateWorker = null\n this.activitySummary = null\n // Engine owns the shm-readers cache — close exactly here, not inside\n // the dispatcher (the cache is shared, double-close would be a bug).\n this.readers?.close()\n this.readers = null\n }\n\n // ── Three-level settings API (Phase 3) ─────────────────────────────\n //\n // Enrichment engine is post-detection infra. The three boolean toggle\n // flags (embedding/scene/activity) live in `getGlobalSettings` until\n // the dedicated post-detection UI exists. The underlying\n // EnrichmentConfig blob (stored opaquely in 'addon-settings' →\n // 'enrichment:global') is a separate concern — the flags below mirror\n // the `.enabled` field of each worker's config.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'enrichment-engine-settings',\n title: 'Enrichment Engine',\n columns: 2,\n fields: [\n {\n type: 'boolean',\n key: 'embeddingEnabled',\n label: 'Embedding Enabled',\n description: 'Compute face/object embeddings from detection crops.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'sceneMonitorEnabled',\n label: 'Scene Monitor Enabled',\n description: 'Run periodic scene state capture for change detection.',\n default: true,\n },\n {\n type: 'boolean',\n key: 'activitySummaryEnabled',\n label: 'Activity Summary Enabled',\n description: 'Aggregate hourly activity summaries per camera.',\n default: true,\n },\n ],\n },\n ],\n })\n }\n\n async updateGlobalSettings(patch: Record<string, unknown>): Promise<void> {\n await this.ctx?.settings?.writeAddonStore(patch)\n const prev = this.currentFlags\n const next = {\n embeddingEnabled: typeof patch['embeddingEnabled'] === 'boolean' ? patch['embeddingEnabled'] : prev.embeddingEnabled,\n sceneMonitorEnabled: typeof patch['sceneMonitorEnabled'] === 'boolean' ? patch['sceneMonitorEnabled'] : prev.sceneMonitorEnabled,\n activitySummaryEnabled: typeof patch['activitySummaryEnabled'] === 'boolean' ? patch['activitySummaryEnabled'] : prev.activitySummaryEnabled,\n }\n this.currentFlags = next\n\n // Toggle workers based on flag changes\n if (prev.embeddingEnabled && !next.embeddingEnabled) {\n this.ctx?.logger.info('Stopping embedding dispatcher (disabled via config)')\n await this.embeddingDispatcher?.stop()\n } else if (!prev.embeddingEnabled && next.embeddingEnabled) {\n this.ctx?.logger.info('Starting embedding dispatcher (enabled via config)')\n await this.embeddingDispatcher?.start()\n }\n\n if (prev.sceneMonitorEnabled && !next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Stopping scene state worker (disabled via config)')\n await this.sceneStateWorker?.stop()\n } else if (!prev.sceneMonitorEnabled && next.sceneMonitorEnabled) {\n this.ctx?.logger.info('Starting scene state worker (enabled via config)')\n await this.sceneStateWorker?.start()\n }\n\n if (prev.activitySummaryEnabled && !next.activitySummaryEnabled) {\n this.ctx?.logger.info('Stopping activity summary worker (disabled via config)')\n await this.activitySummary?.stop()\n } else if (!prev.activitySummaryEnabled && next.activitySummaryEnabled) {\n this.ctx?.logger.info('Starting activity summary worker (enabled via config)')\n await this.activitySummary?.start()\n }\n\n this.ctx?.logger.info('Enrichment engine flags updated', { meta: { flags: this.currentFlags } })\n }\n\n private async loadConfig(): Promise<EnrichmentConfig> {\n try {\n const stored = asJsonObject(await this.ctx.api?.settingsStore.get.query({ collection: 'addon-settings', key: 'enrichment:global' }))\n if (stored) {\n // Shallow structural merge: only keys from DEFAULT_ENRICHMENT_CONFIG\n // are preserved; stored values override their counterparts when\n // present with a matching type.\n const merged: EnrichmentConfig = { ...DEFAULT_ENRICHMENT_CONFIG, ...stored }\n return merged\n }\n } catch {\n // First boot — use defaults\n }\n return DEFAULT_ENRICHMENT_CONFIG\n }\n}\n\n// Default export — kernel addon-loader prefers mod.default to identify the addon class\nexport default EnrichmentEngineAddon\n"],"names":["z.object","z.number","z.tuple","z.string","z.array","z.boolean","z.enum"],"mappings":"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"}