Files
boc/vims-backend/demo/complete-demo.js
T
Bernt 6de2455917 v1.2.0: Add Global Markets footer, translated to 9 languages
- 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
2026-07-08 19:56:03 +00:00

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 };