117 lines
5.2 KiB
JavaScript
117 lines
5.2 KiB
JavaScript
const cache = require('../cache');
|
|
|
|
const SANCTIONS_NAMES = [
|
|
'IVANOV PETR', 'KIM JONG UN', 'RODRIGUEZ CARLOS', 'AL-RASHID OMAR',
|
|
'PETROV SERGEI', 'WANG WEI', 'MULLER HANS', 'SILVA ANA', 'NAKAMURA YUKI', 'PETROVA ANNA'
|
|
];
|
|
|
|
let txCounter = 0;
|
|
const hitsTimeline = [];
|
|
const riskBuckets = { low: 0, medium: 0, high: 0, critical: 0 };
|
|
|
|
async function loadSanctions() {
|
|
const cached = cache.get('sanctions-list', 24 * 60 * 60 * 1000);
|
|
if (cached) return cached;
|
|
const list = SANCTIONS_NAMES.map((name, i) => ({
|
|
id: i + 1, name, list: 'EU Consolidated', country: ['RU', 'KP', 'VE', 'SY', 'RU', 'CN', 'DE', 'BR', 'JP', 'RU'][i]
|
|
}));
|
|
cache.set('sanctions-list', list);
|
|
return list;
|
|
}
|
|
|
|
function generateTx(sanctions) {
|
|
txCounter++;
|
|
const hit = Math.random() < 0.09;
|
|
const name = hit
|
|
? sanctions[Math.floor(Math.random() * sanctions.length)].name
|
|
: `CUSTOMER ${Math.floor(Math.random() * 9000 + 1000)}`;
|
|
const amount = +(Math.random() * 50000 + 100).toFixed(2);
|
|
const risk = hit ? Math.round(85 + Math.random() * 15) : Math.round(Math.random() * 55);
|
|
if (risk < 25) riskBuckets.low++;
|
|
else if (risk < 50) riskBuckets.medium++;
|
|
else if (risk < 75) riskBuckets.high++;
|
|
else riskBuckets.critical++;
|
|
|
|
const tx = {
|
|
id: `TX-${Date.now()}-${txCounter}`,
|
|
name, amount,
|
|
currency: ['EUR', 'USD', 'GBP'][Math.floor(Math.random() * 3)],
|
|
country: ['NL', 'DE', 'BE', 'FR', 'UK', 'US', 'RU', 'CN'][Math.floor(Math.random() * 8)],
|
|
risk, hit,
|
|
type: ['SEPA', 'SWIFT', 'CARD', 'CRYPTO'][Math.floor(Math.random() * 4)],
|
|
ts: new Date().toISOString()
|
|
};
|
|
return tx;
|
|
}
|
|
|
|
async function getFraudSnapshot() {
|
|
const sanctions = await loadSanctions();
|
|
const recent = Array.from({ length: 25 }, () => generateTx(sanctions));
|
|
const hits = recent.filter(t => t.hit).length;
|
|
|
|
hitsTimeline.push({ t: new Date().toISOString().slice(11, 19), hits, screened: recent.length });
|
|
if (hitsTimeline.length > 30) hitsTimeline.shift();
|
|
|
|
const countryRisk = ['NL', 'DE', 'BE', 'FR', 'UK', 'US', 'RU', 'CN'].map(c => ({
|
|
country: c,
|
|
txs: Math.round(50 + Math.random() * 200),
|
|
hits: c === 'RU' || c === 'CN' ? Math.round(3 + Math.random() * 8) : Math.round(Math.random() * 2)
|
|
}));
|
|
|
|
const riskDistribution = [
|
|
{ name: 'Laag', value: riskBuckets.low || 120, fill: '#22c55e' },
|
|
{ name: 'Medium', value: riskBuckets.medium || 80, fill: '#f59e0b' },
|
|
{ name: 'Hoog', value: riskBuckets.high || 40, fill: '#f97316' },
|
|
{ name: 'Kritiek', value: riskBuckets.critical || 15, fill: '#ef4444' }
|
|
];
|
|
|
|
const typeBreakdown = [
|
|
{ type: 'SEPA', blocked: Math.round(Math.random() * 5), cleared: 420 },
|
|
{ type: 'SWIFT', blocked: Math.round(Math.random() * 8), cleared: 180 },
|
|
{ type: 'CARD', blocked: Math.round(Math.random() * 12), cleared: 890 },
|
|
{ type: 'CRYPTO', blocked: Math.round(Math.random() * 15), cleared: 65 }
|
|
];
|
|
|
|
const insights = [
|
|
{ type: 'warn', label: 'Compliance', text: `${sanctions.length} entiteiten op EU-lijst — elke tx gescored in <20ms.` },
|
|
{ type: 'info', label: 'ML', text: 'Velocity + geo-mismatch rules vangen 94% demo-anomalies — uitbreidbaar met eigen modellen.' },
|
|
{ type: 'ok', label: 'Audit', text: 'Volledige audit trail exporteerbaar voor DNB/AFM toezicht.' },
|
|
{ type: 'warn', label: 'Hits', text: `${hits} hits in huidige batch — RU/CN geo-risico geconcentreerd.` },
|
|
{ type: 'info', label: 'Throughput', text: `${1240 + Math.floor(Math.random() * 50)} tx/min — SSE stream elke 1,5s.` },
|
|
{ type: 'ok', label: 'Actie', text: 'False-positive review queue koppelbaar — zelfde Global Ops UI als treasury & retail.' }
|
|
];
|
|
|
|
const contextBlocks = [
|
|
{ title: 'Waarom Fraud Command Center?', body: 'Financiële instellingen moeten sancties, PEP en anomalies combineren — realtime, auditable, zonder vendor lock-in.' },
|
|
{ title: 'Wat u hier ziet', body: 'Live tx stream, sanctions hits, risk verdeling, geo-risico en type breakdown — refresh elke 1,5 seconden.' },
|
|
{ title: 'Technische aanpak Mek-Tech', body: 'EU Consolidated List + rule engine + SSE. ML models plug-in ready. Geen opslag van demo-tx.' },
|
|
{ title: 'ROI voor uw organisatie', body: 'Snellere compliance response, lagere false-positive kosten, audit-ready logging.' },
|
|
{ title: 'Productie-architectuur', body: 'Payment gateway → screening service → Kafka audit log → dit dashboard + case management.' },
|
|
{ title: 'Volgende stap', body: 'Pilot met echte sanctions feed + 3 custom rules — productie POC binnen 3 weken.' }
|
|
];
|
|
|
|
return {
|
|
ts: new Date().toISOString(),
|
|
refreshMs: 1500,
|
|
sanctionsCount: sanctions.length,
|
|
recent,
|
|
hitsLastMinute: hits,
|
|
hitsTimeline: [...hitsTimeline],
|
|
countryRisk,
|
|
riskDistribution,
|
|
typeBreakdown,
|
|
avgLatencyMs: Math.round(12 + Math.random() * 8),
|
|
screeningRate: 1240 + Math.floor(Math.random() * 50),
|
|
blockRate: +(hits / recent.length * 100).toFixed(2),
|
|
insights,
|
|
contextBlocks,
|
|
criticalCount: recent.filter(t => t.risk > 85).length,
|
|
context: {
|
|
pitch: 'Global Fraud & Compliance Operations — sanctions screening live.',
|
|
deployment: 'EU Consolidated List · OpenSanctions · ECB Payment Stats'
|
|
}
|
|
};
|
|
}
|
|
|
|
module.exports = { getFraudSnapshot, generateTx, loadSanctions };
|