@camstack/addon-pipeline-analytics 0.1.1

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 (56) hide show
  1. package/dist/@mf-types/compiled-types/widgets/AudioHistoryChart.d.ts +5 -0
  2. package/dist/@mf-types/compiled-types/widgets/AudioHistoryChart.d.ts.map +1 -0
  3. package/dist/@mf-types/compiled-types/widgets/AudioMetricsPanel.d.ts +11 -0
  4. package/dist/@mf-types/compiled-types/widgets/AudioMetricsPanel.d.ts.map +1 -0
  5. package/dist/@mf-types/compiled-types/widgets/DetectionHistoryChart.d.ts +5 -0
  6. package/dist/@mf-types/compiled-types/widgets/DetectionHistoryChart.d.ts.map +1 -0
  7. package/dist/@mf-types/compiled-types/widgets/MotionHistoryChart.d.ts +5 -0
  8. package/dist/@mf-types/compiled-types/widgets/MotionHistoryChart.d.ts.map +1 -0
  9. package/dist/@mf-types/compiled-types/widgets/OccupancyHistoryChart.d.ts +5 -0
  10. package/dist/@mf-types/compiled-types/widgets/OccupancyHistoryChart.d.ts.map +1 -0
  11. package/dist/@mf-types/compiled-types/widgets/OccupancyPanel.d.ts +11 -0
  12. package/dist/@mf-types/compiled-types/widgets/OccupancyPanel.d.ts.map +1 -0
  13. package/dist/@mf-types/compiled-types/widgets/chart-utils.d.ts +98 -0
  14. package/dist/@mf-types/compiled-types/widgets/chart-utils.d.ts.map +1 -0
  15. package/dist/@mf-types/compiled-types/widgets/index.d.ts +28 -0
  16. package/dist/@mf-types/compiled-types/widgets/index.d.ts.map +1 -0
  17. package/dist/@mf-types/widgets.d.ts +2 -0
  18. package/dist/@mf-types.d.ts +3 -0
  19. package/dist/@mf-types.zip +0 -0
  20. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-CCBTZBOa.mjs +12 -0
  21. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-OesvKBZV.mjs +16 -0
  22. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-D0mniK1l.mjs +15 -0
  23. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.mjs-DoWbefqS.mjs +104 -0
  24. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_client__loadShare__.mjs-52bfkwC8.mjs +85 -0
  25. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.mjs-CVrnrGED.mjs +62 -0
  26. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-DuO9h7li.mjs +85 -0
  27. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CmqNjq44.mjs +29 -0
  28. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-BsyrX6NO.mjs +36 -0
  29. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs-Dp8hqYOB.mjs +45 -0
  30. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CA8cCIEl.mjs +6 -0
  31. package/dist/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom_mf_1_client__loadShare__.mjs-BZjEt71l.mjs +34 -0
  32. package/dist/_stub.js +1397 -0
  33. package/dist/_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Cm7MAUA1.mjs +157 -0
  34. package/dist/client-DdXDZxzK.mjs +10063 -0
  35. package/dist/getErrorShape-BPSzUA7W-TlK8ipWe.mjs +211 -0
  36. package/dist/hostInit-WKMmag4S.mjs +168 -0
  37. package/dist/index-B4OKsa9p.mjs +2603 -0
  38. package/dist/index-C3iAUQqS.mjs +533 -0
  39. package/dist/index-D0dNM7_R.mjs +2892 -0
  40. package/dist/index-DKqbmJDl.mjs +2464 -0
  41. package/dist/index-DnFVXz0U.mjs +14162 -0
  42. package/dist/index-DyYvUfc7.mjs +725 -0
  43. package/dist/index-Oq45bZIA.mjs +17936 -0
  44. package/dist/index-k0CA0h_r.mjs +185 -0
  45. package/dist/index-kIgjN-uq.mjs +435 -0
  46. package/dist/index-xncRG7-x.mjs +2713 -0
  47. package/dist/index.d.mts +190 -0
  48. package/dist/index.d.ts +190 -0
  49. package/dist/index.js +2623 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/index.mjs +2602 -0
  52. package/dist/index.mjs.map +1 -0
  53. package/dist/jsx-runtime-4ro1c69i.mjs +55 -0
  54. package/dist/remoteEntry.js +85 -0
  55. package/dist/virtualExposes-8FzWTdq3.mjs +42 -0
  56. package/package.json +89 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/pipeline/zones/geometry.ts","../src/pipeline/tracker/sort-tracker.ts","../src/pipeline/tracker/state-analyzer.ts","../src/pipeline/events/event-filter.ts","../src/pipeline/events/event-emitter.ts","../src/pipeline/zones/overlap.ts","../src/pipeline/zones/zone-engine.ts","../src/pipeline/frame-processor.ts","../src/runtime/binding-cache.ts","../src/store/track-store.ts","../src/store/media-store.ts","../src/store/event-store.ts","../src/runtime/slice-throttler.ts","../src/zone-analytics-provider.ts","../src/audio-metrics-provider.ts"],"sourcesContent":["import type {\n ProviderRegistration, IPipelineAnalyticsProvider,\n ConfigSectionWithValues,\n ConfigUISchemaWithValues, PipelineInferenceResultPayload,\n PipelineAudioInferenceResultPayload, Track, MediaFile,\n MotionEvent, ObjectEvent, AudioEvent, MotionAnalysisPayload,\n MotionOnMotionChangedPayload,\n Unsubscribe, TrackedDetection, DeviceProxy,\n IAddonWidgetsSourceProvider,\n} from '@camstack/types'\nimport { BaseAddon, DeviceType, pipelineAnalyticsCapability, zoneAnalyticsCapability, audioMetricsCapability, addonWidgetsSourceCapability, EventCategory, hydrateSchema } from '@camstack/types'\nimport { randomUUID } from 'node:crypto'\n\nimport { FrameProcessor } from './pipeline/frame-processor.js'\nimport { BindingCache } from './runtime/binding-cache.js'\nimport { TrackStore } from './store/track-store.js'\nimport { MediaStore } from './store/media-store.js'\nimport { EventStore } from './store/event-store.js'\nimport { ZoneAnalyticsProvider } from './zone-analytics-provider.js'\nimport { AudioMetricsProvider } from './audio-metrics-provider.js'\n\n/**\n * Pipeline Analytics addon — subscribes to `PipelineInferenceResult` +\n * `PipelineAudioInferenceResult` on the bus, refines raw detections\n * into tracks + per-kind events + persisted media, and emits canonical\n * events (`onFrameTracked` / `onTrackStarted` / `onTrackEnded` /\n * `onDetectionEvent`) for downstream consumers.\n *\n * Pure subscriber architecture: no `processFrame` cap method. Per-\n * device gating via the `pipeline-analytics` wrapper binding in\n * `device-manager.setWrapperActive` — BindingsTab is the single\n * surface to turn the refinement pipeline on/off for a camera.\n */\n\nconst TTL_SWEEP_INTERVAL_MS = 5_000\nconst RETENTION_SWEEP_INTERVAL_MS = 5 * 60_000\n// Coalescing window for audio event inserts. While the same\n// classification persists, only one row is inserted per heartbeat\n// (operators still see continuous activity via the bus event, which\n// fires on every chunk).\nconst AUDIO_EVENT_HEARTBEAT_MS = 5_000\n// Same coalescing strategy for motion. The MotionAnalysis bus event\n// fires at the analyzer fps (typically 5-15 Hz) — without throttling\n// a 1-min motion burst would produce 300-900 SQL rows. Insert one\n// row per heartbeat while motion stays active; insert immediately on\n// off→on transition so the histogram bucket containing the trigger\n// timestamp is non-empty even on short bursts.\nconst MOTION_EVENT_HEARTBEAT_MS = 5_000\n\nexport default class PipelineAnalyticsAddon extends BaseAddon implements IPipelineAnalyticsProvider {\n private processors = new Map<number, FrameProcessor>()\n private trackStore: TrackStore | null = null\n private mediaStore: MediaStore | null = null\n private eventStore: EventStore | null = null\n private bindingCache: BindingCache | null = null\n private zoneAnalytics: ZoneAnalyticsProvider | null = null\n private audioMetrics: AudioMetricsProvider | null = null\n /**\n * Per-device {@link DeviceProxy} used to read live zones +\n * detection rules off the kernel runtime-state mirror. Replaces\n * the manual DeviceStateChanged subscription + per-cap-name caches\n * — `proxy.state.zones.value` and `proxy.state.zoneRules.value`\n * stay warm as long as the slice handles are subscribed.\n */\n private readonly proxies = new Map<number, DeviceProxy>()\n /** Slice subscription pins for cache warmth + pushed updates into\n * the per-device FrameProcessor. */\n private readonly proxyUnsubs = new Map<number, ReadonlyArray<() => void>>()\n\n private unsubInference: Unsubscribe | null = null\n private unsubAudio: Unsubscribe | null = null\n private unsubMotion: Unsubscribe | null = null\n private unsubMotionOnboard: Unsubscribe | null = null\n private unsubBindings: Unsubscribe | null = null\n private unsubDeviceUnreg: Unsubscribe | null = null\n private ttlSweepTimer: ReturnType<typeof setInterval> | null = null\n private retentionSweepTimer: ReturnType<typeof setInterval> | null = null\n\n // Current tracked-state snapshot per active track. Used so\n // TrackStarted/TrackEnded fire exactly once per lifecycle transition.\n private readonly lastActiveTrackIds = new Map<number, Set<string>>()\n // Throttle state for audio event inserts. Without this every AAC\n // chunk (~30 Hz) becomes a row, blowing the analytics DB to 1M+\n // rows in a single day on one camera.\n private readonly lastAudioInsertByDevice = new Map<number, { className: string | undefined; atMs: number }>()\n // Same throttle for motion: one row per heartbeat while detected stays\n // true; immediate insert on off→on transition. Off→off skipped.\n private readonly lastMotionInsertByDevice = new Map<number, { detected: boolean; atMs: number }>()\n private shuttingDown = false\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const api = this.ctx.api\n if (!api) throw new Error('pipeline-analytics requires ctx.api (device-manager + settings-store)')\n\n // Declare typed collections up-front so the first insert doesn't race\n // with a CREATE TABLE. Idempotent.\n await TrackStore.declare(api.settingsStore)\n await MediaStore.declare(api.settingsStore)\n await EventStore.declare(api.settingsStore)\n\n const logger = this.ctx.logger\n const storage = this.ctx.kernel.storage\n if (!storage) throw new Error('pipeline-analytics requires ctx.kernel.storage')\n this.trackStore = new TrackStore({ store: api.settingsStore, logger: logger.child('TrackStore') })\n this.mediaStore = new MediaStore({\n storage,\n store: api.settingsStore,\n logger: logger.child('MediaStore'),\n })\n this.eventStore = new EventStore({ store: api.settingsStore, logger: logger.child('EventStore') })\n this.bindingCache = new BindingCache({ api, logger: logger.child('BindingCache') })\n this.zoneAnalytics = new ZoneAnalyticsProvider({\n logger: logger.child('ZoneAnalytics'),\n fetchDevice: (deviceId) => this.ctx.fetchDevice(deviceId),\n })\n this.audioMetrics = new AudioMetricsProvider({\n logger: logger.child('AudioMetrics'),\n fetchDevice: (deviceId) => this.ctx.fetchDevice(deviceId),\n // Per-camera report knobs — operator tunes via the\n // device-settings panel (\"Audio metrics reporting\" section).\n // Cascade is global default → device override; raw read goes\n // straight through ctx.settings, no kernel round-trip.\n resolveReportSettings: async (deviceId) => {\n const raw = (await this.ctx?.settings?.readDeviceStore(deviceId)) ?? {}\n const enabled = typeof raw['audioMetricsReportEnabled'] === 'boolean'\n ? (raw['audioMetricsReportEnabled'] as boolean)\n : true\n const thresholdRaw = raw['audioMetricsReportThresholdDb']\n const thresholdDb = typeof thresholdRaw === 'number' && Number.isFinite(thresholdRaw)\n ? Math.max(0, Math.min(10, thresholdRaw))\n : 2\n return { enabled, thresholdDb }\n },\n })\n\n this.unsubInference = this.ctx.eventBus.subscribe(\n { category: EventCategory.PipelineInferenceResult },\n (ev) => { void this.handleInferenceResult(ev.data as PipelineInferenceResultPayload) },\n ) as Unsubscribe\n\n this.unsubAudio = this.ctx.eventBus.subscribe(\n { category: EventCategory.PipelineAudioInferenceResult },\n (ev) => { void this.handleAudioResult(ev.data as PipelineAudioInferenceResultPayload) },\n ) as Unsubscribe\n\n // Motion-analysis subscriber — emitted by the pipeline runner on\n // every analyzer pass with a `device` source whose `deviceId` is\n // the originating camera. Persisted via `EventStore.insertMotion`\n // following the same heartbeat-throttling pattern as audio.\n this.unsubMotion = this.ctx.eventBus.subscribe(\n { category: EventCategory.MotionAnalysis },\n (ev) => {\n const src = ev.source as { type?: string; deviceId?: number }\n if (src?.type !== 'device' || typeof src.deviceId !== 'number') return\n void this.handleMotionAnalysis(src.deviceId, ev.data as MotionAnalysisPayload, ev.timestamp instanceof Date ? ev.timestamp.getTime() : Date.now())\n },\n ) as Unsubscribe\n\n // Onboard / firmware-driven motion subscriber — providers that\n // produce motion without a frame-diff analyzer (Reolink Baichuan\n // simpleEvent, Hikvision ISAPI alarm stream, ONVIF analytics)\n // emit `MotionOnMotionChanged` with `source !== 'analyzer'` and\n // never fire `MotionAnalysis`. Without this subscription their\n // motion never lands in the SQL store, leaving\n // `getMotionEvents` empty and the history chart blank for any\n // camera configured with `motionSources: ['onboard']`.\n //\n // Analyzer-source events are skipped here — the runner already\n // emits a parallel `MotionAnalysis` event per frame which the\n // handler above persists with full region payload. Routing\n // analyzer through both paths would double-count (and lose\n // region detail since onboard payloads don't carry frame\n // dimensions).\n this.unsubMotionOnboard = this.ctx.eventBus.subscribe(\n { category: EventCategory.MotionOnMotionChanged },\n (ev) => {\n const data = ev.data as MotionOnMotionChangedPayload\n if (data.source === 'analyzer') return\n const timestamp = typeof data.timestamp === 'number'\n ? data.timestamp\n : (ev.timestamp instanceof Date ? ev.timestamp.getTime() : Date.now())\n void this.handleOnboardMotion(data.deviceId, data, timestamp)\n },\n ) as Unsubscribe\n\n this.unsubBindings = this.ctx.eventBus.subscribe(\n { category: EventCategory.DeviceBindingsChanged },\n (ev) => {\n const data = ev.data as {\n deviceId: number\n capName: string\n reason: 'native-registered' | 'native-unregistered' | 'wrapper-activated' | 'wrapper-deactivated'\n }\n this.bindingCache?.onBindingsChanged(data)\n if (data.capName === 'pipeline-analytics' && data.reason === 'wrapper-deactivated') {\n // Operator turned us off for this device — drop its in-RAM\n // tracks (no history persist since nothing was completed).\n this.trackStore?.clearDevice(data.deviceId)\n this.processors.delete(data.deviceId)\n this.lastActiveTrackIds.delete(data.deviceId)\n }\n },\n ) as Unsubscribe\n\n this.unsubDeviceUnreg = this.ctx.eventBus.subscribe(\n { category: EventCategory.DeviceUnregistered },\n (ev) => {\n const { deviceId } = ev.data as { deviceId: number }\n this.trackStore?.clearDevice(deviceId)\n this.processors.delete(deviceId)\n this.lastActiveTrackIds.delete(deviceId)\n this.bindingCache?.invalidate(deviceId)\n this.zoneAnalytics?.forgetDevice(deviceId)\n this.audioMetrics?.forgetDevice(deviceId)\n this.releaseProxy(deviceId)\n },\n ) as Unsubscribe\n\n // Periodic TTL sweep — promote stale active tracks to persisted.\n this.ttlSweepTimer = setInterval(() => { void this.sweepExpiredTracks() }, TTL_SWEEP_INTERVAL_MS)\n\n // Periodic retention sweep — drop old events + media beyond the\n // retention window. Defaults live here for now; device-settings\n // cascade lands in P8.\n this.retentionSweepTimer = setInterval(() => { void this.sweepRetention() }, RETENTION_SWEEP_INTERVAL_MS)\n\n this.ctx.logger.info('pipeline-analytics subscribers installed')\n\n // Widget bundle contribution — analytics owns the audio-metrics +\n // zone-analytics caps, so the per-camera live charts (audio dBFS,\n // occupancy, motion histogram, detection histogram) and the live\n // panels (audio metrics card, occupancy panel) ship from this\n // addon's bundle. Admin-ui's <WidgetRegistryProvider> imports\n // /api/addon-widgets/pipeline-analytics/widgets.mjs?v=<mtime>;\n // <WidgetSlot widgetId=\"pipeline-analytics/<stableId>\"\n // deviceId={…}/> mounts each one.\n const widgetsProvider: IAddonWidgetsSourceProvider = {\n listWidgets: async () => [\n {\n stableId: 'audio-history-chart',\n label: 'Audio Level History',\n description: 'Sparkline of audio dBFS over a configurable window with class hits.',\n icon: 'activity',\n remoteName: 'addon_pipeline_analytics_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'lg' as const,\n allowedSizes: ['md', 'lg', 'xl'] as const,\n defaultColumns: 12,\n defaultRows: 2,\n },\n {\n stableId: 'audio-metrics-panel',\n label: 'Audio Metrics',\n description: 'Live dBFS + dominant class + per-class summary card.',\n icon: 'mic',\n remoteName: 'addon_pipeline_analytics_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'md' as const,\n allowedSizes: ['sm', 'md', 'lg'] as const,\n defaultColumns: 6,\n defaultRows: 2,\n },\n {\n stableId: 'occupancy-history-chart',\n label: 'Occupancy History',\n description: 'Frame-wide tracked-object count over time.',\n icon: 'users',\n remoteName: 'addon_pipeline_analytics_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'lg' as const,\n allowedSizes: ['md', 'lg', 'xl'] as const,\n defaultColumns: 12,\n defaultRows: 2,\n },\n {\n stableId: 'motion-history-chart',\n label: 'Motion History',\n description: 'Histogram of motion events over a configurable window.',\n icon: 'activity',\n remoteName: 'addon_pipeline_analytics_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'lg' as const,\n allowedSizes: ['md', 'lg', 'xl'] as const,\n defaultColumns: 12,\n defaultRows: 2,\n },\n {\n stableId: 'detection-history-chart',\n label: 'Detection History',\n description: 'Stacked-by-class histogram of object detections.',\n icon: 'eye',\n remoteName: 'addon_pipeline_analytics_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'lg' as const,\n allowedSizes: ['md', 'lg', 'xl'] as const,\n defaultColumns: 12,\n defaultRows: 2,\n },\n {\n stableId: 'live-occupancy-panel',\n label: 'Live Occupancy',\n description: 'Per-zone breakdown + frame-wide aggregate (no polling).',\n icon: 'layers',\n remoteName: 'addon_pipeline_analytics_widgets',\n bundle: 'remoteEntry.js',\n hosts: ['device-tab', 'dashboard'] as const,\n requires: { deviceContext: true, integrationContext: false },\n defaultSize: 'md' as const,\n allowedSizes: ['sm', 'md', 'lg'] as const,\n defaultColumns: 6,\n defaultRows: 2,\n },\n ],\n }\n\n return [\n {\n capability: pipelineAnalyticsCapability,\n provider: this,\n kind: 'wrapper',\n defaultActive: true,\n },\n {\n capability: zoneAnalyticsCapability,\n provider: this.zoneAnalytics,\n },\n {\n capability: audioMetricsCapability,\n provider: this.audioMetrics,\n },\n {\n capability: addonWidgetsSourceCapability,\n provider: widgetsProvider,\n },\n ]\n }\n\n protected async onShutdown(): Promise<void> {\n this.shuttingDown = true\n this.unsubInference?.(); this.unsubInference = null\n this.unsubAudio?.(); this.unsubAudio = null\n this.unsubMotion?.(); this.unsubMotion = null\n this.unsubMotionOnboard?.(); this.unsubMotionOnboard = null\n this.unsubBindings?.(); this.unsubBindings = null\n this.unsubDeviceUnreg?.(); this.unsubDeviceUnreg = null\n for (const id of this.proxies.keys()) this.releaseProxy(id)\n if (this.ttlSweepTimer) { clearInterval(this.ttlSweepTimer); this.ttlSweepTimer = null }\n if (this.retentionSweepTimer) { clearInterval(this.retentionSweepTimer); this.retentionSweepTimer = null }\n // Cancel any in-flight slice-throttle timers so a pending trailing\n // flush can't fire after the addon is gone (would throw on\n // `fetchDevice` against a torn-down ctx).\n this.zoneAnalytics?.destroy()\n this.audioMetrics?.destroy()\n this.processors.clear()\n this.lastActiveTrackIds.clear()\n this.trackStore?.clearAll()\n this.bindingCache?.clearAll()\n }\n\n // ── Subscriber handlers ──────────────────────────────────────────────\n\n private async handleInferenceResult(data: PipelineInferenceResultPayload): Promise<void> {\n if (this.shuttingDown) return\n const { deviceId, frame } = data\n const active = await this.bindingCache!.isActive(deviceId)\n if (!active) return\n\n const processor = this.getOrCreateProcessor(deviceId)\n // Resolve (or build) the device proxy on first frame for this\n // device. The proxy's slice subscribers (set up in ensureProxy)\n // push zones + rules into the FrameProcessor asynchronously.\n // Reading `.value` here is the SAFE per-frame source: it\n // guarantees the snapshot's `zones` array and the per-track zone\n // memberships agree, even when the subscription hasn't fired\n // for a freshly-added zone yet (race between zone CRUD and the\n // next inference frame). Without this re-push the snapshot would\n // show the new zone with zero occupants and every detection\n // would land in `unzoned` even though the bbox is inside.\n const proxy = await this.ensureProxy(deviceId)\n const liveZones = proxy?.state.zones.value?.zones ?? []\n const liveRules = proxy?.state.zoneRules.value?.detection ?? []\n processor.setZones(liveZones)\n processor.setDetectionRules(liveRules)\n const result = processor.process({ timestamp: frame.timestamp, frame })\n\n // Record per-frame occupancy aggregate. recordFrame mirrors the\n // snapshot to `device.state.zoneAnalytics` for live UI subscribers\n // and appends to the rolling history ring.\n void this.zoneAnalytics?.recordFrame({\n deviceId,\n timestamp: result.timestamp,\n frameWidth: result.frameWidth,\n frameHeight: result.frameHeight,\n tracked: result.tracked,\n zones: liveZones,\n })\n\n // Update TrackStore — per-track upsert with position + zones.\n const currentTrackIds = new Set<string>()\n for (const t of result.tracked) {\n currentTrackIds.add(t.trackId)\n const center = {\n x: t.bbox.x + t.bbox.w / 2,\n y: t.bbox.y + t.bbox.h / 2,\n }\n // Raw TrackedDetection is a flat SpatialDetection (extended with\n // trackId/velocity/path); its `originalClass` is the raw detector\n // label we keep as the track's `label` when it differs from the\n // macro class.\n const raw = result.rawTrackedDetections.find(r => r.trackId === t.trackId)\n this.trackStore!.upsert({\n trackId: t.trackId,\n deviceId,\n className: t.className,\n ...(raw?.originalClass && raw.originalClass !== t.className\n ? { label: raw.originalClass }\n : {}),\n timestamp: result.timestamp,\n position: {\n x: center.x,\n y: center.y,\n timestamp: result.timestamp,\n bbox: { ...t.bbox },\n },\n zones: t.zones,\n state: t.state,\n })\n }\n\n // Detect track start/end transitions vs previous frame.\n const prevIds = this.lastActiveTrackIds.get(deviceId) ?? new Set<string>()\n for (const id of currentTrackIds) {\n if (!prevIds.has(id)) {\n const t = result.tracked.find(x => x.trackId === id)\n if (t) {\n this.ctx.eventBus.emit({\n id: `pa-${randomUUID()}`,\n timestamp: new Date(result.timestamp),\n source: { type: 'addon', id: 'pipeline-analytics', addonId: 'pipeline-analytics' },\n category: EventCategory.PipelineAnalyticsTrackStarted,\n data: { deviceId, trackId: id, className: t.className },\n })\n }\n }\n }\n this.lastActiveTrackIds.set(deviceId, currentTrackIds)\n\n // Persist object events in parallel.\n await Promise.all(result.objectEvents.map(e => this.eventStore!.insertObject(e)))\n\n // Emit canonical detection-event notifications on the bus for each\n // persisted object event.\n for (const e of result.objectEvents) {\n this.ctx.eventBus.emit({\n id: `pa-${e.id}`,\n timestamp: new Date(e.timestamp),\n source: { type: 'addon', id: 'pipeline-analytics', addonId: 'pipeline-analytics' },\n category: EventCategory.PipelineAnalyticsDetectionEvent,\n data: { deviceId, kind: 'object', eventId: e.id, timestamp: e.timestamp },\n })\n }\n\n // Enriched frame emit for downstream stream overlays, notifications etc.\n this.ctx.eventBus.emit({\n id: `pa-${randomUUID()}`,\n timestamp: new Date(result.timestamp),\n source: { type: 'addon', id: 'pipeline-analytics', addonId: 'pipeline-analytics' },\n category: EventCategory.PipelineAnalyticsFrameTracked,\n data: {\n deviceId,\n timestamp: result.timestamp,\n frameWidth: result.frameWidth,\n frameHeight: result.frameHeight,\n detections: result.tracked,\n },\n })\n }\n\n private async handleAudioResult(data: PipelineAudioInferenceResultPayload): Promise<void> {\n if (this.shuttingDown) return\n const { deviceId, frame } = data\n const active = await this.bindingCache!.isActive(deviceId)\n if (!active) return\n\n // AudioResult shape: has level + detections + timestamp.\n const level = frame.level\n const timestamp = frame.timestamp ?? Date.now()\n const topClassification = frame.detections && frame.detections.length > 0\n ? frame.detections[0]\n : undefined\n\n // Feed the rolling-window audio metrics aggregator. The provider\n // mirrors a snapshot to `device.state.audioMetrics` on every call\n // so the operator UI gets push updates without a custom event\n // subscription.\n void this.audioMetrics?.recordAudioFrame({\n deviceId,\n timestamp,\n ...(level ? { level } : {}),\n ...(topClassification\n ? {\n topClassification: {\n className: topClassification.macroClass,\n score: topClassification.score,\n },\n }\n : {}),\n })\n\n // Audio chunks arrive at ~25-30 Hz (32 ms windows). Inserting one\n // row per chunk floods the analytics DB (1425 events/min/camera\n // observed live, → 1.1M+ rows/cam in 24h, →5s+ query latency on\n // /trpc/pipelineAnalytics.getAudioEvents). Throttle by:\n // 1. Skipping silent chunks (no classification + below dbfs floor).\n // 2. Coalescing: only insert when class changes OR every\n // AUDIO_EVENT_HEARTBEAT_MS while same class persists.\n // Operators wanting per-chunk telemetry should subscribe to the\n // `pipeline.audio-inference-result` bus event directly instead.\n const className = topClassification?.macroClass\n const scoreFloor = 0.4\n const hasMeaningfulClassification = className !== undefined && (topClassification?.score ?? 0) >= scoreFloor\n const dbfsFloor = -55\n const aboveSilence = (level?.dbfs ?? -Infinity) > dbfsFloor\n\n if (!hasMeaningfulClassification && !aboveSilence) return // pure silence — drop\n\n const last = this.lastAudioInsertByDevice.get(deviceId)\n const sameClass = last?.className === className\n const heartbeatDue = !last || (timestamp - last.atMs) >= AUDIO_EVENT_HEARTBEAT_MS\n if (sameClass && !heartbeatDue) return\n\n const ev: AudioEvent = {\n id: randomUUID(),\n deviceId,\n timestamp,\n kind: 'audio',\n rms: level?.rms ?? 0,\n dbfs: level?.dbfs ?? 0,\n ...(topClassification\n ? {\n classification: {\n className: topClassification.macroClass,\n ...(topClassification.debug?.originalClass !== undefined\n ? { originalClass: topClassification.debug.originalClass }\n : {}),\n score: topClassification.score,\n },\n }\n : {}),\n }\n this.lastAudioInsertByDevice.set(deviceId, { className, atMs: timestamp })\n await this.eventStore!.insertAudio(ev)\n this.ctx.eventBus.emit({\n id: `pa-${ev.id}`,\n timestamp: new Date(ev.timestamp),\n source: { type: 'addon', id: 'pipeline-analytics', addonId: 'pipeline-analytics' },\n category: EventCategory.PipelineAnalyticsDetectionEvent,\n data: { deviceId, kind: 'audio', eventId: ev.id, timestamp: ev.timestamp },\n })\n }\n\n /**\n * Persist motion-analysis bus events. Mirrors `handleAudioResult`'s\n * coalescing strategy: emit one row on off→on transition, then one\n * per `MOTION_EVENT_HEARTBEAT_MS` while motion stays detected. No\n * row on off→off (silence) or while heartbeat hasn't elapsed.\n *\n * Gated on the same `pipeline-analytics` wrapper binding as the\n * inference + audio handlers so toggling the wrapper off for a\n * camera stops every kind of event persistence at once.\n */\n private async handleMotionAnalysis(\n deviceId: number,\n payload: MotionAnalysisPayload,\n timestamp: number,\n ): Promise<void> {\n if (this.shuttingDown) return\n const active = await this.bindingCache!.isActive(deviceId)\n if (!active) return\n\n const detected = payload.detected === true\n const last = this.lastMotionInsertByDevice.get(deviceId)\n const heartbeatDue = !last || (timestamp - last.atMs) >= MOTION_EVENT_HEARTBEAT_MS\n const transitionedOn = detected && (last === undefined || last.detected === false)\n if (!detected) return // pure silence — drop, mirrors audio's silent-chunk skip\n if (!transitionedOn && !heartbeatDue) return\n\n const ev: MotionEvent = {\n id: randomUUID(),\n deviceId,\n timestamp,\n kind: 'motion',\n regionCount: payload.regionCount,\n regions: payload.regions.map((r) => ({\n bbox: { x: r.bbox.x, y: r.bbox.y, w: r.bbox.w, h: r.bbox.h },\n pixelCount: r.pixelCount,\n intensity: r.intensity,\n })),\n frameWidth: payload.frameWidth,\n frameHeight: payload.frameHeight,\n }\n this.lastMotionInsertByDevice.set(deviceId, { detected, atMs: timestamp })\n await this.eventStore!.insertMotion(ev)\n this.ctx.eventBus.emit({\n id: `pa-${ev.id}`,\n timestamp: new Date(ev.timestamp),\n source: { type: 'addon', id: 'pipeline-analytics', addonId: 'pipeline-analytics' },\n category: EventCategory.PipelineAnalyticsDetectionEvent,\n data: { deviceId, kind: 'motion', eventId: ev.id, timestamp: ev.timestamp },\n })\n }\n\n /**\n * Persist firmware-driven (onboard) motion events. Mirrors\n * {@link handleMotionAnalysis}'s coalescing — one row on off→on\n * transition, then one per `MOTION_EVENT_HEARTBEAT_MS` while motion\n * stays detected — but reads from the {@link MotionOnMotionChangedPayload}\n * shape which lacks frame dimensions and pixel-level region details\n * (firmware reports a binary detected flag plus, on rare devices, a\n * coarse bbox via the optional `regions` field).\n *\n * The same `lastMotionInsertByDevice` map is shared with the analyzer\n * path so a camera that briefly switches `motionSources` between\n * `onboard` and `analyzer` gets consistent throttling — both paths\n * are mutually exclusive at the event-emit layer (the runner only\n * fires `MotionAnalysis` when its analyzer runs; onboard providers\n * never fire `MotionAnalysis`), so there's no double-count risk.\n */\n private async handleOnboardMotion(\n deviceId: number,\n payload: MotionOnMotionChangedPayload,\n timestamp: number,\n ): Promise<void> {\n if (this.shuttingDown) return\n const active = await this.bindingCache!.isActive(deviceId)\n if (!active) return\n\n const detected = payload.detected === true\n const last = this.lastMotionInsertByDevice.get(deviceId)\n const heartbeatDue = !last || (timestamp - last.atMs) >= MOTION_EVENT_HEARTBEAT_MS\n const transitionedOn = detected && (last === undefined || last.detected === false)\n if (!detected) return\n if (!transitionedOn && !heartbeatDue) return\n\n // Onboard payloads carry an optional `regions` array (firmware\n // bbox passed through the cap event). Most cameras don't expose\n // it, so default to an empty array + zero counts. Frame\n // dimensions are unknown (omitted from the row).\n const regions = payload.regions ?? []\n const ev: MotionEvent = {\n id: randomUUID(),\n deviceId,\n timestamp,\n kind: 'motion',\n regionCount: regions.length,\n regions: regions.map((r) => ({\n bbox: { x: r.bbox.x, y: r.bbox.y, w: r.bbox.w, h: r.bbox.h },\n pixelCount: r.pixelCount,\n intensity: r.intensity,\n })),\n frameWidth: 0,\n frameHeight: 0,\n }\n this.lastMotionInsertByDevice.set(deviceId, { detected, atMs: timestamp })\n await this.eventStore!.insertMotion(ev)\n this.ctx.eventBus.emit({\n id: `pa-${ev.id}`,\n timestamp: new Date(ev.timestamp),\n source: { type: 'addon', id: 'pipeline-analytics', addonId: 'pipeline-analytics' },\n category: EventCategory.PipelineAnalyticsDetectionEvent,\n data: { deviceId, kind: 'motion', eventId: ev.id, timestamp: ev.timestamp },\n })\n }\n\n // ── Housekeeping ─────────────────────────────────────────────────────\n\n private async sweepExpiredTracks(): Promise<void> {\n if (this.shuttingDown || !this.trackStore) return\n try {\n const expired = await this.trackStore.expireStale(Date.now())\n for (const t of expired) {\n const duration = t.lastSeen - t.firstSeen\n this.ctx.eventBus.emit({\n id: `pa-end-${t.trackId}`,\n timestamp: new Date(t.lastSeen),\n source: { type: 'addon', id: 'pipeline-analytics', addonId: 'pipeline-analytics' },\n category: EventCategory.PipelineAnalyticsTrackEnded,\n data: {\n deviceId: t.deviceId,\n trackId: t.trackId,\n className: t.className,\n durationMs: duration,\n },\n })\n }\n } catch (err) {\n if (this.shuttingDown) return\n this.ctx.logger.debug('sweepExpiredTracks failed', { meta: { error: String(err) } })\n }\n }\n\n private async sweepRetention(): Promise<void> {\n if (this.shuttingDown || !this.eventStore || !this.mediaStore) return\n // Defaults: 14d motion, 30d object, 7d audio, 14d media.\n const now = Date.now()\n const day = 24 * 60 * 60 * 1000\n try {\n await this.eventStore.evictBefore({\n motionCutoffMs: now - 14 * day,\n objectCutoffMs: now - 30 * day,\n audioCutoffMs: now - 7 * day,\n })\n await this.mediaStore.evictBefore(now - 14 * day)\n } catch (err) {\n this.ctx.logger.debug('sweepRetention failed', { meta: { error: String(err) } })\n }\n }\n\n private getOrCreateProcessor(deviceId: number): FrameProcessor {\n let p = this.processors.get(deviceId)\n if (!p) {\n p = new FrameProcessor(deviceId)\n this.processors.set(deviceId, p)\n }\n return p\n }\n\n /**\n * Resolve and cache a {@link DeviceProxy} for a device. Pins the\n * `state.zones` + `state.zoneRules` slice handles so `.value` stays\n * warm via the kernel runtime-state mirror. Slice subscribers also\n * forward updates to the per-device FrameProcessor so a rule change\n * applies to the very next frame even when frames stop briefly\n * (e.g. during binding flips).\n */\n private async ensureProxy(deviceId: number): Promise<DeviceProxy | null> {\n const cached = this.proxies.get(deviceId)\n if (cached) return cached\n try {\n const proxy = await this.ctx.api!.deviceManager\n ? await this.ctx.fetchDevice(deviceId)\n : null\n if (!proxy) return null\n this.proxies.set(deviceId, proxy)\n const unsubs = [\n proxy.state.zones.subscribe((slice) => {\n const zones = slice?.zones ?? []\n this.processors.get(deviceId)?.setZones(zones)\n }),\n proxy.state.zoneRules.subscribe((slice) => {\n const rules = slice?.detection ?? []\n this.processors.get(deviceId)?.setDetectionRules(rules)\n }),\n ]\n this.proxyUnsubs.set(deviceId, unsubs)\n return proxy\n } catch (err: unknown) {\n this.ctx.logger.debug('analytics ensureProxy failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return null\n }\n }\n\n private releaseProxy(deviceId: number): void {\n const unsubs = this.proxyUnsubs.get(deviceId)\n if (unsubs) {\n for (const u of unsubs) {\n try { u() } catch { /* swallow */ }\n }\n }\n this.proxyUnsubs.delete(deviceId)\n this.proxies.delete(deviceId)\n }\n\n // ── Cap query methods ────────────────────────────────────────────────\n\n async getActiveTracks(input: { deviceId: number }): Promise<readonly Track[]> {\n return this.trackStore?.getActive(input.deviceId) ?? []\n }\n\n async getTrack(input: { deviceId: number; trackId: string }): Promise<Track | null> {\n const active = this.trackStore?.getActiveByTrack(input.trackId)\n if (active) return active\n return (await this.trackStore?.getPersistedByTrackId(input.trackId)) ?? null\n }\n\n async listTracks(input: {\n deviceId: number; since?: number; until?: number; limit?: number\n }): Promise<readonly Track[]> {\n return this.trackStore?.queryHistorical(input) ?? []\n }\n\n async clearTracks(input: { deviceId: number }): Promise<void> {\n this.trackStore?.clearDevice(input.deviceId)\n this.lastActiveTrackIds.delete(input.deviceId)\n }\n\n async getMotionEvents(input: {\n deviceId: number; since?: number; until?: number; limit?: number\n }): Promise<readonly MotionEvent[]> {\n return this.eventStore?.queryMotion(input) ?? []\n }\n\n async getObjectEvents(input: {\n deviceId: number; since?: number; until?: number; classFilter?: string; limit?: number\n }): Promise<readonly ObjectEvent[]> {\n return this.eventStore?.queryObject(input) ?? []\n }\n\n async getAudioEvents(input: {\n deviceId: number; since?: number; until?: number; limit?: number\n }): Promise<readonly AudioEvent[]> {\n return this.eventStore?.queryAudio(input) ?? []\n }\n\n async getEventMedia(input: { eventId: string }): Promise<readonly MediaFile[]> {\n return this.mediaStore?.listByOwner('event', input.eventId) ?? []\n }\n\n async getTrackMedia(input: { trackId: string }): Promise<readonly MediaFile[]> {\n return this.mediaStore?.listByOwner('track', input.trackId) ?? []\n }\n\n // ── Global + per-device settings (P8) ─────────────────────────────\n //\n // Cascade: global defaults live on this addon's global settings\n // (getGlobalSettings); per-device overrides live on the device\n // settings aggregator (getDeviceSettingsContribution → BindingsTab's\n // device panel). A missing device override falls back to the global\n // default — same pattern pipeline-orchestrator uses.\n\n protected globalSettingsSchema() {\n return this.schema({\n sections: [\n {\n id: 'tracking-core',\n title: 'Tracking',\n description: 'Default track lifecycle + history knobs. Per-device overrides on each camera.',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'trackTtlMs',\n label: 'Track TTL',\n description: 'Milliseconds without a sighting before a track is finalized and promoted to history.',\n min: 5_000,\n max: 120_000,\n step: 1_000,\n default: 30_000,\n showValue: true,\n unit: 's',\n displayScale: 1000,\n },\n {\n type: 'slider',\n key: 'maxPositionHistory',\n label: 'Max positions per track',\n description: 'Positions retained in memory; older entries get first-half downsampled.',\n min: 50,\n max: 1_000,\n step: 50,\n default: 300,\n showValue: true,\n },\n ],\n },\n {\n id: 'media-policy',\n title: 'Snapshots & media',\n columns: 2,\n fields: [\n {\n type: 'boolean',\n key: 'saveThumbnails',\n label: 'Save track thumbnails',\n description: 'Periodic snapshot crops attached to tracks for timeline review.',\n default: true,\n },\n {\n type: 'slider',\n key: 'snapshotIntervalMs',\n label: 'Snapshot interval',\n description: 'How often a snapshot is persisted per active track.',\n min: 500,\n max: 60_000,\n step: 500,\n default: 2_000,\n showValue: true,\n unit: 's',\n displayScale: 1000,\n },\n {\n type: 'select',\n key: 'mediaAttachPolicy',\n label: 'Attach media to events',\n description: 'Which events get a persisted crop.',\n default: 'motion',\n options: [\n { value: 'always', label: 'Every event' },\n { value: 'motion', label: 'Only while motion is active' },\n { value: 'never', label: 'Never' },\n ],\n },\n ],\n },\n {\n id: 'audio-metrics-report',\n title: 'Audio metrics reporting',\n description: 'Coalesce audio-metrics state updates so the events tab + bus aren\\'t flooded by per-second dBFS jitter on idle scenes. Per-camera override.',\n columns: 2,\n fields: [\n {\n type: 'boolean',\n key: 'audioMetricsReportEnabled',\n label: 'Mirror to device-state',\n description: 'When off, the audio classifier still runs (the live AUDIO METRICS panel reads through the cap method), but the periodic state-changed events are suppressed. Use for cameras where audio is informational only.',\n default: true,\n },\n {\n type: 'slider',\n key: 'audioMetricsReportThresholdDb',\n label: 'Report threshold',\n description: 'Minimum dBFS delta between consecutive snapshots to trigger an emit. 2dB means quiet noise jitter (±1dB) is suppressed; class changes always emit regardless. Lower = more updates.',\n min: 0,\n max: 10,\n step: 1,\n default: 2,\n showValue: true,\n unit: 'dB',\n },\n ],\n },\n {\n id: 'retention',\n title: 'Retention',\n description: 'How long each event kind is kept in the SQL store. Media files follow the minimum of these.',\n columns: 3,\n fields: [\n {\n type: 'number',\n key: 'retentionMotionDays',\n label: 'Motion events',\n min: 1, max: 365, step: 1, default: 14, unit: 'days',\n },\n {\n type: 'number',\n key: 'retentionObjectDays',\n label: 'Object events',\n min: 1, max: 365, step: 1, default: 30, unit: 'days',\n },\n {\n type: 'number',\n key: 'retentionAudioDays',\n label: 'Audio events',\n min: 1, max: 365, step: 1, default: 7, unit: 'days',\n },\n ],\n },\n ],\n })\n }\n\n async getDeviceSettingsContribution(input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> {\n // Camera-only — analytics knobs (tracking, retention, media policy)\n // are meaningless on Lights / Switches / Sensors / Buttons. Source-\n // side gate keeps the policy with the addon owning the cap; the\n // admin-ui no longer needs the `device-section-policy` allow-list.\n if (!(await this.isCameraDevice(input.deviceId))) return null\n const schema = this.globalSettingsSchema()\n const raw = (await this.ctx?.settings?.readDeviceStore(input.deviceId)) ?? {}\n // Re-home every section into the `analytics` tab so the device-manager\n // aggregator groups pipeline-analytics knobs in one place.\n const baseSections = schema\n ? hydrateSchema(\n {\n ...schema,\n sections: schema.sections.map(s => ({ ...s, tab: s.tab ?? 'Analytics' })),\n },\n raw,\n ).sections\n : []\n // Live Stats top-tab — declares a single-column section keyed\n // by a custom-field type `'live-stats'` that the admin-ui's\n // `CustomFieldRenderersProvider` resolves to `LiveStatsTab`.\n // Mirrors how `pipeline-orchestrator` declares the Detection\n // (Zones) tab via `'zone-editor'` — the addon owns the intent\n // (which devices have audio/zone analytics), the UI owns the\n // React component.\n const liveStatsSection: ConfigSectionWithValues = {\n id: 'live-stats',\n title: 'Live Stats',\n tab: 'live-stats',\n location: 'top-tab',\n columns: 1,\n order: 0,\n fields: [\n {\n type: 'live-stats',\n key: 'liveStats',\n label: 'Live Stats',\n span: 1,\n // Renderer resolves `deviceId` via `useDeviceContext`; the\n // hydrated value is unused — kept for the form runtime\n // which requires a value field on every entry.\n value: undefined,\n },\n ],\n }\n return { sections: [...baseSections, liveStatsSection] }\n }\n\n async getDeviceLiveContribution(_input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> {\n return null\n }\n\n async applyDeviceSettingsPatch(input: { deviceId: number; patch: Record<string, unknown> }): Promise<{ success: true }> {\n await this.updateDeviceSettings(input.deviceId, input.patch)\n return { success: true as const }\n }\n\n /**\n * Best-effort camera-type check. Used to short-circuit the settings\n * contribution on non-camera devices. Returns `true` on lookup\n * failure so a transient device-manager hiccup never silently hides\n * legitimate camera sections.\n */\n private async isCameraDevice(deviceId: number): Promise<boolean> {\n const api = this.ctx?.api\n if (!api) return true\n try {\n const dev = await api.deviceManager.getDevice.query({ deviceId })\n if (!dev) return true\n return (dev as { type?: string }).type === DeviceType.Camera\n } catch {\n return true\n }\n }\n}\n\n// Re-export the FrameProcessor types so TrackedDetection consumers can\n// import from the addon root if needed.\nexport type { FrameProcessorResult, TrackedDetectionOut } from './pipeline/frame-processor.js'\n\n// Appease eslint \"unused import\" — TrackedDetection type is referenced\n// through the FrameProcessorResult below.\nexport type _TrackedDetectionBridge = TrackedDetection\n","export interface Point {\n readonly x: number\n readonly y: number\n}\n\nexport interface LineSegment {\n readonly p1: Point\n readonly p2: Point\n}\n\nexport interface BboxRect {\n readonly x: number\n readonly y: number\n readonly w: number\n readonly h: number\n}\n\nexport interface TripwireResult {\n readonly crossed: boolean\n readonly direction: 'left' | 'right'\n}\n\n/** Ray-casting point-in-polygon test */\nexport function pointInPolygon(point: Point, polygon: readonly Point[]): boolean {\n if (polygon.length < 3) return false\n let inside = false\n for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n const xi = polygon[i]!.x, yi = polygon[i]!.y\n const xj = polygon[j]!.x, yj = polygon[j]!.y\n const intersect = (yi > point.y) !== (yj > point.y)\n && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi\n if (intersect) inside = !inside\n }\n return inside\n}\n\n/** Parametric line-segment intersection. Returns intersection point or null. */\nexport function lineIntersection(a: LineSegment, b: LineSegment): Point | null {\n const dx1 = a.p2.x - a.p1.x\n const dy1 = a.p2.y - a.p1.y\n const dx2 = b.p2.x - b.p1.x\n const dy2 = b.p2.y - b.p1.y\n\n const denom = dx1 * dy2 - dy1 * dx2\n if (Math.abs(denom) < 1e-10) return null\n\n const t = ((b.p1.x - a.p1.x) * dy2 - (b.p1.y - a.p1.y) * dx2) / denom\n const u = ((b.p1.x - a.p1.x) * dy1 - (b.p1.y - a.p1.y) * dx1) / denom\n\n if (t < 0 || t > 1 || u < 0 || u > 1) return null\n\n return { x: a.p1.x + t * dx1, y: a.p1.y + t * dy1 }\n}\n\n/** Detect tripwire crossing and direction via cross product. */\nexport function tripwireCrossing(\n prev: Point,\n curr: Point,\n wire: LineSegment,\n): TripwireResult | null {\n const movement: LineSegment = { p1: prev, p2: curr }\n const intersection = lineIntersection(movement, wire)\n if (!intersection) return null\n\n const cross = (curr.x - prev.x) * (wire.p2.y - wire.p1.y)\n - (curr.y - prev.y) * (wire.p2.x - wire.p1.x)\n\n return { crossed: true, direction: cross > 0 ? 'right' : 'left' }\n}\n\n/** Centroid of a bounding box { x, y, w, h } */\nexport function bboxCentroid(bbox: BboxRect): Point {\n return { x: bbox.x + bbox.w / 2, y: bbox.y + bbox.h / 2 }\n}\n\n/** Convert normalized (0–1) coordinates to pixel coordinates */\nexport function normalizeToPixel(p: Point, width: number, height: number): Point {\n return { x: p.x * width, y: p.y * height }\n}\n","import type { SpatialDetection, TrackedDetection, BoundingBox } from '@camstack/types'\nimport { bboxCentroid } from '../zones/geometry.js'\n\nexport interface TrackerConfig {\n readonly iouThreshold: number\n readonly maxMissedFrames: number\n readonly minHits: number\n}\n\nexport const DEFAULT_TRACKER_CONFIG: TrackerConfig = {\n iouThreshold: 0.3,\n maxMissedFrames: 30,\n minHits: 3,\n}\n\ninterface InternalTrack {\n id: string\n bbox: BoundingBox\n class: string\n originalClass: string\n score: number\n age: number\n hits: number\n path: BoundingBox[]\n firstSeen: number\n lastSeen: number\n velocity: { dx: number; dy: number }\n lost: boolean\n}\n\nconst MAX_PATH_LENGTH = 300\nlet nextTrackId = 1\n\nfunction iou(a: BoundingBox, b: BoundingBox): number {\n const ax1 = a.x, ay1 = a.y, ax2 = a.x + a.w, ay2 = a.y + a.h\n const bx1 = b.x, by1 = b.y, bx2 = b.x + b.w, by2 = b.y + b.h\n\n const ix1 = Math.max(ax1, bx1), iy1 = Math.max(ay1, by1)\n const ix2 = Math.min(ax2, bx2), iy2 = Math.min(ay2, by2)\n const iw = Math.max(0, ix2 - ix1), ih = Math.max(0, iy2 - iy1)\n const interArea = iw * ih\n\n const aArea = a.w * a.h\n const bArea = b.w * b.h\n const unionArea = aArea + bArea - interArea\n\n return unionArea > 0 ? interArea / unionArea : 0\n}\n\nexport class SortTracker {\n private readonly config: TrackerConfig\n private tracks: InternalTrack[] = []\n private lostTracks: InternalTrack[] = []\n\n constructor(config: Partial<TrackerConfig> = {}) {\n this.config = { ...DEFAULT_TRACKER_CONFIG, ...config }\n }\n\n update(detections: readonly SpatialDetection[], timestamp: number): TrackedDetection[] {\n const used = new Set<number>()\n const matchedTracks = new Set<InternalTrack>()\n const matched = new Map<InternalTrack, SpatialDetection>()\n\n const pairs: { track: InternalTrack; detIdx: number; score: number }[] = []\n for (const track of this.tracks) {\n for (let di = 0; di < detections.length; di++) {\n const score = iou(track.bbox, detections[di]!.bbox)\n if (score >= this.config.iouThreshold) {\n pairs.push({ track, detIdx: di, score })\n }\n }\n }\n pairs.sort((a, b) => b.score - a.score)\n\n for (const pair of pairs) {\n if (matchedTracks.has(pair.track) || used.has(pair.detIdx)) continue\n matched.set(pair.track, detections[pair.detIdx]!)\n matchedTracks.add(pair.track)\n used.add(pair.detIdx)\n }\n\n for (const [track, det] of matched) {\n const prevCenter = bboxCentroid({ x: track.bbox.x, y: track.bbox.y, w: track.bbox.w, h: track.bbox.h })\n const newCenter = bboxCentroid({ x: det.bbox.x, y: det.bbox.y, w: det.bbox.w, h: det.bbox.h })\n\n track.bbox = det.bbox\n track.class = det.class\n track.originalClass = det.originalClass\n track.score = det.score\n track.age = 0\n track.hits++\n track.lastSeen = timestamp\n track.velocity = { dx: newCenter.x - prevCenter.x, dy: newCenter.y - prevCenter.y }\n track.path.push(det.bbox)\n if (track.path.length > MAX_PATH_LENGTH) track.path.shift()\n }\n\n const surviving: InternalTrack[] = []\n for (const track of this.tracks) {\n if (matchedTracks.has(track)) {\n surviving.push(track)\n } else {\n track.age++\n if (track.age > this.config.maxMissedFrames) {\n track.lost = true\n this.lostTracks.push(track)\n } else {\n surviving.push(track)\n }\n }\n }\n\n for (let di = 0; di < detections.length; di++) {\n if (used.has(di)) continue\n const det = detections[di]!\n surviving.push({\n id: `track-${nextTrackId++}`,\n bbox: det.bbox,\n class: det.class,\n originalClass: det.originalClass,\n score: det.score,\n age: 0,\n hits: 1,\n path: [det.bbox],\n firstSeen: timestamp,\n lastSeen: timestamp,\n velocity: { dx: 0, dy: 0 },\n lost: false,\n })\n }\n\n this.tracks = surviving\n\n return this.tracks\n .filter(t => t.hits >= this.config.minHits)\n .map(t => ({\n class: t.class,\n originalClass: t.originalClass,\n score: t.score,\n bbox: t.bbox,\n trackId: t.id,\n trackAge: t.hits,\n velocity: t.velocity,\n path: [...t.path],\n }))\n }\n\n getActiveTracks(): readonly InternalTrack[] {\n return this.tracks\n }\n\n getLostTracks(): readonly InternalTrack[] {\n return this.lostTracks\n }\n\n reset(): void {\n this.tracks = []\n this.lostTracks = []\n }\n}\n","import type { TrackedDetection, TrackedObjectState, ObjectState } from '@camstack/types'\nimport { bboxCentroid } from '../zones/geometry.js'\n\nexport interface StateAnalyzerConfig {\n readonly stationaryThresholdSec: number\n readonly loiteringThresholdSec: number\n readonly velocityThreshold: number\n readonly enteringFrames: number\n}\n\nexport const DEFAULT_STATE_ANALYZER_CONFIG: StateAnalyzerConfig = {\n stationaryThresholdSec: 10,\n loiteringThresholdSec: 60,\n velocityThreshold: 2,\n enteringFrames: 5,\n}\n\ninterface TrackState {\n enteredAt: number\n stationarySince: number | undefined\n totalDistancePx: number\n lastPosition: { x: number; y: number }\n frameCount: number\n}\n\nexport class StateAnalyzer {\n private readonly config: StateAnalyzerConfig\n private readonly states = new Map<string, TrackState>()\n\n constructor(config: Partial<StateAnalyzerConfig> = {}) {\n this.config = { ...DEFAULT_STATE_ANALYZER_CONFIG, ...config }\n }\n\n analyze(tracks: readonly TrackedDetection[], timestamp: number): TrackedObjectState[] {\n const currentIds = new Set(tracks.map(t => t.trackId))\n const results: TrackedObjectState[] = []\n\n for (const track of tracks) {\n let ts = this.states.get(track.trackId)\n const center = bboxCentroid({ x: track.bbox.x, y: track.bbox.y, w: track.bbox.w, h: track.bbox.h })\n\n if (!ts) {\n ts = {\n enteredAt: timestamp,\n stationarySince: undefined,\n totalDistancePx: 0,\n lastPosition: center,\n frameCount: 1,\n }\n this.states.set(track.trackId, ts)\n } else {\n const dx = center.x - ts.lastPosition.x\n const dy = center.y - ts.lastPosition.y\n const dist = Math.sqrt(dx * dx + dy * dy)\n ts.totalDistancePx += dist\n ts.lastPosition = center\n ts.frameCount++\n }\n\n const velocity = track.velocity\n ? Math.sqrt(track.velocity.dx ** 2 + track.velocity.dy ** 2)\n : 0\n const dwellTimeMs = timestamp - ts.enteredAt\n\n let state: ObjectState\n if (ts.frameCount <= this.config.enteringFrames) {\n state = 'entering'\n } else if (velocity <= this.config.velocityThreshold) {\n if (!ts.stationarySince) {\n ts.stationarySince = timestamp\n }\n const stationaryDurationSec = (timestamp - ts.stationarySince) / 1000\n if (stationaryDurationSec >= this.config.loiteringThresholdSec) {\n state = 'loitering'\n } else if (stationaryDurationSec >= this.config.stationaryThresholdSec) {\n state = 'stationary'\n } else {\n state = 'moving'\n }\n } else {\n ts.stationarySince = undefined\n state = 'moving'\n }\n\n results.push({\n trackId: track.trackId,\n state,\n stationarySince: ts.stationarySince,\n enteredAt: ts.enteredAt,\n totalDistancePx: ts.totalDistancePx,\n dwellTimeMs,\n })\n }\n\n for (const [trackId, ts] of this.states) {\n if (!currentIds.has(trackId)) {\n results.push({\n trackId,\n state: 'leaving',\n stationarySince: ts.stationarySince,\n enteredAt: ts.enteredAt,\n totalDistancePx: ts.totalDistancePx,\n dwellTimeMs: timestamp - ts.enteredAt,\n })\n this.states.delete(trackId)\n }\n }\n\n return results\n }\n\n reset(): void {\n this.states.clear()\n }\n}\n","export interface EventEmitterConfig {\n readonly minTrackAge: number\n readonly cooldownSec: number\n readonly enabledTypes: readonly string[]\n}\n\nexport const DEFAULT_EVENT_EMITTER_CONFIG: EventEmitterConfig = {\n minTrackAge: 3,\n cooldownSec: 5,\n enabledTypes: [\n 'object.entering', 'object.leaving', 'object.stationary', 'object.loitering',\n 'zone.enter', 'zone.exit', 'tripwire.cross',\n ],\n}\n","import type {\n TrackedDetection, TrackedObjectState, ZoneEvent,\n Classification, DetectionEvent, DetectionEventType,\n} from '@camstack/types'\nimport { DEFAULT_EVENT_EMITTER_CONFIG, type EventEmitterConfig } from './event-filter.js'\n\nexport type { EventEmitterConfig } from './event-filter.js'\n\nconst STATE_TO_EVENT: Record<string, DetectionEventType> = {\n entering: 'object.entering',\n leaving: 'object.leaving',\n stationary: 'object.stationary',\n loitering: 'object.loitering',\n}\n\nconst ZONE_TYPE_TO_EVENT: Record<string, DetectionEventType> = {\n 'zone-enter': 'zone.enter',\n 'zone-exit': 'zone.exit',\n 'zone-loiter': 'zone.enter',\n 'tripwire-cross': 'tripwire.cross',\n}\n\nlet eventIdCounter = 0\n\nexport class DetectionEventEmitter {\n private readonly config: EventEmitterConfig\n private readonly previousStates = new Map<string, string>()\n private readonly lastEmitted = new Map<string, number>()\n\n constructor(config: Partial<EventEmitterConfig> = {}) {\n this.config = { ...DEFAULT_EVENT_EMITTER_CONFIG, ...config }\n }\n\n emit(\n tracks: readonly TrackedDetection[],\n states: readonly TrackedObjectState[],\n zoneEvents: readonly ZoneEvent[],\n classifications: readonly Classification[],\n deviceId: string,\n ): DetectionEvent[] {\n const events: DetectionEvent[] = []\n const now = Date.now()\n const stateMap = new Map(states.map(s => [s.trackId, s]))\n const trackMap = new Map(tracks.map(t => [t.trackId, t]))\n\n for (const state of states) {\n const track = trackMap.get(state.trackId)\n if (!track) continue\n if (track.trackAge < this.config.minTrackAge) continue\n\n const eventType = STATE_TO_EVENT[state.state]\n if (!eventType) continue\n if (!this.config.enabledTypes.includes(eventType)) continue\n\n const prevState = this.previousStates.get(state.trackId)\n if (prevState === state.state) continue\n\n const cooldownKey = `${state.trackId}:${eventType}`\n const lastTime = this.lastEmitted.get(cooldownKey) ?? 0\n if ((now - lastTime) / 1000 < this.config.cooldownSec) continue\n\n this.previousStates.set(state.trackId, state.state)\n this.lastEmitted.set(cooldownKey, now)\n\n events.push({\n id: `evt-${++eventIdCounter}`,\n type: eventType,\n timestamp: now,\n deviceId,\n detection: track,\n classifications,\n objectState: state,\n zoneEvents: zoneEvents.filter(z => z.trackId === state.trackId),\n trackPath: [...track.path],\n })\n }\n\n for (const ze of zoneEvents) {\n const eventType = ZONE_TYPE_TO_EVENT[ze.type]\n if (!eventType) continue\n if (!this.config.enabledTypes.includes(eventType)) continue\n\n const track = trackMap.get(ze.trackId)\n if (!track || track.trackAge < this.config.minTrackAge) continue\n\n const state = stateMap.get(ze.trackId)\n\n events.push({\n id: `evt-${++eventIdCounter}`,\n type: eventType,\n timestamp: now,\n deviceId,\n detection: track,\n classifications,\n objectState: state ?? {\n trackId: ze.trackId, state: 'moving',\n enteredAt: now, totalDistancePx: 0, dwellTimeMs: 0,\n },\n zoneEvents: [ze],\n trackPath: [...track.path],\n })\n }\n\n for (const state of states) {\n if (state.state === 'leaving') {\n this.previousStates.delete(state.trackId)\n }\n }\n\n return events\n }\n\n reset(): void {\n this.previousStates.clear()\n this.lastEmitted.clear()\n }\n}\n","import { pointInPolygon, type Point, type BboxRect } from './geometry.js'\n\n/**\n * Calculate overlap of a bounding box with a polygon.\n * Uses sampling: divides bbox into grid, counts points inside polygon.\n * Returns fraction 0–1 (intersection area / bbox area).\n */\nexport function bboxPolygonOverlap(bbox: BboxRect, polygon: readonly Point[]): number {\n const area = bbox.w * bbox.h\n if (area <= 0) return 0\n\n const gridSize = 8\n let inside = 0\n const total = gridSize * gridSize\n\n for (let row = 0; row < gridSize; row++) {\n for (let col = 0; col < gridSize; col++) {\n const px = bbox.x + (col + 0.5) * (bbox.w / gridSize)\n const py = bbox.y + (row + 0.5) * (bbox.h / gridSize)\n if (pointInPolygon({ x: px, y: py }, polygon)) {\n inside++\n }\n }\n }\n\n return inside / total\n}\n\n/**\n * Calculate overlap of a segmentation mask with a polygon.\n * Mask is a binary array (0/1) at maskWidth×maskHeight resolution,\n * positioned at bbox within a frame of frameWidth×frameHeight.\n * Polygon points are in pixel coordinates.\n * Returns fraction 0–1 (mask pixels inside polygon / total mask pixels).\n */\nexport function maskPolygonOverlap(\n mask: Uint8Array,\n maskWidth: number,\n maskHeight: number,\n bbox: BboxRect,\n polygon: readonly Point[],\n _frameWidth: number,\n _frameHeight: number,\n): number {\n let totalMaskPixels = 0\n let insidePolygon = 0\n\n for (let my = 0; my < maskHeight; my++) {\n for (let mx = 0; mx < maskWidth; mx++) {\n if (mask[my * maskWidth + mx] === 0) continue\n totalMaskPixels++\n\n const frameX = bbox.x + (mx / maskWidth) * bbox.w\n const frameY = bbox.y + (my / maskHeight) * bbox.h\n\n if (pointInPolygon({ x: frameX, y: frameY }, polygon)) {\n insidePolygon++\n }\n }\n }\n\n if (totalMaskPixels === 0) return 0\n return insidePolygon / totalMaskPixels\n}\n","import type { Zone, ZoneRule } from '@camstack/types'\nimport { normalizeToPixel, type BboxRect } from './geometry.js'\nimport { bboxPolygonOverlap, maskPolygonOverlap } from './overlap.js'\n\n/**\n * Default overlap threshold applied when a rule omits both\n * `bboxInclusionPct` and the legacy `overlapThreshold`. Aligned with\n * the operator-friendly default of 85% on the schema — historically\n * this was 10% but that rarely matched intent (a person standing\n * with one foot inside a zone would count as fully inside).\n */\nconst DEFAULT_OVERLAP_THRESHOLD = 0.85\n\n/**\n * Resolve the effective 0–1 overlap threshold for a rule, preferring\n * the operator-facing `bboxInclusionPct` (0–100) when it diverges from\n * the schema default, falling back to the legacy `overlapThreshold`\n * (0–1) when present, and finally to the engine default.\n */\nfunction resolveRuleThreshold(rule: { readonly bboxInclusionPct?: number; readonly overlapThreshold?: number }): number {\n if (typeof rule.bboxInclusionPct === 'number') return rule.bboxInclusionPct / 100\n if (typeof rule.overlapThreshold === 'number') return rule.overlapThreshold\n return DEFAULT_OVERLAP_THRESHOLD\n}\n\n/**\n * Membership annotation produced by the engine when a detection\n * intersects a zone above threshold. Carried back to consumers so\n * the live UI overlay + per-event payloads can display zone tags.\n */\nexport interface DetectionZoneMembership {\n readonly zoneId: string\n readonly zoneName: string\n /** Overlap fraction (0–1). */\n readonly overlap: number\n}\n\nexport interface FilterResult<T> {\n readonly passed: readonly T[]\n readonly excluded: readonly T[]\n /** Per-detection zone memberships (every zone with overlap above\n * the rule threshold, regardless of include/exclude verdict). */\n readonly annotations: ReadonlyMap<T, readonly DetectionZoneMembership[]>\n}\n\ninterface ResolvedRule {\n readonly rule: ZoneRule\n readonly threshold: number\n readonly zonesById: ReadonlyMap<string, Zone>\n}\n\n/**\n * Pure zone engine — accepts a zone catalogue + a rule set and\n * decides which detections pass for a stage.\n *\n * Stage gating semantics:\n * - any active `include` rule fires ⇒ whitelist mode: a detection\n * passes iff it satisfies at least one include rule\n * - otherwise ⇒ blacklist mode: a detection passes iff no exclude\n * rule fires for it\n *\n * Inactive rules (`enabled: false`) are skipped before the gate is\n * evaluated so a partially-toggled rule set behaves intuitively.\n *\n * Rule satisfaction is decided per-rule:\n * 1. If the rule has a non-empty `classFilter` and the detection's\n * class isn't in it, the rule is skipped for this detection.\n * 2. Otherwise the engine tests overlap against every zone the\n * rule references; the rule fires if ANY of those overlaps\n * meets `rule.overlapThreshold` (or the engine default).\n */\nexport class ZoneEngine {\n /**\n * Annotate a single detection with its zone memberships.\n * Returns zones where the detection overlaps above any active\n * rule's threshold (or the engine default if no rule sets one).\n */\n annotateDetection(\n bbox: BboxRect,\n zones: readonly Zone[],\n frameWidth: number,\n frameHeight: number,\n mask?: Uint8Array,\n maskWidth?: number,\n maskHeight?: number,\n ): DetectionZoneMembership[] {\n const memberships: DetectionZoneMembership[] = []\n for (const zone of zones) {\n const pixelPolygon = zone.polygon.map(p => normalizeToPixel(p, frameWidth, frameHeight))\n const overlap = mask && maskWidth && maskHeight\n ? maskPolygonOverlap(mask, maskWidth, maskHeight, bbox, pixelPolygon, frameWidth, frameHeight)\n : bboxPolygonOverlap(bbox, pixelPolygon)\n if (overlap >= DEFAULT_OVERLAP_THRESHOLD) {\n memberships.push({ zoneId: zone.id, zoneName: zone.name, overlap })\n }\n }\n return memberships\n }\n\n /**\n * Filter detections through a zone-rule set. `zones` provides the\n * geometry catalogue; `rules` decides include / exclude behaviour.\n * Pass an empty `rules` array to short-circuit (everything passes).\n */\n filterDetections<T extends BboxRect>(\n detections: readonly T[],\n zones: readonly Zone[],\n rules: readonly ZoneRule[],\n frameWidth: number,\n frameHeight: number,\n getClassName?: (d: T) => string | undefined,\n getMask?: (d: T) => { mask: Uint8Array; width: number; height: number } | undefined,\n ): FilterResult<T> {\n const annotations = new Map<T, DetectionZoneMembership[]>()\n if (rules.length === 0 || zones.length === 0) {\n for (const det of detections) {\n annotations.set(det, this.annotateDetection(\n det, zones, frameWidth, frameHeight,\n ))\n }\n return { passed: detections, excluded: [], annotations }\n }\n\n const zonesById = new Map(zones.map(z => [z.id, z]))\n const activeRules: ResolvedRule[] = rules\n .filter(r => r.enabled !== false)\n .map(rule => ({\n rule,\n threshold: resolveRuleThreshold(rule),\n zonesById,\n }))\n\n const includeRules = activeRules.filter(r => r.rule.mode === 'include')\n const excludeRules = activeRules.filter(r => r.rule.mode === 'exclude')\n const whitelistMode = includeRules.length > 0\n\n const passed: T[] = []\n const excluded: T[] = []\n\n for (const det of detections) {\n const memberships = this.annotateDetection(det, zones, frameWidth, frameHeight)\n annotations.set(det, memberships)\n\n const className = getClassName?.(det)\n const maskInfo = getMask?.(det)\n\n const inIncludeRule = includeRules.some(r =>\n ruleApplies(r, det, className, maskInfo, zones, frameWidth, frameHeight),\n )\n const inExcludeRule = excludeRules.some(r =>\n ruleApplies(r, det, className, maskInfo, zones, frameWidth, frameHeight),\n )\n\n if (whitelistMode) {\n if (inIncludeRule && !inExcludeRule) {\n passed.push(det)\n } else {\n excluded.push(det)\n }\n } else {\n if (inExcludeRule) {\n excluded.push(det)\n } else {\n passed.push(det)\n }\n }\n }\n\n return { passed, excluded, annotations }\n }\n}\n\nfunction ruleApplies<T extends BboxRect>(\n resolved: ResolvedRule,\n det: T,\n className: string | undefined,\n maskInfo: { mask: Uint8Array; width: number; height: number } | undefined,\n _zones: readonly Zone[],\n frameWidth: number,\n frameHeight: number,\n): boolean {\n const { rule, threshold, zonesById } = resolved\n if (rule.classFilter && rule.classFilter.length > 0) {\n if (!className || !rule.classFilter.includes(className)) return false\n }\n for (const zoneId of rule.zoneIds) {\n const zone = zonesById.get(zoneId)\n if (!zone) continue\n const pixelPolygon = zone.polygon.map(p => normalizeToPixel(p, frameWidth, frameHeight))\n const overlap = rule.preferMask && maskInfo\n ? maskPolygonOverlap(maskInfo.mask, maskInfo.width, maskInfo.height, det, pixelPolygon, frameWidth, frameHeight)\n : bboxPolygonOverlap(det, pixelPolygon)\n if (overlap >= threshold) return true\n }\n return false\n}\n","/**\n * FrameProcessor — per-device pipeline stage runner.\n *\n * Consumes a raw `PipelineInferenceResult` payload and runs:\n * 1. Flatten first-level detections\n * 2. Zone filter (ZoneEngine)\n * 3. Tracking (SortTracker)\n * 4. State analysis (StateAnalyzer)\n * 5. Event emission (DetectionEventEmitter)\n *\n * Returns a structured result the addon uses to:\n * - upsert TrackStore\n * - insert ObjectEvent rows into the declared collection\n * - emit onFrameTracked / onDetectionEvent on the bus\n *\n * One FrameProcessor per device — lifecycle managed by DeviceRegistry.\n */\n\nimport type {\n FrameResult, SpatialDetection, BoundingBox, Zone, ZoneRule,\n TrackedDetection, TrackState, ObjectEvent,\n} from '@camstack/types'\nimport type { BboxRect } from './zones/geometry.js'\nimport { SortTracker } from './tracker/sort-tracker.js'\nimport { StateAnalyzer } from './tracker/state-analyzer.js'\nimport { DetectionEventEmitter } from './events/event-emitter.js'\nimport { ZoneEngine } from './zones/zone-engine.js'\nimport { randomUUID } from 'node:crypto'\n\ninterface FlatDetection extends BboxRect {\n readonly detection: SpatialDetection\n readonly sourceId: string\n}\n\nexport interface TrackedDetectionOut {\n readonly trackId: string\n readonly className: string\n readonly confidence: number\n readonly bbox: { x: number; y: number; w: number; h: number }\n readonly zones: readonly string[]\n readonly state: TrackState\n}\n\nexport interface FrameProcessorResult {\n readonly deviceId: number\n readonly timestamp: number\n readonly frameWidth: number\n readonly frameHeight: number\n readonly tracked: readonly TrackedDetectionOut[]\n readonly objectEvents: readonly ObjectEvent[]\n /** Raw TrackedDetection array — consumed by TrackStore for position\n * history updates. Kept separate from `tracked` which is the\n * lightweight shape consumed by the bus emit. */\n readonly rawTrackedDetections: readonly TrackedDetection[]\n}\n\n/** Mapping from StateAnalyzer's `ObjectState.state` values to the\n * canonical TrackState enum used on tracks + events. */\nfunction mapObjectStateToTrackState(s: string | undefined): TrackState {\n switch (s) {\n case 'entering': return 'entered'\n case 'leaving': return 'left'\n case 'stationary':\n case 'loitering':\n return 'idle'\n case 'moving': return 'moving'\n default: return 'new'\n }\n}\n\nexport class FrameProcessor {\n readonly deviceId: number\n private readonly tracker: SortTracker\n private readonly stateAnalyzer: StateAnalyzer\n private readonly eventEmitter: DetectionEventEmitter\n private zones: readonly Zone[]\n /**\n * Detection-stage rules — applied as a safety-net post-pipeline\n * filter. When motion-wasm + pipeline-executor land their own\n * runtime gating (Phase 2b) the upstream pipeline already drops\n * filtered detections; keeping the analytics-side filter as well\n * ensures the same semantics for legacy paths and forked-worker\n * runners that haven't picked up the new gating yet.\n */\n private detectionRules: readonly ZoneRule[]\n private readonly zoneEngine = new ZoneEngine()\n\n constructor(deviceId: number) {\n this.deviceId = deviceId\n this.tracker = new SortTracker()\n this.stateAnalyzer = new StateAnalyzer()\n this.eventEmitter = new DetectionEventEmitter()\n this.zones = []\n this.detectionRules = []\n }\n\n setZones(zones: readonly Zone[]): void {\n this.zones = zones\n }\n\n setDetectionRules(rules: readonly ZoneRule[]): void {\n this.detectionRules = rules\n }\n\n process(input: { readonly timestamp: number; readonly frame: FrameResult }): FrameProcessorResult {\n const { timestamp, frame } = input\n const frameWidth = frame.width\n const frameHeight = frame.height\n\n // 1. Flatten first-level detections\n const flatDetections: FlatDetection[] = frame.detections\n .filter(d => d.kind === 'first-level')\n .map(det => {\n const bbox: BoundingBox = {\n x: det.bbox.x,\n y: det.bbox.y,\n w: det.bbox.width,\n h: det.bbox.height,\n }\n const detection: SpatialDetection = {\n class: det.macroClass,\n originalClass: det.debug?.originalClass ?? det.macroClass,\n score: det.score,\n bbox,\n }\n return { ...bbox, detection, sourceId: det.id }\n })\n\n // 2. Zone filter (detection stage) — driven by `detectionZoneRules`\n // forwarded with the runner config. Empty rules ⇒ everything passes.\n const { passed } = this.zoneEngine.filterDetections(\n flatDetections,\n this.zones,\n this.detectionRules,\n frameWidth,\n frameHeight,\n fd => fd.detection.class,\n )\n\n const filteredDetections: SpatialDetection[] = passed.map(fd => fd.detection)\n\n // 3. Track\n const trackedDetections = this.tracker.update(filteredDetections, timestamp)\n\n // 4. State analysis\n const objectStates = this.stateAnalyzer.analyze(trackedDetections, timestamp)\n\n // 5. Event emission — zone events empty since we only filter on detection\n // stage here; full zone entry/exit events are a follow-up.\n const rawEvents = this.eventEmitter.emit(\n trackedDetections,\n objectStates,\n [],\n [],\n String(this.deviceId),\n )\n\n // Build lightweight output + per-track zone lookup. Annotation\n // is geometry-only (no class narrowing): a track is \"in\" every\n // zone whose polygon overlaps its bbox above the engine\n // threshold. Per-class semantics live on rules, not on the\n // membership annotation.\n const zonesByTrack = new Map<string, readonly string[]>()\n for (const td of trackedDetections) {\n const memberships = this.zoneEngine.annotateDetection(\n td.bbox,\n this.zones,\n frameWidth,\n frameHeight,\n )\n zonesByTrack.set(td.trackId, memberships.map(m => m.zoneId))\n }\n\n const tracked: TrackedDetectionOut[] = trackedDetections.map(td => {\n const state = mapObjectStateToTrackState(\n objectStates.find(o => o.trackId === td.trackId)?.state,\n )\n return {\n trackId: td.trackId,\n className: td.class,\n confidence: td.score,\n bbox: { ...td.bbox },\n zones: zonesByTrack.get(td.trackId) ?? [],\n state,\n }\n })\n\n // 6. Turn raw events into ObjectEvent rows (cap schema). One event\n // per detected transition — the event emitter already dedupes.\n const objectEvents: ObjectEvent[] = rawEvents\n .filter(e => e.detection.trackId)\n .map(e => {\n const td = trackedDetections.find(t => t.trackId === e.detection.trackId)\n const state = mapObjectStateToTrackState(\n objectStates.find(o => o.trackId === e.detection.trackId)?.state,\n )\n const zones = zonesByTrack.get(e.detection.trackId) ?? []\n return {\n id: randomUUID(),\n deviceId: this.deviceId,\n timestamp,\n kind: 'object' as const,\n trackId: e.detection.trackId,\n className: e.detection.class,\n confidence: e.detection.score,\n bbox: td ? { ...td.bbox } : { x: 0, y: 0, w: 0, h: 0 },\n zones,\n state,\n }\n })\n\n return {\n deviceId: this.deviceId,\n timestamp,\n frameWidth,\n frameHeight,\n tracked,\n objectEvents,\n rawTrackedDetections: trackedDetections,\n }\n }\n}\n","/**\n * BindingCache — in-memory cache of `pipeline-analytics` wrapper binding\n * state per device. Populated lazily on first lookup via\n * `device-manager.getBindings`, invalidated by `DeviceBindingsChanged`\n * events for the same cap.\n *\n * Read path is frame-rate hot (`PipelineInferenceResult` fires ~10fps\n * per camera). Avoiding a tRPC round-trip for every frame matters.\n * Cache is event-driven (no TTL), so the source of truth stays\n * device-manager.\n */\n\nimport type { AddonApi, IScopedLogger } from '@camstack/types'\n\nexport interface BindingCacheDeps {\n readonly api: AddonApi\n readonly logger: IScopedLogger\n readonly capName?: string\n}\n\nexport class BindingCache {\n private readonly api: AddonApi\n private readonly logger: IScopedLogger\n private readonly capName: string\n private readonly state = new Map<number, boolean>()\n private readonly inflight = new Map<number, Promise<boolean>>()\n\n constructor(deps: BindingCacheDeps) {\n this.api = deps.api\n this.logger = deps.logger\n this.capName = deps.capName ?? 'pipeline-analytics'\n }\n\n async isActive(deviceId: number): Promise<boolean> {\n const cached = this.state.get(deviceId)\n if (cached !== undefined) return cached\n const pending = this.inflight.get(deviceId)\n if (pending) return pending\n const promise = (async () => {\n try {\n const res = await this.api.deviceManager.getBindings.query({ deviceId })\n const active = res.entries.some(e => e.capName === this.capName)\n this.state.set(deviceId, active)\n return active\n } catch (err) {\n this.logger.debug('BindingCache.isActive lookup failed', {\n tags: { deviceId },\n meta: { error: String(err) },\n })\n // Conservative default: treat as inactive so we don't write\n // garbage while the cap registry is racing to come up.\n return false\n } finally {\n this.inflight.delete(deviceId)\n }\n })()\n this.inflight.set(deviceId, promise)\n return promise\n }\n\n onBindingsChanged(event: {\n readonly deviceId: number\n readonly capName: string\n readonly reason: 'native-registered' | 'native-unregistered' | 'wrapper-activated' | 'wrapper-deactivated'\n }): void {\n if (event.capName !== this.capName) return\n if (event.reason === 'wrapper-activated') {\n this.state.set(event.deviceId, true)\n return\n }\n if (event.reason === 'wrapper-deactivated') {\n this.state.set(event.deviceId, false)\n return\n }\n // native-registered / native-unregistered don't apply to a wrapper\n // cap — just invalidate so the next lookup re-fetches.\n this.state.delete(event.deviceId)\n }\n\n invalidate(deviceId: number): void {\n this.state.delete(deviceId)\n }\n\n clearAll(): void {\n this.state.clear()\n this.inflight.clear()\n }\n}\n","/**\n * TrackStore — unified in-memory state + persisted history for tracked\n * objects. Active tracks (within TTL) live in RAM only; expired tracks\n * are written to the declared `tracks` collection for historical review\n * (UI timeline, forensic queries).\n *\n * Per the user's direction (P4 design): active tracks are never\n * persisted on cold-start — if the hub restarts, the tracker starts\n * fresh and the analysis pipeline picks up new tracks from the next\n * frame. Historical completed tracks stay queryable forever (subject to\n * retention).\n */\n\nimport type { SettingsStoreClient, Track, TrackState } from '@camstack/types'\nimport type { IScopedLogger } from '@camstack/types'\n\nexport interface TrackPosition {\n readonly x: number\n readonly y: number\n readonly timestamp: number\n readonly bbox: { x: number; y: number; w: number; h: number }\n}\n\nexport interface TrackSnapshot {\n readonly timestamp: number\n readonly position: TrackPosition\n readonly mediaKey: string\n}\n\nexport interface MutableTrackState {\n trackId: string\n deviceId: number\n className: string\n label?: string\n firstSeen: number\n lastSeen: number\n positions: TrackPosition[]\n snapshots: TrackSnapshot[]\n zonesVisited: string[]\n totalDistance: number\n state: TrackState\n active: boolean\n lastSnapshotAt: number\n}\n\nexport interface TrackStoreConfig {\n /** Milliseconds without a sighting before a track is marked inactive. */\n readonly ttlMs: number\n /** Maximum positions retained per track (FIFO trim beyond this). */\n readonly maxPositionHistory: number\n}\n\nconst DEFAULT_CONFIG: TrackStoreConfig = {\n ttlMs: 30_000,\n maxPositionHistory: 300,\n}\n\nexport const TRACKS_COLLECTION = 'pipeline-analytics:tracks'\n\nexport const TRACKS_COLUMNS = [\n { name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true },\n { name: 'deviceId', type: 'INTEGER' as const, notNull: true },\n { name: 'className', type: 'TEXT' as const, notNull: true },\n { name: 'label', type: 'TEXT' as const },\n { name: 'firstSeen', type: 'INTEGER' as const, notNull: true },\n { name: 'lastSeen', type: 'INTEGER' as const, notNull: true },\n { name: 'positions', type: 'JSON' as const },\n { name: 'snapshots', type: 'JSON' as const },\n { name: 'zonesVisited', type: 'JSON' as const },\n { name: 'totalDistance', type: 'REAL' as const },\n { name: 'state', type: 'TEXT' as const },\n] as const\n\nexport const TRACKS_INDEXES = [\n { name: 'idx_tracks_device_lastSeen', columns: ['deviceId', 'lastSeen'] as const },\n { name: 'idx_tracks_device_firstSeen', columns: ['deviceId', 'firstSeen'] as const },\n] as const\n\nexport interface TrackStoreDeps {\n readonly store: SettingsStoreClient\n readonly logger: IScopedLogger\n readonly config?: Partial<TrackStoreConfig>\n}\n\nfunction cloneTrack(t: MutableTrackState): Track {\n return {\n trackId: t.trackId,\n deviceId: t.deviceId,\n className: t.className,\n label: t.label,\n firstSeen: t.firstSeen,\n lastSeen: t.lastSeen,\n positions: t.positions.map(p => ({ ...p, bbox: { ...p.bbox } })),\n snapshots: t.snapshots.map(s => ({ ...s, position: { ...s.position, bbox: { ...s.position.bbox } } })),\n zonesVisited: [...t.zonesVisited],\n totalDistance: t.totalDistance,\n state: t.state,\n active: t.active,\n }\n}\n\nexport class TrackStore {\n private readonly active = new Map<string, MutableTrackState>()\n private readonly config: TrackStoreConfig\n private readonly logger: IScopedLogger\n private readonly store: SettingsStoreClient\n\n constructor(deps: TrackStoreDeps) {\n this.logger = deps.logger\n this.store = deps.store\n this.config = { ...DEFAULT_CONFIG, ...deps.config }\n }\n\n /** One-time collection declaration. Call from addon onInitialize. */\n static async declare(store: SettingsStoreClient): Promise<void> {\n await store.declareCollection.mutate({\n collection: TRACKS_COLLECTION,\n columns: [...TRACKS_COLUMNS],\n indexes: [...TRACKS_INDEXES],\n })\n }\n\n /** Create or update the track record for a sighting in this frame. */\n upsert(params: {\n trackId: string\n deviceId: number\n className: string\n label?: string\n timestamp: number\n position: TrackPosition\n zones: readonly string[]\n state: TrackState\n }): MutableTrackState {\n const existing = this.active.get(params.trackId)\n if (existing) {\n const last = existing.positions[existing.positions.length - 1]\n const dist = last\n ? Math.sqrt((params.position.x - last.x) ** 2 + (params.position.y - last.y) ** 2)\n : 0\n existing.lastSeen = params.timestamp\n existing.totalDistance += dist\n existing.state = params.state\n if (existing.positions.length >= this.config.maxPositionHistory) {\n // Downsample first half: keep every other, preserve second half.\n const half = Math.floor(existing.positions.length / 2)\n existing.positions = existing.positions\n .filter((_, i) => i >= half || i % 2 === 0)\n existing.positions.push(params.position)\n } else {\n existing.positions.push(params.position)\n }\n for (const z of params.zones) {\n if (!existing.zonesVisited.includes(z)) existing.zonesVisited.push(z)\n }\n return existing\n }\n const fresh: MutableTrackState = {\n trackId: params.trackId,\n deviceId: params.deviceId,\n className: params.className,\n ...(params.label !== undefined ? { label: params.label } : {}),\n firstSeen: params.timestamp,\n lastSeen: params.timestamp,\n positions: [params.position],\n snapshots: [],\n zonesVisited: [...params.zones],\n totalDistance: 0,\n state: params.state,\n active: true,\n lastSnapshotAt: 0,\n }\n this.active.set(params.trackId, fresh)\n return fresh\n }\n\n /** Attach a snapshot reference to an active track. */\n addSnapshot(trackId: string, snapshot: TrackSnapshot): void {\n const t = this.active.get(trackId)\n if (!t) return\n t.snapshots.push(snapshot)\n t.lastSnapshotAt = snapshot.timestamp\n }\n\n lastSnapshotAt(trackId: string): number {\n return this.active.get(trackId)?.lastSnapshotAt ?? 0\n }\n\n getActive(deviceId: number): readonly Track[] {\n const out: Track[] = []\n for (const t of this.active.values()) {\n if (t.deviceId === deviceId && t.active) out.push(cloneTrack(t))\n }\n return out\n }\n\n getActiveByTrack(trackId: string): Track | null {\n const t = this.active.get(trackId)\n return t && t.active ? cloneTrack(t) : null\n }\n\n /** Expire tracks whose `lastSeen` is older than TTL. Persists each\n * expired track to the declared collection and returns them. */\n async expireStale(nowMs: number): Promise<readonly Track[]> {\n const expired: Track[] = []\n for (const [trackId, t] of this.active) {\n if (nowMs - t.lastSeen < this.config.ttlMs) continue\n t.active = false\n const record = cloneTrack(t)\n try {\n await this.persistCompleted(record)\n } catch (err) {\n this.logger.warn('persist completed track failed', {\n meta: { trackId, error: String(err) },\n })\n }\n this.active.delete(trackId)\n expired.push(record)\n }\n return expired\n }\n\n /** Discard active tracks for a device without persisting (used on\n * operator clearTracks / device unregistration). */\n clearDevice(deviceId: number): void {\n for (const [trackId, t] of this.active) {\n if (t.deviceId === deviceId) this.active.delete(trackId)\n }\n }\n\n clearAll(): void {\n this.active.clear()\n }\n\n /** Historical query — hits the persisted collection. */\n async queryHistorical(params: {\n deviceId: number\n since?: number\n until?: number\n limit?: number\n }): Promise<readonly Track[]> {\n const filter: Record<string, unknown> = { where: { deviceId: params.deviceId } }\n if (params.since !== undefined || params.until !== undefined) {\n ;(filter as { whereBetween: Record<string, [unknown, unknown]> }).whereBetween = {\n firstSeen: [params.since ?? 0, params.until ?? Date.now()],\n }\n }\n const records = await this.store.query.query({\n collection: TRACKS_COLLECTION,\n filter: {\n ...filter,\n orderBy: { field: 'firstSeen', direction: 'desc' },\n limit: params.limit ?? 50,\n },\n })\n return records.map(r => this.rowToTrack(r.id, r.data))\n }\n\n async getPersistedByTrackId(trackId: string): Promise<Track | null> {\n const records = await this.store.query.query({\n collection: TRACKS_COLLECTION,\n filter: { where: { id: trackId }, limit: 1 },\n })\n if (records.length === 0) return null\n const row = records[0]!\n return this.rowToTrack(row.id, row.data)\n }\n\n private async persistCompleted(t: Track): Promise<void> {\n // `set` upserts — tolerates re-persists of the same trackId\n // (e.g. a track that expires twice because `expireStale` fires\n // before the previous call finishes clearing `this.active`).\n // `insert` would throw `UNIQUE constraint failed` on the second\n // attempt and leave the track un-persisted.\n await this.store.set.mutate({\n collection: TRACKS_COLLECTION,\n key: t.trackId,\n value: {\n deviceId: t.deviceId,\n className: t.className,\n ...(t.label !== undefined ? { label: t.label } : {}),\n firstSeen: t.firstSeen,\n lastSeen: t.lastSeen,\n positions: [...t.positions],\n snapshots: [...t.snapshots],\n zonesVisited: [...t.zonesVisited],\n totalDistance: t.totalDistance,\n state: t.state,\n },\n })\n }\n\n private rowToTrack(id: string, data: Record<string, unknown>): Track {\n const positions = (data['positions'] as TrackPosition[] | null) ?? []\n const snapshots = (data['snapshots'] as TrackSnapshot[] | null) ?? []\n const zones = (data['zonesVisited'] as string[] | null) ?? []\n const label = data['label']\n return {\n trackId: id,\n deviceId: Number(data['deviceId']),\n className: String(data['className']),\n ...(typeof label === 'string' ? { label } : {}),\n firstSeen: Number(data['firstSeen']),\n lastSeen: Number(data['lastSeen']),\n positions,\n snapshots,\n zonesVisited: zones,\n totalDistance: Number(data['totalDistance'] ?? 0),\n state: (data['state'] as TrackState) ?? 'idle',\n active: false,\n }\n }\n}\n","/**\n * MediaStore — unified filesystem persistence for crops, thumbnails and\n * track snapshots. Replaces three scattered systems the old addon used\n * (EventPersistenceService `events/{eventId}/`, TrackTrailService\n * `debug-trails/{trackId}/`, ThumbnailExtractor `recording_thumbnails`).\n *\n * All media lives under one tree:\n * <storage:data>/pipeline-analytics/<deviceId>/<kind>/<ownerId>/<ts>.jpg\n *\n * where:\n * - `kind` is 'event' | 'track' (trail snapshots)\n * - `ownerId` is the eventId or trackId\n * - `ts` is the frame timestamp in ms\n *\n * The store tracks per-entry metadata in a typed collection\n * `pipeline-analytics:media` so retention can walk the index without\n * listing the FS, and queries by owner don't need a glob pattern.\n */\n\nimport type { IStorageProvider, SettingsStoreClient, MediaFile } from '@camstack/types'\nimport type { IScopedLogger } from '@camstack/types'\n\nexport type MediaKind = 'crop' | 'thumbnail' | 'snapshot'\n\nexport interface MediaPutInput {\n readonly deviceId: number\n readonly ownerKind: 'event' | 'track'\n readonly ownerId: string\n readonly kind: MediaKind\n readonly timestamp: number\n readonly data: Buffer\n}\n\nexport const MEDIA_COLLECTION = 'pipeline-analytics:media'\n\nexport const MEDIA_COLUMNS = [\n { name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true },\n { name: 'deviceId', type: 'INTEGER' as const, notNull: true },\n { name: 'ownerKind', type: 'TEXT' as const, notNull: true },\n { name: 'ownerId', type: 'TEXT' as const, notNull: true },\n { name: 'kind', type: 'TEXT' as const, notNull: true },\n { name: 'timestamp', type: 'INTEGER' as const, notNull: true },\n { name: 'path', type: 'TEXT' as const, notNull: true },\n { name: 'sizeBytes', type: 'INTEGER' as const, notNull: true },\n] as const\n\nexport const MEDIA_INDEXES = [\n { name: 'idx_media_owner', columns: ['ownerKind', 'ownerId'] as const },\n { name: 'idx_media_device_ts', columns: ['deviceId', 'timestamp'] as const },\n] as const\n\nexport interface MediaStoreDeps {\n readonly storage: IStorageProvider\n readonly store: SettingsStoreClient\n readonly logger: IScopedLogger\n}\n\nfunction buildKey(params: MediaPutInput): string {\n return `${params.ownerKind}:${params.ownerId}:${params.kind}:${params.timestamp}`\n}\n\nfunction buildPath(params: MediaPutInput): string {\n return `pipeline-analytics/${params.deviceId}/${params.ownerKind}/${params.ownerId}/${params.kind}-${params.timestamp}.jpg`\n}\n\nexport class MediaStore {\n private readonly storage: IStorageProvider\n private readonly store: SettingsStoreClient\n private readonly logger: IScopedLogger\n\n constructor(deps: MediaStoreDeps) {\n this.storage = deps.storage\n this.store = deps.store\n this.logger = deps.logger\n }\n\n static async declare(store: SettingsStoreClient): Promise<void> {\n await store.declareCollection.mutate({\n collection: MEDIA_COLLECTION,\n columns: [...MEDIA_COLUMNS],\n indexes: [...MEDIA_INDEXES],\n })\n }\n\n /** Persist a media blob and register its metadata row. Returns the\n * collection key (MediaFile.key) that callers store as reference. */\n async put(params: MediaPutInput): Promise<string> {\n const key = buildKey(params)\n const path = buildPath(params)\n try {\n await this.storage.write({ location: 'data', relativePath: path, data: params.data })\n await this.store.insert.mutate({\n collection: MEDIA_COLLECTION,\n record: {\n id: key,\n data: {\n deviceId: params.deviceId,\n ownerKind: params.ownerKind,\n ownerId: params.ownerId,\n kind: params.kind,\n timestamp: params.timestamp,\n path,\n sizeBytes: params.data.length,\n },\n },\n })\n return key\n } catch (err) {\n this.logger.warn('media put failed', { meta: { key, error: String(err) } })\n throw err\n }\n }\n\n async listByOwner(ownerKind: 'event' | 'track', ownerId: string): Promise<readonly MediaFile[]> {\n const rows = await this.store.query.query({\n collection: MEDIA_COLLECTION,\n filter: { where: { ownerKind, ownerId }, orderBy: { field: 'timestamp', direction: 'asc' } },\n })\n const files: MediaFile[] = []\n for (const row of rows) {\n const data = row.data as Record<string, unknown>\n const path = String(data['path'])\n const timestamp = Number(data['timestamp'])\n const sizeBytes = Number(data['sizeBytes'])\n const kind = String(data['kind']) as MediaKind\n try {\n const buf = await this.storage.read({ location: 'data', relativePath: path })\n files.push({\n key: row.id,\n kind,\n base64: buf.toString('base64'),\n sizeBytes,\n timestamp,\n })\n } catch (err) {\n this.logger.debug('media read failed — row kept but blob missing', {\n meta: { key: row.id, path, error: String(err) },\n })\n }\n }\n return files\n }\n\n /** Retention sweep: delete any media row + blob older than cutoff.\n * Returns number of entries removed. */\n async evictBefore(cutoffMs: number): Promise<number> {\n const rows = await this.store.query.query({\n collection: MEDIA_COLLECTION,\n filter: { whereBetween: { timestamp: [0, cutoffMs] }, limit: 500 },\n })\n let removed = 0\n for (const row of rows) {\n const path = String((row.data as Record<string, unknown>)['path'] ?? '')\n try {\n if (path) await this.storage.delete({ location: 'data', relativePath: path })\n } catch { /* best-effort */ }\n try {\n await this.store.delete.mutate({ collection: MEDIA_COLLECTION, key: row.id })\n removed++\n } catch (err) {\n this.logger.debug('media evict delete failed', {\n meta: { key: row.id, error: String(err) },\n })\n }\n }\n return removed\n }\n}\n","/**\n * EventStore — per-kind event collections for motion / object / audio.\n *\n * Three SQL-backed collections with independent schemas, declared once\n * in onInitialize via `declareCollection` (P4). Each has typed\n * (deviceId, timestamp) indexes so the UI timeline gets sub-linear\n * queries even at high volume.\n *\n * Collection names:\n * - pipeline-analytics:motion-events\n * - pipeline-analytics:object-events\n * - pipeline-analytics:audio-events\n */\n\nimport type {\n SettingsStoreClient,\n MotionEvent, ObjectEvent, AudioEvent,\n IScopedLogger,\n} from '@camstack/types'\n\nexport const MOTION_EVENTS_COLLECTION = 'pipeline-analytics:motion-events'\nexport const OBJECT_EVENTS_COLLECTION = 'pipeline-analytics:object-events'\nexport const AUDIO_EVENTS_COLLECTION = 'pipeline-analytics:audio-events'\n\nconst COMMON_BASE_COLUMNS = [\n { name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true },\n { name: 'deviceId', type: 'INTEGER' as const, notNull: true },\n { name: 'timestamp', type: 'INTEGER' as const, notNull: true },\n] as const\n\nconst MOTION_COLUMNS = [\n ...COMMON_BASE_COLUMNS,\n { name: 'regionCount', type: 'INTEGER' as const, notNull: true },\n { name: 'regions', type: 'JSON' as const },\n { name: 'frameWidth', type: 'INTEGER' as const },\n { name: 'frameHeight', type: 'INTEGER' as const },\n] as const\n\nconst OBJECT_COLUMNS = [\n ...COMMON_BASE_COLUMNS,\n { name: 'trackId', type: 'TEXT' as const, notNull: true },\n { name: 'className', type: 'TEXT' as const, notNull: true },\n { name: 'label', type: 'TEXT' as const },\n { name: 'confidence', type: 'REAL' as const, notNull: true },\n { name: 'bbox', type: 'JSON' as const },\n { name: 'zones', type: 'JSON' as const },\n { name: 'state', type: 'TEXT' as const },\n { name: 'mediaKey', type: 'TEXT' as const },\n] as const\n\nconst AUDIO_COLUMNS = [\n ...COMMON_BASE_COLUMNS,\n { name: 'rms', type: 'REAL' as const, notNull: true },\n { name: 'dbfs', type: 'REAL' as const, notNull: true },\n { name: 'classification', type: 'JSON' as const },\n] as const\n\nconst COMMON_INDEXES = (prefix: string) => [\n { name: `idx_${prefix}_device_ts`, columns: ['deviceId', 'timestamp'] as const },\n]\n\nexport interface EventStoreDeps {\n readonly store: SettingsStoreClient\n readonly logger: IScopedLogger\n}\n\nexport interface EventQuery {\n readonly deviceId: number\n readonly since?: number\n readonly until?: number\n readonly limit?: number\n}\n\nexport interface ObjectEventQuery extends EventQuery {\n readonly classFilter?: string\n}\n\nexport class EventStore {\n private readonly store: SettingsStoreClient\n private readonly logger: IScopedLogger\n\n constructor(deps: EventStoreDeps) {\n this.store = deps.store\n this.logger = deps.logger\n }\n\n static async declare(store: SettingsStoreClient): Promise<void> {\n await store.declareCollection.mutate({\n collection: MOTION_EVENTS_COLLECTION,\n columns: [...MOTION_COLUMNS],\n indexes: COMMON_INDEXES('motion'),\n })\n await store.declareCollection.mutate({\n collection: OBJECT_EVENTS_COLLECTION,\n columns: [...OBJECT_COLUMNS],\n indexes: [\n ...COMMON_INDEXES('object'),\n { name: 'idx_object_track', columns: ['trackId'] as const },\n ],\n })\n await store.declareCollection.mutate({\n collection: AUDIO_EVENTS_COLLECTION,\n columns: [...AUDIO_COLUMNS],\n indexes: COMMON_INDEXES('audio'),\n })\n }\n\n // ── Writes ────────────────────────────────────────────────────────\n\n async insertMotion(ev: MotionEvent): Promise<void> {\n try {\n const { id, ...rest } = ev\n await this.store.insert.mutate({\n collection: MOTION_EVENTS_COLLECTION,\n record: { id, data: rest as unknown as Record<string, unknown> },\n })\n } catch (err) {\n this.logger.warn('insertMotion failed', { meta: { eventId: ev.id, error: String(err) } })\n }\n }\n\n async insertObject(ev: ObjectEvent): Promise<void> {\n try {\n const { id, ...rest } = ev\n await this.store.insert.mutate({\n collection: OBJECT_EVENTS_COLLECTION,\n record: { id, data: rest as unknown as Record<string, unknown> },\n })\n } catch (err) {\n this.logger.warn('insertObject failed', { meta: { eventId: ev.id, error: String(err) } })\n }\n }\n\n async insertAudio(ev: AudioEvent): Promise<void> {\n try {\n const { id, ...rest } = ev\n await this.store.insert.mutate({\n collection: AUDIO_EVENTS_COLLECTION,\n record: { id, data: rest as unknown as Record<string, unknown> },\n })\n } catch (err) {\n this.logger.warn('insertAudio failed', { meta: { eventId: ev.id, error: String(err) } })\n }\n }\n\n // ── Reads ────────────────────────────────────────────────────────\n\n private buildFilter(q: EventQuery): Record<string, unknown> {\n const filter: Record<string, unknown> = {\n where: { deviceId: q.deviceId },\n orderBy: { field: 'timestamp', direction: 'desc' },\n }\n if (q.since !== undefined || q.until !== undefined) {\n filter.whereBetween = { timestamp: [q.since ?? 0, q.until ?? Date.now()] }\n }\n if (q.limit !== undefined) filter.limit = q.limit\n return filter\n }\n\n async queryMotion(q: EventQuery): Promise<readonly MotionEvent[]> {\n const rows = await this.store.query.query({\n collection: MOTION_EVENTS_COLLECTION,\n filter: this.buildFilter(q),\n })\n return rows.map(r => {\n const ev: MotionEvent = { id: r.id, kind: 'motion', ...(stripNulls(r.data) as Omit<MotionEvent, 'id' | 'kind'>) }\n return ev\n })\n }\n\n async queryObject(q: ObjectEventQuery): Promise<readonly ObjectEvent[]> {\n const filter = this.buildFilter(q)\n if (q.classFilter !== undefined) {\n const where = filter.where as Record<string, unknown>\n where['className'] = q.classFilter\n }\n const rows = await this.store.query.query({\n collection: OBJECT_EVENTS_COLLECTION,\n filter,\n })\n return rows.map(r => {\n const ev: ObjectEvent = { id: r.id, kind: 'object', ...(stripNulls(r.data) as Omit<ObjectEvent, 'id' | 'kind'>) }\n return ev\n })\n }\n\n async queryAudio(q: EventQuery): Promise<readonly AudioEvent[]> {\n const rows = await this.store.query.query({\n collection: AUDIO_EVENTS_COLLECTION,\n filter: this.buildFilter(q),\n })\n return rows.map(r => {\n const ev: AudioEvent = { id: r.id, kind: 'audio', ...(stripNulls(r.data) as Omit<AudioEvent, 'id' | 'kind'>) }\n return ev\n })\n }\n\n // ── Retention ────────────────────────────────────────────────────\n\n // (helper below)\n\n async evictBefore(params: {\n readonly motionCutoffMs: number\n readonly objectCutoffMs: number\n readonly audioCutoffMs: number\n }): Promise<{ motion: number; object: number; audio: number }> {\n const deletedCounts = { motion: 0, object: 0, audio: 0 }\n\n // For each kind: query a batch of old rows, delete them one by one.\n const batch = async (collection: string, cutoffMs: number): Promise<number> => {\n const rows = await this.store.query.query({\n collection,\n filter: { whereBetween: { timestamp: [0, cutoffMs] }, limit: 500 },\n })\n let count = 0\n for (const row of rows) {\n try {\n await this.store.delete.mutate({ collection, key: row.id })\n count++\n } catch { /* best-effort */ }\n }\n return count\n }\n\n deletedCounts.motion = await batch(MOTION_EVENTS_COLLECTION, params.motionCutoffMs)\n deletedCounts.object = await batch(OBJECT_EVENTS_COLLECTION, params.objectCutoffMs)\n deletedCounts.audio = await batch(AUDIO_EVENTS_COLLECTION, params.audioCutoffMs)\n return deletedCounts\n }\n}\n\n// SQLite stores nullable columns as `null`; cap output schemas use\n// `z.string().optional()` (= `string | undefined`) which rejects\n// `null`. Drop null entries before spreading into the cap shape.\nfunction stripNulls(data: Record<string, unknown>): Record<string, unknown> {\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(data)) {\n if (v !== null) out[k] = v\n }\n return out\n}\n","/**\n * SliceThrottler — coalesce per-device slice writes.\n *\n * The runtime-state slice for `zone-analytics` and `audio-metrics`\n * is updated on every inference frame / audio chunk respectively.\n * That can hit 5-30 Hz per camera and saturates the\n * `DeviceStateChanged` event bus, the StateValuesStream UI, and the\n * kernel's runtime-state mirror — without giving operators any extra\n * useful information past 1 Hz.\n *\n * Two gates per device:\n * 1. Content diff ignoring `ts` — skip when the slice payload\n * shape didn't actually change (steady-state idle camera).\n * 2. Trailing-edge throttle — write at most once per\n * `intervalMs`, but always flush the LAST queued snapshot\n * after the interval so the UI can't get stuck on a stale\n * value when motion stops.\n */\n\ninterface PendingState<T> {\n snapshot: T\n scheduledTimer: ReturnType<typeof setTimeout> | null\n}\n\nexport interface SliceThrottlerOptions<T> {\n readonly intervalMs: number\n /** Performs the slice write. Throws are caught upstream. */\n readonly write: (deviceId: number, snapshot: T) => Promise<void>\n /**\n * Equality check: returns true when `next` represents the same\n * meaningful state as `prev` and the write should be skipped.\n * Implementations typically clone-without-`ts` and JSON.stringify\n * compare. Falsey return value forces a write.\n */\n readonly equalsIgnoringTs: (prev: T | undefined, next: T) => boolean\n}\n\nexport class SliceThrottler<T> {\n private readonly opts: SliceThrottlerOptions<T>\n private readonly lastWrittenAt = new Map<number, number>()\n private readonly lastWritten = new Map<number, T>()\n private readonly pending = new Map<number, PendingState<T>>()\n\n constructor(opts: SliceThrottlerOptions<T>) {\n this.opts = opts\n }\n\n /**\n * Record a fresh snapshot for the given device. Triggers an\n * immediate write when the throttle window has elapsed AND the\n * content changed; otherwise queues a trailing flush.\n */\n push(deviceId: number, snapshot: T): void {\n const last = this.lastWritten.get(deviceId)\n if (this.opts.equalsIgnoringTs(last, snapshot)) {\n // No meaningful change — skip and clear any pending so we don't\n // flush a stale duplicate later.\n const queued = this.pending.get(deviceId)\n if (queued?.scheduledTimer) clearTimeout(queued.scheduledTimer)\n this.pending.delete(deviceId)\n return\n }\n\n const now = Date.now()\n const lastTs = this.lastWrittenAt.get(deviceId) ?? 0\n const elapsed = now - lastTs\n if (elapsed >= this.opts.intervalMs) {\n void this.flushNow(deviceId, snapshot)\n return\n }\n\n // Inside the throttle window — queue the latest snapshot. If a\n // timer is already scheduled, replacing the snapshot is enough;\n // it will fire at the originally scheduled instant. Otherwise\n // schedule one for the remaining window.\n const queued = this.pending.get(deviceId)\n const remainingMs = this.opts.intervalMs - elapsed\n if (queued) {\n queued.snapshot = snapshot\n } else {\n const slot: PendingState<T> = { snapshot, scheduledTimer: null }\n slot.scheduledTimer = setTimeout(() => {\n slot.scheduledTimer = null\n const current = this.pending.get(deviceId)\n if (!current) return\n this.pending.delete(deviceId)\n void this.flushNow(deviceId, current.snapshot)\n }, remainingMs)\n this.pending.set(deviceId, slot)\n }\n }\n\n forgetDevice(deviceId: number): void {\n const queued = this.pending.get(deviceId)\n if (queued?.scheduledTimer) clearTimeout(queued.scheduledTimer)\n this.pending.delete(deviceId)\n this.lastWritten.delete(deviceId)\n this.lastWrittenAt.delete(deviceId)\n }\n\n /** Drop all pending timers — call from `onShutdown`. */\n destroy(): void {\n for (const queued of this.pending.values()) {\n if (queued.scheduledTimer) clearTimeout(queued.scheduledTimer)\n }\n this.pending.clear()\n this.lastWritten.clear()\n this.lastWrittenAt.clear()\n }\n\n private async flushNow(deviceId: number, snapshot: T): Promise<void> {\n this.lastWrittenAt.set(deviceId, Date.now())\n this.lastWritten.set(deviceId, snapshot)\n await this.opts.write(deviceId, snapshot)\n }\n}\n\n/**\n * Reusable equality helper: serialises both snapshots to JSON after\n * stripping the top-level `ts` field. Cheap for the small snapshot\n * objects (zone-analytics, audio-metrics) that ship through here.\n * Returns `false` when the previous value is undefined so the very\n * first sample always writes.\n */\nexport function snapshotEqualsIgnoringTs<T extends { ts?: unknown }>(\n prev: T | undefined,\n next: T,\n): boolean {\n if (!prev) return false\n const { ts: _prevTs, ...prevRest } = prev as T & { ts?: unknown }\n const { ts: _nextTs, ...nextRest } = next as T & { ts?: unknown }\n return JSON.stringify(prevRest) === JSON.stringify(nextRest)\n}\n","/**\n * `zone-analytics-provider.ts` — implements `zoneAnalyticsCapability`\n * for the analytics addon.\n *\n * Hosts the live per-device occupancy snapshot + a 60-minute rolling\n * in-memory history ring. The analytics addon's frame handler calls\n * {@link ZoneAnalyticsProvider.recordFrame} once per inference result;\n * the provider derives the snapshot, mirrors it to the device-state\n * `zone-occupancy` slice, and appends a sample to the history ring.\n *\n * History methods bucket samples at the requested resolution\n * (`minute` / `5min` / `hour`) and return one count per bucket. The\n * count is the per-bucket average rounded to the nearest integer —\n * matches what an operator chart would show for \"person count over\n * the last hour\" without tripping on per-frame jitter.\n *\n * Out-of-window queries (i.e. `from < now - 60 min`) are clamped to\n * the available range and return whatever buckets are populated.\n * Step 4b will land a SQL-backed sample store for queries beyond the\n * in-memory window; this provider's interface stays unchanged.\n */\n\nimport type {\n IZoneAnalyticsProvider,\n CameraOccupancySnapshot,\n HistoryPoint,\n HistoryResolution,\n Zone,\n IScopedLogger,\n DeviceProxy,\n} from '@camstack/types'\nimport { SliceThrottler, snapshotEqualsIgnoringTs } from './runtime/slice-throttler.js'\n\ninterface RecordedSample {\n readonly ts: number\n readonly snapshot: CameraOccupancySnapshot\n}\n\nexport interface ZoneAnalyticsProviderContext {\n readonly logger: IScopedLogger\n /**\n * Per-device facade — same shape as `AddonContext.fetchDevice`.\n * Used to push the `device-state.setCapSlice` write through the\n * codegen DeviceProxy.\n */\n readonly fetchDevice: (deviceId: number) => Promise<DeviceProxy>\n}\n\nconst HISTORY_WINDOW_MS = 60 * 60 * 1000 // 60 min\n/** Cap name for the runtime-state mirror — matches the cap's declared\n * `name` so the codegen DeviceProxy auto-wires\n * `device.state.zoneAnalytics.value`. The slice IS the snapshot\n * itself (no wrapping object) since the cap declares\n * `runtimeState: CameraOccupancySnapshotSchema`. */\nconst ZONE_ANALYTICS_CAP_NAME = 'zone-analytics'\n\nconst RESOLUTION_MS: Record<HistoryResolution, number> = {\n minute: 60_000,\n '5min': 5 * 60_000,\n hour: 60 * 60_000,\n}\n\n/**\n * Maximum slice-write rate per camera. The frame handler can call\n * `recordFrame` at the inference fps (5-30 Hz); without throttling\n * every frame would emit a `DeviceStateChanged` event and a kernel\n * mirror update. UI consumers don't need sub-second granularity for\n * occupancy counts — coalesce at 1 Hz with trailing flush so the\n * latest state is never lost.\n */\nconst SLICE_WRITE_INTERVAL_MS = 1_000\n\ninterface TrackedDetectionLike {\n readonly trackId: string\n readonly className: string\n readonly zones: readonly string[]\n}\n\nexport class ZoneAnalyticsProvider implements IZoneAnalyticsProvider {\n private readonly snapshots = new Map<number, CameraOccupancySnapshot>()\n /** Per-device rolling ring of frame samples. Capped by HISTORY_WINDOW_MS\n * on append; older entries are evicted lazily on each record. */\n private readonly history = new Map<number, RecordedSample[]>()\n /**\n * Coalesces slice writes to the kernel-side runtime-state mirror.\n * Skips no-op snapshots (idle camera, zone counts unchanged) and\n * caps the rate at 1 Hz with a trailing flush so a transient\n * change isn't held back beyond `SLICE_WRITE_INTERVAL_MS`.\n */\n private readonly sliceThrottle: SliceThrottler<CameraOccupancySnapshot>\n\n constructor(private readonly ctx: ZoneAnalyticsProviderContext) {\n this.sliceThrottle = new SliceThrottler<CameraOccupancySnapshot>({\n intervalMs: SLICE_WRITE_INTERVAL_MS,\n equalsIgnoringTs: snapshotEqualsIgnoringTs,\n write: async (deviceId, snapshot) => {\n try {\n const dev = await this.ctx.fetchDevice(deviceId)\n await dev.deviceState.setCapSlice({\n capName: ZONE_ANALYTICS_CAP_NAME,\n slice: snapshot as unknown as Record<string, unknown>,\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('zone-analytics slice mirror failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n },\n })\n }\n\n /** Stop pending throttle timers — called from addon shutdown. */\n destroy(): void {\n this.sliceThrottle.destroy()\n }\n\n // ── Cap surface ───────────────────────────────────────────────────\n\n async getCurrentSnapshot({\n deviceId,\n }: {\n deviceId: number\n }): Promise<CameraOccupancySnapshot | null> {\n return this.snapshots.get(deviceId) ?? null\n }\n\n async getZoneHistory(input: {\n deviceId: number\n zoneId: string\n className?: string\n from: number\n to: number\n resolution: HistoryResolution\n }): Promise<readonly HistoryPoint[]> {\n return this.bucketize(input.deviceId, input.from, input.to, input.resolution, (snap) => {\n const zone = snap.zones.find(z => z.zoneId === input.zoneId)\n if (!zone) return 0\n if (input.className === undefined) return zone.totalObjects\n return zone.byClass[input.className] ?? 0\n })\n }\n\n async getCameraHistory(input: {\n deviceId: number\n className?: string\n from: number\n to: number\n resolution: HistoryResolution\n }): Promise<readonly HistoryPoint[]> {\n return this.bucketize(input.deviceId, input.from, input.to, input.resolution, (snap) => {\n if (input.className === undefined) return snap.frame.totalObjects\n return snap.frame.byClass[input.className] ?? 0\n })\n }\n\n async getUnzonedHistory(input: {\n deviceId: number\n className?: string\n from: number\n to: number\n resolution: HistoryResolution\n }): Promise<readonly HistoryPoint[]> {\n return this.bucketize(input.deviceId, input.from, input.to, input.resolution, (snap) => {\n if (input.className === undefined) return snap.unzoned.totalObjects\n return snap.unzoned.byClass[input.className] ?? 0\n })\n }\n\n // ── Recording surface (called by the analytics frame handler) ─────\n\n /**\n * Compute and record a snapshot from a tracked-detection list.\n * Called once per inference result by the analytics addon's\n * `handleInferenceResult`. Mirrors to the `zone-occupancy`\n * device-state slice for live UI subscribers.\n */\n async recordFrame(input: {\n deviceId: number\n timestamp: number\n frameWidth: number\n frameHeight: number\n tracked: readonly TrackedDetectionLike[]\n zones: readonly Zone[]\n }): Promise<void> {\n const snapshot = computeSnapshot(input)\n // In-memory snapshot + history get every frame — these feed the\n // cap's `getCurrentSnapshot` method and history queries, both of\n // which the operator explicitly polls when they want detail.\n this.snapshots.set(input.deviceId, snapshot)\n this.appendHistory(input.deviceId, snapshot)\n // Slice mirror, in contrast, drives the live UI state stream and\n // every cross-process consumer of `device.state.zoneAnalytics` —\n // throttled to 1 Hz and de-duplicated so a quiet camera produces\n // zero events instead of one per frame.\n this.sliceThrottle.push(input.deviceId, snapshot)\n }\n\n /** Drop a device's snapshot + history. Called on device removal. */\n forgetDevice(deviceId: number): void {\n this.snapshots.delete(deviceId)\n this.history.delete(deviceId)\n this.sliceThrottle.forgetDevice(deviceId)\n }\n\n // ── Internals ─────────────────────────────────────────────────────\n\n private appendHistory(deviceId: number, snapshot: CameraOccupancySnapshot): void {\n const ring = this.history.get(deviceId) ?? []\n const cutoff = snapshot.ts - HISTORY_WINDOW_MS\n // Trim everything older than the window. Linear scan from the front\n // is fine — samples arrive monotonically so the trim front is small.\n let trimIdx = 0\n while (trimIdx < ring.length && ring[trimIdx]!.ts < cutoff) trimIdx++\n const trimmed = trimIdx > 0 ? ring.slice(trimIdx) : ring\n trimmed.push({ ts: snapshot.ts, snapshot })\n this.history.set(deviceId, trimmed)\n }\n\n private bucketize(\n deviceId: number,\n from: number,\n to: number,\n resolution: HistoryResolution,\n extract: (snap: CameraOccupancySnapshot) => number,\n ): readonly HistoryPoint[] {\n const ring = this.history.get(deviceId)\n if (!ring || ring.length === 0) return []\n const bucketMs = RESOLUTION_MS[resolution]\n const buckets = new Map<number, { sum: number; count: number }>()\n for (const sample of ring) {\n if (sample.ts < from || sample.ts > to) continue\n const bucketStart = Math.floor(sample.ts / bucketMs) * bucketMs\n const bucket = buckets.get(bucketStart)\n const value = extract(sample.snapshot)\n if (bucket) {\n bucket.sum += value\n bucket.count += 1\n } else {\n buckets.set(bucketStart, { sum: value, count: 1 })\n }\n }\n const result: HistoryPoint[] = []\n for (const [bucketStart, agg] of [...buckets.entries()].sort(([a], [b]) => a - b)) {\n result.push({\n ts: bucketStart + bucketMs / 2,\n count: Math.round(agg.sum / agg.count),\n })\n }\n return result\n }\n}\n\n// ── Pure snapshot computation (exported for tests) ──────────────────\n\nexport function computeSnapshot(input: {\n deviceId: number\n timestamp: number\n frameWidth: number\n frameHeight: number\n tracked: readonly TrackedDetectionLike[]\n zones: readonly Zone[]\n}): CameraOccupancySnapshot {\n // Frame-wide aggregate.\n const frameByClass: Record<string, number> = {}\n let frameTotal = 0\n for (const td of input.tracked) {\n frameTotal += 1\n frameByClass[td.className] = (frameByClass[td.className] ?? 0) + 1\n }\n\n // Per-zone aggregate. count-in-each: a track inside multiple zones\n // contributes to every zone's counters.\n const zoneAccumulators = new Map<\n string,\n { name: string; total: number; byClass: Record<string, number>; trackIds: string[] }\n >()\n for (const z of input.zones) {\n zoneAccumulators.set(z.id, {\n name: z.name,\n total: 0,\n byClass: {},\n trackIds: [],\n })\n }\n let unzonedTotal = 0\n const unzonedByClass: Record<string, number> = {}\n for (const td of input.tracked) {\n if (td.zones.length === 0) {\n unzonedTotal += 1\n unzonedByClass[td.className] = (unzonedByClass[td.className] ?? 0) + 1\n continue\n }\n for (const zoneId of td.zones) {\n const acc = zoneAccumulators.get(zoneId)\n if (!acc) continue\n acc.total += 1\n acc.byClass[td.className] = (acc.byClass[td.className] ?? 0) + 1\n acc.trackIds.push(td.trackId)\n }\n }\n\n return {\n ts: input.timestamp,\n frameWidth: input.frameWidth,\n frameHeight: input.frameHeight,\n zones: [...zoneAccumulators.entries()].map(([zoneId, acc]) => ({\n zoneId,\n zoneName: acc.name,\n totalObjects: acc.total,\n byClass: acc.byClass,\n trackIds: acc.trackIds,\n })),\n frame: {\n totalObjects: frameTotal,\n byClass: frameByClass,\n },\n unzoned: {\n totalObjects: unzonedTotal,\n byClass: unzonedByClass,\n },\n }\n}\n","/**\n * `audio-metrics-provider.ts` — implements `audioMetricsCapability`.\n *\n * Hosts a sliding-window aggregator that consumes the audio\n * inference results the analytics addon already subscribes to and\n * produces a runtime-state snapshot mirrored into\n * `device.state.audioMetrics`. Operators read it via the canonical\n * reactive surface (no custom subscription needed).\n *\n * Aggregation is per-device and stays in-memory — same scope as the\n * 60-min ring on zone-analytics. Persisted history would be a\n * follow-up if needed (e.g. dBFS peaks beyond the rolling window).\n */\nimport type {\n IAudioMetricsProvider,\n AudioMetricsSnapshot,\n AudioMetricsHistory,\n AudioMetricsHistoryPoint,\n AudioClassSummary,\n IScopedLogger,\n DeviceProxy,\n} from '@camstack/types'\nimport { SliceThrottler, snapshotEqualsIgnoringTs } from './runtime/slice-throttler.js'\n\nconst AUDIO_METRICS_CAP_NAME = 'audio-metrics'\n\n/**\n * Per-device retention cap on the in-memory history ring. At ~1 sample\n * per second this covers ~1 hour with ~30 bytes/sample. Total memory\n * footprint stays under ~110 KB per device — fine for the hot ring,\n * matches what zone-analytics retains in its own ring buffer.\n */\nconst MAX_HISTORY_POINTS_KEPT = 3600\n\n/** Hard cap on samples returned by `getHistory` per call. The provider\n * bucket-averages when more would otherwise be returned. Keeps the\n * payload bounded for slow links + browser charts. */\nconst MAX_HISTORY_POINTS_RETURNED = 1024\n\n/** Default window when the caller doesn't override `windowSec`. */\nconst DEFAULT_HISTORY_WINDOW_SEC = 300\n\n/** Default sample spacing when the caller doesn't override\n * `sampleEveryMs`. Matches the slice-write tick rate so a freshly-\n * populated ring lines up with what the operator sees on the live\n * panel. */\nconst DEFAULT_HISTORY_SAMPLE_EVERY_MS = 1_000\n\n/**\n * Slice-write rate cap. Audio chunks land at 10-20 Hz; pushing the\n * runtime-state slice on every chunk would saturate the\n * `DeviceStateChanged` channel and the kernel mirror without\n * actually telling the operator anything new — dB readings flicker\n * by ±1 dB at idle. 1 Hz with a trailing flush is the sweet spot:\n * UI feels live, bus stays quiet.\n */\nconst SLICE_WRITE_INTERVAL_MS = 1_000\n\n/** Cache TTL for per-camera report settings. The audio fast path\n * resolves on the first frame after expiry; settings changes apply\n * within this window. */\nconst REPORT_SETTINGS_TTL_MS = 30_000\n\nconst DEFAULT_REPORT_SETTINGS: AudioMetricsReportSettings = {\n enabled: true,\n thresholdDb: 2,\n}\n\n/**\n * Audio-metrics-aware equality. Returns true (skip emit) when:\n * - `current.className` is unchanged (class transitions always emit)\n * - `byClass` shape is unchanged (top class set / hit counts identical)\n * - level / peak / avg dBFS deltas are all under `thresholdDb`\n *\n * Coarsens the default `snapshotEqualsIgnoringTs` (which serialised\n * the whole snapshot to JSON and never matched because dBFS varies\n * by ±1dB every second). With `thresholdDb=2` an idle camera emits\n * only on real audio events; with `thresholdDb=0` we fall back to\n * exact equality (legacy behavior).\n */\nfunction audioMetricsSnapshotEquals(\n prev: AudioMetricsSnapshot | undefined,\n next: AudioMetricsSnapshot,\n thresholdDb: number,\n): boolean {\n if (!prev) return false\n if (thresholdDb <= 0) return snapshotEqualsIgnoringTs(prev, next)\n const prevClass = prev.current?.className ?? null\n const nextClass = next.current?.className ?? null\n if (prevClass !== nextClass) return false\n // Different number of classes / different top hits → not equal.\n if (prev.byClass.length !== next.byClass.length) return false\n for (let i = 0; i < prev.byClass.length; i++) {\n const a = prev.byClass[i]!\n const b = next.byClass[i]!\n if (a.className !== b.className || a.hits !== b.hits) return false\n }\n if (Math.abs(prev.level.dbfs - next.level.dbfs) >= thresholdDb) return false\n if (Math.abs(prev.peakDbfs - next.peakDbfs) >= thresholdDb) return false\n if (Math.abs(prev.avgDbfs - next.avgDbfs) >= thresholdDb) return false\n return true\n}\n\n/**\n * Per-camera report tunables — sourced from the analytics addon's\n * device-settings cascade (audioMetricsReportEnabled +\n * audioMetricsReportThresholdDb fields). Operators tune per-camera\n * to suppress per-second dBFS jitter on idle scenes without losing\n * class-change events. Returning `null` (or a fetch error) falls\n * back to the defaults.\n */\nexport interface AudioMetricsReportSettings {\n readonly enabled: boolean\n readonly thresholdDb: number\n}\n\nexport interface AudioMetricsProviderContext {\n readonly logger: IScopedLogger\n /**\n * Per-device facade — same shape as `AddonContext.fetchDevice`.\n * Used to push the `device-state.setCapSlice` write through the\n * codegen DeviceProxy.\n */\n readonly fetchDevice: (deviceId: number) => Promise<DeviceProxy>\n /** Sliding-window length in seconds. Default 60s. */\n readonly windowSec?: number\n /** Score threshold below which a classification doesn't count\n * toward `current` or `byClass`. Default 0.4. */\n readonly scoreThreshold?: number\n /**\n * Resolves the per-camera report settings (enabled + dB threshold).\n * Called lazily and cached for `REPORT_SETTINGS_TTL_MS` so the\n * audio fast path stays sync. Falls back to `{enabled: true,\n * thresholdDb: 2}` on missing/invalid settings.\n */\n readonly resolveReportSettings?: (deviceId: number) => Promise<AudioMetricsReportSettings>\n}\n\ninterface RecordedHit {\n readonly ts: number\n readonly className: string\n readonly score: number\n}\n\ninterface RecordedLevel {\n readonly ts: number\n readonly dbfs: number\n}\n\ninterface DeviceAudioState {\n readonly hits: RecordedHit[] // mutable rolling buffer\n readonly levels: RecordedLevel[]\n /** Latest level reading regardless of silence — used for\n * `level.rms` / `level.dbfs` on the snapshot. */\n lastLevel: { rms: number; dbfs: number; ts: number } | null\n}\n\nexport class AudioMetricsProvider implements IAudioMetricsProvider {\n private readonly snapshots = new Map<number, AudioMetricsSnapshot>()\n private readonly state = new Map<number, DeviceAudioState>()\n /**\n * Per-device history ring buffer. Each push appends a compact\n * `AudioMetricsHistoryPoint` derived from the just-computed\n * snapshot; the oldest entry is dropped once\n * `MAX_HISTORY_POINTS_KEPT` is reached. We use a plain array\n * (not a circular buffer) for readability — the trim cost is\n * O(1) on `.shift()` for arrays Node/V8 keeps short, and\n * `getHistory` doesn't have a hot-loop requirement.\n *\n * The ring is populated at the same cadence as the\n * `recordAudioFrame()` calls (1 push per call), but most builds\n * receive ~10–20 audio chunks per second. That rate is faster\n * than what `getHistory` reports (default 1Hz spacing); the\n * downstream subsampling step in `getHistory` averages adjacent\n * samples back to the requested spacing, so retention covers\n * roughly `MAX_HISTORY_POINTS_KEPT × pushIntervalMs / 1000`s of\n * wall-clock time. At 10Hz that's ~6 minutes; at 1Hz ~1 hour.\n */\n private readonly history = new Map<number, AudioMetricsHistoryPoint[]>()\n private readonly windowMs: number\n private readonly scoreThreshold: number\n /**\n * Coalesces slice writes to the kernel-side runtime-state mirror.\n * Equality check is audio-metrics-aware: dB jitter under the\n * per-device threshold is suppressed; class changes always pass.\n * Trailing-flushes at most once per SLICE_WRITE_INTERVAL_MS.\n */\n private readonly sliceThrottle: SliceThrottler<AudioMetricsSnapshot>\n /** Per-device settings cache. Refreshed lazily inside the audio\n * fast path (REPORT_SETTINGS_TTL_MS). */\n private readonly reportSettings = new Map<number, { value: AudioMetricsReportSettings; resolvedAt: number }>()\n /** In-flight settings-fetch dedupe per device. */\n private readonly settingsFetches = new Map<number, Promise<void>>()\n /** Working snapshot of the equality threshold while the throttler\n * is running its diff. Set before push(), reset to default after.\n * Allows the closure passed to SliceThrottler to look up the\n * per-device threshold without a deeper API change. */\n private currentThresholdDb: number = DEFAULT_REPORT_SETTINGS.thresholdDb\n\n constructor(private readonly ctx: AudioMetricsProviderContext) {\n this.windowMs = (ctx.windowSec ?? 60) * 1000\n this.scoreThreshold = ctx.scoreThreshold ?? 0.4\n this.sliceThrottle = new SliceThrottler<AudioMetricsSnapshot>({\n intervalMs: SLICE_WRITE_INTERVAL_MS,\n equalsIgnoringTs: (prev, next) => audioMetricsSnapshotEquals(prev, next, this.currentThresholdDb),\n write: async (deviceId, snapshot) => {\n try {\n const dev = await this.ctx.fetchDevice(deviceId)\n await dev.deviceState.setCapSlice({\n capName: AUDIO_METRICS_CAP_NAME,\n slice: snapshot as unknown as Record<string, unknown>,\n })\n } catch (err: unknown) {\n this.ctx.logger.debug('audio-metrics slice mirror failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n },\n })\n }\n\n /**\n * Resolve per-device report settings with TTL caching. Returns\n * the cached value synchronously when fresh; on stale or first\n * access, kicks off an async refresh and returns the previous\n * value (falling back to defaults). Keeps the audio fast path\n * non-blocking — the worst case is one slightly-stale tick per\n * camera per TTL window.\n */\n private getReportSettings(deviceId: number): AudioMetricsReportSettings {\n const cached = this.reportSettings.get(deviceId)\n const now = Date.now()\n if (cached && now - cached.resolvedAt < REPORT_SETTINGS_TTL_MS) {\n return cached.value\n }\n if (this.ctx.resolveReportSettings && !this.settingsFetches.has(deviceId)) {\n const fetch = this.ctx.resolveReportSettings(deviceId).then((value) => {\n this.reportSettings.set(deviceId, { value, resolvedAt: Date.now() })\n }).catch((err: unknown) => {\n this.ctx.logger.debug('audio-metrics: report settings resolve failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n // Cache defaults so we don't hammer the resolver on persistent failure.\n this.reportSettings.set(deviceId, { value: DEFAULT_REPORT_SETTINGS, resolvedAt: Date.now() })\n }).finally(() => {\n this.settingsFetches.delete(deviceId)\n })\n this.settingsFetches.set(deviceId, fetch)\n }\n return cached?.value ?? DEFAULT_REPORT_SETTINGS\n }\n\n /** Stop pending throttle timers — called from addon shutdown. */\n destroy(): void {\n this.sliceThrottle.destroy()\n }\n\n // ── Cap surface ───────────────────────────────────────────────────\n\n async getCurrentSnapshot({\n deviceId,\n }: {\n deviceId: number\n }): Promise<AudioMetricsSnapshot | null> {\n return this.snapshots.get(deviceId) ?? null\n }\n\n async getHistory({\n deviceId,\n windowSec,\n sampleEveryMs,\n }: {\n deviceId: number\n windowSec?: number\n sampleEveryMs?: number\n }): Promise<AudioMetricsHistory> {\n const ring = this.history.get(deviceId)\n if (!ring || ring.length === 0) {\n return { points: [], effectiveSampleEveryMs: sampleEveryMs ?? DEFAULT_HISTORY_SAMPLE_EVERY_MS, windowMsActual: 0 }\n }\n const effectiveWindowSec = windowSec ?? DEFAULT_HISTORY_WINDOW_SEC\n const requestedSampleEveryMs = sampleEveryMs ?? DEFAULT_HISTORY_SAMPLE_EVERY_MS\n const cutoffTs = Date.now() - effectiveWindowSec * 1000\n // Slice the trailing window. Ring is monotonic-ish (push order =\n // wall-clock order); a short linear scan is cheap up to\n // MAX_HISTORY_POINTS_KEPT.\n const inWindow: AudioMetricsHistoryPoint[] = []\n for (const p of ring) {\n if (p.ts >= cutoffTs) inWindow.push(p)\n }\n if (inWindow.length === 0) {\n return { points: [], effectiveSampleEveryMs: requestedSampleEveryMs, windowMsActual: 0 }\n }\n\n // Subsample to satisfy `sampleEveryMs` AND the per-call cap.\n // - Compute target bucket count from windowSec + sampleEveryMs.\n // - Cap at MAX_HISTORY_POINTS_RETURNED.\n // - When the source has more points per target bucket, average\n // each bucket; otherwise emit the source verbatim.\n const naiveBucketCount = Math.max(1, Math.ceil((effectiveWindowSec * 1000) / requestedSampleEveryMs))\n const targetBucketCount = Math.min(naiveBucketCount, MAX_HISTORY_POINTS_RETURNED)\n const downsampled = downsampleHistory(inWindow, targetBucketCount)\n const first = downsampled[0]\n const last = downsampled[downsampled.length - 1]\n const windowMsActual = first && last ? last.ts - first.ts : 0\n const effectiveSampleEveryMs =\n downsampled.length >= 2\n ? Math.max(1, Math.round(windowMsActual / Math.max(1, downsampled.length - 1)))\n : requestedSampleEveryMs\n return {\n points: downsampled,\n effectiveSampleEveryMs,\n windowMsActual,\n }\n }\n\n // ── Recording (called by the analytics addon's audio handler) ────\n\n /**\n * Record one audio window into the rolling aggregator and emit a\n * fresh snapshot. Called once per `pipeline.audio-inference-result`\n * event by the analytics addon. `topClassification` may be omitted\n * for silent windows — the level still updates.\n */\n async recordAudioFrame(input: {\n readonly deviceId: number\n readonly timestamp: number\n readonly level?: { readonly rms: number; readonly dbfs: number }\n readonly topClassification?: { readonly className: string; readonly score: number }\n }): Promise<void> {\n let s = this.state.get(input.deviceId)\n if (!s) {\n s = { hits: [], levels: [], lastLevel: null }\n this.state.set(input.deviceId, s)\n }\n const cutoff = input.timestamp - this.windowMs\n // Trim trailing-stale samples (front-of-buffer-first since\n // timestamps are monotonic in practice).\n while (s.hits.length > 0 && s.hits[0]!.ts < cutoff) s.hits.shift()\n while (s.levels.length > 0 && s.levels[0]!.ts < cutoff) s.levels.shift()\n\n if (input.level) {\n s.lastLevel = { ...input.level, ts: input.timestamp }\n s.levels.push({ ts: input.timestamp, dbfs: input.level.dbfs })\n }\n if (input.topClassification && input.topClassification.score >= this.scoreThreshold) {\n s.hits.push({\n ts: input.timestamp,\n className: input.topClassification.className,\n score: input.topClassification.score,\n })\n }\n\n const snapshot = computeAudioSnapshot(input.timestamp, s, this.windowMs / 1000, this.scoreThreshold)\n // In-memory snapshot is always fresh — the cap's `getCurrentSnapshot`\n // returns the latest regardless of throttle/disable. Live UI panels\n // that subscribe directly to the cap stay live.\n this.snapshots.set(input.deviceId, snapshot)\n\n // History ring — grow lazily, trim oldest. The ring is always\n // populated regardless of `enabled` / threshold settings: the\n // cap's `getHistory` is for live charts, separate from the\n // throttled slice-mirror channel. Operators who turn the live\n // mirror off still get charts on the device-detail panel.\n const point = snapshotToHistoryPoint(snapshot)\n let ring = this.history.get(input.deviceId)\n if (!ring) {\n ring = []\n this.history.set(input.deviceId, ring)\n }\n ring.push(point)\n if (ring.length > MAX_HISTORY_POINTS_KEPT) {\n ring.shift()\n }\n\n // Slice-mirror is the only path that fires `device.state-changed`\n // events. Operators can disable it per-camera when audio is\n // informational only (still classified — just not surfaced via the\n // reactive state slice / events tab).\n const settings = this.getReportSettings(input.deviceId)\n if (!settings.enabled) return\n\n // Stage the threshold for this push; the SliceThrottler closure\n // reads it during its equality check. Reset afterward so a\n // per-camera read doesn't leak the previous device's threshold\n // into another concurrent push (recordAudioFrame is async-but-\n // non-yielding from here through push, so the assignment is safe).\n this.currentThresholdDb = settings.thresholdDb\n this.sliceThrottle.push(input.deviceId, snapshot)\n this.currentThresholdDb = DEFAULT_REPORT_SETTINGS.thresholdDb\n }\n\n /** Drop a device's audio state. Called on device removal. */\n forgetDevice(deviceId: number): void {\n this.snapshots.delete(deviceId)\n this.state.delete(deviceId)\n this.history.delete(deviceId)\n this.sliceThrottle.forgetDevice(deviceId)\n this.reportSettings.delete(deviceId)\n }\n}\n\n/** Project a snapshot down to the compact history-point shape. */\nfunction snapshotToHistoryPoint(snapshot: AudioMetricsSnapshot): AudioMetricsHistoryPoint {\n return {\n ts: snapshot.ts,\n dbfs: Number.isFinite(snapshot.level.dbfs) ? snapshot.level.dbfs : null,\n peakDbfs: snapshot.peakDbfs,\n avgDbfs: snapshot.avgDbfs,\n topClass: snapshot.current?.className ?? null,\n topScore: snapshot.current?.score ?? null,\n }\n}\n\n/**\n * Equal-width bucketed downsampling. Splits the source array into\n * `targetCount` equal-time buckets (by index since samples are\n * monotonic in practice) and averages numeric fields. The `topClass`\n * within each bucket is the most common label, breaking ties by\n * highest score; `topScore` is the per-bucket peak score.\n *\n * When `targetCount >= source.length` the source is returned\n * verbatim (no degenerate single-element buckets).\n */\nfunction downsampleHistory(\n source: readonly AudioMetricsHistoryPoint[],\n targetCount: number,\n): AudioMetricsHistoryPoint[] {\n if (source.length === 0) return []\n if (targetCount >= source.length) return [...source]\n const out: AudioMetricsHistoryPoint[] = []\n // Bucket size in source-array indices. `Math.max(1, …)` guards\n // against the targetCount==source.length edge case (already handled\n // above, but keeps the ceil safe).\n const bucketSize = Math.max(1, Math.floor(source.length / targetCount))\n for (let bucketStart = 0; bucketStart < source.length; bucketStart += bucketSize) {\n const bucketEnd = Math.min(source.length, bucketStart + bucketSize)\n let dbfsSum = 0\n let dbfsCount = 0\n let peakDbfs = -Infinity\n let avgDbfsSum = 0\n let peakBucketScore = -Infinity\n let peakBucketClass: string | null = null\n const classCounts = new Map<string, number>()\n let lastTs = source[bucketStart]!.ts\n for (let i = bucketStart; i < bucketEnd; i++) {\n const p = source[i]!\n if (p.dbfs !== null) {\n dbfsSum += p.dbfs\n dbfsCount += 1\n }\n if (p.peakDbfs > peakDbfs) peakDbfs = p.peakDbfs\n avgDbfsSum += p.avgDbfs\n if (p.topClass !== null) {\n classCounts.set(p.topClass, (classCounts.get(p.topClass) ?? 0) + 1)\n if (p.topScore !== null && p.topScore > peakBucketScore) {\n peakBucketScore = p.topScore\n peakBucketClass = p.topClass\n }\n }\n lastTs = p.ts\n }\n // Pick the most-frequent class in the bucket. Ties resolved by the\n // peak-score winner already tracked above.\n let dominantClass: string | null = null\n let dominantCount = 0\n for (const [cls, count] of classCounts) {\n if (count > dominantCount) {\n dominantClass = cls\n dominantCount = count\n }\n }\n const topClass = dominantClass ?? peakBucketClass\n const topScore = topClass !== null && peakBucketScore !== -Infinity ? peakBucketScore : null\n out.push({\n ts: lastTs,\n dbfs: dbfsCount > 0 ? dbfsSum / dbfsCount : null,\n peakDbfs: Number.isFinite(peakDbfs) ? peakDbfs : 0,\n avgDbfs: avgDbfsSum / Math.max(1, bucketEnd - bucketStart),\n topClass,\n topScore,\n })\n }\n return out\n}\n\n// ── Pure aggregation (exported for tests) ──────────────────────────\n\nexport function computeAudioSnapshot(\n ts: number,\n s: DeviceAudioState,\n windowSec: number,\n scoreThreshold: number,\n): AudioMetricsSnapshot {\n const byClassMap = new Map<string, { hits: number; sum: number; peak: number }>()\n for (const h of s.hits) {\n const acc = byClassMap.get(h.className) ?? { hits: 0, sum: 0, peak: 0 }\n acc.hits += 1\n acc.sum += h.score\n if (h.score > acc.peak) acc.peak = h.score\n byClassMap.set(h.className, acc)\n }\n const byClass: AudioClassSummary[] = [...byClassMap.entries()]\n .map(([className, agg]) => ({\n className,\n hits: agg.hits,\n avgScore: agg.hits > 0 ? agg.sum / agg.hits : 0,\n peakScore: agg.peak,\n }))\n .sort((a, b) => b.hits - a.hits || b.peakScore - a.peakScore)\n\n let peakDbfs = -Infinity\n let sumDbfs = 0\n for (const l of s.levels) {\n if (l.dbfs > peakDbfs) peakDbfs = l.dbfs\n sumDbfs += l.dbfs\n }\n const avgDbfs = s.levels.length > 0 ? sumDbfs / s.levels.length : 0\n\n // Latest above-threshold hit becomes the `current` field. Since\n // we only push hits that pass the threshold, the last entry is\n // already the canonical \"current\".\n const lastHit = s.hits.length > 0 ? s.hits[s.hits.length - 1]! : null\n const current = lastHit\n ? { className: lastHit.className, score: lastHit.score, timestamp: lastHit.ts }\n : null\n\n // `scoreThreshold` is consumed at hit-recording time\n // (recordAudioFrame). The aggregator just consumes the already-\n // filtered hits, so no further filtering needed here. Kept on the\n // signature for future per-window thresholding.\n void scoreThreshold\n\n return {\n ts,\n windowSec,\n level: s.lastLevel\n ? { rms: s.lastLevel.rms, dbfs: s.lastLevel.dbfs }\n : { rms: 0, dbfs: -Infinity },\n peakDbfs: Number.isFinite(peakDbfs) ? peakDbfs : 0,\n avgDbfs,\n current,\n byClass,\n }\n}\n"],"mappings":"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","names":["randomUUID","queued","SLICE_WRITE_INTERVAL_MS","randomUUID"]}
