6de2455917
- Added GLOBAL_MARKETS_TITLE to all translation files - Updated footer with 12 markets (4 active + 8 upcoming) - Translated market section to: zh-cn, zh-tw, ja, ko, th, vi, id, ms, hi - Built and deployed to production - CloudFront invalidation: I3RTMXVFDJXWLG3SYX208OP1CC
258 lines
8.7 KiB
JavaScript
258 lines
8.7 KiB
JavaScript
/**
|
|
* Complete VIMS Demo
|
|
* Demonstrates entire flow from object creation to alert generation
|
|
*/
|
|
|
|
const { ObjectDetectionService } = require('../src/services/objectDetection');
|
|
const { ChangeDetectionService } = require('../src/services/changeDetection');
|
|
const { RiskClassifierService } = require('../src/services/riskClassifier');
|
|
const sharp = require('sharp');
|
|
|
|
class VIMSCompleteDemo {
|
|
constructor() {
|
|
this.detectionService = new ObjectDetectionService();
|
|
this.changeService = new ChangeDetectionService();
|
|
this.riskService = new RiskClassifierService();
|
|
this.results = [];
|
|
}
|
|
|
|
log(step, data) {
|
|
console.log(`\n${'='.repeat(70)}`);
|
|
console.log(` ${step}`);
|
|
console.log('='.repeat(70));
|
|
console.log(JSON.stringify(data, null, 2));
|
|
this.results.push({ step, data });
|
|
}
|
|
|
|
async createBaselineImage() {
|
|
// Create a "normal" ATM image
|
|
const width = 640;
|
|
const height = 640;
|
|
|
|
// Gray background
|
|
const image = Buffer.alloc(width * height * 3, 200);
|
|
|
|
// Add components (simulated)
|
|
const components = [
|
|
{ name: 'card_reader', x: 220, y: 180, w: 200, h: 80, color: [100, 100, 100] },
|
|
{ name: 'pin_pad', x: 240, y: 300, w: 160, h: 120, color: [80, 80, 80] },
|
|
{ name: 'display', x: 200, y: 80, w: 240, h: 80, color: [50, 50, 50] },
|
|
{ name: 'cash_dispenser', x: 260, y: 450, w: 120, h: 60, color: [120, 120, 120] },
|
|
{ name: 'nfc_reader', x: 280, y: 530, w: 80, h: 40, color: [90, 90, 90] }
|
|
];
|
|
|
|
for (const comp of components) {
|
|
for (let y = comp.y; y < comp.y + comp.h; y++) {
|
|
for (let x = comp.x; x < comp.x + comp.w; x++) {
|
|
const idx = (y * width + x) * 3;
|
|
image[idx] = comp.color[0];
|
|
image[idx + 1] = comp.color[1];
|
|
image[idx + 2] = comp.color[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
async createModifiedImage(baseline) {
|
|
// Create a modified version with a "skimmer" attached
|
|
const modified = Buffer.from(baseline);
|
|
const width = 640;
|
|
|
|
// Add skimmer over card reader (slightly larger, different color)
|
|
const skimmer = { x: 215, y: 175, w: 210, h: 90, color: [150, 50, 50] };
|
|
|
|
for (let y = skimmer.y; y < skimmer.y + skimmer.h; y++) {
|
|
for (let x = skimmer.x; x < skimmer.x + skimmer.w; x++) {
|
|
const idx = (y * width + x) * 3;
|
|
modified[idx] = skimmer.color[0];
|
|
modified[idx + 1] = skimmer.color[1];
|
|
modified[idx + 2] = skimmer.color[2];
|
|
}
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
async run() {
|
|
console.log('\n🚀 VIMS Complete Demo');
|
|
console.log('=====================\n');
|
|
|
|
// Step 1: Create baseline
|
|
console.log('📸 Step 1: Creating baseline image...');
|
|
const baselineImage = await this.createBaselineImage();
|
|
|
|
// Convert to JPEG for storage
|
|
const baselineJpeg = await sharp(baselineImage, {
|
|
raw: { width: 640, height: 640, channels: 3 }
|
|
}).jpeg().toBuffer();
|
|
|
|
this.log('BASELINE IMAGE CREATED', {
|
|
size: baselineJpeg.length,
|
|
dimensions: '640x640',
|
|
description: 'Normal ATM with all components'
|
|
});
|
|
|
|
// Step 2: Object Detection on baseline
|
|
console.log('🔍 Step 2: Running object detection on baseline...');
|
|
const baselineResult = await this.detectionService.detect(baselineJpeg, 'atm');
|
|
|
|
this.log('BASELINE DETECTION', {
|
|
modelVersion: baselineResult.modelVersion,
|
|
inferenceTime: `${baselineResult.inferenceTime}ms`,
|
|
detections: baselineResult.detections.map(d => ({
|
|
component: d.componentName,
|
|
confidence: `${(d.confidence * 100).toFixed(1)}%`,
|
|
critical: d.critical
|
|
}))
|
|
});
|
|
|
|
// Step 3: Create modified image (with skimmer)
|
|
console.log('⚠️ Step 3: Creating modified image (with skimmer)...');
|
|
const modifiedImage = await this.createModifiedImage(baselineImage);
|
|
|
|
const modifiedJpeg = await sharp(modifiedImage, {
|
|
raw: { width: 640, height: 640, channels: 3 }
|
|
}).jpeg().toBuffer();
|
|
|
|
this.log('MODIFIED IMAGE CREATED', {
|
|
size: modifiedJpeg.length,
|
|
description: 'ATM with skimmer device attached to card reader'
|
|
});
|
|
|
|
// Step 4: Object Detection on modified
|
|
console.log('🔍 Step 4: Running object detection on modified image...');
|
|
const modifiedResult = await this.detectionService.detect(modifiedJpeg, 'atm');
|
|
|
|
this.log('MODIFIED DETECTION', {
|
|
modelVersion: modifiedResult.modelVersion,
|
|
inferenceTime: `${modifiedResult.inferenceTime}ms`,
|
|
detections: modifiedResult.detections.map(d => ({
|
|
component: d.componentName,
|
|
confidence: `${(d.confidence * 100).toFixed(1)}%`,
|
|
critical: d.critical
|
|
}))
|
|
});
|
|
|
|
// Step 5: Change Detection
|
|
console.log('⚡ Step 5: Running change detection...');
|
|
const changeResult = await this.changeService.compare(modifiedJpeg, baselineJpeg);
|
|
|
|
this.log('CHANGE DETECTION RESULT', {
|
|
isSignificant: changeResult.isSignificant,
|
|
changeType: changeResult.changeType,
|
|
confidence: `${(changeResult.confidence * 100).toFixed(1)}%`,
|
|
metrics: {
|
|
pixelDifference: `${(changeResult.metrics.pixelDifference * 100).toFixed(2)}%`,
|
|
structuralDifference: `${(changeResult.metrics.structuralDifference * 100).toFixed(2)}%`,
|
|
hashDifference: `${(changeResult.metrics.hashDifference * 100).toFixed(2)}%`,
|
|
overallDifference: `${(changeResult.metrics.overallDifference * 100).toFixed(2)}%`
|
|
}
|
|
});
|
|
|
|
// Step 6: Anomaly Detection
|
|
console.log('🚨 Step 6: Detecting anomalies...');
|
|
const anomalies = await this.detectionService.detectAnomalies(
|
|
modifiedResult.detections,
|
|
baselineResult.detections,
|
|
'atm'
|
|
);
|
|
|
|
this.log('ANOMALY DETECTION', {
|
|
anomalyCount: anomalies.length,
|
|
anomalies: anomalies.map(a => ({
|
|
type: a.type,
|
|
component: a.component,
|
|
confidence: `${(a.confidence * 100).toFixed(1)}%`,
|
|
severity: a.severity,
|
|
description: a.description
|
|
}))
|
|
});
|
|
|
|
// Step 7: Risk Classification
|
|
console.log('🎯 Step 7: Classifying risk...');
|
|
|
|
// Create mock observation
|
|
const observation = {
|
|
id: 'demo-obs-001',
|
|
objectId: 'demo-atm-001',
|
|
imageUrl: 'demo://modified.jpg'
|
|
};
|
|
|
|
// Mark detections as anomalies
|
|
const detectionsWithAnomalies = modifiedResult.detections.map(d => ({
|
|
...d,
|
|
isAnomaly: anomalies.some(a => a.component === d.componentType),
|
|
anomalyType: anomalies.find(a => a.component === d.componentType)?.type || null,
|
|
anomalyConfidence: anomalies.find(a => a.component === d.componentType)?.confidence || 0
|
|
}));
|
|
|
|
const riskResult = this.riskService.processObservation(
|
|
observation,
|
|
detectionsWithAnomalies,
|
|
'atm'
|
|
);
|
|
|
|
this.log('RISK CLASSIFICATION', {
|
|
level: riskResult.level.toUpperCase(),
|
|
reason: riskResult.reason,
|
|
action: riskResult.action,
|
|
details: riskResult.details?.map(d => ({
|
|
component: d.component,
|
|
level: d.level,
|
|
confidence: `${(d.confidence * 100).toFixed(1)}%`
|
|
}))
|
|
});
|
|
|
|
// Step 8: Alert Decision
|
|
console.log('🔔 Step 8: Alert decision...');
|
|
const shouldAlert = ['orange', 'red'].includes(riskResult.level);
|
|
|
|
this.log('ALERT DECISION', {
|
|
shouldAlert,
|
|
alertLevel: riskResult.level,
|
|
title: shouldAlert ? `🚨 SECURITY ALERT: ${riskResult.reason}` : '✅ No action required',
|
|
notificationChannels: shouldAlert ? ['email', 'sms', 'webhook'] : [],
|
|
recommendedAction: riskResult.action
|
|
});
|
|
|
|
// Summary
|
|
console.log('\n' + '='.repeat(70));
|
|
console.log(' DEMO SUMMARY');
|
|
console.log('='.repeat(70));
|
|
console.log(`✅ Baseline created with ${baselineResult.detections.length} components detected`);
|
|
console.log(`⚠️ Modified image shows ${anomalies.length} anomalies`);
|
|
console.log(`🔴 Risk level: ${riskResult.level.toUpperCase()}`);
|
|
console.log(`${shouldAlert ? '🚨 ALERT GENERATED' : '✅ No alert needed'}`);
|
|
console.log(`⏱️ Total processing time: ${baselineResult.inferenceTime + modifiedResult.inferenceTime + changeResult.processingTime}ms`);
|
|
console.log('='.repeat(70) + '\n');
|
|
|
|
// Save results
|
|
const fs = require('fs').promises;
|
|
await fs.writeFile(
|
|
'demo/complete-demo-results.json',
|
|
JSON.stringify(this.results, null, 2)
|
|
);
|
|
|
|
console.log('📄 Results saved to demo/complete-demo-results.json\n');
|
|
|
|
return {
|
|
baseline: baselineResult,
|
|
modified: modifiedResult,
|
|
changes: changeResult,
|
|
anomalies,
|
|
risk: riskResult,
|
|
shouldAlert
|
|
};
|
|
}
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
const demo = new VIMSCompleteDemo();
|
|
demo.run().catch(console.error);
|
|
}
|
|
|
|
module.exports = { VIMSCompleteDemo };
|