@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/main.nodejs.js +1 -1
- package/dist/main.nodejs.js.map +1 -1
- package/dist/plugin.zip +0 -0
- package/out/main.nodejs.js +50 -33
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/alerts/alert-manager.ts +2 -1
- package/src/core/tracking-engine.ts +54 -30
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -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
|
-
//
|
|
37243
|
-
let mediaObject;
|
|
37244
|
-
this.console.log(`[Entry Alert]
|
|
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
|
-
//
|
|
37337
|
-
let mediaObject;
|
|
37338
|
-
this.console.log(`[Exit Alert]
|
|
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,
|