@@ -0,0 +1,55 @@
1
+ import { a as f } from "./__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CmqNjq44.mjs";
2
+ function p(s, t) {
3
+ for (var e = 0; e < t.length; e++) {
4
+ const r = t[e];
5
+ if (typeof r != "string" && !Array.isArray(r)) {
6
+ for (const o in r)
7
+ if (o !== "default" && !(o in s)) {
8
+ const u = Object.getOwnPropertyDescriptor(r, o);
9
+ u && Object.defineProperty(s, o, u.get ? u : {
10
+ enumerable: !0,
11
+ get: () => r[o]
12
+ });
13
+ }
14
+ }
15
+ }
16
+ return Object.freeze(Object.defineProperty(s, Symbol.toStringTag, { value: "Module" }));
17
+ }
18
+ var a = { exports: {} }, n = {};
19
+ /**
20
+ * @license React
21
+ * react-jsx-runtime.production.js
22
+ *
23
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
24
+ *
25
+ * This source code is licensed under the MIT license found in the
26
+ * LICENSE file in the root directory of this source tree.
27
+ */
28
+ var x = Symbol.for("react.transitional.element"), j = Symbol.for("react.fragment");
29
+ function l(s, t, e) {
30
+ var r = null;
31
+ if (e !== void 0 && (r = "" + e), t.key !== void 0 && (r = "" + t.key), "key" in t) {
32
+ e = {};
33
+ for (var o in t)
34
+ o !== "key" && (e[o] = t[o]);
35
+ } else e = t;
36
+ return t = e.ref, {
37
+ $$typeof: x,
38
+ type: s,
39
+ key: r,
40
+ ref: t !== void 0 ? t : null,
41
+ props: e
42
+ };
43
+ }
44
+ n.Fragment = j;
45
+ n.jsx = l;
46
+ n.jsxs = l;
47
+ a.exports = n;
48
+ var i = a.exports;
49
+ const d = /* @__PURE__ */ f(i), _ = /* @__PURE__ */ p({
50
+ __proto__: null,
51
+ default: d
52
+ }, [i]);
53
+ export {
54
+ _ as j
55
+ };