@camstack/addon-post-analysis 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 (80) hide show
  1. package/dist/embedding-encoder/index.js +928 -0
  2. package/dist/embedding-encoder/index.js.map +1 -0
  3. package/dist/embedding-encoder/index.mjs +888 -0
  4. package/dist/embedding-encoder/index.mjs.map +1 -0
  5. package/dist/enrichment-engine/index.js +774 -0
  6. package/dist/enrichment-engine/index.js.map +1 -0
  7. package/dist/enrichment-engine/index.mjs +774 -0
  8. package/dist/enrichment-engine/index.mjs.map +1 -0
  9. package/dist/ffmpeg-config-DRONlBsj.mjs +56 -0
  10. package/dist/ffmpeg-config-DRONlBsj.mjs.map +1 -0
  11. package/dist/ffmpeg-config-uANz3sV5.js +73 -0
  12. package/dist/ffmpeg-config-uANz3sV5.js.map +1 -0
  13. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/AudioHistoryChart.d.ts +4 -0
  14. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/AudioMetricsPanel.d.ts +10 -0
  15. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/DetectionHistoryChart.d.ts +4 -0
  16. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/MotionHistoryChart.d.ts +4 -0
  17. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/OccupancyHistoryChart.d.ts +4 -0
  18. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/OccupancyPanel.d.ts +10 -0
  19. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/chart-utils.d.ts +97 -0
  20. package/dist/pipeline-analytics/@mf-types/compiled-types/pipeline-analytics/widgets/index.d.ts +27 -0
  21. package/dist/pipeline-analytics/@mf-types/widgets.d.ts +2 -0
  22. package/dist/pipeline-analytics/@mf-types.d.ts +3 -0
  23. package/dist/pipeline-analytics/@mf-types.zip +0 -0
  24. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs +12 -0
  25. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-46iNwbxa.mjs +16 -0
  26. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-Db6yh4op.mjs +15 -0
  27. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_tanstack_mf_1_react_mf_2_query__loadShare__.mjs-DoWbefqS.mjs +104 -0
  28. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_client__loadShare__.mjs-52bfkwC8.mjs +85 -0
  29. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_trpc_mf_1_react_mf_2_query__loadShare__.mjs-CVrnrGED.mjs +62 -0
  30. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs-DuO9h7li.mjs +85 -0
  31. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-CmqNjq44.mjs +29 -0
  32. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-BsyrX6NO.mjs +36 -0
  33. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs-Dp8hqYOB.mjs +45 -0
  34. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CA8cCIEl.mjs +6 -0
  35. package/dist/pipeline-analytics/__mfe_internal__addon_pipeline_analytics_widgets__loadShare__react_mf_2_dom_mf_1_client__loadShare__.mjs-BZjEt71l.mjs +34 -0
  36. package/dist/pipeline-analytics/_stub.js +1397 -0
  37. package/dist/pipeline-analytics/_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-Bfo-or09.mjs +156 -0
  38. package/dist/pipeline-analytics/client-DdXDZxzK.mjs +10063 -0
  39. package/dist/pipeline-analytics/getErrorShape-BPSzUA7W-TlK8ipWe.mjs +211 -0
  40. package/dist/pipeline-analytics/hostInit-CqUKNfOr.mjs +168 -0
  41. package/dist/pipeline-analytics/index-B4OKsa9p.mjs +2603 -0
  42. package/dist/pipeline-analytics/index-B7r1koiV.mjs +19109 -0
  43. package/dist/pipeline-analytics/index-CRN37Hr9.mjs +15195 -0
  44. package/dist/pipeline-analytics/index-DRx99znz.mjs +1617 -0
  45. package/dist/pipeline-analytics/index-DyYvUfc7.mjs +725 -0
  46. package/dist/pipeline-analytics/index-k0CA0h_r.mjs +185 -0
  47. package/dist/pipeline-analytics/index-kIgjN-uq.mjs +435 -0
  48. package/dist/pipeline-analytics/index-xncRG7-x.mjs +2713 -0
  49. package/dist/pipeline-analytics/index.js +2563 -0
  50. package/dist/pipeline-analytics/index.js.map +1 -0
  51. package/dist/pipeline-analytics/index.mjs +2564 -0
  52. package/dist/pipeline-analytics/index.mjs.map +1 -0
  53. package/dist/pipeline-analytics/jsx-runtime-4ro1c69i.mjs +55 -0
  54. package/dist/pipeline-analytics/remoteEntry.js +2973 -0
  55. package/dist/pipeline-analytics/virtualExposes-8FzWTdq3.mjs +42 -0
  56. package/dist/playlist-generator-EhPaB7Hn.js +48 -0
  57. package/dist/playlist-generator-EhPaB7Hn.js.map +1 -0
  58. package/dist/playlist-generator-VTkgn53O.mjs +48 -0
  59. package/dist/playlist-generator-VTkgn53O.mjs.map +1 -0
  60. package/dist/recording/index.js +257 -0
  61. package/dist/recording/index.js.map +1 -0
  62. package/dist/recording/index.mjs +235 -0
  63. package/dist/recording/index.mjs.map +1 -0
  64. package/dist/recording-coordinator-C2sATEhe.js +1052 -0
  65. package/dist/recording-coordinator-C2sATEhe.js.map +1 -0
  66. package/dist/recording-coordinator-DuP3BUTV.mjs +1012 -0
  67. package/dist/recording-coordinator-DuP3BUTV.mjs.map +1 -0
  68. package/dist/recording-db-gOgaoQh0.js +348 -0
  69. package/dist/recording-db-gOgaoQh0.js.map +1 -0
  70. package/dist/recording-db-lIkSMTLq.mjs +348 -0
  71. package/dist/recording-db-lIkSMTLq.mjs.map +1 -0
  72. package/dist/recording-service-facade-B9lG6OFn.mjs +123 -0
  73. package/dist/recording-service-facade-B9lG6OFn.mjs.map +1 -0
  74. package/dist/recording-service-facade-Do1PKlAL.js +123 -0
  75. package/dist/recording-service-facade-Do1PKlAL.js.map +1 -0
  76. package/dist/storage-estimator-CRpoQc9j.js +72 -0
  77. package/dist/storage-estimator-CRpoQc9j.js.map +1 -0
  78. package/dist/storage-estimator-DzD8gWJH.mjs +72 -0
  79. package/dist/storage-estimator-DzD8gWJH.mjs.map +1 -0
  80. package/package.json +148 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recording-service-facade-Do1PKlAL.js","sources":["../src/recording/recording-service-facade.ts"],"sourcesContent":["/**\n * RecordingServiceFacade — implements the recording-engine capability\n * contract by delegating to the internal service classes.\n *\n * Introduced in session 6 Sprint D.2 to replace the broken hand-written\n * recording.router.ts which invoked non-existent methods via a generic\n * `call()` dispatcher. Each facade method delegates to the correct\n * internal service: RecordingCoordinator, RecordingDb, PlaylistGenerator,\n * or StorageEstimator.\n *\n * The facade is the capability provider registered via\n * `context.registerProvider('recording-engine', facade)` — the auto-\n * generated cap router dispatches typed tRPC procedures directly to\n * these methods with full Zod input/output validation.\n */\n\nimport type { RecordingCoordinator } from './recording/recording-coordinator.js'\nimport type { RecordingDb } from './recording/recording-db.js'\nimport type { PlaylistGenerator } from './recording/playlist-generator.js'\nimport type { StorageEstimator } from './recording/storage-estimator.js'\nimport type {\n RecordingPolicy,\n RecordingSegment,\n RecordingThumbnail,\n RecordingStorageConfig,\n StorageEstimate,\n DataCategory,\n} from './recording/types.js'\n\ninterface FacadeDeps {\n readonly coordinator: RecordingCoordinator\n readonly db: RecordingDb\n readonly playlistGenerator: PlaylistGenerator\n readonly storageEstimator: StorageEstimator\n}\n\nexport class RecordingServiceFacade {\n constructor(private readonly deps: FacadeDeps) {}\n\n // ── Status ────────────────────────────────────────────────────────\n\n getStatus(): { activeRecordings: number; totalSegments: number; totalSizeMB: number } {\n const { db, coordinator } = this.deps\n const activeRecordings = coordinator.getActiveCount()\n const { segmentCount: totalSegments, totalBytes } = db.getGlobalStorageUsage()\n return {\n activeRecordings,\n totalSegments,\n totalSizeMB: Math.round(totalBytes / (1024 * 1024)),\n }\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────\n\n async enable(input: {\n deviceId: number\n policy: Omit<RecordingPolicy, 'deviceId'>\n storageOverrides?: readonly Omit<RecordingStorageConfig, 'deviceId'>[]\n ffmpegOverrides?: Record<string, unknown>\n }): Promise<void> {\n await this.deps.coordinator.enableRecording(input.deviceId, {\n policy: input.policy,\n storageOverrides: input.storageOverrides,\n ffmpegOverrides: input.ffmpegOverrides,\n })\n }\n\n async disable(input: { deviceId: number }): Promise<void> {\n await this.deps.coordinator.disableRecording(input.deviceId)\n }\n\n // ── Config ────────────────────────────────────────────────────────\n\n getConfig(input: { deviceId: number }): RecordingPolicy | null {\n return this.deps.db.getPolicy(input.deviceId)\n }\n\n async updateConfig(input: {\n deviceId: number\n policy: Omit<RecordingPolicy, 'deviceId'>\n ffmpegOverrides?: Record<string, unknown>\n }): Promise<void> {\n this.deps.db.upsertPolicy({ ...input.policy, deviceId: input.deviceId })\n // If the device is actively recording, restart with the new config\n if (this.deps.coordinator.isRecording(input.deviceId)) {\n await this.deps.coordinator.disableRecording(input.deviceId)\n await this.deps.coordinator.enableRecording(input.deviceId, {\n policy: input.policy,\n ffmpegOverrides: input.ffmpegOverrides,\n })\n }\n }\n\n // ── Playback ──────────────────────────────────────────────────────\n\n getPlaylist(input: {\n deviceId: number\n streamId: string\n startTime: number\n endTime: number\n live?: boolean\n }): string {\n return this.deps.playlistGenerator.generate(\n input.deviceId,\n input.streamId,\n input.startTime,\n input.endTime,\n { live: input.live },\n )\n }\n\n getThumbnail(input: {\n deviceId: number\n timestamp: number\n category?: string\n }): RecordingThumbnail | null {\n return this.deps.db.findNearestThumbnail(\n input.deviceId,\n input.timestamp,\n input.category ?? 'scrub',\n )\n }\n\n getSegments(input: {\n deviceId: number\n streamId: string\n startTime: number\n endTime: number\n }): readonly RecordingSegment[] {\n return this.deps.db.querySegments(\n input.deviceId,\n input.streamId,\n input.startTime,\n input.endTime,\n )\n }\n\n getAvailability(input: {\n deviceId: number\n startTime: number\n endTime: number\n }): readonly { startTime: number; endTime: number; streams: readonly string[] }[] {\n return this.deps.db.getAvailability(\n input.deviceId,\n input.startTime,\n input.endTime,\n )\n }\n\n // ── Storage ───────────────────────────────────────────────────────\n\n estimateStorage(input: {\n deviceId: number\n motionInput?: { avgEventsPerDay: number; avgDurationSec: number }\n }): StorageEstimate {\n return this.deps.storageEstimator.estimateForDevice(\n input.deviceId,\n input.motionInput,\n )\n }\n\n estimateGlobalStorage(): StorageEstimate {\n return this.deps.storageEstimator.estimateGlobal()\n }\n\n getStorageUsage(input: { deviceId: number; streamId: string }): {\n totalBytes: number\n segmentCount: number\n } {\n return this.deps.db.getStorageUsage(input.deviceId, input.streamId)\n }\n\n // ── Policy ────────────────────────────────────────────────────────\n\n setPolicy(input: {\n deviceId: number\n policy: Omit<RecordingPolicy, 'deviceId'>\n }): void {\n this.deps.db.upsertPolicy({ ...input.policy, deviceId: input.deviceId })\n }\n\n getPolicy(input: { deviceId: number }): RecordingPolicy | null {\n return this.deps.db.getPolicy(input.deviceId)\n }\n\n getPolicyStatus(input: { deviceId: number }): {\n deviceId: number\n enabled: boolean\n mode: RecordingPolicy['mode']\n activeStreams: number\n } | null {\n const policy = this.deps.db.getPolicy(input.deviceId)\n if (!policy) return null\n const activeStreams = this.deps.coordinator.isRecording(input.deviceId)\n ? policy.streams.length\n : 0\n return {\n deviceId: input.deviceId,\n enabled: policy.enabled,\n mode: policy.mode,\n activeStreams,\n }\n }\n\n // ── Retention ─────────────────────────────────────────────────────\n\n getRetentionConfig(input: {\n deviceId: number\n dataCategory: DataCategory\n }): RecordingStorageConfig | null {\n return this.deps.db.resolveStorageConfig(input.deviceId, input.dataCategory)\n }\n\n updateRetentionConfig(input: RecordingStorageConfig): void {\n this.deps.db.upsertStorageConfig(input)\n }\n\n // ── Motion ────────────────────────────────────────────────────────\n\n getMotionStats(input: {\n deviceId: number\n startTime: number\n endTime: number\n }): {\n totalEvents: number\n avgDurationSec: number\n avgEventsPerDay: number\n dutyCyclePercent: number\n } {\n return this.deps.db.getMotionStats(\n input.deviceId,\n input.startTime,\n input.endTime,\n )\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ class StorageEstimator {
4
+ constructor(db, networkTracker) {
5
+ this.db = db;
6
+ this.networkTracker = networkTracker;
7
+ }
8
+ estimateForDevice(deviceId, motionInput) {
9
+ const policy = this.db.getPolicy(deviceId);
10
+ if (!policy) return { perStream: {}, thumbnails: { estimatedGb: 0 }, totalEstimatedGb: 0 };
11
+ const stats = this.networkTracker.getDeviceStats(deviceId);
12
+ const perStream = {};
13
+ let totalGb = 0;
14
+ const motionEstimate = motionInput ? {
15
+ avgEventsPerDay: motionInput.avgEventsPerDay,
16
+ avgDurationSec: motionInput.avgDurationSec,
17
+ dutyCyclePercent: motionInput.avgEventsPerDay * motionInput.avgDurationSec / 86400 * 100
18
+ } : void 0;
19
+ for (const sp of policy.streams) {
20
+ const category = `recording:${sp.streamId}`;
21
+ const config = this.db.resolveStorageConfig(deviceId, category);
22
+ const retentionDays = config?.retentionDays ?? null;
23
+ const retentionGb = config?.retentionGb ?? null;
24
+ const streamStats = stats?.streams[sp.streamId];
25
+ const observedBitrate = streamStats?.observedBitrateKbps ?? 0;
26
+ const bitrateKbps = observedBitrate > 0 ? observedBitrate : streamStats?.nominalBitrateKbps ?? 4e3;
27
+ let estimatedGb = 0;
28
+ if (retentionDays !== null) {
29
+ const retentionSeconds = retentionDays * 86400;
30
+ estimatedGb = bitrateKbps * retentionSeconds / 8 / 1024 / 1024 / 1024;
31
+ }
32
+ if (policy.mode === "motion" && motionEstimate) {
33
+ estimatedGb = estimatedGb * motionEstimate.dutyCyclePercent / 100;
34
+ }
35
+ let estimatedDaysAtCapacity = null;
36
+ if (retentionGb !== null && estimatedGb > retentionGb) {
37
+ estimatedDaysAtCapacity = retentionDays !== null ? retentionDays * (retentionGb / estimatedGb) : null;
38
+ estimatedGb = retentionGb;
39
+ }
40
+ perStream[sp.streamId] = { bitrateKbps, retentionDays, retentionGb, estimatedGb, estimatedDaysAtCapacity };
41
+ totalGb += estimatedGb;
42
+ }
43
+ const thumbRetentionDays = policy.streams[0] ? this.db.resolveStorageConfig(deviceId, "thumbnail:scrub")?.retentionDays ?? 7 : 7;
44
+ const thumbGb = 2 * 24 * thumbRetentionDays / 1024;
45
+ totalGb += thumbGb;
46
+ return { perStream, thumbnails: { estimatedGb: thumbGb }, totalEstimatedGb: totalGb, motionEstimate };
47
+ }
48
+ /**
49
+ * Estimate global storage across all devices with active policies.
50
+ * Sums per-device estimates. No motion weighting — uses continuous mode
51
+ * for the global view.
52
+ */
53
+ estimateGlobal() {
54
+ const allPolicies = this.db.getEnabledPolicies();
55
+ let totalGb = 0;
56
+ const perStream = {};
57
+ let totalThumbGb = 0;
58
+ for (const policy of allPolicies) {
59
+ if (!policy.enabled) continue;
60
+ const deviceEstimate = this.estimateForDevice(policy.deviceId);
61
+ totalGb += deviceEstimate.totalEstimatedGb;
62
+ totalThumbGb += deviceEstimate.thumbnails.estimatedGb;
63
+ for (const [key, val] of Object.entries(deviceEstimate.perStream)) {
64
+ const globalKey = `${policy.deviceId}/${key}`;
65
+ perStream[globalKey] = val;
66
+ }
67
+ }
68
+ return { perStream, thumbnails: { estimatedGb: totalThumbGb }, totalEstimatedGb: totalGb };
69
+ }
70
+ }
71
+ exports.StorageEstimator = StorageEstimator;
72
+ //# sourceMappingURL=storage-estimator-CRpoQc9j.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-estimator-CRpoQc9j.js","sources":["../src/recording/recording/storage-estimator.ts"],"sourcesContent":["import type { INetworkQualityTracker } from '@camstack/types'\nimport type { RecordingDb } from './recording-db.js'\nimport type { DataCategory, StorageEstimate, StreamEstimate } from './types.js'\n\ninterface MotionEstimateInput {\n readonly avgEventsPerDay: number\n readonly avgDurationSec: number\n}\n\nexport class StorageEstimator {\n constructor(\n private readonly db: RecordingDb,\n private readonly networkTracker: INetworkQualityTracker,\n ) {}\n\n estimateForDevice(deviceId: number, motionInput?: MotionEstimateInput): StorageEstimate {\n const policy = this.db.getPolicy(deviceId)\n if (!policy) return { perStream: {}, thumbnails: { estimatedGb: 0 }, totalEstimatedGb: 0 }\n\n const stats = this.networkTracker.getDeviceStats(deviceId)\n const perStream: Record<string, StreamEstimate> = {}\n let totalGb = 0\n\n const motionEstimate = motionInput ? {\n avgEventsPerDay: motionInput.avgEventsPerDay,\n avgDurationSec: motionInput.avgDurationSec,\n dutyCyclePercent: (motionInput.avgEventsPerDay * motionInput.avgDurationSec / 86400) * 100,\n } : undefined\n\n for (const sp of policy.streams) {\n const category = `recording:${sp.streamId}` as DataCategory\n const config = this.db.resolveStorageConfig(deviceId, category)\n const retentionDays = config?.retentionDays ?? null\n const retentionGb = config?.retentionGb ?? null\n\n const streamStats = stats?.streams[sp.streamId]\n const observedBitrate = streamStats?.observedBitrateKbps ?? 0\n const bitrateKbps = observedBitrate > 0\n ? observedBitrate\n : (streamStats?.nominalBitrateKbps ?? 4000)\n\n let estimatedGb = 0\n if (retentionDays !== null) {\n const retentionSeconds = retentionDays * 86400\n estimatedGb = (bitrateKbps * retentionSeconds) / 8 / 1024 / 1024 / 1024\n }\n\n if (policy.mode === 'motion' && motionEstimate) {\n estimatedGb = estimatedGb * motionEstimate.dutyCyclePercent / 100\n }\n\n let estimatedDaysAtCapacity: number | null = null\n if (retentionGb !== null && estimatedGb > retentionGb) {\n estimatedDaysAtCapacity = retentionDays !== null ? retentionDays * (retentionGb / estimatedGb) : null\n estimatedGb = retentionGb\n }\n\n perStream[sp.streamId] = { bitrateKbps, retentionDays, retentionGb, estimatedGb, estimatedDaysAtCapacity }\n totalGb += estimatedGb\n }\n\n const thumbRetentionDays = policy.streams[0]\n ? (this.db.resolveStorageConfig(deviceId, 'thumbnail:scrub')?.retentionDays ?? 7)\n : 7\n const thumbGb = (2 * 24 * thumbRetentionDays) / 1024\n totalGb += thumbGb\n\n return { perStream, thumbnails: { estimatedGb: thumbGb }, totalEstimatedGb: totalGb, motionEstimate }\n }\n\n /**\n * Estimate global storage across all devices with active policies.\n * Sums per-device estimates. No motion weighting — uses continuous mode\n * for the global view.\n */\n estimateGlobal(): StorageEstimate {\n const allPolicies = this.db.getEnabledPolicies()\n let totalGb = 0\n const perStream: Record<string, StreamEstimate> = {}\n let totalThumbGb = 0\n\n for (const policy of allPolicies) {\n if (!policy.enabled) continue\n const deviceEstimate = this.estimateForDevice(policy.deviceId)\n totalGb += deviceEstimate.totalEstimatedGb\n totalThumbGb += deviceEstimate.thumbnails.estimatedGb\n for (const [key, val] of Object.entries(deviceEstimate.perStream)) {\n const globalKey = `${policy.deviceId}/${key}`\n perStream[globalKey] = val\n }\n }\n\n return { perStream, thumbnails: { estimatedGb: totalThumbGb }, totalEstimatedGb: totalGb }\n }\n}\n"],"names":[],"mappings":";;AASO,MAAM,iBAAiB;AAAA,EAC5B,YACmB,IACA,gBACjB;AAFiB,SAAA,KAAA;AACA,SAAA,iBAAA;AAAA,EAChB;AAAA,EAEH,kBAAkB,UAAkB,aAAoD;AACtF,UAAM,SAAS,KAAK,GAAG,UAAU,QAAQ;AACzC,QAAI,CAAC,OAAQ,QAAO,EAAE,WAAW,CAAA,GAAI,YAAY,EAAE,aAAa,KAAK,kBAAkB,EAAA;AAEvF,UAAM,QAAQ,KAAK,eAAe,eAAe,QAAQ;AACzD,UAAM,YAA4C,CAAA;AAClD,QAAI,UAAU;AAEd,UAAM,iBAAiB,cAAc;AAAA,MACnC,iBAAiB,YAAY;AAAA,MAC7B,gBAAgB,YAAY;AAAA,MAC5B,kBAAmB,YAAY,kBAAkB,YAAY,iBAAiB,QAAS;AAAA,IAAA,IACrF;AAEJ,eAAW,MAAM,OAAO,SAAS;AAC/B,YAAM,WAAW,aAAa,GAAG,QAAQ;AACzC,YAAM,SAAS,KAAK,GAAG,qBAAqB,UAAU,QAAQ;AAC9D,YAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,YAAM,cAAc,QAAQ,eAAe;AAE3C,YAAM,cAAc,OAAO,QAAQ,GAAG,QAAQ;AAC9C,YAAM,kBAAkB,aAAa,uBAAuB;AAC5D,YAAM,cAAc,kBAAkB,IAClC,kBACC,aAAa,sBAAsB;AAExC,UAAI,cAAc;AAClB,UAAI,kBAAkB,MAAM;AAC1B,cAAM,mBAAmB,gBAAgB;AACzC,sBAAe,cAAc,mBAAoB,IAAI,OAAO,OAAO;AAAA,MACrE;AAEA,UAAI,OAAO,SAAS,YAAY,gBAAgB;AAC9C,sBAAc,cAAc,eAAe,mBAAmB;AAAA,MAChE;AAEA,UAAI,0BAAyC;AAC7C,UAAI,gBAAgB,QAAQ,cAAc,aAAa;AACrD,kCAA0B,kBAAkB,OAAO,iBAAiB,cAAc,eAAe;AACjG,sBAAc;AAAA,MAChB;AAEA,gBAAU,GAAG,QAAQ,IAAI,EAAE,aAAa,eAAe,aAAa,aAAa,wBAAA;AACjF,iBAAW;AAAA,IACb;AAEA,UAAM,qBAAqB,OAAO,QAAQ,CAAC,IACtC,KAAK,GAAG,qBAAqB,UAAU,iBAAiB,GAAG,iBAAiB,IAC7E;AACJ,UAAM,UAAW,IAAI,KAAK,qBAAsB;AAChD,eAAW;AAEX,WAAO,EAAE,WAAW,YAAY,EAAE,aAAa,WAAW,kBAAkB,SAAS,eAAA;AAAA,EACvF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAkC;AAChC,UAAM,cAAc,KAAK,GAAG,mBAAA;AAC5B,QAAI,UAAU;AACd,UAAM,YAA4C,CAAA;AAClD,QAAI,eAAe;AAEnB,eAAW,UAAU,aAAa;AAChC,UAAI,CAAC,OAAO,QAAS;AACrB,YAAM,iBAAiB,KAAK,kBAAkB,OAAO,QAAQ;AAC7D,iBAAW,eAAe;AAC1B,sBAAgB,eAAe,WAAW;AAC1C,iBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,eAAe,SAAS,GAAG;AACjE,cAAM,YAAY,GAAG,OAAO,QAAQ,IAAI,GAAG;AAC3C,kBAAU,SAAS,IAAI;AAAA,MACzB;AAAA,IACF;AAEA,WAAO,EAAE,WAAW,YAAY,EAAE,aAAa,aAAA,GAAgB,kBAAkB,QAAA;AAAA,EACnF;AACF;;"}
@@ -0,0 +1,72 @@
1
+ class StorageEstimator {
2
+ constructor(db, networkTracker) {
3
+ this.db = db;
4
+ this.networkTracker = networkTracker;
5
+ }
6
+ estimateForDevice(deviceId, motionInput) {
7
+ const policy = this.db.getPolicy(deviceId);
8
+ if (!policy) return { perStream: {}, thumbnails: { estimatedGb: 0 }, totalEstimatedGb: 0 };
9
+ const stats = this.networkTracker.getDeviceStats(deviceId);
10
+ const perStream = {};
11
+ let totalGb = 0;
12
+ const motionEstimate = motionInput ? {
13
+ avgEventsPerDay: motionInput.avgEventsPerDay,
14
+ avgDurationSec: motionInput.avgDurationSec,
15
+ dutyCyclePercent: motionInput.avgEventsPerDay * motionInput.avgDurationSec / 86400 * 100
16
+ } : void 0;
17
+ for (const sp of policy.streams) {
18
+ const category = `recording:${sp.streamId}`;
19
+ const config = this.db.resolveStorageConfig(deviceId, category);
20
+ const retentionDays = config?.retentionDays ?? null;
21
+ const retentionGb = config?.retentionGb ?? null;
22
+ const streamStats = stats?.streams[sp.streamId];
23
+ const observedBitrate = streamStats?.observedBitrateKbps ?? 0;
24
+ const bitrateKbps = observedBitrate > 0 ? observedBitrate : streamStats?.nominalBitrateKbps ?? 4e3;
25
+ let estimatedGb = 0;
26
+ if (retentionDays !== null) {
27
+ const retentionSeconds = retentionDays * 86400;
28
+ estimatedGb = bitrateKbps * retentionSeconds / 8 / 1024 / 1024 / 1024;
29
+ }
30
+ if (policy.mode === "motion" && motionEstimate) {
31
+ estimatedGb = estimatedGb * motionEstimate.dutyCyclePercent / 100;
32
+ }
33
+ let estimatedDaysAtCapacity = null;
34
+ if (retentionGb !== null && estimatedGb > retentionGb) {
35
+ estimatedDaysAtCapacity = retentionDays !== null ? retentionDays * (retentionGb / estimatedGb) : null;
36
+ estimatedGb = retentionGb;
37
+ }
38
+ perStream[sp.streamId] = { bitrateKbps, retentionDays, retentionGb, estimatedGb, estimatedDaysAtCapacity };
39
+ totalGb += estimatedGb;
40
+ }
41
+ const thumbRetentionDays = policy.streams[0] ? this.db.resolveStorageConfig(deviceId, "thumbnail:scrub")?.retentionDays ?? 7 : 7;
42
+ const thumbGb = 2 * 24 * thumbRetentionDays / 1024;
43
+ totalGb += thumbGb;
44
+ return { perStream, thumbnails: { estimatedGb: thumbGb }, totalEstimatedGb: totalGb, motionEstimate };
45
+ }
46
+ /**
47
+ * Estimate global storage across all devices with active policies.
48
+ * Sums per-device estimates. No motion weighting — uses continuous mode
49
+ * for the global view.
50
+ */
51
+ estimateGlobal() {
52
+ const allPolicies = this.db.getEnabledPolicies();
53
+ let totalGb = 0;
54
+ const perStream = {};
55
+ let totalThumbGb = 0;
56
+ for (const policy of allPolicies) {
57
+ if (!policy.enabled) continue;
58
+ const deviceEstimate = this.estimateForDevice(policy.deviceId);
59
+ totalGb += deviceEstimate.totalEstimatedGb;
60
+ totalThumbGb += deviceEstimate.thumbnails.estimatedGb;
61
+ for (const [key, val] of Object.entries(deviceEstimate.perStream)) {
62
+ const globalKey = `${policy.deviceId}/${key}`;
63
+ perStream[globalKey] = val;
64
+ }
65
+ }
66
+ return { perStream, thumbnails: { estimatedGb: totalThumbGb }, totalEstimatedGb: totalGb };
67
+ }
68
+ }
69
+ export {
70
+ StorageEstimator
71
+ };
72
+ //# sourceMappingURL=storage-estimator-DzD8gWJH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-estimator-DzD8gWJH.mjs","sources":["../src/recording/recording/storage-estimator.ts"],"sourcesContent":["import type { INetworkQualityTracker } from '@camstack/types'\nimport type { RecordingDb } from './recording-db.js'\nimport type { DataCategory, StorageEstimate, StreamEstimate } from './types.js'\n\ninterface MotionEstimateInput {\n readonly avgEventsPerDay: number\n readonly avgDurationSec: number\n}\n\nexport class StorageEstimator {\n constructor(\n private readonly db: RecordingDb,\n private readonly networkTracker: INetworkQualityTracker,\n ) {}\n\n estimateForDevice(deviceId: number, motionInput?: MotionEstimateInput): StorageEstimate {\n const policy = this.db.getPolicy(deviceId)\n if (!policy) return { perStream: {}, thumbnails: { estimatedGb: 0 }, totalEstimatedGb: 0 }\n\n const stats = this.networkTracker.getDeviceStats(deviceId)\n const perStream: Record<string, StreamEstimate> = {}\n let totalGb = 0\n\n const motionEstimate = motionInput ? {\n avgEventsPerDay: motionInput.avgEventsPerDay,\n avgDurationSec: motionInput.avgDurationSec,\n dutyCyclePercent: (motionInput.avgEventsPerDay * motionInput.avgDurationSec / 86400) * 100,\n } : undefined\n\n for (const sp of policy.streams) {\n const category = `recording:${sp.streamId}` as DataCategory\n const config = this.db.resolveStorageConfig(deviceId, category)\n const retentionDays = config?.retentionDays ?? null\n const retentionGb = config?.retentionGb ?? null\n\n const streamStats = stats?.streams[sp.streamId]\n const observedBitrate = streamStats?.observedBitrateKbps ?? 0\n const bitrateKbps = observedBitrate > 0\n ? observedBitrate\n : (streamStats?.nominalBitrateKbps ?? 4000)\n\n let estimatedGb = 0\n if (retentionDays !== null) {\n const retentionSeconds = retentionDays * 86400\n estimatedGb = (bitrateKbps * retentionSeconds) / 8 / 1024 / 1024 / 1024\n }\n\n if (policy.mode === 'motion' && motionEstimate) {\n estimatedGb = estimatedGb * motionEstimate.dutyCyclePercent / 100\n }\n\n let estimatedDaysAtCapacity: number | null = null\n if (retentionGb !== null && estimatedGb > retentionGb) {\n estimatedDaysAtCapacity = retentionDays !== null ? retentionDays * (retentionGb / estimatedGb) : null\n estimatedGb = retentionGb\n }\n\n perStream[sp.streamId] = { bitrateKbps, retentionDays, retentionGb, estimatedGb, estimatedDaysAtCapacity }\n totalGb += estimatedGb\n }\n\n const thumbRetentionDays = policy.streams[0]\n ? (this.db.resolveStorageConfig(deviceId, 'thumbnail:scrub')?.retentionDays ?? 7)\n : 7\n const thumbGb = (2 * 24 * thumbRetentionDays) / 1024\n totalGb += thumbGb\n\n return { perStream, thumbnails: { estimatedGb: thumbGb }, totalEstimatedGb: totalGb, motionEstimate }\n }\n\n /**\n * Estimate global storage across all devices with active policies.\n * Sums per-device estimates. No motion weighting — uses continuous mode\n * for the global view.\n */\n estimateGlobal(): StorageEstimate {\n const allPolicies = this.db.getEnabledPolicies()\n let totalGb = 0\n const perStream: Record<string, StreamEstimate> = {}\n let totalThumbGb = 0\n\n for (const policy of allPolicies) {\n if (!policy.enabled) continue\n const deviceEstimate = this.estimateForDevice(policy.deviceId)\n totalGb += deviceEstimate.totalEstimatedGb\n totalThumbGb += deviceEstimate.thumbnails.estimatedGb\n for (const [key, val] of Object.entries(deviceEstimate.perStream)) {\n const globalKey = `${policy.deviceId}/${key}`\n perStream[globalKey] = val\n }\n }\n\n return { perStream, thumbnails: { estimatedGb: totalThumbGb }, totalEstimatedGb: totalGb }\n }\n}\n"],"names":[],"mappings":"AASO,MAAM,iBAAiB;AAAA,EAC5B,YACmB,IACA,gBACjB;AAFiB,SAAA,KAAA;AACA,SAAA,iBAAA;AAAA,EAChB;AAAA,EAEH,kBAAkB,UAAkB,aAAoD;AACtF,UAAM,SAAS,KAAK,GAAG,UAAU,QAAQ;AACzC,QAAI,CAAC,OAAQ,QAAO,EAAE,WAAW,CAAA,GAAI,YAAY,EAAE,aAAa,KAAK,kBAAkB,EAAA;AAEvF,UAAM,QAAQ,KAAK,eAAe,eAAe,QAAQ;AACzD,UAAM,YAA4C,CAAA;AAClD,QAAI,UAAU;AAEd,UAAM,iBAAiB,cAAc;AAAA,MACnC,iBAAiB,YAAY;AAAA,MAC7B,gBAAgB,YAAY;AAAA,MAC5B,kBAAmB,YAAY,kBAAkB,YAAY,iBAAiB,QAAS;AAAA,IAAA,IACrF;AAEJ,eAAW,MAAM,OAAO,SAAS;AAC/B,YAAM,WAAW,aAAa,GAAG,QAAQ;AACzC,YAAM,SAAS,KAAK,GAAG,qBAAqB,UAAU,QAAQ;AAC9D,YAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,YAAM,cAAc,QAAQ,eAAe;AAE3C,YAAM,cAAc,OAAO,QAAQ,GAAG,QAAQ;AAC9C,YAAM,kBAAkB,aAAa,uBAAuB;AAC5D,YAAM,cAAc,kBAAkB,IAClC,kBACC,aAAa,sBAAsB;AAExC,UAAI,cAAc;AAClB,UAAI,kBAAkB,MAAM;AAC1B,cAAM,mBAAmB,gBAAgB;AACzC,sBAAe,cAAc,mBAAoB,IAAI,OAAO,OAAO;AAAA,MACrE;AAEA,UAAI,OAAO,SAAS,YAAY,gBAAgB;AAC9C,sBAAc,cAAc,eAAe,mBAAmB;AAAA,MAChE;AAEA,UAAI,0BAAyC;AAC7C,UAAI,gBAAgB,QAAQ,cAAc,aAAa;AACrD,kCAA0B,kBAAkB,OAAO,iBAAiB,cAAc,eAAe;AACjG,sBAAc;AAAA,MAChB;AAEA,gBAAU,GAAG,QAAQ,IAAI,EAAE,aAAa,eAAe,aAAa,aAAa,wBAAA;AACjF,iBAAW;AAAA,IACb;AAEA,UAAM,qBAAqB,OAAO,QAAQ,CAAC,IACtC,KAAK,GAAG,qBAAqB,UAAU,iBAAiB,GAAG,iBAAiB,IAC7E;AACJ,UAAM,UAAW,IAAI,KAAK,qBAAsB;AAChD,eAAW;AAEX,WAAO,EAAE,WAAW,YAAY,EAAE,aAAa,WAAW,kBAAkB,SAAS,eAAA;AAAA,EACvF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAkC;AAChC,UAAM,cAAc,KAAK,GAAG,mBAAA;AAC5B,QAAI,UAAU;AACd,UAAM,YAA4C,CAAA;AAClD,QAAI,eAAe;AAEnB,eAAW,UAAU,aAAa;AAChC,UAAI,CAAC,OAAO,QAAS;AACrB,YAAM,iBAAiB,KAAK,kBAAkB,OAAO,QAAQ;AAC7D,iBAAW,eAAe;AAC1B,sBAAgB,eAAe,WAAW;AAC1C,iBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,eAAe,SAAS,GAAG;AACjE,cAAM,YAAY,GAAG,OAAO,QAAQ,IAAI,GAAG;AAC3C,kBAAU,SAAS,IAAI;AAAA,MACzB;AAAA,IACF;AAEA,WAAO,EAAE,WAAW,YAAY,EAAE,aAAa,aAAA,GAAgB,kBAAkB,QAAA;AAAA,EACnF;AACF;"}
package/package.json ADDED
@@ -0,0 +1,148 @@
1
+ {
2
+ "name": "@camstack/addon-post-analysis",
3
+ "version": "0.1.1",
4
+ "description": "CamStack Post-Analysis bundle — recording, enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping 4 addons that consume pipeline output.",
5
+ "keywords": [
6
+ "camstack",
7
+ "addon",
8
+ "camstack-addon",
9
+ "post-analysis",
10
+ "recording",
11
+ "enrichment",
12
+ "embedding",
13
+ "analytics"
14
+ ],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/camstack/server"
19
+ },
20
+ "main": "./dist/recording/index.js",
21
+ "exports": {
22
+ "./recording": {
23
+ "import": "./dist/recording/index.mjs",
24
+ "require": "./dist/recording/index.js"
25
+ },
26
+ "./enrichment-engine": {
27
+ "import": "./dist/enrichment-engine/index.mjs",
28
+ "require": "./dist/enrichment-engine/index.js"
29
+ },
30
+ "./embedding-encoder": {
31
+ "import": "./dist/embedding-encoder/index.mjs",
32
+ "require": "./dist/embedding-encoder/index.js"
33
+ },
34
+ "./pipeline-analytics": {
35
+ "import": "./dist/pipeline-analytics/index.mjs",
36
+ "require": "./dist/pipeline-analytics/index.js"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "camstack": {
44
+ "displayName": "Post-Analysis",
45
+ "bundle": {
46
+ "displayName": "Post-Analysis",
47
+ "description": "Recording, enrichment, embedding encoder, and pipeline analytics — consumers of pipeline output."
48
+ },
49
+ "nativeDependencies": {
50
+ "better-sqlite3": "^12.8.0",
51
+ "onnxruntime-node": "^1.24.3",
52
+ "sharp": "^0.34.0"
53
+ },
54
+ "addons": [
55
+ {
56
+ "id": "recording",
57
+ "name": "Recording",
58
+ "description": "Per-camera detection-triggered recording with per-stream profile + retention. Subscribes to detection events on the post-analysis bus and writes timestamped clips to the recordings StorageLocation.",
59
+ "entry": "./dist/recording/index.js",
60
+ "execution": {
61
+ "placement": "any"
62
+ },
63
+ "protected": true,
64
+ "capabilities": [
65
+ {
66
+ "name": "recording-engine"
67
+ }
68
+ ]
69
+ },
70
+ {
71
+ "id": "enrichment-engine",
72
+ "name": "Enrichment Engine",
73
+ "description": "Post-detection enrichment workers — embedding dispatcher, scene-state, activity summary. Hub-only consumers that build derived metadata from the detection stream.",
74
+ "entry": "./dist/enrichment-engine/index.js",
75
+ "execution": {
76
+ "placement": "any"
77
+ },
78
+ "protected": true,
79
+ "capabilities": []
80
+ },
81
+ {
82
+ "id": "embedding-encoder",
83
+ "name": "Embedding Encoder",
84
+ "description": "ONNX-Runtime image-embedding encoder. Produces semantic vectors from detection crops for similarity search + scene-state tracking.",
85
+ "entry": "./dist/embedding-encoder/index.js",
86
+ "execution": {
87
+ "placement": "any"
88
+ },
89
+ "protected": true,
90
+ "capabilities": [
91
+ {
92
+ "name": "embedding-encoder"
93
+ }
94
+ ]
95
+ },
96
+ {
97
+ "id": "pipeline-analytics",
98
+ "name": "Pipeline Analytics",
99
+ "description": "Zone analytics + audio metrics aggregator. Consumes pipeline events to surface per-camera zone counters and audio-level histograms.",
100
+ "entry": "./dist/pipeline-analytics/index.js",
101
+ "execution": {
102
+ "placement": "any"
103
+ },
104
+ "protected": true,
105
+ "capabilities": [
106
+ {
107
+ "name": "pipeline-analytics"
108
+ },
109
+ {
110
+ "name": "zone-analytics"
111
+ },
112
+ {
113
+ "name": "audio-metrics"
114
+ },
115
+ {
116
+ "name": "addon-widgets-source"
117
+ }
118
+ ]
119
+ }
120
+ ]
121
+ },
122
+ "scripts": {
123
+ "build": "vite build && vite build --config vite.widgets.pipeline-analytics.config.ts",
124
+ "build:server": "vite build",
125
+ "build:widgets": "vite build --config vite.widgets.pipeline-analytics.config.ts",
126
+ "dev": "vite build --watch",
127
+ "typecheck": "tsc --noEmit",
128
+ "publish": "npm publish --access public"
129
+ },
130
+ "peerDependencies": {
131
+ "@camstack/types": "^0.1.0",
132
+ "react": ">=18",
133
+ "react-dom": ">=18"
134
+ },
135
+ "dependencies": {
136
+ "@camstack/core": "*",
137
+ "better-sqlite3": "^12.8.0",
138
+ "onnxruntime-node": "^1.24.3",
139
+ "sharp": "^0.34.0"
140
+ },
141
+ "devDependencies": {
142
+ "@camstack/types": "*",
143
+ "@module-federation/vite": "^1.15.2",
144
+ "@vitejs/plugin-react": "^4.0.0",
145
+ "tsup": "^8.0.0",
146
+ "typescript": "~5.9.0"
147
+ }
148
+ }