@blueharford/scrypted-spatial-awareness 0.6.12 → 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
@@ -36943,6 +36943,9 @@ class TrackingEngine {
36943
36943
  trainingConfig = training_1.DEFAULT_TRAINING_CONFIG;
36944
36944
  /** Callback for training status updates */
36945
36945
  onTrainingStatusUpdate;
36946
+ // ==================== Snapshot Cache ====================
36947
+ /** Cached snapshots for tracked objects (for faster notifications) */
36948
+ snapshotCache = new Map();
36946
36949
  constructor(topology, state, alertManager, config, console) {
36947
36950
  this.topology = topology;
36948
36951
  this.state = state;
@@ -37166,6 +37169,12 @@ class TrackingEngine {
37166
37169
  const lastSighting = (0, tracked_object_1.getLastSighting)(tracked);
37167
37170
  if (lastSighting && lastSighting.cameraId !== sighting.cameraId) {
37168
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
+ }
37169
37178
  // Add journey segment
37170
37179
  this.state.addJourney(tracked.globalId, {
37171
37180
  fromCameraId: lastSighting.cameraId,
@@ -37232,6 +37241,13 @@ class TrackingEngine {
37232
37241
  }
37233
37242
  /** Schedule an alert after loitering threshold passes */
37234
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
+ }
37235
37251
  // Check after loitering threshold if object is still being tracked
37236
37252
  setTimeout(async () => {
37237
37253
  const tracked = this.state.getObject(globalId);
@@ -37240,22 +37256,9 @@ class TrackingEngine {
37240
37256
  // Check if we've already alerted for this object
37241
37257
  if (this.isInAlertCooldown(globalId))
37242
37258
  return;
37243
- // Get snapshot for LLM description (if LLM is enabled)
37244
- let mediaObject;
37245
- this.console.log(`[Entry Alert] useLlmDescriptions=${this.config.useLlmDescriptions}`);
37246
- if (this.config.useLlmDescriptions) {
37247
- try {
37248
- const camera = systemManager.getDeviceById(sighting.cameraId);
37249
- this.console.log(`[Entry Alert] Camera ${sighting.cameraId} has Camera interface: ${camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)}`);
37250
- if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
37251
- mediaObject = await camera.takePicture();
37252
- this.console.log(`[Entry Alert] Got snapshot: ${!!mediaObject}`);
37253
- }
37254
- }
37255
- catch (e) {
37256
- this.console.warn('[Entry Alert] Failed to get snapshot:', e);
37257
- }
37258
- }
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}`);
37259
37262
  // Generate spatial description (now async with LLM support)
37260
37263
  this.console.log(`[Entry Alert] Calling generateEntryDescription with mediaObject=${!!mediaObject}`);
37261
37264
  const spatialResult = await this.spatialReasoning.generateEntryDescription(tracked, sighting.cameraId, mediaObject);
@@ -37290,6 +37293,22 @@ class TrackingEngine {
37290
37293
  this.recordAlertTime(globalId);
37291
37294
  }, this.config.loiteringThreshold);
37292
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
+ }
37293
37312
  /** Attempt to correlate a sighting with existing tracked objects */
37294
37313
  async correlateDetection(sighting) {
37295
37314
  const activeObjects = this.state.getActiveObjects();
@@ -37329,27 +37348,20 @@ class TrackingEngine {
37329
37348
  handlePotentialExit(tracked, sighting) {
37330
37349
  // Mark as pending and set timer
37331
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
+ }
37332
37357
  // Wait for correlation window before marking as exited
37333
37358
  const timer = setTimeout(async () => {
37334
37359
  const current = this.state.getObject(tracked.globalId);
37335
37360
  if (current && current.state === 'pending') {
37336
37361
  this.state.markExited(tracked.globalId, sighting.cameraId, sighting.cameraName);
37337
- // Get snapshot for LLM description (if LLM is enabled)
37338
- let mediaObject;
37339
- this.console.log(`[Exit Alert] useLlmDescriptions=${this.config.useLlmDescriptions}`);
37340
- if (this.config.useLlmDescriptions) {
37341
- try {
37342
- const camera = systemManager.getDeviceById(sighting.cameraId);
37343
- this.console.log(`[Exit Alert] Camera ${sighting.cameraId} has Camera interface: ${camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)}`);
37344
- if (camera?.interfaces?.includes(sdk_1.ScryptedInterface.Camera)) {
37345
- mediaObject = await camera.takePicture();
37346
- this.console.log(`[Exit Alert] Got snapshot: ${!!mediaObject}`);
37347
- }
37348
- }
37349
- catch (e) {
37350
- this.console.warn('[Exit Alert] Failed to get snapshot:', e);
37351
- }
37352
- }
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}`);
37353
37365
  // Generate rich exit description using topology context (now async with LLM support)
37354
37366
  this.console.log(`[Exit Alert] Calling generateExitDescription with mediaObject=${!!mediaObject}`);
37355
37367
  const spatialResult = await this.spatialReasoning.generateExitDescription(current, sighting.cameraId, mediaObject);
@@ -37362,6 +37374,8 @@ class TrackingEngine {
37362
37374
  involvedLandmarks: spatialResult.involvedLandmarks?.map(l => l.name),
37363
37375
  usedLlm: spatialResult.usedLlm,
37364
37376
  });
37377
+ // Clean up cached snapshot after exit alert
37378
+ this.snapshotCache.delete(tracked.globalId);
37365
37379
  }
37366
37380
  this.pendingTimers.delete(tracked.globalId);
37367
37381
  }, this.config.correlationWindow);
@@ -37377,6 +37391,8 @@ class TrackingEngine {
37377
37391
  this.state.markLost(tracked.globalId);
37378
37392
  this.console.log(`Object ${tracked.globalId.slice(0, 8)} marked as lost ` +
37379
37393
  `(not seen for ${Math.round(timeSinceSeen / 1000)}s)`);
37394
+ // Clean up cached snapshot
37395
+ this.snapshotCache.delete(tracked.globalId);
37380
37396
  this.alertManager.checkAndAlert('lost_tracking', tracked, {
37381
37397
  objectClass: tracked.className,
37382
37398
  objectLabel: tracked.label,