Initial commit — Mek-Tech Live Labs Showcase v1.0
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user