@blueharford/scrypted-spatial-awareness 0.6.11 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.zip CHANGED
Binary file
@@ -34431,10 +34431,11 @@ class AlertManager {
34431
34431
  return null;
34432
34432
  }
34433
34433
  // Create alert
34434
+ // Note: details.objectLabel may contain LLM-generated description - preserve it if provided
34434
34435
  const fullDetails = {
34435
34436
  ...details,
34436
34437
  objectClass: tracked.className,
34437
- objectLabel: tracked.label,
34438
+ objectLabel: details.objectLabel || tracked.label,
34438
34439
  };
34439
34440
  const alert = (0, alert_1.createAlert)(type, tracked.globalId, fullDetails, rule.severity, rule.id);
34440
34441
  // Store alert
@@ -36942,6 +36943,9 @@ class TrackingEngine {
36942
36943
  trainingConfig = training_1.DEFAULT_TRAINING_CONFIG;
36943
36944
  /** Callback for training status updates */
36944
36945
  onTrainingStatusUpdate;
36946
+ // ==================== Snapshot Cache ====================
36947
+ /** Cached snapshots for tracked objects (for faster notifications) */
36948
+ snapshotCache = new Map();
36945
36949
  constructor(topology, state, alertManager, config, console) {
36946
36950
  this.topology = topology;
36947
36951
  this.state = state;
@@ -37165,6 +37169,12 @@ class TrackingEngine {
37165
37169
  const lastSighting = (0, tracked_object_1.getLastSighting)(tracked);
37166
37170
  if (lastSighting && lastSighting.cameraId !== sighting.cameraId) {
37167
37171
  const transitDuration = sighting.timestamp - lastSighting.timestamp;
37172
+ // Update cached snapshot from new camera (object is now visible here)
37173
+ if (this.config.useLlmDescriptions) {
37174
+ this.captureAndCacheSnapshot(tracked.globalId, sighting.cameraId).catch(e => {
37175
+ this.console.warn(`[Transition Snapshot] Failed to update snapshot: ${e}`);
37176
+ });
37177
+ }
37168
37178
  // Add journey segment
37169
37179
  this.state.addJourney(tracked.globalId, {
37170
37180
  fromCameraId: lastSighting.cameraId,
@@ -37231,6 +37241,13 @@ class TrackingEngine {
37231
37241
  }
37232
37242
  /** Schedule an alert after loitering threshold passes */
37233
37243
  scheduleLoiteringAlert(globalId, sighting, isEntryPoint) {
37244
+ // Capture snapshot IMMEDIATELY when object is first detected (don't wait for loitering threshold)
37245
+ // This ensures we have a good image while the person/object is still in frame
37246
+ if (this.config.useLlmDescriptions) {
37247
+ this.captureAndCacheSnapshot(globalId, sighting.cameraId).catch(e => {
37248
+ this.console.warn(`[Snapshot] Failed to cache initial snapshot: ${e}`);
37249
+ });
37250
+ }
37234
37251
  // Check after loitering threshold if object is still being tracked
37235
37252
  setTimeout(async () => {
37236
37253
  const tracked = this.state.getObject(globalId);
@@ -37239,22 +37256,9 @@ class TrackingEngine {
37239
37256
  // Check if we've already alerted for this object
37240
37257
  if (this.isInAlertCooldown(globalId))
37241
37258
  return;
37242
- // Get snapshot for LLM description (if LLM is enabled)
37243
- let mediaObject;
37244
- this.console.log(`[Entry Alert] useLlmDescriptions=${this.config.useLlmDescriptions}`);
37245
- if (this.config.useLlmDescriptions) {
37246
- try {
37247
- const camera = systemManager.getDeviceById(sighting.cameraId);
37248
- this.console.log(`[Entry Alert] Camera ${sighting.cameraId} has Camera interface: ${camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)}`);
37249
- if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
37250
- mediaObject = await camera.takePicture();
37251
- this.console.log(`[Entry Alert] Got snapshot: ${!!mediaObject}`);
37252
- }
37253
- }
37254
- catch (e) {
37255
- this.console.warn('[Entry Alert] Failed to get snapshot:', e);
37256
- }
37257
- }
37259
+ // Use cached snapshot (captured when object was first detected)
37260
+ let mediaObject = this.snapshotCache.get(globalId);
37261
+ this.console.log(`[Entry Alert] Using cached snapshot: ${!!mediaObject}`);
37258
37262
  // Generate spatial description (now async with LLM support)
37259
37263
  this.console.log(`[Entry Alert] Calling generateEntryDescription with mediaObject=${!!mediaObject}`);
37260
37264
  const spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
@@ -37289,6 +37293,22 @@ class TrackingEngine {
37289
37293
  this.recordAlertTime(globalId);
37290
37294
  }, this.config.loiteringThreshold);
37291
37295
  }
37296
+ /** Capture and cache a snapshot for a tracked object */
37297
+ async captureAndCacheSnapshot(globalId, cameraId) {
37298
+ try {
37299
+ const camera = systemManager.getDeviceById(cameraId);
37300
+ if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
37301
+ const mediaObject = await camera.takePicture();
37302
+ if (mediaObject) {
37303
+ this.snapshotCache.set(globalId, mediaObject);
37304
+ this.console.log(`[Snapshot] Cached snapshot for ${globalId.slice(0, 8)} from ${cameraId}`);
37305
+ }
37306
+ }
37307
+ }
37308
+ catch (e) {
37309
+ this.console.warn(`[Snapshot] Failed to capture snapshot: ${e}`);
37310
+ }
37311
+ }
37292
37312
  /** Attempt to correlate a sighting with existing tracked objects */
37293
37313
  async correlateDetection(sighting) {
37294
37314
  const activeObjects = this.state.getActiveObjects();
@@ -37328,27 +37348,20 @@ class TrackingEngine {
37328
37348
  handlePotentialExit(tracked, sighting) {
37329
37349
  // Mark as pending and set timer
37330
37350
  this.state.markPending(tracked.globalId);
37351
+ // Capture a fresh snapshot now while object is still visible (before they leave)
37352
+ if (this.config.useLlmDescriptions) {
37353
+ this.captureAndCacheSnapshot(tracked.globalId, sighting.cameraId).catch(e => {
37354
+ this.console.warn(`[Exit Snapshot] Failed to update snapshot: ${e}`);
37355
+ });
37356
+ }
37331
37357
  // Wait for correlation window before marking as exited
37332
37358
  const timer = setTimeout(async () => {
37333
37359
  const current = this.state.getObject(tracked.globalId);
37334
37360
  if (current && current.state === 'pending') {
37335
37361
  this.state.markExited(tracked.globalId, sighting.cameraId, sighting.cameraName);
37336
- // Get snapshot for LLM description (if LLM is enabled)
37337
- let mediaObject;
37338
- this.console.log(`[Exit Alert] useLlmDescriptions=${this.config.useLlmDescriptions}`);
37339
- if (this.config.useLlmDescriptions) {
37340
- try {
37341
- const camera = systemManager.getDeviceById(sighting.cameraId);
37342
- this.console.log(`[Exit Alert] Camera ${sighting.cameraId} has Camera interface: ${camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)}`);
37343
- if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
37344
- mediaObject = await camera.takePicture();
37345
- this.console.log(`[Exit Alert] Got snapshot: ${!!mediaObject}`);
37346
- }
37347
- }
37348
- catch (e) {
37349
- this.console.warn('[Exit Alert] Failed to get snapshot:', e);
37350
- }
37351
- }
37362
+ // Use cached snapshot (captured when exit was first detected, while object was still visible)
37363
+ let mediaObject = this.snapshotCache.get(tracked.globalId);
37364
+ this.console.log(`[Exit Alert] Using cached snapshot: ${!!mediaObject}`);
37352
37365
  // Generate rich exit description using topology context (now async with LLM support)
37353
37366
  this.console.log(`[Exit Alert] Calling generateExitDescription with mediaObject=${!!mediaObject}`);
37354
37367
  const spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
@@ -37361,6 +37374,8 @@ class TrackingEngine {
37361
37374
  involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
37362
37375
  usedLlm: spatialResult.usedLlm,
37363
37376
  });
37377
+ // Clean up cached snapshot after exit alert
37378
+ this.snapshotCache.delete(tracked.globalId);
37364
37379
  }
37365
37380
  this.pendingTimers.delete(tracked.globalId);
37366
37381
  }, this.config.correlationWindow);
@@ -37376,6 +37391,8 @@ class TrackingEngine {
37376
37391
  this.state.markLost(tracked.globalId);
37377
37392
  this.console.log(`Object ${tracked.globalId.slice(0, 8)} marked as lost ` +
37378
37393
  `(not seen for ${Math.round(timeSinceSeen / 1000)}s)`);
37394
+ // Clean up cached snapshot
37395
+ this.snapshotCache.delete(tracked.globalId);
37379
37396
  this.alertManager.checkAndAlert('lost_tracking', tracked, {
37380
37397
  objectClass: tracked.className,
37381
37398
  objectLabel: tracked.label,